Rename project from travler-rotor to birdcage

The radome looks like a birdcage, ham operators call satellites
"birds", and it's a nod to saveitforparts saving dishes "for parts."

Package, CLI entry point, class names (BirdcageAntenna), env vars
(BIRDCAGE_PORT, etc.), and CLAUDE.md updated. Hardware references
(Winegard Trav'ler, Trav'ler Pro, Carryout G2) unchanged.
This commit is contained in:
Ryan Malloy 2026-02-13 05:16:00 -07:00
parent c010cee282
commit a2e807f973
9 changed files with 207 additions and 57 deletions

161
CLAUDE.md
View file

@ -4,18 +4,19 @@ Control a Winegard Trav'ler motorized satellite dish via RS-485 for amateur radi
## Project
- **Package:** `travler-rotor` (installed via `uv sync`)
- **CLI entry point:** `travler-rotor` (init / serve / pos / move)
- **Source layout:** `src/travler_rotor/` (src-layout)
- **Packages:** `birdcage` + `console-probe` (installed via `uv sync`)
- **CLI entry points:** `birdcage` (init / serve / pos / move), `console-probe` (probe / discover)
- **Source layout:** `src/birdcage/` and `src/console_probe/` (src-layout)
- **Original upstream:** `Trav-ler-Rotor-For-HAL-2.05/` — Gabe Emerson's scripts, kept as reference (do not modify)
## Build & Lint
```bash
uv sync # Install deps + package
uv sync # Install deps + both packages
uv run ruff check src/ # Lint
uv run ruff format --check src/ # Format check
uv run travler-rotor --help # CLI smoke test
uv run birdcage --help # CLI smoke test
uv run console-probe --help # Probe tool smoke test
```
## Architecture
@ -25,13 +26,29 @@ protocol.py — FirmwareProtocol ABC + HAL205Protocol / HAL000Protocol
Serial I/O owned here. Each firmware version is a subclass.
leapfrog.py — Pure function: apply_leapfrog(target, current) -> adjusted
Predictive overshoot to compensate for mechanical motor lag.
antenna.py — TravlerAntenna: high-level control wrapping protocol + leapfrog
antenna.py — BirdcageAntenna: high-level control wrapping protocol + leapfrog
This is what consumers (CLI, rotctld, future MCP server) call.
rotctld.py — RotctldServer: Hamlib rotctld TCP protocol (p/P/S/_/q)
Bridges Gpredict to the antenna.
cli.py — Click CLI with init/serve/pos/move subcommands
```
### console-probe package
```
profile.py — DeviceProfile + HelpEntry dataclasses
serial_io.py — Prompt-aware serial I/O (fixes > termination bug)
discovery.py — Auto-discovery, help parsing, submenu probing, candidates
report.py — JSON report with format_version 2 (menus/help/undiscovered)
cli.py — argparse CLI: --discover-only, --deep, --submenu, --json
```
**Usage:**
```bash
console-probe --port /dev/ttyUSB2 --baud 115200 --discover-only --json /tmp/d.json
console-probe --port /dev/ttyUSB2 --baud 115200 --deep --wordlist scripts/wordlists/winegard.txt
```
## Firmware Variants
Five known Winegard dish variants documented by Gabe Emerson (KL1FI) / saveitforparts and cdavidson0522:
@ -455,6 +472,138 @@ v — go to velocity (continuous spin): `v [motor] [ustep/sec]`
? / q — help / return to TRK>
```
### K60 GPIO Functional Pin Map (Carryout G2)
Cross-referenced from live `gpio dir`/`gpio regs` queries (2026-02-13), K60 datasheet
pin mux table (MK60DN512VLQ10, 144-LQFP), boot log peripheral init, and A3981 datasheet.
**SPI1 — A3981 Stepper Motor Drivers (4 MHz, mode 0x03)**
| K60 Pin | GPIO | Alt | Function | Dir | State | Notes |
|---------|------|-----|----------|-----|-------|-------|
| PTE0 | E0 | ALT2 | SPI1_PCS1 | OUT | 1 | A3981 #2 chip select (EL motor) |
| PTE1 | E1 | ALT2 | SPI1_SOUT | (periph) | 1 | MOSI — MCU to A3981 |
| PTE2 | E2 | ALT2 | SPI1_SCK | (periph) | 1 | SPI clock |
| PTE3 | E3 | ALT2 | SPI1_SIN | (periph) | 0 | MISO — A3981 to MCU |
| PTE4 | E4 | ALT2 | SPI1_PCS0 | IN* | 1 | A3981 #1 chip select (AZ motor) |
| PTE5 | E5 | ALT2 | SPI1_PCS2 | OUT | 1 | Possibly A3981 RESET or enable |
*PTE4 shows INPUT in GPIO dir register, but this is irrelevant when muxed to SPI peripheral.
The SPI controller manages chip select assertion/deassertion directly.
**SPI2 — BCM4515 DVB-S2 Tuner (6.857 MHz, mode 0x03)**
| K60 Pin | GPIO | Alt | Function | Dir | State | Notes |
|---------|------|-----|----------|-----|-------|-------|
| PTD11 | D11 | ALT2 | SPI2_PCS0 | OUT | 1 | BCM4515 chip select |
| PTD12 | D12 | ALT2 | SPI2_SCK | IN* | 1 | SPI clock |
| PTD13 | D13 | ALT2 | SPI2_SOUT | IN* | 1 | MOSI — MCU to BCM4515 |
| PTD14 | D14 | ALT2 | SPI2_SIN | — | 0 | MISO — BCM4515 to MCU |
| PTD15 | D15 | ALT2 | SPI2_PCS1 | — | 0 | Secondary chip select (unused?) |
*GPIO dir register not meaningful for peripheral-muxed pins.
**UART4 — RS-422 Console (115200 baud)**
| K60 Pin | GPIO | Alt | Function | Dir | State | Notes |
|---------|------|-----|----------|-----|-------|-------|
| PTE24 | E24 | ALT3 | UART4_TX | OUT | 1 | Console TX (to computer RX pair) |
| PTE25 | E25 | ALT3 | UART4_RX | IN | 1 | Console RX (from computer TX pair) |
| PTE26 | E26 | ALT3 | UART4_CTS | IN | 1 | Hardware flow control (idle high) |
| PTE27 | E27 | — | GPIO | IN | 1 | Unknown (RTS? or pullup) |
| PTE28 | E28 | — | GPIO | IN | 1 | Unknown |
**DIP Switch GPIOs**
`dipswitch` reads raw value `val:ffffff01` (all OFF/up) → `app_dipswitch:101` (DISH 110+119+129W).
Exact GPIO pins TBD — likely Port A or Port C inputs with internal pullups. The 0xffffff01
raw value suggests a 32-bit register read where bits 1-24 are all high (pullup, switches open)
and bit 0 is high (LSB).
**A3981 Diagnostic Pins**
The `a3981 diag` command reads fault status from two GPIO pins (one per motor driver).
Confirmed both read "OK" when motors are healthy. The A3981 DIAG output is active-low
open-drain, pulled high when no fault. Exact GPIO pins TBD.
**Unidentified High-State Outputs**
| GPIO | Dir | State | Likely Function |
|------|-----|-------|-----------------|
| D10 | OUT | 1 | BCM4515 reset or power enable |
| B0-B3 | — | 1 | SPI0 or I2C bus (B0-B3 cluster) |
| B11 | — | 1 | Status LED or peripheral enable |
| C10-C13 | — | 1 | Contiguous block — possibly bus interface |
| C18 | — | 1 | LNB voltage control or relay |
### azscanwxp — Radio Telescope Mode (Carryout G2)
The `azscanwxp` command in MOT> performs an azimuth sweep while cycling through
DVB transponders at each position. This is the core of Davidson's winegard-sky-scan
project for RF imaging of the sky.
**Usage:** `azscanwxp [motor] [span] [resolution] [num_xponders]`
| Parameter | Type | Units | Description |
|-----------|------|-------|-------------|
| motor | int | — | Motor ID (0=AZ, 1=EL) |
| span | float | degrees | Total azimuth sweep range |
| resolution | int | centidegrees (0.01 deg) | Step size per position |
| num_xponders | int | — | Number of transponders to cycle at each position |
**Example:** `azscanwxp 0 10 100 3` — sweep 10 degrees on AZ at 1.00 degree steps,
checking 3 transponders per position.
**Output format** (from ADC `scan` documentation):
```
Motor:<id> Angle:<cdeg> RSSI:<adc> Lock:<0/1> SNR:<dB> Scan Delta:<step>
```
**Safety:** Requires homed motors. Do NOT run on uncalibrated axes — the firmware
may target INT_MAX (2147483647 steps) and deadlock the shell.
**For ham radio sky mapping:** Set the DVB tuner to a frequency near your target
(e.g., 10 GHz Ku-band downconverted through the LNB to ~1178 MHz IF), enable LNA
with `dvb``lnbdc odu`, then run azscanwxp. The RSSI values map RF power at
each AZ/EL grid point. Post-process the output into a 2D heatmap for sky imaging.
### DiSEqC 2.x Interface (Carryout G2)
The BCM4515 provides a DiSEqC 2.x controller accessible from the DVB> submenu.
DiSEqC (Digital Satellite Equipment Control) uses 22 kHz tone bursts on the coax
LNB bias line to control switches, LNB polarity, and band selection.
**Timing Parameters (confirmed live 2026-02-13):**
| Command | Value | Description |
|---------|-------|-------------|
| `ovraddr` | 0x11 | Target LNB address (standard first LNB) |
| `rrto` | 210 ms | Receive reply timeout |
| `pretx` | 15 ms | Pre-command TX delay |
| `tdthresh` | 110 | Tone detect threshold (0.16 counts/mV) |
**DiSEqC Commands:**
| Command | Function | Status |
|---------|----------|--------|
| `di2conf` | Read LNB config register | RxReplyTimeout (no switch connected) |
| `di2id` | Read LNB hardware ID | RxReplyTimeout |
| `di2stat` | Read LNB status flags | RxReplyTimeout |
| `di2rcs` | Read committed switch status | RxReplyTimeout |
| `di2cs` | Configure committed switch | Needs parameters |
| `di2sc` | Short circuit test | Untested |
| `send <hex>` | Raw DiSEqC packet (max 6 bytes) | Functional |
**Raw DiSEqC packets:** The `send` command accepts space-delimited hex bytes.
Standard DiSEqC 1.x commands use the format: `send E0 10 38 Fx` where the
last byte selects the switch port (F0-F3 for ports 1-4).
**For ham radio:** DiSEqC can control LNB polarity (13V=V-pol, 18V=H-pol) and
22 kHz tone (band select) without rewiring. The `lnbdc odu` command sets 13V;
boot default is 18V. Polarity affects which transponders are visible and RSSI
readings from `rssits` in the PEAK> submenu, which alternates between even
(H-pol/18V) and odd (V-pol/13V) transponders.
### Known NVS Indices
Full dump in `docs/g2-nvs-dump.md` (firmware 02.02.48, captured 2026-02-12).