PyPlumIO 0.5.5__py3-none-any.whl → 0.5.7__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-0.5.5.dist-info → PyPlumIO-0.5.7.dist-info}/METADATA +7 -12
- PyPlumIO-0.5.7.dist-info/RECORD +60 -0
- pyplumio/__init__.py +83 -16
- pyplumio/_version.py +2 -2
- pyplumio/connection.py +46 -14
- pyplumio/const.py +2 -2
- pyplumio/devices/__init__.py +38 -12
- pyplumio/devices/ecomax.py +4 -6
- pyplumio/devices/ecoster.py +2 -2
- pyplumio/devices/mixer.py +2 -2
- pyplumio/devices/thermostat.py +2 -2
- pyplumio/filters.py +56 -2
- pyplumio/frames/__init__.py +12 -7
- pyplumio/frames/messages.py +6 -2
- pyplumio/frames/requests.py +53 -19
- pyplumio/frames/responses.py +46 -18
- pyplumio/helpers/data_types.py +32 -1
- pyplumio/helpers/event_manager.py +57 -8
- pyplumio/helpers/parameter.py +18 -8
- pyplumio/helpers/schedule.py +2 -2
- pyplumio/helpers/task_manager.py +0 -2
- pyplumio/helpers/typing.py +0 -1
- pyplumio/protocol.py +142 -71
- pyplumio/stream.py +0 -4
- pyplumio/structures/alerts.py +9 -15
- pyplumio/structures/ecomax_parameters.py +4 -2
- pyplumio/structures/network_info.py +13 -1
- pyplumio/structures/regulator_data.py +4 -4
- pyplumio/structures/{data_schema.py → regulator_data_schema.py} +4 -6
- pyplumio/structures/schedules.py +2 -2
- pyplumio/structures/temperatures.py +0 -2
- pyplumio/structures/thermostat_parameters.py +2 -2
- PyPlumIO-0.5.5.dist-info/RECORD +0 -60
- {PyPlumIO-0.5.5.dist-info → PyPlumIO-0.5.7.dist-info}/LICENSE +0 -0
- {PyPlumIO-0.5.5.dist-info → PyPlumIO-0.5.7.dist-info}/WHEEL +0 -0
- {PyPlumIO-0.5.5.dist-info → PyPlumIO-0.5.7.dist-info}/top_level.txt +0 -0
pyplumio/helpers/parameter.py
CHANGED
@@ -58,7 +58,7 @@ def _normalize_parameter_value(value: ParameterValueType) -> int:
|
|
58
58
|
class ParameterValues:
|
59
59
|
"""Represents a parameter values."""
|
60
60
|
|
61
|
-
__slots__ = "value", "min_value", "max_value"
|
61
|
+
__slots__ = ("value", "min_value", "max_value")
|
62
62
|
|
63
63
|
value: int
|
64
64
|
min_value: int
|
@@ -81,13 +81,13 @@ class BinaryParameterDescription(ParameterDescription):
|
|
81
81
|
class Parameter(ABC):
|
82
82
|
"""Represents a parameter."""
|
83
83
|
|
84
|
-
__slots__ = ("device", "values", "description", "
|
84
|
+
__slots__ = ("device", "values", "description", "_pending_update", "_index")
|
85
85
|
|
86
86
|
device: Device
|
87
87
|
values: ParameterValues
|
88
88
|
description: ParameterDescription
|
89
|
-
_index: int
|
90
89
|
_pending_update: bool
|
90
|
+
_index: int
|
91
91
|
|
92
92
|
def __init__(
|
93
93
|
self,
|
@@ -98,8 +98,8 @@ class Parameter(ABC):
|
|
98
98
|
):
|
99
99
|
"""Initialize a new parameter."""
|
100
100
|
self.device = device
|
101
|
-
self.description = description
|
102
101
|
self.values = values
|
102
|
+
self.description = description
|
103
103
|
self._pending_update = False
|
104
104
|
self._index = index
|
105
105
|
|
@@ -235,19 +235,29 @@ class BinaryParameter(Parameter):
|
|
235
235
|
"""Represents binary device parameter."""
|
236
236
|
|
237
237
|
async def turn_on(self) -> bool:
|
238
|
-
"""Set a parameter value to 'on'.
|
238
|
+
"""Set a parameter value to 'on'.
|
239
|
+
|
240
|
+
:return: `True` if parameter was successfully turned on, `False`
|
241
|
+
otherwise.
|
242
|
+
:rtype: bool
|
243
|
+
"""
|
239
244
|
return await self.set(STATE_ON)
|
240
245
|
|
241
246
|
async def turn_off(self) -> bool:
|
242
|
-
"""Set a parameter value to 'off'.
|
247
|
+
"""Set a parameter value to 'off'.
|
248
|
+
|
249
|
+
:return: `True` if parameter was successfully turned off, `False`
|
250
|
+
otherwise.
|
251
|
+
:rtype: bool
|
252
|
+
"""
|
243
253
|
return await self.set(STATE_OFF)
|
244
254
|
|
245
255
|
def turn_on_nowait(self) -> None:
|
246
|
-
"""Set a parameter
|
256
|
+
"""Set a parameter value to 'on' without waiting."""
|
247
257
|
self.set_nowait(STATE_ON)
|
248
258
|
|
249
259
|
def turn_off_nowait(self) -> None:
|
250
|
-
"""Set a parameter
|
260
|
+
"""Set a parameter value to 'off' without waiting."""
|
251
261
|
self.set_nowait(STATE_OFF)
|
252
262
|
|
253
263
|
@property
|
pyplumio/helpers/schedule.py
CHANGED
@@ -8,7 +8,7 @@ import math
|
|
8
8
|
from typing import Final, Literal
|
9
9
|
|
10
10
|
from pyplumio.const import STATE_OFF, STATE_ON
|
11
|
-
from pyplumio.devices import
|
11
|
+
from pyplumio.devices import AddressableDevice
|
12
12
|
from pyplumio.helpers.factory import factory
|
13
13
|
from pyplumio.structures.schedules import collect_schedule_data
|
14
14
|
|
@@ -136,7 +136,7 @@ class Schedule(Iterable):
|
|
136
136
|
)
|
137
137
|
|
138
138
|
name: str
|
139
|
-
device:
|
139
|
+
device: AddressableDevice
|
140
140
|
monday: ScheduleDay
|
141
141
|
tuesday: ScheduleDay
|
142
142
|
wednesday: ScheduleDay
|
pyplumio/helpers/task_manager.py
CHANGED
pyplumio/helpers/typing.py
CHANGED
@@ -7,7 +7,6 @@ from typing import Any, Literal, Protocol, Union
|
|
7
7
|
ParameterValueType = Union[int, float, bool, Literal["off"], Literal["on"]]
|
8
8
|
EventDataType = dict[Union[str, int], Any]
|
9
9
|
EventCallbackType = Callable[[Any], Awaitable[Any]]
|
10
|
-
UndefinedType = Literal["undefined"]
|
11
10
|
|
12
11
|
|
13
12
|
class SupportsSubtraction(Protocol):
|
pyplumio/protocol.py
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
"""Contains protocol representation."""
|
2
2
|
from __future__ import annotations
|
3
3
|
|
4
|
+
from abc import ABC, abstractmethod
|
4
5
|
import asyncio
|
5
6
|
from collections.abc import Awaitable, Callable
|
6
7
|
import logging
|
7
8
|
from typing import Final, cast
|
8
9
|
|
9
10
|
from pyplumio.const import ATTR_CONNECTED, DeviceType
|
10
|
-
from pyplumio.devices import
|
11
|
+
from pyplumio.devices import AddressableDevice, get_device_handler_and_name
|
11
12
|
from pyplumio.exceptions import (
|
12
13
|
FrameDataError,
|
13
14
|
FrameError,
|
@@ -30,24 +31,14 @@ _LOGGER = logging.getLogger(__name__)
|
|
30
31
|
CONSUMERS_NUMBER: Final = 2
|
31
32
|
|
32
33
|
|
33
|
-
class Protocol(
|
34
|
-
"""Represents protocol."""
|
35
|
-
|
36
|
-
__slots__ = (
|
37
|
-
"writer",
|
38
|
-
"reader",
|
39
|
-
"connected",
|
40
|
-
"_network",
|
41
|
-
"_queues",
|
42
|
-
"_connection_lost_callback",
|
43
|
-
)
|
34
|
+
class Protocol(ABC):
|
35
|
+
"""Represents a protocol."""
|
44
36
|
|
45
37
|
writer: FrameWriter | None
|
46
38
|
reader: FrameReader | None
|
47
39
|
connected: asyncio.Event
|
48
|
-
_network: NetworkInfo
|
49
|
-
_queues: tuple[asyncio.Queue, asyncio.Queue]
|
50
40
|
_connection_lost_callback: Callable[[], Awaitable[None]] | None
|
41
|
+
_network: NetworkInfo
|
51
42
|
|
52
43
|
def __init__(
|
53
44
|
self,
|
@@ -60,60 +51,107 @@ class Protocol(EventManager):
|
|
60
51
|
self.writer = None
|
61
52
|
self.reader = None
|
62
53
|
self.connected = asyncio.Event()
|
63
|
-
read_queue: asyncio.Queue = asyncio.Queue()
|
64
|
-
write_queue: asyncio.Queue = asyncio.Queue()
|
65
|
-
self._queues = (read_queue, write_queue)
|
66
54
|
self._connection_lost_callback = connection_lost_callback
|
67
55
|
if ethernet_parameters is None:
|
68
|
-
ethernet_parameters = EthernetParameters()
|
56
|
+
ethernet_parameters = EthernetParameters(status=False)
|
69
57
|
|
70
58
|
if wireless_parameters is None:
|
71
|
-
wireless_parameters = WirelessParameters()
|
59
|
+
wireless_parameters = WirelessParameters(status=False)
|
72
60
|
|
73
61
|
self._network = NetworkInfo(eth=ethernet_parameters, wlan=wireless_parameters)
|
74
62
|
|
75
|
-
async def
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
await self.connected.wait()
|
80
|
-
while self.connected.is_set():
|
81
|
-
try:
|
82
|
-
if write_queue.qsize() > 0:
|
83
|
-
await self.writer.write(await write_queue.get())
|
84
|
-
write_queue.task_done()
|
63
|
+
async def close_writer(self) -> None:
|
64
|
+
"""Ensure that writer is closed."""
|
65
|
+
if not self.writer:
|
66
|
+
return
|
85
67
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
68
|
+
try:
|
69
|
+
await self.writer.close()
|
70
|
+
except (OSError, asyncio.TimeoutError):
|
71
|
+
# Ignore any connection errors when closing the writer.
|
72
|
+
pass
|
73
|
+
finally:
|
74
|
+
self.writer = None
|
90
75
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
_LOGGER.debug("Unknown device: %s", e)
|
97
|
-
except FrameError as e:
|
98
|
-
_LOGGER.debug("Can't process received frame: %s", e)
|
99
|
-
except (OSError, asyncio.TimeoutError):
|
100
|
-
self.create_task(self.connection_lost())
|
101
|
-
break
|
102
|
-
except Exception as e: # pylint: disable=broad-except
|
103
|
-
_LOGGER.exception(e)
|
76
|
+
@abstractmethod
|
77
|
+
def connection_established(
|
78
|
+
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
79
|
+
):
|
80
|
+
"""Called when connection is established."""
|
104
81
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
82
|
+
@abstractmethod
|
83
|
+
async def connection_lost(self):
|
84
|
+
"""Called when connection is lost."""
|
85
|
+
|
86
|
+
@abstractmethod
|
87
|
+
async def shutdown(self):
|
88
|
+
"""Shutdown the protocol."""
|
89
|
+
|
90
|
+
|
91
|
+
class DummyProtocol(Protocol):
|
92
|
+
"""Represents a dummy protocol.
|
93
|
+
|
94
|
+
This protocol sets frame reader and writer as attributes, then
|
95
|
+
sets connected event and does nothing.
|
96
|
+
"""
|
97
|
+
|
98
|
+
def connection_established(
|
99
|
+
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
100
|
+
):
|
101
|
+
"""Called when connection is established."""
|
102
|
+
self.reader = FrameReader(reader)
|
103
|
+
self.writer = FrameWriter(writer)
|
104
|
+
self.connected.set()
|
105
|
+
|
106
|
+
async def connection_lost(self):
|
107
|
+
"""Called when connection is lost."""
|
108
|
+
if self.connected.is_set():
|
109
|
+
self.connected.clear()
|
110
|
+
await self.close_writer()
|
111
|
+
if self._connection_lost_callback is not None:
|
112
|
+
await self._connection_lost_callback()
|
113
|
+
|
114
|
+
async def shutdown(self):
|
115
|
+
"""Shutdown the protocol."""
|
116
|
+
if self.connected.is_set():
|
117
|
+
self.connected.clear()
|
118
|
+
await self.close_writer()
|
119
|
+
|
120
|
+
|
121
|
+
class AsyncProtocol(Protocol, EventManager):
|
122
|
+
"""Represents an async protocol.
|
123
|
+
|
124
|
+
This protocol implements producer-consumers pattern using
|
125
|
+
asyncio queues.
|
126
|
+
|
127
|
+
The frame producer tries to read frames from write queue, if any
|
128
|
+
available, and sends them to the device via frame writer.
|
129
|
+
|
130
|
+
It reads stream via frame reader, creates device handler entry
|
131
|
+
and puts received frame and handler into the read queue.
|
132
|
+
|
133
|
+
Frame consumers reads handler-frame tuples from the read queue and
|
134
|
+
sends frame to respective handler for further processing.
|
135
|
+
"""
|
136
|
+
|
137
|
+
_queues: tuple[asyncio.Queue, asyncio.Queue]
|
138
|
+
|
139
|
+
def __init__(
|
140
|
+
self,
|
141
|
+
connection_lost_callback: Callable[[], Awaitable[None]] | None = None,
|
142
|
+
ethernet_parameters: EthernetParameters | None = None,
|
143
|
+
wireless_parameters: WirelessParameters | None = None,
|
144
|
+
):
|
145
|
+
"""Initialize a new default protocol."""
|
146
|
+
super().__init__(
|
147
|
+
connection_lost_callback, ethernet_parameters, wireless_parameters
|
148
|
+
)
|
149
|
+
self._queues = (asyncio.Queue(), asyncio.Queue())
|
112
150
|
|
113
151
|
def connection_established(
|
114
152
|
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
115
153
|
):
|
116
|
-
"""
|
154
|
+
"""Called when connection is established."""
|
117
155
|
self.reader = FrameReader(reader)
|
118
156
|
self.writer = FrameWriter(writer)
|
119
157
|
read_queue, write_queue = self.queues
|
@@ -128,27 +166,69 @@ class Protocol(EventManager):
|
|
128
166
|
self.connected.set()
|
129
167
|
|
130
168
|
async def connection_lost(self):
|
131
|
-
"""
|
169
|
+
"""Called when connection is lost."""
|
132
170
|
if self.connected.is_set():
|
133
171
|
self.connected.clear()
|
134
172
|
for device in self.data.values():
|
135
173
|
# Notify devices about connection loss.
|
136
174
|
await device.dispatch(ATTR_CONNECTED, False)
|
137
175
|
|
138
|
-
await self.
|
176
|
+
await self.close_writer()
|
139
177
|
if self._connection_lost_callback is not None:
|
140
178
|
await self._connection_lost_callback()
|
141
179
|
|
142
180
|
async def shutdown(self):
|
143
181
|
"""Shutdown protocol tasks."""
|
144
182
|
await asyncio.gather(*[queue.join() for queue in self.queues])
|
145
|
-
await super().shutdown()
|
183
|
+
await super(Protocol, self).shutdown()
|
146
184
|
for device in self.data.values():
|
147
185
|
await device.shutdown()
|
148
186
|
|
149
|
-
|
187
|
+
if self.connected.is_set():
|
188
|
+
self.connected.clear()
|
189
|
+
await self.close_writer()
|
190
|
+
|
191
|
+
async def frame_producer(
|
192
|
+
self, read_queue: asyncio.Queue, write_queue: asyncio.Queue
|
193
|
+
) -> None:
|
194
|
+
"""Handle frame reads and writes."""
|
195
|
+
await self.connected.wait()
|
196
|
+
while self.connected.is_set():
|
197
|
+
try:
|
198
|
+
if write_queue.qsize() > 0:
|
199
|
+
await self.writer.write(await write_queue.get())
|
200
|
+
write_queue.task_done()
|
201
|
+
|
202
|
+
if (response := await self.reader.read()) is not None:
|
203
|
+
read_queue.put_nowait(
|
204
|
+
(self.setup_device_entry(response.sender), response)
|
205
|
+
)
|
206
|
+
|
207
|
+
except FrameDataError as e:
|
208
|
+
_LOGGER.warning("Incorrect payload: %s", e)
|
209
|
+
except ReadError as e:
|
210
|
+
_LOGGER.debug("Read error: %s", e)
|
211
|
+
except UnknownDeviceError as e:
|
212
|
+
_LOGGER.debug("Unknown device: %s", e)
|
213
|
+
except FrameError as e:
|
214
|
+
_LOGGER.debug("Can't process received frame: %s", e)
|
215
|
+
except (OSError, asyncio.TimeoutError):
|
216
|
+
self.create_task(self.connection_lost())
|
217
|
+
break
|
218
|
+
except Exception as e: # pylint: disable=broad-except
|
219
|
+
_LOGGER.exception(e)
|
220
|
+
|
221
|
+
async def frame_consumer(self, read_queue: asyncio.Queue) -> None:
|
222
|
+
"""Handle frame processing."""
|
223
|
+
await self.connected.wait()
|
224
|
+
while self.connected.is_set():
|
225
|
+
device, frame = cast(
|
226
|
+
tuple[AddressableDevice, Frame], await read_queue.get()
|
227
|
+
)
|
228
|
+
device.handle_frame(frame)
|
229
|
+
read_queue.task_done()
|
150
230
|
|
151
|
-
def setup_device_entry(self, device_type: DeviceType) ->
|
231
|
+
def setup_device_entry(self, device_type: DeviceType) -> AddressableDevice:
|
152
232
|
"""Setup the device entry."""
|
153
233
|
handler, name = get_device_handler_and_name(device_type)
|
154
234
|
if name not in self.data:
|
@@ -159,23 +239,14 @@ class Protocol(EventManager):
|
|
159
239
|
def _create_device_entry(self, name: str, handler: str) -> None:
|
160
240
|
"""Create the device entry."""
|
161
241
|
write_queue = self.queues[1]
|
162
|
-
device:
|
242
|
+
device: AddressableDevice = factory(
|
243
|
+
handler, queue=write_queue, network=self._network
|
244
|
+
)
|
163
245
|
device.dispatch_nowait(ATTR_CONNECTED, True)
|
164
246
|
self.create_task(device.async_setup())
|
165
247
|
self.data[name] = device
|
166
248
|
self.set_event(name)
|
167
249
|
|
168
|
-
async def _remove_writer(self):
|
169
|
-
"""Attempt to gracefully remove the frame writer."""
|
170
|
-
if self.writer:
|
171
|
-
try:
|
172
|
-
await self.writer.close()
|
173
|
-
except (OSError, asyncio.TimeoutError):
|
174
|
-
# Ignore any connection errors when closing the writer.
|
175
|
-
pass
|
176
|
-
finally:
|
177
|
-
self.writer = None
|
178
|
-
|
179
250
|
@property
|
180
251
|
def queues(self) -> tuple[asyncio.Queue, asyncio.Queue]:
|
181
252
|
"""Protocol queues."""
|
pyplumio/stream.py
CHANGED
@@ -23,8 +23,6 @@ _LOGGER = logging.getLogger(__name__)
|
|
23
23
|
class FrameWriter:
|
24
24
|
"""Represents a frame writer."""
|
25
25
|
|
26
|
-
__slots__ = ("_writer",)
|
27
|
-
|
28
26
|
_writer: StreamWriter
|
29
27
|
|
30
28
|
def __init__(self, writer: StreamWriter):
|
@@ -50,8 +48,6 @@ class FrameWriter:
|
|
50
48
|
class FrameReader:
|
51
49
|
"""Represents a frame reader."""
|
52
50
|
|
53
|
-
__slots__ = ("_reader",)
|
54
|
-
|
55
51
|
_reader: StreamReader
|
56
52
|
|
57
53
|
def __init__(self, reader: StreamReader):
|
pyplumio/structures/alerts.py
CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
3
3
|
|
4
4
|
from dataclasses import dataclass
|
5
5
|
from datetime import datetime
|
6
|
+
from functools import lru_cache
|
6
7
|
from typing import Any, Final, Generator
|
7
8
|
|
8
9
|
from pyplumio.const import AlertType
|
@@ -12,28 +13,21 @@ from pyplumio.structures import StructureDecoder
|
|
12
13
|
from pyplumio.utils import ensure_dict
|
13
14
|
|
14
15
|
ATTR_ALERTS: Final = "alerts"
|
15
|
-
ATTR_YEAR: Final = "year"
|
16
|
-
ATTR_MONTH: Final = "month"
|
17
|
-
ATTR_DAY: Final = "day"
|
18
|
-
ATTR_HOUR: Final = "hour"
|
19
|
-
ATTR_MINUTE: Final = "minute"
|
20
|
-
ATTR_SECOND: Final = "second"
|
21
|
-
|
22
|
-
ALERT_SIZE: Final = 9
|
23
16
|
|
24
17
|
|
18
|
+
@lru_cache(maxsize=10)
|
25
19
|
def _convert_to_datetime(seconds: int) -> datetime:
|
26
20
|
"""Convert timestamp to a datetime object."""
|
27
21
|
|
28
22
|
def _seconds_to_datetime_args(seconds: int) -> Generator[Any, None, None]:
|
29
|
-
"""Convert seconds to a
|
23
|
+
"""Convert seconds to a kwargs for a datetime class."""
|
30
24
|
intervals: tuple[tuple[str, int, int], ...] = (
|
31
|
-
(
|
32
|
-
(
|
33
|
-
(
|
34
|
-
(
|
35
|
-
(
|
36
|
-
(
|
25
|
+
("year", 32140800, 2000), # 60sec * 60min * 24h * 31d * 12m
|
26
|
+
("month", 2678400, 1), # 60sec * 60min * 24h * 31d
|
27
|
+
("day", 86400, 1), # 60sec * 60min * 24h
|
28
|
+
("hour", 3600, 0), # 60sec * 60min
|
29
|
+
("minute", 60, 0),
|
30
|
+
("second", 1, 0),
|
37
31
|
)
|
38
32
|
|
39
33
|
for name, count, offset in intervals:
|
@@ -13,7 +13,7 @@ from pyplumio.const import (
|
|
13
13
|
ProductType,
|
14
14
|
UnitOfMeasurement,
|
15
15
|
)
|
16
|
-
from pyplumio.devices import
|
16
|
+
from pyplumio.devices import AddressableDevice
|
17
17
|
from pyplumio.frames import Request
|
18
18
|
from pyplumio.helpers.factory import factory
|
19
19
|
from pyplumio.helpers.parameter import (
|
@@ -38,7 +38,9 @@ ECOMAX_PARAMETER_SIZE: Final = 3
|
|
38
38
|
class EcomaxParameter(Parameter):
|
39
39
|
"""Represents an ecoMAX parameter."""
|
40
40
|
|
41
|
-
|
41
|
+
__slots__ = ()
|
42
|
+
|
43
|
+
device: AddressableDevice
|
42
44
|
description: EcomaxParameterDescription
|
43
45
|
|
44
46
|
async def set(self, value: ParameterValueType, retries: int = 5) -> bool:
|
@@ -22,18 +22,30 @@ NETWORK_INFO_SIZE: Final = 25
|
|
22
22
|
class EthernetParameters:
|
23
23
|
"""Represents an ethernet parameters."""
|
24
24
|
|
25
|
+
#: IP address.
|
25
26
|
ip: str = DEFAULT_IP
|
27
|
+
|
28
|
+
#: IP subnet mask.
|
26
29
|
netmask: str = DEFAULT_NETMASK
|
30
|
+
|
31
|
+
#: Gateway IP address.
|
27
32
|
gateway: str = DEFAULT_IP
|
28
|
-
|
33
|
+
|
34
|
+
#: Connection status. Parameters will be ignored if set to False.
|
35
|
+
status: bool = True
|
29
36
|
|
30
37
|
|
31
38
|
@dataclass
|
32
39
|
class WirelessParameters(EthernetParameters):
|
33
40
|
"""Represents a wireless network parameters."""
|
34
41
|
|
42
|
+
#: Wireless Service Set IDentifier.
|
35
43
|
ssid: str = ""
|
44
|
+
|
45
|
+
#: Wireless encryption standard.
|
36
46
|
encryption: EncryptionType = EncryptionType.NONE
|
47
|
+
|
48
|
+
#: Wireless signal strength in percentage.
|
37
49
|
signal_quality: int = 100
|
38
50
|
|
39
51
|
|
@@ -3,13 +3,13 @@ from __future__ import annotations
|
|
3
3
|
|
4
4
|
from typing import Final
|
5
5
|
|
6
|
-
from pyplumio.devices import
|
6
|
+
from pyplumio.devices import AddressableDevice
|
7
7
|
from pyplumio.helpers.data_types import BitArray, DataType
|
8
8
|
from pyplumio.helpers.event_manager import EventManager
|
9
9
|
from pyplumio.helpers.typing import EventDataType
|
10
10
|
from pyplumio.structures import StructureDecoder
|
11
|
-
from pyplumio.structures.data_schema import ATTR_SCHEMA
|
12
11
|
from pyplumio.structures.frame_versions import FrameVersionsStructure
|
12
|
+
from pyplumio.structures.regulator_data_schema import ATTR_REGDATA_SCHEMA
|
13
13
|
from pyplumio.utils import ensure_dict
|
14
14
|
|
15
15
|
ATTR_REGDATA: Final = "regdata"
|
@@ -60,8 +60,8 @@ class RegulatorDataStructure(StructureDecoder):
|
|
60
60
|
)
|
61
61
|
|
62
62
|
sender = self.frame.sender
|
63
|
-
if isinstance(sender,
|
64
|
-
schema := sender.get_nowait(
|
63
|
+
if isinstance(sender, AddressableDevice) and (
|
64
|
+
schema := sender.get_nowait(ATTR_REGDATA_SCHEMA, [])
|
65
65
|
):
|
66
66
|
data.setdefault(ATTR_REGDATA, RegulatorData()).load(
|
67
67
|
{
|
@@ -8,13 +8,11 @@ from pyplumio.helpers.typing import EventDataType
|
|
8
8
|
from pyplumio.structures import StructureDecoder
|
9
9
|
from pyplumio.utils import ensure_dict
|
10
10
|
|
11
|
-
|
11
|
+
ATTR_REGDATA_SCHEMA: Final = "regdata_schema"
|
12
12
|
|
13
|
-
BLOCK_SIZE: Final = 3
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
"""Represents a data schema structure."""
|
14
|
+
class RegulatorDataSchemaStructure(StructureDecoder):
|
15
|
+
"""Represents a regulator data schema structure."""
|
18
16
|
|
19
17
|
_offset: int
|
20
18
|
|
@@ -39,7 +37,7 @@ class DataSchemaStructure(StructureDecoder):
|
|
39
37
|
ensure_dict(
|
40
38
|
data,
|
41
39
|
{
|
42
|
-
|
40
|
+
ATTR_REGDATA_SCHEMA: [
|
43
41
|
self._unpack_block(message) for _ in range(blocks.value)
|
44
42
|
]
|
45
43
|
},
|
pyplumio/structures/schedules.py
CHANGED
@@ -8,7 +8,7 @@ from itertools import chain
|
|
8
8
|
from typing import Final
|
9
9
|
|
10
10
|
from pyplumio.const import ATTR_PARAMETER, ATTR_SCHEDULE, ATTR_SWITCH, ATTR_TYPE
|
11
|
-
from pyplumio.devices import
|
11
|
+
from pyplumio.devices import AddressableDevice, Device
|
12
12
|
from pyplumio.exceptions import FrameDataError
|
13
13
|
from pyplumio.frames import Request
|
14
14
|
from pyplumio.helpers.factory import factory
|
@@ -80,7 +80,7 @@ class ScheduleParameter(Parameter):
|
|
80
80
|
|
81
81
|
__slots__ = ()
|
82
82
|
|
83
|
-
device:
|
83
|
+
device: AddressableDevice
|
84
84
|
|
85
85
|
@property
|
86
86
|
def request(self) -> Request:
|
@@ -11,7 +11,7 @@ from pyplumio.const import (
|
|
11
11
|
ATTR_VALUE,
|
12
12
|
UnitOfMeasurement,
|
13
13
|
)
|
14
|
-
from pyplumio.devices import
|
14
|
+
from pyplumio.devices import AddressableDevice
|
15
15
|
from pyplumio.frames import Request
|
16
16
|
from pyplumio.helpers.factory import factory
|
17
17
|
from pyplumio.helpers.parameter import (
|
@@ -211,7 +211,7 @@ class ThermostatParametersStructure(StructureDecoder):
|
|
211
211
|
"""Decode bytes and return message data and offset."""
|
212
212
|
sender = self.frame.sender
|
213
213
|
if (
|
214
|
-
isinstance(sender,
|
214
|
+
isinstance(sender, AddressableDevice)
|
215
215
|
and (thermostats := sender.get_nowait(ATTR_THERMOSTATS_CONNECTED, 0)) == 0
|
216
216
|
):
|
217
217
|
return (
|