From 00d3454bb6d0fc7dd7e297530d9826127c8b1694 Mon Sep 17 00:00:00 2001 From: pgdalmeida Date: Mon, 22 Jun 2026 21:49:33 +0200 Subject: [PATCH] Add LattePanda remote deploy workflow; fix MQTT build after move --- .deploy.env | 5 ++ .deploy.env.example | 15 ++++ Makefile | 35 -------- README.md | 61 ++++++++++++- cmake/Paho.cmake | 57 +++++++----- deploy.sh | 203 ++++++++++++++++++++++++++++++++++++++++++ docs/deployment.md | 204 +++++++++++++++++++++++++++++++++++++++++++ docs/known-issues.md | 9 +- 8 files changed, 527 insertions(+), 62 deletions(-) create mode 100644 .deploy.env create mode 100644 .deploy.env.example delete mode 100644 Makefile create mode 100755 deploy.sh create mode 100644 docs/deployment.md diff --git a/.deploy.env b/.deploy.env new file mode 100644 index 0000000..82bf932 --- /dev/null +++ b/.deploy.env @@ -0,0 +1,5 @@ +# Local deploy config for the LattePanda. Not rsync'd to the device. +REMOTE_HOST=ggs@10.11.12.111 +REMOTE_DIR=/home/ggs/projects/fwt_2a/software +CMAKE_ARGS="-DWITH_MQTT=ON -DWITH_VIMBA=ON" +RUN_ARGS="--start" diff --git a/.deploy.env.example b/.deploy.env.example new file mode 100644 index 0000000..70140a1 --- /dev/null +++ b/.deploy.env.example @@ -0,0 +1,15 @@ +# Copy to .deploy.env and edit for your LattePanda. .deploy.env is git-ignored +# and is not rsync'd to the device. +REMOTE_HOST=ggs@10.11.12.111 # ssh target (host or user@host) +REMOTE_DIR=/home/ggs/projects/fwt_2a/software # where the project is rsync'd on the LattePanda + +# CMake configure flags. The LattePanda has the gimbal + camera, so both real +# backends are built in. Drop -DWITH_VIMBA=ON to build mock-camera only. +CMAKE_ARGS="-DWITH_MQTT=ON -DWITH_VIMBA=ON" + +# Args passed to the binary by `./deploy.sh --run`. +RUN_ARGS="--start" + +# Optional overrides: +# BUILD_JOBS=4 # parallel build jobs (default: remote nproc) +# REMOTE_CMAKE=/usr/bin/cmake # if cmake isn't on the non-interactive ssh PATH diff --git a/Makefile b/Makefile deleted file mode 100644 index c85dcc7..0000000 --- a/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -CXX := g++ -CC := gcc -CXXFLAGS := -std=c++17 -g -Wall -I. -CFLAGS := -g -Wall -I. -LDFLAGS := -lpaho-mqttpp3 -lpaho-mqtt3a \ - -lopencv_core -lopencv_highgui \ - -ljxl -ljxl_threads \ - -lboost_program_options \ - -lVmbC -lVmbCPP - -TARGET := bin/Fire_Gimbal_Control.out -OBJDIR := obj - -CXX_SRCS := main.cpp MQTT.cpp Camera.cpp -C_SRCS := ini.c - -CXX_OBJS := $(CXX_SRCS:%.cpp=$(OBJDIR)/%.o) -C_OBJS := $(C_SRCS:%.c=$(OBJDIR)/%.o) -OBJS := $(CXX_OBJS) $(C_OBJS) - -.PHONY: all clean - -all: $(TARGET) - -$(TARGET): $(OBJS) - $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) - -$(OBJDIR)/%.o: %.cpp - $(CXX) $(CXXFLAGS) -c -o $@ $< - -$(OBJDIR)/%.o: %.c - $(CC) $(CFLAGS) -c -o $@ $< - -clean: - rm -f $(OBJS) $(TARGET) diff --git a/README.md b/README.md index cc55b21..1d000a4 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,11 @@ scripts/run.sh --init --start # find endstops first scripts/run.sh --mock-serial --mock-camera --no-mqtt --start --log-level debug ``` +`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 `, then `$FGC_CONFIG`, `./config.ini`, the executable's directory, and `$XDG_CONFIG_HOME/fire_gimbal_control/config.ini`. Copy the template to get started: @@ -64,7 +69,42 @@ Config is resolved from `--config `, then `$FGC_CONFIG`, `./config.ini`, t cp config/config.example.ini config.ini # then edit ``` -Type `exit` (or Ctrl-D) to stop. See [docs/configuration.md](docs/configuration.md) for all options. +### Command-line options + +| Flag | Effect | +|------|--------| +| `-c, --config ` | 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. | +| `--log-level ` | `trace`, `debug`, `info`, `warn`, `error`, or `off`. | +| `-h, --help` | Print options and exit. | + +CLI flags override the matching `[Features]` keys in `config.ini`. + +### Interactive commands + +While running, the program reads commands from stdin (one per line): + +| Command | Action | +|---------|--------| +| `start` | Start the capture cycle. | +| `stop` | Stop capturing (the gimbal stays powered and homed). | +| `debug` | Toggle debug-level logging on/off. | +| `set fps ` | Capture rate, in images per second. | +| `set camera fps ` | Camera sensor frame rate. | +| `set camera jxlq ` | JPEG XL quality as butteraugli **distance** (lower = higher quality / larger files). | +| `set camera jxle ` | JPEG XL encoder effort (higher = slower, smaller). | +| `set camera display <0\|1>` | Toggle the local preview window. | +| `set motorctl ` | Send a raw command string straight to the motor controller. | +| `exit` | Stop everything and quit (Ctrl-D also works). | + +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. ## Test @@ -76,12 +116,31 @@ 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/modules-reference.md](docs/modules-reference.md) | Per-file reference and data structures | diff --git a/cmake/Paho.cmake b/cmake/Paho.cmake index 2d89538..bded371 100644 --- a/cmake/Paho.cmake +++ b/cmake/Paho.cmake @@ -1,48 +1,59 @@ -# Paho.cmake - provide Eclipse Paho MQTT C and C++ via FetchContent. +# Paho.cmake - provide Eclipse Paho MQTT C++ (and its bundled C library) via +# FetchContent. # -# Rationale (security): we build Paho from its OFFICIAL upstream Git repos -# rather than from the AUR. This removes the anonymous AUR packager from the -# trust chain - you trust only the Eclipse project source. +# Rationale (security): we build Paho from its OFFICIAL upstream Git repo rather +# than from the AUR. This removes the anonymous AUR packager from the trust +# chain - you trust only the Eclipse project source. The C library is pulled in +# as paho.mqtt.cpp's own pinned git submodule (PAHO_WITH_MQTT_C=ON), which is +# likewise official Eclipse source. # -# HARDENING (recommended): the tags below are mutable refs. For maximum -# integrity, pin PAHO_C_TAG / PAHO_CPP_TAG to a full commit SHA (immutable), -# or switch to URL + URL_HASH (SHA256) of the signed release tarballs and -# verify the hash out-of-band. Bump the versions deliberately, not silently. +# HARDENING (recommended): the tag below is a mutable ref. For maximum integrity +# pin PAHO_CPP_TAG to a full commit SHA (immutable), or switch to URL + URL_HASH +# (SHA256) of the signed release tarball and verify the hash out-of-band. Bump +# the version deliberately, not silently. # -# NOTE: these exact tag strings and the cross-build wiring below must be -# verified on a machine with network access + cmake; they could not be built -# offline in the environment where this file was authored. +# Design note: we let paho.mqtt.cpp build its own bundled C submodule +# (PAHO_WITH_MQTT_C=ON) rather than fetching paho.mqtt.c separately. The +# separate-fetch approach forces the C++ wrapper down its find_package() path, +# whose unconditional build-tree export() then drags the in-tree C target into +# an export set it does not belong to and fails at generation time. The bundled +# path keeps the C target inside the C++ wrapper's own export set, sidestepping +# that conflict entirely. include(FetchContent) -set(PAHO_C_TAG "v1.3.14" CACHE STRING "Eclipse paho.mqtt.c git tag/commit") -set(PAHO_CPP_TAG "v1.4.1" CACHE STRING "Eclipse paho.mqtt.cpp git tag/commit") +set(PAHO_CPP_TAG "v1.4.1" CACHE STRING "Eclipse paho.mqtt.cpp git tag/commit") -# ---- Paho MQTT C (dependency of the C++ wrapper) ---- +# ---- Build options for the bundled Paho C and the C++ wrapper ---- +set(PAHO_WITH_MQTT_C ON CACHE BOOL "" FORCE) # build C from cpp's submodule set(PAHO_BUILD_SHARED OFF CACHE BOOL "" FORCE) set(PAHO_BUILD_STATIC ON CACHE BOOL "" FORCE) set(PAHO_WITH_SSL OFF CACHE BOOL "" FORCE) # current code uses plain tcp:// set(PAHO_ENABLE_TESTING OFF CACHE BOOL "" FORCE) set(PAHO_BUILD_SAMPLES OFF CACHE BOOL "" FORCE) set(PAHO_BUILD_DOCUMENTATION OFF CACHE BOOL "" FORCE) - -FetchContent_Declare(paho_mqtt_c - GIT_REPOSITORY https://github.com/eclipse-paho/paho.mqtt.c.git - GIT_TAG ${PAHO_C_TAG} - GIT_SHALLOW TRUE -) - -# ---- Paho MQTT C++ ---- set(PAHO_BUILD_CPP_SHARED OFF CACHE BOOL "" FORCE) set(PAHO_BUILD_CPP_STATIC ON CACHE BOOL "" FORCE) +# Toolchain-compatibility shims for these pinned upstream releases: +# * The bundled paho.mqtt.c declares cmake_minimum_required below the floor +# that current CMake (>= 4.0) still accepts; allow it to configure anyway. +# * paho.mqtt.c has `typedef unsigned int bool;`, which is illegal under C23 +# (the default on GCC >= 14 / Clang >= 18, where `bool` is a keyword). Pin +# the C dialect to C17 for the duration of the fetch so it compiles. +set(_fgc_c_std_save "${CMAKE_C_STANDARD}") +set(CMAKE_C_STANDARD 17) +set(CMAKE_POLICY_VERSION_MINIMUM 3.5) + FetchContent_Declare(paho_mqtt_cpp GIT_REPOSITORY https://github.com/eclipse-paho/paho.mqtt.cpp.git GIT_TAG ${PAHO_CPP_TAG} GIT_SHALLOW TRUE ) +FetchContent_MakeAvailable(paho_mqtt_cpp) -FetchContent_MakeAvailable(paho_mqtt_c paho_mqtt_cpp) +unset(CMAKE_POLICY_VERSION_MINIMUM) +set(CMAKE_C_STANDARD "${_fgc_c_std_save}") # Provide a stable alias the top-level CMakeLists links against, regardless of # the exact target name the upstream version exports. diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..42b97ff --- /dev/null +++ b/deploy.sh @@ -0,0 +1,203 @@ +#!/usr/bin/env bash +# +# deploy.sh — sync this project to the LattePanda and build/run it there. +# The LattePanda is the machine wired to the gimbal + camera, so the actual +# cmake build (and the live run) happen remotely over ssh. We edit locally and +# mirror. Mirrors ../firmware/deploy.sh, but for the CMake/C++ side. +# +# Usage: +# ./deploy.sh rsync + remote configure + build +# ./deploy.sh --run ... then run the app over ssh (RUN_ARGS, ctrl-c to stop) +# ./deploy.sh --clean wipe the remote build dir first (fresh configure) +# ./deploy.sh --check-deps only check the remote build dependencies +# ./deploy.sh --run --force skip the placeholder-config guard before running +# +# Flags combine, e.g. ./deploy.sh --clean --run +# +# Config comes from .deploy.env (see .deploy.env.example). +# +set -euo pipefail + +cd "$(dirname "$0")" + +ENV_FILE=".deploy.env" +if [[ ! -f "$ENV_FILE" ]]; then + echo "error: $ENV_FILE not found (copy .deploy.env.example and edit it)" >&2 + exit 1 +fi +# shellcheck disable=SC1090 +source "$ENV_FILE" + +: "${REMOTE_HOST:?set REMOTE_HOST in .deploy.env}" +: "${REMOTE_DIR:?set REMOTE_DIR in .deploy.env}" +CMAKE_ARGS="${CMAKE_ARGS:--DWITH_MQTT=ON -DWITH_VIMBA=ON}" +BUILD_JOBS="${BUILD_JOBS:-}" +RUN_ARGS="${RUN_ARGS:---start}" + +CLEAN=0 +RUN=0 +CHECK_DEPS=0 +FORCE=0 +for arg in "$@"; do + case "$arg" in + --clean) CLEAN=1 ;; + --run|--start) RUN=1 ;; + --check-deps) CHECK_DEPS=1 ;; + --force) FORCE=1 ;; + *) echo "unknown option: $arg" >&2; exit 1 ;; + esac +done + +# Locate cmake on the remote. Non-interactive ssh shells don't source .bashrc, +# so allow an explicit REMOTE_CMAKE override in .deploy.env; otherwise search +# the usual locations. +RESOLVE_CMAKE=" +CMAKE='${REMOTE_CMAKE:-}' +if [ -z \"\$CMAKE\" ]; then + for c in \"\$(command -v cmake 2>/dev/null)\" /usr/bin/cmake /usr/local/bin/cmake; do + if [ -x \"\$c\" ]; then CMAKE=\"\$c\"; break; fi + done +fi +if [ -z \"\$CMAKE\" ]; then + echo 'error: cmake not found on remote; set REMOTE_CMAKE in .deploy.env' >&2 + exit 127 +fi +" + +# Vimba X needs the GenTL transport-layer dir on GENICAM_GENTL64_PATH at runtime, +# or VmbStartup() fails with "Could not start Vimba X API". The SDK installs that +# via /etc/profile.d, which non-interactive ssh shells don't source — so set it +# ourselves before launching. Optional VIMBA_CTI_PATH override in .deploy.env. +RESOLVE_VIMBA_ENV=" +for s in /etc/profile.d/*Vimba*GenTL*.sh; do + [ -f \"\$s\" ] && . \"\$s\" +done +if [ -z \"\${GENICAM_GENTL64_PATH:-}\" ]; then + for d in '${VIMBA_CTI_PATH:-}' /opt/VimbaX/cti /opt/VimbaX_*/cti; do + if [ -d \"\$d\" ]; then export GENICAM_GENTL64_PATH=\"\$d\"; break; fi + done +fi +" + +# Preflight: the two heavyweight deps that aren't auto-fetched (Paho is). OpenCV +# and Boost.program_options must be present or the configure step fails opaquely. +CHECK_DEPS_CMD=" +miss='' +pkg-config --exists opencv4 2>/dev/null || miss=\"\$miss opencv4\" +pkg-config --exists libjxl libjxl_threads 2>/dev/null || miss=\"\$miss libjxl\" +[ -f /usr/include/boost/version.hpp ] || ls /usr/include/boost*/boost/version.hpp >/dev/null 2>&1 || miss=\"\$miss boost\" +if [ -n \"\$miss\" ]; then + echo \"error: missing remote build deps:\$miss\" >&2 + echo 'install on the LattePanda (Ubuntu):' >&2 + echo ' sudo apt-get install -y build-essential cmake git libopencv-dev libboost-program-options-dev libjxl-dev' >&2 + exit 1 +fi +echo 'remote deps OK (OpenCV, Boost, libjxl; Vimba via cmake/FindVmb; Paho fetched)' +" + +if [[ "$CHECK_DEPS" == "1" ]]; then + echo ">> checking remote build deps on $REMOTE_HOST" + # shellcheck disable=SC2029 + ssh "$REMOTE_HOST" "$CHECK_DEPS_CMD" + exit 0 +fi + +# .git is excluded from rsync, so capture build identity locally and pass it on. +if git rev-parse --git-dir >/dev/null 2>&1; then + GIT_REV="$(git describe --always --dirty 2>/dev/null || echo nogit)" +else + GIT_REV="nogit" +fi +echo ">> build id: $GIT_REV" + +echo ">> ensuring remote dir exists" +ssh "$REMOTE_HOST" "mkdir -p '$REMOTE_DIR'" + +# config.ini and images/ are device-local (runtime config + captured output) and +# are deliberately NOT mirrored, so a deploy never clobbers them. build/ is +# machine-specific and regenerated remotely. +echo ">> syncing to $REMOTE_HOST:$REMOTE_DIR" +rsync -avz --delete \ + --exclude 'build/' \ + --exclude '.git' \ + --exclude '.venv' \ + --exclude '.vscode' \ + --exclude '__pycache__' \ + --exclude '.deploy.env' \ + --exclude 'config.ini' \ + --exclude 'images/' \ + ./ "$REMOTE_HOST:$REMOTE_DIR/" + +# Seed a config.ini on the device from the example if it doesn't have one yet, +# so the first run finds a config. Never overwrites an existing device config. +echo ">> ensuring remote config.ini exists" +ssh "$REMOTE_HOST" "cd '$REMOTE_DIR' && \ + if [ ! -f config.ini ]; then \ + cp config/config.example.ini config.ini && \ + echo ' seeded config.ini from config/config.example.ini — edit it on the device'; \ + else echo ' keeping existing device config.ini'; fi" + +if [[ "$CLEAN" == "1" ]]; then + echo ">> wiping remote build dir" + ssh "$REMOTE_HOST" "rm -rf '$REMOTE_DIR/build'" +fi + +echo ">> remote preflight + configure + build ($CMAKE_ARGS)" +JOBS_FLAG="" +[[ -n "$BUILD_JOBS" ]] && JOBS_FLAG="-j$BUILD_JOBS" || JOBS_FLAG="-j\$(nproc)" +# shellcheck disable=SC2029 +ssh "$REMOTE_HOST" "$RESOLVE_CMAKE $CHECK_DEPS_CMD cd '$REMOTE_DIR' && \ + GIT_REV='$GIT_REV' \"\$CMAKE\" -S . -B build $CMAKE_ARGS && \ + \"\$CMAKE\" --build build $JOBS_FLAG" +echo ">> build complete: $REMOTE_DIR/build/fire_gimbal_control" + +if [[ "$RUN" == "1" ]]; then + # Guard: a freshly seeded config.ini is the example template, with placeholders + # that connect nowhere and silently drop to degraded mode. Refuse to launch on + # placeholders (unless --force) so it's an explicit failure, not a confusing + # half-run. Only enforce placeholders for subsystems this run actually uses, + # mirroring Application.cpp's resolution: MQTT off if --no-mqtt, a WITH_MQTT=OFF + # build, OR [Features] enable_mqtt is falsey; camera mocked if --mock-camera OR + # [Features] mock_camera true / enable_camera false. CLI/build signals are + # computed here; the config's own flags are parsed remotely where the file is. + if [[ "$FORCE" != "1" ]]; then + MQTT_OFF_CLI=0; CAM_MOCK_CLI=0 + [[ "$RUN_ARGS" == *--no-mqtt* || "$CMAKE_ARGS" == *WITH_MQTT=OFF* ]] && MQTT_OFF_CLI=1 + [[ "$RUN_ARGS" == *--mock-camera* ]] && CAM_MOCK_CLI=1 + echo ">> checking device config.ini for unedited placeholders" + # shellcheck disable=SC2029 + if ! ssh "$REMOTE_HOST" " + cfg='$REMOTE_DIR/config.ini' + [ -f \"\$cfg\" ] || { echo \"error: \$cfg missing on device\" >&2; exit 2; } + # Read an INI value (last assignment wins), strip inline ';' comment + ws, lowercase. + ini_val() { grep -iE \"^[[:space:]]*\$1[[:space:]]*=\" \"\$cfg\" | tail -1 \ + | sed -E 's/^[^=]*=[[:space:]]*//; s/[[:space:]]*(;.*)?\$//' | tr 'A-Z' 'a-z'; } + is_false() { case \"\$(ini_val \"\$1\")\" in 0|false|no|off) return 0;; *) return 1;; esac; } + is_true() { case \"\$(ini_val \"\$1\")\" in 1|true|yes|on) return 0;; *) return 1;; esac; } + + mqtt_on=1; [ '$MQTT_OFF_CLI' = 1 ] && mqtt_on=0; is_false enable_mqtt && mqtt_on=0 + cam_real=1; [ '$CAM_MOCK_CLI' = 1 ] && cam_real=0 + is_true mock_camera && cam_real=0; is_false enable_camera && cam_real=0 + + pat='ExampleTower' # tower identity: always + [ \"\$mqtt_on\" = 1 ] && pat=\"\$pat|CHANGE_ME\" # MQTT creds: only if MQTT on + [ \"\$cam_real\" = 1 ] && pat=\"\$pat|DEV_0000000000\" # camera id: only if real cam + + if grep -Eq \"\$pat\" \"\$cfg\"; then + echo 'error: device config.ini still has example placeholders:' >&2 + grep -En \"\$pat\" \"\$cfg\" | sed 's/^/ /' >&2 + echo 'edit them on the device, or pass --force to run anyway.' >&2 + exit 3 + fi + "; then + echo ">> aborting run; fix the config or re-run with --force" >&2 + exit 1 + fi + fi + + echo ">> running on $REMOTE_HOST (ctrl-c to stop)" + # -t for a real tty so the capture loop's logs stream and ctrl-c propagates. + # cwd = REMOTE_DIR so ./config.ini and the output dir resolve. + # shellcheck disable=SC2029 + ssh -t "$REMOTE_HOST" "$RESOLVE_VIMBA_ENV cd '$REMOTE_DIR' && exec ./build/fire_gimbal_control $RUN_ARGS" +fi diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..6543724 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,204 @@ +# Deploying to the Gimbal LattePanda + +Step-by-step guide to deploy `fire_gimbal_control` on a tower's LattePanda Sigma (Linux x86-64). Like the +firmware workflow, this is a repeatable **build → install → configure → enable → verify** cycle; the +[Update / redeploy](#9-update--redeploy) section at the end is the short loop you run for every new version. + +> Assumes a Debian/Ubuntu-based Linux on the LattePanda. For Arch/Manjaro swap `apt` for `pacman` +> (see [build-and-setup.md](build-and-setup.md)). Run as a normal user with `sudo` rights; the service runs +> under a dedicated `ggs` user. + +## 0. Prerequisites (one-time per device) + +Install the toolchain and system libraries: + +```bash +sudo apt update +sudo apt install -y git cmake build-essential \ + libopencv-dev libjxl-dev libboost-all-dev +``` + +Install the **Allied Vision Vimba X SDK** (proprietary, needed for the cameras): + +1. Download Vimba X for Linux x86-64 from + . +2. Unpack to a fixed location, e.g. `/opt/VimbaX_2024-1`. +3. Run the SDK's transport-layer installer so cameras are discoverable, e.g.: + ```bash + sudo /opt/VimbaX_2024-1/cti/Install_GenTL_Path.sh + ``` +4. Make its runtime libraries findable at run time: + ```bash + echo /opt/VimbaX_2024-1/api/lib | sudo tee /etc/ld.so.conf.d/vimbax.conf + sudo ldconfig + ``` + +Ensure an **MQTT broker** is reachable from the tower (the ground-station/ZKMS broker, or a local Mosquitto +for bring-up). + +## 1. Get the code + +```bash +sudo mkdir -p /opt/src && sudo chown "$USER" /opt/src +cd /opt/src +git clone fire_gimbal_control_src +cd fire_gimbal_control_src +# (or: git pull, for an update) +``` + +## 2. Build (full, with cameras + MQTT) + +```bash +cmake -B build -DCMAKE_BUILD_TYPE=Release \ + -DWITH_VIMBA=ON -DWITH_MQTT=ON \ + -DVMB_HOME=/opt/VimbaX_2024-1 +cmake --build build -j"$(nproc)" +``` + +- `WITH_MQTT=ON` fetches + builds Eclipse Paho from source on first configure (needs network). +- If a camera build isn't needed yet, use `-DWITH_VIMBA=OFF` for a mock binary to test the pipeline. +- Smoke-test the core before installing: `ctest --test-dir build` (after adding `-DBUILD_TESTING=ON`). + +## 3. Install to a fixed location + +```bash +sudo useradd -r -s /usr/sbin/nologin ggs 2>/dev/null || true +sudo mkdir -p /opt/fire_gimbal_control +sudo cp build/fire_gimbal_control /opt/fire_gimbal_control/ +# demo-mode placeholder image (only needed if you ever run --demo) +sudo cp bin/x64/Release/test_smoke.jxl /opt/fire_gimbal_control/ 2>/dev/null || true +``` + +## 4. Configure + +```bash +sudo cp config/config.example.ini /opt/fire_gimbal_control/config.ini +sudo nano /opt/fire_gimbal_control/config.ini +``` + +Set at least: + +```ini +[General] +tower_name = Staeffelsberg ; this tower's name (drives all MQTT topics) +image_interval = 3 + +[Network] +zkms_server_ip = 10.11.12.13 ; the broker address + +[Serial] +device = /dev/ttyACM0 ; the motor-controller MCU +baud = 115200 + +[Camera] +id_Cam1 = DEV_1AB22C0AADED ; or GigE IPs like 192.168.11.101 +``` + +**Credentials** go in the environment, not the config file. Create a root-only env file for the service: + +```bash +sudo install -m 600 /dev/null /etc/fire_gimbal_control/credentials.env 2>/dev/null || \ + { sudo mkdir -p /etc/fire_gimbal_control && sudo install -m 600 /dev/null /etc/fire_gimbal_control/credentials.env; } +printf 'FGC_MQTT_USER=fwt_gimbal\nFGC_MQTT_PW=CHANGE_ME\n' | sudo tee /etc/fire_gimbal_control/credentials.env >/dev/null +sudo chmod 600 /etc/fire_gimbal_control/credentials.env +``` + +Give the `ggs` user access to its files and the image output dir: + +```bash +sudo chown -R ggs:ggs /opt/fire_gimbal_control +``` + +## 5. Hardware access + +**Serial (motor controller):** let the service user open the port. + +```bash +sudo usermod -aG dialout ggs +``` + +For a stable device name across reboots, add a udev rule keyed on the MCU's USB IDs (find them with +`udevadm info -a -n /dev/ttyACM0 | grep -m1 idVendor`) and point `[Serial] device` at the symlink: + +```bash +echo 'SUBSYSTEM=="tty", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="YYYY", SYMLINK+="ttyGimbal"' | \ + sudo tee /etc/udev/rules.d/99-gimbal.rules +sudo udevadm control --reload && sudo udevadm trigger +``` + +**GigE cameras (if used):** put the host NIC on the camera subnet (e.g. `192.168.11.0/24`), enable jumbo +frames, and confirm discovery with the Vimba X `VimbaXViewer`/`ListCameras` tool. + +## 6. First run by hand (verify before enabling the service) + +```bash +cd /opt/fire_gimbal_control +set -a; . /etc/fire_gimbal_control/credentials.env; set +a +./fire_gimbal_control --config ./config.ini --start --log-level debug +``` + +Watch for: `Loaded config`, `Serial controller started`, `Opened camera ...`, `MQTT connected`, then +`Saved .../RGB/.jxl` after the first capture. Type `exit` (or Ctrl-D) to stop. To validate without +cameras first: add `--mock-camera --mock-serial --no-mqtt`. + +## 7. Install the systemd service + +```bash +sudo cp scripts/fire-gimbal-control.service /etc/systemd/system/ +sudoedit /etc/systemd/system/fire-gimbal-control.service +``` + +Confirm/adjust these lines: + +```ini +User=ggs +WorkingDirectory=/opt/fire_gimbal_control +ExecStart=/opt/fire_gimbal_control/fire_gimbal_control --start +Environment=FGC_CONFIG=/opt/fire_gimbal_control/config.ini +EnvironmentFile=/etc/fire_gimbal_control/credentials.env +``` + +(Use `ExecStart=... --init --start` for the first power-on if endstops must be found at boot.) + +## 8. Enable and verify + +```bash +sudo systemctl daemon-reload +sudo systemctl enable --now fire-gimbal-control +systemctl status fire-gimbal-control +journalctl -u fire-gimbal-control -f # live logs +``` + +Confirm end to end: +- Images accumulating under the configured `[Paths] output_dir` (default + `~ggs/.local/share/fire_gimbal_control/images/RGB/`). +- CamEvents on the broker: + `mosquitto_sub -h -t 'GGS/FWT/Staeffelsberg/#' -v`. + +## 9. Update / redeploy + +The short loop for each new version (mirrors the firmware flash cycle): + +```bash +cd /opt/src/fire_gimbal_control_src +git pull +cmake --build build -j"$(nproc)" # reuses the configured build dir +sudo systemctl stop fire-gimbal-control +sudo cp build/fire_gimbal_control /opt/fire_gimbal_control/ +sudo systemctl start fire-gimbal-control +journalctl -u fire-gimbal-control -n 50 -f # confirm healthy +``` + +If `CMakeLists.txt` or options changed, re-run the `cmake -B build ...` configure step from +[section 2](#2-build-full-with-cameras--mqtt) first. To roll back, keep the previous binary +(`fire_gimbal_control.prev`) and swap it back. + +## Troubleshooting + +| Symptom | Check | +|---------|-------| +| `No config.ini found` | `FGC_CONFIG` / `--config` path; service `Environment=` line | +| `Failed to open serial port` | MCU plugged in, `dialout` group, `[Serial] device` path / udev symlink | +| `No camera found` / Vimba startup error | SDK installed, GenTL path set, `ldconfig`, NIC subnet, camera IDs in config | +| `MQTT connect failed` (degraded mode) | broker reachable, credentials env, firewall | +| Runs but no images | capture started (`--start` or `start` command), `output_dir` writable by `ggs` | diff --git a/docs/known-issues.md b/docs/known-issues.md index 85df0f1..f5e333d 100644 --- a/docs/known-issues.md +++ b/docs/known-issues.md @@ -30,9 +30,12 @@ doctest unit-test suite (`ctest`). ## Verification caveats -- **Real Serial/MQTT/Vimba wrappers** were verified by code review, not compilation, on the development - machine (no Paho/Vimba installed there). They are faithful adaptations of the original code. First compile - happens on a machine with the SDKs, or via `WITH_MQTT=ON` (Paho is fetched) + the Vimba X SDK. +- **Real MQTT wrapper** now compiles and links: `cmake -B build -DWITH_MQTT=ON` fetches and builds Paho + (C++ wrapper plus its bundled C library) from official Eclipse upstream and links `MqttControlChannel` + into the binary. Verified on GCC 16 / CMake 4 (see `cmake/Paho.cmake` for the toolchain-compat shims). +- **Real Serial/Vimba wrappers** were verified by code review, not compilation (no Vimba X SDK on the + development machine). They are faithful adaptations of the original code. First Vimba compile happens on + a machine with the SDK via `WITH_VIMBA=ON`. - **Makefile parity** was never re-checked because the original Makefile build also can't run without the SDKs. The Makefile has been removed in favour of CMake; if you need to confirm byte-for-byte behaviour, do a full `WITH_VIMBA=ON WITH_MQTT=ON` build on a tower PC.