PyPlumIO 0.5.29__py3-none-any.whl → 0.5.30__py3-none-any.whl
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.29.dist-info → PyPlumIO-0.5.30.dist-info}/METADATA +21 -21
- {PyPlumIO-0.5.29.dist-info → PyPlumIO-0.5.30.dist-info}/RECORD +13 -13
- {PyPlumIO-0.5.29.dist-info → PyPlumIO-0.5.30.dist-info}/WHEEL +1 -1
- pyplumio/_version.py +2 -2
- pyplumio/devices/__init__.py +38 -1
- pyplumio/devices/ecomax.py +1 -30
- pyplumio/helpers/parameter.py +38 -2
- pyplumio/structures/ecomax_parameters.py +16 -3
- pyplumio/structures/mixer_parameters.py +11 -0
- pyplumio/structures/schedules.py +13 -2
- pyplumio/structures/thermostat_parameters.py +20 -11
- {PyPlumIO-0.5.29.dist-info → PyPlumIO-0.5.30.dist-info}/LICENSE +0 -0
- {PyPlumIO-0.5.29.dist-info → PyPlumIO-0.5.30.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: PyPlumIO
|
3
|
-
Version: 0.5.
|
3
|
+
Version: 0.5.30
|
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
|
@@ -22,27 +22,27 @@ Classifier: Topic :: Home Automation
|
|
22
22
|
Requires-Python: >=3.9
|
23
23
|
Description-Content-Type: text/markdown
|
24
24
|
License-File: LICENSE
|
25
|
-
Requires-Dist: dataslots
|
26
|
-
Requires-Dist: pyserial-asyncio
|
27
|
-
Requires-Dist: typing-extensions
|
28
|
-
Provides-Extra: dev
|
29
|
-
Requires-Dist: pyplumio[docs,test] ; extra == 'dev'
|
30
|
-
Requires-Dist: pre-commit ==4.0.1 ; extra == 'dev'
|
31
|
-
Requires-Dist: tomli ==2.0.2 ; extra == 'dev'
|
32
|
-
Provides-Extra: docs
|
33
|
-
Requires-Dist: sphinx ==8.1.3 ; extra == 'docs'
|
34
|
-
Requires-Dist: sphinx-rtd-theme ==3.0.1 ; extra == 'docs'
|
35
|
-
Requires-Dist: readthedocs-sphinx-search ==0.3.2 ; extra == 'docs'
|
25
|
+
Requires-Dist: dataslots==1.2.0
|
26
|
+
Requires-Dist: pyserial-asyncio==0.6
|
27
|
+
Requires-Dist: typing-extensions==4.12.2
|
36
28
|
Provides-Extra: test
|
37
|
-
Requires-Dist: codespell
|
38
|
-
Requires-Dist: coverage
|
39
|
-
Requires-Dist: mypy
|
40
|
-
Requires-Dist: pyserial-asyncio-fast
|
41
|
-
Requires-Dist: pytest
|
42
|
-
Requires-Dist: pytest-asyncio
|
43
|
-
Requires-Dist: ruff
|
44
|
-
Requires-Dist: tox
|
45
|
-
Requires-Dist: types-pyserial
|
29
|
+
Requires-Dist: codespell==2.3.0; extra == "test"
|
30
|
+
Requires-Dist: coverage==7.6.8; extra == "test"
|
31
|
+
Requires-Dist: mypy==1.13.0; extra == "test"
|
32
|
+
Requires-Dist: pyserial-asyncio-fast==0.14; extra == "test"
|
33
|
+
Requires-Dist: pytest==8.3.4; extra == "test"
|
34
|
+
Requires-Dist: pytest-asyncio==0.24.0; extra == "test"
|
35
|
+
Requires-Dist: ruff==0.8.1; extra == "test"
|
36
|
+
Requires-Dist: tox==4.23.2; extra == "test"
|
37
|
+
Requires-Dist: types-pyserial==3.5.0.20240826; extra == "test"
|
38
|
+
Provides-Extra: docs
|
39
|
+
Requires-Dist: sphinx==8.1.3; extra == "docs"
|
40
|
+
Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
|
41
|
+
Requires-Dist: readthedocs-sphinx-search==0.3.2; extra == "docs"
|
42
|
+
Provides-Extra: dev
|
43
|
+
Requires-Dist: pyplumio[docs,test]; extra == "dev"
|
44
|
+
Requires-Dist: pre-commit==4.0.1; extra == "dev"
|
45
|
+
Requires-Dist: tomli==2.2.1; 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
|
pyplumio/__init__.py,sha256=ditJTIOFGJDg60atHzOpiggdUrZHpSynno7MtpZUGVk,3299
|
2
2
|
pyplumio/__main__.py,sha256=3IwHHSq-iay5FaeMc95klobe-xv82yydSKcBE7BFZ6M,500
|
3
|
-
pyplumio/_version.py,sha256=
|
3
|
+
pyplumio/_version.py,sha256=PRcySwknPDLJzIxasFyVZNYI8LFj_28fjnJMHK2q0Ko,413
|
4
4
|
pyplumio/connection.py,sha256=6mUbcjGxxEhMVIbzZgCqH-Ez-fcYoRj7ZbVSzpikpNA,5949
|
5
5
|
pyplumio/const.py,sha256=LyXa5aVy2KxnZq7H7F8s5SYsAgEC2UzZYMMRauliB2E,5502
|
6
6
|
pyplumio/exceptions.py,sha256=Wn-y5AJ5xfaBlHhTUVKB27_0Us8_OVHqh-sicnr9sYA,700
|
@@ -9,8 +9,8 @@ pyplumio/protocol.py,sha256=VRxrj8vZ1FMawqblKkyxg_V61TBSvVynd9u0JXYnMUU,8090
|
|
9
9
|
pyplumio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
pyplumio/stream.py,sha256=mtMpnUR3TfEmL5JUGXr6GnpPGBwzCokqIKDWp4vYiVg,4654
|
11
11
|
pyplumio/utils.py,sha256=TnBzRopinyp92wruguijxcIYmaeyNVTFX0dygI5FCMU,823
|
12
|
-
pyplumio/devices/__init__.py,sha256=
|
13
|
-
pyplumio/devices/ecomax.py,sha256=
|
12
|
+
pyplumio/devices/__init__.py,sha256=YN09bGsyJ5WVmF8_-veUckqUl57eBwmfNr8M5qhyqy0,8149
|
13
|
+
pyplumio/devices/ecomax.py,sha256=ybFLJN7O3unBcyzuVmYTssBv86bPiiTGvFpFJezwUE4,15478
|
14
14
|
pyplumio/devices/ecoster.py,sha256=jNWli7ye9T6yfkcFJZhhUHH7KOv-L6AgYFp_dKyv3OM,263
|
15
15
|
pyplumio/devices/mixer.py,sha256=CnHWrJELtFgs2YTHGpQwKr2UTRdetX76OvLBA2PH-fs,3207
|
16
16
|
pyplumio/devices/thermostat.py,sha256=-CZNRyywoDU6csFu85KSmQ5woVXY0x6peXkeOsi_fqg,2617
|
@@ -22,7 +22,7 @@ pyplumio/helpers/__init__.py,sha256=H2xxdkF-9uADLwEbfBUoxNTdwru3L5Z2cfJjgsuRsn0,
|
|
22
22
|
pyplumio/helpers/data_types.py,sha256=nB3afOLmppgSCWkZoX1-1yWPNMMNSem77x7XQ1Mi8H8,9103
|
23
23
|
pyplumio/helpers/event_manager.py,sha256=xQOfiP_nP1Pz5zhB6HU5gXyyJXjhisYshL8_HRxDgt8,6412
|
24
24
|
pyplumio/helpers/factory.py,sha256=6ArzJDq3MiiMaRpMEP0kC6wJWsoqOqe32V1RCxg1478,1005
|
25
|
-
pyplumio/helpers/parameter.py,sha256=
|
25
|
+
pyplumio/helpers/parameter.py,sha256=yO3KSX8FtU-dMe5FzNW-RXs5yW-lYu2F5Po-4ghcSnM,12533
|
26
26
|
pyplumio/helpers/schedule.py,sha256=PnVEkgthg6tHpHvZK9fXJz9VKNDyQ_7BFT4TTVEwNhI,5310
|
27
27
|
pyplumio/helpers/task_manager.py,sha256=HAd69yGTRL0zQsu-ywnbLu1UXiJzgHWuhYWA--vs4lQ,1181
|
28
28
|
pyplumio/helpers/timeout.py,sha256=JAhWNtIpcXyVILIwHWVy5mYofqbbRDGKLdTUKkQuajs,772
|
@@ -31,13 +31,13 @@ pyplumio/structures/__init__.py,sha256=EjK-5qJZ0F7lpP2b6epvTMg9cIBl4Kn91nqNkEcLw
|
|
31
31
|
pyplumio/structures/alerts.py,sha256=8ievMl5_tUBlnTLCiZoIloucIngCcoAYy6uI9sSXrt0,3664
|
32
32
|
pyplumio/structures/boiler_load.py,sha256=p3mOzZUU-g7A2tG_yp8podEqpI81hlsOZmHELyPNRY8,838
|
33
33
|
pyplumio/structures/boiler_power.py,sha256=72qsvccg49FdRdXv2f2K5sGpjT7wAOLFjlIGWpO-DVg,901
|
34
|
-
pyplumio/structures/ecomax_parameters.py,sha256=
|
34
|
+
pyplumio/structures/ecomax_parameters.py,sha256=ki9YSzVRhCD_rYHFogJoyk0NwyNlWdZrHcp-rrCuKCU,28440
|
35
35
|
pyplumio/structures/fan_power.py,sha256=Q5fv-7_2NVuLeQPIVIylvgN7M8-a9D8rRUE0QGjyS3w,871
|
36
36
|
pyplumio/structures/frame_versions.py,sha256=hbcVuhuPNy5qd39Vk7w4WdPCW-TNx1cAYWzA2mXocyk,1548
|
37
37
|
pyplumio/structures/fuel_consumption.py,sha256=_p2dI4H67Eopn7IF0Gj77A8c_8lNKhhDDAtmugxLd4s,976
|
38
38
|
pyplumio/structures/fuel_level.py,sha256=mJpp1dnRD1wXi_6EyNX7TNXosjcr905rSHOnuZ5VD74,1069
|
39
39
|
pyplumio/structures/lambda_sensor.py,sha256=JNSCiBJoM8Uk3OGbmFIigaLOntQST5U_UrmCpaQBlM0,1595
|
40
|
-
pyplumio/structures/mixer_parameters.py,sha256=
|
40
|
+
pyplumio/structures/mixer_parameters.py,sha256=S_YOigzM9TsdEjk5EluME5fJ8owO84-xOFT9tTdBgEo,9553
|
41
41
|
pyplumio/structures/mixer_sensors.py,sha256=-cN7U-Fr2fmAQ5McQL7bZUC8CFlb1y8TN0f_dqy3UK0,2312
|
42
42
|
pyplumio/structures/modules.py,sha256=oXUIqrOAV1dZzBV5zUH3HDUSFvNOjpUSx0TF9nZVnbs,2569
|
43
43
|
pyplumio/structures/network_info.py,sha256=kPxmIaDGm5SyLRKVFzcrODlUtB0u5JjiZqekoKSyDpA,4159
|
@@ -48,13 +48,13 @@ pyplumio/structures/product_info.py,sha256=uiEN6DFQlzmBvQByTirFzXQShoex0YGdFS9WI
|
|
48
48
|
pyplumio/structures/program_version.py,sha256=R-medELYHDlk_ALsw5HOVbZRb7JD3yBUsGwqwVCjrkU,2550
|
49
49
|
pyplumio/structures/regulator_data.py,sha256=z2mSE-cxImn8YRr_yZCcDlIbXnKdETkN7GigV5vEJqA,2265
|
50
50
|
pyplumio/structures/regulator_data_schema.py,sha256=XM6M9ep3NyogbLPqp88mMTg8Sa9e5SFzV5I5pSYw5GY,1487
|
51
|
-
pyplumio/structures/schedules.py,sha256=
|
51
|
+
pyplumio/structures/schedules.py,sha256=rKPWC5qcKAh6xxs91oY23NqQcWby8e3gXrD6qJAVDGo,7153
|
52
52
|
pyplumio/structures/statuses.py,sha256=wkoynyMRr1VREwfBC6vU48kPA8ZQ83pcXuciy2xHJrk,1166
|
53
53
|
pyplumio/structures/temperatures.py,sha256=1CDzehNmbALz1Jyt_9gZNIk52q6Wv-xQXjijVDCVYec,2337
|
54
|
-
pyplumio/structures/thermostat_parameters.py,sha256=
|
54
|
+
pyplumio/structures/thermostat_parameters.py,sha256=6r8_EU9T1IJ0vzZMduiLzsAaaWr6cKW_zHHAGsfGZI4,8804
|
55
55
|
pyplumio/structures/thermostat_sensors.py,sha256=8e1TxYIJTQKT0kIGO9gG4hGdLOBUpIhiPToQyOMyeNE,3237
|
56
|
-
PyPlumIO-0.5.
|
57
|
-
PyPlumIO-0.5.
|
58
|
-
PyPlumIO-0.5.
|
59
|
-
PyPlumIO-0.5.
|
60
|
-
PyPlumIO-0.5.
|
56
|
+
PyPlumIO-0.5.30.dist-info/LICENSE,sha256=m-UuZFjXJ22uPTGm9kSHS8bqjsf5T8k2wL9bJn1Y04o,1088
|
57
|
+
PyPlumIO-0.5.30.dist-info/METADATA,sha256=KMMtx2B4gTlnFBoLVzVHGs75Od9qf6BN1tH3pqeYgqM,5458
|
58
|
+
PyPlumIO-0.5.30.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
59
|
+
PyPlumIO-0.5.30.dist-info/top_level.txt,sha256=kNBz9UPPkPD9teDn3U_sEy5LjzwLm9KfADCXtBlbw8A,9
|
60
|
+
PyPlumIO-0.5.30.dist-info/RECORD,,
|
pyplumio/_version.py
CHANGED
pyplumio/devices/__init__.py
CHANGED
@@ -5,17 +5,21 @@ from __future__ import annotations
|
|
5
5
|
from abc import ABC
|
6
6
|
import asyncio
|
7
7
|
from functools import cache
|
8
|
+
import logging
|
8
9
|
from typing import Any, ClassVar
|
9
10
|
|
10
11
|
from pyplumio.const import ATTR_FRAME_ERRORS, ATTR_LOADED, DeviceType, FrameType
|
11
12
|
from pyplumio.exceptions import UnknownDeviceError
|
12
|
-
from pyplumio.frames import DataFrameDescription, Frame, Request
|
13
|
+
from pyplumio.frames import DataFrameDescription, Frame, Request, is_known_frame_type
|
13
14
|
from pyplumio.helpers.event_manager import EventManager
|
14
15
|
from pyplumio.helpers.factory import create_instance
|
15
16
|
from pyplumio.helpers.parameter import Parameter, ParameterValue
|
17
|
+
from pyplumio.structures.frame_versions import ATTR_FRAME_VERSIONS
|
16
18
|
from pyplumio.structures.network_info import NetworkInfo
|
17
19
|
from pyplumio.utils import to_camelcase
|
18
20
|
|
21
|
+
_LOGGER = logging.getLogger(__name__)
|
22
|
+
|
19
23
|
|
20
24
|
@cache
|
21
25
|
def is_known_device_type(device_type: int) -> bool:
|
@@ -125,11 +129,44 @@ class PhysicalDevice(Device, ABC):
|
|
125
129
|
address: ClassVar[int]
|
126
130
|
_network: NetworkInfo
|
127
131
|
_setup_frames: tuple[DataFrameDescription, ...]
|
132
|
+
_frame_versions: dict[int, int]
|
128
133
|
|
129
134
|
def __init__(self, queue: asyncio.Queue[Frame], network: NetworkInfo) -> None:
|
130
135
|
"""Initialize a new physical device."""
|
131
136
|
super().__init__(queue)
|
132
137
|
self._network = network
|
138
|
+
self._frame_versions = {}
|
139
|
+
|
140
|
+
async def update_frame_versions(versions: dict[int, int]) -> None:
|
141
|
+
"""Check frame versions and update outdated frames."""
|
142
|
+
for frame_type, version in versions.items():
|
143
|
+
if (
|
144
|
+
is_known_frame_type(frame_type)
|
145
|
+
and self.supports_frame_type(frame_type)
|
146
|
+
and not self.has_frame_version(frame_type, version)
|
147
|
+
):
|
148
|
+
_LOGGER.debug(
|
149
|
+
"Updating frame %s to version %i", repr(frame_type), version
|
150
|
+
)
|
151
|
+
request = await Request.create(frame_type, recipient=self.address)
|
152
|
+
self.queue.put_nowait(request)
|
153
|
+
self._frame_versions[frame_type] = version
|
154
|
+
|
155
|
+
self.subscribe(ATTR_FRAME_VERSIONS, update_frame_versions)
|
156
|
+
|
157
|
+
def has_frame_version(self, frame_type: int, version: int | None = None) -> bool:
|
158
|
+
"""Return True if frame data is up to date, False otherwise."""
|
159
|
+
if frame_type not in self._frame_versions:
|
160
|
+
return False
|
161
|
+
|
162
|
+
if version is None or self._frame_versions[frame_type] == version:
|
163
|
+
return True
|
164
|
+
|
165
|
+
return False
|
166
|
+
|
167
|
+
def supports_frame_type(self, frame_type: int) -> bool:
|
168
|
+
"""Check if frame type is supported by the device."""
|
169
|
+
return frame_type not in self.data.get(ATTR_FRAME_ERRORS, [])
|
133
170
|
|
134
171
|
def handle_frame(self, frame: Frame) -> None:
|
135
172
|
"""Handle frame received from the device."""
|
pyplumio/devices/ecomax.py
CHANGED
@@ -9,7 +9,6 @@ import time
|
|
9
9
|
from typing import Any, Final
|
10
10
|
|
11
11
|
from pyplumio.const import (
|
12
|
-
ATTR_FRAME_ERRORS,
|
13
12
|
ATTR_PASSWORD,
|
14
13
|
ATTR_SENSORS,
|
15
14
|
ATTR_STATE,
|
@@ -21,7 +20,7 @@ from pyplumio.devices import PhysicalDevice
|
|
21
20
|
from pyplumio.devices.mixer import Mixer
|
22
21
|
from pyplumio.devices.thermostat import Thermostat
|
23
22
|
from pyplumio.filters import on_change
|
24
|
-
from pyplumio.frames import DataFrameDescription, Frame, Request
|
23
|
+
from pyplumio.frames import DataFrameDescription, Frame, Request
|
25
24
|
from pyplumio.helpers.parameter import ParameterValues
|
26
25
|
from pyplumio.helpers.schedule import Schedule, ScheduleDay
|
27
26
|
from pyplumio.structures.alerts import ATTR_TOTAL_ALERTS
|
@@ -35,7 +34,6 @@ from pyplumio.structures.ecomax_parameters import (
|
|
35
34
|
EcomaxSwitch,
|
36
35
|
EcomaxSwitchDescription,
|
37
36
|
)
|
38
|
-
from pyplumio.structures.frame_versions import ATTR_FRAME_VERSIONS
|
39
37
|
from pyplumio.structures.fuel_consumption import ATTR_FUEL_CONSUMPTION
|
40
38
|
from pyplumio.structures.mixer_parameters import ATTR_MIXER_PARAMETERS
|
41
39
|
from pyplumio.structures.mixer_sensors import ATTR_MIXER_SENSORS
|
@@ -106,17 +104,14 @@ class EcoMAX(PhysicalDevice):
|
|
106
104
|
|
107
105
|
address = DeviceType.ECOMAX
|
108
106
|
|
109
|
-
_frame_versions: dict[int, int]
|
110
107
|
_fuel_burned_timestamp_ns: int
|
111
108
|
_setup_frames = SETUP_FRAME_TYPES
|
112
109
|
|
113
110
|
def __init__(self, queue: asyncio.Queue[Frame], network: NetworkInfo) -> None:
|
114
111
|
"""Initialize a new ecoMAX controller."""
|
115
112
|
super().__init__(queue, network)
|
116
|
-
self._frame_versions = {}
|
117
113
|
self._fuel_burned_timestamp_ns = time.perf_counter_ns()
|
118
114
|
self.subscribe(ATTR_ECOMAX_PARAMETERS, self._handle_ecomax_parameters)
|
119
|
-
self.subscribe(ATTR_FRAME_VERSIONS, self._update_frame_versions)
|
120
115
|
self.subscribe(ATTR_FUEL_CONSUMPTION, self._add_burned_fuel_counter)
|
121
116
|
self.subscribe(ATTR_MIXER_PARAMETERS, self._handle_mixer_parameters)
|
122
117
|
self.subscribe(ATTR_MIXER_SENSORS, self._handle_mixer_sensors)
|
@@ -142,17 +137,6 @@ class EcoMAX(PhysicalDevice):
|
|
142
137
|
|
143
138
|
super().handle_frame(frame)
|
144
139
|
|
145
|
-
def _has_frame_version(self, frame_type: FrameType | int, version: int) -> bool:
|
146
|
-
"""Check if ecoMAX controller has this version of the frame."""
|
147
|
-
return (
|
148
|
-
frame_type in self._frame_versions
|
149
|
-
and self._frame_versions[frame_type] == version
|
150
|
-
)
|
151
|
-
|
152
|
-
def _frame_is_supported(self, frame_type: FrameType | int) -> bool:
|
153
|
-
"""Check if frame is supported by the device."""
|
154
|
-
return frame_type not in self.data.get(ATTR_FRAME_ERRORS, [])
|
155
|
-
|
156
140
|
def _mixers(self, indexes: Iterable[int]) -> Generator[Mixer, None, None]:
|
157
141
|
"""Iterate through the mixer indexes.
|
158
142
|
|
@@ -224,19 +208,6 @@ class EcoMAX(PhysicalDevice):
|
|
224
208
|
await asyncio.gather(*_ecomax_parameter_events())
|
225
209
|
return True
|
226
210
|
|
227
|
-
async def _update_frame_versions(self, versions: dict[int, int]) -> None:
|
228
|
-
"""Check frame versions and update outdated frames."""
|
229
|
-
for frame_type, version in versions.items():
|
230
|
-
if (
|
231
|
-
is_known_frame_type(frame_type)
|
232
|
-
and self._frame_is_supported(frame_type)
|
233
|
-
and not self._has_frame_version(frame_type, version)
|
234
|
-
):
|
235
|
-
# We don't have this frame or it's version has changed.
|
236
|
-
request = await Request.create(frame_type, recipient=self.address)
|
237
|
-
self.queue.put_nowait(request)
|
238
|
-
self._frame_versions[frame_type] = version
|
239
|
-
|
240
211
|
async def _add_burned_fuel_counter(self, fuel_consumption: float) -> None:
|
241
212
|
"""Calculate fuel burned since last sensor's data message."""
|
242
213
|
current_timestamp_ns = time.perf_counter_ns()
|
pyplumio/helpers/parameter.py
CHANGED
@@ -77,11 +77,19 @@ class ParameterDescription:
|
|
77
77
|
class Parameter(ABC):
|
78
78
|
"""Represents a base parameter."""
|
79
79
|
|
80
|
-
__slots__ = (
|
80
|
+
__slots__ = (
|
81
|
+
"device",
|
82
|
+
"description",
|
83
|
+
"_pending_update",
|
84
|
+
"_previous_value",
|
85
|
+
"_index",
|
86
|
+
"_values",
|
87
|
+
)
|
81
88
|
|
82
89
|
device: Device
|
83
90
|
description: ParameterDescription
|
84
91
|
_pending_update: bool
|
92
|
+
_previous_value: int
|
85
93
|
_index: int
|
86
94
|
_values: ParameterValues
|
87
95
|
|
@@ -96,6 +104,7 @@ class Parameter(ABC):
|
|
96
104
|
self.device = device
|
97
105
|
self.description = description
|
98
106
|
self._pending_update = False
|
107
|
+
self._previous_value = 0
|
99
108
|
self._index = index
|
100
109
|
self._values = values if values else ParameterValues(0, 0, 0)
|
101
110
|
|
@@ -185,6 +194,7 @@ class Parameter(ABC):
|
|
185
194
|
f"Value must be between '{self.min_value}' and '{self.max_value}'"
|
186
195
|
)
|
187
196
|
|
197
|
+
self._previous_value = self._values.value
|
188
198
|
self._values.value = value
|
189
199
|
self._pending_update = True
|
190
200
|
while self.pending_update:
|
@@ -196,6 +206,9 @@ class Parameter(ABC):
|
|
196
206
|
return False
|
197
207
|
|
198
208
|
await self.device.queue.put(await self.create_request())
|
209
|
+
if not self.is_tracking_changes:
|
210
|
+
await self.force_refresh()
|
211
|
+
|
199
212
|
await asyncio.sleep(timeout)
|
200
213
|
retries -= 1
|
201
214
|
|
@@ -203,8 +216,19 @@ class Parameter(ABC):
|
|
203
216
|
|
204
217
|
def update(self, values: ParameterValues) -> None:
|
205
218
|
"""Update the parameter values."""
|
219
|
+
if self.pending_update and self._previous_value != values.value:
|
220
|
+
self._pending_update = False
|
221
|
+
|
206
222
|
self._values = values
|
207
|
-
|
223
|
+
|
224
|
+
async def force_refresh(self) -> None:
|
225
|
+
"""Refresh the parameter from remote."""
|
226
|
+
await self.device.queue.put(await self.create_refresh_request())
|
227
|
+
|
228
|
+
@property
|
229
|
+
def is_tracking_changes(self) -> bool:
|
230
|
+
"""Return True if remote's tracking changes, False otherwise."""
|
231
|
+
return False
|
208
232
|
|
209
233
|
@property
|
210
234
|
def pending_update(self) -> bool:
|
@@ -254,6 +278,10 @@ class Parameter(ABC):
|
|
254
278
|
async def create_request(self) -> Request:
|
255
279
|
"""Create a request to change the parameter."""
|
256
280
|
|
281
|
+
@abstractmethod
|
282
|
+
async def create_refresh_request(self) -> Request:
|
283
|
+
"""Create a request to refresh the parameter."""
|
284
|
+
|
257
285
|
|
258
286
|
@dataslots
|
259
287
|
@dataclass
|
@@ -286,6 +314,10 @@ class Number(Parameter):
|
|
286
314
|
"""Create a request to change the number."""
|
287
315
|
return Request()
|
288
316
|
|
317
|
+
async def create_refresh_request(self) -> Request:
|
318
|
+
"""Create a request to refresh the number."""
|
319
|
+
return Request()
|
320
|
+
|
289
321
|
@property
|
290
322
|
def value(self) -> int | float:
|
291
323
|
"""Return the value."""
|
@@ -362,6 +394,10 @@ class Switch(Parameter):
|
|
362
394
|
"""Create a request to change the switch."""
|
363
395
|
return Request()
|
364
396
|
|
397
|
+
async def create_refresh_request(self) -> Request:
|
398
|
+
"""Create a request to refresh the switch."""
|
399
|
+
return Request()
|
400
|
+
|
365
401
|
@property
|
366
402
|
def value(self) -> Literal["off", "on"]:
|
367
403
|
"""Return the value."""
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
5
5
|
from collections.abc import Generator
|
6
6
|
from dataclasses import dataclass
|
7
7
|
from functools import partial
|
8
|
-
from typing import Any, Final
|
8
|
+
from typing import TYPE_CHECKING, Any, Final
|
9
9
|
|
10
10
|
from dataslots import dataslots
|
11
11
|
|
@@ -19,7 +19,6 @@ from pyplumio.const import (
|
|
19
19
|
ProductType,
|
20
20
|
UnitOfMeasurement,
|
21
21
|
)
|
22
|
-
from pyplumio.devices import PhysicalDevice
|
23
22
|
from pyplumio.frames import Request
|
24
23
|
from pyplumio.helpers.parameter import (
|
25
24
|
Number,
|
@@ -35,6 +34,9 @@ from pyplumio.structures import StructureDecoder
|
|
35
34
|
from pyplumio.structures.thermostat_parameters import ATTR_THERMOSTAT_PROFILE
|
36
35
|
from pyplumio.utils import ensure_dict
|
37
36
|
|
37
|
+
if TYPE_CHECKING:
|
38
|
+
from pyplumio.devices.ecomax import EcoMAX
|
39
|
+
|
38
40
|
ATTR_ECOMAX_CONTROL: Final = "ecomax_control"
|
39
41
|
ATTR_ECOMAX_PARAMETERS: Final = "ecomax_parameters"
|
40
42
|
|
@@ -53,7 +55,7 @@ class EcomaxParameter(Parameter):
|
|
53
55
|
|
54
56
|
__slots__ = ()
|
55
57
|
|
56
|
-
device:
|
58
|
+
device: EcoMAX
|
57
59
|
description: EcomaxParameterDescription
|
58
60
|
|
59
61
|
async def create_request(self) -> Request:
|
@@ -81,6 +83,17 @@ class EcomaxParameter(Parameter):
|
|
81
83
|
data={ATTR_INDEX: self._index, ATTR_VALUE: self.values.value},
|
82
84
|
)
|
83
85
|
|
86
|
+
async def create_refresh_request(self) -> Request:
|
87
|
+
"""Create a request to refresh the parameter."""
|
88
|
+
return await Request.create(
|
89
|
+
FrameType.REQUEST_ECOMAX_PARAMETERS, recipient=self.device.address
|
90
|
+
)
|
91
|
+
|
92
|
+
@property
|
93
|
+
def is_tracking_changes(self) -> bool:
|
94
|
+
"""Return True if remote's tracking changes, False otherwise."""
|
95
|
+
return self.device.has_frame_version(FrameType.REQUEST_ECOMAX_PARAMETERS)
|
96
|
+
|
84
97
|
|
85
98
|
@dataslots
|
86
99
|
@dataclass
|
@@ -65,6 +65,17 @@ class MixerParameter(Parameter):
|
|
65
65
|
},
|
66
66
|
)
|
67
67
|
|
68
|
+
async def create_refresh_request(self) -> Request:
|
69
|
+
"""Create a request to refresh the parameter."""
|
70
|
+
return await Request.create(
|
71
|
+
FrameType.REQUEST_MIXER_PARAMETERS, recipient=self.device.parent.address
|
72
|
+
)
|
73
|
+
|
74
|
+
@property
|
75
|
+
def is_tracking_changes(self) -> bool:
|
76
|
+
"""Return True if remote's tracking changes, False otherwise."""
|
77
|
+
return self.device.parent.has_frame_version(FrameType.REQUEST_MIXER_PARAMETERS)
|
78
|
+
|
68
79
|
|
69
80
|
@dataslots
|
70
81
|
@dataclass
|
pyplumio/structures/schedules.py
CHANGED
@@ -100,13 +100,24 @@ class ScheduleParameter(Parameter):
|
|
100
100
|
|
101
101
|
async def create_request(self) -> Request:
|
102
102
|
"""Create a request to change the parameter."""
|
103
|
-
schedule_name
|
103
|
+
schedule_name = self.description.name.split("_schedule_", 1)[0]
|
104
104
|
return await Request.create(
|
105
105
|
FrameType.REQUEST_SET_SCHEDULE,
|
106
106
|
recipient=self.device.address,
|
107
107
|
data=collect_schedule_data(schedule_name, self.device),
|
108
108
|
)
|
109
109
|
|
110
|
+
async def create_refresh_request(self) -> Request:
|
111
|
+
"""Create a request to refresh the parameter."""
|
112
|
+
return await Request.create(
|
113
|
+
FrameType.REQUEST_SCHEDULES, recipient=self.device.address
|
114
|
+
)
|
115
|
+
|
116
|
+
@property
|
117
|
+
def is_tracking_changes(self) -> bool:
|
118
|
+
"""Return True if remote's tracking changes, False otherwise."""
|
119
|
+
return self.device.has_frame_version(FrameType.REQUEST_SCHEDULES)
|
120
|
+
|
110
121
|
|
111
122
|
@dataslots
|
112
123
|
@dataclass
|
@@ -163,7 +174,7 @@ def _split_byte(byte: int) -> list[bool]:
|
|
163
174
|
|
164
175
|
def _join_bits(bits: Sequence[int | bool]) -> int:
|
165
176
|
"""Join eight bits into a single byte."""
|
166
|
-
return reduce(lambda
|
177
|
+
return reduce(lambda bit, byte: (bit << 1) | byte, bits)
|
167
178
|
|
168
179
|
|
169
180
|
class SchedulesStructure(Structure):
|
@@ -86,6 +86,20 @@ class ThermostatParameter(Parameter):
|
|
86
86
|
},
|
87
87
|
)
|
88
88
|
|
89
|
+
async def create_refresh_request(self) -> Request:
|
90
|
+
"""Create a request to refresh the parameter."""
|
91
|
+
return await Request.create(
|
92
|
+
FrameType.REQUEST_THERMOSTAT_PARAMETERS,
|
93
|
+
recipient=self.device.parent.address,
|
94
|
+
)
|
95
|
+
|
96
|
+
@property
|
97
|
+
def is_tracking_changes(self) -> bool:
|
98
|
+
"""Return True if remote's tracking changes, False otherwise."""
|
99
|
+
return self.device.parent.has_frame_version(
|
100
|
+
FrameType.REQUEST_THERMOSTAT_PARAMETERS
|
101
|
+
)
|
102
|
+
|
89
103
|
|
90
104
|
@dataslots
|
91
105
|
@dataclass
|
@@ -113,25 +127,20 @@ class ThermostatNumber(ThermostatParameter, Number):
|
|
113
127
|
@property
|
114
128
|
def value(self) -> float:
|
115
129
|
"""Return the value."""
|
116
|
-
|
117
|
-
|
118
|
-
)
|
130
|
+
value = self.values.value * self.description.multiplier
|
131
|
+
return round(value, self.description.precision)
|
119
132
|
|
120
133
|
@property
|
121
134
|
def min_value(self) -> float:
|
122
135
|
"""Return the minimum allowed value."""
|
123
|
-
|
124
|
-
|
125
|
-
self.description.precision,
|
126
|
-
)
|
136
|
+
value = self.values.min_value * self.description.multiplier
|
137
|
+
return round(value, self.description.precision)
|
127
138
|
|
128
139
|
@property
|
129
140
|
def max_value(self) -> float:
|
130
141
|
"""Return the maximum allowed value."""
|
131
|
-
|
132
|
-
|
133
|
-
self.description.precision,
|
134
|
-
)
|
142
|
+
value = self.values.max_value * self.description.multiplier
|
143
|
+
return round(value, self.description.precision)
|
135
144
|
|
136
145
|
|
137
146
|
@dataslots
|
File without changes
|
File without changes
|