@@ -401,9 +401,10 @@ async def energy_update(self) -> EnergyStatistics | None: # noqa: PLR0911 PLR09
401401 self ._log_no_energy_stats_update ()
402402 return None
403403
404- # Always request last energy log records at initial startup
404+ # Always request the most recent energy log records at initial startup, check if the current
405+ # address is actually reported by the node even when all slots at that address are empty.
405406 if not self ._last_energy_log_requested :
406- self ._last_energy_log_requested = await self .energy_log_update (
407+ self ._last_energy_log_requested , _ = await self .energy_log_update (
407408 self ._current_log_address , save_cache = False
408409 )
409410
@@ -416,9 +417,9 @@ async def energy_update(self) -> EnergyStatistics | None: # noqa: PLR0911 PLR09
416417 )
417418 return None
418419
419- # Try collecting energy-stats for _current_log_address
420- result = await self .energy_log_update (
421- self ._current_log_address , save_cache = True
420+ # Try collecting energy-stats from _current_log_address
421+ result , slots_empty_cur = await self .energy_log_update (
422+ self ._current_log_address , save_cache = False
422423 )
423424 if not result :
424425 _LOGGER .debug (
@@ -428,18 +429,22 @@ async def energy_update(self) -> EnergyStatistics | None: # noqa: PLR0911 PLR09
428429 )
429430 return None
430431
431- if self ._current_log_address is not None :
432- # Retry with previous log address as Circle node pointer to self._current_log_address
433- # could be rolled over while the last log is at previous address/slot
434- prev_log_address , _ = calc_log_address (self ._current_log_address , 1 , - 4 )
435- result = await self .energy_log_update (prev_log_address , save_cache = True )
436- if not result :
437- _LOGGER .debug (
438- "async_energy_update | %s | Log rollover | energy_log_update from address %s failed" ,
439- self ._mac_in_str ,
440- prev_log_address ,
441- )
442- return None
432+ # Retry with previous log address as Circle node pointer to self._current_log_address
433+ # could be rolled over while the last log is at previous address
434+ prev_log_address , _ = calc_log_address (self ._current_log_address , 1 , - 4 )
435+ result , slots_empty_prev = await self .energy_log_update (
436+ prev_log_address , save_cache = False
437+ )
438+ if not result :
439+ _LOGGER .debug (
440+ "async_energy_update | %s | Log rollover | energy_log_update from address %s failed" ,
441+ self ._mac_in_str ,
442+ prev_log_address ,
443+ )
444+ return None
445+
446+ if self ._cache_enabled and (not slots_empty_cur or not slots_empty_prev ):
447+ await self .save_cache ()
443448
444449 if (
445450 missing_addresses := self ._energy_counters .log_addresses_missing
@@ -453,7 +458,7 @@ async def energy_update(self) -> EnergyStatistics | None: # noqa: PLR0911 PLR09
453458 return self ._energy_counters .energy_statistics
454459
455460 if len (missing_addresses ) == 1 :
456- result = await self .energy_log_update (
461+ result , _ = await self .energy_log_update (
457462 missing_addresses [0 ], save_cache = True
458463 )
459464 if result :
@@ -515,9 +520,12 @@ async def _get_initial_energy_logs(self) -> None:
515520 max_addresses_to_collect , ceil (datetime .now (tz = UTC ).hour / factor ) + 1
516521 )
517522 log_address = self ._current_log_address
523+ any_updates = False
518524 while total_addresses > 0 :
519- result = await self .energy_log_update (log_address , save_cache = False )
520- if not result :
525+ result , slots_empty = await self .energy_log_update (
526+ log_address , save_cache = False
527+ )
528+ if result and slots_empty :
521529 # Stop initial log collection when an address contains no (None) or outdated data
522530 # Outdated data can indicate a EnergyLog address rollover: from address 6014 to 0
523531 _LOGGER .debug (
@@ -526,11 +534,12 @@ async def _get_initial_energy_logs(self) -> None:
526534 )
527535 break
528536
537+ any_updates |= not slots_empty
529538 log_address , _ = calc_log_address (log_address , 1 , - 4 )
530539 total_addresses -= 1
531540
532- if self ._cache_enabled :
533- await self ._energy_log_records_save_to_cache ()
541+ if self ._cache_enabled and any_updates :
542+ await self .save_cache ()
534543
535544 async def get_missing_energy_logs (self ) -> None :
536545 """Task to retrieve missing energy logs."""
@@ -554,8 +563,9 @@ async def get_missing_energy_logs(self) -> None:
554563 create_task (self .energy_log_update (address , save_cache = False ))
555564 for address in missing_addresses
556565 ]
566+ any_updates = False
557567 for idx , task in enumerate (tasks ):
558- result = await task
568+ result , slots_empty = await task
559569 # When an energy log collection task returns False, stop and cancel the remaining tasks
560570 if not result :
561571 to_cancel = tasks [idx + 1 :]
@@ -565,15 +575,25 @@ async def get_missing_energy_logs(self) -> None:
565575 await gather (* to_cancel , return_exceptions = True )
566576 break
567577
568- if self ._cache_enabled :
569- await self ._energy_log_records_save_to_cache ()
578+ any_updates |= not slots_empty
579+
580+ if self ._cache_enabled and any_updates :
581+ await self .save_cache ()
570582
571583 async def energy_log_update (
572584 self , address : int | None , save_cache : bool = True
573- ) -> bool :
574- """Request energy logs and return True only when at least one recent, non-empty record was stored; otherwise return False."""
585+ ) -> tuple [bool , bool ]:
586+ """Request energy logs from node and store them.
587+
588+ First bool: True when processing succeeded (records stored in memory, possibly all-empty);
589+ False only on transport or address errors.
590+ Second bool: slots_empty — True when all four slots at the address are empty or outdated;
591+ False when at least one recent, non-empty record was stored.
592+ """
593+ result = False
594+ slots_empty = True
575595 if address is None :
576- return False
596+ return result , slots_empty
577597
578598 _LOGGER .debug (
579599 "Requesting EnergyLogs from node %s address %s" ,
@@ -586,8 +606,9 @@ async def energy_log_update(
586606 "Retrieving EnergyLogs data from node %s failed" ,
587607 self ._mac_in_str ,
588608 )
589- return False
609+ return result , slots_empty
590610
611+ result = True
591612 _LOGGER .debug ("EnergyLogs from node %s, address=%s:" , self ._mac_in_str , address )
592613 await self ._available_update_state (True , response .timestamp )
593614
@@ -600,31 +621,31 @@ async def energy_log_update(
600621 _LOGGER .debug (
601622 "In slot=%s: pulses=%s, timestamp=%s" , _slot , log_pulses , log_timestamp
602623 )
603- if (
604- log_timestamp is None
605- or log_pulses is None
606- # Don't store an old log record; store an empty record instead
607- or not self ._check_timestamp_is_recent (address , _slot , log_timestamp )
608- ):
609- self ._energy_counters .add_empty_log (response .log_address , _slot )
610- continue
611-
612- cache_updated = await self ._energy_log_record_update_state (
613- response .log_address ,
614- _slot ,
615- log_timestamp .replace (tzinfo = UTC ),
616- log_pulses ,
617- import_only = True ,
618- )
624+ address = response .log_address
625+ if log_timestamp is None or log_pulses is None :
626+ self ._energy_counters .add_empty_log (address , _slot )
627+ else :
628+ log_timestamp = log_timestamp .replace (tzinfo = UTC )
629+ if self ._check_timestamp_is_recent (address , _slot , log_timestamp ):
630+ self ._energy_counters .add_pulse_log (
631+ address ,
632+ _slot ,
633+ log_timestamp ,
634+ log_pulses ,
635+ import_only = True ,
636+ )
637+ cache_updated = True
619638
620639 self ._energy_counters .update ()
621- if cache_updated and save_cache :
622- _LOGGER .debug (
623- "Saving energy record update to cache for %s" , self ._mac_in_str
624- )
625- await self .save_cache ()
640+ if cache_updated :
641+ slots_empty = False
642+ await self ._energy_log_records_save_to_cache ()
643+ if save_cache :
644+ _LOGGER .debug ("Saving energy cache for %s" , self ._mac_in_str )
645+ await self .save_cache ()
646+ return result , slots_empty
626647
627- return True
648+ return result , slots_empty
628649
629650 def _check_timestamp_is_recent (
630651 self , address : int , slot : int , timestamp : datetime
@@ -695,7 +716,7 @@ async def _energy_log_records_load_from_cache(self) -> bool:
695716 return True
696717
697718 async def _energy_log_records_save_to_cache (self ) -> None :
698- """Save currently collected energy logs to cached file."""
719+ """Update the in-memory energy log cache string (no file I/O) ."""
699720 if not self ._cache_enabled :
700721 return
701722
@@ -711,60 +732,8 @@ async def _energy_log_records_save_to_cache(self) -> None:
711732 f"{ address } :{ slot } :{ ts .strftime ('%Y-%m-%d-%H-%M-%S' )} :{ log .pulses } "
712733 )
713734 cached_logs = "|" .join (records )
714- _LOGGER .debug ("Saving energy logrecords to cache for %s" , self ._mac_in_str )
735+ _LOGGER .debug ("Updating in-memory energy log records for %s" , self ._mac_in_str )
715736 self ._set_cache (CACHE_ENERGY_COLLECTION , cached_logs )
716- # Persist new cache entries to disk immediately
717- await self .save_cache (trigger_only = True )
718-
719- async def _energy_log_record_update_state (
720- self ,
721- address : int ,
722- slot : int ,
723- timestamp : datetime ,
724- pulses : int ,
725- import_only : bool = False ,
726- ) -> bool :
727- """Process new energy log record. Returns true if record is new or changed."""
728- self ._energy_counters .add_pulse_log (
729- address , slot , timestamp , pulses , import_only = import_only
730- )
731- if not self ._cache_enabled :
732- return False
733-
734- log_cache_record = (
735- f"{ address } :{ slot } :{ timestamp .strftime ('%Y-%m-%d-%H-%M-%S' )} :{ pulses } "
736- )
737- if (cached_logs := self ._get_cache (CACHE_ENERGY_COLLECTION )) is not None :
738- entries = cached_logs .split ("|" ) if cached_logs else []
739- if log_cache_record not in entries :
740- _LOGGER .debug (
741- "Adding logrecord (%s, %s) to cache of %s" ,
742- str (address ),
743- str (slot ),
744- self ._mac_in_str ,
745- )
746- new_cache = (
747- f"{ log_cache_record } |{ cached_logs } "
748- if cached_logs
749- else log_cache_record
750- )
751- self ._set_cache (CACHE_ENERGY_COLLECTION , new_cache )
752- await self .save_cache (trigger_only = True )
753- return True
754-
755- _LOGGER .debug (
756- "Energy logrecord already present for %s, ignoring" , self ._mac_in_str
757- )
758- return False
759-
760- _LOGGER .debug (
761- "Cache is empty, adding new logrecord (%s, %s) for %s" ,
762- str (address ),
763- str (slot ),
764- self ._mac_in_str ,
765- )
766- self ._set_cache (CACHE_ENERGY_COLLECTION , log_cache_record )
767- return True
768737
769738 @raise_not_loaded
770739 async def set_relay (self , state : bool ) -> bool :
@@ -1187,7 +1156,7 @@ async def _relay_init_update_state(self, state: bool) -> None:
11871156 NodeFeature .RELAY_INIT , self ._relay_config
11881157 )
11891158 _LOGGER .debug (
1190- "Saving relay_init state update to cachefor %s" , self ._mac_in_str
1159+ "Saving relay_init state update to cache for %s" , self ._mac_in_str
11911160 )
11921161 await self .save_cache ()
11931162
0 commit comments