PyPlumIO 0.5.20__py3-none-any.whl → 0.5.21__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyPlumIO
3
- Version: 0.5.20
3
+ Version: 0.5.21
4
4
  Summary: PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
5
5
  Author-email: Denis Paavilainen <denpa@denpa.pro>
6
6
  License: MIT License
@@ -35,11 +35,11 @@ Provides-Extra: test
35
35
  Requires-Dist: codespell ==2.3.0 ; extra == 'test'
36
36
  Requires-Dist: coverage ==7.5.3 ; extra == 'test'
37
37
  Requires-Dist: mypy ==1.10.0 ; extra == 'test'
38
- Requires-Dist: pyserial-asyncio-fast ==0.11 ; extra == 'test'
39
- Requires-Dist: pytest ==8.2.1 ; extra == 'test'
38
+ Requires-Dist: pyserial-asyncio-fast ==0.12 ; extra == 'test'
39
+ Requires-Dist: pytest ==8.2.2 ; extra == 'test'
40
40
  Requires-Dist: pytest-asyncio ==0.23.7 ; extra == 'test'
41
- Requires-Dist: ruff ==0.4.7 ; extra == 'test'
42
- Requires-Dist: tox ==4.15.0 ; extra == 'test'
41
+ Requires-Dist: ruff ==0.4.9 ; extra == 'test'
42
+ Requires-Dist: tox ==4.15.1 ; extra == 'test'
43
43
  Requires-Dist: types-pyserial ==3.5.0.20240527 ; extra == 'test'
44
44
 
45
45
  # PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
@@ -1,30 +1,30 @@
1
1
  pyplumio/__init__.py,sha256=cclyAwy7OsW673iHcwkVrJSNnf32oF51Y_0uEEF5cdI,3293
2
2
  pyplumio/__main__.py,sha256=3IwHHSq-iay5FaeMc95klobe-xv82yydSKcBE7BFZ6M,500
3
- pyplumio/_version.py,sha256=eONbmd59kUJvI6efSg_Cy7fvmhvKc1Uota8ebahOmE0,413
3
+ pyplumio/_version.py,sha256=qwvYpiZ4pV_W2HOchGsudGTbl7hCvV-xhS0ii39Ac0I,413
4
4
  pyplumio/connection.py,sha256=ZZHXHFpbOBVd9DGZV_H8lpdYtYoc3nP9fRolKATKDnQ,6096
5
5
  pyplumio/const.py,sha256=8rpiVbVb5R_6Rm6J2sgCnaVrkD-2Fzhd1RYMz0MBgwo,3915
6
6
  pyplumio/exceptions.py,sha256=193z3zfnswYhIYPzCIpxCiWat4qI3cV85sqT4YOSo-4,699
7
7
  pyplumio/filters.py,sha256=bIonYc_QbGMsL8aWweSLUmP7gKqDD646zELf_PqqQBg,11161
8
- pyplumio/protocol.py,sha256=i4C7WYALp6BEHzeMjiebH8GWI2qGIEPk6OlUIx_2UP4,7870
8
+ pyplumio/protocol.py,sha256=Ci4p4bqADfWeGk9fzcGMudRbe-dFFa1UNot9no5Lj3M,7845
9
9
  pyplumio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  pyplumio/stream.py,sha256=DqMqdi3HG9hODgfGo4eTKLkfoaSh5RS4kBHNn3ODvVg,4472
11
11
  pyplumio/utils.py,sha256=GV7P1hPLoQsx3uqYviQ15FXJmkmTxwtDibAc-yRarvo,688
12
12
  pyplumio/devices/__init__.py,sha256=O5SyEt_x1nJ1JYkG6v3dTZ54tu9sKIdj4l256JhvLHg,6585
13
- pyplumio/devices/ecomax.py,sha256=W9YW4nw6v2wdKKqFblUx03_hFJw0dVjnVkDHNL8P2dg,16632
13
+ pyplumio/devices/ecomax.py,sha256=IPHyC8OjnGaQ_ZztcchgQjEJmNj8LfnP9sTJyslEQ14,16914
14
14
  pyplumio/devices/ecoster.py,sha256=J4YtPmFmFwaq4LzYf28aMmB97cRAbMsVyUdBLGki42g,313
15
- pyplumio/devices/mixer.py,sha256=PGk0lXveN6q5sm0B50YG0yCVYoKi1a9TA3v44bGgi3A,2947
16
- pyplumio/devices/thermostat.py,sha256=UR3KEkIBU-zx2LALvrt_QiFkiBT1CpKk7vYR1MSYdWY,2402
15
+ pyplumio/devices/mixer.py,sha256=qJAmar7DdsQL1Syg0WOCBVQn3GyBTWEVyr5ZfpGytCk,2975
16
+ pyplumio/devices/thermostat.py,sha256=HCnLVBX8mn6lmpCgl1DbDoCMI6T97sqmK-36cYcjXVA,2430
17
17
  pyplumio/frames/__init__.py,sha256=BAMbMHbn4F9psrf3sv0eJQA2Jd86qf7LQ5vBQY59gjA,7462
18
18
  pyplumio/frames/messages.py,sha256=QLuvo1wlpDZR1MpOdu7s6fRUX20Dtt6EWFLkAsqyax4,3617
19
19
  pyplumio/frames/requests.py,sha256=Ra8xH5oKYhkEUtadN-9ZsJKkt5xZkz5O7edQVsDhNsM,7221
20
20
  pyplumio/frames/responses.py,sha256=j4awA2-MfsoPdENC4Fvae4_Oa70rDhH19ebmEoAqhh8,6532
21
21
  pyplumio/helpers/__init__.py,sha256=H2xxdkF-9uADLwEbfBUoxNTdwru3L5Z2cfJjgsuRsn0,31
22
22
  pyplumio/helpers/data_types.py,sha256=H_pYkLgIu30lDFU0UUZ1V3vYxa9A_-1nhiJu-HCLuoc,8212
23
- pyplumio/helpers/event_manager.py,sha256=eSaqFGwED_UlugDwGRusfpivolqV6TISs1XnzBH6e60,5863
23
+ pyplumio/helpers/event_manager.py,sha256=dCNLnSRZgewZ9Ppi-JtkxtvOmNd4ZejA7UT4oAT8FWM,5865
24
24
  pyplumio/helpers/factory.py,sha256=eiTkYUCernUn0VNDDdEN4IyjNPrXK8vnJESXyLaqFzE,1017
25
25
  pyplumio/helpers/parameter.py,sha256=gYCA2SLU_lbdtQZq5U64yzpyLoEIa0R1wyJJGmgL63I,8699
26
26
  pyplumio/helpers/schedule.py,sha256=-IZJ-CU4PhFlsE586wTw--ovDrTo2Hs4JneCHhc0e-Y,5013
27
- pyplumio/helpers/task_manager.py,sha256=RpdYSguc0cap_Onf9VnL-yCd_KwR2JPD49trZCRKPpI,1090
27
+ pyplumio/helpers/task_manager.py,sha256=y5j7u31V6UE7g2ZhdsYsPykY-Awo73oWsNRUOrLSILg,1075
28
28
  pyplumio/helpers/timeout.py,sha256=k-829fBcHT5IR3isrMSgNbPYK-ubeY1BAwndCDIiX9E,824
29
29
  pyplumio/helpers/typing.py,sha256=y55UdpIpPIRuUBPgfPmZHAwPdIUjQO924-kO7AVXhes,685
30
30
  pyplumio/helpers/uid.py,sha256=yaBjcsFKuhOaznftk33kdIepQHpK-labEQr59QNKhPM,975
@@ -32,7 +32,7 @@ pyplumio/structures/__init__.py,sha256=EjK-5qJZ0F7lpP2b6epvTMg9cIBl4Kn91nqNkEcLw
32
32
  pyplumio/structures/alerts.py,sha256=a1CIf8vSEj5aefdqECIfCY5kV4tQ4kabMkp-_ixeWic,3260
