11import logging
2+ from collections import deque
23from datetime import datetime
3- from typing import Optional , Iterable
4+ from typing import Optional , Iterable , List , Dict
45
56from meross_iot .controller .device import GenericSubDevice
67from meross_iot .model .enums import OnlineStatus , ThermostatV3Mode
@@ -463,22 +464,51 @@ class Ms405Sensor(GenericSubDevice):
463464 Class that represents a Meross MS400 Smart Water Leak Sensor.
464465 """
465466
466- def __init__ (self , hubdevice_uuid : str , subdevice_id : str , manager , ** kwargs ):
467+ def __init__ (self , hubdevice_uuid : str , subdevice_id : str , manager , max_events_queue_len = 30 , ** kwargs ):
467468 super ().__init__ (hubdevice_uuid , subdevice_id , manager , ** kwargs )
468- self .__water_leak = {}
469- self ._last_active_time = None
469+
470+ self ._last_active_time : Optional [int ] = None
471+ # Represents the last time we contacted the device
472+
473+ self .__water_leak_state : Optional [bool ] = None
474+ # Represents the current state
475+
476+ self .__last_event_ts : Optional [int ] = None
477+ # Represents the timestamp of the last sample (current state sampling)
478+
479+ self .__cached_events : deque = deque (maxlen = max_events_queue_len )
480+ # Last N samples we collected
481+
482+ self .__last_waterleak_event_ts : Optional [int ] = None
483+ # Last timestamp we've seen a leak
470484
471485 @property
472486 def is_leaking (self ) -> Optional [bool ]:
473487 """
474- Returns True if a water leak is detected (1), False otherwise (0).
475- Returns None if the state has not been fetched yet.
488+ Returns the latest updated state available for the water leak sensor, if available.
476489 """
477- cur_val = self .__water_leak .get ("latestWaterLeak" )
478- if cur_val is None :
479- return None
480- else :
481- return cur_val == 1
490+ return self .__water_leak_state
491+
492+ @property
493+ def latest_sample_time (self ) -> Optional [int ]:
494+ """
495+ Returns the timestamp (GMT) of the latest available sampling.
496+ """
497+ return self .__last_event_ts
498+
499+ @property
500+ def latest_detected_water_leak_ts (self ) -> Optional [int ]:
501+ """
502+ Return the timestamp (GMT) of the latest time the sensor sampled a water leak.
503+ """
504+ return self .__last_waterleak_event_ts
505+
506+ @property
507+ def get_last_events (self ) -> List [Dict ]:
508+ """
509+ Returns the last cached items
510+ """
511+ return [x for x in self .__cached_events ]
482512
483513 async def async_update (self ,
484514 timeout : Optional [float ] = None ,
@@ -503,6 +533,26 @@ async def async_update(self,
503533 await self .async_handle_subdevice_notification (namespace = Namespace .HUB_SENSOR_ALL , data = subdev_state )
504534 break
505535
536+ def _handle_water_leak_fresh_data (self , leaking : bool , timestamp : int ):
537+ # If handling an event with an older timestamp than the one we have, just discard it.
538+ if self .latest_sample_time is not None and timestamp <= self .latest_sample_time :
539+ return
540+
541+ # If this is the first update or if it's more recent than the last we have, update the current state.
542+ if self .__last_event_ts is None or timestamp >= self .__last_event_ts :
543+ self .__last_event_ts = timestamp
544+ self .__water_leak_state = leaking
545+
546+ # If the event is a leak and is more recent than the latest leak event, update it.
547+ if leaking and (self .__last_waterleak_event_ts is None or timestamp >= self .__last_waterleak_event_ts ):
548+ self .__last_waterleak_event_ts = timestamp
549+
550+ # In any case, register the event in the queue
551+ self .__cached_events .append ({
552+ "leaking" : leaking ,
553+ "timestamp" : timestamp
554+ })
555+
506556 async def async_handle_push_notification (self , namespace : Namespace , data : dict ) -> bool :
507557 locally_handled = False
508558 if namespace == Namespace .HUB_ONLINE :
@@ -511,7 +561,10 @@ async def async_handle_push_notification(self, namespace: Namespace, data: dict)
511561 self ._online = OnlineStatus (update_element .get ('status' , - 1 ))
512562 locally_handled = True
513563 elif namespace == Namespace .HUB_SENSOR_WATERLEAK :
514- self .__water_leak = data .get ('waterLeak' )
564+ water_leak_state = data .get ('waterLeak' )
565+ latestWaterLeak = water_leak_state .get ('latestWaterLeak' )
566+ latestSampleTime = water_leak_state .get ('latestSampleTime' )
567+ self ._handle_water_leak_fresh_data (leaking = latestWaterLeak == 1 , timestamp = latestSampleTime )
515568 locally_handled = True
516569
517570 return locally_handled
@@ -523,15 +576,17 @@ async def async_handle_subdevice_notification(self, namespace: Namespace, data:
523576 self ._last_active_time = data .get ('online' , {}).get ('lastActiveTime' )
524577 elif namespace == Namespace .HUB_SENSOR_WATERLEAK :
525578 latestWaterLeak = data .get ('latestWaterLeak' )
526- latestSaampleTime = data .get ('latestSampleTime' )
527- if latestWaterLeak is not None :
528- self .__water_leak ["latestWaterLeak" ]= latestWaterLeak
529- if latestSaampleTime is not None :
530- self .__water_leak ["latestSaampleTime" ] = latestSaampleTime
579+ latestSampleTime = data .get ('latestSampleTime' )
580+ self ._handle_water_leak_fresh_data (leaking = latestWaterLeak == 1 , timestamp = latestSampleTime )
531581 locally_handled = True
532582 elif namespace == Namespace .HUB_SENSOR_ALL :
533583 self ._online = OnlineStatus (data .get ('online' , {}).get ('status' , - 1 ))
534- self .__water_leak .update (data .get ('waterLeak' , {}))
584+ water_leak_state = data .get ('waterLeak' )
585+ if water_leak_state is not None :
586+ latestWaterLeak = water_leak_state .get ('latestWaterLeak' )
587+ latestSampleTime = water_leak_state .get ('latestSampleTime' )
588+ self ._handle_water_leak_fresh_data (leaking = latestWaterLeak == 1 , timestamp = latestSampleTime )
589+
535590 locally_handled = True
536591 else :
537592 _LOGGER .warning (f"Could not handle event %s in subdevice %s handler" , namespace , self .name )
0 commit comments