From 66da963ff0aa727a97ee5dd1a6bab477bfaf4196 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Tue, 20 Feb 2024 00:32:29 +0100 Subject: [PATCH] Add initial support for H100 and T315 Adds initial support for H100 and its alarmmodule. Also implements the following modules for T315: * reportmodule (reporting interval) * battery * humidity * temperature --- kasa/device_factory.py | 3 +- kasa/deviceconfig.py | 1 + kasa/smart/modules/__init__.py | 9 ++++ kasa/smart/modules/alarmmodule.py | 87 ++++++++++++++++++++++++++++++ kasa/smart/modules/battery.py | 47 ++++++++++++++++ kasa/smart/modules/humidity.py | 47 ++++++++++++++++ kasa/smart/modules/reportmodule.py | 31 +++++++++++ kasa/smart/modules/temperature.py | 57 ++++++++++++++++++++ 8 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 kasa/smart/modules/alarmmodule.py create mode 100644 kasa/smart/modules/battery.py create mode 100644 kasa/smart/modules/humidity.py create mode 100644 kasa/smart/modules/reportmodule.py create mode 100644 kasa/smart/modules/temperature.py diff --git a/kasa/device_factory.py b/kasa/device_factory.py index 3550539c..d504f12f 100755 --- a/kasa/device_factory.py +++ b/kasa/device_factory.py @@ -14,7 +14,7 @@ from .protocol import ( BaseProtocol, BaseTransport, ) -from .smart import SmartBulb, SmartPlug +from .smart import SmartBulb, SmartDevice, SmartPlug from .smartprotocol import SmartProtocol from .xortransport import XorTransport @@ -138,6 +138,7 @@ def get_device_class_from_family(device_type: str) -> Optional[Type[Device]]: "SMART.TAPOPLUG": SmartPlug, "SMART.TAPOBULB": SmartBulb, "SMART.TAPOSWITCH": SmartBulb, + "SMART.TAPOHUB": SmartDevice, "SMART.KASAPLUG": SmartPlug, "SMART.KASASWITCH": SmartBulb, "IOT.SMARTPLUGSWITCH": IotPlug, diff --git a/kasa/deviceconfig.py b/kasa/deviceconfig.py index ffb2988e..35e63902 100644 --- a/kasa/deviceconfig.py +++ b/kasa/deviceconfig.py @@ -31,6 +31,7 @@ class DeviceFamilyType(Enum): SmartTapoPlug = "SMART.TAPOPLUG" SmartTapoBulb = "SMART.TAPOBULB" SmartTapoSwitch = "SMART.TAPOSWITCH" + SmartTapoHub = "SMART.TAPOHUB" def _dataclass_from_dict(klass, in_val): diff --git a/kasa/smart/modules/__init__.py b/kasa/smart/modules/__init__.py index 6031ef2a..6f1eabd6 100644 --- a/kasa/smart/modules/__init__.py +++ b/kasa/smart/modules/__init__.py @@ -1,18 +1,27 @@ """Modules for SMART devices.""" from .autooffmodule import AutoOffModule +from .battery import BatterySensor from .childdevicemodule import ChildDeviceModule from .cloudmodule import CloudModule from .devicemodule import DeviceModule from .energymodule import EnergyModule from .ledmodule import LedModule from .lighttransitionmodule import LightTransitionModule +from .humidity import HumiditySensor +from .reportmodule import ReportModule +from .temperature import TemperatureSensor from .timemodule import TimeModule __all__ = [ + "AlarmModule", "TimeModule", "EnergyModule", "DeviceModule", "ChildDeviceModule", + "BatterySensor", + "HumiditySensor", + "TemperatureSensor", + "ReportModule", "AutoOffModule", "LedModule", "CloudModule", diff --git a/kasa/smart/modules/alarmmodule.py b/kasa/smart/modules/alarmmodule.py new file mode 100644 index 00000000..637c4497 --- /dev/null +++ b/kasa/smart/modules/alarmmodule.py @@ -0,0 +1,87 @@ +"""Implementation of alarm module.""" +from typing import TYPE_CHECKING, Dict, List, Optional + +from ...feature import Feature, FeatureType +from ..smartmodule import SmartModule + +if TYPE_CHECKING: + from ..smartdevice import SmartDevice + + +class AlarmModule(SmartModule): + """Implementation of alarm module.""" + + REQUIRED_COMPONENT = "alarm" + + def query(self) -> Dict: + """Query to execute during the update cycle.""" + return { + "get_alarm_configure": None, + "get_support_alarm_type_list": None, # This should be needed only once + } + + def __init__(self, device: "SmartDevice", module: str): + super().__init__(device, module) + self._add_feature( + Feature( + device, + "Alarm", + container=self, + attribute_getter="active", + icon="mdi:bell", + type=FeatureType.BinarySensor, + ) + ) + self._add_feature( + Feature( + device, + "Alarm source", + container=self, + attribute_getter="source", + icon="mdi:bell", + ) + ) + self._add_feature( + Feature( + device, "Alarm sound", container=self, attribute_getter="alarm_sound" + ) + ) + self._add_feature( + Feature( + device, "Alarm volume", container=self, attribute_getter="alarm_volume" + ) + ) + + @property + def alarm_sound(self): + """Return current alarm sound.""" + return self.data["get_alarm_configure"]["type"] + + @property + def alarm_sounds(self) -> List[str]: + """Return list of available alarm sounds.""" + return self.data["get_support_alarm_type_list"]["alarm_type_list"] + + @property + def alarm_volume(self): + """Return alarm volume.""" + return self.data["get_alarm_configure"]["volume"] + + @property + def active(self) -> bool: + """Return true if alarm is active.""" + return self._device.sys_info["in_alarm"] + + @property + def source(self) -> Optional[str]: + """Return the alarm cause.""" + src = self._device.sys_info["in_alarm_source"] + return src if src else None + + async def play(self): + """Play alarm.""" + return self.call("play_alarm") + + async def stop(self): + """Stop alarm.""" + return self.call("stop_alarm") diff --git a/kasa/smart/modules/battery.py b/kasa/smart/modules/battery.py new file mode 100644 index 00000000..accf875b --- /dev/null +++ b/kasa/smart/modules/battery.py @@ -0,0 +1,47 @@ +"""Implementation of battery module.""" +from typing import TYPE_CHECKING + +from ...feature import Feature, FeatureType +from ..smartmodule import SmartModule + +if TYPE_CHECKING: + from ..smartdevice import SmartDevice + + +class BatterySensor(SmartModule): + """Implementation of battery module.""" + + REQUIRED_COMPONENT = "battery_detect" + QUERY_GETTER_NAME = "get_battery_detect_info" + + def __init__(self, device: "SmartDevice", module: str): + super().__init__(device, module) + self._add_feature( + Feature( + device, + "Battery level", + container=self, + attribute_getter="battery", + icon="mdi:battery", + ) + ) + self._add_feature( + Feature( + device, + "Battery low", + container=self, + attribute_getter="battery_low", + icon="mdi:alert", + type=FeatureType.BinarySensor, + ) + ) + + @property + def battery(self): + """Return battery level.""" + return self._device.sys_info["battery_percentage"] + + @property + def battery_low(self): + """Return True if battery is low.""" + return self._device.sys_info["at_low_battery"] diff --git a/kasa/smart/modules/humidity.py b/kasa/smart/modules/humidity.py new file mode 100644 index 00000000..454bedcd --- /dev/null +++ b/kasa/smart/modules/humidity.py @@ -0,0 +1,47 @@ +"""Implementation of humidity module.""" +from typing import TYPE_CHECKING + +from ...feature import Feature, FeatureType +from ..smartmodule import SmartModule + +if TYPE_CHECKING: + from ..smartdevice import SmartDevice + + +class HumiditySensor(SmartModule): + """Implementation of humidity module.""" + + REQUIRED_COMPONENT = "humidity" + QUERY_GETTER_NAME = "get_comfort_humidity_config" + + def __init__(self, device: "SmartDevice", module: str): + super().__init__(device, module) + self._add_feature( + Feature( + device, + "Humidity", + container=self, + attribute_getter="humidity", + icon="mdi:water-percent", + ) + ) + self._add_feature( + Feature( + device, + "Humidity warning", + container=self, + attribute_getter="humidity_warning", + type=FeatureType.BinarySensor, + icon="mdi:alert", + ) + ) + + @property + def humidity(self): + """Return current humidity in percentage.""" + return self._device.sys_info["current_humidity"] + + @property + def humidity_warning(self) -> bool: + """Return true if humidity is outside of the wanted range.""" + return self._device.sys_info["current_humidity_exception"] != 0 diff --git a/kasa/smart/modules/reportmodule.py b/kasa/smart/modules/reportmodule.py new file mode 100644 index 00000000..04301bb4 --- /dev/null +++ b/kasa/smart/modules/reportmodule.py @@ -0,0 +1,31 @@ +"""Implementation of report module.""" +from typing import TYPE_CHECKING + +from ...feature import Feature +from ..smartmodule import SmartModule + +if TYPE_CHECKING: + from ..smartdevice import SmartDevice + + +class ReportModule(SmartModule): + """Implementation of report module.""" + + REQUIRED_COMPONENT = "report_mode" + QUERY_GETTER_NAME = "get_report_mode" + + def __init__(self, device: "SmartDevice", module: str): + super().__init__(device, module) + self._add_feature( + Feature( + device, + "Report interval", + container=self, + attribute_getter="report_interval", + ) + ) + + @property + def report_interval(self): + """Reporting interval of a sensor device.""" + return self._device.sys_info["report_interval"] diff --git a/kasa/smart/modules/temperature.py b/kasa/smart/modules/temperature.py new file mode 100644 index 00000000..659fb7db --- /dev/null +++ b/kasa/smart/modules/temperature.py @@ -0,0 +1,57 @@ +"""Implementation of temperature module.""" +from typing import TYPE_CHECKING, Literal + +from ...feature import Feature, FeatureType +from ..smartmodule import SmartModule + +if TYPE_CHECKING: + from ..smartdevice import SmartDevice + + +class TemperatureSensor(SmartModule): + """Implementation of temperature module.""" + + REQUIRED_COMPONENT = "humidity" + QUERY_GETTER_NAME = "get_comfort_temp_config" + + def __init__(self, device: "SmartDevice", module: str): + super().__init__(device, module) + self._add_feature( + Feature( + device, + "Temperature", + container=self, + attribute_getter="temperature", + icon="mdi:thermometer", + ) + ) + self._add_feature( + Feature( + device, + "Temperature warning", + container=self, + attribute_getter="temperature_warning", + type=FeatureType.BinarySensor, + icon="mdi:alert", + ) + ) + # TODO: use temperature_unit for feature creation + + @property + def temperature(self): + """Return current humidity in percentage.""" + return self._device.sys_info["current_temp"] + + @property + def temperature_warning(self) -> bool: + """Return True if humidity is outside of the wanted range.""" + return self._device.sys_info["current_temp_exception"] != 0 + + @property + def temperature_unit(self): + """Return current temperature unit.""" + return self._device.sys_info["temp_unit"] + + async def set_temperature_unit(self, unit: Literal["celsius", "fahrenheit"]): + """Set the device temperature unit.""" + return await self.call("set_temperature_unit", {"temp_unit": unit})