From a7835ac1560b566132b4078747f7fd4113f4bbda Mon Sep 17 00:00:00 2001 From: MarcelineVPQ Date: Mon, 18 May 2026 22:17:46 -0600 Subject: [PATCH] =?UTF-8?q?chore(version):=20single=20source=20of=20truth?= =?UTF-8?q?=20=E2=80=94=20release=20tag=20=E2=86=92=20FIRMWARE=5FVERSION?= =?UTF-8?q?=20macro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit revealed the Status screen hardcoded "v0.6.0" while v0.6.1 and v0.6.2 had already shipped — the version string was scattered and fell behind every release. Centralizing: - CMakeLists.txt: target_compile_definitions adds FIRMWARE_VERSION="${VERSION}" so the existing -DVERSION=$tag flag passed by release.yml propagates into C++ as a compile-time macro. Local builds without -DVERSION get "dev" — a deliberate "this is not a release build" tell on the Status header. - src/oled.cpp render_screen() now renders "DS5 Bridge " FIRMWARE_VERSION via string-literal concatenation. The only place a version literal exists in source is now the macro at the link layer. - CLAUDE.md gets a "Versioning — single source of truth" section documenting the release ritual (CHANGELOG bump → tag → push → gh release create → CI builds + propagates) and naming the WEB_REPO_DISPATCH_PAT follow-up that's still pending. Web preview is updated separately to read firmware-latest.json at runtime — same single source of truth, different distribution path (the GitHub API). Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 27 +++++++++++++++++++++++++++ CMakeLists.txt | 8 ++++++++ src/oled.cpp | 5 ++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index eb54d70..b180e85 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -95,6 +95,33 @@ These are non-obvious from the code; they cost time when forgotten. - **Inactivity-disconnect uses `packet[3..12]`** in the L2CAP interrupt data path (`src/bt.cpp:l2cap_packet_handler`). It's looking at sticks and DPad/buttons to decide "idle." Don't touch those bytes' layout without updating the heuristic. - **The 0x36 BT audio packet layout is load-bearing — speaker + HD haptic actuators silently die without the SetStateData sub-report.** Upstream commit `3a31bd7` (May 2026, "refactor: add SetStateData and audio send priority") moved the `0x10` SetStateData block out of every audio frame and into a one-time L2CAP-open setup. The DualSense hardware requires that sub-report (specifically the `0x7f 0x7f` Headphones + Speaker volume bytes) at `pkt[11..75]` of every `0x36` frame, or the actuators stop producing output even though USB and BT byte counts look fine. Our fork keeps `state_data[63]` in `src/audio.cpp` and re-asserts it on every frame; the pre-3a31bd7 packet layout is: state_data at `pkt[11..75]`, haptic at `pkt[76..141]`, speaker format at `pkt[142]`, opus payload at `pkt[144..343]`. If you rebase onto an upstream that has the refactor and don't preserve this restoration, speaker + HD haptics silently break. The `scripts/test_speaker.sh` helper + the `USB aud / BT 0x32` counters on the OLED Diagnostics screen are the regression tripwire — if bytes are flowing but you hear nothing, look at packet contents not flow. Upstream PR #93 is tracking a proper unified fix; ours is the pre-refactor revert applied just to `audio.cpp`. Same fix was shipped independently by `loteran/DS5Dongle` (commit `c7a8d3c`). +## Versioning — single source of truth + +The release tag is the **only** place the version is written. Everything else flows from it: + +- **Release tag** (e.g. `v0.6.2-oled-edition`) → created with `git tag` then `gh release create`. +- **`.github/workflows/release.yml`** picks the tag up as `$FIRMWARE_VERSION` and passes it to CMake via `-DVERSION="$FIRMWARE_VERSION"`. +- **`CMakeLists.txt`** exposes it to C++ as a compile-time macro: + ```cmake + target_compile_definitions(ds5-bridge PRIVATE FIRMWARE_VERSION="${VERSION}") + ``` + Local builds without `-DVERSION=...` get the default `"dev"` — that's a deliberate visual signal so an untagged build is obvious on the OLED Status header. +- **`src/oled.cpp` `render_screen()`** renders `"DS5 Bridge " FIRMWARE_VERSION` for the Status screen header. No string literal for the version exists anywhere else in the firmware source. +- **Web preview** (`DS5Dongle-OLED-Config-Web`) reads the same tag at runtime from `public/firmware-latest.json`, which CI bundles in `.github/workflows/deploy.yml`'s "Bundle latest firmware UF2 from GitHub releases" step (it pulls from `MarcelineVPQ/DS5Dongle-OLED-Edition/releases/latest` via the GitHub API). `OledEmulator.tsx` fetches that JSON on mount and writes the short form (suffix `-oled-edition` stripped) into `state.firmwareVersionLabel`, which `screens.ts:renderStatus` consumes. + +**The release ritual** is therefore: + +1. Update `CHANGELOG.md` — move `[Unreleased]` content into a new `[X.Y.Z-oled-edition]` section dated today. +2. Commit the CHANGELOG bump. +3. `git tag -a vX.Y.Z-oled-edition -m "..."` +4. `git push origin master && git push origin vX.Y.Z-oled-edition` +5. `gh release create vX.Y.Z-oled-edition -R MarcelineVPQ/DS5Dongle-OLED-Edition --title "vX.Y.Z — OLED Edition" --notes "..."` +6. CI builds the UF2s (~5–7 min), uploads them with SHA256SUMS, edits the release notes to append checksums, and (when `WEB_REPO_DISPATCH_PAT` is configured on the firmware repo — currently unset, see below) fires a `repository_dispatch` event to the web repo to refresh `firmware-latest.json`. Without the secret, the next push to the web repo's `master` does the refresh instead. + +There is **no other place** to edit the version. If you find a hardcoded version string in source (`"v0.6.0"`, `"v0.5.4"`, etc.), it's a bug — replace it with the macro / JSON lookup. + +**Known follow-up:** `WEB_REPO_DISPATCH_PAT` secret is unset on the firmware repo, so the firmware-release → web-rebuild dispatch is currently silently no-op'd (peter-evans/repository-dispatch with continue-on-error). The web bundle still updates via push events to the web repo's master, but not automatically on every firmware release. + ## Git / branch model - **`master` (origin)** = `MarcelineVPQ/DS5Dongle-OLED-Edition` (this fork's primary branding, what users download). diff --git a/CMakeLists.txt b/CMakeLists.txt index 349d01a..80c876a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,6 +146,14 @@ if(WAKE_DEBUG) endif() set(VERSION "dev" CACHE STRING "Program version string") +# Expose VERSION to firmware C++ as FIRMWARE_VERSION so src/oled.cpp's +# Status header always reflects the release tag (set by release.yml via +# -DVERSION="$FIRMWARE_VERSION"). Local builds without -DVERSION show +# "dev" — visible signal that you're not on a tagged release. +target_compile_definitions(ds5-bridge PRIVATE + FIRMWARE_VERSION="${VERSION}" +) + set_target_properties(ds5-bridge PROPERTIES OUTPUT_NAME "ds5-bridge-oled") pico_set_program_name(ds5-bridge "ds5-bridge-oled") pico_set_program_version(ds5-bridge "${VERSION}") diff --git a/src/oled.cpp b/src/oled.cpp index a686a2a..d00286b 100644 --- a/src/oled.cpp +++ b/src/oled.cpp @@ -479,7 +479,10 @@ __attribute__((noinline)) void render_screen() { const bool connected = bt_is_connected(); - draw_text(kContentX, 0, "DS5 Bridge v0.6.0"); + // FIRMWARE_VERSION is set via CMake from -DVERSION=... on the build + // command line (release.yml passes the tag name). Local builds get + // "dev" so a non-tagged build is visible at a glance. + draw_text(kContentX, 0, "DS5 Bridge " FIRMWARE_VERSION); draw_icon(120, 0, connected ? kIconLinkOn : kIconLinkOff, 8, 8); if (connected) {