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
@@ -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, SubDevice
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
- EcomaxBinaryParameter,
35
- EcomaxBinaryParameterDescription,
36
- EcomaxParameter,
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
- ScheduleBinaryParameter,
51
- ScheduleBinaryParameterDescription,
52
- ScheduleParameter,
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.REQUEST_REGULATOR_DATA_SCHEMA, provides=ATTR_REGDATA_SCHEMA
68
+ frame_type=FrameType.REQUEST_UID,
69
+ provides=ATTR_PRODUCT,
70
70
  ),
71
71
  DataFrameDescription(
72
- frame_type=FrameType.REQUEST_ECOMAX_PARAMETERS, provides=ATTR_ECOMAX_PARAMETERS
72
+ frame_type=FrameType.REQUEST_REGULATOR_DATA_SCHEMA,
73
+ provides=ATTR_REGDATA_SCHEMA,
73
74
  ),
74
75
  DataFrameDescription(
75
- frame_type=FrameType.REQUEST_ALERTS, provides=ATTR_TOTAL_ALERTS
76
+ frame_type=FrameType.REQUEST_ECOMAX_PARAMETERS,
77
+ provides=ATTR_ECOMAX_PARAMETERS,
76
78
  ),
77
79
  DataFrameDescription(
78
- frame_type=FrameType.REQUEST_SCHEDULES, provides=ATTR_SCHEDULES
80
+ frame_type=FrameType.REQUEST_ALERTS,
81
+ provides=ATTR_TOTAL_ALERTS,
79
82
  ),
80
83
  DataFrameDescription(
81
- frame_type=FrameType.REQUEST_MIXER_PARAMETERS, provides=ATTR_MIXER_PARAMETERS
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(frame_type=FrameType.REQUEST_PASSWORD, provides=ATTR_PASSWORD),
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: Iterable[DataFrameDescription] = SETUP_FRAME_TYPES
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 frame.frame_type in (
127
- FrameType.REQUEST_CHECK_DEVICE,
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(frame.response(data={ATTR_NETWORK: self._network}))
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
- if index not in mixers:
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
- if index not in thermostats:
170
- thermostats[index] = Thermostat(self.queue, parent=self, index=index)
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
- for index, values in parameters:
186
- try:
187
- description = ECOMAX_PARAMETERS[product.type][index]
188
- except IndexError:
189
- _LOGGER.warning(
190
- (
191
- "Encountered unknown ecoMAX parameter (%i): %s. "
192
- "Your device isn't fully compatible with this software and "
193
- "may not work properly. "
194
- "Please visit the issue tracker and open a feature "
195
- "request to support %s"
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
- await self.dispatch(
246
- ATTR_FUEL_BURNED,
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(self, sensors: dict[int, dict[str, Any]]) -> bool:
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
- *[self.dispatch(name, value) for name, value in sensors.items()]
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
- EcomaxBinaryParameter(
365
- device=self, description=ECOMAX_CONTROL_PARAMETER, values=values
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
- ) -> EcomaxParameter | None:
394
+ ) -> EcomaxNumber | None:
396
395
  """Add thermostat profile parameter to the dataset."""
397
- if values is not None:
398
- return EcomaxParameter(
399
- device=self, description=THERMOSTAT_PROFILE_PARAMETER, values=values
400
- )
396
+ if not values:
397
+ return None
401
398
 
402
- return None
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: EcomaxBinaryParameter = self.data[ATTR_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: EcomaxBinaryParameter = self.data[ATTR_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: Iterable[SubDevice] = (mixers | thermostats).values()
456
- await asyncio.gather(*[device.shutdown() for device in devices])
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
- MixerBinaryParameter,
16
- MixerBinaryParameterDescription,
17
- MixerParameter,
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__(self, queue: asyncio.Queue, parent: AddressableDevice, index: int = 0):
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._handle_sensors)
32
- self.subscribe(ATTR_MIXER_PARAMETERS, self._handle_parameters)
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 _handle_sensors(self, sensors: dict[str, Any]) -> bool:
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
- *[self.dispatch(name, value) for name, value in sensors.items()]
46
+ *(self.dispatch(name, value) for name, value in sensors.items())
42
47
  )
43
-
44
48
  return True
45
49
 
46
- async def _handle_parameters(
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
- name = description.name
74
- if name in self.data:
75
- parameter: MixerParameter = self.data[name]
76
- parameter.values = values
77
- await self.dispatch(name, parameter)
78
- continue
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
- cls = (
81
- MixerBinaryParameter
82
- if isinstance(description, MixerBinaryParameterDescription)
83
- else MixerParameter
84
- )
85
- await self.dispatch(
86
- name,
87
- cls(device=self, values=values, description=description, index=index),
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