diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index b34a6b7cf40..a1f1563f5e1 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -17,7 +17,6 @@ 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) -from homeassistant.components import logbook from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import extract_domain_configs, script, condition from homeassistant.helpers.entity import ToggleEntity @@ -369,8 +368,8 @@ def _async_get_action(hass, config, name): async def action(entity_id, variables, context): """Execute an action.""" _LOGGER.info('Executing %s', name) - logbook.async_log_entry( - hass, name, 'has been triggered', DOMAIN, entity_id) + hass.components.logbook.async_log_entry( + name, 'has been triggered', DOMAIN, entity_id) await script_obj.async_run(variables, context) return action diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index c4fcf53a9c1..e282a133f1d 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -10,6 +10,7 @@ import logging import voluptuous as vol +from homeassistant.loader import bind_hass from homeassistant.components import sun from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( @@ -17,8 +18,9 @@ from homeassistant.const import ( CONF_INCLUDE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED, HTTP_BAD_REQUEST, STATE_NOT_HOME, STATE_OFF, STATE_ON) -from homeassistant.core import DOMAIN as HA_DOMAIN -from homeassistant.core import State, callback, split_entity_id +from homeassistant.core import ( + DOMAIN as HA_DOMAIN, State, callback, split_entity_id) +from homeassistant.components.alexa.smart_home import EVENT_ALEXA_SMART_HOME import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util @@ -53,7 +55,8 @@ CONFIG_SCHEMA = vol.Schema({ ALL_EVENT_TYPES = [ EVENT_STATE_CHANGED, EVENT_LOGBOOK_ENTRY, - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, + EVENT_ALEXA_SMART_HOME ] LOG_MESSAGE_SCHEMA = vol.Schema({ @@ -64,11 +67,13 @@ LOG_MESSAGE_SCHEMA = vol.Schema({ }) +@bind_hass def log_entry(hass, name, message, domain=None, entity_id=None): """Add an entry to the logbook.""" hass.add_job(async_log_entry, hass, name, message, domain, entity_id) +@bind_hass def async_log_entry(hass, name, message, domain=None, entity_id=None): """Add an entry to the logbook.""" data = { @@ -140,30 +145,7 @@ class LogbookView(HomeAssistantView): return await hass.async_add_job(json_events) -class Entry: - """A human readable version of the log.""" - - def __init__(self, when=None, name=None, message=None, domain=None, - entity_id=None): - """Initialize the entry.""" - self.when = when - self.name = name - self.message = message - self.domain = domain - self.entity_id = entity_id - - def as_dict(self): - """Convert entry to a dict to be used within JSON.""" - return { - 'when': self.when, - 'name': self.name, - 'message': self.message, - 'domain': self.domain, - 'entity_id': self.entity_id, - } - - -def humanify(events): +def humanify(hass, events): """Generate a converted list of events into Entry objects. Will try to group events if possible: @@ -224,20 +206,28 @@ def humanify(events): to_state.attributes.get('unit_of_measurement'): continue - yield Entry( - event.time_fired, - name=to_state.name, - message=_entry_message_from_state(domain, to_state), - domain=domain, - entity_id=to_state.entity_id) + yield { + 'when': event.time_fired, + 'name': to_state.name, + 'message': _entry_message_from_state(domain, to_state), + 'domain': domain, + 'entity_id': to_state.entity_id, + 'context_id': event.context.id, + 'context_user_id': event.context.user_id + } elif event.event_type == EVENT_HOMEASSISTANT_START: if start_stop_events.get(event.time_fired.minute) == 2: continue - yield Entry( - event.time_fired, "Home Assistant", "started", - domain=HA_DOMAIN) + yield { + 'when': event.time_fired, + 'name': "Home Assistant", + 'message': "started", + 'domain': HA_DOMAIN, + 'context_id': event.context.id, + 'context_user_id': event.context.user_id + } elif event.event_type == EVENT_HOMEASSISTANT_STOP: if start_stop_events.get(event.time_fired.minute) == 2: @@ -245,9 +235,14 @@ def humanify(events): else: action = "stopped" - yield Entry( - event.time_fired, "Home Assistant", action, - domain=HA_DOMAIN) + yield { + 'when': event.time_fired, + 'name': "Home Assistant", + 'message': action, + 'domain': HA_DOMAIN, + 'context_id': event.context.id, + 'context_user_id': event.context.user_id + } elif event.event_type == EVENT_LOGBOOK_ENTRY: domain = event.data.get(ATTR_DOMAIN) @@ -258,10 +253,38 @@ def humanify(events): except IndexError: pass - yield Entry( - event.time_fired, event.data.get(ATTR_NAME), - event.data.get(ATTR_MESSAGE), domain, - entity_id) + yield { + 'when': event.time_fired, + 'name': event.data.get(ATTR_NAME), + 'message': event.data.get(ATTR_MESSAGE), + 'domain': domain, + 'entity_id': entity_id, + 'context_id': event.context.id, + 'context_user_id': event.context.user_id + } + + elif event.event_type == EVENT_ALEXA_SMART_HOME: + data = event.data + entity_id = data.get('entity_id') + + if entity_id: + state = hass.states.get(entity_id) + name = state.name if state else entity_id + message = "send command {}/{} for {}".format( + data['namespace'], data['name'], name) + else: + message = "send command {}/{}".format( + data['namespace'], data['name']) + + yield { + 'when': event.time_fired, + 'name': 'Amazon Alexa', + 'message': message, + 'domain': 'alexa', + 'entity_id': entity_id, + 'context_id': event.context.id, + 'context_user_id': event.context.user_id + } def _get_events(hass, config, start_day, end_day): @@ -279,7 +302,7 @@ def _get_events(hass, config, start_day, end_day): .filter((States.last_updated == States.last_changed) | (States.state_id.is_(None))) events = execute(query) - return humanify(_exclude_events(events, config)) + return humanify(hass, _exclude_events(events, config)) def _exclude_events(events, config): diff --git a/homeassistant/scripts/benchmark/__init__.py b/homeassistant/scripts/benchmark/__init__.py index 98de59f2da1..f0df58a51f4 100644 --- a/homeassistant/scripts/benchmark/__init__.py +++ b/homeassistant/scripts/benchmark/__init__.py @@ -186,6 +186,6 @@ def _logbook_filtering(hass, last_changed, last_updated): # pylint: disable=protected-access events = logbook._exclude_events(events, {}) - list(logbook.humanify(events)) + list(logbook.humanify(None, events)) return timer() - start diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py index cf78fbec352..9ccb8f58a87 100644 --- a/tests/components/test_logbook.py +++ b/tests/components/test_logbook.py @@ -11,6 +11,7 @@ from homeassistant.const import ( 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 from homeassistant.setup import setup_component, async_setup_component from tests.common import ( @@ -99,7 +100,7 @@ class TestComponentLogbook(unittest.TestCase): eventB = self.create_state_changed_event(pointB, entity_id, 20) eventC = self.create_state_changed_event(pointC, entity_id, 30) - entries = list(logbook.humanify((eventA, eventB, eventC))) + entries = list(logbook.humanify(self.hass, (eventA, eventB, eventC))) self.assertEqual(2, len(entries)) self.assert_entry( @@ -116,7 +117,7 @@ class TestComponentLogbook(unittest.TestCase): eventA = self.create_state_changed_event( pointA, entity_id, 10, attributes) - entries = list(logbook.humanify((eventA,))) + entries = list(logbook.humanify(self.hass, (eventA,))) self.assertEqual(0, len(entries)) @@ -133,7 +134,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), {}) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry( @@ -155,7 +156,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), {}) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry( @@ -177,7 +178,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), {}) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry( @@ -203,7 +204,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events( (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), config[logbook.DOMAIN]) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry( @@ -229,7 +230,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events( (ha.Event(EVENT_HOMEASSISTANT_START), eventA, eventB), config[logbook.DOMAIN]) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry(entries[0], name='Home Assistant', message='started', @@ -266,7 +267,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events( (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), config[logbook.DOMAIN]) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry( @@ -292,7 +293,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events( (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), config[logbook.DOMAIN]) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry( @@ -318,7 +319,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events( (ha.Event(EVENT_HOMEASSISTANT_START), eventA, eventB), config[logbook.DOMAIN]) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(2, len(entries)) self.assert_entry(entries[0], name='Home Assistant', message='started', @@ -352,7 +353,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events( (ha.Event(EVENT_HOMEASSISTANT_START), eventA1, eventA2, eventA3, eventB1, eventB2), config[logbook.DOMAIN]) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(3, len(entries)) self.assert_entry(entries[0], name='Home Assistant', message='started', @@ -373,7 +374,7 @@ class TestComponentLogbook(unittest.TestCase): {'auto': True}) events = logbook._exclude_events((eventA, eventB), {}) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(1, len(entries)) self.assert_entry(entries[0], pointA, 'bla', domain='switch', @@ -391,29 +392,18 @@ class TestComponentLogbook(unittest.TestCase): pointA, entity_id2, 20, last_changed=pointA, last_updated=pointB) events = logbook._exclude_events((eventA, eventB), {}) - entries = list(logbook.humanify(events)) + entries = list(logbook.humanify(self.hass, events)) self.assertEqual(1, len(entries)) self.assert_entry(entries[0], pointA, 'bla', domain='switch', entity_id=entity_id) - def test_entry_to_dict(self): - """Test conversion of entry to dict.""" - entry = logbook.Entry( - dt_util.utcnow(), 'Alarm', 'is triggered', 'switch', 'test_switch' - ) - data = entry.as_dict() - self.assertEqual('Alarm', data.get(logbook.ATTR_NAME)) - self.assertEqual('is triggered', data.get(logbook.ATTR_MESSAGE)) - self.assertEqual('switch', data.get(logbook.ATTR_DOMAIN)) - self.assertEqual('test_switch', data.get(logbook.ATTR_ENTITY_ID)) - def test_home_assistant_start_stop_grouped(self): """Test if HA start and stop events are grouped. Events that are occurring in the same minute. """ - entries = list(logbook.humanify(( + entries = list(logbook.humanify(self.hass, ( ha.Event(EVENT_HOMEASSISTANT_STOP), ha.Event(EVENT_HOMEASSISTANT_START), ))) @@ -428,7 +418,7 @@ class TestComponentLogbook(unittest.TestCase): entity_id = 'switch.bla' pointA = dt_util.utcnow() - entries = list(logbook.humanify(( + entries = list(logbook.humanify(self.hass, ( ha.Event(EVENT_HOMEASSISTANT_START), self.create_state_changed_event(pointA, entity_id, 10) ))) @@ -509,7 +499,7 @@ class TestComponentLogbook(unittest.TestCase): message = 'has a custom entry' entity_id = 'sun.sun' - entries = list(logbook.humanify(( + entries = list(logbook.humanify(self.hass, ( ha.Event(logbook.EVENT_LOGBOOK_ENTRY, { logbook.ATTR_NAME: name, logbook.ATTR_MESSAGE: message, @@ -526,19 +516,19 @@ class TestComponentLogbook(unittest.TestCase): domain=None, entity_id=None): """Assert an entry is what is expected.""" if when: - self.assertEqual(when, entry.when) + self.assertEqual(when, entry['when']) if name: - self.assertEqual(name, entry.name) + self.assertEqual(name, entry['name']) if message: - self.assertEqual(message, entry.message) + self.assertEqual(message, entry['message']) if domain: - self.assertEqual(domain, entry.domain) + self.assertEqual(domain, entry['domain']) if entity_id: - self.assertEqual(entity_id, entry.entity_id) + self.assertEqual(entity_id, entry['entity_id']) def create_state_changed_event(self, event_time_fired, entity_id, state, attributes=None, last_changed=None, @@ -566,3 +556,44 @@ async def test_logbook_view(hass, aiohttp_client): response = await client.get( '/api/logbook/{}'.format(dt_util.utcnow().isoformat())) assert response.status == 200 + + +async def test_humanify_alexa_event(hass): + """Test humanifying Alexa event.""" + hass.states.async_set('light.kitchen', 'on', { + 'friendly_name': 'Kitchen Light' + }) + + results = list(logbook.humanify(hass, [ + ha.Event(EVENT_ALEXA_SMART_HOME, { + 'namespace': 'Alexa.Discovery', + 'name': 'Discover', + }), + ha.Event(EVENT_ALEXA_SMART_HOME, { + 'namespace': 'Alexa.PowerController', + 'name': 'TurnOn', + 'entity_id': 'light.kitchen' + }), + ha.Event(EVENT_ALEXA_SMART_HOME, { + 'namespace': 'Alexa.PowerController', + 'name': 'TurnOn', + 'entity_id': 'light.non_existing' + }), + + ])) + + event1, event2, event3 = results + + assert event1['name'] == 'Amazon Alexa' + assert event1['message'] == 'send command Alexa.Discovery/Discover' + assert event1['entity_id'] is None + + assert event2['name'] == 'Amazon Alexa' + assert event2['message'] == \ + 'send command Alexa.PowerController/TurnOn for Kitchen Light' + assert event2['entity_id'] == 'light.kitchen' + + assert event3['name'] == 'Amazon Alexa' + assert event3['message'] == \ + 'send command Alexa.PowerController/TurnOn for light.non_existing' + assert event3['entity_id'] == 'light.non_existing'