diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py index b8d3dc39187..d6587f435d7 100644 --- a/homeassistant/components/history_stats/sensor.py +++ b/homeassistant/components/history_stats/sensor.py @@ -20,7 +20,7 @@ from homeassistant.core import CoreState, callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change_event -from homeassistant.helpers.reload import setup_reload_service +from homeassistant.helpers.reload import async_setup_reload_service import homeassistant.util.dt as dt_util from . import DOMAIN, PLATFORMS @@ -74,9 +74,9 @@ PLATFORM_SCHEMA = vol.All( # noinspection PyUnusedLocal -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the History Stats sensor.""" - setup_reload_service(hass, DOMAIN, PLATFORMS) + await async_setup_reload_service(hass, DOMAIN, PLATFORMS) entity_id = config.get(CONF_ENTITY_ID) entity_states = config.get(CONF_STATE) @@ -90,7 +90,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if template is not None: template.hass = hass - add_entities( + async_add_entities( [ HistoryStatsSensor( hass, entity_id, entity_states, start, end, duration, sensor_type, name @@ -108,6 +108,7 @@ class HistoryStatsSensor(SensorEntity): self, hass, entity_id, entity_states, start, end, duration, sensor_type, name ): """Initialize the HistoryStats sensor.""" + self.hass = hass self._entity_id = entity_id self._entity_states = entity_states self._duration = duration @@ -186,7 +187,7 @@ class HistoryStatsSensor(SensorEntity): """Return the icon to use in the frontend, if any.""" return ICON - def update(self): + async def async_update(self): """Get the latest data and updates the states.""" # Get previous values of start and end p_start, p_end = self._period @@ -218,6 +219,11 @@ class HistoryStatsSensor(SensorEntity): # Don't compute anything as the value cannot have changed return + await self.hass.async_add_executor_job( + self._update, start, end, now_timestamp, start_timestamp, end_timestamp + ) + + def _update(self, start, end, now_timestamp, start_timestamp, end_timestamp): # Get history between start and end history_list = history.state_changes_during_period( self.hass, start, end, str(self._entity_id) @@ -265,7 +271,7 @@ class HistoryStatsSensor(SensorEntity): # Parse start if self._start is not None: try: - start_rendered = self._start.render() + start_rendered = self._start.async_render() except (TemplateError, TypeError) as ex: HistoryStatsHelper.handle_template_exception(ex, "start") return @@ -285,7 +291,7 @@ class HistoryStatsSensor(SensorEntity): # Parse end if self._end is not None: try: - end_rendered = self._end.render() + end_rendered = self._end.async_render() except (TemplateError, TypeError) as ex: HistoryStatsHelper.handle_template_exception(ex, "end") return diff --git a/tests/components/history_stats/test_sensor.py b/tests/components/history_stats/test_sensor.py index f074003ab86..37dc27e9e91 100644 --- a/tests/components/history_stats/test_sensor.py +++ b/tests/components/history_stats/test_sensor.py @@ -118,144 +118,6 @@ class TestHistoryStatsSensor(unittest.TestCase): assert sensor2_end.minute == 0 assert sensor2_end.second == 0 - def test_measure(self): - """Test the history statistics sensor measure.""" - t0 = dt_util.utcnow() - timedelta(minutes=40) - t1 = t0 + timedelta(minutes=20) - t2 = dt_util.utcnow() - timedelta(minutes=10) - - # Start t0 t1 t2 End - # |--20min--|--20min--|--10min--|--10min--| - # |---off---|---on----|---off---|---on----| - - fake_states = { - "binary_sensor.test_id": [ - ha.State("binary_sensor.test_id", "on", last_changed=t0), - ha.State("binary_sensor.test_id", "off", last_changed=t1), - ha.State("binary_sensor.test_id", "on", last_changed=t2), - ] - } - - start = Template("{{ as_timestamp(now()) - 3600 }}", self.hass) - end = Template("{{ now() }}", self.hass) - - sensor1 = HistoryStatsSensor( - self.hass, "binary_sensor.test_id", "on", start, end, None, "time", "Test" - ) - - sensor2 = HistoryStatsSensor( - self.hass, "unknown.id", "on", start, end, None, "time", "Test" - ) - - sensor3 = HistoryStatsSensor( - self.hass, "binary_sensor.test_id", "on", start, end, None, "count", "test" - ) - - sensor4 = HistoryStatsSensor( - self.hass, "binary_sensor.test_id", "on", start, end, None, "ratio", "test" - ) - - assert sensor1._type == "time" - assert sensor3._type == "count" - assert sensor4._type == "ratio" - - with patch( - "homeassistant.components.history.state_changes_during_period", - return_value=fake_states, - ), patch("homeassistant.components.history.get_state", return_value=None): - sensor1.update() - sensor2.update() - sensor3.update() - sensor4.update() - - assert sensor1.state == 0.5 - assert sensor2.state is None - assert sensor3.state == 2 - assert sensor4.state == 50 - - def test_measure_multiple(self): - """Test the history statistics sensor measure for multiple states.""" - t0 = dt_util.utcnow() - timedelta(minutes=40) - t1 = t0 + timedelta(minutes=20) - t2 = dt_util.utcnow() - timedelta(minutes=10) - - # Start t0 t1 t2 End - # |--20min--|--20min--|--10min--|--10min--| - # |---------|--orange-|-default-|---blue--| - - fake_states = { - "input_select.test_id": [ - ha.State("input_select.test_id", "orange", last_changed=t0), - ha.State("input_select.test_id", "default", last_changed=t1), - ha.State("input_select.test_id", "blue", last_changed=t2), - ] - } - - start = Template("{{ as_timestamp(now()) - 3600 }}", self.hass) - end = Template("{{ now() }}", self.hass) - - sensor1 = HistoryStatsSensor( - self.hass, - "input_select.test_id", - ["orange", "blue"], - start, - end, - None, - "time", - "Test", - ) - - sensor2 = HistoryStatsSensor( - self.hass, - "unknown.id", - ["orange", "blue"], - start, - end, - None, - "time", - "Test", - ) - - sensor3 = HistoryStatsSensor( - self.hass, - "input_select.test_id", - ["orange", "blue"], - start, - end, - None, - "count", - "test", - ) - - sensor4 = HistoryStatsSensor( - self.hass, - "input_select.test_id", - ["orange", "blue"], - start, - end, - None, - "ratio", - "test", - ) - - assert sensor1._type == "time" - assert sensor3._type == "count" - assert sensor4._type == "ratio" - - with patch( - "homeassistant.components.history.state_changes_during_period", - return_value=fake_states, - ), patch("homeassistant.components.history.get_state", return_value=None): - sensor1.update() - sensor2.update() - sensor3.update() - sensor4.update() - - assert sensor1.state == 0.5 - assert sensor2.state is None - assert sensor3.state == 2 - assert sensor4.state == 50 - def test_wrong_date(self): """Test when start or end value is not a timestamp or a date.""" good = Template("{{ now() }}", self.hass) @@ -415,5 +277,145 @@ async def test_reload(hass): assert hass.states.get("sensor.second_test") +async def test_measure_multiple(hass): + """Test the history statistics sensor measure for multiple states.""" + t0 = dt_util.utcnow() - timedelta(minutes=40) + t1 = t0 + timedelta(minutes=20) + t2 = dt_util.utcnow() - timedelta(minutes=10) + + # Start t0 t1 t2 End + # |--20min--|--20min--|--10min--|--10min--| + # |---------|--orange-|-default-|---blue--| + + fake_states = { + "input_select.test_id": [ + ha.State("input_select.test_id", "orange", last_changed=t0), + ha.State("input_select.test_id", "default", last_changed=t1), + ha.State("input_select.test_id", "blue", last_changed=t2), + ] + } + + start = Template("{{ as_timestamp(now()) - 3600 }}", hass) + end = Template("{{ now() }}", hass) + + sensor1 = HistoryStatsSensor( + hass, + "input_select.test_id", + ["orange", "blue"], + start, + end, + None, + "time", + "Test", + ) + + sensor2 = HistoryStatsSensor( + hass, + "unknown.id", + ["orange", "blue"], + start, + end, + None, + "time", + "Test", + ) + + sensor3 = HistoryStatsSensor( + hass, + "input_select.test_id", + ["orange", "blue"], + start, + end, + None, + "count", + "test", + ) + + sensor4 = HistoryStatsSensor( + hass, + "input_select.test_id", + ["orange", "blue"], + start, + end, + None, + "ratio", + "test", + ) + + assert sensor1._type == "time" + assert sensor3._type == "count" + assert sensor4._type == "ratio" + + with patch( + "homeassistant.components.history.state_changes_during_period", + return_value=fake_states, + ), patch("homeassistant.components.history.get_state", return_value=None): + await sensor1.async_update() + await sensor2.async_update() + await sensor3.async_update() + await sensor4.async_update() + + assert sensor1.state == 0.5 + assert sensor2.state is None + assert sensor3.state == 2 + assert sensor4.state == 50 + + +async def async_test_measure(hass): + """Test the history statistics sensor measure.""" + t0 = dt_util.utcnow() - timedelta(minutes=40) + t1 = t0 + timedelta(minutes=20) + t2 = dt_util.utcnow() - timedelta(minutes=10) + + # Start t0 t1 t2 End + # |--20min--|--20min--|--10min--|--10min--| + # |---off---|---on----|---off---|---on----| + + fake_states = { + "binary_sensor.test_id": [ + ha.State("binary_sensor.test_id", "on", last_changed=t0), + ha.State("binary_sensor.test_id", "off", last_changed=t1), + ha.State("binary_sensor.test_id", "on", last_changed=t2), + ] + } + + start = Template("{{ as_timestamp(now()) - 3600 }}", hass) + end = Template("{{ now() }}", hass) + + sensor1 = HistoryStatsSensor( + hass, "binary_sensor.test_id", "on", start, end, None, "time", "Test" + ) + + sensor2 = HistoryStatsSensor( + hass, "unknown.id", "on", start, end, None, "time", "Test" + ) + + sensor3 = HistoryStatsSensor( + hass, "binary_sensor.test_id", "on", start, end, None, "count", "test" + ) + + sensor4 = HistoryStatsSensor( + hass, "binary_sensor.test_id", "on", start, end, None, "ratio", "test" + ) + + assert sensor1._type == "time" + assert sensor3._type == "count" + assert sensor4._type == "ratio" + + with patch( + "homeassistant.components.history.state_changes_during_period", + return_value=fake_states, + ), patch("homeassistant.components.history.get_state", return_value=None): + await sensor1.async_update() + await sensor2.async_update() + await sensor3.async_update() + await sensor4.async_update() + + assert sensor1.state == 0.5 + assert sensor2.state is None + assert sensor3.state == 2 + assert sensor4.state == 50 + + def _get_fixtures_base_path(): return path.dirname(path.dirname(path.dirname(__file__)))