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.
193 lines
8.9 KiB
Plaintext
193 lines
8.9 KiB
Plaintext
---
|
|
title: Known Bugs
|
|
description: Documented bugs in the upstream code and firmware, with analysis and fixes.
|
|
sidebar:
|
|
order: 4
|
|
---
|
|
|
|
import { Aside } from '@astrojs/starlight/components';
|
|
|
|
This page documents bugs we've found and fixed in the upstream code, as well as firmware hazards that can't be fixed in software but need to be understood.
|
|
|
|
## Leap-frog elevation bug (upstream)
|
|
|
|
**Location:** `Trav-ler-Rotor-For-HAL-2.05/travler_rotor.py`, lines 98-105
|
|
|
|
**Severity:** Tracking accuracy degraded on the elevation axis
|
|
|
|
**Affected repos:**
|
|
- `saveitforparts/Trav-ler-Rotor-For-HAL-2.05` -- `travler_rotor.py` lines 98-105
|
|
- `saveitforparts/Travler-Pro-Rotor` -- same bug copy-pasted into `travler_pro_rotor.py`
|
|
|
|
### The bug
|
|
|
|
The leap-frog algorithm has two sections: azimuth compensation and elevation compensation. Both were written from the same template. In the elevation section, a copy-paste error left `target_az` as the variable being modified instead of changing it to `target_el`.
|
|
|
|
**Original code (lines 90-105):**
|
|
|
|
```python
|
|
# Azimuth compensation (correct)
|
|
if target_az - current_az > 2:
|
|
target_az+=1
|
|
elif target_az - current_az < -2:
|
|
target_az-=1
|
|
elif target_az - current_az > 1:
|
|
target_az+=0.5
|
|
elif target_az - current_az < -1:
|
|
target_az-=0.5
|
|
|
|
# Elevation compensation (BUG: modifies target_az instead of target_el)
|
|
if target_el - current_el > 2:
|
|
target_az+=1 # <-- should be target_el
|
|
elif target_el - current_el < -2:
|
|
target_az-=1 # <-- should be target_el
|
|
elif target_el - current_el > 1:
|
|
target_az+=0.5 # <-- should be target_el
|
|
elif target_el - current_el < -1:
|
|
target_az-=0.5 # <-- should be target_el
|
|
```
|
|
|
|
### Impact
|
|
|
|
Two things go wrong simultaneously:
|
|
|
|
1. **Elevation never gets leap-frog compensation.** The elevation delta is computed correctly (`target_el - current_el`), but the adjustment is applied to the wrong variable. During fast satellite passes with significant elevation change, the dish lags behind on the EL axis.
|
|
|
|
2. **Azimuth gets double compensation.** The azimuth correction from its own section is applied first, then the elevation section adds a second correction to the same variable. If both axes have large deltas (common during a pass), azimuth overshoots its target.
|
|
|
|
For a satellite pass where both AZ and EL are changing by more than 2 degrees per update:
|
|
- AZ gets +2.0 degrees of correction (1.0 from its own section + 1.0 from the elevation section)
|
|
- EL gets +0.0 degrees of correction
|
|
|
|
### The fix
|
|
|
|
In `birdcage/leapfrog.py`, the elevation section correctly modifies `target_el`:
|
|
|
|
```python
|
|
def apply_leapfrog(
|
|
target_az: float,
|
|
target_el: float,
|
|
current_az: float,
|
|
current_el: float,
|
|
) -> tuple[float, float]:
|
|
# Azimuth compensation
|
|
az_delta = target_az - current_az
|
|
if abs(az_delta) > 2:
|
|
target_az += 1.0 if az_delta > 0 else -1.0
|
|
elif abs(az_delta) > 1:
|
|
target_az += 0.5 if az_delta > 0 else -0.5
|
|
|
|
# Elevation compensation (fixed: modifies target_el, not target_az)
|
|
el_delta = target_el - current_el
|
|
if abs(el_delta) > 2:
|
|
target_el += 1.0 if el_delta > 0 else -1.0
|
|
elif abs(el_delta) > 1:
|
|
target_el += 0.5 if el_delta > 0 else -0.5
|
|
|
|
return target_az, target_el
|
|
```
|
|
|
|
The fix also restructures the conditionals to use `abs()` and ternary expressions, making the symmetry between the two axes explicit and harder to get wrong in future edits.
|
|
|
|
## Prompt termination bug (console-probe)
|
|
|
|
**Location:** `console_probe/serial_io.py`, `_is_prompt_terminated()`
|
|
|
|
**Severity:** False prompt detection causes truncated responses
|
|
|
|
### The bug
|
|
|
|
The original prompt detection logic checked whether the response ended with `>`. This worked for the firmware prompt (`TRK>`, `MOT>`, etc.) but also matched the `>` character inside parameter syntax in help text:
|
|
|
|
```
|
|
help [<command>]
|
|
```
|
|
|
|
The `>` in `<command>` would trigger prompt detection, cutting off the rest of the help output.
|
|
|
|
### The fix
|
|
|
|
The `_is_prompt_terminated()` function now distinguishes between prompts and parameter syntax using two strategies:
|
|
|
|
1. **Known prompt matching** -- when the profile has a list of known prompts (`TRK>`, `MOT>`, `DVB>`, etc.), it checks for exact suffix matches. This is the fast path and avoids false positives entirely.
|
|
|
|
2. **Bracket filtering** -- for pattern-based detection (when known prompts aren't populated yet), the function rejects any line containing `[` brackets before accepting a `>` match. Parameter syntax like `[<command>]` always appears inside brackets.
|
|
|
|
```python
|
|
def _is_prompt_terminated(text: str, profile: DeviceProfile) -> bool:
|
|
last_line = stripped.split("\n")[-1]
|
|
|
|
if profile.prompts:
|
|
# Check known prompts first (fast path)
|
|
for p in profile.prompts:
|
|
if last_stripped.endswith(p):
|
|
return True
|
|
# Accept PROMPT_RE match only if no brackets on that line
|
|
if "[" not in last_line:
|
|
m = PROMPT_RE.search(last_line)
|
|
if m:
|
|
return True
|
|
return False
|
|
|
|
# No known prompts yet -- fallback to bare > check
|
|
return stripped.endswith(">")
|
|
```
|
|
|
|
During initial discovery (before any prompts are known), the fallback to `stripped.endswith(">")` is intentionally permissive -- it may occasionally truncate, but it gets the first prompt detected so the more precise logic can take over.
|
|
|
|
## ADC scan deadlock (firmware hazard)
|
|
|
|
<Aside type="danger" title="This can brick your session">
|
|
The ADC `scan` command without arguments on an uncalibrated azimuth axis will deadlock the firmware shell. **No serial input can recover it** -- not CR, Ctrl+C, ESC, `q`, or `reboot`. The only recovery is a hardware power cycle.
|
|
</Aside>
|
|
|
|
### What happens
|
|
|
|
The ADC submenu's `scan` command performs an azimuth sweep with RSSI readings. When called without explicit position arguments, it reads the current AZ target from the motor controller.
|
|
|
|
If the AZ motor has not been homed (which happens when NVS 20 disables the tracker, skipping the boot homing sequence), the position register contains the sentinel value **2147483647** (INT_MAX, or 0x7FFFFFFF).
|
|
|
|
The firmware interprets this as a real target position and commands the motor to move there. The motor task blocks on this impossible move, and because the firmware shell is single-threaded -- UART input parsing only happens between command completions -- the shell becomes permanently unresponsive.
|
|
|
|
### Why serial input can't help
|
|
|
|
The K60's UART4 receive buffer fills up with the bytes you send (CR, `q`, etc.), but the main loop never reads them because it's stuck inside the motor move handler. There is no interrupt-based command abort mechanism in this firmware. The motor task runs to completion (or forever, in this case) before control returns to the shell parser.
|
|
|
|
### Mitigation
|
|
|
|
- Always home both axes before using ADC `scan`: run `mot` -> `h 0` (AZ) and `h 1` (EL) first
|
|
- The `birdcage` software never calls ADC `scan` directly
|
|
- The `console-probe` tool's timeout-based reads will eventually time out, but the firmware shell itself remains dead
|
|
|
|
### Other commands affected
|
|
|
|
Any command that internally reads motor position and initiates a move could theoretically hit this on an unhomed axis. The `azscanwxp` command in the MOT submenu is similarly dangerous without homing. However, simple position queries (`a` in MOT) safely return the INT_MAX value without attempting a move.
|
|
|
|
## Root menu `q` command (firmware design, not a bug)
|
|
|
|
The `q` command at the `TRK>` root prompt terminates the firmware shell task. This is by design -- it's a clean shutdown of the command interpreter. But the consequence is identical to the scan deadlock: the console becomes unresponsive and requires a power cycle.
|
|
|
|
Inside submenus, `q` safely exits to the parent menu. The hazard is only at the root level.
|
|
|
|
The `birdcage` code's `reset_to_root()` method sends `q` to exit submenus, which is safe. But if called when already at root, it would kill the shell. The CarryoutG2Protocol avoids this by using `_send("q")` which reads until the prompt -- if the shell dies, `_send` raises a `TimeoutError` instead of silently losing the connection.
|
|
|
|
## `command` false positive in help parsing
|
|
|
|
During automated probing, the word `command` appeared as a discovered command in the root menu. This is a false positive extracted from the help text:
|
|
|
|
```
|
|
help [<command>]
|
|
```
|
|
|
|
The help parser saw `<command>` as a valid angle-bracket command name. The fix in `console_probe/discovery.py` maintains a set of known parameter placeholders:
|
|
|
|
```python
|
|
_PARAM_PLACEHOLDERS: set[str] = {
|
|
"command", "commands", "parameter", "parameters",
|
|
"value", "values", "index", "name", "arg", "args",
|
|
...
|
|
}
|
|
```
|
|
|
|
Any word matching this set is rejected during help parsing, preventing it from appearing as a discovered command.
|