Support enrolling newer RTL8720cm/CLIP firmware (protocolVer 4.9): publicKey whitespace fix + generic device fallback - #81
Conversation
Today an unknown modelId is dropped (logs 'device type ... unknown' and returns), so a brand-new appliance that provisions perfectly still never enrolls in HA, gets no answer from the cloud side, and you get zero captured data to build a device class from — the chicken-and-egg that keeps new models unsupported. Add a generic 'loose capture' fallback: an unknown thinq2 device enrols via a minimal class that completes onboarding (so it stays connected) and republishes every raw packet's hex to a diagnostic HA sensor. Adding real support for a new model then becomes a decode exercise against live data instead of guesswork. Claude-Session: https://claude.ai/code/session_01LGQAZq7ycoMjk4WdckeAQZ
Ties together the two code changes (publicKey whitespace, generic fallback) and documents the findings that don't need code here: the legacy-TLS profile (issues anszom#17/anszom#18), the post-provision power-cycle this firmware needs to settle, and the things that were NOT the problem (fake OTP, shared publicKey, minimal /route body, svcphase) so nobody re-chases them. Claude-Session: https://claude.ai/code/session_01LGQAZq7ycoMjk4WdckeAQZ
|
Good find! It's likely that the whitespace issue might have been the root cause for #58. I'll need some time to work through the backlog of PRs and issues, so I'll make more comments later. |
In some old notes I've found mention that a "resetDevice" command does exactly that. You can try it on your module. |
| encrypt_val: '' | ||
| extra: 'POWER_ON|...|encryptRes:ffff' # the device's RSA-encrypt step failed | ||
| ``` | ||
| The device then can't build a cert request, reboots onto Wi-Fi, polls `/route` a few times, sends a |
|
|
||
| ## 4. Post-provision: power-cycle the appliance once | ||
|
|
||
| After `releaseDev` the device reboots itself, but on this firmware it then sits in "connecting…", |
There was a problem hiding this comment.
This did not occured on my machine, it stayed connected
|
Worked on my device, x-service-phase was set to QA, version 2.11.306 of the modem clip_ble_v1.9.215. I would suggest checking encrypt_val in provisioning flow and aborting if it is empty string, will be easier to catch failures. I am not sure about reboot after provisioning, my device did not needed it. logs are at #58 (comment) |
|
The Home Assistant instance does not seem to have much use for such "placeholder" device - why not just leave the packets streaming to This will also prevent polluting HA dashboard or history. |
Indeed this is the first device that appears to require this. But if it works, then maybe we should simply send the reboot command to every device for maximum compatibility. |
Agreed. I don't think mapping the raw packets to HomeAssistant would provide any advantages. Raw packets can also be observed through the management web UI & its underlying websocket. |
|
My intention for tracking raw packets was to collect as much variety of history as possible (over a week or two of operations) which can then be dumped as a full body of data all at once. For me, maintaining multiple endpoints is inconvenient in a home network. I performed some testing this morning and if x-service-phase is set to QA, enrollment of the dryer fails repeatedly, until set to OP. The newer firmware seems to want qic-qa-* routing and reject common.lgthinq.com (4.9+) |
|
Just thinking out loud here - if someone intentionally or unknowingly enrolls a machine that's not supported, I think it would be a much better experience if the machine can at least be captured (a phase one, if you will) and marked as not supported (...yet 😉) (the intention of the code changes) which means that the traces can be exported. As new versions of the backend get opcode translation, then the unrecognized opcodes are marked as supported and data arrives in HA. It seems like a lot of extra steps to just discard opcodes. Anyway, that's why I marked this as a draft PR with a teach/ header - no reason to pull any of this in if it doesn't fit the architecture you have in mind. |
|
Confirming this fixes enrollment for LG wall-mount AC units with the same firmware generation. Device info: Symptom before fix — {
"cmd": "getDeviceInfo",
"data": {
"protocolVer": "4.9",
"deviceType": "401",
"modelName": "RAC_0B0001_WW",
"modemVer": "clip_ble_v1.9.223",
"demandType": "RTK_RTL8720cm",
"isReg": "N",
"countryCode": "DE",
"encrypt_val": "",
"extra": "POWER_ON|...|encryptRes:ffff",
"regStep": 0
}
}
After applying the fix ( Additional finding: devices connect to port 443, not 4433 After provisioning, the device connects back on port 443. tcpdump before setting DNS resolves correctly, then immediately SYNs to After both fixes — rethink logs confirming device connection: Device streams Note: the |
So I tried to just use the changes from first commit here, and the device starting provisioning, but then went offline after a few minutes. After using the changes from both the first and second commits here, the device has stayed online overnight. Which is to say, I'm not sure how much the reverse engineering you can do without the device staying online for more than a few minutes. Maybe I did something wrong the first time, or maybe there's something else going on with these RTL8720cm devices where they go offline after a few minutes that the other devices don't do (and so that's why it seems like it needs to be fully provisioned into Home Assistant). But maybe there's a way to finish provisioning enough that the device can communicate with |
Can you repeat the test? As far as I can tell from reading the sources, the device should not see any difference whether we expose its packets to HA or not. |
|
Hmm, sorry if this sounds dense, but what exactly are you looking for me to do? I assume the only way to recreate the conditions would be to factory reset the device. Because anything else will be just like the second time I tried to provision it from the changes in this PR, no? Or am I misunderstanding something? |
|
I actually wonder a Wi-Fi reset is equivalent to the manual power cycle that was mentioned above, and it's happenstance that I did that to get it to stay provisioned. Like, they're not the same thing, but maybe they have the same effect? |
This is the behavior my devices had. In my situation I just unplugged the dryer and plugged it back in while it's status was "finishing wifi setup" (and it was reachable on network via icmp)...after the fixes for the cert newline things and changed out of qa mode. Once there, a quick power cycle the dryer came directly up on wifi and began sending mqtt data. |
Then maybe that's all I had to do, and using the other commits was a red herring. I.e. it didn't matter that I added the other commits, what mattered that I re-provisioned after the initial provision. |
|
As far as moving forward with the fix, should the first commit be split out so it can be merged and the RTL8720cm devices can be provisioned again with |
|
Confirming that part of this PR fixed my issue attempting to connect my microwave to rethink. I'm just trying to intercept log and reverse engineer things, so getting the device provisioned successfully was my primary blocker so far. Device type: WMVEL2137 Part of this PR should get merged because it's actually fixing a critical bug. I support splitting the commit to a simpler narrow scoped change to just get the provisioning working again so others can add devices without issue as well. Right now, adding newer chipsets is completely broken. |
|
Successfully tested this PR with an LG WMVEL2137 microwave/hood (DeviceType 302, RTL8720cm CLIP, firmware clip_ble_v1.9.210, protocolVer 6, softVer 20230920). Everything confirmed to work:
Additional findings from the bridge mode handshake: Beyond the preDeploy/completeProvisioning that local rethink handles, the bridge showed two things needed to exit setup cleanly:
After these, the microwave chimed and exited setup mode. Device details captured:
TLV packets observed (raw hex):
Thanks for the detailed writeup in |
|
Update: Local-only provisioning now works end-to-end, no bridge mode needed. After analyzing the bridge mode handshake, I found the device needed 1. - provisioningType: payload.cmd,
+ provisioningType: 'deploy',The device sends 2. - if (payload.cmd === 'req_timesync' && client.deviceObj && ...)
+ if (payload.cmd === 'req_timesync' && client.deployMsg && ...)The device sends 3. Result: factory reset → provision → device completes full handshake locally → chime → exits setup. Bridge mode can then optionally register with LG cloud (already works with the With the publicKey whitespace fix, generic fallback, raw logging, and local handshake, this is now a complete pipeline for bringing up an unsupported RTL8720cm device entirely locally. |
|
I've taken the liberty to split this into three parts:
|
I'd guess that this may not be related to the domain being exactly the same. Official LG servers use various hostnames for various services. My bet is that rethink.lan is not resolvable via DNS (mDNS maybe?), or has a zero TTL.
I've tried this approach before, but it's not reliable. CloudFront changes IP assignments without notice, and you end up with a dead entry.
I will need to test this - at least with my own AC, to verify that the changes are portable across devices. |
Maybe make it a configuration mode of the server; discovery/learning/verbose mode and a "production" ode where the flag turns off raw forwarding. Raw mode has been helpful to snoop cycle data on the washer and dryer. |
Let me put another vote for forwarding the raw data. I'm trying to add support for a dishwasher. It sometimes has multiple hour long cycles. I can't sit at the computer watching the There's also things like the "refill rinse aid" light that will only come on after so many cycles. I live in a house with other people that run the dishwasher. So, I cannot watch I think for certain devices, you probably could use |
|
And FWIW, I've had this forwarding the raw packets to Home Assistant for the past couple of weeks, and it hasn't ballooned my Home Assistant database any more than normal. I think the data doesn't persist to long-term storage, because I can't see anything older than 7 days. But I don't really know how persistence works in Home Assistant. I just see the (lack of) difference in backup size, and infer that it's not making that much difference. But maybe I'm not looking at the right thing. |
|
To be clear, even if we end up adding an ability to forward raw packets from an (un)known device to a HA sensor I am still against enabling this functionality by default. Some "device-handler-developer" config file switch would probably be okay though. |
Before you read
I wasn't sure how to introduce this into the project, because in order to enroll the machine, I had to broaden some assumptions built into the code that rethink has now - for instance, greedy device enroll vs selective, QA mode in headers, geo regions, crypto pairing, cert storage tics, etc. It's also not finished - I had to cycle the power on the dryer after enrollment was finished to put it in the active state, because I don't know what command to send back to issue a reset. Anyway, it's here for reference, not necessarily to pull in.
What & why
Brings up a current-gen LG appliance — an
BDH_D30007_USdryer (DeviceType 202, RTK_RTL8720cm"CLIP" module,
protocolVer 4.9, sw 2.11.263) — fully local on rethink. It now completes SoftAP →/route→ cert → MQTT clip, enrolls, and streams telemetry with zero LG cloud contact (verifiedacross a full ~50-minute dryer cycle: one MQTT connection, 0 undeploys, ~960 telemetry packets).
Two focused code changes + a docs page. Each commit is self-explaining.
1.
rethink-setup: strip whitespace from the setuppublicKey(the real blocker)The hardcoded setup
publicKeyis a tab-indented template literal, so every base64 line carries aleading TAB inside the PEM. Older firmware tolerates this; the RTL8720cm CLIP parser is strict —
with the indentation,
getDeviceInfo's RSA-encrypt step fails:…so the device can't build a cert request and loops on
/routeforever — which looks like a/routebug but isn't. A clean PEM (same key bytes, base64 at column 0) →
encryptRes:0, a validencrypt_val, and it proceeds straight through/route/certificate→ cert → MQTT.2.
ha_bridge: generic raw-capture fallback for unknown device typesToday an unknown
modelIdis dropped (device type ... unknown→ return), so a brand-new appliancecan provision perfectly and still never enroll — and you get no captured data to build a class from.
The fallback enrolls any unknown thinq2 device via a minimal class that completes onboarding (so it
stays connected) and republishes raw packet hex to a diagnostic HA sensor. Adding real support then
becomes a decode exercise against live data instead of guesswork.
3.
docs/enrolling-newer-firmware.mdTies it together and documents the findings that don't need code here: the legacy-TLS profile (your
issues #17/#18), the post-provision power-cycle this firmware needs to settle, and the things that
were NOT the problem (fake OTP, shared
publicKey, minimal/routebody,svcphase) so nobodyre-chases them.
Deliberately excluded / honest caveats
/routebody, or TLS defaults here — kept focused. The OTP and theminimal
/routebody work as-is for this firmware; the legacy-TLS profile is your Support newer OpenSSL versions #17/Handshake failed #18.svcphase: 'OP'(vs the debug-UART'QA'default). We could notisolate whether OP is required for this firmware, so we left your default alone — flagging in
case it matters for other newer modules.
reset exists is an open question.
What I'd value your input on
specific to RTL8720cm?
I can share full packet captures + decrypted MQTT clip transcripts (SoftAP setup,
/route, cert,deploy/completeProvisioning/_ack, telemetry). Device didfe8b2ea0-…-3034dbd055fe.This was obviously drafted with Claude code and my edits and steering. Further work I won't continue to disclose that, assume it is co-written or just me replying like a caveman or something.