Add type configuration in history_stats (#6430)

pull/6529/head^2
Boris K 2017-03-11 19:38:18 +01:00 committed by Paulus Schoutsen
parent 62e57456e1
commit 9ac3928600
2 changed files with 64 additions and 23 deletions

View File

@ -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):

View File

@ -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