Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
17c22fb
Implement KlapTransportV3 for new_klap
ZeliardM Sep 29, 2025
1dea040
Fix test coverages
ZeliardM Sep 29, 2025
047efc4
Fix deviceconfig test coverage
ZeliardM Sep 29, 2025
81885d3
Fix new_klap with deviceconfig and try_connect_all
ZeliardM Sep 29, 2025
ffa5960
Remove KlapTransportV3 and correct new_klap discovery
ZeliardM Oct 2, 2025
2eb3977
Fix copilot assertation comments
ZeliardM Oct 2, 2025
2b6e35b
Reverse janitoring for separate PR
ZeliardM Oct 3, 2025
11c2143
Add comment for new_klap XOR transport selection
ZeliardM Oct 3, 2025
8229dee
Change new_klap transport from XOR to KlapTransportV2
ZeliardM Oct 7, 2025
5547172
Return dump_devinfo to exclude system
ZeliardM Oct 7, 2025
0b75c26
Change device creation to async to handle new_klap and tests
ZeliardM Oct 7, 2025
12d97cc
Correct test_discovery.py to handle all code changes
ZeliardM Oct 7, 2025
0390356
Fix test parameters
ZeliardM Oct 7, 2025
3857d65
Change new_klap to boolean
ZeliardM Oct 12, 2025
be34a58
Merge branch 'python-kasa:master' into feature/new-klap
ZeliardM Oct 18, 2025
496cbb6
Quick test fixes
ZeliardM Oct 31, 2025
86349ed
Merge branch 'python-kasa:master' into feature/new-klap
ZeliardM Nov 4, 2025
e9fc5dc
Update discover and tests for new fixtures
ZeliardM Nov 7, 2025
5a6aa03
Add CLI encrypt_type KLAPV2
ZeliardM Nov 12, 2025
d3add0b
Merge remote-tracking branch 'upstream/master' into feature/new-klap
ZeliardM Nov 13, 2025
2e051e4
Update device_models for L430P Bulb
ZeliardM Nov 13, 2025
b9d96e9
Fix tests and KLAPV2 encrypt-type call
ZeliardM Dec 9, 2025
87b6741
Merge branch 'python-kasa:master' into feature/new-klap
ZeliardM Dec 9, 2025
06cc424
Merge branch 'master' into feature/new-klap
ZeliardM Jan 27, 2026
9e75565
Update device_models for new supported devices
ZeliardM Jan 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ The following devices have been tested and confirmed as working. If your device
### Supported Kasa devices

- **Plugs**: EP10, EP25[^2], HS100[^2], HS103, HS105, HS110, KP100, KP105, KP115, KP125, KP125M[^1], KP401
- **Power Strips**: EP40, EP40M[^1], HS107, HS300, KP200, KP303, KP400
- **Power Strips**: EP40, EP40M[^1], HS107, HS300[^2], KP200, KP303, KP400
- **Wall Switches**: ES20M, HS200[^2], HS210, HS220[^2], KP405, KS200, KS200M, KS205[^1], KS220, KS220M, KS225[^1], KS230, KS240[^1]
- **Bulbs**: KL110, KL120, KL125, KL130, KL135, KL50, KL60, LB100, LB110
- **Light Strips**: KL400L10, KL400L5, KL420L5, KL430
Expand Down
1 change: 1 addition & 0 deletions SUPPORTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Some newer Kasa devices require authentication. These are marked with [^1] in th
- Hardware: 1.0 (US) / Firmware: 1.0.21
- Hardware: 2.0 (US) / Firmware: 1.0.12
- Hardware: 2.0 (US) / Firmware: 1.0.3
- Hardware: 2.0 (US) / Firmware: 1.1.2[^1]
- **KP200**
- Hardware: 3.0 (US) / Firmware: 1.0.3
- **KP303**
Expand Down
1 change: 1 addition & 0 deletions devtools/dump_devinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ def capture_raw(discovered: DiscoveredRaw):
login_version=dr.mgt_encrypt_schm.lv,
https=dr.mgt_encrypt_schm.is_support_https,
http_port=dr.mgt_encrypt_schm.http_port,
new_klap=dr.mgt_encrypt_schm.new_klap,
)
dc = DeviceConfig(
host=host,
Expand Down
7 changes: 7 additions & 0 deletions kasa/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
]

ENCRYPT_TYPES = [encrypt_type.value for encrypt_type in DeviceEncryptionType]
ENCRYPT_TYPES.append("KLAPV2")
DEFAULT_TARGET = "255.255.255.255"


