Skip to content

release 3.1.0: raise the OTA image cap 320 KiB → 408 KiB - #124

Merged
TheAngryRaven merged 1 commit into
masterfrom
claude/ota-flash-rebalance-master
Aug 3, 2026
Merged

release 3.1.0: raise the OTA image cap 320 KiB → 408 KiB#124
TheAngryRaven merged 1 commit into
masterfrom
claude/ota-flash-rebalance-master

Conversation

@TheAngryRaven

Copy link
Copy Markdown
Owner

Ports the flash-split rebalance from beta (#122 — merged and flashed clean on hardware) and cuts it as 3.1.0.

The change

staging  [0x8E000, 0xF4000) = 417,792 B = 408 KiB   FW_MAX_IMAGE_SIZE
app      [0x27000, 0x8E000) = 421,888 B = 412 KiB   >= the cap

App and staging share one 820 KiB stretch and both must hold the image, so the largest installable image is half the span. The old split was 320 KiB staging against 500 KiB app — capping OTA at 320 KiB while ~180 KiB of app region sat where no legal image could reach it. Two constants, plus static_asserts for page alignment and app-region fit. Nothing about the apply sequence, the FW* protocol, or the CRC moves.

Why MINOR and not PATCH

The device now accepts OTA images it would previously have rejected with FWERR:SIZE — new device behavior, backwards compatible. Per the semver policy in the changelog header that's MINOR, so 3.0.23.1.0.

Upgrade safety

Safe from any earlier build. Staging is chosen at apply time from the installed firmware's constants, so a 3.0.x device stages at the old 0xA4000 and installs 3.1.0 normally. The new app region also ends below that old staging base, so a new-layout image can never collide with an old device's staging area.

Devices still on 3.0.x keep their own 320 KiB limit until they take this release. A future build over 320 KiB can't be sent to one over the air — it would answer FWERR:SIZE — and would need USB FWDFU → UF2 instead. CI now warns when a build crosses that line so a fleet split shows up before release rather than in the field.

Scope note

Only the OTA gate block is taken from beta's compile-sketch.yml. Deliberately left behind:

  • the channel-selection rewrite (head_ref == 'BETA') — only affects BETA-targeted PRs, master builds identically either way
  • -DDOVES_DISABLE_DEBUG — a DovesLapTimer BETA-branch feature; master pins v4.2.0, which doesn't have it, so the flag would be inert

Both come across naturally when BETA merges to master. Also note docs/plans/ doesn't exist on master yet, so the "plan 0004" reference in the gate's error message resolves against the BETA branch for now — kept verbatim rather than reworded so the two workflow files don't diverge further.

Changes

  • BirdsEye/firmware_ota.ino — the two constants, rewritten memory-map comment, two static_asserts
  • .github/workflows/compile-sketch.ymlOTA_IMAGE_MAX_BYTES 327680 → 417792, new non-fatal OTA_LEGACY_MAX_BYTES fleet-split warning
  • BirdsEye/project.hFIRMWARE_VERSION 3.0.23.1.0
  • CHANGELOG.md[3.1.0] release section (the crossing-pattern entry from perf: generate the crossing animation instead of storing 2 KB of bitmaps (backport of #119) #123 rolls into it) + compare links
  • CLAUDE.md — constants table

Test plan

  • Host tests on this branch: 271 cases / 4,192 assertions, all pass
  • static_asserts negative-tested — a 412 KiB cap trips the app-region-fit assert, 407 KiB trips the page-alignment one
  • Arithmetic exact: 421,888 + 417,792 = 839,680 = the full span
  • firmware_ota.ino delta vs master confirmed to be only the rebalance (nothing else rode along from beta)
  • Workflow YAML parses; channel logic confirmed unchanged on master
  • Already validated on real hardware via the beta channel
  • CI green, then tag v3.1.0 to trigger release.yml (publishes per-board .hex / .uf2 / .zip + the prod OTA manifest)

Generated by Claude Code

Ports the flash-split rebalance from the beta channel (PR #122, merged and
flashed clean on beta hardware) and cuts it as 3.1.0.

The app and the OTA staging region share one 820 KiB stretch
([0x27000, 0xF4000) = 839,680 B) and BOTH must be able to hold the image:
the incoming one is staged up top, then copied down over the app. So the
largest installable image is half the span. The split was lopsided --
320 KiB staging against 500 KiB of app region -- which capped OTA at
320 KiB while leaving ~180 KiB of app region no legal image could reach.

Staging base moves 0xA4000 -> 0x8E000 for an even, page-aligned split:

    staging [0x8E000, 0xF4000) = 417,792 B = 408 KiB  (FW_MAX_IMAGE_SIZE)
    app     [0x27000, 0x8E000) = 421,888 B = 412 KiB  (>= the cap)

Two constants, plus static_asserts for page alignment and app-region fit.
No change to the apply sequence, the FW* protocol, the CRC, or the web
client (which never enforced a cap of its own -- it relies on FWERR:SIZE).

MINOR rather than PATCH: the device now accepts OTA images it previously
rejected with FWERR:SIZE, which is new backwards-compatible behavior.

Upgrading to 3.1.0 is safe from any earlier build -- staging is chosen at
apply time from the INSTALLED firmware's constants, so a 3.0.x device
stages at the old 0xA4000 and installs this normally, and the new app
region ends below that old base so the two can never collide. Devices
still on 3.0.x keep their own 320 KiB limit until they take this release;
CI now warns when a build crosses that line so a fleet split shows up
before release instead of in the field.

Scope note: only the OTA gate block is taken from the beta workflow. The
channel-selection rewrite and -DDOVES_DISABLE_DEBUG stay behind -- the
latter is a DovesLapTimer BETA-branch feature and master pins v4.2.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnTP6BdA9xjLR5hSWE9frb
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Coverage — host-testable units

📂 Overall coverage

Metric Coverage
Lines 🟢 799/809 (98.8%)
Functions 🟢 83/83 (100.0%)
Branches 🟢 622/691 (90.0%)

📄 File coverage

File Lines Functions Branches
BirdsEye/camera_fsm.cpp 🟢 222/230 (96.5%) 🟢 20/20 (100.0%) 🟡 126/144 (87.5%)
BirdsEye/crc32.cpp 🟢 30/30 (100.0%) 🟢 4/4 (100.0%) 🟢 24/24 (100.0%)
BirdsEye/crossing_pattern.cpp 🟢 15/15 (100.0%) 🟢 1/1 (100.0%) 🟢 12/12 (100.0%)
BirdsEye/dovex_header.cpp 🟢 100/101 (99.0%) 🟢 6/6 (100.0%) 🔴 61/92 (66.3%)
BirdsEye/filename_validator.cpp 🟢 14/14 (100.0%) 🟢 1/1 (100.0%) 🟢 30/30 (100.0%)
BirdsEye/gps_stats.cpp 🟢 25/25 (100.0%) 🟢 3/3 (100.0%) 🟢 8/8 (100.0%)
BirdsEye/gps_status_page.cpp 🟢 25/25 (100.0%) 🟢 3/3 (100.0%) 🟢 24/24 (100.0%)
BirdsEye/gps_time.cpp 🟢 45/45 (100.0%) 🟢 6/6 (100.0%) 🟢 30/32 (93.8%)
BirdsEye/gps_validation.cpp 🟢 24/24 (100.0%) 🟢 2/2 (100.0%) 🟢 66/66 (100.0%)
BirdsEye/haversine.cpp 🟢 8/8 (100.0%) 🟢 1/1 (100.0%) ⚫ 0/0 (0.0%)
BirdsEye/insta360_protocol.cpp 🟢 140/140 (100.0%) 🟢 16/16 (100.0%) 🟡 86/98 (87.8%)
BirdsEye/lap_format.cpp 🟢 18/18 (100.0%) 🟢 1/1 (100.0%) 🟢 9/9 (100.0%)
BirdsEye/sat_bars.cpp 🟢 33/33 (100.0%) 🟢 2/2 (100.0%) 🟢 51/54 (94.4%)
BirdsEye/sd_access_policy.cpp 🟢 9/9 (100.0%) 🟢 3/3 (100.0%) 🟢 18/18 (100.0%)
BirdsEye/sd_format_page.cpp 🟢 25/25 (100.0%) 🟢 3/3 (100.0%) 🟢 25/26 (96.2%)
BirdsEye/sensoregg_protocol.cpp 🟢 39/40 (97.5%) 🟢 7/7 (100.0%) 🟢 25/26 (96.2%)
BirdsEye/tach_filter.cpp 🟢 15/15 (100.0%) 🟢 3/3 (100.0%) 🟡 7/8 (87.5%)
BirdsEye/wake_cause.cpp 🟢 12/12 (100.0%) 🟢 1/1 (100.0%) 🟢 20/20 (100.0%)

@TheAngryRaven
TheAngryRaven merged commit 4716a90 into master Aug 3, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants