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.
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:
| Object | Written by | Role |
|---|---|---|
fw/<tag>/UPDATE.BIN | every tag | immutable package |
fw/manifest.json | stable tags only | default channel pointer |
fw/prerelease/manifest.json | SemVer prerelease tags only | opt-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.
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.BINpackage 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:
| Check | Performed by | Purpose |
|---|---|---|
| HTTPS | client | authenticates the update service |
| Manifest size and SHA-256 | client | detects a wrong or incomplete download |
ARM authorization | BLE authentication or USB enumeration | authorizes the install request |
| Ed25519 signature | device application | authenticates the package |
| Version monotonicity | device application | prevents downgrade and reinstall |
| Image CRC-32 | application and bootloader | detects storage or transfer corruption |
| Trial confirmation | new application | proves 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.
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
- OBCU and boot-state formats:
OBCU_Spec.md - Install protocol:
FLAT_Store_Protocol.md - Flat-store layout:
FLAT_Store_Format.md - Shared DFU logic:
obc-dfu - Bootloader:
obc-boot - Package tool:
obc-mkimage - Release workflow:
release.yml - Web release client:
release.ts - iOS release client:
Firmware