mirror of
				https://github.com/python-kasa/python-kasa.git
				synced 2025-10-31 20:51:54 +00:00 
			
		
		
		
	Add initial support for H100 and T315 (#776)
Adds initial support for H100 and its alarmmodule. Also implements the following modules for T315: * reportmodule (reporting interval) * battery * humidity * temperature
This commit is contained in:
		| @@ -581,7 +581,7 @@ async def state(ctx, dev: Device): | ||||
|     echo(f"\tHost: {dev.host}") | ||||
|     echo(f"\tPort: {dev.port}") | ||||
|     echo(f"\tDevice state: {dev.is_on}") | ||||
|     if dev.is_strip: | ||||
|     if dev.children: | ||||
|         echo("\t[bold]== Children ==[/bold]") | ||||
|         for child in dev.children: | ||||
|             echo(f"\t* {child.alias} ({child.model}, {child.device_type})") | ||||
|   | ||||
| @@ -139,6 +139,7 @@ def get_device_class_from_family(device_type: str) -> Optional[Type[Device]]: | ||||
|         "SMART.TAPOBULB": SmartBulb, | ||||
|         "SMART.TAPOSWITCH": SmartBulb, | ||||
|         "SMART.KASAPLUG": SmartDevice, | ||||
|         "SMART.TAPOHUB": SmartDevice, | ||||
|         "SMART.KASASWITCH": SmartBulb, | ||||
|         "IOT.SMARTPLUGSWITCH": IotPlug, | ||||
|         "IOT.SMARTBULB": IotBulb, | ||||
|   | ||||
| @@ -15,6 +15,7 @@ class DeviceType(Enum): | ||||
|     Dimmer = "dimmer" | ||||
|     LightStrip = "lightstrip" | ||||
|     Sensor = "sensor" | ||||
|     Hub = "hub" | ||||
|     Unknown = "unknown" | ||||
|  | ||||
|     @staticmethod | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -1,19 +1,29 @@ | ||||
| """Modules for SMART devices.""" | ||||
| from .alarmmodule import AlarmModule | ||||
| 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 .firmware import Firmware | ||||
| from .humidity import HumiditySensor | ||||
| from .ledmodule import LedModule | ||||
| from .lighttransitionmodule import LightTransitionModule | ||||
| from .reportmodule import ReportModule | ||||
| from .temperature import TemperatureSensor | ||||
| from .timemodule import TimeModule | ||||
|  | ||||
| __all__ = [ | ||||
|     "AlarmModule", | ||||
|     "TimeModule", | ||||
|     "EnergyModule", | ||||
|     "DeviceModule", | ||||
|     "ChildDeviceModule", | ||||
|     "BatterySensor", | ||||
|     "HumiditySensor", | ||||
|     "TemperatureSensor", | ||||
|     "ReportModule", | ||||
|     "AutoOffModule", | ||||
|     "LedModule", | ||||
|     "Firmware", | ||||
|   | ||||
							
								
								
									
										87
									
								
								kasa/smart/modules/alarmmodule.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								kasa/smart/modules/alarmmodule.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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") | ||||
							
								
								
									
										47
									
								
								kasa/smart/modules/battery.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								kasa/smart/modules/battery.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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"] | ||||
							
								
								
									
										47
									
								
								kasa/smart/modules/humidity.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								kasa/smart/modules/humidity.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										31
									
								
								kasa/smart/modules/reportmodule.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								kasa/smart/modules/reportmodule.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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"] | ||||
							
								
								
									
										57
									
								
								kasa/smart/modules/temperature.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								kasa/smart/modules/temperature.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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}) | ||||
