Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Build
run: |
sudo apt-get update
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
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
./build.sh goke
./build.sh hisi
./build.sh star6b0
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/preflight-pack.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: preflight-pack

# Build the standalone preflight map app (gs/mapserver.py + web/) into a single
# self-contained binary for Linux, macOS and Windows. Run manually or by pushing
# a `preflight-v*` tag; grab the binaries from the run's Artifacts.

on:
workflow_dispatch:
push:
tags:
- "preflight-v*"

jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
artifact: msposd-preflight-linux
- os: macos-latest
artifact: msposd-preflight-macos
- os: windows-latest
artifact: msposd-preflight-windows
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install PyInstaller
run: python -m pip install --upgrade pyinstaller

- name: Build
run: python -m PyInstaller --clean --noconfirm gs/pack/mapserver.spec

- name: Upload binary
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: |
dist/msposd-preflight
dist/msposd-preflight.exe
if-no-files-found: error
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@ msposd_star6b0
msposd_star6e
msposd_star6c
msposd
msposd_x
msposd_rockchip
version.h
serial_monitor.c
*.ini
# PyInstaller output (gs/pack standalone build)
/build/
/dist/
*.spec.bak


.claude/
6 changes: 4 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"program": "${workspaceFolder}/msposd",
"args": [
"--master",
"127.0.0.1:14550",
"127.0.0.1:14560",
"--baudrate",
"115200",
"--osd",
Expand Down Expand Up @@ -54,7 +54,9 @@
"args": [
"-m",
"/tmp/inav_pty",
"-d",
"-d",
"--out",
"127.0.0.1:14560",
"-r",
"1030",
"--ahi",
Expand Down
64 changes: 64 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Project Instructions

This file is the single source of truth for all AI coding agents (Claude Code,
OpenAI Codex, or any tool that reads AGENTS.md / CLAUDE.md).

## Project Overview

A program that draws flight controller's OSD(On-Screen Display) information via MSP DisplayPort protocil directly onto the video stream coming from an OpenIPC camera.

## Development Workflow: spec - draft - simplify - verify

Every non-trivial task MUST follow this four-phase pipeline.
Each phase is a gate: do not advance until the current phase passes.

### Phase 1: Spec (Plan)

Before writing any code, produce a plan:

1. Read relevant documentation in `documentation/`.
2. Read the source files you intend to modify.
3. Write a concise plan covering: what changes, which files, why.
4. Document key design decisions and their rationale in the plan. This
prevents oscillating between approaches mid-implementation.
5. Get human approval on the plan before proceeding.

Do NOT skip planning. A good plan lets you one-shot the implementation.

### Phase 2: Draft (Implement)

Execute the plan:

- Follow the coding conventions below.
- Make minimal, focused changes. Do not refactor unrelated code.
- Do not add features beyond what the spec calls for.

### Phase 3: Simplify (Review)

After implementation, review your own work:

- Can any function be shorter or clearer?
- Are there unnecessary abstractions, error paths, or comments?
- Does the architecture stay clean? No dead code, no orphan headers.
- Remove anything that is not strictly needed.

### Phase 4: Verify (Build + Test)

Run verification before declaring done:



## Documentation in code Rules:

Every function/method MUST include a documentation comment.

Requirements:
- Use standard language conventions.
- Include:
- Short function description (MAX 50 words)
- Description of every parameter
- Return value description if applicable
- Keep descriptions concise and technical.
- Do NOT exceed 50 words for the function summary.


4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ star6e: version.h
native: version.h
$(eval SDK = ./sdk/gk7205v300)
$(eval CFLAGS += -D_x86)
$(eval LIB = -lcsfml-graphics -lcsfml-window -lcsfml-system `pkg-config --libs cairo x11 xext` -lXext -lm)
$(eval LIB = -lcsfml-graphics -lcsfml-window -lcsfml-system `pkg-config --libs cairo x11 xext` -lXext -lm -lsqlite3)
$(eval BUILD = $(CC) $(SRCS) -I $(SDK)/include -L $(DRV) $(CFLAGS) $(LIB) -levent_core -O0 -g -o $(OUTPUT))
$(BUILD)

rockchip: version.h
$(eval SDK = ./sdk/gk7205v300)
$(eval CFLAGS += -D__ROCKCHIP__)
$(eval LIB = `pkg-config --libs cairo x11 xext` -lXext -lm -lrt)
$(eval LIB = `pkg-config --libs cairo x11 xext` -lXext -lm -lrt -lsqlite3)
$(eval BUILD = $(CC) $(SRCS) -I $(SDK)/include -L $(DRV) $(CFLAGS) $(LIB) -levent_core -O0 -g -o $(OUTPUT))
$(BUILD)
2 changes: 1 addition & 1 deletion build_rockchip.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ if [ ! -f $output/tmp/prepare_chroot.done ]; then
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

apt-get update
apt-get install -y git gcc make pkg-config libspdlog-dev libevent-dev libcairo-dev
apt-get install -y git gcc make pkg-config libspdlog-dev libevent-dev libcairo-dev libsqlite3-dev
apt clean
touch /tmp/prepare_chroot.done
EOL
Expand Down
49 changes: 49 additions & 0 deletions documentation/build-preflight-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Build a release package — preflight map

How to package the preflight map (`map.sh preflight` / `gs/mapserver.py`) into a **single
self-contained binary per OS** that end users run without installing Python. It starts a
local server and opens the map in the user's **default browser** (no Python/WebKit to
install). Writable data (`config.ini`, `maps/`, `state.ini`, `landmarks.db`) is created
next to the executable on first run — that folder is the pack you copy to the OSD station.

## Prerequisites
- Python **3.9+** and `pip` on the build machine (`pyinstaller` is installed automatically).
- Build **on each target OS** — a Windows `.exe` needs Windows, a macOS build needs a Mac.

## Build (current OS)
```bash
./gs/pack/build.sh # Linux / macOS
gs\pack\build.bat # Windows
```
Output: **`dist/msposd-preflight`** (`.exe` on Windows) — one file.

## Build all three OSes at once (CI)
The `.github/workflows/preflight-pack.yml` matrix builds Linux/macOS/Windows. Trigger it:
- **Manually:** GitHub → Actions → *preflight-pack* → *Run workflow*, or
- **By tag:** push a tag matching `preflight-v*`, e.g.
```bash
git tag preflight-v1.0 && git push origin preflight-v1.0
```
Download the three binaries from the run's **Artifacts**.

## Run / verify
```bash
./dist/msposd-preflight # starts server, opens the browser
./dist/msposd-preflight --port 9000 # custom port
./dist/msposd-preflight --no-browser # server only
```
Ctrl+C (or closing the console) stops it.

## Ship it
Give users the single binary. On first run it creates its data folder alongside itself;
after they download an area, hand the resulting `maps/<basemap>.mbtiles` + `landmarks.db`
to the OSD/flight station.

## Notes
- **Unsigned binaries** trip macOS Gatekeeper / Windows SmartScreen — code-sign (and
notarize on macOS) for public release; fine as-is for internal use.
- Some Windows AV engines false-positive on PyInstaller one-file builds.
- The dev workflow is unaffected: `./map.sh preflight` still runs `mapserver.py` + the
WebKit `mapwin` window.

See [`../gs/pack/README.md`](../gs/pack/README.md) for how the packaging works internally.
132 changes: 132 additions & 0 deletions documentation/map-modes-and-home-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Plan — Map Modes, WebKitGTK Renderer, Target & Home Persistence

Status: **DRAFT — awaiting approval & decisions (§9)**. Phase 1 (Spec). No code yet.

Builds on [offline-map-overlay-spec.md](offline-map-overlay-spec.md). Goal: turn the
single Firefox window into a lightweight, cross-platform (x86 + Radxa Zero 2W) viewer
with three run modes, click-to-set target, and automatic home capture on ARM.

## 1. Renderer decision

Move overlay rendering to **WebKitGTK** via a small `mapwin` host (Python 3 + `gi` +
`WebKit2 4.1`), which is already installed here and available as the same Debian packages
on the Radxa (`gir1.2-webkit2-4.1`, `python3-gi`). Lighter than Chromium, borderless +
always-on-top capable, transparent-background capable (for overlay over video).

- **Preflight (mode 1)** uses `mapwin` as a decorated/maximized config window.
- **Preview / Full (modes 2/3)** use `mapwin` as borderless overlay windows.

The same `viewer.html` is loaded in all cases; mode-specific behavior is driven by URL
query params, so there is one UI codebase.

## 2. New / changed files

```
gs/
├── mapwin # NEW: Python GTK3+WebKit2 host (executable, takes window args)
├── map.sh # NEW: mode launcher wrapper (preflight|preview|full [flags])
├── mapserver.py # CHANGED: STATUS/armed parsing, home capture, state file, /target
├── web/viewer.html # CHANGED: modes via query params, click-to-target, house icon, fit mode
├── state.ini # NEW (generated): shared target+home (see §6)
└── run-map.sh # kept as a thin alias to `map.sh preflight`
```

## 3. `mapwin` (WebKitGTK host)

A ~80-line GTK3 + WebKit2 window. Arguments:

| Flag | Meaning |
| ---- | ------- |
| `--url URL` | page to load (required) |
| `--fullscreen` | fullscreen window (mode 3, preflight on Radxa) |
| `--geometry WxH+X+Y` | explicit size/pos (mode 2 default = ¼ screen, bottom-right) |
| `--borderless` | undecorated + keep-above + skip taskbar (modes 2/3 overlay) |
| `--transparent` | RGBA visual + transparent background (future: map over video) |

mapwin only manages the *window*; all map logic stays in `viewer.html`.

## 4. Modes & CLI (`map.sh`)

| Command | Window | Toolbox | Telemetry behavior |
| ------- | ------ | ------- | ------------------ |
| `map.sh preflight` | fullscreen (any browser) | yes | free browse; download; click-to-target |
| `map.sh preview --follow plane` | ¼ screen, borderless, keep-above | no | plane centered, map moves, fixed zoom |
| `map.sh preview --follow fit` | ¼ screen, borderless, keep-above | no | fit home+plane in view (auto zoom/center) |
| `map.sh full --follow plane\|fit` | fullscreen, borderless | no | as above, fullscreen (OSD renders over it) |

`map.sh` starts `mapserver.py` if not already running, then launches the browser/`mapwin`
with the right URL params and window flags. The "executable that takes arguments" is
`mapwin`; `map.sh` is the convenience wrapper.

## 5. `viewer.html` behavior by query param

- `?mode=preflight` → toolbox visible (basemap, download, target fields), **map click sets
target**, full pan/zoom. (current behavior + click-to-target)
- `?mode=preview|full` → toolbox hidden, user interaction locked; layers driven by SSE.
- `&follow=plane` → `map.panTo(plane)` each fix, fixed zoom (config `zoom`).
- `&follow=fit` → `map.fitBounds([home, plane])` each fix → scale & center from the two
coordinates.
- **House icon** for home (from `/status.home`) in every mode once home is known.
- Plane / heading vector / target vector as today.

## 6. Shared state file — target + home

Both `mapserver.py` (write) and `msposd.c` (read) need it. **Recommended format: INI**,
because msposd already ships an INI parser (`osd/util/ini_parser.c`, `ReadIniInt`/Float),
so the C side is a few lines and no JSON dependency. (Decision in §9.)

`gs/state.ini` (path also readable by msposd via config):
```ini
[target]
set = 1 ; 0 = no target
lat = 43.2500
lon = 27.8500

[home]
set = 1 ; 0 = home not yet captured
lat = 43.1430
lon = 27.9340
```
- `mapserver.py` writes `[target]` on user set/clear, `[home]` on ARM capture.
- Written atomically (temp file + rename) so a concurrent C read never sees a half file.
- `msposd.c` integration (reading/drawing target & home) is **out of scope for this plan**;
we only guarantee a C-friendly format and stable path. A follow-up task wires msposd.c.

## 7. Home capture on ARM (`mapserver.py`)

- Parse `MSP_CMD_STATUS` (cmd 101; also 150 STATUS_EX): `armed = payload[6] & 0x01`.
- Track armed edge. On **disarmed→armed** with a valid GPS fix, set `home = current pos`
and persist to `[home]`. If armed fires before a fix, capture on the first valid fix
while still armed and home unset this session.
- **On startup, load `[home]` from `state.ini`.** Do not overwrite it except on a fresh
arm edge — so restarting the server mid-flight keeps the correct home (the plane is no
longer at home, but the persisted value is right).
- Expose `home` in `/status` for the house icon.

## 8. `mapserver.py` endpoint changes

- `GET /status` → add `home: {lat,lon}|null`, `target: {lat,lon}|null`, `armed: bool`.
- `POST /target {lat,lon|null}` → set/clear target, persist `[target]`. (viewer click + fields)
- Keep `/pos`, `/tiles`, `/download`, `/settings`, basemaps as-is.
- State-file IO helpers (load on boot, atomic write).

## 9. Decisions (locked — approved 2026-06-22)

1. **State file format: INI** — `gs/state.ini`, reusing msposd's `ini_parser.c` on the C side.
2. **Phasing: Phase A now** — modes 1+2, click-to-target, ARM home capture. **Mode 3
(fullscreen + OSD-over-map) deferred to Phase B.**
3. **Renderer: Python GTK3 + WebKit2 `mapwin`.**

Target storage moves out of `config.ini [map]` into `state.ini [target]`; `config.ini`
keeps zoom/basemap/center. The viewer reads target+home from `/status`, sets target via
`POST /target`.

## 10. Verify plan (Phase 4)

- `mapwin`: opens borderless ¼-screen keep-above window at the right geometry (x86 now).
- Home: simulate ARM via an injected MSP_STATUS frame while a fix is present → `state.ini`
`[home]` written; restart server → home reloaded → house icon shown at the same spot.
- Target: click map in preflight → `[target]` written and re-read on reload.
- Modes: `follow=plane` keeps plane centered; `follow=fit` keeps both home and plane in view.
- `sim_msp.py` extended to optionally send ARMED so the whole flow is testable on the desk.
```
Loading
Loading