PyPlumIO 0.5.40__py3-none-any.whl → 0.5.41__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/_version.py +2 -2
- pyplumio/helpers/parameter.py +57 -29
- pyplumio/structures/ecomax_parameters.py +26 -31
- pyplumio/structures/mixer_parameters.py +23 -28
- pyplumio/structures/thermostat_parameters.py +24 -30
- pyplumio/utils.py +11 -0
- {pyplumio-0.5.40.dist-info → pyplumio-0.5.41.dist-info}/METADATA +1 -1
- {pyplumio-0.5.40.dist-info → pyplumio-0.5.41.dist-info}/RECORD +11 -11
- {pyplumio-0.5.40.dist-info → pyplumio-0.5.41.dist-info}/WHEEL +0 -0
- {pyplumio-0.5.40.dist-info → pyplumio-0.5.41.dist-info}/licenses/LICENSE +0 -0
- {pyplumio-0.5.40.dist-info → pyplumio-0.5.41.dist-info}/top_level.txt +0 -0
pyplumio/_version.py
CHANGED
pyplumio/helpers/parameter.py
CHANGED
@@ -47,17 +47,6 @@ def is_valid_parameter(data: bytearray) -> bool:
|
|
47
47
|
return any(x for x in data if x != BYTE_UNDEFINED)
|
48
48
|
|
49
49
|
|
50
|
-
def parameter_value_to_int(value: NumericType | State | bool) -> int:
|
51
|
-
"""Convert a parameter value to an integer.
|
52
|
-
|
53
|
-
If the value is STATE_OFF or STATE_ON, it returns 0 or 1 respectively.
|
54
|
-
"""
|
55
|
-
if value in get_args(State):
|
56
|
-
return 1 if value == STATE_ON else 0
|
57
|
-
|
58
|
-
return int(value)
|
59
|
-
|
60
|
-
|
61
50
|
@dataclass
|
62
51
|
class ParameterValues:
|
63
52
|
"""Represents a parameter values."""
|
@@ -125,7 +114,7 @@ class Parameter(ABC):
|
|
125
114
|
|
126
115
|
if isinstance(other, (int, float, bool)) or other in get_args(State):
|
127
116
|
handler = getattr(self.values.value, method_to_call)
|
128
|
-
return handler(
|
117
|
+
return handler(self._pack_value(other))
|
129
118
|
else:
|
130
119
|
return NotImplemented
|
131
120
|
|
@@ -180,25 +169,16 @@ class Parameter(ABC):
|
|
180
169
|
)
|
181
170
|
return type(self)(self.device, self.description, values)
|
182
171
|
|
183
|
-
def validate(self, value: NumericType | State | bool) -> int:
|
184
|
-
"""Validate a parameter value."""
|
185
|
-
int_value = parameter_value_to_int(value)
|
186
|
-
if int_value < self.values.min_value or int_value > self.values.max_value:
|
187
|
-
raise ValueError(
|
188
|
-
f"Invalid value: {value}. Must be between "
|
189
|
-
f"{self.min_value} and {self.max_value}."
|
190
|
-
)
|
191
|
-
|
192
|
-
return int_value
|
193
|
-
|
194
172
|
async def set(self, value: Any, retries: int = 5, timeout: float = 5.0) -> bool:
|
195
173
|
"""Set a parameter value."""
|
196
|
-
|
174
|
+
self.validate(value)
|
175
|
+
return await self._attempt_update(self._pack_value(value), retries, timeout)
|
197
176
|
|
198
177
|
def set_nowait(self, value: Any, retries: int = 5, timeout: float = 5.0) -> None:
|
199
178
|
"""Set a parameter value without waiting."""
|
179
|
+
self.validate(value)
|
200
180
|
self.device.create_task(
|
201
|
-
self._attempt_update(self.
|
181
|
+
self._attempt_update(self._pack_value(value), retries, timeout)
|
202
182
|
)
|
203
183
|
|
204
184
|
async def _attempt_update(
|
@@ -269,6 +249,18 @@ class Parameter(ABC):
|
|
269
249
|
|
270
250
|
return parameter
|
271
251
|
|
252
|
+
@abstractmethod
|
253
|
+
def _pack_value(self, value: Any) -> int:
|
254
|
+
"""Pack the parameter value."""
|
255
|
+
|
256
|
+
@abstractmethod
|
257
|
+
def _unpack_value(self, value: int) -> Any:
|
258
|
+
"""Unpack the parameter value."""
|
259
|
+
|
260
|
+
@abstractmethod
|
261
|
+
def validate(self, value: Any) -> bool:
|
262
|
+
"""Validate a parameter value."""
|
263
|
+
|
272
264
|
@property
|
273
265
|
@abstractmethod
|
274
266
|
def value(self) -> NumericType | State | bool:
|
@@ -304,6 +296,24 @@ class Number(Parameter):
|
|
304
296
|
|
305
297
|
description: NumberDescription
|
306
298
|
|
299
|
+
def _pack_value(self, value: NumericType) -> int:
|
300
|
+
"""Pack the parameter value."""
|
301
|
+
return int(value)
|
302
|
+
|
303
|
+
def _unpack_value(self, value: int) -> NumericType:
|
304
|
+
"""Unpack the parameter value."""
|
305
|
+
return value
|
306
|
+
|
307
|
+
def validate(self, value: Any) -> bool:
|
308
|
+
"""Validate a parameter value."""
|
309
|
+
if value < self.min_value or value > self.max_value:
|
310
|
+
raise ValueError(
|
311
|
+
f"Invalid number value: {value}. Must be between "
|
312
|
+
f"{self.min_value} and {self.max_value}."
|
313
|
+
)
|
314
|
+
|
315
|
+
return True
|
316
|
+
|
307
317
|
async def set(
|
308
318
|
self, value: NumericType, retries: int = 5, timeout: float = 5.0
|
309
319
|
) -> bool:
|
@@ -323,17 +333,17 @@ class Number(Parameter):
|
|
323
333
|
@property
|
324
334
|
def value(self) -> NumericType:
|
325
335
|
"""Return the value."""
|
326
|
-
return self.values.value
|
336
|
+
return self._unpack_value(self.values.value)
|
327
337
|
|
328
338
|
@property
|
329
339
|
def min_value(self) -> NumericType:
|
330
340
|
"""Return the minimum allowed value."""
|
331
|
-
return self.values.min_value
|
341
|
+
return self._unpack_value(self.values.min_value)
|
332
342
|
|
333
343
|
@property
|
334
344
|
def max_value(self) -> NumericType:
|
335
345
|
"""Return the maximum allowed value."""
|
336
|
-
return self.values.max_value
|
346
|
+
return self._unpack_value(self.values.max_value)
|
337
347
|
|
338
348
|
@property
|
339
349
|
def unit_of_measurement(self) -> UnitOfMeasurement | Literal["%"] | None:
|
@@ -354,6 +364,24 @@ class Switch(Parameter):
|
|
354
364
|
|
355
365
|
description: SwitchDescription
|
356
366
|
|
367
|
+
def _pack_value(self, value: State | bool) -> int:
|
368
|
+
"""Pack the parameter value."""
|
369
|
+
if value in get_args(State):
|
370
|
+
return 1 if value == STATE_ON else 0
|
371
|
+
|
372
|
+
return int(value)
|
373
|
+
|
374
|
+
def _unpack_value(self, value: int) -> State:
|
375
|
+
"""Unpack the parameter value."""
|
376
|
+
return STATE_ON if value == 1 else STATE_OFF
|
377
|
+
|
378
|
+
def validate(self, value: Any) -> bool:
|
379
|
+
"""Validate a parameter value."""
|
380
|
+
if not isinstance(value, bool) and value not in get_args(State):
|
381
|
+
raise ValueError(f"Invalid switch value: {value}. Must be 'on' or 'off'.")
|
382
|
+
|
383
|
+
return True
|
384
|
+
|
357
385
|
async def set(
|
358
386
|
self, value: State | bool, retries: int = 5, timeout: float = 5.0
|
359
387
|
) -> bool:
|
@@ -399,7 +427,7 @@ class Switch(Parameter):
|
|
399
427
|
@property
|
400
428
|
def value(self) -> State:
|
401
429
|
"""Return the value."""
|
402
|
-
return
|
430
|
+
return self._unpack_value(self.values.value)
|
403
431
|
|
404
432
|
@property
|
405
433
|
def min_value(self) -> Literal["off"]:
|
@@ -23,6 +23,7 @@ from pyplumio.frames import Request
|
|
23
23
|
from pyplumio.helpers.parameter import (
|
24
24
|
Number,
|
25
25
|
NumberDescription,
|
26
|
+
NumericType,
|
26
27
|
Parameter,
|
27
28
|
ParameterDescription,
|
28
29
|
ParameterValues,
|
@@ -32,7 +33,7 @@ from pyplumio.helpers.parameter import (
|
|
32
33
|
)
|
33
34
|
from pyplumio.structures import StructureDecoder
|
34
35
|
from pyplumio.structures.thermostat_parameters import ATTR_THERMOSTAT_PROFILE
|
35
|
-
from pyplumio.utils import ensure_dict
|
36
|
+
from pyplumio.utils import ensure_dict, is_divisible
|
36
37
|
|
37
38
|
if TYPE_CHECKING:
|
38
39
|
from pyplumio.devices.ecomax import EcoMAX
|
@@ -89,7 +90,7 @@ class EcomaxParameter(Parameter):
|
|
89
90
|
class EcomaxNumberDescription(EcomaxParameterDescription, NumberDescription):
|
90
91
|
"""Represents an ecoMAX number description."""
|
91
92
|
|
92
|
-
|
93
|
+
step: float = 1.0
|
93
94
|
offset: int = 0
|
94
95
|
precision: int = 6
|
95
96
|
|
@@ -101,31 +102,25 @@ class EcomaxNumber(EcomaxParameter, Number):
|
|
101
102
|
|
102
103
|
description: EcomaxNumberDescription
|
103
104
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
105
|
+
def validate(self, value: NumericType) -> bool:
|
106
|
+
"""Validate the parameter value."""
|
107
|
+
if not is_divisible(value, self.description.step, self.description.precision):
|
108
|
+
raise ValueError(
|
109
|
+
f"Invalid value: {value}. The value must be adjusted in increments of "
|
110
|
+
f"{self.description.step}."
|
111
|
+
)
|
111
112
|
|
112
|
-
|
113
|
-
def value(self) -> float:
|
114
|
-
"""Return the value."""
|
115
|
-
value = self.values.value - self.description.offset
|
116
|
-
return round(value * self.description.multiplier, self.description.precision)
|
113
|
+
return super().validate(value)
|
117
114
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
value
|
122
|
-
return round(value * self.description.multiplier, self.description.precision)
|
115
|
+
def _pack_value(self, value: NumericType) -> int:
|
116
|
+
"""Pack the parameter value."""
|
117
|
+
value += self.description.offset
|
118
|
+
return round(value / self.description.step)
|
123
119
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
value
|
128
|
-
return round(value * self.description.multiplier, self.description.precision)
|
120
|
+
def _unpack_value(self, value: int) -> NumericType:
|
121
|
+
"""Unpack the parameter value."""
|
122
|
+
value -= self.description.offset
|
123
|
+
return round(value * self.description.step, self.description.precision)
|
129
124
|
|
130
125
|
|
131
126
|
@dataslots
|
@@ -468,8 +463,8 @@ ECOMAX_PARAMETERS: dict[ProductType, tuple[EcomaxParameterDescription, ...]] = {
|
|
468
463
|
),
|
469
464
|
EcomaxNumberDescription(
|
470
465
|
name="max_fuel_flow",
|
471
|
-
|
472
|
-
unit_of_measurement=UnitOfMeasurement.
|
466
|
+
step=0.2,
|
467
|
+
unit_of_measurement=UnitOfMeasurement.KILOGRAMS_PER_HOUR,
|
473
468
|
),
|
474
469
|
EcomaxNumberDescription(
|
475
470
|
name="feeder_calibration",
|
@@ -479,7 +474,7 @@ ECOMAX_PARAMETERS: dict[ProductType, tuple[EcomaxParameterDescription, ...]] = {
|
|
479
474
|
),
|
480
475
|
EcomaxNumberDescription(
|
481
476
|
name="fuel_calorific_value",
|
482
|
-
|
477
|
+
step=0.1,
|
483
478
|
unit_of_measurement=UnitOfMeasurement.KILO_WATT_HOUR_PER_KILOGRAM,
|
484
479
|
),
|
485
480
|
EcomaxNumberDescription(
|
@@ -546,7 +541,7 @@ ECOMAX_PARAMETERS: dict[ProductType, tuple[EcomaxParameterDescription, ...]] = {
|
|
546
541
|
),
|
547
542
|
EcomaxNumberDescription(
|
548
543
|
name="heating_curve",
|
549
|
-
|
544
|
+
step=0.1,
|
550
545
|
),
|
551
546
|
EcomaxNumberDescription(
|
552
547
|
name="heating_curve_shift",
|
@@ -703,12 +698,12 @@ ECOMAX_PARAMETERS: dict[ProductType, tuple[EcomaxParameterDescription, ...]] = {
|
|
703
698
|
),
|
704
699
|
EcomaxNumberDescription(
|
705
700
|
name="solar_pump_on_delta_temp",
|
706
|
-
|
701
|
+
step=0.1,
|
707
702
|
unit_of_measurement=UnitOfMeasurement.CELSIUS,
|
708
703
|
),
|
709
704
|
EcomaxNumberDescription(
|
710
705
|
name="solar_pump_off_delta_temp",
|
711
|
-
|
706
|
+
step=0.1,
|
712
707
|
unit_of_measurement=UnitOfMeasurement.CELSIUS,
|
713
708
|
),
|
714
709
|
EcomaxNumberDescription(
|
@@ -801,7 +796,7 @@ ECOMAX_PARAMETERS: dict[ProductType, tuple[EcomaxParameterDescription, ...]] = {
|
|
801
796
|
),
|
802
797
|
EcomaxNumberDescription(
|
803
798
|
name="thermostat_hysteresis",
|
804
|
-
|
799
|
+
step=0.1,
|
805
800
|
unit_of_measurement=UnitOfMeasurement.CELSIUS,
|
806
801
|
),
|
807
802
|
EcomaxNumberDescription(
|
@@ -20,6 +20,7 @@ from pyplumio.frames import Request
|
|
20
20
|
from pyplumio.helpers.parameter import (
|
21
21
|
Number,
|
22
22
|
NumberDescription,
|
23
|
+
NumericType,
|
23
24
|
Parameter,
|
24
25
|
ParameterDescription,
|
25
26
|
ParameterValues,
|
@@ -28,7 +29,7 @@ from pyplumio.helpers.parameter import (
|
|
28
29
|
unpack_parameter,
|
29
30
|
)
|
30
31
|
from pyplumio.structures import StructureDecoder
|
31
|
-
from pyplumio.utils import ensure_dict
|
32
|
+
from pyplumio.utils import ensure_dict, is_divisible
|
32
33
|
|
33
34
|
if TYPE_CHECKING:
|
34
35
|
from pyplumio.devices.mixer import Mixer
|
@@ -71,7 +72,7 @@ class MixerParameter(Parameter):
|
|
71
72
|
class MixerNumberDescription(MixerParameterDescription, NumberDescription):
|
72
73
|
"""Represent a mixer number description."""
|
73
74
|
|
74
|
-
|
75
|
+
step: float = 1.0
|
75
76
|
offset: int = 0
|
76
77
|
precision: int = 6
|
77
78
|
|
@@ -83,31 +84,25 @@ class MixerNumber(MixerParameter, Number):
|
|
83
84
|
|
84
85
|
description: MixerNumberDescription
|
85
86
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
+
)
|
93
94
|
|
94
|
-
|
95
|
-
def value(self) -> float:
|
96
|
-
"""Return the parameter value."""
|
97
|
-
value = self.values.value - self.description.offset
|
98
|
-
return round(value * self.description.multiplier, self.description.precision)
|
95
|
+
return super().validate(value)
|
99
96
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
value
|
104
|
-
return round(value * self.description.multiplier, self.description.precision)
|
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)
|
105
101
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
value
|
110
|
-
return round(value * self.description.multiplier, self.description.precision)
|
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)
|
111
106
|
|
112
107
|
|
113
108
|
@dataslots
|
@@ -147,7 +142,7 @@ MIXER_PARAMETERS: dict[ProductType, tuple[MixerParameterDescription, ...]] = {
|
|
147
142
|
),
|
148
143
|
MixerNumberDescription(
|
149
144
|
name="heating_curve",
|
150
|
-
|
145
|
+
step=0.1,
|
151
146
|
),
|
152
147
|
MixerNumberDescription(
|
153
148
|
name="heating_curve_shift",
|
@@ -162,7 +157,7 @@ MIXER_PARAMETERS: dict[ProductType, tuple[MixerParameterDescription, ...]] = {
|
|
162
157
|
),
|
163
158
|
MixerNumberDescription(
|
164
159
|
name="mixer_input_dead_zone",
|
165
|
-
|
160
|
+
step=0.1,
|
166
161
|
unit_of_measurement=UnitOfMeasurement.CELSIUS,
|
167
162
|
),
|
168
163
|
MixerSwitchDescription(
|
@@ -232,7 +227,7 @@ MIXER_PARAMETERS: dict[ProductType, tuple[MixerParameterDescription, ...]] = {
|
|
232
227
|
),
|
233
228
|
MixerNumberDescription(
|
234
229
|
name="mixer_input_dead_zone",
|
235
|
-
|
230
|
+
step=0.1,
|
236
231
|
unit_of_measurement=UnitOfMeasurement.CELSIUS,
|
237
232
|
),
|
238
233
|
MixerNumberDescription(
|
@@ -243,7 +238,7 @@ MIXER_PARAMETERS: dict[ProductType, tuple[MixerParameterDescription, ...]] = {
|
|
243
238
|
),
|
244
239
|
MixerNumberDescription(
|
245
240
|
name="heating_curve",
|
246
|
-
|
241
|
+
step=0.1,
|
247
242
|
),
|
248
243
|
MixerNumberDescription(
|
249
244
|
name="heating_curve_shift",
|
@@ -20,6 +20,7 @@ from pyplumio.frames import Request
|
|
20
20
|
from pyplumio.helpers.parameter import (
|
21
21
|
Number,
|
22
22
|
NumberDescription,
|
23
|
+
NumericType,
|
23
24
|
Parameter,
|
24
25
|
ParameterDescription,
|
25
26
|
ParameterValues,
|
@@ -29,7 +30,7 @@ from pyplumio.helpers.parameter import (
|
|
29
30
|
)
|
30
31
|
from pyplumio.structures import StructureDecoder
|
31
32
|
from pyplumio.structures.thermostat_sensors import ATTR_THERMOSTATS_AVAILABLE
|
32
|
-
from pyplumio.utils import ensure_dict
|
33
|
+
from pyplumio.utils import ensure_dict, is_divisible
|
33
34
|
|
34
35
|
if TYPE_CHECKING:
|
35
36
|
from pyplumio.devices.thermostat import Thermostat
|
@@ -92,7 +93,7 @@ class ThermostatParameter(Parameter):
|
|
92
93
|
class ThermostatNumberDescription(ThermostatParameterDescription, NumberDescription):
|
93
94
|
"""Represent a thermostat number description."""
|
94
95
|
|
95
|
-
|
96
|
+
step: float = 1.0
|
96
97
|
precision: int = 6
|
97
98
|
|
98
99
|
|
@@ -103,30 +104,23 @@ class ThermostatNumber(ThermostatParameter, Number):
|
|
103
104
|
|
104
105
|
description: ThermostatNumberDescription
|
105
106
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
107
|
+
def validate(self, value: NumericType) -> bool:
|
108
|
+
"""Validate the parameter value."""
|
109
|
+
if not is_divisible(value, self.description.step, self.description.precision):
|
110
|
+
raise ValueError(
|
111
|
+
f"Invalid value: {value}. The value must be adjusted in increments of "
|
112
|
+
f"{self.description.step}."
|
113
|
+
)
|
112
114
|
|
113
|
-
|
114
|
-
def value(self) -> float:
|
115
|
-
"""Return the value."""
|
116
|
-
value = self.values.value * self.description.multiplier
|
117
|
-
return round(value, self.description.precision)
|
115
|
+
return super().validate(value)
|
118
116
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
value = self.values.min_value * self.description.multiplier
|
123
|
-
return round(value, self.description.precision)
|
117
|
+
def _pack_value(self, value: NumericType) -> int:
|
118
|
+
"""Pack the parameter value."""
|
119
|
+
return round(value / self.description.step)
|
124
120
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
value = self.values.max_value * self.description.multiplier
|
129
|
-
return round(value, self.description.precision)
|
121
|
+
def _unpack_value(self, value: int) -> NumericType:
|
122
|
+
"""Unpack the parameter value."""
|
123
|
+
return round(value * self.description.step, self.description.precision)
|
130
124
|
|
131
125
|
|
132
126
|
@dataslots
|
@@ -150,13 +144,13 @@ THERMOSTAT_PARAMETERS: tuple[ThermostatParameterDescription, ...] = (
|
|
150
144
|
ThermostatNumberDescription(
|
151
145
|
name="party_target_temp",
|
152
146
|
size=2,
|
153
|
-
|
147
|
+
step=0.1,
|
154
148
|
unit_of_measurement=UnitOfMeasurement.CELSIUS,
|
155
149
|
),
|
156
150
|
ThermostatNumberDescription(
|
157
151
|
name="holidays_target_temp",
|
158
152
|
size=2,
|
159
|
-
|
153
|
+
step=0.1,
|
160
154
|
unit_of_measurement=UnitOfMeasurement.CELSIUS,
|
161
155
|
),
|
162
156
|
ThermostatNumberDescription(
|
@@ -181,31 +175,31 @@ THERMOSTAT_PARAMETERS: tuple[ThermostatParameterDescription, ...] = (
|
|
181
175
|
),
|
182
176
|
ThermostatNumberDescription(
|
183
177
|
name="hysteresis",
|
184
|
-
|
178
|
+
step=0.1,
|
185
179
|
unit_of_measurement=UnitOfMeasurement.CELSIUS,
|
186
180
|
),
|
187
181
|
ThermostatNumberDescription(
|
188
182
|
name="day_target_temp",
|
189
183
|
size=2,
|
190
|
-
|
184
|
+
step=0.1,
|
191
185
|
unit_of_measurement=UnitOfMeasurement.CELSIUS,
|
192
186
|
),
|
193
187
|
ThermostatNumberDescription(
|
194
188
|
name="night_target_temp",
|
195
189
|
size=2,
|
196
|
-
|
190
|
+
step=0.1,
|
197
191
|
unit_of_measurement=UnitOfMeasurement.CELSIUS,
|
198
192
|
),
|
199
193
|
ThermostatNumberDescription(
|
200
194
|
name="antifreeze_target_temp",
|
201
195
|
size=2,
|
202
|
-
|
196
|
+
step=0.1,
|
203
197
|
unit_of_measurement=UnitOfMeasurement.CELSIUS,
|
204
198
|
),
|
205
199
|
ThermostatNumberDescription(
|
206
200
|
name="heating_target_temp",
|
207
201
|
size=2,
|
208
|
-
|
202
|
+
step=0.1,
|
209
203
|
unit_of_measurement=UnitOfMeasurement.CELSIUS,
|
210
204
|
),
|
211
205
|
ThermostatNumberDescription(
|
pyplumio/utils.py
CHANGED
@@ -28,3 +28,14 @@ def ensure_dict(initial: dict[KT, VT] | None, *args: dict[KT, VT]) -> dict[KT, V
|
|
28
28
|
data |= extra
|
29
29
|
|
30
30
|
return data
|
31
|
+
|
32
|
+
|
33
|
+
def is_divisible(a: float, b: float, precision: int = 6) -> bool:
|
34
|
+
"""Check if a is divisible by b."""
|
35
|
+
scale: int = 10**precision
|
36
|
+
b_scaled = round(b * scale)
|
37
|
+
if b_scaled == 0:
|
38
|
+
raise ValueError("Division by zero is not allowed.")
|
39
|
+
|
40
|
+
a_scaled = round(a * scale)
|
41
|
+
return a_scaled % b_scaled == 0
|
@@ -1,6 +1,6 @@
|
|
1
1
|
pyplumio/__init__.py,sha256=3ibJ43RIdfFrWp1PAsQixybAA--NPRw43B5OdLOwsU8,3319
|
2
2
|
pyplumio/__main__.py,sha256=3IwHHSq-iay5FaeMc95klobe-xv82yydSKcBE7BFZ6M,500
|
3
|
-
pyplumio/_version.py,sha256=
|
3
|
+
pyplumio/_version.py,sha256=TuHCjk-kMeHq9XPeWUeIY_Liz22Ivd6BQxf4Zhp3zGg,513
|
4
4
|
pyplumio/connection.py,sha256=-dbrIK6ewoYNeBQod9ZmXT8JkxMKbcS6nosINFsg9RI,5972
|
5
5
|
pyplumio/const.py,sha256=26s1TJF7IJa6o1pjDmHaAzPgMJ5c-fb0jeSkzDQ6Bic,5577
|
6
6
|
pyplumio/exceptions.py,sha256=_B_0EgxDxd2XyYv3WpZM733q0cML5m6J-f55QOvYRpI,996
|
@@ -8,7 +8,7 @@ pyplumio/filters.py,sha256=AMW1zHQ1YjJfHX7e87Dhv7AGixJ3y9Vn-_JAQn7vIsg,12526
|
|
8
8
|
pyplumio/protocol.py,sha256=VRxrj8vZ1FMawqblKkyxg_V61TBSvVynd9u0JXYnMUU,8090
|
9
9
|
pyplumio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
pyplumio/stream.py,sha256=Ne-mWkO6FpILAjGdagbAh_VL3QEla-eDiT2N-kOc5o4,4883
|
11
|
-
pyplumio/utils.py,sha256=
|
11
|
+
pyplumio/utils.py,sha256=psvR5hTJGs8Hi3xv7dH58S7BVFv14qIZLZmiHCdPgdY,1146
|
12
12
|
pyplumio/devices/__init__.py,sha256=Erjd3DeEop_yelnLtRRaPbwMIuD1NwVh7dMM1_2KxtI,8155
|
13
13
|
pyplumio/devices/ecomax.py,sha256=0LCVeTMzC1isu0HE_MHp7bEXJXUCinXNWVVFTn4k92E,15855
|
14
14
|
pyplumio/devices/ecoster.py,sha256=jNWli7ye9T6yfkcFJZhhUHH7KOv-L6AgYFp_dKyv3OM,263
|
@@ -22,7 +22,7 @@ pyplumio/helpers/__init__.py,sha256=H2xxdkF-9uADLwEbfBUoxNTdwru3L5Z2cfJjgsuRsn0,
|
|
22
22
|
pyplumio/helpers/data_types.py,sha256=nB3afOLmppgSCWkZoX1-1yWPNMMNSem77x7XQ1Mi8H8,9103
|
23
23
|
pyplumio/helpers/event_manager.py,sha256=xQOfiP_nP1Pz5zhB6HU5gXyyJXjhisYshL8_HRxDgt8,6412
|
24
24
|
pyplumio/helpers/factory.py,sha256=9uXUmVRvPkg9IyrfYYVbz9wsYAXltMTXkm1x82dhMyA,1126
|
25
|
-
pyplumio/helpers/parameter.py,sha256=
|
25
|
+
pyplumio/helpers/parameter.py,sha256=j-t2Yj8NhQzqGSRN9DFtsWVuoh49Lq7I5_8CY5dqaU8,13587
|
26
26
|
pyplumio/helpers/schedule.py,sha256=Dl28p3iz8okr5AT5v78WiJv6ggYlO-f2Jk6r5t1wY0A,5266
|
27
27
|
pyplumio/helpers/task_manager.py,sha256=HAd69yGTRL0zQsu-ywnbLu1UXiJzgHWuhYWA--vs4lQ,1181
|
28
28
|
pyplumio/helpers/timeout.py,sha256=JAhWNtIpcXyVILIwHWVy5mYofqbbRDGKLdTUKkQuajs,772
|
@@ -31,13 +31,13 @@ pyplumio/structures/__init__.py,sha256=EjK-5qJZ0F7lpP2b6epvTMg9cIBl4Kn91nqNkEcLw
|
|
31
31
|
pyplumio/structures/alerts.py,sha256=O4P0sbBu1g7AN_AApcViy9CcrY5Vry_LZJgidNUF7Co,3664
|
32
32
|
pyplumio/structures/boiler_load.py,sha256=p3mOzZUU-g7A2tG_yp8podEqpI81hlsOZmHELyPNRY8,838
|
33
33
|
pyplumio/structures/boiler_power.py,sha256=72qsvccg49FdRdXv2f2K5sGpjT7wAOLFjlIGWpO-DVg,901
|
34
|
-
pyplumio/structures/ecomax_parameters.py,sha256=
|
34
|
+
pyplumio/structures/ecomax_parameters.py,sha256=ysnHYa0OscL8fxrkpIn2VxqOdNXZdEDD12N2HZ39kQU,27760
|
35
35
|
pyplumio/structures/fan_power.py,sha256=Q5fv-7_2NVuLeQPIVIylvgN7M8-a9D8rRUE0QGjyS3w,871
|
36
36
|
pyplumio/structures/frame_versions.py,sha256=hbcVuhuPNy5qd39Vk7w4WdPCW-TNx1cAYWzA2mXocyk,1548
|
37
37
|
pyplumio/structures/fuel_consumption.py,sha256=_p2dI4H67Eopn7IF0Gj77A8c_8lNKhhDDAtmugxLd4s,976
|
38
38
|
pyplumio/structures/fuel_level.py,sha256=mJpp1dnRD1wXi_6EyNX7TNXosjcr905rSHOnuZ5VD74,1069
|
39
39
|
pyplumio/structures/lambda_sensor.py,sha256=JNSCiBJoM8Uk3OGbmFIigaLOntQST5U_UrmCpaQBlM0,1595
|
40
|
-
pyplumio/structures/mixer_parameters.py,sha256=
|
40
|
+
pyplumio/structures/mixer_parameters.py,sha256=MZ97_g14krUdMPlyrOLqKcwEfq8PYq7f7AzJDbcM0O0,8840
|
41
41
|
pyplumio/structures/mixer_sensors.py,sha256=-cN7U-Fr2fmAQ5McQL7bZUC8CFlb1y8TN0f_dqy3UK0,2312
|
42
42
|
pyplumio/structures/modules.py,sha256=oXUIqrOAV1dZzBV5zUH3HDUSFvNOjpUSx0TF9nZVnbs,2569
|
43
43
|
pyplumio/structures/network_info.py,sha256=kPxmIaDGm5SyLRKVFzcrODlUtB0u5JjiZqekoKSyDpA,4159
|
@@ -51,10 +51,10 @@ pyplumio/structures/regulator_data_schema.py,sha256=XM6M9ep3NyogbLPqp88mMTg8Sa9e
|
|
51
51
|
pyplumio/structures/schedules.py,sha256=_D8HmxMVvAAPb0cc_xSxXFRNwR9u-RWuyTy0Z5KscUk,6717
|
52
52
|
pyplumio/structures/statuses.py,sha256=wkoynyMRr1VREwfBC6vU48kPA8ZQ83pcXuciy2xHJrk,1166
|
53
53
|
pyplumio/structures/temperatures.py,sha256=1CDzehNmbALz1Jyt_9gZNIk52q6Wv-xQXjijVDCVYec,2337
|
54
|
-
pyplumio/structures/thermostat_parameters.py,sha256=
|
54
|
+
pyplumio/structures/thermostat_parameters.py,sha256=j7uyw5eIgMgXxndaqV-XKFne6DL2_RFtU_BeJcOo9sk,8075
|
55
55
|
pyplumio/structures/thermostat_sensors.py,sha256=8e1TxYIJTQKT0kIGO9gG4hGdLOBUpIhiPToQyOMyeNE,3237
|
56
|
-
pyplumio-0.5.
|
57
|
-
pyplumio-0.5.
|
58
|
-
pyplumio-0.5.
|
59
|
-
pyplumio-0.5.
|
60
|
-
pyplumio-0.5.
|
56
|
+
pyplumio-0.5.41.dist-info/licenses/LICENSE,sha256=m-UuZFjXJ22uPTGm9kSHS8bqjsf5T8k2wL9bJn1Y04o,1088
|
57
|
+
pyplumio-0.5.41.dist-info/METADATA,sha256=HyZfZdbTrMXlAbh4-CTtIJxM3NZRLXKm_8AfK37RkzM,5532
|
58
|
+
pyplumio-0.5.41.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
59
|
+
pyplumio-0.5.41.dist-info/top_level.txt,sha256=kNBz9UPPkPD9teDn3U_sEy5LjzwLm9KfADCXtBlbw8A,9
|
60
|
+
pyplumio-0.5.41.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|