33
33
  pyplumio/structures/boiler_load.py,sha256=p3mOzZUU-g7A2tG_yp8podEqpI81hlsOZmHELyPNRY8,838
34
34
  pyplumio/structures/boiler_power.py,sha256=72qsvccg49FdRdXv2f2K5sGpjT7wAOLFjlIGWpO-DVg,901
35
- pyplumio/structures/ecomax_parameters.py,sha256=07v97o9paqj_Ua9HkmEvwPgDT6a9btd8PKVSpVcTC4c,25835
35
+ pyplumio/structures/ecomax_parameters.py,sha256=6HVEh4aNw0CGZD3CVQeYyKXQ0pzueQR_Tpm5fF3_0hA,25815
36
36
  pyplumio/structures/fan_power.py,sha256=Q5fv-7_2NVuLeQPIVIylvgN7M8-a9D8rRUE0QGjyS3w,871
37
37
  pyplumio/structures/frame_versions.py,sha256=x_OSirGYopQYgsRZIM3b1YlKHNIPmCbvAzhzO1wqy5k,1560
38
38
  pyplumio/structures/fuel_consumption.py,sha256=_p2dI4H67Eopn7IF0Gj77A8c_8lNKhhDDAtmugxLd4s,976
@@ -41,7 +41,7 @@ pyplumio/structures/lambda_sensor.py,sha256=6iUVyrPe6_QaGPo1lRzOfqorcTIIXRwnq3h8
41
41
  pyplumio/structures/mixer_parameters.py,sha256=ny7Ox94IooQd1ua22zGYkXLFaZQWGUYLEIM2_8vXk0U,8249
42
42
  pyplumio/structures/mixer_sensors.py,sha256=O91929Ts1YXFmKdPRc1r_BYDgrqkv5QVtE1nGzLpuAI,2260
43
43
  pyplumio/structures/modules.py,sha256=ukju4TQmRRJfgl94QU4zytZLU5px8nw3sgfSLn9JysU,2520
44
- pyplumio/structures/network_info.py,sha256=d1_MvG6n5f57ZEVZwSFFTXux0N07Gt1KnPDtlDfoMNQ,4055
44
+ pyplumio/structures/network_info.py,sha256=ws2UdOhB89oKqmtW1Vsmfj0InRW4Sp6_kL1d4psk-8w,4094
45
45
  pyplumio/structures/output_flags.py,sha256=07N0kxlvR5WZAURuChk_BqSiXR8eaQrtI5qlkgCf4Yc,1345
46
46
  pyplumio/structures/outputs.py,sha256=1xsJPkjN643-aFawqVoupGatUIUJfQG_g252n051Qi0,1916
47
47
  pyplumio/structures/pending_alerts.py,sha256=Uq9WpB4MW9AhDkqmDhk-g0J0h4pVq0Q50z12dYEv6kY,739
@@ -54,8 +54,8 @@ pyplumio/structures/statuses.py,sha256=wkoynyMRr1VREwfBC6vU48kPA8ZQ83pcXuciy2xHJ
54
54
  pyplumio/structures/temperatures.py,sha256=1CDzehNmbALz1Jyt_9gZNIk52q6Wv-xQXjijVDCVYec,2337
55
55
  pyplumio/structures/thermostat_parameters.py,sha256=pjbWsT6z7mlDiUrC5MWGqMtGP0deeVMYeeTa7yGEwJ8,7706
56
56
  pyplumio/structures/thermostat_sensors.py,sha256=ZmjWgYtTZ5M8Lnz_Q5N4JD8G3MvEmByPFjYsy6XZOmo,3177
