diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py index 0bb63b12ca9..612177533b7 100644 --- a/homeassistant/components/binary_sensor/template.py +++ b/homeassistant/components/binary_sensor/template.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.template/ import logging from homeassistant.components.binary_sensor import (BinarySensorDevice, - DOMAIN, + ENTITY_ID_FORMAT, SENSOR_CLASSES) from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE from homeassistant.core import EVENT_STATE_CHANGED @@ -16,7 +16,6 @@ from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers import template from homeassistant.util import slugify -ENTITY_ID_FORMAT = DOMAIN + '.{}' CONF_SENSORS = 'sensors' _LOGGER = logging.getLogger(__name__) @@ -76,34 +75,22 @@ class BinarySensorTemplate(BinarySensorDevice): def __init__(self, hass, device, friendly_name, sensor_class, value_template): """Initialize the Template binary sensor.""" - self._hass = hass - self._device = device + self.hass = hass + self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device, + hass=hass) self._name = friendly_name self._sensor_class = sensor_class self._template = value_template self._state = None - self.entity_id = generate_entity_id( - ENTITY_ID_FORMAT, device, - hass=hass) + self.update() - _LOGGER.info('Started template sensor %s', device) - hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener) + def template_bsensor_event_listener(event): + """Called when the target device changes state.""" + self.update_ha_state(True) - def _event_listener(self, event): - if not hasattr(self, 'hass') or self.hass is None: - return - self.update_ha_state(True) - - @property - def should_poll(self): - """No polling needed.""" - return False - - @property - def sensor_class(self): - """Return the sensor class of the sensor.""" - return self._sensor_class + hass.bus.listen(EVENT_STATE_CHANGED, + template_bsensor_event_listener) @property def name(self): @@ -115,10 +102,21 @@ class BinarySensorTemplate(BinarySensorDevice): """Return true if sensor is on.""" return self._state + @property + def sensor_class(self): + """Return the sensor class of the sensor.""" + return self._sensor_class + + @property + def should_poll(self): + """No polling needed.""" + return False + def update(self): """Get the latest data and update the state.""" try: - value = template.render(self._hass, self._template) + self._state = template.render(self.hass, + self._template).lower() == 'true' except TemplateError as ex: if ex.args and ex.args[0].startswith( "UndefinedError: 'None' has no attribute"): @@ -126,5 +124,4 @@ class BinarySensorTemplate(BinarySensorDevice): _LOGGER.warning(ex) return _LOGGER.error(ex) - value = 'false' - self._state = value.lower() == 'true' + self._state = False diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/sensor/template.py index 590cc754759..b2b702646cb 100644 --- a/homeassistant/components/sensor/template.py +++ b/homeassistant/components/sensor/template.py @@ -6,7 +6,7 @@ https://home-assistant.io/components/sensor.template/ """ import logging -from homeassistant.components.sensor import DOMAIN +from homeassistant.components.sensor import ENTITY_ID_FORMAT from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE) from homeassistant.core import EVENT_STATE_CHANGED @@ -15,11 +15,8 @@ from homeassistant.helpers.entity import Entity, generate_entity_id from homeassistant.helpers import template from homeassistant.util import slugify -ENTITY_ID_FORMAT = DOMAIN + '.{}' - _LOGGER = logging.getLogger(__name__) CONF_SENSORS = 'sensors' -STATE_ERROR = 'error' # pylint: disable=unused-argument @@ -70,21 +67,21 @@ class SensorTemplate(Entity): def __init__(self, hass, device_id, friendly_name, unit_of_measurement, state_template): """Initialize the sensor.""" + self.hass = hass self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) - - self.hass = hass self._name = friendly_name self._unit_of_measurement = unit_of_measurement self._template = state_template - self.update() - self.hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener) + self._state = None - def _event_listener(self, event): - """Called when the target device changes state.""" - if not hasattr(self, 'hass') or self.hass is None: - return - self.update_ha_state(True) + self.update() + + def template_sensor_event_listener(event): + """Called when the target device changes state.""" + self.update_ha_state(True) + + hass.bus.listen(EVENT_STATE_CHANGED, template_sensor_event_listener) @property def name(self): @@ -111,10 +108,10 @@ class SensorTemplate(Entity): try: self._state = template.render(self.hass, self._template) except TemplateError as ex: - self._state = STATE_ERROR if ex.args and ex.args[0].startswith( "UndefinedError: 'None' has no attribute"): # Common during HA startup - so just a warning _LOGGER.warning(ex) return + self._state = None _LOGGER.error(ex) diff --git a/homeassistant/components/switch/template.py b/homeassistant/components/switch/template.py index ea433b55e3c..68aa1c3bd69 100644 --- a/homeassistant/components/switch/template.py +++ b/homeassistant/components/switch/template.py @@ -6,7 +6,7 @@ https://home-assistant.io/components/switch.template/ """ import logging -from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice from homeassistant.const import ( ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, STATE_OFF, STATE_ON) from homeassistant.core import EVENT_STATE_CHANGED @@ -16,17 +16,14 @@ from homeassistant.helpers.service import call_from_config from homeassistant.helpers import template from homeassistant.util import slugify -ENTITY_ID_FORMAT = DOMAIN + '.{}' - -_LOGGER = logging.getLogger(__name__) - CONF_SWITCHES = 'switches' -STATE_ERROR = 'error' - ON_ACTION = 'turn_on' OFF_ACTION = 'turn_off' +_LOGGER = logging.getLogger(__name__) +_VALID_STATES = [STATE_ON, STATE_OFF, 'true', 'false'] + # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): @@ -84,32 +81,44 @@ class SwitchTemplate(SwitchDevice): def __init__(self, hass, device_id, friendly_name, state_template, on_action, off_action): """Initialize the Template switch.""" + self.hass = hass self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) - self.hass = hass self._name = friendly_name self._template = state_template self._on_action = on_action self._off_action = off_action - self.update() - self.hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener) + self._state = False - def _event_listener(self, event): - """Called when the target device changes state.""" - if not hasattr(self, 'hass') or self.hass is None: - return - self.update_ha_state(True) + self.update() + + def template_switch_event_listener(event): + """Called when the target device changes state.""" + self.update_ha_state(True) + + hass.bus.listen(EVENT_STATE_CHANGED, + template_switch_event_listener) @property def name(self): """Return the name of the switch.""" return self._name + @property + def is_on(self): + """Return true if device is on.""" + return self._state + @property def should_poll(self): """No polling needed.""" return False + @property + def available(self): + """If switch is available.""" + return self._state is not None + def turn_on(self, **kwargs): """Fire the on action.""" call_from_config(self.hass, self._on_action, True) @@ -118,30 +127,19 @@ class SwitchTemplate(SwitchDevice): """Fire the off action.""" call_from_config(self.hass, self._off_action, True) - @property - def is_on(self): - """Return true if device is on.""" - return self._value.lower() == 'true' or self._value == STATE_ON - - @property - def is_off(self): - """Return true if device is off.""" - return self._value.lower() == 'false' or self._value == STATE_OFF - - @property - def available(self): - """Return true if entity is available.""" - return self.is_on or self.is_off - def update(self): """Update the state from the template.""" try: - self._value = template.render(self.hass, self._template) - if not self.available: + state = template.render(self.hass, self._template).lower() + + if state in _VALID_STATES: + self._state = state in ('true', STATE_ON) + else: _LOGGER.error( - "`%s` is not a switch state, setting %s to unavailable", - self._value, self.entity_id) + 'Received invalid switch is_on state: %s. Expected: %s', + state, ', '.join(_VALID_STATES)) + self._state = None except TemplateError as ex: - self._value = STATE_ERROR _LOGGER.error(ex) + self._state = None diff --git a/tests/components/binary_sensor/test_template.py b/tests/components/binary_sensor/test_template.py index af3f65c306d..478439209f5 100644 --- a/tests/components/binary_sensor/test_template.py +++ b/tests/components/binary_sensor/test_template.py @@ -2,9 +2,12 @@ import unittest from unittest import mock +from homeassistant.const import EVENT_STATE_CHANGED from homeassistant.components.binary_sensor import template from homeassistant.exceptions import TemplateError +from tests.common import get_test_home_assistant + class TestBinarySensorTemplate(unittest.TestCase): """Test for Binary sensor template platform.""" @@ -88,21 +91,19 @@ class TestBinarySensorTemplate(unittest.TestCase): def test_event(self): """"Test the event.""" - hass = mock.MagicMock() + hass = get_test_home_assistant() vs = template.BinarySensorTemplate(hass, 'parent', 'Parent', 'motion', '{{ 1 > 1 }}') - with mock.patch.object(vs, '_event_listener') as mock_update: - vs._event_listener(None) - assert mock_update.call_count == 1 + vs.update_ha_state() + hass.pool.block_till_done() - def test_update(self): - """"Test the update.""" - hass = mock.MagicMock() - vs = template.BinarySensorTemplate(hass, 'parent', 'Parent', - 'motion', '{{ 2 > 1 }}') - self.assertEqual(None, vs._state) - vs.update() - self.assertTrue(vs._state) + with mock.patch.object(vs, 'update') as mock_update: + hass.bus.fire(EVENT_STATE_CHANGED) + hass.pool.block_till_done() + try: + assert mock_update.call_count == 1 + finally: + hass.stop() @mock.patch('homeassistant.helpers.template.render') def test_update_template_error(self, mock_render): diff --git a/tests/components/sensor/test_template.py b/tests/components/sensor/test_template.py index aa657f04bfa..0170e6b3dfa 100644 --- a/tests/components/sensor/test_template.py +++ b/tests/components/sensor/test_template.py @@ -54,7 +54,7 @@ class TestTemplateSensor: self.hass.states.set('sensor.test_state', 'Works') self.hass.pool.block_till_done() state = self.hass.states.get('sensor.test_template_sensor') - assert state.state == 'error' + assert state.state == 'unknown' def test_template_attribute_missing(self): """Test missing attribute template.""" @@ -71,7 +71,7 @@ class TestTemplateSensor: }) state = self.hass.states.get('sensor.test_template_sensor') - assert state.state == 'error' + assert state.state == 'unknown' def test_invalid_name_does_not_create(self): """Test invalid name."""