1818)
1919from ..connection import StickController
2020from ..exceptions import MessageError , NodeError
21- from ..messages .requests import SenseConfigureHysteresisRequest
21+ from ..messages .requests import (
22+ SenseConfigureHysteresisRequest ,
23+ SenseReportIntervalRequest ,
24+ )
2225from ..messages .responses import (
2326 NODE_SWITCH_GROUP_ID ,
2427 SENSE_REPORT_ID ,
5962CACHE_SENSE_HYSTERESIS_TEMPERATURE_UPPER_BOUND = "temperature_upper_bound"
6063CACHE_SENSE_HYSTERESIS_TEMPERATURE_LOWER_BOUND = "temperature_lower_bound"
6164CACHE_SENSE_HYSTERESIS_TEMPERATURE_DIRECTION = "temperature_direction"
65+ CACHE_SENSE_HYSTERESIS_REPORT_INTERVAL = "report_interval"
6266CACHE_SENSE_HYSTERESIS_CONFIG_DIRTY = "sense_hysteresis_config_dirty"
6367
6468DEFAULT_SENSE_HYSTERESIS_HUMIDITY_ENABLED : Final = False
65- DEFAULT_SENSE_HYSTERESIS_HUMIDITY_UPPER_BOUND : Final = 24.0
66- DEFAULT_SENSE_HYSTERESIS_HUMIDITY_LOWER_BOUND : Final = 24.0
67- DEFAULT_SENSE_HYSTERESIS_HUMIDITY_DIRECTION : Final = True
68- DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_ENABLED : Final = False
69- DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_UPPER_BOUND : Final = 50.0
70- DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_LOWER_BOUND : Final = 50.0
71- DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_DIRECTION : Final = True
69+ DEFAULT_SENSE_HYSTERESIS_HUMIDITY_UPPER_BOUND : Final [float ] = 24.0
70+ DEFAULT_SENSE_HYSTERESIS_HUMIDITY_LOWER_BOUND : Final [float ] = 24.0
71+ DEFAULT_SENSE_HYSTERESIS_HUMIDITY_DIRECTION : Final [bool ] = True
72+ DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_ENABLED : Final [bool ] = False
73+ DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_UPPER_BOUND : Final [float ] = 50.0
74+ DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_LOWER_BOUND : Final [float ] = 50.0
75+ DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_DIRECTION : Final [bool ] = True
76+ DEFAULT_SENSE_HYSTERESIS_REPORT_INTERVAL : Final [int ] = 15
7277
7378
7479class PlugwiseSense (NodeSED ):
@@ -175,6 +180,9 @@ async def _load_from_cache(self) -> bool:
175180 if (temperature_direction := self ._temperature_direction_from_cache ()) is None :
176181 dirty = True
177182 temperature_direction = DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_DIRECTION
183+ if (report_interval := self ._report_interval_from_cache ()) is None :
184+ dirty = True
185+ report_interval = DEFAULT_SENSE_HYSTERESIS_REPORT_INTERVAL
178186 dirty |= self ._sense_hysteresis_config_dirty_from_cache ()
179187
180188 self ._hysteresis_config = SenseHysteresisConfig (
@@ -186,6 +194,7 @@ async def _load_from_cache(self) -> bool:
186194 temperature_upper_bound = temperature_upper_bound ,
187195 temperature_lower_bound = temperature_lower_bound ,
188196 temperature_direction = temperature_direction ,
197+ report_interval = report_interval ,
189198 dirty = dirty ,
190199 )
191200 if dirty :
@@ -248,6 +257,14 @@ def _temperature_direction_from_cache(self) -> bool | None:
248257 """Load Temperature hysteresis switch direction from cache."""
249258 return self ._get_cache_as_bool (CACHE_SENSE_HYSTERESIS_TEMPERATURE_DIRECTION )
250259
260+ def _report_interval_from_cache (self ) -> int | None :
261+ """Load report interval from cache."""
262+ if (
263+ report_interval := self ._get_cache (CACHE_SENSE_HYSTERESIS_REPORT_INTERVAL )
264+ ) is not None :
265+ return int (report_interval )
266+ return None
267+
251268 def _sense_hysteresis_config_dirty_from_cache (self ) -> bool :
252269 """Load sense hysteresis dirty from cache."""
253270 if (
@@ -278,6 +295,7 @@ def hysteresis_config(self) -> SenseHysteresisConfig:
278295 temperature_upper_bound = self .temperature_upper_bound ,
279296 temperature_lower_bound = self .temperature_lower_bound ,
280297 temperature_direction = self .temperature_direction ,
298+ report_interval = self .report_interval ,
281299 dirty = self .hysteresis_config_dirty ,
282300 )
283301
@@ -337,6 +355,13 @@ def temperature_direction(self) -> bool:
337355 return self ._hysteresis_config .temperature_direction
338356 return DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_DIRECTION
339357
358+ @property
359+ def report_interval (self ) -> int :
360+ """Sense report interval in minutes."""
361+ if self ._hysteresis_config .report_interval is not None :
362+ return self ._hysteresis_config .report_interval
363+ return DEFAULT_SENSE_HYSTERESIS_REPORT_INTERVAL
364+
340365 @property
341366 def hysteresis_config_dirty (self ) -> bool :
342367 """Sense hysteresis configuration dirty flag."""
@@ -537,6 +562,31 @@ async def set_hysteresis_temperature_lower_bound(self, lower_bound: float) -> bo
537562 await self ._sense_configure_update ()
538563 return True
539564
565+ async def set_report_interval (self , report_interval : int ) -> bool :
566+ """Configure Sense measurement interval.
567+
568+ Configuration request will be queued and will be applied the next time when node is awake for maintenance.
569+ """
570+ _LOGGER .debug (
571+ "set_report_interval | Device %s | %s -> %s" ,
572+ self .name ,
573+ self ._hysteresis_config .report_interval ,
574+ report_interval ,
575+ )
576+ if report_interval < 1 or report_interval > 60 :
577+ raise ValueError (
578+ f"Invalid measurement interval { report_interval } . It must be between 1 and 60 minutes"
579+ )
580+ if self ._hysteresis_config .report_interval == report_interval :
581+ return False
582+ self ._hysteresis_config = replace (
583+ self ._hysteresis_config ,
584+ report_interval = report_interval ,
585+ dirty = True ,
586+ )
587+ await self ._sense_configure_update ()
588+ return True
589+
540590 async def set_hysteresis_temperature_direction (self , state : bool ) -> bool :
541591 """Configure temperature hysteresis to switch on or off on increasing or decreasing direction.
542592
@@ -637,6 +687,7 @@ async def _run_awake_tasks(self) -> None:
637687 configure_result = await gather (
638688 self ._configure_sense_humidity_task (),
639689 self ._configure_sense_temperature_task (),
690+ self ._configure_sense_report_interval_task (),
640691 )
641692 if all (configure_result ):
642693 self ._hysteresis_config = replace (self ._hysteresis_config , dirty = False )
@@ -645,10 +696,11 @@ async def _run_awake_tasks(self) -> None:
645696 else :
646697 _LOGGER .warning (
647698 "Sense hysteresis configuration partially failed for %s "
648- "(humidity=%s, temperature=%s); will retry on next wake." ,
699+ "(humidity=%s, temperature=%s, report_interval=%s ); will retry on next wake." ,
649700 self .name ,
650701 configure_result [0 ],
651702 configure_result [1 ],
703+ configure_result [2 ],
652704 )
653705 await self .publish_feature_update_to_subscribers (
654706 NodeFeature .SENSE_HYSTERESIS ,
@@ -686,27 +738,18 @@ async def _configure_sense_humidity_task(self) -> bool:
686738 self .humidity_direction ,
687739 )
688740 if (response := await request .send ()) is None :
689- _LOGGER .warning (
690- "No response from %s to configure humidity hysteresis settings request" ,
691- self .name ,
692- )
741+ self ._log_configure_failed ("humidity hysteresis" )
693742 return False
694743 if response .node_ack_type == NodeAckResponseType .SENSE_BOUNDARIES_FAILED :
695744 _LOGGER .warning (
696745 "Failed to configure humidity hysteresis settings for %s" , self .name
697746 )
698747 return False
699748 if response .node_ack_type == NodeAckResponseType .SENSE_BOUNDARIES_ACCEPTED :
700- _LOGGER .debug (
701- "Successful configure humidity hysteresis settings for %s" , self .name
702- )
749+ self ._log_configure_success ("humidity hysteresis" )
703750 return True
704751
705- _LOGGER .warning (
706- "Unexpected response ack type %s for %s" ,
707- response .node_ack_type ,
708- self .name ,
709- )
752+ self ._log_unexpected_response_ack (response .node_ack_type )
710753 return False
711754
712755 async def _configure_sense_temperature_task (self ) -> bool :
@@ -746,22 +789,55 @@ async def _configure_sense_temperature_task(self) -> bool:
746789 )
747790 return False
748791 if response .node_ack_type == NodeAckResponseType .SENSE_BOUNDARIES_FAILED :
749- _LOGGER .warning (
750- "Failed to configure temperature hysteresis settings for %s" , self .name
751- )
792+ self ._log_configure_failed ("temperature hysteresis" )
752793 return False
753794 if response .node_ack_type == NodeAckResponseType .SENSE_BOUNDARIES_ACCEPTED :
754- _LOGGER .debug (
755- "Successful configure temperature hysteresis settings for %s" , self .name
795+ self ._log_configure_success ("temperature hysteresis" )
796+ return True
797+
798+ self ._log_unexpected_response_ack (response .node_ack_type )
799+ return False
800+
801+ async def _configure_sense_report_interval_task (self ) -> bool :
802+ """Configure Sense report interval setting. Returns True if successful."""
803+ if not self ._hysteresis_config .dirty :
804+ return True
805+ request = SenseReportIntervalRequest (
806+ self ._send ,
807+ self ._mac_in_bytes ,
808+ self .report_interval ,
809+ )
810+ if (response := await request .send ()) is None :
811+ _LOGGER .warning (
812+ "No response from %s to configure report interval." ,
813+ self .name ,
756814 )
815+ return False
816+ if response .node_ack_type == NodeAckResponseType .SENSE_INTERVAL_FAILED :
817+ self ._log_configure_failed ("report interval" )
818+ return False
819+ if response .node_ack_type == NodeAckResponseType .SENSE_INTERVAL_ACCEPTED :
820+ self ._log_configure_success ("report interval" )
757821 return True
758822
823+ self ._log_unexpected_response_ack (response .node_ack_type )
824+ return False
825+
826+ def _log_unexpected_response_ack (self , response : NodeAckResponseType ) -> None :
827+ """Log unexpected response."""
759828 _LOGGER .warning (
760829 "Unexpected response ack type %s for %s" ,
761- response .node_ack_type ,
830+ response .name ,
762831 self .name ,
763832 )
764- return False
833+
834+ def _log_configure_failed (self , parameter : str ) -> None :
835+ """Log configuration failed."""
836+ _LOGGER .warning ("Failed to configure %s for %s" , parameter , self .name )
837+
838+ def _log_configure_success (self , parameter : str ) -> None :
839+ """Log configuration success."""
840+ _LOGGER .debug ("Successful configure %s for %s" , parameter , self .name )
765841
766842 async def _sense_configure_update (self ) -> None :
767843 """Push sense configuration update to cache."""
@@ -787,6 +863,7 @@ async def _sense_configure_update(self) -> None:
787863 self ._set_cache (
788864 CACHE_SENSE_HYSTERESIS_TEMPERATURE_DIRECTION , self .temperature_direction
789865 )
866+ self ._set_cache (CACHE_SENSE_HYSTERESIS_REPORT_INTERVAL , self .report_interval )
790867 self ._set_cache (
791868 CACHE_SENSE_HYSTERESIS_CONFIG_DIRTY , self .hysteresis_config_dirty
792869 )
0 commit comments