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 @property
@abstractmethod @abstractmethod
def on_since(self) -> datetime | None: 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 @abstractmethod
async def wifi_scan(self) -> list[WifiNetwork]: async def wifi_scan(self) -> list[WifiNetwork]:

View File

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

View File

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

View File

@ -66,6 +66,7 @@ class SmartDevice(Device):
self._children: Mapping[str, SmartDevice] = {} self._children: Mapping[str, SmartDevice] = {}
self._last_update = {} self._last_update = {}
self._last_update_time: float | None = None self._last_update_time: float | None = None
self._on_since: datetime | None = None
async def _initialize_children(self): async def _initialize_children(self):
"""Initialize children for power strips.""" """Initialize children for power strips."""
@ -494,15 +495,25 @@ class SmartDevice(Device):
@property @property
def on_since(self) -> datetime | None: 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 ( if (
not self._info.get("device_on") not self._info.get("device_on")
or (on_time := self._info.get("on_time")) is None or (on_time := self._info.get("on_time")) is None
): ):
self._on_since = None
return None return None
on_time = cast(float, on_time) 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 @property
def timezone(self) -> dict: def timezone(self) -> dict: