diff --git a/kasa/module.py b/kasa/module.py index db343810..873d8e1b 100644 --- a/kasa/module.py +++ b/kasa/module.py @@ -160,6 +160,9 @@ class Module(ABC): ) ChildLock: Final[ModuleName[smart.ChildLock]] = ModuleName("ChildLock") TriggerLogs: Final[ModuleName[smart.TriggerLogs]] = ModuleName("TriggerLogs") + TemperatureRecords: Final[ModuleName[smart.TemperatureRecords]] = ModuleName( + "TemperatureRecords" + ) PowerProtection: Final[ModuleName[smart.PowerProtection]] = ModuleName( "PowerProtection" ) diff --git a/kasa/smart/modules/__init__.py b/kasa/smart/modules/__init__.py index 0cb3e751..91d56b18 100644 --- a/kasa/smart/modules/__init__.py +++ b/kasa/smart/modules/__init__.py @@ -39,6 +39,7 @@ from .powerprotection import PowerProtection from .reportmode import ReportMode from .speaker import Speaker from .temperaturecontrol import TemperatureControl +from .temperaturerecords import TemperatureRecords from .temperaturesensor import TemperatureSensor from .thermostat import Thermostat from .time import Time @@ -78,6 +79,7 @@ __all__ = [ "TriggerLogs", "FrostProtection", "Thermostat", + "TemperatureRecords", "Clean", "Consumables", "CleanRecords", diff --git a/kasa/smart/modules/temperaturerecords.py b/kasa/smart/modules/temperaturerecords.py new file mode 100644 index 00000000..b36b3ac6 --- /dev/null +++ b/kasa/smart/modules/temperaturerecords.py @@ -0,0 +1,32 @@ +"""Implementation of the thermostat temperature records module.""" + +from __future__ import annotations + +from datetime import timedelta + +from ..smartmodule import SmartModule + +#: Value the device uses for slots without a reading. +NO_READING = -1000 + + +class TemperatureRecords(SmartModule): + """Implementation of the temperature history of a thermostat. + + The device keeps a reading for every 15 minutes of the last 24 hours, + ordered oldest first, with the most recent reading last. + """ + + REQUIRED_COMPONENT = "temp_record" + QUERY_GETTER_NAME = "get_temperature_records" + INTERVAL = timedelta(minutes=15) + MINIMUM_UPDATE_INTERVAL_SECS = 15 * 60 + MINIMUM_HUB_CHILD_UPDATE_INTERVAL_SECS = 15 * 60 + + @property + def temperatures(self) -> list[float | None]: + """Return the readings oldest first, None where there is no reading.""" + return [ + None if value == NO_READING else value / 10 + for value in self.data.get("temp_data", []) + ] diff --git a/tests/fakeprotocol_smart.py b/tests/fakeprotocol_smart.py index 7a46ba6c..040f06c3 100644 --- a/tests/fakeprotocol_smart.py +++ b/tests/fakeprotocol_smart.py @@ -96,6 +96,10 @@ class FakeSmartTransport(BaseTransport): return self._credentials.username + self._credentials.password + "hash" FIXTURE_MISSING_MAP = { + "get_temperature_records": ( + "temp_record", + {"temp_data": [-1000] * 88 + [253, 254, 256, 255, 252, 249, 247, 245]}, + ), "get_wireless_scan_info": ("wireless", {"ap_list": [], "wep_supported": False}), "get_auto_off_config": ("auto_off", {"delay_min": 10, "enable": False}), "get_led_info": ( diff --git a/tests/smart/modules/test_temperaturerecords.py b/tests/smart/modules/test_temperaturerecords.py new file mode 100644 index 00000000..f7bc7db6 --- /dev/null +++ b/tests/smart/modules/test_temperaturerecords.py @@ -0,0 +1,24 @@ +from datetime import timedelta + +from kasa import Module +from kasa.smart import SmartDevice + +from ...device_fixtures import parametrize + +temperature_records = parametrize( + "has temperature records", + component_filter="temp_record", + protocol_filter={"SMART.CHILD"}, +) + + +@temperature_records +async def test_temperature_records(dev: SmartDevice) -> None: + """Test the thermostat temperature history.""" + records = dev.modules[Module.TemperatureRecords] + + temps = records.temperatures + assert len(temps) == 96 + assert all(t is None or isinstance(t, float) for t in temps) + assert temps[-1] is not None + assert timedelta(minutes=15) == records.INTERVAL