Initial commit: Birdcage documentation site

Starlight/Astro docs covering hardware reverse engineering,
satellite tracking guides, firmware command reference, and
engineering journal entries from the Carryout G2 exploration.

32 pages across getting-started, guides, reference,
understanding, and journal sections.
This commit is contained in:
Ryan Malloy 2026-02-13 05:20:10 -07:00
commit 088c1a5ace
44 changed files with 468217 additions and 0 deletions

View file

@ -0,0 +1,269 @@
---
title: "BLE-to-RS422 Bridge"
description: Building a wireless BLE bridge for the Carryout G2 using an ESP32-S3 and two MAX485 modules
sidebar:
order: 7
---
import { Steps, Aside } from '@astrojs/starlight/components';
This guide covers building a transparent BLE-to-RS422 bridge for the Winegard Carryout G2 satellite dish. The bridge replaces the USB-to-RS422 adapter with a wireless connection using Bluetooth Low Energy (BLE) via the Nordic UART Service (NUS). Optional IMU and barometric sensors add orientation feedback and atmospheric refraction correction for improved tracking accuracy.
## Parts list
### Bridge (required)
- ESP32-S3-DevKitC-1-N16R8
- 2x MAX485 TTL-to-RS485 module
- 1x SparkFun Bidirectional Logic Level Converter (BOB-12009, BSS138-based)
- RJ-12 6P6C straight-wired cable with breakout
- Hookup wire / jumpers
### Sensors (optional)
- 1x GY-9250 (MPU-9250) -- 9-axis IMU (accelerometer + gyroscope + magnetometer)
- 1x BMP388 -- barometric pressure + temperature
- 1x RYS352A GPS module -- observer location + PPS timing
## Build procedure
<Steps>
1. **Wire the power rails.** The ESP32's 5V output powers the level converter's high side and both MAX485 modules. The 3.3V output powers the level converter's low side. All grounds must be connected together.
2. **Wire the level converter.** Connect ESP32 GPIO17 (TX) to LV1 and ESP32 GPIO18 (RX) to LV2 on the SparkFun converter. Connect HV1 to MAX485 board 1 DI, and HV2 to MAX485 board 2 RO.
3. **Lock the TX module (Board 1).** Tie DE and RE on Board 1 to 5V. This permanently enables the driver and disables the receiver -- the board only transmits.
4. **Lock the RX module (Board 2).** Tie DE and RE on Board 2 to GND. This permanently disables the driver and enables the receiver -- the board only receives.
5. **Wire the RJ-12 breakout.** Connect Board 1's A/B to pins 2/3 (TX pair) and Board 2's A/B to pins 4/5 (RX pair). Connect pin 1 to common ground.
6. **Wire the I2C sensors (optional).** Connect MPU-9250 and BMP388 to GPIO8 (SDA) and GPIO9 (SCL) on the shared 3.3V I2C bus.
7. **Wire the GPS module (optional).** Connect RYS352A TX to GPIO5 (UART2 RX), RX to GPIO6 (UART2 TX), and PPS to GPIO7.
8. **Flash the firmware.** See `firmware/ble-bridge/` in the project repository.
</Steps>
## Schematic
### Main bridge circuit
```
SparkFun Level Converter (BOB-12009)
+--------------------------------------+
| |
ESP32 3V3 --------------+| LV HV |+-- ESP32 5V
ESP32 GND --------------+| GND GND |+-- (shared)
| |
ESP32 GPIO17 (TX) ------+| LV1 HV1 |+-----> MAX485_1 DI
ESP32 GPIO18 (RX) +------| LV2 HV2 |+---- MAX485_2 RO
| |
| LV3 (spare) HV3 (spare) |
| LV4 (spare) HV4 (spare) |
+--------------------------------------+
MAX485 Board 1 (TX only) MAX485 Board 2 (RX only)
+------------------------+ +------------------------+
| VCC <-- 5V | | VCC <-- 5V |
| GND <-- GND | | GND <-- GND |
| | | |
| DI <-- HV1 | | RO --> HV2 |
| RO (unused) | | DI (unused) |
| | | |
| DE <-- 5V | locked | | DE <-- GND | locked |
| RE <-- 5V | TX mode | | RE <-- GND | RX mode |
| | | |
| A ---------------------+--> pin 2 | A <---------------------+-- pin 4
| B ---------------------+--> pin 3 | B <---------------------+-- pin 5
+------------------------+ +------------------------+
RJ-12 to Carryout G2
+---------------------------+
| Pin 1 (White) -- GND |<-- ESP32 GND
| Pin 2 (Red) -- TX+/TA |<-- A_1
| Pin 3 (Black) -- TX-/TB |<-- B_1
| Pin 4 (Yellow) -- RX+/RA |--> A_2
| Pin 5 (Green) -- RX-/RB |--> B_2
| Pin 6 (Blue) -- N/C |
+---------------------------+
```
### Power rails
```
ESP32 5V --+-- Level Converter HV
+-- MAX485_1 VCC
+-- MAX485_1 DE + RE (tied high = TX mode)
+-- MAX485_2 VCC
ESP32 3V3 --- Level Converter LV
ESP32 GND -+-- Level Converter GND
+-- MAX485_1 GND
+-- MAX485_2 GND
+-- MAX485_2 DE + RE (tied low = RX mode)
+-- RJ-12 Pin 1
```
## How it works
The Carryout G2 uses RS-422 full-duplex: two separate differential pairs, one for each direction. The MAX485 is a half-duplex RS-485 transceiver with a shared A/B pair and direction control pins (DE/RE). By hardwiring DE/RE, each board is locked into a single direction:
- **Board 1 (TX):** DE=HIGH, RE=HIGH -- driver always enabled, receiver disabled. ESP32 UART1 TX goes through the level shifter to DI, out as differential A/B to the G2 serial RX.
- **Board 2 (RX):** DE=LOW, RE=LOW -- driver disabled, receiver always enabled. G2 serial TX comes in on differential A/B, through RO and the level shifter to ESP32 UART1 RX.
The SparkFun level converter translates between 3.3V (ESP32) and 5V (MAX485) on both data lines. The two spare channels (LV3/HV3, LV4/HV4) are available if DE/RE ever need GPIO control for a half-duplex variant.
The firmware is the same regardless of whether the RS-422 transceiver is a MAX490 (single full-duplex chip) or two MAX485s (locked half-duplex pair). It only sees UART TX/RX on GPIO17/18.
## RJ-12 cable notes
Straight-wired 6P6C. Pin 1 is leftmost when looking at the jack with the clip facing away from you (tab down).
| Pin | Color | Function | Connects to |
|-----|--------|------------------|--------------------|
| 1 | White | GND | Common ground |
| 2 | Red | TX+ (TA) | MAX485 Board 1 A |
| 3 | Black | TX- (TB) | MAX485 Board 1 B |
| 4 | Yellow | RX+ (RA) | MAX485 Board 2 A |
| 5 | Green | RX- (RB) | MAX485 Board 2 B |
| 6 | Blue | N/C | -- |
If crimping your own cable, verify pin-to-color with a multimeter before connecting to the dish. RJ-12 crimps are easy to get reversed (pins mirror if the connector is flipped). A wrong connection won't damage anything (differential signals are current-limited) but communication won't work.
## Sensors -- I2C bus
The MPU-9250 and BMP388 share a single I2C bus on GPIO8 (SDA) / GPIO9 (SCL). Both run at 3.3V directly from the ESP32, no level shifting needed.
### I2C bus schematic
```
I2C Bus (3.3V, 400kHz)
----------------------
ESP32 3V3 --+------------------+--- MPU-9250 VCC
| +--- BMP388 VCC
|
+-- 4.7K_ohm -- SDA bus --+-- MPU-9250 SDA
| +-- BMP388 SDI
|
+-- 4.7K_ohm -- SCL bus --+-- MPU-9250 SCL
+-- BMP388 SCK
ESP32 GPIO8 (SDA) ---- SDA bus
ESP32 GPIO9 (SCL) ---- SCL bus
ESP32 GND --+-- MPU-9250 GND
+-- BMP388 GND (SDO to GND = addr 0x76)
MPU-9250 AD0 -- GND (I2C address = 0x68)
BMP388 SDO -- GND (I2C address = 0x76)
```
The 4.7K-ohm pull-ups are shared -- one pair for the whole bus. Many breakout boards include onboard pull-ups already; if both the GY-9250 and BMP388 boards have them, the combined parallel resistance (~2.3K-ohm) is still fine for 400kHz I2C at 3.3V. Only add external pull-ups if neither board has them.
### MPU-9250 (GY-9250) -- 9-axis IMU
| Property | Value |
|----------|-------|
| I2C Address | 0x68 (AD0 to GND) |
| VCC | 3-5V (onboard LDO) |
| Interface | I2C (up to 400kHz) or SPI |
What it provides for satellite tracking:
- **Magnetometer (AK8963):** Compass heading for automatic north alignment. Eliminates manual alignment of dish base "BACK" marking to true north. Apply local magnetic declination to convert magnetic north to true north.
- **Accelerometer:** Gravity vector gives tilt angle = elevation. Independent verification of the dish firmware's reported EL position.
- **Gyroscope:** Angular rate during slews. Detect oscillation, overshoot, and vibration for tuning the leapfrog overshoot compensation algorithm.
<Aside type="tip" title="Mounting matters">
The magnetometer is extremely sensitive to nearby ferrous metals and electromagnetic interference from motors. Mount on the fixed base plate, away from motor housings, with a known axis aligned to the dish's reference direction. Rigid mounting -- any flex between sensor and dish structure introduces measurement error.
</Aside>
### BMP388 -- barometric pressure + temperature
| Property | Value |
|----------|-------|
| I2C Address | 0x76 (SDO to GND) |
| VCC | 3.3V |
| Pressure range | 300-1250 hPa |
| Pressure resolution | +/-0.01 hPa (+/-8 cm altitude) |
| Temperature accuracy | +/-0.5 degrees C |
| Interface | I2C (up to 3.4MHz) or SPI |
What it provides for satellite tracking:
- **Atmospheric refraction correction.** Radio signals bend as they pass through the atmosphere, especially at low elevation angles. The amount of bending depends on air pressure and temperature. At 15 degrees elevation (the Trav'ler's minimum), refraction shifts apparent position by ~0.2 degrees. Standard refraction models (Bennett, Saemundsson) take pressure and temperature as inputs -- the BMP388 provides both in real time.
- **Temperature monitoring.** Ambient temperature at the dish for thermal drift awareness and electronics health monitoring.
The simplified Bennett refraction formula:
```
R = 1/tan(el + 7.31/(el + 4.4)) * (P/1010) * (283/(273 + T))
```
Where R is refraction in arcminutes, el is apparent elevation in degrees, P is pressure in hPa, T is temperature in degrees C. At el=15, P=1013, T=20: R is approximately 3.4 arcmin (0.057 degrees). Small but meaningful for narrow-beam antennas.
## GPS -- RYS352A
The RYS352A is a compact GPS module with PPS output. It connects via UART2 and provides observer location for satellite pass prediction and a 1Hz PPS pulse for precise UTC time synchronization.
### GPS wiring
```
ESP32 GPIO5 (UART2 RX) <-- RYS352A TX (NMEA sentences out)
ESP32 GPIO6 (UART2 TX) --> RYS352A RX (config commands in, optional)
ESP32 GPIO7 <-- RYS352A PPS (1Hz rising edge, ~100ns jitter)
ESP32 3V3 --> RYS352A VCC
ESP32 GND --> RYS352A GND
```
| Module Pin | ESP32 Pin | Function |
|------------|-----------|----------|
| VCC | 3V3 | 3.3V power (onboard LDO on most breakouts) |
| GND | GND | Ground |
| TX | GPIO5 (UART2 RX) | NMEA sentence output at 115200 baud |
| RX | GPIO6 (UART2 TX) | PAIR/NMEA config input (optional) |
| PPS | GPIO7 | 1Hz pulse synchronized to GPS time |
**PPS (Pulse Per Second):** The RYS352A outputs a precise 1Hz pulse on the rising edge, synchronized to UTC via GPS constellation. The firmware captures this edge via interrupt for correlating satellite events with sub-microsecond precision. The module's RTC battery backup enables warm starts (~5s) after initial cold start fix (~30-60s).
**UART notes:** The RYS352A defaults to 115200 baud NMEA output with `GN` talker ID (multi-constellation). The TX line (GPIO6) sends `$PAIR` proprietary commands at boot to configure the GPS module for satellite tracking use:
- Only GGA (position/quality), GSA (fix mode/DOP), RMC (time/date/speed), and GSV (satellite visibility, every 5th fix) are enabled
- Redundant sentences (GLL, VTG, ZDA, GRS, GST, GNS) are disabled to reduce parser load and latency
- PPS is configured to pulse only on 2D/3D fix with 100ms pulse width
- Each command waits for `$PAIR001` ACK; failures are logged but non-fatal
## Full GPIO map
| GPIO | Function | Interface | Notes |
|------|----------|-----------|-------|
| 5 | GPS RX | UART2 RX | From RYS352A TX (NMEA out) |
| 6 | GPS TX | UART2 TX | To RYS352A RX (config in) |
| 7 | GPS PPS | GPIO interrupt | 1Hz rising edge |
| 8 | I2C SDA | I2C | MPU-9250 + BMP388 (shared bus) |
| 9 | I2C SCL | I2C | MPU-9250 + BMP388 (shared bus) |
| 17 | RS-422 TX | UART1 TX | To level shifter to MAX485 Board 1 DI |
| 18 | RS-422 RX | UART1 RX | From level shifter from MAX485 Board 2 RO |
| 38 | RGB LED | WS2812 | Onboard NeoPixel (DevKitC V1.1) |
| 43 | USB Console TX | UART0 | CH343 USB-serial (untouched) |
| 44 | USB Console RX | UART0 | CH343 USB-serial (untouched) |
## Loopback test
<Aside type="tip" title="Test before connecting to the dish">
Before connecting to the G2, verify the bridge by shorting MAX485 Board 1 A to MAX485 Board 2 A, and MAX485 Board 1 B to MAX485 Board 2 B (loop TX back into RX). Anything sent via BLE or USB serial should echo back. This confirms:
- The level converter is translating correctly between 3.3V and 5V
- Both MAX485 modules are in the correct mode (TX-only and RX-only)
- The ESP32 UART1 is configured at the right baud rate
- The BLE Nordic UART Service is passing data transparently
If the loopback works but the dish doesn't respond after connecting, check the RJ-12 wiring -- particularly the TX+/TX- polarity. Swapping the TX pair causes silent failure (the dish can't decode the inverted framing).
</Aside>

