PyPlumIO 0.5.21__py3-none-any.whl → 0.5.23__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 (37) hide show
  1. {PyPlumIO-0.5.21.dist-info → PyPlumIO-0.5.23.dist-info}/METADATA +12 -10
  2. PyPlumIO-0.5.23.dist-info/RECORD +60 -0
  3. {PyPlumIO-0.5.21.dist-info → PyPlumIO-0.5.23.dist-info}/WHEEL +1 -1
  4. pyplumio/__init__.py +2 -2
  5. pyplumio/_version.py +2 -2
  6. pyplumio/connection.py +3 -12
  7. pyplumio/devices/__init__.py +16 -16
  8. pyplumio/devices/ecomax.py +126 -126
  9. pyplumio/devices/mixer.py +50 -44
  10. pyplumio/devices/thermostat.py +36 -35
  11. pyplumio/exceptions.py +9 -9
  12. pyplumio/filters.py +56 -37
  13. pyplumio/frames/__init__.py +6 -6
  14. pyplumio/frames/messages.py +4 -6
  15. pyplumio/helpers/data_types.py +8 -7
  16. pyplumio/helpers/event_manager.py +53 -33
  17. pyplumio/helpers/parameter.py +138 -52
  18. pyplumio/helpers/task_manager.py +7 -2
  19. pyplumio/helpers/timeout.py +0 -3
  20. pyplumio/helpers/uid.py +2 -2
  21. pyplumio/protocol.py +35 -28
  22. pyplumio/stream.py +2 -2
  23. pyplumio/structures/alerts.py +40 -31
  24. pyplumio/structures/ecomax_parameters.py +493 -282
  25. pyplumio/structures/frame_versions.py +5 -6
  26. pyplumio/structures/lambda_sensor.py +6 -6
  27. pyplumio/structures/mixer_parameters.py +136 -71
  28. pyplumio/structures/network_info.py +2 -3
  29. pyplumio/structures/product_info.py +0 -4
  30. pyplumio/structures/program_version.py +24 -17
  31. pyplumio/structures/schedules.py +35 -15
  32. pyplumio/structures/thermostat_parameters.py +82 -50
  33. pyplumio/utils.py +12 -7
  34. PyPlumIO-0.5.21.dist-info/RECORD +0 -61
  35. pyplumio/helpers/typing.py +0 -29
  36. {PyPlumIO-0.5.21.dist-info → PyPlumIO-0.5.23.dist-info}/LICENSE +0 -0
  37. {PyPlumIO-0.5.21.dist-info → PyPlumIO-0.5.23.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from contextlib import suppress
5
6
  from typing import Any, Final
6
7
 
7
8
  from pyplumio.const import FrameType
@@ -21,15 +22,13 @@ class FrameVersionsStructure(StructureDecoder):
21
22
 
22
23
  def _unpack_frame_versions(self, message: bytearray) -> tuple[FrameType | int, int]:
23
24
  """Unpack frame versions."""
24
- try:
25
- frame_type = message[self._offset]
26
- frame_type = FrameType(frame_type)
27
- except ValueError:
28
- pass
29
-
25
+ frame_type = message[self._offset]
30
26
  self._offset += 1
31
27
  version = UnsignedShort.from_bytes(message, self._offset)
32
28
  self._offset += version.size
29
+ with suppress(ValueError):
30
+ frame_type = FrameType(frame_type)
31
+
33
32
  return frame_type, version.value
34
33
 
35
34
  def decode(
@@ -30,22 +30,22 @@ class LambdaSensorStructure(StructureDecoder):
30
30
  if lambda_state == BYTE_UNDEFINED:
31
31
  return ensure_dict(data), offset
32
32
 
33
- with suppress(ValueError):
34
- lambda_state = LambdaState(lambda_state)
35
-
36
33
  lambda_target = message[offset]
37
34
  offset += 1
38
35
  level = UnsignedShort.from_bytes(message, offset)
39
36
  offset += level.size
37
+ with suppress(ValueError):
38
+ lambda_state = LambdaState(lambda_state)
39
+
40
40
  return (
41
41
  ensure_dict(
42
42
  data,
43
43
  {
44
44
  ATTR_LAMBDA_STATE: lambda_state,
45
45
  ATTR_LAMBDA_TARGET: lambda_target,
46
- ATTR_LAMBDA_LEVEL: None
47
- if math.isnan(level.value)
48
- else (level.value / 10),
46
+ ATTR_LAMBDA_LEVEL: (
47
+ None if math.isnan(level.value) else (level.value / 10)
48
+ ),
49
49
  },
50
50
  ),
51
51
  offset,
@@ -6,6 +6,8 @@ from collections.abc import Generator
6
6
  from dataclasses import dataclass
7
7
  from typing import TYPE_CHECKING, Any, Final
8
8
 
9
+ from dataslots import dataslots
10
+
9
11
  from pyplumio.const import (
10
12
  ATTR_DEVICE_INDEX,
11
13
  ATTR_INDEX,
@@ -16,14 +18,16 @@ from pyplumio.const import (
16
18
  )
17
19
  from pyplumio.frames import Request
18
20
  from pyplumio.helpers.parameter import (
19
- BinaryParameter,
20
- BinaryParameterDescription,
21
+ SET_RETRIES,
22
+ Number,
23
+ NumberDescription,
21
24
  Parameter,
22
25
  ParameterDescription,
23
26
  ParameterValues,
27
+ Switch,
28
+ SwitchDescription,
24
29
  unpack_parameter,
25
30
  )
26
- from pyplumio.helpers.typing import ParameterValueType
27
31
  from pyplumio.structures import StructureDecoder
28
32
  from pyplumio.utils import ensure_dict
29
33
 
@@ -35,8 +39,18 @@ ATTR_MIXER_PARAMETERS: Final = "mixer_parameters"
35
39
  MIXER_PARAMETER_SIZE: Final = 3
36
40
 
37
41
 
42
+ @dataclass
43
+ class MixerParameterDescription(ParameterDescription):
44
+ """Represents a mixer parameter description."""
45
+
46
+ __slots__ = ()
47
+
48
+ multiplier: float = 1.0
49
+ offset: int = 0
50
+
51
+
38
52
  class MixerParameter(Parameter):
39
- """Represents a mixer parameter."""
53
+ """Represent a mixer parameter."""
40
54
 
41
55
  __slots__ = ()
42
56
 
@@ -55,140 +69,191 @@ class MixerParameter(Parameter):
55
69
  },
56
70
  )
57
71
 
58
- async def set(self, value: ParameterValueType, retries: int = 5) -> bool:
59
- """Set a parameter value."""
60
- if isinstance(value, (int, float)):
61
- value = int((value + self.description.offset) / self.description.multiplier)
62
72
 
73
+ @dataslots
74
+ @dataclass
75
+ class MixerNumberDescription(MixerParameterDescription, NumberDescription):
76
+ """Represent a mixer number description."""
77
+
78
+
79
+ class MixerNumber(MixerParameter, Number):
80
+ """Represents a mixer number."""
81
+
82
+ __slots__ = ()
83
+
84
+ description: MixerNumberDescription
85
+
86
+ async def set(self, value: int | float, retries: int = SET_RETRIES) -> bool:
87
+ """Set a parameter value."""
88
+ value = (value + self.description.offset) / self.description.multiplier
63
89
  return await super().set(value, retries)
64
90
 
65
91
  @property
66
- def value(self) -> ParameterValueType:
92
+ def value(self) -> float:
67
93
  """Return the parameter value."""
68
94
  return (
69
95
  self.values.value - self.description.offset
70
96
  ) * self.description.multiplier
71
97
 
72
98
  @property
73
- def min_value(self) -> ParameterValueType:
99
+ def min_value(self) -> float:
74
100
  """Return the minimum allowed value."""
75
101
  return (
76
102
  self.values.min_value - self.description.offset
77
103
  ) * self.description.multiplier
78
104
 
79
105
  @property
80
- def max_value(self) -> ParameterValueType:
106
+ def max_value(self) -> float:
81
107
  """Return the maximum allowed value."""
82
108
  return (
83
109
  self.values.max_value - self.description.offset
84
110
  ) * self.description.multiplier
85
111
 
86
112
 
87
- class MixerBinaryParameter(BinaryParameter, MixerParameter):
88
- """Represents a mixer binary parameter."""
89
-
90
- __slots__ = ()
91
-
92
-
113
+ @dataslots
93
114
  @dataclass
94
- class MixerParameterDescription(ParameterDescription):
95
- """Represents a mixer parameter description."""
115
+ class MixerSwitchDescription(MixerParameterDescription, SwitchDescription):
116
+ """Represents a mixer switch description."""
96
117
 
97
- multiplier: float = 1
98
- offset: int = 0
99
118
 
119
+ class MixerSwitch(MixerParameter, Switch):
120
+ """Represents a mixer switch."""
100
121
 
101
- @dataclass
102
- class MixerBinaryParameterDescription(
103
- MixerParameterDescription, BinaryParameterDescription
104
- ):
105
- """Represents a mixer binary parameter description."""
122
+ __slots__ = ()
123
+
124
+ description: MixerSwitchDescription
106
125
 
107
126
 
108
127
  MIXER_PARAMETERS: dict[ProductType, tuple[MixerParameterDescription, ...]] = {
109
128
  ProductType.ECOMAX_P: (
110
- MixerParameterDescription(
111
- name="mixer_target_temp", unit_of_measurement=UnitOfMeasurement.CELSIUS
129
+ MixerNumberDescription(
130
+ name="mixer_target_temp",
131
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
112
132
  ),
113
- MixerParameterDescription(
114
- name="min_target_temp", unit_of_measurement=UnitOfMeasurement.CELSIUS
133
+ MixerNumberDescription(
134
+ name="min_target_temp",
135
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
115
136
  ),
116
- MixerParameterDescription(
117
- name="max_target_temp", unit_of_measurement=UnitOfMeasurement.CELSIUS
137
+ MixerNumberDescription(
138
+ name="max_target_temp",
139
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
118
140
  ),
119
- MixerParameterDescription(
141
+ MixerNumberDescription(
120
142
  name="thermostat_decrease_target_temp",
121
143
  unit_of_measurement=UnitOfMeasurement.CELSIUS,
122
144
  ),
123
- MixerBinaryParameterDescription(name="weather_control"),
124
- MixerParameterDescription(name="heating_curve", multiplier=0.1),
125
- MixerParameterDescription(
145
+ MixerSwitchDescription(
146
+ name="weather_control",
147
+ ),
148
+ MixerNumberDescription(
149
+ name="heating_curve",
150
+ multiplier=0.1,
151
+ ),
152
+ MixerNumberDescription(
126
153
  name="heating_curve_shift",
127
154
  offset=20,
128
155
  unit_of_measurement=UnitOfMeasurement.CELSIUS,
129
156
  ),
130
- MixerParameterDescription(name="weather_factor"),
131
- MixerParameterDescription(name="work_mode"),
132
- MixerParameterDescription(
157
+ MixerNumberDescription(
158
+ name="weather_factor",
159
+ ),
160
+ MixerNumberDescription(
161
+ name="work_mode",
162
+ ),
163
+ MixerNumberDescription(
133
164
  name="mixer_input_dead_zone",
134
165
  multiplier=0.1,
135
166
  unit_of_measurement=UnitOfMeasurement.CELSIUS,
136
167
  ),
137
- MixerBinaryParameterDescription(name="thermostat_operation"),
138
- MixerParameterDescription(name="thermostat_mode"),
139
- MixerBinaryParameterDescription(name="disable_pump_on_thermostat"),
140
- MixerBinaryParameterDescription(name="summer_work"),
168
+ MixerSwitchDescription(
169
+ name="thermostat_operation",
170
+ ),
171
+ MixerNumberDescription(
172
+ name="thermostat_mode",
173
+ ),
174
+ MixerSwitchDescription(
175
+ name="disable_pump_on_thermostat",
176
+ ),
177
+ MixerSwitchDescription(
178
+ name="summer_work",
179
+ ),
141
180
  ),
142
181
  ProductType.ECOMAX_I: (
143
- MixerParameterDescription(name="work_mode"),
144
- MixerParameterDescription(
145
- name="circuit_target_temp", unit_of_measurement=UnitOfMeasurement.CELSIUS
182
+ MixerNumberDescription(
183
+ name="work_mode",
184
+ ),
185
+ MixerNumberDescription(
186
+ name="circuit_target_temp",
187
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
146
188
  ),
147
- MixerParameterDescription(
148
- name="day_target_temp", unit_of_measurement=UnitOfMeasurement.CELSIUS
189
+ MixerNumberDescription(
190
+ name="day_target_temp",
191
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
192
+ ),
193
+ MixerNumberDescription(
194
+ name="night_target_temp",
195
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
196
+ ),
197
+ MixerNumberDescription(
198
+ name="min_target_temp",
199
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
200
+ ),
201
+ MixerNumberDescription(
202
+ name="max_target_temp",
203
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
149
204
  ),
150
- MixerParameterDescription(
151
- name="night_target_temp", unit_of_measurement=UnitOfMeasurement.CELSIUS
205
+ MixerSwitchDescription(
206
+ name="summer_work",
152
207
  ),
153
- MixerParameterDescription(
154
- name="min_target_temp", unit_of_measurement=UnitOfMeasurement.CELSIUS
208
+ MixerSwitchDescription(
209
+ name="weather_control",
155
210
  ),
156
- MixerParameterDescription(
157
- name="max_target_temp", unit_of_measurement=UnitOfMeasurement.CELSIUS
211
+ MixerNumberDescription(
212
+ name="enable_circuit",
158
213
  ),
159
- MixerBinaryParameterDescription(name="summer_work"),
160
- MixerBinaryParameterDescription(name="weather_control"),
161
- MixerParameterDescription(name="enable_circuit"),
162
- MixerParameterDescription(
214
+ MixerNumberDescription(
163
215
  name="constant_water_preset_temp",
164
216
  unit_of_measurement=UnitOfMeasurement.CELSIUS,
165
217
  ),
166
- MixerParameterDescription(
218
+ MixerNumberDescription(
167
219
  name="thermostat_decrease_temp",
168
220
  unit_of_measurement=UnitOfMeasurement.CELSIUS,
169
221
  ),
170
- MixerParameterDescription(
171
- name="thermostat_correction", unit_of_measurement=UnitOfMeasurement.CELSIUS
222
+ MixerNumberDescription(
223
+ name="thermostat_correction",
224
+ unit_of_measurement=UnitOfMeasurement.CELSIUS,
225
+ ),
226
+ MixerSwitchDescription(
227
+ name="thermostat_pump_lock",
172
228
  ),
173
- MixerBinaryParameterDescription(name="thermostat_pump_lock"),
174
- MixerParameterDescription(
175
- name="valve_opening_time", unit_of_measurement=UnitOfMeasurement.SECONDS
229
+ MixerNumberDescription(
230
+ name="valve_opening_time",
231
+ unit_of_measurement=UnitOfMeasurement.SECONDS,
176
232
  ),
177
- MixerParameterDescription(
233
+ MixerNumberDescription(
178
234
  name="mixer_input_dead_zone",
179
235
  multiplier=0.1,
180
236
  unit_of_measurement=UnitOfMeasurement.CELSIUS,
181
237
  ),
182
- MixerParameterDescription(name="proportional_range"),
183
- MixerParameterDescription(name="integration_time_constant"),
184
- MixerParameterDescription(name="heating_curve", multiplier=0.1),
185
- MixerParameterDescription(
238
+ MixerNumberDescription(
239
+ name="proportional_range",
240
+ ),
241
+ MixerNumberDescription(
242
+ name="integration_time_constant",
243
+ ),
244
+ MixerNumberDescription(
245
+ name="heating_curve",
246
+ multiplier=0.1,
247
+ ),
248
+ MixerNumberDescription(
186
249
  name="heating_curve_shift",
187
250
  offset=20,
188
251
  unit_of_measurement=UnitOfMeasurement.CELSIUS,
189
252
  ),
190
- MixerParameterDescription(name="thermostat_mode"),
191
- MixerParameterDescription(
253
+ MixerNumberDescription(
254
+ name="thermostat_mode",
255
+ ),
256
+ MixerNumberDescription(
192
257
  name="decreasing_constant_water_temp",
193
258
  unit_of_measurement=UnitOfMeasurement.CELSIUS,
194
259
  ),
@@ -66,9 +66,8 @@ class NetworkInfoStructure(Structure):
66
66
 
67
67
  def encode(self, data: dict[str, Any]) -> bytearray:
68
68
  """Encode data to the bytearray message."""
69
- message = bytearray()
70
- message += b"\x01"
71
- network_info = data[ATTR_NETWORK] if ATTR_NETWORK in data else NetworkInfo()
69
+ message = bytearray(b"\x01")
70
+ network_info: NetworkInfo = data.get(ATTR_NETWORK, NetworkInfo())
72
71
  message += IPv4(network_info.eth.ip).to_bytes()
73
72
  message += IPv4(network_info.eth.netmask).to_bytes()
74
73
  message += IPv4(network_info.eth.gateway).to_bytes()
@@ -53,16 +53,12 @@ class ProductInfoStructure(StructureDecoder):
53
53
  """Decode bytes and return message data and offset."""
54
54
  product_type, product_id = struct.unpack_from("<BH", message)
55
55
  offset += 3
56
-
57
56
  uid = VarBytes.from_bytes(message, offset)
58
57
  offset += uid.size
59
-
60
58
  logo = UnsignedShort.from_bytes(message, offset)
61
59
  offset += logo.size
62
-
63
60
  image = UnsignedShort.from_bytes(message, offset)
64
61
  offset += image.size
65
-
66
62
  model_name = VarString.from_bytes(message, offset)
67
63
  offset += model_name.size
68
64
 
@@ -14,7 +14,7 @@ ATTR_VERSION: Final = "version"
14
14
 
15
15
  VERSION_INFO_SIZE: Final = 15
16
16
 
17
- SOFTWARE_VERSION: str = ".".join(str(x) for x in __version_tuple__[0:3])
17
+ SOFTWARE_VERSION: Final = ".".join(str(x) for x in __version_tuple__[0:3])
18
18
 
19
19
  struct_program_version = struct.Struct("<2sB2s3s3HB")
20
20
 
@@ -38,7 +38,7 @@ class ProgramVersionStructure(Structure):
38
38
  def encode(self, data: dict[str, Any]) -> bytearray:
39
39
  """Encode data to the bytearray message."""
40
40
  message = bytearray(struct_program_version.size)
41
- version_info = data[ATTR_VERSION] if ATTR_VERSION in data else VersionInfo()
41
+ version_info: VersionInfo = data.get(ATTR_VERSION, VersionInfo())
42
42
  struct_program_version.pack_into(
43
43
  message,
44
44
  0,
@@ -55,22 +55,29 @@ class ProgramVersionStructure(Structure):
55
55
  self, message: bytearray, offset: int = 0, data: dict[str, Any] | None = None
56
56
  ) -> tuple[dict[str, Any], int]:
57
57
  """Decode bytes and return message data and offset."""
58
- version_info = VersionInfo()
59
- [
60
- version_info.struct_tag,
61
- version_info.struct_version,
62
- version_info.device_id,
63
- version_info.processor_signature,
64
- software_version1,
65
- software_version2,
66
- software_version3,
67
- self.frame.recipient,
68
- ] = struct_program_version.unpack_from(message)
69
- version_info.software = ".".join(
70
- map(str, [software_version1, software_version2, software_version3])
71
- )
58
+ (
59
+ struct_tag,
60
+ struct_version,
61
+ device_id,
62
+ processor_signature,
63
+ software1,
64
+ software2,
65
+ software3,
66
+ _, # recipient
67
+ ) = struct_program_version.unpack_from(message)
72
68
 
73
69
  return (
74
- ensure_dict(data, {ATTR_VERSION: version_info}),
70
+ ensure_dict(
71
+ data,
72
+ {
73
+ ATTR_VERSION: VersionInfo(
74
+ software=".".join(map(str, [software1, software2, software3])),
75
+ struct_tag=struct_tag,
76
+ struct_version=struct_version,
77
+ device_id=device_id,
78
+ processor_signature=processor_signature,
79
+ )
80
+ },
81
+ ),
75
82
  offset + VERSION_INFO_SIZE,
76
83
  )
@@ -8,6 +8,8 @@ from functools import reduce
8
8
  from itertools import chain
9
9
  from typing import Any, Final
10
10
 
11
+ from dataslots import dataslots
12
+
11
13
  from pyplumio.const import (
12
14
  ATTR_PARAMETER,
13
15
  ATTR_SCHEDULE,
@@ -19,11 +21,13 @@ from pyplumio.devices import AddressableDevice, Device
19
21
  from pyplumio.exceptions import FrameDataError
20
22
  from pyplumio.frames import Request
21
23
  from pyplumio.helpers.parameter import (
22
- BinaryParameter,
23
- BinaryParameterDescription,
24
+ Number,
25
+ NumberDescription,
24
26
  Parameter,
25
27
  ParameterDescription,
26
28
  ParameterValues,
29
+ Switch,
30
+ SwitchDescription,
27
31
  unpack_parameter,
28
32
  )
29
33
  from pyplumio.structures import Structure
@@ -80,12 +84,20 @@ SCHEDULES: tuple[str, ...] = (
80
84
  )
81
85
 
82
86
 
87
+ @dataclass
88
+ class ScheduleParameterDescription(ParameterDescription):
89
+ """Represent a schedule parameter description."""
90
+
91
+ __slots__ = ()
92
+
93
+
83
94
  class ScheduleParameter(Parameter):
84
95
  """Represents a schedule parameter."""
85
96
 
86
97
  __slots__ = ()
87
98
 
88
99
  device: AddressableDevice
100
+ description: ScheduleParameterDescription
89
101
 
90
102
  async def create_request(self) -> Request:
91
103
  """Create a request to change the parameter."""
@@ -97,32 +109,40 @@ class ScheduleParameter(Parameter):
97
109
  )
98
110
 
99
111
 
100
- class ScheduleBinaryParameter(ScheduleParameter, BinaryParameter):
101
- """Represents a schedule binary parameter."""
112
+ @dataslots
113
+ @dataclass
114
+ class ScheduleNumberDescription(ScheduleParameterDescription, NumberDescription):
115
+ """Represents a schedule number description."""
116
+
117
+
118
+ class ScheduleNumber(ScheduleParameter, Number):
119
+ """Represents a schedule number."""
102
120
 
103
121
  __slots__ = ()
104
122
 
123
+ description: ScheduleNumberDescription
105
124
 
125
+
126
+ @dataslots
106
127
  @dataclass
107
- class ScheduleParameterDescription(ParameterDescription):
108
- """Represents a schedule parameter description."""
128
+ class ScheduleSwitchDescription(ScheduleParameterDescription, SwitchDescription):
129
+ """Represents a schedule switch description."""
109
130
 
110
131
 
111
- @dataclass
112
- class ScheduleBinaryParameterDescription(
113
- BinaryParameterDescription, ScheduleParameterDescription
114
- ):
115
- """Represents a schedule binary parameter description."""
132
+ class ScheduleSwitch(ScheduleParameter, Switch):
133
+ """Represents a schedule switch."""
134
+
135
+ __slots__ = ()
136
+
137
+ description: ScheduleSwitchDescription
116
138
 
117
139
 
118
140
  SCHEDULE_PARAMETERS: list[ScheduleParameterDescription] = list(
119
141
  chain.from_iterable(
120
142
  [
121
143
  [
122
- ScheduleBinaryParameterDescription(
123
- name=f"{name}_{ATTR_SCHEDULE_SWITCH}"
124
- ),
125
- ScheduleParameterDescription(name=f"{name}_{ATTR_SCHEDULE_PARAMETER}"),
144
+ ScheduleSwitchDescription(name=f"{name}_{ATTR_SCHEDULE_SWITCH}"),
145
+ ScheduleNumberDescription(name=f"{name}_{ATTR_SCHEDULE_PARAMETER}"),
126
146
  ]
127
147
  for name in SCHEDULES
128
148
  ]