Stabilise on_since value for smart devices (#1144)

Caches the `on_since` value to prevent jitter caused by the device calculations.
This commit is contained in:
Steven B. 2024-10-02 15:04:16 +01:00 committed by GitHub
parent 81e2685605
commit 1fcf3e44c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 43 additions and 13 deletions

View File

@ -435,7 +435,11 @@ class Device(ABC):
@property
@abstractmethod
def on_since(self) -> datetime | None:
"""Return the time that the device was turned on or None if turned off."""
"""Return the time that the device was turned on or None if turned off.
This returns a cached value if the device reported value difference is under
five seconds to avoid device-caused jitter.
"""
@abstractmethod
async def wifi_scan(self) -> list[WifiNetwork]:

View File

@ -181,6 +181,7 @@ class IotDevice(Device):
self._legacy_features: set[str] = set()
self._children: Mapping[str, IotDevice] = {}
self._modules: dict[str | ModuleName[Module], IotModule] = {}
self._on_since: datetime | None = None
@property
def children(self) -> Sequence[IotDevice]:
@ -594,18 +595,25 @@ class IotDevice(Device):
@property # type: ignore
@requires_update
def on_since(self) -> datetime | None:
"""Return pretty-printed on-time, or None if not available."""
if "on_time" not in self._sys_info:
return None
"""Return the time that the device was turned on or None if turned off.
if self.is_off:
This returns a cached value if the device reported value difference is under
five seconds to avoid device-caused jitter.
"""
if self.is_off or "on_time" not in self._sys_info:
self._on_since = None
return None
on_time = self._sys_info["on_time"]
return datetime.now(timezone.utc).astimezone().replace(
microsecond=0
) - timedelta(seconds=on_time)
time = datetime.now(timezone.utc).astimezone().replace(microsecond=0)
on_since = time - timedelta(seconds=on_time)
if not self._on_since or timedelta(
seconds=0
) < on_since - self._on_since > timedelta(seconds=5):
self._on_since = on_since
return self._on_since
@property # type: ignore
@requires_update

View File

@ -318,6 +318,7 @@ class IotStripPlug(IotPlug):
self._set_sys_info(parent.sys_info)
self._device_type = DeviceType.StripSocket
self.protocol = parent.protocol # Must use the same connection as the parent
self._on_since: datetime | None = None
async def _initialize_modules(self):
"""Initialize modules not added in init."""
@ -438,14 +439,20 @@ class IotStripPlug(IotPlug):
def on_since(self) -> datetime | None:
"""Return on-time, if available."""
if self.is_off:
self._on_since = None
return None
info = self._get_child_info()
on_time = info["on_time"]
return datetime.now(timezone.utc).astimezone().replace(
microsecond=0
) - timedelta(seconds=on_time)
time = datetime.now(timezone.utc).astimezone().replace(microsecond=0)
on_since = time - timedelta(seconds=on_time)
if not self._on_since or timedelta(
seconds=0
) < on_since - self._on_since > timedelta(seconds=5):
self._on_since = on_since
return self._on_since
@property # type: ignore
@requires_update

View File

@ -66,6 +66,7 @@ class SmartDevice(Device):
self._children: Mapping[str, SmartDevice] = {}
self._last_update = {}
self._last_update_time: float | None = None
self._on_since: datetime | None = None
async def _initialize_children(self):
"""Initialize children for power strips."""
@ -494,15 +495,25 @@ class SmartDevice(Device):
@property
def on_since(self) -> datetime | None:
"""Return the time that the device was turned on or None if turned off."""
"""Return the time that the device was turned on or None if turned off.
This returns a cached value if the device reported value difference is under
five seconds to avoid device-caused jitter.
"""
if (
not self._info.get("device_on")
or (on_time := self._info.get("on_time")) is None
):
self._on_since = None
return None
on_time = cast(float, on_time)
return self.time - timedelta(seconds=on_time)
on_since = self.time - timedelta(seconds=on_time)
if not self._on_since or timedelta(
seconds=0
) < on_since - self._on_since > timedelta(seconds=5):
self._on_since = on_since
return self._on_since
@property
def timezone(self) -> dict: