mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 01:07:10 +00:00
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:
parent
954bd4e13b
commit
616c7628d7
@ -313,6 +313,7 @@ class Filter:
|
|||||||
self._entity = entity
|
self._entity = entity
|
||||||
self._skip_processing = False
|
self._skip_processing = False
|
||||||
self._window_size = window_size
|
self._window_size = window_size
|
||||||
|
self._store_raw = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def window_size(self):
|
def window_size(self):
|
||||||
@ -337,6 +338,9 @@ class Filter:
|
|||||||
"""Implement a common interface for filters."""
|
"""Implement a common interface for filters."""
|
||||||
filtered = self._filter_state(FilterState(new_state))
|
filtered = self._filter_state(FilterState(new_state))
|
||||||
filtered.set_precision(self.precision)
|
filtered.set_precision(self.precision)
|
||||||
|
if self._store_raw:
|
||||||
|
self.states.append(copy(FilterState(new_state)))
|
||||||
|
else:
|
||||||
self.states.append(copy(filtered))
|
self.states.append(copy(filtered))
|
||||||
new_state.state = filtered.state
|
new_state.state = filtered.state
|
||||||
return new_state
|
return new_state
|
||||||
@ -402,12 +406,14 @@ class OutlierFilter(Filter):
|
|||||||
super().__init__(FILTER_NAME_OUTLIER, window_size, precision, entity)
|
super().__init__(FILTER_NAME_OUTLIER, window_size, precision, entity)
|
||||||
self._radius = radius
|
self._radius = radius
|
||||||
self._stats_internal = Counter()
|
self._stats_internal = Counter()
|
||||||
|
self._store_raw = True
|
||||||
|
|
||||||
def _filter_state(self, new_state):
|
def _filter_state(self, new_state):
|
||||||
"""Implement the outlier filter."""
|
"""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
|
if (len(self.states) == self.states.maxlen and
|
||||||
abs(new_state.state -
|
abs(new_state.state - median) >
|
||||||
statistics.median([s.state for s in self.states])) >
|
|
||||||
self._radius):
|
self._radius):
|
||||||
|
|
||||||
self._stats_internal['erasures'] += 1
|
self._stats_internal['erasures'] += 1
|
||||||
@ -415,7 +421,7 @@ class OutlierFilter(Filter):
|
|||||||
_LOGGER.debug("Outlier nr. %s in %s: %s",
|
_LOGGER.debug("Outlier nr. %s in %s: %s",
|
||||||
self._stats_internal['erasures'],
|
self._stats_internal['erasures'],
|
||||||
self._entity, new_state)
|
self._entity, new_state)
|
||||||
return self.states[-1]
|
new_state.state = median
|
||||||
return new_state
|
return new_state
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,6 +106,23 @@ class TestFilterSensor(unittest.TestCase):
|
|||||||
precision=2,
|
precision=2,
|
||||||
entity=None,
|
entity=None,
|
||||||
radius=4.0)
|
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:
|
for state in self.values:
|
||||||
filtered = filt.filter_state(state)
|
filtered = filt.filter_state(state)
|
||||||
assert 22 == filtered.state
|
assert 22 == filtered.state
|
||||||
@ -119,7 +136,7 @@ class TestFilterSensor(unittest.TestCase):
|
|||||||
out = ha.State('sensor.test_monitored', 4000)
|
out = ha.State('sensor.test_monitored', 4000)
|
||||||
for state in [out]+self.values:
|
for state in [out]+self.values:
|
||||||
filtered = filt.filter_state(state)
|
filtered = filt.filter_state(state)
|
||||||
assert 22 == filtered.state
|
assert 21 == filtered.state
|
||||||
|
|
||||||
def test_lowpass(self):
|
def test_lowpass(self):
|
||||||
"""Test if lowpass filter works."""
|
"""Test if lowpass filter works."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user