Compare commits

...
Sign in to create a new pull request.

5 commits

Author SHA1 Message Date
Paulus Schoutsen
43cde6aeae Fix logbook entry 2018-09-28 17:04:25 +02:00
Paulus Schoutsen
3d6198d8dc Lint 2018-09-28 17:03:22 +02:00
Paulus Schoutsen
ca10d9b726 Enhance logbook 2018-09-28 15:06:32 +02:00
Paulus Schoutsen
9e7e59f2fe Add context to events 2018-09-28 14:30:31 +02:00
Paulus Schoutsen
636de511e4 Convert logbook to use attr 2018-09-28 14:20:48 +02:00
4 changed files with 133 additions and 65 deletions

View file

@ -17,7 +17,6 @@ from homeassistant.loader import bind_hass
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, 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)
from homeassistant.components import logbook
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import extract_domain_configs, script, condition from homeassistant.helpers import extract_domain_configs, script, condition
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
@ -369,8 +368,8 @@ def _async_get_action(hass, config, name):
async def action(entity_id, variables, context): async def action(entity_id, variables, context):
"""Execute an action.""" """Execute an action."""
_LOGGER.info('Executing %s', name) _LOGGER.info('Executing %s', name)
logbook.async_log_entry( hass.components.logbook.async_log_entry(
hass, name, 'has been triggered', DOMAIN, entity_id) name, 'has been triggered', DOMAIN, entity_id)
await script_obj.async_run(variables, context) await script_obj.async_run(variables, context)
return action return action

View file

@ -8,8 +8,10 @@ from datetime import timedelta
from itertools import groupby from itertools import groupby
import logging import logging
import attr
import voluptuous as vol import voluptuous as vol
from homeassistant.loader import bind_hass
from homeassistant.components import sun from homeassistant.components import sun
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
from homeassistant.const import ( from homeassistant.const import (
@ -17,8 +19,9 @@ from homeassistant.const import (
CONF_INCLUDE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, CONF_INCLUDE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED, HTTP_BAD_REQUEST, STATE_NOT_HOME, EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED, HTTP_BAD_REQUEST, STATE_NOT_HOME,
STATE_OFF, STATE_ON) STATE_OFF, STATE_ON)
from homeassistant.core import DOMAIN as HA_DOMAIN from homeassistant.core import (
from homeassistant.core import State, callback, split_entity_id 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.helpers.config_validation as cv
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -53,7 +56,8 @@ CONFIG_SCHEMA = vol.Schema({
ALL_EVENT_TYPES = [ ALL_EVENT_TYPES = [
EVENT_STATE_CHANGED, EVENT_LOGBOOK_ENTRY, 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({ LOG_MESSAGE_SCHEMA = vol.Schema({
@ -64,11 +68,13 @@ LOG_MESSAGE_SCHEMA = vol.Schema({
}) })
@bind_hass
def log_entry(hass, name, message, domain=None, entity_id=None): def log_entry(hass, name, message, domain=None, entity_id=None):
"""Add an entry to the logbook.""" """Add an entry to the logbook."""
hass.add_job(async_log_entry, hass, name, message, domain, entity_id) 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): def async_log_entry(hass, name, message, domain=None, entity_id=None):
"""Add an entry to the logbook.""" """Add an entry to the logbook."""
data = { data = {
@ -134,36 +140,27 @@ class LogbookView(HomeAssistantView):
def json_events(): def json_events():
"""Fetch events and generate JSON.""" """Fetch events and generate JSON."""
return self.json(list( return self.json([
_get_events(hass, self.config, start_day, end_day))) attr.asdict(entry) for entry in
_get_events(hass, self.config, start_day, end_day)])
return await hass.async_add_job(json_events) return await hass.async_add_job(json_events)
@attr.s(slots=True, frozen=True)
class Entry: class Entry:
"""A human readable version of the log.""" """A human readable version of the log."""
def __init__(self, when=None, name=None, message=None, domain=None, when = attr.ib(type=str)
entity_id=None): name = attr.ib(type=str)
"""Initialize the entry.""" message = attr.ib(type=str)
self.when = when domain = attr.ib(type=str)
self.name = name context_id = attr.ib(type=str)
self.message = message entity_id = attr.ib(type=str, default=None)
self.domain = domain context_user_id = attr.ib(type=str, default=None)
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. """Generate a converted list of events into Entry objects.
Will try to group events if possible: Will try to group events if possible:
@ -225,19 +222,27 @@ def humanify(events):
continue continue
yield Entry( yield Entry(
event.time_fired, when=event.time_fired,
name=to_state.name, name=to_state.name,
message=_entry_message_from_state(domain, to_state), message=_entry_message_from_state(domain, to_state),
domain=domain, domain=domain,
entity_id=to_state.entity_id) 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: elif event.event_type == EVENT_HOMEASSISTANT_START:
if start_stop_events.get(event.time_fired.minute) == 2: if start_stop_events.get(event.time_fired.minute) == 2:
continue continue
yield Entry( yield Entry(
event.time_fired, "Home Assistant", "started", when=event.time_fired,
domain=HA_DOMAIN) 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: elif event.event_type == EVENT_HOMEASSISTANT_STOP:
if start_stop_events.get(event.time_fired.minute) == 2: if start_stop_events.get(event.time_fired.minute) == 2:
@ -246,8 +251,13 @@ def humanify(events):
action = "stopped" action = "stopped"
yield Entry( yield Entry(
event.time_fired, "Home Assistant", action, when=event.time_fired,
domain=HA_DOMAIN) 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: elif event.event_type == EVENT_LOGBOOK_ENTRY:
domain = event.data.get(ATTR_DOMAIN) domain = event.data.get(ATTR_DOMAIN)
@ -259,9 +269,37 @@ def humanify(events):
pass pass
yield Entry( yield Entry(
event.time_fired, event.data.get(ATTR_NAME), when=event.time_fired,
event.data.get(ATTR_MESSAGE), domain, name=event.data.get(ATTR_NAME),
entity_id) 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 Entry(
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): def _get_events(hass, config, start_day, end_day):
@ -279,7 +317,7 @@ def _get_events(hass, config, start_day, end_day):
.filter((States.last_updated == States.last_changed) .filter((States.last_updated == States.last_changed)
| (States.state_id.is_(None))) | (States.state_id.is_(None)))
events = execute(query) events = execute(query)
return humanify(_exclude_events(events, config)) return humanify(hass, _exclude_events(events, config))
def _exclude_events(events, config): def _exclude_events(events, config):

View file

@ -186,6 +186,6 @@ def _logbook_filtering(hass, last_changed, last_updated):
# pylint: disable=protected-access # pylint: disable=protected-access
events = logbook._exclude_events(events, {}) events = logbook._exclude_events(events, {})
list(logbook.humanify(events)) list(logbook.humanify(None, events))
return timer() - start return timer() - start

View file

@ -11,6 +11,7 @@ from homeassistant.const import (
ATTR_HIDDEN, STATE_NOT_HOME, STATE_ON, STATE_OFF) ATTR_HIDDEN, STATE_NOT_HOME, STATE_ON, STATE_OFF)
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.components import logbook, recorder 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 homeassistant.setup import setup_component, async_setup_component
from tests.common import ( from tests.common import (
@ -99,7 +100,7 @@ class TestComponentLogbook(unittest.TestCase):
eventB = self.create_state_changed_event(pointB, entity_id, 20) eventB = self.create_state_changed_event(pointB, entity_id, 20)
eventC = self.create_state_changed_event(pointC, entity_id, 30) 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.assertEqual(2, len(entries))
self.assert_entry( self.assert_entry(
@ -116,7 +117,7 @@ class TestComponentLogbook(unittest.TestCase):
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, entity_id, 10, attributes) pointA, entity_id, 10, attributes)
entries = list(logbook.humanify((eventA,))) entries = list(logbook.humanify(self.hass, (eventA,)))
self.assertEqual(0, len(entries)) self.assertEqual(0, len(entries))
@ -133,7 +134,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP), events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP),
eventA, eventB), {}) eventA, eventB), {})
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry( self.assert_entry(
@ -155,7 +156,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP), events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP),
eventA, eventB), {}) eventA, eventB), {})
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry( self.assert_entry(
@ -177,7 +178,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP), events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP),
eventA, eventB), {}) eventA, eventB), {})
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry( self.assert_entry(
@ -203,7 +204,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events( events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB),
config[logbook.DOMAIN]) config[logbook.DOMAIN])
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry( self.assert_entry(
@ -229,7 +230,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events( events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_START), eventA, eventB), (ha.Event(EVENT_HOMEASSISTANT_START), eventA, eventB),
config[logbook.DOMAIN]) config[logbook.DOMAIN])
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry(entries[0], name='Home Assistant', message='started', self.assert_entry(entries[0], name='Home Assistant', message='started',
@ -266,7 +267,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events( events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB),
config[logbook.DOMAIN]) config[logbook.DOMAIN])
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry( self.assert_entry(
@ -292,7 +293,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events( events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB), (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB),
config[logbook.DOMAIN]) config[logbook.DOMAIN])
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry( self.assert_entry(
@ -318,7 +319,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events( events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_START), eventA, eventB), (ha.Event(EVENT_HOMEASSISTANT_START), eventA, eventB),
config[logbook.DOMAIN]) config[logbook.DOMAIN])
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries)) self.assertEqual(2, len(entries))
self.assert_entry(entries[0], name='Home Assistant', message='started', self.assert_entry(entries[0], name='Home Assistant', message='started',
@ -352,7 +353,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events( events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_START), eventA1, eventA2, eventA3, (ha.Event(EVENT_HOMEASSISTANT_START), eventA1, eventA2, eventA3,
eventB1, eventB2), config[logbook.DOMAIN]) eventB1, eventB2), config[logbook.DOMAIN])
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(3, len(entries)) self.assertEqual(3, len(entries))
self.assert_entry(entries[0], name='Home Assistant', message='started', self.assert_entry(entries[0], name='Home Assistant', message='started',
@ -373,7 +374,7 @@ class TestComponentLogbook(unittest.TestCase):
{'auto': True}) {'auto': True})
events = logbook._exclude_events((eventA, eventB), {}) events = logbook._exclude_events((eventA, eventB), {})
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(1, len(entries)) self.assertEqual(1, len(entries))
self.assert_entry(entries[0], pointA, 'bla', domain='switch', 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) pointA, entity_id2, 20, last_changed=pointA, last_updated=pointB)
events = logbook._exclude_events((eventA, eventB), {}) events = logbook._exclude_events((eventA, eventB), {})
entries = list(logbook.humanify(events)) entries = list(logbook.humanify(self.hass, events))
self.assertEqual(1, len(entries)) self.assertEqual(1, len(entries))
self.assert_entry(entries[0], pointA, 'bla', domain='switch', self.assert_entry(entries[0], pointA, 'bla', domain='switch',
entity_id=entity_id) 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): def test_home_assistant_start_stop_grouped(self):
"""Test if HA start and stop events are grouped. """Test if HA start and stop events are grouped.
Events that are occurring in the same minute. 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_STOP),
ha.Event(EVENT_HOMEASSISTANT_START), ha.Event(EVENT_HOMEASSISTANT_START),
))) )))
@ -428,7 +418,7 @@ class TestComponentLogbook(unittest.TestCase):
entity_id = 'switch.bla' entity_id = 'switch.bla'
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
entries = list(logbook.humanify(( entries = list(logbook.humanify(self.hass, (
ha.Event(EVENT_HOMEASSISTANT_START), ha.Event(EVENT_HOMEASSISTANT_START),
self.create_state_changed_event(pointA, entity_id, 10) self.create_state_changed_event(pointA, entity_id, 10)
))) )))
@ -509,7 +499,7 @@ class TestComponentLogbook(unittest.TestCase):
message = 'has a custom entry' message = 'has a custom entry'
entity_id = 'sun.sun' entity_id = 'sun.sun'
entries = list(logbook.humanify(( entries = list(logbook.humanify(self.hass, (
ha.Event(logbook.EVENT_LOGBOOK_ENTRY, { ha.Event(logbook.EVENT_LOGBOOK_ENTRY, {
logbook.ATTR_NAME: name, logbook.ATTR_NAME: name,
logbook.ATTR_MESSAGE: message, logbook.ATTR_MESSAGE: message,
@ -566,3 +556,44 @@ async def test_logbook_view(hass, aiohttp_client):
response = await client.get( response = await client.get(
'/api/logbook/{}'.format(dt_util.utcnow().isoformat())) '/api/logbook/{}'.format(dt_util.utcnow().isoformat()))
assert response.status == 200 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'