diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index d985473792e..a9f28d56669 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -62,8 +62,7 @@ POSITION_ACTION = "set_cover_position" TILT_ACTION = "set_cover_tilt_position" CONF_TILT_OPTIMISTIC = "tilt_optimistic" -CONF_VALUE_OR_POSITION_TEMPLATE = "value_or_position" -CONF_OPEN_OR_CLOSE = "open_or_close" +CONF_OPEN_AND_CLOSE = "open_or_close" TILT_FEATURES = ( SUPPORT_OPEN_TILT @@ -76,15 +75,10 @@ COVER_SCHEMA = vol.All( cv.deprecated(CONF_ENTITY_ID), vol.Schema( { - vol.Inclusive(OPEN_ACTION, CONF_OPEN_OR_CLOSE): cv.SCRIPT_SCHEMA, - vol.Inclusive(CLOSE_ACTION, CONF_OPEN_OR_CLOSE): cv.SCRIPT_SCHEMA, + vol.Inclusive(OPEN_ACTION, CONF_OPEN_AND_CLOSE): cv.SCRIPT_SCHEMA, + vol.Inclusive(CLOSE_ACTION, CONF_OPEN_AND_CLOSE): cv.SCRIPT_SCHEMA, vol.Optional(STOP_ACTION): cv.SCRIPT_SCHEMA, - vol.Exclusive( - CONF_POSITION_TEMPLATE, CONF_VALUE_OR_POSITION_TEMPLATE - ): cv.template, - vol.Exclusive( - CONF_VALUE_TEMPLATE, CONF_VALUE_OR_POSITION_TEMPLATE - ): cv.template, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_AVAILABILITY_TEMPLATE): cv.template, vol.Optional(CONF_POSITION_TEMPLATE): cv.template, vol.Optional(CONF_TILT_TEMPLATE): cv.template, @@ -258,10 +252,11 @@ class CoverTemplate(TemplateEntity, CoverEntity): state = str(result).lower() if state in _VALID_STATES: - if state in ("true", STATE_OPEN): - self._position = 100 - else: - self._position = 0 + if not self._position_template: + if state in ("true", STATE_OPEN): + self._position = 100 + else: + self._position = 0 self._is_opening = state == STATE_OPENING self._is_closing = state == STATE_CLOSING @@ -271,7 +266,8 @@ class CoverTemplate(TemplateEntity, CoverEntity): state, ", ".join(_VALID_STATES), ) - self._position = None + if not self._position_template: + self._position = None @callback def _update_position(self, result): diff --git a/tests/components/template/test_cover.py b/tests/components/template/test_cover.py index c1309a16e67..e2b65abcf25 100644 --- a/tests/components/template/test_cover.py +++ b/tests/components/template/test_cover.py @@ -34,7 +34,7 @@ def calls_fixture(hass): return async_mock_service(hass, "test", "automation") -async def test_template_state_text(hass, calls): +async def test_template_state_text(hass, calls, caplog): """Test the state text of a template.""" with assert_setup_component(1, "cover"): assert await setup.async_setup_component( @@ -64,30 +64,147 @@ async def test_template_state_text(hass, calls): await hass.async_start() await hass.async_block_till_done() - state = hass.states.async_set("cover.test_state", STATE_OPEN) + hass.states.async_set("cover.test_state", STATE_OPEN) await hass.async_block_till_done() - state = hass.states.get("cover.test_template_cover") assert state.state == STATE_OPEN - state = hass.states.async_set("cover.test_state", STATE_CLOSED) + hass.states.async_set("cover.test_state", STATE_CLOSED) await hass.async_block_till_done() - state = hass.states.get("cover.test_template_cover") assert state.state == STATE_CLOSED - state = hass.states.async_set("cover.test_state", STATE_OPENING) + hass.states.async_set("cover.test_state", STATE_OPENING) await hass.async_block_till_done() - state = hass.states.get("cover.test_template_cover") assert state.state == STATE_OPENING - state = hass.states.async_set("cover.test_state", STATE_CLOSING) + hass.states.async_set("cover.test_state", STATE_CLOSING) await hass.async_block_till_done() - state = hass.states.get("cover.test_template_cover") assert state.state == STATE_CLOSING + # Unknown state sets position to None - "closing" takes precedence + state = hass.states.async_set("cover.test_state", "dog") + await hass.async_block_till_done() + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_CLOSING + assert "Received invalid cover is_on state: dog" in caplog.text + + # Set state to open + hass.states.async_set("cover.test_state", STATE_OPEN) + await hass.async_block_till_done() + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_OPEN + + # Unknown state sets position to None -> Open + state = hass.states.async_set("cover.test_state", "cat") + await hass.async_block_till_done() + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_OPEN + assert "Received invalid cover is_on state: cat" in caplog.text + + # Set state to closed + hass.states.async_set("cover.test_state", STATE_CLOSED) + await hass.async_block_till_done() + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_CLOSED + + # Unknown state sets position to None -> Open + state = hass.states.async_set("cover.test_state", "bear") + await hass.async_block_till_done() + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_OPEN + assert "Received invalid cover is_on state: bear" in caplog.text + + +async def test_template_state_text_combined(hass, calls, caplog): + """Test the state text of a template which combines position and value templates.""" + with assert_setup_component(1, "cover"): + assert await setup.async_setup_component( + hass, + "cover", + { + "cover": { + "platform": "template", + "covers": { + "test_template_cover": { + "position_template": "{{ states.cover.test.attributes.position }}", + "value_template": "{{ states.cover.test_state.state }}", + "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() + + # Test default state + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_OPEN + + # Change to "open" should be ignored + state = hass.states.async_set("cover.test_state", STATE_OPEN) + await hass.async_block_till_done() + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_OPEN + + # Change to "closed" should be ignored + state = hass.states.async_set("cover.test_state", STATE_CLOSED) + await hass.async_block_till_done() + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_OPEN + + # Change to "opening" should be accepted + state = hass.states.async_set("cover.test_state", STATE_OPENING) + await hass.async_block_till_done() + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_OPENING + + # Change to "closing" should be accepted + state = hass.states.async_set("cover.test_state", STATE_CLOSING) + await hass.async_block_till_done() + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_CLOSING + + # Set position to 0=closed + hass.states.async_set("cover.test", STATE_CLOSED, attributes={"position": 0}) + await hass.async_block_till_done() + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_CLOSING + assert state.attributes["current_position"] == 0 + + # Clear "closing" state, STATE_OPEN will be ignored and state derived from position + state = hass.states.async_set("cover.test_state", STATE_OPEN) + await hass.async_block_till_done() + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_CLOSED + + # Set position to 10 + hass.states.async_set("cover.test", STATE_CLOSED, attributes={"position": 10}) + await hass.async_block_till_done() + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_OPEN + assert state.attributes["current_position"] == 10 + + # Unknown state should be ignored + state = hass.states.async_set("cover.test_state", "dog") + await hass.async_block_till_done() + state = hass.states.get("cover.test_template_cover") + assert state.state == STATE_OPEN + assert state.attributes["current_position"] == 10 + assert "Received invalid cover is_on state: dog" in caplog.text + async def test_template_state_boolean(hass, calls): """Test the value_template attribute.""" @@ -250,43 +367,6 @@ async def test_template_out_of_bounds(hass, calls): assert state.attributes.get("current_position") is None -async def test_template_mutex(hass, calls): - """Test that only value or position template can be used.""" - with assert_setup_component(0, "cover"): - assert await setup.async_setup_component( - hass, - "cover", - { - "cover": { - "platform": "template", - "covers": { - "test_template_cover": { - "value_template": "{{ 1 == 1 }}", - "position_template": "{{ 42 }}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, - "icon_template": "{% if states.cover.test_state.state %}" - "mdi:check" - "{% endif %}", - } - }, - } - }, - ) - - await hass.async_block_till_done() - await hass.async_start() - await hass.async_block_till_done() - - assert hass.states.async_all() == [] - - async def test_template_open_or_position(hass, caplog): """Test that at least one of open_cover or set_position is used.""" assert await setup.async_setup_component(