The companion link
The companion link moves stored objects between OpenBikeComputer and a client. BLE and USB use the same protocol-v4 frames. BLE also supplies pairing, settings, clock, bond removal, and weather-refresh controls. USB supplies object transfer and device information only.
The normative contracts are:
Two planes: control and data
Protocol v4 has a control channel and a stream channel. Control frames select an operation and report its result. Stream frames carry PUT and GET payload bytes. Only one PUT or GET can be active.
BLE maps control frames to the objectControl GATT characteristic. It maps stream frames to one L2CAP connection-oriented channel (CoC). The open protocolVersion characteristic contains u16 value 4. After authentication, the psm characteristic identifies the CoC.
USB binding v5 uses one bulk endpoint pair for each plane. Both transports deliver identical protocol-v4 frame bytes to one transfer engine.
Each control frame has a 16-byte header. It contains the OBC4 magic, protocol major, opcode, flags, length, and RequestId. The client selects a nonzero RequestId. The terminal response echoes it. The same value identifies all stream frames for a PUT or GET.
The adapter delivers the control frame before related stream frames. It uses link backpressure if a stream frame arrives first. BLE credits and USB packet completion do not mean that data are durable. Only a successful store commit makes an upload durable.
Protocol-v4 operations
| Operation | Function |
|---|---|
LIST | Read paged catalog entries, StoreId, and commit sequence. |
STATUS | Check one known object and revision. |
GET | Download one committed object revision. |
PUT | Create or replace one object with one commit. |
REMOVE | Remove one object head and any retained revision. |
CANCEL | Stop the active PUT or GET. |
ARM | Request validation and installation of an uploaded firmware package. |
FORMAT | Replace the card with a new empty flat store. |
The protocol, clients, and board adapters implement the ARM request and response. The current nRF54LM20 board policy rejects every ARM request with rejected. Uploading an update package does not install it.
There is no protocol negotiation, wire minor, session, operation ID, or unsolicited status frame. A client sends LIST before other operations. A new StoreId invalidates all cached catalog data. A changed commit sequence tells the client to read the catalog again.
Stored object kinds
| Value | Kind | Payload or function |
|---|---|---|
| 1 | Route | OBCR route |
| 2 | Trip | Ordered route membership |
| 3 | Ride | Device-produced recording |
| 4 | Weather bundle | OBCW weather data |
| 5 | Map | One OBCM file with embedded terrain |
| 6 | Retired | Map-set manifest; producers must not write it |
| 7 | Update package | OBCU firmware package |
| 8 | Rollback reserve | Bootloader rollback space |
ObjectId and Revision are unsigned 64-bit values. Object IDs are store-global and are not reused. A create starts at revision 1. A replace increments the revision. A LIST entry also supplies kind, flags, length, CRC-32, and a UTF-8 display name. The display name has a maximum of 48 bytes.
Transfers and commits
PUT
A PUT declares the object identity, expected revision, kind, name, length, and CRC-32. Object ID zero creates an object. A nonzero ID replaces the expected revision.
The client sends stream frames from absolute offset zero. Offsets must be contiguous and increasing. The device writes to an unpublished allocation. After the final byte, it verifies these items:
- Declared payload length.
- Whole-payload CRC-32/IEEE.
- Validator rules for the object kind.
- Expected revision immediately before commit.
A successful response supplies the object ID, new revision, length, and CRC. An error makes the new bytes unreachable. A cancelled or disconnected transfer releases its allocation. There is no resume or checkpoint operation.
GET
A GET selects an object ID and an optional revision. The device opens that revision and streams bytes in increasing offset order. The response supplies the served revision, length, and CRC. The client verifies the complete length and CRC.
REMOVE and CANCEL
REMOVE uses the object ID and expected head revision. It removes the head and its retained revision in one commit. It cannot remove an active recording or reserved object.
CANCEL names the active PUT or GET RequestId. It stops the transfer but does not remove an existing committed object. A link loss has the same transfer result.
Reconciliation
Use STATUS after an interrupted replacement. A committed result confirms the requested revision. An absent or superseded result means that replacement did not become the head.
A create has no assigned ID before its commit response. After a lost create response, use LIST. Match kind, payload length, payload CRC, and display name. If multiple entries match, the iOS client keeps the greatest ObjectId. It removes the other matches with their exact revisions. Do not infer state from a notification or operation log.
Rides become downloadable after the RECORDING flag clears. The iOS client lists finished rides, downloads them, and verifies their CRC. Protocol v4 has no ride-possession mutation. The current iOS ackRides compatibility method sends no command. The board does not accept the retired ackRides command.
Pairing and BLE controls
The phone uses LE Secure Connections passkey entry. The device displays a six-digit passkey. The rider enters it in the phone system dialog. This process creates one authenticated bond.
The device stores one phone bond. While this bond exists, it rejects pairing from a different phone. The device action Forget phone clears the bond. The bonded phone can also send forgetBond. The Bluetooth power setting does not remove the bond.
Device Information, Battery, and protocolVersion are open before pairing. psm, objectControl, commands, and configuration require encryption and authentication. The device also refuses an unencrypted CoC.
BLE keeps a device-local command and configuration surface beside protocol v4. It supports clock setting, bond removal, weather refresh, and settings. These controls are not flat-store objects. They do not exist in USB binding v5.
The phone sets UTC and local offset after encryption. A GPS fix can also establish trusted UTC.
Sensors: the device as BLE central
For the phone, the device is a BLE peripheral. For sensors, the device is a BLE central. Both roles use one radio at the same time.
The sensor manager supports these standard services:
- Heart Rate Service.
- Cycling Power Service.
- Cycling Speed and Cadence Service.
- Battery Service.
Sensors use their saved address and do not use the phone bond. The device has one saved slot for heart rate, power, and cadence. A power meter can supply cadence when no dedicated cadence sensor is configured.
The manager scans, connects, discovers, subscribes, decodes, and dispatches measurements. It reconnects after a link failure while Bluetooth is enabled. A value older than 5 seconds becomes unavailable. The ride recorder stores fresh sensor samples and summary statistics. The device does not stream live sensor values to the phone.
BLE and USB binding differences
| Property | BLE | USB binding v5 |
|---|---|---|
| Protocol frames | Version 4 | Version 4 |
| Control plane | GATT objectControl | Control bulk endpoint pair |
| Stream plane | L2CAP CoC | Stream bulk endpoint pair |
| Authorization | Authenticated bond | Physical cable access |
| Device-local controls | Available | Not available |
| Device information | GATT services | EP0 GET_DEVICE_INFO |
USB advertises bInterfaceProtocol = 5 and bcdDevice = 0x0500. A host checks these values before it exchanges a record. Protocol frames still contain major 4.
Each USB record contains these parts:
- A little-endian 32-bit record length.
- Exactly that many protocol-frame bytes.
- Zero padding to a four-byte boundary.
USB packet boundaries have no record meaning. A record can span multiple packets. The stream-record ceiling is 8,208 bytes, including its 16-byte header. A stream payload is therefore at most 8,192 bytes. The host-to-device control-record ceiling is 256 bytes.
USB has no mass-storage binding. The firmware remains the only owner of the card.
Implementation
- Protocol engine:
firmware/obc-link/src/flat - Flat store:
firmware/obc-storage/src/flat - BLE adapter:
firmware/obc-fw-nrf54l/src/ble - USB device adapter:
firmware/obc-fw-nrf54l/src/usb - USB host library:
host/obc-usb - iOS protocol client:
OBCProtocolV4 - Builder USB client:
builder/app/src/lib/usb - BLE codecs and sensor decoders:
obc-ble - Sensor mailbox:
sensor_hub.rs