Add auto_off to binary sensor template entity (#49615)
parent
87a595d928
commit
f5e4b13814
|
@ -2,6 +2,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
@ -49,6 +50,7 @@ from .trigger_entity import TriggerEntity
|
||||||
|
|
||||||
CONF_DELAY_ON = "delay_on"
|
CONF_DELAY_ON = "delay_on"
|
||||||
CONF_DELAY_OFF = "delay_off"
|
CONF_DELAY_OFF = "delay_off"
|
||||||
|
CONF_AUTO_OFF = "auto_off"
|
||||||
CONF_ATTRIBUTE_TEMPLATES = "attribute_templates"
|
CONF_ATTRIBUTE_TEMPLATES = "attribute_templates"
|
||||||
|
|
||||||
LEGACY_FIELDS = {
|
LEGACY_FIELDS = {
|
||||||
|
@ -74,6 +76,7 @@ BINARY_SENSOR_SCHEMA = vol.Schema(
|
||||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||||
vol.Optional(CONF_DELAY_ON): vol.Any(cv.positive_time_period, cv.template),
|
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_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."""
|
"""Initialize the entity."""
|
||||||
super().__init__(hass, coordinator, config)
|
super().__init__(hass, coordinator, config)
|
||||||
|
|
||||||
if isinstance(config.get(CONF_DELAY_ON), template.Template):
|
for key in (CONF_DELAY_ON, CONF_DELAY_OFF, CONF_AUTO_OFF):
|
||||||
self._to_render.append(CONF_DELAY_ON)
|
if isinstance(config.get(key), template.Template):
|
||||||
self._parse_result.add(CONF_DELAY_ON)
|
self._to_render.append(key)
|
||||||
|
self._parse_result.add(key)
|
||||||
if isinstance(config.get(CONF_DELAY_OFF), template.Template):
|
|
||||||
self._to_render.append(CONF_DELAY_OFF)
|
|
||||||
self._parse_result.add(CONF_DELAY_OFF)
|
|
||||||
|
|
||||||
self._delay_cancel = None
|
self._delay_cancel = None
|
||||||
|
self._auto_off_cancel = None
|
||||||
self._state = False
|
self._state = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -378,22 +379,23 @@ class TriggerBinarySensorEntity(TriggerEntity, BinarySensorEntity):
|
||||||
self._delay_cancel()
|
self._delay_cancel()
|
||||||
self._delay_cancel = None
|
self._delay_cancel = None
|
||||||
|
|
||||||
|
if self._auto_off_cancel:
|
||||||
|
self._auto_off_cancel()
|
||||||
|
self._auto_off_cancel = None
|
||||||
|
|
||||||
if not self.available:
|
if not self.available:
|
||||||
|
self.async_write_ha_state()
|
||||||
return
|
return
|
||||||
|
|
||||||
raw = self._rendered.get(CONF_STATE)
|
raw = self._rendered.get(CONF_STATE)
|
||||||
state = template.result_as_boolean(raw)
|
state = template.result_as_boolean(raw)
|
||||||
|
|
||||||
if state == self._state:
|
|
||||||
return
|
|
||||||
|
|
||||||
key = CONF_DELAY_ON if state else CONF_DELAY_OFF
|
key = CONF_DELAY_ON if state else CONF_DELAY_OFF
|
||||||
delay = self._rendered.get(key) or self._config.get(key)
|
delay = self._rendered.get(key) or self._config.get(key)
|
||||||
|
|
||||||
# state without delay. None means rendering failed.
|
# state without delay. None means rendering failed.
|
||||||
if state is None or delay is None:
|
if self._state == state or state is None or delay is None:
|
||||||
self._state = state
|
self._set_state(state)
|
||||||
self.async_write_ha_state()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if not isinstance(delay, timedelta):
|
if not isinstance(delay, timedelta):
|
||||||
|
@ -405,14 +407,43 @@ class TriggerBinarySensorEntity(TriggerEntity, BinarySensorEntity):
|
||||||
)
|
)
|
||||||
return
|
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
|
# state with delay. Cancelled if new trigger received
|
||||||
self._delay_cancel = async_call_later(
|
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
|
||||||
)
|
)
|
||||||
|
|
|
@ -965,7 +965,8 @@ async def test_trigger_entity(hass):
|
||||||
"picture": "{{ '/local/dogs.png' }}",
|
"picture": "{{ '/local/dogs.png' }}",
|
||||||
"icon": "{{ 'mdi:pirate' }}",
|
"icon": "{{ 'mdi:pirate' }}",
|
||||||
"attributes": {
|
"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("icon") == "mdi:pirate"
|
||||||
assert state.attributes.get("entity_picture") == "/local/dogs.png"
|
assert state.attributes.get("entity_picture") == "/local/dogs.png"
|
||||||
assert state.attributes.get("plus_one") == 3
|
assert state.attributes.get("plus_one") == 3
|
||||||
|
assert state.attributes.get("another") == 1
|
||||||
assert state.context is context
|
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):
|
async def test_template_with_trigger_templated_delay_on(hass):
|
||||||
"""Test binary sensor template with template delay on."""
|
"""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 }}",
|
"state": "{{ trigger.event.data.beer == 2 }}",
|
||||||
"device_class": "motion",
|
"device_class": "motion",
|
||||||
"delay_on": '{{ ({ "seconds": 6 / 2 }) }}',
|
"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")
|
state = hass.states.get("binary_sensor.test")
|
||||||
assert state.state == "on"
|
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"
|
||||||
|
|
Loading…
Reference in New Issue