From 9ac39286004afcde3db52399e3312e46a8530f23 Mon Sep 17 00:00:00 2001 From: Boris K Date: Sat, 11 Mar 2017 19:38:18 +0100 Subject: [PATCH] Add type configuration in history_stats (#6430) --- .../components/sensor/history_stats.py | 49 ++++++++++++++----- tests/components/sensor/test_history_stats.py | 38 +++++++++----- 2 files changed, 64 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/sensor/history_stats.py b/homeassistant/components/sensor/history_stats.py index e436939d036..d5a02f8744e 100644 --- a/homeassistant/components/sensor/history_stats.py +++ b/homeassistant/components/sensor/history_stats.py @@ -16,7 +16,8 @@ import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_ENTITY_ID, CONF_STATE, EVENT_HOMEASSISTANT_START) + CONF_NAME, CONF_ENTITY_ID, CONF_STATE, CONF_TYPE, + EVENT_HOMEASSISTANT_START) from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_state_change @@ -31,15 +32,22 @@ CONF_END = 'end' CONF_DURATION = 'duration' CONF_PERIOD_KEYS = [CONF_START, CONF_END, CONF_DURATION] +CONF_TYPE_TIME = 'time' +CONF_TYPE_RATIO = 'ratio' +CONF_TYPE_COUNT = 'count' +CONF_TYPE_KEYS = [CONF_TYPE_TIME, CONF_TYPE_RATIO, CONF_TYPE_COUNT] + DEFAULT_NAME = 'unnamed statistics' -UNIT = 'h' -UNIT_RATIO = '%' +UNITS = { + CONF_TYPE_TIME: 'h', + CONF_TYPE_RATIO: '%', + CONF_TYPE_COUNT: '' +} ICON = 'mdi:chart-line' ATTR_START = 'from' ATTR_END = 'to' ATTR_VALUE = 'value' -ATTR_RATIO = 'ratio' def exactly_two_period_keys(conf): @@ -62,6 +70,7 @@ PLATFORM_SCHEMA = vol.All(PLATFORM_SCHEMA.extend({ vol.Optional(CONF_START, default=None): cv.template, vol.Optional(CONF_END, default=None): cv.template, vol.Optional(CONF_DURATION, default=None): cv.time_period, + vol.Optional(CONF_TYPE, default=CONF_TYPE_TIME): vol.In(CONF_TYPE_KEYS), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }), exactly_two_period_keys) @@ -74,14 +83,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None): start = config.get(CONF_START) end = config.get(CONF_END) duration = config.get(CONF_DURATION) + sensor_type = config.get(CONF_TYPE) name = config.get(CONF_NAME) for template in [start, end]: if template is not None: template.hass = hass - add_devices([HistoryStatsSensor( - hass, entity_id, entity_state, start, end, duration, name)]) + add_devices([HistoryStatsSensor(hass, entity_id, entity_state, start, end, + duration, sensor_type, name)]) return True @@ -90,7 +100,8 @@ class HistoryStatsSensor(Entity): """Representation of a HistoryStats sensor.""" def __init__( - self, hass, entity_id, entity_state, start, end, duration, name): + self, hass, entity_id, entity_state, start, end, duration, + sensor_type, name): """Initialize the HistoryStats sensor.""" self._hass = hass @@ -99,11 +110,13 @@ class HistoryStatsSensor(Entity): self._duration = duration self._start = start self._end = end + self._type = sensor_type self._name = name - self._unit_of_measurement = UNIT + self._unit_of_measurement = UNITS[sensor_type] self._period = (datetime.datetime.now(), datetime.datetime.now()) self.value = 0 + self.count = 0 def force_refresh(*args): """Force the component to refresh.""" @@ -123,7 +136,14 @@ class HistoryStatsSensor(Entity): @property def state(self): """Return the state of the sensor.""" - return round(self.value, 2) + if self._type == CONF_TYPE_TIME: + return round(self.value, 2) + + if self._type == CONF_TYPE_RATIO: + return HistoryStatsHelper.pretty_ratio(self.value, self._period) + + if self._type == CONF_TYPE_COUNT: + return self.count @property def unit_of_measurement(self): @@ -142,7 +162,6 @@ class HistoryStatsSensor(Entity): hsh = HistoryStatsHelper return { ATTR_VALUE: hsh.pretty_duration(self.value), - ATTR_RATIO: hsh.pretty_ratio(self.value, self._period), ATTR_START: start.strftime('%Y-%m-%d %H:%M:%S'), ATTR_END: end.strftime('%Y-%m-%d %H:%M:%S'), } @@ -175,6 +194,7 @@ class HistoryStatsSensor(Entity): last_state == self._entity_state) last_time = dt_util.as_timestamp(start) elapsed = 0 + count = 0 # Make calculations for item in history_list.get(self._entity_id): @@ -183,6 +203,8 @@ class HistoryStatsSensor(Entity): if last_state: elapsed += current_time - last_time + if current_state and not last_state: + count += 1 last_state = current_state last_time = current_time @@ -196,6 +218,9 @@ class HistoryStatsSensor(Entity): # Save value in hours self.value = elapsed / 3600 + # Save counter + self.count = count + def update_period(self): """Parse the templates and store a datetime tuple in _period.""" start = None @@ -267,10 +292,10 @@ class HistoryStatsHelper: def pretty_ratio(value, period): """Format the ratio of value / period duration.""" if len(period) != 2 or period[0] == period[1]: - return '0,0' + UNIT_RATIO + return 0.0 ratio = 100 * 3600 * value / (period[1] - period[0]).total_seconds() - return str(round(ratio, 1)) + UNIT_RATIO + return round(ratio, 1) @staticmethod def handle_template_exception(ex, field): diff --git a/tests/components/sensor/test_history_stats.py b/tests/components/sensor/test_history_stats.py index db1ccd95a99..282991fba0b 100644 --- a/tests/components/sensor/test_history_stats.py +++ b/tests/components/sensor/test_history_stats.py @@ -53,9 +53,9 @@ class TestHistoryStatsSensor(unittest.TestCase): duration = timedelta(hours=2, minutes=1) sensor1 = HistoryStatsSensor( - self.hass, 'test', 'on', today, None, duration, 'test') + self.hass, 'test', 'on', today, None, duration, 'time', 'test') sensor2 = HistoryStatsSensor( - self.hass, 'test', 'on', None, today, duration, 'test') + self.hass, 'test', 'on', None, today, duration, 'time', 'test') sensor1.update_period() sensor2.update_period() @@ -91,10 +91,23 @@ class TestHistoryStatsSensor(unittest.TestCase): end = Template('{{ now() }}', self.hass) sensor1 = HistoryStatsSensor( - self.hass, 'binary_sensor.test_id', 'on', start, end, None, 'Test') + self.hass, 'binary_sensor.test_id', 'on', start, end, None, + 'time', 'Test') sensor2 = HistoryStatsSensor( - self.hass, 'unknown.id', 'on', start, end, None, 'Test') + 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') + + self.assertEqual(sensor1._type, 'time') + self.assertEqual(sensor3._type, 'count') + self.assertEqual(sensor4._type, 'ratio') with patch('homeassistant.components.history.' 'state_changes_during_period', return_value=fake_states): @@ -102,10 +115,13 @@ class TestHistoryStatsSensor(unittest.TestCase): return_value=None): sensor1.update() sensor2.update() + sensor3.update() + sensor4.update() - self.assertEqual(round(sensor1.value, 3), 0.5) - self.assertEqual(round(sensor2.value, 3), 0) - self.assertEqual(sensor1.device_state_attributes['ratio'], '50.0%') + self.assertEqual(sensor1.state, 0.5) + self.assertEqual(sensor2.state, 0) + self.assertEqual(sensor3.state, 2) + self.assertEqual(sensor4.state, 50) def test_wrong_date(self): """Test when start or end value is not a timestamp or a date.""" @@ -113,9 +129,9 @@ class TestHistoryStatsSensor(unittest.TestCase): bad = Template('{{ TEST }}', self.hass) sensor1 = HistoryStatsSensor( - self.hass, 'test', 'on', good, bad, None, 'Test') + self.hass, 'test', 'on', good, bad, None, 'time', 'Test') sensor2 = HistoryStatsSensor( - self.hass, 'test', 'on', bad, good, None, 'Test') + self.hass, 'test', 'on', bad, good, None, 'time', 'Test') before_update1 = sensor1._period before_update2 = sensor2._period @@ -153,9 +169,9 @@ class TestHistoryStatsSensor(unittest.TestCase): duration = '01:00' sensor1 = HistoryStatsSensor( - self.hass, 'test', 'on', bad, None, duration, 'Test') + self.hass, 'test', 'on', bad, None, duration, 'time', 'Test') sensor2 = HistoryStatsSensor( - self.hass, 'test', 'on', None, bad, duration, 'Test') + self.hass, 'test', 'on', None, bad, duration, 'time', 'Test') before_update1 = sensor1._period before_update2 = sensor2._period