From a34812328bfbf9a1b0a3f354811fd56c238db76f Mon Sep 17 00:00:00 2001 From: nielstron Date: Thu, 22 Mar 2018 09:57:05 +0100 Subject: [PATCH 1/5] Enable setting a median manually for the band-pass (outlier) filter --- homeassistant/components/sensor/filter.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/filter.py b/homeassistant/components/sensor/filter.py index 3faf51a5f47eeb..b1c09d7e38975e 100644 --- a/homeassistant/components/sensor/filter.py +++ b/homeassistant/components/sensor/filter.py @@ -21,6 +21,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change import homeassistant.util.dt as dt_util +import math _LOGGER = logging.getLogger(__name__) @@ -35,6 +36,7 @@ CONF_FILTER_WINDOW_SIZE = 'window_size' CONF_FILTER_PRECISION = 'precision' CONF_FILTER_RADIUS = 'radius' +CONF_FILTER_MEDIAN = 'median' CONF_FILTER_TIME_CONSTANT = 'time_constant' CONF_TIME_SMA_TYPE = 'type' @@ -44,6 +46,7 @@ DEFAULT_PRECISION = 2 DEFAULT_FILTER_RADIUS = 2.0 DEFAULT_FILTER_TIME_CONSTANT = 10 +NO_MANUAL_MEDIAN = math.inf NAME_TEMPLATE = "{} filter" ICON = 'mdi:chart-line-variant' @@ -60,6 +63,8 @@ default=DEFAULT_WINDOW_SIZE): vol.Coerce(int), vol.Optional(CONF_FILTER_RADIUS, default=DEFAULT_FILTER_RADIUS): vol.Coerce(float), + vol.Optional(CONF_FILTER_MEDIAN, + default=NO_MANUAL_MEDIAN): vol.Coerce(float), }) FILTER_LOWPASS_SCHEMA = FILTER_SCHEMA.extend({ @@ -244,21 +249,29 @@ class OutlierFilter(Filter): Args: radius (float): band radius + median (float): band center + (defaults to NO_MANUAL_MEDIAN for automatic computation) """ - def __init__(self, window_size, precision, entity, radius): + def __init__(self, window_size, precision, entity, radius, + median=NO_MANUAL_MEDIAN): """Initialize Filter.""" super().__init__(FILTER_NAME_OUTLIER, window_size, precision, entity) self._radius = radius + self._manual_median_enabled = (median == NO_MANUAL_MEDIAN) + self._manual_median = median self._stats_internal = Counter() def _filter_state(self, new_state): """Implement the outlier filter.""" new_state = float(new_state) - if (self.states and - abs(new_state - statistics.median(self.states)) - > self._radius): + diff = 0 + if (self._manual_median_enabled): + diff = abs(new_state - self._manual_median) + elif (self.states): + diff = abs(new_state - statistics.median(self.states)) + if (diff > self._radius): self._stats_internal['erasures'] += 1 From 11a1ebfa564e21543f24ae0e78993e6945f84f9e Mon Sep 17 00:00:00 2001 From: nielstron Date: Thu, 22 Mar 2018 10:04:54 +0100 Subject: [PATCH 2/5] Added test for manually set median --- homeassistant/components/sensor/filter.py | 26 +++++++++++------------ tests/components/sensor/test_filter.py | 11 ++++++++++ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/sensor/filter.py b/homeassistant/components/sensor/filter.py index b1c09d7e38975e..8be76cf10adde3 100644 --- a/homeassistant/components/sensor/filter.py +++ b/homeassistant/components/sensor/filter.py @@ -266,19 +266,19 @@ def _filter_state(self, new_state): """Implement the outlier filter.""" new_state = float(new_state) - diff = 0 - if (self._manual_median_enabled): - diff = abs(new_state - self._manual_median) - elif (self.states): - diff = abs(new_state - statistics.median(self.states)) - if (diff > self._radius): - - self._stats_internal['erasures'] += 1 - - _LOGGER.debug("Outlier nr. %s in %s: %s", - self._stats_internal['erasures'], - self._entity, new_state) - return self.states[-1] + if(self.states): + if (self._manual_median_enabled): + diff = abs(new_state - self._manual_median) + else: + diff = abs(new_state - statistics.median(self.states)) + if (diff > self._radius): + + self._stats_internal['erasures'] += 1 + + _LOGGER.debug("Outlier nr. %s in %s: %s", + self._stats_internal['erasures'], + self._entity, new_state) + return self.states[-1] return new_state diff --git a/tests/components/sensor/test_filter.py b/tests/components/sensor/test_filter.py index 0d4082731ab38f..15b658e4072940 100644 --- a/tests/components/sensor/test_filter.py +++ b/tests/components/sensor/test_filter.py @@ -72,6 +72,17 @@ def test_outlier(self): filtered = filt.filter_state(state) self.assertEqual(22, filtered) + def test_outlier_median(self): + """Test if outlier filter with manual median works.""" + filt = OutlierFilter(window_size=10, + precision=2, + entity=None, + radius=4.0, + median=0) + for state in self.values: + filtered = filt.filter_state(state) + self.assertEqual(0, filtered) + def test_lowpass(self): """Test if lowpass filter works.""" filt = LowPassFilter(window_size=10, From d530e66af85b0c60c068187644b831922228eb54 Mon Sep 17 00:00:00 2001 From: nielstron Date: Thu, 22 Mar 2018 10:05:50 +0100 Subject: [PATCH 3/5] PEP fixes --- homeassistant/components/sensor/filter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/filter.py b/homeassistant/components/sensor/filter.py index 8be76cf10adde3..a798ac5aa9f3a3 100644 --- a/homeassistant/components/sensor/filter.py +++ b/homeassistant/components/sensor/filter.py @@ -272,9 +272,9 @@ def _filter_state(self, new_state): else: diff = abs(new_state - statistics.median(self.states)) if (diff > self._radius): - + self._stats_internal['erasures'] += 1 - + _LOGGER.debug("Outlier nr. %s in %s: %s", self._stats_internal['erasures'], self._entity, new_state) From 54c3c389231fcdef2c56be4303d307e090dc6c02 Mon Sep 17 00:00:00 2001 From: nielstron Date: Thu, 22 Mar 2018 11:12:21 +0100 Subject: [PATCH 4/5] Small syntax fixes --- homeassistant/components/sensor/filter.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/filter.py b/homeassistant/components/sensor/filter.py index a798ac5aa9f3a3..f1f91190be9954 100644 --- a/homeassistant/components/sensor/filter.py +++ b/homeassistant/components/sensor/filter.py @@ -258,7 +258,6 @@ def __init__(self, window_size, precision, entity, radius, """Initialize Filter.""" super().__init__(FILTER_NAME_OUTLIER, window_size, precision, entity) self._radius = radius - self._manual_median_enabled = (median == NO_MANUAL_MEDIAN) self._manual_median = median self._stats_internal = Counter() @@ -266,12 +265,12 @@ def _filter_state(self, new_state): """Implement the outlier filter.""" new_state = float(new_state) - if(self.states): - if (self._manual_median_enabled): + if self.states: + if self._manual_median == NO_MANUAL_MEDIAN: diff = abs(new_state - self._manual_median) else: diff = abs(new_state - statistics.median(self.states)) - if (diff > self._radius): + if diff > self._radius: self._stats_internal['erasures'] += 1 From 3a0ca803f12ad72a6cd3813197ab190e45659e29 Mon Sep 17 00:00:00 2001 From: nielstron Date: Thu, 22 Mar 2018 11:37:57 +0100 Subject: [PATCH 5/5] Fixed branching Use the median if set manuall, not else --- homeassistant/components/sensor/filter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/filter.py b/homeassistant/components/sensor/filter.py index f1f91190be9954..13b8ae9a82a7d7 100644 --- a/homeassistant/components/sensor/filter.py +++ b/homeassistant/components/sensor/filter.py @@ -8,6 +8,7 @@ import statistics from collections import deque, Counter from numbers import Number +import math import voluptuous as vol @@ -21,7 +22,6 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change import homeassistant.util.dt as dt_util -import math _LOGGER = logging.getLogger(__name__) @@ -266,7 +266,7 @@ def _filter_state(self, new_state): new_state = float(new_state) if self.states: - if self._manual_median == NO_MANUAL_MEDIAN: + if self._manual_median != NO_MANUAL_MEDIAN: diff = abs(new_state - self._manual_median) else: diff = abs(new_state - statistics.median(self.states))