PyPlumIO 0.4.0.post1__tar.gz → 0.4.1__tar.gz
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.4.0.post1 → PyPlumIO-0.4.1}/.vscode/settings.json +5 -2
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/PKG-INFO +1 -1
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/PyPlumIO.egg-info/PKG-INFO +1 -1
- PyPlumIO-0.4.1/pyplumio/_version.py +4 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/const.py +1 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/devices/__init__.py +19 -18
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/devices/ecomax.py +16 -3
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/ecomax_parameters.py +25 -12
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/mixer_parameters.py +28 -34
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/thermostat_parameters.py +49 -51
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/test_devices.py +14 -3
- PyPlumIO-0.4.0.post1/pyplumio/_version.py +0 -4
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/.gitattributes +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/.github/CODE_OF_CONDUCT.md +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/.github/workflows/ci.yml +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/.github/workflows/codeql-analysis.yml +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/.github/workflows/deploy.yml +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/.github/workflows/documentation.yml +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/.gitignore +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/.pre-commit-config.yaml +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/LICENSE +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/MANIFEST.in +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/PyPlumIO.egg-info/SOURCES.txt +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/PyPlumIO.egg-info/dependency_links.txt +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/PyPlumIO.egg-info/requires.txt +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/PyPlumIO.egg-info/top_level.txt +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/README.md +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/docs/Makefile +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/docs/make.bat +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/docs/source/conf.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/docs/source/index.rst +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/docs/source/protocol.rst +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/docs/source/usage.rst +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/images/ecomax.png +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/images/rs485.png +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/__init__.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/__main__.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/connection.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/devices/ecoster.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/devices/mixer.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/devices/thermostat.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/exceptions.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/filters.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/frames/__init__.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/frames/messages.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/frames/requests.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/frames/responses.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/helpers/__init__.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/helpers/data_types.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/helpers/event_manager.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/helpers/factory.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/helpers/parameter.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/helpers/schedule.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/helpers/task_manager.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/helpers/timeout.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/helpers/typing.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/helpers/uid.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/protocol.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/stream.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/__init__.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/alerts.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/data_schema.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/fan_power.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/frame_versions.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/fuel_consumption.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/fuel_level.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/lambda_sensor.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/load.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/mixer_sensors.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/modules.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/network_info.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/output_flags.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/outputs.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/pending_alerts.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/power.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/product_info.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/program_version.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/regulator_data.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/schedules.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/statuses.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/temperatures.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/structures/thermostat_sensors.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyplumio/util.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/pyproject.toml +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/requirements.txt +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/requirements_test.txt +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/setup.cfg +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/__init__.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/conftest.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/frames/test_init.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/frames/test_messages.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/frames/test_requests.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/frames/test_responses.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/helpers/__init__.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/helpers/test_data_types.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/helpers/test_event_manager.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/helpers/test_factory.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/helpers/test_parameter.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/helpers/test_schedule.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/helpers/test_task_manager.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/helpers/test_timeout.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/helpers/test_uid.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/test_connection.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/test_filters.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/test_init.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/test_main.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/test_protocol.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/test_stream.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tests/test_util.py +0 -0
- {PyPlumIO-0.4.0.post1 → PyPlumIO-0.4.1}/tox.ini +0 -0
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"python.testing.unittestEnabled": false,
|
3
3
|
"python.testing.pytestEnabled": true,
|
4
|
-
"python.formatting.provider": "
|
4
|
+
"python.formatting.provider": "none",
|
5
5
|
"editor.formatOnSave": true,
|
6
6
|
"editor.codeActionsOnSave": {
|
7
7
|
"source.organizeImports": true
|
@@ -16,5 +16,8 @@
|
|
16
16
|
"editor.rulers": [
|
17
17
|
72,
|
18
18
|
88
|
19
|
-
]
|
19
|
+
],
|
20
|
+
"[python]": {
|
21
|
+
"editor.defaultFormatter": "ms-python.black-formatter"
|
22
|
+
}
|
20
23
|
}
|
@@ -12,6 +12,7 @@ STATE_OFF: Final = "off"
|
|
12
12
|
ATTR_CONNECTED: Final = "connected"
|
13
13
|
ATTR_CURRENT_TEMP: Final = "current_temp"
|
14
14
|
ATTR_DEVICE_INDEX: Final = "device_index"
|
15
|
+
ATTR_FRAME_ERRORS: Final = "frame_errors"
|
15
16
|
ATTR_INDEX: Final = "index"
|
16
17
|
ATTR_LOADED: Final = "loaded"
|
17
18
|
ATTR_OFFSET: Final = "offset"
|
@@ -6,7 +6,7 @@ import logging
|
|
6
6
|
from typing import ClassVar
|
7
7
|
|
8
8
|
from pyplumio import util
|
9
|
-
from pyplumio.const import ATTR_LOADED, DeviceType, FrameType
|
9
|
+
from pyplumio.const import ATTR_FRAME_ERRORS, ATTR_LOADED, DeviceType, FrameType
|
10
10
|
from pyplumio.exceptions import ParameterNotFoundError, UnknownDeviceError
|
11
11
|
from pyplumio.frames import DataFrameDescription, Frame, Request, get_frame_handler
|
12
12
|
from pyplumio.helpers.event_manager import EventManager
|
@@ -99,22 +99,23 @@ class Addressable(Device):
|
|
99
99
|
|
100
100
|
async def async_setup(self) -> bool:
|
101
101
|
"""Setup addressable device object."""
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
self.
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
102
|
+
results = await asyncio.gather(
|
103
|
+
*{
|
104
|
+
self.create_task(
|
105
|
+
self.request(description.provides, description.frame_type)
|
106
|
+
)
|
107
|
+
for description in self._frame_types
|
108
|
+
},
|
109
|
+
return_exceptions=True,
|
110
|
+
)
|
111
|
+
|
112
|
+
errors = [
|
113
|
+
result.args[1] for result in results if isinstance(result, ValueError)
|
114
|
+
]
|
115
|
+
|
116
|
+
await self.dispatch(ATTR_FRAME_ERRORS, errors)
|
117
|
+
await self.dispatch(ATTR_LOADED, True)
|
118
|
+
return True
|
118
119
|
|
119
120
|
async def request(
|
120
121
|
self,
|
@@ -136,7 +137,7 @@ class Addressable(Device):
|
|
136
137
|
except asyncio.TimeoutError:
|
137
138
|
retries -= 1
|
138
139
|
|
139
|
-
raise ValueError(f'could not request "{name}"
|
140
|
+
raise ValueError(f'could not request "{name}"', frame_type)
|
140
141
|
|
141
142
|
|
142
143
|
class SubDevice(Device):
|
@@ -8,6 +8,7 @@ import time
|
|
8
8
|
from typing import ClassVar, Final
|
9
9
|
|
10
10
|
from pyplumio.const import (
|
11
|
+
ATTR_FRAME_ERRORS,
|
11
12
|
ATTR_PASSWORD,
|
12
13
|
ATTR_SENSORS,
|
13
14
|
ATTR_STATE,
|
@@ -140,12 +141,24 @@ class EcoMAX(Addressable):
|
|
140
141
|
|
141
142
|
super().handle_frame(frame)
|
142
143
|
|
144
|
+
def _has_frame_version(self, frame_type: FrameType | int, version: int) -> bool:
|
145
|
+
"""Check if device instance has a version of the frame."""
|
146
|
+
return (
|
147
|
+
frame_type in self._frame_versions
|
148
|
+
and self._frame_versions[frame_type] == version
|
149
|
+
)
|
150
|
+
|
151
|
+
def _frame_is_supported(self, frame_type: FrameType | int) -> bool:
|
152
|
+
"""Check if frame is supported by the device."""
|
153
|
+
return frame_type not in self.data.get(ATTR_FRAME_ERRORS, [])
|
154
|
+
|
143
155
|
async def _update_frame_versions(self, versions: dict[int, int]) -> None:
|
144
156
|
"""Check versions and fetch outdated frames."""
|
145
157
|
for frame_type, version in versions.items():
|
146
|
-
if
|
147
|
-
frame_type
|
148
|
-
|
158
|
+
if (
|
159
|
+
is_known_frame_type(frame_type)
|
160
|
+
and self._frame_is_supported(frame_type)
|
161
|
+
and not self._has_frame_version(frame_type, version)
|
149
162
|
):
|
150
163
|
# We don't have this frame or it's version has changed.
|
151
164
|
request = factory(get_frame_handler(frame_type), recipient=self.address)
|
@@ -10,13 +10,15 @@ from pyplumio.devices import Addressable
|
|
10
10
|
from pyplumio.frames import Request
|
11
11
|
from pyplumio.helpers.factory import factory
|
12
12
|
from pyplumio.helpers.parameter import BinaryParameter, Parameter, ParameterDescription
|
13
|
-
from pyplumio.helpers.typing import EventDataType,
|
13
|
+
from pyplumio.helpers.typing import EventDataType, ParameterValueType
|
14
14
|
from pyplumio.structures import StructureDecoder, ensure_device_data
|
15
15
|
from pyplumio.structures.thermostat_parameters import ATTR_THERMOSTAT_PROFILE
|
16
16
|
|
17
17
|
ATTR_ECOMAX_CONTROL: Final = "ecomax_control"
|
18
18
|
ATTR_ECOMAX_PARAMETERS: Final = "ecomax_parameters"
|
19
19
|
|
20
|
+
ECOMAX_PARAMETER_SIZE: Final = 3
|
21
|
+
|
20
22
|
|
21
23
|
class EcomaxParameter(Parameter):
|
22
24
|
"""Represents ecoMAX parameter."""
|
@@ -301,22 +303,33 @@ THERMOSTAT_PROFILE_PARAMETER = EcomaxParameterDescription(name=ATTR_THERMOSTAT_P
|
|
301
303
|
class EcomaxParametersStructure(StructureDecoder):
|
302
304
|
"""Represents ecoMAX parameters data structure."""
|
303
305
|
|
306
|
+
_offset: int
|
307
|
+
|
308
|
+
def _ecomax_parameter(self, message: bytearray, start: int, end: int):
|
309
|
+
"""Yields ecoMAX parameters."""
|
310
|
+
for index in range(start, start + end):
|
311
|
+
if parameter := util.unpack_parameter(message, self._offset):
|
312
|
+
yield (index, parameter)
|
313
|
+
|
314
|
+
self._offset += ECOMAX_PARAMETER_SIZE
|
315
|
+
|
304
316
|
def decode(
|
305
317
|
self, message: bytearray, offset: int = 0, data: EventDataType | None = None
|
306
318
|
) -> tuple[EventDataType, int]:
|
307
319
|
"""Decode bytes and return message data and offset."""
|
308
|
-
first_index = message[offset + 1]
|
309
|
-
last_index = message[offset + 2]
|
310
|
-
offset += 3
|
311
|
-
ecomax_parameters: list[tuple[int, ParameterDataType]] = []
|
312
|
-
for index in range(first_index, first_index + last_index):
|
313
|
-
parameter = util.unpack_parameter(message, offset)
|
314
|
-
if parameter is not None:
|
315
|
-
ecomax_parameters.append((index, parameter))
|
316
320
|
|
317
|
-
|
321
|
+
start = message[offset + 1]
|
322
|
+
end = message[offset + 2]
|
323
|
+
self._offset = offset + 3
|
318
324
|
|
319
325
|
return (
|
320
|
-
ensure_device_data(
|
321
|
-
|
326
|
+
ensure_device_data(
|
327
|
+
data,
|
328
|
+
{
|
329
|
+
ATTR_ECOMAX_PARAMETERS: list(
|
330
|
+
self._ecomax_parameter(message, start, end)
|
331
|
+
)
|
332
|
+
},
|
333
|
+
),
|
334
|
+
self._offset,
|
322
335
|
)
|
@@ -1,7 +1,6 @@
|
|
1
1
|
"""Contains mixer parameter structure decoder."""
|
2
2
|
from __future__ import annotations
|
3
3
|
|
4
|
-
from collections.abc import Iterable
|
5
4
|
from dataclasses import dataclass
|
6
5
|
from typing import TYPE_CHECKING, Final
|
7
6
|
|
@@ -18,6 +17,8 @@ if TYPE_CHECKING:
|
|
18
17
|
|
19
18
|
ATTR_MIXER_PARAMETERS: Final = "mixer_parameters"
|
20
19
|
|
20
|
+
MIXER_PARAMETER_SIZE: Final = 3
|
21
|
+
|
21
22
|
|
22
23
|
class MixerParameter(Parameter):
|
23
24
|
"""Represents mixer parameter."""
|
@@ -117,48 +118,41 @@ ECOMAX_I_MIXER_PARAMETERS: tuple[MixerParameterDescription, ...] = (
|
|
117
118
|
)
|
118
119
|
|
119
120
|
|
120
|
-
|
121
|
-
|
122
|
-
) -> tuple[list[tuple[int, ParameterDataType]], int]:
|
123
|
-
"""Decode parameters for a single mixer."""
|
124
|
-
parameters: list[tuple[int, ParameterDataType]] = []
|
125
|
-
for index in indexes:
|
126
|
-
parameter = util.unpack_parameter(message, offset)
|
127
|
-
if parameter is not None:
|
128
|
-
parameters.append((index, parameter))
|
121
|
+
class MixerParametersStructure(StructureDecoder):
|
122
|
+
"""Represent mixer parameters data structure."""
|
129
123
|
|
130
|
-
|
124
|
+
_offset: int
|
131
125
|
|
132
|
-
|
126
|
+
def _mixer_parameter(self, message: bytearray, start: int, end: int):
|
127
|
+
"""Yields mixer parameters."""
|
128
|
+
for index in range(start, start + end):
|
129
|
+
if (parameter := util.unpack_parameter(message, self._offset)) is not None:
|
130
|
+
yield (index, parameter)
|
133
131
|
|
134
|
-
|
135
|
-
class MixerParametersStructure(StructureDecoder):
|
136
|
-
"""Represent mixer parameters data structure."""
|
132
|
+
self._offset += MIXER_PARAMETER_SIZE
|
137
133
|
|
138
134
|
def decode(
|
139
135
|
self, message: bytearray, offset: int = 0, data: EventDataType | None = None
|
140
136
|
) -> tuple[EventDataType, int]:
|
141
137
|
"""Decode bytes and return message data and offset."""
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
138
|
+
start = message[offset + 1]
|
139
|
+
end = message[offset + 2]
|
140
|
+
mixers = message[offset + 3]
|
141
|
+
self._offset = offset + 4
|
142
|
+
|
147
143
|
mixer_parameters: dict[int, list[tuple[int, ParameterDataType]]] = {}
|
148
|
-
for
|
149
|
-
parameters,
|
150
|
-
|
151
|
-
offset,
|
152
|
-
range(first_index, parameter_count_per_mixer),
|
153
|
-
)
|
154
|
-
if parameters:
|
155
|
-
mixer_parameters[index] = parameters
|
156
|
-
|
157
|
-
if not mixer_parameters:
|
158
|
-
# No mixer parameters detected.
|
159
|
-
return ensure_device_data(data, {ATTR_MIXER_PARAMETERS: None}), offset
|
144
|
+
for mixer in range(mixers):
|
145
|
+
if parameters := list(self._mixer_parameter(message, start, end)):
|
146
|
+
mixer_parameters[mixer] = parameters
|
160
147
|
|
161
148
|
return (
|
162
|
-
ensure_device_data(
|
163
|
-
|
149
|
+
ensure_device_data(
|
150
|
+
data,
|
151
|
+
{
|
152
|
+
ATTR_MIXER_PARAMETERS: (
|
153
|
+
None if not mixer_parameters else mixer_parameters
|
154
|
+
)
|
155
|
+
},
|
156
|
+
),
|
157
|
+
self._offset,
|
164
158
|
)
|
@@ -1,7 +1,6 @@
|
|
1
1
|
"""Contains thermostat parameter structure decoder."""
|
2
2
|
from __future__ import annotations
|
3
3
|
|
4
|
-
from collections.abc import Iterable
|
5
4
|
from dataclasses import dataclass
|
6
5
|
from typing import TYPE_CHECKING, Final
|
7
6
|
|
@@ -14,12 +13,15 @@ from pyplumio.helpers.typing import EventDataType, ParameterDataType, ParameterV
|
|
14
13
|
from pyplumio.structures import StructureDecoder, ensure_device_data
|
15
14
|
from pyplumio.structures.thermostat_sensors import ATTR_THERMOSTAT_COUNT
|
16
15
|
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from pyplumio.devices.thermostat import Thermostat
|
18
|
+
|
19
|
+
|
17
20
|
ATTR_THERMOSTAT_PROFILE: Final = "thermostat_profile"
|
18
21
|
ATTR_THERMOSTAT_PARAMETERS: Final = "thermostat_parameters"
|
19
22
|
ATTR_THERMOSTAT_PARAMETERS_DECODER: Final = "thermostat_parameters_decoder"
|
20
23
|
|
21
|
-
|
22
|
-
from pyplumio.devices.thermostat import Thermostat
|
24
|
+
THERMOSTAT_PARAMETER_SIZE: Final = 3
|
23
25
|
|
24
26
|
|
25
27
|
class ThermostatParameter(Parameter):
|
@@ -107,72 +109,68 @@ THERMOSTAT_PARAMETERS: tuple[ThermostatParameterDescription, ...] = (
|
|
107
109
|
)
|
108
110
|
|
109
111
|
|
110
|
-
def
|
111
|
-
|
112
|
-
) -> tuple[
|
113
|
-
"""
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
offset += 3 * description.size
|
122
|
-
|
123
|
-
return parameters, offset
|
112
|
+
def _empty_response(
|
113
|
+
offset: int, data: EventDataType | None = None
|
114
|
+
) -> tuple[EventDataType, int]:
|
115
|
+
"""Return empty response."""
|
116
|
+
return (
|
117
|
+
ensure_device_data(
|
118
|
+
data,
|
119
|
+
{ATTR_THERMOSTAT_PARAMETERS: None, ATTR_THERMOSTAT_PROFILE: None},
|
120
|
+
),
|
121
|
+
offset,
|
122
|
+
)
|
124
123
|
|
125
124
|
|
126
125
|
class ThermostatParametersStructure(StructureDecoder):
|
127
126
|
"""Represent thermostat parameters data structure."""
|
128
127
|
|
128
|
+
_offset: int
|
129
|
+
|
130
|
+
def _thermostat_parameter(
|
131
|
+
self, message: bytearray, thermostats: int, start: int, end: int
|
132
|
+
):
|
133
|
+
"""Yields thermostat parameters."""
|
134
|
+
for index in range(start, (start + end) // thermostats):
|
135
|
+
description = THERMOSTAT_PARAMETERS[index]
|
136
|
+
if (
|
137
|
+
parameter := util.unpack_parameter(
|
138
|
+
message, self._offset, size=description.size
|
139
|
+
)
|
140
|
+
) is not None:
|
141
|
+
yield (index, parameter)
|
142
|
+
|
143
|
+
self._offset += THERMOSTAT_PARAMETER_SIZE * description.size
|
144
|
+
|
129
145
|
def decode(
|
130
146
|
self, message: bytearray, offset: int = 0, data: EventDataType | None = None
|
131
147
|
) -> tuple[EventDataType, int]:
|
132
148
|
"""Decode bytes and return message data and offset."""
|
133
149
|
data = ensure_device_data(data)
|
134
|
-
|
135
|
-
if
|
136
|
-
return (
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
),
|
141
|
-
offset,
|
142
|
-
)
|
143
|
-
|
144
|
-
first_index = message[offset + 1]
|
145
|
-
last_index = message[offset + 2]
|
150
|
+
thermostats = data.get(ATTR_THERMOSTAT_COUNT, 0)
|
151
|
+
if thermostats == 0:
|
152
|
+
return _empty_response(offset, data)
|
153
|
+
|
154
|
+
start = message[offset + 1]
|
155
|
+
end = message[offset + 2]
|
146
156
|
thermostat_profile = util.unpack_parameter(message, offset + 3)
|
147
|
-
|
148
|
-
offset += 6
|
157
|
+
self._offset = offset + 6
|
149
158
|
thermostat_parameters: dict[int, list[tuple[int, ParameterDataType]]] = {}
|
150
|
-
for
|
151
|
-
parameters
|
152
|
-
message,
|
153
|
-
|
154
|
-
|
155
|
-
)
|
156
|
-
if parameters:
|
157
|
-
thermostat_parameters[index] = parameters
|
158
|
-
|
159
|
-
if not thermostat_parameters:
|
160
|
-
# No thermostat parameters detected.
|
161
|
-
return (
|
162
|
-
ensure_device_data(
|
163
|
-
data,
|
164
|
-
{ATTR_THERMOSTAT_PARAMETERS: None, ATTR_THERMOSTAT_PROFILE: None},
|
165
|
-
),
|
166
|
-
offset,
|
167
|
-
)
|
159
|
+
for thermostat in range(thermostats):
|
160
|
+
if parameters := list(
|
161
|
+
self._thermostat_parameter(message, thermostats, start, end)
|
162
|
+
):
|
163
|
+
thermostat_parameters[thermostat] = parameters
|
168
164
|
|
169
165
|
return (
|
170
166
|
ensure_device_data(
|
171
167
|
data,
|
172
168
|
{
|
173
169
|
ATTR_THERMOSTAT_PROFILE: thermostat_profile,
|
174
|
-
ATTR_THERMOSTAT_PARAMETERS:
|
170
|
+
ATTR_THERMOSTAT_PARAMETERS: None
|
171
|
+
if not thermostat_parameters
|
172
|
+
else thermostat_parameters,
|
175
173
|
},
|
176
174
|
),
|
177
|
-
|
175
|
+
self._offset,
|
178
176
|
)
|
@@ -7,6 +7,7 @@ import pytest
|
|
7
7
|
|
8
8
|
from pyplumio.const import (
|
9
9
|
ATTR_DEVICE_INDEX,
|
10
|
+
ATTR_FRAME_ERRORS,
|
10
11
|
ATTR_INDEX,
|
11
12
|
ATTR_LOADED,
|
12
13
|
ATTR_OFFSET,
|
@@ -117,6 +118,7 @@ async def test_async_setup() -> None:
|
|
117
118
|
await ecomax.wait_until_done()
|
118
119
|
|
119
120
|
assert await ecomax.get(ATTR_LOADED)
|
121
|
+
assert not ecomax.data[ATTR_FRAME_ERRORS]
|
120
122
|
assert mock_request.await_count == len(DATA_FRAME_TYPES)
|
121
123
|
|
122
124
|
|
@@ -126,13 +128,22 @@ async def test_async_setup_error(caplog) -> None:
|
|
126
128
|
|
127
129
|
with patch("pyplumio.devices.ecomax.EcoMAX.wait_for"), patch(
|
128
130
|
"pyplumio.devices.ecomax.EcoMAX.request",
|
129
|
-
side_effect=(
|
131
|
+
side_effect=(
|
132
|
+
ValueError("test", FrameType.REQUEST_ALERTS),
|
133
|
+
True,
|
134
|
+
True,
|
135
|
+
True,
|
136
|
+
True,
|
137
|
+
True,
|
138
|
+
True,
|
139
|
+
True,
|
140
|
+
),
|
130
141
|
) as mock_request:
|
131
142
|
await ecomax.async_setup()
|
132
143
|
await ecomax.wait_until_done()
|
133
144
|
|
134
|
-
assert
|
135
|
-
assert
|
145
|
+
assert await ecomax.get(ATTR_LOADED)
|
146
|
+
assert ecomax.data[ATTR_FRAME_ERRORS][0] == FrameType.REQUEST_ALERTS
|
136
147
|
assert mock_request.await_count == len(DATA_FRAME_TYPES)
|
137
148
|
|
138
149
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|