fwt_software/docs/modules-reference.md

18 KiB
Raw Blame History

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, 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, src/core/Paths.cpp ~/$ENV expansion, executable dir, config search order, default output dir
include/fgc/Logger.h, src/core/Logger.cpp Leveled, thread-safe logger + per-category wire trace; LOG_TRACE..LOG_ERROR, LOG_TRACE_CAT
include/fgc/FileLogSink.h, 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, src/core/Geometry.cpp Per-axis degrees↔encoder-counts affine map ([Motor] calibration)
include/fgc/ScanGrid.h, src/core/ScanGrid.cpp Capture waypoints (CSV only, never generated) + one-way wrapping cursor ([Scan])
include/fgc/TelemetryParser.h, src/core/TelemetryParser.cpp parseTelemetryLine (firmware ST line) → std::optional<MotorTelemetry>
include/fgc/CommandParser.h, src/core/CommandParser.cpp parseCommand whitespace tokenizer → Command
include/fgc/HelpText.h, src/core/HelpText.cpp Operator command catalog (helpCatalog) + renderHelp (console & TUI help)
include/fgc/TmcCurrent.h, 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, 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, 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, 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, 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, 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, src/core/DiagParser.cpp parseDiag firmware DG self-test stream → DiagResult (per-axis tests, PASS/FAIL); formatDiag
include/fgc/Calibration.h, src/core/Calibration.cpp linearFit (least-squares, R²) + circularMeanDeg for the IMU-referenced calibration
include/fgc/PitchSetup.h, 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, 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, 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, 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 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, 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/CameraProfile.h, 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, 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, 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, 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/Hash.h, 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, src/core/CaptureScheduler.cpp Capture state machine over the interfaces; injectable clock; setGeometry adopts a recalibration
include/fgc/Application.h, 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, 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/HeadlessUi.h, src/ui/HeadlessUi.cpp Default line console: stdin → command sink; logs via the default stdout/stderr writer
ini.c, ini.h Bundled third-party inih INI parser

Interfaces

File Interface Shared structs
include/fgc/IMotorController.h IMotorController MotorTelemetry
include/fgc/IControlChannel.h IControlChannel ControlCommand, CamEvent
include/fgc/ICameraSource.h ICameraSource Frame
include/fgc/IImuSource.h IImuSource (incl. config(), configProblems(), refreshConfig(), noRotation(), headingReset(), setFilterProfile()) ImuSample (from MtiProtocol.h)
include/fgc/IEnvSensor.h IEnvSensor (connected(), sample(), name()) EnvSample
include/fgc/ui/IUserInterface.h IUserInterface UiSnapshot

Real implementations (SDK-gated)

File Implements Built when
src/serial/SerialMotorController.cpp IMotorController over Boost.Asio serial (pImpl) always
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 Thin /dev/i2c-N wrapper (open/close, write, writeRead via I2C_SLAVE ioctl) always
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 IControlChannel over Eclipse Paho WITH_MQTT
src/ui/TuiUi.cpp IUserInterface over FTXUI: panels, key bar, log pane WITH_TUI
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 libjxl encode-to-file always
src/camera/ImagePipeline.cpp frame → rotate → encode → write → CamEvent (worker thread). 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 Simulated sweeping gimbal (incl. canned DUMP)
include/fgc/mock/NullControlChannel.h No-op channel; auto-sweep
include/fgc/mock/MockCameraSource.h Synthetic gradient frames
include/fgc/mock/MockImuSource.h Synthetic IMU (sinusoidal orientation, gravity on accZ)
include/fgc/mock/MockEnvSensor.h Synthetic ambient sensor (slow sinusoidal temp/humidity, never stale); --mock-env

Entry point & scripts

File Role
main.cpp CLI parsing → AppConfig + RuntimeOptions → Application::run()
scripts/run.sh Path-independent launcher
scripts/fire-gimbal-control.service systemd unit template

Data structures

MotorTelemetry / AxisTelemetry (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. 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)

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)

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).

EnvSample (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)

Owned pixel buffer + width, height, channels (1 or 3), timestamp_ms, cam_id.

ImuSample (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)

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
Demo placeholder bin/x64/Release/test_smoke.jxl copied verbatim in demo mode
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.