PyPlumIO 0.5.41__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.
Files changed (60) hide show
  1. pyplumio/__init__.py +3 -2
  2. pyplumio/_version.py +2 -2
  3. pyplumio/connection.py +14 -14
  4. pyplumio/const.py +7 -0
  5. pyplumio/devices/__init__.py +32 -19
  6. pyplumio/devices/ecomax.py +112 -128
  7. pyplumio/devices/ecoster.py +5 -0
  8. pyplumio/devices/mixer.py +21 -31
  9. pyplumio/devices/thermostat.py +19 -29
  10. pyplumio/filters.py +166 -147
  11. pyplumio/frames/__init__.py +20 -8
  12. pyplumio/frames/messages.py +3 -0
  13. pyplumio/frames/requests.py +21 -0
  14. pyplumio/frames/responses.py +18 -0
  15. pyplumio/helpers/data_types.py +23 -21
  16. pyplumio/helpers/event_manager.py +40 -3
  17. pyplumio/helpers/factory.py +5 -2
  18. pyplumio/helpers/schedule.py +8 -5
  19. pyplumio/helpers/task_manager.py +3 -0
  20. pyplumio/helpers/timeout.py +8 -8
  21. pyplumio/helpers/uid.py +8 -5
  22. pyplumio/{helpers/parameter.py → parameters/__init__.py} +111 -8
  23. pyplumio/parameters/ecomax.py +868 -0
  24. pyplumio/parameters/mixer.py +245 -0
  25. pyplumio/parameters/thermostat.py +197 -0
  26. pyplumio/protocol.py +6 -3
  27. pyplumio/stream.py +3 -0
  28. pyplumio/structures/__init__.py +3 -0
  29. pyplumio/structures/alerts.py +8 -5
  30. pyplumio/structures/boiler_load.py +3 -0
  31. pyplumio/structures/boiler_power.py +3 -0
  32. pyplumio/structures/ecomax_parameters.py +7 -815
  33. pyplumio/structures/fan_power.py +3 -0
  34. pyplumio/structures/frame_versions.py +3 -0
  35. pyplumio/structures/fuel_consumption.py +3 -0
  36. pyplumio/structures/fuel_level.py +3 -0
  37. pyplumio/structures/lambda_sensor.py +8 -0
  38. pyplumio/structures/mixer_parameters.py +9 -245
  39. pyplumio/structures/mixer_sensors.py +9 -0
  40. pyplumio/structures/modules.py +14 -0
  41. pyplumio/structures/network_info.py +11 -0
  42. pyplumio/structures/output_flags.py +9 -0
  43. pyplumio/structures/outputs.py +21 -0
  44. pyplumio/structures/pending_alerts.py +3 -0
  45. pyplumio/structures/product_info.py +5 -2
  46. pyplumio/structures/program_version.py +3 -0
  47. pyplumio/structures/regulator_data.py +4 -1
  48. pyplumio/structures/regulator_data_schema.py +3 -0
  49. pyplumio/structures/schedules.py +18 -1
  50. pyplumio/structures/statuses.py +9 -0
  51. pyplumio/structures/temperatures.py +22 -0
  52. pyplumio/structures/thermostat_parameters.py +13 -199
  53. pyplumio/structures/thermostat_sensors.py +9 -0
  54. pyplumio/utils.py +14 -12
  55. {pyplumio-0.5.41.dist-info → pyplumio-0.5.43.dist-info}/METADATA +30 -17
  56. pyplumio-0.5.43.dist-info/RECORD +63 -0
  57. {pyplumio-0.5.41.dist-info → pyplumio-0.5.43.dist-info}/WHEEL +1 -1
  58. pyplumio-0.5.41.dist-info/RECORD +0 -60
  59. {pyplumio-0.5.41.dist-info → pyplumio-0.5.43.dist-info}/licenses/LICENSE +0 -0
  60. {pyplumio-0.5.41.dist-info → pyplumio-0.5.43.dist-info}/top_level.txt +0 -0
@@ -28,3 +28,6 @@ class FanPowerStructure(StructureDecoder):
28
28
  return ensure_dict(data), offset
29
29
 
30
30
  return ensure_dict(data, {ATTR_FAN_POWER: fan_power.value}), offset
31
+
32
+
33
+ __all__ = ["ATTR_FAN_POWER", "FanPowerStructure"]
@@ -48,3 +48,6 @@ class FrameVersionsStructure(StructureDecoder):
48
48
  ),
49
49
  self._offset,
50
50
  )
