Merge pull request #353 from stefan-jonasson/dev

numeric_state automation platform
This commit is contained in:
Paulus Schoutsen 2015-09-13 17:13:06 -07:00
commit 513f6e9c3c
2 changed files with 302 additions and 0 deletions

View File

@ -0,0 +1,68 @@
"""
homeassistant.components.automation.state
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers state listening automation rules.
"""
import logging
from homeassistant.helpers.event import track_state_change
CONF_ENTITY_ID = "state_entity_id"
CONF_BELOW = "state_below"
CONF_ABOVE = "state_above"
_LOGGER = logging.getLogger(__name__)
def register(hass, config, action):
""" Listen for state changes based on `config`. """
entity_id = config.get(CONF_ENTITY_ID)
if entity_id is None:
_LOGGER.error("Missing configuration key %s", CONF_ENTITY_ID)
return False
below = config.get(CONF_BELOW)
above = config.get(CONF_ABOVE)
if below is None and above is None:
_LOGGER.error("Missing configuration key."
" One of %s or %s is required",
CONF_BELOW, CONF_ABOVE)
return False
def _in_range(value, range_start, range_end):
""" Checks if value is inside the range
:param value:
:param range_start:
:param range_end:
:return:
"""
try:
value = float(value)
except ValueError:
_LOGGER.warn("Missing value in numeric check")
return False
if range_start is not None and range_end is not None:
return float(range_start) <= value < float(range_end)
elif range_end is not None:
return value < float(range_end)
else:
return float(range_start) <= value
def state_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """
# Fire action if we go from outside range into range
if _in_range(to_s.state, above, below) and \
(from_s is None or not _in_range(from_s.state, above, below)):
action()
track_state_change(
hass, entity_id, state_automation_listener)
return True

View File

@ -0,0 +1,234 @@
"""
tests.test_component_demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests demo component.
"""
import unittest
import homeassistant.core as ha
import homeassistant.components.automation as automation
import homeassistant.components.automation.numeric_state as numeric_state
from homeassistant.const import CONF_PLATFORM
class TestAutomationNumericState(unittest.TestCase):
""" Test the event automation. """
def setUp(self): # pylint: disable=invalid-name
self.hass = ha.HomeAssistant()
self.calls = []
def record_call(service):
self.calls.append(service)
self.hass.services.register('test', 'automation', record_call)
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_setup_fails_if_no_entity_id(self):
self.assertFalse(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
}
}))
def test_setup_fails_if_no_condition(self):
self.assertFalse(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
automation.CONF_SERVICE: 'test.automation'
}
}))
def test_if_fires_on_entity_change_below(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
}
}))
# 9 is below 10
self.hass.states.set('test.entity', 9)
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_on_entity_change_over_to_below(self):
self.hass.states.set('test.entity', 11)
self.hass.pool.block_till_done()
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
}
}))
# 9 is below 10
self.hass.states.set('test.entity', 9)
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_not_fires_on_entity_change_below_to_below(self):
self.hass.states.set('test.entity', 9)
self.hass.pool.block_till_done()
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
}
}))
# 9 is below 10 so this should not fire again
self.hass.states.set('test.entity', 8)
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_fires_on_entity_change_above(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_ABOVE: 10,
automation.CONF_SERVICE: 'test.automation'
}
}))
# 11 is above 10
self.hass.states.set('test.entity', 11)
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_on_entity_change_below_to_above(self):
# set initial state
self.hass.states.set('test.entity', 9)
self.hass.pool.block_till_done()
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_ABOVE: 10,
automation.CONF_SERVICE: 'test.automation'
}
}))
# 11 is above 10 and 9 is below
self.hass.states.set('test.entity', 11)
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_not_fires_on_entity_change_above_to_above(self):
# set initial state
self.hass.states.set('test.entity', 11)
self.hass.pool.block_till_done()
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_ABOVE: 10,
automation.CONF_SERVICE: 'test.automation'
}
}))
# 11 is above 10 so this should fire again
self.hass.states.set('test.entity', 12)
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_fires_on_entity_change_below_range(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_ABOVE: 5,
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
}
}))
# 9 is below 10
self.hass.states.set('test.entity', 9)
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_on_entity_change_below_above_range(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_ABOVE: 5,
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
}
}))
# 4 is below 5
self.hass.states.set('test.entity', 4)
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_fires_on_entity_change_over_to_below_range(self):
self.hass.states.set('test.entity', 11)
self.hass.pool.block_till_done()
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_ABOVE: 5,
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
}
}))
# 9 is below 10
self.hass.states.set('test.entity', 9)
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fires_on_entity_change_over_to_below_above_range(self):
self.hass.states.set('test.entity', 11)
self.hass.pool.block_till_done()
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.entity',
numeric_state.CONF_ABOVE: 5,
numeric_state.CONF_BELOW: 10,
automation.CONF_SERVICE: 'test.automation'
}
}))
# 4 is below 5 so it should not fire
self.hass.states.set('test.entity', 4)
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
def test_if_not_fires_if_entity_not_match(self):
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
CONF_PLATFORM: 'numeric_state',
numeric_state.CONF_ENTITY_ID: 'test.another_entity',
numeric_state.CONF_ABOVE: 10,
automation.CONF_SERVICE: 'test.automation'
}
}))
self.hass.states.set('test.entity', 11)
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))