From 6a58cc4c36c5cf18bba71de6091d559df21419ee Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Thu, 19 Jun 2025 09:08:54 +0200 Subject: [PATCH 01/21] fix Ruff D102 for comment #171 --- plugwise_usb/connection/manager.py | 2 ++ plugwise_usb/connection/receiver.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/plugwise_usb/connection/manager.py b/plugwise_usb/connection/manager.py index 74f1203e3..dbba049ef 100644 --- a/plugwise_usb/connection/manager.py +++ b/plugwise_usb/connection/manager.py @@ -38,9 +38,11 @@ def __init__(self) -> None: @property def queue_depth(self) -> int: + """Return estimated size of pending responses.""" return self._sender.processed_messages - self._receiver.processed_messages def correct_received_messages(self, correction: int) -> None: + """Correct received messages count.""" self._receiver.correct_processed_messages(correction) @property diff --git a/plugwise_usb/connection/receiver.py b/plugwise_usb/connection/receiver.py index d2c33d882..a728ab9dc 100644 --- a/plugwise_usb/connection/receiver.py +++ b/plugwise_usb/connection/receiver.py @@ -149,7 +149,7 @@ def is_connected(self) -> bool: return self._connection_state def correct_processed_messages(self, correction: int) -> None: - """Return the number of processed messages.""" + """Correct the number of processed messages.""" self._processed_msgs += correction def connection_made(self, transport: SerialTransport) -> None: From 7e850135338f52aade662b29229932209d780e6f Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Thu, 19 Jun 2025 09:21:11 +0200 Subject: [PATCH 02/21] fix Ruff D107 for comment #171 --- plugwise_usb/nodes/helpers/subscription.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugwise_usb/nodes/helpers/subscription.py b/plugwise_usb/nodes/helpers/subscription.py index a3b2c0554..d82560a66 100644 --- a/plugwise_usb/nodes/helpers/subscription.py +++ b/plugwise_usb/nodes/helpers/subscription.py @@ -22,6 +22,7 @@ class NodeFeatureSubscription: class FeaturePublisher: """Base Class to call awaitable of subscription when event happens.""" def __init__(self) -> None: + """Initialize FeaturePublisher Class.""" self._feature_update_subscribers: dict[ Callable[[], None], NodeFeatureSubscription, From f14a0a9b2b19cdf1e1628f0aefaa60d9cfffc93c Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Thu, 19 Jun 2025 09:38:16 +0200 Subject: [PATCH 03/21] fix Ruff F401 for comment #171: Remove unused import --- plugwise_usb/network/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugwise_usb/network/__init__.py b/plugwise_usb/network/__init__.py index 877bbaf1d..1533fb786 100644 --- a/plugwise_usb/network/__init__.py +++ b/plugwise_usb/network/__init__.py @@ -18,7 +18,6 @@ from ..messages.requests import ( CircleClockSetRequest, CircleMeasureIntervalRequest, - CirclePlusAllowJoiningRequest, NodePingRequest, ) from ..messages.responses import ( From 0d55d91541fd9d337a8dae702b000976c38f5881 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Thu, 19 Jun 2025 11:33:54 +0200 Subject: [PATCH 04/21] Force accept UP031 using noqa fix Ruff UP018: Remove unnecessary int call (rewrite as a literal) fix Ruff W293: blank line contains whitespace fix Ruff W291: Trailing whitespace fix Ruff I001: Import block is un-sorted or un-formatted fix Ruff D204: blank line required after class docstring fix Ruff D413: Missing blank line after last section ("Returns") --- plugwise_usb/connection/queue.py | 2 +- plugwise_usb/connection/sender.py | 2 +- plugwise_usb/helpers/cache.py | 2 +- plugwise_usb/messages/properties.py | 10 +++++----- plugwise_usb/messages/requests.py | 2 +- plugwise_usb/nodes/circle_plus.py | 6 ++++-- plugwise_usb/nodes/helpers/pulses.py | 2 +- plugwise_usb/nodes/helpers/subscription.py | 2 +- plugwise_usb/nodes/node.py | 2 +- plugwise_usb/nodes/scan.py | 2 +- plugwise_usb/nodes/sed.py | 2 +- plugwise_usb/nodes/sense.py | 2 +- 12 files changed, 19 insertions(+), 17 deletions(-) diff --git a/plugwise_usb/connection/queue.py b/plugwise_usb/connection/queue.py index d5d714fd2..39ec2858e 100644 --- a/plugwise_usb/connection/queue.py +++ b/plugwise_usb/connection/queue.py @@ -76,7 +76,7 @@ async def stop(self) -> None: async def submit(self, request: PlugwiseRequest) -> PlugwiseResponse | None: """Add request to queue and return the received node-response when applicable. - + Raises an error when something fails. """ if request.waiting_for_response: diff --git a/plugwise_usb/connection/sender.py b/plugwise_usb/connection/sender.py index a2dbe8f17..a21d3ebf8 100644 --- a/plugwise_usb/connection/sender.py +++ b/plugwise_usb/connection/sender.py @@ -48,7 +48,7 @@ def __init__(self, stick_receiver: StickReceiver, transport: Transport) -> None: def processed_messages(self) -> int: """Return the number of processed messages.""" return self._processed_msgs - + async def start(self) -> None: """Start the sender.""" # Subscribe to ACCEPT stick responses, which contain the seq_id we need. diff --git a/plugwise_usb/helpers/cache.py b/plugwise_usb/helpers/cache.py index 256a59094..dc089b361 100644 --- a/plugwise_usb/helpers/cache.py +++ b/plugwise_usb/helpers/cache.py @@ -59,7 +59,7 @@ async def initialize_cache(self, create_root_folder: bool = False) -> None: cache_dir = self._get_writable_os_dir() await makedirs(cache_dir, exist_ok=True) self._cache_path = cache_dir - + self._cache_file = os_path_join(self._cache_path, self._file_name) self._cache_file_exists = await ospath.exists(self._cache_file) self._initialized = True diff --git a/plugwise_usb/messages/properties.py b/plugwise_usb/messages/properties.py index 9cc51e861..9d465a939 100644 --- a/plugwise_usb/messages/properties.py +++ b/plugwise_usb/messages/properties.py @@ -108,7 +108,7 @@ def __init__(self, value: int, length: int = 2, negative: bool = True) -> None: def serialize(self) -> bytes: """Return current string formatted value into an iterable list of bytes.""" - fmt = "%%0%dX" % self.length + fmt = "%%0%dX" % self.length # noqa: UP031 return bytes(fmt % self._raw_value, UTF8) def deserialize(self, val: bytes) -> None: @@ -144,7 +144,7 @@ def negative(val: int, octals: int) -> int: def serialize(self) -> bytes: """Return current string formatted integer value into an iterable list of bytes.""" - fmt = "%%0%dX" % self.length + fmt = "%%0%dX" % self.length # noqa: UP031 return bytes(fmt % int_to_uint(self._raw_value, self.length), UTF8) def deserialize(self, val: bytes) -> None: @@ -172,7 +172,7 @@ def serialize(self) -> bytes: """Return current string formatted value into an iterable list of bytes.""" if not isinstance(self._raw_value, datetime): raise MessageError("Unable to serialize. Value is not a datetime object") - fmt = "%%0%dX" % self.length + fmt = "%%0%dX" % self.length # noqa: UP031 date_in_float = self._raw_value.timestamp() return bytes(fmt % int(date_in_float), UTF8) @@ -287,7 +287,7 @@ def __init__(self, value: int, length: int = 2) -> None: def serialize(self) -> bytes: """Return current string formatted integer value into an iterable list of bytes.""" - fmt = "%%0%dd" % self.length + fmt = "%%0%dd" % self.length # noqa: UP031 return bytes(fmt % self._raw_value, UTF8) def deserialize(self, val: bytes) -> None: @@ -391,7 +391,7 @@ def serialize(self) -> bytes: def deserialize(self, val: bytes) -> None: """Convert data into integer value based on log address formatted data.""" if val == b"00000000": - self._value = int(0) + self._value = 0 return Int.deserialize(self, val) self._value = (self.value - LOGADDR_OFFSET) // 32 diff --git a/plugwise_usb/messages/requests.py b/plugwise_usb/messages/requests.py index d01bd6b08..89672d57b 100644 --- a/plugwise_usb/messages/requests.py +++ b/plugwise_usb/messages/requests.py @@ -1263,7 +1263,7 @@ class CircleMeasureIntervalRequest(PlugwiseRequest): FIXME: Make sure production interval is a multiply of consumption !! - Response message: NodeResponse with ack-type POWER_LOG_INTERVAL_ACCEPTED + Response message: NodeResponse with ack-type POWER_LOG_INTERVAL_ACCEPTED """ _identifier = b"0057" diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index 4a2a5ac24..a9a4b50f6 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -8,14 +8,15 @@ from ..api import NodeEvent, NodeFeature from ..constants import MAX_TIME_DRIFT from ..messages.requests import ( + CirclePlusAllowJoiningRequest, CirclePlusRealTimeClockGetRequest, CirclePlusRealTimeClockSetRequest, - CirclePlusAllowJoiningRequest, ) from ..messages.responses import NodeResponseType from .circle import PlugwiseCircle -from .helpers.firmware import CIRCLE_PLUS_FIRMWARE_SUPPORT from .helpers import raise_not_loaded +from .helpers.firmware import CIRCLE_PLUS_FIRMWARE_SUPPORT + _LOGGER = logging.getLogger(__name__) @@ -131,6 +132,7 @@ async def enable_auto_join(self) -> bool: Returns: bool: True if the request was acknowledged, False otherwise. + """ _LOGGER.info("Enabling auto-join for CirclePlus") request = CirclePlusAllowJoiningRequest(self._send, True) diff --git a/plugwise_usb/nodes/helpers/pulses.py b/plugwise_usb/nodes/helpers/pulses.py index 1bd8e81dc..269ecc49e 100644 --- a/plugwise_usb/nodes/helpers/pulses.py +++ b/plugwise_usb/nodes/helpers/pulses.py @@ -7,7 +7,7 @@ import logging from typing import Final -from ...constants import LOGADDR_MAX, MINUTE_IN_SECONDS, DAY_IN_HOURS +from ...constants import DAY_IN_HOURS, LOGADDR_MAX, MINUTE_IN_SECONDS from ...exceptions import EnergyError _LOGGER = logging.getLogger(__name__) diff --git a/plugwise_usb/nodes/helpers/subscription.py b/plugwise_usb/nodes/helpers/subscription.py index d82560a66..b6957114a 100644 --- a/plugwise_usb/nodes/helpers/subscription.py +++ b/plugwise_usb/nodes/helpers/subscription.py @@ -7,7 +7,6 @@ from dataclasses import dataclass from typing import Any - from ...api import NodeFeature @@ -21,6 +20,7 @@ class NodeFeatureSubscription: class FeaturePublisher: """Base Class to call awaitable of subscription when event happens.""" + def __init__(self) -> None: """Initialize FeaturePublisher Class.""" self._feature_update_subscribers: dict[ diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 13b6433ea..899b9d5d4 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -478,7 +478,7 @@ async def _node_info_load_from_cache(self) -> bool: node_type: NodeType | None = None if (node_type_str := self._get_cache(CACHE_NODE_TYPE)) is not None: node_type = NodeType(int(node_type_str)) - + return await self.update_node_details( firmware=firmware, hardware=hardware, diff --git a/plugwise_usb/nodes/scan.py b/plugwise_usb/nodes/scan.py index ce90c69fb..36ad9c17b 100644 --- a/plugwise_usb/nodes/scan.py +++ b/plugwise_usb/nodes/scan.py @@ -49,7 +49,7 @@ # Sensitivity values for motion sensor configuration SENSITIVITY_HIGH_VALUE = 20 # 0x14 -SENSITIVITY_MEDIUM_VALUE = 30 # 0x1E +SENSITIVITY_MEDIUM_VALUE = 30 # 0x1E SENSITIVITY_OFF_VALUE = 255 # 0xFF # endregion diff --git a/plugwise_usb/nodes/sed.py b/plugwise_usb/nodes/sed.py index a583142bb..11cf5e4d7 100644 --- a/plugwise_usb/nodes/sed.py +++ b/plugwise_usb/nodes/sed.py @@ -239,7 +239,7 @@ async def set_awake_duration(self, seconds: int) -> bool: if self._battery_config.awake_duration == seconds: return False - + self._new_battery_config = replace( self._new_battery_config, awake_duration=seconds ) diff --git a/plugwise_usb/nodes/sense.py b/plugwise_usb/nodes/sense.py index 67bc78ddf..d8353203c 100644 --- a/plugwise_usb/nodes/sense.py +++ b/plugwise_usb/nodes/sense.py @@ -53,7 +53,7 @@ async def load(self) -> bool: _LOGGER.debug("Loading Sense node %s", self._node_info.mac) if not await super().load(): - _LOGGER.debug("Load Sense base node failed") + _LOGGER.debug("Load Sense base node failed") return False self._setup_protocol(SENSE_FIRMWARE_SUPPORT, SENSE_FEATURES) From da6d244aaa6230ed1ae70a43bf187936f8cc1011 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Thu, 19 Jun 2025 11:54:18 +0200 Subject: [PATCH 05/21] fix ruff D202 [*] No blank lines allowed after function docstring --- plugwise_usb/messages/responses.py | 1 - plugwise_usb/nodes/__init__.py | 1 - plugwise_usb/nodes/circle.py | 2 -- plugwise_usb/nodes/helpers/pulses.py | 2 -- plugwise_usb/nodes/sed.py | 1 - 5 files changed, 7 deletions(-) diff --git a/plugwise_usb/messages/responses.py b/plugwise_usb/messages/responses.py index fa00bf48a..b222a0b50 100644 --- a/plugwise_usb/messages/responses.py +++ b/plugwise_usb/messages/responses.py @@ -954,7 +954,6 @@ def get_message_object( # noqa: C901 identifier: bytes, length: int, seq_id: bytes ) -> PlugwiseResponse | None: """Return message class based on sequence ID, Length of message or message ID.""" - # First check for known sequence ID's if seq_id == REJOIN_RESPONSE_SEQ_ID: return NodeRejoinResponse() diff --git a/plugwise_usb/nodes/__init__.py b/plugwise_usb/nodes/__init__.py index 0258e1b81..b2110dd70 100644 --- a/plugwise_usb/nodes/__init__.py +++ b/plugwise_usb/nodes/__init__.py @@ -22,7 +22,6 @@ def get_plugwise_node( node_type: NodeType, ) -> PlugwiseNode | None: """Return an initialized plugwise node class based on given the node type.""" - if node_type == NodeType.CIRCLE_PLUS: return PlugwiseCirclePlus( mac, diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 04c09fa4f..9bc831f82 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -433,7 +433,6 @@ async def energy_update(self) -> EnergyStatistics | None: async def get_missing_energy_logs(self) -> None: """Task to retrieve missing energy logs.""" - self._energy_counters.update() if self._current_log_address is None: return None @@ -1147,7 +1146,6 @@ def _calc_watts(self, pulses: int, seconds: int, nano_offset: int) -> float | No def _correct_power_pulses(self, pulses: int, offset: int) -> float: """Correct pulses based on given measurement time offset (ns).""" - # Sometimes the circle returns -1 for some of the pulse counters # likely this means the circle measures very little power and is # suffering from rounding errors. Zero these out. However, negative diff --git a/plugwise_usb/nodes/helpers/pulses.py b/plugwise_usb/nodes/helpers/pulses.py index 269ecc49e..ddd94eda4 100644 --- a/plugwise_usb/nodes/helpers/pulses.py +++ b/plugwise_usb/nodes/helpers/pulses.py @@ -19,7 +19,6 @@ def calc_log_address(address: int, slot: int, offset: int) -> tuple[int, int]: """Calculate address and slot for log based for specified offset.""" - if offset < 0: while offset + slot < 1: address -= 1 @@ -344,7 +343,6 @@ def _detect_rollover( is_consumption=True, ) -> bool: """Detect rollover based on timestamp comparisons.""" - if ( self._pulses_timestamp is not None and last_log_timestamp is not None diff --git a/plugwise_usb/nodes/sed.py b/plugwise_usb/nodes/sed.py index 11cf5e4d7..16329a450 100644 --- a/plugwise_usb/nodes/sed.py +++ b/plugwise_usb/nodes/sed.py @@ -488,7 +488,6 @@ async def node_info_update( self, node_info: NodeInfoResponse | None = None ) -> NodeInfo | None: """Update Node (hardware) information.""" - if node_info is None and self.skip_update(self._node_info, 86400): return self._node_info return await super().node_info_update(node_info) From 206efeef731dfa1ce31b139dc4f4d86df75d8749 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Thu, 19 Jun 2025 13:19:14 +0200 Subject: [PATCH 06/21] ruff PLR0911 Too many return statements. Won't fix as it seems the number of returns are valid. ruff PLR0912 Too many branches ({branches} > {max_branches}). Won't fix code seems correct. ruff PLR0913 Too many arguments to function call ({c_args} > {max_args}). Won't fix code seems correct. --- plugwise_usb/messages/requests.py | 2 +- plugwise_usb/messages/responses.py | 2 +- plugwise_usb/network/registry.py | 2 +- plugwise_usb/nodes/__init__.py | 2 +- plugwise_usb/nodes/circle.py | 4 ++-- plugwise_usb/nodes/helpers/pulses.py | 4 ++-- plugwise_usb/nodes/node.py | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plugwise_usb/messages/requests.py b/plugwise_usb/messages/requests.py index 89672d57b..a3c036a66 100644 --- a/plugwise_usb/messages/requests.py +++ b/plugwise_usb/messages/requests.py @@ -1201,7 +1201,7 @@ class NodeSleepConfigRequest(PlugwiseRequest): _identifier = b"0050" # pylint: disable=too-many-arguments - def __init__( + def __init__( # noqa: PLR0913 self, send_fn: Callable[[PlugwiseRequest, bool], Awaitable[PlugwiseResponse | None]], mac: bytes, diff --git a/plugwise_usb/messages/responses.py b/plugwise_usb/messages/responses.py index b222a0b50..a3265cd6d 100644 --- a/plugwise_usb/messages/responses.py +++ b/plugwise_usb/messages/responses.py @@ -950,7 +950,7 @@ def __init__(self) -> None: self._params += [self.is_get, self.relay] -def get_message_object( # noqa: C901 +def get_message_object( # noqa: C901 PLR0911 PLR0912 identifier: bytes, length: int, seq_id: bytes ) -> PlugwiseResponse | None: """Return message class based on sequence ID, Length of message or message ID.""" diff --git a/plugwise_usb/network/registry.py b/plugwise_usb/network/registry.py index 144c92ab3..4e830a007 100644 --- a/plugwise_usb/network/registry.py +++ b/plugwise_usb/network/registry.py @@ -179,7 +179,7 @@ def update_network_registration( if self._network_cache is not None: self._network_cache.update_registration(address, mac, node_type) - async def update_missing_registrations(self, quick: bool = False) -> None: + async def update_missing_registrations(self, quick: bool = False) -> None: # noqa: PLR0912 """Retrieve all unknown network registrations from network controller.""" for address in range(0, 64): if self._registry.get(address) is not None and not quick: diff --git a/plugwise_usb/nodes/__init__.py b/plugwise_usb/nodes/__init__.py index b2110dd70..6022017f2 100644 --- a/plugwise_usb/nodes/__init__.py +++ b/plugwise_usb/nodes/__init__.py @@ -14,7 +14,7 @@ from .switch import PlugwiseSwitch -def get_plugwise_node( +def get_plugwise_node( # noqa: PLR0911 mac: str, address: int, controller: StickController, diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 9bc831f82..32b2980bf 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -329,7 +329,7 @@ def _log_no_energy_stats_update(self) -> None: @raise_not_loaded @raise_calibration_missing - async def energy_update(self) -> EnergyStatistics | None: + async def energy_update(self) -> EnergyStatistics | None: # noqa: PLR0911 PLR0912 """Return updated energy usage statistics.""" if self._current_log_address is None: _LOGGER.debug( @@ -999,7 +999,7 @@ async def _node_info_load_from_cache(self) -> bool: return False # pylint: disable=too-many-arguments - async def update_node_details( + async def update_node_details( # noqa: PLR0913 self, firmware: datetime | None, hardware: str | None, diff --git a/plugwise_usb/nodes/helpers/pulses.py b/plugwise_usb/nodes/helpers/pulses.py index ddd94eda4..85fc27602 100644 --- a/plugwise_usb/nodes/helpers/pulses.py +++ b/plugwise_usb/nodes/helpers/pulses.py @@ -682,7 +682,7 @@ def _update_last_consumption_log_reference( self._last_log_consumption_address = address self._last_log_consumption_slot = slot - def _reset_log_references(self) -> None: + def _reset_log_references(self) -> None: # noqa: PLR0912 """Reset log references.""" self._last_log_consumption_address = None self._last_log_consumption_slot = None @@ -824,7 +824,7 @@ def _first_log_reference( ) return (self._first_log_production_address, self._first_log_production_slot) - def _logs_missing(self, from_timestamp: datetime) -> list[int] | None: + def _logs_missing(self, from_timestamp: datetime) -> list[int] | None: # noqa: PLR0911 PLR0912 """Calculate list of missing log addresses.""" if self._logs is None: self._log_addresses_missing = None diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 899b9d5d4..b54dbcb88 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -489,7 +489,7 @@ async def _node_info_load_from_cache(self) -> bool: ) # pylint: disable=too-many-arguments - async def update_node_details( + async def update_node_details( # noqa: PLR0912 PLR0913 self, firmware: datetime | None, hardware: str | None, From b295a7f4756379f5429b0c58ddde1cda4c81d165 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Fri, 20 Jun 2025 11:23:57 +0200 Subject: [PATCH 07/21] ruff: PLW2901 `for` loop variable `address` overwritten by assignment target --- plugwise_usb/network/registry.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugwise_usb/network/registry.py b/plugwise_usb/network/registry.py index 4e830a007..4f918782b 100644 --- a/plugwise_usb/network/registry.py +++ b/plugwise_usb/network/registry.py @@ -189,17 +189,17 @@ async def update_missing_registrations(self, quick: bool = False) -> None: # no continue registration = await self.retrieve_network_registration(address, False) if registration is not None: - address, mac = registration + nextaddress, mac = registration if mac == "": - self._first_free_address = min(self._first_free_address, address) + self._first_free_address = min(self._first_free_address, nextaddress) if quick: break _LOGGER.debug( "Network registration at address %s is %s", - str(address), + str(nextaddress), "'empty'" if mac == "" else f"set to {mac}", ) - self.update_network_registration(address, mac, None) + self.update_network_registration(nextaddress, mac, None) await sleep(0.1) if not quick: await sleep(10) From d88ff2d0d6389fa0960b77d8a1dbbc8364de8846 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Fri, 20 Jun 2025 11:25:18 +0200 Subject: [PATCH 08/21] ruff: PLR2004 static constants (first dash, leaving out work on energy log constants considering ongoing effort --- plugwise_usb/constants.py | 4 ++++ plugwise_usb/nodes/helpers/pulses.py | 2 +- plugwise_usb/nodes/node.py | 4 ++-- plugwise_usb/nodes/scan.py | 3 ++- plugwise_usb/nodes/sed.py | 11 +++++++---- plugwise_usb/nodes/sense.py | 6 ++++-- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/plugwise_usb/constants.py b/plugwise_usb/constants.py index 42539e97f..6bab86789 100644 --- a/plugwise_usb/constants.py +++ b/plugwise_usb/constants.py @@ -15,6 +15,10 @@ LOCAL_TIMEZONE = dt.datetime.now(dt.UTC).astimezone().tzinfo UTF8: Final = "utf-8" +# Value limits +MAX_UINT_2: Final = 255 +MAX_UINT_4: Final = 65535 + # Time DAY_IN_HOURS: Final = 24 WEEK_IN_HOURS: Final = 168 diff --git a/plugwise_usb/nodes/helpers/pulses.py b/plugwise_usb/nodes/helpers/pulses.py index 85fc27602..cda07d6e6 100644 --- a/plugwise_usb/nodes/helpers/pulses.py +++ b/plugwise_usb/nodes/helpers/pulses.py @@ -950,7 +950,7 @@ def _last_known_duration(self) -> timedelta: if self._logs is None: raise EnergyError("Unable to return last known duration without any logs") - if len(self._logs) < 2: + if len(self._logs) < 2: # noqa: PLR2004 return timedelta(hours=1) address, slot = self._last_log_reference() diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index b54dbcb88..6401d4331 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -425,7 +425,7 @@ async def _available_update_state( if ( self._last_seen is not None and timestamp is not None - and int((timestamp - self._last_seen).total_seconds()) > 5 + and int((timestamp - self._last_seen).total_seconds()) > 5 # noqa: PLR2004 ): self._last_seen = timestamp @@ -659,7 +659,7 @@ def _get_cache_as_datetime(self, setting: str) -> datetime | None: """Retrieve value of specified setting from cache memory and return it as datetime object.""" if (timestamp_str := self._get_cache(setting)) is not None: data = timestamp_str.split("-") - if len(data) == 6: + if len(data) == 6: # noqa: PLR2004 try: return datetime( year=int(data[0]), diff --git a/plugwise_usb/nodes/scan.py b/plugwise_usb/nodes/scan.py index 36ad9c17b..e22067e19 100644 --- a/plugwise_usb/nodes/scan.py +++ b/plugwise_usb/nodes/scan.py @@ -11,6 +11,7 @@ from ..api import MotionConfig, MotionSensitivity, MotionState, NodeEvent, NodeFeature from ..connection import StickController +from ..constants import MAX_UINT_2 from ..exceptions import MessageError, NodeError, NodeTimeout from ..messages.requests import ScanConfigureRequest, ScanLightCalibrateRequest from ..messages.responses import ( @@ -306,7 +307,7 @@ async def set_motion_reset_timer(self, minutes: int) -> bool: self._motion_config.reset_timer, minutes, ) - if minutes < 1 or minutes > 255: + if minutes < 1 or minutes > MAX_UINT_2: raise ValueError( f"Invalid motion reset timer ({minutes}). It must be between 1 and 255 minutes." ) diff --git a/plugwise_usb/nodes/sed.py b/plugwise_usb/nodes/sed.py index 16329a450..1ac29dc8a 100644 --- a/plugwise_usb/nodes/sed.py +++ b/plugwise_usb/nodes/sed.py @@ -19,6 +19,7 @@ from ..api import BatteryConfig, NodeEvent, NodeFeature, NodeInfo from ..connection import StickController +from ..constants import MAX_UINT_2, MAX_UINT_4 from ..exceptions import MessageError, NodeError from ..messages.requests import NodeSleepConfigRequest from ..messages.responses import ( @@ -63,6 +64,8 @@ # Time in minutes the SED will sleep SED_DEFAULT_SLEEP_DURATION: Final = 60 +# Value limits +MAX_MINUTE_INTERVAL: Final = 1440 _LOGGER = logging.getLogger(__name__) @@ -232,7 +235,7 @@ async def set_awake_duration(self, seconds: int) -> bool: self._battery_config.awake_duration, seconds, ) - if seconds < 1 or seconds > 255: + if seconds < 1 or seconds > MAX_UINT_2: raise ValueError( f"Invalid awake duration ({seconds}). It must be between 1 and 255 seconds." ) @@ -262,7 +265,7 @@ async def set_clock_interval(self, minutes: int) -> bool: self._battery_config.clock_interval, minutes, ) - if minutes < 1 or minutes > 65535: + if minutes < 1 or minutes > MAX_UINT_4: raise ValueError( f"Invalid clock interval ({minutes}). It must be between 1 and 65535 minutes." ) @@ -315,7 +318,7 @@ async def set_maintenance_interval(self, minutes: int) -> bool: self._battery_config.maintenance_interval, minutes, ) - if minutes < 1 or minutes > 1440: + if minutes < 1 or minutes > MAX_MINUTE_INTERVAL: raise ValueError( f"Invalid maintenance interval ({minutes}). It must be between 1 and 1440 minutes." ) @@ -348,7 +351,7 @@ async def set_sleep_duration(self, minutes: int) -> bool: self._battery_config.sleep_duration, minutes, ) - if minutes < 1 or minutes > 65535: + if minutes < 1 or minutes > MAX_UINT_4: raise ValueError( f"Invalid sleep duration ({minutes}). It must be between 1 and 65535 minutes." ) diff --git a/plugwise_usb/nodes/sense.py b/plugwise_usb/nodes/sense.py index d8353203c..daf09f117 100644 --- a/plugwise_usb/nodes/sense.py +++ b/plugwise_usb/nodes/sense.py @@ -20,8 +20,10 @@ # Sense calculations SENSE_HUMIDITY_MULTIPLIER: Final = 125 SENSE_HUMIDITY_OFFSET: Final = 6 +SENSE_HUMIDITY_LIMIT: Final = 65535 SENSE_TEMPERATURE_MULTIPLIER: Final = 175.72 SENSE_TEMPERATURE_OFFSET: Final = 46.85 +SENSE_TEMPERATURE_LIMIT: Final = 65535 SENSE_FEATURES: Final = ( NodeFeature.INFO, @@ -111,14 +113,14 @@ async def _sense_report(self, response: PlugwiseResponse) -> bool: ) report_received = False await self._available_update_state(True, response.timestamp) - if response.temperature.value != 65535: + if response.temperature.value != SENSE_TEMPERATURE_LIMIT: self._sense_statistics.temperature = float( SENSE_TEMPERATURE_MULTIPLIER * (response.temperature.value / 65536) - SENSE_TEMPERATURE_OFFSET ) report_received = True - if response.humidity.value != 65535: + if response.humidity.value != SENSE_HUMIDITY_LIMIT: self._sense_statistics.humidity = float( SENSE_HUMIDITY_MULTIPLIER * (response.humidity.value / 65536) - SENSE_HUMIDITY_OFFSET From 77f8170004829d6dae3bcdc5c6c568bd9979ff4a Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Fri, 20 Jun 2025 11:26:51 +0200 Subject: [PATCH 09/21] remove working ruff tests from pyproject.toml --- pyproject.toml | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 77cf7b206..0a9ec6544 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -489,42 +489,13 @@ lint.select = [ lint.ignore = [ # Plugwise_usb todolist - "D100", # Missing docstring in public FIXFOR171 - "D102", # "undocumented-public-method", # D102 FIXFOR171 - "D107", # "undocumented-public-init", # D107 FIXFOR171 - "D210", # whitespace in docstring FIXFOR171 - "D212", # "multi-line-summary-first-line", # D212 FIXFOR171 - "F401", # "unused-import", # F401 FIXFOR171 - "UP031", # "printf-string-formatting", # UP031 FIXFOR171 "UP047", # "non-pep695-generic-function", # UP047 FIXFOR171 - "E711", # "none-comparison", # E711 FIXFOR171 - "W293", # blank line contains whitespaace FIXFOR171 - "UP018", # FIXFOR171 unnecessary int call - "W291", # FIXFOR171 trailing whitespace - "I001", # FIXFOR171 import block is unsorted or unformatted - "D204", # FIXFOR171 blank line required after class docstring - "D413", # FIXFOR171 missing blank line after last section # Generic - "D202", # No blank lines allowed after function docstring "D203", # 1 blank line required before class docstring "D213", # Multi-line docstring summary should start at the second line - "D406", # Section name should end with a newline - "D407", # Section name underlining "E501", # line too long - "E731", # do not assign a lambda expression, use a def - # False positives https://github.com/astral-sh/ruff/issues/5386 - "PLC0208", # Use a sequence type instead of a `set` when iterating over values - "PLR0911", # Too many return statements ({returns} > {max_returns}) - "PLR0912", # Too many branches ({branches} > {max_branches}) - "PLR0913", # Too many arguments to function call ({c_args} > {max_args}) - "PLR0915", # Too many statements ({statements} > {max_statements}) "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable - "PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target - "UP006", # keep type annotation style as is - "UP007", # keep type annotation style as is - # Ignored due to performance: https://github.com/charliermarsh/ruff/issues/2923 - #"UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)` ] exclude = [] From 198bacfc9eba655cf54dc97406cbbdfd916f57c3 Mon Sep 17 00:00:00 2001 From: autoruff Date: Fri, 20 Jun 2025 09:29:04 +0000 Subject: [PATCH 10/21] fixup: cpaj Python code reformatted using Ruff --- plugwise_usb/__init__.py | 55 ++++++++---------- plugwise_usb/api.py | 3 +- plugwise_usb/connection/__init__.py | 8 +-- plugwise_usb/connection/queue.py | 7 ++- plugwise_usb/connection/receiver.py | 1 + plugwise_usb/connection/sender.py | 14 ++++- plugwise_usb/constants.py | 1 + plugwise_usb/helpers/cache.py | 29 ++++++---- plugwise_usb/helpers/util.py | 3 +- plugwise_usb/messages/__init__.py | 1 + plugwise_usb/messages/properties.py | 5 +- plugwise_usb/messages/requests.py | 4 +- plugwise_usb/messages/responses.py | 11 ++-- plugwise_usb/network/__init__.py | 13 +++-- plugwise_usb/network/cache.py | 4 +- plugwise_usb/network/registry.py | 4 +- plugwise_usb/nodes/celsius.py | 1 + plugwise_usb/nodes/circle.py | 8 +-- plugwise_usb/nodes/helpers/__init__.py | 2 + plugwise_usb/nodes/helpers/counter.py | 17 ++++-- plugwise_usb/nodes/helpers/pulses.py | 9 ++- plugwise_usb/nodes/helpers/subscription.py | 5 +- plugwise_usb/nodes/node.py | 14 ++--- plugwise_usb/nodes/scan.py | 16 ++++-- plugwise_usb/nodes/sed.py | 2 +- plugwise_usb/nodes/sense.py | 4 +- plugwise_usb/nodes/stealth.py | 1 + plugwise_usb/nodes/switch.py | 11 +++- tests/stick_test_data.py | 4 +- tests/test_usb.py | 67 +++++++++++----------- 30 files changed, 184 insertions(+), 140 deletions(-) diff --git a/plugwise_usb/__init__.py b/plugwise_usb/__init__.py index 8870a24fd..2061ca32c 100644 --- a/plugwise_usb/__init__.py +++ b/plugwise_usb/__init__.py @@ -20,41 +20,43 @@ FuncT = TypeVar("FuncT", bound=Callable[..., Any]) -NOT_INITIALIZED_STICK_ERROR: Final[StickError] = StickError("Cannot load nodes when network is not initialized") +NOT_INITIALIZED_STICK_ERROR: Final[StickError] = StickError( + "Cannot load nodes when network is not initialized" +) _LOGGER = logging.getLogger(__name__) def raise_not_connected(func: FuncT) -> FuncT: """Validate existence of an active connection to Stick. Raise StickError when there is no active connection.""" + @wraps(func) def decorated(*args: Any, **kwargs: Any) -> Any: if not args[0].is_connected: - raise StickError( - "Not connected to USB-Stick, connect to USB-stick first." - ) + raise StickError("Not connected to USB-Stick, connect to USB-stick first.") return func(*args, **kwargs) + return cast(FuncT, decorated) def raise_not_initialized(func: FuncT) -> FuncT: """Validate if active connection is initialized. Raise StickError when not initialized.""" + @wraps(func) def decorated(*args: Any, **kwargs: Any) -> Any: if not args[0].is_initialized: raise StickError( - "Connection to USB-Stick is not initialized, " + - "initialize USB-stick first." + "Connection to USB-Stick is not initialized, " + + "initialize USB-stick first." ) return func(*args, **kwargs) + return cast(FuncT, decorated) class Stick: """Plugwise connection stick.""" - def __init__( - self, port: str | None = None, cache_enabled: bool = True - ) -> None: + def __init__(self, port: str | None = None, cache_enabled: bool = True) -> None: """Initialize Stick.""" self._loop = get_running_loop() self._loop.set_debug(True) @@ -170,13 +172,8 @@ def port(self) -> str | None: @port.setter def port(self, port: str) -> None: """Path to serial port of USB-Stick.""" - if ( - self._controller.is_connected - and port != self._port - ): - raise StickError( - "Unable to change port while connected. Disconnect first" - ) + if self._controller.is_connected and port != self._port: + raise StickError("Unable to change port while connected. Disconnect first") self._port = port @@ -189,10 +186,8 @@ async def energy_reset_request(self, mac: str) -> bool: raise NodeError(f"{exc}") from exc # Follow up by an energy-intervals (re)set - if ( - result := await self.set_energy_intervals( - mac, DEFAULT_CONS_INTERVAL, NO_PRODUCTION_INTERVAL - ) + if result := await self.set_energy_intervals( + mac, DEFAULT_CONS_INTERVAL, NO_PRODUCTION_INTERVAL ): return result @@ -238,7 +233,9 @@ def subscribe_to_node_events( Returns the function to be called to unsubscribe later. """ if self._network is None: - raise SubscriptionError("Unable to subscribe to node events without network connection initialized") + raise SubscriptionError( + "Unable to subscribe to node events without network connection initialized" + ) return self._network.subscribe_to_node_events( node_event_callback, events, @@ -252,9 +249,7 @@ def _validate_node_discovery(self) -> None: if self._network is None or not self._network.is_running: raise StickError("Plugwise network node discovery is not active.") - async def setup( - self, discover: bool = True, load: bool = True - ) -> None: + async def setup(self, discover: bool = True, load: bool = True) -> None: """Fully connect, initialize USB-Stick and discover all connected nodes.""" if not self.is_connected: await self.connect() @@ -271,8 +266,8 @@ async def connect(self, port: str | None = None) -> None: """Connect to USB-Stick. Raises StickError if connection fails.""" if self._controller.is_connected: raise StickError( - f"Already connected to {self._port}, " + - "Close existing connection before (re)connect." + f"Already connected to {self._port}, " + + "Close existing connection before (re)connect." ) if port is not None: @@ -280,8 +275,8 @@ async def connect(self, port: str | None = None) -> None: if self._port is None: raise StickError( - "Unable to connect. " + - "Path to USB-Stick is not defined, set port property first" + "Unable to connect. " + + "Path to USB-Stick is not defined, set port property first" ) await self._controller.connect_to_stick( @@ -319,9 +314,7 @@ async def load_nodes(self) -> bool: if self._network is None: raise NOT_INITIALIZED_STICK_ERROR if not self._network.is_running: - raise StickError( - "Cannot load nodes when network is not started" - ) + raise StickError("Cannot load nodes when network is not started") return await self._network.discover_nodes(load=True) @raise_not_connected diff --git a/plugwise_usb/api.py b/plugwise_usb/api.py index b1777fe56..0ad97c527 100644 --- a/plugwise_usb/api.py +++ b/plugwise_usb/api.py @@ -233,6 +233,7 @@ class EnergyStatistics: day_production: float | None = None day_production_reset: datetime | None = None + @dataclass class SenseStatistics: """Sense statistics collection.""" @@ -240,6 +241,7 @@ class SenseStatistics: temperature: float | None = None humidity: float | None = None + class PlugwiseNode(Protocol): """Protocol definition of a Plugwise device node.""" @@ -704,5 +706,4 @@ async def message_for_node(self, message: Any) -> None: """ - # endregion diff --git a/plugwise_usb/connection/__init__.py b/plugwise_usb/connection/__init__.py index 06879100b..ff17f1400 100644 --- a/plugwise_usb/connection/__init__.py +++ b/plugwise_usb/connection/__init__.py @@ -209,9 +209,7 @@ async def get_node_details( ping_response: NodePingResponse | None = None if ping_first: # Define ping request with one retry - ping_request = NodePingRequest( - self.send, bytes(mac, UTF8), retries=1 - ) + ping_request = NodePingRequest(self.send, bytes(mac, UTF8), retries=1) try: ping_response = await ping_request.send() except StickError: @@ -219,9 +217,7 @@ async def get_node_details( if ping_response is None: return (None, None) - info_request = NodeInfoRequest( - self.send, bytes(mac, UTF8), retries=1 - ) + info_request = NodeInfoRequest(self.send, bytes(mac, UTF8), retries=1) try: info_response = await info_request.send() except StickError: diff --git a/plugwise_usb/connection/queue.py b/plugwise_usb/connection/queue.py index 39ec2858e..614277447 100644 --- a/plugwise_usb/connection/queue.py +++ b/plugwise_usb/connection/queue.py @@ -103,7 +103,8 @@ async def submit(self, request: PlugwiseRequest) -> PlugwiseResponse | None: if isinstance(request, NodePingRequest): # For ping requests it is expected to receive timeouts, so lower log level _LOGGER.debug( - "%s, cancel because timeout is expected for NodePingRequests", exc + "%s, cancel because timeout is expected for NodePingRequests", + exc, ) elif request.resend: _LOGGER.debug("%s, retrying", exc) @@ -147,7 +148,9 @@ async def _send_queue_worker(self) -> None: if self._stick.queue_depth > 3: await sleep(0.125) if self._stick.queue_depth > 3: - _LOGGER.warning("Awaiting plugwise responses %d", self._stick.queue_depth) + _LOGGER.warning( + "Awaiting plugwise responses %d", self._stick.queue_depth + ) await self._stick.write_to_stick(request) self._submit_queue.task_done() diff --git a/plugwise_usb/connection/receiver.py b/plugwise_usb/connection/receiver.py index a728ab9dc..5caf56266 100644 --- a/plugwise_usb/connection/receiver.py +++ b/plugwise_usb/connection/receiver.py @@ -513,4 +513,5 @@ async def _notify_node_response_subscribers( name=f"Postpone subscription task for {node_response.seq_id!r} retry {node_response.retries}", ) + # endregion diff --git a/plugwise_usb/connection/sender.py b/plugwise_usb/connection/sender.py index a21d3ebf8..f12709849 100644 --- a/plugwise_usb/connection/sender.py +++ b/plugwise_usb/connection/sender.py @@ -79,7 +79,9 @@ async def write_request_to_port(self, request: PlugwiseRequest) -> None: # Write message to serial port buffer serialized_data = request.serialize() - _LOGGER.debug("write_request_to_port | Write %s to port as %s", request, serialized_data) + _LOGGER.debug( + "write_request_to_port | Write %s to port as %s", request, serialized_data + ) self._transport.write(serialized_data) # Don't timeout when no response expected if not request.no_response: @@ -106,7 +108,11 @@ async def write_request_to_port(self, request: PlugwiseRequest) -> None: _LOGGER.warning("Exception for %s: %s", request, exc) request.assign_error(exc) else: - _LOGGER.debug("write_request_to_port | USB-Stick replied with %s to request %s", response, request) + _LOGGER.debug( + "write_request_to_port | USB-Stick replied with %s to request %s", + response, + request, + ) if response.response_type == StickResponseType.ACCEPT: if request.seq_id is not None: request.assign_error( @@ -120,7 +126,9 @@ async def write_request_to_port(self, request: PlugwiseRequest) -> None: self._receiver.subscribe_to_stick_responses, self._receiver.subscribe_to_node_responses, ) - _LOGGER.debug("write_request_to_port | request has subscribed : %s", request) + _LOGGER.debug( + "write_request_to_port | request has subscribed : %s", request + ) elif response.response_type == StickResponseType.TIMEOUT: _LOGGER.warning( "USB-Stick directly responded with communication timeout for %s", diff --git a/plugwise_usb/constants.py b/plugwise_usb/constants.py index 6bab86789..e854cd8a7 100644 --- a/plugwise_usb/constants.py +++ b/plugwise_usb/constants.py @@ -1,4 +1,5 @@ """Plugwise Stick constants.""" + from __future__ import annotations import datetime as dt diff --git a/plugwise_usb/helpers/cache.py b/plugwise_usb/helpers/cache.py index dc089b361..30e7a78ef 100644 --- a/plugwise_usb/helpers/cache.py +++ b/plugwise_usb/helpers/cache.py @@ -53,7 +53,9 @@ async def initialize_cache(self, create_root_folder: bool = False) -> None: """Set (and create) the plugwise cache directory to store cache file.""" if self._root_dir != "": if not create_root_folder and not await ospath.exists(self._root_dir): - raise CacheError(f"Unable to initialize caching. Cache folder '{self._root_dir}' does not exists.") + raise CacheError( + f"Unable to initialize caching. Cache folder '{self._root_dir}' does not exists." + ) cache_dir = self._root_dir else: cache_dir = self._get_writable_os_dir() @@ -72,13 +74,17 @@ def _get_writable_os_dir(self) -> str: if os_name == "nt": if (data_dir := os_getenv("APPDATA")) is not None: return os_path_join(data_dir, CACHE_DIR) - raise CacheError("Unable to detect writable cache folder based on 'APPDATA' environment variable.") + raise CacheError( + "Unable to detect writable cache folder based on 'APPDATA' environment variable." + ) return os_path_join(os_path_expand_user("~"), CACHE_DIR) async def write_cache(self, data: dict[str, str], rewrite: bool = False) -> None: - """"Save information to cache file.""" + """ "Save information to cache file.""" if not self._initialized: - raise CacheError(f"Unable to save cache. Initialize cache file '{self._file_name}' first.") + raise CacheError( + f"Unable to save cache. Initialize cache file '{self._file_name}' first." + ) current_data: dict[str, str] = {} if not rewrite: @@ -111,19 +117,20 @@ async def write_cache(self, data: dict[str, str], rewrite: bool = False) -> None if not self._cache_file_exists: self._cache_file_exists = True _LOGGER.debug( - "Saved %s lines to cache file %s", - str(len(data)), - self._cache_file + "Saved %s lines to cache file %s", str(len(data)), self._cache_file ) async def read_cache(self) -> dict[str, str]: """Return current data from cache file.""" if not self._initialized: - raise CacheError(f"Unable to save cache. Initialize cache file '{self._file_name}' first.") + raise CacheError( + f"Unable to save cache. Initialize cache file '{self._file_name}' first." + ) current_data: dict[str, str] = {} if not self._cache_file_exists: _LOGGER.debug( - "Cache file '%s' does not exists, return empty cache data", self._cache_file + "Cache file '%s' does not exists, return empty cache data", + self._cache_file, ) return current_data try: @@ -146,10 +153,10 @@ async def read_cache(self) -> dict[str, str]: _LOGGER.warning( "Skip invalid line '%s' in cache file %s", data, - str(self._cache_file) + str(self._cache_file), ) break - current_data[data[:index_separator]] = data[index_separator + 1:] + current_data[data[:index_separator]] = data[index_separator + 1 :] return current_data async def delete_cache(self) -> None: diff --git a/plugwise_usb/helpers/util.py b/plugwise_usb/helpers/util.py index 8e85878fc..19f9d1159 100644 --- a/plugwise_usb/helpers/util.py +++ b/plugwise_usb/helpers/util.py @@ -1,4 +1,5 @@ """Plugwise utility helpers.""" + from __future__ import annotations import re @@ -21,7 +22,7 @@ def validate_mac(mac: str) -> bool: return True -def version_to_model(version: str | None) -> tuple[str|None, str]: +def version_to_model(version: str | None) -> tuple[str | None, str]: """Translate hardware_version to device type.""" if version is None: return (None, "Unknown") diff --git a/plugwise_usb/messages/__init__.py b/plugwise_usb/messages/__init__.py index 9f2e7260f..e9c9306e6 100644 --- a/plugwise_usb/messages/__init__.py +++ b/plugwise_usb/messages/__init__.py @@ -19,6 +19,7 @@ class Priority(Enum): MEDIUM = 2 LOW = 3 + class PlugwiseMessage: """Plugwise message base class.""" diff --git a/plugwise_usb/messages/properties.py b/plugwise_usb/messages/properties.py index 9d465a939..a052ae0aa 100644 --- a/plugwise_usb/messages/properties.py +++ b/plugwise_usb/messages/properties.py @@ -9,7 +9,10 @@ from ..exceptions import MessageError from ..helpers.util import int_to_uint -DESERIALIZE_ERROR: Final[MessageError] = MessageError("Unable to return value. Deserialize data first") +DESERIALIZE_ERROR: Final[MessageError] = MessageError( + "Unable to return value. Deserialize data first" +) + class BaseType: """Generic single instance property.""" diff --git a/plugwise_usb/messages/requests.py b/plugwise_usb/messages/requests.py index a3c036a66..bbaf00145 100644 --- a/plugwise_usb/messages/requests.py +++ b/plugwise_usb/messages/requests.py @@ -311,7 +311,9 @@ async def _process_stick_response(self, stick_response: StickResponse) -> None: self, ) - async def _send_request(self, suppress_node_errors=False) -> PlugwiseResponse | None: + async def _send_request( + self, suppress_node_errors=False + ) -> PlugwiseResponse | None: """Send request.""" if self._send_fn is None: return None diff --git a/plugwise_usb/messages/responses.py b/plugwise_usb/messages/responses.py index a3265cd6d..52bf1a7e5 100644 --- a/plugwise_usb/messages/responses.py +++ b/plugwise_usb/messages/responses.py @@ -778,19 +778,19 @@ def log_data(self) -> dict[int, tuple[datetime | None, int | None]]: if self.logdate1.value_set: log_data[1] = (self.logdate1.value, self.pulses1.value) else: - log_data[1] = (None, None) + log_data[1] = (None, None) if self.logdate2.value_set: log_data[2] = (self.logdate2.value, self.pulses2.value) else: - log_data[2] = (None, None) + log_data[2] = (None, None) if self.logdate3.value_set: log_data[3] = (self.logdate3.value, self.pulses3.value) else: - log_data[3] = (None, None) + log_data[3] = (None, None) if self.logdate4.value_set: log_data[4] = (self.logdate4.value, self.pulses4.value) else: - log_data[4] = (None, None) + log_data[4] = (None, None) return log_data def __repr__(self) -> str: @@ -856,12 +856,13 @@ def __init__(self) -> None: @property def switch_state(self) -> bool: """Return state of switch (True = On, False = Off).""" - return (self._power_state.value != 0) + return self._power_state.value != 0 def __repr__(self) -> str: """Convert request into writable str.""" return f"{super().__repr__()[:-1]}, power_state={self._power_state.value}, group={self.group.value})" + class NodeFeaturesResponse(PlugwiseResponse): """Returns supported features of node. diff --git a/plugwise_usb/network/__init__.py b/plugwise_usb/network/__init__.py index 1533fb786..8006dfa40 100644 --- a/plugwise_usb/network/__init__.py +++ b/plugwise_usb/network/__init__.py @@ -257,7 +257,9 @@ async def node_join_available_message(self, response: PlugwiseResponse) -> bool: ) mac = response.mac_decoded - _LOGGER.debug("node_join_available_message | sending NodeAddRequest for %s", mac) + _LOGGER.debug( + "node_join_available_message | sending NodeAddRequest for %s", mac + ) try: result = await self.register_node(mac) except NodeError as exc: @@ -555,9 +557,13 @@ async def set_energy_intervals( if production < 0: raise ValueError("Production interval must be non-negative") if production > 0 and production % consumption != 0: - raise ValueError("Production interval must be a multiple of consumption interval") + raise ValueError( + "Production interval must be a multiple of consumption interval" + ) - _LOGGER.debug("set_energy_intervals | cons=%s, prod=%s", consumption, production) + _LOGGER.debug( + "set_energy_intervals | cons=%s, prod=%s", consumption, production + ) request = CircleMeasureIntervalRequest( self._controller.send, bytes(mac, UTF8), consumption, production ) @@ -611,4 +617,3 @@ async def _notify_node_event_subscribers(self, event: NodeEvent, mac: str) -> No callback_list.append(callback(event, mac)) if len(callback_list) > 0: await gather(*callback_list) - diff --git a/plugwise_usb/network/cache.py b/plugwise_usb/network/cache.py index a9fb1eda2..f73ef3add 100644 --- a/plugwise_usb/network/cache.py +++ b/plugwise_usb/network/cache.py @@ -34,7 +34,9 @@ async def save_cache(self) -> None: node_value = "" else: node_value = str(node_type) - cache_data_to_save[str(address)] = f"{mac}{CACHE_DATA_SEPARATOR}{node_value}" + cache_data_to_save[str(address)] = ( + f"{mac}{CACHE_DATA_SEPARATOR}{node_value}" + ) await self.write_cache(cache_data_to_save) async def clear_cache(self) -> None: diff --git a/plugwise_usb/network/registry.py b/plugwise_usb/network/registry.py index 4f918782b..237986480 100644 --- a/plugwise_usb/network/registry.py +++ b/plugwise_usb/network/registry.py @@ -191,7 +191,9 @@ async def update_missing_registrations(self, quick: bool = False) -> None: # no if registration is not None: nextaddress, mac = registration if mac == "": - self._first_free_address = min(self._first_free_address, nextaddress) + self._first_free_address = min( + self._first_free_address, nextaddress + ) if quick: break _LOGGER.debug( diff --git a/plugwise_usb/nodes/celsius.py b/plugwise_usb/nodes/celsius.py index c8245a747..6807c369d 100644 --- a/plugwise_usb/nodes/celsius.py +++ b/plugwise_usb/nodes/celsius.py @@ -2,6 +2,7 @@ TODO: Finish node """ + from __future__ import annotations import logging diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 32b2980bf..a89ce5a1e 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -326,7 +326,6 @@ def _log_no_energy_stats_update(self) -> None: self.name, ) - @raise_not_loaded @raise_calibration_missing async def energy_update(self) -> EnergyStatistics | None: # noqa: PLR0911 PLR0912 @@ -500,10 +499,7 @@ async def energy_log_update(self, address: int | None) -> bool: for _slot in range(4, 0, -1): log_timestamp, log_pulses = response.log_data[_slot] _LOGGER.debug( - "In slot=%s: pulses=%s, timestamp=%s", - _slot, - log_pulses, - log_timestamp + "In slot=%s: pulses=%s, timestamp=%s", _slot, log_pulses, log_timestamp ) if log_timestamp is None or log_pulses is None: self._energy_counters.add_empty_log(response.log_address, _slot) @@ -991,7 +987,7 @@ async def _node_info_load_from_cache(self) -> bool: self._current_log_address = int(current_log_address) _LOGGER.debug( "circle._node_info_load_from_cache | current_log_address=%s", - self._current_log_address + self._current_log_address, ) return True diff --git a/plugwise_usb/nodes/helpers/__init__.py b/plugwise_usb/nodes/helpers/__init__.py index 1ef0b8a86..8b32732cb 100644 --- a/plugwise_usb/nodes/helpers/__init__.py +++ b/plugwise_usb/nodes/helpers/__init__.py @@ -25,9 +25,11 @@ class EnergyCalibration: def raise_not_loaded(func: FuncT) -> FuncT: """Raise NodeError when node is not loaded.""" + @wraps(func) def decorated(*args: Any, **kwargs: Any) -> Any: if not args[0].is_loaded: raise NodeError(f"Node {args[0].mac} is not loaded yet") return func(*args, **kwargs) + return cast(FuncT, decorated) diff --git a/plugwise_usb/nodes/helpers/counter.py b/plugwise_usb/nodes/helpers/counter.py index 8f64a3efb..c55aa46bd 100644 --- a/plugwise_usb/nodes/helpers/counter.py +++ b/plugwise_usb/nodes/helpers/counter.py @@ -80,9 +80,12 @@ def add_pulse_log( # pylint: disable=too-many-arguments import_only: bool = False, ) -> None: """Add pulse log.""" - if self._pulse_collection.add_log( - address, slot, timestamp, pulses, import_only - ) and not import_only: + if ( + self._pulse_collection.add_log( + address, slot, timestamp, pulses, import_only + ) + and not import_only + ): self.update() def get_pulse_logs(self) -> dict[int, dict[int, PulseLogRecord]]: @@ -160,7 +163,9 @@ def update(self) -> None: ( self._energy_statistics.hour_production, self._energy_statistics.hour_production_reset, - ) = self._counters[EnergyType.PRODUCTION_HOUR].update(self._pulse_collection) + ) = self._counters[EnergyType.PRODUCTION_HOUR].update( + self._pulse_collection + ) ( self._energy_statistics.day_production, self._energy_statistics.day_production_reset, @@ -295,7 +300,9 @@ def update( self._midnight_reset_passed = True if last_reset.hour == 1 and self._midnight_reset_passed: self._midnight_reset_passed = False - last_reset = last_reset.replace(hour=0, minute=0, second=0, microsecond=0) + last_reset = last_reset.replace( + hour=0, minute=0, second=0, microsecond=0 + ) pulses, last_update = pulse_collection.collected_pulses( last_reset, self._is_consumption diff --git a/plugwise_usb/nodes/helpers/pulses.py b/plugwise_usb/nodes/helpers/pulses.py index cda07d6e6..dda31cd02 100644 --- a/plugwise_usb/nodes/helpers/pulses.py +++ b/plugwise_usb/nodes/helpers/pulses.py @@ -172,7 +172,7 @@ def collected_pulses( self._mac, from_timestamp, is_consumption, - self._log_production + self._log_production, ) if not is_consumption: if self._log_production is None or not self._log_production: @@ -373,7 +373,7 @@ def _detect_rollover( _LOGGER.debug( "_update_rollover | %s | reset %s rollover", self._mac, - direction + direction, ) return False @@ -518,9 +518,8 @@ def _update_log_direction( if self._first_prev_log_processed and self._first_next_log_processed: # _log_production is True when 2 out of 3 consecutive slots have # the same timestamp - self._log_production = ( - (prev_timestamp == timestamp) - ^ (next_timestamp == timestamp) + self._log_production = (prev_timestamp == timestamp) ^ ( + next_timestamp == timestamp ) def _check_prev_production( diff --git a/plugwise_usb/nodes/helpers/subscription.py b/plugwise_usb/nodes/helpers/subscription.py index b6957114a..a7bd09489 100644 --- a/plugwise_usb/nodes/helpers/subscription.py +++ b/plugwise_usb/nodes/helpers/subscription.py @@ -28,7 +28,6 @@ def __init__(self) -> None: NodeFeatureSubscription, ] = {} - def subscribe_to_feature_update( self, node_feature_callback: Callable[[NodeFeature, Any], Coroutine[Any, Any, None]], @@ -60,6 +59,8 @@ async def publish_feature_update_to_subscribers( self._feature_update_subscribers.values() ): if feature in node_feature_subscription.features: - callback_list.append(node_feature_subscription.callback_fn(feature, state)) + callback_list.append( + node_feature_subscription.callback_fn(feature, state) + ) if len(callback_list) > 0: await gather(*callback_list) diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 6401d4331..21fea661d 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -51,6 +51,7 @@ CACHE_HARDWARE = "hardware" CACHE_NODE_INFO_TIMESTAMP = "node_info_timestamp" + class PlugwiseBaseNode(FeaturePublisher, ABC): """Abstract Base Class for a Plugwise node.""" @@ -316,9 +317,7 @@ def switch(self) -> bool: def sense(self) -> SenseStatistics: """Sense statistics.""" if NodeFeature.SENSE not in self._features: - raise FeatureError( - f"Sense statistics is not supported for node {self.mac}" - ) + raise FeatureError(f"Sense statistics is not supported for node {self.mac}") # endregion @@ -344,9 +343,7 @@ def _setup_protocol( if ( required_version := FEATURE_SUPPORTED_AT_FIRMWARE.get(feature) ) is not None and ( - self._node_protocols.min - <= required_version - <= self._node_protocols.max + self._node_protocols.min <= required_version <= self._node_protocols.max and feature not in self._features ): self._features += (feature,) @@ -426,7 +423,6 @@ async def _available_update_state( self._last_seen is not None and timestamp is not None and int((timestamp - self._last_seen).total_seconds()) > 5 # noqa: PLR2004 - ): self._last_seen = timestamp await self.publish_feature_update_to_subscribers( @@ -538,7 +534,9 @@ async def update_node_details( # noqa: PLR0912 PLR0913 allowed_models = TYPE_MODEL.get(self._node_info.node_type.value) if allowed_models and model_info[0] not in allowed_models: # Replace model_info list - model_info = [allowed_models[0]] # Not correct for 1 but should not be a problem + model_info = [ + allowed_models[0] + ] # Not correct for 1 but should not be a problem self._node_info.model = model_info[0] # Handle + devices diff --git a/plugwise_usb/nodes/scan.py b/plugwise_usb/nodes/scan.py index e22067e19..db65d550b 100644 --- a/plugwise_usb/nodes/scan.py +++ b/plugwise_usb/nodes/scan.py @@ -174,8 +174,10 @@ def _motion_from_cache(self) -> bool: if (cached_motion_state := self._get_cache(CACHE_MOTION_STATE)) is not None: if ( cached_motion_state == "True" - and (motion_timestamp := self._motion_timestamp_from_cache()) is not None - and int((datetime.now(tz=UTC) - motion_timestamp).total_seconds()) < self._reset_timer_from_cache() * 60 + and (motion_timestamp := self._motion_timestamp_from_cache()) + is not None + and int((datetime.now(tz=UTC) - motion_timestamp).total_seconds()) + < self._reset_timer_from_cache() * 60 ): return True return False @@ -360,7 +362,7 @@ async def _switch_group(self, response: PlugwiseResponse) -> bool: _LOGGER.warning("%s received %s", self.name, response) await gather( self._available_update_state(True, response.timestamp), - self._motion_state_update(response.switch_state, response.timestamp) + self._motion_state_update(response.switch_state, response.timestamp), ) return True @@ -384,7 +386,9 @@ async def _motion_state_update( self._set_cache(CACHE_MOTION_STATE, "False") if self._motion_state.state is None or self._motion_state.state: if self._reset_timer_motion_on is not None: - reset_timer = int((timestamp - self._reset_timer_motion_on).total_seconds()) + reset_timer = int( + (timestamp - self._reset_timer_motion_on).total_seconds() + ) if self._motion_config.reset_timer is None: self._motion_config = replace( self._motion_config, @@ -476,7 +480,9 @@ async def scan_configure( MotionSensitivity.OFF: SENSITIVITY_OFF_VALUE, } # Default to medium - sensitivity_value = sensitivity_map.get(sensitivity_level, SENSITIVITY_MEDIUM_VALUE) + sensitivity_value = sensitivity_map.get( + sensitivity_level, SENSITIVITY_MEDIUM_VALUE + ) request = ScanConfigureRequest( self._send, self._mac_in_bytes, diff --git a/plugwise_usb/nodes/sed.py b/plugwise_usb/nodes/sed.py index 1ac29dc8a..e45849c5a 100644 --- a/plugwise_usb/nodes/sed.py +++ b/plugwise_usb/nodes/sed.py @@ -241,7 +241,7 @@ async def set_awake_duration(self, seconds: int) -> bool: ) if self._battery_config.awake_duration == seconds: - return False + return False self._new_battery_config = replace( self._new_battery_config, awake_duration=seconds diff --git a/plugwise_usb/nodes/sense.py b/plugwise_usb/nodes/sense.py index daf09f117..dad2dd37d 100644 --- a/plugwise_usb/nodes/sense.py +++ b/plugwise_usb/nodes/sense.py @@ -95,7 +95,7 @@ def _load_defaults(self) -> None: humidity=0.0, ) -# region properties + # region properties @property @raise_not_loaded @@ -103,7 +103,7 @@ def sense_statistics(self) -> SenseStatistics: """Sense Statistics.""" return self._sense_statistics -# end region + # end region async def _sense_report(self, response: PlugwiseResponse) -> bool: """Process sense report message to extract current temperature and humidity values.""" diff --git a/plugwise_usb/nodes/stealth.py b/plugwise_usb/nodes/stealth.py index 33be3907a..f33d18542 100644 --- a/plugwise_usb/nodes/stealth.py +++ b/plugwise_usb/nodes/stealth.py @@ -1,4 +1,5 @@ """Plugwise Stealth node object.""" + from ..nodes.circle import PlugwiseCircle diff --git a/plugwise_usb/nodes/switch.py b/plugwise_usb/nodes/switch.py index 62d82262e..b1c6d4f18 100644 --- a/plugwise_usb/nodes/switch.py +++ b/plugwise_usb/nodes/switch.py @@ -53,7 +53,12 @@ async def load(self) -> bool: self._loaded = True self._setup_protocol( SWITCH_FIRMWARE_SUPPORT, - (NodeFeature.BATTERY, NodeFeature.INFO, NodeFeature.PING, NodeFeature.SWITCH), + ( + NodeFeature.BATTERY, + NodeFeature.INFO, + NodeFeature.PING, + NodeFeature.SWITCH, + ), ) if await self.initialize(): await self._loaded_callback(NodeEvent.LOADED, self.mac) @@ -89,7 +94,7 @@ def switch(self) -> bool: """Current state of switch.""" return bool(self._switch_state) - #endregion + # endregion async def _switch_group(self, response: PlugwiseResponse) -> bool: """Switch group request from Switch.""" @@ -99,7 +104,7 @@ async def _switch_group(self, response: PlugwiseResponse) -> bool: ) await gather( self._available_update_state(True, response.timestamp), - self._switch_state_update(response.switch_state, response.timestamp) + self._switch_state_update(response.switch_state, response.timestamp), ) return True diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 0f5cd92bc..4559bd3c7 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -60,11 +60,11 @@ + b"00044280" # log address 20 + b"01" # relay + b"01" # hz - + b"000000730007" # hw_ver + + b"000000730007" # hw_ver + b"4E0843A9" # fw_ver + b"01", # node_type (Circle+) ), - b"\x05\x05\x03\x030008014068\r\n":( + b"\x05\x05\x03\x030008014068\r\n": ( "Reply to CirclePlusAllowJoiningRequest", b"000000C1", # Success ack b"000000D9" # JOIN_ACCEPTED diff --git a/tests/test_usb.py b/tests/test_usb.py index 0631b7983..7b6d9788d 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -646,36 +646,36 @@ async def node_join(self, event: pw_api.NodeEvent, mac: str) -> None: # type: i ) ) - #@pytest.mark.asyncio - #async def test_stick_node_join_subscription( + # @pytest.mark.asyncio + # async def test_stick_node_join_subscription( # self, monkeypatch: pytest.MonkeyPatch - #) -> None: - #"""Testing "new_node" subscription.""" - #mock_serial = MockSerial(None) - #monkeypatch.setattr( - # pw_connection_manager, - # "create_serial_connection", - # mock_serial.mock_connection, - #) - #monkeypatch.setattr(pw_sender, "STICK_TIME_OUT", 0.1) - #monkeypatch.setattr(pw_requests, "NODE_TIME_OUT", 0.5) - #stick = pw_stick.Stick("test_port", cache_enabled=False) - #await stick.connect() - #await stick.initialize() - #await stick.discover_nodes(load=False) - - #self.test_node_join = asyncio.Future() - #unusb_join = stick.subscribe_to_node_events( - # node_event_callback=self.node_join, - # events=(pw_api.NodeEvent.JOIN,), - #) - - ## Inject NodeJoinAvailableResponse - #mock_serial.inject_message(b"00069999999999999999", b"1253") # @bouwew: seq_id is not FFFC! - #mac_join_node = await self.test_node_join - #assert mac_join_node == "9999999999999999" - #unusb_join() - #await stick.disconnect() + # ) -> None: + # """Testing "new_node" subscription.""" + # mock_serial = MockSerial(None) + # monkeypatch.setattr( + # pw_connection_manager, + # "create_serial_connection", + # mock_serial.mock_connection, + # ) + # monkeypatch.setattr(pw_sender, "STICK_TIME_OUT", 0.1) + # monkeypatch.setattr(pw_requests, "NODE_TIME_OUT", 0.5) + # stick = pw_stick.Stick("test_port", cache_enabled=False) + # await stick.connect() + # await stick.initialize() + # await stick.discover_nodes(load=False) + + # self.test_node_join = asyncio.Future() + # unusb_join = stick.subscribe_to_node_events( + # node_event_callback=self.node_join, + # events=(pw_api.NodeEvent.JOIN,), + # ) + + ## Inject NodeJoinAvailableResponse + # mock_serial.inject_message(b"00069999999999999999", b"1253") # @bouwew: seq_id is not FFFC! + # mac_join_node = await self.test_node_join + # assert mac_join_node == "9999999999999999" + # unusb_join() + # await stick.disconnect() @pytest.mark.asyncio async def test_node_discovery(self, monkeypatch: pytest.MonkeyPatch) -> None: @@ -783,7 +783,10 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No unsub_relay = stick.nodes["0098765432101234"].subscribe_to_feature_update( node_feature_callback=self.node_relay_state, - features=(pw_api.NodeFeature.RELAY, pw_api.NodeFeature.RELAY_LOCK,), + features=( + pw_api.NodeFeature.RELAY, + pw_api.NodeFeature.RELAY_LOCK, + ), ) # Test async switching back from on to off @@ -1118,7 +1121,6 @@ def test_pulse_collection_consumption( tst_consumption.add_log(94, 1, (fixed_this_hour - td(hours=24)), 1000) assert tst_consumption.collected_logs == 24 - # Test rollover by updating pulses before log record pulse_update_3 = fixed_this_hour + td(hours=1, minutes=0, seconds=30) test_timestamp = fixed_this_hour + td(hours=1) @@ -1142,7 +1144,7 @@ def test_pulse_collection_consumption( # Collected pulses last day: assert tst_consumption.collected_pulses( test_timestamp - td(hours=24), is_consumption=True - ) == (45 + 22861, pulse_update_4) + ) == (45 + 22861, pulse_update_4) # pulse-count of 2500 is ignored, the code does not export this incorrect value tst_consumption.add_log(100, 2, (fixed_this_hour + td(hours=1)), 2222) @@ -2552,7 +2554,6 @@ async def test_node_discovery_and_load( assert state[pw_api.NodeFeature.RELAY].state assert not state[pw_api.NodeFeature.RELAY_LOCK].state - # region Scan self.test_node_awake = asyncio.Future() unsub_awake = stick.subscribe_to_node_events( From 3cfcb2be16af62e821305e9085a5b7816a124119 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Fri, 20 Jun 2025 11:45:49 +0200 Subject: [PATCH 11/21] nitpick --- plugwise_usb/constants.py | 4 ++-- plugwise_usb/helpers/cache.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugwise_usb/constants.py b/plugwise_usb/constants.py index e854cd8a7..d9975a601 100644 --- a/plugwise_usb/constants.py +++ b/plugwise_usb/constants.py @@ -17,8 +17,8 @@ UTF8: Final = "utf-8" # Value limits -MAX_UINT_2: Final = 255 -MAX_UINT_4: Final = 65535 +MAX_UINT_2: Final = 255 # 8-bit unsigned integer max +MAX_UINT_4: Final = 65535 # 16-bit unsigned integer max # Time DAY_IN_HOURS: Final = 24 diff --git a/plugwise_usb/helpers/cache.py b/plugwise_usb/helpers/cache.py index 30e7a78ef..ad11b70d3 100644 --- a/plugwise_usb/helpers/cache.py +++ b/plugwise_usb/helpers/cache.py @@ -80,7 +80,7 @@ def _get_writable_os_dir(self) -> str: return os_path_join(os_path_expand_user("~"), CACHE_DIR) async def write_cache(self, data: dict[str, str], rewrite: bool = False) -> None: - """ "Save information to cache file.""" + """Save information to cache file.""" if not self._initialized: raise CacheError( f"Unable to save cache. Initialize cache file '{self._file_name}' first." From b0a4dc20a3822941f1edfbf7b5de6e2d664f0d01 Mon Sep 17 00:00:00 2001 From: autoruff Date: Fri, 20 Jun 2025 10:01:10 +0000 Subject: [PATCH 12/21] fixup: cpaj Python code reformatted using Ruff --- plugwise_usb/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugwise_usb/constants.py b/plugwise_usb/constants.py index d9975a601..b1f1a23fb 100644 --- a/plugwise_usb/constants.py +++ b/plugwise_usb/constants.py @@ -17,8 +17,8 @@ UTF8: Final = "utf-8" # Value limits -MAX_UINT_2: Final = 255 # 8-bit unsigned integer max -MAX_UINT_4: Final = 65535 # 16-bit unsigned integer max +MAX_UINT_2: Final = 255 # 8-bit unsigned integer max +MAX_UINT_4: Final = 65535 # 16-bit unsigned integer max # Time DAY_IN_HOURS: Final = 24 From 0bbfc5b06e21bd73019372ec41d4b7d3212fc065 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Fri, 20 Jun 2025 12:12:44 +0200 Subject: [PATCH 13/21] exclude .cache --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 11dac2da0..6d2dd5d23 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ fixtures/* !fixtures/.keep *.sedbck tmp +.cache From f296b43905dc17617cacc64442015362817d140f Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Fri, 20 Jun 2025 14:23:49 +0200 Subject: [PATCH 14/21] fix ruff warnings --- tests/stick_test_data.py | 52 ++++++++++++++++++++-------------------- tests/test_usb.py | 33 +++++++++++-------------- 2 files changed, 40 insertions(+), 45 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 4559bd3c7..278ad28bc 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -15,10 +15,10 @@ for x in range(168): delta_month = hour_timestamp - hour_timestamp.replace(day=1, hour=0) LOG_TIMESTAMPS[x] = ( - bytes(("%%0%dX" % 2) % (hour_timestamp.year - 2000), pw_constants.UTF8) - + bytes(("%%0%dX" % 2) % hour_timestamp.month, pw_constants.UTF8) + bytes(("%%0%dX" % 2) % (hour_timestamp.year - 2000), pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dX" % 2) % hour_timestamp.month, pw_constants.UTF8) # noqa: UP031 + bytes( - ("%%0%dX" % 4) + ("%%0%dX" % 4) # noqa: UP031 % int((delta_month.days * 1440) + (delta_month.seconds / 60)), pw_constants.UTF8, ) @@ -594,23 +594,23 @@ b"000000C1", # Success ack b"003A" # msg_id + b"0098765432101234" # mac - + bytes(("%%0%dd" % 2) % utc_now.second, pw_constants.UTF8) - + bytes(("%%0%dd" % 2) % utc_now.minute, pw_constants.UTF8) - + bytes(("%%0%dd" % 2) % utc_now.hour, pw_constants.UTF8) - + bytes(("%%0%dd" % 2) % utc_now.weekday(), pw_constants.UTF8) - + bytes(("%%0%dd" % 2) % utc_now.day, pw_constants.UTF8) - + bytes(("%%0%dd" % 2) % utc_now.month, pw_constants.UTF8) - + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8), + + bytes(("%%0%dd" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.day, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.month, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8), # noqa: UP031 ), b"\x05\x05\x03\x03003E11111111111111111B8A\r\n": ( "clock for 0011111111111111", b"000000C1", # Success ack b"003F" # msg_id + b"1111111111111111" # mac - + bytes(("%%0%dX" % 2) % utc_now.hour, pw_constants.UTF8) - + bytes(("%%0%dX" % 2) % utc_now.minute, pw_constants.UTF8) - + bytes(("%%0%dX" % 2) % utc_now.second, pw_constants.UTF8) - + bytes(("%%0%dX" % 2) % utc_now.weekday(), pw_constants.UTF8) + + bytes(("%%0%dX" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dX" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dX" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dX" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 ), @@ -619,10 +619,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"2222222222222222" # mac - + bytes(("%%0%dX" % 2) % utc_now.hour, pw_constants.UTF8) - + bytes(("%%0%dX" % 2) % utc_now.minute, pw_constants.UTF8) - + bytes(("%%0%dX" % 2) % utc_now.second, pw_constants.UTF8) - + bytes(("%%0%dX" % 2) % utc_now.weekday(), pw_constants.UTF8) + + bytes(("%%0%dX" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dX" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dX" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dX" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 ), @@ -631,10 +631,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"3333333333333333" # mac - + bytes(("%%0%dX" % 2) % utc_now.hour, pw_constants.UTF8) - + bytes(("%%0%dX" % 2) % utc_now.minute, pw_constants.UTF8) - + bytes(("%%0%dX" % 2) % utc_now.second, pw_constants.UTF8) - + bytes(("%%0%dX" % 2) % utc_now.weekday(), pw_constants.UTF8) + + bytes(("%%0%dX" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dX" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dX" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dX" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 ), @@ -643,10 +643,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"4444444444444444" # mac - + bytes(("%%0%dX" % 2) % utc_now.hour, pw_constants.UTF8) - + bytes(("%%0%dX" % 2) % utc_now.minute, pw_constants.UTF8) - + bytes(("%%0%dX" % 2) % utc_now.second, pw_constants.UTF8) - + bytes(("%%0%dX" % 2) % utc_now.weekday(), pw_constants.UTF8) + + bytes(("%%0%dX" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dX" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dX" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dX" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 ), diff --git a/tests/test_usb.py b/tests/test_usb.py index 7b6d9788d..73acecc04 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -205,7 +205,7 @@ async def mock_connection( class MockOsPath: """Mock aiofiles.path class.""" - async def exists(self, file_or_path: str) -> bool: + async def exists(self, file_or_path: str) -> bool: # noqa: PLR0911 """Exists folder.""" if file_or_path == "mock_folder_that_exists": return True @@ -285,7 +285,6 @@ async def dummy_fn(self, request: pw_requests.PlugwiseRequest, test: bool) -> No @pytest.mark.asyncio async def test_sorting_request_messages(self) -> None: """Test request message priority sorting.""" - node_add_request = pw_requests.NodeAddRequest( self.dummy_fn, b"1111222233334444", True ) @@ -329,7 +328,6 @@ async def test_sorting_request_messages(self) -> None: @pytest.mark.asyncio async def test_msg_properties(self) -> None: """Test message properties.""" - # UnixTimestamp unix_timestamp = pw_msg_properties.UnixTimestamp( dt(2011, 6, 27, 9, 4, 10, tzinfo=UTC), 8 @@ -585,7 +583,7 @@ async def test_stick_node_discovered_subscription( ) assert stick.nodes["5555555555555555"].node_info.version == "080007" assert stick.nodes["5555555555555555"].node_info.model == "Scan" - assert stick.nodes["5555555555555555"].node_info.model_type == None + assert stick.nodes["5555555555555555"].node_info.model_type is None assert stick.nodes["5555555555555555"].available assert stick.nodes["5555555555555555"].node_info.is_battery_powered assert sorted(stick.nodes["5555555555555555"].features) == sorted( @@ -752,7 +750,7 @@ async def node_init_relay_state( ) @pytest.mark.asyncio - async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> None: + async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> None: # noqa: PLR0915 """Testing discovery of nodes.""" mock_serial = MockSerial(None) monkeypatch.setattr( @@ -954,7 +952,7 @@ async def fake_get_missing_energy_logs(address: int) -> None: await stick.disconnect() @freeze_time("2025-04-03 22:00:00") - def test_pulse_collection_consumption( + def test_pulse_collection_consumption( # noqa: PLR0915 self, monkeypatch: pytest.MonkeyPatch ) -> None: """Testing pulse collection class.""" @@ -1216,7 +1214,6 @@ def test_pulse_collection_consumption_empty( @freeze_time(dt.now()) def test_pulse_collection_production(self, monkeypatch: pytest.MonkeyPatch) -> None: """Testing pulse collection class.""" - # Set log hours to 1 week monkeypatch.setattr(pw_energy_pulses, "MAX_LOG_HOURS", 168) @@ -1290,7 +1287,6 @@ def test_pulse_collection_production(self, monkeypatch: pytest.MonkeyPatch) -> N @freeze_time(dt.now()) def test_log_address_rollover(self, monkeypatch: pytest.MonkeyPatch) -> None: """Test log address rollover.""" - # Set log hours to 25 monkeypatch.setattr(pw_energy_pulses, "MAX_LOG_HOURS", 24) @@ -1535,7 +1531,7 @@ def os_path_join(self, str_a: str, str_b: str) -> str: return f"{str_a}/{str_b}" @pytest.mark.asyncio - async def test_cache(self, monkeypatch: pytest.MonkeyPatch) -> None: + async def test_cache(self, monkeypatch: pytest.MonkeyPatch) -> None: # noqa: PLR0915 """Test PlugwiseCache class.""" monkeypatch.setattr(pw_helpers_cache, "os_name", "nt") monkeypatch.setattr(pw_helpers_cache, "os_getenv", self.fake_env) @@ -1791,9 +1787,8 @@ async def test_node_cache(self, monkeypatch: pytest.MonkeyPatch) -> None: ) @pytest.mark.asyncio - async def test_base_node(self, monkeypatch: pytest.MonkeyPatch) -> None: + async def test_base_node(self, monkeypatch: pytest.MonkeyPatch) -> None: # noqa: PLR0915 """Testing properties of base node.""" - mock_stick_controller = MockStickController() async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ignore[name-defined] @@ -1907,10 +1902,10 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign assert test_node.mac == "1298347650AFBECD" @pytest.mark.asyncio - async def test_sed_node(self, monkeypatch: pytest.MonkeyPatch) -> None: + async def test_sed_node(self, monkeypatch: pytest.MonkeyPatch) -> None: # noqa: PLR0915 """Testing properties of SED.""" - def fake_cache(dummy: object, setting: str) -> str | None: + def fake_cache(dummy: object, setting: str) -> str | None: # noqa: PLR0911 """Fake cache retrieval.""" if setting == pw_node.CACHE_FIRMWARE: return "2011-6-27-8-55-44" @@ -2106,10 +2101,10 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign assert test_sed.sleep_duration == 120 @pytest.mark.asyncio - async def test_scan_node(self, monkeypatch: pytest.MonkeyPatch) -> None: + async def test_scan_node(self, monkeypatch: pytest.MonkeyPatch) -> None: # noqa: PLR0915 """Testing properties of scan.""" - def fake_cache(dummy: object, setting: str) -> str | None: + def fake_cache(dummy: object, setting: str) -> str | None: # noqa: PLR0911 PLR0912 """Fake cache retrieval.""" if setting == pw_node.CACHE_FIRMWARE: return "2011-6-27-8-55-44" @@ -2301,7 +2296,7 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign async def test_switch_node(self, monkeypatch: pytest.MonkeyPatch) -> None: """Testing properties of switch.""" - def fake_cache(dummy: object, setting: str) -> str | None: + def fake_cache(dummy: object, setting: str) -> str | None: # noqa: PLR0911 """Fake cache retrieval.""" if setting == pw_node.CACHE_FIRMWARE: return "2011-5-13-7-26-54" @@ -2390,7 +2385,7 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign assert not state[pw_api.NodeFeature.AVAILABLE].state @pytest.mark.asyncio - async def test_node_discovery_and_load( + async def test_node_discovery_and_load( # noqa: PLR0915 self, monkeypatch: pytest.MonkeyPatch ) -> None: """Testing discovery of nodes.""" @@ -2569,7 +2564,7 @@ async def test_node_discovery_and_load( ) assert stick.nodes["5555555555555555"].node_info.version == "080007" assert stick.nodes["5555555555555555"].node_info.model == "Scan" - assert stick.nodes["5555555555555555"].node_info.model_type == None + assert stick.nodes["5555555555555555"].node_info.model_type is None assert stick.nodes["5555555555555555"].available assert stick.nodes["5555555555555555"].node_info.is_battery_powered assert sorted(stick.nodes["5555555555555555"].features) == sorted( @@ -2634,7 +2629,7 @@ async def test_node_discovery_and_load( ) assert stick.nodes["8888888888888888"].node_info.version == "070051" assert stick.nodes["8888888888888888"].node_info.model == "Switch" - assert stick.nodes["8888888888888888"].node_info.model_type == None + assert stick.nodes["8888888888888888"].node_info.model_type is None assert stick.nodes["8888888888888888"].available assert stick.nodes["8888888888888888"].node_info.is_battery_powered assert sorted(stick.nodes["8888888888888888"].features) == sorted( From f0a272d99a5aa37ab2d1abb3ab17f1cb6d042205 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Fri, 20 Jun 2025 14:56:51 +0200 Subject: [PATCH 15/21] fix ruff D100 missing note --- tests/stick_test_data.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 278ad28bc..897f56f89 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -1,3 +1,5 @@ +"""Stick Test Program.""" + from datetime import UTC, datetime, timedelta import importlib From 4ad45615029350a82220a2d8564960cecbce65b1 Mon Sep 17 00:00:00 2001 From: Tom Scholten Date: Fri, 20 Jun 2025 20:31:50 +0200 Subject: [PATCH 16/21] Attempt to fix biome --- .github/workflows/verify.yml | 2 +- scripts/setup_test.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index f73b74679..86aa5ce1d 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -130,7 +130,7 @@ jobs: - name: Biome lint run: | . venv/bin/activate - mkdir -p ./tmp && curl -sL "https://github.com/biomejs/biome/releases/latest/download/biome-linux-x64" -o ./tmp/biome && chmod +x ./tmp/biome + mkdir -p ./tmp && curl -sL "https://github.com/biomejs/biome/releases/download/%40biomejs%2Fbiome%402.0.0/biome-darwin-arm64" -o ./tmp/biome && chmod +x ./tmp/biome pre-commit run --show-diff-on-failure --color=always --all-files --hook-stage manual biome - name: Lint markdown files run: | diff --git a/scripts/setup_test.sh b/scripts/setup_test.sh index 6535660ca..a691d8e3a 100755 --- a/scripts/setup_test.sh +++ b/scripts/setup_test.sh @@ -31,7 +31,7 @@ case "$arch" in x86_64) use_arch="linux-x64" ;; *) echo "Unsupported arch for biome cli version: $arch"; exit 2 ;; esac -curl -sL "https://github.com/biomejs/biome/releases/latest/download/biome-${use_arch}" -o "${my_path}/tmp/biome" +curl -sL "https://github.com/biomejs/biome/releases/download/%40biomejs%2Fbiome%402.0.0/biome-{$use_arch}" -o "${my_path}/tmp/biome" # Make biome executable (if necessary) chmod +x "${my_path}/tmp/biome" From 3c4c9acb958f71316b5698ec9b178738e3da6ec0 Mon Sep 17 00:00:00 2001 From: Tom Scholten Date: Fri, 20 Jun 2025 20:37:27 +0200 Subject: [PATCH 17/21] Disable biome for now --- .github/workflows/verify.yml | 10 +++++----- scripts/tests_and_coverage.sh | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 86aa5ce1d..0608c2da1 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -127,11 +127,11 @@ jobs: run: | . venv/bin/activate pre-commit run --show-diff-on-failure --color=always --all-files --hook-stage manual pylint - - name: Biome lint - run: | - . venv/bin/activate - mkdir -p ./tmp && curl -sL "https://github.com/biomejs/biome/releases/download/%40biomejs%2Fbiome%402.0.0/biome-darwin-arm64" -o ./tmp/biome && chmod +x ./tmp/biome - pre-commit run --show-diff-on-failure --color=always --all-files --hook-stage manual biome +# - name: Biome lint +# run: | +# . venv/bin/activate +# mkdir -p ./tmp && curl -sL "https://github.com/biomejs/biome/releases/download/%40biomejs%2Fbiome%402.0.0/biome-darwin-arm64" -o ./tmp/biome && chmod +x ./tmp/biome +# pre-commit run --show-diff-on-failure --color=always --all-files --hook-stage manual biome - name: Lint markdown files run: | . venv/bin/activate diff --git a/scripts/tests_and_coverage.sh b/scripts/tests_and_coverage.sh index b347decbf..8329350e0 100755 --- a/scripts/tests_and_coverage.sh +++ b/scripts/tests_and_coverage.sh @@ -61,8 +61,9 @@ if [ -z "${GITHUB_ACTIONS}" ] || [ "$1" == "test_and_coverage" ] ; then fi if [ -z "${GITHUB_ACTIONS}" ] || [ "$1" == "linting" ] ; then - echo "... biome-ing (prettier) ..." - biome_format + # Disabled as per #270 + # echo "... biome-ing (prettier) ..." + # biome_format echo "... ruff checking ..." ruff check plugwise_usb/ tests/ From 5cad20e584db93d753559218698b7273d1c5f2d5 Mon Sep 17 00:00:00 2001 From: Tom Scholten Date: Fri, 20 Jun 2025 20:40:38 +0200 Subject: [PATCH 18/21] Disable biome for now but leave correctly --- .github/workflows/verify.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 0608c2da1..7a78313e7 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -130,7 +130,7 @@ jobs: # - name: Biome lint # run: | # . venv/bin/activate -# mkdir -p ./tmp && curl -sL "https://github.com/biomejs/biome/releases/download/%40biomejs%2Fbiome%402.0.0/biome-darwin-arm64" -o ./tmp/biome && chmod +x ./tmp/biome +# mkdir -p ./tmp && curl -sL "https://github.com/biomejs/biome/releases/download/%40biomejs%2Fbiome%402.0.0/biome-linux-x64" -o ./tmp/biome && chmod +x ./tmp/biome # pre-commit run --show-diff-on-failure --color=always --all-files --hook-stage manual biome - name: Lint markdown files run: | From de0bfb89475fe695c796ef5cfd751433cab85a85 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Fri, 20 Jun 2025 22:16:38 +0200 Subject: [PATCH 19/21] fix ruff PLR0912 and PLR0913 disable update_node_details tests (need help!) --- plugwise_usb/api.py | 12 +++++ plugwise_usb/nodes/circle.py | 33 +++++--------- plugwise_usb/nodes/node.py | 86 +++++++++++++++++------------------- tests/test_usb.py | 64 +++++++++++++-------------- 4 files changed, 95 insertions(+), 100 deletions(-) diff --git a/plugwise_usb/api.py b/plugwise_usb/api.py index 0ad97c527..4f1140f4e 100644 --- a/plugwise_usb/api.py +++ b/plugwise_usb/api.py @@ -126,6 +126,18 @@ class BatteryConfig: sleep_duration: int | None = None +@dataclass +class NodeInfoMessage: + """Node hardware information Message.""" + + firmware: datetime | None = None + hardware: str | None = None + node_type: NodeType | None = None + timestamp: datetime | None = None + relay_state: bool | None = None + current_logaddress_pointer: int | None = None + + @dataclass class NodeInfo: """Node hardware information.""" diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index a89ce5a1e..be90c9d0f 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -15,7 +15,7 @@ NodeEvent, NodeFeature, NodeInfo, - NodeType, + NodeInfoMessage, PowerStatistics, RelayConfig, RelayLock, @@ -931,7 +931,7 @@ async def initialize(self) -> bool: return True async def node_info_update( - self, node_info: NodeInfoResponse | None = None + self, node_info: NodeInfoResponse | NodeInfoMessage | None = None ) -> NodeInfo | None: """Update Node (hardware) information.""" if node_info is None: @@ -995,32 +995,21 @@ async def _node_info_load_from_cache(self) -> bool: return False # pylint: disable=too-many-arguments - async def update_node_details( # noqa: PLR0913 - self, - firmware: datetime | None, - hardware: str | None, - node_type: NodeType | None, - timestamp: datetime | None, - relay_state: bool | None, - logaddress_pointer: int | None, + async def update_node_details( + self, node_info: NodeInfoResponse | None = None ) -> bool: """Process new node info and return true if all fields are updated.""" - if relay_state is not None: + if node_info.relay_state is not None: self._relay_state = replace( - self._relay_state, state=relay_state, timestamp=timestamp + self._relay_state, + state=node_info.relay_state, + timestamp=node_info.timestamp, ) - if logaddress_pointer is not None: - self._current_log_address = logaddress_pointer + if node_info.current_logaddress_pointer is not None: + self._current_log_address = node_info.current_logaddress_pointer - return await super().update_node_details( - firmware, - hardware, - node_type, - timestamp, - relay_state, - logaddress_pointer, - ) + return await super().update_node_details(node_info) async def unload(self) -> None: """Deactivate and unload node features.""" diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 21fea661d..7fa52d5b1 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -20,6 +20,7 @@ NodeEvent, NodeFeature, NodeInfo, + NodeInfoMessage, NodeType, PowerStatistics, RelayConfig, @@ -456,14 +457,7 @@ async def node_info_update( await self._available_update_state(False) return self._node_info await self._available_update_state(True, node_info.timestamp) - await self.update_node_details( - firmware=node_info.firmware, - node_type=node_info.node_type, - hardware=node_info.hardware, - timestamp=node_info.timestamp, - relay_state=node_info.relay_state, - logaddress_pointer=node_info.current_logaddress_pointer, - ) + await self.update_node_details(node_info) return self._node_info async def _node_info_load_from_cache(self) -> bool: @@ -474,54 +468,67 @@ async def _node_info_load_from_cache(self) -> bool: node_type: NodeType | None = None if (node_type_str := self._get_cache(CACHE_NODE_TYPE)) is not None: node_type = NodeType(int(node_type_str)) - - return await self.update_node_details( + node_info = NodeInfoMessage( firmware=firmware, hardware=hardware, node_type=node_type, timestamp=timestamp, relay_state=None, - logaddress_pointer=None, + current_logaddress_pointer=None, ) + return await self.update_node_details(node_info) - # pylint: disable=too-many-arguments - async def update_node_details( # noqa: PLR0912 PLR0913 - self, - firmware: datetime | None, - hardware: str | None, - node_type: NodeType | None, - timestamp: datetime | None, - relay_state: bool | None, - logaddress_pointer: int | None, + async def update_node_details( + self, node_info: NodeInfoResponse | NodeInfoMessage | None = None ) -> bool: """Process new node info and return true if all fields are updated.""" _LOGGER.debug( "update_node_details | firmware=%s, hardware=%s, nodetype=%s", - firmware, - hardware, - node_type, + node_info.firmware, + node_info.hardware, + node_info.node_type, ) _LOGGER.debug( "update_node_details | timestamp=%s, relay_state=%s, logaddress_pointer=%s,", - timestamp, - relay_state, - logaddress_pointer, + node_info.timestamp, + node_info.relay_state, + node_info.current_logaddress_pointer, ) complete = True - if node_type is None: + if node_info.node_type is None: complete = False else: - self._node_info.node_type = NodeType(node_type) + self._node_info.node_type = NodeType(node_info.node_type) self._set_cache(CACHE_NODE_TYPE, self._node_info.node_type.value) - if firmware is None: + if node_info.firmware is None: complete = False else: - self._node_info.firmware = firmware - self._set_cache(CACHE_FIRMWARE, firmware) + self._node_info.firmware = node_info.firmware + self._set_cache(CACHE_FIRMWARE, node_info.firmware) + + complete &= await self._update_node_details_hardware(node_info.hardware) + complete &= await self._update_node_details_timestamp(node_info.timestamp) + + await self.save_cache() + if node_info.timestamp is not None and node_info.timestamp > datetime.now( + tz=UTC + ) - timedelta(minutes=5): + await self._available_update_state(True, node_info.timestamp) + + return complete + + async def _update_node_details_timestamp(self, timestamp: datetime | None) -> bool: + if timestamp is None: + return False + else: + self._node_info.timestamp = timestamp + self._set_cache(CACHE_NODE_INFO_TIMESTAMP, timestamp) + return True + async def _update_node_details_hardware(self, hardware: str | None) -> bool: if hardware is None: - complete = False + return False else: if self._node_info.version != hardware: # Generate modelname based on hardware version @@ -561,20 +568,7 @@ async def update_node_details( # noqa: PLR0912 PLR0913 self._node_info.name = f"{model_info[0]} {self._node_info.mac[-5:]}" self._set_cache(CACHE_HARDWARE, hardware) - - if timestamp is None: - complete = False - else: - self._node_info.timestamp = timestamp - self._set_cache(CACHE_NODE_INFO_TIMESTAMP, timestamp) - - await self.save_cache() - if timestamp is not None and timestamp > datetime.now(tz=UTC) - timedelta( - minutes=5 - ): - await self._available_update_state(True, timestamp) - - return complete + return True async def is_online(self) -> bool: """Check if node is currently online.""" diff --git a/tests/test_usb.py b/tests/test_usb.py index 73acecc04..f433d432e 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -2156,14 +2156,14 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign ) assert not test_scan.cache_enabled - await test_scan.update_node_details( - firmware=dt(2011, 6, 27, 8, 55, 44, tzinfo=UTC), - hardware="080007", - node_type=None, - timestamp=None, - relay_state=None, - logaddress_pointer=None, - ) + # await test_scan.update_node_details( + # firmware=dt(2011, 6, 27, 8, 55, 44, tzinfo=UTC), + # hardware="080007", + # node_type=None, + # timestamp=None, + # relay_state=None, + # logaddress_pointer=None, + # ) assert await test_scan.load() # test motion reset timer @@ -2260,14 +2260,14 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign test_scan = pw_scan.PlugwiseScan( "1298347650AFBECD", 1, mock_stick_controller, load_callback ) - await test_scan.update_node_details( - firmware=dt(2011, 6, 27, 8, 55, 44, tzinfo=UTC), - hardware="080007", - node_type=None, - timestamp=None, - relay_state=None, - logaddress_pointer=None, - ) + # await test_scan.update_node_details( + # firmware=dt(2011, 6, 27, 8, 55, 44, tzinfo=UTC), + # hardware="080007", + # node_type=None, + # timestamp=None, + # relay_state=None, + # logaddress_pointer=None, + # ) test_scan.cache_enabled = True assert await test_scan.load() assert sorted(test_scan.features) == sorted( @@ -2336,14 +2336,14 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign pw_api.NodeFeature.PING, ) ) - await test_switch.update_node_details( - firmware=dt(2011, 6, 27, 9, 4, 10, tzinfo=UTC), - hardware="070051", - node_type=None, - timestamp=None, - relay_state=None, - logaddress_pointer=None, - ) + # await test_switch.update_node_details( + # firmware=dt(2011, 6, 27, 9, 4, 10, tzinfo=UTC), + # hardware="070051", + # node_type=None, + # timestamp=None, + # relay_state=None, + # logaddress_pointer=None, + # ) assert await test_switch.load() # Switch specific defaults @@ -2353,14 +2353,14 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign test_switch = pw_switch.PlugwiseSwitch( "1298347650AFBECD", 1, mock_stick_controller, load_callback ) - await test_switch.update_node_details( - firmware=dt(2011, 6, 27, 9, 4, 10, tzinfo=UTC), - hardware="070051", - node_type=None, - timestamp=None, - relay_state=None, - logaddress_pointer=None, - ) + # await test_switch.update_node_details( + # firmware=dt(2011, 6, 27, 9, 4, 10, tzinfo=UTC), + # hardware="070051", + # node_type=None, + # timestamp=None, + # relay_state=None, + # logaddress_pointer=None, + # ) test_switch.cache_enabled = True assert test_switch.cache_enabled is True assert await test_switch.load() From b3d927863ec80ab113eb2d6ce160b6407d489ada Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Fri, 20 Jun 2025 22:31:56 +0200 Subject: [PATCH 20/21] re-enable node_details test --- tests/test_usb.py | 69 ++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/tests/test_usb.py b/tests/test_usb.py index f433d432e..831ed4e83 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -2155,15 +2155,15 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign "1298347650AFBECD", 1, mock_stick_controller, load_callback ) assert not test_scan.cache_enabled - - # await test_scan.update_node_details( - # firmware=dt(2011, 6, 27, 8, 55, 44, tzinfo=UTC), - # hardware="080007", - # node_type=None, - # timestamp=None, - # relay_state=None, - # logaddress_pointer=None, - # ) + node_info = pw_api.NodeInfoMessage( + firmware=dt(2011, 6, 27, 8, 55, 44, tzinfo=UTC), + hardware="080007", + node_type=None, + timestamp=None, + relay_state=None, + current_logaddress_pointer=None, + ) + await test_scan.update_node_details(node_info) assert await test_scan.load() # test motion reset timer @@ -2260,14 +2260,15 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign test_scan = pw_scan.PlugwiseScan( "1298347650AFBECD", 1, mock_stick_controller, load_callback ) - # await test_scan.update_node_details( - # firmware=dt(2011, 6, 27, 8, 55, 44, tzinfo=UTC), - # hardware="080007", - # node_type=None, - # timestamp=None, - # relay_state=None, - # logaddress_pointer=None, - # ) + node_info = pw_api.NodeInfoMessage( + firmware=dt(2011, 6, 27, 8, 55, 44, tzinfo=UTC), + hardware="080007", + node_type=None, + timestamp=None, + relay_state=None, + current_logaddress_pointer=None, + ) + await test_scan.update_node_details(node_info) test_scan.cache_enabled = True assert await test_scan.load() assert sorted(test_scan.features) == sorted( @@ -2336,14 +2337,15 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign pw_api.NodeFeature.PING, ) ) - # await test_switch.update_node_details( - # firmware=dt(2011, 6, 27, 9, 4, 10, tzinfo=UTC), - # hardware="070051", - # node_type=None, - # timestamp=None, - # relay_state=None, - # logaddress_pointer=None, - # ) + node_info = pw_api.NodeInfoMessage( + firmware=dt(2011, 6, 27, 9, 4, 10, tzinfo=UTC), + hardware="070051", + node_type=None, + timestamp=None, + relay_state=None, + current_logaddress_pointer=None, + ) + await test_switch.update_node_details(node_info) assert await test_switch.load() # Switch specific defaults @@ -2353,14 +2355,15 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign test_switch = pw_switch.PlugwiseSwitch( "1298347650AFBECD", 1, mock_stick_controller, load_callback ) - # await test_switch.update_node_details( - # firmware=dt(2011, 6, 27, 9, 4, 10, tzinfo=UTC), - # hardware="070051", - # node_type=None, - # timestamp=None, - # relay_state=None, - # logaddress_pointer=None, - # ) + node_info = pw_api.NodeInfoMessage( + firmware=dt(2011, 6, 27, 9, 4, 10, tzinfo=UTC), + hardware="070051", + node_type=None, + timestamp=None, + relay_state=None, + current_logaddress_pointer=None, + ) + await test_switch.update_node_details(node_info) test_switch.cache_enabled = True assert test_switch.cache_enabled is True assert await test_switch.load() From bbce8249269df301a49c7be3226a1eb628ed50db Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Fri, 20 Jun 2025 22:34:29 +0200 Subject: [PATCH 21/21] remove async keyword --- plugwise_usb/nodes/node.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 7fa52d5b1..91c7fb797 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -507,8 +507,8 @@ async def update_node_details( self._node_info.firmware = node_info.firmware self._set_cache(CACHE_FIRMWARE, node_info.firmware) - complete &= await self._update_node_details_hardware(node_info.hardware) - complete &= await self._update_node_details_timestamp(node_info.timestamp) + complete &= self._update_node_details_hardware(node_info.hardware) + complete &= self._update_node_details_timestamp(node_info.timestamp) await self.save_cache() if node_info.timestamp is not None and node_info.timestamp > datetime.now( @@ -518,7 +518,7 @@ async def update_node_details( return complete - async def _update_node_details_timestamp(self, timestamp: datetime | None) -> bool: + def _update_node_details_timestamp(self, timestamp: datetime | None) -> bool: if timestamp is None: return False else: @@ -526,7 +526,7 @@ async def _update_node_details_timestamp(self, timestamp: datetime | None) -> bo self._set_cache(CACHE_NODE_INFO_TIMESTAMP, timestamp) return True - async def _update_node_details_hardware(self, hardware: str | None) -> bool: + def _update_node_details_hardware(self, hardware: str | None) -> bool: if hardware is None: return False else: