From ccfd4148d6992519f5f6aa7cf9d251735762d86b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 9 Aug 2026 15:04:43 +0000 Subject: [PATCH 01/15] Mockup: full-screen PFD with real three-axis mount calibration Three problems with the first cut, all fair. **The mounting compensation was wrong.** The old "cage" button zeroed pitch and roll, which silently assumes the phone's axes line up with the airframe's. They do not, and cannot be made to by two offsets: a phone tilted on a dash or lying on a kneeboard has a completely different mapping from device axes to aircraft axes, and zeroing two numbers just relabels the phone's attitude as the aircraft's. SET LEVEL now captures the full three-axis mounting rotation. With the aircraft level, gravity in the device frame gives the true down axis through the airframe; combined with the screen direction nominated as the right wing, that fixes all three aircraft axes in device coordinates, and everything after is a change of basis. Any mount angle works: vertical on the dash, flat on a kneeboard, or anything between. The calibration persists across launches. Display rotation is adjustable in 90 degree steps and 5 degree trim, for a phone mounted rotated or slightly skewed. **It scrolled.** The page is now fixed and full bleed with no scroll at all. Everything that was prose below the display moved into an overlay behind a single small button, out of the scan. **It was a toy.** It now runs as an instrument: fullscreen, landscape lock, wake lock, and calibration in local storage, so it can go on the aircraft as-is. Also added, since the mounting work made it nearly free: an angle of attack estimate from pitch minus flight path angle, with flight path angle derived from GNSS vertical speed against ground speed, plus a flight path marker on the attitude indicator. Both are marked as estimates. True AoA needs a vane or a differential probe; this is attitude minus trajectory and it is honest about that. The uncalibrated state is annunciated in red rather than quietly showing phone attitude as though it were the aircraft's. --- app/mockup/index.html | 1590 +++++++++++++++++------------------------ app/mockup/sw.js | 2 +- 2 files changed, 676 insertions(+), 916 deletions(-) diff --git a/app/mockup/index.html b/app/mockup/index.html index 313f6cb..2ef6005 100644 --- a/app/mockup/index.html +++ b/app/mockup/index.html @@ -2,984 +2,744 @@ - - + + -Junco — Primary Flight Display +Junco PFD - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PHONE AHRS · ADVISORY - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - 28 - MPH IAS - NODE PITOT - - - - - - - - - 1180 - FT - NODE PLENUM - - - - - - - 0 - - FPM - - - - - - - - - - - - — - - - PHONE GNSS TRACK - - - - ENGINE - - - - - - - - - - POSITION - — - ALERTS - - - - - - - -
- -
- - - - - - - -
- -
-

Attitude source diagnostics

-
Modedemo flight
-
Secure context—
-
Running in iframe—
-
DeviceOrientationEvent—
-
Permission request—
-
Orientation events seen0
-
Generic Sensor API—
-
Geolocation—
-
Screen angle0°
-
- -
-

It flies itself out of the box. A scripted circuit drives the attitude so the whole - layout is judgeable without any sensor at all. Drag anywhere on the display to fly it - by hand instead. The annunciator on the attitude indicator always names which source is driving it.

- -

If “Enable phone sensors” does nothing

-

That is expected here, and the diagnostics panel above says why. This page runs inside an iframe, - and deviceorientation is gated by Permissions Policy: in a cross-origin frame it silently - never fires unless the parent sets allow="gyroscope; accelerometer; magnetometer", which a - page cannot grant itself. Desktop Chrome has no motion sensors to begin with.

-

Three ways to get real attitude:

-

— Desktop Chrome: DevTools → ⋮ → More tools → Sensors, then set - a custom orientation. It drives deviceorientation and this page will pick it up and flip - the annunciator to PHONE AHRS.
- — Your phone, properly: save this page as an .html file and serve it - over HTTPS or from localhost as a top-level page, not framed.
- — Or just don't. For judging layout, the demo flight is better than real sensors - anyway, because it exercises bank angles and climb rates you would not get waving a phone around.

- -

What is real when sensors do work

-

Attitude, heading, track, ground speed, GPS altitude and position come from the device. - The attitude is low-pass filtered, because raw phone fusion is too jittery to look at even sitting - still on a desk — which is an early and not especially encouraging data point for a question the - PRD lists as open.

- -

What's simulated

-

Everything on the engine strip, plus indicated airspeed, pressure altitude and vertical speed. - Those are node channels and no node exists yet. Values are plausible PM-2 cruise numbers.

