From 435c62ae8e4f61b508349285fa571ecb293cf56e Mon Sep 17 00:00:00 2001 From: Ashley Gittins Date: Thu, 14 Nov 2024 04:48:12 +1100 Subject: [PATCH] Fix: Global sensor update rate and refpower calibration update (#383) * fix: Slowed global diag entity updates fixes [Rate-limit] Global visible device count [and others] #377 - Added _cached_ratelimit to BermudaGlobalEntity and applied to sensors - Added ref_power_changed stamp and tied between ref_power changes and cache invalidation in entities, so calibration that causes increased distances show up immediately. - allowed specifying custom interval to main _cached_rateliimit as well as global. --- custom_components/bermuda/bermuda_device.py | 4 ++++ custom_components/bermuda/entity.py | 26 +++++++++++++++++++-- custom_components/bermuda/sensor.py | 8 +++---- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/custom_components/bermuda/bermuda_device.py b/custom_components/bermuda/bermuda_device.py index 836b9a1..c54f4cd 100644 --- a/custom_components/bermuda/bermuda_device.py +++ b/custom_components/bermuda/bermuda_device.py @@ -55,6 +55,7 @@ def __init__(self, address, options) -> None: self.prefname: str | None = None # "preferred" name - ideally local_name self.address: str = address self.ref_power: float = 0 # If non-zero, use in place of global ref_power. + self.ref_power_changed: float = 0 # Stamp for last change to ref_power, for cache zapping. self.options = options self.unique_id: str | None = None # mac address formatted. self.address_type = BDADDR_TYPE_UNKNOWN @@ -152,6 +153,9 @@ def set_ref_power(self, new_ref_power: float): # gets applied. # if nearest_scanner is not None: self.apply_scanner_selection(nearest_scanner) + # Update the stamp so that the BermudaEntity can clear the cache and show the + # new measurement(s) immediately. + self.ref_power_changed = MONOTONIC_TIME() def apply_scanner_selection(self, closest_scanner: BermudaDeviceScanner | None): """ diff --git a/custom_components/bermuda/entity.py b/custom_components/bermuda/entity.py index cc5e98f..42558b6 100644 --- a/custom_components/bermuda/entity.py +++ b/custom_components/bermuda/entity.py @@ -52,16 +52,22 @@ def __init__( self.bermuda_last_state: Any = 0 self.bermuda_last_stamp: float = 0 - def _cached_ratelimit(self, statevalue: Any, fast_falling=True, fast_rising=False): + def _cached_ratelimit(self, statevalue: Any, fast_falling=True, fast_rising=False, interval=None): """ Uses the CONF_UPDATE_INTERVAL and other logic to return either the given statevalue or an older, cached value. Helps to reduce excess sensor churn without compromising latency. - Only suitable for MEASUREMENTS, as numerical comparison is used. + Mostly suitable for MEASUREMENTS, but should work with strings, too. + If interval is specified the cache will use that (in seconds), otherwise the deafult is + the CONF_UPPDATE_INTERVAL (typically suitable for fast-close slow-far sensors) """ + if interval is not None: + self.bermuda_update_interval = interval + nowstamp = MONOTONIC_TIME() if ( (self.bermuda_last_stamp < nowstamp - self.bermuda_update_interval) # Cache is stale + or (self._device.ref_power_changed > nowstamp + 2) # ref power changed in last 2sec or (self.bermuda_last_state is None) # Nothing compares to you. or (statevalue is None) # or you. or (fast_falling and statevalue < self.bermuda_last_state) # (like Distance) @@ -165,6 +171,9 @@ def __init__( super().__init__(coordinator) self.coordinator = coordinator self.config_entry = config_entry + self._cache_ratelimit_value = None + self._cache_ratelimit_stamp: float = 0 + self._cache_ratelimit_interval = 60 @callback def _handle_coordinator_update(self) -> None: @@ -175,6 +184,19 @@ def _handle_coordinator_update(self) -> None: """ self.async_write_ha_state() + def _cached_ratelimit(self, statevalue: Any, interval:int|None=None): + """A simple way to rate-limit sensor updates.""" + if interval is not None: + self._cache_ratelimit_interval = interval + nowstamp = MONOTONIC_TIME() + + if nowstamp > self._cache_ratelimit_stamp + self._cache_ratelimit_interval: + self._cache_ratelimit_stamp = nowstamp + self._cache_ratelimit_value = statevalue + return statevalue + else: + return self._cache_ratelimit_value + @property def device_info(self): """Implementing this creates an entry in the device registry.""" diff --git a/custom_components/bermuda/sensor.py b/custom_components/bermuda/sensor.py index 56a4120..95095f3 100644 --- a/custom_components/bermuda/sensor.py +++ b/custom_components/bermuda/sensor.py @@ -351,7 +351,7 @@ def unique_id(self): @property def native_value(self) -> int: """Gets the number of proxies we have access to.""" - return len(self.coordinator.scanner_list) + return self._cached_ratelimit(len(self.coordinator.scanner_list)) or 0 @property def name(self): @@ -375,7 +375,7 @@ def unique_id(self): @property def native_value(self) -> int: """Gets the number of proxies we have access to.""" - return self.coordinator.count_active_scanners() + return self._cached_ratelimit(self.coordinator.count_active_scanners()) or 0 @property def name(self): @@ -399,7 +399,7 @@ def unique_id(self): @property def native_value(self) -> int: """Gets the amount of devices we have seen.""" - return len(self.coordinator.devices) + return self._cached_ratelimit(len(self.coordinator.devices)) or 0 @property def name(self): @@ -423,7 +423,7 @@ def unique_id(self): @property def native_value(self) -> int: """Gets the amount of devices that are active.""" - return self.coordinator.count_active_devices() + return self._cached_ratelimit(self.coordinator.count_active_devices()) or 0 @property def name(self):