Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions homeassistant/components/automation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
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
Expand Down Expand Up @@ -286,6 +287,10 @@ async def async_trigger(self, variables, skip_condition=False,
"""
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()
Expand Down Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions homeassistant/components/image_processing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,14 @@ async def async_scan_service(service):
"""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,
Expand Down
25 changes: 24 additions & 1 deletion homeassistant/components/logbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
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
Expand Down Expand Up @@ -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
Expand Down
11 changes: 9 additions & 2 deletions homeassistant/components/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

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
Expand Down Expand Up @@ -170,8 +171,14 @@ def is_on(self):

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."""
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@
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'
Expand Down
67 changes: 64 additions & 3 deletions tests/components/automation/test_init.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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'
Expand Down
55 changes: 52 additions & 3 deletions tests/components/test_logbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
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
Expand Down Expand Up @@ -731,7 +732,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'
52 changes: 49 additions & 3 deletions tests/components/test_script.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -254,3 +255,48 @@ def test_reload_service(self):

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