- -

Where design rule 8 went

-

Not twenty small tags. Real avionics annunciate a degraded or alternate source on the instrument - itself, so that's what this does: PHONE - AHRS · ADVISORY sits on the attitude indicator, - cyan labels mark node-plumbed channels, and - magenta marks GPS-derived data, which is already - the convention every pilot reads without thinking. Same rule, native idiom.

- -

Worth saying plainly

-

A full PFD makes phone attitude look authoritative, and it isn't. PRD section 19 rejects phone - attitude for a control loop because OS fusion is tuned for handheld use and drifts in sustained turns, - and section 15 still lists “phone attitude is stable enough on a vibrating airframe to be - worth showing” as an open question. That question is exactly what this layout is for — - put it on the aircraft, run the engine, and see whether the horizon is steady enough to keep. - Design rule 2 stands either way: the mechanical gauges stay installed.

- -

Layout mockup only. Not an instrument, not airworthy, not the Android client.

+ + + + + + + + + + + + + + + + + + + + + + + PHONE AHRS · ADVISORY + + + + + + + + + + 28 + MPH + NODE PITOT + + + + + + + 1180 + FT + NODE PLENUM + + + + + + + 0 + + FPM + + + + + + + + + — + + + + ENGINE + + + + + + + + + + + + + +
+
+ +
+

Mounting calibration

+
+ +
+
+ + not set +
+

Park the aircraft level, mount the phone however you like, then press + SET LEVEL. This captures the full three-axis mounting rotation from gravity, so the + display shows aircraft pitch and roll rather than the phone's. Any mount angle works: + vertical on the dash, flat on a kneeboard, or anything between.

+
+ +
+

Display rotation

+
+ + + 0° + + +
+

Which way the aircraft's right wing lies across the screen. Adjust until + rolling the aircraft rolls the horizon the correct way. Re-run SET LEVEL after changing it.

+
+ +
+

Attitude source

+
+ + +
+
+ Mode demo
+ Secure context —
+ In iframe —
+ Orientation events 0
+ Permission —
+ GNSS — +
+

Device orientation needs a secure top-level context. Inside an iframe it never + fires and the page falls back to the demo circuit.

+
+ +
+

Display

+
+ + + + +
+

Engine strip, indicated airspeed, pressure altitude and vertical speed are + simulated — those are node channels and no node exists yet. Attitude, heading, track, + ground speed, GPS altitude and position are real. Not an instrument. Not airworthy.