57
- PyPlumIO-0.5.20.dist-info/LICENSE,sha256=m-UuZFjXJ22uPTGm9kSHS8bqjsf5T8k2wL9bJn1Y04o,1088
58
- PyPlumIO-0.5.20.dist-info/METADATA,sha256=nTyxUOv5kPz-K5xNnm389H-eFlLN48mXYMmc3E4zHQ0,5415
59
- PyPlumIO-0.5.20.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
60
- PyPlumIO-0.5.20.dist-info/top_level.txt,sha256=kNBz9UPPkPD9teDn3U_sEy5LjzwLm9KfADCXtBlbw8A,9
61
- PyPlumIO-0.5.20.dist-info/RECORD,,
57
+ PyPlumIO-0.5.21.dist-info/LICENSE,sha256=m-UuZFjXJ22uPTGm9kSHS8bqjsf5T8k2wL9bJn1Y04o,1088
58
+ PyPlumIO-0.5.21.dist-info/METADATA,sha256=mc-yvbFJArE1iYRxb8RCkL2MdoKZjkZKOn8nWgWD8t8,5415
59
+ PyPlumIO-0.5.21.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
60
+ PyPlumIO-0.5.21.dist-info/top_level.txt,sha256=kNBz9UPPkPD9teDn3U_sEy5LjzwLm9KfADCXtBlbw8A,9
61
+ PyPlumIO-0.5.21.dist-info/RECORD,,
pyplumio/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.5.20'
16
- __version_tuple__ = version_tuple = (0, 5, 20)
15
+ __version__ = version = '0.5.21'
16
+ __version_tuple__ = version_tuple = (0, 5, 21)
@@ -4,7 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  from collections.abc import Generator, Iterable, Sequence
7
- from contextlib import suppress
8
7
  import logging
9
8
  import time
10
9
  from typing import Any, ClassVar, Final
@@ -18,7 +17,7 @@ from pyplumio.const import (
18
17
  DeviceType,
19
18
  FrameType,
20
19
  )
21
- from pyplumio.devices import AddressableDevice
20
+ from pyplumio.devices import AddressableDevice, SubDevice
22
21
  from pyplumio.devices.mixer import Mixer
23
22
  from pyplumio.devices.thermostat import Thermostat
24
23
  from pyplumio.filters import on_change
@@ -263,8 +262,12 @@ class EcoMAX(AddressableDevice):
263
262
  if not parameters:
264
263
  return False
265
264
 
266
- for mixer in self._mixers(parameters.keys()):
267
- await mixer.dispatch(ATTR_MIXER_PARAMETERS, parameters[mixer.index])
265
+ await asyncio.gather(
266
+ *[
267
+ mixer.dispatch(ATTR_MIXER_PARAMETERS, parameters[mixer.index])
268
+ for mixer in self._mixers(indexes=parameters.keys())
269
+ ]
270
+ )
268
271
 
269
272
  return True
270
273
 
@@ -278,14 +281,18 @@ class EcoMAX(AddressableDevice):
278
281
  if not sensors:
279
282
  return False
280
283
 
281
- for mixer in self._mixers(sensors.keys()):
282
- await mixer.dispatch(ATTR_MIXER_SENSORS, sensors[mixer.index])
284
+ await asyncio.gather(
285
+ *[
286
+ mixer.dispatch(ATTR_MIXER_SENSORS, sensors[mixer.index])
287
+ for mixer in self._mixers(indexes=sensors.keys())
288
+ ]
289
+ )
283
290
 
284
291
  return True
285
292
 
286
293
  async def _add_schedules(
287
294
  self, schedules: list[tuple[int, list[list[bool]]]]
288
- ) -> dict[str, Any]:
295
+ ) -> dict[str, Schedule]:
289
296
  """Add schedules to the dataset."""
