Fix G2 position/RSSI parsers, document motor and DVB test results
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.
This commit is contained in:
parent
71ffafdd3f
commit
6b94f079aa
24
CLAUDE.md
24
CLAUDE.md
@ -64,12 +64,12 @@ Five known Winegard dish variants documented by Gabe Emerson (KL1FI) / saveitfor
|
|||||||
- **Carryout DIP switches:** All switches to off (up) may disable search mode, but behavior varies by unit.
|
- **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 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 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. The `CarryoutG2Protocol` parser needs to handle this format.
|
- **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 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 → SPI init → Motor init (System=12Inch, master=40000 steps, slave=24960 steps, ratio=1.602564) → DVB tuner init (BCM4515) → 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.
|
- **Carryout G2 boot sequence:** Bootloader → SPI init → Motor init (System=12Inch, master=40000 steps, slave=24960 steps, ratio=1.602564) → DVB tuner init (BCM4515) → 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.
|
||||||
- **Carryout G2 cable wrap:** Confirmed from homing output: `wrap_min:-42333 wrap_max:2333` (centidegrees). Total range ~446.66°.
|
- **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 `h <id>` homing:** Explicit motor home-to-reference command. Not documented on other variants.
|
||||||
- **Carryout G2 has DVB/RSSI:** Signal strength measurement via `dvb` submenu (`lnbdc odu` to enable LNA, `rssi <n>` to sample). Used for sky scanning / RF imaging. Atmospheric baseline measured at boot: ~494 (18V) / ~499 (13V) wideband.
|
- **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)
|
## Hardware Specs (SK-1000)
|
||||||
|
|
||||||
@ -209,9 +209,23 @@ nvs — enter non-volatile storage submenu
|
|||||||
e <idx> — read NVS value
|
e <idx> — read NVS value
|
||||||
e <idx> <v> — write NVS value
|
e <idx> <v> — write NVS value
|
||||||
s — save changes
|
s — save changes
|
||||||
dvb — signal info / LNB signal strength submenu
|
dvb — DVB tuner submenu (BCM4515)
|
||||||
lnbdc odu — enable LNA in ODU mode (powers LNB for reception)
|
config — hardware/firmware version
|
||||||
rssi <n> — read RSSI signal strength averaged over n samples
|
dis — display channel parameters (frequency, symbol rate, LNB polarity, etc.)
|
||||||
|
lnbdc odu — enable LNA in ODU mode (13V = V-pol; boot default 18V = H-pol)
|
||||||
|
lnbv — stream LNB voltage readings (continuous, interrupt with q)
|
||||||
|
rssi <n> — RSSI averaged over n samples (bounded, returns avg + cur)
|
||||||
|
snr — SNR level
|
||||||
|
agc — stream RF/IF AGC + SNR + NID (continuous, interrupt with q)
|
||||||
|
ls — lock status
|
||||||
|
qls — quick lock status
|
||||||
|
t <n> — select transponder
|
||||||
|
table — generate transponder table
|
||||||
|
e <n> <v> — edit channel parameter
|
||||||
|
freqs — tuner frequency list
|
||||||
|
di2id — DiSEqC read LNB hardware ID
|
||||||
|
di2stat — DiSEqC read LNB status flags
|
||||||
|
send <hex> — raw DiSEqC packet (max 6 bytes, space-delimited hex)
|
||||||
reboot — reboot firmware
|
reboot — reboot firmware
|
||||||
stow — fold dish flat (caution: modified feeds may not survive)
|
stow — fold dish flat (caution: modified feeds may not survive)
|
||||||
```
|
```
|
||||||
|
|||||||
@ -153,6 +153,141 @@ From homing output: `wrap_min:-42333 wrap_max:2333`
|
|||||||
- In centidegrees: -423.33° to +23.33° from home position
|
- In centidegrees: -423.33° to +23.33° from home position
|
||||||
- Total range: 446.66° (~1.24 full rotations)
|
- Total range: 446.66° (~1.24 full rotations)
|
||||||
|
|
||||||
|
## Motor Control
|
||||||
|
|
||||||
|
### Position Query
|
||||||
|
|
||||||
|
In the `MOT>` submenu, `a` returns position with 4-space indentation:
|
||||||
|
|
||||||
|
```
|
||||||
|
a
|
||||||
|
Angle[0] = 180.00 ← AZ (degrees)
|
||||||
|
Angle[1] = 45.00 ← EL (degrees)
|
||||||
|
MOT>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Move Command
|
||||||
|
|
||||||
|
`a <id> <deg>` returns a confirmation (no array index) and the prompt immediately
|
||||||
|
while the motor moves in the background:
|
||||||
|
|
||||||
|
```
|
||||||
|
a 1 46
|
||||||
|
Angle = 46.00
|
||||||
|
MOT>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Observed Motor Behavior
|
||||||
|
|
||||||
|
| Test | Command | Target | Actual | Overshoot |
|
||||||
|
|------|---------|--------|--------|-----------|
|
||||||
|
| EL move out | `a 1 46` | 46.00 | 46.05 | +0.05° |
|
||||||
|
| AZ move out | `a 0 181` | 181.00 | 181.01 | +0.01° |
|
||||||
|
| EL return | `a 1 45` | 45.00 | 44.94 | -0.06° |
|
||||||
|
| AZ return | `a 0 180` | 180.00 | 179.98 | -0.02° |
|
||||||
|
|
||||||
|
Direction-dependent overshoot: the motor consistently overshoots in the
|
||||||
|
direction of travel, undershooting on return. This is classic stepper
|
||||||
|
backlash + PID settling behavior and is what the leapfrog algorithm
|
||||||
|
compensates for.
|
||||||
|
|
||||||
|
## DVB Subsystem (BCM4515)
|
||||||
|
|
||||||
|
### Hardware
|
||||||
|
|
||||||
|
```
|
||||||
|
BCM Hardware= ID: 0x4515 VER: 0xB0
|
||||||
|
BCM Firmware= MAJOR VER: 0x71 (113) MINOR VER: 0x25 (37)
|
||||||
|
BCM Strap Config: 0x25018
|
||||||
|
```
|
||||||
|
|
||||||
|
### Channel Parameters (`dis`)
|
||||||
|
|
||||||
|
```
|
||||||
|
Power Mode: ON
|
||||||
|
Search Transponders: ON
|
||||||
|
Auto Search Mode: 1
|
||||||
|
Shuffle Mode: ON
|
||||||
|
Frequency List: Non-Stacked
|
||||||
|
|
||||||
|
Num Parameter Current Default
|
||||||
|
1 Frequency 1090640 (kHz) 974000 (kHz)
|
||||||
|
2 Symbol Rate 0 (PeakScanEnabled) 20000 (ksps)
|
||||||
|
3 Trans_Mod_CRate blind_scan blind_scan
|
||||||
|
4 Blind Scan Mode ___trb_dvb_dss_____ ___trb_dvb_dss_____
|
||||||
|
5 LNB Polarity ODU:13V ---
|
||||||
|
6 LNB Tone (ODU) off off
|
||||||
|
7 Roll-off 0.35 0.35
|
||||||
|
8 LPF Cutoff 0 (auto) 0 (MHz)
|
||||||
|
9 Carrier Offset 0 (kHz) 0 (kHz)
|
||||||
|
10 FreqSearchRange 5000 (kHz) 5000 (kHz)
|
||||||
|
11 DCII Mode dcii_qpsk_comb dcii_qpsk_comb
|
||||||
|
12 Spectral Inv scan scan
|
||||||
|
13 PScnSymRtRngMin 18000 (ksps) 18000 (ksps)
|
||||||
|
14 PScnSymRtRngMax 24000 (ksps) 24000 (ksps)
|
||||||
|
15 SignalDetectMode off off
|
||||||
|
```
|
||||||
|
|
||||||
|
### RSSI Response Format
|
||||||
|
|
||||||
|
```
|
||||||
|
rssi 5
|
||||||
|
iterations:5 interval(msec):20
|
||||||
|
Reads:5 RSSI[avg: 500 cur: 500]
|
||||||
|
DVB>
|
||||||
|
```
|
||||||
|
|
||||||
|
500 is the noise floor (no signal lock, dish pointed at arbitrary sky).
|
||||||
|
|
||||||
|
### LNB Voltage
|
||||||
|
|
||||||
|
`lnbdc odu` enables LNA at 13V. `lnbv` streams continuous voltage readings:
|
||||||
|
|
||||||
|
```
|
||||||
|
Reads:1 LNB Voltage (mV): 13239 ( ADC value: 119 )
|
||||||
|
Reads:2 LNB Voltage (mV): 13182 ( ADC value: 118 )
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Stable at ~13.11V (ADC 117). Boot default is 18V; `lnbdc odu` switches to 13V.
|
||||||
|
13V = vertical polarization, 18V = horizontal polarization on a standard LNB.
|
||||||
|
|
||||||
|
### AGC (Streaming)
|
||||||
|
|
||||||
|
`agc` streams RF and IF automatic gain control plus SNR/NID:
|
||||||
|
|
||||||
|
```
|
||||||
|
Reads:1 RF_AGC[avg: 1327353088 cur: 1327353088] IF_AGC[avg: 2684354560 cur: 2684354560] SNR: 0.0 NID: FFFF/none
|
||||||
|
```
|
||||||
|
|
||||||
|
- RF_AGC values are raw BCM4515 32-bit register values
|
||||||
|
- IF_AGC constant at 0xA0000000 (fixed IF gain)
|
||||||
|
- SNR: 0.0 when no signal lock
|
||||||
|
- NID: FFFF/none = no DVB network ID detected
|
||||||
|
|
||||||
|
### DVB Command Reference
|
||||||
|
|
||||||
|
| Command | Type | Description |
|
||||||
|
|---------|------|-------------|
|
||||||
|
| `rssi <n>` | One-shot | Average signal strength over n samples |
|
||||||
|
| `snr` | Unknown | SNR level |
|
||||||
|
| `agc` | Streaming | RF/IF AGC + SNR + NID (runs until interrupted) |
|
||||||
|
| `lnbdc odu` | One-shot | Enable LNB in ODU mode (13V) |
|
||||||
|
| `lnbv` | Streaming | Continuous LNB voltage monitoring |
|
||||||
|
| `ls` | Unknown | Lock status |
|
||||||
|
| `qls` | Unknown | Quick lock status |
|
||||||
|
| `config` | One-shot | BCM hardware/firmware version |
|
||||||
|
| `dis` | One-shot | Display channel parameters |
|
||||||
|
| `freqs` | One-shot | Tuner frequency list |
|
||||||
|
| `diag` | Unknown | Diagnostic data |
|
||||||
|
| `t <n>` | One-shot | Select transponder |
|
||||||
|
| `table` | One-shot | Generate transponder table |
|
||||||
|
| `e <n> <v>` | One-shot | Edit channel parameter |
|
||||||
|
| `di2*` | One-shot | DiSEqC 2.x LNB commands |
|
||||||
|
| `send <hex>` | One-shot | Raw DiSEqC packet (max 6 bytes) |
|
||||||
|
|
||||||
|
Streaming commands (`agc`, `lnbv`) run until a new command or `q` interrupts them.
|
||||||
|
|
||||||
## Satellite Configuration
|
## Satellite Configuration
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@ -312,29 +312,24 @@ class CarryoutG2Protocol(FirmwareProtocol):
|
|||||||
def get_position(self) -> Position:
|
def get_position(self) -> Position:
|
||||||
"""Query dish position.
|
"""Query dish position.
|
||||||
|
|
||||||
The G2 may return floats without AZ=/EL= labels, so we try the
|
G2 firmware 02.02.48 returns position as::
|
||||||
labeled format first and fall back to raw float extraction.
|
|
||||||
|
Angle[0] = 180.00
|
||||||
|
Angle[1] = 45.00
|
||||||
|
MOT>
|
||||||
|
|
||||||
|
Where Angle[0] is azimuth and Angle[1] is elevation.
|
||||||
"""
|
"""
|
||||||
response = self._send("a")
|
response = self._send("a")
|
||||||
|
|
||||||
# Try labeled format (AZ = / EL =) for compatibility
|
# G2 format: Angle[0] = <az>, Angle[1] = <el>
|
||||||
az_match = re.search(r"AZ\s*=?\s*(\d+\.\d+)", response)
|
az_match = re.search(r"Angle\[0\]\s*=\s*(-?\d+\.\d+)", response)
|
||||||
el_match = re.search(r"EL\s*=?\s*(\d+\.\d+)", response)
|
el_match = re.search(r"Angle\[1\]\s*=\s*(-?\d+\.\d+)", response)
|
||||||
|
|
||||||
if az_match and el_match:
|
if az_match and el_match:
|
||||||
sk_match = re.search(r"SK\s*=?\s*(\d+\.\d+)", response)
|
|
||||||
return Position(
|
return Position(
|
||||||
azimuth=float(az_match.group(1)),
|
azimuth=float(az_match.group(1)),
|
||||||
elevation=float(el_match.group(1)),
|
elevation=float(el_match.group(1)),
|
||||||
skew=float(sk_match.group(1)) if sk_match else None,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Fall back to raw float extraction (G2-style: just two numbers)
|
|
||||||
floats = re.findall(r"\d+\.\d+", response)
|
|
||||||
if len(floats) >= 2:
|
|
||||||
return Position(
|
|
||||||
azimuth=float(floats[0]),
|
|
||||||
elevation=float(floats[1]),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
raise ValueError(f"Could not parse position from: {response!r}")
|
raise ValueError(f"Could not parse position from: {response!r}")
|
||||||
@ -367,6 +362,12 @@ class CarryoutG2Protocol(FirmwareProtocol):
|
|||||||
def get_rssi(self, iterations: int = 10) -> RssiReading:
|
def get_rssi(self, iterations: int = 10) -> RssiReading:
|
||||||
"""Read averaged RSSI signal strength (DVB submenu).
|
"""Read averaged RSSI signal strength (DVB submenu).
|
||||||
|
|
||||||
|
Firmware response format::
|
||||||
|
|
||||||
|
iterations:5 interval(msec):20
|
||||||
|
Reads:5 RSSI[avg: 500 cur: 500]
|
||||||
|
DVB>
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
iterations: Number of samples to average.
|
iterations: Number of samples to average.
|
||||||
|
|
||||||
@ -377,13 +378,16 @@ class CarryoutG2Protocol(FirmwareProtocol):
|
|||||||
ValueError: If the RSSI response can't be parsed.
|
ValueError: If the RSSI response can't be parsed.
|
||||||
"""
|
"""
|
||||||
response = self._send(f"rssi {iterations}")
|
response = self._send(f"rssi {iterations}")
|
||||||
results = re.findall(r"\d+", response)
|
|
||||||
|
|
||||||
if len(results) >= 6:
|
match = re.search(
|
||||||
|
r"Reads:(\d+)\s+RSSI\[avg:\s*(\d+)\s+cur:\s*(\d+)\]",
|
||||||
|
response,
|
||||||
|
)
|
||||||
|
if match:
|
||||||
return RssiReading(
|
return RssiReading(
|
||||||
reads=int(results[3]),
|
reads=int(match.group(1)),
|
||||||
average=int(results[4]),
|
average=int(match.group(2)),
|
||||||
current=int(results[5]),
|
current=int(match.group(3)),
|
||||||
)
|
)
|
||||||
|
|
||||||
raise ValueError(f"Could not parse RSSI from: {response!r}")
|
raise ValueError(f"Could not parse RSSI from: {response!r}")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user