PyPlumIO 0.5.43__tar.gz → 0.5.45__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.43 → pyplumio-0.5.45}/PKG-INFO +3 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/PyPlumIO.egg-info/PKG-INFO +3 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/PyPlumIO.egg-info/SOURCES.txt +4 -2
- {pyplumio-0.5.43 → pyplumio-0.5.45}/PyPlumIO.egg-info/requires.txt +2 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/docs/source/callbacks.rst +6 -3
- {pyplumio-0.5.43 → pyplumio-0.5.45}/docs/source/frames.rst +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/_version.py +2 -2
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/const.py +1 -3
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/devices/__init__.py +16 -28
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/devices/ecomax.py +112 -59
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/devices/mixer.py +6 -6
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/devices/thermostat.py +9 -6
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/filters.py +44 -22
- pyplumio-0.5.45/pyplumio/helpers/async_cache.py +48 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/helpers/event_manager.py +20 -2
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/helpers/timeout.py +6 -5
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/parameters/__init__.py +9 -3
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/protocol.py +15 -7
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/alerts.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/boiler_power.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/fan_power.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/frame_versions.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/fuel_consumption.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/lambda_sensor.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/mixer_sensors.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/network_info.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/output_flags.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/outputs.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/product_info.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/regulator_data.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/regulator_data_schema.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/temperatures.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/thermostat_parameters.py +5 -7
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/thermostat_sensors.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyproject.toml +2 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/requirements_test.txt +2 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/conftest.py +8 -0
- pyplumio-0.5.45/tests/helpers/test_async_cache.py +49 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/helpers/test_event_manager.py +17 -2
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/parameters/test_init.py +9 -2
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/test_connection.py +5 -1
- {pyplumio-0.5.43/tests/helpers → pyplumio-0.5.45/tests}/test_data_types.py +1 -1
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/test_devices.py +45 -17
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/test_filters.py +63 -11
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/test_protocol.py +9 -2
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/responses/regulator_data_schema.json +44 -44
- {pyplumio-0.5.43 → pyplumio-0.5.45}/.gitattributes +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/.github/CODE_OF_CONDUCT.md +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/.github/dependabot.yml +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/.github/workflows/ci.yml +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/.github/workflows/codeql.yml +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/.github/workflows/deploy.yml +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/.github/workflows/documentation.yml +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/.gitignore +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/.pre-commit-config.yaml +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/.qlty/qlty.toml +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/.vscode/settings.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/LICENSE +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/MANIFEST.in +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/PyPlumIO.egg-info/dependency_links.txt +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/PyPlumIO.egg-info/top_level.txt +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/README.md +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/docs/Makefile +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/docs/make.bat +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/docs/source/conf.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/docs/source/connecting.rst +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/docs/source/index.rst +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/docs/source/mixers_thermostats.rst +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/docs/source/protocol.rst +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/docs/source/reading.rst +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/docs/source/schedules.rst +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/docs/source/writing.rst +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/images/ecomax.png +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/images/rs485.png +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/__init__.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/__main__.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/connection.py +0 -0
- {pyplumio-0.5.43/pyplumio/helpers → pyplumio-0.5.45/pyplumio}/data_types.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/devices/ecoster.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/exceptions.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/frames/__init__.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/frames/messages.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/frames/requests.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/frames/responses.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/helpers/__init__.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/helpers/factory.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/helpers/schedule.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/helpers/task_manager.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/helpers/uid.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/parameters/ecomax.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/parameters/mixer.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/parameters/thermostat.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/py.typed +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/stream.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/__init__.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/boiler_load.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/ecomax_parameters.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/fuel_level.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/mixer_parameters.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/modules.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/pending_alerts.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/program_version.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/schedules.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/structures/statuses.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/pyplumio/utils.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/requirements.txt +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/requirements_docs.txt +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/setup.cfg +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/__init__.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/frames/test_init.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/frames/test_messages.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/frames/test_requests.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/frames/test_responses.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/helpers/__init__.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/helpers/test_factory.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/helpers/test_schedule.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/helpers/test_task_manager.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/helpers/test_timeout.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/helpers/test_uid.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/parameters/__init__.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/parameters/test_ecomax.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/parameters/test_mixers.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/parameters/test_thermostats.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/ruff.toml +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/test_init.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/test_main.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/test_stream.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/test_utils.py +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/messages/regulator_data.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/messages/sensor_data.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/requests/alerts.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/requests/ecomax_control.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/requests/ecomax_parameters.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/requests/mixer_parameters.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/requests/set_ecomax_parameter.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/requests/set_mixer_parameter.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/requests/set_schedule.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/requests/set_thermostat_parameter.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/requests/thermostat_parameters.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/responses/alerts.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/responses/device_available.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/responses/ecomax_parameters.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/responses/mixer_parameters.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/responses/password.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/responses/program_version.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/responses/schedules.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/responses/thermostat_parameters.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/responses/uid.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/tests/testdata/unknown/unknown_ecomax_parameter.json +0 -0
- {pyplumio-0.5.43 → pyplumio-0.5.45}/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.5.
|
3
|
+
Version: 0.5.45
|
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
|
@@ -29,7 +29,9 @@ Requires-Dist: typing-extensions==4.13.2
|
|
29
29
|
Provides-Extra: test
|
30
30
|
Requires-Dist: codespell==2.4.1; extra == "test"
|
31
31
|
Requires-Dist: coverage==7.8.0; extra == "test"
|
32
|
+
Requires-Dist: freezegun==1.5.1; extra == "test"
|
32
33
|
Requires-Dist: mypy==1.15.0; extra == "test"
|
34
|
+
Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "test"
|
33
35
|
Requires-Dist: pyserial-asyncio-fast==0.16; extra == "test"
|
34
36
|
Requires-Dist: pytest==8.3.5; extra == "test"
|
35
37
|
Requires-Dist: pytest-asyncio==0.26.0; extra == "test"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: PyPlumIO
|
3
|
-
Version: 0.5.
|
3
|
+
Version: 0.5.45
|
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
|
@@ -29,7 +29,9 @@ Requires-Dist: typing-extensions==4.13.2
|
|
29
29
|
Provides-Extra: test
|
30
30
|
Requires-Dist: codespell==2.4.1; extra == "test"
|
31
31
|
Requires-Dist: coverage==7.8.0; extra == "test"
|
32
|
+
Requires-Dist: freezegun==1.5.1; extra == "test"
|
32
33
|
Requires-Dist: mypy==1.15.0; extra == "test"
|
34
|
+
Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "test"
|
33
35
|
Requires-Dist: pyserial-asyncio-fast==0.16; extra == "test"
|
34
36
|
Requires-Dist: pytest==8.3.5; extra == "test"
|
35
37
|
Requires-Dist: pytest-asyncio==0.26.0; extra == "test"
|
@@ -42,6 +42,7 @@ pyplumio/__main__.py
|
|
42
42
|
pyplumio/_version.py
|
43
43
|
pyplumio/connection.py
|
44
44
|
pyplumio/const.py
|
45
|
+
pyplumio/data_types.py
|
45
46
|
pyplumio/exceptions.py
|
46
47
|
pyplumio/filters.py
|
47
48
|
pyplumio/protocol.py
|
@@ -58,7 +59,7 @@ pyplumio/frames/messages.py
|
|
58
59
|
pyplumio/frames/requests.py
|
59
60
|
pyplumio/frames/responses.py
|
60
61
|
pyplumio/helpers/__init__.py
|
61
|
-
pyplumio/helpers/
|
62
|
+
pyplumio/helpers/async_cache.py
|
62
63
|
pyplumio/helpers/event_manager.py
|
63
64
|
pyplumio/helpers/factory.py
|
64
65
|
pyplumio/helpers/schedule.py
|
@@ -99,6 +100,7 @@ tests/__init__.py
|
|
99
100
|
tests/conftest.py
|
100
101
|
tests/ruff.toml
|
101
102
|
tests/test_connection.py
|
103
|
+
tests/test_data_types.py
|
102
104
|
tests/test_devices.py
|
103
105
|
tests/test_filters.py
|
104
106
|
tests/test_init.py
|
@@ -111,7 +113,7 @@ tests/frames/test_messages.py
|
|
111
113
|
tests/frames/test_requests.py
|
112
114
|
tests/frames/test_responses.py
|
113
115
|
tests/helpers/__init__.py
|
114
|
-
tests/helpers/
|
116
|
+
tests/helpers/test_async_cache.py
|
115
117
|
tests/helpers/test_event_manager.py
|
116
118
|
tests/helpers/test_factory.py
|
117
119
|
tests/helpers/test_schedule.py
|
@@ -38,15 +38,18 @@ All built-in filters are described below.
|
|
38
38
|
|
39
39
|
.. autofunction:: pyplumio.filters.aggregate
|
40
40
|
|
41
|
-
This filter aggregates
|
42
|
-
then calls the callback with the
|
41
|
+
This filter aggregates values for specified amount of seconds or until
|
42
|
+
certain sample size is reached and then calls the callback with the
|
43
|
+
sum of values collected.
|
43
44
|
|
44
45
|
.. code-block:: python
|
45
46
|
|
46
47
|
from pyplumio import filters
|
47
48
|
|
48
49
|
# Await the callback with the fuel burned during 30 seconds.
|
49
|
-
ecomax.subscribe(
|
50
|
+
ecomax.subscribe(
|
51
|
+
"fuel_burned", filters.aggregate(my_callback, seconds=30, sample_size=100)
|
52
|
+
)
|
50
53
|
|
51
54
|
.. autofunction:: pyplumio.filters.clamp
|
52
55
|
|
@@ -436,7 +436,7 @@ Get the regulator data schema, that describes the data type of :ref:`regulatorda
|
|
436
436
|
It's represented by dictionary, that's indexed by regulator data field id and
|
437
437
|
a member of the following DataType class, that defines the regulator data field type.
|
438
438
|
|
439
|
-
.. autoclass:: pyplumio.
|
439
|
+
.. autoclass:: pyplumio.data_types.DataType
|
440
440
|
|
441
441
|
Request
|
442
442
|
^^^^^^^
|
@@ -14,11 +14,11 @@ ATTR_CURRENT_TEMP: Final = "current_temp"
|
|
14
14
|
ATTR_DEVICE_INDEX: Final = "device_index"
|
15
15
|
ATTR_FRAME_ERRORS: Final = "frame_errors"
|
16
16
|
ATTR_INDEX: Final = "index"
|
17
|
-
ATTR_LOADED: Final = "loaded"
|
18
17
|
ATTR_OFFSET: Final = "offset"
|
19
18
|
ATTR_PARAMETER: Final = "parameter"
|
20
19
|
ATTR_PASSWORD: Final = "password"
|
21
20
|
ATTR_SCHEDULE: Final = "schedule"
|
21
|
+
ATTR_SETUP: Final = "setup"
|
22
22
|
ATTR_SENSORS: Final = "sensors"
|
23
23
|
ATTR_START: Final = "start"
|
24
24
|
ATTR_STATE: Final = "state"
|
@@ -229,6 +229,4 @@ PERCENTAGE: Final = "%"
|
|
229
229
|
|
230
230
|
STATE_ON: Final = "on"
|
231
231
|
STATE_OFF: Final = "off"
|
232
|
-
|
233
|
-
|
234
232
|
State: TypeAlias = Literal["on", "off"]
|
@@ -8,14 +8,13 @@ from functools import cache
|
|
8
8
|
import logging
|
9
9
|
from typing import Any, ClassVar
|
10
10
|
|
11
|
-
from pyplumio.const import ATTR_FRAME_ERRORS,
|
11
|
+
from pyplumio.const import ATTR_FRAME_ERRORS, DeviceType, FrameType, State
|
12
12
|
from pyplumio.exceptions import RequestError, UnknownDeviceError
|
13
13
|
from pyplumio.filters import on_change
|
14
|
-
from pyplumio.frames import
|
14
|
+
from pyplumio.frames import Frame, Request, is_known_frame_type
|
15
15
|
from pyplumio.helpers.event_manager import EventManager, event_listener
|
16
16
|
from pyplumio.helpers.factory import create_instance
|
17
17
|
from pyplumio.parameters import NumericType, Parameter
|
18
|
-
from pyplumio.structures.frame_versions import ATTR_FRAME_VERSIONS
|
19
18
|
from pyplumio.structures.network_info import NetworkInfo
|
20
19
|
from pyplumio.utils import to_camelcase
|
21
20
|
|
@@ -126,11 +125,11 @@ class PhysicalDevice(Device, ABC):
|
|
126
125
|
virtual devices associated with them via parent property.
|
127
126
|
"""
|
128
127
|
|
129
|
-
__slots__ = ("address", "_network", "
|
128
|
+
__slots__ = ("address", "_network", "_frame_versions")
|
130
129
|
|
131
130
|
address: ClassVar[int]
|
131
|
+
|
132
132
|
_network: NetworkInfo
|
133
|
-
_setup_frames: tuple[DataFrameDescription, ...]
|
134
133
|
_frame_versions: dict[int, int]
|
135
134
|
|
136
135
|
def __init__(self, queue: asyncio.Queue[Frame], network: NetworkInfo) -> None:
|
@@ -139,20 +138,27 @@ class PhysicalDevice(Device, ABC):
|
|
139
138
|
self._network = network
|
140
139
|
self._frame_versions = {}
|
141
140
|
|
142
|
-
@event_listener(
|
141
|
+
@event_listener(filter=on_change)
|
143
142
|
async def on_event_frame_versions(self, versions: dict[int, int]) -> None:
|
144
143
|
"""Check frame versions and update outdated frames."""
|
144
|
+
_LOGGER.info("Received frame version table")
|
145
145
|
for frame_type, version in versions.items():
|
146
146
|
if (
|
147
147
|
is_known_frame_type(frame_type)
|
148
148
|
and self.supports_frame_type(frame_type)
|
149
149
|
and not self.has_frame_version(frame_type, version)
|
150
150
|
):
|
151
|
-
|
152
|
-
request = await Request.create(frame_type, recipient=self.address)
|
153
|
-
self.queue.put_nowait(request)
|
151
|
+
await self._request_frame_version(frame_type, version)
|
154
152
|
self._frame_versions[frame_type] = version
|
155
153
|
|
154
|
+
async def _request_frame_version(
|
155
|
+
self, frame_type: FrameType | int, version: int
|
156
|
+
) -> None:
|
157
|
+
"""Request frame version from the device."""
|
158
|
+
_LOGGER.info("Updating frame %s to version %i", repr(frame_type), version)
|
159
|
+
request = await Request.create(frame_type, recipient=self.address)
|
160
|
+
self.queue.put_nowait(request)
|
161
|
+
|
156
162
|
def has_frame_version(self, frame_type: FrameType | int, version: int) -> bool:
|
157
163
|
"""Return True if frame data is up to date, False otherwise."""
|
158
164
|
return (
|
@@ -171,25 +177,6 @@ class PhysicalDevice(Device, ABC):
|
|
171
177
|
for name, value in frame.data.items():
|
172
178
|
self.dispatch_nowait(name, value)
|
173
179
|
|
174
|
-
async def async_setup(self) -> bool:
|
175
|
-
"""Set up addressable device."""
|
176
|
-
results = await asyncio.gather(
|
177
|
-
*(
|
178
|
-
self.request(description.provides, description.frame_type)
|
179
|
-
for description in self._setup_frames
|
180
|
-
),
|
181
|
-
return_exceptions=True,
|
182
|
-
)
|
183
|
-
|
184
|
-
errors = [
|
185
|
-
result.frame_type for result in results if isinstance(result, RequestError)
|
186
|
-
]
|
187
|
-
|
188
|
-
await asyncio.gather(
|
189
|
-
self.dispatch(ATTR_FRAME_ERRORS, errors), self.dispatch(ATTR_LOADED, True)
|
190
|
-
)
|
191
|
-
return True
|
192
|
-
|
193
180
|
async def request(
|
194
181
|
self, name: str, frame_type: FrameType, retries: int = 3, timeout: float = 3.0
|
195
182
|
) -> Any:
|
@@ -197,6 +184,7 @@ class PhysicalDevice(Device, ABC):
|
|
197
184
|
|
198
185
|
If value is not available before timeout, retry request.
|
199
186
|
"""
|
187
|
+
_LOGGER.info("Requesting '%s' with %s", name, repr(frame_type))
|
200
188
|
request = await Request.create(frame_type, recipient=self.address)
|
201
189
|
while retries > 0:
|
202
190
|
try:
|
@@ -3,15 +3,15 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import asyncio
|
6
|
-
from collections.abc import Coroutine, Generator, Iterable
|
6
|
+
from collections.abc import Coroutine, Generator, Iterable
|
7
7
|
import logging
|
8
8
|
import time
|
9
9
|
from typing import Any, Final
|
10
10
|
|
11
11
|
from pyplumio.const import (
|
12
|
+
ATTR_FRAME_ERRORS,
|
12
13
|
ATTR_PASSWORD,
|
13
14
|
ATTR_SENSORS,
|
14
|
-
ATTR_STATE,
|
15
15
|
STATE_OFF,
|
16
16
|
STATE_ON,
|
17
17
|
DeviceState,
|
@@ -22,6 +22,7 @@ from pyplumio.const import (
|
|
22
22
|
from pyplumio.devices import PhysicalDevice
|
23
23
|
from pyplumio.devices.mixer import Mixer
|
24
24
|
from pyplumio.devices.thermostat import Thermostat
|
25
|
+
from pyplumio.exceptions import RequestError
|
25
26
|
from pyplumio.filters import on_change
|
26
27
|
from pyplumio.frames import DataFrameDescription, Frame, Request
|
27
28
|
from pyplumio.helpers.event_manager import event_listener
|
@@ -40,14 +41,12 @@ from pyplumio.structures.ecomax_parameters import (
|
|
40
41
|
ATTR_ECOMAX_CONTROL,
|
41
42
|
ATTR_ECOMAX_PARAMETERS,
|
42
43
|
)
|
43
|
-
from pyplumio.structures.fuel_consumption import ATTR_FUEL_CONSUMPTION
|
44
44
|
from pyplumio.structures.mixer_parameters import ATTR_MIXER_PARAMETERS
|
45
45
|
from pyplumio.structures.mixer_sensors import ATTR_MIXER_SENSORS
|
46
46
|
from pyplumio.structures.network_info import ATTR_NETWORK, NetworkInfo
|
47
47
|
from pyplumio.structures.product_info import ATTR_PRODUCT, ProductInfo
|
48
48
|
from pyplumio.structures.regulator_data_schema import ATTR_REGDATA_SCHEMA
|
49
49
|
from pyplumio.structures.schedules import (
|
50
|
-
ATTR_SCHEDULE_PARAMETERS,
|
51
50
|
ATTR_SCHEDULES,
|
52
51
|
SCHEDULE_PARAMETERS,
|
53
52
|
SCHEDULES,
|
@@ -55,20 +54,53 @@ from pyplumio.structures.schedules import (
|
|
55
54
|
ScheduleSwitch,
|
56
55
|
ScheduleSwitchDescription,
|
57
56
|
)
|
58
|
-
from pyplumio.structures.thermostat_parameters import
|
59
|
-
ATTR_THERMOSTAT_PARAMETERS,
|
60
|
-
ATTR_THERMOSTAT_PROFILE,
|
61
|
-
)
|
57
|
+
from pyplumio.structures.thermostat_parameters import ATTR_THERMOSTAT_PARAMETERS
|
62
58
|
from pyplumio.structures.thermostat_sensors import ATTR_THERMOSTAT_SENSORS
|
63
59
|
|
60
|
+
_LOGGER = logging.getLogger(__name__)
|
61
|
+
|
62
|
+
|
64
63
|
ATTR_MIXERS: Final = "mixers"
|
65
64
|
ATTR_THERMOSTATS: Final = "thermostats"
|
66
65
|
ATTR_FUEL_BURNED: Final = "fuel_burned"
|
67
66
|
|
68
|
-
|
69
|
-
|
67
|
+
MAX_TIME_SINCE_LAST_FUEL_UPDATE: Final = 5 * 60
|
68
|
+
|
69
|
+
|
70
|
+
class FuelMeter:
|
71
|
+
"""Represents a fuel meter.
|
72
|
+
|
73
|
+
Calculates the fuel burned based on the time
|
74
|
+
elapsed since the last sensor message, which contains fuel
|
75
|
+
consumption data. If the elapsed time is within the acceptable
|
76
|
+
range, it returns the fuel burned data. Otherwise, it logs a
|
77
|
+
warning and returns None.
|
78
|
+
"""
|
79
|
+
|
80
|
+
__slots__ = ("_last_update_time",)
|
81
|
+
|
82
|
+
_last_update_time: float
|
83
|
+
|
84
|
+
def __init__(self) -> None:
|
85
|
+
"""Initialize a new fuel meter."""
|
86
|
+
self._last_update_time = time.monotonic()
|
70
87
|
|
71
|
-
|
88
|
+
def calculate(self, fuel_consumption: float) -> float | None:
|
89
|
+
"""Calculate the amount of burned fuel since last update."""
|
90
|
+
current_time = time.monotonic()
|
91
|
+
time_since_update = current_time - self._last_update_time
|
92
|
+
self._last_update_time = current_time
|
93
|
+
if time_since_update < MAX_TIME_SINCE_LAST_FUEL_UPDATE:
|
94
|
+
return fuel_consumption * (time_since_update / 3600)
|
95
|
+
|
96
|
+
_LOGGER.warning(
|
97
|
+
"Skipping outdated fuel consumption data (was %f seconds old)",
|
98
|
+
time_since_update,
|
99
|
+
)
|
100
|
+
return None
|
101
|
+
|
102
|
+
|
103
|
+
REQUIRED: tuple[DataFrameDescription, ...] = (
|
72
104
|
DataFrameDescription(
|
73
105
|
frame_type=FrameType.REQUEST_UID,
|
74
106
|
provides=ATTR_PRODUCT,
|
@@ -103,28 +135,22 @@ SETUP_FRAME_TYPES: tuple[DataFrameDescription, ...] = (
|
|
103
135
|
),
|
104
136
|
)
|
105
137
|
|
106
|
-
|
138
|
+
REQUIRED_TYPES = [description.frame_type for description in REQUIRED]
|
107
139
|
|
108
140
|
|
109
141
|
class EcoMAX(PhysicalDevice):
|
110
142
|
"""Represents an ecoMAX controller."""
|
111
143
|
|
112
|
-
__slots__ = ("
|
144
|
+
__slots__ = ("_fuel_meter",)
|
113
145
|
|
114
|
-
|
115
|
-
_setup_frames = SETUP_FRAME_TYPES
|
146
|
+
_fuel_meter: FuelMeter
|
116
147
|
|
117
|
-
|
148
|
+
address = DeviceType.ECOMAX
|
118
149
|
|
119
150
|
def __init__(self, queue: asyncio.Queue[Frame], network: NetworkInfo) -> None:
|
120
151
|
"""Initialize a new ecoMAX controller."""
|
121
152
|
super().__init__(queue, network)
|
122
|
-
self.
|
123
|
-
|
124
|
-
async def async_setup(self) -> bool:
|
125
|
-
"""Set up an ecoMAX controller."""
|
126
|
-
await self.wait_for(ATTR_SENSORS)
|
127
|
-
return await super().async_setup()
|
153
|
+
self._fuel_meter = FuelMeter()
|
128
154
|
|
129
155
|
def handle_frame(self, frame: Frame) -> None:
|
130
156
|
"""Handle frame received from the ecoMAX device."""
|
@@ -162,6 +188,22 @@ class EcoMAX(PhysicalDevice):
|
|
162
188
|
|
163
189
|
return self.dispatch_nowait(ATTR_THERMOSTATS, thermostats)
|
164
190
|
|
191
|
+
async def _request_frame_version(
|
192
|
+
self, frame_type: FrameType | int, version: int
|
193
|
+
) -> None:
|
194
|
+
"""Request frame version from the device."""
|
195
|
+
request_frame = False
|
196
|
+
if frame_type not in REQUIRED_TYPES:
|
197
|
+
# Frame not in required, so we'll request it right away.
|
198
|
+
request_frame = True
|
199
|
+
|
200
|
+
if frame_type in self._frame_versions:
|
201
|
+
# Frame in required and has been requested once by setup.
|
202
|
+
request_frame = True
|
203
|
+
|
204
|
+
if request_frame:
|
205
|
+
await super()._request_frame_version(frame_type, version)
|
206
|
+
|
165
207
|
async def _set_ecomax_state(self, state: State) -> bool:
|
166
208
|
"""Try to set the ecoMAX control state."""
|
167
209
|
try:
|
@@ -196,11 +238,34 @@ class EcoMAX(PhysicalDevice):
|
|
196
238
|
await asyncio.gather(*(device.shutdown() for device in devices))
|
197
239
|
await super().shutdown()
|
198
240
|
|
199
|
-
@event_listener
|
241
|
+
@event_listener
|
242
|
+
async def on_event_setup(self, setup: bool) -> None:
|
243
|
+
"""Request frames required to set up an ecoMAX entry."""
|
244
|
+
_LOGGER.info("Setting up device entry")
|
245
|
+
await self.wait_for(ATTR_SENSORS)
|
246
|
+
results = await asyncio.gather(
|
247
|
+
*(
|
248
|
+
self.request(description.provides, description.frame_type)
|
249
|
+
for description in REQUIRED
|
250
|
+
),
|
251
|
+
return_exceptions=True,
|
252
|
+
)
|
253
|
+
|
254
|
+
errors = [
|
255
|
+
result.frame_type for result in results if isinstance(result, RequestError)
|
256
|
+
]
|
257
|
+
|
258
|
+
if errors:
|
259
|
+
self.dispatch_nowait(ATTR_FRAME_ERRORS, errors)
|
260
|
+
|
261
|
+
_LOGGER.info("Device entry setup done")
|
262
|
+
|
263
|
+
@event_listener
|
200
264
|
async def on_event_ecomax_parameters(
|
201
|
-
self, parameters:
|
265
|
+
self, parameters: list[tuple[int, ParameterValues]]
|
202
266
|
) -> bool:
|
203
267
|
"""Update ecoMAX parameters and dispatch the events."""
|
268
|
+
_LOGGER.info("Received device parameters")
|
204
269
|
product_info: ProductInfo = await self.get(ATTR_PRODUCT)
|
205
270
|
|
206
271
|
def _ecomax_parameter_events() -> Generator[Coroutine, Any, None]:
|
@@ -236,37 +301,20 @@ class EcoMAX(PhysicalDevice):
|
|
236
301
|
await asyncio.gather(*_ecomax_parameter_events())
|
237
302
|
return True
|
238
303
|
|
239
|
-
@event_listener
|
304
|
+
@event_listener
|
240
305
|
async def on_event_fuel_consumption(self, fuel_consumption: float) -> None:
|
241
|
-
"""Update the amount of burned fuel.
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
consumption data. If the elapsed time is within the acceptable
|
246
|
-
range, it dispatches the fuel burned data. Otherwise, it logs a
|
247
|
-
warning and skips the outdated data.
|
248
|
-
"""
|
249
|
-
time_ns = time.perf_counter_ns()
|
250
|
-
nanoseconds_passed = time_ns - self._fuel_burned_time_ns
|
251
|
-
self._fuel_burned_time_ns = time_ns
|
252
|
-
if nanoseconds_passed < MAX_TIME_SINCE_LAST_FUEL_UPDATE_NS:
|
253
|
-
return self.dispatch_nowait(
|
254
|
-
ATTR_FUEL_BURNED,
|
255
|
-
fuel_consumption * nanoseconds_passed / (3600 * NANOSECONDS_IN_SECOND),
|
256
|
-
)
|
257
|
-
|
258
|
-
_LOGGER.warning(
|
259
|
-
"Skipping outdated fuel consumption data: %f (was %i seconds old)",
|
260
|
-
fuel_consumption,
|
261
|
-
nanoseconds_passed / NANOSECONDS_IN_SECOND,
|
262
|
-
)
|
306
|
+
"""Update the amount of burned fuel."""
|
307
|
+
fuel_burned = self._fuel_meter.calculate(fuel_consumption)
|
308
|
+
if fuel_burned is not None:
|
309
|
+
self.dispatch_nowait(ATTR_FUEL_BURNED, fuel_burned)
|
263
310
|
|
264
|
-
@event_listener
|
311
|
+
@event_listener
|
265
312
|
async def on_event_mixer_parameters(
|
266
313
|
self,
|
267
|
-
parameters: dict[int,
|
314
|
+
parameters: dict[int, list[tuple[int, ParameterValues]]] | None,
|
268
315
|
) -> bool:
|
269
316
|
"""Handle mixer parameters and dispatch the events."""
|
317
|
+
_LOGGER.info("Received mixer parameters")
|
270
318
|
if parameters:
|
271
319
|
await asyncio.gather(
|
272
320
|
*(
|
@@ -278,11 +326,12 @@ class EcoMAX(PhysicalDevice):
|
|
278
326
|
|
279
327
|
return False
|
280
328
|
|
281
|
-
@event_listener
|
329
|
+
@event_listener
|
282
330
|
async def on_event_mixer_sensors(
|
283
331
|
self, sensors: dict[int, dict[str, Any]] | None
|
284
332
|
) -> bool:
|
285
333
|
"""Update mixer sensors and dispatch the events."""
|
334
|
+
_LOGGER.info("Received mixer sensors")
|
286
335
|
if sensors:
|
287
336
|
await asyncio.gather(
|
288
337
|
*(
|
@@ -294,9 +343,9 @@ class EcoMAX(PhysicalDevice):
|
|
294
343
|
|
295
344
|
return False
|
296
345
|
|
297
|
-
@event_listener
|
346
|
+
@event_listener
|
298
347
|
async def on_event_schedule_parameters(
|
299
|
-
self, parameters:
|
348
|
+
self, parameters: list[tuple[int, ParameterValues]]
|
300
349
|
) -> bool:
|
301
350
|
"""Update schedule parameters and dispatch the events."""
|
302
351
|
|
@@ -319,20 +368,22 @@ class EcoMAX(PhysicalDevice):
|
|
319
368
|
await asyncio.gather(*_schedule_parameter_events())
|
320
369
|
return True
|
321
370
|
|
322
|
-
@event_listener
|
371
|
+
@event_listener
|
323
372
|
async def on_event_sensors(self, sensors: dict[str, Any]) -> bool:
|
324
373
|
"""Update ecoMAX sensors and dispatch the events."""
|
374
|
+
_LOGGER.info("Received device sensors")
|
325
375
|
await asyncio.gather(
|
326
376
|
*(self.dispatch(name, value) for name, value in sensors.items())
|
327
377
|
)
|
328
378
|
return True
|
329
379
|
|
330
|
-
@event_listener
|
380
|
+
@event_listener
|
331
381
|
async def on_event_thermostat_parameters(
|
332
382
|
self,
|
333
|
-
parameters: dict[int,
|
383
|
+
parameters: dict[int, list[tuple[int, ParameterValues]]] | None,
|
334
384
|
) -> bool:
|
335
385
|
"""Handle thermostat parameters and dispatch the events."""
|
386
|
+
_LOGGER.info("Received thermostat parameters")
|
336
387
|
if parameters:
|
337
388
|
await asyncio.gather(
|
338
389
|
*(
|
@@ -346,7 +397,7 @@ class EcoMAX(PhysicalDevice):
|
|
346
397
|
|
347
398
|
return False
|
348
399
|
|
349
|
-
@event_listener
|
400
|
+
@event_listener
|
350
401
|
async def on_event_thermostat_profile(
|
351
402
|
self, values: ParameterValues | None
|
352
403
|
) -> EcomaxNumber | None:
|
@@ -358,11 +409,12 @@ class EcoMAX(PhysicalDevice):
|
|
358
409
|
|
359
410
|
return None
|
360
411
|
|
361
|
-
@event_listener
|
412
|
+
@event_listener
|
362
413
|
async def on_event_thermostat_sensors(
|
363
414
|
self, sensors: dict[int, dict[str, Any]] | None
|
364
415
|
) -> bool:
|
365
416
|
"""Update thermostat sensors and dispatch the events."""
|
417
|
+
_LOGGER.info("Received thermostat sensors")
|
366
418
|
if sensors:
|
367
419
|
await asyncio.gather(
|
368
420
|
*(
|
@@ -377,11 +429,12 @@ class EcoMAX(PhysicalDevice):
|
|
377
429
|
|
378
430
|
return False
|
379
431
|
|
380
|
-
@event_listener
|
432
|
+
@event_listener
|
381
433
|
async def on_event_schedules(
|
382
434
|
self, schedules: list[tuple[int, list[list[bool]]]]
|
383
435
|
) -> dict[str, Schedule]:
|
384
436
|
"""Update schedules."""
|
437
|
+
_LOGGER.info("Received device schedules")
|
385
438
|
return {
|
386
439
|
SCHEDULES[index]: Schedule(
|
387
440
|
name=SCHEDULES[index],
|
@@ -397,7 +450,7 @@ class EcoMAX(PhysicalDevice):
|
|
397
450
|
for index, schedule in schedules
|
398
451
|
}
|
399
452
|
|
400
|
-
@event_listener(
|
453
|
+
@event_listener(filter=on_change)
|
401
454
|
async def on_event_state(self, state: DeviceState) -> None:
|
402
455
|
"""Update the ecoMAX control parameter."""
|
403
456
|
await self.dispatch(
|
@@ -3,7 +3,7 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import asyncio
|
6
|
-
from collections.abc import Coroutine, Generator
|
6
|
+
from collections.abc import Coroutine, Generator
|
7
7
|
import logging
|
8
8
|
from typing import Any
|
9
9
|
|
@@ -16,8 +16,6 @@ from pyplumio.parameters.mixer import (
|
|
16
16
|
MixerSwitchDescription,
|
17
17
|
get_mixer_parameter_types,
|
18
18
|
)
|
19
|
-
from pyplumio.structures.mixer_parameters import ATTR_MIXER_PARAMETERS
|
20
|
-
from pyplumio.structures.mixer_sensors import ATTR_MIXER_SENSORS
|
21
19
|
from pyplumio.structures.product_info import ATTR_PRODUCT, ProductInfo
|
22
20
|
|
23
21
|
_LOGGER = logging.getLogger(__name__)
|
@@ -28,19 +26,21 @@ class Mixer(VirtualDevice):
|
|
28
26
|
|
29
27
|
__slots__ = ()
|
30
28
|
|
31
|
-
@event_listener
|
29
|
+
@event_listener
|
32
30
|
async def on_event_mixer_sensors(self, sensors: dict[str, Any]) -> bool:
|
33
31
|
"""Update mixer sensors and dispatch the events."""
|
32
|
+
_LOGGER.info("Received mixer %i sensors", self.index)
|
34
33
|
await asyncio.gather(
|
35
34
|
*(self.dispatch(name, value) for name, value in sensors.items())
|
36
35
|
)
|
37
36
|
return True
|
38
37
|
|
39
|
-
@event_listener
|
38
|
+
@event_listener
|
40
39
|
async def on_event_mixer_parameters(
|
41
|
-
self, parameters:
|
40
|
+
self, parameters: list[tuple[int, ParameterValues]]
|
42
41
|
) -> bool:
|
43
42
|
"""Update mixer parameters and dispatch the events."""
|
43
|
+
_LOGGER.info("Received mixer %i parameters", self.index)
|
44
44
|
product_info: ProductInfo = await self.parent.get(ATTR_PRODUCT)
|
45
45
|
|
46
46
|
def _mixer_parameter_events() -> Generator[Coroutine, Any, None]:
|
@@ -3,7 +3,8 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import asyncio
|
6
|
-
from collections.abc import Coroutine, Generator
|
6
|
+
from collections.abc import Coroutine, Generator
|
7
|
+
import logging
|
7
8
|
from typing import Any
|
8
9
|
|
9
10
|
from pyplumio.devices import VirtualDevice
|
@@ -15,8 +16,8 @@ from pyplumio.parameters.thermostat import (
|
|
15
16
|
ThermostatSwitchDescription,
|
16
17
|
get_thermostat_parameter_types,
|
17
18
|
)
|
18
|
-
|
19
|
-
|
19
|
+
|
20
|
+
_LOGGER = logging.getLogger()
|
20
21
|
|
21
22
|
|
22
23
|
class Thermostat(VirtualDevice):
|
@@ -24,19 +25,21 @@ class Thermostat(VirtualDevice):
|
|
24
25
|
|
25
26
|
__slots__ = ()
|
26
27
|
|
27
|
-
@event_listener
|
28
|
+
@event_listener
|
28
29
|
async def on_event_thermostat_sensors(self, sensors: dict[str, Any]) -> bool:
|
29
30
|
"""Update thermostat sensors and dispatch the events."""
|
31
|
+
_LOGGER.info("Received thermostat %i sensors", self.index)
|
30
32
|
await asyncio.gather(
|
31
33
|
*(self.dispatch(name, value) for name, value in sensors.items())
|
32
34
|
)
|
33
35
|
return True
|
34
36
|
|
35
|
-
@event_listener
|
37
|
+
@event_listener
|
36
38
|
async def on_event_thermostat_parameters(
|
37
|
-
self, parameters:
|
39
|
+
self, parameters: list[tuple[int, ParameterValues]]
|
38
40
|
) -> bool:
|
39
41
|
"""Update thermostat parameters and dispatch the events."""
|
42
|
+
_LOGGER.info("Received thermostat %i parameters", self.index)
|
40
43
|
|
41
44
|
def _thermostat_parameter_events() -> Generator[Coroutine, Any, None]:
|
42
45
|
"""Get dispatch calls for thermostat parameter events."""
|