diff --git a/homeassistant/components/template/alarm_control_panel.py b/homeassistant/components/template/alarm_control_panel.py index b7ad219eff7..4209388ae8a 100644 --- a/homeassistant/components/template/alarm_control_panel.py +++ b/homeassistant/components/template/alarm_control_panel.py @@ -17,6 +17,7 @@ from homeassistant.components.alarm_control_panel.const import ( from homeassistant.const import ( ATTR_CODE, CONF_NAME, + CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, MATCH_ALL, @@ -62,6 +63,7 @@ ALARM_CONTROL_PANEL_SCHEMA = vol.Schema( vol.Optional(CONF_ARM_NIGHT_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean, vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_UNIQUE_ID): cv.string, } ) @@ -86,6 +88,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= arm_home_action = device_config.get(CONF_ARM_HOME_ACTION) arm_night_action = device_config.get(CONF_ARM_NIGHT_ACTION) code_arm_required = device_config[CONF_CODE_ARM_REQUIRED] + unique_id = device_config.get(CONF_UNIQUE_ID) template_entity_ids = set() @@ -111,6 +114,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= arm_night_action, code_arm_required, template_entity_ids, + unique_id, ) ) @@ -132,6 +136,7 @@ class AlarmControlPanelTemplate(AlarmControlPanelEntity): arm_night_action, code_arm_required, template_entity_ids, + unique_id, ): """Initialize the panel.""" self.hass = hass @@ -156,6 +161,7 @@ class AlarmControlPanelTemplate(AlarmControlPanelEntity): self._state = None self._entities = template_entity_ids + self._unique_id = unique_id if self._template is not None: self._template.hass = self.hass @@ -165,6 +171,11 @@ class AlarmControlPanelTemplate(AlarmControlPanelEntity): """Return the display name of this alarm control panel.""" return self._name + @property + def unique_id(self): + """Return the unique id of this alarm control panel.""" + return self._unique_id + @property def should_poll(self): """Return the polling state.""" diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index 101651fabd5..22eb8b9d242 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -16,6 +16,7 @@ from homeassistant.const import ( CONF_ENTITY_PICTURE_TEMPLATE, CONF_ICON_TEMPLATE, CONF_SENSORS, + CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, MATCH_ALL, @@ -50,6 +51,7 @@ SENSOR_SCHEMA = vol.Schema( vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DELAY_ON): vol.All(cv.time_period, cv.positive_timedelta), vol.Optional(CONF_DELAY_OFF): vol.All(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_UNIQUE_ID): cv.string, } ) @@ -73,6 +75,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= device_class = device_config.get(CONF_DEVICE_CLASS) delay_on = device_config.get(CONF_DELAY_ON) delay_off = device_config.get(CONF_DELAY_OFF) + unique_id = device_config.get(CONF_UNIQUE_ID) templates = { CONF_VALUE_TEMPLATE: value_template, @@ -104,6 +107,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= delay_on, delay_off, attribute_templates, + unique_id, ) ) @@ -127,6 +131,7 @@ class BinarySensorTemplate(BinarySensorEntity): delay_on, delay_off, attribute_templates, + unique_id, ): """Initialize the Template binary sensor.""" self.hass = hass @@ -146,6 +151,7 @@ class BinarySensorTemplate(BinarySensorEntity): self._available = True self._attribute_templates = attribute_templates self._attributes = {} + self._unique_id = unique_id async def async_added_to_hass(self): """Register callbacks.""" @@ -175,6 +181,11 @@ class BinarySensorTemplate(BinarySensorEntity): """Return the name of the sensor.""" return self._name + @property + def unique_id(self): + """Return the unique id of this binary sensor.""" + return self._unique_id + @property def icon(self): """Return the icon to use in the frontend, if any.""" diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index e2f67acf2bd..08dd18ae3a4 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -26,6 +26,7 @@ from homeassistant.const import ( CONF_FRIENDLY_NAME, CONF_ICON_TEMPLATE, CONF_OPTIMISTIC, + CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, MATCH_ALL, @@ -90,6 +91,7 @@ COVER_SCHEMA = vol.All( vol.Optional(TILT_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_FRIENDLY_NAME): cv.string, vol.Optional(CONF_ENTITY_ID): cv.entity_ids, + vol.Optional(CONF_UNIQUE_ID): cv.string, } ), cv.has_at_least_one_key(OPEN_ACTION, POSITION_ACTION), @@ -121,6 +123,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= tilt_action = device_config.get(TILT_ACTION) optimistic = device_config.get(CONF_OPTIMISTIC) tilt_optimistic = device_config.get(CONF_TILT_OPTIMISTIC) + unique_id = device_config.get(CONF_UNIQUE_ID) templates = { CONF_VALUE_TEMPLATE: state_template, @@ -156,6 +159,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= optimistic, tilt_optimistic, entity_ids, + unique_id, ) ) @@ -185,6 +189,7 @@ class CoverTemplate(CoverEntity): optimistic, tilt_optimistic, entity_ids, + unique_id, ): """Initialize the Template cover.""" self.hass = hass @@ -222,6 +227,7 @@ class CoverTemplate(CoverEntity): self._tilt_value = None self._entities = entity_ids self._available = True + self._unique_id = unique_id async def async_added_to_hass(self): """Register callbacks.""" @@ -251,6 +257,11 @@ class CoverTemplate(CoverEntity): """Return the name of the cover.""" return self._name + @property + def unique_id(self): + """Return the unique id of this cover.""" + return self._unique_id + @property def is_closed(self): """Return if the cover is closed.""" diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index 1f5c433bf89..a6a0f6b8135 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -21,6 +21,7 @@ from homeassistant.components.fan import ( from homeassistant.const import ( CONF_ENTITY_ID, CONF_FRIENDLY_NAME, + CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, MATCH_ALL, @@ -72,6 +73,7 @@ FAN_SCHEMA = vol.Schema( CONF_SPEED_LIST, default=[SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] ): cv.ensure_list, vol.Optional(CONF_ENTITY_ID): cv.entity_ids, + vol.Optional(CONF_UNIQUE_ID): cv.string, } ) @@ -100,6 +102,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= set_direction_action = device_config.get(CONF_SET_DIRECTION_ACTION) speed_list = device_config[CONF_SPEED_LIST] + unique_id = device_config.get(CONF_UNIQUE_ID) templates = { CONF_VALUE_TEMPLATE: state_template, @@ -129,6 +132,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= set_direction_action, speed_list, entity_ids, + unique_id, ) ) @@ -155,6 +159,7 @@ class TemplateFan(FanEntity): set_direction_action, speed_list, entity_ids, + unique_id, ): """Initialize the fan.""" self.hass = hass @@ -199,6 +204,8 @@ class TemplateFan(FanEntity): self._supported_features |= SUPPORT_DIRECTION self._entities = entity_ids + self._unique_id = unique_id + # List of valid speeds self._speed_list = speed_list @@ -207,6 +214,11 @@ class TemplateFan(FanEntity): """Return the display name of this fan.""" return self._name + @property + def unique_id(self): + """Return the unique id of this fan.""" + return self._unique_id + @property def supported_features(self) -> int: """Flag supported features.""" diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 6832ca04017..b85aa6f3a95 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -21,6 +21,7 @@ from homeassistant.const import ( CONF_FRIENDLY_NAME, CONF_ICON_TEMPLATE, CONF_LIGHTS, + CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, MATCH_ALL, @@ -70,6 +71,7 @@ LIGHT_SCHEMA = vol.Schema( vol.Optional(CONF_COLOR_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_WHITE_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_WHITE_VALUE_ACTION): cv.SCRIPT_SCHEMA, + vol.Optional(CONF_UNIQUE_ID): cv.string, } ) @@ -89,6 +91,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= icon_template = device_config.get(CONF_ICON_TEMPLATE) entity_picture_template = device_config.get(CONF_ENTITY_PICTURE_TEMPLATE) availability_template = device_config.get(CONF_AVAILABILITY_TEMPLATE) + unique_id = device_config.get(CONF_UNIQUE_ID) on_action = device_config[CONF_ON_ACTION] off_action = device_config[CONF_OFF_ACTION] @@ -141,6 +144,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= color_template, white_value_action, white_value_template, + unique_id, ) ) @@ -170,6 +174,7 @@ class LightTemplate(LightEntity): color_template, white_value_action, white_value_template, + unique_id, ): """Initialize the light.""" self.hass = hass @@ -209,6 +214,7 @@ class LightTemplate(LightEntity): self._white_value = None self._entities = entity_ids self._available = True + self._unique_id = unique_id @property def brightness(self): @@ -235,6 +241,11 @@ class LightTemplate(LightEntity): """Return the display name of this light.""" return self._name + @property + def unique_id(self): + """Return the unique id of this light.""" + return self._unique_id + @property def supported_features(self): """Flag supported features.""" diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index 0d8cdd7d290..07aeda70be1 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -7,6 +7,7 @@ from homeassistant.components.lock import PLATFORM_SCHEMA, LockEntity from homeassistant.const import ( CONF_NAME, CONF_OPTIMISTIC, + CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, MATCH_ALL, @@ -38,6 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( vol.Required(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_AVAILABILITY_TEMPLATE): cv.template, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, + vol.Optional(CONF_UNIQUE_ID): cv.string, } ) @@ -67,6 +69,7 @@ async def async_setup_platform(hass, config, async_add_devices, discovery_info=N config.get(CONF_LOCK), config.get(CONF_UNLOCK), config.get(CONF_OPTIMISTIC), + config.get(CONF_UNIQUE_ID), ) ] ) @@ -85,6 +88,7 @@ class TemplateLock(LockEntity): command_lock, command_unlock, optimistic, + unique_id, ): """Initialize the lock.""" self._state = None @@ -97,6 +101,7 @@ class TemplateLock(LockEntity): self._command_unlock = Script(hass, command_unlock) self._optimistic = optimistic self._available = True + self._unique_id = unique_id async def async_added_to_hass(self): """Register callbacks.""" @@ -135,6 +140,11 @@ class TemplateLock(LockEntity): """Return the name of the lock.""" return self._name + @property + def unique_id(self): + """Return the unique id of this lock.""" + return self._unique_id + @property def is_locked(self): """Return true if lock is locked.""" diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index d4977d626ca..53736050ed3 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -18,6 +18,7 @@ from homeassistant.const import ( CONF_FRIENDLY_NAME_TEMPLATE, CONF_ICON_TEMPLATE, CONF_SENSORS, + CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, MATCH_ALL, @@ -49,6 +50,7 @@ SENSOR_SCHEMA = vol.Schema( vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(CONF_UNIQUE_ID): cv.string, } ) @@ -71,6 +73,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= unit_of_measurement = device_config.get(ATTR_UNIT_OF_MEASUREMENT) device_class = device_config.get(CONF_DEVICE_CLASS) attribute_templates = device_config[CONF_ATTRIBUTE_TEMPLATES] + unique_id = device_config.get(CONF_UNIQUE_ID) templates = { CONF_VALUE_TEMPLATE: state_template, @@ -103,6 +106,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= entity_ids, device_class, attribute_templates, + unique_id, ) ) @@ -128,6 +132,7 @@ class SensorTemplate(Entity): entity_ids, device_class, attribute_templates, + unique_id, ): """Initialize the sensor.""" self.hass = hass @@ -149,6 +154,7 @@ class SensorTemplate(Entity): self._available = True self._attribute_templates = attribute_templates self._attributes = {} + self._unique_id = unique_id async def async_added_to_hass(self): """Register callbacks.""" @@ -178,6 +184,11 @@ class SensorTemplate(Entity): """Return the name of the sensor.""" return self._name + @property + def unique_id(self): + """Return the unique id of this sensor.""" + return self._unique_id + @property def state(self): """Return the state of the sensor.""" diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 124d12d194f..f9b07fa1dec 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -14,6 +14,7 @@ from homeassistant.const import ( CONF_ENTITY_PICTURE_TEMPLATE, CONF_ICON_TEMPLATE, CONF_SWITCHES, + CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, MATCH_ALL, @@ -47,6 +48,7 @@ SWITCH_SCHEMA = vol.Schema( vol.Required(OFF_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(ATTR_FRIENDLY_NAME): cv.string, vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(CONF_UNIQUE_ID): cv.string, } ) @@ -67,6 +69,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= availability_template = device_config.get(CONF_AVAILABILITY_TEMPLATE) on_action = device_config[ON_ACTION] off_action = device_config[OFF_ACTION] + unique_id = device_config.get(CONF_UNIQUE_ID) templates = { CONF_VALUE_TEMPLATE: state_template, @@ -92,6 +95,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= on_action, off_action, entity_ids, + unique_id, ) ) @@ -113,6 +117,7 @@ class SwitchTemplate(SwitchEntity, RestoreEntity): on_action, off_action, entity_ids, + unique_id, ): """Initialize the Template switch.""" self.hass = hass @@ -131,6 +136,7 @@ class SwitchTemplate(SwitchEntity, RestoreEntity): self._entity_picture = None self._entities = entity_ids self._available = True + self._unique_id = unique_id async def async_added_to_hass(self): """Register callbacks.""" @@ -172,6 +178,11 @@ class SwitchTemplate(SwitchEntity, RestoreEntity): """Return the name of the switch.""" return self._name + @property + def unique_id(self): + """Return the unique id of this switch.""" + return self._unique_id + @property def is_on(self): """Return true if device is on.""" diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index 1209e617a7e..a61a1690e5a 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -33,6 +33,7 @@ from homeassistant.components.vacuum import ( from homeassistant.const import ( CONF_ENTITY_ID, CONF_FRIENDLY_NAME, + CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_START, MATCH_ALL, @@ -84,6 +85,7 @@ VACUUM_SCHEMA = vol.Schema( vol.Optional(SERVICE_SET_FAN_SPEED): cv.SCRIPT_SCHEMA, vol.Optional(CONF_FAN_SPEED_LIST, default=[]): cv.ensure_list, vol.Optional(CONF_ENTITY_ID): cv.entity_ids, + vol.Optional(CONF_UNIQUE_ID): cv.string, } ) @@ -114,6 +116,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= set_fan_speed_action = device_config.get(SERVICE_SET_FAN_SPEED) fan_speed_list = device_config[CONF_FAN_SPEED_LIST] + unique_id = device_config.get(CONF_UNIQUE_ID) templates = { CONF_VALUE_TEMPLATE: state_template, @@ -146,6 +149,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= fan_speed_list, entity_ids, attribute_templates, + unique_id, ) ) @@ -174,6 +178,7 @@ class TemplateVacuum(StateVacuumEntity): fan_speed_list, entity_ids, attribute_templates, + unique_id, ): """Initialize the vacuum.""" self.hass = hass @@ -233,6 +238,8 @@ class TemplateVacuum(StateVacuumEntity): self._supported_features |= SUPPORT_BATTERY self._entities = entity_ids + self._unique_id = unique_id + # List of valid fan speeds self._fan_speed_list = fan_speed_list @@ -241,6 +248,11 @@ class TemplateVacuum(StateVacuumEntity): """Return the display name of this vacuum.""" return self._name + @property + def unique_id(self): + """Return the unique id of this vacuum.""" + return self._unique_id + @property def supported_features(self) -> int: """Flag supported features.""" diff --git a/homeassistant/const.py b/homeassistant/const.py index 22e24e69c25..cedfced2f7c 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -171,6 +171,7 @@ CONF_TOKEN = "token" CONF_TRIGGER_TIME = "trigger_time" CONF_TTL = "ttl" CONF_TYPE = "type" +CONF_UNIQUE_ID = "unique_id" CONF_UNIT_OF_MEASUREMENT = "unit_of_measurement" CONF_UNIT_SYSTEM = "unit_system" CONF_UNTIL = "until" diff --git a/tests/components/template/test_alarm_control_panel.py b/tests/components/template/test_alarm_control_panel.py index 1126f6b60a1..e27359fd56e 100644 --- a/tests/components/template/test_alarm_control_panel.py +++ b/tests/components/template/test_alarm_control_panel.py @@ -586,3 +586,32 @@ async def test_disarm_action(hass): await hass.async_block_till_done() assert len(service_calls) == 1 + + +async def test_unique_id(hass): + """Test unique_id option only creates one alarm control panel per id.""" + await setup.async_setup_component( + hass, + "alarm_control_panel", + { + "alarm_control_panel": { + "platform": "template", + "panels": { + "test_template_alarm_control_panel_01": { + "unique_id": "not-so-unique-anymore", + "value_template": "{{ true }}", + }, + "test_template_alarm_control_panel_02": { + "unique_id": "not-so-unique-anymore", + "value_template": "{{ false }}", + }, + }, + }, + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 diff --git a/tests/components/template/test_binary_sensor.py b/tests/components/template/test_binary_sensor.py index 482a72082cd..aeee08dc757 100644 --- a/tests/components/template/test_binary_sensor.py +++ b/tests/components/template/test_binary_sensor.py @@ -253,6 +253,7 @@ class TestBinarySensorTemplate(unittest.TestCase): None, None, None, + None, ).result() assert not vs.should_poll assert "motion" == vs.device_class @@ -315,6 +316,7 @@ class TestBinarySensorTemplate(unittest.TestCase): None, None, None, + None, ).result() mock_render.side_effect = TemplateError("foo") run_callback_threadsafe(self.hass.loop, vs.async_check_state).result() @@ -640,3 +642,32 @@ async def test_no_update_template_match_all(hass, caplog): assert hass.states.get("binary_sensor.all_icon").state == "off" assert hass.states.get("binary_sensor.all_entity_picture").state == "off" assert hass.states.get("binary_sensor.all_attribute").state == "off" + + +async def test_unique_id(hass): + """Test unique_id option only creates one binary sensor per id.""" + await setup.async_setup_component( + hass, + "binary_sensor", + { + "binary_sensor": { + "platform": "template", + "sensors": { + "test_template_cover_01": { + "unique_id": "not-so-unique-anymore", + "value_template": "{{ true }}", + }, + "test_template_cover_02": { + "unique_id": "not-so-unique-anymore", + "value_template": "{{ false }}", + }, + }, + }, + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 diff --git a/tests/components/template/test_cover.py b/tests/components/template/test_cover.py index 56db2445f6b..d51d71648bf 100644 --- a/tests/components/template/test_cover.py +++ b/tests/components/template/test_cover.py @@ -1034,3 +1034,48 @@ async def test_invalid_device_class(hass, calls): state = hass.states.get("cover.test_template_cover") assert not state + + +async def test_unique_id(hass): + """Test unique_id option only creates one cover per id.""" + await setup.async_setup_component( + hass, + "cover", + { + "cover": { + "platform": "template", + "covers": { + "test_template_cover_01": { + "unique_id": "not-so-unique-anymore", + "value_template": "{{ true }}", + "open_cover": { + "service": "cover.open_cover", + "entity_id": "cover.test_state", + }, + "close_cover": { + "service": "cover.close_cover", + "entity_id": "cover.test_state", + }, + }, + "test_template_cover_02": { + "unique_id": "not-so-unique-anymore", + "value_template": "{{ false }}", + "open_cover": { + "service": "cover.open_cover", + "entity_id": "cover.test_state", + }, + "close_cover": { + "service": "cover.close_cover", + "entity_id": "cover.test_state", + }, + }, + }, + }, + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index 58fa80c10d5..a56b55fa123 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -758,3 +758,48 @@ async def _register_components(hass, speed_list=None): await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() + + +async def test_unique_id(hass): + """Test unique_id option only creates one fan per id.""" + await setup.async_setup_component( + hass, + "fan", + { + "fan": { + "platform": "template", + "fans": { + "test_template_fan_01": { + "unique_id": "not-so-unique-anymore", + "value_template": "{{ true }}", + "turn_on": { + "service": "fan.turn_on", + "entity_id": "fan.test_state", + }, + "turn_off": { + "service": "fan.turn_off", + "entity_id": "fan.test_state", + }, + }, + "test_template_fan_02": { + "unique_id": "not-so-unique-anymore", + "value_template": "{{ false }}", + "turn_on": { + "service": "fan.turn_on", + "entity_id": "fan.test_state", + }, + "turn_off": { + "service": "fan.turn_off", + "entity_id": "fan.test_state", + }, + }, + }, + }, + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 diff --git a/tests/components/template/test_light.py b/tests/components/template/test_light.py index 8e27a6ba15d..5353aaa7d17 100644 --- a/tests/components/template/test_light.py +++ b/tests/components/template/test_light.py @@ -1164,3 +1164,46 @@ async def test_invalid_availability_template_keeps_component_available(hass, cap assert hass.states.get("light.test_template_light").state != STATE_UNAVAILABLE assert ("UndefinedError: 'x' is undefined") in caplog.text + + +async def test_unique_id(hass): + """Test unique_id option only creates one light per id.""" + await setup.async_setup_component( + hass, + "light", + { + "light": { + "platform": "template", + "lights": { + "test_template_light_01": { + "unique_id": "not-so-unique-anymore", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + }, + "test_template_light_02": { + "unique_id": "not-so-unique-anymore", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + }, + }, + }, + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 diff --git a/tests/components/template/test_lock.py b/tests/components/template/test_lock.py index 1389040c4bb..4c15babdfe2 100644 --- a/tests/components/template/test_lock.py +++ b/tests/components/template/test_lock.py @@ -406,3 +406,48 @@ async def test_invalid_availability_template_keeps_component_available(hass, cap assert hass.states.get("lock.template_lock").state != STATE_UNAVAILABLE assert ("UndefinedError: 'x' is undefined") in caplog.text + + +async def test_unique_id(hass): + """Test unique_id option only creates one lock per id.""" + await setup.async_setup_component( + hass, + "lock", + { + "lock": { + "platform": "template", + "name": "test_template_lock_01", + "unique_id": "not-so-unique-anymore", + "value_template": "{{ true }}", + "lock": {"service": "switch.turn_on", "entity_id": "switch.test_state"}, + "unlock": { + "service": "switch.turn_off", + "entity_id": "switch.test_state", + }, + }, + }, + ) + + await setup.async_setup_component( + hass, + "lock", + { + "lock": { + "platform": "template", + "name": "test_template_lock_02", + "unique_id": "not-so-unique-anymore", + "value_template": "{{ false }}", + "lock": {"service": "switch.turn_on", "entity_id": "switch.test_state"}, + "unlock": { + "service": "switch.turn_off", + "entity_id": "switch.test_state", + }, + }, + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index d61b1be4a7f..8a3a731f953 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -639,3 +639,32 @@ async def test_no_template_match_all(hass, caplog): assert hass.states.get("sensor.invalid_entity_picture").state == "hello" assert hass.states.get("sensor.invalid_friendly_name").state == "hello" assert hass.states.get("sensor.invalid_attribute").state == "hello" + + +async def test_unique_id(hass): + """Test unique_id option only creates one sensor per id.""" + await async_setup_component( + hass, + "sensor", + { + "sensor": { + "platform": "template", + "sensors": { + "test_template_sensor_01": { + "unique_id": "not-so-unique-anymore", + "value_template": "{{ true }}", + }, + "test_template_sensor_02": { + "unique_id": "not-so-unique-anymore", + "value_template": "{{ false }}", + }, + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 diff --git a/tests/components/template/test_switch.py b/tests/components/template/test_switch.py index a252bea9758..191e26a4266 100644 --- a/tests/components/template/test_switch.py +++ b/tests/components/template/test_switch.py @@ -650,3 +650,48 @@ async def test_invalid_availability_template_keeps_component_available(hass, cap assert hass.states.get("switch.test_template_switch").state != STATE_UNAVAILABLE assert ("UndefinedError: 'x' is undefined") in caplog.text + + +async def test_unique_id(hass): + """Test unique_id option only creates one switch per id.""" + await setup.async_setup_component( + hass, + "switch", + { + "switch": { + "platform": "template", + "switches": { + "test_template_switch_01": { + "unique_id": "not-so-unique-anymore", + "value_template": "{{ true }}", + "turn_on": { + "service": "switch.turn_on", + "entity_id": "switch.test_state", + }, + "turn_off": { + "service": "switch.turn_off", + "entity_id": "switch.test_state", + }, + }, + "test_template_switch_02": { + "unique_id": "not-so-unique-anymore", + "value_template": "{{ false }}", + "turn_on": { + "service": "switch.turn_on", + "entity_id": "switch.test_state", + }, + "turn_off": { + "service": "switch.turn_off", + "entity_id": "switch.test_state", + }, + }, + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 diff --git a/tests/components/template/test_vacuum.py b/tests/components/template/test_vacuum.py index 19cd9f0a8ee..fd77e5455c6 100644 --- a/tests/components/template/test_vacuum.py +++ b/tests/components/template/test_vacuum.py @@ -612,3 +612,34 @@ async def _register_components(hass): await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() + + +async def test_unique_id(hass): + """Test unique_id option only creates one vacuum per id.""" + await setup.async_setup_component( + hass, + "vacuum", + { + "vacuum": { + "platform": "template", + "vacuums": { + "test_template_vacuum_01": { + "unique_id": "not-so-unique-anymore", + "value_template": "{{ true }}", + "start": {"service": "script.vacuum_start"}, + }, + "test_template_vacuum_02": { + "unique_id": "not-so-unique-anymore", + "value_template": "{{ false }}", + "start": {"service": "script.vacuum_start"}, + }, + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1