From 97cac66195cdb49b61298b3bd0bf03acba955eae Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 10 May 2023 18:52:10 +0200 Subject: [PATCH] Add counter.set_value service (#92863) --- homeassistant/components/counter/__init__.py | 27 ++++++ .../components/counter/services.yaml | 17 ++++ tests/components/counter/test_init.py | 91 ++++++++++++++++++- 3 files changed, 132 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py index 768491f6085..a77e4340882 100644 --- a/homeassistant/components/counter/__init__.py +++ b/homeassistant/components/counter/__init__.py @@ -44,6 +44,7 @@ SERVICE_DECREMENT = "decrement" SERVICE_INCREMENT = "increment" SERVICE_RESET = "reset" SERVICE_CONFIGURE = "configure" +SERVICE_SET_VALUE = "set_value" STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 @@ -124,6 +125,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: component.async_register_entity_service(SERVICE_INCREMENT, {}, "async_increment") component.async_register_entity_service(SERVICE_DECREMENT, {}, "async_decrement") component.async_register_entity_service(SERVICE_RESET, {}, "async_reset") + component.async_register_entity_service( + SERVICE_SET_VALUE, + {vol.Required(VALUE): cv.positive_int}, + "async_set_value", + ) component.async_register_entity_service( SERVICE_CONFIGURE, { @@ -261,6 +267,27 @@ class Counter(collection.CollectionEntity, RestoreEntity): self._state = self.compute_next_state(self._config[CONF_INITIAL]) self.async_write_ha_state() + @callback + def async_set_value(self, value: int) -> None: + """Set counter to value.""" + if (maximum := self._config.get(CONF_MAXIMUM)) is not None and value > maximum: + raise ValueError( + f"Value {value} for {self.entity_id} exceeding the maximum value of {maximum}" + ) + + if (minimum := self._config.get(CONF_MINIMUM)) is not None and value < minimum: + raise ValueError( + f"Value {value} for {self.entity_id} exceeding the minimum value of {minimum}" + ) + + if (step := self._config.get(CONF_STEP)) is not None and value % step != 0: + raise ValueError( + f"Value {value} for {self.entity_id} is not a multiple of the step size {step}" + ) + + self._state = value + self.async_write_ha_state() + @callback def async_configure(self, **kwargs) -> None: """Change the counter's settings with a service.""" diff --git a/homeassistant/components/counter/services.yaml b/homeassistant/components/counter/services.yaml index 1930ba0d45b..d94d05dd7bd 100644 --- a/homeassistant/components/counter/services.yaml +++ b/homeassistant/components/counter/services.yaml @@ -21,6 +21,23 @@ reset: entity: domain: counter +set_value: + name: Set + description: Set the counter value + target: + entity: + domain: counter + fields: + value: + name: Value + required: true + description: The new counter value the entity should be set to. + selector: + number: + min: 0 + max: 9223372036854775807 + mode: box + configure: name: Configure description: Change counter parameters. diff --git a/tests/components/counter/test_init.py b/tests/components/counter/test_init.py index 349695874cf..5c6dbba71ce 100644 --- a/tests/components/counter/test_init.py +++ b/tests/components/counter/test_init.py @@ -11,14 +11,18 @@ from homeassistant.components.counter import ( ATTR_STEP, CONF_ICON, CONF_INITIAL, + CONF_MAXIMUM, + CONF_MINIMUM, CONF_NAME, CONF_RESTORE, CONF_STEP, DEFAULT_INITIAL, DEFAULT_STEP, DOMAIN, + SERVICE_SET_VALUE, + VALUE, ) -from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_ICON, ATTR_NAME +from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_ICON, ATTR_NAME from homeassistant.core import Context, CoreState, HomeAssistant, State from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component @@ -124,7 +128,7 @@ async def test_config_options(hass: HomeAssistant) -> None: async def test_methods(hass: HomeAssistant) -> None: - """Test increment, decrement, and reset methods.""" + """Test increment, decrement, set value, and reset methods.""" config = {DOMAIN: {"test_1": {}}} assert await async_setup_component(hass, "counter", config) @@ -158,11 +162,31 @@ async def test_methods(hass: HomeAssistant) -> None: state = hass.states.get(entity_id) assert int(state.state) == 0 + await hass.services.async_call( + DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: entity_id, + VALUE: 5, + }, + blocking=True, + ) + state = hass.states.get(entity_id) + assert state.state == "5" + async def test_methods_with_config(hass: HomeAssistant) -> None: """Test increment, decrement, and reset methods with configuration.""" config = { - DOMAIN: {"test": {CONF_NAME: "Hello World", CONF_INITIAL: 10, CONF_STEP: 5}} + DOMAIN: { + "test": { + CONF_NAME: "Hello World", + CONF_INITIAL: 10, + CONF_STEP: 5, + CONF_MINIMUM: 5, + CONF_MAXIMUM: 20, + } + } } assert await async_setup_component(hass, "counter", config) @@ -190,6 +214,67 @@ async def test_methods_with_config(hass: HomeAssistant) -> None: state = hass.states.get(entity_id) assert int(state.state) == 15 + await hass.services.async_call( + DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: entity_id, + VALUE: 5, + }, + blocking=True, + ) + state = hass.states.get(entity_id) + assert state.state == "5" + + with pytest.raises( + ValueError, match=r"Value 25 for counter.test exceeding the maximum value of 20" + ): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: entity_id, + VALUE: 25, + }, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.state == "5" + + with pytest.raises( + ValueError, match=r"Value 0 for counter.test exceeding the minimum value of 5" + ): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: entity_id, + VALUE: 0, + }, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.state == "5" + + with pytest.raises( + ValueError, + match=r"Value 6 for counter.test is not a multiple of the step size 5", + ): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: entity_id, + VALUE: 6, + }, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.state == "5" + async def test_initial_state_overrules_restore_state(hass: HomeAssistant) -> None: """Ensure states are restored on startup."""