diff --git a/CHANGELOG.md b/CHANGELOG.md index aa2c5ae44..ea852f188 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.44.7 - 2025-07-08 + +- PR [282](https://github.com/plugwise/python-plugwise-usb/pull/282): Finalize switch implementation + ## v0.44.6 - 2025-07-06 - PR [279](https://github.com/plugwise/python-plugwise-usb/pull/279): Improve registry cache and node load behaviour diff --git a/plugwise_usb/api.py b/plugwise_usb/api.py index 528617978..cb6f6e171 100644 --- a/plugwise_usb/api.py +++ b/plugwise_usb/api.py @@ -205,6 +205,15 @@ class RelayState: timestamp: datetime | None = None +@dataclass(frozen=True) +class SwitchGroup: + """Status and Group of Switch.""" + + state: bool | None = None + group: int | None = None + timestamp: datetime | None = None + + @dataclass(frozen=True) class MotionState: """Status of motion sensor.""" diff --git a/plugwise_usb/messages/responses.py b/plugwise_usb/messages/responses.py index 52bf1a7e5..1f4f1923d 100644 --- a/plugwise_usb/messages/responses.py +++ b/plugwise_usb/messages/responses.py @@ -858,6 +858,11 @@ def switch_state(self) -> bool: """Return state of switch (True = On, False = Off).""" return self._power_state.value != 0 + @property + def switch_group(self) -> int: + """Return group number.""" + return self.group.value + def __repr__(self) -> str: """Convert request into writable str.""" return f"{super().__repr__()[:-1]}, power_state={self._power_state.value}, group={self.group.value})" diff --git a/plugwise_usb/nodes/switch.py b/plugwise_usb/nodes/switch.py index b1c6d4f18..a0f0edec2 100644 --- a/plugwise_usb/nodes/switch.py +++ b/plugwise_usb/nodes/switch.py @@ -4,11 +4,12 @@ from asyncio import gather from collections.abc import Awaitable, Callable +from dataclasses import replace from datetime import datetime import logging -from typing import Any, Final +from typing import Any -from ..api import NodeEvent, NodeFeature +from ..api import NodeEvent, NodeFeature, SwitchGroup from ..connection import StickController from ..exceptions import MessageError, NodeError from ..messages.responses import ( @@ -22,9 +23,6 @@ _LOGGER = logging.getLogger(__name__) -CACHE_SWITCH_STATE: Final = "switch_state" -CACHE_SWITCH_TIMESTAMP: Final = "switch_timestamp" - class PlugwiseSwitch(NodeSED): """Plugwise Switch node.""" @@ -39,7 +37,7 @@ def __init__( """Initialize Scan Device.""" super().__init__(mac, address, controller, loaded_callback) self._switch_subscription: Callable[[], None] | None = None - self._switch_state: bool | None = None + self._switch = SwitchGroup() async def load(self) -> bool: """Load and activate Switch node features.""" @@ -73,7 +71,7 @@ async def initialize(self) -> bool: return True self._switch_subscription = await self._message_subscribe( - self._switch_group, + self._switch_response, self._mac_in_bytes, (NODE_SWITCH_GROUP_ID,), ) @@ -92,11 +90,11 @@ async def unload(self) -> None: @raise_not_loaded def switch(self) -> bool: """Current state of switch.""" - return bool(self._switch_state) + return bool(self._switch.state) # endregion - async def _switch_group(self, response: PlugwiseResponse) -> bool: + async def _switch_response(self, response: PlugwiseResponse) -> bool: """Switch group request from Switch.""" if not isinstance(response, NodeSwitchGroupResponse): raise MessageError( @@ -104,38 +102,28 @@ 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.switch_group, response.timestamp + ), ) return True async def _switch_state_update( - self, switch_state: bool, timestamp: datetime + self, switch_state: bool, switch_group: int, timestamp: datetime ) -> None: """Process switch state update.""" _LOGGER.debug( - "_switch_state_update for %s: %s -> %s", + "_switch_state_update for %s: %s", self.name, - self._switch_state, switch_state, ) - state_update = False - # Update cache - self._set_cache(CACHE_SWITCH_STATE, str(switch_state)) - # Check for a state change - if self._switch_state != switch_state: - self._switch_state = switch_state - state_update = True - - self._set_cache(CACHE_SWITCH_TIMESTAMP, timestamp) - if state_update: - await gather( - *[ - self.publish_feature_update_to_subscribers( - NodeFeature.SWITCH, self._switch_state - ), - self.save_cache(), - ] - ) + self._switch = replace( + self._switch, state=switch_state, group=switch_group, timestamp=timestamp + ) + + await self.publish_feature_update_to_subscribers( + NodeFeature.SWITCH, self._switch + ) @raise_not_loaded async def get_state(self, features: tuple[NodeFeature]) -> dict[NodeFeature, Any]: @@ -155,7 +143,7 @@ async def get_state(self, features: tuple[NodeFeature]) -> dict[NodeFeature, Any match feature: case NodeFeature.SWITCH: - states[NodeFeature.SWITCH] = self._switch_state + states[NodeFeature.SWITCH] = self._switch case _: state_result = await super().get_state((feature,)) states[feature] = state_result[feature] diff --git a/pyproject.toml b/pyproject.toml index 3a0040b57..ff8bc48cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.44.6" +version = "0.44.7" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [