New Events and Context Fixes (#18765)
* Add new events for automation trigger and script run, fix context for image processing, add tests to ensure same context * remove custom logbook entry for automation and add new automation event to logbook * code review updates
This commit is contained in:
parent
8e9c73eb18
commit
b900005d1e
8 changed files with 214 additions and 19 deletions
|
@ -16,7 +16,8 @@ from homeassistant.core import CoreState
|
|||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
||||
SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID)
|
||||
SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID,
|
||||
EVENT_AUTOMATION_TRIGGERED, ATTR_NAME)
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import extract_domain_configs, script, condition
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
|
@ -286,6 +287,10 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
|||
"""
|
||||
if skip_condition or self._cond_func(variables):
|
||||
self.async_set_context(context)
|
||||
self.hass.bus.async_fire(EVENT_AUTOMATION_TRIGGERED, {
|
||||
ATTR_NAME: self._name,
|
||||
ATTR_ENTITY_ID: self.entity_id,
|
||||
}, context=context)
|
||||
await self._async_action(self.entity_id, variables, context)
|
||||
self._last_triggered = utcnow()
|
||||
await self.async_update_ha_state()
|
||||
|
@ -370,8 +375,6 @@ def _async_get_action(hass, config, name):
|
|||
async def action(entity_id, variables, context):
|
||||
"""Execute an action."""
|
||||
_LOGGER.info('Executing %s', name)
|
||||
hass.components.logbook.async_log_entry(
|
||||
name, 'has been triggered', DOMAIN, entity_id)
|
||||
await script_obj.async_run(variables, context)
|
||||
|
||||
return action
|
||||
|
|
|
@ -76,10 +76,14 @@ async def async_setup(hass, config):
|
|||
"""Service handler for scan."""
|
||||
image_entities = component.async_extract_from_service(service)
|
||||
|
||||
update_task = [entity.async_update_ha_state(True) for
|
||||
entity in image_entities]
|
||||
if update_task:
|
||||
await asyncio.wait(update_task, loop=hass.loop)
|
||||
update_tasks = []
|
||||
for entity in image_entities:
|
||||
entity.async_set_context(service.context)
|
||||
update_tasks.append(
|
||||
entity.async_update_ha_state(True))
|
||||
|
||||
if update_tasks:
|
||||
await asyncio.wait(update_tasks, loop=hass.loop)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_SCAN, async_scan_service,
|
||||
|
|
|
@ -17,7 +17,8 @@ from homeassistant.const import (
|
|||
ATTR_DOMAIN, ATTR_ENTITY_ID, ATTR_HIDDEN, ATTR_NAME, ATTR_SERVICE,
|
||||
CONF_EXCLUDE, CONF_INCLUDE, EVENT_HOMEASSISTANT_START,
|
||||
EVENT_HOMEASSISTANT_STOP, EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED,
|
||||
HTTP_BAD_REQUEST, STATE_NOT_HOME, STATE_OFF, STATE_ON)
|
||||
EVENT_AUTOMATION_TRIGGERED, EVENT_SCRIPT_STARTED, HTTP_BAD_REQUEST,
|
||||
STATE_NOT_HOME, STATE_OFF, STATE_ON)
|
||||
from homeassistant.core import (
|
||||
DOMAIN as HA_DOMAIN, State, callback, split_entity_id)
|
||||
from homeassistant.components.alexa.smart_home import EVENT_ALEXA_SMART_HOME
|
||||
|
@ -316,6 +317,28 @@ def humanify(hass, events):
|
|||
'context_user_id': event.context.user_id
|
||||
}
|
||||
|
||||
elif event.event_type == EVENT_AUTOMATION_TRIGGERED:
|
||||
yield {
|
||||
'when': event.time_fired,
|
||||
'name': event.data.get(ATTR_NAME),
|
||||
'message': "has been triggered",
|
||||
'domain': 'automation',
|
||||
'entity_id': event.data.get(ATTR_ENTITY_ID),
|
||||
'context_id': event.context.id,
|
||||
'context_user_id': event.context.user_id
|
||||
}
|
||||
|
||||
elif event.event_type == EVENT_SCRIPT_STARTED:
|
||||
yield {
|
||||
'when': event.time_fired,
|
||||
'name': event.data.get(ATTR_NAME),
|
||||
'message': 'started',
|
||||
'domain': 'script',
|
||||
'entity_id': event.data.get(ATTR_ENTITY_ID),
|
||||
'context_id': event.context.id,
|
||||
'context_user_id': event.context.user_id
|
||||
}
|
||||
|
||||
|
||||
def _get_related_entity_ids(session, entity_filter):
|
||||
from homeassistant.components.recorder.models import States
|
||||
|
|
|
@ -14,7 +14,8 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
||||
SERVICE_TOGGLE, SERVICE_RELOAD, STATE_ON, CONF_ALIAS)
|
||||
SERVICE_TOGGLE, SERVICE_RELOAD, STATE_ON, CONF_ALIAS,
|
||||
EVENT_SCRIPT_STARTED, ATTR_NAME)
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
@ -170,8 +171,14 @@ class ScriptEntity(ToggleEntity):
|
|||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the script on."""
|
||||
context = kwargs.get('context')
|
||||
self.async_set_context(context)
|
||||
self.hass.bus.async_fire(EVENT_SCRIPT_STARTED, {
|
||||
ATTR_NAME: self.script.name,
|
||||
ATTR_ENTITY_ID: self.entity_id,
|
||||
}, context=context)
|
||||
await self.script.async_run(
|
||||
kwargs.get(ATTR_VARIABLES), kwargs.get('context'))
|
||||
kwargs.get(ATTR_VARIABLES), context)
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn script off."""
|
||||
|
|
|
@ -170,6 +170,8 @@ EVENT_SERVICE_REMOVED = 'service_removed'
|
|||
EVENT_LOGBOOK_ENTRY = 'logbook_entry'
|
||||
EVENT_THEMES_UPDATED = 'themes_updated'
|
||||
EVENT_TIMER_OUT_OF_SYNC = 'timer_out_of_sync'
|
||||
EVENT_AUTOMATION_TRIGGERED = 'automation_triggered'
|
||||
EVENT_SCRIPT_STARTED = 'script_started'
|
||||
|
||||
# #### DEVICE CLASSES ####
|
||||
DEVICE_CLASS_BATTERY = 'battery'
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
"""The tests for the automation component."""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.core import State, CoreState
|
||||
from homeassistant.core import State, CoreState, Context
|
||||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.components.automation as automation
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, STATE_ON, STATE_OFF, EVENT_HOMEASSISTANT_START)
|
||||
ATTR_NAME, ATTR_ENTITY_ID, STATE_ON, STATE_OFF,
|
||||
EVENT_HOMEASSISTANT_START, EVENT_AUTOMATION_TRIGGERED)
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
|
@ -342,6 +343,66 @@ async def test_automation_calling_two_actions(hass, calls):
|
|||
assert calls[1].data['position'] == 1
|
||||
|
||||
|
||||
async def test_shared_context(hass, calls):
|
||||
"""Test that the shared context is passed down the chain."""
|
||||
assert await async_setup_component(hass, automation.DOMAIN, {
|
||||
automation.DOMAIN: [
|
||||
{
|
||||
'alias': 'hello',
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event',
|
||||
},
|
||||
'action': {'event': 'test_event2'}
|
||||
},
|
||||
{
|
||||
'alias': 'bye',
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event2',
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation',
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
context = Context()
|
||||
automation_mock = Mock()
|
||||
event_mock = Mock()
|
||||
|
||||
hass.bus.async_listen('test_event2', automation_mock)
|
||||
hass.bus.async_listen(EVENT_AUTOMATION_TRIGGERED, event_mock)
|
||||
hass.bus.async_fire('test_event', context=context)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Ensure events was fired
|
||||
assert automation_mock.call_count == 1
|
||||
assert event_mock.call_count == 2
|
||||
|
||||
# Ensure context carries through the event
|
||||
args, kwargs = automation_mock.call_args
|
||||
assert args[0].context == context
|
||||
|
||||
for call in event_mock.call_args_list:
|
||||
args, kwargs = call
|
||||
assert args[0].context == context
|
||||
# Ensure event data has all attributes set
|
||||
assert args[0].data.get(ATTR_NAME) is not None
|
||||
assert args[0].data.get(ATTR_ENTITY_ID) is not None
|
||||
|
||||
# Ensure the automation state shares the same context
|
||||
state = hass.states.get('automation.hello')
|
||||
assert state is not None
|
||||
assert state.context == context
|
||||
|
||||
# Ensure the service call from the second automation
|
||||
# shares the same context
|
||||
assert len(calls) == 1
|
||||
assert calls[0].context == context
|
||||
|
||||
|
||||
async def test_services(hass, calls):
|
||||
"""Test the automation services for turning entities on/off."""
|
||||
entity_id = 'automation.hello'
|
||||
|
|
|
@ -10,9 +10,10 @@ import voluptuous as vol
|
|||
from homeassistant.components import sun
|
||||
import homeassistant.core as ha
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_SERVICE,
|
||||
ATTR_ENTITY_ID, ATTR_SERVICE, ATTR_NAME,
|
||||
EVENT_STATE_CHANGED, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
||||
ATTR_HIDDEN, STATE_NOT_HOME, STATE_ON, STATE_OFF)
|
||||
EVENT_AUTOMATION_TRIGGERED, EVENT_SCRIPT_STARTED, ATTR_HIDDEN,
|
||||
STATE_NOT_HOME, STATE_ON, STATE_OFF)
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.components import logbook, recorder
|
||||
from homeassistant.components.alexa.smart_home import EVENT_ALEXA_SMART_HOME
|
||||
|
@ -751,7 +752,55 @@ async def test_humanify_homekit_changed_event(hass):
|
|||
assert event1['entity_id'] == 'lock.front_door'
|
||||
|
||||
assert event2['name'] == 'HomeKit'
|
||||
assert event1['domain'] == DOMAIN_HOMEKIT
|
||||
assert event2['domain'] == DOMAIN_HOMEKIT
|
||||
assert event2['message'] == \
|
||||
'send command set_cover_position to 75 for Window'
|
||||
assert event2['entity_id'] == 'cover.window'
|
||||
|
||||
|
||||
async def test_humanify_automation_triggered_event(hass):
|
||||
"""Test humanifying Automation Trigger event."""
|
||||
event1, event2 = list(logbook.humanify(hass, [
|
||||
ha.Event(EVENT_AUTOMATION_TRIGGERED, {
|
||||
ATTR_ENTITY_ID: 'automation.hello',
|
||||
ATTR_NAME: 'Hello Automation',
|
||||
}),
|
||||
ha.Event(EVENT_AUTOMATION_TRIGGERED, {
|
||||
ATTR_ENTITY_ID: 'automation.bye',
|
||||
ATTR_NAME: 'Bye Automation',
|
||||
}),
|
||||
]))
|
||||
|
||||
assert event1['name'] == 'Hello Automation'
|
||||
assert event1['domain'] == 'automation'
|
||||
assert event1['message'] == 'has been triggered'
|
||||
assert event1['entity_id'] == 'automation.hello'
|
||||
|
||||
assert event2['name'] == 'Bye Automation'
|
||||
assert event2['domain'] == 'automation'
|
||||
assert event2['message'] == 'has been triggered'
|
||||
assert event2['entity_id'] == 'automation.bye'
|
||||
|
||||
|
||||
async def test_humanify_script_started_event(hass):
|
||||
"""Test humanifying Script Run event."""
|
||||
event1, event2 = list(logbook.humanify(hass, [
|
||||
ha.Event(EVENT_SCRIPT_STARTED, {
|
||||
ATTR_ENTITY_ID: 'script.hello',
|
||||
ATTR_NAME: 'Hello Script'
|
||||
}),
|
||||
ha.Event(EVENT_SCRIPT_STARTED, {
|
||||
ATTR_ENTITY_ID: 'script.bye',
|
||||
ATTR_NAME: 'Bye Script'
|
||||
}),
|
||||
]))
|
||||
|
||||
assert event1['name'] == 'Hello Script'
|
||||
assert event1['domain'] == 'script'
|
||||
assert event1['message'] == 'started'
|
||||
assert event1['entity_id'] == 'script.hello'
|
||||
|
||||
assert event2['name'] == 'Bye Script'
|
||||
assert event2['domain'] == 'script'
|
||||
assert event2['message'] == 'started'
|
||||
assert event2['entity_id'] == 'script.bye'
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
"""The tests for the Script component."""
|
||||
# pylint: disable=protected-access
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
from homeassistant.components import script
|
||||
from homeassistant.components.script import DOMAIN
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_RELOAD, SERVICE_TOGGLE, SERVICE_TURN_OFF)
|
||||
ATTR_ENTITY_ID, ATTR_NAME, SERVICE_RELOAD, SERVICE_TOGGLE,
|
||||
SERVICE_TURN_OFF, SERVICE_TURN_ON, EVENT_SCRIPT_STARTED)
|
||||
from homeassistant.core import Context, callback, split_entity_id
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.setup import setup_component
|
||||
from homeassistant.setup import setup_component, async_setup_component
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
@ -254,3 +255,48 @@ class TestScriptComponent(unittest.TestCase):
|
|||
|
||||
assert self.hass.states.get("script.test2") is not None
|
||||
assert self.hass.services.has_service(script.DOMAIN, 'test2')
|
||||
|
||||
|
||||
async def test_shared_context(hass):
|
||||
"""Test that the shared context is passed down the chain."""
|
||||
event = 'test_event'
|
||||
context = Context()
|
||||
|
||||
event_mock = Mock()
|
||||
run_mock = Mock()
|
||||
|
||||
hass.bus.async_listen(event, event_mock)
|
||||
hass.bus.async_listen(EVENT_SCRIPT_STARTED, run_mock)
|
||||
|
||||
assert await async_setup_component(hass, 'script', {
|
||||
'script': {
|
||||
'test': {
|
||||
'sequence': [
|
||||
{'event': event}
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await hass.services.async_call(DOMAIN, SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||
context=context)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert event_mock.call_count == 1
|
||||
assert run_mock.call_count == 1
|
||||
|
||||
args, kwargs = run_mock.call_args
|
||||
assert args[0].context == context
|
||||
# Ensure event data has all attributes set
|
||||
assert args[0].data.get(ATTR_NAME) == 'test'
|
||||
assert args[0].data.get(ATTR_ENTITY_ID) == 'script.test'
|
||||
|
||||
# Ensure context carries through the event
|
||||
args, kwargs = event_mock.call_args
|
||||
assert args[0].context == context
|
||||
|
||||
# Ensure the script state shares the same context
|
||||
state = hass.states.get('script.test')
|
||||
assert state is not None
|
||||
assert state.context == context
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue