# Fire Gimbal Control

Real-time control software for an automated **fire-watch gimbal**. A C++17 program runs on a tower-mounted
PC, rotates a pan gimbal carrying up to four industrial cameras, captures a 360° panorama for wildfire
detection, compresses each frame to JPEG XL, and reports to a ground station over MQTT.

A single codebase serves all fire-watch towers (FWT); the tower identity comes from `config.ini`.

The code is organized around an **SDK-independent core** (`fgc_core`: config, logging, capture scheduler,
parsers) and **swappable I/O implementations** behind three interfaces — motor controller, control channel,
and camera source — each with a real and a mock/null version. This makes the system deployable on any machine
and runnable **without hardware or an MQTT broker** for development.

State is held in memory (mutex-guarded), configuration is read once from an INI file, images are written to
the filesystem as `.jxl`, and telemetry flows over MQTT. There is no database.

## Architecture at a glance

```
            ┌──────────────────────── fgc_core (no SDKs) ─────────────────────────┐
            │ Config · Logger · CaptureScheduler · TelemetryParser · CommandParser │
            └───────▲─────────────────▲──────────────────▲────────────────────────┘
                    │ IMotorController │ IControlChannel   │ ICameraSource
        ┌───────────┴──────┐  ┌────────┴─────────┐  ┌──────┴────────────────┐
   real │ SerialMotorCtrl  │  │ MqttControlChannel│  │ VimbaCameraSource     │  (WITH_VIMBA/WITH_MQTT)
   mock │ MockMotorCtrl    │  │ NullControlChannel│  │ MockCameraSource      │
        └──────────────────┘  └──────────────────┘  └──────────┬────────────┘
                                                               frames
                                                                ▼
                                                        ImagePipeline → .jxl + CamEvent
```

See [docs/architecture.md](docs/architecture.md) for the full design.

## Build

```bash
cmake -B build                 # configure (needs the Vimba X SDK + Paho for a full build)
cmake --build build            # -> build/fire_gimbal_control

# Development build with NO proprietary SDKs (mocks only):
cmake -B build -DWITH_VIMBA=OFF -DWITH_MQTT=OFF
cmake --build build
```

Dependencies and the Vimba X SDK setup are in [docs/build-and-setup.md](docs/build-and-setup.md).

## Run

```bash
# Real deployment (needs cameras, motor MCU on the configured serial port, MQTT broker):
scripts/run.sh --start
scripts/run.sh --init --start          # find endstops first

# Development, no hardware or broker:
scripts/run.sh --mock-serial --mock-camera --no-mqtt --start --log-level debug

# Same, but with the full-screen terminal dashboard:
scripts/run.sh --tui --mock-serial --mock-camera --no-mqtt
```

`run.sh` resolves the binary relative to the repo, so it works from any checkout; extra args are forwarded to
`fire_gimbal_control`. On launch the program starts the motor controller, opens the camera(s), starts the
image pipeline, connects the control channel (continuing in **degraded mode** if the broker is unreachable),
then enters a control loop. Without `--start` it idles until you type `start`. Stop with `exit` or Ctrl-D.

Config is resolved from `--config <path>`, then `$FGC_CONFIG`, `./config.ini`, the executable's directory, and
`$XDG_CONFIG_HOME/fire_gimbal_control/config.ini`. Copy the template to get started:

```bash
cp config/config.example.ini config.ini   # then edit
```

### Command-line options

| Flag | Effect |
|------|--------|
| `-c, --config <path>` | Use a specific `config.ini` (otherwise the search path above). |
| `-i, --init` | Run the endstop-finding init sequence before entering the loop. |
| `-s, --start` | Begin capturing immediately on launch (else wait for the `start` command). |
| `-d, --demo` | Demo mode: copy a placeholder `.jxl` instead of encoding live frames. |
| `--no-mqtt` | Disable MQTT; use a null control channel (overrides `enable_mqtt`). |
| `--mock-camera` | Use a simulated camera — no Vimba hardware needed. |
| `--mock-serial` | Use a simulated motor controller — no serial hardware needed. |
| `--tui` / `--no-tui` | Show the full-screen terminal dashboard / force the headless console (overrides `[UI] enable_tui`). |
| `--log-level <lvl>` | `trace`, `debug`, `info`, `warn`, `error`, or `off`. |
| `--trace <cats>` | Verbatim wire tracing; comma list of `serial,mqtt,camera,control,all,none`. |
| `-h, --help` | Print options and exit. |