51
+
52
+
53
+ __all__ = ["ATTR_FRAME_VERSIONS", "FrameVersionsStructure"]
@@ -31,3 +31,6 @@ class FuelConsumptionStructure(StructureDecoder):
31
31
  ensure_dict(data, {ATTR_FUEL_CONSUMPTION: fuel_consumption.value}),
32
32
  offset,
33
33
  )
34
+
35
+
36
+ __all__ = ["ATTR_FUEL_CONSUMPTION", "FuelConsumptionStructure"]
@@ -34,3 +34,6 @@ class FuelLevelStructure(StructureDecoder):
34
34
  fuel_level -= FUEL_LEVEL_OFFSET
35
35
 
36
36
  return (ensure_dict(data, {ATTR_FUEL_LEVEL: fuel_level}), offset)
37
+
38
+
39
+ __all__ = ["ATTR_FUEL_LEVEL", "FuelLevelStructure"]
@@ -50,3 +50,11 @@ class LambdaSensorStructure(StructureDecoder):
50
50
  ),
51
51
  offset,
52
52
  )
53
+
54
+
55
+ __all__ = [
56
+ "ATTR_LAMBDA_STATE",
57
+ "ATTR_LAMBDA_TARGET",
58
+ "ATTR_LAMBDA_LEVEL",
59
+ "LambdaSensorStructure",
60
+ ]
@@ -3,259 +3,17 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from collections.abc import Generator
6
- from dataclasses import dataclass
7
- from typing import TYPE_CHECKING, Any, Final
6
+ from typing import Any, Final
8
7
 
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.helpers.parameter import (
21
- Number,
22
- NumberDescription,
23
- NumericType,
24
- Parameter,
25
- ParameterDescription,
26
- ParameterValues,
27
- Switch,
28
- SwitchDescription,
29
- unpack_parameter,
30
- )
8
+ from pyplumio.parameters import ParameterValues, unpack_parameter
31
9
  from pyplumio.structures import StructureDecoder
32
- from pyplumio.utils import ensure_dict, is_divisible
33
-
34
- if TYPE_CHECKING:
35
- from pyplumio.devices.mixer import Mixer
10
+ from pyplumio.utils import ensure_dict
36
11
 
37
12
  ATTR_MIXER_PARAMETERS: Final = "mixer_parameters"
38
13
 
39
14
  MIXER_PARAMETER_SIZE: Final = 3
40
15
 
41
16
 
