Reject unifi uptime sensor updates if time delta is small (#108464)

* Reject unifi uptime sensor updates if time delta is small

* Revise uptime change threshold tuning

* Use StateType helper

* Treat missing or zero uptime as None (unknown)
This commit is contained in:
Stephen Eisenhauer 2024-01-29 11:30:07 -08:00 committed by GitHub
parent 80bfd4cef7
commit a9fe63ed90
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -7,7 +7,8 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime, timedelta
from datetime import date, datetime, timedelta
from decimal import Decimal
from typing import Generic
from aiounifi.interfaces.api_handlers import ItemEvent
@ -35,6 +36,7 @@ from homeassistant.const import EntityCategory, UnitOfDataRate, UnitOfPower
from homeassistant.core import Event as core_Event, HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
import homeassistant.util.dt as dt_util
from .const import DEVICE_STATES
@ -110,11 +112,22 @@ def async_wlan_client_value_fn(controller: UniFiController, wlan: Wlan) -> int:
@callback
def async_device_uptime_value_fn(
controller: UniFiController, device: Device
) -> datetime:
"""Calculate the uptime of the device."""
return (dt_util.now() - timedelta(seconds=device.uptime)).replace(
second=0, microsecond=0
)
) -> datetime | None:
"""Calculate the approximate time the device started (based on uptime returned from API, in seconds)."""
if device.uptime <= 0:
# Library defaults to 0 if uptime is not provided, e.g. when offline
return None
return (dt_util.now() - timedelta(seconds=device.uptime)).replace(microsecond=0)
@callback
def async_device_uptime_value_changed_fn(
old: StateType | date | datetime | Decimal, new: datetime | float | str | None
) -> bool:
"""Reject the new uptime value if it's too similar to the old one. Avoids unwanted fluctuation."""
if isinstance(old, datetime) and isinstance(new, datetime):
return new != old and abs((new - old).total_seconds()) > 120
return old is None or (new != old)
@callback
@ -169,6 +182,11 @@ class UnifiSensorEntityDescription(
"""Class describing UniFi sensor entity."""
is_connected_fn: Callable[[UniFiController, str], bool] | None = None
# Custom function to determine whether a state change should be recorded
value_changed_fn: Callable[
[StateType | date | datetime | Decimal, datetime | float | str | None],
bool,
] = lambda old, new: old != new
ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
@ -349,6 +367,7 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
supported_fn=lambda controller, obj_id: True,
unique_id_fn=lambda controller, obj_id: f"device_uptime-{obj_id}",
value_fn=async_device_uptime_value_fn,
value_changed_fn=async_device_uptime_value_changed_fn,
),
UnifiSensorEntityDescription[Devices, Device](
key="Device temperature",
@ -425,7 +444,10 @@ class UnifiSensorEntity(UnifiEntity[HandlerT, ApiItemT], SensorEntity):
"""
description = self.entity_description
obj = description.object_fn(self.controller.api, self._obj_id)
if (value := description.value_fn(self.controller, obj)) != self.native_value:
# Update the value only if value is considered to have changed relative to its previous state
if description.value_changed_fn(
self.native_value, (value := description.value_fn(self.controller, obj))
):
self._attr_native_value = value
if description.is_connected_fn is not None: