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.
- {PyPlumIO-0.5.21.dist-info → PyPlumIO-0.5.23.dist-info}/METADATA +12 -10
- PyPlumIO-0.5.23.dist-info/RECORD +60 -0
- {PyPlumIO-0.5.21.dist-info → PyPlumIO-0.5.23.dist-info}/WHEEL +1 -1
- pyplumio/__init__.py +2 -2
- pyplumio/_version.py +2 -2
- pyplumio/connection.py +3 -12
- pyplumio/devices/__init__.py +16 -16
- pyplumio/devices/ecomax.py +126 -126
- pyplumio/devices/mixer.py +50 -44
- pyplumio/devices/thermostat.py +36 -35
- pyplumio/exceptions.py +9 -9
- pyplumio/filters.py +56 -37
- pyplumio/frames/__init__.py +6 -6
- pyplumio/frames/messages.py +4 -6
- pyplumio/helpers/data_types.py +8 -7
- pyplumio/helpers/event_manager.py +53 -33
- pyplumio/helpers/parameter.py +138 -52
- pyplumio/helpers/task_manager.py +7 -2
- pyplumio/helpers/timeout.py +0 -3
- pyplumio/helpers/uid.py +2 -2
- pyplumio/protocol.py +35 -28
- pyplumio/stream.py +2 -2
- pyplumio/structures/alerts.py +40 -31
- pyplumio/structures/ecomax_parameters.py +493 -282
- pyplumio/structures/frame_versions.py +5 -6
- pyplumio/structures/lambda_sensor.py +6 -6
- pyplumio/structures/mixer_parameters.py +136 -71
- pyplumio/structures/network_info.py +2 -3
- pyplumio/structures/product_info.py +0 -4
- pyplumio/structures/program_version.py +24 -17
- pyplumio/structures/schedules.py +35 -15
- pyplumio/structures/thermostat_parameters.py +82 -50
- pyplumio/utils.py +12 -7
- PyPlumIO-0.5.21.dist-info/RECORD +0 -61
- pyplumio/helpers/typing.py +0 -29
- {PyPlumIO-0.5.21.dist-info → PyPlumIO-0.5.23.dist-info}/LICENSE +0 -0
- {PyPlumIO-0.5.21.dist-info → PyPlumIO-0.5.23.dist-info}/top_level.txt +0 -0
pyplumio/devices/ecomax.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import asyncio
|
6
|
-
from collections.abc import Generator, Iterable, Sequence
|
6
|
+
from collections.abc import Coroutine, Generator, Iterable, Sequence
|
7
7
|
import logging
|
8
8
|
import time
|
9
9
|
from typing import Any, ClassVar, Final
|
@@ -17,7 +17,7 @@ from pyplumio.const import (
|
|
17
17
|
DeviceType,
|
18
18
|
FrameType,
|
19
19
|
)
|
20
|
-
from pyplumio.devices import AddressableDevice
|
20
|
+
from pyplumio.devices import AddressableDevice
|
21
21
|
from pyplumio.devices.mixer import Mixer
|
22
22
|
from pyplumio.devices.thermostat import Thermostat
|
23
23
|
from pyplumio.filters import on_change
|
@@ -31,9 +31,9 @@ from pyplumio.structures.ecomax_parameters import (
|
|
31
31
|
ECOMAX_CONTROL_PARAMETER,
|
32
32
|
ECOMAX_PARAMETERS,
|
33
33
|
THERMOSTAT_PROFILE_PARAMETER,
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
EcomaxNumber,
|
35
|
+
EcomaxSwitch,
|
36
|
+
EcomaxSwitchDescription,
|
37
37
|
)
|
38
38
|
from pyplumio.structures.frame_versions import ATTR_FRAME_VERSIONS
|
39
39
|
from pyplumio.structures.fuel_consumption import ATTR_FUEL_CONSUMPTION
|
@@ -47,9 +47,9 @@ from pyplumio.structures.schedules import (
|
|
47
47
|
ATTR_SCHEDULES,
|
48
48
|
SCHEDULE_PARAMETERS,
|
49
49
|
SCHEDULES,
|
50
|
-
|
51
|
-
|
52
|
-
|
50
|
+
ScheduleNumber,
|
51
|
+
ScheduleSwitch,
|
52
|
+
ScheduleSwitchDescription,
|
53
53
|
)
|
54
54
|
from pyplumio.structures.thermostat_parameters import (
|
55
55
|
ATTR_THERMOSTAT_PARAMETERS,
|
@@ -64,27 +64,38 @@ ATTR_FUEL_BURNED: Final = "fuel_burned"
|
|
64
64
|
MAX_TIME_SINCE_LAST_FUEL_UPDATE_NS: Final = 300 * 1000000000
|
65
65
|
|
66
66
|
SETUP_FRAME_TYPES: tuple[DataFrameDescription, ...] = (
|
67
|
-
DataFrameDescription(frame_type=FrameType.REQUEST_UID, provides=ATTR_PRODUCT),
|
68
67
|
DataFrameDescription(
|
69
|
-
frame_type=FrameType.
|
68
|
+
frame_type=FrameType.REQUEST_UID,
|
69
|
+
provides=ATTR_PRODUCT,
|
70
70
|
),
|
71
71
|
DataFrameDescription(
|
72
|
-
frame_type=FrameType.
|
72
|
+
frame_type=FrameType.REQUEST_REGULATOR_DATA_SCHEMA,
|
73
|
+
provides=ATTR_REGDATA_SCHEMA,
|
73
74
|
),
|
74
75
|
DataFrameDescription(
|
75
|
-
frame_type=FrameType.
|
76
|
+
frame_type=FrameType.REQUEST_ECOMAX_PARAMETERS,
|
77
|
+
provides=ATTR_ECOMAX_PARAMETERS,
|
76
78
|
),
|
77
79
|
DataFrameDescription(
|
78
|
-
frame_type=FrameType.
|
80
|
+
frame_type=FrameType.REQUEST_ALERTS,
|
81
|
+
provides=ATTR_TOTAL_ALERTS,
|
79
82
|
),
|
80
83
|
DataFrameDescription(
|
81
|
-
frame_type=FrameType.
|
84
|
+
frame_type=FrameType.REQUEST_SCHEDULES,
|
85
|
+
provides=ATTR_SCHEDULES,
|
86
|
+
),
|
87
|
+
DataFrameDescription(
|
88
|
+
frame_type=FrameType.REQUEST_MIXER_PARAMETERS,
|
89
|
+
provides=ATTR_MIXER_PARAMETERS,
|
82
90
|
),
|
83
91
|
DataFrameDescription(
|
84
92
|
frame_type=FrameType.REQUEST_THERMOSTAT_PARAMETERS,
|
85
93
|
provides=ATTR_THERMOSTAT_PARAMETERS,
|
86
94
|
),
|
87
|
-
DataFrameDescription(
|
95
|
+
DataFrameDescription(
|
96
|
+
frame_type=FrameType.REQUEST_PASSWORD,
|
97
|
+
provides=ATTR_PASSWORD,
|
98
|
+
),
|
88
99
|
)
|
89
100
|
|
90
101
|
_LOGGER = logging.getLogger(__name__)
|
@@ -94,11 +105,11 @@ class EcoMAX(AddressableDevice):
|
|
94
105
|
"""Represents an ecoMAX controller."""
|
95
106
|
|
96
107
|
address: ClassVar[int] = DeviceType.ECOMAX
|
97
|
-
_setup_frames:
|
108
|
+
_setup_frames: tuple[DataFrameDescription, ...] = SETUP_FRAME_TYPES
|
98
109
|
_frame_versions: dict[int, int]
|
99
110
|
_fuel_burned_timestamp_ns: int
|
100
111
|
|
101
|
-
def __init__(self, queue: asyncio.Queue, network: NetworkInfo):
|
112
|
+
def __init__(self, queue: asyncio.Queue[Frame], network: NetworkInfo):
|
102
113
|
"""Initialize a new ecoMAX controller."""
|
103
114
|
super().__init__(queue, network)
|
104
115
|
self._frame_versions = {}
|
@@ -123,11 +134,10 @@ class EcoMAX(AddressableDevice):
|
|
123
134
|
|
124
135
|
def handle_frame(self, frame: Frame) -> None:
|
125
136
|
"""Handle frame received from the ecoMAX device."""
|
126
|
-
if isinstance(frame, Request) and
|
127
|
-
|
128
|
-
FrameType.REQUEST_PROGRAM_VERSION,
|
137
|
+
if isinstance(frame, Request) and (
|
138
|
+
response := frame.response(data={ATTR_NETWORK: self._network})
|
129
139
|
):
|
130
|
-
self.queue.put_nowait(
|
140
|
+
self.queue.put_nowait(response)
|
131
141
|
|
132
142
|
super().handle_frame(frame)
|
133
143
|
|
@@ -148,12 +158,9 @@ class EcoMAX(AddressableDevice):
|
|
148
158
|
For each index, return or create an instance of the mixer class.
|
149
159
|
Once done, dispatch the 'mixers' event without waiting.
|
150
160
|
"""
|
151
|
-
mixers = self.data.setdefault(ATTR_MIXERS, {})
|
161
|
+
mixers: dict[int, Mixer] = self.data.setdefault(ATTR_MIXERS, {})
|
152
162
|
for index in indexes:
|
153
|
-
|
154
|
-
mixers[index] = Mixer(self.queue, parent=self, index=index)
|
155
|
-
|
156
|
-
yield mixers[index]
|
163
|
+
yield mixers.setdefault(index, Mixer(self.queue, parent=self, index=index))
|
157
164
|
|
158
165
|
return self.dispatch_nowait(ATTR_MIXERS, mixers)
|
159
166
|
|
@@ -164,12 +171,11 @@ class EcoMAX(AddressableDevice):
|
|
164
171
|
class. Once done, dispatch the 'thermostats' event without
|
165
172
|
waiting.
|
166
173
|
"""
|
167
|
-
thermostats = self.data.setdefault(ATTR_THERMOSTATS, {})
|
174
|
+
thermostats: dict[int, Thermostat] = self.data.setdefault(ATTR_THERMOSTATS, {})
|
168
175
|
for index in indexes:
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
yield thermostats[index]
|
176
|
+
yield thermostats.setdefault(
|
177
|
+
index, Thermostat(self.queue, parent=self, index=index)
|
178
|
+
)
|
173
179
|
|
174
180
|
return self.dispatch_nowait(ATTR_THERMOSTATS, thermostats)
|
175
181
|
|
@@ -182,41 +188,42 @@ class EcoMAX(AddressableDevice):
|
|
182
188
|
and value.
|
183
189
|
"""
|
184
190
|
product: ProductInfo = await self.get(ATTR_PRODUCT)
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
191
|
+
|
192
|
+
def _ecomax_parameter_events() -> Generator[Coroutine, Any, None]:
|
193
|
+
"""Get dispatch calls for ecoMAX parameter events."""
|
194
|
+
for index, values in parameters:
|
195
|
+
try:
|
196
|
+
description = ECOMAX_PARAMETERS[product.type][index]
|
197
|
+
except IndexError:
|
198
|
+
_LOGGER.warning(
|
199
|
+
(
|
200
|
+
"Encountered unknown ecoMAX parameter (%i): %s. "
|
201
|
+
"Your device isn't fully compatible with this software and "
|
202
|
+
"may not work properly. "
|
203
|
+
"Please visit the issue tracker and open a feature "
|
204
|
+
"request to support %s"
|
205
|
+
),
|
206
|
+
index,
|
207
|
+
values,
|
208
|
+
product.model,
|
209
|
+
)
|
210
|
+
|
211
|
+
handler = (
|
212
|
+
EcomaxSwitch
|
213
|
+
if isinstance(description, EcomaxSwitchDescription)
|
214
|
+
else EcomaxNumber
|
215
|
+
)
|
216
|
+
yield self.dispatch(
|
217
|
+
description.name,
|
218
|
+
handler.create_or_update(
|
219
|
+
device=self,
|
220
|
+
description=description,
|
221
|
+
values=values,
|
222
|
+
index=index,
|
196
223
|
),
|
197
|
-
index,
|
198
|
-
values,
|
199
|
-
product.model,
|
200
224
|
)
|
201
|
-
return False
|
202
|
-
|
203
|
-
name = description.name
|
204
|
-
if name in self.data:
|
205
|
-
parameter: EcomaxParameter = self.data[name]
|
206
|
-
parameter.values = values
|
207
|
-
await self.dispatch(name, parameter)
|
208
|
-
continue
|
209
|
-
|
210
|
-
cls = (
|
211
|
-
EcomaxBinaryParameter
|
212
|
-
if isinstance(description, EcomaxBinaryParameterDescription)
|
213
|
-
else EcomaxParameter
|
214
|
-
)
|
215
|
-
await self.dispatch(
|
216
|
-
name,
|
217
|
-
cls(device=self, values=values, description=description, index=index),
|
218
|
-
)
|
219
225
|
|
226
|
+
await asyncio.gather(*_ecomax_parameter_events())
|
220
227
|
return True
|
221
228
|
|
222
229
|
async def _update_frame_versions(self, versions: dict[int, int]) -> None:
|
@@ -236,18 +243,15 @@ class EcoMAX(AddressableDevice):
|
|
236
243
|
"""Calculate fuel burned since last sensor's data message."""
|
237
244
|
current_timestamp_ns = time.perf_counter_ns()
|
238
245
|
time_passed_ns = current_timestamp_ns - self._fuel_burned_timestamp_ns
|
246
|
+
self._fuel_burned_timestamp_ns = current_timestamp_ns
|
239
247
|
if time_passed_ns >= MAX_TIME_SINCE_LAST_FUEL_UPDATE_NS:
|
240
248
|
_LOGGER.warning(
|
241
249
|
"Skipping outdated fuel consumption data, was %i seconds old",
|
242
250
|
time_passed_ns / 1000000000,
|
243
251
|
)
|
244
252
|
else:
|
245
|
-
|
246
|
-
|
247
|
-
fuel_consumption * time_passed_ns / (3600 * 1000000000),
|
248
|
-
)
|
249
|
-
|
250
|
-
self._fuel_burned_timestamp_ns = current_timestamp_ns
|
253
|
+
fuel_burned = fuel_consumption * time_passed_ns / (3600 * 1000000000)
|
254
|
+
await self.dispatch(ATTR_FUEL_BURNED, fuel_burned)
|
251
255
|
|
252
256
|
async def _handle_mixer_parameters(
|
253
257
|
self,
|
@@ -263,15 +267,17 @@ class EcoMAX(AddressableDevice):
|
|
263
267
|
return False
|
264
268
|
|
265
269
|
await asyncio.gather(
|
266
|
-
*
|
270
|
+
*(
|
267
271
|
mixer.dispatch(ATTR_MIXER_PARAMETERS, parameters[mixer.index])
|
268
272
|
for mixer in self._mixers(indexes=parameters.keys())
|
269
|
-
|
273
|
+
)
|
270
274
|
)
|
271
275
|
|
272
276
|
return True
|
273
277
|
|
274
|
-
async def _handle_mixer_sensors(
|
278
|
+
async def _handle_mixer_sensors(
|
279
|
+
self, sensors: dict[int, dict[str, Any]] | None
|
280
|
+
) -> bool:
|
275
281
|
"""Handle mixer sensors.
|
276
282
|
|
277
283
|
For each sensor dispatch an event with the
|
@@ -282,10 +288,10 @@ class EcoMAX(AddressableDevice):
|
|
282
288
|
return False
|
283
289
|
|
284
290
|
await asyncio.gather(
|
285
|
-
*
|
291
|
+
*(
|
286
292
|
mixer.dispatch(ATTR_MIXER_SENSORS, sensors[mixer.index])
|
287
293
|
for mixer in self._mixers(indexes=sensors.keys())
|
288
|
-
|
294
|
+
)
|
289
295
|
)
|
290
296
|
|
291
297
|
return True
|
@@ -313,25 +319,27 @@ class EcoMAX(AddressableDevice):
|
|
313
319
|
self, parameters: Sequence[tuple[int, ParameterValues]]
|
314
320
|
) -> bool:
|
315
321
|
"""Add schedule parameters to the dataset."""
|
316
|
-
for index, values in parameters:
|
317
|
-
description = SCHEDULE_PARAMETERS[index]
|
318
|
-
name = description.name
|
319
|
-
if name in self.data:
|
320
|
-
parameter: ScheduleParameter = self.data[name]
|
321
|
-
parameter.values = values
|
322
|
-
await self.dispatch(name, parameter)
|
323
|
-
continue
|
324
|
-
|
325
|
-
cls = (
|
326
|
-
ScheduleBinaryParameter
|
327
|
-
if isinstance(description, ScheduleBinaryParameterDescription)
|
328
|
-
else ScheduleParameter
|
329
|
-
)
|
330
|
-
await self.dispatch(
|
331
|
-
name,
|
332
|
-
cls(device=self, values=values, description=description, index=index),
|
333
|
-
)
|
334
322
|
|
323
|
+
def _schedule_parameter_events() -> Generator[Coroutine, Any, None]:
|
324
|
+
"""Get dispatch calls for schedule parameter events."""
|
325
|
+
for index, values in parameters:
|
326
|
+
description = SCHEDULE_PARAMETERS[index]
|
327
|
+
handler = (
|
328
|
+
ScheduleSwitch
|
329
|
+
if isinstance(description, ScheduleSwitchDescription)
|
330
|
+
else ScheduleNumber
|
331
|
+
)
|
332
|
+
yield self.dispatch(
|
333
|
+
description.name,
|
334
|
+
handler.create_or_update(
|
335
|
+
device=self,
|
336
|
+
description=description,
|
337
|
+
values=values,
|
338
|
+
index=index,
|
339
|
+
),
|
340
|
+
)
|
341
|
+
|
342
|
+
await asyncio.gather(*_schedule_parameter_events())
|
335
343
|
return True
|
336
344
|
|
337
345
|
async def _handle_ecomax_sensors(self, sensors: dict[str, Any]) -> bool:
|
@@ -341,28 +349,20 @@ class EcoMAX(AddressableDevice):
|
|
341
349
|
value.
|
342
350
|
"""
|
343
351
|
await asyncio.gather(
|
344
|
-
*
|
352
|
+
*(self.dispatch(name, value) for name, value in sensors.items())
|
345
353
|
)
|
346
|
-
|
347
354
|
return True
|
348
355
|
|
349
356
|
async def _add_ecomax_control_parameter(self, mode: DeviceState) -> None:
|
350
357
|
"""Create ecoMAX control parameter instance and dispatch an event."""
|
351
|
-
description = ECOMAX_CONTROL_PARAMETER
|
352
|
-
name = description.name
|
353
|
-
values = ParameterValues(
|
354
|
-
value=int(mode != DeviceState.OFF), min_value=0, max_value=1
|
355
|
-
)
|
356
|
-
|
357
|
-
if name in self.data:
|
358
|
-
parameter: EcomaxBinaryParameter = self.data[name]
|
359
|
-
parameter.values = values
|
360
|
-
return await self.dispatch(name, parameter)
|
361
|
-
|
362
358
|
await self.dispatch(
|
363
|
-
name,
|
364
|
-
|
365
|
-
|
359
|
+
ECOMAX_CONTROL_PARAMETER.name,
|
360
|
+
EcomaxSwitch.create_or_update(
|
361
|
+
description=ECOMAX_CONTROL_PARAMETER,
|
362
|
+
device=self,
|
363
|
+
values=ParameterValues(
|
364
|
+
value=int(mode != DeviceState.OFF), min_value=0, max_value=1
|
365
|
+
),
|
366
366
|
),
|
367
367
|
)
|
368
368
|
|
@@ -380,29 +380,28 @@ class EcoMAX(AddressableDevice):
|
|
380
380
|
return False
|
381
381
|
|
382
382
|
await asyncio.gather(
|
383
|
-
*
|
383
|
+
*(
|
384
384
|
thermostat.dispatch(
|
385
385
|
ATTR_THERMOSTAT_PARAMETERS, parameters[thermostat.index]
|
386
386
|
)
|
387
387
|
for thermostat in self._thermostats(indexes=parameters.keys())
|
388
|
-
|
388
|
+
)
|
389
389
|
)
|
390
|
-
|
391
390
|
return True
|
392
391
|
|
393
392
|
async def _add_thermostat_profile_parameter(
|
394
393
|
self, values: ParameterValues | None
|
395
|
-
) ->
|
394
|
+
) -> EcomaxNumber | None:
|
396
395
|
"""Add thermostat profile parameter to the dataset."""
|
397
|
-
if
|
398
|
-
return
|
399
|
-
device=self, description=THERMOSTAT_PROFILE_PARAMETER, values=values
|
400
|
-
)
|
396
|
+
if not values:
|
397
|
+
return None
|
401
398
|
|
402
|
-
return
|
399
|
+
return EcomaxNumber(
|
400
|
+
device=self, description=THERMOSTAT_PROFILE_PARAMETER, values=values
|
401
|
+
)
|
403
402
|
|
404
403
|
async def _handle_thermostat_sensors(
|
405
|
-
self, sensors: dict[int, dict[str, Any]]
|
404
|
+
self, sensors: dict[int, dict[str, Any]] | None
|
406
405
|
) -> bool:
|
407
406
|
"""Handle thermostat sensors.
|
408
407
|
|
@@ -414,10 +413,11 @@ class EcoMAX(AddressableDevice):
|
|
414
413
|
return False
|
415
414
|
|
416
415
|
await asyncio.gather(
|
417
|
-
*
|
416
|
+
*(
|
418
417
|
thermostat.dispatch(ATTR_THERMOSTAT_SENSORS, sensors[thermostat.index])
|
419
418
|
for thermostat in self._thermostats(indexes=sensors.keys())
|
420
|
-
|
419
|
+
),
|
420
|
+
return_exceptions=True,
|
421
421
|
)
|
422
422
|
|
423
423
|
return True
|
@@ -425,7 +425,7 @@ class EcoMAX(AddressableDevice):
|
|
425
425
|
async def turn_on(self) -> bool:
|
426
426
|
"""Turn on the ecoMAX controller."""
|
427
427
|
try:
|
428
|
-
ecomax_control:
|
428
|
+
ecomax_control: EcomaxSwitch = self.data[ATTR_ECOMAX_CONTROL]
|
429
429
|
return await ecomax_control.turn_on()
|
430
430
|
except KeyError:
|
431
431
|
_LOGGER.error("ecoMAX control isn't available, please try later")
|
@@ -434,7 +434,7 @@ class EcoMAX(AddressableDevice):
|
|
434
434
|
async def turn_off(self) -> bool:
|
435
435
|
"""Turn off the ecoMAX controller."""
|
436
436
|
try:
|
437
|
-
ecomax_control:
|
437
|
+
ecomax_control: EcomaxSwitch = self.data[ATTR_ECOMAX_CONTROL]
|
438
438
|
return await ecomax_control.turn_off()
|
439
439
|
except KeyError:
|
440
440
|
_LOGGER.error("ecoMAX control isn't available, please try later")
|
@@ -450,8 +450,8 @@ class EcoMAX(AddressableDevice):
|
|
450
450
|
|
451
451
|
async def shutdown(self) -> None:
|
452
452
|
"""Shutdown tasks for the ecoMAX controller and sub-devices."""
|
453
|
-
mixers = self.get_nowait(ATTR_MIXERS, {})
|
454
|
-
thermostats = self.get_nowait(ATTR_THERMOSTATS, {})
|
455
|
-
devices
|
456
|
-
await asyncio.gather(*
|
453
|
+
mixers: dict[str, Mixer] = self.get_nowait(ATTR_MIXERS, {})
|
454
|
+
thermostats: dict[str, Thermostat] = self.get_nowait(ATTR_THERMOSTATS, {})
|
455
|
+
devices = (mixers | thermostats).values()
|
456
|
+
await asyncio.gather(*(device.shutdown() for device in devices))
|
457
457
|
await super().shutdown()
|
pyplumio/devices/mixer.py
CHANGED
@@ -3,47 +3,51 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import asyncio
|
6
|
-
from collections.abc import Sequence
|
6
|
+
from collections.abc import Coroutine, Generator, Sequence
|
7
7
|
import logging
|
8
|
-
from typing import Any
|
8
|
+
from typing import TYPE_CHECKING, Any
|
9
9
|
|
10
10
|
from pyplumio.devices import AddressableDevice, SubDevice
|
11
11
|
from pyplumio.helpers.parameter import ParameterValues
|
12
12
|
from pyplumio.structures.mixer_parameters import (
|
13
13
|
ATTR_MIXER_PARAMETERS,
|
14
14
|
MIXER_PARAMETERS,
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
MixerNumber,
|
16
|
+
MixerSwitch,
|
17
|
+
MixerSwitchDescription,
|
18
18
|
)
|
19
19
|
from pyplumio.structures.mixer_sensors import ATTR_MIXER_SENSORS
|
20
20
|
from pyplumio.structures.product_info import ATTR_PRODUCT, ProductInfo
|
21
21
|
|
22
|
+
if TYPE_CHECKING:
|
23
|
+
from pyplumio.frames import Frame
|
24
|
+
|
22
25
|
_LOGGER = logging.getLogger(__name__)
|
23
26
|
|
24
27
|
|
25
28
|
class Mixer(SubDevice):
|
26
29
|
"""Represents an mixer."""
|
27
30
|
|
28
|
-
def __init__(
|
31
|
+
def __init__(
|
32
|
+
self, queue: asyncio.Queue[Frame], parent: AddressableDevice, index: int = 0
|
33
|
+
):
|
29
34
|
"""Initialize a new mixer."""
|
30
35
|
super().__init__(queue, parent, index)
|
31
|
-
self.subscribe(ATTR_MIXER_SENSORS, self.
|
32
|
-
self.subscribe(ATTR_MIXER_PARAMETERS, self.
|
36
|
+
self.subscribe(ATTR_MIXER_SENSORS, self._handle_mixer_sensors)
|
37
|
+
self.subscribe(ATTR_MIXER_PARAMETERS, self._handle_mixer_parameters)
|
33
38
|
|
34
|
-
async def
|
39
|
+
async def _handle_mixer_sensors(self, sensors: dict[str, Any]) -> bool:
|
35
40
|
"""Handle mixer sensors.
|
36
41
|
|
37
42
|
For each sensor dispatch an event with the
|
38
43
|
sensor's name and value.
|
39
44
|
"""
|
40
45
|
await asyncio.gather(
|
41
|
-
*
|
46
|
+
*(self.dispatch(name, value) for name, value in sensors.items())
|
42
47
|
)
|
43
|
-
|
44
48
|
return True
|
45
49
|
|
46
|
-
async def
|
50
|
+
async def _handle_mixer_parameters(
|
47
51
|
self, parameters: Sequence[tuple[int, ParameterValues]]
|
48
52
|
) -> bool:
|
49
53
|
"""Handle mixer parameters.
|
@@ -52,39 +56,41 @@ class Mixer(SubDevice):
|
|
52
56
|
parameter's name and value.
|
53
57
|
"""
|
54
58
|
product: ProductInfo = await self.parent.get(ATTR_PRODUCT)
|
55
|
-
for index, values in parameters:
|
56
|
-
try:
|
57
|
-
description = MIXER_PARAMETERS[product.type][index]
|
58
|
-
except IndexError:
|
59
|
-
_LOGGER.warning(
|
60
|
-
(
|
61
|
-
"Encountered unknown mixer parameter (%i): %s. "
|
62
|
-
"Your device isn't fully compatible with this software and "
|
63
|
-
"may not work properly. "
|
64
|
-
"Please visit the issue tracker and open a feature "
|
65
|
-
"request to support %s"
|
66
|
-
),
|
67
|
-
index,
|
68
|
-
values,
|
69
|
-
product.model,
|
70
|
-
)
|
71
|
-
return False
|
72
59
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
60
|
+
def _mixer_parameter_events() -> Generator[Coroutine, Any, None]:
|
61
|
+
"""Get dispatch calls for mixer parameter events."""
|
62
|
+
for index, values in parameters:
|
63
|
+
try:
|
64
|
+
description = MIXER_PARAMETERS[product.type][index]
|
65
|
+
except IndexError:
|
66
|
+
_LOGGER.warning(
|
67
|
+
(
|
68
|
+
"Encountered unknown mixer parameter (%i): %s. "
|
69
|
+
"Your device isn't fully compatible with this software and "
|
70
|
+
"may not work properly. "
|
71
|
+
"Please visit the issue tracker and open a feature "
|
72
|
+
"request to support %s"
|
73
|
+
),
|
74
|
+
index,
|
75
|
+
values,
|
76
|
+
product.model,
|
77
|
+
)
|
78
|
+
return
|
79
79
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
80
|
+
handler = (
|
81
|
+
MixerSwitch
|
82
|
+
if isinstance(description, MixerSwitchDescription)
|
83
|
+
else MixerNumber
|
84
|
+
)
|
85
|
+
yield self.dispatch(
|
86
|
+
description.name,
|
87
|
+
handler.create_or_update(
|
88
|
+
device=self,
|
89
|
+
description=description,
|
90
|
+
values=values,
|
91
|
+
index=index,
|
92
|
+
),
|
93
|
+
)
|
89
94
|
|
95
|
+
await asyncio.gather(*_mixer_parameter_events())
|
90
96
|
return True
|