PyPlumIO 0.5.56__py3-none-any.whl → 0.6.0__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/__init__.py CHANGED
@@ -23,17 +23,17 @@ from pyplumio.structures.network_info import EthernetParameters, WirelessParamet
23
23
 
24
24
 
25
25
  def open_serial_connection(
26
- device: str,
26
+ url: str,
27
27
  baudrate: int = 115200,
28
28
  *,
29
29
  protocol: Protocol | None = None,
30
30
  reconnect_on_failure: bool = True,
31
- **kwargs: Any,
31
+ **options: Any,
32
32
  ) -> SerialConnection:
33
33
  r"""Create a serial connection.
34
34
 
35
- :param device: Serial port device name. e. g. /dev/ttyUSB0
36
- :type device: str
35
+ :param url: Serial port device url. e. g. /dev/ttyUSB0
36
+ :type url: str
37
37
  :param baudrate: Serial port baud rate, defaults to 115200
38
38
  :type baudrate: int, optional
39
39
  :param protocol: Protocol that will be used for communication with
@@ -42,17 +42,17 @@ def open_serial_connection(
42
42
  :param reconnect_on_failure: `True` if PyPlumIO should try
43
43
  reconnecting on failure, otherwise `False`, default to `True`
44
44
  :type reconnect_on_failure: bool, optional
45
- :param \**kwargs: Additional keyword arguments to be passed to
45
+ :param \**options: Additional arguments to be passed to
46
46
  serial_asyncio.open_serial_connection()
47
47
  :return: An instance of serial connection
48
48
  :rtype: SerialConnection
49
49
  """
50
50
  return SerialConnection(
51
- device,
51
+ url,
52
52
  baudrate,
53
53
  protocol=protocol,
54
54
  reconnect_on_failure=reconnect_on_failure,
55
- **kwargs,
55
+ **options,
56
56
  )
57
57
 
58
58
 
@@ -62,7 +62,7 @@ def open_tcp_connection(
62
62
  *,
63
63
  protocol: Protocol | None = None,
64
64
  reconnect_on_failure: bool = True,
65
- **kwargs: Any,
65
+ **options: Any,
66
66
  ) -> TcpConnection:
67
67
  r"""Create a TCP connection.
68
68
 
@@ -76,7 +76,7 @@ def open_tcp_connection(
76
76
  :param reconnect_on_failure: `True` if PyPlumIO should try
77
77
  reconnecting on failure, otherwise `False`, default to `True`
78
78
  :type reconnect_on_failure: bool, optional
79
- :param \**kwargs: Additional keyword arguments to be passed to
79
+ :param \**options: Additional arguments to be passed to
80
80
  asyncio.open_connection()
81
81
  :return: An instance of TCP connection
82
82
  :rtype: TcpConnection
@@ -86,7 +86,7 @@ def open_tcp_connection(
86
86
  port,
87
87
  protocol=protocol,
88
88
  reconnect_on_failure=reconnect_on_failure,
89
- **kwargs,
89
+ **options,
90
90
  )
91
91
 
92
92
 
pyplumio/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.5.56'
32
- __version_tuple__ = version_tuple = (0, 5, 56)
31
+ __version__ = version = '0.6.0'
32
+ __version_tuple__ = version_tuple = (0, 6, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
pyplumio/connection.py CHANGED
@@ -4,11 +4,14 @@ from __future__ import annotations
4
4
 
5
5
  from abc import ABC, abstractmethod
6
6
  import asyncio
7
+ from collections.abc import AsyncGenerator
8
+ from contextlib import asynccontextmanager
7
9
  import logging
8
10
  from typing import Any, Final
9
11
 
10
12
  from serial import EIGHTBITS, PARITY_NONE, STOPBITS_ONE
11
13
 
14
+ from pyplumio.devices import PhysicalDevice
12
15
  from pyplumio.exceptions import ConnectionFailedError
13
16
  from pyplumio.helpers.task_manager import TaskManager
14
17
  from pyplumio.protocol import AsyncProtocol, Protocol
@@ -33,15 +36,17 @@ class Connection(ABC, TaskManager):
33
36
  All specific connection classes MUST be inherited from this class.
34
37
  """
35
38
 
39
+ __slots__ = ("_protocol", "_reconnect_on_failure", "_options")
40
+
36
41
  _protocol: Protocol
37
42
  _reconnect_on_failure: bool
38
- _kwargs: dict[str, Any]
43
+ _options: dict[str, Any]
39
44
 
40
45
  def __init__(
41
46
  self,
42
47
  protocol: Protocol | None = None,
43
48
  reconnect_on_failure: bool = True,
44
- **kwargs: Any,
49
+ **options: Any,
45
50
  ) -> None:
46
51
  """Initialize a new connection."""
47
52
  super().__init__()
@@ -53,7 +58,7 @@ class Connection(ABC, TaskManager):
53
58
 
54
59
  self._reconnect_on_failure = reconnect_on_failure
55
60
  self._protocol = protocol
56
- self._kwargs = kwargs
61
+ self._options = options
57
62
 
58
63
  async def __aenter__(self) -> Connection:
59
64
  """Provide an entry point for the context manager."""
@@ -101,11 +106,62 @@ class Connection(ABC, TaskManager):
101
106
  self.cancel_tasks()
102
107
  await self.protocol.shutdown()
103
108
 
109
+ @asynccontextmanager
110
+ async def device(
111
+ self, name: str, timeout: float | None = None
112
+ ) -> AsyncGenerator[PhysicalDevice]:
113
+ """Get the device in context manager."""
114
+ if not isinstance(self.protocol, AsyncProtocol):
115
+ raise NotImplementedError
116
+
117
+ yield await self.protocol.get(name, timeout=timeout)
118
+
119
+ @property
120
+ def get(self): # type: ignore[no-untyped-def]
121
+ """Access the remote device.
122
+
123
+ Raise NotImplementedError when using protocol
124
+ different from AsyncProtocol.
125
+ """
126
+ if isinstance(self.protocol, AsyncProtocol):
127
+ return self.protocol.get
128
+
129
+ raise NotImplementedError
130
+
131
+ @property
132
+ def get_nowait(self): # type: ignore[no-untyped-def]
133
+ """Access the remote device without waiting.
134
+
135
+ Raise NotImplementedError when using protocol
136
+ different from AsyncProtocol.
137
+ """
138
+ if isinstance(self.protocol, AsyncProtocol):
139
+ return self.protocol.get_nowait
140
+
141
+ raise NotImplementedError
142
+
143
+ @property
144
+ def wait_for(self): # type: ignore[no-untyped-def]
145
+ """Wait for the remote device to become available.
146
+
147
+ Raise NotImplementedError when using protocol
148
+ different from AsyncProtocol.
149
+ """
150
+ if isinstance(self.protocol, AsyncProtocol):
151
+ return self.protocol.wait_for
152
+
153
+ raise NotImplementedError
154
+
104
155
  @property
