mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Add if to automation
This commit is contained in:
parent
046c5653cb
commit
2a11d02fe4
@ -9,7 +9,7 @@ import logging
|
|||||||
from homeassistant.bootstrap import prepare_setup_platform
|
from homeassistant.bootstrap import prepare_setup_platform
|
||||||
from homeassistant.helpers import config_per_platform
|
from homeassistant.helpers import config_per_platform
|
||||||
from homeassistant.util import split_entity_id
|
from homeassistant.util import split_entity_id
|
||||||
from homeassistant.const import ATTR_ENTITY_ID
|
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
|
||||||
|
|
||||||
DOMAIN = "automation"
|
DOMAIN = "automation"
|
||||||
|
|
||||||
@ -19,6 +19,7 @@ CONF_ALIAS = "alias"
|
|||||||
CONF_SERVICE = "execute_service"
|
CONF_SERVICE = "execute_service"
|
||||||
CONF_SERVICE_ENTITY_ID = "service_entity_id"
|
CONF_SERVICE_ENTITY_ID = "service_entity_id"
|
||||||
CONF_SERVICE_DATA = "service_data"
|
CONF_SERVICE_DATA = "service_data"
|
||||||
|
CONF_IF = "if"
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -34,7 +35,12 @@ def setup(hass, config):
|
|||||||
_LOGGER.error("Unknown automation platform specified: %s", p_type)
|
_LOGGER.error("Unknown automation platform specified: %s", p_type)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if platform.register(hass, p_config, _get_action(hass, p_config)):
|
action = _get_action(hass, p_config)
|
||||||
|
|
||||||
|
if CONF_IF in p_config:
|
||||||
|
action = _process_if(hass, config, p_config[CONF_IF], action)
|
||||||
|
|
||||||
|
if platform.trigger(hass, p_config, action):
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Initialized %s rule %s", p_type, p_config.get(CONF_ALIAS, ""))
|
"Initialized %s rule %s", p_type, p_config.get(CONF_ALIAS, ""))
|
||||||
success = True
|
success = True
|
||||||
@ -72,3 +78,28 @@ def _get_action(hass, config):
|
|||||||
hass.services.call(domain, service, service_data)
|
hass.services.call(domain, service, service_data)
|
||||||
|
|
||||||
return action
|
return action
|
||||||
|
|
||||||
|
|
||||||
|
def _process_if(hass, config, if_configs, action):
|
||||||
|
""" Processes if checks. """
|
||||||
|
|
||||||
|
if isinstance(if_configs, dict):
|
||||||
|
if_configs = [if_configs]
|
||||||
|
|
||||||
|
for if_config in if_configs:
|
||||||
|
p_type = if_config.get(CONF_PLATFORM)
|
||||||
|
if p_type is None:
|
||||||
|
_LOGGER.error("No platform defined found for if-statement %s",
|
||||||
|
if_config)
|
||||||
|
continue
|
||||||
|
|
||||||
|
platform = prepare_setup_platform(hass, config, DOMAIN, p_type)
|
||||||
|
|
||||||
|
if platform is None or not hasattr(platform, 'if_action'):
|
||||||
|
_LOGGER.error("Unsupported if-statement platform specified: %s",
|
||||||
|
p_type)
|
||||||
|
continue
|
||||||
|
|
||||||
|
action = platform.if_action(hass, if_config, action)
|
||||||
|
|
||||||
|
return action
|
||||||
|
@ -12,7 +12,7 @@ CONF_EVENT_DATA = "event_data"
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def register(hass, config, action):
|
def trigger(hass, config, action):
|
||||||
""" Listen for events based on config. """
|
""" Listen for events based on config. """
|
||||||
event_type = config.get(CONF_EVENT_TYPE)
|
event_type = config.get(CONF_EVENT_TYPE)
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ CONF_TOPIC = 'mqtt_topic'
|
|||||||
CONF_PAYLOAD = 'mqtt_payload'
|
CONF_PAYLOAD = 'mqtt_payload'
|
||||||
|
|
||||||
|
|
||||||
def register(hass, config, action):
|
def trigger(hass, config, action):
|
||||||
""" Listen for state changes based on `config`. """
|
""" Listen for state changes based on `config`. """
|
||||||
topic = config.get(CONF_TOPIC)
|
topic = config.get(CONF_TOPIC)
|
||||||
payload = config.get(CONF_PAYLOAD)
|
payload = config.get(CONF_PAYLOAD)
|
||||||
|
@ -16,7 +16,7 @@ CONF_ABOVE = "state_above"
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def register(hass, config, action):
|
def trigger(hass, config, action):
|
||||||
""" Listen for state changes based on `config`. """
|
""" Listen for state changes based on `config`. """
|
||||||
entity_id = config.get(CONF_ENTITY_ID)
|
entity_id = config.get(CONF_ENTITY_ID)
|
||||||
|
|
||||||
|
@ -13,15 +13,16 @@ from homeassistant.const import MATCH_ALL
|
|||||||
CONF_ENTITY_ID = "state_entity_id"
|
CONF_ENTITY_ID = "state_entity_id"
|
||||||
CONF_FROM = "state_from"
|
CONF_FROM = "state_from"
|
||||||
CONF_TO = "state_to"
|
CONF_TO = "state_to"
|
||||||
|
CONF_STATE = "state"
|
||||||
|
|
||||||
|
|
||||||
def register(hass, config, action):
|
def trigger(hass, config, action):
|
||||||
""" Listen for state changes based on `config`. """
|
""" Listen for state changes based on `config`. """
|
||||||
entity_id = config.get(CONF_ENTITY_ID)
|
entity_id = config.get(CONF_ENTITY_ID)
|
||||||
|
|
||||||
if entity_id is None:
|
if entity_id is None:
|
||||||
logging.getLogger(__name__).error(
|
logging.getLogger(__name__).error(
|
||||||
"Missing configuration key %s", CONF_ENTITY_ID)
|
"Missing trigger configuration key %s", CONF_ENTITY_ID)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
from_state = config.get(CONF_FROM, MATCH_ALL)
|
from_state = config.get(CONF_FROM, MATCH_ALL)
|
||||||
@ -35,3 +36,22 @@ def register(hass, config, action):
|
|||||||
hass, entity_id, state_automation_listener, from_state, to_state)
|
hass, entity_id, state_automation_listener, from_state, to_state)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def if_action(hass, config, action):
|
||||||
|
""" Wraps action method with state based condition. """
|
||||||
|
entity_id = config.get(CONF_ENTITY_ID)
|
||||||
|
state = config.get(CONF_STATE)
|
||||||
|
|
||||||
|
if entity_id is None or state is None:
|
||||||
|
logging.getLogger(__name__).error(
|
||||||
|
"Missing if-condition configuration key %s or %s", CONF_ENTITY_ID,
|
||||||
|
CONF_STATE)
|
||||||
|
return action
|
||||||
|
|
||||||
|
def state_if():
|
||||||
|
""" Execute action if state matches. """
|
||||||
|
if hass.states.is_state(entity_id, state):
|
||||||
|
action()
|
||||||
|
|
||||||
|
return state_if
|
||||||
|
@ -4,15 +4,23 @@ homeassistant.components.automation.time
|
|||||||
|
|
||||||
Offers time listening automation rules.
|
Offers time listening automation rules.
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
from homeassistant.util import convert
|
from homeassistant.util import convert
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.helpers.event import track_time_change
|
from homeassistant.helpers.event import track_time_change
|
||||||
|
|
||||||
CONF_HOURS = "time_hours"
|
CONF_HOURS = "time_hours"
|
||||||
CONF_MINUTES = "time_minutes"
|
CONF_MINUTES = "time_minutes"
|
||||||
CONF_SECONDS = "time_seconds"
|
CONF_SECONDS = "time_seconds"
|
||||||
|
CONF_BEFORE = "before"
|
||||||
|
CONF_AFTER = "after"
|
||||||
|
CONF_WEEKDAY = "weekday"
|
||||||
|
|
||||||
|
WEEKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
|
||||||
|
|
||||||
|
|
||||||
def register(hass, config, action):
|
def trigger(hass, config, action):
|
||||||
""" Listen for state changes based on `config`. """
|
""" Listen for state changes based on `config`. """
|
||||||
hours = convert(config.get(CONF_HOURS), int)
|
hours = convert(config.get(CONF_HOURS), int)
|
||||||
minutes = convert(config.get(CONF_MINUTES), int)
|
minutes = convert(config.get(CONF_MINUTES), int)
|
||||||
@ -26,3 +34,49 @@ def register(hass, config, action):
|
|||||||
hour=hours, minute=minutes, second=seconds)
|
hour=hours, minute=minutes, second=seconds)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def if_action(hass, config, action):
|
||||||
|
""" Wraps action method with time based condition. """
|
||||||
|
before = config.get(CONF_BEFORE)
|
||||||
|
after = config.get(CONF_AFTER)
|
||||||
|
weekday = config.get(CONF_WEEKDAY)
|
||||||
|
|
||||||
|
if before is None and after is None and weekday is None:
|
||||||
|
logging.getLogger(__name__).error(
|
||||||
|
"Missing if-condition configuration key %s, %s or %s",
|
||||||
|
CONF_BEFORE, CONF_AFTER, CONF_WEEKDAY)
|
||||||
|
|
||||||
|
def time_if():
|
||||||
|
""" Validate time based if-condition """
|
||||||
|
now = dt_util.now()
|
||||||
|
|
||||||
|
if before is not None:
|
||||||
|
# Strip seconds if given
|
||||||
|
before_h, before_m = before.split(':')[0:2]
|
||||||
|
|
||||||
|
before_point = now.replace(hour=int(before_h),
|
||||||
|
minute=int(before_m))
|
||||||
|
|
||||||
|
if now > before_point:
|
||||||
|
return
|
||||||
|
|
||||||
|
if after is not None:
|
||||||
|
# Strip seconds if given
|
||||||
|
after_h, after_m = after.split(':')[0:2]
|
||||||
|
|
||||||
|
after_point = now.replace(hour=int(after_h), minute=int(after_m))
|
||||||
|
|
||||||
|
if now < after_point:
|
||||||
|
return
|
||||||
|
|
||||||
|
if weekday is not None:
|
||||||
|
now_weekday = WEEKDAYS[now.weekday()]
|
||||||
|
|
||||||
|
if isinstance(weekday, str) and weekday != now_weekday or \
|
||||||
|
now_weekday not in weekday:
|
||||||
|
return
|
||||||
|
|
||||||
|
action()
|
||||||
|
|
||||||
|
return time_if
|
||||||
|
@ -8,7 +8,7 @@ import unittest
|
|||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
import homeassistant.components.automation.state as state
|
from homeassistant.components.automation import event, state
|
||||||
from homeassistant.const import CONF_PLATFORM
|
from homeassistant.const import CONF_PLATFORM
|
||||||
|
|
||||||
|
|
||||||
@ -137,3 +137,31 @@ class TestAutomationState(unittest.TestCase):
|
|||||||
self.hass.states.set('test.entity', 'world')
|
self.hass.states.set('test.entity', 'world')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_action(self):
|
||||||
|
entity_id = 'domain.test_entity'
|
||||||
|
test_state = 'new_state'
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
CONF_PLATFORM: 'event',
|
||||||
|
event.CONF_EVENT_TYPE: 'test_event',
|
||||||
|
automation.CONF_SERVICE: 'test.automation',
|
||||||
|
automation.CONF_IF: [{
|
||||||
|
CONF_PLATFORM: 'state',
|
||||||
|
state.CONF_ENTITY_ID: entity_id,
|
||||||
|
state.CONF_STATE: test_state,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, test_state)
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
self.hass.states.set(entity_id, test_state + 'something')
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
@ -4,13 +4,14 @@ tests.test_component_demo
|
|||||||
|
|
||||||
Tests demo component.
|
Tests demo component.
|
||||||
"""
|
"""
|
||||||
|
from datetime import timedelta
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.loader as loader
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
import homeassistant.components.automation.time as time
|
from homeassistant.components.automation import time, event
|
||||||
from homeassistant.const import CONF_PLATFORM
|
from homeassistant.const import CONF_PLATFORM
|
||||||
|
|
||||||
from tests.common import fire_time_changed
|
from tests.common import fire_time_changed
|
||||||
@ -94,3 +95,133 @@ class TestAutomationTime(unittest.TestCase):
|
|||||||
self.hass.states.set('test.entity', 'world')
|
self.hass.states.set('test.entity', 'world')
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual(1, len(self.calls))
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_action_before(self):
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
CONF_PLATFORM: 'event',
|
||||||
|
event.CONF_EVENT_TYPE: 'test_event',
|
||||||
|
automation.CONF_SERVICE: 'test.automation',
|
||||||
|
automation.CONF_IF: {
|
||||||
|
CONF_PLATFORM: 'time',
|
||||||
|
time.CONF_BEFORE: '10:00'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
before_10 = dt_util.now().replace(hour=8)
|
||||||
|
after_10 = dt_util.now().replace(hour=14)
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=before_10):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=after_10):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_action_after(self):
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
CONF_PLATFORM: 'event',
|
||||||
|
event.CONF_EVENT_TYPE: 'test_event',
|
||||||
|
automation.CONF_SERVICE: 'test.automation',
|
||||||
|
automation.CONF_IF: {
|
||||||
|
CONF_PLATFORM: 'time',
|
||||||
|
time.CONF_AFTER: '10:00'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
before_10 = dt_util.now().replace(hour=8)
|
||||||
|
after_10 = dt_util.now().replace(hour=14)
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=before_10):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=after_10):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_action_one_weekday(self):
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
CONF_PLATFORM: 'event',
|
||||||
|
event.CONF_EVENT_TYPE: 'test_event',
|
||||||
|
automation.CONF_SERVICE: 'test.automation',
|
||||||
|
automation.CONF_IF: {
|
||||||
|
CONF_PLATFORM: 'time',
|
||||||
|
time.CONF_WEEKDAY: 'mon',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
days_past_monday = dt_util.now().weekday()
|
||||||
|
monday = dt_util.now() - timedelta(days=days_past_monday)
|
||||||
|
tuesday = monday + timedelta(days=1)
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=monday):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=tuesday):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_action_list_weekday(self):
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
CONF_PLATFORM: 'event',
|
||||||
|
event.CONF_EVENT_TYPE: 'test_event',
|
||||||
|
automation.CONF_SERVICE: 'test.automation',
|
||||||
|
automation.CONF_IF: {
|
||||||
|
CONF_PLATFORM: 'time',
|
||||||
|
time.CONF_WEEKDAY: ['mon', 'tue'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
days_past_monday = dt_util.now().weekday()
|
||||||
|
monday = dt_util.now() - timedelta(days=days_past_monday)
|
||||||
|
tuesday = monday + timedelta(days=1)
|
||||||
|
wednesday = tuesday + timedelta(days=1)
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=monday):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=tuesday):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(2, len(self.calls))
|
||||||
|
|
||||||
|
with patch('homeassistant.components.automation.time.dt_util.now',
|
||||||
|
return_value=wednesday):
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
|
self.assertEqual(2, len(self.calls))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user