Adds send_with_timeout() to CarryoutG2Protocol for long-running
commands, and az_sweep_firmware() to both SerialBridge and DemoDevice.
Sweep and Sky Map modes now try the firmware path first (single
azscanwxp command, streaming results) and fall back to software
step-dwell-measure on error or when "Software mode" checkbox is
checked. Software sweep fixed to set EL once and move AZ only.
Replace time.sleep() with threading.Event.wait() in all poll
loops so worker threads exit immediately on shutdown instead of
blocking for up to 500ms per iteration. Fixes the on_unmount
crash (NoMatches from querying removed DOM nodes) by signaling
the event directly rather than iterating child widgets.
Three shutdown paths covered: q key (on_unmount), Ctrl+C
(try/finally in main), and Textual internal shutdown.
Poll threads (position at 2 Hz, signal when monitoring) run
while-loops with time.sleep() that never exit on app quit.
Add on_unmount() to PositionScreen, SignalScreen, and BirdcageApp
to clear polling flags and disconnect the device, so worker threads
exit within the sleep interval instead of hitting the 300s timeout.
F1 Position (compass rose, motor control, sparklines),
F2 Signal (RSSI gauge with sub-char precision, DVB/ADC sparklines, LNA toggle),
F3 Scan (AZ/EL grid sweep with heatmap and CSV export),
F4 System (NVS table, A3981 diagnostics, motor dynamics),
F5 Console (raw serial terminal with prompt detection and safety gates).
Includes SerialBridge (thread-safe protocol wrapper), DemoDevice
(synthetic simulation for --demo mode), dark RF theme with rounded
borders and teal accents, and send_raw() on CarryoutG2Protocol.
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.
Rewrote hidden_menu_probe.py from Winegard-hardcoded to auto-discovering:
detects prompt, error string, and submenu structure from any firmware console.
Extracted Winegard-specific candidate words to scripts/wordlists/winegard.txt.
Deep probe of all 12 G2 submenus discovered commands across A3981 (driver
diagnostics), ADC (RSSI monitoring + position sweep), DVB (extended help via
man, transponder selection), EEPROM (read/write), GPIO (pin R/W), LATLON
(calculator), MOT (azscan, sw), PEAK (EchoStar switch), and STEP (raw
stepper control). NVS submenu generates false positives — treats any input
as sequential index reads.
Safety: added q/Q to default blocklist, bare-CR check before navigate_to_root
to prevent accidental shell termination between submenus.
Position parser now matches the actual Angle[0]/Angle[1] format
instead of falling back to fragile raw-float extraction. RSSI parser
uses a proper named-group regex matching the real firmware output
format (Reads:<n> RSSI[avg: <v> cur: <v>]) — the old index-based
approach would fail on the actual 5-field response.
Motor test results: both axes move correctly, direction-dependent
overshoot of 0.01-0.06 degrees confirmed. DVB subsystem explored:
BCM4515 Rev B0, firmware v113.37, full command set documented
including DiSEqC 2.x, transponder scanning, and streaming AGC/SNR.
RSSI noise floor is ~500.
Confirmed via DSD TECH SH-U11 RS-422 adapter at 115200 baud:
- Firmware version 02.02.48, bootloader 1.01, Kinetis MCU, BCM4515 DVB
- Position format is Angle[0]/Angle[1], not AZ=/EL= like Trav'ler
- Prompts are TRK>/MOT>/NVS> (not bare >)
- NVS 20 disable tracker confirmed working after power cycle
- Full NVS dump captured (indices 0-143)
- RJ-12 wire colors documented (differ from Davidson's guide)
- RS-422 polarity swap symptoms documented (garbled RX vs silent TX)
- Cable wrap range confirmed: -423.33° to +23.33° (446.66° total)
Replace altitude-validity heuristic with authoritative GGA quality
field (SPS/DGPS/RTK) and GSA nav mode (2D/3D) via TinyGPSCustom
extractors. Send $PAIR062 commands at boot to filter NMEA output
to only GGA/GSA/RMC/GSV and configure PPS for fix-only pulses.
GpsPayload struct gains fix_quality field (16 -> 14 bytes packed).
The RYS352A uses Airoha AG3352 engine with $PAIR proprietary
commands. UBX is u-blox, PMTK is MediaTek — neither applies.
Also document TinyGPS++ v1.1 requirement for GN talker ID
(multi-constellation) support and reference the PAIR command guide.
The AG3352 GNSS engine in the RYS352A ships at 115200 8N1 per
the datasheet spec table. 9600 was a generic assumption that
would cause the UART to read garbage and never acquire a fix.
RYS352A GPS on UART2 (GPIO5/6) with PPS interrupt (GPIO7),
MPU-9250 IMU and BMP388 barometer on shared I2C bus (GPIO8/9).
Sensor data exposed via dedicated BLE service with binary
notify characteristics alongside the existing NUS serial bridge.
Sensors degrade gracefully if not wired.
MPU-9250 provides magnetometer (auto north alignment), accelerometer
(elevation verification), and gyroscope (slew quality). BMP388 provides
pressure and temperature for atmospheric refraction correction at low
elevation angles. Both share I2C bus on GPIO8/9.
NimBLE-based Nordic UART Service (NUS) bridge on ESP32-S3-DevKitC-1.
Transparent passthrough: BLE client writes → UART1 TX → RS-422 → G2,
and G2 → RS-422 → UART1 RX → BLE notifications. USB serial serves as
debug monitor and fallback input.
Uses two MAX485 modules (one locked TX, one locked RX) with a SparkFun
BSS138 level converter for 3.3V/5V translation. Wiring schematic and
RJ-12 pinout documented in docs/ble-bridge-wiring.md.
Extend the rotctld wire protocol with three new commands that enable
signal strength measurement through the Carryout G2's DVB subsystem:
- R [n]: Read RSSI (handles motor→dvb→rssi→motor menu dance internally)
- L: Enable LNA for signal reception (one-time pre-scan setup)
- D: Discover capabilities (returns CAPS:rssi,lna for G2, empty for others)
Non-G2 protocols return RPRT -6 (not available) for R and L commands.
The menu state invariant is maintained after every operation so P commands
continue to work between RSSI reads.
Implement CarryoutG2Protocol based on cdavidson0522/winegard-sky-scan:
prompt-terminated reads via '>' char, 115200 baud RS-422, h <id>
motor homing, DVB/RSSI signal strength measurement. Update CLAUDE.md
with G2 variant column, NVS index 20, dvb sub-commands, and wiring
differences. CLI now accepts --firmware g2.
Add Carryout and Trav'ler Pro to firmware variant table, SK-1000
physical specs, error messages, calibration procedures, emergency
stow notes, motor behavior details, and Gpredict setup config.
Note leap-frog bug is also present in Pro repo.
HAL 0.0.00 kills the search task via the OS task manager (os -> kill Search)
rather than the ngsearch submenu used by HAL 2.05. Boot signal is NoGPS only.
Source: saveitforparts/Travler_Rotor upstream repo.
CLAUDE.md now includes firmware variant comparison table, full command
reference, NVS indices, RS-485 pinout, and cable wiring notes.
Extract Gabe Emerson's Trav'ler rotor scripts into a proper Python
library with firmware protocol abstraction (HAL 2.05 + HAL 0.0.00),
Hamlib rotctld TCP server, Click CLI, and isolated leap-frog algorithm
with the elevation copy-paste bug fixed.