PyPlumIO 0.5.42__py3-none-any.whl → 0.5.44__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.
Files changed (61) hide show
  1. pyplumio/__init__.py +3 -2
  2. pyplumio/_version.py +2 -2
  3. pyplumio/connection.py +14 -14
  4. pyplumio/const.py +8 -3
  5. pyplumio/{helpers/data_types.py → data_types.py} +23 -21
  6. pyplumio/devices/__init__.py +42 -41
  7. pyplumio/devices/ecomax.py +202 -174
  8. pyplumio/devices/ecoster.py +5 -0
  9. pyplumio/devices/mixer.py +24 -34
  10. pyplumio/devices/thermostat.py +24 -31
  11. pyplumio/filters.py +188 -147
  12. pyplumio/frames/__init__.py +20 -8
  13. pyplumio/frames/messages.py +3 -0
  14. pyplumio/frames/requests.py +21 -0
  15. pyplumio/frames/responses.py +18 -0
  16. pyplumio/helpers/async_cache.py +48 -0
  17. pyplumio/helpers/event_manager.py +58 -3
  18. pyplumio/helpers/factory.py +5 -2
  19. pyplumio/helpers/schedule.py +8 -5
  20. pyplumio/helpers/task_manager.py +3 -0
  21. pyplumio/helpers/timeout.py +7 -6
  22. pyplumio/helpers/uid.py +8 -5
  23. pyplumio/{helpers/parameter.py → parameters/__init__.py} +105 -5
  24. pyplumio/parameters/ecomax.py +868 -0
  25. pyplumio/parameters/mixer.py +245 -0
  26. pyplumio/parameters/thermostat.py +197 -0
  27. pyplumio/protocol.py +21 -10
  28. pyplumio/stream.py +3 -0
  29. pyplumio/structures/__init__.py +3 -0
  30. pyplumio/structures/alerts.py +9 -6
  31. pyplumio/structures/boiler_load.py +3 -0
  32. pyplumio/structures/boiler_power.py +4 -1
  33. pyplumio/structures/ecomax_parameters.py +6 -800
  34. pyplumio/structures/fan_power.py +4 -1
  35. pyplumio/structures/frame_versions.py +4 -1
  36. pyplumio/structures/fuel_consumption.py +4 -1
  37. pyplumio/structures/fuel_level.py +3 -0
  38. pyplumio/structures/lambda_sensor.py +9 -1
  39. pyplumio/structures/mixer_parameters.py +8 -230
  40. pyplumio/structures/mixer_sensors.py +10 -1
  41. pyplumio/structures/modules.py +14 -0
  42. pyplumio/structures/network_info.py +12 -1
  43. pyplumio/structures/output_flags.py +10 -1
  44. pyplumio/structures/outputs.py +22 -1
  45. pyplumio/structures/pending_alerts.py +3 -0
  46. pyplumio/structures/product_info.py +6 -3
  47. pyplumio/structures/program_version.py +3 -0
  48. pyplumio/structures/regulator_data.py +5 -2
  49. pyplumio/structures/regulator_data_schema.py +4 -1
  50. pyplumio/structures/schedules.py +18 -1
  51. pyplumio/structures/statuses.py +9 -0
  52. pyplumio/structures/temperatures.py +23 -1
  53. pyplumio/structures/thermostat_parameters.py +18 -184
  54. pyplumio/structures/thermostat_sensors.py +10 -1
  55. pyplumio/utils.py +14 -12
  56. {pyplumio-0.5.42.dist-info → pyplumio-0.5.44.dist-info}/METADATA +32 -17
  57. pyplumio-0.5.44.dist-info/RECORD +64 -0
  58. {pyplumio-0.5.42.dist-info → pyplumio-0.5.44.dist-info}/WHEEL +1 -1
  59. pyplumio-0.5.42.dist-info/RECORD +0 -60
  60. {pyplumio-0.5.42.dist-info → pyplumio-0.5.44.dist-info}/licenses/LICENSE +0 -0
  61. {pyplumio-0.5.42.dist-info → pyplumio-0.5.44.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,245 @@
1
+ """Contains mixer parameter descriptors."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from functools import cache
7
+ from typing import TYPE_CHECKING
8
+
9
+ from dataslots import dataslots
10
+
11
+ from pyplumio.const import (
12
+ ATTR_DEVICE_INDEX,
13
+ ATTR_INDEX,
14
+ ATTR_VALUE,
15
+ FrameType,
16
+ ProductType,
17
+ UnitOfMeasurement,
18
+ )
19
+ from pyplumio.frames import Request
20
+ from pyplumio.parameters import (
21
+ OffsetNumber,
22
+ OffsetNumberDescription,
23
+ Parameter,
24
+ ParameterDescription,
25
+ Switch,
26
+ SwitchDescription,
27
+ )
28
+ from pyplumio.structures.product_info import ProductInfo
29
+
30
+ if TYPE_CHECKING:
31
+ from pyplumio.devices.mixer import Mixer
32
+
33
+
34
+ @dataclass
35
+ class MixerParameterDescription(ParameterDescription):
36
+ """Represents a mixer parameter description."""
37
+
38
+ __slots__ = ()
39
+
40
+
41
+ class MixerParameter(Parameter):
42
+ """Represent a mixer parameter."""
43
+
44
+ __slots__ = ()
45
+
46
+ device: Mixer
47
+ description: MixerParameterDescription
48
+
49
+ async def create_request(self) -> Request:
50
+ """Create a request to change the parameter."""
51
+ return await Request.create(
52
+ FrameType.REQUEST_SET_MIXER_PARAMETER,
53
+ recipient=self.device.parent.address,
54
+ data={
55
+ ATTR_INDEX: self._index,
56
+ ATTR_VALUE: self.values.value,
57
+ ATTR_DEVICE_INDEX: self.device.index,
58
+ },
59
+ )
60
+
61
+
62
+ @dataclass
63
+ class MixerNumberDescription(MixerParameterDescription, OffsetNumberDescription):
64
+ """Represent a mixer number description."""
65
+
66
+ __slots__ = ()
67
+
68
+
69
+ class MixerNumber(MixerParameter, OffsetNumber):
70
+ """Represents a mixer number."""
71
+
72
+ __slots__ = ()
73
+
74
+ description: MixerNumberDescription
75
+
76
+
77
+ @dataslots
78
+ @dataclass
79
+ class MixerSwitchDescription(MixerParameterDescription, SwitchDescription):
80
+ """Represents a mixer switch description."""
81
+
82
+
83
+ class MixerSwitch(MixerParameter, Switch):
84
+ """Represents a mixer switch."""
85
+
86
+ __slots__ = ()
87
+
88
+ description: MixerSwitchDescription
89
+
90
+
91
+ PARAMETER_TYPES: dict[ProductType, list[MixerParameterDescription]] = {
92
+ ProductType.ECOMAX_P: [
93
+ MixerNumberDescription(
94
+ name="mixer_target_temp",
95
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
96
+ ),
97
+ MixerNumberDescription(
98
+ name="min_target_temp",
99
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
100
+ ),
101
+ MixerNumberDescription(
102
+ name="max_target_temp",
103
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
104
+ ),
105
+ MixerNumberDescription(
106
+ name="thermostat_decrease_target_temp",
107
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
108
+ ),
109
+ MixerSwitchDescription(
110
+ name="weather_control",
111
+ ),
112
+ MixerNumberDescription(
113
+ name="heating_curve",
114
+ step=0.1,
115
+ ),
116
+ MixerNumberDescription(
117
+ name="heating_curve_shift",
118
+ offset=20,
119
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
120
+ ),
121
+ MixerNumberDescription(
122
+ name="weather_factor",
123
+ ),
124
+ MixerNumberDescription(
125
+ name="work_mode",
126
+ ),
127
+ MixerNumberDescription(
128
+ name="mixer_input_dead_zone",
129
+ step=0.1,
130
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
131
+ ),
132
+ MixerSwitchDescription(
133
+ name="thermostat_operation",
134
+ ),
135
+ MixerNumberDescription(
136
+ name="thermostat_mode",
137
+ ),
138
+ MixerSwitchDescription(
139
+ name="disable_pump_on_thermostat",
140
+ ),
141
+ MixerSwitchDescription(
142
+ name="summer_work",
143
+ ),
144
+ ],
145
+ ProductType.ECOMAX_I: [
146
+ MixerNumberDescription(
147
+ name="work_mode",
148
+ ),
149
+ MixerNumberDescription(
150
+ name="circuit_target_temp",
151
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
152
+ ),
153
+ MixerNumberDescription(
154
+ name="day_target_temp",
155
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
156
+ ),
157
+ MixerNumberDescription(
158
+ name="night_target_temp",
159
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
160
+ ),
161
+ MixerNumberDescription(
162
+ name="min_target_temp",
163
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
164
+ ),
165
+ MixerNumberDescription(
166
+ name="max_target_temp",
167
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
168
+ ),
169
+ MixerSwitchDescription(
170
+ name="summer_work",
171
+ ),
172
+ MixerSwitchDescription(
173
+ name="weather_control",
174
+ ),
175
+ MixerNumberDescription(
176
+ name="enable_circuit",
177
+ ),
178
+ MixerNumberDescription(
179
+ name="constant_water_preset_temp",
180
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
181
+ ),
182
+ MixerNumberDescription(
183
+ name="thermostat_decrease_temp",
184
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
185
+ ),
186
+ MixerNumberDescription(
187
+ name="thermostat_correction",
188
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
189
+ ),
190
+ MixerSwitchDescription(
191
+ name="thermostat_pump_lock",
192
+ ),
193
+ MixerNumberDescription(
194
+ name="valve_opening_time",
195
+ unit_of_measurement=UnitOfMeasurement.SECONDS,
196
+ ),
197
+ MixerNumberDescription(
198
+ name="mixer_input_dead_zone",
199
+ step=0.1,
200
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
201
+ ),
202
+ MixerNumberDescription(
203
+ name="proportional_range",
204
+ ),
205
+ MixerNumberDescription(
206
+ name="integration_time_constant",
207
+ ),
208
+ MixerNumberDescription(
209
+ name="heating_curve",
210
+ step=0.1,
211
+ ),
212
+ MixerNumberDescription(
213
+ name="heating_curve_shift",
214
+ offset=20,
215
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
216
+ ),
217
+ MixerNumberDescription(
218
+ name="thermostat_mode",
219
+ ),
220
+ MixerNumberDescription(
221
+ name="decreasing_constant_water_temp",
222
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
223
+ ),
224
+ ],
225
+ }
226
+
227
+
228
+ @cache
229
+ def get_mixer_parameter_types(
230
+ product_info: ProductInfo,
231
+ ) -> list[MixerParameterDescription]:
232
+ """Return cached mixer parameter types for specific product."""
233
+ return PARAMETER_TYPES[product_info.type]
234
+
235
+
236
+ __all__ = [
237
+ "get_mixer_parameter_types",
238
+ "MixerNumber",
239
+ "MixerNumberDescription",
240
+ "MixerParameter",
241
+ "MixerParameterDescription",
242
+ "MixerSwitch",
243
+ "MixerSwitchDescription",
244
+ "PARAMETER_TYPES",
245
+ ]
@@ -0,0 +1,197 @@
1
+ """Contains thermostat parameter descriptors."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from functools import cache
7
+ from typing import TYPE_CHECKING
8
+
9
+ from dataslots import dataslots
10
+
11
+ from pyplumio.const import (
12
+ ATTR_INDEX,
13
+ ATTR_OFFSET,
14
+ ATTR_SIZE,
15
+ ATTR_VALUE,
16
+ FrameType,
17
+ UnitOfMeasurement,
18
+ )
19
+ from pyplumio.frames import Request
20
+ from pyplumio.parameters import (
21
+ Number,
22
+ NumberDescription,
23
+ Parameter,
24
+ ParameterDescription,
25
+ ParameterValues,
26
+ Switch,
27
+ SwitchDescription,
28
+ )
29
+
30
+ if TYPE_CHECKING:
31
+ from pyplumio.devices.thermostat import Thermostat
32
+
33
+
34
+ @dataclass
35
+ class ThermostatParameterDescription(ParameterDescription):
36
+ """Represents a thermostat parameter description."""
37
+
38
+ __slots__ = ()
39
+
40
+ size: int = 1
41
+
42
+
43
+ class ThermostatParameter(Parameter):
44
+ """Represents a thermostat parameter."""
45
+
46
+ __slots__ = ("offset",)
47
+
48
+ device: Thermostat
49
+ description: ThermostatParameterDescription
50
+ offset: int
51
+
52
+ def __init__(
53
+ self,
54
+ device: Thermostat,
55
+ description: ThermostatParameterDescription,
56
+ values: ParameterValues | None = None,
57
+ index: int = 0,
58
+ offset: int = 0,
59
+ ) -> None:
60
+ """Initialize a new thermostat parameter."""
61
+ self.offset = offset
62
+ super().__init__(device, description, values, index)
63
+
64
+ async def create_request(self) -> Request:
65
+ """Create a request to change the parameter."""
66
+ return await Request.create(
67
+ FrameType.REQUEST_SET_THERMOSTAT_PARAMETER,
68
+ recipient=self.device.parent.address,
69
+ data={
70
+ # Increase the index by one to account for thermostat
71
+ # profile, which is being set at ecoMAX device level.
72
+ ATTR_INDEX: self._index + 1,
73
+ ATTR_VALUE: self.values.value,
74
+ ATTR_OFFSET: self.offset,
75
+ ATTR_SIZE: self.description.size,
76
+ },
77
+ )
78
+
79
+
80
+ @dataslots
81
+ @dataclass
82
+ class ThermostatNumberDescription(ThermostatParameterDescription, NumberDescription):
83
+ """Represent a thermostat number description."""
84
+
85
+
86
+ class ThermostatNumber(ThermostatParameter, Number):
87
+ """Represents a thermostat number."""
88
+
89
+ __slots__ = ()
90
+
91
+ description: ThermostatNumberDescription
92
+
93
+
94
+ @dataslots
95
+ @dataclass
96
+ class ThermostatSwitchDescription(ThermostatParameterDescription, SwitchDescription):
97
+ """Represents a thermostat switch description."""
98
+
99
+
100
+ class ThermostatSwitch(ThermostatParameter, Switch):
101
+ """Represents a thermostat switch."""
102
+
103
+ __slots__ = ()
104
+
105
+ description: ThermostatSwitchDescription
106
+
107
+
108
+ PARAMETER_TYPES: list[ThermostatParameterDescription] = [
109
+ ThermostatNumberDescription(
110
+ name="mode",
111
+ ),
112
+ ThermostatNumberDescription(
113
+ name="party_target_temp",
114
+ size=2,
115
+ step=0.1,
116
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
117
+ ),
118
+ ThermostatNumberDescription(
119
+ name="holidays_target_temp",
120
+ size=2,
121
+ step=0.1,
122
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
123
+ ),
124
+ ThermostatNumberDescription(
125
+ name="correction",
126
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
127
+ ),
128
+ ThermostatNumberDescription(
129
+ name="away_timer",
130
+ unit_of_measurement=UnitOfMeasurement.DAYS,
131
+ ),
132
+ ThermostatNumberDescription(
133
+ name="airing_timer",
134
+ unit_of_measurement=UnitOfMeasurement.DAYS,
135
+ ),
136
+ ThermostatNumberDescription(
137
+ name="party_timer",
138
+ unit_of_measurement=UnitOfMeasurement.DAYS,
139
+ ),
140
+ ThermostatNumberDescription(
141
+ name="holidays_timer",
142
+ unit_of_measurement=UnitOfMeasurement.DAYS,
143
+ ),
144
+ ThermostatNumberDescription(
145
+ name="hysteresis",
146
+ step=0.1,
147
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
148
+ ),
149
+ ThermostatNumberDescription(
150
+ name="day_target_temp",
151
+ size=2,
152
+ step=0.1,
153
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
154
+ ),
155
+ ThermostatNumberDescription(
156
+ name="night_target_temp",
157
+ size=2,
158
+ step=0.1,
159
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
160
+ ),
161
+ ThermostatNumberDescription(
162
+ name="antifreeze_target_temp",
163
+ size=2,
164
+ step=0.1,
165
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
166
+ ),
167
+ ThermostatNumberDescription(
168
+ name="heating_target_temp",
169
+ size=2,
170
+ step=0.1,
171
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
172
+ ),
173
+ ThermostatNumberDescription(
174
+ name="heating_timer",
175
+ ),
176
+ ThermostatNumberDescription(
177
+ name="off_timer",
178
+ ),
179
+ ]
180
+
181
+
182
+ @cache
183
+ def get_thermostat_parameter_types() -> list[ThermostatParameterDescription]:
184
+ """Return cached thermostat parameter types for specific product."""
185
+ return PARAMETER_TYPES
186
+
187
+
188
+ __all__ = [
189
+ "get_thermostat_parameter_types",
190
+ "PARAMETER_TYPES",
191
+ "ThermostatNumber",
192
+ "ThermostatNumberDescription",
193
+ "ThermostatParameter",
194
+ "ThermostatParameterDescription",
195
+ "ThermostatSwitch",
196
+ "ThermostatSwitchDescription",
197
+ ]
pyplumio/protocol.py CHANGED
@@ -10,11 +10,12 @@ import logging
10
10
 
11
11
  from typing_extensions import TypeAlias
12
12
 
13
- from pyplumio.const import ATTR_CONNECTED, DeviceType
13
+ from pyplumio.const import ATTR_CONNECTED, ATTR_SETUP, DeviceType
14
14
  from pyplumio.devices import PhysicalDevice
15
15
  from pyplumio.exceptions import ProtocolError
16
16
  from pyplumio.frames import Frame
17
17
  from pyplumio.frames.requests import StartMasterRequest
18
+ from pyplumio.helpers.async_cache import acache
18
19
  from pyplumio.helpers.event_manager import EventManager
19
20
  from pyplumio.stream import FrameReader, FrameWriter
20
21
  from pyplumio.structures.network_info import (
@@ -25,7 +26,7 @@ from pyplumio.structures.network_info import (
25
26
 
26
27
  _LOGGER = logging.getLogger(__name__)
27
28
 
28
- _Callback: TypeAlias = Callable[[], Awaitable[None]]
29
+ Callback: TypeAlias = Callable[[], Awaitable[None]]
29
30
 
30
31
 
31
32
  class Protocol(ABC):
@@ -34,7 +35,7 @@ class Protocol(ABC):
34
35
  connected: asyncio.Event
35
36
  reader: FrameReader | None
36
37
  writer: FrameWriter | None
37
- _on_connection_lost: set[_Callback]
38
+ _on_connection_lost: set[Callback]
38
39
 
39
40
  def __init__(self) -> None:
40
41
  """Initialize a new protocol."""
@@ -51,7 +52,7 @@ class Protocol(ABC):
51
52
  self.writer = None
52
53
 
53
54
  @property
54
- def on_connection_lost(self) -> set[_Callback]:
55
+ def on_connection_lost(self) -> set[Callback]:
55
56
  """Return the callbacks that'll be called on connection lost."""
56
57
  return self._on_connection_lost
57
58
 
@@ -132,6 +133,7 @@ class AsyncProtocol(Protocol, EventManager[PhysicalDevice]):
132
133
  consumers_count: int
133
134
  _network: NetworkInfo
134
135
  _queues: Queues
136
+ _entry_lock: asyncio.Lock
135
137
 
136
138
  def __init__(
137
139
  self,
@@ -147,6 +149,7 @@ class AsyncProtocol(Protocol, EventManager[PhysicalDevice]):
147
149
  wlan=wireless_parameters or WirelessParameters(status=False),
148
150
  )
149
151
  self._queues = Queues(read=asyncio.Queue(), write=asyncio.Queue())
152
+ self._entry_lock = asyncio.Lock()
150
153
 
151
154
  def connection_established(
152
155
  self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
@@ -224,15 +227,23 @@ class AsyncProtocol(Protocol, EventManager[PhysicalDevice]):
224
227
  device.handle_frame(frame)
225
228
  queue.task_done()
226
229
 
230
+ @acache
227
231
  async def get_device_entry(self, device_type: DeviceType) -> PhysicalDevice:
228
- """Set up or return a device entry."""
229
- name = device_type.name.lower()
230
- if name not in self.data:
232
+ """Return the device entry."""
233
+
234
+ @acache
235
+ async def _setup_device_entry(device_type: DeviceType) -> PhysicalDevice:
236
+ """Set up the device entry."""
231
237
  device = await PhysicalDevice.create(
232
238
  device_type, queue=self._queues.write, network=self._network
233
239
  )
234
240
  device.dispatch_nowait(ATTR_CONNECTED, True)
235
- self.create_task(device.async_setup(), name=f"device_setup_task ({name})")
236
- await self.dispatch(name, device)
241
+ device.dispatch_nowait(ATTR_SETUP, True)
242
+ self.dispatch_nowait(device_type.name.lower(), device)
243
+ return device
244
+
245
+ async with self._entry_lock:
246
+ return await _setup_device_entry(device_type)
247
+
237
248
 
238
- return self.data[name]
249
+ __all__ = ["Protocol", "DummyProtocol", "AsyncProtocol"]
pyplumio/stream.py CHANGED
@@ -153,3 +153,6 @@ class FrameReader:
153
153
  _LOGGER.debug("Received frame: %s, bytes: %s", frame, buffer.hex())
154
154
 
155
155
  return frame
156
+
157
+
158
+ __all__ = ["FrameReader", "FrameWriter"]
@@ -51,3 +51,6 @@ class StructureDecoder(Structure, ABC):
51
51
  self, message: bytearray, offset: int = 0, data: dict[str, Any] | None = None
52
52
  ) -> tuple[dict[str, Any], int]:
53
53
  """Decode bytes and return message data and offset."""
54
+
55
+
56
+ __all__ = ["Structure", "StructureDecoder"]
@@ -10,7 +10,7 @@ from functools import lru_cache
10
10
  from typing import Any, Final, Literal, NamedTuple
11
11
 
12
12
  from pyplumio.const import AlertType
13
- from pyplumio.helpers.data_types import UnsignedInt
13
+ from pyplumio.data_types import UnsignedInt
14
14
  from pyplumio.structures import StructureDecoder
15
15
  from pyplumio.utils import ensure_dict
16
16
 
@@ -39,21 +39,21 @@ DATETIME_INTERVALS: tuple[DateTimeInterval, ...] = (
39
39
 
40
40
 
41
41
  @lru_cache(maxsize=10)
42
- def _seconds_to_datetime(timestamp: int) -> datetime:
42
+ def seconds_to_datetime(timestamp: int) -> datetime:
43
43
  """Convert timestamp to a datetime object.
44
44
 
45
45
  The ecoMAX controller stores alert time as a special timestamp value
46
46
  in seconds counted from Jan 1st, 2000.
47
47
  """
48
48
 
49
- def _datetime_kwargs(timestamp: int) -> Generator[Any, None, None]:
49
+ def datetime_kwargs(timestamp: int) -> Generator[Any, None, None]:
50
50
  """Yield a tuple, that represents a single datetime kwarg."""
51
51
  for name, seconds, offset in DATETIME_INTERVALS:
52
52
  value = timestamp // seconds
53
53
  timestamp -= value * seconds
54
54
  yield name, (value + offset)
55
55
 
56
- return datetime(**dict(_datetime_kwargs(timestamp)))
56
+ return datetime(**dict(datetime_kwargs(timestamp)))
57
57
 
58
58
 
59
59
  @dataclass
@@ -83,11 +83,11 @@ class AlertsStructure(StructureDecoder):
83
83
  offset += from_seconds.size
84
84
  to_seconds = UnsignedInt.from_bytes(message, offset)
85
85
  offset += to_seconds.size
86
- from_dt = _seconds_to_datetime(from_seconds.value)
86
+ from_dt = seconds_to_datetime(from_seconds.value)
87
87
  to_dt = (
88
88
  None
89
89
  if to_seconds.value == MAX_UINT32
90
- else _seconds_to_datetime(to_seconds.value)
90
+ else seconds_to_datetime(to_seconds.value)
91
91
  )
92
92
  with suppress(ValueError):
93
93
  code = AlertType(code)
@@ -119,3 +119,6 @@ class AlertsStructure(StructureDecoder):
119
119
  ),
120
120
  self._offset,
121
121
  )
122
+
123
+
124
+ __all__ = ["AlertsStructure", "Alert"]
@@ -27,3 +27,6 @@ class BoilerLoadStructure(StructureDecoder):
27
27
  return ensure_dict(data), offset
28
28
 
29
29
  return (ensure_dict(data, {ATTR_BOILER_LOAD: boiler_load}), offset)
30
+
31
+
32
+ __all__ = ["ATTR_BOILER_LOAD", "BoilerLoadStructure"]
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import math
6
6
  from typing import Any, Final
7
7
 
8
- from pyplumio.helpers.data_types import Float
8
+ from pyplumio.data_types import Float
9
9
  from pyplumio.structures import StructureDecoder
10
10
  from pyplumio.utils import ensure_dict
11
11
 
@@ -28,3 +28,6 @@ class BoilerPowerStructure(StructureDecoder):
28
28
  return ensure_dict(data), offset
29
29
 
30
30
  return ensure_dict(data, {ATTR_BOILER_POWER: boiler_power.value}), offset
31
+
32
+
33
+ __all__ = ["ATTR_BOILER_POWER", "BoilerPowerStructure"]