# 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 ` CLI flag 2. `$FGC_CONFIG` environment variable 3. `./config.ini` (current directory) 4. `/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) | | `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 ` | 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 | | `--log-level ` | `trace`/`debug`/`info`/`warn`/`error`/`off` | | `--trace ` | 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 (` [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 [on\|off]` | Toggle a wire-trace category (`serial`/`mqtt`/`camera`/`control`) | | `trace all` / `trace off` | Enable every category / silence all | | `set camera jxlq ` | JPEG XL distance (0 = lossless) | | `set camera jxle ` | JPEG XL effort | | `set camera display <0\|1>` | Toggle OpenCV preview window | | `set camera fps ` | Camera acquisition frame rate (real camera only) | | `set fps ` | Capture interval rate (images/second) | | `set motorctl ` | Forward a raw command to the motor controller (e.g. `set motorctl MOVE Y 20000`) | | `exit` | Quit (Ctrl-D also works) | ## 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 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 ` | init | set the production move speed (VMAX, counts/s) | | `MOVE ,` | each capture point | drive both axes to absolute counts (combined form) | | `MOVE Y\|P ` | — | 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).