Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 28 additions & 15 deletions homeassistant/components/template/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
STATE_CLOSING,
"true",
"false",
"none",
]

CONF_POSITION_TEMPLATE = "position_template"
Expand Down Expand Up @@ -138,36 +139,36 @@ class CoverTemplate(TemplateEntity, CoverEntity):

def __init__(
self,
hass,
hass: HomeAssistant,
object_id,
config,
unique_id,
):
) -> None:
"""Initialize the Template cover."""
super().__init__(
hass, config=config, fallback_name=object_id, unique_id=unique_id
)
self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, object_id, hass=hass
)
friendly_name = self._attr_name
friendly_name = self._attr_name or ""
self._template = config.get(CONF_VALUE_TEMPLATE)
self._position_template = config.get(CONF_POSITION_TEMPLATE)
self._tilt_template = config.get(CONF_TILT_TEMPLATE)
self._device_class: CoverDeviceClass | None = config.get(CONF_DEVICE_CLASS)
self._open_script = None
self._open_script: Script | None = None
if (open_action := config.get(OPEN_ACTION)) is not None:
self._open_script = Script(hass, open_action, friendly_name, DOMAIN)
self._close_script = None
self._close_script: Script | None = None
if (close_action := config.get(CLOSE_ACTION)) is not None:
self._close_script = Script(hass, close_action, friendly_name, DOMAIN)
self._stop_script = None
self._stop_script: Script | None = None
if (stop_action := config.get(STOP_ACTION)) is not None:
self._stop_script = Script(hass, stop_action, friendly_name, DOMAIN)
self._position_script = None
self._position_script: Script | None = None
if (position_action := config.get(POSITION_ACTION)) is not None:
self._position_script = Script(hass, position_action, friendly_name, DOMAIN)
self._tilt_script = None
self._tilt_script: Script | None = None
if (tilt_action := config.get(TILT_ACTION)) is not None:
self._tilt_script = Script(hass, tilt_action, friendly_name, DOMAIN)
optimistic = config.get(CONF_OPTIMISTIC)
Expand All @@ -176,10 +177,10 @@ def __init__(
)
tilt_optimistic = config.get(CONF_TILT_OPTIMISTIC)
self._tilt_optimistic = tilt_optimistic or not self._tilt_template
self._position = None
self._position: int | None = None
self._is_opening = False
self._is_closing = False
self._tilt_value = None
self._tilt_value: int | None = None

async def async_added_to_hass(self) -> None:
"""Register callbacks."""
Expand All @@ -206,7 +207,7 @@ async def async_added_to_hass(self) -> None:
await super().async_added_to_hass()

@callback
def _update_state(self, result):
def _update_state(self, result: Any | TemplateError) -> None:
super()._update_state(result)
if isinstance(result, TemplateError):
self._position = None
Expand Down Expand Up @@ -237,7 +238,11 @@ def _update_state(self, result):
self._is_closing = False

@callback
def _update_position(self, result):
def _update_position(self, result: Any) -> None:
if result is None:
self._position = None
return

try:
state = float(result)
except ValueError as err:
Expand All @@ -252,10 +257,14 @@ def _update_position(self, result):
state,
)
else:
self._position = state
self._position = round(state)

@callback
def _update_tilt(self, result):
def _update_tilt(self, result: Any) -> None:
if result is None:
self._tilt_value = None
return

try:
state = float(result)
except ValueError as err:
Expand All @@ -270,7 +279,7 @@ def _update_tilt(self, result):
state,
)
else:
self._tilt_value = state
self._tilt_value = round(state)

@property
def is_closed(self) -> bool | None:
Expand Down Expand Up @@ -365,6 +374,7 @@ async def async_stop_cover(self, **kwargs: Any) -> None:
async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Set cover position."""
self._position = kwargs[ATTR_POSITION]
assert self._position_script is not None
await self.async_run_script(
self._position_script,
run_variables={"position": self._position},
Expand All @@ -376,6 +386,7 @@ async def async_set_cover_position(self, **kwargs: Any) -> None:
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
"""Tilt the cover open."""
self._tilt_value = 100
assert self._tilt_script is not None
await self.async_run_script(
self._tilt_script,
run_variables={"tilt": self._tilt_value},
Expand All @@ -387,6 +398,7 @@ async def async_open_cover_tilt(self, **kwargs: Any) -> None:
async def async_close_cover_tilt(self, **kwargs: Any) -> None:
"""Tilt the cover closed."""
self._tilt_value = 0
assert self._tilt_script is not None
await self.async_run_script(
self._tilt_script,
run_variables={"tilt": self._tilt_value},
Expand All @@ -398,6 +410,7 @@ async def async_close_cover_tilt(self, **kwargs: Any) -> None:
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
"""Move the cover tilt to a specific position."""
self._tilt_value = kwargs[ATTR_TILT_POSITION]
assert self._tilt_script is not None
await self.async_run_script(
self._tilt_script,
run_variables={"tilt": self._tilt_value},
Expand Down
124 changes: 108 additions & 16 deletions tests/components/template/test_cover.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""The tests for the Template cover platform."""
from typing import Any

