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
@@ -159,43 +161,39 @@ def _handle_response(self, data):
159161 elif message_type == 0x94 :
160162 self ._handle_data_extended_response (data )
161163 else :
162- pass
163164 _LOGGER .warning (
164165 "Ignore data package because invalid message type %s" % message_type
165166 )
166167
167168 def _handle_passcode_response (self , data ):
168- pass
169169 _LOGGER .warning ("Passcode resonse ignored" )
170170
171171 def _handle_login_response (self , data ):
172- pass
173172 _LOGGER .warning ("Login resonse ignored" )
174173
175174 def _handle_data_response (self , data ):
176175 if len (data ) == 18 :
177176 meas = Measurement (data )
177+ if self ._measurements_filter is None :
178+ self ._measurements_filter = MeasOutlierFilter (meas .ph , meas .orp )
179+ else :
180+ self ._measurements_filter .add (meas .ph , meas .orp )
181+ meas .add_filtered (
182+ self ._measurements_filter .get_ph (), self ._measurements_filter .get_orp ()
183+ )
178184 _LOGGER .debug ("Adding result: %s" % meas )
179185 self ._measurements .append (meas )
180186 self ._latest_measurement = meas
181187 if len (self ._measurements ) > 100 :
182188 self ._measurements .pop (0 )
183189 for callback in self ._callbacks :
184190 callback ()
185- else :
186- pass
187- _LOGGER .debug (meas )
191+ _LOGGER .debug (meas )
188192
189193 def _handle_data_extended_response (self , data ):
190- pass
191194 _LOGGER .warning ("Extended data ignored" )
192195
193196 def _handle_ping_pong_response (self ):
194- # if self._pong_thread is None or self._pong_thread.done:
195- # self._pong_thread = asyncio.create_task(self._async_queue_ping())
196- # pass
197- # else:
198- # _LOGGER.debug("Pong thread alredy running")
199197 _LOGGER .debug ("Pong message received" )
200198
201199 def _send_ping (self ):
@@ -208,10 +206,6 @@ def _ping_loop(self):
208206 self ._send_ping ()
209207 sleep (PH803W_PING_INTERVAL )
210208
211- # async def _async_queue_ping(self):
212- # await asyncio.sleep(PH803W_PING_INTERVAL)
213- # self._send_ping()
214-
215209 def abort (self ):
216210 self ._loop = False
217211
@@ -237,6 +231,46 @@ def __exit__(self, type, value, traceback):
237231 self .close ()
238232
239233
234+ class MeasOutlierFilter :
235+ def __init__ (self , ph : float , orp : float , history : int = 10 ) -> None :
236+ self ._ph_filter = OutlierFilter (ph , history )
237+ self ._orp_filter = OutlierFilter (orp , history )
238+
239+ def add (self , ph : float , orp : float ) -> None :
240+ self ._ph_filter .add (ph )
241+ self ._orp_filter .add (orp )
242+
243+ def get_ph (self ) -> float :
244+ return self ._ph_filter .get ()
245+
246+ def get_orp (self ) -> float :
247+ return self ._orp_filter .get ()
248+
249+
250+ class OutlierFilter :
251+ def __init__ (self , init_value : float , history : int = 10 ) -> None :
252+ self ._values = []
253+ self ._values .append (init_value )
254+ self ._history = history
255+
256+ def add (self , value : float ) -> None :
257+ self ._values .append (value )
258+ if len (self ._values ) > self ._history :
259+ self ._values .pop (0 )
260+
261+ def get (self ) -> float :
262+ try :
263+ stddev_val = stdev (self ._values )
264+ mean_val = mean (self ._values )
265+ for val in reversed (self ._values ):
266+ if (val <= mean_val + stddev_val ) and (val >= mean_val - stddev_val ):
267+ return val
268+ except StatisticsError :
269+ return self ._values [- 1 ]
270+ _LOGGER .error ("No match in outlier filter shall never happen!" )
271+ return self ._values [- 1 ]
272+
273+
240274class Measurement :
241275 def __init__ (self , data ) -> None :
242276 flag1 = data [8 ]
@@ -253,6 +287,10 @@ def __init__(self, data) -> None:
253287 unknown2_raw = data [15 :18 ]
254288 self .unknown2 = int .from_bytes (unknown2_raw , "big" )
255289
290+ def add_filtered (self , ph_filt : float , orp_filt : float ) -> None :
291+ self .ph = ph_filt
292+ self .orp = orp_filt
293+
256294 def __str__ (self ) -> str :
257295 return "pH: %s, Orp: %s, In-water: %s, pH-on: %s, Orp-on: %s" % (
258296 self .ph ,
0 commit comments