nx_iot_local_kasa.nx
buildroot/runtime/nx_iot_local_kasa.nx
about
nx_iot_local_kasa.nx -- TP-Link Kasa (TP-Link Smart Home) LAN protocol.
Roadmap: NISHI_IOT_HUB_ROADMAP.md Epoch 2 (SPEAK). The 5th vendor
kind generator, added 2026-06-18 because the device found blinking
on the operator's LAN is a Kasa HS210 (open SoftAP TP-LINK_HS210_C209,
BSSID d8:0d:17:a1:c2:09). Siblings: nx_iot_local_tuya_v33,
nx_iot_local_magic_home, nx_iot_local_wiz, nx_iot_local_yeelight.
S-class invariants: S0 (bit-equal encode), S6 (no cloud -- pure LAN),
S7 (sealed-enum verdicts), S8 (kind-isolated -- imports no sibling).
Wire format (community RE per python-kasa / tplink-smarthome-api;
RESEARCH_CATALOG_ONLY route -> INDEPENDENT_REDERIVE):
Transport: TCP/9999 (length-prefixed) or UDP/9999 (no prefix)
Crypto: "autokey" XOR stream -- NOT real crypto, just obfuscation.
key starts at 0xAB (171); cipher_byte = plain XOR key;
the cipher byte THEN becomes the next key (autokey).
Decrypt mirrors it: plain = cipher XOR key; key = cipher.
Framing: TCP prepends a 4-byte big-endian length of the cipher
payload. UDP omits the length.
Format: JSON commands.
On/off (HS210 switch, HS100/110 plug):
{"system":{"set_relay_state":{"state":1}}}
Discovery / capability probe:
{"system":{"get_sysinfo":{}}}
Colour bulb (KL130 etc.) -- hue/sat/brightness/colour-temp:
{"smartlife.iot.smartbulb.lightingservice":{"transition_light_state":
{"on_off":1,"hue":120,"saturation":100,"brightness":75,
"color_temp":0,"transition_period":0}}}
WHY a Kasa driver matters to the operator's outcome:
The HS210 that keeps dropping is Kasa. Owning the LAN protocol
means the hub controls it WITHOUT the Kasa cloud (which is the
round-trip that introduces the disconnect/blink variance), and the
capability builders below give the "change colour + light intensity"
control the operator asked for, locally + deterministically.
VERIFICATION (per the project verification doctrine -- this is a
dependencies 0 imports · 4 importers
imports: none
imported by: nx_iot_ctl_lib.nxnx_iot_discover.nxnx_iot_kasa_probe.nxnx_iot_local_kasa_test.nx
structs
| none |
consts
| 99 | const NX_IOT_KASA_VERDICT_UNKNOWN: i64 = 0 |
| 100 | const NX_IOT_KASA_VERDICT_OK: i64 = 1 |
| 101 | const NX_IOT_KASA_VERDICT_BUF_TOO_SMALL: i64 = 2 |
| 102 | const NX_IOT_KASA_VERDICT_BAD_LEN: i64 = 3 |
| 103 | const NX_IOT_KASA_VERDICT_BAD_ARG: i64 = 4 |
| 104 | const NX_IOT_KASA_VERDICT_N: i64 = 5 |
| 114 | const NX_IOT_KASA_PORT: i64 = 9999 |
| 115 | const NX_IOT_KASA_INIT_KEY: i64 = 0xAB |
functions
| 106 | func nx_iot_kasa_verdict_is_valid(v: i64) -> i64 called by 1: main |
| 122 | func nx_iot_kasa_encrypt(plain: *u8, n: i64, out: *u8) -> i64 |
| 134 | func nx_iot_kasa_decrypt(cipher: *u8, n: i64, out: *u8) -> i64 |
| 149 | func nx_iot_kasa_put_u32_be(buf: *u8, off: i64, v: i64) -> i64 |
| 157 | func nx_iot_kasa_get_u32_be(buf: *u8, off: i64) -> i64 |
| 167 | func nx_iot_kasa_strlen(s: *u8) -> i64 called by 1: nx_iot_kasa_looks_like_sysinfo |
| 178 | func nx_iot_kasa_puts_z(out: *u8, off: i64, s: *u8) -> i64 |
| 193 | func nx_iot_kasa_itoa(out: *u8, off: i64, v: i64) -> i64 |
| 217 | func nx_iot_kasa_find(buf: *u8, len: i64, pat: *u8, pat_len: i64) -> i64 |
| 245 | func nx_iot_kasa_pt_relay(out: *u8, out_cap: i64, on: i64) -> i64 |
| 256 | func nx_iot_kasa_pt_sysinfo(out: *u8, out_cap: i64) -> i64 |
| 262 | func nx_iot_kasa_pt_light(out: *u8, out_cap: i64, on: i64, |
| 301 | func nx_iot_kasa_puts_json_escaped(out: *u8, off: i64, s: *u8, n: i64) -> i64 called by 1: nx_iot_kasa_pt_set_stainfo |
| 325 | func nx_iot_kasa_pt_set_stainfo(out: *u8, out_cap: i64, |
| 347 | func nx_iot_kasa_frame_tcp(plain: *u8, n: i64, out: *u8, out_cap: i64, |
| 366 | func nx_iot_kasa_deframe_tcp(frame: *u8, frame_n: i64, out: *u8, out_cap: i64, |
| 398 | func nx_iot_kasa_looks_like_sysinfo(plain: *u8, n: i64) -> i64 called by 5: mainiotc_emit_probemainnx_iot_kasa_probe_sysinfomain calls 2: nx_iot_kasa_findnx_iot_kasa_strlen |