PyPlumIO 0.6.0__py3-none-any.whl → 0.6.2__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 +3 -1
- pyplumio/_version.py +2 -2
- pyplumio/connection.py +0 -36
- pyplumio/const.py +0 -5
- pyplumio/data_types.py +2 -2
- pyplumio/devices/__init__.py +23 -5
- pyplumio/devices/ecomax.py +30 -53
- pyplumio/devices/ecoster.py +2 -3
- pyplumio/filters.py +199 -136
- pyplumio/frames/__init__.py +101 -15
- pyplumio/frames/messages.py +8 -65
- pyplumio/frames/requests.py +38 -38
- pyplumio/frames/responses.py +30 -86
- pyplumio/helpers/async_cache.py +13 -8
- pyplumio/helpers/event_manager.py +24 -18
- pyplumio/helpers/factory.py +0 -3
- pyplumio/parameters/__init__.py +38 -35
- pyplumio/protocol.py +63 -47
- pyplumio/structures/alerts.py +2 -2
- pyplumio/structures/ecomax_parameters.py +1 -1
- pyplumio/structures/frame_versions.py +3 -2
- pyplumio/structures/mixer_parameters.py +5 -3
- pyplumio/structures/network_info.py +1 -0
- pyplumio/structures/product_info.py +1 -1
- pyplumio/structures/program_version.py +2 -2
- pyplumio/structures/schedules.py +8 -40
- pyplumio/structures/sensor_data.py +498 -0
- pyplumio/structures/thermostat_parameters.py +7 -4
- pyplumio/utils.py +41 -4
- {pyplumio-0.6.0.dist-info → pyplumio-0.6.2.dist-info}/METADATA +7 -8
- pyplumio-0.6.2.dist-info/RECORD +50 -0
- pyplumio/structures/boiler_load.py +0 -32
- pyplumio/structures/boiler_power.py +0 -33
- pyplumio/structures/fan_power.py +0 -33
- pyplumio/structures/fuel_consumption.py +0 -36
- pyplumio/structures/fuel_level.py +0 -39
- pyplumio/structures/lambda_sensor.py +0 -57
- pyplumio/structures/mixer_sensors.py +0 -80
- pyplumio/structures/modules.py +0 -102
- pyplumio/structures/output_flags.py +0 -47
- pyplumio/structures/outputs.py +0 -88
- pyplumio/structures/pending_alerts.py +0 -28
- pyplumio/structures/statuses.py +0 -52
- pyplumio/structures/temperatures.py +0 -94
- pyplumio/structures/thermostat_sensors.py +0 -106
- pyplumio-0.6.0.dist-info/RECORD +0 -63
- {pyplumio-0.6.0.dist-info → pyplumio-0.6.2.dist-info}/WHEEL +0 -0
- {pyplumio-0.6.0.dist-info → pyplumio-0.6.2.dist-info}/licenses/LICENSE +0 -0
- {pyplumio-0.6.0.dist-info → pyplumio-0.6.2.dist-info}/top_level.txt +0 -0
pyplumio/__init__.py
CHANGED
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
from typing import Any
|
6
6
|
|
7
|
-
from pyplumio._version import __version__, __version_tuple__
|
7
|
+
from pyplumio._version import __version__, __version_tuple__, version, version_tuple
|
8
8
|
from pyplumio.connection import SerialConnection, TcpConnection
|
9
9
|
from pyplumio.exceptions import (
|
10
10
|
ChecksumError,
|
@@ -93,6 +93,8 @@ def open_tcp_connection(
|
|
93
93
|
__all__ = [
|
94
94
|
"__version__",
|
95
95
|
"__version_tuple__",
|
96
|
+
"version",
|
97
|
+
"version_tuple",
|
96
98
|
"AsyncProtocol",
|
97
99
|
"ChecksumError",
|
98
100
|
"ConnectionFailedError",
|
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.6.
|
32
|
-
__version_tuple__ = version_tuple = (0, 6,
|
31
|
+
__version__ = version = '0.6.2'
|
32
|
+
__version_tuple__ = version_tuple = (0, 6, 2)
|
33
33
|
|
34
34
|
__commit_id__ = commit_id = None
|
pyplumio/connection.py
CHANGED
@@ -116,42 +116,6 @@ class Connection(ABC, TaskManager):
|
|
116
116
|
|
117
117
|
yield await self.protocol.get(name, timeout=timeout)
|
118
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
|
-
|
155
119
|
@property
|
156
120
|
def protocol(self) -> Protocol:
|
157
121
|
"""Return the protocol object."""
|
pyplumio/const.py
CHANGED
@@ -8,7 +8,6 @@ from typing import Any, Final, Literal, TypeAlias
|
|
8
8
|
# General attributes.
|
9
9
|
ATTR_CONNECTED: Final = "connected"
|
10
10
|
ATTR_COUNT: Final = "count"
|
11
|
-
ATTR_CURRENT_TEMP: Final = "current_temp"
|
12
11
|
ATTR_DEVICE_INDEX: Final = "device_index"
|
13
12
|
ATTR_FRAME_ERRORS: Final = "frame_errors"
|
14
13
|
ATTR_INDEX: Final = "index"
|
@@ -19,11 +18,7 @@ ATTR_SCHEDULE: Final = "schedule"
|
|
19
18
|
ATTR_SETUP: Final = "setup"
|
20
19
|
ATTR_SENSORS: Final = "sensors"
|
21
20
|
ATTR_START: Final = "start"
|
22
|
-
ATTR_STATE: Final = "state"
|
23
21
|
ATTR_SWITCH: Final = "switch"
|
24
|
-
ATTR_TARGET_TEMP: Final = "target_temp"
|
25
|
-
ATTR_THERMOSTAT: Final = "thermostat"
|
26
|
-
ATTR_TRANSMISSION: Final = "transmission"
|
27
22
|
ATTR_TYPE: Final = "type"
|
28
23
|
ATTR_VALUE: Final = "value"
|
29
24
|
ATTR_SIZE: Final = "size"
|
pyplumio/data_types.py
CHANGED
@@ -8,7 +8,7 @@ import struct
|
|
8
8
|
from typing import ClassVar, Final, Generic, TypeVar
|
9
9
|
|
10
10
|
T = TypeVar("T")
|
11
|
-
|
11
|
+
_DataTypeT = TypeVar("_DataTypeT", bound="DataType")
|
12
12
|
|
13
13
|
|
14
14
|
class DataType(ABC, Generic[T]):
|
@@ -66,7 +66,7 @@ class DataType(ABC, Generic[T]):
|
|
66
66
|
return buffer if self.size == 0 else buffer[: self.size]
|
67
67
|
|
68
68
|
@classmethod
|
69
|
-
def from_bytes(cls: type[
|
69
|
+
def from_bytes(cls: type[_DataTypeT], buffer: bytes, offset: int = 0) -> _DataTypeT:
|
70
70
|
"""Initialize a new data type from bytes."""
|
71
71
|
data_type = cls()
|
72
72
|
data_type.unpack(buffer[offset:])
|
pyplumio/devices/__init__.py
CHANGED
@@ -4,9 +4,10 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
from abc import ABC
|
6
6
|
import asyncio
|
7
|
+
from collections.abc import Callable
|
7
8
|
from functools import cache
|
8
9
|
import logging
|
9
|
-
from typing import Any, ClassVar
|
10
|
+
from typing import Any, ClassVar, TypeVar
|
10
11
|
|
11
12
|
from pyplumio.const import ATTR_FRAME_ERRORS, DeviceType, FrameType, State
|
12
13
|
from pyplumio.exceptions import RequestError, UnknownDeviceError
|
@@ -14,7 +15,7 @@ from pyplumio.filters import on_change
|
|
14
15
|
from pyplumio.frames import Frame, Request, is_known_frame_type
|
15
16
|
from pyplumio.helpers.event_manager import EventManager, event_listener
|
16
17
|
from pyplumio.helpers.factory import create_instance
|
17
|
-
from pyplumio.parameters import
|
18
|
+
from pyplumio.parameters import Numeric, Parameter
|
18
19
|
from pyplumio.structures.network_info import NetworkInfo
|
19
20
|
from pyplumio.utils import to_camelcase
|
20
21
|
|
@@ -63,7 +64,7 @@ class Device(ABC, EventManager):
|
|
63
64
|
async def set(
|
64
65
|
self,
|
65
66
|
name: str,
|
66
|
-
value:
|
67
|
+
value: Numeric | State | bool,
|
67
68
|
retries: int = 0,
|
68
69
|
timeout: float | None = None,
|
69
70
|
) -> bool:
|
@@ -95,7 +96,7 @@ class Device(ABC, EventManager):
|
|
95
96
|
def set_nowait(
|
96
97
|
self,
|
97
98
|
name: str,
|
98
|
-
value:
|
99
|
+
value: Numeric | State | bool,
|
99
100
|
retries: int = 0,
|
100
101
|
timeout: float | None = None,
|
101
102
|
) -> None:
|
@@ -232,10 +233,27 @@ class VirtualDevice(Device, ABC):
|
|
232
233
|
self.index = index
|
233
234
|
|
234
235
|
|
236
|
+
_PhysicalDeviceT = TypeVar("_PhysicalDeviceT", bound=PhysicalDevice)
|
237
|
+
|
238
|
+
|
239
|
+
def device_handler(
|
240
|
+
device_type: DeviceType,
|
241
|
+
) -> Callable[[type[_PhysicalDeviceT]], type[_PhysicalDeviceT]]:
|
242
|
+
"""Specify device type (address) for the physical device class."""
|
243
|
+
|
244
|
+
def wrapper(cls: type[_PhysicalDeviceT]) -> type[_PhysicalDeviceT]:
|
245
|
+
"""Wrap the physical device class."""
|
246
|
+
setattr(cls, "address", device_type)
|
247
|
+
return cls
|
248
|
+
|
249
|
+
return wrapper
|
250
|
+
|
251
|
+
|
235
252
|
__all__ = [
|
236
253
|
"Device",
|
237
254
|
"PhysicalDevice",
|
238
255
|
"VirtualDevice",
|
239
|
-
"
|
256
|
+
"device_hander",
|
240
257
|
"get_device_handler",
|
258
|
+
"is_known_device_type",
|
241
259
|
]
|
pyplumio/devices/ecomax.py
CHANGED
@@ -6,7 +6,7 @@ import asyncio
|
|
6
6
|
from collections.abc import Coroutine, Generator, Iterable
|
7
7
|
import logging
|
8
8
|
import time
|
9
|
-
from typing import Any, Final
|
9
|
+
from typing import Any, Final, NamedTuple
|
10
10
|
|
11
11
|
from pyplumio.const import (
|
12
12
|
ATTR_FRAME_ERRORS,
|
@@ -20,12 +20,12 @@ from pyplumio.const import (
|
|
20
20
|
FrameType,
|
21
21
|
State,
|
22
22
|
)
|
23
|
-
from pyplumio.devices import PhysicalDevice
|
23
|
+
from pyplumio.devices import PhysicalDevice, device_handler
|
24
24
|
from pyplumio.devices.mixer import Mixer
|
25
25
|
from pyplumio.devices.thermostat import Thermostat
|
26
26
|
from pyplumio.exceptions import RequestError
|
27
27
|
from pyplumio.filters import on_change
|
28
|
-
from pyplumio.frames import
|
28
|
+
from pyplumio.frames import Frame, Request
|
29
29
|
from pyplumio.helpers.event_manager import event_listener
|
30
30
|
from pyplumio.parameters import ParameterValues
|
31
31
|
from pyplumio.parameters.ecomax import (
|
@@ -42,7 +42,6 @@ from pyplumio.structures.ecomax_parameters import (
|
|
42
42
|
ATTR_ECOMAX_PARAMETERS,
|
43
43
|
)
|
44
44
|
from pyplumio.structures.mixer_parameters import ATTR_MIXER_PARAMETERS
|
45
|
-
from pyplumio.structures.mixer_sensors import ATTR_MIXER_SENSORS
|
46
45
|
from pyplumio.structures.network_info import ATTR_NETWORK, NetworkInfo
|
47
46
|
from pyplumio.structures.product_info import ATTR_PRODUCT, ProductInfo
|
48
47
|
from pyplumio.structures.regulator_data_schema import ATTR_REGDATA_SCHEMA
|
@@ -56,8 +55,8 @@ from pyplumio.structures.schedules import (
|
|
56
55
|
ScheduleSwitch,
|
57
56
|
ScheduleSwitchDescription,
|
58
57
|
)
|
58
|
+
from pyplumio.structures.sensor_data import ATTR_MIXER_SENSORS, ATTR_THERMOSTAT_SENSORS
|
59
59
|
from pyplumio.structures.thermostat_parameters import ATTR_THERMOSTAT_PARAMETERS
|
60
|
-
from pyplumio.structures.thermostat_sensors import ATTR_THERMOSTAT_SENSORS
|
61
60
|
|
62
61
|
_LOGGER = logging.getLogger(__name__)
|
63
62
|
|
@@ -102,46 +101,30 @@ class FuelMeter:
|
|
102
101
|
return None
|
103
102
|
|
104
103
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
),
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
),
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
),
|
122
|
-
DataFrameDescription(
|
123
|
-
frame_type=FrameType.REQUEST_SCHEDULES,
|
124
|
-
provides=ATTR_SCHEDULES,
|
125
|
-
),
|
126
|
-
DataFrameDescription(
|
127
|
-
frame_type=FrameType.REQUEST_MIXER_PARAMETERS,
|
128
|
-
provides=ATTR_MIXER_PARAMETERS,
|
129
|
-
),
|
130
|
-
DataFrameDescription(
|
131
|
-
frame_type=FrameType.REQUEST_THERMOSTAT_PARAMETERS,
|
132
|
-
provides=ATTR_THERMOSTAT_PARAMETERS,
|
133
|
-
),
|
134
|
-
DataFrameDescription(
|
135
|
-
frame_type=FrameType.REQUEST_PASSWORD,
|
136
|
-
provides=ATTR_PASSWORD,
|
137
|
-
),
|
104
|
+
class DataKey(NamedTuple):
|
105
|
+
"""Map a data key to frame type."""
|
106
|
+
|
107
|
+
key: str
|
108
|
+
provided_by: FrameType
|
109
|
+
|
110
|
+
|
111
|
+
REQUIRED_KEYS: tuple[DataKey, ...] = (
|
112
|
+
DataKey(ATTR_PRODUCT, FrameType.REQUEST_UID),
|
113
|
+
DataKey(ATTR_REGDATA_SCHEMA, FrameType.REQUEST_REGULATOR_DATA_SCHEMA),
|
114
|
+
DataKey(ATTR_ECOMAX_PARAMETERS, FrameType.REQUEST_ECOMAX_PARAMETERS),
|
115
|
+
DataKey(ATTR_TOTAL_ALERTS, FrameType.REQUEST_ALERTS),
|
116
|
+
DataKey(ATTR_SCHEDULES, FrameType.REQUEST_SCHEDULES),
|
117
|
+
DataKey(ATTR_MIXER_PARAMETERS, FrameType.REQUEST_MIXER_PARAMETERS),
|
118
|
+
DataKey(ATTR_THERMOSTAT_PARAMETERS, FrameType.REQUEST_THERMOSTAT_PARAMETERS),
|
119
|
+
DataKey(ATTR_PASSWORD, FrameType.REQUEST_PASSWORD),
|
138
120
|
)
|
139
121
|
|
140
|
-
REQUIRED_TYPES = [
|
122
|
+
REQUIRED_TYPES = [frame_type for _, frame_type in REQUIRED_KEYS]
|
141
123
|
|
142
124
|
SETUP_TIMEOUT: Final = 60.0
|
143
125
|
|
144
126
|
|
127
|
+
@device_handler(DeviceType.ECOMAX)
|
145
128
|
class EcoMAX(PhysicalDevice):
|
146
129
|
"""Represents an ecoMAX controller."""
|
147
130
|
|
@@ -149,8 +132,6 @@ class EcoMAX(PhysicalDevice):
|
|
149
132
|
|
150
133
|
_fuel_meter: FuelMeter
|
151
134
|
|
152
|
-
address = DeviceType.ECOMAX
|
153
|
-
|
154
135
|
def __init__(self, queue: asyncio.Queue[Frame], network: NetworkInfo) -> None:
|
155
136
|
"""Initialize a new ecoMAX controller."""
|
156
137
|
super().__init__(queue, network)
|
@@ -165,26 +146,26 @@ class EcoMAX(PhysicalDevice):
|
|
165
146
|
|
166
147
|
super().handle_frame(frame)
|
167
148
|
|
168
|
-
def _mixers(self, indexes: Iterable[int]) -> Generator[Mixer
|
149
|
+
def _mixers(self, indexes: Iterable[int]) -> Generator[Mixer]:
|
169
150
|
"""Iterate through the mixer indexes.
|
170
151
|
|
171
152
|
For each index, return or create an instance of the mixer class.
|
172
153
|
Once done, dispatch the 'mixers' event without waiting.
|
173
154
|
"""
|
174
|
-
mixers: dict[int, Mixer] = self.
|
155
|
+
mixers: dict[int, Mixer] = self._data.setdefault(ATTR_MIXERS, {})
|
175
156
|
for index in indexes:
|
176
157
|
yield mixers.setdefault(index, Mixer(self.queue, parent=self, index=index))
|
177
158
|
|
178
159
|
return self.dispatch_nowait(ATTR_MIXERS, mixers)
|
179
160
|
|
180
|
-
def _thermostats(self, indexes: Iterable[int]) -> Generator[Thermostat
|
161
|
+
def _thermostats(self, indexes: Iterable[int]) -> Generator[Thermostat]:
|
181
162
|
"""Iterate through the thermostat indexes.
|
182
163
|
|
183
164
|
For each index, return or create an instance of the thermostat
|
184
165
|
class. Once done, dispatch the 'thermostats' event without
|
185
166
|
waiting.
|
186
167
|
"""
|
187
|
-
thermostats: dict[int, Thermostat] = self.
|
168
|
+
thermostats: dict[int, Thermostat] = self._data.setdefault(ATTR_THERMOSTATS, {})
|
188
169
|
for index in indexes:
|
189
170
|
yield thermostats.setdefault(
|
190
171
|
index, Thermostat(self.queue, parent=self, index=index)
|
@@ -203,9 +184,8 @@ class EcoMAX(PhysicalDevice):
|
|
203
184
|
async def _set_ecomax_state(self, state: State) -> bool:
|
204
185
|
"""Try to set the ecoMAX control state."""
|
205
186
|
try:
|
206
|
-
|
207
|
-
|
208
|
-
except KeyError:
|
187
|
+
return await self.set(ATTR_ECOMAX_CONTROL, state, timeout=0.0)
|
188
|
+
except asyncio.TimeoutError:
|
209
189
|
_LOGGER.error("ecoMAX control is not available. Please try again later.")
|
210
190
|
|
211
191
|
return False
|
@@ -249,10 +229,7 @@ class EcoMAX(PhysicalDevice):
|
|
249
229
|
return False
|
250
230
|
|
251
231
|
results = await asyncio.gather(
|
252
|
-
*(
|
253
|
-
self.request(description.provides, description.frame_type)
|
254
|
-
for description in REQUIRED
|
255
|
-
),
|
232
|
+
*(self.request(key, frame_type) for key, frame_type in REQUIRED_KEYS),
|
256
233
|
return_exceptions=True,
|
257
234
|
)
|
258
235
|
|
@@ -352,7 +329,7 @@ class EcoMAX(PhysicalDevice):
|
|
352
329
|
) -> bool:
|
353
330
|
"""Update schedule parameters and dispatch the events."""
|
354
331
|
|
355
|
-
def _schedule_parameter_events() -> Generator[Coroutine
|
332
|
+
def _schedule_parameter_events() -> Generator[Coroutine]:
|
356
333
|
"""Get dispatch calls for schedule parameter events."""
|
357
334
|
for index, values in parameters:
|
358
335
|
description = SCHEDULE_PARAMETERS[index]
|
pyplumio/devices/ecoster.py
CHANGED
@@ -3,15 +3,14 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
from pyplumio.const import DeviceType
|
6
|
-
from pyplumio.devices import PhysicalDevice
|
6
|
+
from pyplumio.devices import PhysicalDevice, device_handler
|
7
7
|
|
8
8
|
|
9
|
+
@device_handler(DeviceType.ECOSTER)
|
9
10
|
class EcoSTER(PhysicalDevice):
|
10
11
|
"""Represents an ecoSTER thermostat."""
|
11
12
|
|
12
13
|
__slots__ = ()
|
13
14
|
|
14
|
-
address = DeviceType.ECOSTER
|
15
|
-
|
16
15
|
|
17
16
|
__all__ = ["EcoSTER"]
|