42
- @dataclass
43
- class MixerParameterDescription(ParameterDescription):
44
- """Represents a mixer parameter description."""
45
-
46
- __slots__ = ()
47
-
48
-
49
- class MixerParameter(Parameter):
50
- """Represent a mixer parameter."""
51
-
52
- __slots__ = ()
53
-
54
- device: Mixer
55
- description: MixerParameterDescription
56
-
57
- async def create_request(self) -> Request:
58
- """Create a request to change the parameter."""
59
- return await Request.create(
60
- FrameType.REQUEST_SET_MIXER_PARAMETER,
61
- recipient=self.device.parent.address,
62
- data={
63
- ATTR_INDEX: self._index,
64
- ATTR_VALUE: self.values.value,
65
- ATTR_DEVICE_INDEX: self.device.index,
66
- },
67
- )
68
-
69
-
70
- @dataslots
71
- @dataclass
72
- class MixerNumberDescription(MixerParameterDescription, NumberDescription):
73
- """Represent a mixer number description."""
74
-
75
- step: float = 1.0
76
- offset: int = 0
77
- precision: int = 6
78
-
79
-
80
- class MixerNumber(MixerParameter, Number):
81
- """Represents a mixer number."""
82
-
83
- __slots__ = ()
84
-
85
- description: MixerNumberDescription
86
-
87
- def validate(self, value: NumericType) -> bool:
88
- """Validate the parameter value."""
89
- if not is_divisible(value, self.description.step, self.description.precision):
90
- raise ValueError(
91
- f"Invalid value: {value}. The value must be adjusted in increments of "
92
- f"{self.description.step}."
93
- )
94
-
95
- return super().validate(value)
96
-
97
- def _pack_value(self, value: NumericType) -> int:
98
- """Pack the parameter value."""
99
- value += self.description.offset
100
- return round(value / self.description.step)
101
-
102
- def _unpack_value(self, value: int) -> NumericType:
103
- """Unpack the parameter value."""
104
- value -= self.description.offset
105
- return round(value * self.description.step, self.description.precision)
106
-
107
-
108
- @dataslots
109
- @dataclass
110
- class MixerSwitchDescription(MixerParameterDescription, SwitchDescription):
111
- """Represents a mixer switch description."""
112
-
113
-
114
- class MixerSwitch(MixerParameter, Switch):
115
- """Represents a mixer switch."""
116
-
117
- __slots__ = ()
118
-
119
- description: MixerSwitchDescription
120
-
121
-
122
- MIXER_PARAMETERS: dict[ProductType, tuple[MixerParameterDescription, ...]] = {
123
- ProductType.ECOMAX_P: (
124
- MixerNumberDescription(
125
- name="mixer_target_temp",
126
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
127
- ),
128
- MixerNumberDescription(
129
- name="min_target_temp",
130
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
131
- ),
132
- MixerNumberDescription(
133
- name="max_target_temp",
134
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
135
- ),
136
- MixerNumberDescription(
137
- name="thermostat_decrease_target_temp",
138
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
139
- ),
140
- MixerSwitchDescription(
141
- name="weather_control",
142
- ),
143
- MixerNumberDescription(
144
- name="heating_curve",
145
- step=0.1,
146
- ),
147
- MixerNumberDescription(
148
- name="heating_curve_shift",
149
- offset=20,
150
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
151
- ),
152
- MixerNumberDescription(
153
- name="weather_factor",
154
- ),
155
- MixerNumberDescription(
156
- name="work_mode",
157
- ),
158
- MixerNumberDescription(
159
- name="mixer_input_dead_zone",
160
- step=0.1,
161
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
162
- ),
163
- MixerSwitchDescription(
164
- name="thermostat_operation",
165
- ),
166
- MixerNumberDescription(
167
- name="thermostat_mode",
168
- ),
169
- MixerSwitchDescription(
170
- name="disable_pump_on_thermostat",
171
- ),
172
- MixerSwitchDescription(
173
- name="summer_work",
174
- ),
175
- ),
176
- ProductType.ECOMAX_I: (
177
- MixerNumberDescription(
178
- name="work_mode",
179
- ),
180
- MixerNumberDescription(
181
- name="circuit_target_temp",
182
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
183
- ),
184
- MixerNumberDescription(
185
- name="day_target_temp",
186
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
187
- ),
188
- MixerNumberDescription(
189
- name="night_target_temp",
190
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
191
- ),
192
- MixerNumberDescription(
193
- name="min_target_temp",
194
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
195
- ),
196
- MixerNumberDescription(
197
- name="max_target_temp",
198
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
199
- ),
200
- MixerSwitchDescription(
201
- name="summer_work",
202
- ),
203
- MixerSwitchDescription(
204
- name="weather_control",
205
- ),
206
- MixerNumberDescription(
207
- name="enable_circuit",
208
- ),
209
- MixerNumberDescription(
210
- name="constant_water_preset_temp",
211
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
212
- ),
213
- MixerNumberDescription(
214
- name="thermostat_decrease_temp",
215
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
216
- ),
217
- MixerNumberDescription(
218
- name="thermostat_correction",
219
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
220
- ),
221
- MixerSwitchDescription(
222
- name="thermostat_pump_lock",
223
- ),
224
- MixerNumberDescription(
225
- name="valve_opening_time",
226
- unit_of_measurement=UnitOfMeasurement.SECONDS,
227
- ),
228
- MixerNumberDescription(
229
- name="mixer_input_dead_zone",
230
- step=0.1,
231
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
232
- ),
233
- MixerNumberDescription(
234
- name="proportional_range",
235
- ),
236
- MixerNumberDescription(
237
- name="integration_time_constant",
238
- ),
239
- MixerNumberDescription(
240
- name="heating_curve",
241
- step=0.1,
242
- ),
243
- MixerNumberDescription(
244
- name="heating_curve_shift",
245
- offset=20,
246
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
247
- ),
248
- MixerNumberDescription(
249
- name="thermostat_mode",
250
- ),
251
- MixerNumberDescription(
252
- name="decreasing_constant_water_temp",
253
- unit_of_measurement=UnitOfMeasurement.CELSIUS,
254
- ),
255
- ),
256
- }
257
-
258
-
259
17
  class MixerParametersStructure(StructureDecoder):
260
18
  """Represents a mixer parameters data structure."""
261
19
 
@@ -300,3 +58,9 @@ class MixerParametersStructure(StructureDecoder):
300
58
  ),
301
59
  self._offset,
302
60
  )
61
+
62
+
63
+ __all__ = [
64
+ "ATTR_MIXER_PARAMETERS",
65
+ "MixerParametersStructure",
66
+ ]
@@ -69,3 +69,12 @@ class MixerSensorsStructure(StructureDecoder):
69
69
  ),
70
70
  self._offset,
71
71
  )
