From a691846b5db9b6ac8b9fd8197dde62bb54f1ccd9 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 26 Jun 2023 20:29:28 +0200 Subject: [PATCH] Use entity registry id in climate device conditions (#95252) --- .../components/climate/device_condition.py | 26 ++- .../climate/test_device_condition.py | 199 ++++++++++++++++-- 2 files changed, 203 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/climate/device_condition.py b/homeassistant/components/climate/device_condition.py index 97dc27cfa09..d9f1b240a9a 100644 --- a/homeassistant/components/climate/device_condition.py +++ b/homeassistant/components/climate/device_condition.py @@ -3,6 +3,9 @@ from __future__ import annotations import voluptuous as vol +from homeassistant.components.device_automation import ( + async_get_entity_registry_entry_or_raise, +) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_CONDITION, @@ -28,7 +31,7 @@ CONDITION_TYPES = {"is_hvac_mode", "is_preset_mode"} HVAC_MODE_CONDITION = DEVICE_CONDITION_BASE_SCHEMA.extend( { - vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid, vol.Required(CONF_TYPE): "is_hvac_mode", vol.Required(const.ATTR_HVAC_MODE): vol.In(const.HVAC_MODES), } @@ -36,7 +39,7 @@ HVAC_MODE_CONDITION = DEVICE_CONDITION_BASE_SCHEMA.extend( PRESET_MODE_CONDITION = DEVICE_CONDITION_BASE_SCHEMA.extend( { - vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid, vol.Required(CONF_TYPE): "is_preset_mode", vol.Required(const.ATTR_PRESET_MODE): str, } @@ -63,7 +66,7 @@ async def async_get_conditions( CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, - CONF_ENTITY_ID: entry.entity_id, + CONF_ENTITY_ID: entry.id, } conditions.append({**base_condition, CONF_TYPE: "is_hvac_mode"}) @@ -80,9 +83,12 @@ def async_condition_from_config( ) -> condition.ConditionCheckerType: """Create a function to test a device condition.""" + registry = er.async_get(hass) + entity_id = er.async_resolve_entity_id(registry, config[ATTR_ENTITY_ID]) + def test_is_state(hass: HomeAssistant, variables: TemplateVarsType) -> bool: """Test if an entity is a certain state.""" - if (state := hass.states.get(config[ATTR_ENTITY_ID])) is None: + if not entity_id or (state := hass.states.get(entity_id)) is None: return False if config[CONF_TYPE] == "is_hvac_mode": @@ -106,9 +112,11 @@ async def async_get_condition_capabilities( if condition_type == "is_hvac_mode": try: + entry = async_get_entity_registry_entry_or_raise( + hass, config[CONF_ENTITY_ID] + ) hvac_modes = ( - get_capability(hass, config[ATTR_ENTITY_ID], const.ATTR_HVAC_MODES) - or [] + get_capability(hass, entry.entity_id, const.ATTR_HVAC_MODES) or [] ) except HomeAssistantError: hvac_modes = [] @@ -116,9 +124,11 @@ async def async_get_condition_capabilities( elif condition_type == "is_preset_mode": try: + entry = async_get_entity_registry_entry_or_raise( + hass, config[CONF_ENTITY_ID] + ) preset_modes = ( - get_capability(hass, config[ATTR_ENTITY_ID], const.ATTR_PRESET_MODES) - or [] + get_capability(hass, entry.entity_id, const.ATTR_PRESET_MODES) or [] ) except HomeAssistantError: preset_modes = [] diff --git a/tests/components/climate/test_device_condition.py b/tests/components/climate/test_device_condition.py index 20bbe05386f..33df78bf347 100644 --- a/tests/components/climate/test_device_condition.py +++ b/tests/components/climate/test_device_condition.py @@ -69,7 +69,7 @@ async def test_get_conditions( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) - entity_registry.async_get_or_create( + entity_entry = entity_registry.async_get_or_create( DOMAIN, "test", "5678", @@ -87,7 +87,7 @@ async def test_get_conditions( "domain": DOMAIN, "type": condition, "device_id": device_entry.id, - "entity_id": f"{DOMAIN}.test_5678", + "entity_id": entity_entry.id, "metadata": {"secondary": False}, } for condition in expected_condition_types @@ -121,7 +121,7 @@ async def test_get_conditions_hidden_auxiliary( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) - entity_registry.async_get_or_create( + entity_entry = entity_registry.async_get_or_create( DOMAIN, "test", "5678", @@ -135,7 +135,7 @@ async def test_get_conditions_hidden_auxiliary( "domain": DOMAIN, "type": condition, "device_id": device_entry.id, - "entity_id": f"{DOMAIN}.test_5678", + "entity_id": entity_entry.id, "metadata": {"secondary": True}, } for condition in ["is_hvac_mode"] @@ -146,8 +146,12 @@ async def test_get_conditions_hidden_auxiliary( assert conditions == unordered(expected_conditions) -async def test_if_state(hass: HomeAssistant, calls) -> None: +async def test_if_state( + hass: HomeAssistant, entity_registry: er.EntityRegistry, calls +) -> None: """Test for turn_on and turn_off conditions.""" + entry = entity_registry.async_get_or_create(DOMAIN, "test", "5678") + assert await async_setup_component( hass, automation.DOMAIN, @@ -160,7 +164,7 @@ async def test_if_state(hass: HomeAssistant, calls) -> None: "condition": "device", "domain": DOMAIN, "device_id": "", - "entity_id": "climate.entity", + "entity_id": entry.id, "type": "is_hvac_mode", "hvac_mode": "cool", } @@ -182,7 +186,7 @@ async def test_if_state(hass: HomeAssistant, calls) -> None: "condition": "device", "domain": DOMAIN, "device_id": "", - "entity_id": "climate.entity", + "entity_id": entry.id, "type": "is_preset_mode", "preset_mode": "away", } @@ -207,7 +211,7 @@ async def test_if_state(hass: HomeAssistant, calls) -> None: assert len(calls) == 0 hass.states.async_set( - "climate.entity", + entry.entity_id, HVACMode.COOL, { const.ATTR_PRESET_MODE: const.PRESET_AWAY, @@ -220,7 +224,7 @@ async def test_if_state(hass: HomeAssistant, calls) -> None: assert calls[0].data["some"] == "is_hvac_mode - event - test_event1" hass.states.async_set( - "climate.entity", + entry.entity_id, HVACMode.AUTO, { const.ATTR_PRESET_MODE: const.PRESET_AWAY, @@ -239,7 +243,7 @@ async def test_if_state(hass: HomeAssistant, calls) -> None: assert calls[1].data["some"] == "is_preset_mode - event - test_event2" hass.states.async_set( - "climate.entity", + entry.entity_id, HVACMode.AUTO, { const.ATTR_PRESET_MODE: const.PRESET_HOME, @@ -252,6 +256,54 @@ async def test_if_state(hass: HomeAssistant, calls) -> None: assert len(calls) == 2 +async def test_if_state_legacy( + hass: HomeAssistant, entity_registry: er.EntityRegistry, calls +) -> None: + """Test for turn_on and turn_off conditions.""" + entry = entity_registry.async_get_or_create(DOMAIN, "test", "5678") + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": {"platform": "event", "event_type": "test_event1"}, + "condition": [ + { + "condition": "device", + "domain": DOMAIN, + "device_id": "", + "entity_id": entry.entity_id, + "type": "is_hvac_mode", + "hvac_mode": "cool", + } + ], + "action": { + "service": "test.automation", + "data_template": { + "some": ( + "is_hvac_mode - {{ trigger.platform }} " + "- {{ trigger.event.event_type }}" + ) + }, + }, + }, + ] + }, + ) + + hass.states.async_set( + entry.entity_id, + HVACMode.COOL, + ) + + hass.bus.async_fire("test_event1") + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["some"] == "is_hvac_mode - event - test_event1" + + @pytest.mark.parametrize( ( "set_state", @@ -336,7 +388,7 @@ async def test_capabilities( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) - entity_registry.async_get_or_create( + entity_entry = entity_registry.async_get_or_create( DOMAIN, "test", "5678", @@ -345,7 +397,7 @@ async def test_capabilities( ) if set_state: hass.states.async_set( - f"{DOMAIN}.test_5678", + entity_entry.entity_id, HVACMode.COOL, capabilities_state, ) @@ -356,7 +408,126 @@ async def test_capabilities( "condition": "device", "domain": DOMAIN, "device_id": "abcdefgh", - "entity_id": f"{DOMAIN}.test_5678", + "entity_id": entity_entry.id, + "type": condition, + }, + ) + + assert capabilities and "extra_fields" in capabilities + + assert ( + voluptuous_serialize.convert( + capabilities["extra_fields"], custom_serializer=cv.custom_serializer + ) + == expected_capabilities + ) + + +@pytest.mark.parametrize( + ( + "set_state", + "capabilities_reg", + "capabilities_state", + "condition", + "expected_capabilities", + ), + [ + ( + False, + {const.ATTR_HVAC_MODES: [HVACMode.COOL, HVACMode.OFF]}, + {}, + "is_hvac_mode", + [ + { + "name": "hvac_mode", + "options": [("cool", "cool"), ("off", "off")], + "required": True, + "type": "select", + } + ], + ), + ( + False, + {const.ATTR_PRESET_MODES: [const.PRESET_HOME, const.PRESET_AWAY]}, + {}, + "is_preset_mode", + [ + { + "name": "preset_mode", + "options": [("home", "home"), ("away", "away")], + "required": True, + "type": "select", + } + ], + ), + ( + True, + {}, + {const.ATTR_HVAC_MODES: [HVACMode.COOL, HVACMode.OFF]}, + "is_hvac_mode", + [ + { + "name": "hvac_mode", + "options": [("cool", "cool"), ("off", "off")], + "required": True, + "type": "select", + } + ], + ), + ( + True, + {}, + {const.ATTR_PRESET_MODES: [const.PRESET_HOME, const.PRESET_AWAY]}, + "is_preset_mode", + [ + { + "name": "preset_mode", + "options": [("home", "home"), ("away", "away")], + "required": True, + "type": "select", + } + ], + ), + ], +) +async def test_capabilities_legacy( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + set_state, + capabilities_reg, + capabilities_state, + condition, + expected_capabilities, +) -> None: + """Test getting capabilities.""" + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_entry = entity_registry.async_get_or_create( + DOMAIN, + "test", + "5678", + device_id=device_entry.id, + capabilities=capabilities_reg, + ) + if set_state: + hass.states.async_set( + entity_entry.entity_id, + HVACMode.COOL, + capabilities_state, + ) + + capabilities = await device_condition.async_get_condition_capabilities( + hass, + { + "condition": "device", + "domain": DOMAIN, + "device_id": "abcdefgh", + "entity_id": entity_entry.entity_id, "type": condition, }, ) @@ -388,7 +559,7 @@ async def test_capabilities_missing_entity( "condition": "device", "domain": DOMAIN, "device_id": "abcdefgh", - "entity_id": f"{DOMAIN}.test_5678", + "entity_id": "01234567890123456789012345678901", "type": condition, }, )