From 7213d5f31be7a8ac7765362830d6a9028477230c Mon Sep 17 00:00:00 2001 From: Indu Prakash <6459774+iprak@users.noreply.github.com> Date: Sat, 21 Nov 2020 05:44:37 -0600 Subject: [PATCH] Support for multiple states in history_stats (#43416) Co-authored-by: Indu Prakash <6459774+InduPrakash@users.noreply.github.com> --- .../components/history_stats/sensor.py | 15 ++- tests/components/history_stats/test_sensor.py | 106 ++++++++++++++++++ 2 files changed, 113 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py index 8f4a89e3e37..6778e893f6f 100644 --- a/homeassistant/components/history_stats/sensor.py +++ b/homeassistant/components/history_stats/sensor.py @@ -62,7 +62,7 @@ PLATFORM_SCHEMA = vol.All( PLATFORM_SCHEMA.extend( { vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Required(CONF_STATE): cv.string, + vol.Required(CONF_STATE): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_START): cv.template, vol.Optional(CONF_END): cv.template, vol.Optional(CONF_DURATION): cv.time_period, @@ -77,11 +77,10 @@ PLATFORM_SCHEMA = vol.All( # noinspection PyUnusedLocal def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the History Stats sensor.""" - setup_reload_service(hass, DOMAIN, PLATFORMS) entity_id = config.get(CONF_ENTITY_ID) - entity_state = config.get(CONF_STATE) + entity_states = config.get(CONF_STATE) start = config.get(CONF_START) end = config.get(CONF_END) duration = config.get(CONF_DURATION) @@ -95,7 +94,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities( [ HistoryStatsSensor( - hass, entity_id, entity_state, start, end, duration, sensor_type, name + hass, entity_id, entity_states, start, end, duration, sensor_type, name ) ] ) @@ -107,11 +106,11 @@ class HistoryStatsSensor(Entity): """Representation of a HistoryStats sensor.""" def __init__( - self, hass, entity_id, entity_state, start, end, duration, sensor_type, name + self, hass, entity_id, entity_states, start, end, duration, sensor_type, name ): """Initialize the HistoryStats sensor.""" self._entity_id = entity_id - self._entity_state = entity_state + self._entity_states = entity_states self._duration = duration self._start = start self._end = end @@ -230,14 +229,14 @@ class HistoryStatsSensor(Entity): # Get the first state last_state = history.get_state(self.hass, start, self._entity_id) - last_state = last_state is not None and last_state == self._entity_state + last_state = last_state is not None and last_state in self._entity_states last_time = start_timestamp elapsed = 0 count = 0 # Make calculations for item in history_list.get(self._entity_id): - current_state = item.state == self._entity_state + current_state = item.state in self._entity_states current_time = item.last_changed.timestamp() if last_state: diff --git a/tests/components/history_stats/test_sensor.py b/tests/components/history_stats/test_sensor.py index bab6eae4564..db6d7476912 100644 --- a/tests/components/history_stats/test_sensor.py +++ b/tests/components/history_stats/test_sensor.py @@ -50,6 +50,28 @@ class TestHistoryStatsSensor(unittest.TestCase): state = self.hass.states.get("sensor.test") assert state.state == STATE_UNKNOWN + def test_setup_multiple_states(self): + """Test the history statistics sensor setup for multiple states.""" + self.init_recorder() + config = { + "history": {}, + "sensor": { + "platform": "history_stats", + "entity_id": "binary_sensor.test_id", + "state": ["on", "true"], + "start": "{{ now().replace(hour=0)" + ".replace(minute=0).replace(second=0) }}", + "duration": "02:00", + "name": "Test", + }, + } + + assert setup_component(self.hass, "sensor", config) + self.hass.block_till_done() + + state = self.hass.states.get("sensor.test") + assert state.state == STATE_UNKNOWN + @patch( "homeassistant.helpers.template.TemplateEnvironment.is_safe_callable", return_value=True, @@ -152,6 +174,90 @@ class TestHistoryStatsSensor(unittest.TestCase): 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, + ): + with 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)