From 048e16c241cecf9cf074e29ec4fa6d6bba764add Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Apr 2024 17:39:11 -0500 Subject: [PATCH] perf: only write state when data changes (#954) * perf: avoid writing state when the controller did not actually update #948 made me realize that the car data was being polled far less frequently than I thought as the underlying library was caching, but every time the coordinator fired, it would still callback all the listeners and write the state of all the entities which meant we ended up writing state every 10 seconds even if nothing has changed. Keep track of when the controller was last updated in each entity and if it has not changed, we skip the state write. This reduced the number of calls to `async_write_ha_state` by 62% on my production HA instance! * fix async_added_to_hass was being overridden and forgot to remove in pr --- custom_components/tesla_custom/__init__.py | 6 ++++- custom_components/tesla_custom/base.py | 28 +++++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/custom_components/tesla_custom/__init__.py b/custom_components/tesla_custom/__init__.py index c698f11f..8765f258 100644 --- a/custom_components/tesla_custom/__init__.py +++ b/custom_components/tesla_custom/__init__.py @@ -427,6 +427,7 @@ def __init__( self.update_vehicles = update_vehicles self._debounce_task = None self._last_update_time = None + self.last_controller_update_time: float | None = None self.assumed_state = True update_interval = timedelta(seconds=MIN_SCAN_INTERVAL) @@ -477,8 +478,11 @@ async def _async_update_data(self): raise UpdateFailed(f"Error communicating with API: {err}") from err else: if vin := self.vin: + self.last_controller_update_time = controller.get_last_update_time( + vin=vin + ) self.assumed_state = not controller.is_car_online(vin=vin) and ( - controller.get_last_update_time(vin=vin) + self.last_controller_update_time - controller.get_last_wake_up_time(vin=vin) > controller.update_interval ) diff --git a/custom_components/tesla_custom/base.py b/custom_components/tesla_custom/base.py index d2569f63..daa77e03 100644 --- a/custom_components/tesla_custom/base.py +++ b/custom_components/tesla_custom/base.py @@ -1,5 +1,6 @@ """Support for Tesla cars and energy sites.""" +from homeassistant.core import callback from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util import slugify @@ -29,12 +30,6 @@ def __init__( self._attr_name = self.type.capitalize() self._attr_entity_registry_enabled_default = self._enabled_by_default - async def async_added_to_hass(self) -> None: - """Register state update callback.""" - self.async_on_remove( - self.coordinator.async_add_listener(self.async_write_ha_state) - ) - class TeslaCarEntity(TeslaBaseEntity): """Representation of a Tesla car device.""" @@ -61,6 +56,27 @@ def __init__( model=car.car_type, sw_version=car.car_version, ) + self._last_update_success: bool | None = None + self._last_controller_update_time: float | None = None + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + prev_last_update_success = self._last_update_success + prev_last_controller_update_time = self._last_controller_update_time + coordinator = self.coordinator + current_last_update_success = coordinator.last_update_success + current_last_controller_update_time = coordinator.last_controller_update_time + self._last_update_success = current_last_update_success + self._last_controller_update_time = current_last_controller_update_time + if ( + prev_last_update_success == current_last_update_success + and prev_last_controller_update_time == current_last_controller_update_time + ): + # If there was no change in the last update success or time, + # avoid writing state to prevent unnecessary entity updates. + return + super()._handle_coordinator_update() async def update_controller( self, *, wake_if_asleep: bool = False, force: bool = True, blocking: bool = True