From d2e102ee2f82414da64122a317c0611412edf399 Mon Sep 17 00:00:00 2001 From: ehendrix23 Date: Wed, 14 Nov 2018 07:13:32 -0700 Subject: [PATCH] Init statistics sensor upon HASS start (#18236) * Update query to include maxAge Updated the query from recorded to include MaxAge if set; reducing the amount of records retrieved that would otherwise be purged anyway for the sensor. * Initialization upon HASS start Register the state listener and read previous information from recorder once HASS is started. * Updated test_statistics.py for HASS start Updated test_statistics.py to start HASS and wait it is completed before running test. * Added newline in docstring Added newline in docstring. * Added start of HASS to test_initialize_from_database_with_maxage Added start of HASS to new test test_initialize_from_database_with_maxage. * Updates based on review Following updates based on review: -) Removed self._hass and passing hass -) Changed async_add_job to async_create_task -) For state update, calling async_schedule_update_ha_state --- homeassistant/components/sensor/statistics.py | 40 +++++++++++++------ tests/components/sensor/test_statistics.py | 25 ++++++++++++ 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/sensor/statistics.py b/homeassistant/components/sensor/statistics.py index 26253abd484..e7a35b5fdf0 100644 --- a/homeassistant/components/sensor/statistics.py +++ b/homeassistant/components/sensor/statistics.py @@ -13,7 +13,8 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_ENTITY_ID, STATE_UNKNOWN, ATTR_UNIT_OF_MEASUREMENT) + CONF_NAME, CONF_ENTITY_ID, EVENT_HOMEASSISTANT_START, STATE_UNKNOWN, + ATTR_UNIT_OF_MEASUREMENT) from homeassistant.core import callback from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change @@ -66,7 +67,7 @@ async def async_setup_platform(hass, config, async_add_entities, max_age = config.get(CONF_MAX_AGE, None) precision = config.get(CONF_PRECISION) - async_add_entities([StatisticsSensor(hass, entity_id, name, sampling_size, + async_add_entities([StatisticsSensor(entity_id, name, sampling_size, max_age, precision)], True) return True @@ -75,10 +76,9 @@ async def async_setup_platform(hass, config, async_add_entities, class StatisticsSensor(Entity): """Representation of a Statistics sensor.""" - def __init__(self, hass, entity_id, name, sampling_size, max_age, + def __init__(self, entity_id, name, sampling_size, max_age, precision): """Initialize the Statistics sensor.""" - self._hass = hass self._entity_id = entity_id self.is_binary = True if self._entity_id.split('.')[0] == \ 'binary_sensor' else False @@ -99,10 +99,8 @@ class StatisticsSensor(Entity): self.min_age = self.max_age = None self.change = self.average_change = self.change_rate = None - if 'recorder' in self._hass.config.components: - # only use the database if it's configured - hass.async_add_job(self._initialize_from_database) - + async def async_added_to_hass(self): + """Register callbacks.""" @callback def async_stats_sensor_state_listener(entity, old_state, new_state): """Handle the sensor state changes.""" @@ -111,10 +109,24 @@ class StatisticsSensor(Entity): self._add_state_to_queue(new_state) - hass.async_add_job(self.async_update_ha_state, True) + self.async_schedule_update_ha_state(True) - async_track_state_change( - hass, entity_id, async_stats_sensor_state_listener) + @callback + def async_stats_sensor_startup(event): + """Add listener and get recorded state.""" + _LOGGER.debug("Startup for %s", self.entity_id) + + async_track_state_change( + self.hass, self._entity_id, async_stats_sensor_state_listener) + + if 'recorder' in self.hass.config.components: + # only use the database if it's configured + self.hass.async_create_task( + self._async_initialize_from_database() + ) + + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_START, async_stats_sensor_startup) def _add_state_to_queue(self, new_state): try: @@ -241,7 +253,7 @@ class StatisticsSensor(Entity): self.change = self.average_change = STATE_UNKNOWN self.change_rate = STATE_UNKNOWN - async def _initialize_from_database(self): + async def _async_initialize_from_database(self): """Initialize the list of states from the database. The query will get the list of states in DESCENDING order so that we @@ -255,7 +267,7 @@ class StatisticsSensor(Entity): _LOGGER.debug("%s: initializing values from the database", self.entity_id) - with session_scope(hass=self._hass) as session: + with session_scope(hass=self.hass) as session: query = session.query(States)\ .filter(States.entity_id == self._entity_id.lower()) @@ -275,5 +287,7 @@ class StatisticsSensor(Entity): for state in reversed(states): self._add_state_to_queue(state) + self.async_schedule_update_ha_state(True) + _LOGGER.debug("%s: initializing from database completed", self.entity_id) diff --git a/tests/components/sensor/test_statistics.py b/tests/components/sensor/test_statistics.py index 9188513b861..5d1137c35e6 100644 --- a/tests/components/sensor/test_statistics.py +++ b/tests/components/sensor/test_statistics.py @@ -49,6 +49,9 @@ class TestStatisticsSensor(unittest.TestCase): } }) + self.hass.start() + self.hass.block_till_done() + for value in values: self.hass.states.set('binary_sensor.test_monitored', value) self.hass.block_till_done() @@ -67,6 +70,9 @@ class TestStatisticsSensor(unittest.TestCase): } }) + self.hass.start() + self.hass.block_till_done() + for value in self.values: self.hass.states.set('sensor.test_monitored', value, {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) @@ -100,6 +106,9 @@ class TestStatisticsSensor(unittest.TestCase): } }) + self.hass.start() + self.hass.block_till_done() + for value in self.values: self.hass.states.set('sensor.test_monitored', value, {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) @@ -121,6 +130,9 @@ class TestStatisticsSensor(unittest.TestCase): } }) + self.hass.start() + self.hass.block_till_done() + for value in self.values[-3:]: # just the last 3 will do self.hass.states.set('sensor.test_monitored', value, {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) @@ -162,6 +174,9 @@ class TestStatisticsSensor(unittest.TestCase): } }) + self.hass.start() + self.hass.block_till_done() + for value in self.values: self.hass.states.set('sensor.test_monitored', value, {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) @@ -194,6 +209,9 @@ class TestStatisticsSensor(unittest.TestCase): } }) + self.hass.start() + self.hass.block_till_done() + for value in self.values: self.hass.states.set('sensor.test_monitored', value, {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) @@ -231,6 +249,10 @@ class TestStatisticsSensor(unittest.TestCase): 'sampling_size': 100, } }) + + self.hass.start() + self.hass.block_till_done() + # check if the result is as in test_sensor_source() state = self.hass.states.get('sensor.test_mean') assert str(self.mean) == state.state @@ -284,6 +306,9 @@ class TestStatisticsSensor(unittest.TestCase): } }) + self.hass.start() + self.hass.block_till_done() + # check if the result is as in test_sensor_source() state = self.hass.states.get('sensor.test_mean')