Repository navigation
IOT devices connected over KLAP are always built as IotPlug, never IotStrip — HS300 loses all child sockets #1748
Description
Activity
Credit where due — this was independently identified by @iamteedoh in #1604 three weeks ago: #1604 (comment)
I hadn't seen it when filing. I've opened this as its own issue because that analysis is a comment inside a 48-comment thread about the authentication bug, and this is a separate defect that survives #1731 — but I'm happy for this to be closed in favour of that comment if you'd rather keep it in one place.
Two things to add.
1. Second independent confirmation, on a different firmware build. @iamteedoh reported HS300 hw2.0; mine are HS300(US) hw2.0 on
1.1.2 Build 241220 Rel.171333. Three units here, all reproduced, all fixed by the same change — so at least it isn't unique to a single build.2.
Discover._get_device_instance()needs the same treatment, which my original post missed. Quoting @iamteedoh:Keying on the discovery model alone isn't enough. HA's coordinator reaches the device through a direct-connect path that carries no discovery info, so the model string is empty there.
sys_info["children"]is the reliable signal, and bothdevice_factory._connect()andDiscover._get_device_instance()need covering.I only patched
_connect(). That is sufficient for Home Assistant's setup path — all three strips came back with 18 entities, working per-outlet switching and per-outlet emeter — but a fix covering only_connect()would leave the discovery path still mis-typing strips asIotPlug.One more data point, in case it saves someone time: the Third-Party Integration workaround (re-opening port 9999 from the Kasa app) didn't work for me. The setting was already enabled on my account, and my three strips on
1.1.2 Build 241220still returnConnectionRefusedon tcp/9999, while another strip on the older1.0.21 Build 210524serves it normally. @beckerben's units were on1.1.6 Build 240130and it worked there, so I'd guess it varies by build — but that's only my two builds against theirs, so treat it as one report rather than a general conclusion.Update after 0.11.0.1: #1769 fixed the connect path, but discovery still builds these strips as
IotPlug.Tested on 0.11.0.1 with no local patches, against three HS300(US) hw 2.0 on fw
1.1.2 Build 241220(IOT.SMARTPLUGSWITCH/KLAP/login_version: 2):Path Class Transport Children Device.connect(config=...)IotStripKlapTransportV26 (per-outlet emeter works) Discover.discover()+update()IotPlugKlapTransportV20 So #1731 fixed authentication, and #1769 fixed
device_factory._connect.Discover._get_device_instance()still picks the class withget_device_class_from_family(type_, ...), whereIOT.SMARTPLUGSWITCHmaps toIotPlug. That path never sees sysinfo, so a strip found by discovery loses all its child sockets.update()now succeeds on it (on 0.10.2 it raised), so nothing signals that the class is wrong.Strips on fw
1.0.21answer over XOR and come out of both paths asIotStrip, as expected.The workaround we run is to rebuild the device after
update()whensys_info["children"]is non-empty, on the same protocol object:if isinstance(device, IotDevice) and not isinstance(device, IotStrip) and device.sys_info.get("children"): strip = IotStrip(device.host, protocol=device.protocol) await strip.update()
A fix upstream could do the same in the discovery flow, or have
_get_device_instancedefer the IOT class choice until sysinfo is available, as_connectnow does.
Summary
An HS300 power strip reached over KLAP is instantiated as
IotPluginstead ofIotStrip, so it exposes no child sockets — no per-outlet switching and no per-outlet emeter. The same strip on XOR firmware works correctly.This is separate from, and in addition to, the authentication issue in #1604 / PR #1731. With #1731 applied, the device authenticates but is still childless.
Environment
1.1.2 Build 241220 Rel.171333ConnectionRefusedon this firmware; tcp/80 serves KLAPIOT.SMARTPLUGSWITCH/KLAP/login_version: 2Observed
On the KLAP strips the parent
switchstaysunavailablewhile strip-level sensors report real values (voltage, consumption), so communication is fine — only the child devices are missing.Cause
kasa/device_factory.py::_connect()gates sysinfo-based class detection onXorTransport:get_device_class_from_sys_info()is the only path that inspects the device's ownsysinfoand can therefore returnDeviceType.Strip->IotStrip. AnIotProtocolrunning overKlapTransportV2skips that branch and falls through toget_device_class_from_family(), where:is unconditional and has no concept of children. So any IoT device that reaches over KLAP loses its children, regardless of what it actually is.
Suggested fix
Drop the transport check —
GET_SYSINFO_QUERYworks over KLAP:Verified
Applied to three HS300s on KLAP firmware. All three went from 11 entities / 0 outlets to 18 entities with 6 switchable outlets and per-outlet emeter. No regression on XOR devices, which already take that branch.
Happy to open a PR if the approach looks right — there may be a preferred way to handle non-IoT
IotProtocoltransports (e.g.LinkieTransportV2for IOT cameras) that I haven't considered.