Skip to content

Commit ea08020

Browse files
tipoman9claude
andauthored
Add ground-station only offline moving-map overlay + preflight tool t… (#104)
* Add ground-station only offline moving-map overlay + preflight tool to predownload it. Native OSD moving-map renderer (GS builds only, _x86/__ROCKCHIP__) drawn into the OSD with Cairo, over the video: follow modes (plane/north/center/fit), rounded window frame, ini-configurable position/size, on-screen status messages, POI direction markers, home/target from state.ini + landmarks DB. All new rendering is guarded, so air-unit builds (goke/hisi/star6*) are unaffected. Ground-station preflight tool (gs/): Python mapserver + WebKit viewer to browse/download tiles into per-basemap MBTiles, set targets, and build a standalone preflight binary (gs/pack). Keyed-provider (Thunderforest) support with the API key kept out of git. native/rockchip now link sqlite3 (CI dep added). Docs under documentation/ and gs/README.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * GS offline map: terrain elevation + AGL, link horizon, named packs Extends the ground-station moving map and its preflight tool. Ground-side only: the new C sources go in GS_SRCS and are compiled into the native and rockchip targets, so camera builds are unaffected. Terrain elevation - Preflight stores a terrain height grid alongside the tiles in maps/elevation.db (AWS "terrarium" tiles decoded to int16 metres at z12, ~28 m/sample). On by default, capped at 1000 tiles. - mapserver.py gains a stdlib-only PNG decoder, so no new dependency. - osd/util/terrain_elevation.c mirrors the Python lookup in C; terrain_agl.c calibrates GPS altitude against terrain at the armed transition and feeds AGL to the map and to the new !AGL! OSD keyword. - tests/terrain_{elevation,agl}_test.c, run by `make test-terrain` under ASan/UBSan. Link horizon - New /viewshed endpoint and preflight panel: given an antenna height and a flight altitude, draws where terrain stops blocking line of sight. Models 4/3-earth refraction and reproduces the textbook horizon over flat ground. It is a terrain-only upper bound; the DEM is bare earth, so trees, masts and buildings are not in it. Map packs - Each download is allocated a name and never overwrites an existing pack; the panel lists downloaded packs and can delete them. - The OSD map cycles packs at runtime with 'h', and falls back to the first pack in the folder when the configured one is missing. Downloads - Tile fetches reuse a kept-alive connection per host instead of a fresh TLS handshake per tile; the handshake was ~125 ms of a ~220 ms tile. - Progress is reported per phase (tiles -> POIs -> elevation), so the bar no longer sits at 100% while elevation is still downloading. OSD map rendering - JPEG tiles decode via vendored stb_image, so imagery basemaps render. - Track vector: an arrow along the GPS ground course drawn over the plane marker, showing the drift angle when the aircraft crabs. Configurable length and opacity; hidden below a ground-speed threshold, which also suppresses it when no GPS data is arriving. - Up to 5 named waypoints instead of a single target. - Pack filename banner and a key guide shown briefly when the map appears. - Fix: in center (track-up) mode the plane icon was drawn at a fixed angle, so it showed the ground track rather than the heading. Icon and vector now both derive their screen angle from the rotation the tiles were rendered with, making all four follow modes consistent. Preflight UI - Detail zoom selectable z12-z18, per-zoom basemap sources, an Advanced section for pack naming, terrain height under the pointer, waypoint editing and cached-coverage shading. - map.sh/run-map.sh open the system browser by default; --GTK selects the WebKitGTK window. Docs - New documentation/terrain-elevation.md and gs-map-internals.md; gs/README.md rewritten around capabilities. - msposd.ini is gitignored, so add a tracked msposd.ini.example documenting every [poi] and [map] key, including the new ones. Per-FC sections that msposd writes at runtime are excluded. Fixes the dangling ../msposd.ini link in map_control_via_RC.md. Co-Authored-By: Claude Opus 5 * GS map: commit vendored Leaflet 1.9.4 so packaged builds work from a clean checkout leaflet.js/css and the marker images were gitignored and only fetched by fetch-leaflet.sh, so build.bat/CI on a fresh clone produced a binary whose viewer 404s on leaflet.* ("L is not defined"). Commit the files, guard the PyInstaller spec against a missing copy, and whitelist the images/ set that leaflet.css references. Also on Windows: swallow ConnectionAbortedError (WinError 10053, the browser cancelling a tile or SSE stream) like BrokenPipe/Reset by catching the common ConnectionError base, and log the reason when a live tile fetch fails instead of silently answering 204, so blank basemaps can be diagnosed from the console. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * GS map preflight: run on Windows from a fresh checkout - map.sh: fall back to the default port when config.ini is absent - mapserver.py: on Windows, verify TLS with certifi's root bundle (OpenTopoMap failed with an expired cross-cert in the Windows store), bind without SO_REUSEADDR so a relaunch reuses the running server instead of starting a second one, and report reserved ports clearly - run-map.bat: native Windows launcher; build.bat requires Python 3.12+ and bundles certifi; CI installs certifi - docs: Windows notes Linux behaviour unchanged (all changes gated on win32). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Lower MSP attitude latency: early UART read, no attitude send gate, GS AHI draw not throttled - Camera: poll MSP_ATTITUDE first in the tick and read the UART once more 15 ms later, so the reply is forwarded in the same tick (~49 ms -> ~19 ms). - Camera: flush attitude to UDP without the 10 ms gate; a DRAW_SCREEN flush in the same read no longer holds it a full tick. DisplayPort frames are aggregated for 2 refresh intervals to keep the datagram rate unchanged. - GS: attitude-driven AHI redraw bypasses the screen refresh throttle so no sample is skipped. Air units keep the throttle. - Stats: AttitMSGs(ms) is now a true per-second average. * Changes in readme and map.sh to reflect the new offline map build process. Added a new offline map image to the pics directory. * mspmaptool: version 1.0; skip firmware build on mspmaptool tags --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent f226049 commit ea08020

55 files changed

Lines changed: 20278 additions & 61 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/build.yml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
- main
66
tags:
77
- '*'
8+
- '!mspmaptool*' # map tool releases are published by hand, not firmware
89
pull_request:
910
branches:
1011
- main
@@ -20,7 +21,7 @@ jobs:
2021
- name: Build
2122
run: |
2223
sudo apt-get update
23-
DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC sudo apt-get -y install git make wget gcc libevent-dev libcsfml-dev file g++ cpio unzip rsync bc bzip2 libcairo-dev
24+
DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC sudo apt-get -y install git make wget gcc libevent-dev libcsfml-dev file g++ cpio unzip rsync bc bzip2 libcairo-dev libsqlite3-dev
2425
./build.sh goke
2526
./build.sh hisi
2627
./build.sh star6b0
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: preflight-pack
2+
3+
# Build the standalone preflight map app (gs/mapserver.py + web/) into a single
4+
# self-contained binary for Linux, macOS and Windows. Run manually or by pushing
5+
# a `preflight-v*` tag; grab the binaries from the run's Artifacts.
6+
7+
on:
8+
workflow_dispatch:
9+
push:
10+
tags:
11+
- "preflight-v*"
12+
13+
jobs:
14+
build:
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
include:
19+
- os: ubuntu-latest
20+
artifact: mspmaptool_linux
21+
- os: macos-latest
22+
artifact: mspmaptool_macos
23+
- os: windows-latest
24+
artifact: mspmaptool_windows
25+
runs-on: ${{ matrix.os }}
26+
steps:
27+
- uses: actions/checkout@v4
28+
29+
- uses: actions/setup-python@v5
30+
with:
31+
python-version: "3.12"
32+
33+
# certifi: bundled root CAs so the Windows binary verifies Let's Encrypt
34+
# tile sources (OpenTopoMap) regardless of the client's certificate store.
35+
- name: Install PyInstaller
36+
run: python -m pip install --upgrade pyinstaller certifi
37+
38+
- name: Build
39+
run: python -m PyInstaller --clean --noconfirm --distpath gs/dist --workpath gs/build gs/pack/mapserver.spec
40+
41+
- name: Upload binary
42+
uses: actions/upload-artifact@v4
43+
with:
44+
name: ${{ matrix.artifact }}
45+
path: gs/dist/mspmaptool_*
46+
if-no-files-found: error

‎.gitignore‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,18 @@ msposd_star6b0
1717
msposd_star6e
1818
msposd_star6c
1919
msposd
20+
msposd_x
2021
msposd_rockchip
2122
version.h
2223
serial_monitor.c
2324
*.ini
25+
# PyInstaller output (gs/pack standalone build)
26+
/build/
27+
/dist/
28+
gs/build/
29+
gs/dist/
30+
*.spec.bak
2431

32+
33+
.claude/
34+
AGENTS.md

‎.vscode/launch.json‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"program": "${workspaceFolder}/msposd",
1010
"args": [
1111
"--master",
12-
"127.0.0.1:14550",
12+
"127.0.0.1:14560",
1313
"--baudrate",
1414
"115200",
1515
"--osd",
@@ -54,7 +54,9 @@
5454
"args": [
5555
"-m",
5656
"/tmp/inav_pty",
57-
"-d",
57+
"-d",
58+
"--out",
59+
"127.0.0.1:14560",
5860
"-r",
5961
"1030",
6062
"--ahi",

‎Makefile‎

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CFLAGS ?=
44
CFLAGS += -Wno-address-of-packed-member -DVERSION_STRING="\"$(VERSION_STRING)\""
55

66
SRCS := compat.c msposd.c bmp/bitmap.c bmp/region.c bmp/lib/schrift.c bmp/text.c osd/net/network.c osd/msp/msp.c osd/msp/msp_displayport.c libpng/lodepng.c osd/util/interface.c osd/util/settings.c osd/util/ini_parser.c osd/msp/vtxmenu.c osd/util/subtitle.c osd/util/simple_ini.c
7+
GS_SRCS := osd/util/terrain_elevation.c osd/util/terrain_agl.c
78
OUTPUT ?= $(PWD)
89
OPENHISILICON ?= $(PWD)/openhisilicon
910
BUILD = $(CC) $(SRCS) -I $(SDK)/include -I$(TOOLCHAIN)/usr/include -I$(PWD) -L$(DRV) $(CFLAGS) $(LIB) -levent_core -Os -s $(CFLAGS) -o $(OUTPUT)
@@ -69,13 +70,31 @@ star6e: version.h
6970
native: version.h
7071
$(eval SDK = ./sdk/gk7205v300)
7172
$(eval CFLAGS += -D_x86)
72-
$(eval LIB = -lcsfml-graphics -lcsfml-window -lcsfml-system `pkg-config --libs cairo x11 xext` -lXext -lm)
73-
$(eval BUILD = $(CC) $(SRCS) -I $(SDK)/include -L $(DRV) $(CFLAGS) $(LIB) -levent_core -O0 -g -o $(OUTPUT))
73+
$(eval LIB = -lcsfml-graphics -lcsfml-window -lcsfml-system `pkg-config --libs cairo x11 xext` -lXext -lm -lsqlite3)
74+
$(eval BUILD = $(CC) $(SRCS) $(GS_SRCS) -I $(SDK)/include -L $(DRV) $(CFLAGS) $(LIB) -levent_core -O0 -g -o $(OUTPUT))
7475
$(BUILD)
7576

7677
rockchip: version.h
7778
$(eval SDK = ./sdk/gk7205v300)
7879
$(eval CFLAGS += -D__ROCKCHIP__)
79-
$(eval LIB = `pkg-config --libs cairo x11 xext` -lXext -lm -lrt)
80-
$(eval BUILD = $(CC) $(SRCS) -I $(SDK)/include -L $(DRV) $(CFLAGS) $(LIB) -levent_core -O0 -g -o $(OUTPUT))
80+
$(eval LIB = `pkg-config --libs cairo x11 xext` -lXext -lm -lrt -lsqlite3)
81+
$(eval BUILD = $(CC) $(SRCS) $(GS_SRCS) -I $(SDK)/include -L $(DRV) $(CFLAGS) $(LIB) -levent_core -O0 -g -o $(OUTPUT))
8182
$(BUILD)
83+
84+
test-terrain-elevation:
85+
$(CC) tests/terrain_elevation_test.c osd/util/terrain_elevation.c -I$(PWD) \
86+
-DTERRAIN_ELEVATION_DB_PATH='"/tmp/msposd-terrain-elevation-test.db"' \
87+
-Wall -Wextra -Werror -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer -no-pie \
88+
-o /tmp/msposd-terrain-elevation-test -lsqlite3 -lm
89+
ASAN_OPTIONS=detect_leaks=0:abort_on_error=1:handle_segv=0 \
90+
/tmp/msposd-terrain-elevation-test
91+
92+
test-terrain-agl:
93+
$(CC) tests/terrain_agl_test.c osd/util/terrain_agl.c osd/util/terrain_elevation.c \
94+
-I$(PWD) -DTERRAIN_ELEVATION_DB_PATH='"/tmp/msposd-terrain-agl-test.db"' \
95+
-Wall -Wextra -Werror -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer -no-pie \
96+
-o /tmp/msposd-terrain-agl-test -lsqlite3 -lm
97+
ASAN_OPTIONS=detect_leaks=0:abort_on_error=1:handle_segv=0 \
98+
/tmp/msposd-terrain-agl-test
99+
100+
test-terrain: test-terrain-elevation test-terrain-agl

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ This keywords can be added using the PilotName/CraftName fields(INAV/BF/Ardu), o
138138
- ```!TMP!``` Camera board temp
139139
- ```!TMW!``` WiFi module temperature (only 8812EU2/8733BU chipsets supported)
140140
- ```!RC!``` Sticks Position widget (Ground Side only)
141+
- ```!AGL!``` GPS-calibrated height above terrain in metres (Ground Side only);
142+
displays ```----m``` while unavailable
141143

142144
### Usage Example:
143145

‎build_rockchip.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ if [ ! -f $output/tmp/prepare_chroot.done ]; then
3636
echo 'deb [signed-by=/usr/share/keyrings/radxa-archive-keyring.gpg] https://radxa-repo.github.io/bullseye rockchip-bullseye main' > /etc/apt/sources.list.d/80-rockchip.list
3737
3838
apt-get update
39-
apt-get install -y git gcc make pkg-config libspdlog-dev libevent-dev libcairo-dev
39+
apt-get install -y git gcc make pkg-config libspdlog-dev libevent-dev libcairo-dev libsqlite3-dev
4040
apt clean
4141
touch /tmp/prepare_chroot.done
4242
EOL
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Build a release package — preflight map
2+
3+
How to package the preflight map (`map.sh preflight` / `gs/mapserver.py`) into a **single
4+
self-contained binary per OS** that end users run without installing Python. It starts a
5+
local server and opens the map in the user's **default browser** (no Python/WebKit to
6+
install). Writable `config.ini`, `state.ini` and `maps/` are created next to the
7+
executable on first run. Map tiles, shared landmarks and shared elevation data live
8+
inside `maps/`.
9+
10+
## Prerequisites
11+
- Python **3.9+** and `pip` on the build machine (`pyinstaller` is installed automatically).
12+
The Windows build needs **3.12+** like CI, and bundles `certifi` so HTTPS tile sources
13+
verify on every client PC regardless of its certificate store.
14+
- Build **on each target OS** — a Windows `.exe` needs Windows, a macOS build needs a Mac.
15+
16+
PyInstaller bundles a bootloader for the host OS; it does not cross-compile. Running
17+
`build.sh` on Linux or WSL produces a Linux ELF executable. Copying or renaming that
18+
file to `.exe` will produce Windows' “not a valid app” error. Use native Windows with
19+
`build.bat`, or use the Windows artifact from CI.
20+
21+
## Build (current OS)
22+
```bash
23+
./gs/pack/build.sh # Linux / macOS
24+
gs\pack\build.bat # Windows
25+
```
26+
Output: **`gs/dist/mspmaptool_linux`** (`mspmaptool_macos` on macOS, `mspmaptool_windows.exe` on Windows) — one file.
27+
28+
On Linux, verify the artifact type before publishing it:
29+
30+
```bash
31+
file gs/dist/mspmaptool_linux
32+
# ... ELF 64-bit ...
33+
```
34+
35+
## Build all three OSes at once (CI)
36+
The `.github/workflows/preflight-pack.yml` matrix builds Linux/macOS/Windows. Trigger it:
37+
- **Manually:** GitHub → Actions → *preflight-pack* → *Run workflow*, or
38+
- **By tag:** push a tag matching `preflight-v*`, e.g.
39+
```bash
40+
git tag preflight-v1.0 && git push origin preflight-v1.0
41+
```
42+
Download the three binaries from the run's **Artifacts**.
43+
44+
## Run / verify
45+
```bash
46+
./gs/dist/mspmaptool_linux # starts server, opens the browser
47+
./gs/dist/mspmaptool_linux --port 9000 # custom port
48+
./gs/dist/mspmaptool_linux --no-browser # server only
49+
```
50+
Ctrl+C (or closing the console) stops it.
51+
52+
## Install elsewhere on Linux
53+
54+
No installation step is required. The binary can be copied to another writable folder
55+
on the same Linux system:
56+
57+
```bash
58+
mkdir -p ~/Apps/mspmaptool
59+
cp gs/dist/mspmaptool_linux ~/Apps/mspmaptool/
60+
chmod +x ~/Apps/mspmaptool/mspmaptool_linux
61+
~/Apps/mspmaptool/mspmaptool_linux
62+
```
63+
64+
Its new containing directory becomes the writable application directory. Copy the old
65+
`config.ini`, `state.ini` and `maps/` into that directory to preserve settings, map
66+
packs, landmarks and elevation data. Otherwise it starts with defaults and creates data
67+
as needed. Only one server can own the default HTTP port; stop the old copy or start the
68+
new one with `--port 9000`.
69+
70+
## Ship it
71+
Give users the single binary. On first run it creates its data folder alongside itself;
72+
each area download creates a new `maps/<pack>.mbtiles` without modifying older packs.
73+
Select the desired entry under **Downloaded maps**, then use **Export selected map…** to
74+
create a zip containing that pack plus `landmarks.db` and `elevation.db` for the
75+
OSD/flight station.
76+
77+
## Notes
78+
- **Unsigned binaries** trip macOS Gatekeeper / Windows SmartScreen — code-sign (and
79+
notarize on macOS) for public release; fine as-is for internal use.
80+
- Some Windows AV engines false-positive on PyInstaller one-file builds.
81+
- For distribution across different Linux releases, build on an appropriately old
82+
compatible Linux baseline; copying within the build machine itself is safe.
83+
- The dev launcher opens preflight in the system browser by default. Use
84+
`./map.sh preflight --GTK` for the compatibility WebKit `mapwin` window.
85+
86+
See [`../gs/pack/README.md`](../gs/pack/README.md) for how the packaging works internally.

0 commit comments

Comments
 (0)