| @@ -2,11 +2,8 @@ | ||||
| from typing import Any, Dict, List, Optional | ||||
|  | ||||
| from ..bulb import Bulb | ||||
| from ..device_type import DeviceType | ||||
| from ..deviceconfig import DeviceConfig | ||||
| from ..exceptions import KasaException | ||||
| from ..iot.iotbulb import HSV, BulbPreset, ColorTempRange | ||||
| from ..smartprotocol import SmartProtocol | ||||
| from .smartdevice import SmartDevice | ||||
|  | ||||
| AVAILABLE_EFFECTS = { | ||||
| @@ -21,16 +18,6 @@ class SmartBulb(SmartDevice, Bulb): | ||||
|     Documentation TBD. See :class:`~kasa.iot.Bulb` for now. | ||||
|     """ | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         host: str, | ||||
|         *, | ||||
|         config: Optional[DeviceConfig] = None, | ||||
|         protocol: Optional[SmartProtocol] = None, | ||||
|     ) -> None: | ||||
|         super().__init__(host=host, config=config, protocol=protocol) | ||||
|         self._device_type = DeviceType.Bulb | ||||
|  | ||||
|     @property | ||||
|     def is_color(self) -> bool: | ||||
|         """Whether the bulb supports color changes.""" | ||||
|   | ||||
| @@ -63,9 +63,12 @@ class SmartDevice(Device): | ||||
|             ) | ||||
|             for child_info in children | ||||
|         } | ||||
|         # TODO: if all are sockets, then we are a strip, and otherwise a hub? | ||||
|         #  doesn't work for the walldimmer with fancontrol... | ||||
|         self._device_type = DeviceType.Strip | ||||
|         # TODO: This may not be the best approach, but it allows distinguishing | ||||
|         #  between power strips and hubs for the time being. | ||||
|         if all(child.is_plug for child in self._children.values()): | ||||
|             self._device_type = DeviceType.Strip | ||||
|         else: | ||||
|             self._device_type = DeviceType.Hub | ||||
|  | ||||
|     @property | ||||
|     def children(self) -> Sequence["SmartDevice"]: | ||||
| @@ -518,7 +521,7 @@ class SmartDevice(Device): | ||||
|  | ||||
|         if self.children: | ||||
|             if "SMART.TAPOHUB" in self.sys_info["type"]: | ||||
|                 pass  # TODO: placeholder for future hub PR | ||||
|                 self._device_type = DeviceType.Hub | ||||
|             else: | ||||
|                 self._device_type = DeviceType.Strip | ||||
|         elif "light_strip" in self._components: | ||||
|   | ||||
| @@ -47,7 +47,7 @@ SUPPORTED_DEVICES = SUPPORTED_IOT_DEVICES + SUPPORTED_SMART_DEVICES | ||||
| BULBS_SMART_VARIABLE_TEMP = {"L530E", "L930-5"} | ||||
| BULBS_SMART_LIGHT_STRIP = {"L900-5", "L900-10", "L920-5", "L930-5"} | ||||
| BULBS_SMART_COLOR = {"L530E", *BULBS_SMART_LIGHT_STRIP} | ||||
| BULBS_SMART_DIMMABLE = {"KS225", "L510B", "L510E"} | ||||
| BULBS_SMART_DIMMABLE = {"L510B", "L510E"} | ||||
| BULBS_SMART = ( | ||||
|     BULBS_SMART_VARIABLE_TEMP.union(BULBS_SMART_COLOR) | ||||
|     .union(BULBS_SMART_DIMMABLE) | ||||
| @@ -120,7 +120,7 @@ STRIPS_SMART = {"P300", "TP25"} | ||||
| STRIPS = {*STRIPS_IOT, *STRIPS_SMART} | ||||
|  | ||||
| DIMMERS_IOT = {"ES20M", "HS220", "KS220M", "KS230", "KP405"} | ||||
| DIMMERS_SMART = {"S500D", "P135"} | ||||
| DIMMERS_SMART = {"KS225", "S500D", "P135"} | ||||
| DIMMERS = { | ||||
|     *DIMMERS_IOT, | ||||
|     *DIMMERS_SMART, | ||||
|   | ||||
| @@ -13,7 +13,6 @@ from .conftest import strip_smart | ||||
| def test_childdevice_init(dev, dummy_protocol, mocker): | ||||
|     """Test that child devices get initialized and use protocol wrapper.""" | ||||
|     assert len(dev.children) > 0 | ||||
|     assert dev.is_strip | ||||
|  | ||||
|     first = dev.children[0] | ||||
|     assert isinstance(first.protocol, _ChildProtocolWrapper) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Teemu R
					Teemu R