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:
commit
088c1a5ace
44 changed files with 468217 additions and 0 deletions
192
src/content/docs/understanding/known-bugs.mdx
Normal file
192
src/content/docs/understanding/known-bugs.mdx
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
---
|
||||
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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue