From f2c61e030e9549b85621be40dd24e51d33885b57 Mon Sep 17 00:00:00 2001 From: pgdalmeida Date: Tue, 29 Sep 2026 19:50:39 +0200 Subject: [PATCH] Let a yaw-only tower aim with gimbal move, and stop it publishing a made-up pitch --- README.md | 4 +- config/config.example.ini | 2 + config/scan.csv | 3 +- docs/architecture.md | 9 ++- docs/configuration.md | 17 +++-- docs/hardware-config-plan.md | 4 +- docs/modules-reference.md | 8 ++- docs/mqtt-api.md | 11 +++- docs/tower-installation.md | 4 +- include/fgc/CommandParser.h | 16 +++++ include/fgc/Geometry.h | 4 ++ include/fgc/IMotorController.h | 4 ++ include/fgc/ScanGrid.h | 4 ++ src/core/Application.cpp | 109 +++++++++++++++++++++++---------- src/core/CaptureScheduler.cpp | 9 +-- src/core/CommandParser.cpp | 24 ++++++++ src/core/Geometry.cpp | 4 ++ src/core/HelpText.cpp | 13 ++-- src/core/ScanGrid.cpp | 19 ++++-- src/ui/TuiUi.cpp | 26 ++++---- tests/test_command.cpp | 36 +++++++++++ tests/test_geometry.cpp | 8 +++ tests/test_helptext.cpp | 4 +- tests/test_scangrid.cpp | 15 +++++ tests/test_telemetry.cpp | 13 ++++ 25 files changed, 288 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index f92e381..2243967 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,7 @@ press a key that acts. | `:` | type a command, exactly as in the [command list](#everyday-commands) | | `f` | freeze the display, to select and copy text or to page back through the log (the program keeps running) | | `PgUp` `PgDn` | scroll the log, or the open view - the only scroll keys | -| arrows | nudge the gimbal by hand, from any view - manual mode only | +| arrows | nudge the gimbal by hand, from any view - manual mode only (up/down only on a tower with pitch) | | `Space` | take one picture now (`snapshot`) - manual mode only | | `r` | continuous capture on/off (`capture on` / `capture off`) - manual mode only | | `Esc` | close a view; with no view open, **cancel** whatever is running (a test, a homing, a calibration, a heading setup) | @@ -315,7 +315,7 @@ job. Details, payloads and safety rules: [docs/mqtt-api.md](docs/mqtt-api.md). | `mode auto` / `mode manual` | sweep the scan grid / stop and take the gimbal by hand (it stays homed and powered) | | `mode pan []` | watch one direction: `mode pan 90 30` covers 75°-105°, a picture every 6° | | `gimbal home` | find the endstops and centre - needed after every power-up | -| `gimbal move ,` | point at a **true bearing** (0 = north) and an elevation, in degrees | +| `gimbal move [,]` | point at a **true bearing** (0 = north) and an elevation, in degrees; without a pitch only yaw moves. A yaw-only tower ignores the pitch and refuses pitch-only commands | | `gimbal nudge <+/-percent>` | a small move by hand | | `snapshot` | take one picture now (manual mode) | | `capture on` / `capture off` | keep taking pictures where the gimbal is aimed, at the sweep's pace (manual mode; the gimbal can still be nudged) | diff --git a/config/config.example.ini b/config/config.example.ini index c20df69..9f43e46 100644 --- a/config/config.example.ini +++ b/config/config.example.ini @@ -195,6 +195,8 @@ pitch_counts_per_deg = 8333.33 pitch_center_count = 500000 ; Elevation above level (+ = up) at the centre. This is NOT 0: the mechanical ; centre is the middle of pitch travel, which sits well above the horizon. +; A yaw-only tower has no pitch travel: set this to its camera's fixed tilt +; (0 if level) - it is the CamEvent pitch there when no IMU is fitted. pitch_angle_at_center = 10.0 pitch_soft_margin_counts = 5000 ; Live encoder-error WARN threshold in counts (0 = off). diff --git a/config/scan.csv b/config/scan.csv index ae2e7e1..1bbb5ca 100644 --- a/config/scan.csv +++ b/config/scan.csv @@ -1,4 +1,5 @@ -# Scan grid - capture waypoints, one "yaw_deg,pitch_deg" per line. +# Scan grid - capture waypoints, one "yaw_deg,pitch_deg" per line ("yaw_deg" +# alone means pitch 0; a yaw-only tower ignores the pitch). # Edit these to define the exact scan coordinates. Blank lines and lines # starting with '#' are ignored. This file is the only source of waypoints: # nothing is generated, so what the tower photographs is always what is written diff --git a/docs/architecture.md b/docs/architecture.md index e675e03..9efa7a8 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -73,7 +73,8 @@ corrupted. the `mode` command (console, dashboard keys, or MQTT `Command`) through `Application::setMode`, which publishes it on StatusCode. The same header's `allowedIn()` gates every command by mode. 4. **Capture cycle** (`CaptureScheduler::tick`, per 10 ms) — a move → settle → wait → keep machine: - - auto: `MOVE ,` to the next `ScanGrid` waypoint (front to back, then wrap). + - auto: `MOVE ,` to the next `ScanGrid` waypoint (front to back, then wrap). A yaw-only + tower (no `P:` segment in `ST`) gets `MOVE Y ` instead, as its firmware refuses the pair. - pan: `MOVE` to the next of `panHeadings()` (left to right, then back to the left edge) at the pan's pitch, converted to counts via `Geometry`. - manual: nothing, bar a requested snapshot or continuous capture (`capture on`), which skip the @@ -84,7 +85,8 @@ corrupted. auto-exposure tracking the light; `trigger()` hands over the freshest complete frame. See *Image capture* below. 6. **Frame handling** — the kept `Frame` goes to the callback, which `submit()`s it to the - `ImagePipeline`. `submit()` samples the gimbal pose there and then — yaw from the encoder, pitch from the IMU — + `ImagePipeline`. `submit()` samples the gimbal pose there and then — yaw from the encoder, pitch from the IMU + (no IMU: the pitch encoder; no pitch axis either: the fixed `pitch_angle_at_center`) — and the pose travels through the queue with the frame, so encode latency cannot attribute a later pose to earlier pixels. The worker rotates it by `Camera.rotation_deg`, encodes JPEG XL, writes `/