Skip to content

feat(ota): ota branch channel switch (dev) - #63

Merged
agessaman merged 17 commits into
observer-firmware-devfrom
merge/ota-branch-into-observer-firmware-dev
Oct 3, 2026
Merged

agessaman merged 17 commits into
observer-firmware-devfrom
merge/ota-branch-into-observer-firmware-dev

Conversation

@agessaman

Copy link
Copy Markdown
Owner

Adds ota branch [prod|beta|default] (aliases stable, dev) so an observer can move between the production and beta OTA channels without a cable flash. Ported from Dutch-MeshCore/MeshCore PR #7 (Elektr0Vodka; the 5 ported commits keep their author), adapted for our channels.

The feature lives on feat/ota-branch-switch, branched from the prod/dev merge base 47c80fecf and merged into both channels. The commits are shared history, so the later dev→prod merge picks up no conflicts from this feature (simulated: 0 conflicts).

Behaviour

  • ota branch reports the selected channel, the build's own channel and the manifest base. A new setting is saved in /prefs.json (ota_ch); ota update then pulls from that channel.
  • Both channel bases are built into every observer binary (OTA_MANIFEST_BASE_STABLE / _DEV, injected by build.sh and both workflows).
  • Another channel's image always counts as an update, even when it was built from the same commit, because build counters are per channel.
  • The deferred flash uses the channel that ota update checked, so an ota branch sent in the meantime cannot retarget it.

Safety gate for channel switches

Each image carries an ota-compat:<gen>[+eth] tag. On a channel switch, the node writes the image without rebooting, then reads the target's tag (bounded by the image length). It points boot back at the running image unless the target's state generation is at least the node's own and the target keeps the node's transports.

  • prod = gen 1 (/mqtt_prefs), dev = gen 2 (/mqtt.json). Until dev merges into prod at 1.18, beta→prod is refused and prod→beta works.
  • +eth = MQTT over Ethernet (NETWORK_PREFER_ETHERNET, the M7). A switch that would drop Ethernet is refused.

CI

The old strings/grep checks would reject every build now that each binary contains both URLs. They are replaced by scripts/verify_ota_channel.py --expect prod|beta, which reads the ota-base-native|stable|dev: and ota-compat: tags from every .bin in out/, not just one ELF.

Testing

  • build.sh builds of Heltec V3 repeater and V4 room server observers on both channels, plus the M7 observer on dev. verify_ota_channel.py passes for the right channel and fails for the wrong one and for a mixed directory.
  • The on-device tag-scan code, run on the host over real .bin files, reads prod gen 1, dev gen 2 and M7 2+eth.
  • native_ota_channel 12/12; dev native 552/552; serializer round-trip tests for ota_ch.
  • Not yet tested on hardware: bench check a V3 both ways (prod→beta flashes; beta→prod is refused and stays on the running image).

Ship to both channels together: a node switched to a channel whose build lacks ota branch cannot switch back without a cable flash.

The dev merge also sets OTA_STATE_GEN to 2 (dev-only commit) and resolves conflicts against NetworkLink, stopOTAUpdate and the MQTT-stop alert text.

Elektr0Vodka and others added 17 commits October 3, 2026 15:17
Build counters are per channel, so a cross-channel build number is not
comparable: a switch to a channel with a lower counter reported up to date
and never happened. Off the native channel, update unless the hash matches.
…iases

Report the selected channel and the build's own channel, tag each baked-in
base for CI, and list ota branch in the CLI docs and portal autocomplete.
Every observer build now carries both channel URLs, so checking for a URL's
presence or absence can no longer tell prod from beta (and the old checks would
reject every build). Read the ota-base-* tags from every .bin in out/ instead
of a single ELF, with URLs shared by build.sh and both workflows.
…rver-firmware-dev

# Conflicts:
#	.github/workflows/build-observer-firmwares.yml
#	examples/simple_repeater/MyMesh.cpp
#	examples/simple_room_server/MyMesh.cpp
#	src/helpers/ESP32Board.cpp
#	src/helpers/ESP32Board.h
#	test/test_config_serializer/test_config_serializer.cpp
…mmit

Prod and beta are often built from one commit (e.g. after a dev->prod merge),
so a hash match does not mean the images are the same: the native base differs.
After the flash the target channel is native, so this cannot loop.
The deferred flash re-read the channel preference ~2.5 s after ota update
acknowledged its target; capture it when the update is scheduled.
A switch can land on an older build: e.g. prod cannot read dev's /mqtt.json,
and prod's M7 image has no Ethernet. Each image carries an ota-compat tag
(state generation + transports). A switch is flashed without rebooting, the
new image's tag is read (bounded by its length), and boot is pointed back at
the running image unless the target keeps this node's state and transports.
CI now requires the tag in every published binary.
…rver-firmware-dev

# Conflicts:
#	examples/simple_repeater/MyMesh.cpp
Builds that only read /mqtt_prefs (gen 1) are refused as channel-switch
targets until they can read /mqtt.json.
Every image also holds "ota-compat:" with no value (the search string);
finding it first made every channel switch look untagged.
@agessaman

Copy link
Copy Markdown
Owner Author

Bench results: Heltec V3 (2026-10-03)

The build was the #63 Heltec_v3_room_server_observer_mqtt built by build.sh the way the beta CI builds it: native channel beta, build 0. It was flashed to the app partition only.

Test Result
ota branch report, prod/beta/default, stable/dev aliases, bad input (production, ota branchx), setting survives a reboot pass
ota check on prod: v1.17.1.0 -> v1.17.1.4 (channel switch) pass
ota check on beta (native): -> v1.17.1.22 (22 behind) pass
ota update to prod: downloaded all of v1.17.1.4, then ERR: channel switch refused: v1.17.1.4 (c2c4cb5) has no compat tag; cable flash; the bridge resumed pass
After a power-on reboot it still boots the bench build, so pointing boot back at the running image worked pass
ota branch default + ota update: normal update and reboot into live beta v1.17.1.22 pass

Not yet tested: a successful channel switch. That needs a tagged image on a channel; it gets tested once #63 publishes to beta, by switching a #62 build prod→beta.

@agessaman
agessaman merged commit a928498 into observer-firmware-dev Oct 3, 2026
1 check 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