Add auto_off to binary sensor template entity (#49615)

pull/49879/head
Paulus Schoutsen 2021-04-29 09:25:34 -07:00 committed by GitHub
parent 87a595d928
commit f5e4b13814
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 71 additions and 22 deletions

View File

@ -2,6 +2,7 @@
from __future__ import annotations
from datetime import timedelta
from functools import partial
import logging
import voluptuous as vol
@ -49,6 +50,7 @@ from .trigger_entity import TriggerEntity
CONF_DELAY_ON = "delay_on"
CONF_DELAY_OFF = "delay_off"
CONF_AUTO_OFF = "auto_off"
CONF_ATTRIBUTE_TEMPLATES = "attribute_templates"
LEGACY_FIELDS = {
@ -74,6 +76,7 @@ BINARY_SENSOR_SCHEMA = vol.Schema(
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_DELAY_ON): vol.Any(cv.positive_time_period, cv.template),
vol.Optional(CONF_DELAY_OFF): vol.Any(cv.positive_time_period, cv.template),
vol.Optional(CONF_AUTO_OFF): vol.Any(cv.positive_time_period, cv.template),
}
)
@ -353,15 +356,13 @@ class TriggerBinarySensorEntity(TriggerEntity, BinarySensorEntity):
"""Initialize the entity."""
super().__init__(hass, coordinator, config)
if isinstance(config.get(CONF_DELAY_ON), template.Template):
self._to_render.append(CONF_DELAY_ON)
self._parse_result.add(CONF_DELAY_ON)
if isinstance(config.get(CONF_DELAY_OFF), template.Template):
self._to_render.append(CONF_DELAY_OFF)
self._parse_result.add(CONF_DELAY_OFF)
for key in (CONF_DELAY_ON, CONF_DELAY_OFF, CONF_AUTO_OFF):
if isinstance(config.get(key), template.Template):
self._to_render.append(key)
self._parse_result.add(key)
self._delay_cancel = None
self._auto_off_cancel = None
self._state = False
@property
@ -378,22 +379,23 @@ class TriggerBinarySensorEntity(TriggerEntity, BinarySensorEntity):
self._delay_cancel()
self._delay_cancel = None
if self._auto_off_cancel:
self._auto_off_cancel()
self._auto_off_cancel = None
if not self.available:
self.async_write_ha_state()
return
raw = self._rendered.get(CONF_STATE)
state = template.result_as_boolean(raw)
if state == self._state:
return
key = CONF_DELAY_ON if state else CONF_DELAY_OFF
delay = self._rendered.get(key) or self._config.get(key)
# state without delay. None means rendering failed.
if state is None or delay is None:
self._state = state
self.async_write_ha_state()
if self._state == state or state is None or delay is None:
self._set_state(state)
return
if not isinstance(delay, timedelta):
@ -405,14 +407,43 @@ class TriggerBinarySensorEntity(TriggerEntity, BinarySensorEntity):
)
return
@callback
def _set_state(_):
"""Set state of template binary sensor."""
self._state = state
self.async_set_context(self.coordinator.data["context"])
self.async_write_ha_state()
# state with delay. Cancelled if new trigger received
self._delay_cancel = async_call_later(
self.hass, delay.total_seconds(), _set_state
self.hass, delay.total_seconds(), partial(self._set_state, state)
)
@callback
def _set_state(self, state, _=None):
"""Set up auto off."""
self._state = state
self.async_set_context(self.coordinator.data["context"])
self.async_write_ha_state()
if not state:
return
auto_off_time = self._rendered.get(CONF_AUTO_OFF) or self._config.get(
CONF_AUTO_OFF
)
if auto_off_time is None:
return
if not isinstance(auto_off_time, timedelta):
try:
auto_off_time = cv.positive_time_period(auto_off_time)
except vol.Invalid as err:
logging.getLogger(__name__).warning(
"Error rendering %s template: %s", CONF_AUTO_OFF, err
)
return
@callback
def _auto_off(_):
"""Set state of template binary sensor."""
self._state = False
self.async_write_ha_state()
self._auto_off_cancel = async_call_later(
self.hass, auto_off_time.total_seconds(), _auto_off
)

View File

@ -965,7 +965,8 @@ async def test_trigger_entity(hass):
"picture": "{{ '/local/dogs.png' }}",
"icon": "{{ 'mdi:pirate' }}",
"attributes": {
"plus_one": "{{ trigger.event.data.beer + 1 }}"
"plus_one": "{{ trigger.event.data.beer + 1 }}",
"another": "{{ trigger.event.data.uno_mas or 1 }}",
},
}
],
@ -1021,8 +1022,16 @@ async def test_trigger_entity(hass):
assert state.attributes.get("icon") == "mdi:pirate"
assert state.attributes.get("entity_picture") == "/local/dogs.png"
assert state.attributes.get("plus_one") == 3
assert state.attributes.get("another") == 1
assert state.context is context
# Even if state itself didn't change, attributes might have changed
hass.bus.async_fire("test_event", {"beer": 2, "uno_mas": "si"})
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.via_list")
assert state.state == "on"
assert state.attributes.get("another") == "si"
async def test_template_with_trigger_templated_delay_on(hass):
"""Test binary sensor template with template delay on."""
@ -1034,6 +1043,7 @@ async def test_template_with_trigger_templated_delay_on(hass):
"state": "{{ trigger.event.data.beer == 2 }}",
"device_class": "motion",
"delay_on": '{{ ({ "seconds": 6 / 2 }) }}',
"auto_off": '{{ ({ "seconds": 1 + 1 }) }}',
},
}
}
@ -1054,3 +1064,11 @@ async def test_template_with_trigger_templated_delay_on(hass):
state = hass.states.get("binary_sensor.test")
assert state.state == "on"
# Now wait for the auto-off
future = dt_util.utcnow() + timedelta(seconds=2)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test")
assert state.state == "off"