From cff6883b5cb58353971453f4f37dbb718c1576b2 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 17 Aug 2021 12:22:27 -0400 Subject: [PATCH] Add zwave_js Protection CC select entities (#54717) * Add Protection CC select entities comment * Disable entity by default * use class attribute * Enable protection entity by default * add guard for none --- .../components/zwave_js/discovery.py | 10 ++ homeassistant/components/zwave_js/select.py | 36 +++++++ tests/components/zwave_js/test_select.py | 100 ++++++++++++++++++ 3 files changed, 146 insertions(+) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index dcae65b0395b80..d59a3d935a016d 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -646,6 +646,16 @@ def get_config_parameter_discovery_schema( ), required_values=[SIREN_TONE_SCHEMA], ), + # select + # protection CC + ZWaveDiscoverySchema( + platform="select", + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.PROTECTION}, + property={"local", "rf"}, + type={"number"}, + ), + ), ] diff --git a/homeassistant/components/zwave_js/select.py b/homeassistant/components/zwave_js/select.py index 2bd711bfde3185..7aedc6521d934e 100644 --- a/homeassistant/components/zwave_js/select.py +++ b/homeassistant/components/zwave_js/select.py @@ -29,6 +29,8 @@ def async_add_select(info: ZwaveDiscoveryInfo) -> None: entities: list[ZWaveBaseEntity] = [] if info.platform_hint == "Default tone": entities.append(ZwaveDefaultToneSelectEntity(config_entry, client, info)) + else: + entities.append(ZwaveSelectEntity(config_entry, client, info)) async_add_entities(entities) config_entry.async_on_unload( @@ -40,6 +42,40 @@ def async_add_select(info: ZwaveDiscoveryInfo) -> None: ) +class ZwaveSelectEntity(ZWaveBaseEntity, SelectEntity): + """Representation of a Z-Wave select entity.""" + + def __init__( + self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + ) -> None: + """Initialize a ZwaveSelectEntity entity.""" + super().__init__(config_entry, client, info) + + # Entity class attributes + self._attr_name = self.generate_name(include_value_name=True) + self._attr_options = list(self.info.primary_value.metadata.states.values()) + + @property + def current_option(self) -> str | None: + """Return the selected entity option to represent the entity state.""" + if self.info.primary_value.value is None: + return None + return str( + self.info.primary_value.metadata.states.get( + str(self.info.primary_value.value), self.info.primary_value.value + ) + ) + + async def async_select_option(self, option: str | int) -> None: + """Change the selected option.""" + key = next( + key + for key, val in self.info.primary_value.metadata.states.items() + if val == option + ) + await self.info.node.async_set_value(self.info.primary_value, int(key)) + + class ZwaveDefaultToneSelectEntity(ZWaveBaseEntity, SelectEntity): """Representation of a Z-Wave default tone select entity.""" diff --git a/tests/components/zwave_js/test_select.py b/tests/components/zwave_js/test_select.py index b94bac812b6cba..43f44f0bba07da 100644 --- a/tests/components/zwave_js/test_select.py +++ b/tests/components/zwave_js/test_select.py @@ -1,7 +1,10 @@ """Test the Z-Wave JS number platform.""" from zwave_js_server.event import Event +from homeassistant.const import STATE_UNKNOWN + DEFAULT_TONE_SELECT_ENTITY = "select.indoor_siren_6_default_tone_2" +PROTECTION_SELECT_ENTITY = "select.family_room_combo_local_protection_state" async def test_default_tone_select(hass, client, aeotec_zw164_siren, integration): @@ -99,3 +102,100 @@ async def test_default_tone_select(hass, client, aeotec_zw164_siren, integration state = hass.states.get(DEFAULT_TONE_SELECT_ENTITY) assert state.state == "30DOOR~1 (27 sec)" + + +async def test_protection_select(hass, client, inovelli_lzw36, integration): + """Test the default tone select entity.""" + node = inovelli_lzw36 + state = hass.states.get(PROTECTION_SELECT_ENTITY) + + assert state + assert state.state == "Unprotected" + attr = state.attributes + assert attr["options"] == [ + "Unprotected", + "ProtectedBySequence", + "NoOperationPossible", + ] + + # Test select option with string value + await hass.services.async_call( + "select", + "select_option", + {"entity_id": PROTECTION_SELECT_ENTITY, "option": "ProtectedBySequence"}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == node.node_id + assert args["valueId"] == { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "local", + "propertyName": "local", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "label": "Local protection state", + "states": { + "0": "Unprotected", + "1": "ProtectedBySequence", + "2": "NoOperationPossible", + }, + }, + "value": 0, + } + assert args["value"] == 1 + + client.async_send_command.reset_mock() + + # Test value update from value updated event + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": node.node_id, + "args": { + "commandClassName": "Protection", + "commandClass": 117, + "endpoint": 0, + "property": "local", + "newValue": 1, + "prevValue": 0, + "propertyName": "local", + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(PROTECTION_SELECT_ENTITY) + assert state.state == "ProtectedBySequence" + + # Test null value + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": node.node_id, + "args": { + "commandClassName": "Protection", + "commandClass": 117, + "endpoint": 0, + "property": "local", + "newValue": None, + "prevValue": 1, + "propertyName": "local", + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(PROTECTION_SELECT_ENTITY) + assert state.state == STATE_UNKNOWN