View file

@ -0,0 +1,161 @@
---
title: "Calibration & Homing"
description: How the dish establishes its position reference and what to do when it gets lost
sidebar:
order: 3
---
import { Steps, Aside } from '@astrojs/starlight/components';
The Winegard dishes use stepper motors with no absolute position encoders. They determine their position by **homing** -- driving each axis until a mechanical stall is detected, then counting steps from that reference point. If the position reference is lost (power failure, missed steps, uncalibrated boot), the firmware has no idea where the dish is pointing.
## Power-up calibration
On a normal power-up with the tracker enabled, the dish runs a full calibration sequence automatically:
<Steps>
1. **Bootloader initializes.** Firmware loads, peripherals (SPI for motor drivers, SPI for DVB tuner) are configured.
2. **Elevation homes first.** The EL motor drives upward until it stalls against the mechanical hard stop. The firmware uses a 2-second stall detection timeout. This is the EL reference position (65.00 degrees on the G2, per NVS index 103).
3. **Azimuth homes second.** The AZ motor drives in one direction until it stalls (8-second timeout). This establishes the AZ reference and cable wrap limits.
4. **Cable wrap limits are set.** On the Carryout G2, homing output reports `wrap_min:-42333 wrap_max:2333` (centidegrees), giving a total range of approximately 446.66 degrees.
5. **TV satellite search begins.** The firmware starts scanning for DirecTV/DISH satellites. This is where you [disable the search](/guides/disabling-search/).
</Steps>
<Aside type="caution" title="Expect grinding sounds">
The Carryout and Carryout G2 use motor stalling (not limit switches) to detect mechanical boundaries. The stall detection works by driving the motor at a known current and watching for the step position to stop advancing. You will hear audible grinding during this process -- this is normal and expected. The original Carryout calibration takes approximately 10-15 minutes. The G2 is faster.
</Aside>
## Carryout G2 boot sequence (detailed)
The G2's boot process is well-documented from serial capture:
1. Bootloader v1.01 starts
2. SPI1 init at 4 MHz -- configures the two A3981 stepper motor driver ICs (mode 0x03)
3. Motor init -- System=12Inch, master=40000 steps/rev (AZ), slave=24960 steps/rev (EL), ratio=1.602564
4. SPI2 init at 6.857 MHz -- configures the BCM4515 DVB tuner (mode 0x03)
5. `EXTENDED_DVB_DEBUG ENABLED`
6. DVB init -- AP RAM FW verified, BCM4515 ID 0x4515 Rev B0, FW v113.37, strap 0x25018
7. Auto-search config -- blind scan, 18000-24000 ksps, rolloff 0.35
8. `Enabled LNB STB`
9. `Ant ID - 12-IN G2`
10. NVS load from flash
11. EL home (stall detect, 2s timeout)
12. AZ home (stall detect, 8s timeout)
13. `Antenna Facing Front`
14. `TRK>` prompt appears (if tracker disabled) or search starts
## When NVS 20 is TRUE (tracker disabled)
If you've set NVS 20 to TRUE to [permanently disable the TV search](/guides/disabling-search/), the firmware skips the homing sequence entirely. The motors stay uncalibrated, and the AZ position register contains **2147483647** (INT_MAX) -- a sentinel value meaning "position unknown."
This means you must home the motors manually before issuing any move commands.
### Manual homing (Carryout G2)
The G2 has an explicit `h <id>` homing command in the motor submenu:
```
TRK> mot
MOT> h 0
```
This homes motor 0 (azimuth). Wait for it to complete -- the motor will drive until it stalls, then report its reference position.
```
MOT> h 1
```
This homes motor 1 (elevation). Same stall-detect process, shorter timeout (2s vs 8s for AZ).
After both motors are homed, position queries with `a` will return valid angles instead of INT_MAX.
<Aside type="caution" title="Do not skip homing">
Running motor commands on an uncalibrated axis is dangerous. The firmware has no idea where the dish is -- commanding `a 0 180` when the position register says 2147483647 may attempt to drive the motor billions of steps, deadlocking the shell and requiring a power cycle. The ADC `scan` command is especially hazardous on uncalibrated axes.
</Aside>
## EL recalibration via IDU buttons
On the Trav'ler (with the indoor display unit), elevation can be recalibrated through the IDU menu:
<Steps>
1. Press **POWER** to turn on the IDU.
2. Press and hold **ENTER** for 2 seconds to open the User Menu.
3. Navigate to **INSTALLATION**.
4. Select **Calibrate EL**.
5. Confirm the hard stop position when prompted.
</Steps>
This re-establishes the EL reference by driving to the mechanical stop, similar to the automatic homing but initiated manually through the user interface.
## Cable wrap protection
The azimuth motor has a limited rotational range before the internal cables wrap too tightly and risk damage. The firmware tracks this and reverses direction at the wrap limits.
On the Carryout G2, confirmed wrap limits are:
| Parameter | Value | Degrees |
|-----------|-------|---------|
| `wrap_min` | -42333 centidegrees | -423.33 degrees |
| `wrap_max` | 2333 centidegrees | 23.33 degrees |
| **Total range** | 44666 centidegrees | **~446.66 degrees** |
The wrap manager can be queried and controlled from the motor submenu:
```
MOT> w 0
```
Shows the current wrap status for motor 0 (AZ). Use `w 0 ON` or `w 0 OFF` to enable or disable wrap protection (disabling is not recommended).
The SK-1000 (full-size Trav'ler) has a wrap range of 0-455 degrees.
## Elevation limits
Each variant has firmware-enforced elevation limits:
| Variant | Min Elevation | Max Elevation |
|---------|--------------|--------------|
| HAL 0.0.00 | 15 degrees | 90 degrees |
| HAL 2.05 | 15 degrees | 90 degrees |
| Trav'ler Pro | 12 degrees | 75 degrees (hardware cap) |
| Carryout (2003) | 22 degrees | 73 degrees (NVS 102 override) |
| Carryout G2 | 18 degrees | 65 degrees |
On the G2, these are stored in NVS indices 101 (min) and 102 (max). They can be read with:
```
MOT> elminmaxhome
Min: 18.00 Max: 65.00 Home: 65.00
```
## Emergency manual stow
Last resort only, for when the dish is deployed and firmware is unresponsive.
<Steps>
1. Get a **5/16" socket** with a **6" extension**.
2. Insert the socket into the **auxiliary drive hole** on the motor assembly.
3. Turn **clockwise slowly** to drive the arm down.
4. **Ensure the arm faces the "rear" label** before lowering to avoid collision with the base.
</Steps>
<Aside type="caution" title="Risk of motor damage">
Improper execution of manual stow can strip the motor gearing or bend the arm. Only use this procedure when you cannot power the dish or command a software stow. The firmware `stow` command is always preferred.
</Aside>

View file

@ -0,0 +1,143 @@
---
title: "Disabling TV Search"
description: How to prevent the dish from automatically searching for TV satellites on boot
sidebar:
order: 2
---
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
On power-up, every Winegard dish variant attempts to calibrate and then search for DirecTV or DISH Network satellites. For amateur radio use, this search must be killed -- it fights your tracking commands and wastes time scanning the sky for TV signals you don't need.
Each firmware variant has a different method for stopping the search. Some are temporary (per-boot), others are permanent (written to non-volatile storage).
## Per-variant instructions
<Tabs>
<TabItem label="HAL 0.0.00">
### Trav'ler (HAL 0.0.00)
The original Trav'ler uses the `os` (operating system) submenu to kill the search task.
```
> os
OS> kill Search
```
This terminates the `Search` task for the current session. You need to repeat this on every power cycle.
**Sequence:**
1. Wait for the boot prompt (`>` or `NoGPS` message)
2. Enter `os` to open the OS submenu
3. Send `kill Search` to stop the satellite search task
4. Send `q` to return to the root menu
5. Enter `mot` to access motor control
</TabItem>
<TabItem label="HAL 2.05">
### Trav'ler (HAL 2.05.003)
HAL 2.05 uses a different path through the `ngsearch` submenu.
```
> ngsearch
NGSEARCH> s
NGSEARCH> q
```
**Sequence:**
1. Wait for boot (look for `NoGPS` or `No LNB Voltage`)
2. Enter `ngsearch` to open the search submenu
3. Send `s` to stop the current search
4. Send `q` to exit back to root
5. Enter `mot` (or `motor` on some HAL 2.05 builds) to access motor control
This is a per-boot operation -- the search restarts on every power cycle.
</TabItem>
<TabItem label="Trav'ler Pro">
### Trav'ler Pro
The Pro uses the same `os` / `kill Search` method as HAL 0.0.00, but you may need to tunnel to the outdoor unit first since the Pro's IDU has its own MCU.
```
> os
OS> kill Search
```
If the kill doesn't stop dish movement, try tunneling to the ODU first:
```
> odu
ODU> os
OS> kill Search
```
This is a per-boot operation.
</TabItem>
<TabItem label="Carryout">
### Carryout (2003)
The original Carryout doesn't have a firmware command to kill the search. Instead, it uses **physical DIP switches** on the unit.
Setting all DIP switches to **off (up)** may disable search mode, but behavior varies by unit. There is no guaranteed software method.
<Aside type="caution" title="Inconsistent behavior">
DIP switch behavior is not consistent across Carryout units. Some units ignore the switches entirely. If the dish continues searching after flipping all switches off, you may need to wait for the search to complete before taking motor control.
</Aside>
</TabItem>
<TabItem label="Carryout G2">
### Carryout G2
The G2 provides a **permanent** disable via non-volatile storage index 20. Once set, the tracker process is disabled across reboots.
```
TRK> nvs
NVS> e 20 1
NVS> s
NVS> q
```
**Breakdown:**
1. `nvs` -- enter the NVS submenu
2. `e 20 1` -- set index 20 ("Disable Tracker Proc?") to TRUE (1)
3. `s` -- save the change to flash (required -- without this, the change is lost on reboot)
4. `q` -- return to root menu
To re-enable the tracker later:
```
TRK> nvs
NVS> e 20 0
NVS> s
NVS> q
```
<Aside type="caution" title="Homing is skipped when tracker is disabled">
When NVS 20 = TRUE, the firmware skips the homing sequence entirely on boot. The motors stay uncalibrated and the AZ position reads as **2147483647** (INT_MAX sentinel). You must manually home the motors with `mot` then `h 0` (AZ) and `h 1` (EL) before issuing any move commands. See the [Calibration & Homing](/guides/calibration/) guide.
</Aside>
</TabItem>
</Tabs>
## What happens after disabling search
Once the search is killed, the dish stops moving on its own and waits for your commands. At this point you can:
- Enter the motor submenu (`mot` on most variants, `motor` on HAL 2.05, `target` on the original Carryout)
- Query the current position with `a`
- Issue move commands with `a <motor_id> <degrees>`
- Start the [rotctld server](/guides/satellite-tracking/) for Gpredict integration
The `birdcage init` command automates this entire sequence -- it waits for boot, kills the search, enters the motor submenu, and leaves the dish ready for tracking. See the [satellite tracking guide](/guides/satellite-tracking/) for the full workflow.

View file

@ -0,0 +1,193 @@
---
title: "Firmware Probing"
description: How to use the console-probe tool to discover and document firmware commands
sidebar:
order: 5
---
import { Steps, Aside } from '@astrojs/starlight/components';
The `console-probe` tool is a generic embedded console scanner built for reverse-engineering firmware command interfaces. It auto-detects the prompt format, parses help output, enters submenus, and optionally brute-forces candidate command names. It was developed for the Winegard firmware but works with any prompt-based serial console.
## How console-probe works
The discovery process follows a structured sequence:
<Steps>
1. **Prompt detection.** The tool sends empty lines (carriage returns) and analyzes the response to identify the prompt string. On the Carryout G2, this detects `TRK>` as the root prompt.
2. **Error string detection.** A known-bad command is sent to capture the error response pattern. This lets the tool distinguish between "command recognized" and "command unknown" for subsequent probing.
3. **Help parsing.** The tool sends the help command (default: `?`) and parses the response to extract known commands, their parameters, and descriptions. It identifies which commands are submenus (they change the prompt when entered).
4. **Submenu discovery.** For each discovered submenu, the tool enters it, queries help, and records the available commands. The prompt changes (e.g., `TRK>` to `MOT>`) confirming successful submenu entry.
5. **Brute-force probing (optional).** With `--deep`, the tool sends candidate command names from a wordlist and checks whether each one produces a valid response (as opposed to the error string). This catches commands not listed in help output.
</Steps>
## Basic usage
### Discover commands via help only
The fastest mode -- queries `?` at each menu level, no brute-force:
```bash
console-probe --port /dev/ttyUSB2 --baud 115200 --discover-only
```
This enters every discovered submenu, queries help, and prints the command inventory. Add `--json` to save a structured report:
```bash
console-probe --port /dev/ttyUSB2 --baud 115200 --discover-only --json /tmp/discover.json
```
### Deep probe with wordlist
Full discovery plus brute-force probing of all submenus:
```bash
console-probe --port /dev/ttyUSB2 --baud 115200 --deep --wordlist scripts/wordlists/winegard.txt
```
The wordlist file contains one candidate command per line. The bundled `winegard.txt` includes terms extracted from firmware strings and documentation.
### Probe a single submenu
Target a specific submenu without scanning all of them:
```bash
console-probe --port /dev/ttyUSB2 --baud 115200 --submenu dvb
```
## CLI options reference
### Connection options
| Option | Default | Description |
|--------|---------|-------------|
| `--port` | `/dev/ttyUSB0` | Serial port |
| `--baud` | `115200` | Baud rate |
| `--line-ending` | `cr` | Line ending to send (`cr`, `lf`, `crlf`) |
### Discovery overrides
| Option | Default | Description |
|--------|---------|-------------|
| `--prompt` | auto-detect | Override the root prompt string |
| `--error` | auto-detect | Override the error response string |
| `--help-cmd` | `?` | Command to request help |
| `--exit-cmd` | `q` | Command to exit submenus |
### Probing options
| Option | Default | Description |
|--------|---------|-------------|
| `--discover-only` | off | Help-only mode, no brute-force |
| `--deep` | off | Probe all discovered submenus |
| `--submenu NAME` | none | Probe a single named submenu |
| `--timeout` | `0.5` | Per-command timeout in seconds |
| `--blocklist` | `reboot,stow,def,q,Q` | Commands to never send |
| `--wordlist FILE` | none | Extra candidate words file (repeatable) |
### Output options
| Option | Default | Description |
|--------|---------|-------------|
| `--json FILE` | none | Write results as JSON to file |
<Aside type="tip" title="Use ? in submenus manually">
Even without console-probe, you can explore the firmware interactively. Enter any submenu and type `?` to see its commands. On the G2's DVB submenu, help is paginated -- `?` shows the first page and `man` shows extended commands including DiSEqC controls.
</Aside>
## The JSON report
When `--json` is specified, the tool writes a structured report with format version 2. The report contains:
```json
{
"format_version": 2,
"device": {
"port": "/dev/ttyUSB2",
"baud": 115200,
"root_prompt": "TRK>",
"error_string": "type '?' for help"
},
"menus": {
"TRK": {
"help": [
{"name": "mot", "params": "", "description": "enter motor control submenu"},
{"name": "dvb", "params": "", "description": "enter DVB tuner submenu"}
],
"probe_hits": [],
"undiscovered": []
},
"MOT": {
"help": [...],
"probe_hits": [["a", "Angle[0] = 180.00 Angle[1] = 45.00"]],
"undiscovered": [["xyz", "some unexpected response"]]
}
}
}
```
Key sections in each menu:
- **`help`** -- Commands discovered via the `?` command, with parameter syntax and descriptions.
- **`probe_hits`** -- Commands that produced a valid (non-error) response during brute-force probing.
- **`undiscovered`** -- Commands found by probing that were *not* in the help output. These are the interesting findings -- hidden or undocumented commands.
## Practical tips
### Blocklist safety
The default blocklist (`reboot,stow,def,q,Q`) prevents the probe from sending commands that would disrupt the session. The `reboot` command restarts the firmware. The `stow` command folds the dish flat (dangerous if you've modified the feed). The `def` command restores factory defaults. And `q` exits the shell entirely on the G2 root menu (kills UART, requires power cycle).
Add more commands to the blocklist if needed:
```bash
console-probe --port /dev/ttyUSB2 --baud 115200 --deep \
--blocklist "reboot,stow,def,q,Q,scan,kill"
```
<Aside type="caution" title="The scan command is dangerous">
On the Carryout G2, running `scan` in the ADC submenu without arguments on an uncalibrated AZ axis targets position 2147483647 (INT_MAX). The motor task blocks forever and the shell deadlocks. No serial input can recover it -- you need a hardware power cycle. Add `scan` to your blocklist if probing the ADC submenu.
</Aside>
### NVS submenu false positives
The NVS submenu treats any unrecognized input as a sequential index read (no error string). This means every candidate command "succeeds" during brute-force probing, producing false positives. The results are harmless (read-only) but noisy. The `--discover-only` mode avoids this issue since it only queries help.
### Using with non-Winegard devices
The tool is not Winegard-specific. It works with any serial console that has:
- A recognizable prompt string (auto-detected or specified with `--prompt`)
- A consistent error response for unknown commands (auto-detected or `--error`)
- A help command that lists available commands (configurable with `--help-cmd`)
```bash
# U-Boot bootloader example
console-probe --port /dev/ttyUSB0 --baud 115200 \
--prompt "U-Boot>" --error "Unknown command" --help-cmd "help"
```
## Package structure
The `console-probe` package is organized as:
```
profile.py -- DeviceProfile + HelpEntry dataclasses
serial_io.py -- Prompt-aware serial I/O
discovery.py -- Auto-discovery, help parsing, submenu probing, candidate generation
report.py -- JSON report writer (format_version 2)
cli.py -- argparse CLI entry point
```
Install and run:
```bash
uv sync
uv run console-probe --help
```

View file

@ -0,0 +1,241 @@
---
title: "Radio Telescope Mode"
description: Using the Carryout G2's built-in DVB tuner and azscanwxp command for RF sky mapping
sidebar:
order: 6
---
import { Steps, Aside, LinkCard } from '@astrojs/starlight/components';
The Carryout G2's firmware includes a built-in radio telescope mode: the `azscanwxp` command performs an azimuth sweep while cycling through DVB transponders at each position, measuring RSSI (received signal strength) at every grid point. Combined with the Ku-band LNB and motorized AZ/EL positioning, this turns the dish into a rudimentary RF imaging system.
This capability was originally discovered by Chris Davidson in his [winegard-sky-scan](https://github.com/cdavidson0522/winegard-sky-scan) project.
## Hardware requirements
- **Carryout G2** with firmware 02.02.48 (or compatible)
- RS-422 serial connection at 115200 baud (see [Cable Wiring](/guides/wiring/))
- Motors **homed and calibrated** (see [Calibration & Homing](/guides/calibration/))
- TV search disabled (see [Disabling TV Search](/guides/disabling-search/))
<Aside type="danger" title="Homed motors required">
The azscanwxp command requires homed motors. Running it on uncalibrated axes causes the firmware to target position 2147483647 (INT_MAX) -- the motor task blocks forever and the shell deadlocks. No serial input can recover it. You must power-cycle the dish to regain control. Always verify that position queries return valid angles (not INT_MAX) before scanning.
</Aside>
## Setting up the DVB tuner
Before scanning, enable the LNA and configure the tuner frequency.
<Steps>
1. **Enter the DVB submenu from root.**
```
TRK> dvb
DVB>
```
2. **Enable the LNA.** The `lnbdc odu` command enables the LNB low-noise amplifier in outdoor unit mode, setting 13V (V-pol). The boot default is 18V (H-pol).
```
DVB> lnbdc odu
```
3. **Check current channel parameters.**
```
DVB> dis
```
This shows the frequency, symbol rate, and LNB polarity currently configured.
4. **Select a transponder (optional).** If you want to scan at a specific frequency:
```
DVB> t 1
```
Use `freqs` to list available transponder frequencies. 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).
5. **Verify RSSI readings work.**
```
DVB> rssi 10
Reads:10 RSSI[avg: 498 cur: 502]
```
The noise floor is approximately 500. If you see readings near this value, the LNA is active but no strong signal is present -- this is normal for a clear sky pointing away from any satellites.
6. **Return to the root menu.**
```
DVB> q
TRK>
```
</Steps>
## The azscanwxp command
### Entering the command
The command lives in the MOT submenu:
```
TRK> mot
MOT> azscanwxp [motor] [span] [resolution] [num_xponders]
```
### Parameters
| 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
Sweep 10 degrees on azimuth at 1.00 degree steps, checking 3 transponders per position:
```
MOT> azscanwxp 0 10 100 3
```
### Output format
Each measurement point produces a line:
```
Motor:<id> Angle:<cdeg> RSSI:<adc> Lock:<0/1> SNR:<dB> Scan Delta:<step>
```
| Field | Description |
|-------|-------------|
| `Motor` | Motor ID being swept |
| `Angle` | Current position in centidegrees |
| `RSSI` | Received signal strength (raw ADC count) |
| `Lock` | DVB carrier lock status (1 = locked, 0 = no lock) |
| `SNR` | Signal-to-noise ratio in dB |
| `Scan Delta` | Step count since last position |
## Sky mapping workflow
<Steps>
1. **Home the motors** if not already done.
```
TRK> mot
MOT> h 0
MOT> h 1
```
2. **Position the dish at the starting elevation.** Choose an EL angle for the first scan strip.
```
MOT> a 1 30
```
3. **Enable the LNA** (if not done already).
```
MOT> q
TRK> dvb
DVB> lnbdc odu
DVB> q
TRK> mot
```
4. **Run the azimuth sweep.** Log the serial output to a file for post-processing.
```
MOT> azscanwxp 0 360 100 3
```
This sweeps the full 360-degree azimuth range at 1-degree steps with 3 transponders per position.
5. **Increment elevation and repeat.** Move up by your desired EL step and run another AZ sweep.
```
MOT> a 1 35
MOT> azscanwxp 0 360 100 3
```
6. **Post-process the data.** Parse the serial output into a grid of AZ/EL/RSSI values and render as a 2D heatmap. Each scan line gives you one row of the image.
</Steps>
## The azscan command (simpler variant)
The MOT submenu also has a simpler `azscan` command that doesn't cycle transponders:
```
MOT> azscan [az_range] [el_range] [delay]
```
This performs an AZ sweep with RSSI measurements at each position but without the transponder cycling. It's faster but provides less frequency diversity.
## RSSI and signal interpretation
| RSSI Value | Meaning |
|------------|---------|
| ~233-238 | ADC noise floor (no signal, no LNA) |
| ~489-502 | LNA active, noise floor (clear sky) |
| Above 600 | Weak signal detected |
| Above 1000 | Strong signal (likely a satellite) |
The ADC `rssi` command (in the ADC submenu) gives raw ADC counts. The DVB `rssi <n>` command (in the DVB submenu) averages over n samples and provides both average and current readings.
The PEAK submenu's `rssits` command alternates between H-pol (18V, even transponders) and V-pol (13V, odd transponders), reporting separate readings for each polarization:
```
PEAK> rssits
Even_sig = 489, Odd_sig = 235
```
V-pol (odd) has a quieter noise floor than H-pol (even).
## LNB polarity control via DiSEqC
The BCM4515 DVB tuner includes 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 LNB polarity and band selection.
For ham radio use, the key commands are:
| Command | Function |
|---------|----------|
| `lnbdc odu` | Set 13V (V-pol) -- enables LNA in outdoor unit mode |
| `send E0 10 38 F0` | Raw DiSEqC packet: select switch port 1 |
| `send E0 10 38 F1` | Raw DiSEqC packet: select switch port 2 |
The boot default is 18V (H-pol). Polarity affects which transponders are visible and the RSSI noise floor.
### DiSEqC timing parameters
| Parameter | 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 |
## Rotctld RSSI extensions
The `birdcage serve` command adds RSSI support to the rotctld protocol when running with a Carryout G2:
```bash
# Read RSSI (10 samples)
echo "R 10" | nc 127.0.0.1 4533
# Enable LNA
echo "L" | nc 127.0.0.1 4533
# Check capabilities
echo "D" | nc 127.0.0.1 4533
# Returns: CAPS:rssi,lna
```
This allows external software to combine position control with signal measurements over a single TCP connection.
<LinkCard title="winegard-sky-scan" href="https://github.com/cdavidson0522/winegard-sky-scan" description="Chris Davidson's original Carryout G2 sky scan and rotator project on GitHub." />

View file

@ -0,0 +1,220 @@
---
title: "Satellite Tracking with Gpredict"
description: End-to-end guide for tracking amateur radio satellites using Gpredict and the birdcage CLI
sidebar:
order: 4
---
import { Steps, Aside, LinkCard } from '@astrojs/starlight/components';
This guide walks through the complete setup: installing the software, connecting to the dish, starting the rotctld server, configuring Gpredict, and tracking your first satellite pass.
## Prerequisites
Before starting, make sure you have:
- A Winegard dish connected via serial (see [Cable Wiring](/guides/wiring/))
- The dish powered on and the TV search disabled (see [Disabling TV Search](/guides/disabling-search/))
- Motors homed and calibrated (see [Calibration & Homing](/guides/calibration/))
- The base aligned with "BACK" marking pointing to true North
- Clear sky view -- no obstructions taller than 8 inches within 32.5 inches of the base center
## Software installation
<Steps>
1. **Install the `birdcage` package.**
```bash
# From the project directory
uv sync
# Verify installation
uv run birdcage --help
```
2. **Install Gpredict.**
```bash
# Arch Linux
sudo pacman -S gpredict
# Debian/Ubuntu
sudo apt install gpredict
# macOS (Homebrew)
brew install gpredict
```
3. **Update TLE data in Gpredict.** On first launch, go to Edit > Update TLE Data from Network to fetch current orbital elements.
</Steps>
## Architecture overview
The signal chain looks like this:
```
Gpredict ──TCP:4533──► rotctld server ──serial──► dish firmware
(tracking) (birdcage) (motor submenu)
```
Gpredict computes the satellite's predicted AZ/EL position and sends it to the rotctld server over TCP using the Hamlib rotctld protocol. The server translates those coordinates into motor commands and sends them over RS-485/RS-422 to the dish firmware. The firmware drives the stepper motors.
The `birdcage` package implements this full chain:
- **`protocol.py`** -- `FirmwareProtocol` ABC with implementations for HAL 2.05, HAL 0.0.00, and Carryout G2. Handles serial I/O, motor submenu navigation, position parsing.
- **`leapfrog.py`** -- Predictive overshoot algorithm to compensate for mechanical motor lag.
- **`antenna.py`** -- `BirdcageAntenna` class wrapping protocol + leapfrog. High-level `move_to()` and `get_position()`.
- **`rotctld.py`** -- `RotctldServer` TCP server implementing the `p`/`P`/`S`/`_`/`q` command set.
- **`cli.py`** -- Click CLI with `init`, `serve`, `pos`, and `move` subcommands.
## Starting the rotctld server
<Steps>
1. **Initialize the dish.** This waits for boot, kills the TV search, and enters the motor submenu.
```bash
# HAL 2.05 (default)
uv run birdcage init --port /dev/ttyUSB0
# Carryout G2
uv run birdcage init --port /dev/ttyUSB0 --firmware g2
```
Wait for the "Antenna initialized and ready" message.
2. **Start the rotctld server.**
```bash
# HAL 2.05
uv run birdcage serve --port /dev/ttyUSB0
# Carryout G2
uv run birdcage serve --port /dev/ttyUSB0 --firmware g2
# If the dish is already initialized (skip boot wait)
uv run birdcage serve --port /dev/ttyUSB0 --firmware g2 --skip-init
```
You should see:
```
rotctld server listening on 127.0.0.1:4533
Ctrl-C to stop
```
3. **Verify with a manual test.** In another terminal:
```bash
# Query position
echo "p" | nc 127.0.0.1 4533
# Move to AZ=180, EL=45
echo "P 180.0 45.0" | nc 127.0.0.1 4533
```
</Steps>
### CLI options
The `serve` command accepts these options:
| Option | Env Var | Default | Description |
|--------|---------|---------|-------------|
| `--port` | `BIRDCAGE_PORT` | `/dev/ttyUSB0` | Serial port for the adapter |
| `--firmware` | `BIRDCAGE_FIRMWARE` | `hal205` | Firmware variant (`hal205`, `hal000`, `g2`) |
| `--host` | `BIRDCAGE_LISTEN_HOST` | `127.0.0.1` | TCP listen address |
| `--listen-port` | `BIRDCAGE_LISTEN_PORT` | `4533` | TCP listen port |
| `--skip-init` | -- | false | Skip boot wait and search kill |
## Configuring Gpredict
<Steps>
1. **Open Gpredict** and go to **Edit > Preferences > Interfaces > Rotators**.
2. **Add a new rotator** with these settings:
| Setting | Value |
|---------|-------|
| Name | Winegard Trav'ler |
| Host | 127.0.0.1 |
| Port | 4533 |
| Az type | 0 -> 180 -> 360 |
| Min El | 15 (or 18 for G2) |
| Max El | 90 (or 65 for G2) |
3. **Open the rotor control panel.** In the main Gpredict window, click the arrow in the bottom-right of a module and select **Antenna Control**.
4. **Select your rotator** from the dropdown in the antenna control panel.
5. **Select a satellite** to track from the satellite dropdown.
6. **Click "Track"** to begin automated tracking. Gpredict will send position updates to the rotctld server as the satellite moves across the sky.
</Steps>
<Aside type="tip" title="Azimuth mode matters">
The "0 -> 180 -> 360" azimuth mode is required. This tells Gpredict that the rotator measures azimuth as 0-360 degrees (North through East). The alternative "180 -> 360 -> 180" mode is for rotators that use a different reference. Using the wrong mode will cause the dish to point in the wrong direction.
</Aside>
## The leapfrog algorithm
Satellite tracking requires continuous correction -- by the time the motors finish moving to a commanded position, a fast-moving LEO satellite has already moved. The leapfrog algorithm compensates by overshooting slightly in the direction of travel.
For each axis, if the difference between the target and current position exceeds a threshold, the target is nudged further:
| Delta (degrees) | Overshoot applied |
|-----------------|-------------------|
| &gt; 2.0 | +/- 1.0 degree |
| &gt; 1.0 | +/- 0.5 degree |
| &le; 1.0 | None |
The direction of overshoot matches the direction of travel. This keeps the dish slightly *ahead* of the satellite, reducing tracking error during a pass.
The leapfrog algorithm is enabled by default. To disable it for a manual move:
```bash
uv run birdcage move --port /dev/ttyUSB0 --az 180 --el 45 --no-leapfrog
```
<Aside type="tip" title="Bug fix from upstream">
The original `birdcage.py` from Gabe's repo had a copy-paste bug where the elevation delta adjustments modified `target_az` instead of `target_el`. This is fixed in the `leapfrog.py` module. The same bug exists in the Trav'ler Pro repo.
</Aside>
## Physical setup checklist
For accurate tracking, verify these physical conditions:
- **North alignment.** The base marking "BACK" should point to true North (not magnetic north). Use a compass and apply your local magnetic declination.
- **Level mounting.** The base should be level. A tilted base shifts all AZ/EL readings.
- **Clear horizon.** No obstructions taller than 8 inches within 32.5 inches of the base center. The dish arm sweeps a 32.5-inch radius.
- **Power.** Stable 120VAC to the RP-SK87 supply. Voltage dips during motor moves can cause missed steps.
## Quick reference: manual commands
While the rotctld server handles everything automatically, you can also control the dish directly:
```bash
# Query current position
uv run birdcage pos --port /dev/ttyUSB0 --firmware g2
# Move to a specific AZ/EL
uv run birdcage move --port /dev/ttyUSB0 --firmware g2 --az 180 --el 45
```
## Rotctld protocol extensions (G2 only)
When running with a Carryout G2, the rotctld server supports additional commands beyond the standard Hamlib protocol:
| Command | Function |
|---------|----------|
| `R [n]` | Read RSSI signal strength (averaged over n samples) |
| `L` | Enable LNA for signal reception |
| `D` | Discover supported protocol extensions |
These commands allow integration with sky-scanning workflows. A non-G2 rotator returns `RPRT -6` (not available) for these commands.
<LinkCard title="Radio Telescope Mode" href="/guides/radio-telescope/" description="Use the G2's built-in DVB tuner for RF sky mapping with the azscanwxp command." />

View file

@ -0,0 +1,145 @@
---
title: "Cable Wiring"
description: How to wire the serial connection between your computer and each Winegard dish variant
sidebar:
order: 1
---
import { Steps, Tabs, TabItem, Aside } from '@astrojs/starlight/components';
The Winegard dish variants use two different serial bus standards. The Trav'ler family uses **RS-485 half-duplex** (2-wire), while the Carryout G2 uses **RS-422 full-duplex** (4-wire). Choosing the right adapter and wiring it correctly is the first step.
## RS-485 vs RS-422
| 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.) |
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. 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.
## Adapter chain by variant
| Variant | Adapter | Wires Used |
|---------|---------|-----------|
| Trav'ler (Gabe's setup) | USB to RS232 to RS485 (DTECH) | Pins 2-3 only (half-duplex) |
| Carryout G2 (Davidson) | USB to RS422 (5V TTL) | Pins 2-5 (full-duplex) |
| Carryout G2 (confirmed) | DSD TECH SH-U11 USB to RS422 (FTDI FT232R) | Pins 1-5 (full-duplex + GND) |
| Carryout G2 (ESP32) | ESP32 UART2 to RS422 module (DIYables) | Pins 2-5 (full-duplex) |
## Wiring instructions
<Tabs>
<TabItem label="RS-485 (Trav'ler)">
The Trav'ler, Trav'ler HAL 2.05, and original Carryout use RS-485 half-duplex at **57600 baud**. The physical connector is an RJ-25 (6P6C).
### RJ-25 Pinout (bottom view, clip up)
| Pin | Label | RS-485 use |
|-----|-------|-----------|
| 1 | GND | Ground |
| 2 | T/R- | Shared data- |
| 3 | T/R+ | Shared data+ |
| 4 | RXD- | (unused in half-duplex) |
| 5 | RXD+ | (unused in half-duplex) |
| 6 | N/C | Not connected |
### Connection steps
<Steps>
1. **Get a USB-to-RS485 adapter.** The DTECH USB-to-RS232-to-RS485 chain is what Gabe used. A direct USB-to-RS485 adapter also works.
2. **Connect the shared data pair.** Wire the RS-485 adapter's A/+ terminal to pin 3 (T/R+) and B/- terminal to pin 2 (T/R-) on the RJ-25 connector.
3. **Connect ground.** Wire the adapter's GND terminal to pin 1 (GND).
4. **Leave pins 4-6 unconnected.** In half-duplex mode, the RXD pair and pin 6 are not used.
5. **Verify the serial port appears.** On Linux, look for `/dev/ttyUSB0` or similar. Check with `ls /dev/ttyUSB*`.
6. **Test with a terminal emulator.** Open the port at 57600 baud, 8N1. Press Enter -- you should see a `>` prompt or boot messages.
</Steps>
<Aside type="caution" title="The Trav'ler Pro is different">
The Trav'ler Pro uses a **USB A-to-A cable** and shows up as `ttyACM0`, not `ttyUSB`. It does not use RS-485 at all. Connect it directly via USB and use 57600 baud.
</Aside>
</TabItem>
<TabItem label="RS-422 (Carryout G2)">
The Carryout G2 uses RS-422 full-duplex at **115200 baud**. The physical connector is an RJ-12 (6P6C) -- same form factor as the RJ-25, same 6-pin modular jack.
### RJ-12 Pinout (clip away)
| Pin | Wire Color (Davidson) | Wire Color (confirmed) | RS-422 Function |
|-----|----------------------|----------------------|-----------------|
| 1 | White | Orange/White | GND (PE) |
| 2 | Red | Orange | TX+ (TA) -- computer to dish |
| 3 | Black | Green/White | TX- (TB) -- computer to dish |
| 4 | Yellow | Blue | RX+ (RA) -- dish to computer |
| 5 | Green | Blue/White | RX- (RB) -- dish to computer |
| 6 | Blue | Green | Not connected |
<Aside type="caution" title="Wire colors vary">
Wire colors differ between cable manufacturers. The "confirmed" column is from a standard 6P6C flat cable tested 2026-02-12. **Always verify with a multimeter before connecting.**
</Aside>
### Connection steps
<Steps>
1. **Get a USB-to-RS422 adapter.** The DSD TECH SH-U11 (FTDI FT232R) is confirmed working. Any RS-422 or 4-wire RS-485 adapter should work.
2. **Connect the TX pair (computer to dish).** Wire the adapter's TX+/TA terminal to pin 2 (TX+) and TX-/TB terminal to pin 3 (TX-).
3. **Connect the RX pair (dish to computer).** Wire the adapter's RX+/RA terminal to pin 4 (RX+) and RX-/RB terminal to pin 5 (RX-).
4. **Connect ground.** Wire the adapter's GND to pin 1 (GND).
5. **Verify the serial port appears.** On Linux, look for `/dev/ttyUSB0` or similar.
6. **Test with a terminal emulator.** Open the port at 115200 baud, 8N1. Press Enter -- you should see a `TRK>` prompt.
</Steps>
<Aside type="caution" title="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 -- the dish doesn't respond because it can't decode the inverted framing. If you see garbled data, swap the +/- wires on the RX pair first.
</Aside>
</TabItem>
</Tabs>
## RS-422 module notes (DIYables MAX490)
If using the DIYables RS422-to-TTL module with an ESP32, be aware of the **failsafe concern**: the MAX490 does not have failsafe logic. When the RS-422 bus tri-states (no driver active), the receiver inputs float and may produce spurious bytes.
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.
2. **Use prompt-terminated reads** -- our `CarryoutG2Protocol._send()` reads until `>` (ASCII 62) which naturally filters out garbage between commands.
3. **Keep cable runs short** -- under ~3m, the built-in 120 ohm termination is sufficient and bus float rarely causes issues.
## IDU/ODU cable wiring (if cut)
If you need to repair a cut cable between the indoor unit (IDU) and outdoor unit (ODU):
- **Top row:** Green, Yellow, Orange
- **Bottom row:** Red, Brown, Black
## Power
The dish runs on 120VAC input to the RP-SK87 power supply, which outputs 12VDC to the IDU. The internal coax carries 12-18VDC bias for the LNB.
<Aside type="caution" title="LNB bias voltage">
Do not connect 5V equipment (SDR LNAs, etc.) to the coax without bypassing the power injector. The 12-18VDC bias will damage equipment rated for lower voltages.
</Aside>