PyPlumIO 0.5.33__tar.gz → 0.5.35__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.5.33 → pyplumio-0.5.35}/PKG-INFO +1 -1
- {pyplumio-0.5.33 → pyplumio-0.5.35}/PyPlumIO.egg-info/PKG-INFO +1 -1
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/_version.py +2 -2
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/devices/__init__.py +9 -5
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/devices/ecomax.py +5 -2
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/exceptions.py +4 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/helpers/factory.py +5 -2
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/helpers/parameter.py +5 -3
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/helpers/schedule.py +6 -3
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/stream.py +14 -9
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/helpers/test_factory.py +1 -1
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/helpers/test_parameter.py +8 -2
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/test_devices.py +5 -5
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/test_stream.py +7 -4
- {pyplumio-0.5.33 → pyplumio-0.5.35}/.gitattributes +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/.github/CODE_OF_CONDUCT.md +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/.github/dependabot.yml +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/.github/workflows/ci.yml +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/.github/workflows/codeql-analysis.yml +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/.github/workflows/deploy.yml +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/.github/workflows/documentation.yml +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/.gitignore +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/.pre-commit-config.yaml +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/.vscode/settings.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/LICENSE +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/MANIFEST.in +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/PyPlumIO.egg-info/SOURCES.txt +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/PyPlumIO.egg-info/dependency_links.txt +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/PyPlumIO.egg-info/requires.txt +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/PyPlumIO.egg-info/top_level.txt +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/README.md +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/docs/Makefile +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/docs/make.bat +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/docs/source/callbacks.rst +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/docs/source/conf.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/docs/source/connecting.rst +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/docs/source/frames.rst +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/docs/source/index.rst +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/docs/source/mixers_thermostats.rst +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/docs/source/protocol.rst +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/docs/source/reading.rst +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/docs/source/schedules.rst +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/docs/source/writing.rst +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/images/ecomax.png +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/images/rs485.png +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/__init__.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/__main__.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/connection.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/const.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/devices/ecoster.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/devices/mixer.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/devices/thermostat.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/filters.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/frames/__init__.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/frames/messages.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/frames/requests.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/frames/responses.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/helpers/__init__.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/helpers/data_types.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/helpers/event_manager.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/helpers/task_manager.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/helpers/timeout.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/helpers/uid.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/protocol.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/py.typed +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/__init__.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/alerts.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/boiler_load.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/boiler_power.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/ecomax_parameters.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/fan_power.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/frame_versions.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/fuel_consumption.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/fuel_level.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/lambda_sensor.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/mixer_parameters.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/mixer_sensors.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/modules.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/network_info.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/output_flags.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/outputs.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/pending_alerts.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/product_info.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/program_version.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/regulator_data.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/regulator_data_schema.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/schedules.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/statuses.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/temperatures.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/thermostat_parameters.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/structures/thermostat_sensors.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyplumio/utils.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/pyproject.toml +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/requirements.txt +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/requirements_docs.txt +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/requirements_test.txt +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/setup.cfg +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/__init__.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/conftest.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/frames/test_init.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/frames/test_messages.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/frames/test_requests.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/frames/test_responses.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/helpers/__init__.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/helpers/test_data_types.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/helpers/test_event_manager.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/helpers/test_schedule.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/helpers/test_task_manager.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/helpers/test_timeout.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/helpers/test_uid.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/ruff.toml +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/test_connection.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/test_filters.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/test_init.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/test_main.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/test_protocol.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/test_utils.py +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/messages/regulator_data.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/messages/sensor_data.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/requests/alerts.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/requests/ecomax_control.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/requests/ecomax_parameters.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/requests/mixer_parameters.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/requests/set_ecomax_parameter.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/requests/set_mixer_parameter.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/requests/set_schedule.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/requests/set_thermostat_parameter.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/requests/thermostat_parameters.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/responses/alerts.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/responses/device_available.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/responses/ecomax_parameters.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/responses/mixer_parameters.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/responses/password.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/responses/program_version.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/responses/regulator_data_schema.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/responses/schedules.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/responses/thermostat_parameters.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/responses/uid.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/unknown/unknown_ecomax_parameter.json +0 -0
- {pyplumio-0.5.33 → pyplumio-0.5.35}/tests/testdata/unknown/unknown_mixer_parameter.json +0 -0
@@ -9,7 +9,7 @@ import logging
|
|
9
9
|
from typing import Any, ClassVar
|
10
10
|
|
11
11
|
from pyplumio.const import ATTR_FRAME_ERRORS, ATTR_LOADED, DeviceType, FrameType
|
12
|
-
from pyplumio.exceptions import UnknownDeviceError
|
12
|
+
from pyplumio.exceptions import RequestError, UnknownDeviceError
|
13
13
|
from pyplumio.frames import DataFrameDescription, Frame, Request, is_known_frame_type
|
14
14
|
from pyplumio.helpers.event_manager import EventManager
|
15
15
|
from pyplumio.helpers.factory import create_instance
|
@@ -82,7 +82,7 @@ class Device(ABC, EventManager):
|
|
82
82
|
"""
|
83
83
|
parameter = await self.get(name, timeout)
|
84
84
|
if not isinstance(parameter, Parameter):
|
85
|
-
raise TypeError(f"{name} is not valid
|
85
|
+
raise TypeError(f"The parameter '{name}' is not valid or does not exist.")
|
86
86
|
|
87
87
|
return await parameter.set(value, retries=retries)
|
88
88
|
|
@@ -143,7 +143,7 @@ class PhysicalDevice(Device, ABC):
|
|
143
143
|
and not self.has_frame_version(frame_type, version)
|
144
144
|
):
|
145
145
|
_LOGGER.debug(
|
146
|
-
"Updating frame %s to version %i",
|
146
|
+
"Updating frame %s to version %i", frame_type, version
|
147
147
|
)
|
148
148
|
request = await Request.create(frame_type, recipient=self.address)
|
149
149
|
self.queue.put_nowait(request)
|
@@ -180,7 +180,7 @@ class PhysicalDevice(Device, ABC):
|
|
180
180
|
)
|
181
181
|
|
182
182
|
errors = [
|
183
|
-
result.args[1] for result in results if isinstance(result,
|
183
|
+
result.args[1] for result in results if isinstance(result, RequestError)
|
184
184
|
]
|
185
185
|
|
186
186
|
await asyncio.gather(
|
@@ -203,7 +203,11 @@ class PhysicalDevice(Device, ABC):
|
|
203
203
|
except asyncio.TimeoutError:
|
204
204
|
retries -= 1
|
205
205
|
|
206
|
-
raise
|
206
|
+
raise RequestError(
|
207
|
+
f"Failed to request parameter '{name}' with frame type '{frame_type}' "
|
208
|
+
f"after {retries} retries.",
|
209
|
+
frame_type,
|
210
|
+
)
|
207
211
|
|
208
212
|
@classmethod
|
209
213
|
async def create(cls, device_type: int, **kwargs: Any) -> PhysicalDevice:
|
@@ -98,6 +98,8 @@ SETUP_FRAME_TYPES: tuple[DataFrameDescription, ...] = (
|
|
98
98
|
|
99
99
|
_LOGGER = logging.getLogger(__name__)
|
100
100
|
|
101
|
+
ecomax_control_error = "ecoMAX control is not available. Please try again later."
|
102
|
+
|
101
103
|
|
102
104
|
class EcoMAX(PhysicalDevice):
|
103
105
|
"""Represents an ecoMAX controller."""
|
@@ -192,6 +194,7 @@ class EcoMAX(PhysicalDevice):
|
|
192
194
|
values,
|
193
195
|
product.model,
|
194
196
|
)
|
197
|
+
return
|
195
198
|
|
196
199
|
handler = (
|
197
200
|
EcomaxSwitch
|
@@ -393,7 +396,7 @@ class EcoMAX(PhysicalDevice):
|
|
393
396
|
ecomax_control: EcomaxSwitch = self.data[ATTR_ECOMAX_CONTROL]
|
394
397
|
return await ecomax_control.turn_on()
|
395
398
|
except KeyError:
|
396
|
-
_LOGGER.error(
|
399
|
+
_LOGGER.error(ecomax_control_error)
|
397
400
|
return False
|
398
401
|
|
399
402
|
async def turn_off(self) -> bool:
|
@@ -402,7 +405,7 @@ class EcoMAX(PhysicalDevice):
|
|
402
405
|
ecomax_control: EcomaxSwitch = self.data[ATTR_ECOMAX_CONTROL]
|
403
406
|
return await ecomax_control.turn_off()
|
404
407
|
except KeyError:
|
405
|
-
_LOGGER.error(
|
408
|
+
_LOGGER.error(ecomax_control_error)
|
406
409
|
return False
|
407
410
|
|
408
411
|
def turn_on_nowait(self) -> None:
|
@@ -26,9 +26,12 @@ async def create_instance(class_path: str, cls: type[T], **kwargs: Any) -> T:
|
|
26
26
|
module = await _import_module(module_name)
|
27
27
|
instance = getattr(module, class_name)(**kwargs)
|
28
28
|
if not isinstance(instance, cls):
|
29
|
-
raise TypeError(
|
29
|
+
raise TypeError(
|
30
|
+
f"Expected instance of '{cls.__name__}', but got "
|
31
|
+
f"'{type(instance).__name__}' from '{class_name}'"
|
32
|
+
)
|
30
33
|
|
31
34
|
return instance
|
32
35
|
except Exception:
|
33
|
-
_LOGGER.
|
36
|
+
_LOGGER.exception("Failed to create instance for class path '%s'", class_path)
|
34
37
|
raise
|
@@ -189,7 +189,8 @@ class Parameter(ABC):
|
|
189
189
|
value = _normalize_parameter_value(value)
|
190
190
|
if value < self.values.min_value or value > self.values.max_value:
|
191
191
|
raise ValueError(
|
192
|
-
f"
|
192
|
+
f"Invalid value: {value}. Must be between "
|
193
|
+
f"{self.min_value} and {self.max_value}."
|
193
194
|
)
|
194
195
|
|
195
196
|
return value
|
@@ -215,9 +216,10 @@ class Parameter(ABC):
|
|
215
216
|
self._pending_update = True
|
216
217
|
while self.pending_update:
|
217
218
|
if retries <= 0:
|
218
|
-
_LOGGER.
|
219
|
-
"
|
219
|
+
_LOGGER.warning(
|
220
|
+
"Unable to confirm that parameter '%s' was set after %d retries",
|
220
221
|
self.description.name,
|
222
|
+
retries,
|
221
223
|
)
|
222
224
|
return False
|
223
225
|
|
@@ -50,8 +50,8 @@ def _get_time_range(
|
|
50
50
|
|
51
51
|
if end_dt <= start_dt:
|
52
52
|
raise ValueError(
|
53
|
-
f"Invalid
|
54
|
-
"
|
53
|
+
f"Invalid time range: start time ({start}) must be earlier "
|
54
|
+
f"than end time ({end})."
|
55
55
|
)
|
56
56
|
|
57
57
|
def _dt_to_index(dt: dt.datetime) -> int:
|
@@ -107,7 +107,10 @@ class ScheduleDay(MutableMapping):
|
|
107
107
|
) -> None:
|
108
108
|
"""Set a schedule interval state."""
|
109
109
|
if state not in get_args(ScheduleState):
|
110
|
-
raise ValueError(
|
110
|
+
raise ValueError(
|
111
|
+
f"Invalid state '{state}'. Allowed states are: "
|
112
|
+
f"{', '.join(get_args(ScheduleState))}"
|
113
|
+
)
|
111
114
|
|
112
115
|
for index in _get_time_range(start, end):
|
113
116
|
self._intervals[index] = True if state in ON_STATES else False
|
@@ -45,7 +45,7 @@ class FrameWriter:
|
|
45
45
|
"""Send the frame and wait until send buffer is empty."""
|
46
46
|
self._writer.write(frame.bytes)
|
47
47
|
await self._writer.drain()
|
48
|
-
_LOGGER.debug("Sent frame: %s", frame)
|
48
|
+
_LOGGER.debug("Sent frame: %s, bytes: %s", frame, frame.bytes)
|
49
49
|
|
50
50
|
async def close(self) -> None:
|
51
51
|
"""Close the frame writer."""
|
@@ -53,7 +53,9 @@ class FrameWriter:
|
|
53
53
|
self._writer.close()
|
54
54
|
await self.wait_closed()
|
55
55
|
except (OSError, asyncio.TimeoutError):
|
56
|
-
_LOGGER.exception(
|
56
|
+
_LOGGER.exception(
|
57
|
+
"Failed to close the frame writer due to an unexpected error"
|
58
|
+
)
|
57
59
|
|
58
60
|
@timeout(WRITER_TIMEOUT)
|
59
61
|
async def wait_closed(self) -> None:
|
@@ -96,7 +98,7 @@ class FrameReader:
|
|
96
98
|
buffer += await self._reader.readexactly(HEADER_SIZE - DELIMITER_SIZE)
|
97
99
|
except IncompleteReadError as e:
|
98
100
|
raise ReadError(
|
99
|
-
f"
|
101
|
+
f"Incomplete header, expected {e.expected} bytes"
|
100
102
|
) from e
|
101
103
|
|
102
104
|
return Header(*struct_header.unpack_from(buffer)[DELIMITER_SIZE:]), buffer
|
@@ -123,18 +125,21 @@ class FrameReader:
|
|
123
125
|
raise UnknownDeviceError(f"Unknown sender type ({sender})")
|
124
126
|
|
125
127
|
if frame_length > MAX_FRAME_LENGTH or frame_length < MIN_FRAME_LENGTH:
|
126
|
-
raise ReadError(
|
128
|
+
raise ReadError(
|
129
|
+
f"Unexpected frame length ({frame_length}), expected between "
|
130
|
+
f"{MIN_FRAME_LENGTH} and {MAX_FRAME_LENGTH}"
|
131
|
+
)
|
127
132
|
|
128
133
|
try:
|
129
134
|
buffer += await self._reader.readexactly(frame_length - HEADER_SIZE)
|
130
135
|
except IncompleteReadError as e:
|
131
|
-
raise ReadError(
|
132
|
-
f"Got incomplete frame, while trying to read {e.expected} bytes"
|
133
|
-
) from e
|
136
|
+
raise ReadError(f"Incomplete frame, expected {e.expected} bytes") from e
|
134
137
|
|
135
138
|
if (checksum := bcc(buffer[:-2])) and checksum != buffer[-2]:
|
136
139
|
raise ChecksumError(
|
137
|
-
f"Incorrect frame checksum
|
140
|
+
f"Incorrect frame checksum: calculated {checksum}, "
|
141
|
+
f"expected {buffer[-2]}. "
|
142
|
+
f"Frame data: {buffer.hex()}"
|
138
143
|
)
|
139
144
|
|
140
145
|
frame = await Frame.create(
|
@@ -145,6 +150,6 @@ class FrameReader:
|
|
145
150
|
econet_version=econet_version,
|
146
151
|
message=buffer[HEADER_SIZE + 1 : -2],
|
147
152
|
)
|
148
|
-
_LOGGER.debug("Received frame: %s", frame)
|
153
|
+
_LOGGER.debug("Received frame: %s, bytes: %s", frame, buffer.hex())
|
149
154
|
|
150
155
|
return frame
|
@@ -19,7 +19,7 @@ async def test_get_object_with_incorrect_base_class() -> None:
|
|
19
19
|
await create_instance("frames.responses.UIDResponse", cls=Request)
|
20
20
|
|
21
21
|
assert str(exc_info.value) == (
|
22
|
-
"
|
22
|
+
"Expected instance of 'Request', but got 'UIDResponse' from 'UIDResponse'"
|
23
23
|
)
|
24
24
|
|
25
25
|
|
@@ -268,7 +268,10 @@ async def test_number_request_with_unchanged_value(
|
|
268
268
|
assert number.pending_update
|
269
269
|
assert mock_put.await_count == 3 # type: ignore [unreachable]
|
270
270
|
mock_put.reset_mock()
|
271
|
-
assert
|
271
|
+
assert (
|
272
|
+
"Unable to confirm that parameter 'test_number' was set after 0 retries"
|
273
|
+
in caplog.text
|
274
|
+
)
|
272
275
|
await number.set(5)
|
273
276
|
mock_put.assert_not_awaited()
|
274
277
|
|
@@ -283,7 +286,10 @@ async def test_switch_request_with_unchanged_value(
|
|
283
286
|
assert switch.pending_update
|
284
287
|
assert mock_put.await_count == 3 # type: ignore [unreachable]
|
285
288
|
mock_put.reset_mock()
|
286
|
-
assert
|
289
|
+
assert (
|
290
|
+
"Unable to confirm that parameter 'test_switch' was set after 0 retries"
|
291
|
+
in caplog.text
|
292
|
+
)
|
287
293
|
await switch.set(True)
|
288
294
|
mock_put.assert_not_awaited()
|
289
295
|
|
@@ -36,7 +36,7 @@ from pyplumio.devices.ecomax import (
|
|
36
36
|
from pyplumio.devices.ecoster import EcoSTER
|
37
37
|
from pyplumio.devices.mixer import Mixer
|
38
38
|
from pyplumio.devices.thermostat import Thermostat
|
39
|
-
from pyplumio.exceptions import UnknownDeviceError
|
39
|
+
from pyplumio.exceptions import RequestError, UnknownDeviceError
|
40
40
|
from pyplumio.frames import Response
|
41
41
|
from pyplumio.frames.messages import RegulatorDataMessage, SensorDataMessage
|
42
42
|
from pyplumio.frames.requests import (
|
@@ -144,7 +144,7 @@ async def test_async_setup_error() -> None:
|
|
144
144
|
patch(
|
145
145
|
"pyplumio.devices.ecomax.EcoMAX.request",
|
146
146
|
side_effect=(
|
147
|
-
|
147
|
+
RequestError("test", FrameType.REQUEST_ALERTS),
|
148
148
|
True,
|
149
149
|
True,
|
150
150
|
True,
|
@@ -597,7 +597,7 @@ async def test_request_error(ecomax: EcoMAX) -> None:
|
|
597
597
|
"pyplumio.devices.ecomax.EcoMAX.get",
|
598
598
|
side_effect=(asyncio.TimeoutError, asyncio.TimeoutError),
|
599
599
|
),
|
600
|
-
pytest.raises(
|
600
|
+
pytest.raises(RequestError),
|
601
601
|
):
|
602
602
|
await ecomax.request("foo", FrameType.REQUEST_ALERTS, retries=1)
|
603
603
|
|
@@ -656,7 +656,7 @@ async def test_set(ecomax: EcoMAX) -> None:
|
|
656
656
|
async def test_turn_on(ecomax: EcoMAX, caplog) -> None:
|
657
657
|
"""Test turning the controller on."""
|
658
658
|
assert not await ecomax.turn_on()
|
659
|
-
assert "ecoMAX control
|
659
|
+
assert "ecoMAX control is not available. Please try again later." in caplog.text
|
660
660
|
ecomax.data[ATTR_ECOMAX_CONTROL] = AsyncMock()
|
661
661
|
assert await ecomax.turn_on()
|
662
662
|
ecomax.data[ATTR_ECOMAX_CONTROL].turn_on.assert_awaited_once()
|
@@ -665,7 +665,7 @@ async def test_turn_on(ecomax: EcoMAX, caplog) -> None:
|
|
665
665
|
async def test_turn_off(ecomax: EcoMAX, caplog) -> None:
|
666
666
|
"""Test turning the controller off."""
|
667
667
|
await ecomax.turn_off()
|
668
|
-
assert "ecoMAX control
|
668
|
+
assert "ecoMAX control is not available. Please try again later." in caplog.text
|
669
669
|
ecomax.data[ATTR_ECOMAX_CONTROL] = AsyncMock()
|
670
670
|
await ecomax.turn_off()
|
671
671
|
ecomax.data[ATTR_ECOMAX_CONTROL].turn_off.assert_awaited_once()
|
@@ -53,7 +53,7 @@ async def test_frame_writer_with_close_error(
|
|
53
53
|
writer = FrameWriter(mock_stream_writer)
|
54
54
|
getattr(mock_stream_writer, method).side_effect = exception
|
55
55
|
await writer.close()
|
56
|
-
assert "
|
56
|
+
assert "Failed to close the frame writer due to an unexpected error" in caplog.text
|
57
57
|
|
58
58
|
|
59
59
|
@patch(
|
@@ -104,7 +104,7 @@ async def test_frame_reader_with_short_header(
|
|
104
104
|
with pytest.raises(ReadError) as exc_info:
|
105
105
|
await frame_reader.read()
|
106
106
|
|
107
|
-
assert "
|
107
|
+
assert "Incomplete header, expected 7 bytes" in str(exc_info.value)
|
108
108
|
|
109
109
|
|
110
110
|
@patch(
|
@@ -137,7 +137,7 @@ async def test_frame_reader_with_incomplete_read(
|
|
137
137
|
with pytest.raises(ReadError) as exc_info:
|
138
138
|
await frame_reader.read()
|
139
139
|
|
140
|
-
assert "
|
140
|
+
assert "Incomplete frame, expected 10 bytes" in str(exc_info.value)
|
141
141
|
|
142
142
|
|
143
143
|
@patch(
|
@@ -152,7 +152,10 @@ async def test_frame_reader_with_incorrect_bcc(
|
|
152
152
|
with pytest.raises(ChecksumError) as exc_info:
|
153
153
|
await frame_reader.read()
|
154
154
|
|
155
|
-
assert
|
155
|
+
assert (
|
156
|
+
"Incorrect frame checksum: calculated 200, expected 201. "
|
157
|
+
"Frame data: 680c000056300531fe00c916"
|
158
|
+
) in str(exc_info.value)
|
156
159
|
|
157
160
|
|
158
161
|
@patch(
|
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
|
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
|