2017-02-21 07:40:27 +00:00
|
|
|
"""Support for restoring entity states on startup."""
|
|
|
|
import asyncio
|
|
|
|
import logging
|
|
|
|
from datetime import timedelta
|
|
|
|
|
2017-03-05 09:52:08 +00:00
|
|
|
import async_timeout
|
|
|
|
|
2017-02-21 07:40:27 +00:00
|
|
|
from homeassistant.core import HomeAssistant, CoreState, callback
|
|
|
|
from homeassistant.const import EVENT_HOMEASSISTANT_START
|
2017-10-08 15:17:54 +00:00
|
|
|
from homeassistant.loader import bind_hass
|
2017-02-21 07:40:27 +00:00
|
|
|
from homeassistant.components.history import get_states, last_recorder_run
|
2017-02-25 15:14:04 +00:00
|
|
|
from homeassistant.components.recorder import (
|
2017-02-26 22:38:06 +00:00
|
|
|
wait_connection_ready, DOMAIN as _RECORDER)
|
2017-02-21 07:40:27 +00:00
|
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
|
2017-03-05 09:52:08 +00:00
|
|
|
RECORDER_TIMEOUT = 10
|
2017-02-21 07:40:27 +00:00
|
|
|
DATA_RESTORE_CACHE = 'restore_state_cache'
|
|
|
|
_LOCK = 'restore_lock'
|
2017-03-05 09:52:08 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2017-02-21 07:40:27 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _load_restore_cache(hass: HomeAssistant):
|
|
|
|
"""Load the restore cache to be used by other components."""
|
|
|
|
@callback
|
|
|
|
def remove_cache(event):
|
|
|
|
"""Remove the states cache."""
|
|
|
|
hass.data.pop(DATA_RESTORE_CACHE, None)
|
|
|
|
|
|
|
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, remove_cache)
|
|
|
|
|
2017-02-26 22:38:06 +00:00
|
|
|
last_run = last_recorder_run(hass)
|
2017-02-21 07:40:27 +00:00
|
|
|
|
|
|
|
if last_run is None or last_run.end is None:
|
|
|
|
_LOGGER.debug('Not creating cache - no suitable last run found: %s',
|
|
|
|
last_run)
|
|
|
|
hass.data[DATA_RESTORE_CACHE] = {}
|
|
|
|
return
|
|
|
|
|
|
|
|
last_end_time = last_run.end - timedelta(seconds=1)
|
|
|
|
# Unfortunately the recorder_run model do not return offset-aware time
|
|
|
|
last_end_time = last_end_time.replace(tzinfo=dt_util.UTC)
|
|
|
|
_LOGGER.debug("Last run: %s - %s", last_run.start, last_end_time)
|
|
|
|
|
2017-02-26 22:38:06 +00:00
|
|
|
states = get_states(hass, last_end_time, run=last_run)
|
2017-02-21 07:40:27 +00:00
|
|
|
|
|
|
|
# Cache the states
|
|
|
|
hass.data[DATA_RESTORE_CACHE] = {
|
|
|
|
state.entity_id: state for state in states}
|
|
|
|
_LOGGER.debug('Created cache with %s', list(hass.data[DATA_RESTORE_CACHE]))
|
|
|
|
|
|
|
|
|
2017-10-08 15:17:54 +00:00
|
|
|
@bind_hass
|
2018-02-25 11:38:46 +00:00
|
|
|
async def async_get_last_state(hass, entity_id: str):
|
2017-05-02 16:18:47 +00:00
|
|
|
"""Restore state."""
|
2017-02-21 07:40:27 +00:00
|
|
|
if DATA_RESTORE_CACHE in hass.data:
|
|
|
|
return hass.data[DATA_RESTORE_CACHE].get(entity_id)
|
|
|
|
|
2017-03-05 09:54:49 +00:00
|
|
|
if _RECORDER not in hass.config.components:
|
|
|
|
return None
|
|
|
|
|
|
|
|
if hass.state not in (CoreState.starting, CoreState.not_running):
|
|
|
|
_LOGGER.debug("Cache for %s can only be loaded during startup, not %s",
|
|
|
|
entity_id, hass.state)
|
2017-02-24 04:06:21 +00:00
|
|
|
return None
|
|
|
|
|
2017-03-05 09:52:08 +00:00
|
|
|
try:
|
|
|
|
with async_timeout.timeout(RECORDER_TIMEOUT, loop=hass.loop):
|
2018-02-25 11:38:46 +00:00
|
|
|
connected = await wait_connection_ready(hass)
|
2017-03-05 09:52:08 +00:00
|
|
|
except asyncio.TimeoutError:
|
|
|
|
return None
|
|
|
|
|
|
|
|
if not connected:
|
|
|
|
return None
|
2017-02-25 15:14:04 +00:00
|
|
|
|
2017-02-21 07:40:27 +00:00
|
|
|
if _LOCK not in hass.data:
|
|
|
|
hass.data[_LOCK] = asyncio.Lock(loop=hass.loop)
|
|
|
|
|
2018-03-17 11:27:21 +00:00
|
|
|
async with hass.data[_LOCK]:
|
2017-02-21 07:40:27 +00:00
|
|
|
if DATA_RESTORE_CACHE not in hass.data:
|
2018-02-25 11:38:46 +00:00
|
|
|
await hass.async_add_job(
|
2017-05-26 15:28:07 +00:00
|
|
|
_load_restore_cache, hass)
|
2017-02-21 07:40:27 +00:00
|
|
|
|
2017-02-24 04:06:21 +00:00
|
|
|
return hass.data.get(DATA_RESTORE_CACHE, {}).get(entity_id)
|
2017-02-21 07:40:27 +00:00
|
|
|
|
|
|
|
|
2018-02-25 11:38:46 +00:00
|
|
|
async def async_restore_state(entity, extract_info):
|
2017-05-02 16:18:47 +00:00
|
|
|
"""Call entity.async_restore_state with cached info."""
|
2017-03-05 09:54:49 +00:00
|
|
|
if entity.hass.state not in (CoreState.starting, CoreState.not_running):
|
|
|
|
_LOGGER.debug("Not restoring state for %s: Hass is not starting: %s",
|
|
|
|
entity.entity_id, entity.hass.state)
|
2017-02-21 07:40:27 +00:00
|
|
|
return
|
|
|
|
|
2018-02-25 11:38:46 +00:00
|
|
|
state = await async_get_last_state(entity.hass, entity.entity_id)
|
2017-02-21 07:40:27 +00:00
|
|
|
|
|
|
|
if not state:
|
|
|
|
return
|
|
|
|
|
2018-02-25 11:38:46 +00:00
|
|
|
await entity.async_restore_state(**extract_info(state))
|