PyPlumIO 0.5.18__py3-none-any.whl → 0.5.20__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-0.5.18.dist-info → PyPlumIO-0.5.20.dist-info}/METADATA +8 -8
  2. PyPlumIO-0.5.20.dist-info/RECORD +61 -0
  3. pyplumio/__init__.py +1 -0
  4. pyplumio/__main__.py +1 -0
  5. pyplumio/_version.py +2 -2
  6. pyplumio/connection.py +2 -4
  7. pyplumio/const.py +1 -0
  8. pyplumio/devices/__init__.py +31 -22
  9. pyplumio/devices/ecomax.py +3 -11
  10. pyplumio/devices/ecoster.py +1 -0
  11. pyplumio/devices/mixer.py +1 -0
  12. pyplumio/devices/thermostat.py +1 -0
  13. pyplumio/exceptions.py +1 -0
  14. pyplumio/filters.py +8 -10
  15. pyplumio/frames/__init__.py +35 -28
  16. pyplumio/frames/messages.py +1 -0
  17. pyplumio/frames/requests.py +1 -0
  18. pyplumio/frames/responses.py +1 -0
  19. pyplumio/helpers/data_types.py +1 -0
  20. pyplumio/helpers/event_manager.py +1 -5
  21. pyplumio/helpers/factory.py +10 -3
  22. pyplumio/helpers/parameter.py +1 -0
  23. pyplumio/helpers/schedule.py +11 -7
  24. pyplumio/helpers/task_manager.py +1 -0
  25. pyplumio/helpers/timeout.py +7 -20
  26. pyplumio/helpers/typing.py +1 -0
  27. pyplumio/helpers/uid.py +1 -0
  28. pyplumio/protocol.py +63 -81
  29. pyplumio/stream.py +41 -38
  30. pyplumio/structures/__init__.py +1 -0
  31. pyplumio/structures/alerts.py +1 -0
  32. pyplumio/structures/boiler_load.py +1 -0
  33. pyplumio/structures/boiler_power.py +1 -0
  34. pyplumio/structures/ecomax_parameters.py +8 -8
  35. pyplumio/structures/fan_power.py +1 -0
  36. pyplumio/structures/frame_versions.py +1 -0
  37. pyplumio/structures/fuel_consumption.py +1 -0
  38. pyplumio/structures/fuel_level.py +1 -0
  39. pyplumio/structures/lambda_sensor.py +1 -0
  40. pyplumio/structures/mixer_parameters.py +11 -13
  41. pyplumio/structures/mixer_sensors.py +1 -0
  42. pyplumio/structures/modules.py +1 -0
  43. pyplumio/structures/network_info.py +1 -0
  44. pyplumio/structures/output_flags.py +1 -0
  45. pyplumio/structures/outputs.py +1 -0
  46. pyplumio/structures/pending_alerts.py +1 -0
  47. pyplumio/structures/product_info.py +1 -0
  48. pyplumio/structures/program_version.py +3 -2
  49. pyplumio/structures/regulator_data.py +3 -4
  50. pyplumio/structures/regulator_data_schema.py +1 -0
  51. pyplumio/structures/schedules.py +14 -11
  52. pyplumio/structures/statuses.py +1 -0
  53. pyplumio/structures/temperatures.py +1 -0
  54. pyplumio/structures/thermostat_parameters.py +17 -22
  55. pyplumio/structures/thermostat_sensors.py +1 -0
  56. pyplumio/utils.py +1 -0
  57. PyPlumIO-0.5.18.dist-info/RECORD +0 -61
  58. {PyPlumIO-0.5.18.dist-info → PyPlumIO-0.5.20.dist-info}/LICENSE +0 -0
  59. {PyPlumIO-0.5.18.dist-info → PyPlumIO-0.5.20.dist-info}/WHEEL +0 -0
  60. {PyPlumIO-0.5.18.dist-info → PyPlumIO-0.5.20.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  """Contains a task manager class."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import asyncio
@@ -1,4 +1,5 @@
1
1
  """Contains a timeout decorator."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import asyncio
@@ -16,30 +17,16 @@ _LOGGER = logging.getLogger(__name__)
16
17
 
17
18
 
18
19
  def timeout(
19
- seconds: int, raise_exception: bool = True
20
- ) -> Callable[[Callable[P, Awaitable[T]]], Callable[P, Coroutine[Any, Any, T | None]]]:
21
- """Decorate a timeout for the awaitable.
22
-
23
- Return None on exception if raise_exception parameter is set to false.
24
- """
20
+ seconds: int,
21
+ ) -> Callable[[Callable[P, Awaitable[T]]], Callable[P, Coroutine[Any, Any, T]]]:
22
+ """Decorate a timeout for the awaitable."""
25
23
 