105
156
  def protocol(self) -> Protocol:
106
157
  """Return the protocol object."""
107
158
  return self._protocol
108
159
 
160
+ @property
161
+ def options(self) -> dict[str, Any]:
162
+ """Return connection options."""
163
+ return self._options
164
+
109
165
  @timeout(CONNECT_TIMEOUT)
110
166
  @abstractmethod
111
167
  async def _open_connection(
@@ -117,6 +173,8 @@ class Connection(ABC, TaskManager):
117
173
  class TcpConnection(Connection):
118
174
  """Represents a TCP connection."""
119
175
 
176
+ __slots__ = ("host", "port")
177
+
120
178
  host: str
121
179
  port: int
122
180
 
@@ -127,17 +185,17 @@ class TcpConnection(Connection):
127
185
  *,
128
186
  protocol: Protocol | None = None,
129
187
  reconnect_on_failure: bool = True,
130
- **kwargs: Any,
188
+ **options: Any,
131
189
  ) -> None:
132
190
  """Initialize a new TCP connection."""
133
- super().__init__(protocol, reconnect_on_failure, **kwargs)
191
+ super().__init__(protocol, reconnect_on_failure, **options)
134
192
  self.host = host
135
193
  self.port = port
136
194
 
137
195
  def __repr__(self) -> str:
138
196
  """Return a serializable string representation."""
139
197
  return (
140
- f"TcpConnection(host={self.host}, port={self.port}, kwargs={self._kwargs})"
198
+ f"TcpConnection(host={self.host}, port={self.port}, options={self.options})"
141
199
  )
142
200
 
143
201
  @timeout(CONNECT_TIMEOUT)
@@ -146,37 +204,39 @@ class TcpConnection(Connection):
146
204
  ) -> tuple[asyncio.StreamReader, asyncio.StreamWriter]:
147
205
  """Open the connection and return reader and writer objects."""
148
206
  return await asyncio.open_connection(
149
- host=self.host, port=self.port, **self._kwargs
207
+ host=self.host, port=self.port, **self.options
150
208
  )
151
209
 
152
210
 
153
211
  class SerialConnection(Connection):
154
212
  """Represents a serial connection."""
155
213
 
156
- device: str
214
+ __slots__ = ("url", "baudrate")
215
+
216
+ url: str
157
217
  baudrate: int
158
218
 
159
219
  def __init__(
160
220
  self,
161
- device: str,
221
+ url: str,
162
222
  baudrate: int = 115200,
163
223
  *,
164
224
  protocol: Protocol | None = None,
165
225
  reconnect_on_failure: bool = True,
166
- **kwargs: Any,
226
+ **options: Any,
167
227
  ) -> None:
168
228
  """Initialize a new serial connection."""
169
- super().__init__(protocol, reconnect_on_failure, **kwargs)
170
- self.device = device
229
+ super().__init__(protocol, reconnect_on_failure, **options)
230
+ self.url = url
171
231
  self.baudrate = baudrate
172
232
 
173
233
  def __repr__(self) -> str:
174
234
  """Return a serializable string representation."""
175
235
  return (
176
236
  "SerialConnection("
177
- f"device={self.device}, "
237
+ f"url={self.url}, "
178
238
  f"baudrate={self.baudrate}, "
179
- f"kwargs={self._kwargs})"
239
+ f"options={self.options})"
180
240
  )
181
241
 
182
242
  @timeout(CONNECT_TIMEOUT)
@@ -185,12 +245,12 @@ class SerialConnection(Connection):
185
245
  ) -> tuple[asyncio.StreamReader, asyncio.StreamWriter]:
186
246
  """Open the connection and return reader and writer objects."""
187
247
  return await pyserial_asyncio.open_serial_connection(
188
- url=self.device,
248
+ url=self.url,
189
249
  baudrate=self.baudrate,
190
250
  bytesize=EIGHTBITS,
191
251
  parity=PARITY_NONE,
192
252
  stopbits=STOPBITS_ONE,
193
- **self._kwargs,
253
+ **self.options,
194
254
  )
195
255
 
196
256
 
pyplumio/const.py CHANGED
@@ -3,9 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from enum import Enum, IntEnum, unique
6
- from typing import Any, Final, Literal
7
-
8
- from typing_extensions import TypeAlias
6
+ from typing import Any, Final, Literal, TypeAlias
9
7
 
10
8
  # General attributes.
11
9
  ATTR_CONNECTED: Final = "connected"
pyplumio/filters.py CHANGED
@@ -15,13 +15,12 @@ from typing import (
15
15
  Final,
16
16
  Protocol,
17
17
  SupportsFloat,
18
+ TypeAlias,
18
19
  TypeVar,
19
20
  overload,
20
21
  runtime_checkable,
21
22
  )
22
23
 
23
- from typing_extensions import TypeAlias
24
-
25
24
  from pyplumio.helpers.event_manager import Callback
26
25
  from pyplumio.parameters import Parameter
27
26
 
@@ -176,7 +175,7 @@ class _Aggregate(Filter):
176
175
 
177
176
  async def __call__(self, new_value: Any) -> Any:
178
177
  """Set a new value for the callback."""
