From 429a391778b8e9399043396f97f0795826808c7f Mon Sep 17 00:00:00 2001 From: Joerg Schoppet Date: Wed, 6 Nov 2024 10:15:27 +0100 Subject: [PATCH] Implement Forced Option --- src/abbfreeathome/devices/dimming_actuator.py | 45 +++++++++++++++++++ src/abbfreeathome/devices/switch_actuator.py | 45 +++++++++++++++++++ tests/test_dimming_actuator.py | 39 +++++++++++++--- tests/test_switch_actuator.py | 30 +++++++++++++ 4 files changed, 154 insertions(+), 5 deletions(-) diff --git a/src/abbfreeathome/devices/dimming_actuator.py b/src/abbfreeathome/devices/dimming_actuator.py index e4b83e3..8262e1b 100644 --- a/src/abbfreeathome/devices/dimming_actuator.py +++ b/src/abbfreeathome/devices/dimming_actuator.py @@ -11,6 +11,7 @@ class DimmingActuator(Base): """Free@Home DimmingActuator Class.""" _state_refresh_output_pairings: list[Pairing] = [ + Pairing.AL_INFO_FORCE, Pairing.AL_INFO_ON_OFF, Pairing.AL_INFO_ACTUAL_DIMMING_VALUE, ] @@ -31,6 +32,7 @@ def __init__( """Initialize the Free@Home DimmingActuator class.""" self._state: bool | None = None self._brightness: int | None = None + self._forced: int | None = None super().__init__( device_id, @@ -55,6 +57,11 @@ def brightness(self) -> int: """Get the brightness level of the dimmer.""" return int(self._brightness) + @property + def forced(self) -> int | None: + """Get the forced state of the dimmer.""" + return self._forced + async def turn_on(self): """Turn on the dimmer.""" await self._set_switching_datapoint("1") @@ -78,6 +85,24 @@ async def set_brightness(self, value: int): await self._set_brightness_datapoint(str(value)) self._brightness = value + async def set_forced(self, value: int): + """ + Set the forced-option on the dimmer. + + 0 means None + 2 means force off + 3 means force on + """ + if value in (0, 2, 3): + await self._set_force_datapoint(str(value)) + + if value == 0: + self._forced = 0 + elif value == 2: + self._forced = 5 + elif value == 3: + self._forced = 4 + def _refresh_state_from_output(self, output: dict[str, Any]) -> bool: """ Refresh the state of the device from a given output. @@ -90,6 +115,14 @@ def _refresh_state_from_output(self, output: dict[str, Any]) -> bool: if output.get("pairingID") == Pairing.AL_INFO_ACTUAL_DIMMING_VALUE.value: self._brightness = output.get("value") return True + if output.get("pairingID") == Pairing.AL_INFO_FORCE.value: + """ + 0 means off + 4 means force on + 5 means force off + """ + self._forced = int(output.get("value")) + return True return False async def _set_switching_datapoint(self, value: str): @@ -115,3 +148,15 @@ async def _set_brightness_datapoint(self, value: str): datapoint=_brightness_input_id, value=value, ) + + async def _set_force_datapoint(self, value: str): + """Set the force datapoint on the api.""" + _force_input_id, _force_input_value = self.get_input_by_pairing( + pairing=Pairing.AL_FORCED + ) + return await self._api.set_datapoint( + device_id=self.device_id, + channel_id=self.channel_id, + datapoint=_force_input_id, + value=value, + ) diff --git a/src/abbfreeathome/devices/switch_actuator.py b/src/abbfreeathome/devices/switch_actuator.py index d1ef1db..4602ed4 100644 --- a/src/abbfreeathome/devices/switch_actuator.py +++ b/src/abbfreeathome/devices/switch_actuator.py @@ -11,6 +11,7 @@ class SwitchActuator(Base): """Free@Home SwitchActuator Class.""" _state_refresh_output_pairings: list[Pairing] = [ + Pairing.AL_INFO_FORCE, Pairing.AL_INFO_ON_OFF, ] @@ -29,6 +30,7 @@ def __init__( ) -> None: """Initialize the Free@Home SwitchActuator class.""" self._state: bool | None = None + self._forced: int | None = None super().__init__( device_id, @@ -48,6 +50,11 @@ def state(self) -> bool | None: """Get the state of the switch.""" return self._state + @property + def forced(self) -> int | None: + """Get the forced state of the switch.""" + return self._forced + async def turn_on(self): """Turn on the switch.""" await self._set_switching_datapoint("1") @@ -58,6 +65,24 @@ async def turn_off(self): await self._set_switching_datapoint("0") self._state = False + async def set_forced(self, value: int): + """ + Set the forced-option on the switch. + + 0 means None + 2 means force off + 3 means force on + """ + if value in (0, 2, 3): + await self._set_force_datapoint(str(value)) + + if value == 0: + self._forced = 0 + elif value == 2: + self._forced = 5 + elif value == 3: + self._forced = 4 + def _refresh_state_from_output(self, output: dict[str, Any]) -> bool: """ Refresh the state of the device from a given output. @@ -67,6 +92,14 @@ def _refresh_state_from_output(self, output: dict[str, Any]) -> bool: if output.get("pairingID") == Pairing.AL_INFO_ON_OFF.value: self._state = output.get("value") == "1" return True + if output.get("pairingID") == Pairing.AL_INFO_FORCE.value: + """ + 0 means off + 4 means force on + 5 means force off + """ + self._forced = int(output.get("value")) + return True return False async def _set_switching_datapoint(self, value: str): @@ -80,3 +113,15 @@ async def _set_switching_datapoint(self, value: str): datapoint=_switch_input_id, value=value, ) + + async def _set_force_datapoint(self, value: str): + """Set the force datapoint on the api.""" + _force_input_id, _force_input_value = self.get_input_by_pairing( + pairing=Pairing.AL_FORCED + ) + return await self._api.set_datapoint( + device_id=self.device_id, + channel_id=self.channel_id, + datapoint=_force_input_id, + value=value, + ) diff --git a/tests/test_dimming_actuator.py b/tests/test_dimming_actuator.py index e7dcc87..08f91af 100644 --- a/tests/test_dimming_actuator.py +++ b/tests/test_dimming_actuator.py @@ -20,10 +20,12 @@ def dimming_actuator(mock_api): inputs = { "idp0000": {"pairingID": 1, "value": "0"}, "idp0002": {"pairingID": 17, "value": "50"}, + "idp0004": {"pairingID": 3, "value": "0"}, } outputs = { "odp0000": {"pairingID": 256, "value": "0"}, "odp0001": {"pairingID": 272, "value": "50"}, + "odp0002": {"pairingID": 273, "value": "0"}, "odp0003": {"pairingID": 257, "value": "0"}, } parameters = {} @@ -83,6 +85,35 @@ async def test_set_brightness(dimming_actuator): assert dimming_actuator.brightness == 100 +@pytest.mark.asyncio +async def test_set_forced(dimming_actuator): + """Test to set the forced option of the DimmingActuator.""" + await dimming_actuator.set_forced(0) + assert dimming_actuator.forced == 0 + dimming_actuator._api.set_datapoint.assert_called_with( + device_id="ABB70139AF8A", + channel_id="ch0000", + datapoint="idp0004", + value="0", + ) + await dimming_actuator.set_forced(2) + assert dimming_actuator.forced == 5 + dimming_actuator._api.set_datapoint.assert_called_with( + device_id="ABB70139AF8A", + channel_id="ch0000", + datapoint="idp0004", + value="2", + ) + await dimming_actuator.set_forced(3) + assert dimming_actuator.forced == 4 + dimming_actuator._api.set_datapoint.assert_called_with( + device_id="ABB70139AF8A", + channel_id="ch0000", + datapoint="idp0004", + value="3", + ) + + @pytest.mark.asyncio async def test_refresh_state(dimming_actuator): """Test refreshing the state of the DimmingActuator.""" @@ -110,13 +141,11 @@ def test_refresh_state_from_output(dimming_actuator): ) assert dimming_actuator.brightness == 75 - # Check output that does NOT affect the state, - # ensure state, brightness are unchanged. + # Check output that affects the force-option dimming_actuator._refresh_state_from_output( - output={"pairingID": 257, "value": "0"}, + output={"pairingID": 257, "value": "4"}, ) - assert dimming_actuator.brightness == 75 - assert dimming_actuator.state is True + assert dimming_actuator.forced == 4 def test_update_device(dimming_actuator): diff --git a/tests/test_switch_actuator.py b/tests/test_switch_actuator.py index 586b255..d069b44 100644 --- a/tests/test_switch_actuator.py +++ b/tests/test_switch_actuator.py @@ -27,6 +27,7 @@ def switch_actuator(mock_api): outputs = { "odp0000": {"pairingID": 256, "value": "0"}, "odp0001": {"pairingID": 257, "value": "0"}, + "odp0004": {"pairingID": 273, "value": "0"}, } parameters = {} @@ -74,6 +75,35 @@ async def test_turn_off(switch_actuator): ) +@pytest.mark.asyncio +async def test_set_forced(switch_actuator): + """Test to set the forced option of the switch.""" + await switch_actuator.set_forced(0) + assert switch_actuator.forced == 0 + switch_actuator._api.set_datapoint.assert_called_with( + device_id="ABB7F500E17A", + channel_id="ch0003", + datapoint="idp0002", + value="0", + ) + await switch_actuator.set_forced(2) + assert switch_actuator.forced == 5 + switch_actuator._api.set_datapoint.assert_called_with( + device_id="ABB7F500E17A", + channel_id="ch0003", + datapoint="idp0002", + value="2", + ) + await switch_actuator.set_forced(3) + assert switch_actuator.forced == 4 + switch_actuator._api.set_datapoint.assert_called_with( + device_id="ABB7F500E17A", + channel_id="ch0003", + datapoint="idp0002", + value="3", + ) + + @pytest.mark.asyncio async def test_refresh_state(switch_actuator): """Test refreshing the state of the switch."""