# Known Issues — Status

This tracks the reproduction blockers and robustness issues identified in the original code and what the
refactor did about them.

## Resolved

| # | Original issue | Resolution |
|---|----------------|------------|
| 1 | Hardcoded config path (`/home/ggs/...`) | Config search order: `--config` → `$FGC_CONFIG` → `./config.ini` → exe dir → XDG ([src/core/Paths.cpp](../src/core/Paths.cpp)) |
| 2 | Hardcoded image output path | `[Paths] output_dir` with `~`/`$ENV` expansion + sensible default |
| 3 | Startup scripts with `~/projects/...` | Replaced by path-independent [scripts/run.sh](../scripts/run.sh) + [systemd unit](../scripts/fire-gimbal-control.service) |
| 4 | Vimba X required to build | `WITH_VIMBA` CMake option (default ON); `OFF` builds a mock-only binary |
| 5 | No hardware path; exits if MQTT down | Mock implementations + runtime toggles; MQTT failure now logs and continues |
| 8 | Plaintext MQTT credentials | `$FGC_MQTT_USER`/`$FGC_MQTT_PW` env override; `config.ini` gitignored |
| 9 | MQTT busy-wait (`while(running);`) | Gone — Paho async client + `set_automatic_reconnect`; no spin thread |
| 10 | `parser()` missing return | Old parser removed; `parseTelemetryLine` returns `std::optional` cleanly |
| 11 | Fragile Boost.Spirit command grammar | Replaced by `parseCommand` whitespace tokenizer ([src/core/CommandParser.cpp](../src/core/CommandParser.cpp)) |
| 12 | Two divergent `config.ini` files | Single committed `config/config.example.ini`; real configs gitignored |
| 6 | Telemetry field order: humidity before temperature | **Obsolete** — migrated to the current firmware's `ST` protocol (encoder counts, no environmental fields); humidity/temp/fan are gone |
| 7 | Trigger fires while `is_moving == 1` (not when stopped) | **Fixed** by the protocol migration — capture is now move → **settle** → trigger; the camera fires only once both axes report standstill at the target ([CaptureScheduler.cpp](../src/core/CaptureScheduler.cpp)) |

Also added along the way: a leveled logger, typed/validated config, an SDK-independent core library, and a
doctest unit-test suite (`ctest`).

## Open / needs hardware confirmation

| # | Issue | Status |
|---|-------|--------|
| 13 | `[Motor]` degrees↔counts calibration | The `config.example.ini` values are **placeholders**. Calibrate `*_counts_per_deg` / `*_zero_count` against real `xenc` readings after homing on the rig. |
| 14 | Capture sweep untested on hardware | Homing was verified live (see below), but the `MOVE → settle → trigger` sweep was **not** run with `--start`. `kSettleTolCounts` (600) and the per-interval timing in [CaptureScheduler.cpp](../src/core/CaptureScheduler.cpp) still need tuning against observed `ST` behaviour, alongside #13. |

## Camera acquisition (USB3) — root cause + approach

Real-camera capture (Alvium 1800 U-2040c, Sony IMX541, 20.4 MP) was brought up on the LattePanda this
session. Two problems were root-caused (both **host/USB3-side**, reproduced in Allied Vision's own
`vmbpy` — the camera keeps acquiring while the host stops receiving; **no kernel errors**):

1. **Camera shipped in hardware-trigger mode** (`TriggerSource=Line0`) → produced no frames. Fixed by
   configuring **software trigger** explicitly in-session in [VimbaCameraSource.cpp](../src/camera/VimbaCameraSource.cpp)
   (relying on the camera's persisted user set alone did not work).
2. **Acquisition stall** — frame delivery stops after a few frames:
   - **USB3 hardware LPM (U1/U2) was enabled** on the camera's link (the camera logs
     `Enable of device-initiated U2 failed`) → silent bulk-transfer stalls under load. Disable per-port:
     write `0` to `/sys/devices/.../usb2/2-0:1.0/usb2-port<N>/usb3_lpm_permit`, then re-enumerate
     (the per-device `power/usb3_hardware_lpm_u*` files are read-only). **Make persistent** via a udev
     rule matched on idVendor `1ab2`.
   - **The host xHCI cannot reliably move large single frames.** Measured (LPM off, paced ~1 fps): RGB8
     full-res **61 MB stalls in 3–5 frames**; **≤ ~20 MB frames sustain when *paced*** (BayerRG8 full-res
     20 MB → 21 frames/20 s; 3.8 MB → 41/20 s). Freerun at max rate stalls even for medium frames.
   - Also raise `usbcore.usbfs_memory_mb` to 1000 (persist via kernel cmdline; resets to 16 on reboot).

**Approach (config-driven, see `[Camera]` in [configuration.md](configuration.md)):** RGB8 + **2×2 binning**
(~5 MP, ~15 MB) keeps de-Bayering **and** white balance on-camera (the on-camera pipeline applies white
balance before de-Bayering); a **paced low-rate stream** keeps on-camera auto-exposure/gain/white-balance
converged to the changing outdoor light, and the scheduler saves one frame per waypoint. `DeviceLinkThroughputLimit`
is kept conservative (~250 MB/s; ≥450 caused incomplete frames). Full 20 MP is possible via BayerRG8
(20 MB) + a host de-Bayer step if reduced resolution proves insufficient.

**Status:** the `[Camera]` config schema, near-lossless JPEG XL, and unit tests are in place. Still to do
on the rig: wire the in-session imaging config + paced acquisition in `VimbaCameraSource`, add the udev/boot
persistence for LPM + usbfs, and verify a full sweep captures white-balanced frames with no stall.

## Verification caveats

- **Full build verified on the LattePanda**: a `WITH_VIMBA=ON WITH_MQTT=ON` build compiles and links on the
  device (real Vimba X SDK + Paho fetched). The MQTT wrapper also builds on the dev box (GCC 16 / CMake 4 — see
  `cmake/Paho.cmake` for the toolchain-compat shims).
- **Serial protocol verified live against real firmware** (LattePanda, this session): `ENABLE`/`HOME`/`SPEED`
  reach the firmware, the `ST` telemetry parses with zero unparsed lines across a full session, and a live
  `--init` drove a clean re-home of both axes to `READY`. **Still unverified on hardware:** the capture sweep
  (#14) and real-camera (Vimba) frame capture — earlier tests used `--mock-camera`.
- **Makefile parity** is moot: the Makefile was removed in favour of CMake, and the full
  `WITH_VIMBA=ON WITH_MQTT=ON` CMake build now runs on the device.
- **Demo mode** copies `bin/x64/Release/test_smoke.jxl`, resolved relative to the working directory. Run from a
  directory where that path exists, or extend `ImagePipeline::Params::demo_image`.

## Possible follow-ups (not done)

- Graceful shutdown on SIGINT (currently exit via `exit`/Ctrl-D; a pending `getline` can delay shutdown).
- Make the camera index→label map fully config-driven. (JPEG XL distance/effort and the on-camera imaging
  settings are now config-driven under `[Camera]` — see [configuration.md](configuration.md).)
- Reintroduce optional image upload to the ground station, config-driven (the old hardcoded NFS/SMB upload was
  removed).
- **`gimbal calib` persistence**: the fitted `counts_per_deg`/`zero_count` are always applied to the live
  session (display, manual moves, MQTT heading, **and** the capture scheduler) and written to
  `logs/calib_*.log`. In the **TUI**, the activity strip then prompts `Save … as the new default? (y/n)`
  — `y` writes the full per-axis `[Motor]` map (`*_counts_per_deg`, `*_zero_count`,
  `*_min_deg`/`*_max_deg`) into `config.ini` (persists across restarts), `n` keeps them
  session-only. In the **headless** console there is no prompt, so calibration stays session-only there;
  copy the logged values into `[Motor]` by hand to keep them.
- **`gimbal nudge`** moves a fixed fraction of the **homed** endstop-to-endstop travel (from the
  firmware dump), so it is independent of the degrees↔counts calibration. If no dump has been captured
  yet (not homed / no `gimbal dump`), nudge requests one and does nothing that press — it never falls
  back to the configured degree clamps (which, if `*_min_deg`/`*_max_deg` were unset, produced absurd
  ±100000° steps).
- Ambient temperature/humidity (SHT41, `IEnvSensor`/`Sht41EnvSensor`/`MockEnvSensor`) is
  integrated in software — config-gated (`[Features] enable_env`/`mock_env`, `[Env]`), Sensors
  panel + MQTT `Env` topic wired — but defaults to `mock_env = true` in the checked-in example
  config. Bus/address bring-up and validation against the physical sensor on the LattePanda's I2C
  bus is still pending; flip `mock_env = false` on the deployed `config.ini` once confirmed.
