191 lines
11 KiB
Markdown
191 lines
11 KiB
Markdown
# Configuration & Commands
|
||
|
||
Behaviour is controlled by three layers: the **`config.ini`** file, **command-line flags** (which override the
|
||
config), and **interactive console commands** typed while running.
|
||
|
||
## Config file resolution
|
||
|
||
There are no hardcoded paths. The file is resolved in this order ([src/core/Paths.cpp](../src/core/Paths.cpp)):
|
||
|
||
1. `--config <path>` CLI flag
|
||
2. `$FGC_CONFIG` environment variable
|
||
3. `./config.ini` (current directory)
|
||
4. `<executable dir>/config.ini`
|
||
5. `$XDG_CONFIG_HOME/fire_gimbal_control/config.ini` (else `~/.config/...`)
|
||
|
||
If none exist, the program prints every location it searched and exits. Start from the template:
|
||
|
||
```bash
|
||
cp config/config.example.ini config.ini
|
||
```
|
||
|
||
## `config.ini` keys
|
||
|
||
Parsed and validated by `ConfigLoader` ([src/core/Config.cpp](../src/core/Config.cpp)) into a typed
|
||
`AppConfig`. Invalid types/values fail fast with a clear message.
|
||
|
||
| Section | Key | Type | Default | Meaning |
|
||
|---------|-----|------|---------|---------|
|
||
| `General` | `tower_name` | string | `Unnamed` | Tower identity; used in all MQTT topics/payloads |
|
||
| `General` | `image_interval` | int > 0 | `5` | Seconds between captures (→ `image_rate = 1/interval`) |
|
||
| `General` | `debug` | bool | `false` | Start with debug-level logging |
|
||
| `Network` | `zkms_server_ip` | string | `127.0.0.1` | MQTT broker address |
|
||
| `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 |
|
||
| `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) |
|
||
| `Features` | `enable_serial` | bool | `true` | (reserved) |
|
||
| `Features` | `mock_camera` | bool | `false` | Use the simulated camera |
|
||
| `Features` | `mock_serial` | bool | `false` | Use the simulated motor controller |
|
||
| `Logging` | `level` | enum | `info` | Linear log level (`--log-level` overrides) |
|
||
| `Logging` | `trace` | csv | — | Wire-trace categories, off by default (`--trace` overrides) |
|
||
| `UI` | `enable_tui` | bool | `false` | Full-screen terminal dashboard (`--tui`/`--no-tui` override; needs `WITH_TUI=ON`) |
|
||
| `Motor` | `yaw_counts_per_deg` / `pitch_counts_per_deg` | float | `983.33` / — | Encoder counts per degree (**calibrate**; may be negative to flip) |
|
||
| `Motor` | `yaw_zero_count` / `pitch_zero_count` | int | `500000` / `0` | `xenc` value that = 0° |
|
||
| `Motor` | `yaw_min_deg`/`yaw_max_deg`/`pitch_*` | float | `-90`/`90`/… | Soft clamp on commanded degrees |
|
||
| `Scan` | `grid_file` | string | — | CSV of `yaw_deg,pitch_deg` waypoints; empty → generate |
|
||
| `Scan` | `yaw_intervals` | int | `56` | Generated yaw positions across `[yaw_min_deg, yaw_max_deg]` |
|
||
| `Scan` | `yaw_min_deg`/`yaw_max_deg` | float | `-90`/`90` | Generated yaw arc |
|
||
| `Scan` | `pitch_levels` | csv | `0` | Generated pitch elevations (deg) |
|
||
|
||
### `[Motor]` calibration & `[Scan]` grid
|
||
|
||
The firmware reports only **encoder counts**; `[Motor]` maps them to the heading/elevation degrees
|
||
used by MQTT (`target_HDG`) and `CamEvent`. Calibrate `*_counts_per_deg` / `*_zero_count` against
|
||
real `xenc` readings after homing (`MOVE` a known angle, read the resulting `xenc`).
|
||
|
||
The capture **scan grid** is the ordered `(yaw,pitch)` waypoints auto-sweep visits (ping-pong). 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`).
|
||
|
||
### Secrets
|
||
|
||
`mqtt_user` / `mqtt_pw` are read from the environment variables **`FGC_MQTT_USER` / `FGC_MQTT_PW`** first,
|
||
falling back to the config file. Keep credentials out of `config.ini` (which is gitignored anyway) by exporting
|
||
them or using a systemd `EnvironmentFile`.
|
||
|
||
## Command-line flags
|
||
|
||
Parsed by Boost.Program_options ([main.cpp](../main.cpp)). Flags override `[Features]`.
|
||
|
||
| Flag | Effect |
|
||
|------|--------|
|
||
| `-h, --help` | Show help and exit |
|
||
| `-c, --config <path>` | Explicit config file path |
|
||
| `-i, --init` | Run the endstop-finding init sequence before the loop |
|
||
| `-s, --start` | Start capture automatically |
|
||
| `-d, --demo` | Demo mode: copy the placeholder image instead of encoding |
|
||
| `--no-mqtt` | Disable MQTT (use the null channel) |
|
||
| `--mock-camera` | Use the simulated camera |
|
||
| `--mock-serial` | Use the simulated motor controller |
|
||
| `--tui` | Show the full-screen terminal dashboard (overrides `[UI] enable_tui`) |
|
||
| `--no-tui` | Force the headless line console (overrides config; wins over `--tui`) |
|
||
| `--log-level <lvl>` | `trace`/`debug`/`info`/`warn`/`error`/`off` |
|
||
| `--trace <cats>` | Verbatim wire trace; comma list `serial,mqtt,camera,control,all,none` |
|
||
|
||
Typical headless dev run: `scripts/run.sh --mock-serial --mock-camera --no-mqtt --start`.
|
||
|
||
### Init sequence (`--init`)
|
||
|
||
Sends `ENABLE Y`, `ENABLE P`, `HOME`, then polls telemetry until both axes report `READY`
|
||
(firmware `ST` state `A`), bounded by the firmware's 60 s homing timeout, then sets `SPEED Y/P`
|
||
([src/core/Application.cpp](../src/core/Application.cpp), `runInitSequence`).
|
||
|
||
## Interactive console commands
|
||
|
||
Lines on stdin are parsed by `parseCommand` ([src/core/CommandParser.cpp](../src/core/CommandParser.cpp)) — a
|
||
whitespace tokenizer (`<verb> [device] [option] [value]`) that replaced the old fragile Boost.Spirit grammar.
|
||
Handled in `Application::Impl::handleCommand`.
|
||
|
||
| Type this | Meaning |
|
||
|-----------|---------|
|
||
| `start` | Start camera acquisition + capture |
|
||
| `stop` | Stop acquisition |
|
||
| `debug` | Toggle debug logging |
|
||
| `trace <cat> [on\|off]` | Toggle a wire-trace category (`serial`/`mqtt`/`camera`/`control`) |
|
||
| `trace all` / `trace off` | Enable every category / silence all |
|
||
| `set camera jxlq <v>` | JPEG XL distance (0 = lossless) |
|
||
| `set camera jxle <v>` | JPEG XL effort |
|
||
| `set camera display <0\|1>` | Toggle OpenCV preview window |
|
||
| `set camera fps <v>` | Camera acquisition frame rate (real camera only) |
|
||
| `set fps <v>` | Capture interval rate (images/second) |
|
||
| `set motorctl <cmd>` | Forward a raw command to the motor controller (e.g. `set motorctl MOVE Y 20000`) |
|
||
| `exit` | Quit (Ctrl-D also works) |
|
||
|
||
## Terminal dashboard (TUI)
|
||
|
||
An **optional** full-screen interface (`--tui`, or `[UI] enable_tui = true`) renders the tower
|
||
status as sectioned, colored panels updated in place, with a scrolling log pane and a nano-style
|
||
key bar. It is built on [FTXUI](https://github.com/ArthurSonzogni/FTXUI) (fetched via
|
||
[cmake/Ftxui.cmake](../cmake/Ftxui.cmake) when `WITH_TUI=ON`, the default) and is **fully decoupled
|
||
from application logic**: the control loop publishes a plain `UiSnapshot`
|
||
([include/fgc/ui/UiSnapshot.h](../include/fgc/ui/UiSnapshot.h)) that the UI renders, and the UI
|
||
forwards keystrokes/typed commands back through the same command queue the console uses. Headless
|
||
operation is unchanged and remains the default — the same binary runs under systemd/ssh/pipes with
|
||
logs on stdout.
|
||
|
||
Panels (MVP): **Gimbal** (per-axis state, heading, encoder counts, flag badges, target),
|
||
**Sensors** (DHT11 + Xsens MTi — shown as *pending integration* until those drivers land),
|
||
**Camera** (count, capture state, rate, last capture), **Connectivity** (MQTT state, broker, tower,
|
||
control mode, target heading). Adding a panel later (e.g. computer vision) is a struct in
|
||
`UiSnapshot.h` plus one node in [src/ui/TuiUi.cpp](../src/ui/TuiUi.cpp).
|
||
|
||
Keys (shown in the bottom bar): `s` start · `x` stop · `h` home · `r` reset · `:` open a command
|
||
line (any console/`set motorctl …` command) · `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 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
|
||
runs headless.
|
||
|
||
## Logging: level vs. wire-trace categories
|
||
|
||
Two **independent** controls, each settable via config (`[Logging]`), CLI, and a console command:
|
||
|
||
- **Linear level** — `[Logging] level`, `--log-level`, console `debug`. Filters ordinary messages
|
||
(`trace<debug<info<warn<error<off`). Default `info`; an active capture is nearly silent at `info`.
|
||
- **Wire-trace categories** — `[Logging] trace`, `--trace`, console `trace`. Each enabled category
|
||
(`serial`, `mqtt`, `camera`, `control`) logs **every** message exchanged with that subsystem,
|
||
verbatim. They are **off by default** and **independent of the level**: `--trace serial` prints all
|
||
firmware serial traffic even at `info`. Only level `off` silences them. The `camera` category is
|
||
high-rate (one line per frame, metadata only — no pixel data).
|
||
|
||
Precedence at startup: config applies first, then CLI overrides (`--trace` replaces the config set,
|
||
it does not merge). At runtime the `trace` console command edits categories incrementally.
|
||
|
||
Trace lines carry a category tag and a `TX`/`RX` direction so one subsystem is easy to follow/grep:
|
||
|
||
```
|
||
[SERIAL] TX MOVE -90,0 (command to firmware: yaw,pitch counts)
|
||
[SERIAL] RX ST Y:A,982,969,80084000,0,8,8,Se P:A,... (status from firmware)
|
||
[MQTT] PUB GGS/FWT/Tower/StatusCode 0 / [MQTT] RX GGS/FWT/Tower/target_HDG 180
|
||
[CAMERA] TX trigger cam0 / [CAMERA] RX frame cam0 1936x1216 7064576B
|
||
[CONTROL] sweep -> grid yaw=-90 pitch=0 (scheduler decisions + inbound console commands)
|
||
```
|
||
|
||
## Motor command vocabulary (emitted by the software)
|
||
|
||
The firmware speaks **full-word, newline-terminated** commands in absolute **encoder counts**
|
||
(see `../firmware/docs/protocol.md`). The host converts degrees↔counts via the `[Motor]` calibration.
|
||
|
||
| Command | When | Meaning |
|
||
|---------|------|---------|
|
||
| `ENABLE Y` / `ENABLE P` | init | energize the axis coils |
|
||
| `HOME` | init | home all axes (firmware runs it non-blocking; watch `ST` state `R→H→A`) |
|
||
| `SPEED Y\|P <vel>` | init | set the production move speed (VMAX, counts/s) |
|
||
| `MOVE <yaw>,<pitch>` | each capture point | drive both axes to absolute counts (combined form) |
|
||
| `MOVE Y\|P <pos>` | — | single-axis absolute move (`set motorctl`) |
|
||
| `STOP Y\|P\|ALL` | — | ramp to a controlled stop |
|
||
|
||
Capture is a **move → settle → trigger** cycle: the scheduler issues a `MOVE`, waits until both
|
||
axes report standstill at the target, then triggers the cameras. **ControlCode 0** walks the scan
|
||
grid (ping-pong); **ControlCode 1** drives yaw to `target_HDG` (pitch held). Telemetry arrives as
|
||
firmware `ST` lines (per-axis state + encoder counts), parsed by
|
||
[TelemetryParser](../src/core/TelemetryParser.cpp).
|