From 8a17752ae234b93ae5ce32a97e78d470e7cca8e2 Mon Sep 17 00:00:00 2001 From: "Teemu R." Date: Fri, 18 Oct 2024 13:18:12 +0200 Subject: [PATCH] Add waterleak alert timestamp (#1162) The T300 reports the timestamp of the last alarm, this exposes it to consumers. --- kasa/smart/modules/waterleaksensor.py | 26 +- .../smart/child/T300(EU)_1.0_1.7.0.json | 1073 +++++++++-------- kasa/tests/smart/modules/test_waterleak.py | 3 + 3 files changed, 568 insertions(+), 534 deletions(-) diff --git a/kasa/smart/modules/waterleaksensor.py b/kasa/smart/modules/waterleaksensor.py index bba4f61d..6b8a7ae7 100644 --- a/kasa/smart/modules/waterleaksensor.py +++ b/kasa/smart/modules/waterleaksensor.py @@ -2,10 +2,11 @@ from __future__ import annotations +from datetime import datetime from enum import Enum from ...feature import Feature -from ..smartmodule import SmartModule +from ..smartmodule import Module, SmartModule class WaterleakStatus(Enum): @@ -47,6 +48,18 @@ class WaterleakSensor(SmartModule): type=Feature.Type.BinarySensor, ) ) + self._add_feature( + Feature( + self._device, + id="water_alert_timestamp", + name="Last alert timestamp", + container=self, + attribute_getter="alert_timestamp", + icon="mdi:alert", + category=Feature.Category.Info, + type=Feature.Type.Sensor, + ) + ) def query(self) -> dict: """Query to execute during the update cycle.""" @@ -62,3 +75,14 @@ class WaterleakSensor(SmartModule): def alert(self) -> bool: """Return true if alarm is active.""" return self._device.sys_info["in_alarm"] + + @property + def alert_timestamp(self) -> datetime | None: + """Return timestamp of the last leak trigger.""" + # The key is not always be there, maybe if it hasn't ever been triggered? + if "trigger_timestamp" not in self._device.sys_info: + return None + + ts = self._device.sys_info["trigger_timestamp"] + tz = self._device.modules[Module.Time].timezone + return datetime.fromtimestamp(ts, tz=tz) diff --git a/kasa/tests/fixtures/smart/child/T300(EU)_1.0_1.7.0.json b/kasa/tests/fixtures/smart/child/T300(EU)_1.0_1.7.0.json index 7a6c8db3..a08cda11 100644 --- a/kasa/tests/fixtures/smart/child/T300(EU)_1.0_1.7.0.json +++ b/kasa/tests/fixtures/smart/child/T300(EU)_1.0_1.7.0.json @@ -1,533 +1,540 @@ -{ - "component_nego": { - "component_list": [ - { - "id": "device", - "ver_code": 2 - }, - { - "id": "quick_setup", - "ver_code": 3 - }, - { - "id": "trigger_log", - "ver_code": 1 - }, - { - "id": "time", - "ver_code": 1 - }, - { - "id": "device_local_time", - "ver_code": 1 - }, - { - "id": "account", - "ver_code": 1 - }, - { - "id": "synchronize", - "ver_code": 1 - }, - { - "id": "cloud_connect", - "ver_code": 1 - }, - { - "id": "iot_cloud", - "ver_code": 1 - }, - { - "id": "firmware", - "ver_code": 1 - }, - { - "id": "localSmart", - "ver_code": 1 - }, - { - "id": "battery_detect", - "ver_code": 1 - }, - { - "id": "sensor_alarm", - "ver_code": 1 - } - ] - }, - "get_connect_cloud_state": { - "status": 0 - }, - "get_device_info": { - "at_low_battery": false, - "avatar": "sensor_t300", - "battery_percentage": 100, - "bind_count": 1, - "category": "subg.trigger.water-leak-sensor", - "device_id": "SCRUBBED_CHILD_DEVICE_ID_1", - "fw_ver": "1.7.0 Build 230628 Rel.194748", - "hw_id": "00000000000000000000000000000000", - "hw_ver": "1.0", - "in_alarm": false, - "jamming_rssi": -120, - "jamming_signal_level": 1, - "lastOnboardingTimestamp": 1714661760, - "mac": "98254A000000", - "model": "T300", - "nickname": "I01BU0tFRF9OQU1FIw==", - "oem_id": "00000000000000000000000000000000", - "parent_device_id": "0000000000000000000000000000000000000000", - "region": "Europe/Berlin", - "report_interval": 16, - "rssi": -49, - "signal_level": 3, - "specs": "EU", - "status": "online", - "status_follow_edge": false, - "type": "SMART.TAPOSENSOR", - "water_leak_status": "normal" - }, - "get_fw_download_state": { - "cloud_cache_seconds": 1, - "download_progress": 0, - "reboot_time": 5, - "status": 0, - "upgrade_time": 5 - }, - "get_latest_fw": { - "fw_size": 0, - "fw_ver": "1.7.0 Build 230628 Rel.194748", - "hw_id": "", - "need_to_upgrade": false, - "oem_id": "", - "release_date": "", - "release_note": "", - "type": 0 - }, - "get_temp_humidity_records": { - "local_time": 1714681045, - "past24h_humidity": [ - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000 - ], - "past24h_humidity_exception": [ - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000 - ], - "past24h_temp": [ - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000 - ], - "past24h_temp_exception": [ - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000, - -1000 - ], - "temp_unit": "celsius" - }, - "get_trigger_logs": { - "logs": [ - { - "event": "waterDry", - "eventId": "18a67996-611a-a7f9-5689-6699ee55806a", - "id": 8, - "timestamp": 1714680176 - }, - { - "event": "waterLeak", - "eventId": "4b43c78d-a832-7755-cc80-a6357cd88aa3", - "id": 7, - "timestamp": 1714680174 - }, - { - "event": "waterDry", - "eventId": "2a3731ba-7f1d-2c34-38be-f5580e2d3cbc", - "id": 6, - "timestamp": 1714680172 - }, - { - "event": "waterLeak", - "eventId": "eebb19c0-2cda-215c-62f5-be13cda215c6", - "id": 5, - "timestamp": 1714676832 - } - ], - "start_id": 8, - "sum": 4 - } -} +{ + "component_nego": { + "component_list": [ + { + "id": "device", + "ver_code": 2 + }, + { + "id": "quick_setup", + "ver_code": 3 + }, + { + "id": "trigger_log", + "ver_code": 1 + }, + { + "id": "time", + "ver_code": 1 + }, + { + "id": "device_local_time", + "ver_code": 1 + }, + { + "id": "account", + "ver_code": 1 + }, + { + "id": "synchronize", + "ver_code": 1 + }, + { + "id": "cloud_connect", + "ver_code": 1 + }, + { + "id": "iot_cloud", + "ver_code": 1 + }, + { + "id": "firmware", + "ver_code": 1 + }, + { + "id": "localSmart", + "ver_code": 1 + }, + { + "id": "battery_detect", + "ver_code": 1 + }, + { + "id": "sensor_alarm", + "ver_code": 1 + } + ] + }, + "get_connect_cloud_state": { + "status": 0 + }, + "get_device_info": { + "at_low_battery": false, + "avatar": "sensor_t300", + "battery_percentage": 100, + "bind_count": 1, + "category": "subg.trigger.water-leak-sensor", + "device_id": "SCRUBBED_CHILD_DEVICE_ID_6", + "fw_ver": "1.7.0 Build 230628 Rel.194748", + "hw_id": "00000000000000000000000000000000", + "hw_ver": "1.0", + "in_alarm": false, + "jamming_rssi": -119, + "jamming_signal_level": 1, + "lastOnboardingTimestamp": 1728470353, + "mac": "A86E84000000", + "model": "T300", + "nickname": "I01BU0tFRF9OQU1FIw==", + "oem_id": "00000000000000000000000000000000", + "parent_device_id": "0000000000000000000000000000000000000000", + "region": "CEST", + "report_interval": 16, + "rssi": -44, + "signal_level": 3, + "specs": "EU", + "status": "online", + "status_follow_edge": false, + "trigger_timestamp": 1728480717, + "type": "SMART.TAPOSENSOR", + "water_leak_status": "water_dry" + }, + "get_fw_download_state": { + "cloud_cache_seconds": 1, + "download_progress": 0, + "reboot_time": 5, + "status": 0, + "upgrade_time": 5 + }, + "get_latest_fw": { + "fw_size": 0, + "fw_ver": "1.7.0 Build 230628 Rel.194748", + "hw_id": "", + "need_to_upgrade": false, + "oem_id": "", + "release_date": "", + "release_note": "", + "type": 0 + }, + "get_temp_humidity_records": { + "local_time": 1729248928, + "past24h_humidity": [ + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000 + ], + "past24h_humidity_exception": [ + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000 + ], + "past24h_temp": [ + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000 + ], + "past24h_temp_exception": [ + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000, + -1000 + ], + "temp_unit": "celsius" + }, + "get_trigger_logs": { + "logs": [ + { + "event": "waterDry", + "eventId": "d595356d-4953-5654-d59d-b92b6aca9ab2", + "id": 114, + "timestamp": 1728480717 + }, + { + "event": "waterLeak", + "eventId": "c43fc234-4ff2-ac03-d4bf-0254ff2ac03d", + "id": 113, + "timestamp": 1728480714 + }, + { + "event": "waterDry", + "eventId": "3e68c39e-b027-e405-7d41-d714fd81bfa8", + "id": 112, + "timestamp": 1728471129 + }, + { + "event": "waterLeak", + "eventId": "0e8743a9-d46a-bdde-67bb-d562b9542219", + "id": 111, + "timestamp": 1728471123 + }, + { + "event": "waterDry", + "eventId": "97708bf6-4817-b06b-0ebc-ed45917b06b0", + "id": 110, + "timestamp": 1728471106 + } + ], + "start_id": 114, + "sum": 14 + } +} diff --git a/kasa/tests/smart/modules/test_waterleak.py b/kasa/tests/smart/modules/test_waterleak.py index c48d8244..8704ae81 100644 --- a/kasa/tests/smart/modules/test_waterleak.py +++ b/kasa/tests/smart/modules/test_waterleak.py @@ -1,3 +1,4 @@ +from datetime import datetime from enum import Enum import pytest @@ -15,6 +16,8 @@ waterleak = parametrize( ("feature", "prop_name", "type"), [ ("water_alert", "alert", int), + # Can be converted to 'datetime | None' after py3.9 support is dropped + ("water_alert_timestamp", "alert_timestamp", (datetime, type(None))), ("water_leak", "status", Enum), ], )