-
-
Notifications
You must be signed in to change notification settings - Fork 37.8k
Restore_state helper to restore entity states from the DB on startup #4614
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
6bcc1ae
8f1a385
2744d18
d0aa4f1
522a9d0
1a7e514
6a1ab91
692c1e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,6 @@ | |
|
|
||
| from homeassistant.const import ( | ||
| HTTP_BAD_REQUEST, CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE) | ||
| import homeassistant.helpers.config_validation as cv | ||
| import homeassistant.util.dt as dt_util | ||
| from homeassistant.components import recorder, script | ||
| from homeassistant.components.frontend import register_built_in_panel | ||
|
|
@@ -28,34 +27,20 @@ | |
| DEPENDENCIES = ['recorder', 'http'] | ||
|
|
||
| CONFIG_SCHEMA = vol.Schema({ | ||
| DOMAIN: vol.Schema({ | ||
| CONF_EXCLUDE: vol.Schema({ | ||
| vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, | ||
| vol.Optional(CONF_DOMAINS, default=[]): | ||
| vol.All(cv.ensure_list, [cv.string]) | ||
| }), | ||
| CONF_INCLUDE: vol.Schema({ | ||
| vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, | ||
| vol.Optional(CONF_DOMAINS, default=[]): | ||
| vol.All(cv.ensure_list, [cv.string]) | ||
| }) | ||
| }), | ||
| DOMAIN: recorder.FILTER_SCHEMA, | ||
| }, extra=vol.ALLOW_EXTRA) | ||
|
|
||
| SIGNIFICANT_DOMAINS = ('thermostat', 'climate') | ||
| IGNORE_DOMAINS = ('zone', 'scene',) | ||
|
|
||
|
|
||
| def last_5_states(entity_id): | ||
| """Return the last 5 states for entity_id.""" | ||
| entity_id = entity_id.lower() | ||
|
|
||
| states = recorder.get_model('States') | ||
| return recorder.execute( | ||
| recorder.query('States').filter( | ||
| (states.entity_id == entity_id) & | ||
| (states.last_changed == states.last_updated) | ||
| ).order_by(states.state_id.desc()).limit(5)) | ||
| def last_recorder_run(): | ||
| """Retireve the last closed recorder run from the DB.""" | ||
| rec_runs = recorder.get_model('RecorderRuns') | ||
| with recorder.session_scope() as session: | ||
| res = recorder.query(rec_runs).order_by(rec_runs.end.desc()).first() | ||
| session.expunge(res) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| return res | ||
|
|
||
|
|
||
| def get_significant_states(start_time, end_time=None, entity_id=None, | ||
|
|
@@ -91,7 +76,7 @@ def get_significant_states(start_time, end_time=None, entity_id=None, | |
| def state_changes_during_period(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( | ||
| query = recorder.query(states).filter( | ||
| (states.last_changed == states.last_updated) & | ||
| (states.last_changed > start_time)) | ||
|
|
||
|
|
@@ -132,7 +117,7 @@ def get_states(utc_point_in_time, entity_ids=None, run=None, filters=None): | |
| most_recent_state_ids = most_recent_state_ids.group_by( | ||
| states.entity_id).subquery() | ||
|
|
||
| query = recorder.query('States').join(most_recent_state_ids, and_( | ||
| query = recorder.query(states).join(most_recent_state_ids, and_( | ||
| states.state_id == most_recent_state_ids.c.max_state_id)) | ||
|
|
||
| for state in recorder.execute(query): | ||
|
|
@@ -185,27 +170,13 @@ def setup(hass, config): | |
| filters.included_entities = include[CONF_ENTITIES] | ||
| filters.included_domains = include[CONF_DOMAINS] | ||
|
|
||
| hass.http.register_view(Last5StatesView) | ||
| recorder.get_instance() | ||
| hass.http.register_view(HistoryPeriodView(filters)) | ||
| register_built_in_panel(hass, 'history', 'History', 'mdi:poll-box') | ||
|
|
||
| return True | ||
|
|
||
|
|
||
| class Last5StatesView(HomeAssistantView): | ||
| """Handle last 5 state view requests.""" | ||
|
|
||
| url = '/api/history/entity/{entity_id}/recent_states' | ||
| name = 'api:history:entity-recent-states' | ||
|
|
||
| @asyncio.coroutine | ||
| def get(self, request, entity_id): | ||
| """Retrieve last 5 states of entity.""" | ||
| result = yield from request.app['hass'].loop.run_in_executor( | ||
| None, last_5_states, entity_id) | ||
| return self.json(result) | ||
|
|
||
|
|
||
| class HistoryPeriodView(HomeAssistantView): | ||
| """Handle history period requests.""" | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ | |
|
|
||
| from homeassistant.core import callback | ||
| from homeassistant.components import group | ||
| from homeassistant.components.recorder.restore_state import get_last_state | ||
| from homeassistant.config import load_yaml_config_file | ||
| from homeassistant.const import ( | ||
| STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, | ||
|
|
@@ -369,3 +370,16 @@ def state_attributes(self): | |
| def supported_features(self): | ||
| """Flag supported features.""" | ||
| return 0 | ||
|
|
||
| @asyncio.coroutine | ||
| def async_added_to_hass(self): | ||
| """Component added, restore_state using platforms.""" | ||
| state = get_last_state(self) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
if not hasattr(self, 'async_restore_state'):
return
state = get_last_state(self.entity_id) |
||
| if state: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you convert this to a guard clause? This example is going to be copied by a lot of other component/platform developers and thus needs to be perfect 🥇 if state is None:
return
params = …
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your view on moving this to Two reasons:
The cache function can then be a _private
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add another helper to this file that wraps the entity logic around def extract_info(state):
"""Restore properties from state object."""
return {
is_on: state.state == STATE_ON,
# etc…
}
class Light:
def async_added_to_hass(self):
yield from restore_state.xxxxx(self, extract_info)And in restore state, the helper def xxxxx(entity, extract_info):
if not hasattr(entity, 'async_restore_state'):
return
state = get_state(entity.entity_id)
if not state:
return
yield from entity.async_restore_state(**extract_info(state)) |
||
| params = {key: state.attr[key] for key in ( | ||
| ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_XY_COLOR, | ||
| ATTR_COLOR_TEMP, ATTR_COLOR_NAME, ATTR_WHITE_VALUE | ||
| ) if key in state.attr} | ||
| params['is_on'] = state.state == STATE_ON | ||
| # pylint: disable=no-member | ||
| yield from self.async_restore_state(**params) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use hybrid properties instead and add this to the RecorderRun model: http://stackoverflow.com/a/14616299/646416
We then no longer need a helper method. (and yes, we should probably remove more of these helper methods in favor of that)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Postponed for future PR