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.
691 lines
38 KiB
Markdown
691 lines
38 KiB
Markdown
# Winegard Trav'ler
|
||
|
||
Control a Winegard Trav'ler motorized satellite dish via RS-485 for amateur radio satellite tracking.
|
||
|
||
## Project
|
||
|
||
- **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 + both packages
|
||
uv run ruff check src/ # Lint
|
||
uv run ruff format --check src/ # Format check
|
||
uv run birdcage --help # CLI smoke test
|
||
uv run console-probe --help # Probe tool smoke test
|
||
```
|
||
|
||
## Architecture
|
||
|
||
```
|
||
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 — 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:
|
||
|
||
| Detail | HAL 0.0.00 | HAL 2.05.003 | Trav'ler Pro | Carryout | Carryout G2 |
|
||
|--------|-----------|-------------|-------------|---------|------------|
|
||
| **Repo** | Travler_Rotor | Trav-ler-Rotor-For-HAL-2.05 | Travler-Pro-Rotor | Carryout-Rotor | winegard-sky-scan |
|
||
| **Connection** | RS-485 / RJ-25 | RS-485 / RJ-25 | USB A-to-A (`ttyACM0`) | RS-485 / RJ-25 | RS-422 / RJ-12 6P6C |
|
||
| **Baud rate** | 57600 | 57600 | 57600 | 57600 | 115200 |
|
||
| **Motor submenu** | `mot` | `motor` | `odu` then `mot` | N/A (`target` + `g`) | `mot` |
|
||
| **Motor control** | `a <id> <deg>` | `a <id> <deg>` | `a <id> <deg>` | `g <az> <el>` only | `a <id> <deg>` |
|
||
| **Search kill** | `os` -> `kill Search` | `ngsearch` -> `s` -> `q` | `os` -> `kill Search` | N/A | NVS 20 (permanent disable) |
|
||
| **Boot signal** | `NoGPS` | `NoGPS` or `No LNB Voltage` | undocumented | N/A | `Boot Complete` then `Loc Startup: IDU NOT Present` |
|
||
| **Min elevation** | 15 deg (firmware) | 15 deg (firmware) | 12 deg (firmware) | 22 deg (firmware-enforced) | 18 deg (firmware) |
|
||
| **Max elevation** | 90 deg | 90 deg | 75 deg (hardware cap!) | 73 deg (firmware default, NVS 102 override) | 65 deg (firmware) |
|
||
| **Position query** | `a` -> `AZ = / EL =` | `a` -> `AZ = / EL =` | `a` -> `AZ = / EL =` | raw ints / 100 | `a` -> floats |
|
||
| **Tested model** | LG-2112 | LG-2112 | SK2DISH | 2003 Carryout | Carryout G2 |
|
||
| **HAL version** | 0.0.00 | 2.05.003 | unknown | 1.00.065 | 02.02.48 |
|
||
| **Prompt char** | `>` (likely) | `>` (likely) | undocumented | undocumented | `TRK>` / `MOT>` / `NVS>` (confirmed) |
|
||
| **Position format** | `AZ = / EL =` | `AZ = / EL =` | `AZ = / EL =` | raw ints / 100 | `Angle[0] = / Angle[1] =` |
|
||
| **DVB tuner** | unknown | unknown | unknown | unknown | BCM4515 (Broadcom) |
|
||
| **MCU** | unknown | unknown | unknown | unknown | NXP MK60DN512VLQ10 (Kinetis K60, Cortex-M4, 96MHz, 512KB flash, 128KB RAM) |
|
||
| **Motor driver** | unknown | unknown | unknown | unknown | 2× Allegro A3981 (SPI, 1/16 microstep, AUTO mode) |
|
||
|
||
### Key Variant Differences
|
||
|
||
- **Trav'ler Pro `odu` command:** The Pro's IDU has its own MCU. You must first tunnel to the ODU with `odu` before entering the motor submenu. The regular Trav'ler's IDU is a dumb RS-485 passthrough.
|
||
- **Carryout uses `g` not `a`:** The Carryout has no individual motor addressing. It uses `target` to enter targeting mode, then `g <az> <el>` for combined moves. It also can't query its initial position.
|
||
- **Carryout has no limit switches:** Uses motor stalling to detect mechanical boundaries (audible grinding).
|
||
- **Pro has the same leap-frog bug** as the regular Trav'ler (copy-pasted).
|
||
- **NVS `d` command** dumps all NVS values. Confirmed on Pro and Carryout G2; likely available on all variants.
|
||
- **Carryout DIP switches:** All switches to off (up) may disable search mode, but behavior varies by unit.
|
||
- **Carryout G2 uses `a` not `g`:** Unlike the 2003 Carryout, the G2 uses standard `a <id> <deg>` motor addressing and the `mot` submenu — protocol-compatible with the Trav'ler family.
|
||
- **Carryout G2 is RS-422 full-duplex:** Separate TX/RX pairs at 115200 baud via RJ-12 6P6C, vs. RS-485 half-duplex at 57600 on the Trav'ler variants. Tested with DSD TECH SH-U11 USB-to-RS422 adapter (FTDI FT232R). **Polarity matters** — A/B (or +/-) labeling is not standardized; if you get garbled data at the correct baud rate, swap the +/- wires on the RX pair. TX pair polarity swap causes the dish to not receive commands (silent failure).
|
||
- **Carryout G2 position format differs from Trav'ler:** Position query `a` in `MOT>` submenu returns `Angle[0] = 180.00` / `Angle[1] = 45.00` — not the `AZ = / EL =` format used by HAL 0.0.00 and HAL 2.05. Move confirmation returns `Angle = 46.00` (no array index). `CarryoutG2Protocol.get_position()` uses `Angle\[0\]`/`Angle\[1\]` regex. Motor overshoot is direction-dependent: +0.01–0.05° in travel direction, -0.02–0.06° on return (stepper backlash).
|
||
- **Carryout G2 firmware version 02.02.48** confirmed (Copyright 2013 - Winegard Company). Bootloader version 1.01. MCU: Kinetis (NXP ARM Cortex-M). DVB tuner: BCM4515 (Broadcom).
|
||
- **Carryout G2 boot sequence:** Bootloader v1.01 → SPI1 init @ 4 MHz (A3981 motor drivers, mode 0x03) → Motor init (System=12Inch, master=40000 steps, slave=24960 steps, ratio=1.602564) → SPI2 init @ 6.857 MHz (BCM4515 DVB tuner, mode 0x03) → `EXTENDED_DVB_DEBUG ENABLED` → DVB init (AP RAM FW verified, BCM4515 ID 0x4515 Rev B0, FW v113.37, strap 0x25018) → auto-search config (blind scan, 18000-24000 ksps, rolloff 0.35) → `Enabled LNB STB` → `Ant ID - 12-IN G2` → NVS load → EL home (stall detect, 2s timeout) → AZ home (stall detect, 8s timeout) → `Antenna Facing Front` → `TRK>` prompt (if tracker disabled) or search start. When NVS 20 = TRUE (tracker disabled), homing is skipped entirely — motors stay uncalibrated and AZ position reads as INT_MAX (2147483647).
|
||
- **Carryout G2 cable wrap:** Confirmed from homing output: `wrap_min:-42333 wrap_max:2333` (centidegrees). Total range ~446.66°.
|
||
- **Carryout G2 has `h <id>` homing:** Explicit motor home-to-reference command. Not documented on other variants.
|
||
- **Carryout G2 has DVB/RSSI:** BCM4515 tuner (ID 0x4515, Rev B0, firmware v113.37). DVB submenu provides `rssi <n>` (bounded, returns `Reads:<n> RSSI[avg: <v> cur: <v>]`), `agc` (streaming RF/IF AGC + SNR + NID), `snr`, `lnbdc odu` (enable LNA 13V), `lnbv` (streaming voltage monitor), `dis` (channel params), `config` (hardware ID), `table` (transponder scan), and DiSEqC 2.x commands (`di2*`, `send`). RSSI noise floor is ~500. `lnbdc odu` sets 13V (V-pol); boot default is 18V (H-pol). Streaming commands run until interrupted by `q` or another command.
|
||
|
||
## Hardware Specs (SK-1000)
|
||
|
||
| Spec | Value |
|
||
|------|-------|
|
||
| Weight | 45 lbs |
|
||
| Stow dimensions | 10"H x 42"L x 26"W |
|
||
| Stow height | 9.75" |
|
||
| Max deployed height | 37" above mount |
|
||
| Arm reach | 32.5" from base center |
|
||
| Dish size | ~33" x 23" (Ku-band reflector) |
|
||
| Azimuth range | 0-455 deg (before cable wrap) |
|
||
| Power input | 120VAC -> 12VDC (RP-SK87 supply) |
|
||
| LNB bias | 12-18VDC via coax |
|
||
| Motor type | Likely stepper with gearing (unconfirmed) |
|
||
| Satellites (DISH) | 110, 119, 129 (61.5 manual only) |
|
||
| Satellites (Bell) | 82, 91 |
|
||
|
||
## Hardware Protocol Notes
|
||
|
||
- RS-485 serial, 57600 baud, 8N1 (via USB-to-RS232 + DTECH RS232-to-RS485 + RJ-25)
|
||
- Motor commands: `a <motor_id> <degrees>` (0=azimuth, 1=elevation)
|
||
- Position query: `a` (in motor submenu) returns `AZ = <val> EL = <val> SK = <val>`
|
||
- Two motor control methods:
|
||
- `a <id> <deg>` — queues command, waits for current motor to finish. Tolerant of rapid command streams from Gpredict.
|
||
- `g <az> <el> <sk>` — immediate move, aborts on any new keystroke/character. Some firmware has a typo listing the order as az/sk/el.
|
||
- Skew motor can run simultaneously with AZ or EL (the others are mutually exclusive)
|
||
- Elevation floor: HAL 2.05 unreliable below 15 degrees with direct motor commands
|
||
- Cable wrap limit: usually 360 or 455 degrees, dish reverses at limit
|
||
- Console does not accept backspace — hit enter to clear on typo
|
||
- Firmware prompt character is `>` (ASCII 62) — used for reliable response termination in prompt-terminated read strategies
|
||
|
||
### RS-485 vs RS-422 — Serial Bus Differences
|
||
|
||
The Winegard variants use two different differential signaling standards. Understanding which one matters for choosing the right USB adapter.
|
||
|
||
**RS-485 half-duplex (2-wire):** One shared differential pair carries both TX and RX. Only one device talks at a time — the transmitter drives the bus, then releases it so the other side can respond. This is how the Trav'ler IDU communicates with the ODU: the IDU sends a command on the shared T/R pair, then listens for the response on the same wires. Simple wiring (2 signal wires + ground), but throughput is limited by the turn-around time between send and receive.
|
||
|
||
**RS-422 full-duplex (4-wire):** Two separate differential pairs — one dedicated TX pair and one dedicated RX pair. Both sides can transmit simultaneously because the signals don't share wires. Higher throughput (no bus turnaround penalty), and the Carryout G2 uses this at 115200 baud. Point-to-point only (one transmitter per pair).
|
||
|
||
**RS-485 full-duplex (4-wire):** Electrically identical to RS-422 wiring (same 4-wire differential pairs), but the RS-485 spec allows multiple transmitters on each pair (multi-drop bus). For our point-to-point dish↔computer connection, 4-wire RS-485 and RS-422 are interchangeable.
|
||
|
||
| Property | RS-485 half-duplex | RS-422 / RS-485 full-duplex |
|
||
|----------|-------------------|----------------------------|
|
||
| Signal wires | 2 (+ GND) | 4 (+ GND) |
|
||
| Direction | One direction at a time | Both directions simultaneously |
|
||
| Max nodes | 32 drivers + 32 receivers | 1 driver + 10 receivers (RS-422) |
|
||
| Max distance | 1200m / 4000ft | 1200m / 4000ft |
|
||
| Max baud | ~10 Mbps | ~10 Mbps |
|
||
| Voltage swing | ±1.5V to ±5V differential | ±2V to ±5V differential |
|
||
| Bus turnaround | Required (adds latency) | Not needed |
|
||
| Typical adapter | USB-to-RS485 (DTECH, etc.) | USB-to-RS422 (FTDI, DIYables, etc.) |
|
||
|
||
**Practical consequence for this project:** The Trav'ler's RJ-25 connector exposes **both** a half-duplex pair (pins 2-3, labeled T/R) **and** a dedicated receive pair (pins 4-5, labeled RXD). Gabe's code uses only the half-duplex pair via an RS-485 adapter. Davidson's G2 code uses all four wires as RS-422. The same physical connector may support both modes depending on the firmware — this is unconfirmed on the Trav'ler but worth testing if you have a 4-wire adapter available.
|
||
|
||
### Serial Connector Pinout
|
||
|
||
The physical connector is an RJ-25 (6P6C) on the Trav'ler or RJ-12 (6P6C) on the G2 — same form factor, same 6-pin modular jack.
|
||
|
||
**Trav'ler pinout (RJ-25, bottom view, clip up):**
|
||
|
||
| Pin | Label | RS-485 use | RS-422 use |
|
||
|-----|-------|-----------|-----------|
|
||
| 1 | GND | Ground | Ground |
|
||
| 2 | T/R- | Shared data- | TX- (computer→dish) |
|
||
| 3 | T/R+ | Shared data+ | TX+ (computer→dish) |
|
||
| 4 | RXD- | (unused in half-duplex) | RX- (dish→computer) |
|
||
| 5 | RXD+ | (unused in half-duplex) | RX+ (dish→computer) |
|
||
| 6 | N/C | Not connected | Not connected |
|
||
|
||
**Carryout G2 pinout (RJ-12, clip away, per Davidson's wiring guide):**
|
||
|
||
| Pin | Wire Color (Davidson) | Wire Color (confirmed) | RS-422 Function |
|
||
|-----|----------------------|----------------------|-----------------|
|
||
| 1 | White | Orange/White | GND (PE) |
|
||
| 2 | Red | Orange | TX+ (TA) — computer→dish |
|
||
| 3 | Black | Green/White | TX- (TB) — computer→dish |
|
||
| 4 | Yellow | Blue | RX+ (RA) — dish→computer |
|
||
| 5 | Green | Blue/White | RX- (RB) — dish→computer |
|
||
| 6 | Blue | Green | Not connected |
|
||
|
||
**Note:** Wire colors vary by cable manufacturer. The "confirmed" column is from a standard
|
||
6P6C flat cable tested 2026-02-12. Always verify with a multimeter before connecting.
|
||
**Polarity is critical:** swapping +/- on the RX pair produces garbled data at the correct
|
||
baud rate (systematic bit inversion, not random noise). Swapping +/- on the TX pair causes
|
||
silent failure (dish doesn't respond because it can't decode the inverted framing).
|
||
|
||
**Adapter chain by variant:**
|
||
|
||
| Variant | Adapter | Wires Used |
|
||
|---------|---------|-----------|
|
||
| Trav'ler (Gabe's setup) | USB→RS232→RS485 (DTECH) | Pins 2-3 only (half-duplex) |
|
||
| Carryout G2 (Davidson) | USB→RS422 (5V TTL) | Pins 2-5 (full-duplex) |
|
||
| Carryout G2 (confirmed) | DSD TECH SH-U11 USB→RS422 (FTDI FT232R) | Pins 1-5 (full-duplex + GND) |
|
||
| Carryout G2 (ESP32) | ESP32 UART2→RS422 module (DIYables) | Pins 2-5 (full-duplex) |
|
||
|
||
### RS-422 Module Notes (DIYables MAX490)
|
||
|
||
The DIYables RS422-to-TTL module uses the **MAX490** transceiver chip (2.5 Mbps max, well above our 115200 baud). Key specs:
|
||
|
||
- 5V TTL logic on the microcontroller side (RXD/TXD)
|
||
- 15 kV ESD protection on RS-422 lines
|
||
- TVS diode for lightning/spike suppression
|
||
- 10 ohm current-limiting resistors for overcurrent protection
|
||
- Built-in 120 ohm termination resistor (reduces echo on long runs)
|
||
- Power + TX/RX activity LEDs
|
||
- Board size: 5.0cm x 2.7cm
|
||
|
||
**Failsafe concern:** The MAX490 does not have failsafe logic, and the module has no provisions for passive failsafe bias resistors. When the RS-422 bus tri-states (no driver active — e.g., between commands, during power transitions, or if the dish firmware is slow to respond), the receiver inputs float and may see random transitions interpreted as garbage data. This can cause spurious bytes in the serial stream.
|
||
|
||
Workaround options:
|
||
1. **Add external bias resistors** — pull A/RX+ toward V+ and B/RX- toward GND through ~560 ohm resistors. This biases the idle bus to a known logic-high state (RS-422 "mark" / idle). Solder to the module or add inline on the RJ-12 breakout.
|
||
2. **Use the prompt-terminated read strategy** — our `CarryoutG2Protocol._send()` reads until `>` (ASCII 62) which naturally filters out garbage between commands, since random transitions are unlikely to produce a valid `>` in context.
|
||
3. **Ignore idle noise in firmware** — the Winegard firmware likely ignores unexpected input while it's processing or idle, but any bytes received during the bus float could corrupt the next valid command if they land in the UART buffer at the wrong time.
|
||
|
||
For short cable runs (under ~3m between ESP32 and dish), the built-in 120 ohm termination is sufficient and bus float is less likely to cause issues. For longer runs or electrically noisy environments (near motors, power supplies), add the bias resistors.
|
||
|
||
### Firmware Console Commands
|
||
|
||
Full command inventory from automated deep probe + interactive `?` exploration
|
||
(firmware 02.02.48, 2026-02-12). Automated probe finds commands that respond
|
||
without arguments; interactive `?` in each submenu reveals the full set including
|
||
parameter-requiring commands the probe misses.
|
||
|
||
#### Root Menu (TRK>)
|
||
|
||
```
|
||
? — list available commands (alias: help)
|
||
a3981 — enter motor driver submenu
|
||
adc — enter ADC submenu
|
||
dipswitch — enter dipswitch submenu
|
||
dvb — enter DVB tuner submenu
|
||
eeprom — enter EEPROM submenu
|
||
gpio — enter GPIO submenu
|
||
latlon — enter lat/lon calculator submenu
|
||
mot — enter motor control submenu
|
||
nvs — enter non-volatile storage submenu
|
||
os — enter OS submenu
|
||
peak — enter peak/DiSEqC switch submenu
|
||
step — enter stepper motor submenu
|
||
q — terminate shell (WARNING: kills UART, requires power cycle!)
|
||
reboot — reboot firmware
|
||
stow — fold dish flat (caution: modified feeds may not survive)
|
||
odu — tunnel to outdoor unit (Trav'ler Pro only)
|
||
ngsearch — enter search submenu (HAL 2.05 only)
|
||
```
|
||
|
||
Note: `command` appeared in automated probe results — this is a false positive.
|
||
The help parser extracted it from the `help [<command>]` usage text, where
|
||
`<command>` is a parameter placeholder, not an actual command.
|
||
|
||
#### A3981 Submenu (A3981>) — Allegro Stepper Driver
|
||
|
||
6 commands. Controls the two A3981 stepper motor driver ICs via SPI.
|
||
|
||
```
|
||
cm — current control mode: AZ/EL both report "AUTO" or "HiZ"/"LoZ"
|
||
diag — fault pin status: "AZ DIAG: OK EL DIAG: OK" (or FAULT)
|
||
reset — reset AZ/EL A3981 fault flags
|
||
sm — step size mode: AZ/EL both report "AUTO" or fixed mode
|
||
ss — step size: returns integer (FULL=16, HALF=8, QTR=4, EIGHTH=2, SIXTEENTH=1)
|
||
st — torque level: AZ/EL report "HIGH" (moving) or "LOW" (idle/holding)
|
||
? / q — help / return to TRK>
|
||
```
|
||
|
||
#### ADC Submenu (ADC>) — Analog-to-Digital Converter
|
||
|
||
5 commands. Hardware-level ADC readings from the LNB signal chain and board ID.
|
||
|
||
```
|
||
bdid — board identity: returns "STATIONARY" (Carryout G2 variant)
|
||
bdrevid — board revision: returns "A"
|
||
m — monitor RSSI (streaming, CR-overwrite line, interrupt with q)
|
||
rssi — single-shot RSSI (raw ADC count, ~233-238 noise floor)
|
||
scan — AZ sweep with per-position RSSI/Lock/SNR readings
|
||
Output: "Motor:<id> Angle:<cdeg> RSSI:<adc> Lock:<0/1> SNR:<dB> Scan Delta:<step>"
|
||
WARNING: without arguments on uncalibrated AZ, targets INT_MAX (2147483647)
|
||
and DEADLOCKS the shell — requires power cycle to recover!
|
||
? / q — help / return to TRK>
|
||
```
|
||
|
||
#### DIPSWITCH Submenu (DIPSWITCH>)
|
||
|
||
1 command. Reads physical DIP switch GPIOs and interprets satellite config code.
|
||
|
||
```
|
||
dipswitch — read dipswitch: "val:<hex32>" (raw GPIO) + "app_dipswitch:<decimal>" (interpreted)
|
||
val:ffffff01 = all switches OFF/up. app_dipswitch:101 = DISH 110+119+129°W
|
||
? / q — help / return to TRK>
|
||
```
|
||
|
||
#### DVB Submenu (DVB>) — BCM4515 Tuner
|
||
|
||
38 commands. Controls the Broadcom BCM4515 DVB-S2 tuner and DiSEqC 2.x LNB interface.
|
||
Help is paginated: `?` shows first page, `man` shows extended commands.
|
||
|
||
```
|
||
agc — stream RF/IF AGC + SNR + NID (continuous, interrupt with q)
|
||
config — BCM hardware/firmware version (ID 0x4515, Rev B0, FW v113.37)
|
||
def — restore DVB defaults
|
||
diag — multi-block per-transponder diagnostics
|
||
dis — display channel parameters (frequency, symbol rate, LNB polarity)
|
||
e <n> <v> — edit channel parameter
|
||
freqs — tuner frequency list
|
||
lnbdc odu — enable LNA in ODU mode (13V = V-pol; boot default 18V = H-pol)
|
||
lnbv — stream LNB voltage readings (continuous, interrupt with q)
|
||
ls — lock status (total reads, no-signal count, glitch count, NID table)
|
||
man — extended help page (shows srch_mode, stats, DiSEqC commands, etc.)
|
||
msw — multi-switch control
|
||
nid — streaming NID reads (Network ID, FFFF = no signal)
|
||
pwr — power control
|
||
qls — quick lock status
|
||
range — signal range test
|
||
rssi <n> — RSSI averaged over n samples (bounded, returns avg + cur)
|
||
shuf — shuffle/reorder transponders
|
||
snr — SNR level (streaming)
|
||
srch — start satellite search
|
||
srch_mode — auto search mode setting
|
||
stats — accumulated satellite read statistics
|
||
t <n> — select transponder
|
||
table — generate transponder table
|
||
tablex — extended transponder table
|
||
tabto — table timeout setting
|
||
to — timeout setting
|
||
di2conf — DiSEqC LNB config register (raw: "3 21180544 238 <4.5")
|
||
di2cs — DiSEqC committed switch command
|
||
di2id — DiSEqC read LNB hardware ID
|
||
di2rcs — DiSEqC read committed switch status
|
||
di2sc — DiSEqC switch control
|
||
di2stat — DiSEqC read LNB status flags
|
||
ovraddr — DiSEqC override address
|
||
pretx — DiSEqC pre-transmit delay
|
||
rrto — DiSEqC receive reply timeout
|
||
send <hex> — raw DiSEqC packet (max 6 bytes, space-delimited hex)
|
||
tdthresh — DiSEqC tone detect threshold
|
||
? / q — help / return to TRK>
|
||
```
|
||
|
||
#### EEPROM Submenu (EE>) — K60 FlexNVM/EEPROM
|
||
|
||
3 commands. Low-level EEPROM access (separate from NVS). Prompt is `EE>`, not `EEPROM>`.
|
||
Most indices read as 0 (unwritten) or fail with val:65793 (0x10101 marker = uninitialized).
|
||
The firmware primarily uses NVS, not EEPROM, for persistent settings.
|
||
|
||
```
|
||
ee <idx> [<v>] — read/write EEPROM value at index
|
||
Read: "Index:<n> Read value = <v>" or "Failed to read eeprom index:<n> val:65793"
|
||
inv <idx> — INVALIDATE EEPROM index (DESTRUCTIVE — marks entry invalid, not "inventory"!)
|
||
def — restore EEPROM defaults
|
||
? / q — help / return to TRK>
|
||
```
|
||
|
||
#### GPIO Submenu (GPIO>)
|
||
|
||
4 commands. Direct access to K60 GPIO ports A-E. Pin naming: `<port><pin>` (e.g., B0, E12).
|
||
|
||
```
|
||
dir <pin> — query pin direction: returns "INPUT" or "OUTPUT"
|
||
r <pin> — read single GPIO pin value (0 or 1)
|
||
regs — dump ALL GPIO pin states across ports A-E (26+16+20+16+14 = 92 pins)
|
||
Note: A20-A23, B12-B15 absent (not bonded). E29 shows "Unknown bit E29"
|
||
w <pin> <val> — write GPIO pin (requires pin name and value)
|
||
? / q — help / return to TRK>
|
||
```
|
||
|
||
#### LATLON Submenu (LATLON>)
|
||
|
||
1 command. Satellite triangulation calculator — computes ground station lat/lon from
|
||
look angles to two known geostationary satellites. Used for auto-location when GPS
|
||
is unavailable. Values stored internally as centidegrees.
|
||
|
||
```
|
||
l <p1> <p2> <p3> <p4>
|
||
— calculate lat/lon from 4 parameters (likely AZ/EL pairs for 2 satellites)
|
||
Output: "anglesentered = <cdeg1> <cdeg2> <cdeg3> <cdeg4>"
|
||
"Lat = <cdeg> Lon = <cdeg>" (centidegrees)
|
||
? / q — help / return to TRK>
|
||
```
|
||
|
||
#### MOT Submenu (MOT>) — Motor Control
|
||
|
||
25 commands. High-level motor control with angle-based positioning.
|
||
|
||
```
|
||
a — show position: Angle[0] (AZ), Angle[1] (EL)
|
||
a <id> <deg> — move motor to absolute angle (0=AZ, 1=EL)
|
||
a <id> +/-deg — relative move (G2 only, undocumented)
|
||
azscan [az_rel] [el_rel] [delay]
|
||
— AZ sweep: scan relative AZ range at EL steps with delay
|
||
azscanwxp [motor] [span_deg] [res_cdeg] [num_xponders]
|
||
— AZ sweep + transponder cycling (radio telescope mode)
|
||
e — engage motors (energize steppers)
|
||
ela2s <deg> — elevation angle to steps converter (centidegrees internally)
|
||
elminmaxhome — show EL limits: "Min: <v> Max: <v> Home: <v>" (NVS values)
|
||
els2a <steps> — elevation steps to angle converter (reports overflow if out of range)
|
||
g <az> <el> — go to AZ/EL (aborts on new input)
|
||
h <id> — home motor to reference position (stall-detect based)
|
||
l — list motors and state (0=AZIMUTH, 1=ELEVATION)
|
||
life — motor lifetime/usage stats
|
||
ma — read max acceleration per motor
|
||
motorboth — simultaneous dual-motor move test
|
||
motorlife — detailed motor life statistics
|
||
mv — max velocity per motor: "Max Vel [0] = <v> / Max Vel [1] = <v>"
|
||
p — read raw step positions
|
||
pid [motor] [Kp] [Kv] [Ki]
|
||
— read or set PID gains for motor control loop
|
||
r — release motors (de-energize steppers)
|
||
sd — stall detection test (motor, direction, timeout)
|
||
sp [motor] [pos]
|
||
— set position (override current position register)
|
||
sw [motor] [pos]
|
||
— set wrap position (cable wrap reference point)
|
||
v — read motor velocities
|
||
vms [motor] [deg_per_rev] [ms]
|
||
— velocity move for duration: spin motor at velocity for N milliseconds
|
||
w [motor] [ON/OFF]
|
||
— wrap manager: enable/disable cable wrap protection per motor
|
||
? / q — help / return to TRK>
|
||
```
|
||
|
||
#### NVS Submenu (NVS>) — Non-Volatile Storage
|
||
|
||
**Caution:** NVS `e <idx> <value>` writes values. Any unrecognized input is treated
|
||
as a sequential index read (no error string), which generates false positives during
|
||
probing but is harmless. `s` saves pending changes to flash.
|
||
|
||
```
|
||
d — dump all NVS values (name/current/saved/default)
|
||
d <idx> — dump single value with details
|
||
e <idx> — read NVS value at index
|
||
e <idx> <v> — write NVS value at index (NOT saved until `s`)
|
||
s — save pending changes to flash
|
||
? / q — help / return to TRK>
|
||
```
|
||
|
||
#### OS Submenu (OS>)
|
||
|
||
```
|
||
id — full MCU/firmware identification (NVS version, System ID, chip)
|
||
reboot — reboot microcontroller
|
||
tasks — list running tasks (HAL 0.0.00 only, not on G2)
|
||
kill <name> — kill a named task (HAL 0.0.00 only, not on G2)
|
||
? / q — help / return to TRK>
|
||
```
|
||
|
||
#### PEAK Submenu (PEAK>) — Signal Peak / DiSEqC Switch
|
||
|
||
6 commands. EchoStar/DiSEqC switch control and LNB polarity-switched RSSI.
|
||
|
||
```
|
||
pw — peak signal search (likely requires sat lock)
|
||
psnr — peak SNR measurement
|
||
pxy1 — peak XY single-axis (likely az or el sweep)
|
||
rssits — RSSI with LNB toggle switch: alternates H-pol (18V, even transponders)
|
||
and V-pol (13V, odd transponders). Reports "Even_sig = <v>, Odd_sig = <v>".
|
||
Noise floor: even ~489, odd ~235 (V-pol quieter).
|
||
stb — STB (set-top box) control / DiSEqC switch test
|
||
ts — EchoStar switch toggle status: "SW Status: 0b<binary> <decimal>"
|
||
(reads 4-bit status, all zeros = no switch connected)
|
||
? / q — help / return to TRK>
|
||
```
|
||
|
||
#### STEP Submenu (STEP>) — Low-Level Stepper Control
|
||
|
||
7 commands. Raw stepper API in microstep units (ustep/sec, ustep/sec/msec).
|
||
MOT wraps STEP with angle-to-step conversion.
|
||
|
||
```
|
||
e — engage motor (same as MOT `e`)
|
||
ma — max acceleration: "Accel[0] = 44 / Accel[1] = 28" (ustep/sec/ms)
|
||
Set: `ma [motor] [ustep/sec/ms]`
|
||
mv — max velocity: "Max Vel [0] = 7222 / Max Vel [1] = 3120" (ustep/sec)
|
||
Set: `mv [motor] [ustep/sec]`
|
||
(7222 ustep/s ÷ 40000 steps/rev × 360° = 65.0°/s AZ)
|
||
(3120 ustep/s ÷ 24960 steps/rev × 360° = 45.0°/s EL)
|
||
p — goto position in raw step counts: `p [motor] [steps]`
|
||
pid — PID values: "Kp=250 Kv=50" (no Ki at STEP level)
|
||
Set: `pid [motor] [Kp] [Kv]`
|
||
r — release motors (same as MOT `r`)
|
||
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).
|
||
|
||
| Index | Setting | Default | Notes |
|
||
|-------|---------|---------|-------|
|
||
| 20 | Disable Tracker Proc? | FALSE | Set TRUE to prevent TV satellite search on boot |
|
||
| 38 | Sleep Mode Timer Secs | 420 | 7 minutes before sleep |
|
||
| 41 | Satellite Scan Velocity | 55.00 | °/s during TV search |
|
||
| 80 | AZ Max Vel | 65.00 | °/s azimuth max velocity |
|
||
| 81 | AZ Max Accel | 400.00 | °/s² azimuth max acceleration |
|
||
| 83 | AZ Steps/Rev | 40000 | Stepper steps per full rotation |
|
||
| 85 | EL Max Vel | 45.00 | °/s elevation max velocity |
|
||
| 88 | EL Steps/Rev | 24960 | Stepper steps per full EL rotation |
|
||
| 101 | Minimum Elevation Angle | 18.00 | Firmware floor (degrees) |
|
||
| 102 | Maximum Elevation Angle | 65.00 | Firmware ceiling (degrees) |
|
||
| 103 | Elevation Home Angle | 65.00 | EL position after homing |
|
||
| 112 | Disable Dipswitch? | FALSE | Override physical DIP switches |
|
||
| 113 | Dipswitch Value | 101 | DirecTV config (ignored when tracker disabled) |
|
||
| 128-133 | AZ/EL PID Gains | varies | Kp/Kv/Ki tuning parameters |
|
||
|
||
### Error Messages
|
||
|
||
| Message | Meaning |
|
||
|---------|---------|
|
||
| `AZ MOTOR STALLED` | Obstruction preventing rotation |
|
||
| `EL MOTOR STALLED` | Obstruction preventing elevation change |
|
||
| `EL Motor Home Failure` | Requires EL recalibration via IDU menu |
|
||
| `Step to Position EL angle error: 2147483647` | INT_MAX sentinel — motor axis uncalibrated/unhomed |
|
||
|
||
### Known Console Hazards
|
||
|
||
- **ADC `scan` without arguments on uncalibrated AZ:** Targets position 2147483647 (INT_MAX),
|
||
motor task blocks forever, shell deadlocks. No serial input (CR, Ctrl+C, ESC, `q`, `reboot`)
|
||
can recover — requires hardware power cycle. The firmware shell is single-threaded: UART
|
||
input is only parsed between command completions, so a blocking motor move prevents all input.
|
||
- **Root `q` command:** Terminates the shell task entirely. Console becomes unresponsive until
|
||
power cycle (same as deadlock, but intentional).
|
||
|
||
### IDU/ODU Cable Wiring (if cut)
|
||
|
||
Top row: Green, Yellow, Orange. Bottom row: Red, Brown, Black.
|
||
|
||
### Power
|
||
|
||
120VAC input to RP-SK87 power supply, outputs 12VDC to IDU. Internal coax carries 12-18VDC bias for LNB — do not connect 5V equipment (SDR LNAs, etc.) without bypassing the power injector.
|
||
|
||
### Physical Setup
|
||
|
||
- Base marked with arrows and "BACK" at 0/360 deg (North)
|
||
- Align "BACK" with true North for accurate tracking
|
||
- Gpredict rotor config: 127.0.0.1:4533, 0->180->360 mode, min EL 15, max EL 90
|
||
- No obstructions taller than 8" within 32.5" of base center
|
||
|
||
### Calibration
|
||
|
||
On power-up, the dish performs calibration movements to establish position and cable wrap limits (~10-15 minutes on Carryout, shorter on Trav'ler). After calibration, firmware automatically starts a TV satellite search — the init sequence kills this.
|
||
|
||
Carryout uses motor stalling (not limit switches) to detect mechanical boundaries — expect audible grinding during calibration.
|
||
|
||
EL recalibration (via IDU buttons): POWER -> ENTER (hold 2s) -> User Menu -> INSTALLATION -> Calibrate EL -> confirm hard stop position.
|
||
|
||
### Emergency Manual Stow
|
||
|
||
Last resort only. 5/16" socket + 6" extension into auxiliary drive hole. Turn clockwise slowly. Ensure arm faces "rear" label before lowering. Improper execution can damage motor.
|
||
|
||
## Known Bugs (from upstream)
|
||
|
||
- **Leap-frog elevation bug:** original `travler_rotor.py` lines 98-105 modify `target_az` instead of `target_el`. Fixed in `leapfrog.py`. Present in both Trav'ler and Trav'ler Pro repos. See `docs/bugs.md`.
|
||
|
||
## Upstream References
|
||
|
||
- github.com/saveitforparts/Travler_Rotor (HAL 0.0.00)
|
||
- github.com/saveitforparts/Trav-ler-Rotor-For-HAL-2.05 (HAL 2.05)
|
||
- github.com/saveitforparts/Travler-Pro-Rotor (Pro, USB)
|
||
- github.com/saveitforparts/Carryout-Rotor (Carryout, HAL 1.00.065)
|
||
- github.com/saveitforparts/Carryout-Radio-Telescope (RF scanning/imaging)
|
||
- github.com/cdavidson0522/winegard-sky-scan (Carryout G2 sky scan + rotator)
|
||
- Gabe Emerson / KL1FI — gabe@saveitforparts.com
|
||
- YouTube: Trav'ler v1 demo (youtu.be/X1hnReHepFI), v2 demo (youtube.com/watch?v=URJZjo5EcpQ)
|
||
|
||
## Testing
|
||
|
||
No hardware-in-the-loop tests yet. Protocol implementations can be mocked for unit testing — `FirmwareProtocol` is an ABC with clear method contracts.
|