CLI flags override the matching `[Features]` / `[Logging]` keys in `config.ini`.

### Interactive commands

While running, the program reads commands from stdin (one per line):

| Command | Action |
|---------|--------|
| `start` / `stop` | Start / stop the capture cycle (the gimbal stays powered and homed). |
| `gimbal move <yaw>,<pitch>` | Aim at an absolute heading/elevation in **degrees** (calibrated). |
| `gimbal home [y\|p]` | Run the endstop-finding home sequence. |
| `gimbal dump` | Request a firmware state dump (shown in the gimbal `g` view). |
| `gimbal diag [y\|p\|all]` | Run the firmware motor self-test (results in the activity strip + `logs/`). |
| `gimbal calib` | IMU-referenced steps↔degrees calibration (auto-homes if needed; needs the IMU; applied to the session). |
| `gimbal …` | Other motor controls — `steps`/`nudge`/`stop`/`speed`/`reset`/`enable`/`disable`/`setpos`/`status`. |
| `gimbal raw "<command>"` | **Low-level:** send the quoted text to the firmware verbatim (newline added). Bypasses wrappers/unit conversion — use with care. |
| `debug` | Toggle debug-level logging on/off. |
| `set fps <n>` | Capture rate, in images per second. |
| `set camera fps <n>` | Camera sensor frame rate. |
| `set camera jxlq <d>` | JPEG XL quality as butteraugli **distance** (lower = higher quality / larger files). |
| `set camera jxle <n>` | JPEG XL encoder effort (higher = slower, smaller). |
| `set camera display <0\|1>` | Toggle the local preview window. |
| `trace <cat> [on\|off]` | Enable/disable a wire-trace category live (e.g. `trace serial`, `trace mqtt off`). |
| `trace all` / `trace off` | Enable every category / silence all of them. |
| `help [topic]` | List commands / expand one section. |
| `exit` | Stop everything and quit (Ctrl-D also works). |

The **Xsens MTi IMU** (set `[Features] enable_imu` + `[IMU] device`) drives the Sensors panel's live
roll/pitch/yaw and powers `gimbal calib`. In the TUI, `g` and `i` open full-screen **gimbal** and
**IMU** views, and an **activity strip** below the log shows the running calibration/diagnostics/scan
plus the last result. See [docs/configuration.md](docs/configuration.md).

A typical first bring-up: `scripts/run.sh --init` to home the gimbal, then type `start` once you've confirmed
telemetry looks sane; adjust `set fps` / `set camera jxlq` live as needed. See
[docs/configuration.md](docs/configuration.md) for the full `config.ini` reference.

### Verbosity & wire tracing

Two independent knobs control output:

- **Log level** (`--log-level`, the `debug` console toggle, `[Logging] level`) gates ordinary
  messages: `info` (default) shows discrete events only; `debug` adds per-image saves; `trace` is
  the most verbose. At `info` an active capture is nearly silent.
- **Wire-trace categories** (`--trace`, the `trace` console command, `[Logging] trace`) log
  **every** message exchanged with a subsystem, verbatim, and are **independent of the level** —
  `--trace serial` shows all firmware serial traffic even at `info`. Categories are **off by
  default** (the camera one is high-rate). Only `--log-level off` silences them.

Lines are tagged by category with a `TX`/`RX` direction, so a single subsystem is easy to follow
or `grep`:

```
14:02:01.234 [SERIAL]  TX MOVE -90,0
14:02:01.250 [SERIAL]  RX ST Y:A,982,969,80084000,0,8,8,Se P:A,...
14:02:02.000 [MQTT]    PUB GGS/FWT/Tower/StatusCode 0
14:02:02.500 [CAMERA]  TX trigger cam0
14:02:02.910 [CAMERA]  RX frame cam0 1936x1216 7064576B
14:02:02.000 [CONTROL] sweep -> grid yaw=-90 pitch=0
```

Example — watch only firmware serial traffic on the device:
`./deploy.sh --run` with `RUN_ARGS="--start --trace serial"`.

## Test

```bash
cmake -B build -DWITH_VIMBA=OFF -DWITH_MQTT=OFF -DBUILD_TESTING=ON
cmake --build build
ctest --test-dir build --output-on-failure
```

The unit tests cover the core (config, paths, telemetry/command parsers, capture scheduler) and need no SDKs.

## Deploy to the LattePanda

The LattePanda is the machine wired to the gimbal and camera, so it builds and runs there. `deploy.sh`
rsyncs the codebase over ssh and runs the cmake build remotely (mirrors `../firmware/deploy.sh`).

```bash
cp .deploy.env.example .deploy.env   # then edit REMOTE_HOST / REMOTE_DIR
./deploy.sh --check-deps             # verify the device has OpenCV/Boost/libjxl
./deploy.sh                          # rsync + remote configure + build
./deploy.sh --run                    # ... then run it over ssh (RUN_ARGS, ctrl-c to stop)
./deploy.sh --clean --run            # wipe remote build dir first, then build + run
```

`config.ini` and `images/` are device-local and never overwritten by a deploy; the first deploy seeds
`config.ini` from `config/config.example.ini` if the device has none. One-time device prerequisites
(Ubuntu): `sudo apt-get install -y build-essential cmake git libopencv-dev libboost-program-options-dev
libjxl-dev`, plus the Vimba X SDK under `/opt/VimbaX` for `-DWITH_VIMBA=ON`.

## Documentation

| Document | Contents |
|----------|----------|
| [docs/architecture.md](docs/architecture.md) | Components, interfaces, threading, data flow, capture state machine |
| [docs/build-and-setup.md](docs/build-and-setup.md) | CMake, dependencies, options, Vimba X / Paho, host setup |
| [docs/deployment.md](docs/deployment.md) | Step-by-step deploy to the gimbal LattePanda (systemd) |
| [docs/configuration.md](docs/configuration.md) | `config.ini` keys, CLI flags, console commands |
| [docs/mqtt-api.md](docs/mqtt-api.md) | MQTT topic catalog and payloads |
| [docs/test-command.md](docs/test-command.md) | The `test` hardware self-test suite: subsystems, profiles, reports, baselines |
| [docs/modules-reference.md](docs/modules-reference.md) | Per-file reference and data structures |
| [docs/known-issues.md](docs/known-issues.md) | Status of past issues + remaining caveats |
| [docs/roadmap.md](docs/roadmap.md) | Forward-looking project roadmap |
| [docs/test-roadmap.md](docs/test-roadmap.md) | Planned tests blocked on not-yet-integrated components |

## Repository layout

```
CMakeLists.txt          Build (options: WITH_VIMBA, WITH_MQTT, BUILD_TESTING)
cmake/                  FindVmb.cmake (Vimba X), Paho.cmake (FetchContent)
config/                 config.example.ini, scan.csv (real config.ini is gitignored)
include/fgc/            Public headers: interfaces, Config, Logger, scheduler, impls
  mock/                 Mock/null implementations
src/
  core/                 Config, Logger, Paths, parsers, Geometry, ScanGrid, CaptureScheduler, Application
  serial/              SerialMotorController
  mqtt/                MqttControlChannel
  camera/              VimbaCameraSource, ImagePipeline, JpegXlEncoder
  main.cpp             Thin entry point (CLI -> Application)
tests/                  doctest unit tests
scripts/                run.sh + systemd unit template
ini.c / ini.h           Bundled inih INI parser
```

## Ownership

Internal tooling for the GGS fire-watch tower network. No license file is present in the repository.
