PyPlumIO 0.6.1__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/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 +14 -8
- 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.1.dist-info → pyplumio-0.6.2.dist-info}/METADATA +4 -4
- 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.1.dist-info/RECORD +0 -63
- {pyplumio-0.6.1.dist-info → pyplumio-0.6.2.dist-info}/WHEEL +0 -0
- {pyplumio-0.6.1.dist-info → pyplumio-0.6.2.dist-info}/licenses/LICENSE +0 -0
- {pyplumio-0.6.1.dist-info → pyplumio-0.6.2.dist-info}/top_level.txt +0 -0
pyplumio/frames/responses.py
CHANGED
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
5
5
|
from typing import Any
|
6
6
|
|
7
7
|
from pyplumio.const import ATTR_PASSWORD, FrameType
|
8
|
-
from pyplumio.frames import Response
|
8
|
+
from pyplumio.frames import Response, Structured, frame_handler
|
9
9
|
from pyplumio.structures.alerts import AlertsStructure
|
10
10
|
from pyplumio.structures.ecomax_parameters import EcomaxParametersStructure
|
11
11
|
from pyplumio.structures.mixer_parameters import MixerParametersStructure
|
@@ -17,19 +17,15 @@ from pyplumio.structures.schedules import SchedulesStructure
|
|
17
17
|
from pyplumio.structures.thermostat_parameters import ThermostatParametersStructure
|
18
18
|
|
19
19
|
|
20
|
-
|
20
|
+
@frame_handler(FrameType.RESPONSE_ALERTS, structure=AlertsStructure)
|
21
|
+
class AlertsResponse(Structured, Response):
|
21
22
|
"""Represents response to a device alerts request."""
|
22
23
|
|
23
24
|
__slots__ = ()
|
24
25
|
|
25
|
-
frame_type = FrameType.RESPONSE_ALERTS
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
return AlertsStructure(self).decode(message)[0]
|
30
|
-
|
31
|
-
|
32
|
-
class DeviceAvailableResponse(Response):
|
27
|
+
@frame_handler(FrameType.RESPONSE_DEVICE_AVAILABLE, structure=NetworkInfoStructure)
|
28
|
+
class DeviceAvailableResponse(Structured, Response):
|
33
29
|
"""Represents a device available response.
|
34
30
|
|
35
31
|
Contains network information and status.
|
@@ -37,17 +33,8 @@ class DeviceAvailableResponse(Response):
|
|
37
33
|
|
38
34
|
__slots__ = ()
|
39
35
|
|
40
|
-
frame_type = FrameType.RESPONSE_DEVICE_AVAILABLE
|
41
|
-
|
42
|
-
def create_message(self, data: dict[str, Any]) -> bytearray:
|
43
|
-
"""Create a frame message."""
|
44
|
-
return NetworkInfoStructure(self).encode(data)
|
45
|
-
|
46
|
-
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
47
|
-
"""Decode a frame message."""
|
48
|
-
return NetworkInfoStructure(self).decode(message, offset=1)[0]
|
49
|
-
|
50
36
|
|
37
|
+
@frame_handler(FrameType.RESPONSE_ECOMAX_CONTROL)
|
51
38
|
class EcomaxControlResponse(Response):
|
52
39
|
"""Represents response to an ecoMAX control request.
|
53
40
|
|
@@ -57,10 +44,11 @@ class EcomaxControlResponse(Response):
|
|
57
44
|
|
58
45
|
__slots__ = ()
|
59
46
|
|
60
|
-
frame_type = FrameType.RESPONSE_ECOMAX_CONTROL
|
61
47
|
|
62
|
-
|
63
|
-
|
48
|
+
@frame_handler(
|
49
|
+
FrameType.RESPONSE_ECOMAX_PARAMETERS, structure=EcomaxParametersStructure
|
50
|
+
)
|
51
|
+
class EcomaxParametersResponse(Structured, Response):
|
64
52
|
"""Represents an ecoMAX parameters response.
|
65
53
|
|
66
54
|
Contains editable ecoMAX parameters.
|
@@ -68,14 +56,9 @@ class EcomaxParametersResponse(Response):
|
|
68
56
|
|
69
57
|
__slots__ = ()
|
70
58
|
|
71
|
-
frame_type = FrameType.RESPONSE_ECOMAX_PARAMETERS
|
72
|
-
|
73
|
-
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
74
|
-
"""Decode a frame message."""
|
75
|
-
return EcomaxParametersStructure(self).decode(message)[0]
|
76
|
-
|
77
59
|
|
78
|
-
|
60
|
+
@frame_handler(FrameType.RESPONSE_MIXER_PARAMETERS, structure=MixerParametersStructure)
|
61
|
+
class MixerParametersResponse(Structured, Response):
|
79
62
|
"""Represents a mixer parameters response.
|
80
63
|
|
81
64
|
Contains editable mixer parameters.
|
@@ -83,13 +66,8 @@ class MixerParametersResponse(Response):
|
|
83
66
|
|
84
67
|
__slots__ = ()
|
85
68
|
|
86
|
-
frame_type = FrameType.RESPONSE_MIXER_PARAMETERS
|
87
|
-
|
88
|
-
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
89
|
-
"""Decode a frame message."""
|
90
|
-
return MixerParametersStructure(self).decode(message)[0]
|
91
|
-
|
92
69
|
|
70
|
+
@frame_handler(FrameType.RESPONSE_PASSWORD)
|
93
71
|
class PasswordResponse(Response):
|
94
72
|
"""Represents a password response.
|
95
73
|
|
@@ -98,15 +76,14 @@ class PasswordResponse(Response):
|
|
98
76
|
|
99
77
|
__slots__ = ()
|
100
78
|
|
101
|
-
frame_type = FrameType.RESPONSE_PASSWORD
|
102
|
-
|
103
79
|
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
104
80
|
"""Decode a frame message."""
|
105
81
|
password = message[1:].decode() if message[1:] else None
|
106
82
|
return {ATTR_PASSWORD: password}
|
107
83
|
|
108
84
|
|
109
|
-
|
85
|
+
@frame_handler(FrameType.RESPONSE_PROGRAM_VERSION, structure=ProgramVersionStructure)
|
86
|
+
class ProgramVersionResponse(Structured, Response):
|
110
87
|
"""Represents a program version response.
|
111
88
|
|
112
89
|
Contains software version info.
|
@@ -114,18 +91,11 @@ class ProgramVersionResponse(Response):
|
|
114
91
|
|
115
92
|
__slots__ = ()
|
116
93
|
|
117
|
-
frame_type = FrameType.RESPONSE_PROGRAM_VERSION
|
118
|
-
|
119
|
-
def create_message(self, data: dict[str, Any]) -> bytearray:
|
120
|
-
"""Create a frame message."""
|
121
|
-
return ProgramVersionStructure(self).encode(data)
|
122
|
-
|
123
|
-
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
124
|
-
"""Decode a frame message."""
|
125
|
-
return ProgramVersionStructure(self).decode(message)[0]
|
126
|
-
|
127
94
|
|
128
|
-
|
95
|
+
@frame_handler(
|
96
|
+
FrameType.RESPONSE_REGULATOR_DATA_SCHEMA, structure=RegulatorDataSchemaStructure
|
97
|
+
)
|
98
|
+
class RegulatorDataSchemaResponse(Structured, Response):
|
129
99
|
"""Represents a regulator data schema response.
|
130
100
|
|
131
101
|
Contains schema, that describes structure of ecoMAX regulator data
|
@@ -134,25 +104,15 @@ class RegulatorDataSchemaResponse(Response):
|
|
134
104
|
|
135
105
|
__slots__ = ()
|
136
106
|
|
137
|
-
frame_type = FrameType.RESPONSE_REGULATOR_DATA_SCHEMA
|
138
|
-
|
139
|
-
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
140
|
-
"""Decode a frame message."""
|
141
|
-
return RegulatorDataSchemaStructure(self).decode(message)[0]
|
142
|
-
|
143
107
|
|
144
|
-
|
108
|
+
@frame_handler(FrameType.RESPONSE_SCHEDULES, structure=SchedulesStructure)
|
109
|
+
class SchedulesResponse(Structured, Response):
|
145
110
|
"""Represents response to a device schedules request."""
|
146
111
|
|
147
112
|
__slots__ = ()
|
148
113
|
|
149
|
-
frame_type = FrameType.RESPONSE_SCHEDULES
|
150
|
-
|
151
|
-
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
152
|
-
"""Decode a frame message."""
|
153
|
-
return SchedulesStructure(self).decode(message)[0]
|
154
|
-
|
155
114
|
|
115
|
+
@frame_handler(FrameType.RESPONSE_SET_ECOMAX_PARAMETER)
|
156
116
|
class SetEcomaxParameterResponse(Response):
|
157
117
|
"""Represents response to a set ecoMAX parameter request.
|
158
118
|
|
@@ -162,9 +122,8 @@ class SetEcomaxParameterResponse(Response):
|
|
162
122
|
|
163
123
|
__slots__ = ()
|
164
124
|
|
165
|
-
frame_type = FrameType.RESPONSE_SET_ECOMAX_PARAMETER
|
166
|
-
|
167
125
|
|
126
|
+
@frame_handler(FrameType.RESPONSE_SET_MIXER_PARAMETER)
|
168
127
|
class SetMixerParameterResponse(Response):
|
169
128
|
"""Represents response to a set mixer parameter request.
|
170
129
|
|
@@ -174,9 +133,8 @@ class SetMixerParameterResponse(Response):
|
|
174
133
|
|
175
134
|
__slots__ = ()
|
176
135
|
|
177
|
-
frame_type = FrameType.RESPONSE_SET_MIXER_PARAMETER
|
178
|
-
|
179
136
|
|
137
|
+
@frame_handler(FrameType.RESPONSE_SET_THERMOSTAT_PARAMETER)
|
180
138
|
class SetThermostatParameterResponse(Response):
|
181
139
|
"""Represents response to a set thermostat parameter request.
|
182
140
|
|
@@ -186,10 +144,11 @@ class SetThermostatParameterResponse(Response):
|
|
186
144
|
|
187
145
|
__slots__ = ()
|
188
146
|
|
189
|
-
frame_type = FrameType.RESPONSE_SET_THERMOSTAT_PARAMETER
|
190
|
-
|
191
147
|
|
192
|
-
|
148
|
+
@frame_handler(
|
149
|
+
FrameType.RESPONSE_THERMOSTAT_PARAMETERS, structure=ThermostatParametersStructure
|
150
|
+
)
|
151
|
+
class ThermostatParametersResponse(Structured, Response):
|
193
152
|
"""Represents a thermostat parameters response.
|
194
153
|
|
195
154
|
Contains editable thermostat parameters.
|
@@ -197,14 +156,9 @@ class ThermostatParametersResponse(Response):
|
|
197
156
|
|
198
157
|
__slots__ = ()
|
199
158
|
|
200
|
-
frame_type = FrameType.RESPONSE_THERMOSTAT_PARAMETERS
|
201
|
-
|
202
|
-
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
203
|
-
"""Decode a frame message."""
|
204
|
-
return ThermostatParametersStructure(self).decode(message)[0]
|
205
|
-
|
206
159
|
|
207
|
-
|
160
|
+
@frame_handler(FrameType.RESPONSE_UID, structure=ProductInfoStructure)
|
161
|
+
class UIDResponse(Structured, Response):
|
208
162
|
"""Represents an UID response.
|
209
163
|
|
210
164
|
Contains product info and product UID.
|
@@ -212,16 +166,6 @@ class UIDResponse(Response):
|
|
212
166
|
|
213
167
|
__slots__ = ()
|
214
168
|
|
215
|
-
frame_type = FrameType.RESPONSE_UID
|
216
|
-
|
217
|
-
def create_message(self, data: dict[str, Any]) -> bytearray:
|
218
|
-
"""Create a frame message."""
|
219
|
-
return ProductInfoStructure(self).encode(data)
|
220
|
-
|
221
|
-
def decode_message(self, message: bytearray) -> dict[str, Any]:
|
222
|
-
"""Decode a frame message."""
|
223
|
-
return ProductInfoStructure(self).decode(message)[0]
|
224
|
-
|
225
169
|
|
226
170
|
__all__ = [
|
227
171
|
"AlertsResponse",
|
pyplumio/helpers/async_cache.py
CHANGED
@@ -2,30 +2,35 @@
|
|
2
2
|
|
3
3
|
from collections.abc import Awaitable, Callable
|
4
4
|
from functools import wraps
|
5
|
-
from
|
5
|
+
from types import MappingProxyType
|
6
|
+
from typing import Any, ParamSpec, TypeVar, cast
|
6
7
|
|
7
8
|
T = TypeVar("T")
|
8
9
|
P = ParamSpec("P")
|
9
|
-
_CallableT: TypeAlias = Callable[..., Awaitable[Any]]
|
10
10
|
|
11
11
|
|
12
12
|
class AsyncCache:
|
13
13
|
"""A simple cache for asynchronous functions."""
|
14
14
|
|
15
|
-
__slots__ = ("
|
15
|
+
__slots__ = ("_cache",)
|
16
16
|
|
17
|
-
|
17
|
+
_cache: dict[str, Any]
|
18
18
|
|
19
19
|
def __init__(self) -> None:
|
20
20
|
"""Initialize the cache."""
|
21
|
-
self.
|
21
|
+
self._cache = {}
|
22
22
|
|
23
|
-
async def get(self, key: str, coro:
|
23
|
+
async def get(self, key: str, coro: Callable[..., Awaitable[Any]]) -> Any:
|
24
24
|
"""Get a value from the cache or compute and store it."""
|
25
25
|
if key not in self.cache:
|
26
|
-
self.
|
26
|
+
self._cache[key] = await coro()
|
27
27
|
|
28
|
-
return self.
|
28
|
+
return self._cache[key]
|
29
|
+
|
30
|
+
@property
|
31
|
+
def cache(self) -> MappingProxyType[str, Any]:
|
32
|
+
"""Return the internal cache dictionary."""
|
33
|
+
return MappingProxyType(self._cache)
|
29
34
|
|
30
35
|
|
31
36
|
# Create a global cache instance
|
@@ -5,34 +5,35 @@ from __future__ import annotations
|
|
5
5
|
import asyncio
|
6
6
|
from collections.abc import Callable, Coroutine, Generator
|
7
7
|
import inspect
|
8
|
+
from types import MappingProxyType
|
8
9
|
from typing import Any, Generic, TypeAlias, TypeVar, overload
|
9
10
|
|
10
11
|
from pyplumio.helpers.task_manager import TaskManager
|
11
12
|
|
12
|
-
|
13
|
+
EventCallback: TypeAlias = Callable[[Any], Coroutine[Any, Any, Any]]
|
14
|
+
_Callable: TypeAlias = Callable[..., Any]
|
13
15
|
|
14
|
-
|
15
|
-
_CallbackT = TypeVar("_CallbackT", bound=Callback)
|
16
|
+
_EventCallbackT = TypeVar("_EventCallbackT", bound=EventCallback)
|
16
17
|
|
17
18
|
|
18
19
|
@overload
|
19
|
-
def event_listener(name:
|
20
|
+
def event_listener(name: _Callable, filter: None = None) -> EventCallback: ...
|
20
21
|
|
21
22
|
|
22
23
|
@overload
|
23
24
|
def event_listener(
|
24
|
-
name: str | None = None, filter:
|
25
|
-
) ->
|
25
|
+
name: str | None = None, filter: _Callable | None = None
|
26
|
+
) -> _Callable: ...
|
26
27
|
|
27
28
|
|
28
|
-
def event_listener(name: Any = None, filter:
|
29
|
+
def event_listener(name: Any = None, filter: _Callable | None = None) -> Any:
|
29
30
|
"""Mark a function as an event listener.
|
30
31
|
|
31
32
|
This decorator attaches metadata to the function, identifying it
|
32
33
|
as a subscriber for the specified event.
|
33
34
|
"""
|
34
35
|
|
35
|
-
def decorator(func:
|
36
|
+
def decorator(func: _EventCallbackT) -> _EventCallbackT:
|
36
37
|
# Attach metadata to the function to mark it as a listener.
|
37
38
|
event = (
|
38
39
|
name
|
@@ -55,16 +56,16 @@ T = TypeVar("T")
|
|
55
56
|
class EventManager(TaskManager, Generic[T]):
|
56
57
|
"""Represents an event manager."""
|
57
58
|
|
58
|
-
__slots__ = ("
|
59
|
+
__slots__ = ("_data", "_events", "_callbacks")
|
59
60
|
|
60
|
-
|
61
|
+
_data: dict[str, T]
|
61
62
|
_events: dict[str, asyncio.Event]
|
62
|
-
_callbacks: dict[str, list[
|
63
|
+
_callbacks: dict[str, list[EventCallback]]
|
63
64
|
|
64
65
|
def __init__(self) -> None:
|
65
66
|
"""Initialize a new event manager."""
|
66
67
|
super().__init__()
|
67
|
-
self.
|
68
|
+
self._data = {}
|
68
69
|
self._events = {}
|
69
70
|
self._callbacks = {}
|
70
71
|
self._register_event_listeners()
|
@@ -82,7 +83,7 @@ class EventManager(TaskManager, Generic[T]):
|
|
82
83
|
filter_func = getattr(callback, "_on_event_filter", None)
|
83
84
|
self.subscribe(event, filter_func(callback) if filter_func else callback)
|
84
85
|
|
85
|
-
def event_listeners(self) -> Generator[tuple[str,
|
86
|
+
def event_listeners(self) -> Generator[tuple[str, EventCallback]]:
|
86
87
|
"""Get the event listeners."""
|
87
88
|
for _, callback in inspect.getmembers(self, predicate=inspect.ismethod):
|
88
89
|
if event := getattr(callback, "_on_event", None):
|
@@ -141,7 +142,7 @@ class EventManager(TaskManager, Generic[T]):
|
|
141
142
|
except KeyError:
|
142
143
|
return default
|
143
144
|
|
144
|
-
def subscribe(self, name: str, callback:
|
145
|
+
def subscribe(self, name: str, callback: _EventCallbackT) -> _EventCallbackT:
|
145
146
|
"""Subscribe a callback to the event.
|
146
147
|
|
147
148
|
:param name: Event name or ID
|
@@ -157,7 +158,7 @@ class EventManager(TaskManager, Generic[T]):
|
|
157
158
|
callbacks.append(callback)
|
158
159
|
return callback
|
159
160
|
|
160
|
-
def subscribe_once(self, name: str, callback:
|
161
|
+
def subscribe_once(self, name: str, callback: EventCallback) -> EventCallback:
|
161
162
|
"""Subscribe a callback to the event once.
|
162
163
|
|
163
164
|
Callback will be unsubscribed after single event.
|
@@ -179,7 +180,7 @@ class EventManager(TaskManager, Generic[T]):
|
|
179
180
|
|
180
181
|
return self.subscribe(name, _call_once)
|
181
182
|
|
182
|
-
def unsubscribe(self, name: str, callback:
|
183
|
+
def unsubscribe(self, name: str, callback: EventCallback) -> bool:
|
183
184
|
"""Usubscribe a callback from the event.
|
184
185
|
|
185
186
|
:param name: Event name or ID
|
@@ -204,7 +205,7 @@ class EventManager(TaskManager, Generic[T]):
|
|
204
205
|
if (result := await callback(value)) is not None:
|
205
206
|
value = result
|
206
207
|
|
207
|
-
self.
|
208
|
+
self._data[name] = value
|
208
209
|
self.set_event(name)
|
209
210
|
|
210
211
|
def dispatch_nowait(self, name: str, value: T) -> None:
|
@@ -240,5 +241,10 @@ class EventManager(TaskManager, Generic[T]):
|
|
240
241
|
"""Return the events."""
|
241
242
|
return self._events
|
242
243
|
|
244
|
+
@property
|
245
|
+
def data(self) -> MappingProxyType[str, T]:
|
246
|
+
"""Return the event data."""
|
247
|
+
return MappingProxyType(self._data)
|
248
|
+
|
243
249
|
|
244
|
-
__all__ = ["
|
250
|
+
__all__ = ["EventCallback", "EventManager", "event_listener"]
|
pyplumio/helpers/factory.py
CHANGED
@@ -4,14 +4,11 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import asyncio
|
6
6
|
import importlib
|
7
|
-
import logging
|
8
7
|
from types import ModuleType
|
9
8
|
from typing import Any, TypeVar
|
10
9
|
|
11
10
|
from pyplumio.helpers.async_cache import acache
|
12
11
|
|
13
|
-
_LOGGER = logging.getLogger(__name__)
|
14
|
-
|
15
12
|
T = TypeVar("T")
|
16
13
|
|
17
14
|
|
pyplumio/parameters/__init__.py
CHANGED
@@ -5,7 +5,8 @@ from __future__ import annotations
|
|
5
5
|
from abc import ABC, abstractmethod
|
6
6
|
import asyncio
|
7
7
|
from contextlib import suppress
|
8
|
-
from
|
8
|
+
from copy import copy
|
9
|
+
from dataclasses import dataclass, replace
|
9
10
|
import logging
|
10
11
|
from typing import TYPE_CHECKING, Any, Literal, TypeAlias, TypeVar, get_args
|
11
12
|
|
@@ -18,7 +19,7 @@ if TYPE_CHECKING:
|
|
18
19
|
|
19
20
|
_LOGGER = logging.getLogger(__name__)
|
20
21
|
|
21
|
-
|
22
|
+
_ParameterT = TypeVar("_ParameterT", bound="Parameter")
|
22
23
|
|
23
24
|
|
24
25
|
def unpack_parameter(
|
@@ -44,7 +45,7 @@ def is_valid_parameter(data: bytearray) -> bool:
|
|
44
45
|
return any(x for x in data if x != BYTE_UNDEFINED)
|
45
46
|
|
46
47
|
|
47
|
-
@dataclass(slots=True)
|
48
|
+
@dataclass(slots=True, frozen=True)
|
48
49
|
class ParameterValues:
|
49
50
|
"""Represents a parameter values."""
|
50
51
|
|
@@ -61,7 +62,7 @@ class ParameterDescription:
|
|
61
62
|
optimistic: bool = False
|
62
63
|
|
63
64
|
|
64
|
-
|
65
|
+
Numeric: TypeAlias = int | float
|
65
66
|
|
66
67
|
|
67
68
|
class Parameter(ABC):
|
@@ -111,31 +112,34 @@ class Parameter(ABC):
|
|
111
112
|
|
112
113
|
def __hash__(self) -> int:
|
113
114
|
"""Return a hash of the parameter based on its values."""
|
114
|
-
return hash(
|
115
|
+
return hash(self.values)
|
115
116
|
|
116
117
|
def _call_relational_method(self, method_to_call: str, other: Any) -> Any:
|
117
118
|
"""Call a specified relational method."""
|
119
|
+
handler = getattr(self.values.value, method_to_call)
|
118
120
|
if isinstance(other, Parameter):
|
119
|
-
|
121
|
+
return handler(other.values.value)
|
120
122
|
|
121
123
|
if isinstance(other, ParameterValues):
|
122
|
-
handler = getattr(self.values.value, method_to_call)
|
123
124
|
return handler(other.value)
|
124
125
|
|
125
|
-
if isinstance(other,
|
126
|
-
handler = getattr(self.values.value, method_to_call)
|
126
|
+
if isinstance(other, Numeric | bool) or other in get_args(State):
|
127
127
|
return handler(self._pack_value(other))
|
128
|
-
|
129
|
-
|
128
|
+
|
129
|
+
return NotImplemented
|
130
130
|
|
131
131
|
def __int__(self) -> int:
|
132
132
|
"""Return an integer representation of parameter's value."""
|
133
133
|
return self.values.value
|
134
134
|
|
135
135
|
def __add__(self, other: Any) -> Any:
|
136
|
-
"""
|
136
|
+
"""Add a number to this parameter."""
|
137
137
|
return self._call_relational_method("__add__", other)
|
138
138
|
|
139
|
+
def __radd__(self, other: Any) -> Any:
|
140
|
+
"""Add this parameter to another number."""
|
141
|
+
return self._call_relational_method("__radd__", other)
|
142
|
+
|
139
143
|
def __sub__(self, other: Any) -> Any:
|
140
144
|
"""Return result of the subtraction."""
|
141
145
|
return self._call_relational_method("__sub__", other)
|
@@ -174,10 +178,7 @@ class Parameter(ABC):
|
|
174
178
|
|
175
179
|
def __copy__(self) -> Parameter:
|
176
180
|
"""Create a copy of parameter."""
|
177
|
-
|
178
|
-
self.values.value, self.values.min_value, self.values.max_value
|
179
|
-
)
|
180
|
-
return type(self)(self.device, self.description, values)
|
181
|
+
return type(self)(self.device, self.description, values=copy(self.values))
|
181
182
|
|
182
183
|
async def set(self, value: Any, retries: int = 0, timeout: float = 5.0) -> bool:
|
183
184
|
"""Set a parameter value."""
|
@@ -200,7 +201,7 @@ class Parameter(ABC):
|
|
200
201
|
# Value is unchanged
|
201
202
|
return True
|
202
203
|
|
203
|
-
self._values
|
204
|
+
self._values = replace(self._values, value=value)
|
204
205
|
request = await self.create_request()
|
205
206
|
if self.description.optimistic:
|
206
207
|
await self.device.queue.put(request)
|
@@ -262,14 +263,14 @@ class Parameter(ABC):
|
|
262
263
|
|
263
264
|
@classmethod
|
264
265
|
def create_or_update(
|
265
|
-
cls: type[
|
266
|
+
cls: type[_ParameterT],
|
266
267
|
device: Device,
|
267
268
|
description: ParameterDescription,
|
268
269
|
values: ParameterValues,
|
269
270
|
**kwargs: Any,
|
270
|
-
) ->
|
271
|
+
) -> _ParameterT:
|
271
272
|
"""Create new parameter or update parameter values."""
|
272
|
-
parameter:
|
273
|
+
parameter: _ParameterT | None = device.get_nowait(description.name, None)
|
273
274
|
if parameter and isinstance(parameter, cls):
|
274
275
|
parameter.update(values)
|
275
276
|
else:
|
@@ -293,17 +294,17 @@ class Parameter(ABC):
|
|
293
294
|
|
294
295
|
@property
|
295
296
|
@abstractmethod
|
296
|
-
def value(self) ->
|
297
|
+
def value(self) -> Numeric | State | bool:
|
297
298
|
"""Return the value."""
|
298
299
|
|
299
300
|
@property
|
300
301
|
@abstractmethod
|
301
|
-
def min_value(self) ->
|
302
|
+
def min_value(self) -> Numeric | State | bool:
|
302
303
|
"""Return the minimum allowed value."""
|
303
304
|
|
304
305
|
@property
|
305
306
|
@abstractmethod
|
306
|
-
def max_value(self) ->
|
307
|
+
def max_value(self) -> Numeric | State | bool:
|
307
308
|
"""Return the maximum allowed value."""
|
308
309
|
|
309
310
|
@abstractmethod
|
@@ -327,11 +328,15 @@ class Number(Parameter):
|
|
327
328
|
|
328
329
|
description: NumberDescription
|
329
330
|
|
330
|
-
def
|
331
|
+
def __float__(self) -> float:
|
332
|
+
"""Return number value as float."""
|
333
|
+
return float(self.value)
|
334
|
+
|
335
|
+
def _pack_value(self, value: Numeric) -> int:
|
331
336
|
"""Pack the parameter value."""
|
332
337
|
return int(round(value / self.description.step))
|
333
338
|
|
334
|
-
def _unpack_value(self, value: int) ->
|
339
|
+
def _unpack_value(self, value: int) -> Numeric:
|
335
340
|
"""Unpack the parameter value."""
|
336
341
|
return round(value * self.description.step, self.description.precision)
|
337
342
|
|
@@ -351,14 +356,12 @@ class Number(Parameter):
|
|
351
356
|
|
352
357
|
return True
|
353
358
|
|
354
|
-
async def set(
|
355
|
-
self, value: NumericType, retries: int = 0, timeout: float = 5.0
|
356
|
-
) -> bool:
|
359
|
+
async def set(self, value: Numeric, retries: int = 0, timeout: float = 5.0) -> bool:
|
357
360
|
"""Set a parameter value."""
|
358
361
|
return await super().set(value, retries=retries, timeout=timeout)
|
359
362
|
|
360
363
|
def set_nowait(
|
361
|
-
self, value:
|
364
|
+
self, value: Numeric, retries: int = 0, timeout: float = 5.0
|
362
365
|
) -> None:
|
363
366
|
"""Set a parameter value without waiting."""
|
364
367
|
super().set_nowait(value, retries=retries, timeout=timeout)
|
@@ -368,17 +371,17 @@ class Number(Parameter):
|
|
368
371
|
return Request()
|
369
372
|
|
370
373
|
@property
|
371
|
-
def value(self) ->
|
374
|
+
def value(self) -> Numeric:
|
372
375
|
"""Return the value."""
|
373
376
|
return self._unpack_value(self.values.value)
|
374
377
|
|
375
378
|
@property
|
376
|
-
def min_value(self) ->
|
379
|
+
def min_value(self) -> Numeric:
|
377
380
|
"""Return the minimum allowed value."""
|
378
381
|
return self._unpack_value(self.values.min_value)
|
379
382
|
|
380
383
|
@property
|
381
|
-
def max_value(self) ->
|
384
|
+
def max_value(self) -> Numeric:
|
382
385
|
"""Return the maximum allowed value."""
|
383
386
|
return self._unpack_value(self.values.max_value)
|
384
387
|
|
@@ -402,11 +405,11 @@ class OffsetNumber(Number):
|
|
402
405
|
|
403
406
|
description: OffsetNumberDescription
|
404
407
|
|
405
|
-
def _pack_value(self, value:
|
408
|
+
def _pack_value(self, value: Numeric) -> int:
|
406
409
|
"""Pack the parameter value."""
|
407
410
|
return super()._pack_value(value + self.description.offset)
|
408
411
|
|
409
|
-
def _unpack_value(self, value: int) ->
|
412
|
+
def _unpack_value(self, value: int) -> Numeric:
|
410
413
|
"""Unpack the parameter value."""
|
411
414
|
return super()._unpack_value(value - self.description.offset)
|
412
415
|
|
@@ -505,7 +508,7 @@ class Switch(Parameter):
|
|
505
508
|
__all__ = [
|
506
509
|
"Number",
|
507
510
|
"NumberDescription",
|
508
|
-
"
|
511
|
+
"Numeric",
|
509
512
|
"OffsetNumber",
|
510
513
|
"OffsetNumberDescription",
|
511
514
|
"Parameter",
|