Template binary sensor to not track all state changes (#18573)

This commit is contained in:
Anders Melchiorsen 2018-11-19 12:10:48 +01:00 committed by Paulus Schoutsen
parent 97c493448b
commit 84fd66c8a1
2 changed files with 111 additions and 30 deletions

View file

@ -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()

View file

@ -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'