diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index f9c846c60faacb..586064c03a3957 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -332,15 +332,6 @@ def async_prepare_call_from_config( target.update(conf.async_render(variables)) else: target.update(template.render_complex(conf, variables)) - - if CONF_ENTITY_ID in target: - registry = entity_registry.async_get(hass) - entity_ids = cv.comp_entity_ids_or_uuids(target[CONF_ENTITY_ID]) - if entity_ids not in (ENTITY_MATCH_ALL, ENTITY_MATCH_NONE): - entity_ids = entity_registry.async_validate_entity_ids( - registry, entity_ids - ) - target[CONF_ENTITY_ID] = entity_ids except TemplateError as ex: raise HomeAssistantError( f"Error rendering service target template: {ex}" @@ -350,6 +341,30 @@ def async_prepare_call_from_config( f"Template rendered invalid entity IDs: {target[CONF_ENTITY_ID]}" ) from ex + # All config entries but CONF_ENTITY_ID use ensure_list. + # Using ensure_list transforms a template value to a singleton list + # Since we expect a list of strings, a template that evaluates to + # a list will lead to a nested list. + # We cannot easily fix this in schema without breaking old configs. + # This block flattens nested lists but keeps non-list members as is. + for entry, value in target.items(): + if entry == CONF_ENTITY_ID: + registry = entity_registry.async_get(hass) + entity_ids = cv.comp_entity_ids_or_uuids(value) + if entity_ids not in (ENTITY_MATCH_ALL, ENTITY_MATCH_NONE): + entity_ids = entity_registry.async_validate_entity_ids( + registry, entity_ids + ) + target[entry] = entity_ids + else: + ret = [] + for elem in value: + if isinstance(elem, list): + ret.extend(elem) + else: + ret.append(elem) + target[entry] = ret + service_data = {} for conf in (CONF_SERVICE_DATA, CONF_SERVICE_DATA_TEMPLATE): diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 8f0945369882c4..196f847e53ae6a 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -496,6 +496,26 @@ async def test_service_call(hass: HomeAssistant) -> None: "entity_id": ["light.static"], } + config = { + "action": "{{ 'test_domain.test_service' }}", + "target": { + "area_id": ["area-42", "{{ 'area-51' }}"], + "device_id": "{{ ['abcdef', 'fedcba'] }}", + "entity_id": ["light.static", "{{ 'light.dynamic' }}"], + "floor_id": ["floor-first", "{{ 'floor-second' }}"], + }, + } + + await service.async_call_from_config(hass, config) + await hass.async_block_till_done() + + assert dict(calls[3].data) == { + "area_id": ["area-42", "area-51"], + "device_id": ["abcdef", "fedcba"], + "entity_id": ["light.static", "light.dynamic"], + "floor_id": ["floor-first", "floor-second"], + } + async def test_service_template_service_call(hass: HomeAssistant) -> None: """Test legacy service_template call with templating."""