PRD revisions 3–5, copyleft relicense, BLE telemetry spec, and the PFD layout mockup - #1
Merged
Merged
Conversation
Traffic becomes a channel with pluggable backends, following the pattern section 9 already uses for fuel. The node is explicitly outside the path for every backend, so design rule 1 holds without needing to be defended. The section 4 non-goal is re-scoped from "ADS-B receive" to "a Junco-built ADS-B receiver." Consuming GDL90 costs the node nothing, because the receiver belongs to the pilot and the listener is a socket in the app. That is a different feature from emitting it, which stays in phase 2. A Linux single-board variant is admitted as a supported second build. The 0 to 50C commercial rating is recorded as an enclosure and airflow requirement rather than a disqualifier: the aircraft already routes a pitot line to the node's location, so ram air is available there. The distinctions that survive the thermal work, the boot medium and the unclean-stop risk, become mandatory requirements on that build instead. New sections 22 and 23. Revision history renumbered to 24. Also recorded: - Why the ESP32-S3 cannot decode ADS-B, as bandwidth arithmetic - What ADS-B In does not show a Part 103 aircraft, as a display requirement - Internet-sourced traffic may never alert - Four new open items in section 15, instrumented during phase 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W8P8RZQELMR3NQ7Zacx79v
The README described the revision 1 product: GDL90 out as a headline feature, a $250 parts target, and seven design rules. Revision 2 demoted GDL90 to phase 2, cut the target to $180, and added the source-tagging rule. Fixed all three. app/README.md claimed iOS users get GDL90 into ForeFlight, which revision 2 gave up, and led with Wi-Fi socket binding as the reason the client is native. BLE reachability is the first reason now; the binding still matters for the on-demand AP but that AP is never used in flight. firmware/README.md had phase 0 watching for GNSS loss on a node that no longer has GNSS. Now watches BLE dropouts and logs enclosure temperature, since that measurement decides the Pi-class question and the hardware is already running for the EMI work. Spec gaps found while checking consistency: - log-format.md required design rule 7 header content but never required design rule 8 source tags on records, and said nothing about the clock offset that makes the node and phone logs merge - aircraft-profile.md did not say what stays out of the profile, which revision 3 made load-bearing: traffic backend selection is app-side because a receiver lives in the flight bag, not on the airframe - spec/README.md listed ble-telemetry.md as merely not started rather than as the document blocking implementation Also reconciled the PRD firmware license to MIT, matching LICENSE, the README, and MAINTAINERS.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W8P8RZQELMR3NQ7Zacx79v
Relicense code from MIT to GPL-2.0-or-later and hardware from CERN-OHL-P-2.0 to CERN-OHL-S-2.0. Specifications stay CC-BY. The purpose of the project is to put this capability into the world permanently, and a permissive license lets a better funded fork take the work closed and outrun the original. Copyleft prevents that at no cost to any use the project cares about: building, selling, forking and competing all remain permitted. GPL v2 or later specifically, because it matches MakerPlane. Their source headers read "version 2 of the License, or (at your option) any later version," so code moves in both directions between this project and FIX-Gateway or pyEFIS without relicensing, and GPLv3 stays reachable if ArduPilot code is ever wanted for the v2 bus. Specifications stay permissive on purpose. They are meant to be implemented by anyone in anything, and a protocol nobody may adopt freely does not outlive its implementation. CAN-FIX is Creative Commons for the same reason. New section 24 positions Junco as the engine and air data front end for the MakerPlane stack rather than a parallel instrument system. Junco stays a separate project and contributes a plugin: FIX-Gateway is explicitly protocol-agnostic, so a Junco source is the shape it already expects, and that reaches pyEFIS as a panel display. Six items leave Junco's scope because FIX-Gateway already has them, among them the traffic work from revision 3, the annunciator, and the second recording. What remains is the part nobody has built: a two-stroke with CDI ignition and no ECU. The existing open engine plugins are Grand Rapids EIS and MegaSquirt, both of which assume an engine with a computer in it. Section 20 stops being a business plan. The project ends at v2; a boxed product and an autopilot are described so the architecture does not foreclose them, and are explicitly left to anyone who wants them. Section 19 gains rows for AP_Periph, which defeats half the original ArduPilot rejection by being genuinely publish-only but fails v1 on BLE, STM32-only, and thermocouples; and the DroneCAN versus CAN-FIX question, recorded and deliberately not decided since v1 has no bus. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W8P8RZQELMR3NQ7Zacx79v
This is the document that was blocking firmware and app work. Every value the pilot sees crosses this link, and PRD success criterion 4 requires a stranger to write a second client from it without reading the Android source. Revision 4 made that requirement easier to hold honestly: there are now two expected reference clients, the Android app and a FIX-Gateway plugin in Python. A rule only one of them can satisfy is the wrong rule. Settled here: - Four notify characteristics, one per rate class, taken from the channel table in PRD section 8 rather than one characteristic per channel - A common header on every notification so an unrecognised characteristic can be discarded safely - Per-channel value, source, status triples, with the status bit authoritative for validity rather than the value - Connection interval, MTU negotiation, and notifications over indications - Monotonic node time with the client writing wall clock on connect, and published timestamps explicitly not corrected afterward - Exactly two writable characteristics, neither able to influence a published value, which is design rule 1 expressed in the protocol The source tag enumeration is defined here and referenced from log-format.md rather than restated, because two copies of an enumeration diverge. It allocates phone-side sources too, which never cross this link but do appear in the log. The mixed-derivation tag is load-bearing: a value combining node and phone channels inherits the weaker assurance of the two, and vertical speed may never carry it. Eight items are recorded as not yet specified, including the UUID base, which must be generated once and frozen before any unit ships, and the two-clients-at-once case that section 24 turned from hypothetical into real. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W8P8RZQELMR3NQ7Zacx79v
Fifteen questions sitting in "not yet specified" lists were decidable at a desk and were blocking firmware. A question that can be answered without data, and is instead left open, becomes a decision someone makes accidentally while implementing. The three irreversible ones first, since each costs field hardware to change later: - BLE UUID base generated and frozen, with the allocation slot scheme recorded so future versions can add characteristics but never redefine an existing one - Log is a preallocated file on FAT32, not a raw partition. Design rule 4 says the owner owns the data, and a file means they pull the card and read it on any laptop. Raw-sector recovery already handles the interrupted-write case that usually argues for a partition - Profile hash is SHA-256 over the bytes actually stored, not over a re-serialization, so changing the serializer cannot silently invalidate every cached copy and log header referencing an unmodified profile Also closed: integer SI channel encoding with no floats and no display units on the wire, which drops the redundant NaN convention since the status bit is now the only validity signal; whole-profile atomic configuration writes; "in flight" defined as the log file being open, which is one condition the node already tracks; node-side transition counts, because a client that connected late would count wrong; bonding required to write but not to subscribe, putting the security boundary on the design rule 1 line; two concurrent clients with first-clock-write- wins; TOML source plus a compiled binary form, which resolves the tension between hand-editability and not parsing text in flight firmware; schema versioning that refuses an unknown version rather than guessing; validation rules; record type allocation; ArduPilot-style descriptors; and seizure precursors publishing a raw rate rather than a judgment. Log record payloads are now byte-identical to BLE characteristic payloads, with the log adding only magic, type and CRC. One serializer instead of two removes the category of defect where the link and the card disagree about what a flight contained. Design rule 9 added: advisory-only data never raises an alert. This was local to section 22 in revision 3 and generalises past traffic, and a rule living inside one section gets forgotten by the next section that needs it. New docs/open-questions.md indexes what remains, separated into what needs a measurement, what is blocked on a prior decision, and what is merely not urgent. Spec open items drop from 19 to 10. Also fixes the status line, which was left at revision 3 when revision 4 landed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W8P8RZQELMR3NQ7Zacx79v
A primary flight display laid out the way pilots already read one: attitude centre, airspeed tape left, altitude tape right, VSI, HSI below, engine strip down the edge. It exists to settle layout questions before anyone writes an APK, because a tape range or a band order is cheap to change here and expensive once there is an app. It answers one question already. Design rule 8 as twenty small source tags reads as noise on a real layout. The answer this proposes is the one real avionics already use: annunciate the source on the instrument. PHONE AHRS ADVISORY sits on the attitude indicator, cyan marks node-plumbed channels, and magenta marks GPS-derived data, which is a convention every pilot already reads without thinking. Same rule, native idiom, almost no visual cost. It half-answers a second. The attitude is low-pass filtered because raw phone fusion is too jittery to watch sitting still on a desk. Section 15 asks whether phone attitude survives a vibrating airframe; this is an early data point and not an encouraging one. Runs with no sensors at all: a scripted circuit on load, and drag anywhere to fly it by hand. That is enough to judge layout and arguably better than real sensors, since it holds sustained bank angles nobody produces waving a phone around. Real attitude needs a top-level HTTPS document, because device orientation is gated by Permissions Policy and never fires in a cross-origin iframe. Packaged as a PWA with a service worker, so once installed to the home screen it runs fullscreen, landscape-locked, screen awake, and offline. A flight instrument that stops working when the cell signal drops is not an instrument, and at 800 feet over a field the signal drops routinely. Section 19's PWA row is sharpened rather than reversed. Web Bluetooth reaches the node on Android and would genuinely work, so the transport objection now applies to iOS alone, and the socket binding objection scopes to the on-demand Wi-Fi AP since revision 2 moved the in-flight link to BLE. What still decides it is duration: no background operation, a gesture per connection, and the link drops when the page suspends. A second row records that a PWA is adopted as a prototyping vehicle, which is a different question from the shipping client.
The wrapper exists for one reason. Device orientation and geolocation are only available to a secure context, so a WebView loading file:// would silently kill exactly the sensors the app is for. It serves the bundled assets over an https:// origin and intercepts those requests locally. Nothing leaves the device and no network is used. Built with the raw SDK tools rather than Gradle. One activity, no dependencies, nothing to resolve, so the Android Gradle Plugin would add a large download and a version matrix to a build that is otherwise six commands. A real client would use Gradle; this is not a real client. Two things worth recording for whoever builds this next: - build-tools 34.0.0 does not work on JDK 21. Its d8 fails on every class with "Cannot invoke String.length() because <parameter1> is null" while writing the dex. build-tools 35 is fine. The script now selects 35 or newer and says so if it cannot find one. - Do not pass file lists to d8 via @argfile. A trailing newline arrives as an empty argument and produces the same opaque NPE for a different reason. The APK is deliberately not committed. .gitignore already excluded *.apk and that decision stands, so the workflow is where the APK comes from: every run uploads it as an artifact, and a mockup-v* tag attaches it to a release. The workflow also checks the APK is signed, declares the right package, and actually contains the web assets, since a wrapper that ships without its payload would still build and install cleanly. This is the repository's first CI.
allenmcghan
marked this pull request as ready for review
August 9, 2026 13:58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Seven commits. Started as a comparison against the Levil iLevil / BOM line, ended up changing what the project is, and finishes with a working layout prototype and the repository's first CI.
Important
This relicenses the project. Code moves MIT →
GPL-2.0-or-later, hardware moves CERN-OHL-P-2.0 →CERN-OHL-S-2.0. Specifications stay CC-BY. Read that section first — it is the least reversible thing here.1. Relicensing to copyleft
GPL-2.0-or-laterCERN-OHL-S-2.0The purpose of the project is to put this capability into the world permanently, and a permissive license lets a better-funded fork take the work closed and outrun the original. Copyleft prevents that at no cost to any use the project cares about — building, selling, forking and competing all remain permitted.
GPL v2 or later specifically, because it matches MakerPlane. Verified from their source headers:
version 2 of the License, or (at your option) any later version. That is GPL-2.0-or-later, not 2.0-only, so code moves in both directions without relicensing and GPLv3 stays reachable if ArduPilot code is ever wanted for the v2 bus.Specifications stay permissive deliberately. A protocol nobody may adopt freely does not outlive its implementation. CAN-FIX is Creative Commons for the same reason.
LICENSEandLICENSE-HARDWAREare replaced with the canonical upstream texts, verbatim.2. MakerPlane as a neighbour, not a competitor (new section 24)
Junco stays a separate project and contributes a plugin. FIX-Gateway is explicitly protocol-agnostic, so a Junco source is the shape it already expects, and that reaches pyEFIS as a panel display.
Six items leave Junco's scope because FIX-Gateway already has them: traffic (Stratux plugin), the second recording, annunciation, panel display, multi-source voting, and Pi baro/IMU.
What remains is the part nobody has built: a two-stroke with CDI ignition and no ECU. The existing open engine plugins are Grand Rapids EIS and MegaSquirt, and both assume an engine with a computer in it.
Section 20 stops being a business plan. The project ends at v2.
3. Traffic as a pluggable channel (new section 22)
The app sees targets, not a method, and the node is outside the path for every backend. Recorded as a display requirement rather than a footnote: a traffic screen must not imply completeness. Part 103 aircraft mostly carry no ADS-B Out, TIS-B only uplinks inside a service volume some other equipped aircraft triggers, and internet feeds have no coverage below roughly 1000–2000 ft AGL. The traffic most likely to conflict with a powered parachute at 800 feet is the traffic least likely to appear.
4. Compute platform opened to a Pi-class build (new section 23)
The 0–50°C rating is an enclosure and airflow requirement, not a disqualifier — the aircraft already routes a pitot line there, so ram air is available. Two distinctions survive the thermal work and become mandatory on that build: read-only root with the log on a separate card, and a supercapacitor sized for an orderly stop.
5.
spec/ble-telemetry.md— the document that was blocking codeFour notify characteristics, one per rate class; a common header so an unrecognised characteristic can be discarded safely;
value, source, statustriples; monotonic node time with published timestamps explicitly not corrected after a clock write; and exactly two writable characteristics, neither able to influence a published value — design rule 1 expressed in the protocol rather than asserted alongside it.The source tag enumeration is defined here and referenced from
log-format.mdrather than restated. Tag0x41, mixed node-and-phone derivation, is load-bearing: such a value inherits the weaker assurance of its inputs, and vertical speed may never carry it.6. Revision 5 — closing the open questions
Fifteen questions were decidable at a desk and blocking firmware. The three irreversible ones first:
1761601a-XXXX-4e69-b9a1-b45cf63c7638Two came out better than proposed. Integer SI encoding made the NaN convention redundant, so it was deleted — the status bit is now the only validity signal. And log payloads are byte-identical to BLE payloads, so the node serializes once and the link and the card cannot disagree about what a flight contained.
Design rule 9 added — advisory-only data never raises an alert. New
docs/open-questions.mdindexes what remains. Spec open items drop 19 → 10.7. Doc resync
Seven files still described the revision 1 product. Two were live ambiguities rather than stale text:
log-format.mdnever required source tags on records, so a compliant logger could have shipped with none; and §22 didn't say where traffic backend selection lives (resolved app-side — a receiver is in the flight bag, not on the airframe).8. PFD layout mockup, an APK, and the first CI
app/mockup/is a primary flight display in the standard glass-panel layout — attitude centre, speed tape left, altitude tape right, VSI, HSI below, engine strip down the edge. It is not the client and not on the path to being it; it exists so layout questions get answered before anyone writes an APK.It already answered one. Design rule 8 as twenty small source tags reads as noise. The answer it proposes is the one real avionics already use — annunciate the source on the instrument:
PHONE AHRS · ADVISORYon the attitude indicator, cyan for node-plumbed, magenta for GPS-derived. Same rule, native idiom, almost no visual cost.It half-answered another. The attitude needed low-pass filtering to be watchable sitting still on a desk. §15 asks whether phone attitude survives a vibrating airframe; that is an early data point and not an encouraging one.
Packaged as a PWA (offline via service worker, installs fullscreen and landscape-locked) and wrapped in an Android APK. The wrapper exists for exactly one reason: sensors require a secure context, so a WebView on
file://would silently kill them. It serves bundled assets over anhttps://origin and intercepts locally — nothing leaves the device.The APK is not committed.
.gitignorealready excluded*.apkand that decision stands..github/workflows/mockup-apk.ymlis where it comes from: artifact on every run, release asset on amockup-v*tag. The workflow also checks the APK contains its web assets, since a wrapper shipped without its payload would still build and install cleanly. First run passed in 33 s.Two build notes recorded in the script for whoever hits them: build-tools 34.0.0's
d8fails on JDK 21 on every class with an opaqueString.length()NPE, and passing file lists tod8via@argfileproduces the identical error for a different reason (trailing newline → empty argument).§19's PWA row is sharpened rather than reversed — Web Bluetooth reaches the node on Android, so the transport objection now applies to iOS alone; what still decides it is duration, since a web page has no background operation and drops the link when suspended.
Section numbering runs 1–25 contiguous after two renumberings. Design rules match at nine across
README.mdanddocs/prd.md.Needs the maintainer:
ESP32-S31appears three times and refers to no Espressif part that exists.Phase 0 was never blocked — an ESP32, one baro, a breadboard, ground runs. Adding one thermocouple and a logging laptop closes four of the twelve open measurements at once, including both thermal rows that decide the Pi-class build.