OBC OpenBikeComputer / docs

Firmware updates

OpenBikeComputer uses one application slot and a 32 KB bootloader. The update design can reserve storage for the previous image.

An update package uses the OBCU format. The flat store keeps it as object kind 7.

Uploading a package does not install it. A separate ARM request starts the install process.

ARM is the normative install contract. The board policy rejects it, so field installation is disabled.

The trust model

The ARM contract and boot chain have these properties:

  • The device verifies the OBCU structure, image CRC, Ed25519 signature, and version before arming.
  • The device refuses an update during ride recording or when battery power is insufficient.
  • The flat-store service allocates a rollback reserve before it writes the boot handoff.
  • The bootloader verifies the complete staged image before it erases the application slot.
  • A power loss during installation leaves the state as Armed. The next boot repeats the complete install.
  • The new image gets one trial boot. After an unconfirmed trial, the bootloader restores an available reserve.
  • The bootloader starts a 24-second watchdog before the trial. A stalled trial resets into the unconfirmed path.
  • A blank or invalid boot-state page means Idle. The bootloader starts the current application.
The update state machine — one slot, verify before erase Idle running the app the normal state Armed update staged + rollback reserve obc-boot — install engine verify CRC over raw SD extents → flash app slot → readback Trial new image, one boot unconfirmed = suspect ARM — validate package, allocate rollback reserve, reboot reboot into obc-boot power loss mid-install ⇒ still Armed — redo flash ok → Trial verify fails — arm cleared, old app intact (zero cost) app confirms healthy → Idle no confirm next boot → restore available reserve
ARM validates the package and reserves rollback storage. The bootloader verifies the image before erase. The app confirms a healthy trial. Without confirmation, the bootloader restores the old image when a reserve exists.

If the card is unreadable before erase, the bootloader retries for approximately one minute. It then clears the arm and starts the old image.

After erase starts, the bootloader retries until it can complete the install or rollback.

Package validation

The package uses CRC-32 for integrity and Ed25519 for authenticity. The signed message contains:

  • the context string "OBCUv2-sig\0";
  • the version string;
  • the image length;
  • the application image.

The application verifies the signature before it writes Armed. The bootloader verifies the image CRC before erase.

The OBCU container format remains header version 1. The signature marker uses reserved header bytes. The signature follows the application image.

This layout lets the installed bootloader read current packages. See the OBCU specification.

Release publication

Pushing a SemVer v* tag starts release.yml. The tag version must match the board-crate version.

A published release requires a public key that differs from the committed test key. It also requires the release signing seed. A manual dry run can use the test key, but it publishes nothing.

The workflow builds the bootloader and application. It converts the application to binary, wraps it in OBCU, and signs it.

obc-mkimage inspect checks both CRC values and the signature before publication.

Release archive and download service

The GitHub release is the versioned archive. It contains release notes, ELF files, the OBCU package, and checksums.

The workflow copies the package and manifest to updates.openbikecomputer.com. This service permits browser downloads with CORS.

Manifest

Clients read this JSON file:

{
  "version": "v1.3.0",
  "bytes": 1204208,
  "sha256": "…64 lowercase hex…",
  "url": "https://updates.openbikecomputer.com/fw/v1.3.0/UPDATE.BIN",
  "notes": "https://github.com/…/releases/tag/v1.3.0"
}

Clients validate all required fields. The URL must use HTTPS. The byte count must be positive. The digest must contain 64 hexadecimal characters.

HTTP 404 means that the channel has no published release. Clients ignore unknown fields.

Release channels

The service uses three object names:

ObjectWritten byRole
fw/<tag>/UPDATE.BINevery tagimmutable package
fw/manifest.jsonstable tags onlydefault channel pointer
fw/prerelease/manifest.jsonSemVer prerelease tags onlyopt-in channel pointer

A prerelease tag updates only the prerelease manifest. A stable tag updates only the stable manifest.

The package path for each tag is immutable.

Version comparison

The device reports its firmware version through BLE Device Information or the USB EP0 request.

The TypeScript and Swift clients use the same SemVer rules. They ignore build metadata and do not offer a downgrade.

