PyPlumIO 0.6.1__py3-none-any.whl → 0.6.2__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 -1
- pyplumio/_version.py +2 -2
- pyplumio/const.py +0 -5
- pyplumio/data_types.py +2 -2
- pyplumio/devices/__init__.py +23 -5
- pyplumio/devices/ecomax.py +30 -53
- pyplumio/devices/ecoster.py +2 -3
- pyplumio/filters.py +199 -136
- pyplumio/frames/__init__.py +101 -15
- pyplumio/frames/messages.py +8 -65
- pyplumio/frames/requests.py +38 -38
- pyplumio/frames/responses.py +30 -86
- pyplumio/helpers/async_cache.py +13 -8
- pyplumio/helpers/event_manager.py +24 -18
- pyplumio/helpers/factory.py +0 -3
- pyplumio/parameters/__init__.py +38 -35
- pyplumio/protocol.py +14 -8
- pyplumio/structures/alerts.py +2 -2
- pyplumio/structures/ecomax_parameters.py +1 -1
- pyplumio/structures/frame_versions.py +3 -2
- pyplumio/structures/mixer_parameters.py +5 -3
- pyplumio/structures/network_info.py +1 -0
- pyplumio/structures/product_info.py +1 -1
- pyplumio/structures/program_version.py +2 -2
- pyplumio/structures/schedules.py +8 -40
- pyplumio/structures/sensor_data.py +498 -0
- pyplumio/structures/thermostat_parameters.py +7 -4
- pyplumio/utils.py +41 -4
- {pyplumio-0.6.1.dist-info → pyplumio-0.6.2.dist-info}/METADATA +4 -4
- pyplumio-0.6.2.dist-info/RECORD +50 -0
- pyplumio/structures/boiler_load.py +0 -32
- pyplumio/structures/boiler_power.py +0 -33
- pyplumio/structures/fan_power.py +0 -33
- pyplumio/structures/fuel_consumption.py +0 -36
- pyplumio/structures/fuel_level.py +0 -39
- pyplumio/structures/lambda_sensor.py +0 -57
- pyplumio/structures/mixer_sensors.py +0 -80
- pyplumio/structures/modules.py +0 -102
- pyplumio/structures/output_flags.py +0 -47
- pyplumio/structures/outputs.py +0 -88
- pyplumio/structures/pending_alerts.py +0 -28
- pyplumio/structures/statuses.py +0 -52
- pyplumio/structures/temperatures.py +0 -94
- pyplumio/structures/thermostat_sensors.py +0 -106
- pyplumio-0.6.1.dist-info/RECORD +0 -63
- {pyplumio-0.6.1.dist-info → pyplumio-0.6.2.dist-info}/WHEEL +0 -0
- {pyplumio-0.6.1.dist-info → pyplumio-0.6.2.dist-info}/licenses/LICENSE +0 -0
- {pyplumio-0.6.1.dist-info → pyplumio-0.6.2.dist-info}/top_level.txt +0 -0
pyplumio/frames/__init__.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
from abc import ABC, abstractmethod
|
6
|
-
from
|
6
|
+
from collections.abc import Callable
|
7
7
|
from functools import cache, reduce
|
8
8
|
import struct
|
9
9
|
from typing import TYPE_CHECKING, Any, ClassVar, Final, TypeVar
|
@@ -13,6 +13,9 @@ from pyplumio.exceptions import UnknownFrameError
|
|
13
13
|
from pyplumio.helpers.factory import create_instance
|
14
14
|
from pyplumio.utils import ensure_dict, to_camelcase
|
15
15
|
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from pyplumio.structures import Structure
|
18
|
+
|
16
19
|
FRAME_START: Final = 0x68
|
17
20
|
FRAME_END: Final = 0x16
|
18
21
|
|
@@ -60,15 +63,7 @@ def get_frame_handler(frame_type: int) -> str:
|
|
60
63
|
return f"frames.{module.lower()}s.{type_name}{module.capitalize()}"
|
61
64
|
|
62
65
|
|
63
|
-
|
64
|
-
class DataFrameDescription:
|
65
|
-
"""Describes what data is provided by the frame."""
|
66
|
-
|
67
|
-
frame_type: FrameType
|
68
|
-
provides: str
|
69
|
-
|
70
|
-
|
71
|
-
FrameT = TypeVar("FrameT", bound="Frame")
|
66
|
+
_FrameT = TypeVar("_FrameT", bound="Frame")
|
72
67
|
|
73
68
|
|
74
69
|
class Frame(ABC):
|
@@ -235,17 +230,17 @@ class Frame(ABC):
|
|
235
230
|
return bytes(data)
|
236
231
|
|
237
232
|
@classmethod
|
238
|
-
async def create(cls: type[
|
233
|
+
async def create(cls: type[_FrameT], frame_type: int, **kwargs: Any) -> _FrameT:
|
239
234
|
"""Create a frame handler object from frame type."""
|
240
235
|
return await create_instance(get_frame_handler(frame_type), cls=cls, **kwargs)
|
241
236
|
|
242
237
|
@abstractmethod
|
243
238
|
def create_message(self, data: dict[str, Any]) -> bytearray:
|
244
|
-
"""Create frame message."""
|
239
|
+
"""Create a frame message."""
|
245
240
|
|
246
241
|
@abstractmethod
|
247
242
|
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
248
|
-
"""Decode frame message."""
|
243
|
+
"""Decode a frame message."""
|
249
244
|
|
250
245
|
|
251
246
|
class Request(Frame):
|
@@ -286,13 +281,104 @@ class Message(Response):
|
|
286
281
|
__slots__ = ()
|
287
282
|
|
288
283
|
|
284
|
+
class Structured(Frame):
|
285
|
+
"""Represents a frame that has known structure."""
|
286
|
+
|
287
|
+
__slots__ = ("_structures",)
|
288
|
+
|
289
|
+
container: ClassVar[str]
|
290
|
+
structures: ClassVar[list[type[Structure]]]
|
291
|
+
|
292
|
+
_structures: list[Structure]
|
293
|
+
|
294
|
+
def __init__(
|
295
|
+
self,
|
296
|
+
recipient: DeviceType = DeviceType.ALL,
|
297
|
+
sender: DeviceType = DeviceType.ECONET,
|
298
|
+
econet_type: int = ECONET_TYPE,
|
299
|
+
econet_version: int = ECONET_VERSION,
|
300
|
+
message: bytearray | None = None,
|
301
|
+
data: dict[str, Any] | None = None,
|
302
|
+
**kwargs: Any,
|
303
|
+
) -> None:
|
304
|
+
"""Initialize a new structured frame."""
|
305
|
+
if hasattr(self, "structures"):
|
306
|
+
self._structures = [structure(self) for structure in self.structures]
|
307
|
+
|
308
|
+
super().__init__(
|
309
|
+
recipient, sender, econet_type, econet_version, message, data, **kwargs
|
310
|
+
)
|
311
|
+
|
312
|
+
def create_message(self, data: dict[str, Any]) -> bytearray:
|
313
|
+
"""Create frame message."""
|
314
|
+
message = bytearray()
|
315
|
+
for structure in self._structures:
|
316
|
+
message += structure.encode(data)
|
317
|
+
|
318
|
+
return message
|
319
|
+
|
320
|
+
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
321
|
+
"""Decode frame message."""
|
322
|
+
data: dict[str, Any] = {}
|
323
|
+
offset = 0
|
324
|
+
for structure in self._structures:
|
325
|
+
data, offset = structure.decode(message, offset, data)
|
326
|
+
|
327
|
+
if hasattr(self, "container"):
|
328
|
+
return {self.container: data}
|
329
|
+
|
330
|
+
return data
|
331
|
+
|
332
|
+
|
333
|
+
def frame_handler(
|
334
|
+
frame_type: FrameType, structure: type[Structure] | None = None
|
335
|
+
) -> Callable[[type[_FrameT]], type[_FrameT]]:
|
336
|
+
"""Specify frame type for the frame class."""
|
337
|
+
|
338
|
+
def wrapper(cls: type[_FrameT]) -> type[_FrameT]:
|
339
|
+
"""Wrap the frame class."""
|
340
|
+
setattr(cls, "frame_type", frame_type)
|
341
|
+
if structure and issubclass(cls, Structured):
|
342
|
+
setattr(cls, "structures", (structure,))
|
343
|
+
|
344
|
+
return cls
|
345
|
+
|
346
|
+
return wrapper
|
347
|
+
|
348
|
+
|
349
|
+
_StructuredT = TypeVar("_StructuredT", bound=Structured)
|
350
|
+
|
351
|
+
|
352
|
+
def contains(
|
353
|
+
*structures: type[Structure], container: str | None = None
|
354
|
+
) -> Callable[[type[_StructuredT]], type[_StructuredT]]:
|
355
|
+
"""Decorate frame class with structure.
|
356
|
+
|
357
|
+
Indicate which structures need to be used for encoding/decoding
|
358
|
+
the frame as well as provide a way to wrap output data under
|
359
|
+
a single key.
|
360
|
+
"""
|
361
|
+
|
362
|
+
def wrapper(cls: type[_StructuredT]) -> type[_StructuredT]:
|
363
|
+
"""Wrap the frame class."""
|
364
|
+
setattr(cls, "structures", structures)
|
365
|
+
if container:
|
366
|
+
setattr(cls, "container", container)
|
367
|
+
|
368
|
+
return cls
|
369
|
+
|
370
|
+
return wrapper
|
371
|
+
|
372
|
+
|
289
373
|
__all__ = [
|
290
374
|
"Frame",
|
291
375
|
"Request",
|
292
376
|
"Response",
|
293
377
|
"Message",
|
294
|
-
"
|
378
|
+
"Structured",
|
295
379
|
"bcc",
|
296
|
-
"
|
380
|
+
"contains",
|
381
|
+
"frame_handler",
|
297
382
|
"get_frame_handler",
|
383
|
+
"is_known_frame_type",
|
298
384
|
]
|
pyplumio/frames/messages.py
CHANGED
@@ -2,83 +2,26 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from
|
6
|
-
from
|
7
|
-
|
8
|
-
from pyplumio.const import (
|
9
|
-
ATTR_SENSORS,
|
10
|
-
ATTR_STATE,
|
11
|
-
ATTR_THERMOSTAT,
|
12
|
-
ATTR_TRANSMISSION,
|
13
|
-
DeviceState,
|
14
|
-
FrameType,
|
15
|
-
)
|
16
|
-
from pyplumio.frames import Message
|
17
|
-
from pyplumio.structures.boiler_load import BoilerLoadStructure
|
18
|
-
from pyplumio.structures.boiler_power import BoilerPowerStructure
|
19
|
-
from pyplumio.structures.fan_power import FanPowerStructure
|
5
|
+
from pyplumio.const import ATTR_SENSORS, FrameType
|
6
|
+
from pyplumio.frames import Message, Structured, contains, frame_handler
|
20
7
|
from pyplumio.structures.frame_versions import FrameVersionsStructure
|
21
|
-
from pyplumio.structures.fuel_consumption import FuelConsumptionStructure
|
22
|
-
from pyplumio.structures.fuel_level import FuelLevelStructure
|
23
|
-
from pyplumio.structures.lambda_sensor import LambdaSensorStructure
|
24
|
-
from pyplumio.structures.mixer_sensors import MixerSensorsStructure
|
25
|
-
from pyplumio.structures.modules import ModulesStructure
|
26
|
-
from pyplumio.structures.output_flags import OutputFlagsStructure
|
27
|
-
from pyplumio.structures.outputs import OutputsStructure
|
28
|
-
from pyplumio.structures.pending_alerts import PendingAlertsStructure
|
29
8
|
from pyplumio.structures.regulator_data import RegulatorDataStructure
|
30
|
-
from pyplumio.structures.
|
31
|
-
from pyplumio.structures.temperatures import TemperaturesStructure
|
32
|
-
from pyplumio.structures.thermostat_sensors import ThermostatSensorsStructure
|
9
|
+
from pyplumio.structures.sensor_data import SensorDataStructure
|
33
10
|
|
34
11
|
|
35
|
-
|
12
|
+
@frame_handler(FrameType.MESSAGE_REGULATOR_DATA, structure=RegulatorDataStructure)
|
13
|
+
class RegulatorDataMessage(Structured, Message):
|
36
14
|
"""Represents a regulator data message."""
|
37
15
|
|
38
16
|
__slots__ = ()
|
39
17
|
|
40
|
-
frame_type = FrameType.MESSAGE_REGULATOR_DATA
|
41
|
-
|
42
|
-
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
43
|
-
"""Decode a frame message."""
|
44
|
-
return RegulatorDataStructure(self).decode(message)[0]
|
45
|
-
|
46
18
|
|
47
|
-
|
19
|
+
@frame_handler(FrameType.MESSAGE_SENSOR_DATA)
|
20
|
+
@contains(FrameVersionsStructure, SensorDataStructure, container=ATTR_SENSORS)
|
21
|
+
class SensorDataMessage(Structured, Message):
|
48
22
|
"""Represents a sensor data message."""
|
49
23
|
|
50
24
|
__slots__ = ()
|
51
25
|
|
52
|
-
frame_type = FrameType.MESSAGE_SENSOR_DATA
|
53
|
-
|
54
|
-
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
55
|
-
"""Decode a frame message."""
|
56
|
-
sensors, offset = FrameVersionsStructure(self).decode(message, offset=0)
|
57
|
-
sensors[ATTR_STATE] = message[offset]
|
58
|
-
sensors, offset = OutputsStructure(self).decode(message, offset + 1, sensors)
|
59
|
-
sensors, offset = OutputFlagsStructure(self).decode(message, offset, sensors)
|
60
|
-
sensors, offset = TemperaturesStructure(self).decode(message, offset, sensors)
|
61
|
-
sensors, offset = StatusesStructure(self).decode(message, offset, sensors)
|
62
|
-
sensors, offset = PendingAlertsStructure(self).decode(message, offset, sensors)
|
63
|
-
sensors, offset = FuelLevelStructure(self).decode(message, offset, sensors)
|
64
|
-
sensors[ATTR_TRANSMISSION] = message[offset]
|
65
|
-
sensors, offset = FanPowerStructure(self).decode(message, offset + 1, sensors)
|
66
|
-
sensors, offset = BoilerLoadStructure(self).decode(message, offset, sensors)
|
67
|
-
sensors, offset = BoilerPowerStructure(self).decode(message, offset, sensors)
|
68
|
-
sensors, offset = FuelConsumptionStructure(self).decode(
|
69
|
-
message, offset, sensors
|
70
|
-
)
|
71
|
-
sensors[ATTR_THERMOSTAT] = message[offset]
|
72
|
-
sensors, offset = ModulesStructure(self).decode(message, offset + 1, sensors)
|
73
|
-
sensors, offset = LambdaSensorStructure(self).decode(message, offset, sensors)
|
74
|
-
sensors, offset = ThermostatSensorsStructure(self).decode(
|
75
|
-
message, offset, sensors
|
76
|
-
)
|
77
|
-
sensors, offset = MixerSensorsStructure(self).decode(message, offset, sensors)
|
78
|
-
with suppress(ValueError):
|
79
|
-
sensors[ATTR_STATE] = DeviceState(sensors[ATTR_STATE])
|
80
|
-
|
81
|
-
return {ATTR_SENSORS: sensors}
|
82
|
-
|
83
26
|
|
84
27
|
__all__ = ["RegulatorDataMessage", "SensorDataMessage"]
|
pyplumio/frames/requests.py
CHANGED
@@ -2,24 +2,30 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from typing import Any
|
5
|
+
from typing import Any, cast
|
6
6
|
|
7
7
|
from pyplumio.const import (
|
8
8
|
ATTR_COUNT,
|
9
9
|
ATTR_DEVICE_INDEX,
|
10
10
|
ATTR_INDEX,
|
11
11
|
ATTR_OFFSET,
|
12
|
+
ATTR_PARAMETER,
|
13
|
+
ATTR_SCHEDULE,
|
12
14
|
ATTR_SIZE,
|
13
15
|
ATTR_START,
|
16
|
+
ATTR_SWITCH,
|
17
|
+
ATTR_TYPE,
|
14
18
|
ATTR_VALUE,
|
15
19
|
FrameType,
|
16
20
|
)
|
17
21
|
from pyplumio.exceptions import FrameDataError
|
18
|
-
from pyplumio.frames import Request, Response
|
22
|
+
from pyplumio.frames import Request, Response, frame_handler
|
19
23
|
from pyplumio.frames.responses import DeviceAvailableResponse, ProgramVersionResponse
|
20
|
-
from pyplumio.structures.schedules import
|
24
|
+
from pyplumio.structures.schedules import SCHEDULES
|
25
|
+
from pyplumio.utils import join_bits
|
21
26
|
|
22
27
|
|
28
|
+
@frame_handler(FrameType.REQUEST_ALERTS)
|
23
29
|
class AlertsRequest(Request):
|
24
30
|
"""Represents an alerts request.
|
25
31
|
|
@@ -29,25 +35,23 @@ class AlertsRequest(Request):
|
|
29
35
|
|
30
36
|
__slots__ = ()
|
31
37
|
|
32
|
-
frame_type = FrameType.REQUEST_ALERTS
|
33
|
-
|
34
38
|
def create_message(self, data: dict[str, Any]) -> bytearray:
|
35
39
|
"""Create a frame message."""
|
36
40
|
return bytearray([data.get(ATTR_START, 0), data.get(ATTR_COUNT, 10)])
|
37
41
|
|
38
42
|
|
43
|
+
@frame_handler(FrameType.REQUEST_CHECK_DEVICE)
|
39
44
|
class CheckDeviceRequest(Request):
|
40
45
|
"""Represents a check device request."""
|
41
46
|
|
42
47
|
__slots__ = ()
|
43
48
|
|
44
|
-
frame_type = FrameType.REQUEST_CHECK_DEVICE
|
45
|
-
|
46
49
|
def response(self, **kwargs: Any) -> Response | None:
|
47
50
|
"""Return a response frame."""
|
48
51
|
return DeviceAvailableResponse(recipient=self.sender, **kwargs)
|
49
52
|
|
50
53
|
|
54
|
+
@frame_handler(FrameType.REQUEST_ECOMAX_CONTROL)
|
51
55
|
class EcomaxControlRequest(Request):
|
52
56
|
"""Represents an ecoMAX control request.
|
53
57
|
|
@@ -57,8 +61,6 @@ class EcomaxControlRequest(Request):
|
|
57
61
|
|
58
62
|
__slots__ = ()
|
59
63
|
|
60
|
-
frame_type = FrameType.REQUEST_ECOMAX_CONTROL
|
61
|
-
|
62
64
|
def create_message(self, data: dict[str, Any]) -> bytearray:
|
63
65
|
"""Create a frame message."""
|
64
66
|
try:
|
@@ -67,6 +69,7 @@ class EcomaxControlRequest(Request):
|
|
67
69
|
raise FrameDataError from e
|
68
70
|
|
69
71
|
|
72
|
+
@frame_handler(FrameType.REQUEST_ECOMAX_PARAMETERS)
|
70
73
|
class EcomaxParametersRequest(Request):
|
71
74
|
"""Represents an ecoMAX parameters request.
|
72
75
|
|
@@ -75,13 +78,12 @@ class EcomaxParametersRequest(Request):
|
|
75
78
|
|
76
79
|
__slots__ = ()
|
77
80
|
|
78
|
-
frame_type = FrameType.REQUEST_ECOMAX_PARAMETERS
|
79
|
-
|
80
81
|
def create_message(self, data: dict[str, Any]) -> bytearray:
|
81
82
|
"""Create a frame message."""
|
82
83
|
return bytearray([data.get(ATTR_COUNT, 255), data.get(ATTR_START, 0)])
|
83
84
|
|
84
85
|
|
86
|
+
@frame_handler(FrameType.REQUEST_MIXER_PARAMETERS)
|
85
87
|
class MixerParametersRequest(Request):
|
86
88
|
"""Represents a mixer parameters request.
|
87
89
|
|
@@ -91,49 +93,44 @@ class MixerParametersRequest(Request):
|
|
91
93
|
|
92
94
|
__slots__ = ()
|
93
95
|
|
94
|
-
frame_type = FrameType.REQUEST_MIXER_PARAMETERS
|
95
|
-
|
96
96
|
def create_message(self, data: dict[str, Any]) -> bytearray:
|
97
97
|
"""Create a frame message."""
|
98
98
|
return bytearray([data.get(ATTR_COUNT, 255), data.get(ATTR_START, 0)])
|
99
99
|
|
100
100
|
|
101
|
+
@frame_handler(FrameType.REQUEST_PASSWORD)
|
101
102
|
class PasswordRequest(Request):
|
102
103
|
"""Represents a password request."""
|
103
104
|
|
104
105
|
__slots__ = ()
|
105
106
|
|
106
|
-
frame_type = FrameType.REQUEST_PASSWORD
|
107
|
-
|
108
107
|
|
108
|
+
@frame_handler(FrameType.REQUEST_PROGRAM_VERSION)
|
109
109
|
class ProgramVersionRequest(Request):
|
110
110
|
"""Represents a program version request."""
|
111
111
|
|
112
112
|
__slots__ = ()
|
113
113
|
|
114
|
-
frame_type = FrameType.REQUEST_PROGRAM_VERSION
|
115
|
-
|
116
114
|
def response(self, **kwargs: Any) -> Response | None:
|
117
115
|
"""Return a response frame."""
|
118
116
|
return ProgramVersionResponse(recipient=self.sender, **kwargs)
|
119
117
|
|
120
118
|
|
119
|
+
@frame_handler(FrameType.REQUEST_REGULATOR_DATA_SCHEMA)
|
121
120
|
class RegulatorDataSchemaRequest(Request):
|
122
121
|
"""Represents regulator data schema request."""
|
123
122
|
|
124
123
|
__slots__ = ()
|
125
124
|
|
126
|
-
frame_type = FrameType.REQUEST_REGULATOR_DATA_SCHEMA
|
127
|
-
|
128
125
|
|
126
|
+
@frame_handler(FrameType.REQUEST_SCHEDULES)
|
129
127
|
class SchedulesRequest(Request):
|
130
128
|
"""Represents a schedules request."""
|
131
129
|
|
132
130
|
__slots__ = ()
|
133
131
|
|
134
|
-
frame_type = FrameType.REQUEST_SCHEDULES
|
135
|
-
|
136
132
|
|
133
|
+
@frame_handler(FrameType.REQUEST_SET_ECOMAX_PARAMETER)
|
137
134
|
class SetEcomaxParameterRequest(Request):
|
138
135
|
"""Represents a request to set an ecoMAX parameter.
|
139
136
|
|
@@ -142,8 +139,6 @@ class SetEcomaxParameterRequest(Request):
|
|
142
139
|
|
143
140
|
__slots__ = ()
|
144
141
|
|
145
|
-
frame_type = FrameType.REQUEST_SET_ECOMAX_PARAMETER
|
146
|
-
|
147
142
|
def create_message(self, data: dict[str, Any]) -> bytearray:
|
148
143
|
"""Create a frame message."""
|
149
144
|
try:
|
@@ -152,6 +147,7 @@ class SetEcomaxParameterRequest(Request):
|
|
152
147
|
raise FrameDataError from e
|
153
148
|
|
154
149
|
|
150
|
+
@frame_handler(FrameType.REQUEST_SET_MIXER_PARAMETER)
|
155
151
|
class SetMixerParameterRequest(Request):
|
156
152
|
"""Represents a request to set a mixer parameter.
|
157
153
|
|
@@ -160,8 +156,6 @@ class SetMixerParameterRequest(Request):
|
|
160
156
|
|
161
157
|
__slots__ = ()
|
162
158
|
|
163
|
-
frame_type = FrameType.REQUEST_SET_MIXER_PARAMETER
|
164
|
-
|
165
159
|
def create_message(self, data: dict[str, Any]) -> bytearray:
|
166
160
|
"""Create a frame message."""
|
167
161
|
try:
|
@@ -172,18 +166,30 @@ class SetMixerParameterRequest(Request):
|
|
172
166
|
raise FrameDataError from e
|
173
167
|
|
174
168
|
|
169
|
+
@frame_handler(FrameType.REQUEST_SET_SCHEDULE)
|
175
170
|
class SetScheduleRequest(Request):
|
176
171
|
"""Represents a request to set a schedule."""
|
177
172
|
|
178
173
|
__slots__ = ()
|
179
174
|
|
180
|
-
frame_type = FrameType.REQUEST_SET_SCHEDULE
|
181
|
-
|
182
175
|
def create_message(self, data: dict[str, Any]) -> bytearray:
|
183
176
|
"""Create a frame message."""
|
184
|
-
|
177
|
+
message = b"\1"
|
178
|
+
try:
|
179
|
+
schedule_type = SCHEDULES.index(data[ATTR_TYPE])
|
180
|
+
message += schedule_type.to_bytes(length=1, byteorder="little")
|
181
|
+
message += int(data[ATTR_SWITCH]).to_bytes(length=1, byteorder="little")
|
182
|
+
message += int(data[ATTR_PARAMETER]).to_bytes(length=1, byteorder="little")
|
183
|
+
schedule = cast(list[list[bool]], data[ATTR_SCHEDULE])
|
184
|
+
except (KeyError, ValueError) as e:
|
185
|
+
raise FrameDataError from e
|
186
|
+
|
187
|
+
return bytearray(message) + bytearray(
|
188
|
+
join_bits(day[i : i + 8]) for day in schedule for i in range(0, len(day), 8)
|
189
|
+
)
|
185
190
|
|
186
191
|
|
192
|
+
@frame_handler(FrameType.REQUEST_SET_THERMOSTAT_PARAMETER)
|
187
193
|
class SetThermostatParameterRequest(Request):
|
188
194
|
"""Represents a request to set a thermostat parameter.
|
189
195
|
|
@@ -200,8 +206,6 @@ class SetThermostatParameterRequest(Request):
|
|
200
206
|
|
201
207
|
__slots__ = ()
|
202
208
|
|
203
|
-
frame_type = FrameType.REQUEST_SET_THERMOSTAT_PARAMETER
|
204
|
-
|
205
209
|
def create_message(self, data: dict[str, Any]) -> bytearray:
|
206
210
|
"""Create a frame message."""
|
207
211
|
try:
|
@@ -214,6 +218,7 @@ class SetThermostatParameterRequest(Request):
|
|
214
218
|
raise FrameDataError from e
|
215
219
|
|
216
220
|
|
221
|
+
@frame_handler(FrameType.REQUEST_START_MASTER)
|
217
222
|
class StartMasterRequest(Request):
|
218
223
|
"""Represents a request to become a master.
|
219
224
|
|
@@ -223,9 +228,8 @@ class StartMasterRequest(Request):
|
|
223
228
|
|
224
229
|
__slots__ = ()
|
225
230
|
|
226
|
-
frame_type = FrameType.REQUEST_START_MASTER
|
227
|
-
|
228
231
|
|
232
|
+
@frame_handler(FrameType.REQUEST_STOP_MASTER)
|
229
233
|
class StopMasterRequest(Request):
|
230
234
|
"""Represents a request to stop being a master.
|
231
235
|
|
@@ -235,9 +239,8 @@ class StopMasterRequest(Request):
|
|
235
239
|
|
236
240
|
__slots__ = ()
|
237
241
|
|
238
|
-
frame_type = FrameType.REQUEST_STOP_MASTER
|
239
|
-
|
240
242
|
|
243
|
+
@frame_handler(FrameType.REQUEST_THERMOSTAT_PARAMETERS)
|
241
244
|
class ThermostatParametersRequest(Request):
|
242
245
|
"""Represents a thermostat parameters request.
|
243
246
|
|
@@ -247,20 +250,17 @@ class ThermostatParametersRequest(Request):
|
|
247
250
|
|
248
251
|
__slots__ = ()
|
249
252
|
|
250
|
-
frame_type = FrameType.REQUEST_THERMOSTAT_PARAMETERS
|
251
|
-
|
252
253
|
def create_message(self, data: dict[str, Any]) -> bytearray:
|
253
254
|
"""Create a frame message."""
|
254
255
|
return bytearray([data.get(ATTR_COUNT, 255), data.get(ATTR_START, 0)])
|
255
256
|
|
256
257
|
|
258
|
+
@frame_handler(FrameType.REQUEST_UID)
|
257
259
|
class UIDRequest(Request):
|
258
260
|
"""Represents an UID request."""
|
259
261
|
|
260
262
|
__slots__ = ()
|
261
263
|
|
262
|
-
frame_type = FrameType.REQUEST_UID
|
263
|
-
|
264
264
|
|
265
265
|
__all__ = [
|
266
266
|
"AlertsRequest",
|