26
24
  def decorator(
27
25
  func: Callable[P, Awaitable[T]],
28
- ) -> Callable[P, Coroutine[Any, Any, T | None]]:
26
+ ) -> Callable[P, Coroutine[Any, Any, T]]:
29
27
  @wraps(func)
30
- async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T | None:
31
- try:
32
- return await asyncio.wait_for(func(*args, **kwargs), timeout=seconds)
33
- except asyncio.TimeoutError:
34
- if raise_exception:
35
- raise
36
-
37
- _LOGGER.warning(
38
- "Function '%s' timed out after %i seconds",
39
- func.__name__,
40
- seconds,
41
- )
42
- return None
28
+ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
29
+ return await asyncio.wait_for(func(*args, **kwargs), timeout=seconds)
43
30
 
44
31
  return wrapper
45
32
 
@@ -1,4 +1,5 @@
1
1
  """Contains type aliases."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import Literal, Protocol, Union, runtime_checkable
pyplumio/helpers/uid.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """Contains an UID helpers."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from functools import reduce
pyplumio/protocol.py CHANGED
@@ -1,24 +1,19 @@
1
1
  """Contains protocol representation."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from abc import ABC, abstractmethod
5
6
  import asyncio
6
7
  from collections.abc import Awaitable, Callable
8
+ from dataclasses import dataclass
7
9
  import logging
8
- from typing import cast
9
10
 
10
11
  from pyplumio.const import ATTR_CONNECTED, DeviceType
11
- from pyplumio.devices import AddressableDevice, get_device_handler_and_name
12
- from pyplumio.exceptions import (
13
- FrameDataError,
14
- FrameError,
15
- ReadError,
16
- UnknownDeviceError,
17
- )
12
+ from pyplumio.devices import AddressableDevice
13
+ from pyplumio.exceptions import FrameError, ReadError, UnknownDeviceError
18
14
  from pyplumio.frames import Frame
19
15
  from pyplumio.frames.requests import StartMasterRequest
20
16
  from pyplumio.helpers.event_manager import EventManager
21
- from pyplumio.helpers.factory import create_instance
22
17
  from pyplumio.stream import FrameReader, FrameWriter
23
18
  from pyplumio.structures.network_info import (
24
19
  EthernetParameters,
@@ -101,49 +96,56 @@ class DummyProtocol(Protocol):
101
96
  await self.close_writer()
102
97
 
103
98
 
99
+ @dataclass
100
+ class Queues:
101
+ """Represents asyncio queues."""
102
+
103
+ __slots__ = ("read", "write")
104
+
105
+ read: asyncio.Queue
106
+ write: asyncio.Queue
107
+
108
+ async def join(self) -> None:
109
+ """Wait for queues to finish."""
110
+ for queue in (self.read, self.write):
111
+ await queue.join()
112
+
113
+
104
114
  class AsyncProtocol(Protocol, EventManager):
105
115
  """Represents an async protocol.
106
116
 
107
117
  This protocol implements producer-consumers pattern using
108
118
  asyncio queues.
109
119
 
110
- The frame producer tries to read frames from write queue, if any
111
- available, and sends them to the device via frame writer.
120
+ The frame producer tries to read frames from the write queue.
121
+ If any is available, it sends them to the device via frame writer.
112
122
 
113
- It reads stream via frame reader, creates device handler entry
114
- and puts received frame and handler into the read queue.
123
+ It then reads stream via frame reader and puts received frame
124
+ into the read queue.
115
125
 