A development build reports a Git hash. Clients do not offer automatic updates when the running version is not SemVer.

Release, delivery, and installation vX.Y.Z a pushed git tag release.yml build → objcopy → wrap + SIGN inspect CRC values and signature before publication GitHub Release the source of truth notes · ELFs · SHA256SUMS versioned archive mirror updates.openbikecomputer.com — the serving edge fw/<tag>/UPDATE.BIN written for every tag · immutable fw/manifest.json the "latest" pointer · STABLE tags only fw/prerelease/manifest.json opt-in · never moves "latest" the update service permits browser downloads with CORS manual package selection companion app manifest → download → BLE sha256 checked on the phone map builder manifest → download → USB web + desktop, one parser local package select UPDATE.BIN in a transfer client update-package object · kind 7 staged — not installed staging is not installing one way through: ARM the device validates, arms, installs
The release workflow signs and checks the package. Clients upload the package with PUT. The package remains staged until a separate ARM request succeeds.

Three ways an update arrives

A package can arrive in three ways:

  • The companion app downloads a published package and uploads it through BLE.
  • The map builder downloads a published package and uploads it through USB.
  • A user selects a local UPDATE.BIN package in either client. The client uploads it through BLE or USB.

The card is not user-accessible. A computer cannot copy a package directly to the card.

Both clients validate the OBCU header, header CRC, image CRC, signature marker, and size before upload. They do not verify the signature. The device owns the trusted public key.

For published packages, clients also check the manifest byte count and SHA-256 digest. They obtain the running version from BLE Device Information or the USB EP0 request. They offer only a strictly newer SemVer release. They do not offer automatic updates for a development version.

Each client uploads the package with PUT as object kind 7. This operation only stages the package. The client then sends ARM with the package object ID and expected revision. BLE authenticates the control channel. USB enumeration authorizes the request. The device requires no on-device confirmation.

The device rejects ARM if any of these conditions apply:

  • The object ID or revision does not identify the staged package.
  • The OBCU structure, CRC, or Ed25519 signature is invalid.
  • The package version is not strictly newer than the running version.
  • A ride is recording.
  • The battery is below the install threshold.

On success, the device commits a rollback reserve and writes the boot handoff. It sends the response before reboot.

The app records the package version and arm generation before reboot. The bootloader records the install result. After boot, the app uses both records to show one result message. A normal boot shows no update message.

The chain, layer by layer

Each check has one purpose:

CheckPerformed byPurpose
HTTPSclientauthenticates the update service
Manifest size and SHA-256clientdetects a wrong or incomplete download
ARM authorizationBLE authentication or USB enumerationauthorizes the install request
Ed25519 signaturedevice applicationauthenticates the package
Version monotonicitydevice applicationprevents downgrade and reinstall
Image CRC-32application and bootloaderdetects storage or transfer corruption
Trial confirmationnew applicationproves that the new image can start

The signature does not depend on the download server. A compromised server cannot create an accepted package without the signing key.

The SHA-256 digest does not authenticate the package. The manifest and package come from the same service.

The bootloader verifies the complete image CRC before erase. It restores an available rollback reserve after an unconfirmed trial.

RRAM layout

The bootloader and application use one fixed RRAM layout. The application starts at 0x8000.

RRAM partition — one app slot, a 32 KB bootloader, the blob-stage carve, two small pages obc-boot 32 KB app slot obc-fw-nrf54l, linked at 0x8000 · 1976 KB SEMMC_ STAGE 20 KB BOOT_ STATE 4 KB SETTINGS 4 KB 0x0000 0x8000 0x1F6000 0x1FB000 0x1FC000 the BOOT_STATE page is the only app ↔ bootloader control channel — a CRC-framed blob, torn ⇒ Idle flat store UPDATE kind 7 ROLLBACK kind 8
The app writes the boot handoff to BOOT_STATE. It copies the sEMMC image to SEMMC_STAGE. The bootloader reads the update and rollback objects through absolute block ranges.

The bootloader has no filesystem, BLE stack, display driver, or asynchronous executor. It uses blocking storage and RRAM operations.

During installation, the bootloader keeps the display COM waveform active. It also keeps the watchdog active when the current state requires it.

Implementation