+
+ +
+
- - - diff --git a/app/mockup/sw.js b/app/mockup/sw.js index 73df329..d4a8841 100644 --- a/app/mockup/sw.js +++ b/app/mockup/sw.js @@ -8,7 +8,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ -var CACHE = "junco-pfd-v1"; +var CACHE = "junco-pfd-v2"; var SHELL = [ "./", From 5ee97b35b851c68f470e358e9c667d021ecd45bd Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 9 Aug 2026 15:16:02 +0000 Subject: [PATCH 02/15] Mockup: aircraft profiles, profile-driven layout, file-based sharing Four questions, answered in the order they actually depend on each other. **Choosing an aircraft.** A library of profiles lives on the phone, with three presets to start: the PM-2 twin, a generic single, and a four-cylinder experimental. Selecting one reconfigures the display immediately. PRD section 11 still holds: the node stays authoritative for the aircraft it is installed in. The phone holds the collection, which is the part section 11 never had to describe because it only ever considered one aircraft at a time. **Different widgets per aircraft.** The panel is generated FROM the profile rather than configured beside it. A twin gets two RPM rows and a sync bar; a single gets one and neither. A four-cylinder gets four CHT and four EGT rows and the strip re-sizes to fit. V-speeds drive the airspeed tape bands and its scale, limits drive the redlines. Nobody edits a layout, they edit an aircraft. That is design rule 5 applied to the panel. Units are display-only and every value moves in SI internally, which is the same rule ble-telemetry.md enforces on the wire. Switching a profile from mph to knots or Fahrenheit to Celsius changes the glass and nothing else. **Saving several.** Duplicate, edit, delete, all persisted locally, with the same validation the node would apply: usable fuel over capacity, stall at or above never-exceed, an engine with no CHT and no EGT. **Sync, backup, sharing.** Files, not a service. A profile exports as TOML matching spec/aircraft-profile.md, with copy, share sheet, and download, and imports from a file or pasted text. Schema version is checked and a profile from a newer build is refused rather than guessed at. No cloud, deliberately. Design rule 4 says the owner owns the data, section 17 rules out anything requiring renewal, and MAINTAINERS.md exists because the project has to outlive its maintainer. A project-run sync service is exactly the dependency that dies when nobody pays for it, and a profile carries a registration and a home field. A file in the owner's own cloud is rule 4 done properly and costs the project nothing to keep running. --- .github/workflows/mockup-apk.yml | 2 +- app/mockup/android/build.sh | 2 +- app/mockup/index.html | 778 ++++++++++++++++++++----------- app/mockup/profile.js | 279 +++++++++++ app/mockup/sw.js | 3 +- 5 files changed, 777 insertions(+), 287 deletions(-) create mode 100644 app/mockup/profile.js diff --git a/.github/workflows/mockup-apk.yml b/.github/workflows/mockup-apk.yml index 848834e..d07882c 100644 --- a/.github/workflows/mockup-apk.yml +++ b/.github/workflows/mockup-apk.yml @@ -52,7 +52,7 @@ jobs: test -f "$APK" "$BT/apksigner" verify "$APK" # The whole point of the wrapper is that the web assets ship inside it. - for f in index.html manifest.webmanifest sw.js icon-192.png icon-512.png; do + for f in index.html profile.js manifest.webmanifest sw.js icon-192.png icon-512.png; do unzip -l "$APK" | grep -q "assets/$f" || { echo "missing assets/$f"; exit 1; } done "$BT/aapt2" dump badging "$APK" | grep -q "package: name='org.junco.mockup'" diff --git a/app/mockup/android/build.sh b/app/mockup/android/build.sh index 1f1e990..aba754c 100755 --- a/app/mockup/android/build.sh +++ b/app/mockup/android/build.sh @@ -41,7 +41,7 @@ mkdir -p "$OUT/assets" "$OUT/flat" "$OUT/gen" "$OUT/classes" # The web app is the payload. Copy it in rather than symlink so the APK is # self-contained and the source of truth stays one directory up. -cp "$WEB/index.html" "$WEB/manifest.webmanifest" "$WEB/sw.js" \ +cp "$WEB/index.html" "$WEB/profile.js" "$WEB/manifest.webmanifest" "$WEB/sw.js" \ "$WEB/icon-192.png" "$WEB/icon-512.png" "$OUT/assets/" echo "==> resources" diff --git a/app/mockup/index.html b/app/mockup/index.html index 2ef6005..144ab94 100644 --- a/app/mockup/index.html +++ b/app/mockup/index.html @@ -114,6 +114,8 @@ background: #0B0F12; border: 1px solid var(--rule); border-radius: 3px; padding: 8px 10px; min-width: 78px; text-align: center; } + input.val, select.val, textarea.val { font-family: var(--mono); background:#0B0F12; color:var(--white); border:1px solid var(--rule); border-radius:3px; padding:8px; font-size:13px; } + .lbl { font-family: var(--mono); font-size:10px; letter-spacing:.1em; text-transform:uppercase; color:var(--dim); } .hint { font-size: 12px; line-height: 1.5; color: var(--dim); margin: 7px 0 0; } .hint b { color: var(--white); } .stat { font-family: var(--mono); font-size: 11.5px; line-height: 1.7; color: var(--dim); } @@ -183,7 +185,7 @@ 28 - MPH + MPH NODE PITOT @@ -192,7 +194,7 @@ 1180 - FT + FT NODE PLENUM @@ -202,7 +204,7 @@ 0 - FPM + FPM @@ -232,6 +234,68 @@
+ +
+

Aircraft

+
+ + + +
+
Name
+
Reg
+
+ Engines + CHT/eng + EGT/eng +
+
+ RPM max + CHT max + EGT max +
+
+ Vs0 + Vne + Cruise +
+
+ Fuel cap + Burn +
+
+ Units + + + + +
+
+

+

Limits and speeds are entered in the display units above. Stored in SI, because + units are a display concern and never travel with the data.

+
+ +
+

Share, back up, transfer

+
+ + + +
+ + +

A profile is a small TOML file. That is the whole sync story. Copy it, + mail it, drop it in your own cloud, hand it to another builder. There is no account and no + server, because a service that has to be paid for is the thing that kills a project when the + maintainer moves on, and a profile carries your registration and home field.

+
+

Mounting calibration

@@ -295,6 +359,7 @@

Display

+ + + + +