chore: baseline existing CI-V suite before RepeaterBook integration
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
# Handoff: ID-5100 CI-V control suite (library + CLI + web panel)
|
||||
|
||||
## Session Metadata
|
||||
- Created: 2026-06-14 14:40:31
|
||||
- Project: /home/claude/bin/id5100
|
||||
- Branch: [not a git repo — ~/bin is not under git]
|
||||
- Session duration: ~1 session
|
||||
|
||||
### Recent Commits (for context)
|
||||
- [not a git repo — no commits]
|
||||
|
||||
## Handoff Chain
|
||||
|
||||
- **Continues from**: None (fresh start)
|
||||
- **Supersedes**: None
|
||||
|
||||
> This is the first handoff for this task.
|
||||
|
||||
## Current State Summary
|
||||
|
||||
Built a complete software control layer for Seth's ICOM ID-5100 mobile transceiver
|
||||
over CI-V, from nothing to a working web control panel, in one session. Started by
|
||||
verifying the hardware link (RT Systems USB-29A cable into the radio's [SP2] jack),
|
||||
then built a pure-stdlib codec, a serial transport, a CLI, and an industrial-themed
|
||||
web panel. **Everything is verified against the live radio** — reads, writes, and the
|
||||
UI screenshot. The web server is running now and reachable on the LAN. The project is
|
||||
in a clean, finished-MVP state; remaining items are optional enhancements, not gaps.
|
||||
|
||||
## Codebase Understanding
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
Three clean layers, each a front-end on the one below:
|
||||
- **Codec** (`civ.py`) — pure functions, no I/O: BCD freq/level encode-decode, CI-V
|
||||
frame build/parse. Fully unit-tested (11 tests).
|
||||
- **Transport** (`radio.py`) — `Radio` class over pure-stdlib `termios` (no pyserial).
|
||||
get/set frequency, mode, volume, squelch level, S-meter, squelch status.
|
||||
- **Front-ends** — `cli.py` (argparse) and `server.py` + `panel.html` (web). Both just
|
||||
call `radio.Radio`.
|
||||
|
||||
### Critical Files
|
||||
|
||||
| File | Purpose | Relevance |
|
||||
|------|---------|-----------|
|
||||
| `civ.py` | Pure CI-V codec (BCD, frames) | Core; change here = re-run test_civ.py |
|
||||
| `radio.py` | Serial transport + `Radio` class | All control flows through this |
|
||||
| `cli.py` | CLI front end | `./cli.py status` etc. |
|
||||
| `server.py` | Web panel HTTP server + JSON API | Holds the serial port while running |
|
||||
| `panel.html` | Instrument-panel UI (one self-contained file) | Amber VFD aesthetic |
|
||||
| `test_civ.py` | 11 codec unit tests | `python3 -m unittest test_civ` |
|
||||
| `civ_probe.py` | One-shot link probe | Quick "is the radio alive" check |
|
||||
| `README.md`, `DECISIONS.md` | Docs + decision log | Read first when resuming |
|
||||
|
||||
### Key Patterns Discovered
|
||||
|
||||
- **Verified link params:** `/dev/ttyUSB0`, **9600 baud**, radio CI-V address **0x8C**,
|
||||
controller address 0xE0.
|
||||
- **Bus echo:** the radio echoes your TX frame before its reply; `transact()` filters
|
||||
frames whose `frm != RADIO_ADDR` (0x8C) to skip the echo.
|
||||
- **Two BCD byte orders on the same radio:** frequency is 5-byte **little-endian** BCD;
|
||||
levels/meters are 2-byte **big-endian** BCD (128 -> `01 28`). Pinned by unit tests.
|
||||
- **Mode FM = 0x05** confirmed empirically (radio returned `05 01`).
|
||||
|
||||
## Work Completed
|
||||
|
||||
### Tasks Finished
|
||||
|
||||
- [x] Verified PC<->radio CI-V link (read VFO = 147.300000 MHz)
|
||||
- [x] TDD'd pure codec `civ.py` (11 tests green, grounded in real capture)
|
||||
- [x] Built transport `radio.py`, ground-truthed all reads + write path on live radio
|
||||
- [x] Optimized `transact()` early-return (status poll ~3.6s -> ~0.2s)
|
||||
- [x] Built CLI `cli.py` (status/freq/mode/vol/sql/smeter)
|
||||
- [x] Built web panel `server.py` + `panel.html`, verified via headless screenshot + control POST
|
||||
- [x] Added `ID-5100 CI-V` row to ~/bin/CLAUDE.md projects table (backed up first)
|
||||
- [x] Wrote README.md + DECISIONS.md
|
||||
|
||||
### Files Modified
|
||||
|
||||
| File | Changes | Rationale |
|
||||
|------|---------|-----------|
|
||||
| /home/claude/bin/CLAUDE.md | +1 projects-table row | Register the project (backup: .backup/CLAUDE.md-1781461768) |
|
||||
| (all id5100/* files) | Created this session | The deliverable |
|
||||
|
||||
### Decisions Made
|
||||
|
||||
| Decision | Options Considered | Rationale |
|
||||
|----------|-------------------|-----------|
|
||||
| Pure stdlib `termios` | pyserial | System Python is PEP-668 locked; --break-system-packages violates escalation-brake rule |
|
||||
| stdlib `http.server` for web | Flask/FastAPI | Keep no-deps promise |
|
||||
| Early-return `transact()` | fixed timeout | Makes ~1 Hz live S-meter polling viable |
|
||||
| Industrial VFD aesthetic | generic dashboard | Matches radio-gear domain + Sethian dark/orange brand |
|
||||
| Reject full headless-head replica | reverse-engineer head bus | Proprietary/undocumented; CI-V covers operating functions |
|
||||
|
||||
(Full list in DECISIONS.md.)
|
||||
|
||||
## Pending Work
|
||||
|
||||
## Immediate Next Steps
|
||||
|
||||
1. **(Optional) systemd user unit for `server.py`** — it's currently a bare background
|
||||
process (won't survive reboot/logout). This is the most likely next ask.
|
||||
2. **(Optional) Vendor the web fonts locally** — `panel.html` pulls DSEG7 (jsdelivr) +
|
||||
Saira/Chivo Mono (Google Fonts) from CDNs; offline it falls back to monospace.
|
||||
3. **(Optional) Verify DV mode (0x17) on the live radio** — currently from the generic
|
||||
ICOM table, flagged not-trusted. Test `./cli.py mode dv` and confirm the radio ACKs.
|
||||
|
||||
### Blockers/Open Questions
|
||||
|
||||
- [ ] None blocking. Open question: does Seth want this behind Caddy with a subdomain
|
||||
(e.g. `radio.sethpc.xyz`) like his other services? Not discussed.
|
||||
|
||||
### Deferred Items
|
||||
|
||||
- Full software control-head replica (proprietary head bus) — rejected, see DECISIONS.md.
|
||||
- RS-MS1A data-jack protocol (D-STAR DR routing / callsign / messaging) — out of scope
|
||||
for this CI-V layer; separate effort if wanted.
|
||||
- HA/MQTT bridge — offered, not chosen this session.
|
||||
|
||||
## Context for Resuming Agent
|
||||
|
||||
## Important Context
|
||||
|
||||
The whole suite is **verified against real hardware**, not just written. If resuming,
|
||||
you can trust the link params and the codec. The radio is a physical device on Seth's
|
||||
desk — **set-commands change real radio state**, so prefer no-op writes (write current
|
||||
value back) when testing the write path, as was done here. The only process that can
|
||||
hold `/dev/ttyUSB0` is one at a time: **stop `server.py` before running `cli.py`** or
|
||||
the probe, and vice versa.
|
||||
|
||||
### Assumptions Made
|
||||
|
||||
- The radio stays on `/dev/ttyUSB0` (single USB-serial device; confirmed by Seth).
|
||||
- CI-V baud stays 9600 (Seth set this in the radio menu this session).
|
||||
- The PC hosting all this is steel141 (192.168.0.141).
|
||||
|
||||
### Potential Gotchas
|
||||
|
||||
- **Don't `--break-system-packages`** — stdlib-only is a deliberate decision.
|
||||
- **Bus echo** will confuse any new raw-serial code; reuse `civ.parse_frames` + the
|
||||
`frm == RADIO_ADDR` filter.
|
||||
- **DV=0x17 is unverified** — don't present `set_mode('dv')` as trusted yet.
|
||||
- The web panel needs internet for the nice fonts (graceful monospace fallback otherwise).
|
||||
|
||||
## Environment State
|
||||
|
||||
### Tools/Services Used
|
||||
|
||||
- RT Systems USB-29A cable (FTDI), enumerates as `/dev/serial/by-id/usb-RT_Systems_CT29A_Radio_Cable_RT90581N-if00-port0` -> `/dev/ttyUSB0`
|
||||
- `claude` user is in `dialout` group (serial access OK)
|
||||
- chromium (headless) used for the verification screenshot
|
||||
- Python 3.13 (system, PEP-668 externally-managed)
|
||||
|
||||
### Active Processes
|
||||
|
||||
- **`server.py --port 8550` is RUNNING** (was PID 380991 this session) at
|
||||
**http://192.168.0.141:8550/** (bound 0.0.0.0, LAN/phone reachable). Log at
|
||||
`id5100/.server.log`. It holds `/dev/ttyUSB0`. Kill with `pkill -f "server.py --port 8550"`.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- None specific to this project. (Homelab-wide: `HOMELAB_PASSWORD` exists but unused here.)
|
||||
|
||||
## Related Resources
|
||||
|
||||
- `id5100/README.md` — usage + verified-facts
|
||||
- `id5100/DECISIONS.md` — decision log incl. Deferred/Rejected
|
||||
- `~/bin/CLAUDE.md` — projects table entry
|
||||
- ICOM ID-5100 full manual CI-V section (p.305) — frame format / address 0x8C
|
||||
|
||||
---
|
||||
|
||||
**Security Reminder**: Before finalizing, run `validate_handoff.py` to check for accidental secret exposure.
|
||||
Reference in New Issue
Block a user