Expand Down Expand Up @@ -326,11 +327,17 @@ async def cli(
if not encrypt_type:
encrypt_type = "KLAP"

new_klap = None
if encrypt_type and encrypt_type == "KLAPV2":
encrypt_type = "KLAP"
new_klap = True

ctype = DeviceConnectionParameters(
DeviceFamily(device_family),
DeviceEncryptionType(encrypt_type),
login_version,
https,
new_klap=new_klap,
)
config = DeviceConfig(
host=host,
Expand Down
35 changes: 34 additions & 1 deletion kasa/device_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
from typing import Any

from .device import Device
from .device_models import (
BULBS_IOT,
BULBS_IOT_LIGHT_STRIP,
DIMMERS_IOT,
PLUGS_IOT,
STRIPS_IOT,
SWITCHES_IOT,
)
from .device_type import DeviceType
from .deviceconfig import DeviceConfig, DeviceEncryptionType, DeviceFamily
from .exceptions import KasaException, UnsupportedDeviceError
Expand Down Expand Up @@ -118,6 +126,10 @@ def _perf_log(has_params: bool, perf_type: str) -> None:
elif device_class := get_device_class_from_family(
config.connection_type.device_family.value, https=config.connection_type.https
):
if issubclass(device_class, IotDevice):
info = await protocol.query(GET_SYSINFO_QUERY)
_perf_log(True, "get_sysinfo")
device_class = get_device_class_from_sys_info(info)
device = device_class(host=config.host, protocol=protocol)
await device.update()
_perf_log(True, "update")
Expand Down Expand Up @@ -146,7 +158,11 @@ def get_device_class_from_sys_info(sysinfo: dict[str, Any]) -> type[IotDevice]:


def get_device_class_from_family(
device_type: str, *, https: bool, require_exact: bool = False
device_type: str,
*,
https: bool,
require_exact: bool = False,
device_model: str | None = None,
) -> type[Device] | None:
"""Return the device class from the type name."""
supported_device_types: dict[str, type[Device]] = {
Expand Down Expand Up @@ -175,6 +191,21 @@ def get_device_class_from_family(
_LOGGER.debug("Unknown SMART device with %s, using SmartDevice", device_type)
cls = SmartDevice

if cls is not None and issubclass(cls, IotDevice) and device_model is not None:
device_model = device_model.split("(")[0]
if device_model in BULBS_IOT_LIGHT_STRIP:
cls = IotLightStrip
elif device_model in BULBS_IOT:
cls = IotBulb
elif device_model in PLUGS_IOT:
cls = IotPlug
elif device_model in SWITCHES_IOT:
cls = IotWallSwitch
elif device_model in STRIPS_IOT:
cls = IotStrip
elif device_model in DIMMERS_IOT:
cls = IotDimmer

if cls is not None:
_LOGGER.debug("Using %s for %s", cls.__name__, device_type)

Expand Down Expand Up @@ -221,6 +252,7 @@ def get_protocol(config: DeviceConfig, *, strict: bool = False) -> BaseProtocol
+ "."
+ ctype.encryption_type.value
+ (".HTTPS" if ctype.https else "")
+ (".NEW_KLAP" if ctype.new_klap else "")
)

_LOGGER.debug("Finding transport for %s", protocol_transport_key)
Expand All @@ -229,6 +261,7 @@ def get_protocol(config: DeviceConfig, *, strict: bool = False) -> BaseProtocol
] = {
"IOT.XOR": (IotProtocol, XorTransport),
"IOT.KLAP": (IotProtocol, KlapTransport),
"IOT.KLAP.NEW_KLAP": (IotProtocol, KlapTransportV2),
"SMART.AES": (SmartProtocol, AesTransport),
"SMART.KLAP": (SmartProtocol, KlapTransportV2),
"SMART.KLAP.HTTPS": (SmartProtocol, KlapTransportV2),
Expand Down
164 changes: 164 additions & 0 deletions kasa/device_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
"""Device model sets for class resolution and testing."""

BULBS_IOT_LIGHT_STRIP = {"KL400L5", "KL400L10", "KL430", "KL420L5"}
BULBS_IOT_VARIABLE_TEMP = {
"LB120",
"LB130",
"KL120",
"KL125",
"KL130",
"KL135",
"KL430",
}
BULBS_IOT_COLOR = {"LB130", "KL125", "KL130", "KL135", *BULBS_IOT_LIGHT_STRIP}
BULBS_IOT_DIMMABLE = {"KL50", "KL60", "LB100", "LB110", "KL110"}
BULBS_IOT = (
BULBS_IOT_VARIABLE_TEMP.union(BULBS_IOT_COLOR)
.union(BULBS_IOT_DIMMABLE)
.union(BULBS_IOT_LIGHT_STRIP)
)

BULBS_SMART_LIGHT_STRIP = {"L900-5", "L900-10", "L920-5", "L930-5"}
BULBS_SMART_VARIABLE_TEMP = {"L430C", "L430P", "L530E", "L535E", "L930-5"}
BULBS_SMART_COLOR = {"L430C", "L430P", "L530E", "L535E", *BULBS_SMART_LIGHT_STRIP}
BULBS_SMART_DIMMABLE = {"L510B", "L510E"}
BULBS_SMART = (
BULBS_SMART_VARIABLE_TEMP.union(BULBS_SMART_COLOR)
.union(BULBS_SMART_DIMMABLE)
.union(BULBS_SMART_LIGHT_STRIP)
)

BULBS_COLOR = {*BULBS_IOT_COLOR, *BULBS_SMART_COLOR}
BULBS_VARIABLE_TEMP = {*BULBS_IOT_VARIABLE_TEMP, *BULBS_SMART_VARIABLE_TEMP}

BULBS = {
*BULBS_IOT,
*BULBS_SMART,
}

LIGHT_STRIPS = {*BULBS_IOT_LIGHT_STRIP, *BULBS_SMART_LIGHT_STRIP}


PLUGS_IOT = {
"HS100",
"HS103",
"HS105",
"HS110",
"EP10",
"EP25",
"KP100",
"KP105",
"KP115",
"KP125",
"KP401",
}

PLUGS_SMART = {
"P105",
"P100",
"P110",
"P110M",
"P115",
"KP125M",
"EP25",
"P125M",
"TP10",
"TP15",
}

PLUGS = {
*PLUGS_IOT,
*PLUGS_SMART,
}


SWITCHES_IOT = {
"HS200",
"HS210",
"KS200",
"KS200M",
}

SWITCHES_SMART = {
"HS200",
"KS205",
"KS225",
"KS240",
"S500",
"S500D",
"S505",
"S505D",
"TS15",
}

SWITCHES = {*SWITCHES_IOT, *SWITCHES_SMART}


STRIPS_IOT = {"HS107", "HS300", "KP303", "KP200", "KP400", "EP40"}

STRIPS_SMART = {"P300", "P304M", "TP25", "EP40M", "P210M", "P306", "P316M"}

STRIPS = {*STRIPS_IOT, *STRIPS_SMART}


DIMMERS_IOT = {"ES20M", "HS220", "KS220", "KS220M", "KS230", "KP405"}

DIMMERS_SMART = {"HS220", "KS225", "S500D", "P135"}

DIMMERS = {
*DIMMERS_IOT,
*DIMMERS_SMART,
}


HUBS_SMART = {"H100", "KH100"}


SENSORS_SMART = {
"T310",
"T315",
"T300",
"T100",
"T110",
"S200B",
"S200D",
"S210",
"S220",
"D100C",
}


THERMOSTATS_SMART = {"KE100"}


VACUUMS_SMART = {"RV20"}


WITH_EMETER_IOT = {"EP25", "HS110", "HS300", "KP115", "KP125", *BULBS_IOT}

WITH_EMETER_SMART = {"P110", "P110M", "P115", "KP125M", "EP25", "P304M"}

WITH_EMETER = {*WITH_EMETER_IOT, *WITH_EMETER_SMART}


DIMMABLE = {*BULBS, *DIMMERS}


ALL_DEVICES_IOT = (
BULBS_IOT.union(PLUGS_IOT).union(STRIPS_IOT).union(DIMMERS_IOT).union(SWITCHES_IOT)
)


ALL_DEVICES_SMART = (
BULBS_SMART.union(PLUGS_SMART)
.union(STRIPS_SMART)
.union(DIMMERS_SMART)
.union(HUBS_SMART)
.union(SENSORS_SMART)
.union(SWITCHES_SMART)
.union(THERMOSTATS_SMART)
.union(VACUUMS_SMART)
)


ALL_DEVICES = ALL_DEVICES_IOT.union(ALL_DEVICES_SMART)
3 changes: 3 additions & 0 deletions kasa/deviceconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class DeviceConnectionParameters(_DeviceConfigBaseMixin):
login_version: int | None = None
https: bool = False
http_port: int | None = None
new_klap: bool | None = None

@staticmethod
def from_values(
Expand All @@ -110,6 +111,7 @@ def from_values(
login_version: int | None = None,
https: bool | None = None,
http_port: int | None = None,
new_klap: bool | None = None,
) -> DeviceConnectionParameters:
"""Return connection parameters from string values."""
try:
Expand All @@ -121,6 +123,7 @@ def from_values(
login_version,
https,
http_port=http_port,
new_klap=new_klap,
)
except (ValueError, TypeError) as ex:
raise KasaException(
Expand Down
Loading
Loading