mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 23:27:37 +00:00
Add type configuration in history_stats (#6430)
This commit is contained in:
parent
62e57456e1
commit
9ac3928600
@ -16,7 +16,8 @@ import homeassistant.helpers.config_validation as cv
|
|||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||||
from homeassistant.const import (
|
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.exceptions import TemplateError
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.event import track_state_change
|
from homeassistant.helpers.event import track_state_change
|
||||||
@ -31,15 +32,22 @@ CONF_END = 'end'
|
|||||||
CONF_DURATION = 'duration'
|
CONF_DURATION = 'duration'
|
||||||
CONF_PERIOD_KEYS = [CONF_START, CONF_END, CONF_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'
|
DEFAULT_NAME = 'unnamed statistics'
|
||||||
UNIT = 'h'
|
UNITS = {
|
||||||
UNIT_RATIO = '%'
|
CONF_TYPE_TIME: 'h',
|
||||||
|
CONF_TYPE_RATIO: '%',
|
||||||
|
CONF_TYPE_COUNT: ''
|
||||||
|
}
|
||||||
ICON = 'mdi:chart-line'
|
ICON = 'mdi:chart-line'
|
||||||
|
|
||||||
ATTR_START = 'from'
|
ATTR_START = 'from'
|
||||||
ATTR_END = 'to'
|
ATTR_END = 'to'
|
||||||
ATTR_VALUE = 'value'
|
ATTR_VALUE = 'value'
|
||||||
ATTR_RATIO = 'ratio'
|
|
||||||
|
|
||||||
|
|
||||||
def exactly_two_period_keys(conf):
|
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_START, default=None): cv.template,
|
||||||
vol.Optional(CONF_END, default=None): cv.template,
|
vol.Optional(CONF_END, default=None): cv.template,
|
||||||
vol.Optional(CONF_DURATION, default=None): cv.time_period,
|
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,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
}), exactly_two_period_keys)
|
}), exactly_two_period_keys)
|
||||||
|
|
||||||
@ -74,14 +83,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
start = config.get(CONF_START)
|
start = config.get(CONF_START)
|
||||||
end = config.get(CONF_END)
|
end = config.get(CONF_END)
|
||||||
duration = config.get(CONF_DURATION)
|
duration = config.get(CONF_DURATION)
|
||||||
|
sensor_type = config.get(CONF_TYPE)
|
||||||
name = config.get(CONF_NAME)
|
name = config.get(CONF_NAME)
|
||||||
|
|
||||||
for template in [start, end]:
|
for template in [start, end]:
|
||||||
if template is not None:
|
if template is not None:
|
||||||
template.hass = hass
|
template.hass = hass
|
||||||
|
|
||||||
add_devices([HistoryStatsSensor(
|
add_devices([HistoryStatsSensor(hass, entity_id, entity_state, start, end,
|
||||||
hass, entity_id, entity_state, start, end, duration, name)])
|
duration, sensor_type, name)])
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -90,7 +100,8 @@ class HistoryStatsSensor(Entity):
|
|||||||
"""Representation of a HistoryStats sensor."""
|
"""Representation of a HistoryStats sensor."""
|
||||||
|
|
||||||
def __init__(
|
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."""
|
"""Initialize the HistoryStats sensor."""
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
|
|
||||||
@ -99,11 +110,13 @@ class HistoryStatsSensor(Entity):
|
|||||||
self._duration = duration
|
self._duration = duration
|
||||||
self._start = start
|
self._start = start
|
||||||
self._end = end
|
self._end = end
|
||||||
|
self._type = sensor_type
|
||||||
self._name = name
|
self._name = name
|
||||||
self._unit_of_measurement = UNIT
|
self._unit_of_measurement = UNITS[sensor_type]
|
||||||
|
|
||||||
self._period = (datetime.datetime.now(), datetime.datetime.now())
|
self._period = (datetime.datetime.now(), datetime.datetime.now())
|
||||||
self.value = 0
|
self.value = 0
|
||||||
|
self.count = 0
|
||||||
|
|
||||||
def force_refresh(*args):
|
def force_refresh(*args):
|
||||||
"""Force the component to refresh."""
|
"""Force the component to refresh."""
|
||||||
@ -123,7 +136,14 @@ class HistoryStatsSensor(Entity):
|
|||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the sensor."""
|
"""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
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
@ -142,7 +162,6 @@ class HistoryStatsSensor(Entity):
|
|||||||
hsh = HistoryStatsHelper
|
hsh = HistoryStatsHelper
|
||||||
return {
|
return {
|
||||||
ATTR_VALUE: hsh.pretty_duration(self.value),
|
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_START: start.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
ATTR_END: end.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_state == self._entity_state)
|
||||||
last_time = dt_util.as_timestamp(start)
|
last_time = dt_util.as_timestamp(start)
|
||||||
elapsed = 0
|
elapsed = 0
|
||||||
|
count = 0
|
||||||
|
|
||||||
# Make calculations
|
# Make calculations
|
||||||
for item in history_list.get(self._entity_id):
|
for item in history_list.get(self._entity_id):
|
||||||
@ -183,6 +203,8 @@ class HistoryStatsSensor(Entity):
|
|||||||
|
|
||||||
if last_state:
|
if last_state:
|
||||||
elapsed += current_time - last_time
|
elapsed += current_time - last_time
|
||||||
|
if current_state and not last_state:
|
||||||
|
count += 1
|
||||||
|
|
||||||
last_state = current_state
|
last_state = current_state
|
||||||
last_time = current_time
|
last_time = current_time
|
||||||
@ -196,6 +218,9 @@ class HistoryStatsSensor(Entity):
|
|||||||
# Save value in hours
|
# Save value in hours
|
||||||
self.value = elapsed / 3600
|
self.value = elapsed / 3600
|
||||||
|
|
||||||
|
# Save counter
|
||||||
|
self.count = count
|
||||||
|
|
||||||
def update_period(self):
|
def update_period(self):
|
||||||
"""Parse the templates and store a datetime tuple in _period."""
|
"""Parse the templates and store a datetime tuple in _period."""
|
||||||
start = None
|
start = None
|
||||||
@ -267,10 +292,10 @@ class HistoryStatsHelper:
|
|||||||
def pretty_ratio(value, period):
|
def pretty_ratio(value, period):
|
||||||
"""Format the ratio of value / period duration."""
|
"""Format the ratio of value / period duration."""
|
||||||
if len(period) != 2 or period[0] == period[1]:
|
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()
|
ratio = 100 * 3600 * value / (period[1] - period[0]).total_seconds()
|
||||||
return str(round(ratio, 1)) + UNIT_RATIO
|
return round(ratio, 1)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def handle_template_exception(ex, field):
|
def handle_template_exception(ex, field):
|
||||||
|
@ -53,9 +53,9 @@ class TestHistoryStatsSensor(unittest.TestCase):
|
|||||||
duration = timedelta(hours=2, minutes=1)
|
duration = timedelta(hours=2, minutes=1)
|
||||||
|
|
||||||
sensor1 = HistoryStatsSensor(
|
sensor1 = HistoryStatsSensor(
|
||||||
self.hass, 'test', 'on', today, None, duration, 'test')
|
self.hass, 'test', 'on', today, None, duration, 'time', 'test')
|
||||||
sensor2 = HistoryStatsSensor(
|
sensor2 = HistoryStatsSensor(
|
||||||
self.hass, 'test', 'on', None, today, duration, 'test')
|
self.hass, 'test', 'on', None, today, duration, 'time', 'test')
|
||||||
|
|
||||||
sensor1.update_period()
|
sensor1.update_period()
|
||||||
sensor2.update_period()
|
sensor2.update_period()
|
||||||
@ -91,10 +91,23 @@ class TestHistoryStatsSensor(unittest.TestCase):
|
|||||||
end = Template('{{ now() }}', self.hass)
|
end = Template('{{ now() }}', self.hass)
|
||||||
|
|
||||||
sensor1 = HistoryStatsSensor(
|
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(
|
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.'
|
with patch('homeassistant.components.history.'
|
||||||
'state_changes_during_period', return_value=fake_states):
|
'state_changes_during_period', return_value=fake_states):
|
||||||
@ -102,10 +115,13 @@ class TestHistoryStatsSensor(unittest.TestCase):
|
|||||||
return_value=None):
|
return_value=None):
|
||||||
sensor1.update()
|
sensor1.update()
|
||||||
sensor2.update()
|
sensor2.update()
|
||||||
|
sensor3.update()
|
||||||
|
sensor4.update()
|
||||||
|
|
||||||
self.assertEqual(round(sensor1.value, 3), 0.5)
|
self.assertEqual(sensor1.state, 0.5)
|
||||||
self.assertEqual(round(sensor2.value, 3), 0)
|
self.assertEqual(sensor2.state, 0)
|
||||||
self.assertEqual(sensor1.device_state_attributes['ratio'], '50.0%')
|
self.assertEqual(sensor3.state, 2)
|
||||||
|
self.assertEqual(sensor4.state, 50)
|
||||||
|
|
||||||
def test_wrong_date(self):
|
def test_wrong_date(self):
|
||||||
"""Test when start or end value is not a timestamp or a date."""
|
"""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)
|
bad = Template('{{ TEST }}', self.hass)
|
||||||
|
|
||||||
sensor1 = HistoryStatsSensor(
|
sensor1 = HistoryStatsSensor(
|
||||||
self.hass, 'test', 'on', good, bad, None, 'Test')
|
self.hass, 'test', 'on', good, bad, None, 'time', 'Test')
|
||||||
sensor2 = HistoryStatsSensor(
|
sensor2 = HistoryStatsSensor(
|
||||||
self.hass, 'test', 'on', bad, good, None, 'Test')
|
self.hass, 'test', 'on', bad, good, None, 'time', 'Test')
|
||||||
|
|
||||||
before_update1 = sensor1._period
|
before_update1 = sensor1._period
|
||||||
before_update2 = sensor2._period
|
before_update2 = sensor2._period
|
||||||
@ -153,9 +169,9 @@ class TestHistoryStatsSensor(unittest.TestCase):
|
|||||||
duration = '01:00'
|
duration = '01:00'
|
||||||
|
|
||||||
sensor1 = HistoryStatsSensor(
|
sensor1 = HistoryStatsSensor(
|
||||||
self.hass, 'test', 'on', bad, None, duration, 'Test')
|
self.hass, 'test', 'on', bad, None, duration, 'time', 'Test')
|
||||||
sensor2 = HistoryStatsSensor(
|
sensor2 = HistoryStatsSensor(
|
||||||
self.hass, 'test', 'on', None, bad, duration, 'Test')
|
self.hass, 'test', 'on', None, bad, duration, 'time', 'Test')
|
||||||
|
|
||||||
before_update1 = sensor1._period
|
before_update1 = sensor1._period
|
||||||
before_update2 = sensor2._period
|
before_update2 = sensor2._period
|
||||||
|
Loading…
x
Reference in New Issue
Block a user