PyPlumIO 0.5.18__py3-none-any.whl → 0.5.19__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.18.dist-info → PyPlumIO-0.5.19.dist-info}/METADATA +8 -8
- PyPlumIO-0.5.19.dist-info/RECORD +61 -0
- pyplumio/__init__.py +1 -0
- pyplumio/__main__.py +1 -0
- pyplumio/_version.py +2 -2
- pyplumio/connection.py +1 -0
- pyplumio/const.py +1 -0
- pyplumio/devices/__init__.py +25 -19
- pyplumio/devices/ecomax.py +3 -11
- pyplumio/devices/ecoster.py +1 -0
- pyplumio/devices/mixer.py +1 -0
- pyplumio/devices/thermostat.py +1 -0
- pyplumio/exceptions.py +1 -0
- pyplumio/filters.py +8 -10
- pyplumio/frames/__init__.py +35 -28
- pyplumio/frames/messages.py +1 -0
- pyplumio/frames/requests.py +1 -0
- pyplumio/frames/responses.py +1 -0
- pyplumio/helpers/data_types.py +1 -0
- pyplumio/helpers/event_manager.py +1 -0
- pyplumio/helpers/factory.py +10 -3
- pyplumio/helpers/parameter.py +1 -0
- pyplumio/helpers/schedule.py +11 -7
- pyplumio/helpers/task_manager.py +1 -0
- pyplumio/helpers/timeout.py +1 -0
- pyplumio/helpers/typing.py +1 -0
- pyplumio/helpers/uid.py +1 -0
- pyplumio/protocol.py +44 -54
- pyplumio/stream.py +16 -12
- pyplumio/structures/__init__.py +1 -0
- pyplumio/structures/alerts.py +1 -0
- pyplumio/structures/boiler_load.py +1 -0
- pyplumio/structures/boiler_power.py +1 -0
- pyplumio/structures/ecomax_parameters.py +8 -8
- pyplumio/structures/fan_power.py +1 -0
- pyplumio/structures/frame_versions.py +1 -0
- pyplumio/structures/fuel_consumption.py +1 -0
- pyplumio/structures/fuel_level.py +1 -0
- pyplumio/structures/lambda_sensor.py +1 -0
- pyplumio/structures/mixer_parameters.py +11 -13
- pyplumio/structures/mixer_sensors.py +1 -0
- pyplumio/structures/modules.py +1 -0
- pyplumio/structures/network_info.py +1 -0
- pyplumio/structures/output_flags.py +1 -0
- pyplumio/structures/outputs.py +1 -0
- pyplumio/structures/pending_alerts.py +1 -0
- pyplumio/structures/product_info.py +1 -0
- pyplumio/structures/program_version.py +3 -2
- pyplumio/structures/regulator_data.py +3 -4
- pyplumio/structures/regulator_data_schema.py +1 -0
- pyplumio/structures/schedules.py +14 -11
- pyplumio/structures/statuses.py +1 -0
- pyplumio/structures/temperatures.py +1 -0
- pyplumio/structures/thermostat_parameters.py +17 -22
- pyplumio/structures/thermostat_sensors.py +1 -0
- pyplumio/utils.py +1 -0
- PyPlumIO-0.5.18.dist-info/RECORD +0 -61
- {PyPlumIO-0.5.18.dist-info → PyPlumIO-0.5.19.dist-info}/LICENSE +0 -0
- {PyPlumIO-0.5.18.dist-info → PyPlumIO-0.5.19.dist-info}/WHEEL +0 -0
- {PyPlumIO-0.5.18.dist-info → PyPlumIO-0.5.19.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.19
|
4
4
|
Summary: PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
|
5
5
|
Author-email: Denis Paavilainen <denpa@denpa.pro>
|
6
6
|
License: MIT License
|
@@ -25,22 +25,22 @@ License-File: LICENSE
|
|
25
25
|
Requires-Dist: pyserial-asyncio ==0.6
|
26
26
|
Provides-Extra: dev
|
27
27
|
Requires-Dist: pyplumio[docs,test] ; extra == 'dev'
|
28
|
-
Requires-Dist: pre-commit ==3.7.
|
28
|
+
Requires-Dist: pre-commit ==3.7.1 ; extra == 'dev'
|
29
29
|
Requires-Dist: tomli ==2.0.1 ; extra == 'dev'
|
30
30
|
Provides-Extra: docs
|
31
31
|
Requires-Dist: sphinx ==7.3.7 ; extra == 'docs'
|
32
32
|
Requires-Dist: sphinx-rtd-theme ==2.0.0 ; extra == 'docs'
|
33
33
|
Requires-Dist: readthedocs-sphinx-search ==0.3.2 ; extra == 'docs'
|
34
34
|
Provides-Extra: test
|
35
|
-
Requires-Dist: codespell ==2.
|
36
|
-
Requires-Dist: coverage ==7.5.
|
35
|
+
Requires-Dist: codespell ==2.3.0 ; extra == 'test'
|
36
|
+
Requires-Dist: coverage ==7.5.3 ; extra == 'test'
|
37
37
|
Requires-Dist: mypy ==1.10.0 ; extra == 'test'
|
38
38
|
Requires-Dist: pyserial-asyncio-fast ==0.11 ; extra == 'test'
|
39
|
-
Requires-Dist: pytest ==8.2.
|
40
|
-
Requires-Dist: pytest-asyncio ==0.23.
|
41
|
-
Requires-Dist: ruff ==0.4.
|
39
|
+
Requires-Dist: pytest ==8.2.1 ; extra == 'test'
|
40
|
+
Requires-Dist: pytest-asyncio ==0.23.7 ; extra == 'test'
|
41
|
+
Requires-Dist: ruff ==0.4.5 ; extra == 'test'
|
42
42
|
Requires-Dist: tox ==4.15.0 ; extra == 'test'
|
43
|
-
Requires-Dist: types-pyserial ==3.5.0.
|
43
|
+
Requires-Dist: types-pyserial ==3.5.0.20240527 ; extra == 'test'
|
44
44
|
|
45
45
|
# PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
|
46
46
|
[](https://badge.fury.io/py/PyPlumIO)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
pyplumio/__init__.py,sha256=cclyAwy7OsW673iHcwkVrJSNnf32oF51Y_0uEEF5cdI,3293
|
2
|
+
pyplumio/__main__.py,sha256=3IwHHSq-iay5FaeMc95klobe-xv82yydSKcBE7BFZ6M,500
|
3
|
+
pyplumio/_version.py,sha256=aaNQTfdqRF8htFsemknyCzmnRLGw2JWETdmpWppxxVQ,413
|
4
|
+
pyplumio/connection.py,sha256=IFSIUyMj8YkXW7TFP23nNGjs8QoZafgCPYmxaQpvads,6200
|
5
|
+
pyplumio/const.py,sha256=8rpiVbVb5R_6Rm6J2sgCnaVrkD-2Fzhd1RYMz0MBgwo,3915
|
6
|
+
pyplumio/exceptions.py,sha256=193z3zfnswYhIYPzCIpxCiWat4qI3cV85sqT4YOSo-4,699
|
7
|
+
pyplumio/filters.py,sha256=bIonYc_QbGMsL8aWweSLUmP7gKqDD646zELf_PqqQBg,11161
|
8
|
+
pyplumio/protocol.py,sha256=y4JRN5hQVtxMt_y0XwqdSVd_tK_vzPgW8Zje-UunyW0,8184
|
9
|
+
pyplumio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
pyplumio/stream.py,sha256=lKffNkI__oK3kS3_K0m4nyINm_7-sSv7xbu3xleyhJg,4486
|
11
|
+
pyplumio/utils.py,sha256=GV7P1hPLoQsx3uqYviQ15FXJmkmTxwtDibAc-yRarvo,688
|
12
|
+
pyplumio/devices/__init__.py,sha256=MXNPnIcIykE6zrqh9yaP7vjMf_ysyrHauDerDNkInNQ,6502
|
13
|
+
pyplumio/devices/ecomax.py,sha256=W9YW4nw6v2wdKKqFblUx03_hFJw0dVjnVkDHNL8P2dg,16632
|
14
|
+
pyplumio/devices/ecoster.py,sha256=J4YtPmFmFwaq4LzYf28aMmB97cRAbMsVyUdBLGki42g,313
|
15
|
+
pyplumio/devices/mixer.py,sha256=PGk0lXveN6q5sm0B50YG0yCVYoKi1a9TA3v44bGgi3A,2947
|
16
|
+
pyplumio/devices/thermostat.py,sha256=UR3KEkIBU-zx2LALvrt_QiFkiBT1CpKk7vYR1MSYdWY,2402
|
17
|
+
pyplumio/frames/__init__.py,sha256=BAMbMHbn4F9psrf3sv0eJQA2Jd86qf7LQ5vBQY59gjA,7462
|
18
|
+
pyplumio/frames/messages.py,sha256=QLuvo1wlpDZR1MpOdu7s6fRUX20Dtt6EWFLkAsqyax4,3617
|
19
|
+
pyplumio/frames/requests.py,sha256=Ra8xH5oKYhkEUtadN-9ZsJKkt5xZkz5O7edQVsDhNsM,7221
|
20
|
+
pyplumio/frames/responses.py,sha256=j4awA2-MfsoPdENC4Fvae4_Oa70rDhH19ebmEoAqhh8,6532
|
21
|
+
pyplumio/helpers/__init__.py,sha256=H2xxdkF-9uADLwEbfBUoxNTdwru3L5Z2cfJjgsuRsn0,31
|
22
|
+
pyplumio/helpers/data_types.py,sha256=H_pYkLgIu30lDFU0UUZ1V3vYxa9A_-1nhiJu-HCLuoc,8212
|
23
|
+
pyplumio/helpers/event_manager.py,sha256=EJLNykwgTSl_RRE2ulGny3Matr_FSi73rKrdLXIX6ss,6005
|
24
|
+
pyplumio/helpers/factory.py,sha256=eiTkYUCernUn0VNDDdEN4IyjNPrXK8vnJESXyLaqFzE,1017
|
25
|
+
pyplumio/helpers/parameter.py,sha256=gYCA2SLU_lbdtQZq5U64yzpyLoEIa0R1wyJJGmgL63I,8699
|
26
|
+
pyplumio/helpers/schedule.py,sha256=-IZJ-CU4PhFlsE586wTw--ovDrTo2Hs4JneCHhc0e-Y,5013
|
27
|
+
pyplumio/helpers/task_manager.py,sha256=RpdYSguc0cap_Onf9VnL-yCd_KwR2JPD49trZCRKPpI,1090
|
28
|
+
pyplumio/helpers/timeout.py,sha256=ou_pgExP5HqhrOdqEiqadVnRnelPd-N69VIIrDVq-aA,1287
|
29
|
+
pyplumio/helpers/typing.py,sha256=y55UdpIpPIRuUBPgfPmZHAwPdIUjQO924-kO7AVXhes,685
|
30
|
+
pyplumio/helpers/uid.py,sha256=yaBjcsFKuhOaznftk33kdIepQHpK-labEQr59QNKhPM,975
|
31
|
+
pyplumio/structures/__init__.py,sha256=EjK-5qJZ0F7lpP2b6epvTMg9cIBl4Kn91nqNkEcLwTc,1299
|
32
|
+
pyplumio/structures/alerts.py,sha256=a1CIf8vSEj5aefdqECIfCY5kV4tQ4kabMkp-_ixeWic,3260
|
33
|
+
pyplumio/structures/boiler_load.py,sha256=p3mOzZUU-g7A2tG_yp8podEqpI81hlsOZmHELyPNRY8,838
|
34
|
+
pyplumio/structures/boiler_power.py,sha256=72qsvccg49FdRdXv2f2K5sGpjT7wAOLFjlIGWpO-DVg,901
|
35
|
+
pyplumio/structures/ecomax_parameters.py,sha256=07v97o9paqj_Ua9HkmEvwPgDT6a9btd8PKVSpVcTC4c,25835
|
36
|
+
pyplumio/structures/fan_power.py,sha256=Q5fv-7_2NVuLeQPIVIylvgN7M8-a9D8rRUE0QGjyS3w,871
|
37
|
+
pyplumio/structures/frame_versions.py,sha256=x_OSirGYopQYgsRZIM3b1YlKHNIPmCbvAzhzO1wqy5k,1560
|
38
|
+
pyplumio/structures/fuel_consumption.py,sha256=_p2dI4H67Eopn7IF0Gj77A8c_8lNKhhDDAtmugxLd4s,976
|
39
|
+
pyplumio/structures/fuel_level.py,sha256=mJpp1dnRD1wXi_6EyNX7TNXosjcr905rSHOnuZ5VD74,1069
|
40
|
+
pyplumio/structures/lambda_sensor.py,sha256=6iUVyrPe6_QaGPo1lRzOfqorcTIIXRwnq3h861IJYGs,1587
|
41
|
+
pyplumio/structures/mixer_parameters.py,sha256=ny7Ox94IooQd1ua22zGYkXLFaZQWGUYLEIM2_8vXk0U,8249
|
42
|
+
pyplumio/structures/mixer_sensors.py,sha256=O91929Ts1YXFmKdPRc1r_BYDgrqkv5QVtE1nGzLpuAI,2260
|
43
|
+
pyplumio/structures/modules.py,sha256=ukju4TQmRRJfgl94QU4zytZLU5px8nw3sgfSLn9JysU,2520
|
44
|
+
pyplumio/structures/network_info.py,sha256=d1_MvG6n5f57ZEVZwSFFTXux0N07Gt1KnPDtlDfoMNQ,4055
|
45
|
+
pyplumio/structures/output_flags.py,sha256=07N0kxlvR5WZAURuChk_BqSiXR8eaQrtI5qlkgCf4Yc,1345
|
46
|
+
pyplumio/structures/outputs.py,sha256=1xsJPkjN643-aFawqVoupGatUIUJfQG_g252n051Qi0,1916
|
47
|
+
pyplumio/structures/pending_alerts.py,sha256=Uq9WpB4MW9AhDkqmDhk-g0J0h4pVq0Q50z12dYEv6kY,739
|
48
|
+
pyplumio/structures/product_info.py,sha256=bVH7NvIOWwmmHcgbLfD-IIag4sgBRA6RMmuC6SKTrJE,2409
|
49
|
+
pyplumio/structures/program_version.py,sha256=4h3u46l2btUcooDRHyrqN3Y9M8WNEzwCmkNVEzfi-V8,2359
|
50
|
+
pyplumio/structures/regulator_data.py,sha256=Dun3RjfHHoV2W5RTSQcAimBL0Or3O957vYQj7Pbi7CM,2309
|
51
|
+
pyplumio/structures/regulator_data_schema.py,sha256=BMshEpiP-lwTgSkbTuow9KlxCwKwQXV0nFPcBpW0SJg,1505
|
52
|
+
pyplumio/structures/schedules.py,sha256=-koo05nLkpKuj1ZPiC1NB_21MAFn1FzQ6VLC0DboYeg,6346
|
53
|
+
pyplumio/structures/statuses.py,sha256=wkoynyMRr1VREwfBC6vU48kPA8ZQ83pcXuciy2xHJrk,1166
|
54
|
+
pyplumio/structures/temperatures.py,sha256=1CDzehNmbALz1Jyt_9gZNIk52q6Wv-xQXjijVDCVYec,2337
|
55
|
+
pyplumio/structures/thermostat_parameters.py,sha256=pjbWsT6z7mlDiUrC5MWGqMtGP0deeVMYeeTa7yGEwJ8,7706
|
56
|
+
pyplumio/structures/thermostat_sensors.py,sha256=ZmjWgYtTZ5M8Lnz_Q5N4JD8G3MvEmByPFjYsy6XZOmo,3177
|
57
|
+
PyPlumIO-0.5.19.dist-info/LICENSE,sha256=m-UuZFjXJ22uPTGm9kSHS8bqjsf5T8k2wL9bJn1Y04o,1088
|
58
|
+
PyPlumIO-0.5.19.dist-info/METADATA,sha256=7Vbk_OIL0hieiiT0U5LFjYi7dAJkzwAXrxeVnZmG_-A,5415
|
59
|
+
PyPlumIO-0.5.19.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
60
|
+
PyPlumIO-0.5.19.dist-info/top_level.txt,sha256=kNBz9UPPkPD9teDn3U_sEy5LjzwLm9KfADCXtBlbw8A,9
|
61
|
+
PyPlumIO-0.5.19.dist-info/RECORD,,
|
pyplumio/__init__.py
CHANGED
pyplumio/__main__.py
CHANGED
pyplumio/_version.py
CHANGED
pyplumio/connection.py
CHANGED
pyplumio/const.py
CHANGED
pyplumio/devices/__init__.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Contains device classes."""
|
2
|
+
|
2
3
|
from __future__ import annotations
|
3
4
|
|
4
5
|
from abc import ABC
|
@@ -9,7 +10,7 @@ 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
|
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 SET_RETRIES, Parameter
|
@@ -18,28 +19,29 @@ from pyplumio.structures.network_info import NetworkInfo
|
|
18
19
|
from pyplumio.utils import to_camelcase
|
19
20
|
|
20
21
|
|
22
|
+
@cache
|
23
|
+
def is_known_device_type(device_type: int) -> bool:
|
24
|
+
"""Check if device type is known."""
|
25
|
+
try:
|
26
|
+
DeviceType(device_type)
|
27
|
+
return True
|
28
|
+
except ValueError:
|
29
|
+
return False
|
30
|
+
|
31
|
+
|
21
32
|
@cache
|
22
33
|
def get_device_handler(device_type: int) -> str:
|
23
34
|
"""Return module name and class name for a given device type."""
|
24
|
-
|
25
|
-
|
26
|
-
except ValueError as e:
|
27
|
-
raise UnknownDeviceError(f"Unknown device ({device_type})") from e
|
35
|
+
if not is_known_device_type(device_type):
|
36
|
+
raise UnknownDeviceError(f"Unknown device type ({device_type})")
|
28
37
|
|
29
38
|
type_name = to_camelcase(
|
30
|
-
device_type.name,
|
39
|
+
DeviceType(device_type).name,
|
40
|
+
overrides={"ecomax": "EcoMAX", "ecoster": "EcoSTER"},
|
31
41
|
)
|
32
42
|
return f"devices.{type_name.lower()}.{type_name}"
|
33
43
|
|
34
44
|
|
35
|
-
@cache
|
36
|
-
def get_device_handler_and_name(device_type: int) -> tuple[str, str]:
|
37
|
-
"""Get device handler full path and lowercased class name."""
|
38
|
-
handler = get_device_handler(device_type)
|
39
|
-
class_name = handler.rsplit(".", 1)[1]
|
40
|
-
return handler, class_name.lower()
|
41
|
-
|
42
|
-
|
43
45
|
class Device(ABC, EventManager):
|
44
46
|
"""Represents a device."""
|
45
47
|
|
@@ -128,7 +130,7 @@ class AddressableDevice(Device, ABC):
|
|
128
130
|
|
129
131
|
def handle_frame(self, frame: Frame) -> None:
|
130
132
|
"""Handle frame received from the device."""
|
131
|
-
frame.
|
133
|
+
frame.sender_device = self
|
132
134
|
if frame.data is not None:
|
133
135
|
for name, value in frame.data.items():
|
134
136
|
self.dispatch_nowait(name, value)
|
@@ -160,10 +162,7 @@ class AddressableDevice(Device, ABC):
|
|
160
162
|
|
161
163
|
If value is not available before timeout, retry request.
|
162
164
|
"""
|
163
|
-
request
|
164
|
-
get_frame_handler(frame_type), recipient=self.address
|
165
|
-
)
|
166
|
-
|
165
|
+
request = await Request.create(frame_type, recipient=self.address)
|
167
166
|
while retries > 0:
|
168
167
|
try:
|
169
168
|
self.queue.put_nowait(request)
|
@@ -173,6 +172,13 @@ class AddressableDevice(Device, ABC):
|
|
173
172
|
|
174
173
|
raise ValueError(f'could not request "{name}"', frame_type)
|
175
174
|
|
175
|
+
@classmethod
|
176
|
+
async def create(cls, device_type: int, **kwargs: Any) -> AddressableDevice:
|
177
|
+
"""Create a device handler object."""
|
178
|
+
return await create_instance(
|
179
|
+
get_device_handler(device_type), cls=AddressableDevice, **kwargs
|
180
|
+
)
|
181
|
+
|
176
182
|
|
177
183
|
class SubDevice(Device, ABC):
|
178
184
|
"""Represents a sub-device."""
|
pyplumio/devices/ecomax.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Contains an ecoMAX class."""
|
2
|
+
|
2
3
|
from __future__ import annotations
|
3
4
|
|
4
5
|
import asyncio
|
@@ -21,14 +22,7 @@ from pyplumio.devices import AddressableDevice
|
|
21
22
|
from pyplumio.devices.mixer import Mixer
|
22
23
|
from pyplumio.devices.thermostat import Thermostat
|
23
24
|
from pyplumio.filters import on_change
|
24
|
-
from pyplumio.frames import
|
25
|
-
DataFrameDescription,
|
26
|
-
Frame,
|
27
|
-
Request,
|
28
|
-
get_frame_handler,
|
29
|
-
is_known_frame_type,
|
30
|
-
)
|
31
|
-
from pyplumio.helpers.factory import create_instance
|
25
|
+
from pyplumio.frames import DataFrameDescription, Frame, Request, is_known_frame_type
|
32
26
|
from pyplumio.helpers.parameter import ParameterValues
|
33
27
|
from pyplumio.helpers.schedule import Schedule, ScheduleDay
|
34
28
|
from pyplumio.structures.alerts import ATTR_TOTAL_ALERTS
|
@@ -235,9 +229,7 @@ class EcoMAX(AddressableDevice):
|
|
235
229
|
and not self._has_frame_version(frame_type, version)
|
236
230
|
):
|
237
231
|
# We don't have this frame or it's version has changed.
|
238
|
-
request
|
239
|
-
get_frame_handler(frame_type), recipient=self.address
|
240
|
-
)
|
232
|
+
request = await Request.create(frame_type, recipient=self.address)
|
241
233
|
self.queue.put_nowait(request)
|
242
234
|
self._frame_versions[frame_type] = version
|
243
235
|
|
pyplumio/devices/ecoster.py
CHANGED
pyplumio/devices/mixer.py
CHANGED
pyplumio/devices/thermostat.py
CHANGED
pyplumio/exceptions.py
CHANGED
pyplumio/filters.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Contains callback filters."""
|
2
|
+
|
2
3
|
from __future__ import annotations
|
3
4
|
|
4
5
|
from abc import ABC, abstractmethod
|
@@ -18,18 +19,17 @@ Comparable = TypeVar("Comparable", Parameter, SupportsFloat, SupportsComparison)
|
|
18
19
|
|
19
20
|
|
20
21
|
@overload
|
21
|
-
def _significantly_changed(old: Parameter, new: Parameter) -> bool:
|
22
|
-
...
|
22
|
+
def _significantly_changed(old: Parameter, new: Parameter) -> bool: ...
|
23
23
|
|
24
24
|
|
25
25
|
@overload
|
26
|
-
def _significantly_changed(old: SupportsFloat, new: SupportsFloat) -> bool:
|
27
|
-
...
|
26
|
+
def _significantly_changed(old: SupportsFloat, new: SupportsFloat) -> bool: ...
|
28
27
|
|
29
28
|
|
30
29
|
@overload
|
31
|
-
def _significantly_changed(
|
32
|
-
|
30
|
+
def _significantly_changed(
|
31
|
+
old: SupportsComparison, new: SupportsComparison
|
32
|
+
) -> bool: ...
|
33
33
|
|
34
34
|
|
35
35
|
def _significantly_changed(old: Comparable, new: Comparable) -> bool:
|
@@ -45,15 +45,13 @@ def _significantly_changed(old: Comparable, new: Comparable) -> bool:
|
|
45
45
|
|
46
46
|
|
47
47
|
@overload
|
48
|
-
def _diffence_between(old: list, new: list) -> list:
|
49
|
-
...
|
48
|
+
def _diffence_between(old: list, new: list) -> list: ...
|
50
49
|
|
51
50
|
|
52
51
|
@overload
|
53
52
|
def _diffence_between(
|
54
53
|
old: SupportsSubtraction, new: SupportsSubtraction
|
55
|
-
) -> SupportsSubtraction:
|
56
|
-
...
|
54
|
+
) -> SupportsSubtraction: ...
|
57
55
|
|
58
56
|
|
59
57
|
def _diffence_between(
|
pyplumio/frames/__init__.py
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
"""Contains frame classes."""
|
2
|
+
|
2
3
|
from __future__ import annotations
|
3
4
|
|
4
5
|
from abc import ABC, abstractmethod
|
5
6
|
from dataclasses import dataclass
|
6
7
|
from functools import cache, reduce
|
7
8
|
import struct
|
8
|
-
from typing import TYPE_CHECKING, Any, ClassVar, Final,
|
9
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Final, TypeVar
|
9
10
|
|
10
11
|
from pyplumio.const import DeviceType, FrameType
|
11
12
|
from pyplumio.exceptions import UnknownFrameError
|
13
|
+
from pyplumio.helpers.factory import create_instance
|
12
14
|
from pyplumio.utils import ensure_dict, to_camelcase
|
13
15
|
|
14
16
|
FRAME_START: Final = 0x68
|
@@ -27,11 +29,15 @@ if TYPE_CHECKING:
|
|
27
29
|
from pyplumio.devices import AddressableDevice
|
28
30
|
|
29
31
|
|
32
|
+
T = TypeVar("T")
|
33
|
+
|
34
|
+
|
30
35
|
def bcc(data: bytes) -> int:
|
31
36
|
"""Return a block check character."""
|
32
37
|
return reduce(lambda x, y: x ^ y, data)
|
33
38
|
|
34
39
|
|
40
|
+
@cache
|
35
41
|
def is_known_frame_type(frame_type: int) -> bool:
|
36
42
|
"""Check if frame type is known."""
|
37
43
|
try:
|
@@ -44,12 +50,10 @@ def is_known_frame_type(frame_type: int) -> bool:
|
|
44
50
|
@cache
|
45
51
|
def get_frame_handler(frame_type: int) -> str:
|
46
52
|
"""Return handler class path for the frame type."""
|
47
|
-
|
48
|
-
|
49
|
-
except ValueError as e:
|
50
|
-
raise UnknownFrameError(f"Unknown frame ({frame_type})") from e
|
53
|
+
if not is_known_frame_type(frame_type):
|
54
|
+
raise UnknownFrameError(f"Unknown frame type ({frame_type})")
|
51
55
|
|
52
|
-
module, type_name = frame_type.name.split("_", 1)
|
56
|
+
module, type_name = FrameType(frame_type).name.split("_", 1)
|
53
57
|
type_name = to_camelcase(type_name, overrides={"uid": "UID"})
|
54
58
|
return f"frames.{module.lower()}s.{type_name}{module.capitalize()}"
|
55
59
|
|
@@ -69,16 +73,20 @@ class Frame(ABC):
|
|
69
73
|
|
70
74
|
__slots__ = (
|
71
75
|
"recipient",
|
76
|
+
"recipient_device",
|
72
77
|
"sender",
|
73
|
-
"
|
78
|
+
"sender_device",
|
79
|
+
"econet_type",
|
74
80
|
"econet_version",
|
75
81
|
"_message",
|
76
82
|
"_data",
|
77
83
|
)
|
78
84
|
|
79
|
-
recipient: DeviceType
|
80
|
-
|
81
|
-
|
85
|
+
recipient: DeviceType
|
86
|
+
recipient_device: AddressableDevice | None
|
87
|
+
sender: DeviceType
|
88
|
+
sender_device: AddressableDevice | None
|
89
|
+
econet_type: int
|
82
90
|
econet_version: int
|
83
91
|
frame_type: ClassVar[FrameType]
|
84
92
|
_message: bytearray | None
|
@@ -86,26 +94,20 @@ class Frame(ABC):
|
|
86
94
|
|
87
95
|
def __init__(
|
88
96
|
self,
|
89
|
-
recipient: DeviceType
|
90
|
-
sender: DeviceType
|
91
|
-
|
97
|
+
recipient: DeviceType = DeviceType.ALL,
|
98
|
+
sender: DeviceType = DeviceType.ECONET,
|
99
|
+
econet_type: int = ECONET_TYPE,
|
92
100
|
econet_version: int = ECONET_VERSION,
|
93
101
|
message: bytearray | None = None,
|
94
102
|
data: dict[str, Any] | None = None,
|
95
103
|
**kwargs: Any,
|
96
104
|
) -> None:
|
97
105
|
"""Process a frame data and message."""
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
try:
|
104
|
-
self.sender = DeviceType(cast(int, sender))
|
105
|
-
except ValueError:
|
106
|
-
self.sender = sender
|
107
|
-
|
108
|
-
self.sender_type = sender_type
|
106
|
+
self.recipient = recipient
|
107
|
+
self.recipient_device = None
|
108
|
+
self.sender = sender
|
109
|
+
self.sender_device = None
|
110
|
+
self.econet_type = econet_type
|
109
111
|
self.econet_version = econet_version
|
110
112
|
self._data = data if not kwargs else ensure_dict(data, kwargs)
|
111
113
|
self._message = message
|
@@ -116,14 +118,14 @@ class Frame(ABC):
|
|
116
118
|
return (
|
117
119
|
self.recipient,
|
118
120
|
self.sender,
|
119
|
-
self.
|
121
|
+
self.econet_type,
|
120
122
|
self.econet_version,
|
121
123
|
self._message,
|
122
124
|
self._data,
|
123
125
|
) == (
|
124
126
|
self.recipient,
|
125
127
|
self.sender,
|
126
|
-
self.
|
128
|
+
self.econet_type,
|
127
129
|
self.econet_version,
|
128
130
|
self._message,
|
129
131
|
self._data,
|
@@ -137,7 +139,7 @@ class Frame(ABC):
|
|
137
139
|
f"{self.__class__.__name__}("
|
138
140
|
f"recipient={repr(self.recipient)}, "
|
139
141
|
f"sender={repr(self.sender)}, "
|
140
|
-
f"
|
142
|
+
f"econet_type={self.econet_type}, "
|
141
143
|
f"econet_version={self.econet_version}, "
|
142
144
|
f"message={self.message}, "
|
143
145
|
f"data={self.data})"
|
@@ -205,7 +207,7 @@ class Frame(ABC):
|
|
205
207
|
self.length,
|
206
208
|
int(self.recipient),
|
207
209
|
int(self.sender),
|
208
|
-
self.
|
210
|
+
self.econet_type,
|
209
211
|
self.econet_version,
|
210
212
|
)
|
211
213
|
|
@@ -221,6 +223,11 @@ class Frame(ABC):
|
|
221
223
|
data.append(FRAME_END)
|
222
224
|
return bytes(data)
|
223
225
|
|
226
|
+
@classmethod
|
227
|
+
async def create(cls: type[T], frame_type: int, **kwargs: Any) -> T:
|
228
|
+
"""Create a frame handler object from frame type."""
|
229
|
+
return await create_instance(get_frame_handler(frame_type), cls=cls, **kwargs)
|
230
|
+
|
224
231
|
@abstractmethod
|
225
232
|
def create_message(self, data: dict[str, Any]) -> bytearray:
|
226
233
|
"""Create frame message."""
|
pyplumio/frames/messages.py
CHANGED
pyplumio/frames/requests.py
CHANGED
pyplumio/frames/responses.py
CHANGED
pyplumio/helpers/data_types.py
CHANGED
pyplumio/helpers/factory.py
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
"""Contains a factory helper."""
|
2
|
+
|
2
3
|
from __future__ import annotations
|
3
4
|
|
4
5
|
import asyncio
|
5
6
|
import importlib
|
6
7
|
import logging
|
7
8
|
from types import ModuleType
|
8
|
-
from typing import Any
|
9
|
+
from typing import Any, TypeVar
|
9
10
|
|
10
11
|
_LOGGER = logging.getLogger(__name__)
|
11
12
|
|
13
|
+
T = TypeVar("T")
|
14
|
+
|
12
15
|
|
13
16
|
async def _load_module(module_name: str) -> ModuleType:
|
14
17
|
"""Load a module by name."""
|
@@ -17,12 +20,16 @@ async def _load_module(module_name: str) -> ModuleType:
|
|
17
20
|
)
|
18
21
|
|
19
22
|
|
20
|
-
async def create_instance(class_path: str, **kwargs: Any) ->
|
23
|
+
async def create_instance(class_path: str, cls: type[T], **kwargs: Any) -> T:
|
21
24
|
"""Return a class instance from the class path."""
|
22
25
|
module_name, class_name = class_path.rsplit(".", 1)
|
23
26
|
try:
|
24
27
|
module = await _load_module(module_name)
|
25
|
-
|
28
|
+
instance = getattr(module, class_name)(**kwargs)
|
29
|
+
if not isinstance(instance, cls):
|
30
|
+
raise TypeError(f"class '{class_name}' should be derived from {cls}")
|
31
|
+
|
32
|
+
return instance
|
26
33
|
except Exception:
|
27
34
|
_LOGGER.error("Failed to load module (%s)", class_path)
|
28
35
|
raise
|
pyplumio/helpers/parameter.py
CHANGED
pyplumio/helpers/schedule.py
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
"""Contains a schedule helper classes."""
|
2
|
+
|
2
3
|
from __future__ import annotations
|
3
4
|
|
4
5
|
from collections.abc import Iterable, Iterator, MutableMapping
|
5
6
|
from dataclasses import dataclass
|
6
7
|
import datetime as dt
|
8
|
+
from functools import lru_cache
|
7
9
|
import math
|
8
10
|
from typing import Final, Literal
|
9
11
|
|
10
|
-
from pyplumio.const import STATE_OFF, STATE_ON
|
12
|
+
from pyplumio.const import STATE_OFF, STATE_ON, FrameType
|
11
13
|
from pyplumio.devices import AddressableDevice
|
12
|
-
from pyplumio.
|
14
|
+
from pyplumio.frames import Request
|
13
15
|
from pyplumio.structures.schedules import collect_schedule_data
|
14
16
|
|
15
17
|
TIME_FORMAT: Final = "%H:%M"
|
@@ -24,6 +26,7 @@ OFF_STATES: Final = (STATE_OFF, STATE_NIGHT)
|
|
24
26
|
ALLOWED_STATES: Final = ON_STATES + OFF_STATES
|
25
27
|
|
26
28
|
|
29
|
+
@lru_cache(maxsize=10)
|
27
30
|
def _parse_interval(start: str, end: str) -> tuple[int, int]:
|
28
31
|
"""Parse an interval string.
|
29
32
|
|
@@ -160,9 +163,10 @@ class Schedule(Iterable):
|
|
160
163
|
|
161
164
|
async def commit(self) -> None:
|
162
165
|
"""Commit a weekly schedule to the device."""
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
166
|
+
await self.device.queue.put(
|
167
|
+
await Request.create(
|
168
|
+
FrameType.REQUEST_SET_SCHEDULE,
|
169
|
+
recipient=self.device.address,
|
170
|
+
data=collect_schedule_data(self.name, self.device),
|
171
|
+
)
|
167
172
|
)
|
168
|
-
await self.device.queue.put(request)
|
pyplumio/helpers/task_manager.py
CHANGED
pyplumio/helpers/timeout.py
CHANGED
pyplumio/helpers/typing.py
CHANGED