PyPlumIO 0.5.25__py3-none-any.whl → 0.5.27__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.25.dist-info → PyPlumIO-0.5.27.dist-info}/METADATA +7 -7
- PyPlumIO-0.5.27.dist-info/RECORD +60 -0
- {PyPlumIO-0.5.25.dist-info → PyPlumIO-0.5.27.dist-info}/WHEEL +1 -1
- pyplumio/_version.py +2 -2
- pyplumio/devices/__init__.py +27 -28
- pyplumio/devices/ecomax.py +51 -57
- pyplumio/devices/ecoster.py +3 -5
- pyplumio/devices/mixer.py +5 -8
- pyplumio/devices/thermostat.py +3 -3
- pyplumio/filters.py +6 -6
- pyplumio/frames/__init__.py +6 -6
- pyplumio/frames/messages.py +3 -3
- pyplumio/frames/requests.py +18 -18
- pyplumio/frames/responses.py +15 -15
- pyplumio/helpers/data_types.py +104 -68
- pyplumio/helpers/event_manager.py +6 -6
- pyplumio/helpers/factory.py +5 -6
- pyplumio/helpers/parameter.py +17 -15
- pyplumio/helpers/schedule.py +50 -46
- pyplumio/helpers/timeout.py +1 -1
- pyplumio/protocol.py +6 -7
- pyplumio/structures/alerts.py +8 -6
- pyplumio/structures/ecomax_parameters.py +30 -26
- pyplumio/structures/frame_versions.py +2 -3
- pyplumio/structures/mixer_parameters.py +9 -6
- pyplumio/structures/mixer_sensors.py +10 -8
- pyplumio/structures/modules.py +9 -7
- pyplumio/structures/network_info.py +16 -16
- pyplumio/structures/program_version.py +3 -0
- pyplumio/structures/regulator_data.py +2 -4
- pyplumio/structures/regulator_data_schema.py +2 -3
- pyplumio/structures/schedules.py +33 -35
- pyplumio/structures/thermostat_parameters.py +6 -4
- pyplumio/structures/thermostat_sensors.py +13 -10
- PyPlumIO-0.5.25.dist-info/RECORD +0 -60
- {PyPlumIO-0.5.25.dist-info → PyPlumIO-0.5.27.dist-info}/LICENSE +0 -0
- {PyPlumIO-0.5.25.dist-info → PyPlumIO-0.5.27.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.27
|
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
|
@@ -35,14 +35,14 @@ Requires-Dist: sphinx-rtd-theme ==2.0.0 ; extra == 'docs'
|
|
35
35
|
Requires-Dist: readthedocs-sphinx-search ==0.3.2 ; extra == 'docs'
|
36
36
|
Provides-Extra: test
|
37
37
|
Requires-Dist: codespell ==2.3.0 ; extra == 'test'
|
38
|
-
Requires-Dist: coverage ==7.6.
|
39
|
-
Requires-Dist: mypy ==1.11.
|
38
|
+
Requires-Dist: coverage ==7.6.1 ; extra == 'test'
|
39
|
+
Requires-Dist: mypy ==1.11.2 ; extra == 'test'
|
40
40
|
Requires-Dist: pyserial-asyncio-fast ==0.14 ; extra == 'test'
|
41
41
|
Requires-Dist: pytest ==8.3.2 ; extra == 'test'
|
42
|
-
Requires-Dist: pytest-asyncio ==0.
|
43
|
-
Requires-Dist: ruff ==0.
|
44
|
-
Requires-Dist: tox ==4.
|
45
|
-
Requires-Dist: types-pyserial ==3.5.0.
|
42
|
+
Requires-Dist: pytest-asyncio ==0.24.0 ; extra == 'test'
|
43
|
+
Requires-Dist: ruff ==0.6.3 ; extra == 'test'
|
44
|
+
Requires-Dist: tox ==4.18.0 ; extra == 'test'
|
45
|
+
Requires-Dist: types-pyserial ==3.5.0.20240826 ; extra == 'test'
|
46
46
|
|
47
47
|
# PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
|
48
48
|
[](https://badge.fury.io/py/PyPlumIO)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
pyplumio/__init__.py,sha256=ditJTIOFGJDg60atHzOpiggdUrZHpSynno7MtpZUGVk,3299
|
2
|
+
pyplumio/__main__.py,sha256=3IwHHSq-iay5FaeMc95klobe-xv82yydSKcBE7BFZ6M,500
|
3
|
+
pyplumio/_version.py,sha256=6ndDhm_j36uKFAa8jfkMvDlg-OkjeSfudQRWJOg10fs,413
|
4
|
+
pyplumio/connection.py,sha256=6mUbcjGxxEhMVIbzZgCqH-Ez-fcYoRj7ZbVSzpikpNA,5949
|
5
|
+
pyplumio/const.py,sha256=8rpiVbVb5R_6Rm6J2sgCnaVrkD-2Fzhd1RYMz0MBgwo,3915
|
6
|
+
pyplumio/exceptions.py,sha256=Wn-y5AJ5xfaBlHhTUVKB27_0Us8_OVHqh-sicnr9sYA,700
|
7
|
+
pyplumio/filters.py,sha256=r2DZHwHG0cPWzTcsIX1pjAH19BR3iCOQsaqJ3T106t4,11188
|
8
|
+
pyplumio/protocol.py,sha256=VRxrj8vZ1FMawqblKkyxg_V61TBSvVynd9u0JXYnMUU,8090
|
9
|
+
pyplumio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
pyplumio/stream.py,sha256=IVCQFKBtRafRgUkr93p_wN5mXZAD3Jw1d091dfEIK20,4479
|
11
|
+
pyplumio/utils.py,sha256=TnBzRopinyp92wruguijxcIYmaeyNVTFX0dygI5FCMU,823
|
12
|
+
pyplumio/devices/__init__.py,sha256=uKziGOX_pcd-MEt7eqdwyZidLk7-9Uu07_U49Hlel78,6532
|
13
|
+
pyplumio/devices/ecomax.py,sha256=oLl6aYbgC6MtsxCiW0eLSevpXVjVOYar--fn0Qzaz9w,16878
|
14
|
+
pyplumio/devices/ecoster.py,sha256=jNWli7ye9T6yfkcFJZhhUHH7KOv-L6AgYFp_dKyv3OM,263
|
15
|
+
pyplumio/devices/mixer.py,sha256=VE9Kjpq-sTLGgR8F-qnQjOuN8BrD4edrvu1L2X6m4uM,3199
|
16
|
+
pyplumio/devices/thermostat.py,sha256=1vOUWppTzY7iN6zDhNUFlb5rrqOCs4_cegn25h_bags,2609
|
17
|
+
pyplumio/frames/__init__.py,sha256=QqghzVt0r1STmeDlYs_hriHfzZj96-hzRQsxlFdv6Ls,7497
|
18
|
+
pyplumio/frames/messages.py,sha256=iDwZOPdVOZaIcEHYnkwtCazH_N6BjyEDtiJBjTRaePY,3570
|
19
|
+
pyplumio/frames/requests.py,sha256=nbSuOLue2rI4WgtXslqTGfFnWBlwzLE6I9wraKC1uqg,6854
|
20
|
+
pyplumio/frames/responses.py,sha256=Ch1AVBmD6Ek7BazoEMDDEa6ad_fUdUXf4bNssQOu0sI,6228
|
21
|
+
pyplumio/helpers/__init__.py,sha256=H2xxdkF-9uADLwEbfBUoxNTdwru3L5Z2cfJjgsuRsn0,31
|
22
|
+
pyplumio/helpers/data_types.py,sha256=fNQmZ-fifqNl01jDXoF0BjmBJoDKVr4spZNHBuiUVEY,9059
|
23
|
+
pyplumio/helpers/event_manager.py,sha256=xQOfiP_nP1Pz5zhB6HU5gXyyJXjhisYshL8_HRxDgt8,6412
|
24
|
+
pyplumio/helpers/factory.py,sha256=6ArzJDq3MiiMaRpMEP0kC6wJWsoqOqe32V1RCxg1478,1005
|
25
|
+
pyplumio/helpers/parameter.py,sha256=iCtKkYXJI0Zj4ifU3HCwoq_qqSE03giblSP1rqsWzcY,11463
|
26
|
+
pyplumio/helpers/schedule.py,sha256=l-dQwy8TUjlPqiMFcQaMCTxzgtAvQpyuutRIdfI06zo,5302
|
27
|
+
pyplumio/helpers/task_manager.py,sha256=HAd69yGTRL0zQsu-ywnbLu1UXiJzgHWuhYWA--vs4lQ,1181
|
28
|
+
pyplumio/helpers/timeout.py,sha256=JAhWNtIpcXyVILIwHWVy5mYofqbbRDGKLdTUKkQuajs,772
|
29
|
+
pyplumio/helpers/uid.py,sha256=J7gN8i8LE0g6tfL66BJbwsQQqzBBxWx7giyvqaJh4BM,976
|
30
|
+
pyplumio/structures/__init__.py,sha256=EjK-5qJZ0F7lpP2b6epvTMg9cIBl4Kn91nqNkEcLwTc,1299
|
31
|
+
pyplumio/structures/alerts.py,sha256=8ievMl5_tUBlnTLCiZoIloucIngCcoAYy6uI9sSXrt0,3664
|
32
|
+
pyplumio/structures/boiler_load.py,sha256=p3mOzZUU-g7A2tG_yp8podEqpI81hlsOZmHELyPNRY8,838
|
33
|
+
pyplumio/structures/boiler_power.py,sha256=72qsvccg49FdRdXv2f2K5sGpjT7wAOLFjlIGWpO-DVg,901
|
34
|
+
pyplumio/structures/ecomax_parameters.py,sha256=tV97N6uQ1VQmY88Rdtm_BhX2kR7tDGDOfGYFCpynX0A,27880
|
35
|
+
pyplumio/structures/fan_power.py,sha256=Q5fv-7_2NVuLeQPIVIylvgN7M8-a9D8rRUE0QGjyS3w,871
|
36
|
+
pyplumio/structures/frame_versions.py,sha256=hbcVuhuPNy5qd39Vk7w4WdPCW-TNx1cAYWzA2mXocyk,1548
|
37
|
+
pyplumio/structures/fuel_consumption.py,sha256=_p2dI4H67Eopn7IF0Gj77A8c_8lNKhhDDAtmugxLd4s,976
|
38
|
+
pyplumio/structures/fuel_level.py,sha256=mJpp1dnRD1wXi_6EyNX7TNXosjcr905rSHOnuZ5VD74,1069
|
39
|
+
pyplumio/structures/lambda_sensor.py,sha256=JNSCiBJoM8Uk3OGbmFIigaLOntQST5U_UrmCpaQBlM0,1595
|
40
|
+
pyplumio/structures/mixer_parameters.py,sha256=4PR_BgNVpeBzR3Q29GtpF9uJnHxPZ4jgyfhyysPmAUA,8919
|
41
|
+
pyplumio/structures/mixer_sensors.py,sha256=-cN7U-Fr2fmAQ5McQL7bZUC8CFlb1y8TN0f_dqy3UK0,2312
|
42
|
+
pyplumio/structures/modules.py,sha256=oXUIqrOAV1dZzBV5zUH3HDUSFvNOjpUSx0TF9nZVnbs,2569
|
43
|
+
pyplumio/structures/network_info.py,sha256=kPxmIaDGm5SyLRKVFzcrODlUtB0u5JjiZqekoKSyDpA,4159
|
44
|
+
pyplumio/structures/output_flags.py,sha256=07N0kxlvR5WZAURuChk_BqSiXR8eaQrtI5qlkgCf4Yc,1345
|
45
|
+
pyplumio/structures/outputs.py,sha256=1xsJPkjN643-aFawqVoupGatUIUJfQG_g252n051Qi0,1916
|
46
|
+
pyplumio/structures/pending_alerts.py,sha256=Uq9WpB4MW9AhDkqmDhk-g0J0h4pVq0Q50z12dYEv6kY,739
|
47
|
+
pyplumio/structures/product_info.py,sha256=uiEN6DFQlzmBvQByTirFzXQShoex0YGdFS9WI-MAxPc,2405
|
48
|
+
pyplumio/structures/program_version.py,sha256=R-medELYHDlk_ALsw5HOVbZRb7JD3yBUsGwqwVCjrkU,2550
|
49
|
+
pyplumio/structures/regulator_data.py,sha256=wqtRWPiwC4H_98nu9g1Po5wCqqoqUMuPBE0w93vIcuI,2271
|
50
|
+
pyplumio/structures/regulator_data_schema.py,sha256=XM6M9ep3NyogbLPqp88mMTg8Sa9e5SFzV5I5pSYw5GY,1487
|
51
|
+
pyplumio/structures/schedules.py,sha256=YzlfgprZq4pDfl-NBHl-EblhxatmDYr0UOkkHBW0Jok,6707
|
52
|
+
pyplumio/structures/statuses.py,sha256=wkoynyMRr1VREwfBC6vU48kPA8ZQ83pcXuciy2xHJrk,1166
|
53
|
+
pyplumio/structures/temperatures.py,sha256=1CDzehNmbALz1Jyt_9gZNIk52q6Wv-xQXjijVDCVYec,2337
|
54
|
+
pyplumio/structures/thermostat_parameters.py,sha256=EzJVZZhZ19beshRG7X6lw9VWDdq4mQoMFSOhWuW7_0U,8064
|
55
|
+
pyplumio/structures/thermostat_sensors.py,sha256=8e1TxYIJTQKT0kIGO9gG4hGdLOBUpIhiPToQyOMyeNE,3237
|
56
|
+
PyPlumIO-0.5.27.dist-info/LICENSE,sha256=m-UuZFjXJ22uPTGm9kSHS8bqjsf5T8k2wL9bJn1Y04o,1088
|
57
|
+
PyPlumIO-0.5.27.dist-info/METADATA,sha256=Debg6o3vvo_8AbQw14KdBiUwh8xhdQaERpNPntD--18,5490
|
58
|
+
PyPlumIO-0.5.27.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
|
59
|
+
PyPlumIO-0.5.27.dist-info/top_level.txt,sha256=kNBz9UPPkPD9teDn3U_sEy5LjzwLm9KfADCXtBlbw8A,9
|
60
|
+
PyPlumIO-0.5.27.dist-info/RECORD,,
|
pyplumio/_version.py
CHANGED
pyplumio/devices/__init__.py
CHANGED
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
from abc import ABC
|
6
6
|
import asyncio
|
7
|
-
from collections.abc import Iterable
|
8
7
|
from functools import cache
|
9
8
|
from typing import Any, ClassVar
|
10
9
|
|
@@ -13,7 +12,7 @@ from pyplumio.exceptions import UnknownDeviceError
|
|
13
12
|
from pyplumio.frames import DataFrameDescription, Frame, Request
|
14
13
|
from pyplumio.helpers.event_manager import EventManager
|
15
14
|
from pyplumio.helpers.factory import create_instance
|
16
|
-
from pyplumio.helpers.parameter import
|
15
|
+
from pyplumio.helpers.parameter import Parameter, ParameterValue
|
17
16
|
from pyplumio.structures.network_info import NetworkInfo
|
18
17
|
from pyplumio.utils import to_camelcase
|
19
18
|
|
@@ -54,9 +53,9 @@ class Device(ABC, EventManager):
|
|
54
53
|
async def set(
|
55
54
|
self,
|
56
55
|
name: str,
|
57
|
-
value:
|
56
|
+
value: ParameterValue,
|
57
|
+
retries: int = 5,
|
58
58
|
timeout: float | None = None,
|
59
|
-
retries: int = SET_RETRIES,
|
60
59
|
) -> bool:
|
61
60
|
"""Set a parameter value.
|
62
61
|
|
@@ -64,12 +63,12 @@ class Device(ABC, EventManager):
|
|
64
63
|
:type name: str
|
65
64
|
:param value: New value for the parameter
|
66
65
|
:type value: int | float | bool | Literal["off", "on"]
|
67
|
-
:param timeout: Wait this amount of seconds for confirmation,
|
68
|
-
defaults to `None`
|
69
|
-
:type timeout: float, optional
|
70
66
|
:param retries: Try setting parameter for this amount of
|
71
67
|
times, defaults to 5
|
72
68
|
:type retries: int, optional
|
69
|
+
:param timeout: Wait this amount of seconds for confirmation,
|
70
|
+
defaults to `None`
|
71
|
+
:type timeout: float, optional
|
73
72
|
:return: `True` if parameter was successfully set, `False`
|
74
73
|
otherwise.
|
75
74
|
:rtype: bool
|
@@ -77,7 +76,7 @@ class Device(ABC, EventManager):
|
|
77
76
|
:raise ValueError: when a new value is outside of allowed range
|
78
77
|
:raise TypeError: when found data is not valid parameter
|
79
78
|
"""
|
80
|
-
parameter = await self.get(name, timeout
|
79
|
+
parameter = await self.get(name, timeout)
|
81
80
|
if not isinstance(parameter, Parameter):
|
82
81
|
raise TypeError(f"{name} is not valid parameter")
|
83
82
|
|
@@ -86,9 +85,9 @@ class Device(ABC, EventManager):
|
|
86
85
|
def set_nowait(
|
87
86
|
self,
|
88
87
|
name: str,
|
89
|
-
value:
|
88
|
+
value: ParameterValue,
|
89
|
+
retries: int = 5,
|
90
90
|
timeout: float | None = None,
|
91
|
-
retries: int = SET_RETRIES,
|
92
91
|
) -> None:
|
93
92
|
"""Set a parameter value without waiting for the result.
|
94
93
|
|
@@ -96,19 +95,19 @@ class Device(ABC, EventManager):
|
|
96
95
|
:type name: str
|
97
96
|
:param value: New value for the parameter
|
98
97
|
:type value: int | float | bool | Literal["off", "on"]
|
98
|
+
:param retries: Try setting parameter for this amount of
|
99
|
+
times, defaults to 5
|
100
|
+
:type retries: int, optional
|
99
101
|
:param timeout: Wait this amount of seconds for confirmation.
|
100
102
|
As this method operates in the background without waiting,
|
101
103
|
this value is used to determine failure when
|
102
104
|
retrying and doesn't block, defaults to `None`
|
103
105
|
:type timeout: float, optional
|
104
|
-
:param retries: Try setting parameter for this amount of
|
105
|
-
times, defaults to 5
|
106
|
-
:type retries: int, optional
|
107
106
|
:return: `True` if parameter was successfully set, `False`
|
108
107
|
otherwise.
|
109
108
|
:rtype: bool
|
110
109
|
"""
|
111
|
-
self.create_task(self.set(name, value,
|
110
|
+
self.create_task(self.set(name, value, retries, timeout))
|
112
111
|
|
113
112
|
async def shutdown(self) -> None:
|
114
113
|
"""Cancel device tasks."""
|
@@ -116,22 +115,22 @@ class Device(ABC, EventManager):
|
|
116
115
|
await self.wait_until_done()
|
117
116
|
|
118
117
|
|
119
|
-
class
|
120
|
-
"""Represents
|
118
|
+
class PhysicalDevice(Device, ABC):
|
119
|
+
"""Represents a physical device.
|
120
|
+
|
121
|
+
Physical device have network address and can have multiple
|
122
|
+
virtual devices associated with them via parent property.
|
123
|
+
"""
|
121
124
|
|
122
125
|
address: ClassVar[int]
|
123
126
|
_network: NetworkInfo
|
124
|
-
_setup_frames:
|
127
|
+
_setup_frames: tuple[DataFrameDescription, ...]
|
125
128
|
|
126
129
|
def __init__(self, queue: asyncio.Queue[Frame], network: NetworkInfo):
|
127
|
-
"""Initialize a new
|
130
|
+
"""Initialize a new physical device."""
|
128
131
|
super().__init__(queue)
|
129
132
|
self._network = network
|
130
133
|
|
131
|
-
def __int__(self) -> int:
|
132
|
-
"""Return the device address."""
|
133
|
-
return int(self.address)
|
134
|
-
|
135
134
|
def handle_frame(self, frame: Frame) -> None:
|
136
135
|
"""Handle frame received from the device."""
|
137
136
|
frame.sender_device = self
|
@@ -176,19 +175,19 @@ class AddressableDevice(Device, ABC):
|
|
176
175
|
raise ValueError(f'could not request "{name}"', frame_type)
|
177
176
|
|
178
177
|
@classmethod
|
179
|
-
async def create(cls, device_type: int, **kwargs: Any) ->
|
180
|
-
"""Create a device handler object."""
|
178
|
+
async def create(cls, device_type: int, **kwargs: Any) -> PhysicalDevice:
|
179
|
+
"""Create a physical device handler object."""
|
181
180
|
return await create_instance(get_device_handler(device_type), cls=cls, **kwargs)
|
182
181
|
|
183
182
|
|
184
|
-
class
|
185
|
-
"""Represents a
|
183
|
+
class VirtualDevice(Device, ABC):
|
184
|
+
"""Represents a virtual device associated with physical device."""
|
186
185
|
|
187
|
-
parent:
|
186
|
+
parent: PhysicalDevice
|
188
187
|
index: int
|
189
188
|
|
190
189
|
def __init__(
|
191
|
-
self, queue: asyncio.Queue[Frame], parent:
|
190
|
+
self, queue: asyncio.Queue[Frame], parent: PhysicalDevice, index: int = 0
|
192
191
|
):
|
193
192
|
"""Initialize a new sub-device."""
|
194
193
|
super().__init__(queue)
|
pyplumio/devices/ecomax.py
CHANGED
@@ -6,7 +6,7 @@ import asyncio
|
|
6
6
|
from collections.abc import Coroutine, Generator, Iterable, Sequence
|
7
7
|
import logging
|
8
8
|
import time
|
9
|
-
from typing import Any,
|
9
|
+
from typing import Any, Final
|
10
10
|
|
11
11
|
from pyplumio.const import (
|
12
12
|
ATTR_FRAME_ERRORS,
|
@@ -17,7 +17,7 @@ from pyplumio.const import (
|
|
17
17
|
DeviceType,
|
18
18
|
FrameType,
|
19
19
|
)
|
20
|
-
from pyplumio.devices import
|
20
|
+
from pyplumio.devices import PhysicalDevice
|
21
21
|
from pyplumio.devices.mixer import Mixer
|
22
22
|
from pyplumio.devices.thermostat import Thermostat
|
23
23
|
from pyplumio.filters import on_change
|
@@ -101,13 +101,14 @@ SETUP_FRAME_TYPES: tuple[DataFrameDescription, ...] = (
|
|
101
101
|
_LOGGER = logging.getLogger(__name__)
|
102
102
|
|
103
103
|
|
104
|
-
class EcoMAX(
|
104
|
+
class EcoMAX(PhysicalDevice):
|
105
105
|
"""Represents an ecoMAX controller."""
|
106
106
|
|
107
|
-
address
|
108
|
-
|
107
|
+
address = DeviceType.ECOMAX
|
108
|
+
|
109
109
|
_frame_versions: dict[int, int]
|
110
110
|
_fuel_burned_timestamp_ns: int
|
111
|
+
_setup_frames = SETUP_FRAME_TYPES
|
111
112
|
|
112
113
|
def __init__(self, queue: asyncio.Queue[Frame], network: NetworkInfo):
|
113
114
|
"""Initialize a new ecoMAX controller."""
|
@@ -216,10 +217,7 @@ class EcoMAX(AddressableDevice):
|
|
216
217
|
yield self.dispatch(
|
217
218
|
description.name,
|
218
219
|
handler.create_or_update(
|
219
|
-
device=self,
|
220
|
-
description=description,
|
221
|
-
values=values,
|
222
|
-
index=index,
|
220
|
+
device=self, description=description, values=values, index=index
|
223
221
|
),
|
224
222
|
)
|
225
223
|
|
@@ -263,17 +261,16 @@ class EcoMAX(AddressableDevice):
|
|
263
261
|
parameter's name and value. Events are dispatched for the
|
264
262
|
respective mixer instance.
|
265
263
|
"""
|
266
|
-
if
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
for mixer in self._mixers(indexes=parameters.keys())
|
264
|
+
if parameters:
|
265
|
+
await asyncio.gather(
|
266
|
+
*(
|
267
|
+
mixer.dispatch(ATTR_MIXER_PARAMETERS, parameters[mixer.index])
|
268
|
+
for mixer in self._mixers(indexes=parameters.keys())
|
269
|
+
)
|
273
270
|
)
|
274
|
-
|
271
|
+
return True
|
275
272
|
|
276
|
-
return
|
273
|
+
return False
|
277
274
|
|
278
275
|
async def _handle_mixer_sensors(
|
279
276
|
self, sensors: dict[int, dict[str, Any]] | None
|
@@ -284,17 +281,16 @@ class EcoMAX(AddressableDevice):
|
|
284
281
|
sensor's name and value. Events are dispatched for the
|
285
282
|
respective mixer instance.
|
286
283
|
"""
|
287
|
-
if
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
for mixer in self._mixers(indexes=sensors.keys())
|
284
|
+
if sensors:
|
285
|
+
await asyncio.gather(
|
286
|
+
*(
|
287
|
+
mixer.dispatch(ATTR_MIXER_SENSORS, sensors[mixer.index])
|
288
|
+
for mixer in self._mixers(indexes=sensors.keys())
|
289
|
+
)
|
294
290
|
)
|
295
|
-
|
291
|
+
return True
|
296
292
|
|
297
|
-
return
|
293
|
+
return False
|
298
294
|
|
299
295
|
async def _add_schedules(
|
300
296
|
self, schedules: list[tuple[int, list[list[bool]]]]
|
@@ -332,10 +328,7 @@ class EcoMAX(AddressableDevice):
|
|
332
328
|
yield self.dispatch(
|
333
329
|
description.name,
|
334
330
|
handler.create_or_update(
|
335
|
-
device=self,
|
336
|
-
description=description,
|
337
|
-
values=values,
|
338
|
-
index=index,
|
331
|
+
device=self, description=description, values=values, index=index
|
339
332
|
),
|
340
333
|
)
|
341
334
|
|
@@ -376,29 +369,29 @@ class EcoMAX(AddressableDevice):
|
|
376
369
|
parameter's name and value. Events are dispatched for the
|
377
370
|
respective thermostat instance.
|
378
371
|
"""
|
379
|
-
if
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
372
|
+
if parameters:
|
373
|
+
await asyncio.gather(
|
374
|
+
*(
|
375
|
+
thermostat.dispatch(
|
376
|
+
ATTR_THERMOSTAT_PARAMETERS, parameters[thermostat.index]
|
377
|
+
)
|
378
|
+
for thermostat in self._thermostats(indexes=parameters.keys())
|
386
379
|
)
|
387
|
-
for thermostat in self._thermostats(indexes=parameters.keys())
|
388
380
|
)
|
389
|
-
|
390
|
-
|
381
|
+
return True
|
382
|
+
|
383
|
+
return False
|
391
384
|
|
392
385
|
async def _add_thermostat_profile_parameter(
|
393
386
|
self, values: ParameterValues | None
|
394
387
|
) -> EcomaxNumber | None:
|
395
388
|
"""Add thermostat profile parameter to the dataset."""
|
396
|
-
if
|
397
|
-
return
|
389
|
+
if values:
|
390
|
+
return EcomaxNumber(
|
391
|
+
device=self, description=THERMOSTAT_PROFILE_PARAMETER, values=values
|
392
|
+
)
|
398
393
|
|
399
|
-
return
|
400
|
-
device=self, description=THERMOSTAT_PROFILE_PARAMETER, values=values
|
401
|
-
)
|
394
|
+
return None
|
402
395
|
|
403
396
|
async def _handle_thermostat_sensors(
|
404
397
|
self, sensors: dict[int, dict[str, Any]] | None
|
@@ -409,18 +402,19 @@ class EcoMAX(AddressableDevice):
|
|
409
402
|
sensor's name and value. Events are dispatched for the
|
410
403
|
respective thermostat instance.
|
411
404
|
"""
|
412
|
-
if
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
405
|
+
if sensors:
|
406
|
+
await asyncio.gather(
|
407
|
+
*(
|
408
|
+
thermostat.dispatch(
|
409
|
+
ATTR_THERMOSTAT_SENSORS, sensors[thermostat.index]
|
410
|
+
)
|
411
|
+
for thermostat in self._thermostats(indexes=sensors.keys())
|
412
|
+
),
|
413
|
+
return_exceptions=True,
|
414
|
+
)
|
415
|
+
return True
|
422
416
|
|
423
|
-
return
|
417
|
+
return False
|
424
418
|
|
425
419
|
async def turn_on(self) -> bool:
|
426
420
|
"""Turn on the ecoMAX controller."""
|
pyplumio/devices/ecoster.py
CHANGED
@@ -2,13 +2,11 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from typing import ClassVar
|
6
|
-
|
7
5
|
from pyplumio.const import DeviceType
|
8
|
-
from pyplumio.devices import
|
6
|
+
from pyplumio.devices import PhysicalDevice
|
9
7
|
|
10
8
|
|
11
|
-
class EcoSTER(
|
9
|
+
class EcoSTER(PhysicalDevice):
|
12
10
|
"""Represents an ecoSTER thermostat."""
|
13
11
|
|
14
|
-
address
|
12
|
+
address = DeviceType.ECOSTER
|
pyplumio/devices/mixer.py
CHANGED
@@ -7,7 +7,7 @@ from collections.abc import Coroutine, Generator, Sequence
|
|
7
7
|
import logging
|
8
8
|
from typing import TYPE_CHECKING, Any
|
9
9
|
|
10
|
-
from pyplumio.devices import
|
10
|
+
from pyplumio.devices import PhysicalDevice, VirtualDevice
|
11
11
|
from pyplumio.helpers.parameter import ParameterValues
|
12
12
|
from pyplumio.structures.mixer_parameters import (
|
13
13
|
ATTR_MIXER_PARAMETERS,
|
@@ -25,11 +25,11 @@ if TYPE_CHECKING:
|
|
25
25
|
_LOGGER = logging.getLogger(__name__)
|
26
26
|
|
27
27
|
|
28
|
-
class Mixer(
|
29
|
-
"""Represents
|
28
|
+
class Mixer(VirtualDevice):
|
29
|
+
"""Represents a mixer."""
|
30
30
|
|
31
31
|
def __init__(
|
32
|
-
self, queue: asyncio.Queue[Frame], parent:
|
32
|
+
self, queue: asyncio.Queue[Frame], parent: PhysicalDevice, index: int = 0
|
33
33
|
):
|
34
34
|
"""Initialize a new mixer."""
|
35
35
|
super().__init__(queue, parent, index)
|
@@ -85,10 +85,7 @@ class Mixer(SubDevice):
|
|
85
85
|
yield self.dispatch(
|
86
86
|
description.name,
|
87
87
|
handler.create_or_update(
|
88
|
-
device=self,
|
89
|
-
description=description,
|
90
|
-
values=values,
|
91
|
-
index=index,
|
88
|
+
device=self, description=description, values=values, index=index
|
92
89
|
),
|
93
90
|
)
|
94
91
|
|
pyplumio/devices/thermostat.py
CHANGED
@@ -6,7 +6,7 @@ import asyncio
|
|
6
6
|
from collections.abc import Coroutine, Generator, Sequence
|
7
7
|
from typing import TYPE_CHECKING, Any
|
8
8
|
|
9
|
-
from pyplumio.devices import
|
9
|
+
from pyplumio.devices import PhysicalDevice, VirtualDevice
|
10
10
|
from pyplumio.helpers.parameter import ParameterValues
|
11
11
|
from pyplumio.structures.thermostat_parameters import (
|
12
12
|
ATTR_THERMOSTAT_PARAMETERS,
|
@@ -21,11 +21,11 @@ if TYPE_CHECKING:
|
|
21
21
|
from pyplumio.frames import Frame
|
22
22
|
|
23
23
|
|
24
|
-
class Thermostat(
|
24
|
+
class Thermostat(VirtualDevice):
|
25
25
|
"""Represents a thermostat."""
|
26
26
|
|
27
27
|
def __init__(
|
28
|
-
self, queue: asyncio.Queue[Frame], parent:
|
28
|
+
self, queue: asyncio.Queue[Frame], parent: PhysicalDevice, index: int = 0
|
29
29
|
):
|
30
30
|
"""Initialize a new thermostat."""
|
31
31
|
super().__init__(queue, parent, index)
|
pyplumio/filters.py
CHANGED
@@ -150,7 +150,7 @@ def on_change(callback: Callback) -> _OnChange:
|
|
150
150
|
previous call.
|
151
151
|
|
152
152
|
:param callback: A callback function to be awaited on value change
|
153
|
-
:type callback:
|
153
|
+
:type callback: Callback
|
154
154
|
:return: A instance of callable filter
|
155
155
|
:rtype: _OnChange
|
156
156
|
"""
|
@@ -197,7 +197,7 @@ def debounce(callback: Callback, min_calls: int) -> _Debounce:
|
|
197
197
|
across multiple filter calls.
|
198
198
|
|
199
199
|
:param callback: A callback function to be awaited on value change
|
200
|
-
:type callback:
|
200
|
+
:type callback: Callback
|
201
201
|
:param min_calls: Value shouldn't change for this amount of
|
202
202
|
filter calls
|
203
203
|
:type min_calls: int
|
@@ -244,7 +244,7 @@ def throttle(callback: Callback, seconds: float) -> _Throttle:
|
|
244
244
|
|
245
245
|
:param callback: A callback function that will be awaited once
|
246
246
|
filter conditions are fulfilled
|
247
|
-
:type callback:
|
247
|
+
:type callback: Callback
|
248
248
|
:param seconds: A callback will be awaited at most once per
|
249
249
|
this amount of seconds
|
250
250
|
:type seconds: float
|
@@ -284,7 +284,7 @@ def delta(callback: Callback) -> _Delta:
|
|
284
284
|
|
285
285
|
:param callback: A callback function that will be awaited with
|
286
286
|
difference between values in two subsequent calls
|
287
|
-
:type callback:
|
287
|
+
:type callback: Callback
|
288
288
|
:return: A instance of callable filter
|
289
289
|
:rtype: _Delta
|
290
290
|
"""
|
@@ -336,7 +336,7 @@ def aggregate(callback: Callback, seconds: float) -> _Aggregate:
|
|
336
336
|
|
337
337
|
:param callback: A callback function to be awaited once filter
|
338
338
|
conditions are fulfilled
|
339
|
-
:type callback:
|
339
|
+
:type callback: Callback
|
340
340
|
:param seconds: A callback will be awaited with a sum of values
|
341
341
|
aggregated over this amount of seconds.
|
342
342
|
:type seconds: float
|
@@ -378,7 +378,7 @@ def custom(callback: Callback, filter_fn: Callable[[Any], bool]) -> _Custom:
|
|
378
378
|
|
379
379
|
:param callback: A callback function to be awaited when
|
380
380
|
filter function return true
|
381
|
-
:type callback:
|
381
|
+
:type callback: Callback
|
382
382
|
:param filter_fn: Filter function, that will be called with a
|
383
383
|
value and should return `True` to await filter's callback
|
384
384
|
:type filter_fn: Callable[[Any], bool]
|
pyplumio/frames/__init__.py
CHANGED
@@ -26,7 +26,7 @@ ECONET_VERSION: Final = 5
|
|
26
26
|
struct_header = struct.Struct("<BH4B")
|
27
27
|
|
28
28
|
if TYPE_CHECKING:
|
29
|
-
from pyplumio.devices import
|
29
|
+
from pyplumio.devices import PhysicalDevice
|
30
30
|
|
31
31
|
|
32
32
|
def bcc(data: bytes) -> int:
|
@@ -65,6 +65,9 @@ class DataFrameDescription:
|
|
65
65
|
provides: str
|
66
66
|
|
67
67
|
|
68
|
+
FrameT = TypeVar("FrameT", bound="Frame")
|
69
|
+
|
70
|
+
|
68
71
|
class Frame(ABC):
|
69
72
|
"""Represents a frame."""
|
70
73
|
|
@@ -80,9 +83,9 @@ class Frame(ABC):
|
|
80
83
|
)
|
81
84
|
|
82
85
|
recipient: DeviceType
|
83
|
-
recipient_device:
|
86
|
+
recipient_device: PhysicalDevice | None
|
84
87
|
sender: DeviceType
|
85
|
-
sender_device:
|
88
|
+
sender_device: PhysicalDevice | None
|
86
89
|
econet_type: int
|
87
90
|
econet_version: int
|
88
91
|
frame_type: ClassVar[FrameType]
|
@@ -234,9 +237,6 @@ class Frame(ABC):
|
|
234
237
|
"""Decode frame message."""
|
235
238
|
|
236
239
|
|
237
|
-
FrameT = TypeVar("FrameT", bound=Frame)
|
238
|
-
|
239
|
-
|
240
240
|
class Request(Frame):
|
241
241
|
"""Represents a request."""
|
242
242
|
|
pyplumio/frames/messages.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
from contextlib import suppress
|
6
|
-
from typing import Any
|
6
|
+
from typing import Any
|
7
7
|
|
8
8
|
from pyplumio.const import (
|
9
9
|
ATTR_SENSORS,
|
@@ -37,7 +37,7 @@ class RegulatorDataMessage(Message):
|
|
37
37
|
|
38
38
|
__slots__ = ()
|
39
39
|
|
40
|
-
frame_type
|
40
|
+
frame_type = FrameType.MESSAGE_REGULATOR_DATA
|
41
41
|
|
42
42
|
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
43
43
|
"""Decode a frame message."""
|
@@ -49,7 +49,7 @@ class SensorDataMessage(Message):
|
|
49
49
|
|
50
50
|
__slots__ = ()
|
51
51
|
|
52
|
-
frame_type
|
52
|
+
frame_type = FrameType.MESSAGE_SENSOR_DATA
|
53
53
|
|
54
54
|
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
55
55
|
"""Decode a frame message."""
|