2f96013794
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
5.7 KiB
5.7 KiB
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)
9d6f2d2docs: handoff — pairing prompt / IP drift / zombie bridge34adf28fix: IP-independent pairing key fallback; run commands off the paho threadf7422c9fix: survive command exceptions; TV DHCP-reserved so pairing key stays validdf0ae87docs: 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
- 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_initloadsclient_keyfrom storage by IP atcreate()time. Settingclient.client_keybeforeconnect()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
- Diagnosed the zombie bridge (paho thread died on TimeoutExpired; the poll loop masked it)
- Diagnosed the per-IP key problem (TV has drifted through 11 IPs)
- Omada reservation f8:01:b4:2a:26:ed → .20 (set, but the TV took .45 anyway)
- Key fallback in lgtv.sh, worker thread + exception guard in lgtv_mqtt.py, deployed
- 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
- Nothing required. If a prompt reappears, check
last_ipagainst the newest sqlite row. A prompt at a new IP means the TV rejected the fallback key. - 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
offtimeout: 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 assudo -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 forException 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 <MAC> [IP]journalctl -u lgtv-mqtt.service
Active Processes
lgtv-mqtt.serviceon 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