179
- if not isinstance(new_value, (float, int, Decimal)):
178
+ if not isinstance(new_value, float | int | Decimal):
180
179
  raise TypeError(
181
180
  "Aggregate filter can only be used with numeric values, got "
182
181
  f"{type(new_value).__name__}: {new_value}"
@@ -187,7 +186,7 @@ class _Aggregate(Filter):
187
186
  time_since_call = current_time - self._last_call_time
188
187
  if time_since_call >= self._timeout or len(self._values) >= self._sample_size:
189
188
  sum_of_values = (
190
- np.sum(self._values) if numpy_installed else sum(self._values)
189
+ np.sum(np.array(self._values)) if numpy_installed else sum(self._values)
191
190
  )
192
191
  result = await self._callback(float(sum_of_values))
193
192
  self._last_call_time = current_time
@@ -326,7 +325,7 @@ class _Deadband(Filter):
326
325
 
327
326
  async def __call__(self, new_value: Any) -> Any:
328
327
  """Set a new value for the callback."""
329
- if not isinstance(new_value, (float, int, Decimal)):
328
+ if not isinstance(new_value, float | int | Decimal):
330
329
  raise TypeError(
331
330
  "Deadband filter can only be used with numeric values, got "
332
331
  f"{type(new_value).__name__}: {new_value}"
@@ -60,12 +60,10 @@ def get_frame_handler(frame_type: int) -> str:
60
60
  return f"frames.{module.lower()}s.{type_name}{module.capitalize()}"
61
61
 
62
62
 
63
- @dataclass
63
+ @dataclass(slots=True)
64
64
  class DataFrameDescription:
65
65
  """Describes what data is provided by the frame."""
66
66
 
67
- __slots__ = ("frame_type", "provides")
68
-
69
67
  frame_type: FrameType
70
68
  provides: str
71
69
 
@@ -95,6 +93,8 @@ class Frame(ABC):
95
93
  _message: bytearray | None
96
94
  _data: dict[str, Any] | None
97
95
 
96
+ __hash__ = object.__hash__
97
+
98
98
  def __init__(
99
99
  self,
100
100
  recipient: DeviceType = DeviceType.ALL,
@@ -114,19 +114,6 @@ class Frame(ABC):
114
114
  self._data = data if not kwargs else ensure_dict(data, kwargs)
115
115
  self._message = message
116
116
 
117
- def __hash__(self) -> int:
118
- """Return a hash of the frame based on its values."""
119
- return hash(
120
- (
121
- self.recipient,
122
- self.sender,
123
- self.econet_type,
124
- self.econet_version,
125
- self._message,
126
- frozenset(self._data.items()) if self._data else None,
127
- )
128
- )
129
-
130
117
  def __eq__(self, other: object) -> bool:
131
118
  """Compare if this frame is equal to other."""
132
119
  if isinstance(other, Frame):
@@ -1,10 +1,8 @@
1
1
  """Contains a simple async cache for caching results of async functions."""
2
2
 
3
- from collections.abc import Awaitable
3
+ from collections.abc import Awaitable, Callable
4
4
  from functools import wraps
5
- from typing import Any, Callable, TypeVar, cast
6
-
7
- from typing_extensions import ParamSpec, TypeAlias
5
+ from typing import Any, ParamSpec, TypeAlias, TypeVar, cast
8
6
 
9
7
  T = TypeVar("T")
10
8
  P = ParamSpec("P")
@@ -5,9 +5,7 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  from collections.abc import Callable, Coroutine, Generator
7
7
  import inspect
8
- from typing import Any, Generic, TypeVar, overload
9
-
10
- from typing_extensions import TypeAlias
8
+ from typing import Any, Generic, TypeAlias, TypeVar, overload
11
9
 
12
10
  from pyplumio.helpers.task_manager import TaskManager
13
11
 
@@ -7,10 +7,7 @@ import asyncio
7
7
  from contextlib import suppress
8
8
  from dataclasses import asdict, dataclass
9
9
  import logging
10
- from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, get_args
11
-
12
- from dataslots import dataslots
13
- from typing_extensions import TypeAlias
10
+ from typing import TYPE_CHECKING, Any, Literal, TypeAlias, TypeVar, get_args
14
11
 
15
12
  from pyplumio.const import BYTE_UNDEFINED, STATE_OFF, STATE_ON, State, UnitOfMeasurement
16
13
  from pyplumio.frames import Request
@@ -47,19 +44,16 @@ def is_valid_parameter(data: bytearray) -> bool:
47
44
  return any(x for x in data if x != BYTE_UNDEFINED)
48
45
 
49
46
 
50
- @dataclass
47
+ @dataclass(slots=True)
51
48
  class ParameterValues:
52
49
  """Represents a parameter values."""
53
50
 
54
- __slots__ = ("value", "min_value", "max_value")
55
-
56
51
  value: int
57
52
  min_value: int
58
53
  max_value: int
59
54
 
60
55
 
61
- @dataslots
62
- @dataclass
56
+ @dataclass(slots=True)
63
57
  class ParameterDescription:
64
58
  """Represents a parameter description."""
65
59
 
@@ -67,7 +61,7 @@ class ParameterDescription:
67
61
  optimistic: bool = False
68
62
 
69
63
 
70
- NumericType: TypeAlias = Union[int, float]
64
+ NumericType: TypeAlias = int | float
71
65
 
72
66
 
73
67
  class Parameter(ABC):
@@ -128,7 +122,7 @@ class Parameter(ABC):
128
122
  handler = getattr(self.values.value, method_to_call)
129
123
  return handler(other.value)
130
124
 
131
- if isinstance(other, (int, float, bool)) or other in get_args(State):
125
+ if isinstance(other, int | float | bool) or other in get_args(State):
132
126
  handler = getattr(self.values.value, method_to_call)
133
127
  return handler(self._pack_value(other))
134
128
  else:
@@ -317,8 +311,7 @@ class Parameter(ABC):
317
311
  """Create a request to change the parameter."""
318
312
 
319
313
 
320
- @dataslots
321
- @dataclass
314
+ @dataclass(slots=True)
322
315
  class NumberDescription(ParameterDescription):
323
316
  """Represents a parameter description."""
324
317
 
@@ -395,8 +388,7 @@ class Number(Parameter):
395
388
  return self.description.unit_of_measurement
396
389
 
397
390
 
398
- @dataslots
399
- @dataclass
391
+ @dataclass(slots=True)
400
392
  class OffsetNumberDescription(NumberDescription):
401
393
  """Represents a parameter description."""
402
394
 
@@ -419,8 +411,7 @@ class OffsetNumber(Number):
419
411
  return super()._unpack_value(value - self.description.offset)
420
412
 
421
413
 
422
- @dataslots
423
- @dataclass
414
+ @dataclass(slots=True)
424
415
  class SwitchDescription(ParameterDescription):
425
416
  """Represents a switch description."""
426
417
 
@@ -15,22 +15,18 @@ from pyplumio.utils import to_camelcase
15
15
  _LOGGER = logging.getLogger(__name__)
16
16
 
17
17
 
18
- @dataclass
18
+ @dataclass(slots=True, kw_only=True)
19
19
  class Signature:
20
20
  """Represents a product signature."""
21
21
 
22
- __slots__ = ("id", "model")
23
-
24
22
  id: int
25
23
  model: str
26
24
 
27
25
 
28
- @dataclass
26
+ @dataclass(slots=True, kw_only=True)
29
27
  class CustomParameter:
30
28
  """Represents a custom parameter."""
31
29
 
32
- __slots__ = ("original", "replacement")
33
-
34
30
  original: str
35
31
  replacement: ParameterDescription
36
32
 
@@ -6,8 +6,6 @@ from dataclasses import dataclass
6
6
  from functools import partial
7
7
  from typing import TYPE_CHECKING
8
8
 
9
- from dataslots import dataslots
10
-
11
9
  from pyplumio.const import (
12
10
  ATTR_INDEX,
13
11
  ATTR_OFFSET,
@@ -78,12 +76,10 @@ class EcomaxParameter(Parameter):
78
76
  )
79
77
 
80
78
 
81
- @dataclass
79
+ @dataclass(slots=True)
82
80
  class EcomaxNumberDescription(EcomaxParameterDescription, OffsetNumberDescription):
83
81
  """Represents an ecoMAX number description."""
84
82
 
85
- __slots__ = ()
86
-
87
83
 
88
84
  class EcomaxNumber(EcomaxParameter, OffsetNumber):
89
85
  """Represents a ecoMAX number."""
@@ -93,8 +89,7 @@ class EcomaxNumber(EcomaxParameter, OffsetNumber):
93
89
  description: EcomaxNumberDescription
94
90
 
95
91
 
96
- @dataslots
97
- @dataclass
92
+ @dataclass(slots=True)
98
93
  class EcomaxSwitchDescription(EcomaxParameterDescription, SwitchDescription):
99
94
  """Represents an ecoMAX switch description."""
100
95
 
@@ -6,8 +6,6 @@ from dataclasses import dataclass
6
6
  from functools import cache
7
7
  from typing import TYPE_CHECKING
8
8
 
9
- from dataslots import dataslots
10
-
11
9
  from pyplumio.const import (
12
10
  ATTR_DEVICE_INDEX,
13
11
  ATTR_INDEX,
@@ -59,12 +57,10 @@ class MixerParameter(Parameter):
59
57
  )
60
58
 
61
59
 
62
- @dataclass
60
+ @dataclass(slots=True)
63
61
  class MixerNumberDescription(MixerParameterDescription, OffsetNumberDescription):
64
62
  """Represent a mixer number description."""
65
63
 
66
- __slots__ = ()
67
-
68
64
 
69
65
  class MixerNumber(MixerParameter, OffsetNumber):
70
66
  """Represents a mixer number."""
@@ -74,8 +70,7 @@ class MixerNumber(MixerParameter, OffsetNumber):
74
70
  description: MixerNumberDescription
75
71
 
76
72
 
77
- @dataslots
78
- @dataclass
73
+ @dataclass(slots=True)
79
74
  class MixerSwitchDescription(MixerParameterDescription, SwitchDescription):
80
75
  """Represents a mixer switch description."""
81
76
 
@@ -6,8 +6,6 @@ from dataclasses import dataclass
6
6
  from functools import cache
7
7
  from typing import TYPE_CHECKING
8
8
 
9
- from dataslots import dataslots
10
-
11
9
  from pyplumio.const import (
12
10
  ATTR_INDEX,
13
11
  ATTR_OFFSET,
@@ -77,8 +75,7 @@ class ThermostatParameter(Parameter):
77
75
  )
78
76
 
79
77
 
80
- @dataslots
81
- @dataclass
78
+ @dataclass(slots=True)
82
79
  class ThermostatNumberDescription(ThermostatParameterDescription, NumberDescription):
83
80
  """Represent a thermostat number description."""
84
81
 
@@ -91,8 +88,7 @@ class ThermostatNumber(ThermostatParameter, Number):
91
88
  description: ThermostatNumberDescription
92
89
 
93
90
 
94
- @dataslots
95
- @dataclass
91
+ @dataclass(slots=True)
96
92
  class ThermostatSwitchDescription(ThermostatParameterDescription, SwitchDescription):
97
93
  """Represents a thermostat switch description."""
98
94
 
pyplumio/protocol.py CHANGED
@@ -8,10 +8,7 @@ from collections.abc import Awaitable, Callable
8
8
  from dataclasses import dataclass, field
9
9
  from datetime import datetime
10
10
  import logging
11
- from typing import Any, Final, Literal
12
-
13
- from dataslots import dataslots
14
- from typing_extensions import TypeAlias
11
+ from typing import Any, Final, Literal, TypeAlias
15
12
 
16
13
  from pyplumio.const import ATTR_CONNECTED, ATTR_SETUP, DeviceType
17
14
  from pyplumio.devices import PhysicalDevice
@@ -104,12 +101,10 @@ class DummyProtocol(Protocol):
104
101
  await self.close_writer()
105
102
 
106
103
 
107
- @dataclass
104
+ @dataclass(slots=True)
108
105
  class Queues:
109
106
  """Represents asyncio queues."""
110
107
 
111
- __slots__ = ("read", "write")
112
-
113
108
  read: asyncio.Queue[Frame]
114
109
  write: asyncio.Queue[Frame]
115
110
 
@@ -121,39 +116,35 @@ class Queues:
121
116
  NEVER: Final = "never"
122
117
 
123
118
 
124
- @dataslots
125
- @dataclass
119
+ @dataclass(slots=True, kw_only=True)
126
120
  class Statistics:
127
- """Represents a connection statistics.
128
-
129
- :param received_bytes: Number of received bytes. Resets on reconnect.
130
- :type received_bytes: int
131
- :param received_frames: Number of received frames. Resets on reconnect.
132
- :type received_frames: int
133
- :param sent_bytes: Number of sent bytes. Resets on reconnect.
134
- :type sent_bytes: int
135
- :param sent_frames: Number of sent frames. Resets on reconnect.
136
- :type sent_frames: int
137
- :param failed_frames: Number of failed frames. Resets on reconnect.
138
- :type failed_frames: int
139
- :param connected_since: Datetime object representing connection time.
140
- :type connected_since: datetime.datetime | Literal["never"]
141
- :param connection_loss_at: Datetime object representing last connection loss event.
142
- :type connection_loss_at: datetime.datetime | Literal["never"]
143
- :param connection_losses: Number of connection lost event.
144
- :type connection_losses: int
145
- :param devices: Contains list of statistics for connected devices.
146
- :type devices: list[DeviceStatistics]
147
- """
121
+ """Represents a connection statistics."""
148
122
 
123
+ #: Number of received bytes. Resets on reconnect.
149
124
  received_bytes: int = 0
125
+
126
+ #: Number of received frames. Resets on reconnect.
150
127
  received_frames: int = 0
128
+
129
+ #: Number of sent bytes. Resets on reconnect.
151
130
  sent_bytes: int = 0
131
+
132
+ #: Number of sent frames. Resets on reconnect.
152
133
  sent_frames: int = 0
134
+
135
+ #: Number of failed frames. Resets on reconnect.
153
136
  failed_frames: int = 0
137
+
138
+ #: Datetime object representing connection time
154
139
  connected_since: datetime | Literal["never"] = NEVER
140
+
141
+ #: Datetime object representing last connection loss event
155
142
  connection_loss_at: datetime | Literal["never"] = NEVER
143
+
144
+ #: Number of connection lost event
156
145
  connection_losses: int = 0
146
+
147
+ #: List of statistics for connected devices
157
148
  devices: list[DeviceStatistics] = field(default_factory=list)
158
149
 
159
150
  def update_transfer_statistics(
@@ -168,6 +159,11 @@ class Statistics:
168
159
  self.received_bytes += received.length
169
160
  self.received_frames += 1
170
161
 
162
+ def track_connection_loss(self) -> None:
163
+ """Increase connection loss counter and store the datetime."""
164
+ self.connection_losses += 1
165
+ self.connection_loss_at = datetime.now()
166
+
171
167
  def reset_transfer_statistics(self) -> None:
172
168
  """Reset transfer statistics."""
173
169
  self.sent_bytes = 0
@@ -177,24 +173,20 @@ class Statistics:
177
173
  self.failed_frames = 0
178
174
 
179
175
 
180
- @dataslots
181
- @dataclass
176
+ @dataclass(slots=True)
182
177
  class DeviceStatistics:
183
- """Represents a device statistics.
184
-
185
- :param name: Device name.
186
- :type name: str
187
- :param connected_since: Datetime object representing connection time.
188
- :type connected_since: datetime.datetime | Literal["never"]
189
- :param last_seen: Datetime object representing time when device was last seen.
190
- :type last_seen: datetime.datetime | Literal["never"]
191
- """
178
+ """Represents a device statistics."""
192
179
 
180
+ #: Device name
193
181
  name: str
182
+
183
+ #: Datetime object representing connection time
194
184
  connected_since: datetime | Literal["never"] = NEVER
185
+
186
+ #: Datetime object representing time when device was last seen
195
187
  last_seen: datetime | Literal["never"] = NEVER
196
188
 
197
- async def update_last_seen(self, data: Any) -> None:
189
+ async def update_last_seen(self, _: Any) -> None:
198
190
  """Update last seen property."""
199
191
  self.last_seen = datetime.now()
200
192
 
@@ -308,8 +300,7 @@ class AsyncProtocol(Protocol, EventManager[PhysicalDevice]):
308
300
  statistics.failed_frames += 1
309
301
  _LOGGER.debug("Can't process received frame: %s", e)
310
302
  except (OSError, asyncio.TimeoutError):
311
- statistics.connection_losses += 1
312
- statistics.connection_loss_at = datetime.now()
303
+ statistics.track_connection_loss()
313
304
  self.create_task(self.connection_lost())
314
305
  break
315
306
  except Exception:
@@ -9,12 +9,10 @@ from typing import Any
9
9
  from pyplumio.frames import Frame
10
10
 
11
11
 
12
- @dataclass
12
+ @dataclass(slots=True)
13
13
  class StructureDataClass:
14
14
  """Represents a structure dataclass mixin."""
15
15
 
16
- __slots__ = ("frame",)
17
-
18
16
  frame: Frame
19
17
 
20
18
 
@@ -56,12 +56,10 @@ def seconds_to_datetime(timestamp: int) -> datetime:
56
56
  return datetime(**dict(datetime_kwargs(timestamp)))
57
57
 
58
58
 
59
- @dataclass
59
+ @dataclass(slots=True)
60
60
  class Alert:
61
61
  """Represents a device alert."""
62
62
 
63
- __slots__ = ("code", "from_dt", "to_dt")
64
-
65
63
  code: int
66
64
  from_dt: datetime
67
65
  to_dt: datetime | None
@@ -6,8 +6,6 @@ from dataclasses import dataclass
6
6
  import struct
7
7
  from typing import Any, Final
8
8
 
9
- from dataslots import dataslots
10
-
11
9
  from pyplumio.const import BYTE_UNDEFINED
12
10
  from pyplumio.structures import StructureDecoder
13
11
  from pyplumio.utils import ensure_dict
@@ -32,8 +30,7 @@ struct_version = struct.Struct("<BBB")
32
30
  struct_vendor = struct.Struct("<BB")
33
31
 
34
32
 
35
- @dataslots
36
- @dataclass
33
+ @dataclass(slots=True)
37
34
  class ConnectedModules:
38
35
  """Represents a firmware version info for connected module."""
39
36
 
@@ -18,7 +18,7 @@ DEFAULT_NETMASK: Final = "255.255.255.0"
18
18
  NETWORK_INFO_SIZE: Final = 25
19
19
 
20
20
 
21
- @dataclass(frozen=True)
21
+ @dataclass(frozen=True, slots=True, kw_only=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(frozen=True)
38
+ @dataclass(frozen=True, slots=True, kw_only=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(frozen=True)
53
+ @dataclass(frozen=True, slots=True, kw_only=True)
54
54
  class NetworkInfo:
55
55
  """Represents a network parameters."""
56
56
 
@@ -63,12 +63,10 @@ def format_model_name(model_name: str) -> str:
63
63
  return model_name
64
64
 
65
65
 
66
- @dataclass(frozen=True)
66
+ @dataclass(frozen=True, slots=True)
67
67
  class ProductInfo:
68
68
  """Represents a product info provided by an UID response."""
69
69
 
70
- __slots__ = ("type", "id", "uid", "logo", "image", "model")
71
-
72
70
  type: ProductType
73
71
  id: int
74
72
  uid: str
@@ -6,8 +6,6 @@ from dataclasses import dataclass
6
6
  import struct
7
7
  from typing import Any, Final
8
8
 
9
- from dataslots import dataslots
10
-
11
9
  from pyplumio._version import __version_tuple__
12
10
  from pyplumio.structures import Structure
13
11
  from pyplumio.utils import ensure_dict
@@ -21,8 +19,7 @@ SOFTWARE_VERSION: Final = ".".join(str(x) for x in __version_tuple__[0:3])
21
19
  struct_program_version = struct.Struct("<2sB2s3s3HB")
22
20
 
23
21
 
24
- @dataslots
25
- @dataclass
22
+ @dataclass(slots=True)
26
23
  class VersionInfo:
27
24
  """Represents a version info provided in program version response."""
28
25
 
@@ -8,8 +8,6 @@ import datetime as dt
8
8
  from functools import lru_cache, reduce
9
9
  from typing import Annotated, Any, Final, get_args
10
10
 
11
- from dataslots import dataslots
12
-
13
11
  from pyplumio.const import (
14
12
  ATTR_PARAMETER,
15
13
  ATTR_SCHEDULE,
@@ -91,8 +89,6 @@ SCHEDULES: tuple[str, ...] = (
91
89
  class ScheduleParameterDescription(ParameterDescription):
92
90
  """Represent a schedule parameter description."""
93
91
 
94
- __slots__ = ()
95
-
96
92
 
97
93
  class ScheduleParameter(Parameter):
98
94
  """Represents a schedule parameter."""
@@ -112,8 +108,7 @@ class ScheduleParameter(Parameter):
112
108
  )
113
109
 
114
110
 
115
- @dataslots
116
- @dataclass
111
+ @dataclass(slots=True)
117
112
  class ScheduleNumberDescription(ScheduleParameterDescription, NumberDescription):
118
113
  """Represents a schedule number description."""
119
114
 
@@ -126,8 +121,7 @@ class ScheduleNumber(ScheduleParameter, Number):
126
121
  description: ScheduleNumberDescription
127
122
 
128
123
 
129
- @dataslots
130
- @dataclass
124
+ @dataclass(slots=True)
131
125
  class ScheduleSwitchDescription(ScheduleParameterDescription, SwitchDescription):
132
126
  """Represents a schedule switch description."""
133
127
 
@@ -273,22 +267,10 @@ class ScheduleDay(MutableMapping):
273
267
  return cls({get_time(index): state for index, state in enumerate(intervals)})
274
268
 
275
269
 
276
- @dataclass
270
+ @dataclass(slots=True)
277
271
  class Schedule(Iterable):
278
272
  """Represents a weekly schedule."""
279
273
 
280
- __slots__ = (
281
- "name",
282
- "device",
283
- "sunday",
284
- "monday",
285
- "tuesday",
286
- "wednesday",
287
- "thursday",
288
- "friday",
289
- "saturday",
290
- )
291
-
292
274
  name: str
293
275
  device: PhysicalDevice
294
276
 
pyplumio/utils.py CHANGED
@@ -5,9 +5,7 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  from collections.abc import Awaitable, Callable, Mapping
7
7
  from functools import wraps
8
- from typing import TypeVar
9
-
10
- from typing_extensions import ParamSpec
8
+ from typing import ParamSpec, TypeVar
11
9
 
12
10
  KT = TypeVar("KT") # Key type.
13
11
  VT = TypeVar("VT") # Value type.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPlumIO
3
- Version: 0.5.56
3
+ Version: 0.6.0
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
@@ -13,30 +13,27 @@ Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Operating System :: OS Independent
16
- Classifier: Programming Language :: Python :: 3.9
17
16
  Classifier: Programming Language :: Python :: 3.10
18
17
  Classifier: Programming Language :: Python :: 3.11
19
18
  Classifier: Programming Language :: Python :: 3.12
20
19
  Classifier: Programming Language :: Python :: 3.13
21
20
  Classifier: Topic :: Software Development :: Libraries
22
21
  Classifier: Topic :: Home Automation
23
- Requires-Python: >=3.9
22
+ Requires-Python: >=3.10
24
23
  Description-Content-Type: text/markdown
25
24
  License-File: LICENSE
26
- Requires-Dist: dataslots==1.2.0
27
25
  Requires-Dist: pyserial-asyncio==0.6
28
- Requires-Dist: typing-extensions<5.0,>=4.14.0
29
26
  Provides-Extra: test
30
27
  Requires-Dist: codespell==2.4.1; extra == "test"
31
- Requires-Dist: coverage==7.10.5; extra == "test"
28
+ Requires-Dist: coverage==7.10.6; extra == "test"
32
29
  Requires-Dist: freezegun==1.5.5; extra == "test"
33
- Requires-Dist: mypy==1.17.1; extra == "test"
30
+ Requires-Dist: mypy==1.18.1; extra == "test"
34
31
  Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "test"
35
32
  Requires-Dist: pyserial-asyncio-fast==0.16; extra == "test"
36
- Requires-Dist: pytest==8.4.1; extra == "test"
33
+ Requires-Dist: pytest==8.4.2; extra == "test"
37
34
  Requires-Dist: pytest-asyncio==1.1.0; extra == "test"
38
- Requires-Dist: ruff==0.12.10; extra == "test"
39
- Requires-Dist: tox==4.28.4; extra == "test"
35
+ Requires-Dist: ruff==0.13.0; extra == "test"
36
+ Requires-Dist: tox==4.30.2; extra == "test"
40
37
  Requires-Dist: types-pyserial==3.5.0.20250822; extra == "test"
41
38
  Provides-Extra: docs
42
39
  Requires-Dist: sphinx==8.1.3; extra == "docs"
@@ -53,8 +50,8 @@ Dynamic: license-file
53
50
  [![PyPI version](https://badge.fury.io/py/PyPlumIO.svg)](https://badge.fury.io/py/PyPlumIO)
54
51
  [![PyPI Supported Python Versions](https://img.shields.io/pypi/pyversions/pyplumio.svg)](https://pypi.python.org/pypi/pyplumio/)
55
52
  [![PyPlumIO CI](https://github.com/denpamusic/PyPlumIO/actions/workflows/ci.yml/badge.svg)](https://github.com/denpamusic/PyPlumIO/actions/workflows/ci.yml)
56
- [![Maintainability](https://qlty.sh/badges/2455933c-6c18-45bc-a205-da5cc0b49d0b/maintainability.svg)](https://qlty.sh/gh/denpamusic/projects/PyPlumIO)
57
- [![Code Coverage](https://qlty.sh/badges/2455933c-6c18-45bc-a205-da5cc0b49d0b/test_coverage.svg)](https://qlty.sh/gh/denpamusic/projects/PyPlumIO)
53
+ [![Maintainability](https://qlty.sh/gh/denpamusic/projects/PyPlumIO/maintainability.svg)](https://qlty.sh/gh/denpamusic/projects/PyPlumIO)
54
+ [![Code Coverage](https://qlty.sh/gh/denpamusic/projects/PyPlumIO/coverage.svg)](https://qlty.sh/gh/denpamusic/projects/PyPlumIO)
58
55
  [![stability-release-candidate](https://img.shields.io/badge/stability-pre--release-48c9b0.svg)](https://guidelines.denpa.pro/stability#release-candidate)
59
56
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
60
57
 
@@ -82,11 +79,13 @@ through network by using RS-485 to Ethernet/WiFi converter.
82
79
  - [Callbacks](https://pyplumio.denpa.pro/callbacks.html)
83
80
  - [Mixers/Thermostats](https://pyplumio.denpa.pro/mixers_thermostats.html)
84
81
  - [Schedules](https://pyplumio.denpa.pro/schedules.html)
82
+ - [Statistics](https://pyplumio.denpa.pro/statistics.html)
85
83
  - [Protocol](https://pyplumio.denpa.pro/protocol.html)
86
84
  - [Frame Structure](https://pyplumio.denpa.pro/protocol.html#frame-structure)
87
85
  - [Requests and Responses](https://pyplumio.denpa.pro/protocol.html#requests-and-responses)
88
86
  - [Communication](https://pyplumio.denpa.pro/protocol.html#communication)
89
87
  - [Versioning](https://pyplumio.denpa.pro/protocol.html#versioning)
88
+ - [Supported frames](https://pyplumio.denpa.pro/frames.html)
90
89
 
91
90
  ## Quickstart
92
91
 
@@ -1,37 +1,37 @@
1
- pyplumio/__init__.py,sha256=3H5SO4WFw5mBTFeEyD4w0H8-MsNo93NyOH3RyMN7IS0,3337
1
+ pyplumio/__init__.py,sha256=DQg-ZTAxLYuNKyqsGrcO0QrVAMw9aPA69Bv2mZ7ubXQ,3314
2
2
  pyplumio/__main__.py,sha256=3IwHHSq-iay5FaeMc95klobe-xv82yydSKcBE7BFZ6M,500
3
- pyplumio/_version.py,sha256=AF48JunwQGeZYZUVpG4oP5XCtgX_ssZWD1yfHXBvb9I,706
4
- pyplumio/connection.py,sha256=u-iOzEUqoEEL4YLpLtzBWi5Qy8_RABgKD8DyXf-er-4,5892
5
- pyplumio/const.py,sha256=eoq-WNJ8TO3YlP7dC7KkVQRKGjt9FbRZ6M__s29vb1U,5659
3
+ pyplumio/_version.py,sha256=MAYWefOLb6kbIRub18WSzK6ggSjz1LNLy9aDRlX9Ea4,704
4
+ pyplumio/connection.py,sha256=9Gzg6FXMU-HjspsDnm9XH8ZPBO29AZ6dKS2-eg8P8Z0,7686
5
+ pyplumio/const.py,sha256=oYwXB3N6bvFLc6411icbABbBkSoQcj5BGuyD-NaKYp8,5629
6
6
  pyplumio/data_types.py,sha256=BTDxwErRo_odvFT5DNfIniNh8ZfyjRKEDaJmoEJqdEg,9426
7
7
  pyplumio/exceptions.py,sha256=_B_0EgxDxd2XyYv3WpZM733q0cML5m6J-f55QOvYRpI,996
8
- pyplumio/filters.py,sha256=QEtOptXym2Fb82cdPpS1dajkTpvYi3VuQaYoLl4CSQ4,15658
9
- pyplumio/protocol.py,sha256=ndsdnd7juX-NlrBMIAhEkx8x5DNlh_Q4FZ4pzvbT1yQ,12276
8
+ pyplumio/filters.py,sha256=sBEnr0i_1XMbIwIEA24npbpe5yevSRneynlsqJMyfko,15642
9
+ pyplumio/protocol.py,sha256=jGx5b8y_1jbdFbjL_ZbUjpDvgBYhn5JUBsVsf_De6Ls,11614
10
10
  pyplumio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  pyplumio/stream.py,sha256=zFMKZ_GxsSGcaBTJigVM1CK3uGjlEJXgcvKqus8MDzk,7740
12
- pyplumio/utils.py,sha256=ktV8_Th2DiwQ0W6afOCau9kBJ8pOrqR-SM2Y2GRy-xE,1869
12
+ pyplumio/utils.py,sha256=SV47Y6QC_KL-gPmk6KQgx7ArExzNHGKuaddGAHjT9rs,1839
13
13
  pyplumio/devices/__init__.py,sha256=d0E5hTV7UPa8flq8TNlKf_jt4cOSbRigSE9jjDHrmDI,8302
14
14
  pyplumio/devices/ecomax.py,sha256=1QasnLFgNCplSoDXXe5wUr8JQjr6ChSEGijamXtJZVM,16356
15
15
  pyplumio/devices/ecoster.py,sha256=X46ky5XT8jHMFq9sBW0ve8ZI_tjItQDMt4moXsW-ogY,307
16
16
  pyplumio/devices/mixer.py,sha256=7WdUVgwO4VXmaPNzh3ZWpKr2ooRXWemz2KFHAw35_Rk,2731
17
17
  pyplumio/devices/thermostat.py,sha256=MHMKe45fQ7jKlhBVObJ7McbYQKuF6-LOKSHy-9VNsCU,2253
18
- pyplumio/frames/__init__.py,sha256=QAjdvZxQj5Av1OKFqdlSgZBn3RbCcRVMQv_lCnTkW5M,8272
18
+ pyplumio/frames/__init__.py,sha256=jIYP31yP60FVXp8ygOcKkbJCosodiqWCvnrY9FOgH4g,7885
19
19
  pyplumio/frames/messages.py,sha256=ImQGWFFTa2eaXfytQmFZKC-IxyPRkxD8qp0bEm16-ws,3628
20
20
  pyplumio/frames/requests.py,sha256=jr-_XSSCCDDTbAmrw95CKyWa5nb7JNeGzZ2jDXIxlAo,7348
21
21
  pyplumio/frames/responses.py,sha256=M6Ky4gg2AoShmRXX0x6nftajxrvmQLKPVRWbwyhvI0E,6663
22
22
  pyplumio/helpers/__init__.py,sha256=H2xxdkF-9uADLwEbfBUoxNTdwru3L5Z2cfJjgsuRsn0,31
23
- pyplumio/helpers/async_cache.py,sha256=EGQcU8LWJpVx3Hk6iaI-3mqAhnR5ACfBOGb9tWw-VSY,1305
24
- pyplumio/helpers/event_manager.py,sha256=aKNlhsPNTy3eOSfWVb9TJxtIsN9GAQv9XxhOi_BOhlM,8097
23
+ pyplumio/helpers/async_cache.py,sha256=PUkUTo3lmIhslejg0dGWjbcES09E62d9YYgDcBK_G6Q,1275
24
+ pyplumio/helpers/event_manager.py,sha256=cev3_X5a7rBvT4KXIwGpyAnOdWd-3svWDETN3yumkhg,8067
25
25
  pyplumio/helpers/factory.py,sha256=c3sitnkUjJWz7fPpTE9uRIpa8h46Qim3xsAblMw3eDo,1049
26
26
  pyplumio/helpers/task_manager.py,sha256=N71F6Ag1HHxdf5zJeCMcEziFdH9lmJKtMPoRGjJ-E40,1209
27
- pyplumio/parameters/__init__.py,sha256=YKIXmb_-E2HeIVljDHdD2bLbsfUsQ8sVhl9sl7kLzyo,16220
28
- pyplumio/parameters/ecomax.py,sha256=4UqI7cokCt7qS9Du4-7JgLhM7mhHCHt8SWPl_qncmXQ,26239
29
- pyplumio/parameters/mixer.py,sha256=RjhfUU_62STyNV0Ud9A4G4FEvVwo02qGVl8h1QvqQXI,6646
30
- pyplumio/parameters/thermostat.py,sha256=-DK2Mb78CGrKmdhwAD0M3GiGJatczPnl1e2gVeT19tI,5070
31
- pyplumio/parameters/custom/__init__.py,sha256=o1khThLf4FMrjErFIcikAc6jI9gn5IyZlo7LNKKqJG4,3194
27
+ pyplumio/parameters/__init__.py,sha256=d2gLX-Ve6UxwxLbiTQU6AGNYrC4ywXK1dJ7F92OkfgM,16108
28
+ pyplumio/parameters/ecomax.py,sha256=KjHlkVZK2XYEl4HNSdCRLAnv0KEn7gjnEO_CsKFZwIw,26199
29
+ pyplumio/parameters/mixer.py,sha256=cjwe6AJdboAIEnCeiYNqIRmOVo3dSQqbMTWgiCSx8J8,6606
30
+ pyplumio/parameters/thermostat.py,sha256=sRAndI87jANM8uvdQc1LdkT6_baDxf0AEAFVYRstzNE,5039
31
+ pyplumio/parameters/custom/__init__.py,sha256=EeddoseRsh2Gxche3e3woRBgNszraOnLUs9TciK7dCA,3168
32
32
  pyplumio/parameters/custom/ecomax_860d3_hb.py,sha256=IsNgDXmV90QpBilDV4fGSBtIUEQJJbR9rjnfCr3-pHE,2840
33
- pyplumio/structures/__init__.py,sha256=emZVH5OFgdTUPbEJoznMKitmK0nlPm0I4SmF86It1Do,1345
34
- pyplumio/structures/alerts.py,sha256=Whl_WyHV9sXr321SuJAYBc1wUawNzi7xMZc41M8qToY,3724
33
+ pyplumio/structures/__init__.py,sha256=tb62y-x466WSogdjNpsvqcD3Kiz7xMW604m2-yJH3jc,1329
34
+ pyplumio/structures/alerts.py,sha256=9cBzxo1R5erJVeQUdWOmEDG82wXgm7vAU9X6xJjRAjk,3690
35
35
  pyplumio/structures/boiler_load.py,sha256=e-6itp9L6iJeeOyhSTiOclHLuYmqG7KkcepsHwJSQSI,894
36
36
  pyplumio/structures/boiler_power.py,sha256=7CdOk-pYLEpy06oRBAeichvq8o-a2RcesB0tzo9ccBs,951
37
37
  pyplumio/structures/ecomax_parameters.py,sha256=E_s5bO0RqX8p1rM5DtYAsEXcHqS8P6Tg4AGm21cxsnM,1663
@@ -42,22 +42,22 @@ pyplumio/structures/fuel_level.py,sha256=-zUKApVJaZZzc1q52vqO3K2Mya43c6vRgw45d2x
42
42
  pyplumio/structures/lambda_sensor.py,sha256=09nM4Hwn1X275LzFpDihtpzkazwgJXAbx4NFqUkhbNM,1609
43
43
  pyplumio/structures/mixer_parameters.py,sha256=JMSySqI7TUGKdFtDp1P5DJm5EAbijMSz-orRrAe1KlQ,2041
44
44
  pyplumio/structures/mixer_sensors.py,sha256=ChgLhC3p4fyoPy1EKe0BQTvXOPZEISbcK2HyamrNaN8,2450
45
- pyplumio/structures/modules.py,sha256=LviFz3_pPvSQ5i_Mr2S9o5miVad8O4qn48CR_z7yG24,2791
46
- pyplumio/structures/network_info.py,sha256=HYhROSMbVxqYxsOa7aF3xetQXEs0xGvhzH-4OHETZVQ,4327
45
+ pyplumio/structures/modules.py,sha256=-8krDmCtrLwP3GvVMe3e-dN8Zbe4R0F1cZZuEo6N2zc,2759
46
+ pyplumio/structures/network_info.py,sha256=g5SkVS8QhUKa-Pt8rxJ5BAbz09hck6-npZb5a8KjIEg,4405
47
47
  pyplumio/structures/output_flags.py,sha256=upVIgAH2JNncHFXvjE-t6oTFF-JNwwZbyGjfrcKWtz0,1508
48
48
  pyplumio/structures/outputs.py,sha256=3NP5lArzQiihRC4QzBuWAHL9hhjvGxNkKmeoYZnDD-0,2291
49
49
  pyplumio/structures/pending_alerts.py,sha256=b1uMmDHTGv8eE0h1vGBrKsPxlwBmUad7HgChnDDLK_g,801
50
- pyplumio/structures/product_info.py,sha256=Y5Q5UzKcxrixkB3Fd_BZaj1DdUNvUw1XASqR1oKMqn0,3308
51
- pyplumio/structures/program_version.py,sha256=qHmmPComCOa-dgq7cFAucEGuRS-jWYwWi40VCiPS7cc,2621
50
+ pyplumio/structures/product_info.py,sha256=QEr2x8GAoXCc1_UzdaVWALRdi2ouMrBtGb684OBNgAQ,3255
51
+ pyplumio/structures/program_version.py,sha256=QLe_jFZcUOjWsEXrnXRiueFb4MR0coIGOymTtBiYtyg,2589
52
52
  pyplumio/structures/regulator_data.py,sha256=SYKI1YPC3mDAth-SpYejttbD0IzBfobjgC-uy_uUKnw,2333
53
53
  pyplumio/structures/regulator_data_schema.py,sha256=0SapbZCGzqAHmHC7dwhufszJ9FNo_ZO_XMrFGNiUe-w,1547
54
- pyplumio/structures/schedules.py,sha256=SGD9p12G_BVU2PSR1k5AS1cgx_bujFw8rqKSFohtEbc,12052
54
+ pyplumio/structures/schedules.py,sha256=IJ7hxGmLxgCtzGrMncXJBRddbc4quf91Wv_R-Y3ZJXA,11820
55
55
  pyplumio/structures/statuses.py,sha256=1h-EUw1UtuS44E19cNOSavUgZeAxsLgX3iS0eVC8pLI,1325
56
56
  pyplumio/structures/temperatures.py,sha256=2VD3P_vwp9PEBkOn2-WhifOR8w-UYNq35aAxle0z2Vg,2831
57
57
  pyplumio/structures/thermostat_parameters.py,sha256=st3x3HkjQm3hqBrn_fpvPDQu8fuc-Sx33ONB19ViQak,3007
58
58
  pyplumio/structures/thermostat_sensors.py,sha256=rO9jTZWGQpThtJqVdbbv8sYMYHxJi4MfwZQza69L2zw,3399
59
- pyplumio-0.5.56.dist-info/licenses/LICENSE,sha256=m-UuZFjXJ22uPTGm9kSHS8bqjsf5T8k2wL9bJn1Y04o,1088
60
- pyplumio-0.5.56.dist-info/METADATA,sha256=2YzNOAb-PMDVCsLFfR0VLgsPGnQWGzOXL6nzuCTncTk,5617
61
- pyplumio-0.5.56.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
62
- pyplumio-0.5.56.dist-info/top_level.txt,sha256=kNBz9UPPkPD9teDn3U_sEy5LjzwLm9KfADCXtBlbw8A,9
63
- pyplumio-0.5.56.dist-info/RECORD,,
59
+ pyplumio-0.6.0.dist-info/licenses/LICENSE,sha256=m-UuZFjXJ22uPTGm9kSHS8bqjsf5T8k2wL9bJn1Y04o,1088
60
+ pyplumio-0.6.0.dist-info/METADATA,sha256=fyZvRedY6toO1OuENZe4ytYtPA0EFJqYb7CJy-Tswlo,5579
61
+ pyplumio-0.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
62
+ pyplumio-0.6.0.dist-info/top_level.txt,sha256=kNBz9UPPkPD9teDn3U_sEy5LjzwLm9KfADCXtBlbw8A,9
63
+ pyplumio-0.6.0.dist-info/RECORD,,