PyPlumIO 0.5.27__tar.gz → 0.5.29__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.5.27 → pyplumio-0.5.29}/PKG-INFO +10 -10
- {pyplumio-0.5.27 → pyplumio-0.5.29}/PyPlumIO.egg-info/PKG-INFO +10 -10
- {pyplumio-0.5.27 → pyplumio-0.5.29}/PyPlumIO.egg-info/requires.txt +9 -9
- {pyplumio-0.5.27 → pyplumio-0.5.29}/docs/source/connecting.rst +1 -1
- {pyplumio-0.5.27 → pyplumio-0.5.29}/docs/source/mixers_thermostats.rst +2 -2
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/_version.py +2 -2
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/const.py +47 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/devices/__init__.py +4 -4
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/devices/ecomax.py +1 -1
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/devices/mixer.py +1 -1
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/devices/thermostat.py +1 -1
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/filters.py +10 -10
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/frames/__init__.py +13 -6
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/helpers/data_types.py +7 -7
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/helpers/schedule.py +1 -1
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/stream.py +33 -31
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/ecomax_parameters.py +21 -23
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/mixer_parameters.py +9 -10
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/regulator_data.py +1 -1
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/thermostat_parameters.py +15 -6
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyproject.toml +10 -9
- pyplumio-0.5.29/requirements_docs.txt +3 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/requirements_test.txt +7 -7
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/frames/test_messages.py +3 -3
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/frames/test_responses.py +3 -3
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/test_devices.py +5 -5
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/test_stream.py +48 -58
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/responses/ecomax_parameters.json +2 -2
- pyplumio-0.5.27/requirements_docs.txt +0 -3
- {pyplumio-0.5.27 → pyplumio-0.5.29}/.gitattributes +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/.github/CODE_OF_CONDUCT.md +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/.github/dependabot.yml +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/.github/workflows/ci.yml +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/.github/workflows/codeql-analysis.yml +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/.github/workflows/deploy.yml +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/.github/workflows/documentation.yml +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/.gitignore +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/.pre-commit-config.yaml +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/.vscode/settings.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/LICENSE +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/MANIFEST.in +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/PyPlumIO.egg-info/SOURCES.txt +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/PyPlumIO.egg-info/dependency_links.txt +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/PyPlumIO.egg-info/top_level.txt +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/README.md +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/docs/Makefile +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/docs/make.bat +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/docs/source/callbacks.rst +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/docs/source/conf.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/docs/source/frames.rst +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/docs/source/index.rst +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/docs/source/protocol.rst +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/docs/source/reading.rst +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/docs/source/schedules.rst +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/docs/source/writing.rst +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/images/ecomax.png +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/images/rs485.png +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/__init__.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/__main__.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/connection.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/devices/ecoster.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/exceptions.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/frames/messages.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/frames/requests.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/frames/responses.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/helpers/__init__.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/helpers/event_manager.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/helpers/factory.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/helpers/parameter.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/helpers/task_manager.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/helpers/timeout.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/helpers/uid.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/protocol.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/py.typed +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/__init__.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/alerts.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/boiler_load.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/boiler_power.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/fan_power.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/frame_versions.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/fuel_consumption.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/fuel_level.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/lambda_sensor.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/mixer_sensors.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/modules.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/network_info.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/output_flags.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/outputs.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/pending_alerts.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/product_info.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/program_version.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/regulator_data_schema.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/schedules.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/statuses.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/temperatures.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/structures/thermostat_sensors.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/pyplumio/utils.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/requirements.txt +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/setup.cfg +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/__init__.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/conftest.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/frames/test_init.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/frames/test_requests.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/helpers/__init__.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/helpers/test_data_types.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/helpers/test_event_manager.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/helpers/test_factory.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/helpers/test_parameter.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/helpers/test_schedule.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/helpers/test_task_manager.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/helpers/test_timeout.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/helpers/test_uid.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/ruff.toml +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/test_connection.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/test_filters.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/test_init.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/test_main.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/test_protocol.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/test_utils.py +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/messages/regulator_data.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/messages/sensor_data.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/requests/alerts.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/requests/ecomax_control.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/requests/ecomax_parameters.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/requests/mixer_parameters.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/requests/set_ecomax_parameter.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/requests/set_mixer_parameter.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/requests/set_schedule.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/requests/set_thermostat_parameter.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/requests/thermostat_parameters.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/responses/alerts.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/responses/device_available.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/responses/mixer_parameters.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/responses/password.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/responses/program_version.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/responses/regulator_data_schema.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/responses/schedules.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/responses/thermostat_parameters.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/responses/uid.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/unknown/unknown_ecomax_parameter.json +0 -0
- {pyplumio-0.5.27 → pyplumio-0.5.29}/tests/testdata/unknown/unknown_mixer_parameter.json +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: PyPlumIO
|
3
|
-
Version: 0.5.
|
3
|
+
Version: 0.5.29
|
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
|
@@ -27,22 +27,22 @@ Requires-Dist: pyserial-asyncio==0.6
|
|
27
27
|
Requires-Dist: typing-extensions==4.12.2
|
28
28
|
Provides-Extra: test
|
29
29
|
Requires-Dist: codespell==2.3.0; extra == "test"
|
30
|
-
Requires-Dist: coverage==7.6.
|
31
|
-
Requires-Dist: mypy==1.
|
30
|
+
Requires-Dist: coverage==7.6.4; extra == "test"
|
31
|
+
Requires-Dist: mypy==1.13.0; extra == "test"
|
32
32
|
Requires-Dist: pyserial-asyncio-fast==0.14; extra == "test"
|
33
|
-
Requires-Dist: pytest==8.3.
|
33
|
+
Requires-Dist: pytest==8.3.3; extra == "test"
|
34
34
|
Requires-Dist: pytest-asyncio==0.24.0; extra == "test"
|
35
|
-
Requires-Dist: ruff==0.
|
36
|
-
Requires-Dist: tox==4.
|
35
|
+
Requires-Dist: ruff==0.7.1; extra == "test"
|
36
|
+
Requires-Dist: tox==4.23.2; extra == "test"
|
37
37
|
Requires-Dist: types-pyserial==3.5.0.20240826; extra == "test"
|
38
38
|
Provides-Extra: docs
|
39
|
-
Requires-Dist: sphinx==
|
40
|
-
Requires-Dist: sphinx_rtd_theme==
|
39
|
+
Requires-Dist: sphinx==8.1.3; extra == "docs"
|
40
|
+
Requires-Dist: sphinx_rtd_theme==3.0.1; extra == "docs"
|
41
41
|
Requires-Dist: readthedocs-sphinx-search==0.3.2; extra == "docs"
|
42
42
|
Provides-Extra: dev
|
43
43
|
Requires-Dist: pyplumio[docs,test]; extra == "dev"
|
44
|
-
Requires-Dist: pre-commit==
|
45
|
-
Requires-Dist: tomli==2.0.
|
44
|
+
Requires-Dist: pre-commit==4.0.1; extra == "dev"
|
45
|
+
Requires-Dist: tomli==2.0.2; extra == "dev"
|
46
46
|
|
47
47
|
# PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
|
48
48
|
[](https://badge.fury.io/py/PyPlumIO)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: PyPlumIO
|
3
|
-
Version: 0.5.
|
3
|
+
Version: 0.5.29
|
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
|
@@ -27,22 +27,22 @@ Requires-Dist: pyserial-asyncio==0.6
|
|
27
27
|
Requires-Dist: typing-extensions==4.12.2
|
28
28
|
Provides-Extra: test
|
29
29
|
Requires-Dist: codespell==2.3.0; extra == "test"
|
30
|
-
Requires-Dist: coverage==7.6.
|
31
|
-
Requires-Dist: mypy==1.
|
30
|
+
Requires-Dist: coverage==7.6.4; extra == "test"
|
31
|
+
Requires-Dist: mypy==1.13.0; extra == "test"
|
32
32
|
Requires-Dist: pyserial-asyncio-fast==0.14; extra == "test"
|
33
|
-
Requires-Dist: pytest==8.3.
|
33
|
+
Requires-Dist: pytest==8.3.3; extra == "test"
|
34
34
|
Requires-Dist: pytest-asyncio==0.24.0; extra == "test"
|
35
|
-
Requires-Dist: ruff==0.
|
36
|
-
Requires-Dist: tox==4.
|
35
|
+
Requires-Dist: ruff==0.7.1; extra == "test"
|
36
|
+
Requires-Dist: tox==4.23.2; extra == "test"
|
37
37
|
Requires-Dist: types-pyserial==3.5.0.20240826; extra == "test"
|
38
38
|
Provides-Extra: docs
|
39
|
-
Requires-Dist: sphinx==
|
40
|
-
Requires-Dist: sphinx_rtd_theme==
|
39
|
+
Requires-Dist: sphinx==8.1.3; extra == "docs"
|
40
|
+
Requires-Dist: sphinx_rtd_theme==3.0.1; extra == "docs"
|
41
41
|
Requires-Dist: readthedocs-sphinx-search==0.3.2; extra == "docs"
|
42
42
|
Provides-Extra: dev
|
43
43
|
Requires-Dist: pyplumio[docs,test]; extra == "dev"
|
44
|
-
Requires-Dist: pre-commit==
|
45
|
-
Requires-Dist: tomli==2.0.
|
44
|
+
Requires-Dist: pre-commit==4.0.1; extra == "dev"
|
45
|
+
Requires-Dist: tomli==2.0.2; extra == "dev"
|
46
46
|
|
47
47
|
# PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
|
48
48
|
[](https://badge.fury.io/py/PyPlumIO)
|
@@ -4,21 +4,21 @@ typing-extensions==4.12.2
|
|
4
4
|
|
5
5
|
[dev]
|
6
6
|
pyplumio[docs,test]
|
7
|
-
pre-commit==
|
8
|
-
tomli==2.0.
|
7
|
+
pre-commit==4.0.1
|
8
|
+
tomli==2.0.2
|
9
9
|
|
10
10
|
[docs]
|
11
|
-
sphinx==
|
12
|
-
sphinx_rtd_theme==
|
11
|
+
sphinx==8.1.3
|
12
|
+
sphinx_rtd_theme==3.0.1
|
13
13
|
readthedocs-sphinx-search==0.3.2
|
14
14
|
|
15
15
|
[test]
|
16
16
|
codespell==2.3.0
|
17
|
-
coverage==7.6.
|
18
|
-
mypy==1.
|
17
|
+
coverage==7.6.4
|
18
|
+
mypy==1.13.0
|
19
19
|
pyserial-asyncio-fast==0.14
|
20
|
-
pytest==8.3.
|
20
|
+
pytest==8.3.3
|
21
21
|
pytest-asyncio==0.24.0
|
22
|
-
ruff==0.
|
23
|
-
tox==4.
|
22
|
+
ruff==0.7.1
|
23
|
+
tox==4.23.2
|
24
24
|
types-pyserial==3.5.0.20240826
|
@@ -69,7 +69,7 @@ working with device classes and queues.
|
|
69
69
|
async def main():
|
70
70
|
"""Open a connection and request alerts."""
|
71
71
|
async with pyplumio.open_tcp_connection(
|
72
|
-
host="localhost", port=8899, protocol=pyplumio.DummyProtocol
|
72
|
+
host="localhost", port=8899, protocol=pyplumio.DummyProtocol
|
73
73
|
) as connection:
|
74
74
|
await connection.writer.write(
|
75
75
|
requests.AlertsRequest(recipient=DeviceType.ECOMAX, start=0, count=5)
|
@@ -49,8 +49,8 @@ get it's current_temp property and set it's target temperature to
|
|
49
49
|
# Set mixer target temperature to 50 degrees Celsius.
|
50
50
|
await mixer.set("mixer_target_temp", 50)
|
51
51
|
|
52
|
-
|
53
|
-
|
52
|
+
Thermostat Examples
|
53
|
+
-------------------
|
54
54
|
|
55
55
|
In the following example, we'll get single thermostat by it's index,
|
56
56
|
get current room temperature and set daytime target temperature to 20
|
@@ -106,7 +106,54 @@ class AlertType(IntEnum):
|
|
106
106
|
MAX_EXHAUST_TEMP_EXCEEDED = 6
|
107
107
|
KINDLING_FAILURE = 7
|
108
108
|
NO_FUEL = 8
|
109
|
+
LEAK_DETECTED = 9
|
110
|
+
PRESSURE_SENSOR_FAILURE = 10
|
109
111
|
FAN_FAILURE = 11
|
112
|
+
INSUFFICIENT_AIR_PRESSURE = 12
|
113
|
+
BURN_OFF_FAILURE = 13
|
114
|
+
FLAME_SENSOR_FAILURE = 14
|
115
|
+
LINEAR_ACTUATOR_BLOCKED = 15
|
116
|
+
INCORRECT_PARAMETERS = 16
|
117
|
+
CONDENSATION_WARNING = 17
|
118
|
+
BOILER_STB_TRIPPED = 18
|
119
|
+
FEEDER_STB_TRIPPED = 19
|
120
|
+
MIN_WATER_PRESSURE_EXCEEDED = 20
|
121
|
+
MAX_WATER_PRESSURE_EXCEEDED = 21
|
122
|
+
FEEDER_JAMMED = 22
|
123
|
+
FLAMEOUT = 23
|
124
|
+
EXHAUST_FAN_FAILURE = 24
|
125
|
+
EXTERNAL_FEEDER_FAILURE = 25
|
126
|
+
SOLAR_COLLECTOR_TEMP_SENSOR_FAILURE = 26
|
127
|
+
SOLAR_CIRCUIT_TEMP_SENSOR_FAILURE = 27
|
128
|
+
H1_CIRCUIT_TEMP_SENSOR_FAILURE = 28
|
129
|
+
H2_CIRCUIT_TEMP_SENSOR_FAILURE = 29
|
130
|
+
H3_CIRCUIT_TEMP_SENSOR_FAILURE = 30
|
131
|
+
OUTDOOR_TEMP_SENSOR_FAILURE = 31
|
132
|
+
WATER_HEATER_TEMP_SENSOR_FAILURE = 32
|
133
|
+
H0_CIRCUIT_TEMP_SENSOR_FAILURE = 33
|
134
|
+
FROST_PROTECTION_RUNNING_WO_HS = 34
|
135
|
+
FROST_PROTECTION_RUNNING_W_HS = 35
|
136
|
+
MAX_SOLAR_COLLECTOR_TEMP_EXCEEDED = 36
|
137
|
+
MAX_HEATED_FLOOR_TEMP_EXCEEDED = 37
|
138
|
+
BOILER_COOLING_RUNNING = 38
|
139
|
+
ECOLAMBDA_CONNECTION_FAILURE = 39
|
140
|
+
PRIMARY_AIR_THROTTLE_JAMMED = 40
|
141
|
+
SECONDARY_AIR_THROTTLE_JAMMED = 41
|
142
|
+
FEEDER_OVERFLOW = 42
|
143
|
+
FURNANCE_OVERFLOW = 43
|
144
|
+
MODULE_B_CONNECTION_FAILURE = 44
|
145
|
+
CLEANING_ACTUATOR_FAILURE = 45
|
146
|
+
MIN_PRESSURE_EXCEEDED = 46
|
147
|
+
MAX_PRESSURE_EXCEEDED = 47
|
148
|
+
PRESSURE_SENSOR_DAMAGED = 48
|
149
|
+
MAX_MAIN_HS_TEMP_EXCEEDED = 49
|
150
|
+
MAX_ADDITIONAL_HS_TEMP_EXCEEDED = 50
|
151
|
+
SOLAR_PANEL_OFFLINE = 51
|
152
|
+
FEEDER_CONTROL_FAILURE = 52
|
153
|
+
FEEDER_BLOCKED = 53
|
154
|
+
MAX_THERMOCOPLE_TEMP_EXCEEDED = 54
|
155
|
+
THERMOCOUPLE_WIRING_FAILURE = 55
|
156
|
+
UNKNOWN_ERROR = 255
|
110
157
|
|
111
158
|
|
112
159
|
@unique
|
@@ -45,7 +45,7 @@ class Device(ABC, EventManager):
|
|
45
45
|
|
46
46
|
queue: asyncio.Queue[Frame]
|
47
47
|
|
48
|
-
def __init__(self, queue: asyncio.Queue[Frame]):
|
48
|
+
def __init__(self, queue: asyncio.Queue[Frame]) -> None:
|
49
49
|
"""Initialize a new device."""
|
50
50
|
super().__init__()
|
51
51
|
self.queue = queue
|
@@ -126,14 +126,14 @@ class PhysicalDevice(Device, ABC):
|
|
126
126
|
_network: NetworkInfo
|
127
127
|
_setup_frames: tuple[DataFrameDescription, ...]
|
128
128
|
|
129
|
-
def __init__(self, queue: asyncio.Queue[Frame], network: NetworkInfo):
|
129
|
+
def __init__(self, queue: asyncio.Queue[Frame], network: NetworkInfo) -> None:
|
130
130
|
"""Initialize a new physical device."""
|
131
131
|
super().__init__(queue)
|
132
132
|
self._network = network
|
133
133
|
|
134
134
|
def handle_frame(self, frame: Frame) -> None:
|
135
135
|
"""Handle frame received from the device."""
|
136
|
-
frame.
|
136
|
+
frame.assign_to(self)
|
137
137
|
if frame.data is not None:
|
138
138
|
for name, value in frame.data.items():
|
139
139
|
self.dispatch_nowait(name, value)
|
@@ -188,7 +188,7 @@ class VirtualDevice(Device, ABC):
|
|
188
188
|
|
189
189
|
def __init__(
|
190
190
|
self, queue: asyncio.Queue[Frame], parent: PhysicalDevice, index: int = 0
|
191
|
-
):
|
191
|
+
) -> None:
|
192
192
|
"""Initialize a new sub-device."""
|
193
193
|
super().__init__(queue)
|
194
194
|
self.parent = parent
|
@@ -110,7 +110,7 @@ class EcoMAX(PhysicalDevice):
|
|
110
110
|
_fuel_burned_timestamp_ns: int
|
111
111
|
_setup_frames = SETUP_FRAME_TYPES
|
112
112
|
|
113
|
-
def __init__(self, queue: asyncio.Queue[Frame], network: NetworkInfo):
|
113
|
+
def __init__(self, queue: asyncio.Queue[Frame], network: NetworkInfo) -> None:
|
114
114
|
"""Initialize a new ecoMAX controller."""
|
115
115
|
super().__init__(queue, network)
|
116
116
|
self._frame_versions = {}
|
@@ -30,7 +30,7 @@ class Mixer(VirtualDevice):
|
|
30
30
|
|
31
31
|
def __init__(
|
32
32
|
self, queue: asyncio.Queue[Frame], parent: PhysicalDevice, index: int = 0
|
33
|
-
):
|
33
|
+
) -> None:
|
34
34
|
"""Initialize a new mixer."""
|
35
35
|
super().__init__(queue, parent, index)
|
36
36
|
self.subscribe(ATTR_MIXER_SENSORS, self._handle_mixer_sensors)
|
@@ -26,7 +26,7 @@ class Thermostat(VirtualDevice):
|
|
26
26
|
|
27
27
|
def __init__(
|
28
28
|
self, queue: asyncio.Queue[Frame], parent: PhysicalDevice, index: int = 0
|
29
|
-
):
|
29
|
+
) -> None:
|
30
30
|
"""Initialize a new thermostat."""
|
31
31
|
super().__init__(queue, parent, index)
|
32
32
|
self.subscribe(ATTR_THERMOSTAT_SENSORS, self._handle_thermostat_sensors)
|
@@ -66,13 +66,12 @@ def _significantly_changed(
|
|
66
66
|
def _significantly_changed(old: Comparable, new: Comparable) -> bool:
|
67
67
|
"""Check if value is significantly changed."""
|
68
68
|
if isinstance(old, Parameter) and isinstance(new, Parameter):
|
69
|
-
|
70
|
-
elif isinstance(old, SupportsFloat) and isinstance(new, SupportsFloat):
|
71
|
-
result = not math.isclose(old, new, abs_tol=TOLERANCE)
|
72
|
-
else:
|
73
|
-
result = old != new
|
69
|
+
return new.pending_update or old.values.__ne__(new.values)
|
74
70
|
|
75
|
-
|
71
|
+
if isinstance(old, SupportsFloat) and isinstance(new, SupportsFloat):
|
72
|
+
return not math.isclose(old, new, abs_tol=TOLERANCE)
|
73
|
+
|
74
|
+
return old.__ne__(new)
|
76
75
|
|
77
76
|
|
78
77
|
@overload
|
@@ -91,10 +90,11 @@ def _diffence_between(
|
|
91
90
|
"""Return a difference between values."""
|
92
91
|
if isinstance(old, list) and isinstance(new, list):
|
93
92
|
return [x for x in new if x not in old]
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
93
|
+
|
94
|
+
if isinstance(old, SupportsSubtraction) and isinstance(new, SupportsSubtraction):
|
95
|
+
return new.__sub__(old)
|
96
|
+
|
97
|
+
return None
|
98
98
|
|
99
99
|
|
100
100
|
class Filter(ABC):
|
@@ -24,6 +24,7 @@ ECONET_VERSION: Final = 5
|
|
24
24
|
|
25
25
|
# Frame header structure.
|
26
26
|
struct_header = struct.Struct("<BH4B")
|
27
|
+
HEADER_SIZE = struct_header.size
|
27
28
|
|
28
29
|
if TYPE_CHECKING:
|
29
30
|
from pyplumio.devices import PhysicalDevice
|
@@ -73,22 +74,20 @@ class Frame(ABC):
|
|
73
74
|
|
74
75
|
__slots__ = (
|
75
76
|
"recipient",
|
76
|
-
"recipient_device",
|
77
77
|
"sender",
|
78
|
-
"sender_device",
|
79
78
|
"econet_type",
|
80
79
|
"econet_version",
|
80
|
+
"_handler",
|
81
81
|
"_message",
|
82
82
|
"_data",
|
83
83
|
)
|
84
84
|
|
85
85
|
recipient: DeviceType
|
86
|
-
recipient_device: PhysicalDevice | None
|
87
86
|
sender: DeviceType
|
88
|
-
sender_device: PhysicalDevice | None
|
89
87
|
econet_type: int
|
90
88
|
econet_version: int
|
91
89
|
frame_type: ClassVar[FrameType]
|
90
|
+
_handler: PhysicalDevice | None
|
92
91
|
_message: bytearray | None
|
93
92
|
_data: dict[str, Any] | None
|
94
93
|
|
@@ -104,11 +103,10 @@ class Frame(ABC):
|
|
104
103
|
) -> None:
|
105
104
|
"""Process a frame data and message."""
|
106
105
|
self.recipient = recipient
|
107
|
-
self.recipient_device = None
|
108
106
|
self.sender = sender
|
109
|
-
self.sender_device = None
|
110
107
|
self.econet_type = econet_type
|
111
108
|
self.econet_version = econet_version
|
109
|
+
self._handler = None
|
112
110
|
self._data = data if not kwargs else ensure_dict(data, kwargs)
|
113
111
|
self._message = message
|
114
112
|
|
@@ -153,6 +151,15 @@ class Frame(ABC):
|
|
153
151
|
"""Return a frame message represented as hex string."""
|
154
152
|
return self.bytes.hex(*args, **kwargs)
|
155
153
|
|
154
|
+
def assign_to(self, device: PhysicalDevice) -> None:
|
155
|
+
"""Assign device to the frame."""
|
156
|
+
self._handler = device
|
157
|
+
|
158
|
+
@property
|
159
|
+
def handler(self) -> PhysicalDevice | None:
|
160
|
+
"""Return the device associated to the frame."""
|
161
|
+
return self._handler
|
162
|
+
|
156
163
|
@property
|
157
164
|
def data(self) -> dict[str, Any]:
|
158
165
|
"""Return the frame data."""
|
@@ -19,7 +19,7 @@ class DataType(ABC, Generic[T]):
|
|
19
19
|
_value: T
|
20
20
|
_size: int
|
21
21
|
|
22
|
-
def __init__(self, value: T | None = None):
|
22
|
+
def __init__(self, value: T | None = None) -> None:
|
23
23
|
"""Initialize a new data type."""
|
24
24
|
if value is not None:
|
25
25
|
self._value = value
|
@@ -112,7 +112,7 @@ class BitArray(DataType[int]):
|
|
112
112
|
|
113
113
|
_index: int
|
114
114
|
|
115
|
-
def __init__(self, value: bool | None = None, index: int = 0):
|
115
|
+
def __init__(self, value: bool | None = None, index: int = 0) -> None:
|
116
116
|
"""Initialize a new bit array."""
|
117
117
|
super().__init__(value)
|
118
118
|
self._index = index
|
@@ -199,7 +199,7 @@ class String(DataType[str]):
|
|
199
199
|
|
200
200
|
__slots__ = ()
|
201
201
|
|
202
|
-
def __init__(self, value: str = ""):
|
202
|
+
def __init__(self, value: str = "") -> None:
|
203
203
|
"""Initialize a new null-terminated string data type."""
|
204
204
|
super().__init__(value)
|
205
205
|
self._size = len(self.value) + 1
|
@@ -219,7 +219,7 @@ class VarBytes(DataType[bytes]):
|
|
219
219
|
|
220
220
|
__slots__ = ()
|
221
221
|
|
222
|
-
def __init__(self, value: bytes = b""):
|
222
|
+
def __init__(self, value: bytes = b"") -> None:
|
223
223
|
"""Initialize a new variable-length bytes data type."""
|
224
224
|
super().__init__(value)
|
225
225
|
self._size = len(value) + 1
|
@@ -239,7 +239,7 @@ class VarString(DataType[str]):
|
|
239
239
|
|
240
240
|
__slots__ = ()
|
241
241
|
|
242
|
-
def __init__(self, value: str = ""):
|
242
|
+
def __init__(self, value: str = "") -> None:
|
243
243
|
"""Initialize a new variable length bytes data type."""
|
244
244
|
super().__init__(value)
|
245
245
|
self._size = len(value) + 1
|
@@ -326,7 +326,7 @@ class UnsignedInt(BuiltInDataType[int]):
|
|
326
326
|
_struct = struct.Struct("<I")
|
327
327
|
|
328
328
|
|
329
|
-
class Float(BuiltInDataType[
|
329
|
+
class Float(BuiltInDataType[float]):
|
330
330
|
"""Represents a float."""
|
331
331
|
|
332
332
|
__slots__ = ()
|
@@ -334,7 +334,7 @@ class Float(BuiltInDataType[int]):
|
|
334
334
|
_struct = struct.Struct("<f")
|
335
335
|
|
336
336
|
|
337
|
-
class Double(BuiltInDataType[
|
337
|
+
class Double(BuiltInDataType[float]):
|
338
338
|
"""Represents a double."""
|
339
339
|
|
340
340
|
__slots__ = ()
|
@@ -10,7 +10,14 @@ from typing import Final, NamedTuple
|
|
10
10
|
from pyplumio.const import DeviceType
|
11
11
|
from pyplumio.devices import is_known_device_type
|
12
12
|
from pyplumio.exceptions import ChecksumError, ReadError, UnknownDeviceError
|
13
|
-
from pyplumio.frames import
|
13
|
+
from pyplumio.frames import (
|
14
|
+
DELIMITER_SIZE,
|
15
|
+
FRAME_START,
|
16
|
+
HEADER_SIZE,
|
17
|
+
Frame,
|
18
|
+
bcc,
|
19
|
+
struct_header,
|
20
|
+
)
|
14
21
|
from pyplumio.helpers.timeout import timeout
|
15
22
|
|
16
23
|
READER_TIMEOUT: Final = 10
|
@@ -29,7 +36,7 @@ class FrameWriter:
|
|
29
36
|
|
30
37
|
_writer: StreamWriter
|
31
38
|
|
32
|
-
def __init__(self, writer: StreamWriter):
|
39
|
+
def __init__(self, writer: StreamWriter) -> None:
|
33
40
|
"""Initialize a new frame writer."""
|
34
41
|
self._writer = writer
|
35
42
|
|
@@ -46,7 +53,7 @@ class FrameWriter:
|
|
46
53
|
self._writer.close()
|
47
54
|
await self.wait_closed()
|
48
55
|
except (OSError, asyncio.TimeoutError):
|
49
|
-
_LOGGER.exception("Unexpected error while closing the writer")
|
56
|
+
_LOGGER.exception("Unexpected error, while closing the writer")
|
50
57
|
|
51
58
|
@timeout(WRITER_TIMEOUT)
|
52
59
|
async def wait_closed(self) -> None:
|
@@ -57,8 +64,6 @@ class FrameWriter:
|
|
57
64
|
class Header(NamedTuple):
|
58
65
|
"""Represents a frame header."""
|
59
66
|
|
60
|
-
bytes: bytes
|
61
|
-
frame_start: int
|
62
67
|
frame_length: int
|
63
68
|
recipient: int
|
64
69
|
sender: int
|
@@ -73,25 +78,28 @@ class FrameReader:
|
|
73
78
|
|
74
79
|
_reader: StreamReader
|
75
80
|
|
76
|
-
def __init__(self, reader: StreamReader):
|
81
|
+
def __init__(self, reader: StreamReader) -> None:
|
77
82
|
"""Initialize a new frame reader."""
|
78
83
|
self._reader = reader
|
79
84
|
|
80
|
-
async def _read_header(self) -> Header:
|
85
|
+
async def _read_header(self) -> tuple[Header, bytes]:
|
81
86
|
"""Locate and read a frame header.
|
82
87
|
|
83
88
|
Raise pyplumio.ReadError if header size is too small and
|
84
89
|
OSError if serial connection is broken.
|
85
90
|
"""
|
86
|
-
while buffer := await self._reader.read(
|
91
|
+
while buffer := await self._reader.read(DELIMITER_SIZE):
|
87
92
|
if FRAME_START not in buffer:
|
88
93
|
continue
|
89
94
|
|
90
|
-
|
91
|
-
|
92
|
-
|
95
|
+
try:
|
96
|
+
buffer += await self._reader.readexactly(HEADER_SIZE - DELIMITER_SIZE)
|
97
|
+
except IncompleteReadError as e:
|
98
|
+
raise ReadError(
|
99
|
+
f"Got incomplete header, while trying to read {e.expected} bytes"
|
100
|
+
) from e
|
93
101
|
|
94
|
-
return Header(
|
102
|
+
return Header(*struct_header.unpack_from(buffer)[DELIMITER_SIZE:]), buffer
|
95
103
|
|
96
104
|
raise OSError("Serial connection broken")
|
97
105
|
|
@@ -99,21 +107,16 @@ class FrameReader:
|
|
99
107
|
async def read(self) -> Frame | None:
|
100
108
|
"""Read the frame and return corresponding handler object.
|
101
109
|
|
102
|
-
Raise pyplumio.
|
103
|
-
|
104
|
-
frame
|
110
|
+
Raise pyplumio.UnknownDeviceError when sender device has an
|
111
|
+
unknown address, raise pyplumio.ReadError on unexpected frame
|
112
|
+
length or incomplete frame, raise pyplumio.ChecksumError on
|
113
|
+
incorrect frame checksum.
|
105
114
|
"""
|
106
|
-
(
|
107
|
-
|
108
|
-
_,
|
109
|
-
frame_length,
|
110
|
-
recipient,
|
111
|
-
sender,
|
112
|
-
econet_type,
|
113
|
-
econet_version,
|
114
|
-
) = await self._read_header()
|
115
|
+
header, buffer = await self._read_header()
|
116
|
+
frame_length, recipient, sender, econet_type, econet_version = header
|
115
117
|
|
116
118
|
if recipient not in (DeviceType.ECONET, DeviceType.ALL):
|
119
|
+
# Not an intended recipient, ignore the frame.
|
117
120
|
return None
|
118
121
|
|
119
122
|
if not is_known_device_type(sender):
|
@@ -123,25 +126,24 @@ class FrameReader:
|
|
123
126
|
raise ReadError(f"Unexpected frame length ({frame_length})")
|
124
127
|
|
125
128
|
try:
|
126
|
-
|
129
|
+
buffer += await self._reader.readexactly(frame_length - HEADER_SIZE)
|
127
130
|
except IncompleteReadError as e:
|
128
131
|
raise ReadError(
|
129
|
-
"Got
|
130
|
-
+ f"'{frame_length - struct_header.size}' bytes"
|
132
|
+
f"Got incomplete frame, while trying to read {e.expected} bytes"
|
131
133
|
) from e
|
132
134
|
|
133
|
-
if (checksum := bcc(
|
135
|
+
if (checksum := bcc(buffer[:-2])) and checksum != buffer[-2]:
|
134
136
|
raise ChecksumError(
|
135
|
-
f"Incorrect frame checksum ({checksum} != {
|
137
|
+
f"Incorrect frame checksum ({checksum} != {buffer[-2]})"
|
136
138
|
)
|
137
139
|
|
138
140
|
frame = await Frame.create(
|
139
|
-
frame_type=
|
141
|
+
frame_type=buffer[HEADER_SIZE],
|
140
142
|
recipient=DeviceType(recipient),
|
141
143
|
sender=DeviceType(sender),
|
142
144
|
econet_type=econet_type,
|
143
145
|
econet_version=econet_version,
|
144
|
-
message=
|
146
|
+
message=buffer[HEADER_SIZE + 1 : -2],
|
145
147
|
)
|
146
148
|
_LOGGER.debug("Received frame: %s", frame)
|
147
149
|
|