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
158 changes: 85 additions & 73 deletions homeassistant/components/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from homeassistant.components.frontend import register_built_in_panel
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import ATTR_HIDDEN
from homeassistant.components.recorder.util import session_scope, execute

_LOGGER = logging.getLogger(__name__)

Expand All @@ -34,19 +35,20 @@
IGNORE_DOMAINS = ('zone', 'scene',)


def last_recorder_run():
def last_recorder_run(hass):
"""Retireve the last closed recorder run from the DB."""
recorder.get_instance()
rec_runs = recorder.get_model('RecorderRuns')
with recorder.session_scope() as session:
res = recorder.query(rec_runs).order_by(rec_runs.end.desc()).first()
from homeassistant.components.recorder.models import RecorderRuns

with session_scope(hass=hass) as session:
res = (session.query(RecorderRuns)
.order_by(RecorderRuns.end.desc()).first())
if res is None:
return None
session.expunge(res)
return res


def get_significant_states(start_time, end_time=None, entity_id=None,
def get_significant_states(hass, start_time, end_time=None, entity_id=None,
filters=None):
"""
Return states changes during UTC period start_time - end_time.
Expand All @@ -55,80 +57,90 @@ def get_significant_states(start_time, end_time=None, entity_id=None,
as well as all states from certain domains (for instance
thermostat so that we get current temperature in our graphs).
"""
from homeassistant.components.recorder.models import States

entity_ids = (entity_id.lower(), ) if entity_id is not None else None
states = recorder.get_model('States')
query = recorder.query(states).filter(
(states.domain.in_(SIGNIFICANT_DOMAINS) |
(states.last_changed == states.last_updated)) &
(states.last_updated > start_time))
if filters:
query = filters.apply(query, entity_ids)

if end_time is not None:
query = query.filter(states.last_updated < end_time)
with session_scope(hass=hass) as session:
query = session.query(States).filter(
(States.domain.in_(SIGNIFICANT_DOMAINS) |
(States.last_changed == States.last_updated)) &
(States.last_updated > start_time))

if filters:
query = filters.apply(query, entity_ids)

if end_time is not None:
query = query.filter(States.last_updated < end_time)

states = (
state for state in recorder.execute(
query.order_by(states.entity_id, states.last_updated))
if (_is_significant(state) and
not state.attributes.get(ATTR_HIDDEN, False)))
states = (
state for state in execute(
query.order_by(States.entity_id, States.last_updated))
if (_is_significant(state) and
not state.attributes.get(ATTR_HIDDEN, False)))

return states_to_json(states, start_time, entity_id, filters)
return states_to_json(hass, states, start_time, entity_id, filters)


def state_changes_during_period(start_time, end_time=None, entity_id=None):
def state_changes_during_period(hass, start_time, end_time=None,
entity_id=None):
"""Return states changes during UTC period start_time - end_time."""
states = recorder.get_model('States')
query = recorder.query(states).filter(
(states.last_changed == states.last_updated) &
(states.last_changed > start_time))
from homeassistant.components.recorder.models import States

if end_time is not None:
query = query.filter(states.last_updated < end_time)
with session_scope(hass=hass) as session:
query = session.query(States).filter(
(States.last_changed == States.last_updated) &
(States.last_changed > start_time))

if entity_id is not None:
query = query.filter_by(entity_id=entity_id.lower())
if end_time is not None:
query = query.filter(States.last_updated < end_time)

states = recorder.execute(
query.order_by(states.entity_id, states.last_updated))
if entity_id is not None:
query = query.filter_by(entity_id=entity_id.lower())

return states_to_json(states, start_time, entity_id)
states = execute(
query.order_by(States.entity_id, States.last_updated))

return states_to_json(hass, states, start_time, entity_id)

def get_states(utc_point_in_time, entity_ids=None, run=None, filters=None):

def get_states(hass, utc_point_in_time, entity_ids=None, run=None,
filters=None):
"""Return the states at a specific point in time."""
from homeassistant.components.recorder.models import States

if run is None:
run = recorder.run_information(utc_point_in_time)
run = recorder.run_information(hass, utc_point_in_time)

# History did not run before utc_point_in_time
if run is None:
return []

from sqlalchemy import and_, func

states = recorder.get_model('States')
most_recent_state_ids = recorder.query(
func.max(states.state_id).label('max_state_id')
).filter(
(states.created >= run.start) &
(states.created < utc_point_in_time) &
(~states.domain.in_(IGNORE_DOMAINS)))
if filters:
most_recent_state_ids = filters.apply(most_recent_state_ids,
entity_ids)
with session_scope(hass=hass) as session:
most_recent_state_ids = session.query(
func.max(States.state_id).label('max_state_id')
).filter(
(States.created >= run.start) &
(States.created < utc_point_in_time) &
(~States.domain.in_(IGNORE_DOMAINS)))

most_recent_state_ids = most_recent_state_ids.group_by(
states.entity_id).subquery()
if filters:
most_recent_state_ids = filters.apply(most_recent_state_ids,
entity_ids)

query = recorder.query(states).join(most_recent_state_ids, and_(
states.state_id == most_recent_state_ids.c.max_state_id))
most_recent_state_ids = most_recent_state_ids.group_by(
States.entity_id).subquery()

for state in recorder.execute(query):
if not state.attributes.get(ATTR_HIDDEN, False):
yield state
query = session.query(States).join(most_recent_state_ids, and_(
States.state_id == most_recent_state_ids.c.max_state_id))

return [state for state in execute(query)
if not state.attributes.get(ATTR_HIDDEN, False)]

def states_to_json(states, start_time, entity_id, filters=None):

def states_to_json(hass, states, start_time, entity_id, filters=None):
"""Convert SQL results into JSON friendly data structure.

This takes our state list and turns it into a JSON friendly data
Expand All @@ -143,7 +155,7 @@ def states_to_json(states, start_time, entity_id, filters=None):
entity_ids = [entity_id] if entity_id is not None else None

# Get the states at the start time
for state in get_states(start_time, entity_ids, filters=filters):
for state in get_states(hass, start_time, entity_ids, filters=filters):
state.last_changed = start_time
state.last_updated = start_time
result[state.entity_id].append(state)
Expand All @@ -154,9 +166,9 @@ def states_to_json(states, start_time, entity_id, filters=None):
return result


def get_state(utc_point_in_time, entity_id, run=None):
def get_state(hass, utc_point_in_time, entity_id, run=None):
"""Return a state at a specific point in time."""
states = list(get_states(utc_point_in_time, (entity_id,), run))
states = list(get_states(hass, utc_point_in_time, (entity_id,), run))
return states[0] if states else None


Expand All @@ -173,7 +185,6 @@ def setup(hass, config):
filters.included_entities = include[CONF_ENTITIES]
filters.included_domains = include[CONF_DOMAINS]

recorder.get_instance()
hass.http.register_view(HistoryPeriodView(filters))
register_built_in_panel(hass, 'history', 'History', 'mdi:poll-box')

Expand Down Expand Up @@ -223,8 +234,8 @@ def get(self, request, datetime=None):
entity_id = request.GET.get('filter_entity_id')

result = yield from request.app['hass'].loop.run_in_executor(
None, get_significant_states, start_time, end_time, entity_id,
self.filters)
None, get_significant_states, request.app['hass'], start_time,
end_time, entity_id, self.filters)
result = result.values()
if _LOGGER.isEnabledFor(logging.DEBUG):
elapsed = time.perf_counter() - timer_start
Expand Down Expand Up @@ -254,41 +265,42 @@ def apply(self, query, entity_ids=None):
* if include and exclude is defined - select the entities specified in
the include and filter out the ones from the exclude list.
"""
states = recorder.get_model('States')
from homeassistant.components.recorder.models import States

# specific entities requested - do not in/exclude anything
if entity_ids is not None:
return query.filter(states.entity_id.in_(entity_ids))
query = query.filter(~states.domain.in_(IGNORE_DOMAINS))
return query.filter(States.entity_id.in_(entity_ids))
query = query.filter(~States.domain.in_(IGNORE_DOMAINS))

filter_query = None
# filter if only excluded domain is configured
if self.excluded_domains and not self.included_domains:
filter_query = ~states.domain.in_(self.excluded_domains)
filter_query = ~States.domain.in_(self.excluded_domains)
if self.included_entities:
filter_query &= states.entity_id.in_(self.included_entities)
filter_query &= States.entity_id.in_(self.included_entities)
# filter if only included domain is configured
elif not self.excluded_domains and self.included_domains:
filter_query = states.domain.in_(self.included_domains)
filter_query = States.domain.in_(self.included_domains)
if self.included_entities:
filter_query |= states.entity_id.in_(self.included_entities)
filter_query |= States.entity_id.in_(self.included_entities)
# filter if included and excluded domain is configured
elif self.excluded_domains and self.included_domains:
filter_query = ~states.domain.in_(self.excluded_domains)
filter_query = ~States.domain.in_(self.excluded_domains)
if self.included_entities:
filter_query &= (states.domain.in_(self.included_domains) |
states.entity_id.in_(self.included_entities))
filter_query &= (States.domain.in_(self.included_domains) |
States.entity_id.in_(self.included_entities))
else:
filter_query &= (states.domain.in_(self.included_domains) & ~
states.domain.in_(self.excluded_domains))
filter_query &= (States.domain.in_(self.included_domains) & ~
States.domain.in_(self.excluded_domains))
# no domain filter just included entities
elif not self.excluded_domains and not self.included_domains and \
self.included_entities:
filter_query = states.entity_id.in_(self.included_entities)
filter_query = States.entity_id.in_(self.included_entities)
if filter_query is not None:
query = query.filter(filter_query)
# finally apply excluded entities filter if configured
if self.excluded_entities:
query = query.filter(~states.entity_id.in_(self.excluded_entities))
query = query.filter(~States.entity_id.in_(self.excluded_entities))
return query


Expand Down
39 changes: 22 additions & 17 deletions homeassistant/components/logbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.components import recorder, sun
from homeassistant.components import sun
from homeassistant.components.frontend import register_built_in_panel
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import (EVENT_HOMEASSISTANT_START,
Expand Down Expand Up @@ -98,7 +98,7 @@ def log_message(service):
message = message.async_render()
async_log_entry(hass, name, message, domain, entity_id)

hass.http.register_view(LogbookView(config))
hass.http.register_view(LogbookView(config.get(DOMAIN, {})))

register_built_in_panel(hass, 'logbook', 'Logbook',
'mdi:format-list-bulleted-type')
Expand Down Expand Up @@ -132,20 +132,11 @@ def get(self, request, datetime=None):

start_day = dt_util.as_utc(datetime)
end_day = start_day + timedelta(days=1)
hass = request.app['hass']

def get_results():
"""Query DB for results."""
events = recorder.get_model('Events')
query = recorder.query('Events').order_by(
events.time_fired).filter(
(events.time_fired > start_day) &
(events.time_fired < end_day))
events = recorder.execute(query)
return _exclude_events(events, self.config)

events = yield from request.app['hass'].loop.run_in_executor(
None, get_results)

events = yield from hass.loop.run_in_executor(
None, _get_events, hass, start_day, end_day)
events = _exclude_events(events, self.config)
return self.json(humanify(events))


Expand Down Expand Up @@ -282,17 +273,31 @@ def humanify(events):
entity_id)


def _get_events(hass, start_day, end_day):
"""Get events for a period of time."""
from homeassistant.components.recorder.models import Events
from homeassistant.components.recorder.util import (
execute, session_scope)

with session_scope(hass=hass) as session:
query = session.query(Events).order_by(
Events.time_fired).filter(
(Events.time_fired > start_day) &
(Events.time_fired < end_day))
return execute(query)


def _exclude_events(events, config):
"""Get lists of excluded entities and platforms."""
excluded_entities = []
excluded_domains = []
included_entities = []
included_domains = []
exclude = config[DOMAIN].get(CONF_EXCLUDE)
exclude = config.get(CONF_EXCLUDE)
if exclude:
excluded_entities = exclude[CONF_ENTITIES]
excluded_domains = exclude[CONF_DOMAINS]
include = config[DOMAIN].get(CONF_INCLUDE)
include = config.get(CONF_INCLUDE)
if include:
included_entities = include[CONF_ENTITIES]
included_domains = include[CONF_DOMAINS]
Expand Down
Loading