72
+
73
+
74
+ __all__ = [
75
+ "ATTR_PUMP",
76
+ "ATTR_MIXERS_AVAILABLE",
77
+ "ATTR_MIXERS_CONNECTED",
78
+ "ATTR_MIXER_SENSORS",
79
+ "MixerSensorsStructure",
80
+ ]
@@ -89,3 +89,17 @@ class ModulesStructure(StructureDecoder):
89
89
  ),
90
90
  self._offset,
91
91
  )
92
+
93
+
94
+ __all__ = [
95
+ "ATTR_MODULES",
96
+ "ATTR_MODULE_A",
97
+ "ATTR_MODULE_B",
98
+ "ATTR_MODULE_C",
99
+ "ATTR_ECOLAMBDA",
100
+ "ATTR_ECOSTER",
101
+ "ATTR_PANEL",
102
+ "MODULES",
103
+ "ConnectedModules",
104
+ "ModulesStructure",
105
+ ]
@@ -114,3 +114,14 @@ class NetworkInfoStructure(Structure):
114
114
  ),
115
115
  offset + NETWORK_INFO_SIZE,
116
116
  )
117
+
118
+
119
+ __all__ = [
120
+ "EthernetParameters",
121
+ "WirelessParameters",
122
+ "NetworkInfo",
123
+ "NetworkInfoStructure",
124
+ "ATTR_NETWORK",
125
+ "DEFAULT_IP",
126
+ "DEFAULT_NETMASK",
127
+ ]
@@ -36,3 +36,12 @@ class OutputFlagsStructure(StructureDecoder):
36
36
  ),
37
37
  offset + output_flags.size,
38
38
  )
39
+
40
+
41
+ __all__ = [
42
+ "ATTR_HEATING_PUMP_FLAG",
43
+ "ATTR_WATER_HEATER_PUMP_FLAG",
44
+ "ATTR_CIRCULATION_PUMP_FLAG",
45
+ "ATTR_SOLAR_PUMP_FLAG",
46
+ "OutputFlagsStructure",
47
+ ]
@@ -65,3 +65,24 @@ class OutputsStructure(StructureDecoder):
65
65
  ),
66
66
  offset + outputs.size,
67
67
  )
68
+
69
+
70
+ __all__ = [
71
+ "ATTR_FAN",
72
+ "ATTR_FEEDER",
73
+ "ATTR_HEATING_PUMP",
74
+ "ATTR_WATER_HEATER_PUMP",
75
+ "ATTR_CIRCULATION_PUMP",
76
+ "ATTR_LIGHTER",
77
+ "ATTR_ALARM",
78
+ "ATTR_OUTER_BOILER",
79
+ "ATTR_FAN2_EXHAUST",
80
+ "ATTR_FEEDER2",
81
+ "ATTR_OUTER_FEEDER",
82
+ "ATTR_SOLAR_PUMP",
83
+ "ATTR_FIREPLACE_PUMP",
84
+ "ATTR_GCZ_CONTACT",
85
+ "ATTR_BLOW_FAN1",
86
+ "ATTR_BLOW_FAN2",
87
+ "OutputsStructure",
88
+ ]
@@ -23,3 +23,6 @@ class PendingAlertsStructure(StructureDecoder):
23
23
  return ensure_dict(data, {ATTR_PENDING_ALERTS: alerts_number}), (
24
24
  offset + alerts_number + 1
25
25
  )
26
+
27
+
28
+ __all__ = ["ATTR_PENDING_ALERTS", "PendingAlertsStructure"]
@@ -20,7 +20,7 @@ ATTR_PRODUCT: Final = "product"
20
20
  @cache
21
21
  def format_model_name(model_name: str) -> str:
22
22
  """Format a device model name."""
23
- if m := re.match(r"^([A-Z]+)\s{0,}([0-9]{3,})(.+)$", model_name, re.IGNORECASE):
23
+ if m := re.match(r"^([A-Z]+)\s*(\d{3,})(.+)$", model_name, re.IGNORECASE):
24
24
  model_device, model_number, model_suffix = m.groups()
25
25
  model_device = "ecoMAX" if model_device == "EM" else model_device
26
26
  return f"{model_device} {model_number}{model_suffix}"
@@ -28,7 +28,7 @@ def format_model_name(model_name: str) -> str:
28
28
  return model_name
29
29
 
30
30
 
31
- @dataclass
31
+ @dataclass(frozen=True)
32
32
  class ProductInfo:
33
33
  """Represents a product info provided by an UID response."""
34
34
 
@@ -78,3 +78,6 @@ class ProductInfoStructure(StructureDecoder):
78
78
  ),
79
79
  offset,
80
80
  )
