PyPlumIO 0.5.42__py3-none-any.whl → 0.5.43__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/__init__.py +3 -2
- pyplumio/_version.py +2 -2
- pyplumio/connection.py +14 -14
- pyplumio/const.py +7 -0
- pyplumio/devices/__init__.py +32 -19
- pyplumio/devices/ecomax.py +112 -128
- pyplumio/devices/ecoster.py +5 -0
- pyplumio/devices/mixer.py +21 -31
- pyplumio/devices/thermostat.py +19 -29
- pyplumio/filters.py +166 -147
- pyplumio/frames/__init__.py +20 -8
- pyplumio/frames/messages.py +3 -0
- pyplumio/frames/requests.py +21 -0
- pyplumio/frames/responses.py +18 -0
- pyplumio/helpers/data_types.py +23 -21
- pyplumio/helpers/event_manager.py +40 -3
- pyplumio/helpers/factory.py +5 -2
- pyplumio/helpers/schedule.py +8 -5
- pyplumio/helpers/task_manager.py +3 -0
- pyplumio/helpers/timeout.py +8 -8
- pyplumio/helpers/uid.py +8 -5
- pyplumio/{helpers/parameter.py → parameters/__init__.py} +98 -4
- pyplumio/parameters/ecomax.py +868 -0
- pyplumio/parameters/mixer.py +245 -0
- pyplumio/parameters/thermostat.py +197 -0
- pyplumio/protocol.py +6 -3
- pyplumio/stream.py +3 -0
- pyplumio/structures/__init__.py +3 -0
- pyplumio/structures/alerts.py +8 -5
- pyplumio/structures/boiler_load.py +3 -0
- pyplumio/structures/boiler_power.py +3 -0
- pyplumio/structures/ecomax_parameters.py +6 -800
- pyplumio/structures/fan_power.py +3 -0
- pyplumio/structures/frame_versions.py +3 -0
- pyplumio/structures/fuel_consumption.py +3 -0
- pyplumio/structures/fuel_level.py +3 -0
- pyplumio/structures/lambda_sensor.py +8 -0
- pyplumio/structures/mixer_parameters.py +8 -230
- pyplumio/structures/mixer_sensors.py +9 -0
- pyplumio/structures/modules.py +14 -0
- pyplumio/structures/network_info.py +11 -0
- pyplumio/structures/output_flags.py +9 -0
- pyplumio/structures/outputs.py +21 -0
- pyplumio/structures/pending_alerts.py +3 -0
- pyplumio/structures/product_info.py +5 -2
- pyplumio/structures/program_version.py +3 -0
- pyplumio/structures/regulator_data.py +4 -1
- pyplumio/structures/regulator_data_schema.py +3 -0
- pyplumio/structures/schedules.py +18 -1
- pyplumio/structures/statuses.py +9 -0
- pyplumio/structures/temperatures.py +22 -0
- pyplumio/structures/thermostat_parameters.py +13 -177
- pyplumio/structures/thermostat_sensors.py +9 -0
- pyplumio/utils.py +14 -12
- {pyplumio-0.5.42.dist-info → pyplumio-0.5.43.dist-info}/METADATA +30 -17
- pyplumio-0.5.43.dist-info/RECORD +63 -0
- {pyplumio-0.5.42.dist-info → pyplumio-0.5.43.dist-info}/WHEEL +1 -1
- pyplumio-0.5.42.dist-info/RECORD +0 -60
- {pyplumio-0.5.42.dist-info → pyplumio-0.5.43.dist-info}/licenses/LICENSE +0 -0
- {pyplumio-0.5.42.dist-info → pyplumio-0.5.43.dist-info}/top_level.txt +0 -0
pyplumio/devices/thermostat.py
CHANGED
@@ -4,58 +4,45 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import asyncio
|
6
6
|
from collections.abc import Coroutine, Generator, Sequence
|
7
|
-
from typing import
|
7
|
+
from typing import Any
|
8
8
|
|
9
|
-
from pyplumio.devices import
|
10
|
-
from pyplumio.helpers.
|
11
|
-
from pyplumio.
|
12
|
-
|
13
|
-
THERMOSTAT_PARAMETERS,
|
9
|
+
from pyplumio.devices import VirtualDevice
|
10
|
+
from pyplumio.helpers.event_manager import event_listener
|
11
|
+
from pyplumio.parameters import ParameterValues
|
12
|
+
from pyplumio.parameters.thermostat import (
|
14
13
|
ThermostatNumber,
|
15
14
|
ThermostatSwitch,
|
16
15
|
ThermostatSwitchDescription,
|
16
|
+
get_thermostat_parameter_types,
|
17
17
|
)
|
18
|
+
from pyplumio.structures.thermostat_parameters import ATTR_THERMOSTAT_PARAMETERS
|
18
19
|
from pyplumio.structures.thermostat_sensors import ATTR_THERMOSTAT_SENSORS
|
19
20
|
|
20
|
-
if TYPE_CHECKING:
|
21
|
-
from pyplumio.frames import Frame
|
22
|
-
|
23
21
|
|
24
22
|
class Thermostat(VirtualDevice):
|
25
23
|
"""Represents a thermostat."""
|
26
24
|
|
27
|
-
|
28
|
-
self, queue: asyncio.Queue[Frame], parent: PhysicalDevice, index: int = 0
|
29
|
-
) -> None:
|
30
|
-
"""Initialize a new thermostat."""
|
31
|
-
super().__init__(queue, parent, index)
|
32
|
-
self.subscribe(ATTR_THERMOSTAT_SENSORS, self._handle_thermostat_sensors)
|
33
|
-
self.subscribe(ATTR_THERMOSTAT_PARAMETERS, self._handle_thermostat_parameters)
|
34
|
-
|
35
|
-
async def _handle_thermostat_sensors(self, sensors: dict[str, Any]) -> bool:
|
36
|
-
"""Handle thermostat sensors.
|
25
|
+
__slots__ = ()
|
37
26
|
|
38
|
-
|
39
|
-
|
40
|
-
"""
|
27
|
+
@event_listener(ATTR_THERMOSTAT_SENSORS)
|
28
|
+
async def on_event_thermostat_sensors(self, sensors: dict[str, Any]) -> bool:
|
29
|
+
"""Update thermostat sensors and dispatch the events."""
|
41
30
|
await asyncio.gather(
|
42
31
|
*(self.dispatch(name, value) for name, value in sensors.items())
|
43
32
|
)
|
44
33
|
return True
|
45
34
|
|
46
|
-
|
35
|
+
@event_listener(ATTR_THERMOSTAT_PARAMETERS)
|
36
|
+
async def on_event_thermostat_parameters(
|
47
37
|
self, parameters: Sequence[tuple[int, ParameterValues]]
|
48
38
|
) -> bool:
|
49
|
-
"""
|
50
|
-
|
51
|
-
For each parameter dispatch an event with the
|
52
|
-
parameter's name and value.
|
53
|
-
"""
|
39
|
+
"""Update thermostat parameters and dispatch the events."""
|
54
40
|
|
55
41
|
def _thermostat_parameter_events() -> Generator[Coroutine, Any, None]:
|
56
42
|
"""Get dispatch calls for thermostat parameter events."""
|
43
|
+
parameter_types = get_thermostat_parameter_types()
|
57
44
|
for index, values in parameters:
|
58
|
-
description =
|
45
|
+
description = parameter_types[index]
|
59
46
|
handler = (
|
60
47
|
ThermostatSwitch
|
61
48
|
if isinstance(description, ThermostatSwitchDescription)
|
@@ -74,3 +61,6 @@ class Thermostat(VirtualDevice):
|
|
74
61
|
|
75
62
|
await asyncio.gather(*_thermostat_parameter_events())
|
76
63
|
return True
|
64
|
+
|
65
|
+
|
66
|
+
__all__ = ["Thermostat"]
|
pyplumio/filters.py
CHANGED
@@ -17,8 +17,10 @@ from typing import (
|
|
17
17
|
runtime_checkable,
|
18
18
|
)
|
19
19
|
|
20
|
+
from typing_extensions import TypeAlias
|
21
|
+
|
20
22
|
from pyplumio.helpers.event_manager import Callback
|
21
|
-
from pyplumio.
|
23
|
+
from pyplumio.parameters import Parameter
|
22
24
|
|
23
25
|
UNDEFINED: Final = "undefined"
|
24
26
|
TOLERANCE: Final = 0.1
|
@@ -50,20 +52,18 @@ Comparable = TypeVar("Comparable", Parameter, SupportsFloat, SupportsComparison)
|
|
50
52
|
|
51
53
|
|
52
54
|
@overload
|
53
|
-
def
|
55
|
+
def is_close(old: Parameter, new: Parameter) -> bool: ...
|
54
56
|
|
55
57
|
|
56
58
|
@overload
|
57
|
-
def
|
59
|
+
def is_close(old: SupportsFloat, new: SupportsFloat) -> bool: ...
|
58
60
|
|
59
61
|
|
60
62
|
@overload
|
61
|
-
def
|
62
|
-
old: SupportsComparison, new: SupportsComparison
|
63
|
-
) -> bool: ...
|
63
|
+
def is_close(old: SupportsComparison, new: SupportsComparison) -> bool: ...
|
64
64
|
|
65
65
|
|
66
|
-
def
|
66
|
+
def is_close(old: Comparable, new: Comparable) -> bool:
|
67
67
|
"""Check if value is significantly changed."""
|
68
68
|
if isinstance(old, Parameter) and isinstance(new, Parameter):
|
69
69
|
return new.pending_update or old.values.__ne__(new.values)
|
@@ -75,16 +75,16 @@ def _significantly_changed(old: Comparable, new: Comparable) -> bool:
|
|
75
75
|
|
76
76
|
|
77
77
|
@overload
|
78
|
-
def
|
78
|
+
def diffence_between(old: list, new: list) -> list: ...
|
79
79
|
|
80
80
|
|
81
81
|
@overload
|
82
|
-
def
|
82
|
+
def diffence_between(
|
83
83
|
old: SupportsSubtraction, new: SupportsSubtraction
|
84
84
|
) -> SupportsSubtraction: ...
|
85
85
|
|
86
86
|
|
87
|
-
def
|
87
|
+
def diffence_between(
|
88
88
|
old: SupportsSubtraction | list, new: SupportsSubtraction | list
|
89
89
|
) -> SupportsSubtraction | list | None:
|
90
90
|
"""Return a difference between values."""
|
@@ -125,6 +125,61 @@ class Filter(ABC):
|
|
125
125
|
"""Set a new value for the callback."""
|
126
126
|
|
127
127
|
|
128
|
+
class _Aggregate(Filter):
|
129
|
+
"""Represents an aggregate filter.
|
130
|
+
|
131
|
+
Calls a callback with a sum of values collected over a specified
|
132
|
+
time period.
|
133
|
+
"""
|
134
|
+
|
135
|
+
__slots__ = ("_sum", "_last_update", "_timeout")
|
136
|
+
|
137
|
+
_sum: complex
|
138
|
+
_last_update: float
|
139
|
+
_timeout: float
|
140
|
+
|
141
|
+
def __init__(self, callback: Callback, seconds: float) -> None:
|
142
|
+
"""Initialize a new aggregate filter."""
|
143
|
+
super().__init__(callback)
|
144
|
+
self._last_update = time.monotonic()
|
145
|
+
self._timeout = seconds
|
146
|
+
self._sum = 0.0
|
147
|
+
|
148
|
+
async def __call__(self, new_value: Any) -> Any:
|
149
|
+
"""Set a new value for the callback."""
|
150
|
+
current_timestamp = time.monotonic()
|
151
|
+
try:
|
152
|
+
self._sum += new_value
|
153
|
+
except TypeError as e:
|
154
|
+
raise ValueError(
|
155
|
+
"Aggregate filter can only be used with numeric values"
|
156
|
+
) from e
|
157
|
+
|
158
|
+
if current_timestamp - self._last_update >= self._timeout:
|
159
|
+
result = await self._callback(self._sum)
|
160
|
+
self._last_update = current_timestamp
|
161
|
+
self._sum = 0.0
|
162
|
+
return result
|
163
|
+
|
164
|
+
|
165
|
+
def aggregate(callback: Callback, seconds: float) -> _Aggregate:
|
166
|
+
"""Create a new aggregate filter.
|
167
|
+
|
168
|
+
A callback function will be called with a sum of values collected
|
169
|
+
over a specified time period. Can only be used with numeric values.
|
170
|
+
|
171
|
+
:param callback: A callback function to be awaited once filter
|
172
|
+
conditions are fulfilled
|
173
|
+
:type callback: Callback
|
174
|
+
:param seconds: A callback will be awaited with a sum of values
|
175
|
+
aggregated over this amount of seconds.
|
176
|
+
:type seconds: float
|
177
|
+
:return: An instance of callable filter
|
178
|
+
:rtype: _Aggregate
|
179
|
+
"""
|
180
|
+
return _Aggregate(callback, seconds)
|
181
|
+
|
182
|
+
|
128
183
|
class _Clamp(Filter):
|
129
184
|
"""Represents a clamp filter.
|
130
185
|
|
@@ -137,7 +192,7 @@ class _Clamp(Filter):
|
|
137
192
|
_max_value: float
|
138
193
|
|
139
194
|
def __init__(self, callback: Callback, min_value: float, max_value: float) -> None:
|
140
|
-
"""Initialize a new
|
195
|
+
"""Initialize a new Clamp filter."""
|
141
196
|
super().__init__(callback)
|
142
197
|
self._min_value = min_value
|
143
198
|
self._max_value = max_value
|
@@ -154,10 +209,10 @@ class _Clamp(Filter):
|
|
154
209
|
|
155
210
|
|
156
211
|
def clamp(callback: Callback, min_value: float, max_value: float) -> _Clamp:
|
157
|
-
"""
|
212
|
+
"""Create a new clamp filter.
|
158
213
|
|
159
|
-
A callback function will be called
|
160
|
-
specified boundaries.
|
214
|
+
A callback function will be called and passed value clamped
|
215
|
+
between specified boundaries.
|
161
216
|
|
162
217
|
:param callback: A callback function to be awaited on new value
|
163
218
|
:type callback: Callback
|
@@ -171,36 +226,49 @@ def clamp(callback: Callback, min_value: float, max_value: float) -> _Clamp:
|
|
171
226
|
return _Clamp(callback, min_value, max_value)
|
172
227
|
|
173
228
|
|
174
|
-
|
175
|
-
"""Represents a value changed filter.
|
229
|
+
_FilterT: TypeAlias = Callable[[Any], bool]
|
176
230
|
|
177
|
-
|
178
|
-
|
231
|
+
|
232
|
+
class _Custom(Filter):
|
233
|
+
"""Represents a custom filter.
|
234
|
+
|
235
|
+
Calls a callback with value, if user-defined filter function
|
236
|
+
that's called by this class with the value as an argument
|
237
|
+
returns true.
|
179
238
|
"""
|
180
239
|
|
181
|
-
__slots__ = ()
|
240
|
+
__slots__ = ("_filter_fn",)
|
241
|
+
|
242
|
+
_filter_fn: _FilterT
|
243
|
+
|
244
|
+
def __init__(self, callback: Callback, filter_fn: _FilterT) -> None:
|
245
|
+
"""Initialize a new custom filter."""
|
246
|
+
super().__init__(callback)
|
247
|
+
self._filter_fn = filter_fn
|
182
248
|
|
183
249
|
async def __call__(self, new_value: Any) -> Any:
|
184
250
|
"""Set a new value for the callback."""
|
185
|
-
if self.
|
186
|
-
self.
|
187
|
-
copy(new_value) if isinstance(new_value, Parameter) else new_value
|
188
|
-
)
|
189
|
-
return await self._callback(new_value)
|
251
|
+
if self._filter_fn(new_value):
|
252
|
+
await self._callback(new_value)
|
190
253
|
|
191
254
|
|
192
|
-
def
|
193
|
-
"""
|
255
|
+
def custom(callback: Callback, filter_fn: _FilterT) -> _Custom:
|
256
|
+
"""Create a new custom filter.
|
194
257
|
|
195
|
-
A callback function will
|
196
|
-
|
258
|
+
A callback function will be called when a user-defined filter
|
259
|
+
function, that's being called with the value as an argument,
|
260
|
+
returns true.
|
197
261
|
|
198
|
-
:param callback: A callback function to be awaited
|
262
|
+
:param callback: A callback function to be awaited when
|
263
|
+
filter function return true
|
199
264
|
:type callback: Callback
|
265
|
+
:param filter_fn: Filter function, that will be called with a
|
266
|
+
value and should return `True` to await filter's callback
|
267
|
+
:type filter_fn: Callable[[Any], bool]
|
200
268
|
:return: An instance of callable filter
|
201
|
-
:rtype:
|
269
|
+
:rtype: _Custom
|
202
270
|
"""
|
203
|
-
return
|
271
|
+
return _Custom(callback, filter_fn)
|
204
272
|
|
205
273
|
|
206
274
|
class _Debounce(Filter):
|
@@ -223,7 +291,7 @@ class _Debounce(Filter):
|
|
223
291
|
|
224
292
|
async def __call__(self, new_value: Any) -> Any:
|
225
293
|
"""Set a new value for the callback."""
|
226
|
-
if self._value == UNDEFINED or
|
294
|
+
if self._value == UNDEFINED or is_close(self._value, new_value):
|
227
295
|
self._calls += 1
|
228
296
|
else:
|
229
297
|
self._calls = 0
|
@@ -237,9 +305,9 @@ class _Debounce(Filter):
|
|
237
305
|
|
238
306
|
|
239
307
|
def debounce(callback: Callback, min_calls: int) -> _Debounce:
|
240
|
-
"""
|
308
|
+
"""Create a new debounce filter.
|
241
309
|
|
242
|
-
A callback function will only called once value is stabilized
|
310
|
+
A callback function will only be called once the value is stabilized
|
243
311
|
across multiple filter calls.
|
244
312
|
|
245
313
|
:param callback: A callback function to be awaited on value change
|
@@ -253,53 +321,6 @@ def debounce(callback: Callback, min_calls: int) -> _Debounce:
|
|
253
321
|
return _Debounce(callback, min_calls)
|
254
322
|
|
255
323
|
|
256
|
-
class _Throttle(Filter):
|
257
|
-
"""Represents a throttle filter.
|
258
|
-
|
259
|
-
Calls a callback only when certain amount of seconds passed
|
260
|
-
since the last call.
|
261
|
-
"""
|
262
|
-
|
263
|
-
__slots__ = ("_last_called", "_timeout")
|
264
|
-
|
265
|
-
_last_called: float | None
|
266
|
-
_timeout: float
|
267
|
-
|
268
|
-
def __init__(self, callback: Callback, seconds: float) -> None:
|
269
|
-
"""Initialize a new throttle filter."""
|
270
|
-
super().__init__(callback)
|
271
|
-
self._last_called = None
|
272
|
-
self._timeout = seconds
|
273
|
-
|
274
|
-
async def __call__(self, new_value: Any) -> Any:
|
275
|
-
"""Set a new value for the callback."""
|
276
|
-
current_timestamp = time.monotonic()
|
277
|
-
if (
|
278
|
-
self._last_called is None
|
279
|
-
or (current_timestamp - self._last_called) >= self._timeout
|
280
|
-
):
|
281
|
-
self._last_called = current_timestamp
|
282
|
-
return await self._callback(new_value)
|
283
|
-
|
284
|
-
|
285
|
-
def throttle(callback: Callback, seconds: float) -> _Throttle:
|
286
|
-
"""Return a throttle filter.
|
287
|
-
|
288
|
-
A callback function will only be called once a certain amount of
|
289
|
-
seconds passed since the last call.
|
290
|
-
|
291
|
-
:param callback: A callback function that will be awaited once
|
292
|
-
filter conditions are fulfilled
|
293
|
-
:type callback: Callback
|
294
|
-
:param seconds: A callback will be awaited at most once per
|
295
|
-
this amount of seconds
|
296
|
-
:type seconds: float
|
297
|
-
:return: An instance of callable filter
|
298
|
-
:rtype: _Throttle
|
299
|
-
"""
|
300
|
-
return _Throttle(callback, seconds)
|
301
|
-
|
302
|
-
|
303
324
|
class _Delta(Filter):
|
304
325
|
"""Represents a difference filter.
|
305
326
|
|
@@ -310,23 +331,23 @@ class _Delta(Filter):
|
|
310
331
|
|
311
332
|
async def __call__(self, new_value: Any) -> Any:
|
312
333
|
"""Set a new value for the callback."""
|
313
|
-
if self._value == UNDEFINED or
|
334
|
+
if self._value == UNDEFINED or is_close(self._value, new_value):
|
314
335
|
old_value = self._value
|
315
336
|
self._value = (
|
316
337
|
copy(new_value) if isinstance(new_value, Parameter) else new_value
|
317
338
|
)
|
318
339
|
if (
|
319
340
|
self._value != UNDEFINED
|
320
|
-
and (difference :=
|
341
|
+
and (difference := diffence_between(old_value, new_value)) is not None
|
321
342
|
):
|
322
343
|
return await self._callback(difference)
|
323
344
|
|
324
345
|
|
325
346
|
def delta(callback: Callback) -> _Delta:
|
326
|
-
"""
|
347
|
+
"""Create a new difference filter.
|
327
348
|
|
328
349
|
A callback function will be called with a difference between two
|
329
|
-
subsequent
|
350
|
+
subsequent values.
|
330
351
|
|
331
352
|
:param callback: A callback function that will be awaited with
|
332
353
|
difference between values in two subsequent calls
|
@@ -337,98 +358,96 @@ def delta(callback: Callback) -> _Delta:
|
|
337
358
|
return _Delta(callback)
|
338
359
|
|
339
360
|
|
340
|
-
class
|
341
|
-
"""Represents
|
361
|
+
class _OnChange(Filter):
|
362
|
+
"""Represents a value changed filter.
|
342
363
|
|
343
|
-
Calls a callback
|
344
|
-
|
364
|
+
Calls a callback only when value is changed from the
|
365
|
+
previous callback call.
|
345
366
|
"""
|
346
367
|
|
347
|
-
__slots__ = (
|
348
|
-
|
349
|
-
_sum: complex
|
350
|
-
_last_update: float
|
351
|
-
_timeout: float
|
368
|
+
__slots__ = ()
|
352
369
|
|
353
|
-
def __init__(self, callback: Callback
|
354
|
-
"""Initialize a new
|
370
|
+
def __init__(self, callback: Callback) -> None:
|
371
|
+
"""Initialize a new value changed filter."""
|
355
372
|
super().__init__(callback)
|
356
|
-
self._last_update = time.monotonic()
|
357
|
-
self._timeout = seconds
|
358
|
-
self._sum = 0.0
|
359
373
|
|
360
374
|
async def __call__(self, new_value: Any) -> Any:
|
361
375
|
"""Set a new value for the callback."""
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
"Aggregate filter can only be used with numeric values"
|
368
|
-
) from e
|
369
|
-
|
370
|
-
if current_timestamp - self._last_update >= self._timeout:
|
371
|
-
result = await self._callback(self._sum)
|
372
|
-
self._last_update = current_timestamp
|
373
|
-
self._sum = 0.0
|
374
|
-
return result
|
376
|
+
if self._value == UNDEFINED or is_close(self._value, new_value):
|
377
|
+
self._value = (
|
378
|
+
copy(new_value) if isinstance(new_value, Parameter) else new_value
|
379
|
+
)
|
380
|
+
return await self._callback(new_value)
|
375
381
|
|
376
382
|
|
377
|
-
def
|
378
|
-
"""
|
383
|
+
def on_change(callback: Callback) -> _OnChange:
|
384
|
+
"""Create a new value changed filter.
|
379
385
|
|
380
|
-
A callback function will be called
|
381
|
-
|
386
|
+
A callback function will only be called if the value is changed from the
|
387
|
+
previous call.
|
382
388
|
|
383
|
-
:param callback: A callback function to be awaited
|
384
|
-
conditions are fulfilled
|
389
|
+
:param callback: A callback function to be awaited on value change
|
385
390
|
:type callback: Callback
|
386
|
-
:param seconds: A callback will be awaited with a sum of values
|
387
|
-
aggregated over this amount of seconds.
|
388
|
-
:type seconds: float
|
389
391
|
:return: An instance of callable filter
|
390
|
-
:rtype:
|
392
|
+
:rtype: _OnChange
|
391
393
|
"""
|
392
|
-
return
|
394
|
+
return _OnChange(callback)
|
393
395
|
|
394
396
|
|
395
|
-
class
|
396
|
-
"""Represents a
|
397
|
+
class _Throttle(Filter):
|
398
|
+
"""Represents a throttle filter.
|
397
399
|
|
398
|
-
Calls a callback
|
399
|
-
|
400
|
-
returns true.
|
400
|
+
Calls a callback only when certain amount of seconds passed
|
401
|
+
since the last call.
|
401
402
|
"""
|
402
403
|
|
403
|
-
__slots__ = ("
|
404
|
+
__slots__ = ("_last_called", "_timeout")
|
404
405
|
|
405
|
-
|
406
|
+
_last_called: float | None
|
407
|
+
_timeout: float
|
406
408
|
|
407
|
-
def __init__(self, callback: Callback,
|
408
|
-
"""Initialize a new
|
409
|
+
def __init__(self, callback: Callback, seconds: float) -> None:
|
410
|
+
"""Initialize a new throttle filter."""
|
409
411
|
super().__init__(callback)
|
410
|
-
self.
|
412
|
+
self._last_called = None
|
413
|
+
self._timeout = seconds
|
411
414
|
|
412
415
|
async def __call__(self, new_value: Any) -> Any:
|
413
416
|
"""Set a new value for the callback."""
|
414
|
-
|
415
|
-
|
417
|
+
current_timestamp = time.monotonic()
|
418
|
+
if (
|
419
|
+
self._last_called is None
|
420
|
+
or (current_timestamp - self._last_called) >= self._timeout
|
421
|
+
):
|
422
|
+
self._last_called = current_timestamp
|
423
|
+
return await self._callback(new_value)
|
416
424
|
|
417
425
|
|
418
|
-
def
|
419
|
-
"""
|
426
|
+
def throttle(callback: Callback, seconds: float) -> _Throttle:
|
427
|
+
"""Create a new throttle filter.
|
420
428
|
|
421
|
-
A callback function will be called
|
422
|
-
|
423
|
-
returns true.
|
429
|
+
A callback function will only be called once a certain amount of
|
430
|
+
seconds passed since the last call.
|
424
431
|
|
425
|
-
:param callback: A callback function
|
426
|
-
filter
|
432
|
+
:param callback: A callback function that will be awaited once
|
433
|
+
filter conditions are fulfilled
|
427
434
|
:type callback: Callback
|
428
|
-
:param
|
429
|
-
|
430
|
-
:type
|
435
|
+
:param seconds: A callback will be awaited at most once per
|
436
|
+
this amount of seconds
|
437
|
+
:type seconds: float
|
431
438
|
:return: An instance of callable filter
|
432
|
-
:rtype:
|
439
|
+
:rtype: _Throttle
|
433
440
|
"""
|
434
|
-
return
|
441
|
+
return _Throttle(callback, seconds)
|
442
|
+
|
443
|
+
|
444
|
+
__all__ = [
|
445
|
+
"Filter",
|
446
|
+
"aggregate",
|
447
|
+
"clamp",
|
448
|
+
"custom",
|
449
|
+
"debounce",
|
450
|
+
"delta",
|
451
|
+
"on_change",
|
452
|
+
"throttle",
|
453
|
+
]
|
pyplumio/frames/__init__.py
CHANGED
@@ -30,9 +30,9 @@ if TYPE_CHECKING:
|
|
30
30
|
from pyplumio.devices import PhysicalDevice
|
31
31
|
|
32
32
|
|
33
|
-
def bcc(
|
33
|
+
def bcc(buffer: bytes) -> int:
|
34
34
|
"""Return a block check character."""
|
35
|
-
return reduce(lambda x, y: x ^ y,
|
35
|
+
return reduce(lambda x, y: x ^ y, buffer)
|
36
36
|
|
37
37
|
|
38
38
|
@cache
|
@@ -121,12 +121,12 @@ class Frame(ABC):
|
|
121
121
|
self._message,
|
122
122
|
self._data,
|
123
123
|
) == (
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
124
|
+
other.recipient,
|
125
|
+
other.sender,
|
126
|
+
other.econet_type,
|
127
|
+
other.econet_version,
|
128
|
+
other._message,
|
129
|
+
other._data,
|
130
130
|
)
|
131
131
|
|
132
132
|
return NotImplemented
|
@@ -280,3 +280,15 @@ class Message(Response):
|
|
280
280
|
"""Represents a message."""
|
281
281
|
|
282
282
|
__slots__ = ()
|
283
|
+
|
284
|
+
|
285
|
+
__all__ = [
|
286
|
+
"Frame",
|
287
|
+
"Request",
|
288
|
+
"Response",
|
289
|
+
"Message",
|
290
|
+
"DataFrameDescription",
|
291
|
+
"bcc",
|
292
|
+
"is_known_frame_type",
|
293
|
+
"get_frame_handler",
|
294
|
+
]
|
pyplumio/frames/messages.py
CHANGED
pyplumio/frames/requests.py
CHANGED
@@ -260,3 +260,24 @@ class UIDRequest(Request):
|
|
260
260
|
__slots__ = ()
|
261
261
|
|
262
262
|
frame_type = FrameType.REQUEST_UID
|
263
|
+
|
264
|
+
|
265
|
+
__all__ = [
|
266
|
+
"AlertsRequest",
|
267
|
+
"CheckDeviceRequest",
|
268
|
+
"EcomaxControlRequest",
|
269
|
+
"EcomaxParametersRequest",
|
270
|
+
"MixerParametersRequest",
|
271
|
+
"PasswordRequest",
|
272
|
+
"ProgramVersionRequest",
|
273
|
+
"RegulatorDataSchemaRequest",
|
274
|
+
"SchedulesRequest",
|
275
|
+
"SetEcomaxParameterRequest",
|
276
|
+
"SetMixerParameterRequest",
|
277
|
+
"SetScheduleRequest",
|
278
|
+
"SetThermostatParameterRequest",
|
279
|
+
"StartMasterRequest",
|
280
|
+
"StopMasterRequest",
|
281
|
+
"ThermostatParametersRequest",
|
282
|
+
"UIDRequest",
|
283
|
+
]
|
pyplumio/frames/responses.py
CHANGED
@@ -221,3 +221,21 @@ class UIDResponse(Response):
|
|
221
221
|
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
222
222
|
"""Decode a frame message."""
|
223
223
|
return ProductInfoStructure(self).decode(message)[0]
|
224
|
+
|
225
|
+
|
226
|
+
__all__ = [
|
227
|
+
"AlertsResponse",
|
228
|
+
"DeviceAvailableResponse",
|
229
|
+
"EcomaxControlResponse",
|
230
|
+
"EcomaxParametersResponse",
|
231
|
+
"MixerParametersResponse",
|
232
|
+
"PasswordResponse",
|
233
|
+
"ProgramVersionResponse",
|
234
|
+
"RegulatorDataSchemaResponse",
|
235
|
+
"SchedulesResponse",
|
236
|
+
"SetEcomaxParameterResponse",
|
237
|
+
"SetMixerParameterResponse",
|
238
|
+
"SetThermostatParameterResponse",
|
239
|
+
"ThermostatParametersResponse",
|
240
|
+
"UIDResponse",
|
241
|
+
]
|