From 2f960137941f52ccbe9c05c8f42513c59281617e Mon Sep 17 00:00:00 2001 From: Mortdecai Date: Fri, 23 Oct 2026 00:38:40 -0400 Subject: [PATCH] =?UTF-8?q?docs:=20handoff=20=E2=80=94=20pairing=20fix=20v?= =?UTF-8?q?erified=20on/off/on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 (1M context) --- .../2026-10-23-003809-pairing-fix-verified.md | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 .claude/handoffs/2026-10-23-003809-pairing-fix-verified.md diff --git a/.claude/handoffs/2026-10-23-003809-pairing-fix-verified.md b/.claude/handoffs/2026-10-23-003809-pairing-fix-verified.md new file mode 100644 index 0000000..df5b493 --- /dev/null +++ b/.claude/handoffs/2026-10-23-003809-pairing-fix-verified.md @@ -0,0 +1,128 @@ +# Handoff: pairing-prompt / zombie-bridge fix — verified on→off→on + +## Session Metadata +- Created: 2026-10-23 00:38:09 +- Project: /home/claude/bin/lgtv +- Branch: master +- Session duration: ~25 min (00:15–00:40) + +### Recent Commits (for context) + - 9d6f2d2 docs: handoff — pairing prompt / IP drift / zombie bridge + - 34adf28 fix: IP-independent pairing key fallback; run commands off the paho thread + - f7422c9 fix: survive command exceptions; TV DHCP-reserved so pairing key stays valid + - df0ae87 docs: HA VM lost IPv4 — root cause of months-long TV outage; pinned .146 + +## Handoff Chain + +- **Continues from**: [2026-10-23-pairing-prompt-ip-drift.md](./2026-10-23-pairing-prompt-ip-drift.md) + - Previous title: pairing prompt on every IP change + zombie bridge +- **Supersedes**: 2026-10-23-pairing-prompt-ip-drift.md (its "Open" items are restated here) + +## Current State Summary + +Google Home → HA → MQTT → lgtv bridge on steel141 works end to end. Seth tested +ON → OFF → ON at 00:37 with no TV prompt, and the journal shows three commands +received, all rc=0. That closes both outages: the 2026-10-22 HA IPv4 loss and +tonight's pairing-prompt/zombie-bridge bug. Nothing is in progress. + +## Codebase Understanding + +### Architecture Overview + +Google Home → HA (VM 706, .146) → Mosquitto (.154) → `lgtv_mqtt.py` +(systemd `lgtv-mqtt.service`, User=seth, steel141) → shells out to `lgtv.sh on|off|status`. +ON = Wake-on-LAN (no key needed). OFF = bscpylgtv WebSocket `power_off` (needs a pairing key). +Status = TCP probe of :3000/:3001 at the cached IP, falling back to a /24 sweep matched by MAC. + +### Critical Files + +| File | Purpose | Relevance | +|------|---------|-----------| +| `lgtv_mqtt.py` | MQTT↔HA bridge | Commands now run on a 1-thread `ThreadPoolExecutor`, wrapped in try/except | +| `lgtv.sh` | on/off/status | `newest_key()` fallback when no key is stored for the current IP | +| `/home/seth/.aiopylgtv.sqlite` | bscpylgtv key store (keyed by IP, pickled values) | 11 rows; newest = .45 | +| `/home/seth/.cache/lgtv/last_ip` | cached TV IP | currently 192.168.0.45 | +| `install.sh` | deploy → `/usr/local/bin/` | re-run after editing either script | + +### Key Patterns Discovered + +- bscpylgtv `async_init` loads `client_key` from storage by IP at `create()` time. Setting `client.client_key` before `connect()` overrides it, and a key the TV issues is saved under the current IP (`webos_client.py:237`). +- Never block inside a paho callback. An exception kills the network thread, and a long block starves the keepalive, so QoS 0 commands get dropped. + +## Work Completed + +### Tasks Finished + +- [x] Diagnosed the zombie bridge (paho thread died on TimeoutExpired; the poll loop masked it) +- [x] Diagnosed the per-IP key problem (TV has drifted through 11 IPs) +- [x] Omada reservation f8:01:b4:2a:26:ed → .20 (set, but the TV took .45 anyway) +- [x] Key fallback in lgtv.sh, worker thread + exception guard in lgtv_mqtt.py, deployed +- [x] Seth verified ON→OFF→ON via Google Home + +### Files Modified + +| File | Changes | Rationale | +|------|---------|-----------| +| `lgtv_mqtt.py` | worker thread + `safe_handle` | survive and not block on slow/failed commands | +| `lgtv.sh` | `newest_key()` fallback | IP drift no longer forces a re-pair | +| `DECISIONS.md` | two 2026-10-23 entries | record the why | + +### Decisions Made + +| Decision | Options Considered | Rationale | +|----------|-------------------|-----------| +| Newest-key fallback instead of relying on the reservation | reservation only; pass a pinned `client_key` from env | The reservation didn't hold; the fallback self-updates as the TV issues new keys | +| Single worker thread | thread per command; asyncio | Keeps command order; smallest diff | + +## Pending Work + +### Immediate Next Steps + +1. Nothing required. If a prompt reappears, check `last_ip` against the newest sqlite row. A prompt at a *new* IP means the TV rejected the fallback key. +2. Optional: find out why the Omada reservation for .20 wasn't applied (the TV got .45 at 00:31). Either fix it, or re-reserve at .45 to match reality. + +### Blockers/Open Questions + +- [ ] **Still unverified:** whether the TV accepts an old key from a new IP. The TV stayed at .45 during the test, so the fallback path never ran. The first real IP change will tell. +- [ ] Why didn't the reservation stick? Unknown. + +### Deferred Items + +- Reducing the 60s `off` timeout: deferred. It no longer harms the bridge, it only delays the next command. + +## Context for Resuming Agent + +### Important Context + +- The service runs as **seth**, so the key store and cache are under `/home/seth`. Edit them as `sudo -u seth`. +- The ON path never needs a key. Any "can't turn on" bug sits upstream of WoL (HA, MQTT, a lost message) or is the TV's WoL setting. +- The bridge reports ON while the TV keeps :3000 open in network standby, so HA state can lag after OFF. + +### Assumptions Made + +- LG validates the key itself, not the source IP (consistent with the protocol; not yet seen across an IP change). + +### Potential Gotchas + +- A long-running process with a dead paho thread still looks healthy in `systemctl status`. Check the journal for `Exception in thread paho`. +- Backups of pre-edit files are in `lgtv/.backup/` (also the sqlite store as of 00:2x). + +## Environment State + +### Tools/Services Used + +- `network-tools/omada_reserve_ip.py show|reserve [IP]` +- `journalctl -u lgtv-mqtt.service` + +### Active Processes + +- `lgtv-mqtt.service` on steel141, restarted 00:34:40 with the current code. + +### Environment Variables + +- `LGTV_KEY_FILE`, `LGTV_STATE`, `TV_IP` (set internally), MQTT creds from the project env file (names only). + +## Related Resources + +- `DECISIONS.md` (2026-10-23 entries) +- Previous handoffs: `2026-10-22-ha-lost-ipv4.md`, `2026-10-23-pairing-prompt-ip-drift.md`