Skip to content

Commit

Permalink
Template binary sensor to not track all state changes (#18573)
Browse files Browse the repository at this point in the history
  • Loading branch information
amelchio authored and balloob committed Nov 19, 2018
1 parent 97c4934 commit 84fd66c
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 30 deletions.
32 changes: 25 additions & 7 deletions homeassistant/components/binary_sensor/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ async def async_setup_platform(hass, config, async_add_entities,
entity_ids = set()
manual_entity_ids = device_config.get(ATTR_ENTITY_ID)

for template in (
value_template,
icon_template,
entity_picture_template,
invalid_templates = []

for tpl_name, template in (
(CONF_VALUE_TEMPLATE, value_template),
(CONF_ICON_TEMPLATE, icon_template),
(CONF_ENTITY_PICTURE_TEMPLATE, entity_picture_template),
):
if template is None:
continue
Expand All @@ -73,6 +75,8 @@ async def async_setup_platform(hass, config, async_add_entities,
template_entity_ids = template.extract_entities()
if template_entity_ids == MATCH_ALL:
entity_ids = MATCH_ALL
# Cut off _template from name
invalid_templates.append(tpl_name[:-9])
elif entity_ids != MATCH_ALL:
entity_ids |= set(template_entity_ids)

Expand All @@ -81,6 +85,14 @@ async def async_setup_platform(hass, config, async_add_entities,
elif entity_ids != MATCH_ALL:
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)
device_class = device_config.get(CONF_DEVICE_CLASS)
delay_on = device_config.get(CONF_DELAY_ON)
Expand Down Expand Up @@ -132,10 +144,12 @@ def template_bsensor_state_listener(entity, old_state, new_state):
@callback
def template_bsensor_startup(event):
"""Update template on startup."""
async_track_state_change(
self.hass, self._entities, template_bsensor_state_listener)
if self._entities != MATCH_ALL:
# 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(
EVENT_HOMEASSISTANT_START, template_bsensor_startup)
Expand Down Expand Up @@ -233,3 +247,7 @@ def set_state():
async_track_same_state(
self.hass, period, set_state, entity_ids=self._entities,
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()
109 changes: 86 additions & 23 deletions tests/components/binary_sensor/test_template.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
"""The tests for the Template Binary sensor platform."""
import asyncio
from datetime import timedelta
import unittest
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.components.binary_sensor import template
from homeassistant.exceptions import TemplateError
Expand Down Expand Up @@ -182,7 +181,7 @@ def test_match_all(self, _async_render):

self.hass.states.set('sensor.any_state', 'update')
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):
"""Test the attributes."""
Expand Down Expand Up @@ -252,8 +251,7 @@ def test_update_template_error(self, mock_render):
run_callback_threadsafe(self.hass.loop, vs.async_check_state).result()


@asyncio.coroutine
def test_template_delay_on(hass):
async def test_template_delay_on(hass):
"""Test binary sensor template delay on."""
config = {
'binary_sensor': {
Expand All @@ -269,51 +267,50 @@ def test_template_delay_on(hass):
},
},
}
yield from setup.async_setup_component(hass, 'binary_sensor', config)
yield from hass.async_start()
await setup.async_setup_component(hass, 'binary_sensor', config)
await hass.async_start()

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')
assert state.state == 'off'

future = dt_util.utcnow() + timedelta(seconds=5)
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')
assert state.state == 'on'

# check with time changes
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')
assert state.state == 'off'

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')
assert state.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')
assert state.state == 'off'

future = dt_util.utcnow() + timedelta(seconds=5)
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')
assert state.state == 'off'


@asyncio.coroutine
def test_template_delay_off(hass):
async def test_template_delay_off(hass):
"""Test binary sensor template delay off."""
config = {
'binary_sensor': {
Expand All @@ -330,44 +327,110 @@ def test_template_delay_off(hass):
},
}
hass.states.async_set('sensor.test_state', 'on')
yield from setup.async_setup_component(hass, 'binary_sensor', config)
yield from hass.async_start()
await setup.async_setup_component(hass, 'binary_sensor', config)
await hass.async_start()

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')
assert state.state == 'on'

future = dt_util.utcnow() + timedelta(seconds=5)
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')
assert state.state == 'off'

# check with time changes
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')
assert state.state == 'on'

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')
assert state.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')
assert state.state == 'on'

future = dt_util.utcnow() + timedelta(seconds=5)
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')
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'

0 comments on commit 84fd66c

Please sign in to comment.