Known Bugs
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)
Section titled “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.pylines 98-105saveitforparts/Travler-Pro-Rotor— same bug copy-pasted intotravler_pro_rotor.py
The bug
Section titled “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):
# Azimuth compensation (correct)if target_az - current_az > 2: target_az+=1elif target_az - current_az < -2: target_az-=1elif target_az - current_az > 1: target_az+=0.5elif 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_elelif target_el - current_el < -2: target_az-=1 # <-- should be target_elelif target_el - current_el > 1: target_az+=0.5 # <-- should be target_elelif target_el - current_el < -1: target_az-=0.5 # <-- should be target_elImpact
Section titled “Impact”Two things go wrong simultaneously:
-
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. -
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
Section titled “The fix”In birdcage/leapfrog.py, the elevation section correctly modifies target_el:
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_elThe 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)
Section titled “Prompt termination bug (console-probe)”Location: console_probe/serial_io.py, _is_prompt_terminated()
Severity: False prompt detection causes truncated responses
The bug
Section titled “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
Section titled “The fix”The _is_prompt_terminated() function now distinguishes between prompts and parameter syntax using two strategies:
-
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. -
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.
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)
Section titled “ADC scan deadlock (firmware hazard)”What happens
Section titled “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
Section titled “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
Section titled “Mitigation”- Always home both axes before using ADC
scan: runmot->h 0(AZ) andh 1(EL) first - The
birdcagesoftware never calls ADCscandirectly - The
console-probetool’s timeout-based reads will eventually time out, but the firmware shell itself remains dead
Other commands affected
Section titled “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)
Section titled “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
Section titled “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:
_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.