PyPlumIO 0.6.0__tar.gz → 0.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyplumio-0.6.0 → pyplumio-0.6.1}/PKG-INFO +6 -7
- {pyplumio-0.6.0 → pyplumio-0.6.1}/PyPlumIO.egg-info/PKG-INFO +6 -7
- {pyplumio-0.6.0 → pyplumio-0.6.1}/PyPlumIO.egg-info/SOURCES.txt +0 -1
- {pyplumio-0.6.0 → pyplumio-0.6.1}/PyPlumIO.egg-info/requires.txt +5 -5
- {pyplumio-0.6.0 → pyplumio-0.6.1}/README.md +0 -1
- {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/connecting.rst +37 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/index.rst +0 -1
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/_version.py +3 -3
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/connection.py +0 -36
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/protocol.py +50 -40
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyproject.toml +5 -5
- {pyplumio-0.6.0 → pyplumio-0.6.1}/requirements_test.txt +5 -5
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_connection.py +0 -15
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_protocol.py +14 -15
- pyplumio-0.6.0/docs/source/statistics.rst +0 -47
- {pyplumio-0.6.0 → pyplumio-0.6.1}/.gitattributes +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/CODE_OF_CONDUCT.md +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/dependabot.yml +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/workflows/ci.yml +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/workflows/codeql.yml +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/workflows/deploy.yml +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/workflows/documentation.yml +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/.gitignore +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/.pre-commit-config.yaml +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/.qlty/qlty.toml +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/.vscode/settings.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/LICENSE +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/MANIFEST.in +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/PyPlumIO.egg-info/dependency_links.txt +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/PyPlumIO.egg-info/top_level.txt +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/Makefile +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/make.bat +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/callbacks.rst +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/conf.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/frames.rst +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/mixers_thermostats.rst +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/protocol.rst +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/reading.rst +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/schedules.rst +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/writing.rst +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/images/ecomax.png +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/images/rs485.png +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/__init__.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/__main__.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/const.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/data_types.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/devices/__init__.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/devices/ecomax.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/devices/ecoster.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/devices/mixer.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/devices/thermostat.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/exceptions.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/filters.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/frames/__init__.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/frames/messages.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/frames/requests.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/frames/responses.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/helpers/__init__.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/helpers/async_cache.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/helpers/event_manager.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/helpers/factory.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/helpers/task_manager.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/parameters/__init__.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/parameters/custom/__init__.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/parameters/custom/ecomax_860d3_hb.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/parameters/ecomax.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/parameters/mixer.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/parameters/thermostat.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/py.typed +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/stream.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/__init__.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/alerts.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/boiler_load.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/boiler_power.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/ecomax_parameters.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/fan_power.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/frame_versions.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/fuel_consumption.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/fuel_level.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/lambda_sensor.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/mixer_parameters.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/mixer_sensors.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/modules.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/network_info.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/output_flags.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/outputs.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/pending_alerts.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/product_info.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/program_version.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/regulator_data.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/regulator_data_schema.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/schedules.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/statuses.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/temperatures.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/thermostat_parameters.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/thermostat_sensors.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/utils.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/requirements.txt +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/requirements_docs.txt +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/setup.cfg +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/__init__.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/conftest.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/devices/__init__.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/devices/test_ecomax.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/devices/test_ecoster.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/devices/test_init.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/devices/test_mixer.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/devices/test_thermostat.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/frames/test_init.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/frames/test_messages.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/frames/test_requests.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/frames/test_responses.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/helpers/__init__.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/helpers/test_async_cache.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/helpers/test_event_manager.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/helpers/test_factory.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/helpers/test_task_manager.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/helpers/test_uid.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/__init__.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/custom/__init__.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/custom/test_ecomax_860d3_hb.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/custom/test_init.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/test_ecomax.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/test_init.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/test_mixers.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/test_thermostats.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/ruff.toml +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/__init__.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_alerts.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_boiler_load.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_boiler_power.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_ecomax_parameters.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_fan_power.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_frame_versions.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_fuel_consumption.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_fuel_level.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_lambda_sensor.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_mixer_parameters.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_product_info.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_schedules.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_data_types.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_filters.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_init.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_main.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_stream.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_utils.py +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/messages/regulator_data.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/messages/sensor_data.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/parameters/ecomax_860d3_hb.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/alerts.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/ecomax_control.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/ecomax_parameters.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/mixer_parameters.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/set_ecomax_parameter.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/set_mixer_parameter.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/set_schedule.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/set_thermostat_parameter.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/thermostat_parameters.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/alerts.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/device_available.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/ecomax_parameters.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/mixer_parameters.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/password.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/program_version.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/regulator_data_schema.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/schedules.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/thermostat_parameters.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/uid.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/unknown/unknown_ecomax_parameter.json +0 -0
- {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/unknown/unknown_mixer_parameter.json +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: PyPlumIO
|
3
|
-
Version: 0.6.
|
3
|
+
Version: 0.6.1
|
4
4
|
Summary: PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
|
5
5
|
Author-email: Denis Paavilainen <denpa@denpa.pro>
|
6
6
|
License: MIT License
|
@@ -25,16 +25,16 @@ License-File: LICENSE
|
|
25
25
|
Requires-Dist: pyserial-asyncio==0.6
|
26
26
|
Provides-Extra: test
|
27
27
|
Requires-Dist: codespell==2.4.1; extra == "test"
|
28
|
-
Requires-Dist: coverage==7.10.
|
28
|
+
Requires-Dist: coverage==7.10.7; extra == "test"
|
29
29
|
Requires-Dist: freezegun==1.5.5; extra == "test"
|
30
|
-
Requires-Dist: mypy==1.18.
|
30
|
+
Requires-Dist: mypy==1.18.2; extra == "test"
|
31
31
|
Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "test"
|
32
32
|
Requires-Dist: pyserial-asyncio-fast==0.16; extra == "test"
|
33
33
|
Requires-Dist: pytest==8.4.2; extra == "test"
|
34
|
-
Requires-Dist: pytest-asyncio==1.
|
35
|
-
Requires-Dist: ruff==0.13.
|
34
|
+
Requires-Dist: pytest-asyncio==1.2.0; extra == "test"
|
35
|
+
Requires-Dist: ruff==0.13.2; extra == "test"
|
36
36
|
Requires-Dist: tox==4.30.2; extra == "test"
|
37
|
-
Requires-Dist: types-pyserial==3.5.0.
|
37
|
+
Requires-Dist: types-pyserial==3.5.0.20250919; extra == "test"
|
38
38
|
Provides-Extra: docs
|
39
39
|
Requires-Dist: sphinx==8.1.3; extra == "docs"
|
40
40
|
Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
|
@@ -79,7 +79,6 @@ through network by using RS-485 to Ethernet/WiFi converter.
|
|
79
79
|
- [Callbacks](https://pyplumio.denpa.pro/callbacks.html)
|
80
80
|
- [Mixers/Thermostats](https://pyplumio.denpa.pro/mixers_thermostats.html)
|
81
81
|
- [Schedules](https://pyplumio.denpa.pro/schedules.html)
|
82
|
-
- [Statistics](https://pyplumio.denpa.pro/statistics.html)
|
83
82
|
- [Protocol](https://pyplumio.denpa.pro/protocol.html)
|
84
83
|
- [Frame Structure](https://pyplumio.denpa.pro/protocol.html#frame-structure)
|
85
84
|
- [Requests and Responses](https://pyplumio.denpa.pro/protocol.html#requests-and-responses)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: PyPlumIO
|
3
|
-
Version: 0.6.
|
3
|
+
Version: 0.6.1
|
4
4
|
Summary: PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
|
5
5
|
Author-email: Denis Paavilainen <denpa@denpa.pro>
|
6
6
|
License: MIT License
|
@@ -25,16 +25,16 @@ License-File: LICENSE
|
|
25
25
|
Requires-Dist: pyserial-asyncio==0.6
|
26
26
|
Provides-Extra: test
|
27
27
|
Requires-Dist: codespell==2.4.1; extra == "test"
|
28
|
-
Requires-Dist: coverage==7.10.
|
28
|
+
Requires-Dist: coverage==7.10.7; extra == "test"
|
29
29
|
Requires-Dist: freezegun==1.5.5; extra == "test"
|
30
|
-
Requires-Dist: mypy==1.18.
|
30
|
+
Requires-Dist: mypy==1.18.2; extra == "test"
|
31
31
|
Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "test"
|
32
32
|
Requires-Dist: pyserial-asyncio-fast==0.16; extra == "test"
|
33
33
|
Requires-Dist: pytest==8.4.2; extra == "test"
|
34
|
-
Requires-Dist: pytest-asyncio==1.
|
35
|
-
Requires-Dist: ruff==0.13.
|
34
|
+
Requires-Dist: pytest-asyncio==1.2.0; extra == "test"
|
35
|
+
Requires-Dist: ruff==0.13.2; extra == "test"
|
36
36
|
Requires-Dist: tox==4.30.2; extra == "test"
|
37
|
-
Requires-Dist: types-pyserial==3.5.0.
|
37
|
+
Requires-Dist: types-pyserial==3.5.0.20250919; extra == "test"
|
38
38
|
Provides-Extra: docs
|
39
39
|
Requires-Dist: sphinx==8.1.3; extra == "docs"
|
40
40
|
Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
|
@@ -79,7 +79,6 @@ through network by using RS-485 to Ethernet/WiFi converter.
|
|
79
79
|
- [Callbacks](https://pyplumio.denpa.pro/callbacks.html)
|
80
80
|
- [Mixers/Thermostats](https://pyplumio.denpa.pro/mixers_thermostats.html)
|
81
81
|
- [Schedules](https://pyplumio.denpa.pro/schedules.html)
|
82
|
-
- [Statistics](https://pyplumio.denpa.pro/statistics.html)
|
83
82
|
- [Protocol](https://pyplumio.denpa.pro/protocol.html)
|
84
83
|
- [Frame Structure](https://pyplumio.denpa.pro/protocol.html#frame-structure)
|
85
84
|
- [Requests and Responses](https://pyplumio.denpa.pro/protocol.html#requests-and-responses)
|
@@ -12,13 +12,13 @@ readthedocs-sphinx-search==0.3.2
|
|
12
12
|
|
13
13
|
[test]
|
14
14
|
codespell==2.4.1
|
15
|
-
coverage==7.10.
|
15
|
+
coverage==7.10.7
|
16
16
|
freezegun==1.5.5
|
17
|
-
mypy==1.18.
|
17
|
+
mypy==1.18.2
|
18
18
|
numpy<3.0.0,>=2.0.0
|
19
19
|
pyserial-asyncio-fast==0.16
|
20
20
|
pytest==8.4.2
|
21
|
-
pytest-asyncio==1.
|
22
|
-
ruff==0.13.
|
21
|
+
pytest-asyncio==1.2.0
|
22
|
+
ruff==0.13.2
|
23
23
|
tox==4.30.2
|
24
|
-
types-pyserial==3.5.0.
|
24
|
+
types-pyserial==3.5.0.20250919
|
@@ -32,7 +32,6 @@ through network by using RS-485 to Ethernet/WiFi converter.
|
|
32
32
|
- [Callbacks](https://pyplumio.denpa.pro/callbacks.html)
|
33
33
|
- [Mixers/Thermostats](https://pyplumio.denpa.pro/mixers_thermostats.html)
|
34
34
|
- [Schedules](https://pyplumio.denpa.pro/schedules.html)
|
35
|
-
- [Statistics](https://pyplumio.denpa.pro/statistics.html)
|
36
35
|
- [Protocol](https://pyplumio.denpa.pro/protocol.html)
|
37
36
|
- [Frame Structure](https://pyplumio.denpa.pro/protocol.html#frame-structure)
|
38
37
|
- [Requests and Responses](https://pyplumio.denpa.pro/protocol.html#requests-and-responses)
|
@@ -142,6 +142,43 @@ In the example below, we'll set both ethernet and wireless parameters.
|
|
142
142
|
) as conn:
|
143
143
|
...
|
144
144
|
|
145
|
+
Statistics
|
146
|
+
----------
|
147
|
+
|
148
|
+
Since PyPlumIO v0.5.56, you can access statistics via following property.
|
149
|
+
|
150
|
+
.. autoattribute:: pyplumio.protocol.AsyncProtocol.statistics
|
151
|
+
|
152
|
+
Statistics contain transfer data consisting of number of received/sent frames and bytes
|
153
|
+
as well as datetime of when connection was established, when connection was lost and
|
154
|
+
number of connection loss event.
|
155
|
+
|
156
|
+
.. autoclass:: pyplumio.protocol.Statistics
|
157
|
+
:members:
|
158
|
+
:exclude-members: update_sent, update_received, update_connection_lost, update_devices, reset_transfer_statistics
|
159
|
+
|
160
|
+
The `devices` property of statistics class of also contains a list of
|
161
|
+
device statistics objects. Those statistics include time the device was initially
|
162
|
+
connected as well as time, when device was last seen (sent an :ref:`RegulatorData`
|
163
|
+
message).
|
164
|
+
|
165
|
+
.. autoclass:: pyplumio.protocol.DeviceStatistics
|
166
|
+
:members:
|
167
|
+
:exclude-members: __hash__, update_last_seen
|
168
|
+
|
169
|
+
In the following example we'll print connection statistics after establishing a
|
170
|
+
connection.
|
171
|
+
|
172
|
+
.. code-block:: python
|
173
|
+
|
174
|
+
import pyplumio
|
175
|
+
|
176
|
+
|
177
|
+
async def main():
|
178
|
+
"""Print connection statistics."""
|
179
|
+
async with pyplumio.open_tcp_connection("localhost", 8899) as conn:
|
180
|
+
print(conn.statistics)
|
181
|
+
...
|
145
182
|
|
146
183
|
Connection Examples
|
147
184
|
-------------------
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
28
28
|
commit_id: COMMIT_ID
|
29
29
|
__commit_id__: COMMIT_ID
|
30
30
|
|
31
|
-
__version__ = version = '0.6.
|
32
|
-
__version_tuple__ = version_tuple = (0, 6,
|
31
|
+
__version__ = version = '0.6.1'
|
32
|
+
__version_tuple__ = version_tuple = (0, 6, 1)
|
33
33
|
|
34
|
-
__commit_id__ = commit_id = '
|
34
|
+
__commit_id__ = commit_id = 'g7cf7d589c'
|
@@ -116,42 +116,6 @@ class Connection(ABC, TaskManager):
|
|
116
116
|
|
117
117
|
yield await self.protocol.get(name, timeout=timeout)
|
118
118
|
|
119
|
-
@property
|
120
|
-
def get(self): # type: ignore[no-untyped-def]
|
121
|
-
"""Access the remote device.
|
122
|
-
|
123
|
-
Raise NotImplementedError when using protocol
|
124
|
-
different from AsyncProtocol.
|
125
|
-
"""
|
126
|
-
if isinstance(self.protocol, AsyncProtocol):
|
127
|
-
return self.protocol.get
|
128
|
-
|
129
|
-
raise NotImplementedError
|
130
|
-
|
131
|
-
@property
|
132
|
-
def get_nowait(self): # type: ignore[no-untyped-def]
|
133
|
-
"""Access the remote device without waiting.
|
134
|
-
|
135
|
-
Raise NotImplementedError when using protocol
|
136
|
-
different from AsyncProtocol.
|
137
|
-
"""
|
138
|
-
if isinstance(self.protocol, AsyncProtocol):
|
139
|
-
return self.protocol.get_nowait
|
140
|
-
|
141
|
-
raise NotImplementedError
|
142
|
-
|
143
|
-
@property
|
144
|
-
def wait_for(self): # type: ignore[no-untyped-def]
|
145
|
-
"""Wait for the remote device to become available.
|
146
|
-
|
147
|
-
Raise NotImplementedError when using protocol
|
148
|
-
different from AsyncProtocol.
|
149
|
-
"""
|
150
|
-
if isinstance(self.protocol, AsyncProtocol):
|
151
|
-
return self.protocol.wait_for
|
152
|
-
|
153
|
-
raise NotImplementedError
|
154
|
-
|
155
119
|
@property
|
156
120
|
def protocol(self) -> Protocol:
|
157
121
|
"""Return the protocol object."""
|
@@ -145,25 +145,29 @@ class Statistics:
|
|
145
145
|
connection_losses: int = 0
|
146
146
|
|
147
147
|
#: List of statistics for connected devices
|
148
|
-
devices:
|
148
|
+
devices: set[DeviceStatistics] = field(default_factory=set)
|
149
149
|
|
150
|
-
def
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
if sent:
|
155
|
-
self.sent_bytes += sent.length
|
156
|
-
self.sent_frames += 1
|
150
|
+
def update_sent(self, frame: Frame) -> None:
|
151
|
+
"""Update sent frames statistics."""
|
152
|
+
self.sent_bytes += frame.length
|
153
|
+
self.sent_frames += 1
|
157
154
|
|
158
|
-
|
159
|
-
|
160
|
-
|
155
|
+
def update_received(self, frame: Frame) -> None:
|
156
|
+
"""Update received frames statistics."""
|
157
|
+
self.received_bytes += frame.length
|
158
|
+
self.received_frames += 1
|
161
159
|
|
162
|
-
def
|
163
|
-
"""
|
160
|
+
def update_connection_lost(self) -> None:
|
161
|
+
"""Update connection lost counter."""
|
164
162
|
self.connection_losses += 1
|
165
163
|
self.connection_loss_at = datetime.now()
|
166
164
|
|
165
|
+
def update_devices(self, device: PhysicalDevice) -> None:
|
166
|
+
"""Update connected devices."""
|
167
|
+
device_statistics = DeviceStatistics(address=device.address)
|
168
|
+
device.subscribe(ATTR_REGDATA, device_statistics.update_last_seen)
|
169
|
+
self.devices.add(device_statistics)
|
170
|
+
|
167
171
|
def reset_transfer_statistics(self) -> None:
|
168
172
|
"""Reset transfer statistics."""
|
169
173
|
self.sent_bytes = 0
|
@@ -173,18 +177,22 @@ class Statistics:
|
|
173
177
|
self.failed_frames = 0
|
174
178
|
|
175
179
|
|
176
|
-
@dataclass(slots=True)
|
180
|
+
@dataclass(slots=True, kw_only=True)
|
177
181
|
class DeviceStatistics:
|
178
182
|
"""Represents a device statistics."""
|
179
183
|
|
180
|
-
#: Device
|
181
|
-
|
184
|
+
#: Device address
|
185
|
+
address: int
|
182
186
|
|
183
187
|
#: Datetime object representing connection time
|
184
|
-
connected_since: datetime
|
188
|
+
connected_since: datetime = field(default_factory=datetime.now)
|
185
189
|
|
186
190
|
#: Datetime object representing time when device was last seen
|
187
|
-
last_seen: datetime
|
191
|
+
last_seen: datetime = field(default_factory=datetime.now)
|
192
|
+
|
193
|
+
def __hash__(self) -> int:
|
194
|
+
"""Return a hash of the statistics based on unique address."""
|
195
|
+
return self.address
|
188
196
|
|
189
197
|
async def update_last_seen(self, _: Any) -> None:
|
190
198
|
"""Update last seen property."""
|
@@ -241,10 +249,10 @@ class AsyncProtocol(Protocol, EventManager[PhysicalDevice]):
|
|
241
249
|
self.frame_producer(self._queues, reader=self.reader, writer=self.writer),
|
242
250
|
name="frame_producer_task",
|
243
251
|
)
|
244
|
-
for
|
252
|
+
for consumer_id in range(self.consumers_count):
|
245
253
|
self.create_task(
|
246
254
|
self.frame_consumer(self._queues.read),
|
247
|
-
name=f"frame_consumer_task ({
|
255
|
+
name=f"frame_consumer_task ({consumer_id})",
|
248
256
|
)
|
249
257
|
|
250
258
|
for device in self.data.values():
|
@@ -277,30 +285,39 @@ class AsyncProtocol(Protocol, EventManager[PhysicalDevice]):
|
|
277
285
|
await self._connection_close()
|
278
286
|
await asyncio.gather(*(device.shutdown() for device in self.data.values()))
|
279
287
|
|
288
|
+
async def _write_from_queue(
|
289
|
+
self, writer: FrameWriter, queue: asyncio.Queue[Frame]
|
290
|
+
) -> None:
|
291
|
+
"""Send frame from the queue to the remote device."""
|
292
|
+
frame = await queue.get()
|
293
|
+
await writer.write(frame)
|
294
|
+
queue.task_done()
|
295
|
+
self.statistics.update_sent(frame)
|
296
|
+
|
297
|
+
async def _read_into_queue(
|
298
|
+
self, reader: FrameReader, queue: asyncio.Queue[Frame]
|
299
|
+
) -> None:
|
300
|
+
"""Read frame from the remote device into the queue."""
|
301
|
+
if frame := await reader.read():
|
302
|
+
queue.put_nowait(frame)
|
303
|
+
self.statistics.update_received(frame)
|
304
|
+
|
280
305
|
async def frame_producer(
|
281
306
|
self, queues: Queues, reader: FrameReader, writer: FrameWriter
|
282
307
|
) -> None:
|
283
308
|
"""Handle frame reads and writes."""
|
284
|
-
statistics = self.statistics
|
285
309
|
await self.connected.wait()
|
286
310
|
while self.connected.is_set():
|
287
311
|
try:
|
288
|
-
request = None
|
289
312
|
if not queues.write.empty():
|
290
|
-
|
291
|
-
await writer.write(request)
|
292
|
-
queues.write.task_done()
|
293
|
-
|
294
|
-
if response := await reader.read():
|
295
|
-
queues.read.put_nowait(response)
|
296
|
-
|
297
|
-
statistics.update_transfer_statistics(request, response)
|
313
|
+
await self._write_from_queue(writer, queues.write)
|
298
314
|
|
315
|
+
await self._read_into_queue(reader, queues.read)
|
299
316
|
except ProtocolError as e:
|
300
|
-
statistics.failed_frames += 1
|
317
|
+
self.statistics.failed_frames += 1
|
301
318
|
_LOGGER.debug("Can't process received frame: %s", e)
|
302
319
|
except (OSError, asyncio.TimeoutError):
|
303
|
-
statistics.
|
320
|
+
self.statistics.update_connection_lost()
|
304
321
|
self.create_task(self.connection_lost())
|
305
322
|
break
|
306
323
|
except Exception:
|
@@ -327,14 +344,7 @@ class AsyncProtocol(Protocol, EventManager[PhysicalDevice]):
|
|
327
344
|
device.dispatch_nowait(ATTR_CONNECTED, True)
|
328
345
|
device.dispatch_nowait(ATTR_SETUP, True)
|
329
346
|
await self.dispatch(name, device)
|
330
|
-
self.statistics.
|
331
|
-
device_statistics := DeviceStatistics(
|
332
|
-
name=name,
|
333
|
-
connected_since=datetime.now(),
|
334
|
-
last_seen=datetime.now(),
|
335
|
-
)
|
336
|
-
)
|
337
|
-
device.subscribe(ATTR_REGDATA, device_statistics.update_last_seen)
|
347
|
+
self.statistics.update_devices(device)
|
338
348
|
|
339
349
|
return self.data[name]
|
340
350
|
|
@@ -35,16 +35,16 @@ dynamic = ["version"]
|
|
35
35
|
[project.optional-dependencies]
|
36
36
|
test = [
|
37
37
|
"codespell==2.4.1",
|
38
|
-
"coverage==7.10.
|
38
|
+
"coverage==7.10.7",
|
39
39
|
"freezegun==1.5.5",
|
40
|
-
"mypy==1.18.
|
40
|
+
"mypy==1.18.2",
|
41
41
|
"numpy<3.0.0,>=2.0.0",
|
42
42
|
"pyserial-asyncio-fast==0.16",
|
43
43
|
"pytest==8.4.2",
|
44
|
-
"pytest-asyncio==1.
|
45
|
-
"ruff==0.13.
|
44
|
+
"pytest-asyncio==1.2.0",
|
45
|
+
"ruff==0.13.2",
|
46
46
|
"tox==4.30.2",
|
47
|
-
"types-pyserial==3.5.0.
|
47
|
+
"types-pyserial==3.5.0.20250919"
|
48
48
|
]
|
49
49
|
docs = [
|
50
50
|
"sphinx==8.1.3",
|
@@ -1,13 +1,13 @@
|
|
1
1
|
codespell==2.4.1
|
2
|
-
coverage==7.10.
|
2
|
+
coverage==7.10.7
|
3
3
|
freezegun==1.5.5
|
4
|
-
mypy==1.18.
|
4
|
+
mypy==1.18.2
|
5
5
|
numpy<3.0.0,>=2.0.0
|
6
6
|
pre-commit==4.3.0
|
7
7
|
pyserial-asyncio-fast==0.16
|
8
|
-
pytest-asyncio==1.
|
8
|
+
pytest-asyncio==1.2.0
|
9
9
|
pytest==8.4.2
|
10
|
-
ruff==0.13.
|
10
|
+
ruff==0.13.2
|
11
11
|
tomli==2.2.1
|
12
12
|
tox==4.30.2
|
13
|
-
types-pyserial==3.5.0.
|
13
|
+
types-pyserial==3.5.0.20250919
|
@@ -189,21 +189,6 @@ class TestConnection:
|
|
189
189
|
mock_protocol.some_method = Mock(return_value="called")
|
190
190
|
assert connection.some_method() == "called"
|
191
191
|
|
192
|
-
@pytest.mark.parametrize("func", ("get", "get_nowait", "wait_for"))
|
193
|
-
async def test_protocol_proxy_calls(self, func: str) -> None:
|
194
|
-
"""Test calls proxied to protocol instance."""
|
195
|
-
mock_protocol = AsyncMock(spec=AsyncProtocol, autospec=True)
|
196
|
-
connection = DummyConnection(protocol=mock_protocol)
|
197
|
-
connection_func = getattr(connection, func)
|
198
|
-
protocol_func = getattr(mock_protocol, func)
|
199
|
-
assert connection_func is protocol_func
|
200
|
-
|
201
|
-
# Test with error.
|
202
|
-
mock_protocol = AsyncMock(spec=DummyProtocol, autospec=True)
|
203
|
-
connection = DummyConnection(protocol=mock_protocol)
|
204
|
-
with pytest.raises(NotImplementedError):
|
205
|
-
getattr(connection, func)
|
206
|
-
|
207
192
|
|
208
193
|
HOST: Final = "localhost"
|
209
194
|
PORT: Final = 8899
|
@@ -109,25 +109,28 @@ async def test_dummy_protocol() -> None:
|
|
109
109
|
@pytest.mark.usefixtures("frozen_time")
|
110
110
|
def test_statistics() -> None:
|
111
111
|
"""Test statistics dataclass."""
|
112
|
-
statistics = Statistics()
|
113
|
-
statistics.update_transfer_statistics(sent=Request())
|
114
|
-
statistics.update_transfer_statistics(received=Response())
|
115
|
-
statistics.failed_frames = 1
|
112
|
+
statistics = Statistics(failed_frames=1)
|
116
113
|
assert statistics.connected_since == "never"
|
114
|
+
assert statistics.failed_frames == 1
|
115
|
+
assert statistics.connection_losses == 0
|
116
|
+
assert statistics.connection_loss_at == "never"
|
117
|
+
assert statistics.sent_bytes == 0
|
118
|
+
assert statistics.sent_frames == 0
|
119
|
+
assert statistics.received_bytes == 0
|
120
|
+
assert statistics.received_frames == 0
|
121
|
+
statistics.update_sent(Request())
|
117
122
|
assert statistics.sent_bytes == 10
|
118
123
|
assert statistics.sent_frames == 1
|
124
|
+
statistics.update_received(Response())
|
119
125
|
assert statistics.received_bytes == 10
|
120
126
|
assert statistics.received_frames == 1
|
121
|
-
assert statistics.failed_frames == 1
|
122
|
-
assert statistics.connection_losses == 0
|
123
|
-
assert statistics.connection_loss_at == "never"
|
124
127
|
statistics.reset_transfer_statistics()
|
125
128
|
assert statistics.sent_bytes == 0
|
126
129
|
assert statistics.sent_frames == 0
|
127
130
|
assert statistics.received_bytes == 0
|
128
131
|
assert statistics.received_frames == 0
|
129
132
|
assert statistics.failed_frames == 0
|
130
|
-
statistics.
|
133
|
+
statistics.update_connection_lost()
|
131
134
|
assert statistics.connection_losses == 1
|
132
135
|
assert statistics.connection_loss_at == datetime.now()
|
133
136
|
|
@@ -376,7 +379,7 @@ async def test_async_protocol_frame_producer(
|
|
376
379
|
"failed_frames": 5,
|
377
380
|
"connection_losses": 1,
|
378
381
|
"connection_loss_at": datetime.now(),
|
379
|
-
"devices":
|
382
|
+
"devices": set(),
|
380
383
|
}
|
381
384
|
|
382
385
|
|
@@ -453,12 +456,8 @@ async def test_async_protocol_frame_consumer(
|
|
453
456
|
assert mock_read_queue.task_done.call_count == 2
|
454
457
|
|
455
458
|
# Test statistics.
|
456
|
-
connected_since = datetime.now()
|
457
459
|
frozen_time.tick(timedelta(seconds=10))
|
458
460
|
ecomax = cast(EcoMAX, async_protocol.get_nowait("ecomax"))
|
459
461
|
await ecomax.dispatch(ATTR_REGDATA, True)
|
460
|
-
|
461
|
-
|
462
|
-
"connected_since": connected_since,
|
463
|
-
"last_seen": datetime.now(),
|
464
|
-
}
|
462
|
+
device_statistics = async_protocol.statistics.devices.pop()
|
463
|
+
assert device_statistics.address == DeviceType.ECOMAX
|
@@ -1,47 +0,0 @@
|
|
1
|
-
Statistics
|
2
|
-
==========
|
3
|
-
|
4
|
-
About Statistics
|
5
|
-
----------------
|
6
|
-
|
7
|
-
Since PyPlumIO v0.5.56, you can access statistics via following property.
|
8
|
-
|
9
|
-
.. autoattribute:: pyplumio.protocol.AsyncProtocol.statistics
|
10
|
-
|
11
|
-
Statistics contain transfer data consisting of number of received/sent frames and bytes
|
12
|
-
as well as datetime of when connection was established, when connection was lost and
|
13
|
-
number of connection loss event.
|
14
|
-
|
15
|
-
.. autoclass:: pyplumio.protocol.Statistics
|
16
|
-
:members:
|
17
|
-
:exclude-members: update_transfer_statistics, track_connection_loss, reset_transfer_statistics
|
18
|
-
|
19
|
-
The `devices` property of statistics class of also contains a list of
|
20
|
-
device statistics objects. Those statistics include time the device was initially
|
21
|
-
connected as well as time, when device was last seen (sent an :ref:`RegulatorData`
|
22
|
-
message).
|
23
|
-
|
24
|
-
.. autoclass:: pyplumio.protocol.DeviceStatistics
|
25
|
-
:members:
|
26
|
-
:exclude-members: update_last_seen
|
27
|
-
|
28
|
-
Statistics Examples
|
29
|
-
-------------------
|
30
|
-
|
31
|
-
You can easily access statistic object via proxy call through Connection object
|
32
|
-
as in example below.
|
33
|
-
|
34
|
-
.. code-block:: python
|
35
|
-
|
36
|
-
import asyncio
|
37
|
-
|
38
|
-
import pyplumio
|
39
|
-
|
40
|
-
async def main():
|
41
|
-
"""Read the current heating temperature."""
|
42
|
-
async with pyplumio.open_tcp_connection("localhost", 8899) as conn:
|
43
|
-
print(conn.statistics)
|
44
|
-
|
45
|
-
|
46
|
-
asyncio.run(main())
|
47
|
-
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|