Fixes the sensor.filter outlier filter (handle step-changes correctly) (#21332)

* Fix outlier filter median return, Add/update filter outlier tests

* Switch outlier filter to store raw vals (handles step-changes correctly)

* Filter store_raw as attribute instead of filter_state parameter

* Fix linting issues
This commit is contained in:
siberx 2019-02-23 05:51:34 -08:00 committed by Diogo Gomes
parent 954bd4e13b
commit 616c7628d7
2 changed files with 28 additions and 5 deletions

View File

@ -313,6 +313,7 @@ class Filter:
self._entity = entity
self._skip_processing = False
self._window_size = window_size
self._store_raw = False
@property
def window_size(self):
@ -337,7 +338,10 @@ class Filter:
"""Implement a common interface for filters."""
filtered = self._filter_state(FilterState(new_state))
filtered.set_precision(self.precision)
self.states.append(copy(filtered))
if self._store_raw:
self.states.append(copy(FilterState(new_state)))
else:
self.states.append(copy(filtered))
new_state.state = filtered.state
return new_state
@ -402,12 +406,14 @@ class OutlierFilter(Filter):
super().__init__(FILTER_NAME_OUTLIER, window_size, precision, entity)
self._radius = radius
self._stats_internal = Counter()
self._store_raw = True
def _filter_state(self, new_state):
"""Implement the outlier filter."""
median = statistics.median([s.state for s in self.states]) \
if self.states else 0
if (len(self.states) == self.states.maxlen and
abs(new_state.state -
statistics.median([s.state for s in self.states])) >
abs(new_state.state - median) >
self._radius):
self._stats_internal['erasures'] += 1
@ -415,7 +421,7 @@ class OutlierFilter(Filter):
_LOGGER.debug("Outlier nr. %s in %s: %s",
self._stats_internal['erasures'],
self._entity, new_state)
return self.states[-1]
new_state.state = median
return new_state

View File

@ -106,6 +106,23 @@ class TestFilterSensor(unittest.TestCase):
precision=2,
entity=None,
radius=4.0)
for state in self.values:
filtered = filt.filter_state(state)
assert 21 == filtered.state
def test_outlier_step(self):
"""
Test step-change handling in outlier.
Test if outlier filter handles long-running step-changes correctly.
It should converge to no longer filter once just over half the
window_size is occupied by the new post step-change values.
"""
filt = OutlierFilter(window_size=3,
precision=2,
entity=None,
radius=1.1)
self.values[-1].state = 22
for state in self.values:
filtered = filt.filter_state(state)
assert 22 == filtered.state
@ -119,7 +136,7 @@ class TestFilterSensor(unittest.TestCase):
out = ha.State('sensor.test_monitored', 4000)
for state in [out]+self.values:
filtered = filt.filter_state(state)
assert 22 == filtered.state
assert 21 == filtered.state
def test_lowpass(self):
"""Test if lowpass filter works."""