Add LattePanda remote deploy workflow; fix MQTT build after move
This commit is contained in:
parent
f1005f3e31
commit
00d3454bb6
|
|
@ -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"
|
||||||
|
|
@ -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
|
||||||
35
Makefile
35
Makefile
|
|
@ -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)
|
|
||||||
61
README.md
61
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
|
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 <path>`, then `$FGC_CONFIG`, `./config.ini`, the executable's directory, and
|
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:
|
`$XDG_CONFIG_HOME/fire_gimbal_control/config.ini`. Copy the template to get started:
|
||||||
|
|
||||||
|
|
@ -64,7 +69,42 @@ Config is resolved from `--config <path>`, then `$FGC_CONFIG`, `./config.ini`, t
|
||||||
cp config/config.example.ini config.ini # then edit
|
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 <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. |
|
||||||
|
| `--log-level <lvl>` | `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 <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. |
|
||||||
|
| `set motorctl <raw>` | 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
|
## 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.
|
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
|
## Documentation
|
||||||
|
|
||||||
| Document | Contents |
|
| Document | Contents |
|
||||||
|----------|----------|
|
|----------|----------|
|
||||||
| [docs/architecture.md](docs/architecture.md) | Components, interfaces, threading, data flow, capture state machine |
|
| [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/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/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/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 |
|
| [docs/modules-reference.md](docs/modules-reference.md) | Per-file reference and data structures |
|
||||||
|
|
|
||||||
|
|
@ -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
|
# Rationale (security): we build Paho from its OFFICIAL upstream Git repo rather
|
||||||
# rather than from the AUR. This removes the anonymous AUR packager from the
|
# than from the AUR. This removes the anonymous AUR packager from the trust
|
||||||
# trust chain - you trust only the Eclipse project source.
|
# 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
|
# HARDENING (recommended): the tag below is a mutable ref. For maximum integrity
|
||||||
# integrity, pin PAHO_C_TAG / PAHO_CPP_TAG to a full commit SHA (immutable),
|
# pin PAHO_CPP_TAG to a full commit SHA (immutable), or switch to URL + URL_HASH
|
||||||
# or switch to URL + URL_HASH (SHA256) of the signed release tarballs and
|
# (SHA256) of the signed release tarball and verify the hash out-of-band. Bump
|
||||||
# verify the hash out-of-band. Bump the versions deliberately, not silently.
|
# the version deliberately, not silently.
|
||||||
#
|
#
|
||||||
# NOTE: these exact tag strings and the cross-build wiring below must be
|
# Design note: we let paho.mqtt.cpp build its own bundled C submodule
|
||||||
# verified on a machine with network access + cmake; they could not be built
|
# (PAHO_WITH_MQTT_C=ON) rather than fetching paho.mqtt.c separately. The
|
||||||
# offline in the environment where this file was authored.
|
# 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)
|
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_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
set(PAHO_BUILD_STATIC ON 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_WITH_SSL OFF CACHE BOOL "" FORCE) # current code uses plain tcp://
|
||||||
set(PAHO_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
|
set(PAHO_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
|
||||||
set(PAHO_BUILD_SAMPLES OFF CACHE BOOL "" FORCE)
|
set(PAHO_BUILD_SAMPLES OFF CACHE BOOL "" FORCE)
|
||||||
set(PAHO_BUILD_DOCUMENTATION 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_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
set(PAHO_BUILD_CPP_STATIC ON 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
|
FetchContent_Declare(paho_mqtt_cpp
|
||||||
GIT_REPOSITORY https://github.com/eclipse-paho/paho.mqtt.cpp.git
|
GIT_REPOSITORY https://github.com/eclipse-paho/paho.mqtt.cpp.git
|
||||||
GIT_TAG ${PAHO_CPP_TAG}
|
GIT_TAG ${PAHO_CPP_TAG}
|
||||||
GIT_SHALLOW TRUE
|
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
|
# Provide a stable alias the top-level CMakeLists links against, regardless of
|
||||||
# the exact target name the upstream version exports.
|
# the exact target name the upstream version exports.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
<https://www.alliedvision.com/en/products/software/vimba-x-sdk/>.
|
||||||
|
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 <repo-url> 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/<timestamp>.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 <broker> -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` |
|
||||||
|
|
@ -30,9 +30,12 @@ doctest unit-test suite (`ctest`).
|
||||||
|
|
||||||
## Verification caveats
|
## Verification caveats
|
||||||
|
|
||||||
- **Real Serial/MQTT/Vimba wrappers** were verified by code review, not compilation, on the development
|
- **Real MQTT wrapper** now compiles and links: `cmake -B build -DWITH_MQTT=ON` fetches and builds Paho
|
||||||
machine (no Paho/Vimba installed there). They are faithful adaptations of the original code. First compile
|
(C++ wrapper plus its bundled C library) from official Eclipse upstream and links `MqttControlChannel`
|
||||||
happens on a machine with the SDKs, or via `WITH_MQTT=ON` (Paho is fetched) + the Vimba X SDK.
|
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.
|
- **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
|
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.
|
`WITH_VIMBA=ON WITH_MQTT=ON` build on a tower PC.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue