mirror of
https://github.com/home-assistant/core.git
synced 2025-08-13 17:45:19 +02:00
Various Improvements
This commit is contained in:
@@ -7,7 +7,7 @@ https://home-assistant.io/components/sensor.filter/
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import statistics
|
import statistics
|
||||||
from collections import deque
|
from collections import deque, Counter
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@@ -15,7 +15,8 @@ from homeassistant.util import slugify
|
|||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
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, ATTR_UNIT_OF_MEASUREMENT, ATTR_ENTITY_ID)
|
CONF_NAME, CONF_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, ATTR_ENTITY_ID,
|
||||||
|
ATTR_ICON, STATE_UNKNOWN)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.event import async_track_state_change
|
from homeassistant.helpers.event import async_track_state_change
|
||||||
@@ -36,7 +37,7 @@ DEFAULT_FILTER_RADIUS = 2.0
|
|||||||
DEFAULT_FILTER_TIME_CONSTANT = 10
|
DEFAULT_FILTER_TIME_CONSTANT = 10
|
||||||
|
|
||||||
NAME_TEMPLATE = "{} filter"
|
NAME_TEMPLATE = "{} filter"
|
||||||
ICON = 'mdi: chart-line-variant'
|
ICON = 'mdi:chart-line-variant'
|
||||||
|
|
||||||
FILTER_SCHEMA = vol.Schema({
|
FILTER_SCHEMA = vol.Schema({
|
||||||
vol.Optional(CONF_FILTER_WINDOW_SIZE): vol.Coerce(int),
|
vol.Optional(CONF_FILTER_WINDOW_SIZE): vol.Coerce(int),
|
||||||
@@ -67,8 +68,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||||
"""Set up the template sensors."""
|
"""Set up the template sensors."""
|
||||||
sensors = []
|
|
||||||
|
|
||||||
name = config.get(CONF_NAME)
|
name = config.get(CONF_NAME)
|
||||||
entity_id = config.get(CONF_ENTITY_ID)
|
entity_id = config.get(CONF_ENTITY_ID)
|
||||||
filters = []
|
filters = []
|
||||||
@@ -81,16 +80,16 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
|||||||
radius = _filter.get(CONF_FILTER_RADIUS)
|
radius = _filter.get(CONF_FILTER_RADIUS)
|
||||||
filters.append(OutlierFilter(window_size=window_size,
|
filters.append(OutlierFilter(window_size=window_size,
|
||||||
precision=precision,
|
precision=precision,
|
||||||
|
entity=entity_id,
|
||||||
radius=radius))
|
radius=radius))
|
||||||
elif _filter[CONF_FILTER_NAME] == FILTER_NAME_LOWPASS:
|
elif _filter[CONF_FILTER_NAME] == FILTER_NAME_LOWPASS:
|
||||||
time_constant = _filter.get(CONF_FILTER_TIME_CONSTANT)
|
time_constant = _filter.get(CONF_FILTER_TIME_CONSTANT)
|
||||||
filters.append(LowPassFilter(window_size=window_size,
|
filters.append(LowPassFilter(window_size=window_size,
|
||||||
precision=precision,
|
precision=precision,
|
||||||
|
entity=entity_id,
|
||||||
time_constant=time_constant))
|
time_constant=time_constant))
|
||||||
|
|
||||||
sensors.append(SensorFilter(name, entity_id, filters))
|
async_add_devices([SensorFilter(name, entity_id, filters)])
|
||||||
|
|
||||||
async_add_devices(sensors)
|
|
||||||
|
|
||||||
|
|
||||||
class SensorFilter(Entity):
|
class SensorFilter(Entity):
|
||||||
@@ -103,6 +102,7 @@ class SensorFilter(Entity):
|
|||||||
self._unit_of_measurement = None
|
self._unit_of_measurement = None
|
||||||
self._state = None
|
self._state = None
|
||||||
self._filters = filters
|
self._filters = filters
|
||||||
|
self._icon = ICON
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_added_to_hass(self):
|
def async_added_to_hass(self):
|
||||||
@@ -112,13 +112,18 @@ class SensorFilter(Entity):
|
|||||||
"""Handle device state changes."""
|
"""Handle device state changes."""
|
||||||
self._unit_of_measurement = new_state.attributes.get(
|
self._unit_of_measurement = new_state.attributes.get(
|
||||||
ATTR_UNIT_OF_MEASUREMENT)
|
ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
self._icon = new_state.attributes.get(ATTR_ICON, ICON)
|
||||||
|
|
||||||
self._state = new_state.state
|
self._state = new_state.state
|
||||||
|
|
||||||
|
if self._state == STATE_UNKNOWN:
|
||||||
|
return
|
||||||
|
|
||||||
for filt in self._filters:
|
for filt in self._filters:
|
||||||
try:
|
try:
|
||||||
filtered_state = filt.filter_state(self._state)
|
filtered_state = filt.filter_state(self._state)
|
||||||
_LOGGER.debug("%s(%s) -> %s", filt.name, self._state,
|
_LOGGER.debug("%s(%s, %s) -> %s", filt.name, self._entity,
|
||||||
filtered_state)
|
self._state, filtered_state)
|
||||||
self._state = filtered_state
|
self._state = filtered_state
|
||||||
filt.states.append(filtered_state)
|
filt.states.append(filtered_state)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -143,7 +148,7 @@ class SensorFilter(Entity):
|
|||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self):
|
||||||
"""Return the icon to use in the frontend, if any."""
|
"""Return the icon to use in the frontend, if any."""
|
||||||
return ICON
|
return self._icon
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
@@ -162,9 +167,13 @@ class SensorFilter(Entity):
|
|||||||
ATTR_ENTITY_ID: self._entity
|
ATTR_ENTITY_ID: self._entity
|
||||||
}
|
}
|
||||||
for filt in self._filters:
|
for filt in self._filters:
|
||||||
state_attr.update({
|
for filt_stat_key, filt_stat_value in filt.stats.items():
|
||||||
slugify("{} stats".format(filt.name)): filt.stats
|
filt_stat = slugify("{} {}".format(filt.name, filt_stat_key))
|
||||||
})
|
_LOGGER.debug("stats(%s): %s: %s", self._entity,
|
||||||
|
filt_stat, filt_stat_value)
|
||||||
|
state_attr.update({
|
||||||
|
filt_stat: filt_stat_value
|
||||||
|
})
|
||||||
|
|
||||||
return state_attr
|
return state_attr
|
||||||
|
|
||||||
@@ -175,14 +184,17 @@ class Filter(object):
|
|||||||
Args:
|
Args:
|
||||||
window_size (int): size of the sliding window that holds previous
|
window_size (int): size of the sliding window that holds previous
|
||||||
values
|
values
|
||||||
|
precision (int): round filtered value to precision value
|
||||||
|
entity (string): used for debugging only
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, window_size=1, precision=None):
|
def __init__(self, name, window_size=1, precision=None, entity=None):
|
||||||
"""Initialize common attributes."""
|
"""Initialize common attributes."""
|
||||||
self.states = deque(maxlen=window_size)
|
self.states = deque(maxlen=window_size)
|
||||||
self.precision = precision
|
self.precision = precision
|
||||||
self._stats = {}
|
self._stats = {}
|
||||||
self._name = name
|
self._name = name
|
||||||
|
self._entity = entity
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@@ -216,22 +228,29 @@ class OutlierFilter(Filter):
|
|||||||
window_size (int): see Filter()
|
window_size (int): see Filter()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, window_size, precision, radius):
|
def __init__(self, window_size, precision, entity, radius):
|
||||||
"""Initialize Filter."""
|
"""Initialize Filter."""
|
||||||
super().__init__(FILTER_NAME_OUTLIER, window_size, precision)
|
super().__init__(FILTER_NAME_OUTLIER, window_size, precision, entity)
|
||||||
self._radius = radius
|
self._radius = radius
|
||||||
|
self._stats_internal = Counter()
|
||||||
|
|
||||||
def _filter_state(self, new_state):
|
def _filter_state(self, new_state):
|
||||||
"""Implement the outlier filter."""
|
"""Implement the outlier filter."""
|
||||||
|
self._stats_internal['total_filtered'] += 1
|
||||||
new_state = float(new_state)
|
new_state = float(new_state)
|
||||||
|
|
||||||
|
self._stats['erasures'] = "{0:.2f}%".format(
|
||||||
|
100 * self._stats_internal['erasures']
|
||||||
|
/ self._stats_internal['total_filtered']
|
||||||
|
)
|
||||||
|
|
||||||
if (len(self.states) > 1 and
|
if (len(self.states) > 1 and
|
||||||
abs(new_state - statistics.median(self.states))
|
abs(new_state - statistics.median(self.states))
|
||||||
> self._radius):
|
> self._radius):
|
||||||
|
|
||||||
erasures = self._stats.get('erasures', 0)
|
self._stats_internal['erasures'] += 1
|
||||||
self._stats['erasures'] = erasures+1
|
|
||||||
|
|
||||||
_LOGGER.debug("Outlier in %s: %s", self._name, new_state)
|
_LOGGER.debug("Outlier in %s: %s", self._entity, new_state)
|
||||||
return self.states[-1]
|
return self.states[-1]
|
||||||
return new_state
|
return new_state
|
||||||
|
|
||||||
@@ -244,9 +263,9 @@ class LowPassFilter(Filter):
|
|||||||
window_size (int): see Filter()
|
window_size (int): see Filter()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, window_size, precision, time_constant):
|
def __init__(self, window_size, precision, entity, time_constant):
|
||||||
"""Initialize Filter."""
|
"""Initialize Filter."""
|
||||||
super().__init__(FILTER_NAME_LOWPASS, window_size, precision)
|
super().__init__(FILTER_NAME_LOWPASS, window_size, precision, entity)
|
||||||
self._time_constant = time_constant
|
self._time_constant = time_constant
|
||||||
|
|
||||||
def _filter_state(self, new_state):
|
def _filter_state(self, new_state):
|
||||||
|
@@ -45,9 +45,6 @@ class TestFilterSensor(unittest.TestCase):
|
|||||||
with assert_setup_component(1):
|
with assert_setup_component(1):
|
||||||
assert setup_component(self.hass, 'sensor', config)
|
assert setup_component(self.hass, 'sensor', config)
|
||||||
|
|
||||||
self.hass.start()
|
|
||||||
self.hass.block_till_done()
|
|
||||||
|
|
||||||
for value in self.values:
|
for value in self.values:
|
||||||
self.hass.states.set(config['sensor']['entity_id'], value)
|
self.hass.states.set(config['sensor']['entity_id'], value)
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
@@ -72,9 +69,6 @@ class TestFilterSensor(unittest.TestCase):
|
|||||||
with assert_setup_component(1):
|
with assert_setup_component(1):
|
||||||
assert setup_component(self.hass, 'sensor', config)
|
assert setup_component(self.hass, 'sensor', config)
|
||||||
|
|
||||||
self.hass.start()
|
|
||||||
self.hass.block_till_done()
|
|
||||||
|
|
||||||
for value in self.values:
|
for value in self.values:
|
||||||
self.hass.states.set(config['sensor']['entity_id'], value)
|
self.hass.states.set(config['sensor']['entity_id'], value)
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
|
Reference in New Issue
Block a user