Template binary sensor to not track all state changes (#18573)
This commit is contained in:
parent
97c493448b
commit
84fd66c8a1
2 changed files with 111 additions and 30 deletions
|
@ -58,10 +58,12 @@ async def async_setup_platform(hass, config, async_add_entities,
|
||||||
entity_ids = set()
|
entity_ids = set()
|
||||||
manual_entity_ids = device_config.get(ATTR_ENTITY_ID)
|
manual_entity_ids = device_config.get(ATTR_ENTITY_ID)
|
||||||
|
|
||||||
for template in (
|
invalid_templates = []
|
||||||
value_template,
|
|
||||||
icon_template,
|
for tpl_name, template in (
|
||||||
entity_picture_template,
|
(CONF_VALUE_TEMPLATE, value_template),
|
||||||
|
(CONF_ICON_TEMPLATE, icon_template),
|
||||||
|
(CONF_ENTITY_PICTURE_TEMPLATE, entity_picture_template),
|
||||||
):
|
):
|
||||||
if template is None:
|
if template is None:
|
||||||
continue
|
continue
|
||||||
|
@ -73,6 +75,8 @@ async def async_setup_platform(hass, config, async_add_entities,
|
||||||
template_entity_ids = template.extract_entities()
|
template_entity_ids = template.extract_entities()
|
||||||
if template_entity_ids == MATCH_ALL:
|
if template_entity_ids == MATCH_ALL:
|
||||||
entity_ids = MATCH_ALL
|
entity_ids = MATCH_ALL
|
||||||
|
# Cut off _template from name
|
||||||
|
invalid_templates.append(tpl_name[:-9])
|
||||||
elif entity_ids != MATCH_ALL:
|
elif entity_ids != MATCH_ALL:
|
||||||
entity_ids |= set(template_entity_ids)
|
entity_ids |= set(template_entity_ids)
|
||||||
|
|
||||||
|
@ -81,6 +85,14 @@ async def async_setup_platform(hass, config, async_add_entities,
|
||||||
elif entity_ids != MATCH_ALL:
|
elif entity_ids != MATCH_ALL:
|
||||||
entity_ids = list(entity_ids)
|
entity_ids = list(entity_ids)
|
||||||
|
|
||||||
|
if invalid_templates:
|
||||||
|
_LOGGER.warning(
|
||||||
|
'Template binary sensor %s has no entity ids configured to'
|
||||||
|
' track nor were we able to extract the entities to track'
|
||||||
|
' from the %s template(s). This entity will only be able'
|
||||||
|
' to be updated manually.',
|
||||||
|
device, ', '.join(invalid_templates))
|
||||||
|
|
||||||
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
|
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
|
||||||
device_class = device_config.get(CONF_DEVICE_CLASS)
|
device_class = device_config.get(CONF_DEVICE_CLASS)
|
||||||
delay_on = device_config.get(CONF_DELAY_ON)
|
delay_on = device_config.get(CONF_DELAY_ON)
|
||||||
|
@ -132,10 +144,12 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||||
@callback
|
@callback
|
||||||
def template_bsensor_startup(event):
|
def template_bsensor_startup(event):
|
||||||
"""Update template on startup."""
|
"""Update template on startup."""
|
||||||
async_track_state_change(
|
if self._entities != MATCH_ALL:
|
||||||
self.hass, self._entities, template_bsensor_state_listener)
|
# Track state change only for valid templates
|
||||||
|
async_track_state_change(
|
||||||
|
self.hass, self._entities, template_bsensor_state_listener)
|
||||||
|
|
||||||
self.hass.async_add_job(self.async_check_state)
|
self.async_check_state()
|
||||||
|
|
||||||
self.hass.bus.async_listen_once(
|
self.hass.bus.async_listen_once(
|
||||||
EVENT_HOMEASSISTANT_START, template_bsensor_startup)
|
EVENT_HOMEASSISTANT_START, template_bsensor_startup)
|
||||||
|
@ -233,3 +247,7 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||||
async_track_same_state(
|
async_track_same_state(
|
||||||
self.hass, period, set_state, entity_ids=self._entities,
|
self.hass, period, set_state, entity_ids=self._entities,
|
||||||
async_check_same_func=lambda *args: self._async_render() == state)
|
async_check_same_func=lambda *args: self._async_render() == state)
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Force update of the state from the template."""
|
||||||
|
self.async_check_state()
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
"""The tests for the Template Binary sensor platform."""
|
"""The tests for the Template Binary sensor platform."""
|
||||||
import asyncio
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import unittest
|
import unittest
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from homeassistant.const import MATCH_ALL
|
from homeassistant.const import MATCH_ALL, EVENT_HOMEASSISTANT_START
|
||||||
from homeassistant import setup
|
from homeassistant import setup
|
||||||
from homeassistant.components.binary_sensor import template
|
from homeassistant.components.binary_sensor import template
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
|
@ -182,7 +181,7 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
||||||
|
|
||||||
self.hass.states.set('sensor.any_state', 'update')
|
self.hass.states.set('sensor.any_state', 'update')
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
assert len(_async_render.mock_calls) > init_calls
|
assert len(_async_render.mock_calls) == init_calls
|
||||||
|
|
||||||
def test_attributes(self):
|
def test_attributes(self):
|
||||||
"""Test the attributes."""
|
"""Test the attributes."""
|
||||||
|
@ -252,8 +251,7 @@ class TestBinarySensorTemplate(unittest.TestCase):
|
||||||
run_callback_threadsafe(self.hass.loop, vs.async_check_state).result()
|
run_callback_threadsafe(self.hass.loop, vs.async_check_state).result()
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def test_template_delay_on(hass):
|
||||||
def test_template_delay_on(hass):
|
|
||||||
"""Test binary sensor template delay on."""
|
"""Test binary sensor template delay on."""
|
||||||
config = {
|
config = {
|
||||||
'binary_sensor': {
|
'binary_sensor': {
|
||||||
|
@ -269,51 +267,50 @@ def test_template_delay_on(hass):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
yield from setup.async_setup_component(hass, 'binary_sensor', config)
|
await setup.async_setup_component(hass, 'binary_sensor', config)
|
||||||
yield from hass.async_start()
|
await hass.async_start()
|
||||||
|
|
||||||
hass.states.async_set('sensor.test_state', 'on')
|
hass.states.async_set('sensor.test_state', 'on')
|
||||||
yield from hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get('binary_sensor.test')
|
state = hass.states.get('binary_sensor.test')
|
||||||
assert state.state == 'off'
|
assert state.state == 'off'
|
||||||
|
|
||||||
future = dt_util.utcnow() + timedelta(seconds=5)
|
future = dt_util.utcnow() + timedelta(seconds=5)
|
||||||
async_fire_time_changed(hass, future)
|
async_fire_time_changed(hass, future)
|
||||||
yield from hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get('binary_sensor.test')
|
state = hass.states.get('binary_sensor.test')
|
||||||
assert state.state == 'on'
|
assert state.state == 'on'
|
||||||
|
|
||||||
# check with time changes
|
# check with time changes
|
||||||
hass.states.async_set('sensor.test_state', 'off')
|
hass.states.async_set('sensor.test_state', 'off')
|
||||||
yield from hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get('binary_sensor.test')
|
state = hass.states.get('binary_sensor.test')
|
||||||
assert state.state == 'off'
|
assert state.state == 'off'
|
||||||
|
|
||||||
hass.states.async_set('sensor.test_state', 'on')
|
hass.states.async_set('sensor.test_state', 'on')
|
||||||
yield from hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get('binary_sensor.test')
|
state = hass.states.get('binary_sensor.test')
|
||||||
assert state.state == 'off'
|
assert state.state == 'off'
|
||||||
|
|
||||||
hass.states.async_set('sensor.test_state', 'off')
|
hass.states.async_set('sensor.test_state', 'off')
|
||||||
yield from hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get('binary_sensor.test')
|
state = hass.states.get('binary_sensor.test')
|
||||||
assert state.state == 'off'
|
assert state.state == 'off'
|
||||||
|
|
||||||
future = dt_util.utcnow() + timedelta(seconds=5)
|
future = dt_util.utcnow() + timedelta(seconds=5)
|
||||||
async_fire_time_changed(hass, future)
|
async_fire_time_changed(hass, future)
|
||||||
yield from hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get('binary_sensor.test')
|
state = hass.states.get('binary_sensor.test')
|
||||||
assert state.state == 'off'
|
assert state.state == 'off'
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def test_template_delay_off(hass):
|
||||||
def test_template_delay_off(hass):
|
|
||||||
"""Test binary sensor template delay off."""
|
"""Test binary sensor template delay off."""
|
||||||
config = {
|
config = {
|
||||||
'binary_sensor': {
|
'binary_sensor': {
|
||||||
|
@ -330,44 +327,110 @@ def test_template_delay_off(hass):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
hass.states.async_set('sensor.test_state', 'on')
|
hass.states.async_set('sensor.test_state', 'on')
|
||||||
yield from setup.async_setup_component(hass, 'binary_sensor', config)
|
await setup.async_setup_component(hass, 'binary_sensor', config)
|
||||||
yield from hass.async_start()
|
await hass.async_start()
|
||||||
|
|
||||||
hass.states.async_set('sensor.test_state', 'off')
|
hass.states.async_set('sensor.test_state', 'off')
|
||||||
yield from hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get('binary_sensor.test')
|
state = hass.states.get('binary_sensor.test')
|
||||||
assert state.state == 'on'
|
assert state.state == 'on'
|
||||||
|
|
||||||
future = dt_util.utcnow() + timedelta(seconds=5)
|
future = dt_util.utcnow() + timedelta(seconds=5)
|
||||||
async_fire_time_changed(hass, future)
|
async_fire_time_changed(hass, future)
|
||||||
yield from hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get('binary_sensor.test')
|
state = hass.states.get('binary_sensor.test')
|
||||||
assert state.state == 'off'
|
assert state.state == 'off'
|
||||||
|
|
||||||
# check with time changes
|
# check with time changes
|
||||||
hass.states.async_set('sensor.test_state', 'on')
|
hass.states.async_set('sensor.test_state', 'on')
|
||||||
yield from hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get('binary_sensor.test')
|
state = hass.states.get('binary_sensor.test')
|
||||||
assert state.state == 'on'
|
assert state.state == 'on'
|
||||||
|
|
||||||
hass.states.async_set('sensor.test_state', 'off')
|
hass.states.async_set('sensor.test_state', 'off')
|
||||||
yield from hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get('binary_sensor.test')
|
state = hass.states.get('binary_sensor.test')
|
||||||
assert state.state == 'on'
|
assert state.state == 'on'
|
||||||
|
|
||||||
hass.states.async_set('sensor.test_state', 'on')
|
hass.states.async_set('sensor.test_state', 'on')
|
||||||
yield from hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get('binary_sensor.test')
|
state = hass.states.get('binary_sensor.test')
|
||||||
assert state.state == 'on'
|
assert state.state == 'on'
|
||||||
|
|
||||||
future = dt_util.utcnow() + timedelta(seconds=5)
|
future = dt_util.utcnow() + timedelta(seconds=5)
|
||||||
async_fire_time_changed(hass, future)
|
async_fire_time_changed(hass, future)
|
||||||
yield from hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get('binary_sensor.test')
|
state = hass.states.get('binary_sensor.test')
|
||||||
assert state.state == 'on'
|
assert state.state == 'on'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_no_update_template_match_all(hass, caplog):
|
||||||
|
"""Test that we do not update sensors that match on all."""
|
||||||
|
hass.states.async_set('binary_sensor.test_sensor', 'true')
|
||||||
|
|
||||||
|
await setup.async_setup_component(hass, 'binary_sensor', {
|
||||||
|
'binary_sensor': {
|
||||||
|
'platform': 'template',
|
||||||
|
'sensors': {
|
||||||
|
'all_state': {
|
||||||
|
'value_template': '{{ "true" }}',
|
||||||
|
},
|
||||||
|
'all_icon': {
|
||||||
|
'value_template':
|
||||||
|
'{{ states.binary_sensor.test_sensor.state }}',
|
||||||
|
'icon_template': '{{ 1 + 1 }}',
|
||||||
|
},
|
||||||
|
'all_entity_picture': {
|
||||||
|
'value_template':
|
||||||
|
'{{ states.binary_sensor.test_sensor.state }}',
|
||||||
|
'entity_picture_template': '{{ 1 + 1 }}',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(hass.states.async_all()) == 4
|
||||||
|
assert ('Template binary sensor all_state has no entity ids '
|
||||||
|
'configured to track nor were we able to extract the entities to '
|
||||||
|
'track from the value template') in caplog.text
|
||||||
|
assert ('Template binary sensor all_icon has no entity ids '
|
||||||
|
'configured to track nor were we able to extract the entities to '
|
||||||
|
'track from the icon template') in caplog.text
|
||||||
|
assert ('Template binary sensor all_entity_picture has no entity ids '
|
||||||
|
'configured to track nor were we able to extract the entities to '
|
||||||
|
'track from the entity_picture template') in caplog.text
|
||||||
|
|
||||||
|
assert hass.states.get('binary_sensor.all_state').state == 'off'
|
||||||
|
assert hass.states.get('binary_sensor.all_icon').state == 'off'
|
||||||
|
assert hass.states.get('binary_sensor.all_entity_picture').state == 'off'
|
||||||
|
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hass.states.get('binary_sensor.all_state').state == 'on'
|
||||||
|
assert hass.states.get('binary_sensor.all_icon').state == 'on'
|
||||||
|
assert hass.states.get('binary_sensor.all_entity_picture').state == 'on'
|
||||||
|
|
||||||
|
hass.states.async_set('binary_sensor.test_sensor', 'false')
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hass.states.get('binary_sensor.all_state').state == 'on'
|
||||||
|
assert hass.states.get('binary_sensor.all_icon').state == 'on'
|
||||||
|
assert hass.states.get('binary_sensor.all_entity_picture').state == 'on'
|
||||||
|
|
||||||
|
await hass.helpers.entity_component.async_update_entity(
|
||||||
|
'binary_sensor.all_state')
|
||||||
|
await hass.helpers.entity_component.async_update_entity(
|
||||||
|
'binary_sensor.all_icon')
|
||||||
|
await hass.helpers.entity_component.async_update_entity(
|
||||||
|
'binary_sensor.all_entity_picture')
|
||||||
|
|
||||||
|
assert hass.states.get('binary_sensor.all_state').state == 'on'
|
||||||
|
assert hass.states.get('binary_sensor.all_icon').state == 'off'
|
||||||
|
assert hass.states.get('binary_sensor.all_entity_picture').state == 'off'
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue