diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 07591ce9653..19eecaa7006 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -53,6 +53,7 @@ from .entity import AbstractTemplateEntity from .helpers import async_setup_template_platform from .template_entity import ( TEMPLATE_ENTITY_COMMON_SCHEMA_LEGACY, + TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA, TemplateEntity, make_template_entity_common_modern_schema, ) @@ -121,7 +122,7 @@ LEGACY_FIELDS = { DEFAULT_NAME = "Template Light" -LIGHT_YAML_SCHEMA = vol.Schema( +LIGHT_COMMON_SCHEMA = vol.Schema( { vol.Inclusive(CONF_EFFECT_ACTION, "effect"): cv.SCRIPT_SCHEMA, vol.Inclusive(CONF_EFFECT_LIST, "effect"): cv.template, @@ -132,6 +133,8 @@ LIGHT_YAML_SCHEMA = vol.Schema( vol.Optional(CONF_LEVEL): cv.template, vol.Optional(CONF_MAX_MIREDS): cv.template, vol.Optional(CONF_MIN_MIREDS): cv.template, + vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA, + vol.Required(CONF_ON_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_RGB_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_RGB): cv.template, vol.Optional(CONF_RGBW_ACTION): cv.SCRIPT_SCHEMA, @@ -142,9 +145,11 @@ LIGHT_YAML_SCHEMA = vol.Schema( vol.Optional(CONF_SUPPORTS_TRANSITION): cv.template, vol.Optional(CONF_TEMPERATURE_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_TEMPERATURE): cv.template, - vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA, - vol.Required(CONF_ON_ACTION): cv.SCRIPT_SCHEMA, } +) + +LIGHT_YAML_SCHEMA = LIGHT_COMMON_SCHEMA.extend( + TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA ).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema) LIGHT_LEGACY_YAML_SCHEMA = vol.All( @@ -215,6 +220,7 @@ class AbstractTemplateLight(AbstractTemplateEntity, LightEntity): """Representation of a template lights features.""" _entity_id_format = ENTITY_ID_FORMAT + _optimistic_entity = True # The super init is not called because TemplateEntity and TriggerEntity will call AbstractTemplateEntity.__init__. # This ensures that the __init__ on AbstractTemplateEntity is not called twice. @@ -224,7 +230,6 @@ class AbstractTemplateLight(AbstractTemplateEntity, LightEntity): """Initialize the features.""" # Template attributes - self._template = config.get(CONF_STATE) self._level_template = config.get(CONF_LEVEL) self._temperature_template = config.get(CONF_TEMPERATURE) self._hs_template = config.get(CONF_HS) @@ -349,7 +354,7 @@ class AbstractTemplateLight(AbstractTemplateEntity, LightEntity): Returns True if any attribute was updated. """ optimistic_set = False - if self._template is None: + if self._attr_assumed_state: self._state = True optimistic_set = True @@ -1066,7 +1071,7 @@ class StateLightEntity(TemplateEntity, AbstractTemplateLight): ) else: await self.async_run_script(off_script, context=self._context) - if self._template is None: + if self._attr_assumed_state: self._state = False self.async_write_ha_state() @@ -1205,6 +1210,6 @@ class TriggerLightEntity(TriggerEntity, AbstractTemplateLight): ) else: await self.async_run_script(off_script, context=self._context) - if self._template is None: + if self._attr_assumed_state: self._state = False self.async_write_ha_state() diff --git a/tests/components/template/test_light.py b/tests/components/template/test_light.py index bfffd0911a9..b42eba0665d 100644 --- a/tests/components/template/test_light.py +++ b/tests/components/template/test_light.py @@ -37,6 +37,9 @@ from tests.common import assert_setup_component # Represent for light's availability _STATE_AVAILABILITY_BOOLEAN = "availability_boolean.state" +TEST_OBJECT_ID = "test_light" +TEST_ENTITY_ID = f"light.{TEST_OBJECT_ID}" +TEST_STATE_ENTITY_ID = "light.test_state" OPTIMISTIC_ON_OFF_LIGHT_CONFIG = { "turn_on": { @@ -2740,3 +2743,51 @@ async def test_effect_with_empty_action( """Test empty set_effect action.""" state = hass.states.get("light.test_template_light") assert state.attributes["supported_features"] == LightEntityFeature.EFFECT + + +@pytest.mark.parametrize( + ("count", "light_config"), + [ + ( + 1, + { + "name": TEST_OBJECT_ID, + "state": "{{ is_state('light.test_state', 'on') }}", + "turn_on": [], + "turn_off": [], + "optimistic": True, + }, + ) + ], +) +@pytest.mark.parametrize( + "style", + [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER], +) +@pytest.mark.usefixtures("setup_light") +async def test_optimistic_option(hass: HomeAssistant) -> None: + """Test optimistic yaml option.""" + hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_OFF) + await hass.async_block_till_done() + + state = hass.states.get(TEST_ENTITY_ID) + assert state.state == STATE_OFF + + await hass.services.async_call( + light.DOMAIN, + "turn_on", + {"entity_id": TEST_ENTITY_ID}, + blocking=True, + ) + + state = hass.states.get(TEST_ENTITY_ID) + assert state.state == STATE_ON + + hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_ON) + await hass.async_block_till_done() + + hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_OFF) + await hass.async_block_till_done() + + state = hass.states.get(TEST_ENTITY_ID) + assert state.state == STATE_OFF