Confirmed on hardware: 15s idle while remote-enabled with the heater on and powered auto-suspends (R? and S? both drop to 0). P? does not read literal 0 though - it lands on 4095, the raw register's max value past the calibrated curve's low end, not the digit 0. Any check expecting P?=="0" to confirm zero power would be wrong on this firmware; S? is the correct check. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GpePKZiEZWbGo9HrfuML6U
5.3 KiB
Hendi remote-control lockout findings
Reproduced and verified on real hardware using scripts/hendi_ctrl_app.py's
--trigger-error-state, --reset and --test-heater flags, across four
firmware versions (getSoftwareVersion() / I?+V? via
components/actor/hendiCtrl.py).
Trigger
Calling remoteEnable(True) (R1) and then disconnecting/exiting without a
matching remoteEnable(False) (R0) - i.e. an ungraceful disconnect while
remote control is enabled - puts the device into a locked-out state.
Recovery attempts - v1.12 vs v1.14 vs v1.15 vs v1.16
v1.14 flashed via --update-firmware HendiCtrl_0114.srec; v1.15 via
HendiCtrl_0115.srec; v1.16 via HendiCtrl_0116.srec. getSoftwareVersion()
confirmed each version after flashing.
| Recovery method | Mechanism | v1.12 (original) | v1.14 (firmware fix) | v1.15 (firmware fix) | v1.16 (firmware fix) |
|---|---|---|---|---|---|
Plain reconnect, no --reset |
Fresh serial connection only, no DTR/RTS toggle | Fails | Fails - unchanged, a bare reconnect alone still does not clear the lockout | Intermittent - recovered on 1 of 3 isolated trigger/reconnect cycles, failed the other 2; not a reliable recovery path | Recovers - reproducible across 4 of 4 independent trigger/reconnect/verify cycles |
--reset, isolated process |
Soft reset via DTR/RTS toggle, no power loss | Fails (one fluke recovery when combined with --test-heater in the same process/connection - not reproducible in isolation) |
Recovers - reproducible across two independent trigger/reset/verify cycles | Recovers - reproducible across all trigger/reset/verify cycles run | Not retested (superseded by plain reconnect working) |
| Physical power cycle (device knob) | Full power-off/on of the unit | Recovers | Not retested (superseded by --reset working) |
Not retested (superseded by --reset working) |
Not retested (superseded by plain reconnect working) |
10s idle-timeout (remote -> suspend)
Tested on v1.16: with remote enabled (R1), the heater switched on (S1)
and a non-zero power applied (P3151, ~1000W), going idle for 15s with no
further commands sent causes the device to auto-suspend:
| Register | Before idle (R1/S1/P3151) |
After 15s idle |
|---|---|---|
R? (remote enabled) |
1 |
0 - timer drops remote back to suspend |
S? (switch) |
1 |
0 - heater switched off |
P? (power digits) |
3151 (~1000W) |
4095 - see note below |
P? does not read the literal digit 0 after the timeout. The
power/digit mapping is inverted (components/actor/hendiCtrl.py's
poly_w2d/poly_d2w: higher digit = lower watts, calibrated down to
pwr_digits_min = 4092 for the lowest calibrated point, 500W). 4095 is
the raw 12-bit register's maximum value (2^12 - 1), one past the
lowest-calibrated end of that curve (toWatts(4095) extrapolates to
~518W) - i.e. the hardware's actual "off" register value, not the number
0. Any check that expects P? to equal "0" to confirm the heater has
zero power would be wrong on this firmware; the correct check is S? (or
P? >= pwr_digits_min).
Device remained healthy afterward - no lockout was triggered, since remote
was already back to 0 (via the timer) before the test script's connection
closed.
Symptom while locked out
I?/V?(software identifier/version queries) still respond normally.R1(remoteEnable(True)) is the only rejected command; it blocks any command path that needs remote control enabled (e.g.--test-heater).R0(remoteEnable(False)) still succeeds (OK:0) even while locked out.- With
hendi_ctrl_app.py --debug(raw protocol trace added tocomponents/actor/hendiCtrl.py'scmd()), the actual wire exchange for a rejectedR1on v1.15 is:So this isn't a timeout or garbled response - the firmware actively replies withreq='R1' echo=b'R1\r\n' answer=b'\rERR:Invalid state\r\n'ERR:Invalid state.HendiCtrl.cmd()previously only checked for"OK"in the response and raised a genericHendiException("Communication error")for anything else, discarding this actual error text; it now includes the raw echo/answer in the exception message.
Conclusion
On firmware v1.12, only a physical power cycle clears the lockout; neither a serial soft-reset nor a fresh serial reconnect does reliably.
Firmware v1.14 fixes this partially: a soft reset (--reset, i.e.
toggling the DTR/RTS line) now reliably clears the lockout, confirmed across
two independent trigger/reset/verify cycles - no physical power cycle
needed. However, a plain reconnect without --reset still does not clear
it, so the fix specifically ties recovery to the DTR/RTS reset line rather
than to any new connection.
Firmware v1.15 keeps --reset working reliably, same as v1.14. It also
introduces an intermittent plain-reconnect recovery (1 of 3 isolated trials)
that v1.14 didn't show at all - but since it fails the majority of the time,
it should be treated as a remaining bug/race condition rather than a fix,
and --reset is still the recommended recovery path.
Firmware v1.16 appears to fully fix the lockout: a plain reconnect with
no --reset at all recovered on 4 of 4 independent trigger/reconnect/verify
cycles - the intermittent v1.15 behavior looks to have become fully
reliable. --reset was not retested since it's no longer needed as a
workaround.