81
+
82
+
83
+ __all__ = ["ATTR_PRODUCT", "ProductInfo", "ProductInfoStructure"]
@@ -84,3 +84,6 @@ class ProgramVersionStructure(Structure):
84
84
  ),
85
85
  offset + VERSION_INFO_SIZE,
86
86
  )
87
+
88
+
89
+ __all__ = ["ATTR_VERSION", "VersionInfo", "ProgramVersionStructure"]
@@ -45,7 +45,7 @@ class RegulatorDataStructure(StructureDecoder):
45
45
  """Decode bytes and return message data and offset."""
46
46
  data = ensure_dict(data)
47
47
  offset += 2
48
- regdata_version = f"{message[offset+1]}.{message[offset]}"
48
+ regdata_version = f"{message[offset + 1]}.{message[offset]}"
49
49
  if regdata_version != REGDATA_VERSION:
50
50
  return data, offset
51
51
 
@@ -63,3 +63,6 @@ class RegulatorDataStructure(StructureDecoder):
63
63
  }
64
64
 
65
65
  return data, self._offset
66
+
67
+
68
+ __all__ = ["ATTR_REGDATA", "REGDATA_VERSION", "RegulatorDataStructure"]
@@ -45,3 +45,6 @@ class RegulatorDataSchemaStructure(StructureDecoder):
45
45
  ),
46
46
  self._offset,
47
47
  )
48
+
49
+
50
+ __all__ = ["ATTR_REGDATA_SCHEMA", "RegulatorDataSchemaStructure"]
@@ -19,7 +19,7 @@ from pyplumio.const import (
19
19
  from pyplumio.devices import Device, PhysicalDevice
20
20
  from pyplumio.exceptions import FrameDataError
21
21
  from pyplumio.frames import Request
22
- from pyplumio.helpers.parameter import (
22
+ from pyplumio.parameters import (
23
23
  Number,
24
24
  NumberDescription,
25
25
  Parameter,
@@ -238,3 +238,20 @@ class SchedulesStructure(Structure):
238
238
  ),
239
239
  self._offset,
240
240
  )
241
+
242
+
243
+ __all__ = [
244
+ "ATTR_SCHEDULES",
245
+ "ATTR_SCHEDULE_PARAMETERS",
246
+ "ATTR_SCHEDULE_SWITCH",
247
+ "ATTR_SCHEDULE_PARAMETER",
248
+ "ScheduleParameterDescription",
249
+ "ScheduleParameter",
250
+ "ScheduleNumberDescription",
251
+ "ScheduleNumber",
252
+ "ScheduleSwitchDescription",
253
+ "ScheduleSwitch",
254
+ "SCHEDULE_PARAMETERS",
255
+ "collect_schedule_data",
256
+ "SchedulesStructure",
257
+ ]
@@ -41,3 +41,12 @@ class StatusesStructure(StructureDecoder):
41
41
  ),
42
42
  offset + STATUSES_SIZE,
43
43
  )
44
+
45
+
46
+ __all__ = [
47
+ "ATTR_HEATING_TARGET",
48
+ "ATTR_HEATING_STATUS",
49
+ "ATTR_WATER_HEATER_TARGET",
50
+ "ATTR_WATER_HEATER_STATUS",
51
+ "StatusesStructure",
52
+ ]
@@ -70,3 +70,25 @@ class TemperaturesStructure(StructureDecoder):
70
70
  data[TEMPERATURES[index]] = temp.value
71
71
 
72
72
  return data, offset
73
+
74
+
75
+ __all__ = [
76
+ "ATTR_HEATING_TEMP",
77
+ "ATTR_FEEDER_TEMP",
78
+ "ATTR_WATER_HEATER_TEMP",
79
+ "ATTR_OUTSIDE_TEMP",
80
+ "ATTR_RETURN_TEMP",
81
+ "ATTR_EXHAUST_TEMP",
82
+ "ATTR_OPTICAL_TEMP",
83
+ "ATTR_UPPER_BUFFER_TEMP",
84
+ "ATTR_LOWER_BUFFER_TEMP",
85
+ "ATTR_UPPER_SOLAR_TEMP",
86
+ "ATTR_LOWER_SOLAR_TEMP",
87
+ "ATTR_FIREPLACE_TEMP",
88
+ "ATTR_TOTAL_GAIN",
89
+ "ATTR_HYDRAULIC_COUPLER_TEMP",
90
+ "ATTR_EXCHANGER_TEMP",
91
+ "ATTR_AIR_IN_TEMP",
92
+ "ATTR_AIR_OUT_TEMP",
93
+ "TemperaturesStructure",
94
+ ]