From 6cdbbefb908ff1df1683e1fac4fb3ce3ee77f496 Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Fri, 14 Jun 2024 22:04:20 +0100 Subject: [PATCH] Add timezone to on_since attributes (#978) This allows them to displayed in HA without errors. --- kasa/iot/iotdevice.py | 9 ++++++--- kasa/iot/iotstrip.py | 8 +++++--- kasa/smart/smartdevice.py | 39 +++++++++++++++++++-------------------- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/kasa/iot/iotdevice.py b/kasa/iot/iotdevice.py index c7631763..1048034d 100755 --- a/kasa/iot/iotdevice.py +++ b/kasa/iot/iotdevice.py @@ -18,7 +18,7 @@ import collections.abc import functools import inspect import logging -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import TYPE_CHECKING, Any, Mapping, Sequence, cast from ..device import Device, WifiNetwork @@ -345,7 +345,8 @@ class IotDevice(Device): category=Feature.Category.Debug, ) ) - if "on_time" in self._sys_info: + # iot strips calculate on_since from the children + if "on_time" in self._sys_info or self.device_type == Device.Type.Strip: self._add_feature( Feature( device=self, @@ -665,7 +666,9 @@ class IotDevice(Device): on_time = self._sys_info["on_time"] - return datetime.now().replace(microsecond=0) - timedelta(seconds=on_time) + return datetime.now(timezone.utc).astimezone().replace( + microsecond=0 + ) - timedelta(seconds=on_time) @property # type: ignore @requires_update diff --git a/kasa/iot/iotstrip.py b/kasa/iot/iotstrip.py index dde57faa..1ad1bdb8 100755 --- a/kasa/iot/iotstrip.py +++ b/kasa/iot/iotstrip.py @@ -4,7 +4,7 @@ from __future__ import annotations import logging from collections import defaultdict -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Any from ..device_type import DeviceType @@ -148,7 +148,7 @@ class IotStrip(IotDevice): if self.is_off: return None - return max(plug.on_since for plug in self.children if plug.on_since is not None) + return min(plug.on_since for plug in self.children if plug.on_since is not None) async def current_consumption(self) -> float: """Get the current power consumption in watts.""" @@ -372,7 +372,9 @@ class IotStripPlug(IotPlug): info = self._get_child_info() on_time = info["on_time"] - return datetime.now().replace(microsecond=0) - timedelta(seconds=on_time) + return datetime.now(timezone.utc).astimezone().replace( + microsecond=0 + ) - timedelta(seconds=on_time) @property # type: ignore @requires_update diff --git a/kasa/smart/smartdevice.py b/kasa/smart/smartdevice.py index 26bf1396..f4e3eb58 100644 --- a/kasa/smart/smartdevice.py +++ b/kasa/smart/smartdevice.py @@ -4,7 +4,7 @@ from __future__ import annotations import base64 import logging -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import TYPE_CHECKING, Any, Mapping, Sequence, cast from ..aestransport import AesTransport @@ -357,12 +357,25 @@ class SmartDevice(Device): @property def time(self) -> datetime: """Return the time.""" - if self._parent and Module.Time in self._parent.modules: - _timemod = self._parent.modules[Module.Time] - else: - _timemod = self.modules[Module.Time] + if (self._parent and (time_mod := self._parent.modules.get(Module.Time))) or ( + time_mod := self.modules.get(Module.Time) + ): + return time_mod.time - return _timemod.time + # We have no device time, use current local time. + return datetime.now(timezone.utc).astimezone().replace(microsecond=0) + + @property + def on_since(self) -> datetime | None: + """Return the time that the device was turned on or None if turned off.""" + if ( + not self._info.get("device_on") + or (on_time := self._info.get("on_time")) is None + ): + return None + + on_time = cast(float, on_time) + return self.time - timedelta(seconds=on_time) @property def timezone(self) -> dict: @@ -489,20 +502,6 @@ class SmartDevice(Device): energy = self.modules[Module.Energy] return energy.emeter_today - @property - def on_since(self) -> datetime | None: - """Return the time that the device was turned on or None if turned off.""" - if ( - not self._info.get("device_on") - or (on_time := self._info.get("on_time")) is None - ): - return None - on_time = cast(float, on_time) - if (timemod := self.modules.get(Module.Time)) is not None: - return timemod.time - timedelta(seconds=on_time) - else: # We have no device time, use current local time. - return datetime.now().replace(microsecond=0) - timedelta(seconds=on_time) - async def wifi_scan(self) -> list[WifiNetwork]: """Scan for available wifi networks."""