116
- Frame consumers reads handler-frame tuples from the read queue and
117
- sends frame to respective handler for further processing.
126
+ Frame consumers read frames from the read queue, create device
127
+ entry, if needed, and send frame to the entry for the processing.
118
128
  """
119
129
 
130
+ consumers_count: int
120
131
  data: dict[str, AddressableDevice]
121
- consumers_number: int
122
132
  _network: NetworkInfo
123
- _queues: tuple[asyncio.Queue, asyncio.Queue]
133
+ _queues: Queues
124
134
 
125
135
  def __init__(
126
136
  self,
127
137
  ethernet_parameters: EthernetParameters | None = None,
128
138
  wireless_parameters: WirelessParameters | None = None,
129
- consumers_number: int = 3,
139
+ consumers_count: int = 3,
130
140
  ) -> None:
131
141
  """Initialize a new async protocol."""
132
142
  super().__init__()
133
- self.consumers_number = consumers_number
143
+ self.consumers_count = consumers_count
134
144
  self._network = NetworkInfo(
135
- eth=(
136
- EthernetParameters(status=False)
137
- if ethernet_parameters is None
138
- else ethernet_parameters
139
- ),
140
- wlan=(
141
- WirelessParameters(status=False)
142
- if wireless_parameters is None
143
- else wireless_parameters
144
- ),
145
+ eth=ethernet_parameters or EthernetParameters(status=False),
146
+ wlan=wireless_parameters or WirelessParameters(status=False),
145
147
  )
146
- self._queues = (asyncio.Queue(), asyncio.Queue())
148
+ self._queues = Queues(read=asyncio.Queue(), write=asyncio.Queue())
147
149
 
148
150
  def connection_established(
149
151
  self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
@@ -151,11 +153,12 @@ class AsyncProtocol(Protocol, EventManager):
151
153
  """Start frame producer and consumers."""
152
154
  self.reader = FrameReader(reader)
153
155
  self.writer = FrameWriter(writer)
154
- read_queue, write_queue = self.queues
155
- write_queue.put_nowait(StartMasterRequest(recipient=DeviceType.ECOMAX))
156
- self.create_task(self.frame_producer(*self.queues))
157
- for _ in range(self.consumers_number):
158
- self.create_task(self.frame_consumer(read_queue))
156
+ self._queues.write.put_nowait(StartMasterRequest(recipient=DeviceType.ECOMAX))
157
+ self.create_task(
158
+ self.frame_producer(self._queues, reader=self.reader, writer=self.writer)
159
+ )
160
+ for _ in range(self.consumers_count):
161
+ self.create_task(self.frame_consumer(self._queues.read))
159
162
 
160
163
  for device in self.data.values():
161
164
  device.dispatch_nowait(ATTR_CONNECTED, True)
@@ -178,8 +181,9 @@ class AsyncProtocol(Protocol, EventManager):
178
181
 
179
182
  async def shutdown(self) -> None:
180
183
  """Shutdown protocol tasks."""
181
- await asyncio.gather(*[queue.join() for queue in self.queues])
182
- await super(Protocol, self).shutdown()
184
+ await self._queues.join()
185
+ self.cancel_tasks()
186
+ await self.wait_until_done()
183
187
  for device in self.data.values():
184
188
  await device.shutdown()
185
189
 
@@ -188,68 +192,46 @@ class AsyncProtocol(Protocol, EventManager):
188
192
  await self.close_writer()
189
193
 
190
194
  async def frame_producer(
191
- self, read_queue: asyncio.Queue, write_queue: asyncio.Queue
195
+ self, queues: Queues, reader: FrameReader, writer: FrameWriter
192
196
  ) -> None:
193
197
  """Handle frame reads and writes."""
194
198
  await self.connected.wait()
195
- reader = cast(FrameReader, self.reader)
196
- writer = cast(FrameWriter, self.writer)
197
199
  while self.connected.is_set():
198
200
  try:
199
- if write_queue.qsize() > 0:
200
- await writer.write(await write_queue.get())
201
- write_queue.task_done()
201
+ if not queues.write.empty():
202
+ await writer.write(await queues.write.get())
203
+ queues.write.task_done()
202
204
 
203
205
  if (response := await reader.read()) is not None:
204
- device = await self.get_device_entry(
205
- cast(DeviceType, response.sender)
206
- )
207
- read_queue.put_nowait((device, response))
208
-
209
- except FrameDataError as e:
210
- _LOGGER.warning("Incorrect payload: %s", e)
211
- except ReadError as e:
212
- _LOGGER.debug("Read error: %s", e)
213
- except UnknownDeviceError as e:
214
- _LOGGER.debug("Unknown device: %s", e)
215
- except FrameError as e:
206
+ queues.read.put_nowait(response)
207
+
208
+ except (ReadError, UnknownDeviceError, FrameError) as e:
216
209
  _LOGGER.debug("Can't process received frame: %s", e)
217
210
  except (OSError, asyncio.TimeoutError):
218
211
  self.create_task(self.connection_lost())
219
212
  break
220
- except Exception as e: # pylint: disable=broad-except
221
- _LOGGER.exception(e)
213
+ except Exception:
214
+ _LOGGER.exception("Unexpected exception")
222
215
 
223
- async def frame_consumer(self, read_queue: asyncio.Queue) -> None:
216
+ async def frame_consumer(self, queue: asyncio.Queue) -> None:
224
217
  """Handle frame processing."""
225
218
  await self.connected.wait()
226
219
  while self.connected.is_set():
227
- device, frame = cast(
228
- tuple[AddressableDevice, Frame], await read_queue.get()
229
- )
220
+ frame: Frame = await queue.get()
221
+ device = await self.get_device_entry(frame.sender)
230
222
  device.handle_frame(frame)
231
- read_queue.task_done()
223
+ queue.task_done()
232
224
 
233
225
  async def get_device_entry(self, device_type: DeviceType) -> AddressableDevice:
234
- """Set up device entry."""
235
- handler, name = get_device_handler_and_name(device_type)
226
+ """Set up or return a device entry."""
227
+ name = device_type.name.lower()
236
228
  if name not in self.data:
237
- self.data[name] = await self._create_device_entry(name, handler)
229
+ device = await AddressableDevice.create(
230
+ device_type, queue=self._queues.write, network=self._network
231
+ )
232
+ device.dispatch_nowait(ATTR_CONNECTED, True)
233
+ self.create_task(device.async_setup())
234
+ self.set_event(name)
235
+ self.data[name] = device
238
236
 
239
237
  return self.data[name]
240
-
241
- async def _create_device_entry(self, name: str, handler: str) -> AddressableDevice:
242
- """Create device entry."""
243
- write_queue = self.queues[1]
244
- device: AddressableDevice = await create_instance(
245
- handler, queue=write_queue, network=self._network
246
- )
247
- device.dispatch_nowait(ATTR_CONNECTED, True)
248
- self.create_task(device.async_setup())
249
- self.set_event(name)
250
- return device
251
-
252
- @property
253
- def queues(self) -> tuple[asyncio.Queue, asyncio.Queue]:
254
- """Return the protocol queues."""
255
- return self._queues
pyplumio/stream.py CHANGED
@@ -1,15 +1,16 @@
1
1
  """Contains a frame reader and writer classes."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import asyncio
5
6
  from asyncio import IncompleteReadError, StreamReader, StreamWriter
6
7
  import logging
7
- from typing import Final
8
+ from typing import Final, NamedTuple
8
9
 
9
10
  from pyplumio.const import DeviceType
10
- from pyplumio.exceptions import ChecksumError, ReadError
11
- from pyplumio.frames import FRAME_START, Frame, bcc, get_frame_handler, struct_header
12
- from pyplumio.helpers.factory import create_instance
11
+ from pyplumio.devices import is_known_device_type
12
+ from pyplumio.exceptions import ChecksumError, ReadError, UnknownDeviceError
13
+ from pyplumio.frames import FRAME_START, Frame, bcc, struct_header
13
14
  from pyplumio.helpers.timeout import timeout
14
15
 
15
16
  READER_TIMEOUT: Final = 10
@@ -53,6 +54,18 @@ class FrameWriter:
53
54
  await self._writer.wait_closed()
54
55
 
55
56
 
57
+ class Header(NamedTuple):
58
+ """Represents a frame header."""
59
+
60
+ bytes: bytes
61
+ frame_start: int
62
+ frame_length: int
63
+ recipient: int
64
+ sender: int
65
+ econet_type: int
66
+ econet_version: int
67
+
68
+
56
69
  class FrameReader:
57
70
  """Represents a frame reader."""
58
71
 
@@ -64,11 +77,11 @@ class FrameReader:
64
77
  """Initialize a new frame reader."""
65
78
  self._reader = reader
66
79
 
67
- async def _read_header(self) -> tuple[bytes, int, int, int, int, int]:
80
+ async def _read_header(self) -> Header:
68
81
  """Locate and read a frame header.
69
82
 
70
83
  Raise pyplumio.ReadError if header size is too small and
71
- OSError on broken connection.
84
+ OSError if serial connection is broken.
72
85
  """
73
86
  while buffer := await self._reader.read(1):
74
87
  if FRAME_START not in buffer:
@@ -78,23 +91,7 @@ class FrameReader:
78
91
  if len(buffer) < struct_header.size:
79
92
  raise ReadError(f"Header can't be less than {struct_header.size} bytes")
80
93
 
81
- [
82
- _,
83
- length,
84
- recipient,
85
- sender,
86
- sender_type,
87
- econet_version,
88
- ] = struct_header.unpack_from(buffer)
89
-
90
- return (
91
- buffer,
92
- length,
93
- recipient,
94
- sender,
95
- sender_type,
96
- econet_version,
97
- )
94
+ return Header(buffer, *struct_header.unpack_from(buffer))
98
95
 
99
96
  raise OSError("Serial connection broken")
100
97
 
@@ -107,38 +104,44 @@ class FrameReader:
107
104
  checksum.
108
105
  """
109
106
  (
110
- header,
111
- length,
107
+ header_bytes,
108
+ _,
109
+ frame_length,
112
110
  recipient,
113
111
  sender,
114
- sender_type,
112
+ econet_type,
115
113
  econet_version,
116
114
  ) = await self._read_header()
117
115
 
118
116
  if recipient not in (DeviceType.ECONET, DeviceType.ALL):
119
117
  return None
120
118
 
121
- if length > MAX_FRAME_LENGTH or length < MIN_FRAME_LENGTH:
122
- raise ReadError(f"Unexpected frame length ({length})")
119
+ if not is_known_device_type(sender):
120
+ raise UnknownDeviceError(f"Unknown sender type ({sender})")
121
+
122
+ if frame_length > MAX_FRAME_LENGTH or frame_length < MIN_FRAME_LENGTH:
123
+ raise ReadError(f"Unexpected frame length ({frame_length})")
123
124
 
124
125
  try:
125
- payload = await self._reader.readexactly(length - struct_header.size)
126
+ payload = await self._reader.readexactly(frame_length - struct_header.size)
126
127
  except IncompleteReadError as e:
127
128
  raise ReadError(
128
129
  "Got an incomplete frame while trying to read "
129
- + f"'{length - struct_header.size}' bytes"
130
+ + f"'{frame_length - struct_header.size}' bytes"
130
131
  ) from e
131
132
 
132
- if payload[-2] != bcc(header + payload[:-2]):
133
- raise ChecksumError(f"Incorrect frame checksum ({payload[-2]})")
133
+ if (checksum := bcc(header_bytes + payload[:-2])) and checksum != payload[-2]:
134
+ raise ChecksumError(
135
+ f"Incorrect frame checksum ({checksum} != {payload[-2]})"
136
+ )
134
137
 
135
- frame: Frame = await create_instance(
136
- get_frame_handler(frame_type=payload[0]),
137
- recipient=recipient,
138
- message=payload[1:-2],
139
- sender=sender,
140
- sender_type=sender_type,
138
+ frame = await Frame.create(
139
+ frame_type=payload[0],
140
+ recipient=DeviceType(recipient),
141
+ sender=DeviceType(sender),
142
+ econet_type=econet_type,
141
143
  econet_version=econet_version,
144
+ message=payload[1:-2],
142
145
  )
143
146
  _LOGGER.debug("Received frame: %s", frame)
144
147
 
@@ -1,4 +1,5 @@
1
1
  """Contains structure decoders."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from abc import ABC, abstractmethod
@@ -1,4 +1,5 @@
1
1
  """Contains an alerts structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from collections.abc import Generator
@@ -1,4 +1,5 @@
1
1
  """Contains a boiler load structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import Any, Final
@@ -1,4 +1,5 @@
1
1
  """Contains a boiler power structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import math
@@ -1,9 +1,10 @@
1
1
  """Contains regulator parameter structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from collections.abc import Generator
5
6
  from dataclasses import dataclass
6
- from typing import Any, Final, cast
7
+ from typing import Any, Final
7
8
 
