161 lines
24 KiB
Markdown
161 lines
24 KiB
Markdown
# Module Reference
|
||
|
||
Per-file reference for the refactored tree, plus the shared data structures.
|
||
|
||
## Core (`fgc_core` — SDK-independent, unit-tested)
|
||
|
||
| File | Contents |
|
||
|------|----------|
|
||
| [include/fgc/Config.h](../include/fgc/Config.h), [src/core/Config.cpp](../src/core/Config.cpp) | Typed `AppConfig` (General/Network/Serial/Camera/Paths/Features/Logging/UI/Motor/Scan/**IMU**) + `ConfigLoader` (INI parse, env overrides, validation) |
|
||
| [include/fgc/Paths.h](../include/fgc/Paths.h), [src/core/Paths.cpp](../src/core/Paths.cpp) | `~`/`$ENV` expansion, executable dir, config search order, default output dir |
|
||
| [include/fgc/IniEdit.h](../include/fgc/IniEdit.h), [src/core/IniEdit.cpp](../src/core/IniEdit.cpp) | Editing `config.ini` and `hardware.ini` without breaking them, as pure functions on file contents: set keys keeping comments, order and one `;prev` generation, comment out keys `hardware.ini` owns (without their value, so `deploy.sh`'s placeholder guard stays quiet), and refuse lines too long for inih to read whole. `tools/fgc_config.cpp` does the file IO |
|
||
| [include/fgc/SingleInstance.h](../include/fgc/SingleInstance.h), [src/core/SingleInstance.cpp](../src/core/SingleInstance.cpp) | Refuses a second running copy of the program, which would fight the first over the motor's serial port and the camera: an advisory `flock` on a lockfile holding the owner's PID, taken in the binary so every way of starting it is covered |
|
||
| [include/fgc/Logger.h](../include/fgc/Logger.h), [src/core/Logger.cpp](../src/core/Logger.cpp) | Leveled, thread-safe logger + per-category wire trace; `LOG_TRACE..LOG_ERROR`, `LOG_TRACE_CAT` |
|
||
| [include/fgc/FileLogSink.h](../include/fgc/FileLogSink.h), [src/core/FileLogSink.cpp](../src/core/FileLogSink.cpp) | The persistent log file: append, size rotation, count-based pruning, a self-describing header per file. Runs inside `Logger`'s non-recursive lock, so it never logs - failures latch into `failed()`/`lastError()` for the caller to report |
|
||
| [include/fgc/Geometry.h](../include/fgc/Geometry.h), [src/core/Geometry.cpp](../src/core/Geometry.cpp) | Per-axis degrees↔encoder-counts affine map (`[Motor]` calibration); `encoderPitchDeg` (the CamEvent pitch when there is no IMU — the fixed `deg_at_center` on a yaw-only tower) |
|
||
| [include/fgc/ScanGrid.h](../include/fgc/ScanGrid.h), [src/core/ScanGrid.cpp](../src/core/ScanGrid.cpp) | Capture waypoints (CSV only, never generated; a bare `yaw` line is pitch 0) + one-way wrapping cursor (`[Scan]`); `repeatedBearings` for the yaw-only audit |
|
||
| [include/fgc/TelemetryParser.h](../include/fgc/TelemetryParser.h), [src/core/TelemetryParser.cpp](../src/core/TelemetryParser.cpp) | `parseTelemetryLine` (firmware `ST` line) → `std::optional<MotorTelemetry>` |
|
||
| [include/fgc/CommandParser.h](../include/fgc/CommandParser.h), [src/core/CommandParser.cpp](../src/core/CommandParser.cpp) | `parseCommand` whitespace tokenizer → `Command`; `parseAim` (`<yaw>[,<pitch>]` for `gimbal move/steps`) and `moveCommand` (the firmware `MOVE`: the pair, or `MOVE Y` without a pitch) |
|
||
| [include/fgc/HelpText.h](../include/fgc/HelpText.h), [src/core/HelpText.cpp](../src/core/HelpText.cpp) | Operator command catalog (`helpCatalog`) + `renderHelp` (console & TUI help) |
|
||
| [include/fgc/TmcCurrent.h](../include/fgc/TmcCurrent.h), [src/core/TmcCurrent.cpp](../src/core/TmcCurrent.cpp) | Milliamps → TMC5160 `GLOBALSCALER`/`IRUN` register fields, using the sense resistor from `[Motor.<axis>]`. Pure arithmetic, so it is unit-tested; the host owns the conversion because only it knows the resistor |
|
||
| [include/fgc/HardwarePresence.h](../include/fgc/HardwarePresence.h), [src/core/HardwarePresence.cpp](../src/core/HardwarePresence.cpp) | `reconcilePresence` (what `hardware.ini` declares vs what actually came up) + `presenceVerdict` (proceed / warn / refuse, per `strict_presence` and `--degraded`). Pure comparison, so the policy is unit-tested without a rig |
|
||
| [include/fgc/HardwareChecks.h](../include/fgc/HardwareChecks.h), [src/core/HardwareChecks.cpp](../src/core/HardwareChecks.cpp) | The verdicts behind `test hardware`: `checkUsbfsMemory`, `checkUsb3Lpm`, `checkI2cFunctionality`, `checkDrivetrainAdopted`. Pure, so the thresholds that encode known-issues #2 and #19 cannot drift unnoticed |
|
||
| [include/fgc/AxisSpeed.h](../include/fgc/AxisSpeed.h), [src/core/AxisSpeed.cpp](../src/core/AxisSpeed.cpp) | `planAxisSpeed` (deg/s -> TMC `VMAX` through `counts_per_deg`) + `vmaxToDegPerSec`. Pure arithmetic; refuses an uncalibrated scale rather than commanding a near-zero speed |
|
||
| [include/fgc/FirmwareConfig.h](../include/fgc/FirmwareConfig.h), [src/core/FirmwareConfig.cpp](../src/core/FirmwareConfig.cpp) | `parseNeedLine` (firmware `NEED` line -> axis + keys) + `planFirmwareConfig` (-> the `SET` lines that answer it). Reports a key it cannot answer separately from one it does not recognise: incomplete `hardware.ini` vs. a firmware newer than the host |
|
||
| [include/fgc/DumpParser.h](../include/fgc/DumpParser.h), [src/core/DumpParser.cpp](../src/core/DumpParser.cpp) | `parseDump` firmware `DUMP` block → `DumpData` (per-axis state + decoded TMC registers/flags); `formatDump`. Prefers the firmware's own `reset=` name over any host-side decode - `mcusr` is an AVR bitmask on one board and a plain enum on the other, so a single table cannot be right for both |
|
||
| [include/fgc/DiagParser.h](../include/fgc/DiagParser.h), [src/core/DiagParser.cpp](../src/core/DiagParser.cpp) | `parseDiag` firmware `DG` self-test stream → `DiagResult` (per-axis tests, PASS/FAIL); `formatDiag` |
|
||
| [include/fgc/TestRunner.h](../include/fgc/TestRunner.h), [src/core/TestRunner.cpp](../src/core/TestRunner.cpp) | The `test` command: resolves `test [<subsystem> [<leaf>]]` to a set of leaf tests, knows which need a homed gimbal (the `test hardware` leaves deliberately do not), and runs them on a worker thread with live progress, handing back a `TestReport` |
|
||
| [include/fgc/TestReport.h](../include/fgc/TestReport.h), [src/core/TestReport.cpp](../src/core/TestReport.cpp) | A self-test run's result as plain data: formats a human-readable text report that `parseTestReport` reads back, so a run can be compared with the baseline or the previous one |
|
||
| [include/fgc/HostMetrics.h](../include/fgc/HostMetrics.h), [src/core/HostMetrics.cpp](../src/core/HostMetrics.cpp) | Tower computer health for `test host` and the CPU guard: CPU temperature, clock, kernel throttle counters, busy time, disk, memory and load. Every `/sys` and `/proc` path sits under a `root`, so a fake tree stands in for a simulation. Pure parsers split from the live `/sys` and `/proc` readers so they unit-test anywhere; readers report `ok = false` rather than throw |
|
||
| [include/fgc/Calibration.h](../include/fgc/Calibration.h), [src/core/Calibration.cpp](../src/core/Calibration.cpp) | `linearFit` (least-squares, R²) + `circularMeanDeg` for the IMU-referenced calibration |
|
||
| [include/fgc/PitchSetup.h](../include/fgc/PitchSetup.h), [src/core/PitchSetup.cpp](../src/core/PitchSetup.cpp) | `gimbal pitch setup` worker thread: home-if-needed → pitch sweep dwelling on the gravity-referenced IMU pitch → least-squares fit → park level; tunable via `CalibParams`; exposes `progress()`/`report()`/`takeResult()`. Yaw is not touched |
|
||
| [include/fgc/HeadingFit.h](../include/fgc/HeadingFit.h), [src/core/HeadingFit.cpp](../src/core/HeadingFit.cpp) | `gimbal heading fit`: least-squares fit of the yaw map from operator sightings of landmarks with known true bearings. Sorts by encoder position, unwraps bearings so a set spanning north fits one line, and refuses a baseline under 10° (two points always fit exactly, so a short baseline looks perfect and is wrong). No IMU involved |
|
||
| [include/fgc/MtiProtocol.h](../include/fgc/MtiProtocol.h), [src/core/MtiProtocol.cpp](../src/core/MtiProtocol.cpp) | Xsens MTi binary protocol: `MtiFramer` (checksum framing), config-message builders, `parseMTData` → `ImuSample` (temp/acc/gyr/mag/euler), config-readback query builders + `applyImuConfigAck`/`finalizeImuConfig` → `ImuDeviceConfig` (product/firmware/device-id/output mode+settings/sample rate/**XKF scenario**); orientation-control builders `msgSetNoRotation` (gyro-bias update) + `msgResetOrientation` (heading reset / store) + `msgSetFilterProfile` (select XKF profile) + `pickNoMagProfile` (choose a magnetometer-free profile from the available list); named `mtmode`/`mtsettings` bitfields with `mtDataLen` deriving the payload length from them (so `kMTDataLen` cannot drift from what the parser decodes), plus `checkImuDeviceConfig` → `ImuConfigMismatch` (does the device report what the host asked for?) and `mtiErrorText` for MID `0x42` rejections |
|
||
| [include/fgc/ImuMount.h](../include/fgc/ImuMount.h), [src/core/ImuMount.cpp](../src/core/ImuMount.cpp) | `ImuMount` (the `[IMU] x/y/z` mounting rotation) and the pure math around it: `mountRotationZXY` (intrinsic Z-X-Y), the `rotationFromEulerZYX`/`eulerZYXFromRotation` pair for the MTi's own yaw-pitch-roll output, `applyFrames`, which moves a whole `ImuSample` into the gimbal's NED frame by re-referencing the earth frame (`ImuReference`/`nedFromReference`, left-multiplied) and then applying the mount (right-multiplied) — the two are not interchangeable, which is why a mount alone cannot correct a Z-up device; `applyMount` is the NED-already shorthand. Body-frame vectors are rotated by the mount only. Returns the sample untouched when there is nothing to do |
|
||
| [include/fgc/MountedImuSource.h](../include/fgc/MountedImuSource.h) | Decorator over any `IImuSource` applying that rotation in `sample()`. Wrapped around the factory's output in `Application::makeImu()`, so every consumer reads gimbal-frame values without knowing a mount exists; skipped entirely when the mount is the identity |
|
||
| [include/fgc/sensors/Sht41Protocol.h](../include/fgc/sensors/Sht41Protocol.h), [src/sensors/Sht41Protocol.cpp](../src/sensors/Sht41Protocol.cpp) | Sensirion SHT4x wire protocol, pure: command bytes, `sht41Crc8` (poly 0x31/init 0xFF), `decodeSht41Measurement` (6-byte response → `Sht41Reading`, CRC-checked, raw→°C/%RH with RH clamped 0..100) |
|
||
| [include/fgc/sensors/McuEnvSensor.h](../include/fgc/sensors/McuEnvSensor.h), [src/sensors/McuEnvSensor.cpp](../src/sensors/McuEnvSensor.cpp) | `IEnvSensor` for the older towers' DHT11, wired to the motor board (`[Hardware] env = dht11`): serves the firmware's latest `ENV` line, and no reading after a failed checksum or 2.5 read periods of silence |
|
||
| [include/fgc/CameraProfile.h](../include/fgc/CameraProfile.h), [src/core/CameraProfile.cpp](../src/core/CameraProfile.cpp) | `loadCameraProfile` / `loadCameraProfiles`: one VimbaX settings XML per profile. Hashes the XML, regenerates the `.txt` record when it changes, and reports which settings moved. A missing XML throws — the record is never a fallback |
|
||
| [include/fgc/CameraSettingsXml.h](../include/fgc/CameraSettingsXml.h), [src/core/CameraSettingsXml.cpp](../src/core/CameraSettingsXml.cpp) | Parses the Viewer export's `<RemoteDevice>` features (ordered, selector-aware), renders the human-readable `.txt` record, reads one back, and diffs two settings lists. Used for display and change detection only — never to configure a camera |
|
||
| [include/fgc/ChannelTopics.h](../include/fgc/ChannelTopics.h), [src/core/ChannelTopics.cpp](../src/core/ChannelTopics.cpp) | The MQTT wire format in one place: topic builders for the `GGS/FWT/<tower>/…` tree and the `CamEvent`/status payload builders. Pure, so the documented format is unit-tested rather than assembled inline at each publish site |
|
||
| [include/fgc/ChannelHistory.h](../include/fgc/ChannelHistory.h), [src/core/ChannelHistory.cpp](../src/core/ChannelHistory.cpp) | Ring buffer of recently published messages (time, topic, payload, outcome) plus the delivery counters — sent, failed, connects, losses, last status. Feeds the expanded Connectivity view so a publish can be confirmed without a broker-side subscriber |
|
||
| [include/fgc/RemoteCommands.h](../include/fgc/RemoteCommands.h), [src/core/RemoteCommands.cpp](../src/core/RemoteCommands.cpp) | Console commands that arrive over MQTT: run through the same handler as typed ones, on the control thread, and answered on `CommandReply` with every line they logged. Retained messages are never run, a repeated id is answered again rather than re-run, each is audited in the log, and a reply that depends on the firmware (`gimbal dump`) waits for its answer |
|
||
| [include/fgc/Hash.h](../include/fgc/Hash.h), [src/core/Hash.cpp](../src/core/Hash.cpp) | FNV-1a 64 over a string or a file, with an algorithm-prefixed label. Change detection for the profile records, deliberately not a crypto hash |
|
||
| [include/fgc/CaptureScheduler.h](../include/fgc/CaptureScheduler.h), [src/core/CaptureScheduler.cpp](../src/core/CaptureScheduler.cpp) | Capture state machine over the interfaces: move, standstill, settle wait (+ `SettleRule`), the rest of the interval since the last frame, keep; holds while saving is behind; slowed (`setIntervalScale`) or paused (`setPaused`) by the CPU guard; injectable clock; `setGeometry` adopts a recalibration |
|
||
| [include/fgc/ThermalGuard.h](../include/fgc/ThermalGuard.h), [src/core/ThermalGuard.cpp](../src/core/ThermalGuard.cpp) | The CPU guard, pure: fed one host sample at a time (temperature, clock, throttle counters, busy %, whether saving holds the scan), it says normal / slowed / paused and returns the log lines; `ThrottleWatch` logs throttling as it starts, once a minute while it lasts, and when it ends. Never stops the program or changes the mode ([thermal-throttling.md](thermal-throttling.md)) |
|
||
| [include/fgc/SettleRule.h](../include/fgc/SettleRule.h), [src/core/SettleRule.cpp](../src/core/SettleRule.cpp) | The `settle_rule = brightness` keep rule, pure: fed one frame's brightness, exposure and gain at a time, says when the image has settled (in the camera's target band and steady for 3 frames) or is pinned at the auto-exposure limits |
|
||
| [include/fgc/RawImage.h](../include/fgc/RawImage.h), [src/core/RawImage.cpp](../src/core/RawImage.cpp) | Pixel layouts the program accepts (`Mono8`, `RGB8`, `BayerRG8`, `BayerRG12p`), their sizes and bit depths, `unpack12p`, and `sampleBrightness` - the cheap per-frame sample the keep rule reads |
|
||
| [include/fgc/CameraStall.h](../include/fgc/CameraStall.h), [src/core/CameraStall.cpp](../src/core/CameraStall.cpp) | When a camera stream delivers nothing new: wait, restart the stream, or reset the camera. Pure policy, scaled by the exposure and frame period so a slow frame is not taken for a stall |
|
||
| [include/fgc/Application.h](../include/fgc/Application.h), [src/core/Application.cpp](../src/core/Application.cpp) | Factory (real vs mock, headless vs TUI), wiring, control loop, `gimbal …` commands, background-result polling, `buildSnapshot()` |
|
||
| [include/fgc/ui/UiSnapshot.h](../include/fgc/ui/UiSnapshot.h), [src/ui/UiSnapshot.cpp](../src/ui/UiSnapshot.cpp) | Plain-data view model (incl. `ImuView`, `EnvView`, `ActivityView`, `CalibResultView`, `DumpView`) + pure formatting helpers (state label/colour, degrees, time-ago, pending-sensors) |
|
||
| [include/fgc/ui/Scroll.h](../include/fgc/ui/Scroll.h), [src/ui/Scroll.cpp](../src/ui/Scroll.cpp) | Viewport arithmetic for the scrollable TUI panes, line-anchored. Pure integer maths with no FTXUI types, so it is in `fgc_core` and unit-tested without a terminal |
|
||
| [include/fgc/ui/HeadlessUi.h](../include/fgc/ui/HeadlessUi.h), [src/ui/HeadlessUi.cpp](../src/ui/HeadlessUi.cpp) | Default line console: stdin → command sink; logs via the default stdout/stderr writer |
|
||
| [ini.c](../ini.c), [ini.h](../ini.h) | Bundled third-party inih INI parser |
|
||
|
||
## Interfaces
|
||
|
||
| File | Interface | Shared structs |
|
||
|------|-----------|----------------|
|
||
| [include/fgc/IMotorController.h](../include/fgc/IMotorController.h) | `IMotorController` | `MotorTelemetry` |
|
||
| [include/fgc/IControlChannel.h](../include/fgc/IControlChannel.h) | `IControlChannel` | `RemoteCommand`, `CommandReply`, `CamEvent` |
|
||
| [include/fgc/ICameraSource.h](../include/fgc/ICameraSource.h) | `ICameraSource` | `Frame` |
|
||
| [include/fgc/IImuSource.h](../include/fgc/IImuSource.h) | `IImuSource` (incl. `config()`, `configProblems()`, `refreshConfig()`, `noRotation()`, `headingReset()`, `setFilterProfile()`) | `ImuSample` (from `MtiProtocol.h`) |
|
||
| [include/fgc/IEnvSensor.h](../include/fgc/IEnvSensor.h) | `IEnvSensor` (`connected()`, `sample()`, `name()`) | `EnvSample` |
|
||
| [include/fgc/ui/IUserInterface.h](../include/fgc/ui/IUserInterface.h) | `IUserInterface` | `UiSnapshot` |
|
||
|
||
## Real implementations (SDK-gated)
|
||
|
||
| File | Implements | Built when |
|
||
|------|-----------|-----------|
|
||
| [src/serial/SerialMotorController.cpp](../src/serial/SerialMotorController.cpp) | `IMotorController` over Boost.Asio serial (pImpl) | always |
|
||
| [src/serial/MtiImuSource.cpp](../src/serial/MtiImuSource.cpp) | `IImuSource` over Boost.Asio serial: configures the MTi to Euler+calibrated, reads back its config (`config()` → `ImuDeviceConfig`), then frames the MTData stream | always |
|
||
| [src/sensors/I2cBus.cpp](../src/sensors/I2cBus.cpp) | Thin `/dev/i2c-N` wrapper (open/close, `write`, `writeRead` via `I2C_SLAVE` ioctl) | always |
|
||
| [src/sensors/Sht41EnvSensor.cpp](../src/sensors/Sht41EnvSensor.cpp) | `IEnvSensor` over I2C (pImpl): soft-reset, poll thread at `[Env] period_ms`, decode via `Sht41Protocol`, staleness = no valid sample within 2× the period | always |
|
||
| [src/mqtt/MqttControlChannel.cpp](../src/mqtt/MqttControlChannel.cpp) | `IControlChannel` over Eclipse Paho | `WITH_MQTT` |
|
||
| [src/ui/TuiUi.cpp](../src/ui/TuiUi.cpp) | `IUserInterface` over FTXUI: panels, key bar, log pane | `WITH_TUI` |
|
||
| [src/camera/VimbaCameraSource.cpp](../src/camera/VimbaCameraSource.cpp) | `ICameraSource` over Vimba X (pImpl): free-running stream + keep-latest, restart-on-stall, and per-camera profiles applied with the SDK's `LoadSettings` on the Viewer's settings XML | `WITH_VIMBA` |
|
||
| [src/camera/JpegXlEncoder.cpp](../src/camera/JpegXlEncoder.cpp) | libjxl encode-to-file, 8-bit or up to 16-bit values (a 12-bit frame keeps its depth); distance 0 is lossless | always |
|
||
| [src/camera/ImagePipeline.cpp](../src/camera/ImagePipeline.cpp) | frame → de-Bayer (raw profiles) → rotate → encode → write → CamEvent (`save_workers` background workers; `canAccept()` is the scan's save gate). The pose is sampled in `submit()` and carried with the frame, so it belongs to the pixels rather than to whenever the encode finished | always |
|
||
|
||
## Mock / null implementations
|
||
|
||
| File | Implements |
|
||
|------|-----------|
|
||
| [include/fgc/mock/MockMotorController.h](../include/fgc/mock/MockMotorController.h) | Simulated sweeping gimbal (incl. canned `DUMP`) |
|
||
| [include/fgc/mock/NullControlChannel.h](../include/fgc/mock/NullControlChannel.h) | No-op channel; auto-sweep |
|
||
| [include/fgc/mock/MockCameraSource.h](../include/fgc/mock/MockCameraSource.h) | Synthetic gradient frames |
|
||
| [include/fgc/mock/MockImuSource.h](../include/fgc/mock/MockImuSource.h) | Synthetic IMU (sinusoidal orientation, gravity on accZ) |
|
||
| [include/fgc/mock/MockEnvSensor.h](../include/fgc/mock/MockEnvSensor.h) | Synthetic ambient sensor (slow sinusoidal temp/humidity, never stale); `--mock-env` |
|
||
|
||
## Entry point & scripts
|
||
|
||
| File | Role |
|
||
|------|------|
|
||
| [main.cpp](../main.cpp) | CLI parsing → `AppConfig` + `RuntimeOptions` → `Application::run()` |
|
||
| [tools/fgc_config.cpp](../tools/fgc_config.cpp) | `fgc-config`: writes and edits a tower's `config.ini` and `hardware.ini` (`seed`, `set`, `get`, `check`) so what lands on disk is always a file the program accepts. Used by `scripts/install-tower`; the rules are in `IniEdit` |
|
||
| [tools/camera-bench/](../tools/camera-bench/README.md) | Camera and CPU measurements behind the capture design (frame rates, losses, settle time, encode cost). Not part of the build; results in [camera-capture-measurements.md](camera-capture-measurements.md) |
|
||
| [scripts/run.sh](../scripts/run.sh) | Path-independent launcher |
|
||
| [scripts/fire-gimbal-control.service](../scripts/fire-gimbal-control.service) | systemd unit template |
|
||
|
||
## Data structures
|
||
|
||
### `MotorTelemetry` / `AxisTelemetry` ([IMotorController.h](../include/fgc/IMotorController.h))
|
||
Per-axis `yaw` and `pitch` segments (`pitch_present` flag), each an `AxisTelemetry`: `state`
|
||
(`AxisState` B/R/H/A/E/C — the last being NEEDCFG, a firmware waiting to be told its drivetrain), `xactual`, `xenc` (encoder counts), `drv_status`, `sg`/`cs`/`pwm`, and flags
|
||
`standstill`/`stall`/`deviation`/`overtemp`/`endstop_l`/`endstop_r`, with `moving()`/`ready()` helpers.
|
||
`MotorTelemetry::pitchAbsent()` is true for a yaw-only tower — a status line has arrived without a `P:`
|
||
segment — and is what the host checks before refusing a pitch command.
|
||
Parsed from the firmware `ST Y:...[ P:...]` line; degrees are derived via `Geometry`.
|
||
|
||
`deviation` (flag `d`) is the TMC5160's hardware encoder-deviation latch — set when `|xactual − xenc|`
|
||
exceeds `ENC_DEVIATION`, i.e. the shaft is not where it was commanded to be. It is the usable stall signal
|
||
on this rig and is what the TUI's STALL badge shows. `stall` (flag `s`) is StallGuard2, which is inert
|
||
because production runs StealthChop; it is still parsed in case that changes. The latch is cleared by
|
||
homing or `gimbal reset`, so a lit badge means "position untrustworthy since the last home".
|
||
|
||
### `Mode`, `PanSpec` ([OperatingMode.h](../include/fgc/OperatingMode.h))
|
||
`Mode` is auto (0), pan (1) or manual (2) - the values StatusCode carries. `PanSpec` is a pan's
|
||
`heading`, `span` and `pitch` in degrees; `panHeadings()` turns it into the bearings visited, and
|
||
`allowedIn()` says which commands a mode accepts.
|
||
|
||
### `CamEvent` ([IControlChannel.h](../include/fgc/IControlChannel.h))
|
||
`tower`, `camera` (RGB/ACR/NIR), `heading_decideg` (yaw×10), `pitch_decideg` (pitch×10) and
|
||
`timestamp_ms` (which matches the image filename). Serialized to the CamEvent JSON payload (see [mqtt-api.md](mqtt-api.md)).
|
||
|
||
### `EnvSample` ([IEnvSensor.h](../include/fgc/IEnvSensor.h))
|
||
One ambient reading: `valid`, `temp_c` (°C), `humidity_pct` (0..100), `timestamp_ms` (epoch ms).
|
||
`IEnvSensor::sample()` returns `nullopt` rather than a stale sample once nothing valid has arrived
|
||
within 2× the configured poll period, so a silently dead sensor reads as absent, not as a frozen
|
||
value. Surfaced as `EnvView` in the TUI (`s` view) and as `SensorsView::env` in the compact panel.
|
||
|
||
### `Frame` ([ICameraSource.h](../include/fgc/ICameraSource.h))
|
||
Owned pixel buffer + `width`, `height`, `channels` (1 or 3), `timestamp_ms`, `cam_id`.
|
||
|
||
### `ImuSample` ([MtiProtocol.h](../include/fgc/MtiProtocol.h))
|
||
One decoded Xsens MTi reading: `temp_c` (°C), `acc[3]` (m/s², incl. gravity), `gyr[3]` (rad/s),
|
||
`mag[3]` (a.u., earth-normalized), `roll_deg` (−180..180), `pitch_deg` (−90..90), `yaw_deg`
|
||
(**−180..180**, exactly as the device reports it), `sample_counter`. `parseMTData` applies no sign flip and no re-ranging to any axis.
|
||
`gimbal heading fit` phase-unwraps the sighted bearings (`unwrapNear`) so a set crossing 0/360 still fits a clean
|
||
line.
|
||
|
||
### `ImuDeviceConfig` ([MtiProtocol.h](../include/fgc/MtiProtocol.h))
|
||
The device configuration read back during the Config-state handshake (each `has_*` flag marks whether
|
||
the device actually answered): `product_code`, `device_id`, `firmware`, output mode flags
|
||
(temp/calibrated/orientation/aux/status), output settings (`orientation_mode`, `timestamp_mode`,
|
||
per-channel `acc/gyr/mag_enabled`, `data_format`), `period`/`sample_rate_hz`, and the **XKF scenario**
|
||
(`scenario_type`/`version`, resolved to `scenario_label` against `available_profiles`). Built by
|
||
`applyImuConfigAck` (one ack frame at a time) + `finalizeImuConfig` (derive rate, resolve label).
|
||
Surfaced read-only in the TUI's `s` view (IMU CONFIG section); the legacy "scenario" is the modern
|
||
"filter profile" — same MIDs (`0x62/0x64/0x65`).
|
||
|
||
## On-disk artifacts
|
||
|
||
| Artifact | Path | Format |
|
||
|----------|------|--------|
|
||
| Captured images | `<output_dir>/<label>/<unix_ms>.jxl` | JPEG XL, rotated per that camera's `rotation_deg`; `<label>` is `[Camera.<name>] label` |
|
||
| Diagnostics log | `<data_dir>/fire_gimbal_control/logs/diag_<ts>.log` | raw `DG` stream + parsed summary |
|
||
| Calibration log | `<data_dir>/fire_gimbal_control/logs/calib_<ts>.log` | per-axis samples + the least-squares fit |
|
||
|
||
Diagnostics/calibration results are also surfaced live in the TUI (activity strip + gimbal `g` view)
|
||
and applied to the running session's geometry; other state lives in memory.
|