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:
Ryan Malloy 2026-02-12 09:34:42 -07:00
parent 71ffafdd3f
commit 6b94f079aa
3 changed files with 178 additions and 25 deletions

View file

@ -312,29 +312,24 @@ class CarryoutG2Protocol(FirmwareProtocol):
def get_position(self) -> Position:
"""Query dish position.
The G2 may return floats without AZ=/EL= labels, so we try the
labeled format first and fall back to raw float extraction.
G2 firmware 02.02.48 returns position as::
Angle[0] = 180.00
Angle[1] = 45.00
MOT>
Where Angle[0] is azimuth and Angle[1] is elevation.
"""
response = self._send("a")
# Try labeled format (AZ = / EL =) for compatibility
az_match = re.search(r"AZ\s*=?\s*(\d+\.\d+)", response)
el_match = re.search(r"EL\s*=?\s*(\d+\.\d+)", response)
# G2 format: Angle[0] = <az>, Angle[1] = <el>
az_match = re.search(r"Angle\[0\]\s*=\s*(-?\d+\.\d+)", response)
el_match = re.search(r"Angle\[1\]\s*=\s*(-?\d+\.\d+)", response)
if az_match and el_match:
sk_match = re.search(r"SK\s*=?\s*(\d+\.\d+)", response)
return Position(
azimuth=float(az_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}")
@ -367,6 +362,12 @@ class CarryoutG2Protocol(FirmwareProtocol):
def get_rssi(self, iterations: int = 10) -> RssiReading:
"""Read averaged RSSI signal strength (DVB submenu).
Firmware response format::
iterations:5 interval(msec):20
Reads:5 RSSI[avg: 500 cur: 500]
DVB>
Args:
iterations: Number of samples to average.
@ -377,13 +378,16 @@ class CarryoutG2Protocol(FirmwareProtocol):
ValueError: If the RSSI response can't be parsed.
"""
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(
reads=int(results[3]),
average=int(results[4]),
current=int(results[5]),
reads=int(match.group(1)),
average=int(match.group(2)),
current=int(match.group(3)),
)
raise ValueError(f"Could not parse RSSI from: {response!r}")