diff --git a/kasa/feature.py b/kasa/feature.py index c1bbc97b..ffa3df44 100644 --- a/kasa/feature.py +++ b/kasa/feature.py @@ -17,7 +17,7 @@ class FeatureType(Enum): Sensor = auto() BinarySensor = auto() Switch = auto() - Button = auto() + Action = auto() Number = auto() @@ -46,7 +46,7 @@ class Feature: #: User-friendly short description name: str #: Name of the property that allows accessing the value - attribute_getter: str | Callable + attribute_getter: str | Callable | None = None #: Name of the method that allows changing the value attribute_setter: str | None = None #: Container storing the data, this overrides 'device' for getters @@ -95,6 +95,11 @@ class Feature: @property def value(self): """Return the current value.""" + if self.type == FeatureType.Action: + return "" + if self.attribute_getter is None: + raise ValueError("Not an action and no attribute_getter set") + container = self.container if self.container is not None else self.device if isinstance(self.attribute_getter, Callable): return self.attribute_getter(container) @@ -112,6 +117,9 @@ class Feature: ) container = self.container if self.container is not None else self.device + if self.type == FeatureType.Action: + return await getattr(container, self.attribute_setter)() + return await getattr(container, self.attribute_setter)(value) def __repr__(self): diff --git a/kasa/smart/modules/alarmmodule.py b/kasa/smart/modules/alarmmodule.py index 66790326..30e432f4 100644 --- a/kasa/smart/modules/alarmmodule.py +++ b/kasa/smart/modules/alarmmodule.py @@ -54,6 +54,24 @@ class AlarmModule(SmartModule): device, "Alarm volume", container=self, attribute_getter="alarm_volume" ) ) + self._add_feature( + Feature( + device, + "Test alarm", + container=self, + attribute_setter="play", + type=FeatureType.Action, + ) + ) + self._add_feature( + Feature( + device, + "Stop alarm", + container=self, + attribute_setter="stop", + type=FeatureType.Action, + ) + ) @property def alarm_sound(self): @@ -83,8 +101,8 @@ class AlarmModule(SmartModule): async def play(self): """Play alarm.""" - return self.call("play_alarm") + return await self.call("play_alarm") async def stop(self): """Stop alarm.""" - return self.call("stop_alarm") + return await self.call("stop_alarm") diff --git a/kasa/tests/test_feature.py b/kasa/tests/test_feature.py index b37c38e9..db2d27a8 100644 --- a/kasa/tests/test_feature.py +++ b/kasa/tests/test_feature.py @@ -3,11 +3,13 @@ import pytest from kasa import Feature, FeatureType +class DummyDevice: + pass + + @pytest.fixture def dummy_feature() -> Feature: # create_autospec for device slows tests way too much, so we use a dummy here - class DummyDevice: - pass feat = Feature( device=DummyDevice(), # type: ignore[arg-type] @@ -79,3 +81,19 @@ async def test_feature_setter_read_only(dummy_feature): dummy_feature.attribute_setter = None with pytest.raises(ValueError): await dummy_feature.set_value("value for read only feature") + + +async def test_feature_action(mocker): + """Test that setting value on button calls the setter.""" + feat = Feature( + device=DummyDevice(), # type: ignore[arg-type] + name="dummy_feature", + attribute_setter="call_action", + container=None, + icon="mdi:dummy", + type=FeatureType.Action, + ) + mock_call_action = mocker.patch.object(feat.device, "call_action", create=True) + assert feat.value == "" + await feat.set_value(1234) + mock_call_action.assert_called()