11"""A PH-803W device value collector."""
2+ from statistics import stdev , mean , StatisticsError
23import threading
34import socket
45import logging
@@ -23,6 +24,7 @@ def __init__(self, host):
2324 self .passcode = ""
2425 self ._measurements = []
2526 self ._latest_measurement = None
27+ self ._measurements_filter = None
2628 self ._socket = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
2729 self ._loop = True
2830 self ._empty_counter = 0
@@ -178,6 +180,13 @@ def _handle_login_response(self, data):
178180 def _handle_data_response (self , data ):
179181 if len (data ) == 18 :
180182 meas = Measurement (data )
183+ if self ._measurements_filter is None :
184+ self ._measurements_filter = MeasOutlierFilter (meas .ph , meas .orp )
185+ else :
186+ self ._measurements_filter .add (meas .ph , meas .orp )
187+ meas .add_filtered (
188+ self ._measurements_filter .get_ph (), self ._measurements_filter .get_orp ()
189+ )
181190 _LOGGER .debug ("Adding result: %s" % meas )
182191 self ._measurements .append (meas )
183192 self ._latest_measurement = meas
@@ -240,6 +249,46 @@ def __exit__(self, type, value, traceback):
240249 self .close ()
241250
242251
252+ class MeasOutlierFilter :
253+ def __init__ (self , ph : float , orp : float , history : int = 10 ) -> None :
254+ self ._ph_filter = OutlierFilter (ph , history )
255+ self ._orp_filter = OutlierFilter (orp , history )
256+
257+ def add (self , ph , orp ):
258+ self ._ph_filter .add (ph )
259+ self ._orp_filter .add (orp )
260+
261+ def get_ph (self ) -> float :
262+ return self ._ph_filter .get ()
263+
264+ def get_orp (self ) -> float :
265+ return self ._orp_filter .get ()
266+
267+
268+ class OutlierFilter :
269+ def __init__ (self , init_value : float , history : int = 10 ) -> None :
270+ self ._values = []
271+ self ._values .append (init_value )
272+ self ._history = history
273+
274+ def add (self , value ):
275+ self ._values .append (value )
276+ if len (self ._values ) > self ._history :
277+ self ._values .pop (0 )
278+
279+ def get (self ) -> float :
280+ try :
281+ stddev_val = stdev (self ._values )
282+ mean_val = mean (self ._values )
283+ for val in reversed (self ._values ):
284+ if (val <= mean_val + stddev_val ) and (val >= mean_val - stddev_val ):
285+ return val
286+ except StatisticsError :
287+ return self ._values [0 ]
288+ _LOGGER .error ("No match in outlier filter shall never happen!" )
289+ return None
290+
291+
243292class Measurement :
244293 def __init__ (self , data ) -> None :
245294 flag1 = data [8 ]
@@ -249,17 +298,25 @@ def __init__(self, data) -> None:
249298 self .ph_on = flag2 & 0b0000_0001 != 0
250299 ph_raw = data [10 :12 ]
251300 self .ph = int .from_bytes (ph_raw , "big" ) * 0.01
301+ self .ph_filt = None
252302 orp_raw = data [12 :14 ]
253303 self .orp = int .from_bytes (orp_raw , "big" ) - 2000
304+ self .orp_filt = None
254305 unknown1_raw = data [14 :16 ]
255306 self .unknown1 = int .from_bytes (unknown1_raw , "big" )
256307 unknown2_raw = data [15 :18 ]
257308 self .unknown2 = int .from_bytes (unknown2_raw , "big" )
258309
310+ def add_filtered (self , ph_filt , orp_filt ):
311+ self .ph_filt = ph_filt
312+ self .orp_filt = orp_filt
313+
259314 def __str__ (self ) -> str :
260- return "pH: %s, Orp: %s, In-water: %s, pH-on: %s, Orp-on: %s" % (
315+ return "pH: %s (%s) , Orp: %s (%s) , In-water: %s, pH-on: %s, Orp-on: %s" % (
261316 self .ph ,
317+ self .ph_filt ,
262318 self .orp ,
319+ self .orp_filt ,
263320 self .in_water ,
264321 self .ph_on ,
265322 self .orp_on ,
0 commit comments