290
297
  return {
291
298
  SCHEDULES[index]: Schedule(
@@ -333,8 +340,9 @@ class EcoMAX(AddressableDevice):
333
340
  For each sensor dispatch an event with the sensor's name and
334
341
  value.
335
342
  """
336
- for name, value in sensors.items():
337
- await self.dispatch(name, value)
343
+ await asyncio.gather(
344
+ *[self.dispatch(name, value) for name, value in sensors.items()]
345
+ )
338
346
 
339
347
  return True
340
348
 
@@ -371,10 +379,14 @@ class EcoMAX(AddressableDevice):
371
379
  if not parameters:
372
380
  return False
373
381
 
374
- for thermostat in self._thermostats(parameters.keys()):
375
- await thermostat.dispatch(
376
- ATTR_THERMOSTAT_PARAMETERS, parameters[thermostat.index]
377
- )
382
+ await asyncio.gather(
383
+ *[
384
+ thermostat.dispatch(
385
+ ATTR_THERMOSTAT_PARAMETERS, parameters[thermostat.index]
386
+ )
387
+ for thermostat in self._thermostats(indexes=parameters.keys())
388
+ ]
389
+ )
378
390
 
379
391
  return True
380
392
 
@@ -401,10 +413,12 @@ class EcoMAX(AddressableDevice):
401
413
  if not sensors:
402
414
  return False
403
415
 
404
- for thermostat in self._thermostats(sensors.keys()):
405
- await thermostat.dispatch(
406
- ATTR_THERMOSTAT_SENSORS, sensors[thermostat.index]
407
- )
416
+ await asyncio.gather(
417
+ *[
418
+ thermostat.dispatch(ATTR_THERMOSTAT_SENSORS, sensors[thermostat.index])
419
+ for thermostat in self._thermostats(indexes=sensors.keys())
420
+ ]
421
+ )
408
422
 
409
423
  return True
410
424
 
@@ -438,10 +452,6 @@ class EcoMAX(AddressableDevice):
438
452
  """Shutdown tasks for the ecoMAX controller and sub-devices."""
439
453
  mixers = self.get_nowait(ATTR_MIXERS, {})
440
454
  thermostats = self.get_nowait(ATTR_THERMOSTATS, {})
441
- for subdevice in (mixers | thermostats).values():
442
- await subdevice.shutdown()
443
-
444
- with suppress(AttributeError):
445
- await self.regdata.shutdown()
446
-
455
+ devices: Iterable[SubDevice] = (mixers | thermostats).values()
456
+ await asyncio.gather(*[device.shutdown() for device in devices])
447
457
  await super().shutdown()
pyplumio/devices/mixer.py CHANGED
@@ -37,8 +37,9 @@ class Mixer(SubDevice):
37
37
  For each sensor dispatch an event with the
38
38
  sensor's name and value.
39
39
  """
40
- for name, value in sensors.items():
41
- await self.dispatch(name, value)
40
+ await asyncio.gather(
41
+ *[self.dispatch(name, value) for name, value in sensors.items()]
42
+ )
42
43
 
43
44
  return True
44
45
 
@@ -33,8 +33,9 @@ class Thermostat(SubDevice):
33
33
  For each sensor dispatch an event with the
34
34
  sensor's name and value.
35
35
  """
36
- for name, value in sensors.items():
37
- await self.dispatch(name, value)
36
+ await asyncio.gather(
37
+ *[self.dispatch(name, value) for name, value in sensors.items()]
38
+ )
38
39
 
39
40
  return True
40
41
 
@@ -138,16 +138,16 @@ class EventManager(TaskManager):
138
138
  """Call a registered callbacks and dispatch the event without waiting."""
139
139
  self.create_task(self.dispatch(name, value))
140
140
 
141
- def load(self, data: dict[str, Any]) -> None:
142
- """Load an event data."""
143
-
144
- async def _dispatch_events(data: dict[str, Any]) -> None:
145
- """Dispatch events for a loaded data."""
146
- for key, value in data.items():
147
- await self.dispatch(key, value)
148
-
141
+ async def load(self, data: dict[str, Any]) -> None:
142
+ """Load event data."""
149
143
  self.data = data
150
- self.create_task(_dispatch_events(data))
144
+ await asyncio.gather(
145
+ *[self.dispatch(name, value) for name, value in data.items()]
146
+ )
147
+
148
+ def load_nowait(self, data: dict[str, Any]) -> None:
149
+ """Load event data without waiting."""
150
+ self.create_task(self.load(data))
151
151
 
152
152
  def create_event(self, name: str) -> asyncio.Event:
153
153
  """Create an event."""
@@ -18,15 +18,14 @@ class TaskManager:
18
18
 
19
19
  def create_task(self, coro: Coroutine[Any, Any, Any]) -> asyncio.Task:
20
20
  """Create asyncio task and store a reference for it."""
21
- task: asyncio.Task = asyncio.create_task(coro)
21
+ task = asyncio.create_task(coro)
22
22
  self._tasks.add(task)
23
23
  task.add_done_callback(self._tasks.discard)
24
24
  return task
25
25
 
26
- def cancel_tasks(self) -> None:
26
+ def cancel_tasks(self) -> bool:
27
27
  """Cancel all tasks."""
28
- for task in self._tasks:
29
- task.cancel()
28
+ return all(task.cancel() for task in self._tasks)
30
29
 
31
30
  async def wait_until_done(self, return_exceptions: bool = True) -> None:
32
31
  """Wait for all tasks to complete."""
pyplumio/protocol.py CHANGED
@@ -107,8 +107,7 @@ class Queues:
107
107
 
108
108
  async def join(self) -> None:
109
109
  """Wait for queues to finish."""
110
- for queue in (self.read, self.write):
111
- await queue.join()
110
+ await asyncio.gather(self.read.join(), self.write.join())
112
111
 
113
112
 
114
113
  class AsyncProtocol(Protocol, EventManager):
@@ -171,22 +170,18 @@ class AsyncProtocol(Protocol, EventManager):
171
170
  return
172
171
 
173
172
  self.connected.clear()
174
- for device in self.data.values():
175
- # Notify devices about connection loss.
176
- await device.dispatch(ATTR_CONNECTED, False)
177
-
178
173
  await self.close_writer()
179
- for callback in self.on_connection_lost:
180
- await callback()
174
+ await asyncio.gather(
175
+ *[device.dispatch(ATTR_CONNECTED, False) for device in self.data.values()]
176
+ )
177
+ await asyncio.gather(*[callback() for callback in self.on_connection_lost])
181
178
 
182
179
  async def shutdown(self) -> None:
183
180
  """Shutdown protocol tasks."""
184
181
  await self._queues.join()
185
182
  self.cancel_tasks()
186
183
  await self.wait_until_done()
187
- for device in self.data.values():
188
- await device.shutdown()
189
-
184
+ await asyncio.gather(*[device.shutdown() for device in self.data.values()])
190
185
  if self.connected.is_set():
191
186
  self.connected.clear()
192
187
  await self.close_writer()
@@ -116,8 +116,6 @@ class EcomaxBinaryParameterDescription(
116
116
  ):
117
117
  """Represents an ecoMAX binary parameter description."""
118
118
 
119
- __slots__ = ()
120
-
121
119
 
122
120
  ECOMAX_PARAMETERS: dict[ProductType, tuple[EcomaxParameterDescription, ...]] = {
123
121
  ProductType.ECOMAX_P: (
@@ -18,7 +18,7 @@ DEFAULT_NETMASK: Final = "255.255.255.0"
18
18
  NETWORK_INFO_SIZE: Final = 25
19
19
 
20
20
 
21
- @dataclass
21
+ @dataclass(frozen=True)
22
22
  class EthernetParameters:
23
23
  """Represents an ethernet parameters."""
24
24
 
@@ -35,7 +35,7 @@ class EthernetParameters:
35
35
  status: bool = True
36
36
 
37
37
 
38
- @dataclass
38
+ @dataclass(frozen=True)
39
39
  class WirelessParameters(EthernetParameters):
40
40
  """Represents a wireless network parameters."""
41
41
 
@@ -50,7 +50,7 @@ class WirelessParameters(EthernetParameters):
50
50
  signal_quality: int = 100
51
51
 
52
52
 
53
- @dataclass
53
+ @dataclass(frozen=True)
54
54
  class NetworkInfo:
55
55
  """Represents a network parameters."""
56
56