mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Automation: initial state > restore state (#6911)
* Automation: initial state > restore state * Clean up code * Ensure MQTT defaults are used. * Ensure failed platforms always return None * Automation: write state to state machine after start
This commit is contained in:
parent
3895979e39
commit
23645da74c
@ -82,8 +82,7 @@ _CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])
|
|||||||
|
|
||||||
PLATFORM_SCHEMA = vol.Schema({
|
PLATFORM_SCHEMA = vol.Schema({
|
||||||
CONF_ALIAS: cv.string,
|
CONF_ALIAS: cv.string,
|
||||||
vol.Optional(CONF_INITIAL_STATE,
|
vol.Optional(CONF_INITIAL_STATE): cv.boolean,
|
||||||
default=DEFAULT_INITIAL_STATE): cv.boolean,
|
|
||||||
vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean,
|
vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean,
|
||||||
vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
|
vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
|
||||||
vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
|
vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
|
||||||
@ -102,15 +101,13 @@ TRIGGER_SERVICE_SCHEMA = vol.Schema({
|
|||||||
RELOAD_SERVICE_SCHEMA = vol.Schema({})
|
RELOAD_SERVICE_SCHEMA = vol.Schema({})
|
||||||
|
|
||||||
|
|
||||||
def is_on(hass, entity_id=None):
|
def is_on(hass, entity_id):
|
||||||
"""
|
"""
|
||||||
Return true if specified automation entity_id is on.
|
Return true if specified automation entity_id is on.
|
||||||
|
|
||||||
Check all automation if no entity_id specified.
|
Async friendly.
|
||||||
"""
|
"""
|
||||||
entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
|
return hass.states.is_state(entity_id, STATE_ON)
|
||||||
return any(hass.states.is_state(entity_id, STATE_ON)
|
|
||||||
for entity_id in entity_ids)
|
|
||||||
|
|
||||||
|
|
||||||
def turn_on(hass, entity_id=None):
|
def turn_on(hass, entity_id=None):
|
||||||
@ -232,7 +229,6 @@ class AutomationEntity(ToggleEntity):
|
|||||||
self._async_detach_triggers = None
|
self._async_detach_triggers = None
|
||||||
self._cond_func = cond_func
|
self._cond_func = cond_func
|
||||||
self._async_action = async_action
|
self._async_action = async_action
|
||||||
self._enabled = False
|
|
||||||
self._last_triggered = None
|
self._last_triggered = None
|
||||||
self._hidden = hidden
|
self._hidden = hidden
|
||||||
self._initial_state = initial_state
|
self._initial_state = initial_state
|
||||||
@ -262,24 +258,26 @@ class AutomationEntity(ToggleEntity):
|
|||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return True if entity is on."""
|
"""Return True if entity is on."""
|
||||||
return self._enabled
|
return self._async_detach_triggers is not None
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_added_to_hass(self) -> None:
|
def async_added_to_hass(self) -> None:
|
||||||
"""Startup with initial state or previous state."""
|
"""Startup with initial state or previous state."""
|
||||||
enable_automation = False
|
enable_automation = DEFAULT_INITIAL_STATE
|
||||||
|
|
||||||
state = yield from async_get_last_state(self.hass, self.entity_id)
|
if self._initial_state is not None:
|
||||||
if state is None:
|
enable_automation = self._initial_state
|
||||||
if self._initial_state:
|
|
||||||
enable_automation = True
|
|
||||||
else:
|
else:
|
||||||
|
state = yield from async_get_last_state(self.hass, self.entity_id)
|
||||||
|
if state:
|
||||||
|
enable_automation = state.state == STATE_ON
|
||||||
self._last_triggered = state.attributes.get('last_triggered')
|
self._last_triggered = state.attributes.get('last_triggered')
|
||||||
if state.state == STATE_ON:
|
|
||||||
enable_automation = True
|
|
||||||
|
|
||||||
# HomeAssistant is on bootstrap
|
if not enable_automation:
|
||||||
if enable_automation and self.hass.state == CoreState.not_running:
|
return
|
||||||
|
|
||||||
|
# HomeAssistant is starting up
|
||||||
|
elif self.hass.state == CoreState.not_running:
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_enable_automation(event):
|
def async_enable_automation(event):
|
||||||
"""Start automation on startup."""
|
"""Start automation on startup."""
|
||||||
@ -289,27 +287,25 @@ class AutomationEntity(ToggleEntity):
|
|||||||
EVENT_HOMEASSISTANT_START, async_enable_automation)
|
EVENT_HOMEASSISTANT_START, async_enable_automation)
|
||||||
|
|
||||||
# HomeAssistant is running
|
# HomeAssistant is running
|
||||||
elif enable_automation:
|
else:
|
||||||
yield from self.async_enable()
|
yield from self.async_enable()
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_turn_on(self, **kwargs) -> None:
|
def async_turn_on(self, **kwargs) -> None:
|
||||||
"""Turn the entity on and update the state."""
|
"""Turn the entity on and update the state."""
|
||||||
if self._enabled:
|
if self.is_on:
|
||||||
return
|
return
|
||||||
|
|
||||||
yield from self.async_enable()
|
yield from self.async_enable()
|
||||||
yield from self.async_update_ha_state()
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_turn_off(self, **kwargs) -> None:
|
def async_turn_off(self, **kwargs) -> None:
|
||||||
"""Turn the entity off."""
|
"""Turn the entity off."""
|
||||||
if not self._enabled:
|
if not self.is_on:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._async_detach_triggers()
|
self._async_detach_triggers()
|
||||||
self._async_detach_triggers = None
|
self._async_detach_triggers = None
|
||||||
self._enabled = False
|
|
||||||
yield from self.async_update_ha_state()
|
yield from self.async_update_ha_state()
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
@ -335,12 +331,12 @@ class AutomationEntity(ToggleEntity):
|
|||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
"""
|
"""
|
||||||
if self._enabled:
|
if self.is_on:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._async_detach_triggers = yield from self._async_attach_triggers(
|
self._async_detach_triggers = yield from self._async_attach_triggers(
|
||||||
self.async_trigger)
|
self.async_trigger)
|
||||||
self._enabled = True
|
yield from self.async_update_ha_state()
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
@ -359,7 +355,7 @@ def _async_process_config(hass, config, component):
|
|||||||
list_no)
|
list_no)
|
||||||
|
|
||||||
hidden = config_block[CONF_HIDE_ENTITY]
|
hidden = config_block[CONF_HIDE_ENTITY]
|
||||||
initial_state = config_block[CONF_INITIAL_STATE]
|
initial_state = config_block.get(CONF_INITIAL_STATE)
|
||||||
|
|
||||||
action = _async_get_action(hass, config_block.get(CONF_ACTION, {}),
|
action = _async_get_action(hass, config_block.get(CONF_ACTION, {}),
|
||||||
name)
|
name)
|
||||||
|
@ -277,7 +277,10 @@ def _async_setup_discovery(hass, config):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_setup(hass, config):
|
def async_setup(hass, config):
|
||||||
"""Start the MQTT protocol service."""
|
"""Start the MQTT protocol service."""
|
||||||
conf = config.get(DOMAIN, {})
|
conf = config.get(DOMAIN)
|
||||||
|
|
||||||
|
if conf is None:
|
||||||
|
conf = CONFIG_SCHEMA({DOMAIN: {}})[DOMAIN]
|
||||||
|
|
||||||
client_id = conf.get(CONF_CLIENT_ID)
|
client_id = conf.get(CONF_CLIENT_ID)
|
||||||
keepalive = conf.get(CONF_KEEPALIVE)
|
keepalive = conf.get(CONF_KEEPALIVE)
|
||||||
|
@ -253,7 +253,7 @@ def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
|
|||||||
|
|
||||||
if not dep_success:
|
if not dep_success:
|
||||||
log_error('Could not setup all dependencies.')
|
log_error('Could not setup all dependencies.')
|
||||||
return False
|
return None
|
||||||
|
|
||||||
if not hass.config.skip_pip and hasattr(platform, 'REQUIREMENTS'):
|
if not hass.config.skip_pip and hasattr(platform, 'REQUIREMENTS'):
|
||||||
req_success = yield from _async_process_requirements(
|
req_success = yield from _async_process_requirements(
|
||||||
|
@ -14,7 +14,7 @@ import homeassistant.util.dt as dt_util
|
|||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
assert_setup_component, get_test_home_assistant, fire_time_changed,
|
assert_setup_component, get_test_home_assistant, fire_time_changed,
|
||||||
mock_component, mock_service, mock_restore_cache)
|
mock_service, mock_restore_cache)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
@ -24,7 +24,6 @@ class TestAutomation(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Setup things to be run when tests are started."""
|
"""Setup things to be run when tests are started."""
|
||||||
self.hass = get_test_home_assistant()
|
self.hass = get_test_home_assistant()
|
||||||
mock_component(self.hass, 'group')
|
|
||||||
self.calls = mock_service(self.hass, 'test', 'automation')
|
self.calls = mock_service(self.hass, 'test', 'automation')
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
@ -156,46 +155,6 @@ class TestAutomation(unittest.TestCase):
|
|||||||
self.assertEqual(['hello.world'],
|
self.assertEqual(['hello.world'],
|
||||||
self.calls[0].data.get(ATTR_ENTITY_ID))
|
self.calls[0].data.get(ATTR_ENTITY_ID))
|
||||||
|
|
||||||
def test_service_initial_value_off(self):
|
|
||||||
"""Test initial value off."""
|
|
||||||
entity_id = 'automation.hello'
|
|
||||||
|
|
||||||
assert setup_component(self.hass, automation.DOMAIN, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'alias': 'hello',
|
|
||||||
'initial_state': 'off',
|
|
||||||
'trigger': {
|
|
||||||
'platform': 'event',
|
|
||||||
'event_type': 'test_event',
|
|
||||||
},
|
|
||||||
'action': {
|
|
||||||
'service': 'test.automation',
|
|
||||||
'entity_id': ['hello.world', 'hello.world2']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
assert not automation.is_on(self.hass, entity_id)
|
|
||||||
|
|
||||||
def test_service_initial_value_on(self):
|
|
||||||
"""Test initial value on."""
|
|
||||||
entity_id = 'automation.hello'
|
|
||||||
|
|
||||||
assert setup_component(self.hass, automation.DOMAIN, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'alias': 'hello',
|
|
||||||
'initial_state': 'on',
|
|
||||||
'trigger': {
|
|
||||||
'platform': 'event',
|
|
||||||
'event_type': 'test_event',
|
|
||||||
},
|
|
||||||
'action': {
|
|
||||||
'service': 'test.automation',
|
|
||||||
'entity_id': ['hello.world', 'hello.world2']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
assert automation.is_on(self.hass, entity_id)
|
|
||||||
|
|
||||||
def test_service_specify_entity_id_list(self):
|
def test_service_specify_entity_id_list(self):
|
||||||
"""Test service data."""
|
"""Test service data."""
|
||||||
assert setup_component(self.hass, automation.DOMAIN, {
|
assert setup_component(self.hass, automation.DOMAIN, {
|
||||||
@ -569,38 +528,6 @@ class TestAutomation(unittest.TestCase):
|
|||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
assert len(self.calls) == 2
|
assert len(self.calls) == 2
|
||||||
|
|
||||||
def test_automation_not_trigger_on_bootstrap(self):
|
|
||||||
"""Test if automation is not trigger on bootstrap."""
|
|
||||||
self.hass.state = CoreState.not_running
|
|
||||||
|
|
||||||
assert setup_component(self.hass, automation.DOMAIN, {
|
|
||||||
automation.DOMAIN: {
|
|
||||||
'trigger': {
|
|
||||||
'platform': 'event',
|
|
||||||
'event_type': 'test_event',
|
|
||||||
},
|
|
||||||
'action': {
|
|
||||||
'service': 'test.automation',
|
|
||||||
'entity_id': 'hello.world'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.block_till_done()
|
|
||||||
|
|
||||||
assert len(self.calls) == 0
|
|
||||||
|
|
||||||
self.hass.bus.fire(EVENT_HOMEASSISTANT_START)
|
|
||||||
self.hass.block_till_done()
|
|
||||||
self.hass.states = CoreState.running
|
|
||||||
|
|
||||||
self.hass.bus.fire('test_event')
|
|
||||||
self.hass.block_till_done()
|
|
||||||
|
|
||||||
assert len(self.calls) == 1
|
|
||||||
assert ['hello.world'] == self.calls[0].data.get(ATTR_ENTITY_ID)
|
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def test_automation_restore_state(hass):
|
def test_automation_restore_state(hass):
|
||||||
@ -653,3 +580,209 @@ def test_automation_restore_state(hass):
|
|||||||
yield from hass.async_block_till_done()
|
yield from hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_initial_value_off(hass):
|
||||||
|
"""Test initial value off."""
|
||||||
|
calls = mock_service(hass, 'test', 'automation')
|
||||||
|
|
||||||
|
res = yield from async_setup_component(hass, automation.DOMAIN, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'alias': 'hello',
|
||||||
|
'initial_state': 'off',
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation',
|
||||||
|
'entity_id': 'hello.world'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert res
|
||||||
|
assert not automation.is_on(hass, 'automation.hello')
|
||||||
|
|
||||||
|
hass.bus.async_fire('test_event')
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
assert len(calls) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_initial_value_on(hass):
|
||||||
|
"""Test initial value on."""
|
||||||
|
calls = mock_service(hass, 'test', 'automation')
|
||||||
|
|
||||||
|
res = yield from async_setup_component(hass, automation.DOMAIN, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'alias': 'hello',
|
||||||
|
'initial_state': 'on',
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation',
|
||||||
|
'entity_id': ['hello.world', 'hello.world2']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert res
|
||||||
|
assert automation.is_on(hass, 'automation.hello')
|
||||||
|
|
||||||
|
hass.bus.async_fire('test_event')
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_initial_value_off_but_restore_on(hass):
|
||||||
|
"""Test initial value off and restored state is turned on."""
|
||||||
|
calls = mock_service(hass, 'test', 'automation')
|
||||||
|
mock_restore_cache(hass, (
|
||||||
|
State('automation.hello', STATE_ON),
|
||||||
|
))
|
||||||
|
|
||||||
|
res = yield from async_setup_component(hass, automation.DOMAIN, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'alias': 'hello',
|
||||||
|
'initial_state': 'off',
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation',
|
||||||
|
'entity_id': 'hello.world'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert res
|
||||||
|
assert not automation.is_on(hass, 'automation.hello')
|
||||||
|
|
||||||
|
hass.bus.async_fire('test_event')
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
assert len(calls) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_initial_value_on_but_restore_off(hass):
|
||||||
|
"""Test initial value on and restored state is turned off."""
|
||||||
|
calls = mock_service(hass, 'test', 'automation')
|
||||||
|
mock_restore_cache(hass, (
|
||||||
|
State('automation.hello', STATE_OFF),
|
||||||
|
))
|
||||||
|
|
||||||
|
res = yield from async_setup_component(hass, automation.DOMAIN, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'alias': 'hello',
|
||||||
|
'initial_state': 'on',
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation',
|
||||||
|
'entity_id': 'hello.world'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert res
|
||||||
|
assert automation.is_on(hass, 'automation.hello')
|
||||||
|
|
||||||
|
hass.bus.async_fire('test_event')
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_no_initial_value_and_restore_off(hass):
|
||||||
|
"""Test initial value off and restored state is turned on."""
|
||||||
|
calls = mock_service(hass, 'test', 'automation')
|
||||||
|
mock_restore_cache(hass, (
|
||||||
|
State('automation.hello', STATE_OFF),
|
||||||
|
))
|
||||||
|
|
||||||
|
res = yield from async_setup_component(hass, automation.DOMAIN, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'alias': 'hello',
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation',
|
||||||
|
'entity_id': 'hello.world'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert res
|
||||||
|
assert not automation.is_on(hass, 'automation.hello')
|
||||||
|
|
||||||
|
hass.bus.async_fire('test_event')
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
assert len(calls) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_automation_is_on_if_no_initial_state_or_restore(hass):
|
||||||
|
"""Test initial value is on when no initial state or restored state."""
|
||||||
|
calls = mock_service(hass, 'test', 'automation')
|
||||||
|
|
||||||
|
res = yield from async_setup_component(hass, automation.DOMAIN, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'alias': 'hello',
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation',
|
||||||
|
'entity_id': 'hello.world'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert res
|
||||||
|
assert automation.is_on(hass, 'automation.hello')
|
||||||
|
|
||||||
|
hass.bus.async_fire('test_event')
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_automation_not_trigger_on_bootstrap(hass):
|
||||||
|
"""Test if automation is not trigger on bootstrap."""
|
||||||
|
hass.state = CoreState.not_running
|
||||||
|
calls = mock_service(hass, 'test', 'automation')
|
||||||
|
|
||||||
|
res = yield from async_setup_component(hass, automation.DOMAIN, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'alias': 'hello',
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation',
|
||||||
|
'entity_id': 'hello.world'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert res
|
||||||
|
assert not automation.is_on(hass, 'automation.hello')
|
||||||
|
|
||||||
|
hass.bus.async_fire('test_event')
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
assert len(calls) == 0
|
||||||
|
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
assert automation.is_on(hass, 'automation.hello')
|
||||||
|
|
||||||
|
hass.bus.async_fire('test_event')
|
||||||
|
yield from hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(calls) == 1
|
||||||
|
assert ['hello.world'] == calls[0].data.get(ATTR_ENTITY_ID)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user