8
9
  from pyplumio.const import (
9
10
  ATTR_INDEX,
@@ -11,12 +12,12 @@ from pyplumio.const import (
11
12
  ATTR_SIZE,
12
13
  ATTR_VALUE,
13
14
  PERCENTAGE,
15
+ FrameType,
14
16
  ProductType,
15
17
  UnitOfMeasurement,
16
18
  )
17
19
  from pyplumio.devices import AddressableDevice
18
20
  from pyplumio.frames import Request
19
- from pyplumio.helpers.factory import create_instance
20
21
  from pyplumio.helpers.parameter import (
21
22
  BinaryParameter,
22
23
  BinaryParameterDescription,
@@ -47,10 +48,10 @@ class EcomaxParameter(Parameter):
47
48
  async def create_request(self) -> Request:
48
49
  """Create a request to change the parameter."""
49
50
  if self.description.name == ATTR_ECOMAX_CONTROL:
50
- cls = "frames.requests.EcomaxControlRequest"
51
+ frame_type = FrameType.REQUEST_ECOMAX_CONTROL
51
52
  data = {ATTR_VALUE: self.values.value}
52
53
  elif self.description.name == ATTR_THERMOSTAT_PROFILE:
53
- cls = "frames.requests.SetThermostatParameterRequest"
54
+ frame_type = FrameType.REQUEST_SET_THERMOSTAT_PARAMETER
54
55
  data = {
55
56
  ATTR_INDEX: self._index,
56
57
  ATTR_VALUE: self.values.value,
@@ -58,15 +59,14 @@ class EcomaxParameter(Parameter):
58
59
  ATTR_SIZE: 1,
59
60
  }
60
61
  else:
61
- cls = "frames.requests.SetEcomaxParameterRequest"
62
+ frame_type = FrameType.REQUEST_SET_ECOMAX_PARAMETER
62
63
  data = {
63
64
  ATTR_INDEX: self._index,
64
65
  ATTR_VALUE: self.values.value,
65
66
  }
66
67
 
67
- return cast(
68
- Request,
69
- await create_instance(cls, recipient=self.device.address, data=data),
68
+ return await Request.create(
69
+ frame_type, recipient=self.device.address, data=data
70
70
  )
71
71
 
72
72
  async def set(self, value: ParameterValueType, retries: int = 5) -> bool:
@@ -1,4 +1,5 @@
1
1
  """Contains a fan power structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import math
@@ -1,4 +1,5 @@
1
1
  """Contains a frame versions structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import Any, Final
@@ -1,4 +1,5 @@
1
1
  """Contains fuel consumption structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import math
@@ -1,4 +1,5 @@
1
1
  """Contains a fuel level structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import Any, Final
@@ -1,4 +1,5 @@
1
1
  """Contains a lambda sensor structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from contextlib import suppress
@@ -1,19 +1,20 @@
1
1
  """Contains a mixer parameter structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from collections.abc import Generator
5
6
  from dataclasses import dataclass
6
- from typing import TYPE_CHECKING, Any, Final, cast
7
+ from typing import TYPE_CHECKING, Any, Final
7
8
 
8
9
  from pyplumio.const import (
9
10
  ATTR_DEVICE_INDEX,
10
11
  ATTR_INDEX,
11
12
  ATTR_VALUE,
13
+ FrameType,
12
14
  ProductType,
13
15
  UnitOfMeasurement,
14
16
  )
15
17
  from pyplumio.frames import Request
16
- from pyplumio.helpers.factory import create_instance
17
18
  from pyplumio.helpers.parameter import (
18
19
  BinaryParameter,
19
20
  BinaryParameterDescription,
@@ -44,17 +45,14 @@ class MixerParameter(Parameter):
44
45
 
45
46
  async def create_request(self) -> Request:
46
47
  """Create a request to change the parameter."""
47
- return cast(
48
- Request,
49
- await create_instance(
50
- "frames.requests.SetMixerParameterRequest",
51
- recipient=self.device.parent.address,
52
- data={
53
- ATTR_INDEX: self._index,
54
- ATTR_VALUE: self.values.value,
55
- ATTR_DEVICE_INDEX: self.device.index,
56
- },
57
- ),
48
+ return await Request.create(
49
+ FrameType.REQUEST_SET_MIXER_PARAMETER,
50
+ recipient=self.device.parent.address,
51
+ data={
52
+ ATTR_INDEX: self._index,
53
+ ATTR_VALUE: self.values.value,
54
+ ATTR_DEVICE_INDEX: self.device.index,
55
+ },
58
56
  )
59
57
 
60
58
  async def set(self, value: ParameterValueType, retries: int = 5) -> bool:
@@ -1,4 +1,5 @@
1
1
  """Contains a mixer sensors structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from collections.abc import Generator
@@ -1,4 +1,5 @@
1
1
  """Contains a modules structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from dataclasses import dataclass
@@ -1,4 +1,5 @@
1
1
  """Contains a network info structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from dataclasses import dataclass, field
@@ -1,4 +1,5 @@
1
1
  """Contains an output flags structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import Any, Final
@@ -1,4 +1,5 @@
1
1
  """Contains an outputs structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import math
@@ -1,4 +1,5 @@
1
1
  """Contains a pending alerts structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import Any, Final
@@ -1,4 +1,5 @@
1
1
  """Contains a product info structure decoder."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from dataclasses import dataclass