diff --git a/config/camera_profiles/rgb/README.md b/config/camera_profiles/rgb/README.md index a5efaac..42a48cb 100644 --- a/config/camera_profiles/rgb/README.md +++ b/config/camera_profiles/rgb/README.md @@ -60,7 +60,8 @@ Both used 2×2 binning at `RGB8` (2256×2256, 15.3 MB/frame) and a 250 MB/s full-resolution RGB8 frame is 61 MB, which caps out near 4 fps under that limit. The full previous definitions are in git history at -`config/camera_modes/rgb/{standard,night}.ini`. +the hand-written `config/camera_modes/rgb/{standard,night}.ini`, removed in the +same change that made the XML the only thing applied. `settle_wait_ms` / `frame_timeout_ms` are *not* camera settings and are not in the XML — they live in `config.ini` under `[Camera.rgb.]`. diff --git a/docs/configuration.md b/docs/configuration.md index 40c32f4..b2a2319 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -34,8 +34,6 @@ Parsed and validated by `ConfigLoader` ([src/core/Config.cpp](../src/core/Config | `Network` | `mqtt_user` / `mqtt_pw` | string | — | MQTT credentials (see secrets below) | | `Serial` | `device` | string | `/dev/ttyACM0` | Motor-controller serial device | | `Serial` | `baud` | int | `115200` | Serial baud rate | -| `Camera` | `id_Cam1`..`id_Cam4` | string | — | Camera IDs (GigE IP or USB `DEV_...`); non-empty ones used in order | -| `Camera` | `jxl_distance` / `jxl_effort` | | | Defaults inherited by every camera block | | `Camera.` | `id` | string | — | USB `DEV_` id or GigE IP. Required when enabled | | `Camera.` | `driver` | `vimba` | `vimba` | Backend. Only `vimba` is implemented | | `Camera.` | `label` | string | the block name | Output subdirectory and `CamEvent` `cam` field | @@ -45,9 +43,8 @@ Parsed and validated by `ConfigLoader` ([src/core/Config.cpp](../src/core/Config | `Camera.` | `rotation_deg` | `0`/`90`/`180`/`270` | `0` | Clockwise rotation applied to that camera's saved images | | `Camera..` | `settle_wait_ms` | int ≥ 0 | `1000` | Hold after standstill before keeping a frame | | `Camera..` | `frame_timeout_ms` | int ≥ 0 | `2000` | Abandon the waypoint after this | -| `Camera` | `jxl_distance` | double ≥ 0 | `0.8` | JPEG XL distance (`0` = lossless, `~0.8` = near-lossless) | -| `Camera` | `jxl_effort` | int 1..9 | `4` | JPEG XL effort (higher = slower/smaller) | -| `Camera` | `rotation_deg` | `0`/`90`/`180`/`270` | `0` | Clockwise rotation applied to saved images (`0` = as the sensor delivers them) | +| `Camera` | `jxl_distance` / `jxl_effort` | double ≥ 0 / int 1..9 | `0.8` / `4` | Encoder defaults inherited by every camera block | +| `Camera.` | `jxl_distance` / `jxl_effort` | double ≥ 0 / int 1..9 | the `[Camera]` value | Per-camera override of the encoder settings | | `Paths` | `output_dir` | string | `$XDG_DATA_HOME/fire_gimbal_control/images` | Image output dir; supports `~`/`$ENV` | | `Features` | `enable_mqtt` | bool | `true` | Use MQTT (vs null channel) | | `Features` | `enable_camera` | bool | `true` | (reserved) | @@ -117,7 +114,7 @@ back. Set `[Scan] grid_file` to an editable CSV ([config/scan.csv](../config/scan.csv)) to define exact coordinates, or leave it blank to generate `yaw_intervals × pitch_levels` points. -Camera index → output subfolder defaults to `RGB`, `ACR`, `NIR` (`CameraConfig::labels`). +Each camera's output subfolder is its `[Camera.] label` (defaulting to the block name); nothing is derived from the order of the blocks. ### `[IMU]` — Xsens MTi orientation sensor @@ -193,9 +190,25 @@ it. Enable with `[Features] enable_env = true` and point `[Env] i2c_device` at the LattePanda's **own native I2C bus** — this is a separate physical link from the motor Leonardo's serial connection, not -the Arduino's D2 pin (the original DHT11 plan). Bus number varies by board; confirm with -`i2cdetect -y N` (the SHT41 answers at `0x44`/`68`). `i2c_addr` accepts either form — `0x44` hex or -`68` decimal. The backend (`Sht41EnvSensor`) polls at `period_ms`, discarding any sample that fails +the Arduino's D2 pin (the original DHT11 plan). `i2c_addr` accepts either form — `0x44` hex or +`68` decimal. + +**Finding the bus.** The number varies by board, and on this LattePanda the sensor answers on +`/dev/i2c-2`, not the `/dev/i2c-1` default. Two traps make this harder than it looks: + +- **`i2cdetect` false-negatives on the SHT4x.** Its probe (quick-write, or a plain read) is not a + command the sensor answers, so the address can show empty on the very bus the sensor is on. Probe + with a real measurement instead — `i2ctransfer -y N w1@0x44 0xfd`, wait ~10 ms, then + `i2ctransfer -y N r6@0x44`; six bytes that pass the CRC is the bus. +- **Not every `/dev/i2c-*` is a full I2C bus.** An SMBus-only adapter (`i2cdetect -F N` reports + `I2C = no`) cannot carry the SHT4x's transfers at all, so that bus can never work regardless of + wiring. + +A failed read is **logged** — once at the start of a failure streak (naming the bus, the address and +an `i2ctransfer` probe to run), and again when the sensor recovers, so a wrong bus announces itself +instead of leaving a silently empty panel, without reprinting the same line every `period_ms` for +the life of the process. A sample that fails its CRC is logged separately each time. The backend (`Sht41EnvSensor`) +polls at `period_ms`, discarding any sample that fails the SHT4x CRC-8 check (decode lives in the pure, unit-tested [Sht41Protocol](../include/fgc/sensors/Sht41Protocol.h)); once nothing valid arrives within **2× the period**, `sample()` reports absence rather than a frozen value, which is what turns the panel from @@ -252,6 +265,7 @@ Handled in `Application::Impl::handleCommand`. | `debug` | Toggle debug logging | | `trace [on\|off]` | Toggle a wire-trace category (`serial`/`mqtt`/`camera`/`control`) | | `trace all` / `trace off` | Enable every category / silence all | +| `set camera profile [] ` | Switch a camera to another profile from its `profile_dir` (the camera may be omitted when only one is configured) | | `set camera jxlq ` | JPEG XL distance (0 = lossless) | | `set camera jxle ` | JPEG XL effort | | `set camera display <0\|1>` | Toggle OpenCV preview window | @@ -260,8 +274,20 @@ Handled in `Application::Impl::handleCommand`. | `set motorctl ` | Forward a raw command to the motor controller (e.g. `set motorctl MOVE Y 20000`) | | `test [ []] []` | Run the hardware self-test suite (see below) | | `test list` / `test baseline` | List profiles / pin the last report as the comparison baseline | +| `imu status` | One-line orientation + sample age | +| `imu config` | The configuration read back from the device (product, firmware, output mode, sample rate, XKF profile) | +| `imu profile []` | Show the available XKF profiles, or switch to one (by numeric type or by name, e.g. `VRU_general`) | +| `imu zero` | Heading reset — make the current yaw the zero reference | +| `imu norotation []` | Gyro-bias update; the sensor must be still for the whole window | +| `refresh` | Re-read the IMU configuration and request a fresh firmware dump | +| `help [topic]` | The command catalog, or one section/verb expanded | | `exit` | Quit (Ctrl-D also works) | +The full catalog with per-command detail lives in `helpCatalog` +([src/core/HelpText.cpp](../src/core/HelpText.cpp)) and is what `help` prints — the table above is a +summary, and the `gimbal …` positioning/diagnostic verbs are documented there and in the motor +vocabulary section below. + ### `test` — hardware self-test suite `test` validates the gimbal/IMU/host on real hardware, writes a text report to @@ -297,6 +323,14 @@ one node in [src/ui/TuiUi.cpp](../src/ui/TuiUi.cpp). - **Gimbal** (`g`) — both axes side by side: live telemetry, the decoded firmware register **dump** (`d` requests a fresh one), the homing limits, and the **last calibration** result (per-axis `counts_per_deg` / `zero_count` / R² / age). +- **Cameras** (`c`) — a block per configured camera: identity (`name`, `label`, `id`, `driver`, + enabled), the active **profile** and the others available, the `profile_dir` and the exact XML + applied, the per-profile `settle_wait_ms`, `rotation_deg` and the JPEG XL settings, plus live + per-device telemetry (dimensions, streaming, frames, drops) and the scan grid. +- **Connectivity** (`m`) — broker, tower, client id, connection state and control mode, the + delivery counters (sent / failed / connects / losses), and a **history of the last MQTT + messages** — time, topic leaf and payload — so a publish can be confirmed without a broker-side + subscriber. The compact Connectivity panel carries the `m` badge that opens it. - **Sensors** (`i`) — both sensors, in headed sections so every reading is attributable. **MTi**: every channel with units — orientation (°), acceleration (m/s²), rate-of-turn (rad/s), magnetic field (a.u.), `TEMP (internal)`, sample counter, plus an **IMU CONFIG** section @@ -321,11 +355,19 @@ everything else) so it persists across restarts; press **`n`** to keep it for th showing. (Until you answer `y`, calibration remains session-only — see [known-issues.md](known-issues.md).) -Keys (bottom bar): `s` start · `x` stop · `h` home · `g` gimbal view · `i` IMU view · `r` refresh -(re-read IMU config + firmware dump) · arrow keys nudge the gimbal (yaw ±5 % / pitch ±10 %) · `:` open -a command line (any console/`gimbal …` -command) · `?` help · `q` quit. Plain letters are used rather than Ctrl chords so terminal -flow-control (`Ctrl-S`/`Ctrl-Q` XON/XOFF) can't swallow them. In TUI mode all log output is diverted +**The key bar is contextual**: it advertises the keys that are useful in the current view, not every +binding. `s` start and `x` stop lead everywhere — a scan can be started and stopped from any view — +and `:` command, `?` help and `q` quit close every set. The overview adds `h` home, `r` refresh, +`g i c m` views, `p` pause and the arrow keys (nudge the gimbal, yaw ±5 % / pitch ±10 %); an +expanded view instead offers its own (`d` dump in the gimbal view, `r` refresh in sensors, the +scroll keys, `Esc` close). Every binding still *works* everywhere; only the advertising changes. +`p` freezes the live refresh so text can be selected and copied. + +**`q` asks rather than quitting.** It opens a prompt: **`q`** quit (stop capture, close the camera, +end the program), **`b`** background (detach and leave it running — the same as `Ctrl-b d`, shown +dimmed and unavailable when the program is not inside a `scripts/fgc` tmux session), **`Esc`** +cancel. Plain letters are used rather than Ctrl chords so terminal flow-control +(`Ctrl-S`/`Ctrl-Q` XON/XOFF) can't swallow them. In TUI mode all log output is diverted from stdout into the on-screen log pane via a `Logger` sink, so the screen is never corrupted. Build without it (`-DWITH_TUI=OFF`) for a smaller, dependency-free binary; `--tui` then warns and diff --git a/docs/deployment.md b/docs/deployment.md index 8def270..2af33af 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -90,8 +90,11 @@ zkms_server_ip = 10.11.12.13 ; the broker address device = /dev/ttyACM0 ; the motor-controller MCU baud = 115200 -[Camera] -id_Cam1 = DEV_1AB22C0AADED ; or GigE IPs like 192.168.11.101 +[Camera.rgb] +id = DEV_1AB22C0AADED ; or a GigE IP like 192.168.11.101 +label = RGB +profile_dir = config/camera_profiles/rgb +profile = standard ``` **Credentials** go in the environment, not the config file. Create a root-only env file for the service: @@ -158,8 +161,10 @@ scripts/fgc stop # SIGTERM -> the same clean teardown as `exit` scripts/fgc peek # snapshot of the pane without attaching ``` -**`Ctrl-b d` detaches. The dashboard's own `q` key exits the program.** That is the one thing to get -right while attached. +**`Ctrl-b d` detaches. The dashboard's own `q` key opens an exit prompt** offering `q` (quit the +program) or `b` (background — detach and leave it running, the same as `Ctrl-b d`), with `Esc` to +cancel. `b` is offered only inside a `scripts/fgc` session; outside one there is nothing to detach +into and it is shown dimmed. `scripts/fgc stop` sends SIGTERM and waits up to 30s; the program handles it with the same shutdown path as the `exit` command (stop capture, close the camera, flush the image queue). Do **not** `kill -9` — @@ -168,7 +173,7 @@ an unclean exit leaves the camera wedged for the next run. If it will not stop, `scripts/fgc start` refuses if a run is already up, and prints the previous run's last output if it died rather than silently discarding it. -Quitting the dashboard with `q` while attached ends the tmux session too, so you land straight back in +Choosing **quit** at that prompt while attached ends the tmux session too, so you land straight back in your shell — `exit` then closes ssh. A run that *fails* is the exception: its pane is kept so you can read why (`remain-on-exit failed`), and `fgc status` shows it until `fgc stop` or the next `fgc start` clears it. diff --git a/docs/modules-reference.md b/docs/modules-reference.md index d7adbe9..0183a61 100644 --- a/docs/modules-reference.md +++ b/docs/modules-reference.md @@ -22,6 +22,8 @@ Per-file reference for the refactored tree, plus the shared data structures. | [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/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 `` 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//…` tree and the `CamEvent`/`EnvEvent`/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/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; injectable clock; `setGeometry` adopts a recalibration | | [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()` | @@ -128,7 +130,7 @@ Surfaced read-only in the TUI's `i` view (IMU CONFIG section); the legacy "scena | Artifact | Path | Format | |----------|------|--------| -| Captured images | `//.jxl` | JPEG XL, rotated per `Camera.rotation_deg` | +| Captured images | `/