feat: add --debug tracing to hendi_ctrl_app.py, document v1.16 fix

HendiCtrl.cmd() previously raised a bare "Communication error" for any
non-OK response, discarding the firmware's actual reply. It now
includes the raw echo/answer bytes, and --debug prints every
request/response. Using this, the rejected R1 during lockout turned
out to be an explicit "ERR:Invalid state" reply, not a timeout.

Also flashed and retested v1.16: plain reconnect (no --reset) now
recovers from the lockout reliably (4/4 trials), fixing the
intermittent behavior seen on v1.15.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GpePKZiEZWbGo9HrfuML6U
This commit is contained in:
2026-07-02 18:44:00 +02:00
co-authored by Claude Sonnet 5
parent 7ebd2aaa63
commit 5bc278e875
3 changed files with 43 additions and 19 deletions
+29 -12
View File
@@ -1,7 +1,7 @@
# 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 three
`--trigger-error-state`, `--reset` and `--test-heater` flags, across four
firmware versions (`getSoftwareVersion()` / `I?`+`V?` via
`components/actor/hendiCtrl.py`).
@@ -11,24 +11,35 @@ 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
## 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`. `getSoftwareVersion()` confirmed each version after
flashing.
`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) |
|---|---|---|---|---|
| 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 |
| `--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 |
| 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) |
| 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) |
## Symptom while locked out
- `I?` / `V?` (software identifier/version queries) still respond normally.
- `R1` (`remoteEnable(True)`) fails with a communication error - this is the
only observed symptom, and it blocks any command path that needs remote
control enabled (e.g. `--test-heater`).
- `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 to
`components/actor/hendiCtrl.py`'s `cmd()`), the actual wire exchange for a
rejected `R1` on v1.15 is:
```
req='R1' echo=b'R1\r\n' answer=b'\rERR:Invalid state\r\n'
```
So this isn't a timeout or garbled response - the firmware actively replies
with `ERR:Invalid state`. `HendiCtrl.cmd()` previously only checked for
`"OK"` in the response and raised a generic `HendiException("Communication
error")` for anything else, discarding this actual error text; it now
includes the raw echo/answer in the exception message.
## Conclusion
@@ -47,3 +58,9 @@ 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.