import pytest

from homeassistant import setup
Expand Down Expand Up @@ -149,6 +151,72 @@ async def test_template_state_text(
assert text in caplog.text


@pytest.mark.parametrize(("count", "domain"), [(1, DOMAIN)])
@pytest.mark.parametrize(
("config", "entity", "set_state", "test_state", "attr"),
[
(
{
DOMAIN: {
"platform": "template",
"covers": {
"test_template_cover": {
**OPEN_CLOSE_COVER_CONFIG,
"position_template": (
"{{ states.cover.test.attributes.position }}"
),
"value_template": "{{ states.cover.test_state.state }}",
}
},
}
},
"cover.test_state",
"",
STATE_UNKNOWN,
{},
),
(
{
DOMAIN: {
"platform": "template",
"covers": {
"test_template_cover": {
**OPEN_CLOSE_COVER_CONFIG,
"position_template": (
"{{ states.cover.test.attributes.position }}"
),
"value_template": "{{ states.cover.test_state.state }}",
}
},
}
},
"cover.test_state",
None,
STATE_UNKNOWN,
{},
),
],
)
async def test_template_state_text_ignored_if_none_or_empty(
hass: HomeAssistant,
entity: str,
set_state: str,
test_state: str,
attr: dict[str, Any],
start_ha,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test ignoring an empty state text of a template."""
state = hass.states.get("cover.test_template_cover")
assert state.state == STATE_UNKNOWN

hass.states.async_set(entity, set_state, attributes=attr)
await hass.async_block_till_done()
state = hass.states.get("cover.test_template_cover")
assert state.state == test_state
assert "ERROR" not in caplog.text


@pytest.mark.parametrize(("count", "domain"), [(1, DOMAIN)])
@pytest.mark.parametrize(
"config",
Expand Down Expand Up @@ -191,21 +259,25 @@ async def test_template_state_boolean(hass: HomeAssistant, start_ha) -> None:
},
],
)
async def test_template_position(hass: HomeAssistant, start_ha) -> None:
async def test_template_position(
hass: HomeAssistant, start_ha, caplog: pytest.LogCaptureFixture
) -> None:
"""Test the position_template attribute."""
hass.states.async_set("cover.test", STATE_OPEN)
attrs = {}

for set_state, pos, test_state in [
(STATE_CLOSED, 42, STATE_OPEN),
(STATE_OPEN, 0.0, STATE_CLOSED),
(STATE_CLOSED, None, STATE_UNKNOWN),
]:
attrs["position"] = pos
hass.states.async_set("cover.test", set_state, attributes=attrs)
await hass.async_block_till_done()
state = hass.states.get("cover.test_template_cover")
assert state.attributes.get("current_position") == pos
assert state.state == test_state
assert "ValueError" not in caplog.text


@pytest.mark.parametrize(("count", "domain"), [(1, DOMAIN)])
Expand Down Expand Up @@ -233,26 +305,46 @@ async def test_template_not_optimistic(hass: HomeAssistant, start_ha) -> None:

@pytest.mark.parametrize(("count", "domain"), [(1, DOMAIN)])
@pytest.mark.parametrize(
"config",
("config", "tilt_position"),
[
{
DOMAIN: {
"platform": "template",
"covers": {
"test_template_cover": {
**OPEN_CLOSE_COVER_CONFIG,
"value_template": "{{ 1 == 1 }}",
"tilt_template": "{{ 42 }}",
}
},
}
},
(
{
DOMAIN: {
"platform": "template",
"covers": {
"test_template_cover": {
**OPEN_CLOSE_COVER_CONFIG,
"value_template": "{{ 1 == 1 }}",
"tilt_template": "{{ 42 }}",
}
},
}
},
42.0,
),
(
{
DOMAIN: {
"platform": "template",
"covers": {
"test_template_cover": {
**OPEN_CLOSE_COVER_CONFIG,
"value_template": "{{ 1 == 1 }}",
"tilt_template": "{{ None }}",
}
},
}
},
None,
),
],
)
async def test_template_tilt(hass: HomeAssistant, start_ha) -> None:
async def test_template_tilt(
hass: HomeAssistant, tilt_position: float | None, start_ha
) -> None:
"""Test the tilt_template attribute."""
state = hass.states.get("cover.test_template_cover")
assert state.attributes.get("current_tilt_position") == 42.0
assert state.attributes.get("current_tilt_position") == tilt_position


@pytest.mark.parametrize(("count", "domain"), [(1, DOMAIN)])
Expand Down