Skip to content

Commit bc65f96

Browse files
authored
Add initial support for H100 and T315 (python-kasa#776)
Adds initial support for H100 and its alarmmodule. Also implements the following modules for T315: * reportmodule (reporting interval) * battery * humidity * temperature
1 parent 951d41a commit bc65f96

14 files changed

+292
-21
lines changed

kasa/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ async def state(ctx, dev: Device):
581581
echo(f"\tHost: {dev.host}")
582582
echo(f"\tPort: {dev.port}")
583583
echo(f"\tDevice state: {dev.is_on}")
584-
if dev.is_strip:
584+
if dev.children:
585585
echo("\t[bold]== Children ==[/bold]")
586586
for child in dev.children:
587587
echo(f"\t* {child.alias} ({child.model}, {child.device_type})")

kasa/device_factory.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ def get_device_class_from_family(device_type: str) -> Optional[Type[Device]]:
139139
"SMART.TAPOBULB": SmartBulb,
140140
"SMART.TAPOSWITCH": SmartBulb,
141141
"SMART.KASAPLUG": SmartDevice,
142+
"SMART.TAPOHUB": SmartDevice,
142143
"SMART.KASASWITCH": SmartBulb,
143144
"IOT.SMARTPLUGSWITCH": IotPlug,
144145
"IOT.SMARTBULB": IotBulb,

kasa/device_type.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class DeviceType(Enum):
1515
Dimmer = "dimmer"
1616
LightStrip = "lightstrip"
1717
Sensor = "sensor"
18+
Hub = "hub"
1819
Unknown = "unknown"
1920

2021
@staticmethod

kasa/deviceconfig.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class DeviceFamilyType(Enum):
3131
SmartTapoPlug = "SMART.TAPOPLUG"
3232
SmartTapoBulb = "SMART.TAPOBULB"
3333
SmartTapoSwitch = "SMART.TAPOSWITCH"
34+
SmartTapoHub = "SMART.TAPOHUB"
3435

3536

3637
def _dataclass_from_dict(klass, in_val):

kasa/smart/modules/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
11
"""Modules for SMART devices."""
2+
from .alarmmodule import AlarmModule
23
from .autooffmodule import AutoOffModule
4+
from .battery import BatterySensor
35
from .childdevicemodule import ChildDeviceModule
46
from .cloudmodule import CloudModule
57
from .devicemodule import DeviceModule
68
from .energymodule import EnergyModule
79
from .firmware import Firmware
10+
from .humidity import HumiditySensor
811
from .ledmodule import LedModule
912
from .lighttransitionmodule import LightTransitionModule
13+
from .reportmodule import ReportModule
14+
from .temperature import TemperatureSensor
1015
from .timemodule import TimeModule
1116

1217
__all__ = [
18+
"AlarmModule",
1319
"TimeModule",
1420
"EnergyModule",
1521
"DeviceModule",
1622
"ChildDeviceModule",
23+
"BatterySensor",
24+
"HumiditySensor",
25+
"TemperatureSensor",
26+
"ReportModule",
1727
"AutoOffModule",
1828
"LedModule",
1929
"Firmware",

kasa/smart/modules/alarmmodule.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""Implementation of alarm module."""
2+
from typing import TYPE_CHECKING, Dict, List, Optional
3+
4+
from ...feature import Feature, FeatureType
5+
from ..smartmodule import SmartModule
6+
7+
if TYPE_CHECKING:
8+
from ..smartdevice import SmartDevice
9+
10+
11+
class AlarmModule(SmartModule):
12+
"""Implementation of alarm module."""
13+
14+
REQUIRED_COMPONENT = "alarm"
15+
16+
def query(self) -> Dict:
17+
"""Query to execute during the update cycle."""
18+
return {
19+
"get_alarm_configure": None,
20+
"get_support_alarm_type_list": None, # This should be needed only once
21+
}
22+
23+
def __init__(self, device: "SmartDevice", module: str):
24+
super().__init__(device, module)
25+
self._add_feature(
26+
Feature(
27+
device,
28+
"Alarm",
29+
container=self,
30+
attribute_getter="active",
31+
icon="mdi:bell",
32+
type=FeatureType.BinarySensor,
33+
)
34+
)
35+
self._add_feature(
36+
Feature(
37+
device,
38+
"Alarm source",
39+
container=self,
40+
attribute_getter="source",
41+
icon="mdi:bell",
42+
)
43+
)
44+
self._add_feature(
45+
Feature(
46+
device, "Alarm sound", container=self, attribute_getter="alarm_sound"
47+
)
48+
)
49+
self._add_feature(
50+
Feature(
51+
device, "Alarm volume", container=self, attribute_getter="alarm_volume"
52+
)
53+
)
54+
55+
@property
56+
def alarm_sound(self):
57+
"""Return current alarm sound."""
58+
return self.data["get_alarm_configure"]["type"]
59+
60+
@property
61+
def alarm_sounds(self) -> List[str]:
62+
"""Return list of available alarm sounds."""
63+
return self.data["get_support_alarm_type_list"]["alarm_type_list"]
64+
65+
@property
66+
def alarm_volume(self):
67+
"""Return alarm volume."""
68+
return self.data["get_alarm_configure"]["volume"]
69+
70+
@property
71+
def active(self) -> bool:
72+
"""Return true if alarm is active."""
73+
return self._device.sys_info["in_alarm"]
74+
75+
@property
76+
def source(self) -> Optional[str]:
77+
"""Return the alarm cause."""
78+
src = self._device.sys_info["in_alarm_source"]
79+
return src if src else None
80+
81+
async def play(self):
82+
"""Play alarm."""
83+
return self.call("play_alarm")
84+
85+
async def stop(self):
86+
"""Stop alarm."""
87+
return self.call("stop_alarm")

kasa/smart/modules/battery.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Implementation of battery module."""
2+
from typing import TYPE_CHECKING
3+
4+
from ...feature import Feature, FeatureType
5+
from ..smartmodule import SmartModule
6+
7+
if TYPE_CHECKING:
8+
from ..smartdevice import SmartDevice
9+
10+
11+
class BatterySensor(SmartModule):
12+
"""Implementation of battery module."""
13+
14+
REQUIRED_COMPONENT = "battery_detect"
15+
QUERY_GETTER_NAME = "get_battery_detect_info"
16+
17+
def __init__(self, device: "SmartDevice", module: str):
18+
super().__init__(device, module)
19+
self._add_feature(
20+
Feature(
21+
device,
22+
"Battery level",
23+
container=self,
24+
attribute_getter="battery",
25+
icon="mdi:battery",
26+
)
27+
)
28+
self._add_feature(
29+
Feature(
30+
device,
31+
"Battery low",
32+
container=self,
33+
attribute_getter="battery_low",
34+
icon="mdi:alert",
35+
type=FeatureType.BinarySensor,
36+
)
37+
)
38+
39+
@property
40+
def battery(self):
41+
"""Return battery level."""
42+
return self._device.sys_info["battery_percentage"]
43+
44+
@property
45+
def battery_low(self):
46+
"""Return True if battery is low."""
47+
return self._device.sys_info["at_low_battery"]

kasa/smart/modules/humidity.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Implementation of humidity module."""
2+
from typing import TYPE_CHECKING
3+
4+
from ...feature import Feature, FeatureType
5+
from ..smartmodule import SmartModule
6+
7+
if TYPE_CHECKING:
8+
from ..smartdevice import SmartDevice
9+
10+
11+
class HumiditySensor(SmartModule):
12+
"""Implementation of humidity module."""
13+
14+
REQUIRED_COMPONENT = "humidity"
15+
QUERY_GETTER_NAME = "get_comfort_humidity_config"
16+
17+
def __init__(self, device: "SmartDevice", module: str):
18+
super().__init__(device, module)
19+
self._add_feature(
20+
Feature(
21+
device,
22+
"Humidity",
23+
container=self,
24+
attribute_getter="humidity",
25+
icon="mdi:water-percent",
26+
)
27+
)
28+
self._add_feature(
29+
Feature(
30+
device,
31+
"Humidity warning",
32+
container=self,
33+
attribute_getter="humidity_warning",
34+
type=FeatureType.BinarySensor,
35+
icon="mdi:alert",
36+
)
37+
)
38+
39+
@property
40+
def humidity(self):
41+
"""Return current humidity in percentage."""
42+
return self._device.sys_info["current_humidity"]
43+
44+
@property
45+
def humidity_warning(self) -> bool:
46+
"""Return true if humidity is outside of the wanted range."""
47+
return self._device.sys_info["current_humidity_exception"] != 0

kasa/smart/modules/reportmodule.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Implementation of report module."""
2+
from typing import TYPE_CHECKING
3+
4+
from ...feature import Feature
5+
from ..smartmodule import SmartModule
6+
7+
if TYPE_CHECKING:
8+
from ..smartdevice import SmartDevice
9+
10+
11+
class ReportModule(SmartModule):
12+
"""Implementation of report module."""
13+
14+
REQUIRED_COMPONENT = "report_mode"
15+
QUERY_GETTER_NAME = "get_report_mode"
16+
17+
def __init__(self, device: "SmartDevice", module: str):
18+
super().__init__(device, module)
19+
self._add_feature(
20+
Feature(
21+
device,
22+
"Report interval",
23+
container=self,
24+
attribute_getter="report_interval",
25+
)
26+
)
27+
28+
@property
29+
def report_interval(self):
30+
"""Reporting interval of a sensor device."""
31+
return self._device.sys_info["report_interval"]

kasa/smart/modules/temperature.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""Implementation of temperature module."""
2+
from typing import TYPE_CHECKING, Literal
3+
4+
from ...feature import Feature, FeatureType
5+
from ..smartmodule import SmartModule
6+
7+
if TYPE_CHECKING:
8+
from ..smartdevice import SmartDevice
9+
10+
11+
class TemperatureSensor(SmartModule):
12+
"""Implementation of temperature module."""
13+
14+
REQUIRED_COMPONENT = "humidity"
15+
QUERY_GETTER_NAME = "get_comfort_temp_config"
16+
17+
def __init__(self, device: "SmartDevice", module: str):
18+
super().__init__(device, module)
19+
self._add_feature(
20+
Feature(
21+
device,
22+
"Temperature",
23+
container=self,
24+
attribute_getter="temperature",
25+
icon="mdi:thermometer",
26+
)
27+
)
28+
self._add_feature(
29+
Feature(
30+
device,
31+
"Temperature warning",
32+
container=self,
33+
attribute_getter="temperature_warning",
34+
type=FeatureType.BinarySensor,
35+
icon="mdi:alert",
36+
)
37+
)
38+
# TODO: use temperature_unit for feature creation
39+
40+
@property
41+
def temperature(self):
42+
"""Return current humidity in percentage."""
43+
return self._device.sys_info["current_temp"]
44+
45+
@property
46+
def temperature_warning(self) -> bool:
47+
"""Return True if humidity is outside of the wanted range."""
48+
return self._device.sys_info["current_temp_exception"] != 0
49+
50+
@property
51+
def temperature_unit(self):
52+
"""Return current temperature unit."""
53+
return self._device.sys_info["temp_unit"]
54+
55+
async def set_temperature_unit(self, unit: Literal["celsius", "fahrenheit"]):
56+
"""Set the device temperature unit."""
57+
return await self.call("set_temperature_unit", {"temp_unit": unit})

kasa/smart/smartbulb.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@
22
from typing import Any, Dict, List, Optional
33

44
from ..bulb import Bulb
5-
from ..device_type import DeviceType
6-
from ..deviceconfig import DeviceConfig
75
from ..exceptions import KasaException
86
from ..iot.iotbulb import HSV, BulbPreset, ColorTempRange
9-
from ..smartprotocol import SmartProtocol
107
from .smartdevice import SmartDevice
118

129
AVAILABLE_EFFECTS = {
@@ -21,16 +18,6 @@ class SmartBulb(SmartDevice, Bulb):
2118
Documentation TBD. See :class:`~kasa.iot.Bulb` for now.
2219
"""
2320

24-
def __init__(
25-
self,
26-
host: str,
27-
*,
28-
config: Optional[DeviceConfig] = None,
29-
protocol: Optional[SmartProtocol] = None,
30-
) -> None:
31-
super().__init__(host=host, config=config, protocol=protocol)
32-
self._device_type = DeviceType.Bulb
33-
3421
@property
3522
def is_color(self) -> bool:
3623
"""Whether the bulb supports color changes."""

kasa/smart/smartdevice.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,12 @@ async def _initialize_children(self):
6363
)
6464
for child_info in children
6565
}
66-
# TODO: if all are sockets, then we are a strip, and otherwise a hub?
67-
# doesn't work for the walldimmer with fancontrol...
68-
self._device_type = DeviceType.Strip
66+
# TODO: This may not be the best approach, but it allows distinguishing
67+
# between power strips and hubs for the time being.
68+
if all(child.is_plug for child in self._children.values()):
69+
self._device_type = DeviceType.Strip
70+
else:
71+
self._device_type = DeviceType.Hub
6972

7073
@property
7174
def children(self) -> Sequence["SmartDevice"]:
@@ -518,7 +521,7 @@ def device_type(self) -> DeviceType:
518521

519522
if self.children:
520523
if "SMART.TAPOHUB" in self.sys_info["type"]:
521-
pass # TODO: placeholder for future hub PR
524+
self._device_type = DeviceType.Hub
522525
else:
523526
self._device_type = DeviceType.Strip
524527
elif "light_strip" in self._components:

0 commit comments

Comments
 (0)