OpenBikeComputer
On this page

The companion link

The companion link moves stored objects between the device and a client: routes and maps in, rides out. Bluetooth and USB carry the same protocol frames, so there is one transfer engine and one set of rules. Bluetooth adds the device-local controls: pairing, the clock, the settings, and bond removal. USB carries objects and device information only.

The normative contracts are the flat-store protocol, the card format, and the BLE control surface.

Two planes: control and data

One transfer protocol, two transport bindings Binding Control frames Stream frames BLE Authenticated bond GATT objectControl L2CAP CoC PUT / GET payloads USB v5 Cable access Control endpoints Protocol-v4 records Stream endpoints Protocol-v4 records Device transfer engine A successful commit makes an upload durable
BLE and USB carry the same protocol-v4 frames. BLE pairing, clock, and settings controls remain outside this transfer protocol.

The protocol has a control channel and a stream channel. Control frames select an operation and report its result; stream frames carry the payload. Only one transfer is active at a time, which keeps the device's buffers fixed.

Over Bluetooth the control channel is a GATT characteristic and the stream channel is an L2CAP channel; over USB each plane is a pair of bulk endpoints. The frame bytes are identical.

The operations are the ones a small object store needs: list the catalog, check one object, get, put, remove, cancel the active transfer, format the card, and arm an uploaded firmware package. There is no negotiation, no session, and no unsolicited status frame: a client lists first, and lists again when the store identity or the commit sequence changes.

Link credits and packet completion do not mean the data is safe. Only a store commit does.

Objects

The store holds routes, trips, rides, one map, the firmware package, and the rollback reserve. Object identifiers are never reused; a create starts at revision one and a replace raises the revision. A catalog entry carries the kind, length, CRC, and display name, so a client can show the store without downloading anything.

Transfers and commits

One PUT — request · stream · commit companion app OBC device 1 · PUT control frame — objectControl write RequestId · kind · ObjectId · length · CRC-32 2 · stream frames — L2CAP CoC sink → storage, running CRC no whole-object buffer 3 · final byte → verify whole-object CRC-32 4 · PUT response: committed or error
A PUT is one request. A successful response follows the store commit. The current board checks length and CRC but has no object-specific payload validator.

An upload declares the identity, the expected revision, the length, and the CRC before any bytes move, and the device writes into an unpublished allocation. After the last byte it checks all four and only then commits. An error or a lost link makes the new bytes unreachable and leaves the old object exactly as it was. There is no resume: a half-written object is not a state the store can be in.

The current board policy checks the length and the CRC, but does not yet parse the payload for its kind, so a successful upload is not proof that the object can be read.

A download serves one committed revision and reports its length and CRC, and the client verifies both. Removing an object removes its head and its retained revision in one commit, and cannot remove an active recording.

Reconciliation

Reconciliation — LIST identifies the store and catalog on the device store changes upload · ride device-side delete catalog commit StoreId · sequence LIST response on the phone LIST changed → download the list paged LIST GET required objects, on the stream on the next audit Protocol v4 has no ride-acknowledgment mutation phone stores verified ride ARCHIVE_RIDE device persists archive proof
LIST supplies the store identity, commit sequence, and catalog entries. Clients use it to reconcile state.

A reply can be lost after the device has committed, so a client must be able to ask what happened instead of guessing. After an interrupted replacement, a status request says whether the revision became the head. After a lost create, whose identifier the client never learned, the client lists the catalog, matches the kind, length, CRC, and name, and removes the duplicates it finds. A notification is never evidence.

Rides become downloadable when their recording flag clears. The phone downloads the listed revision, verifies it, saves it in one atomic archive generation, and reports success only after the file is durable.

The phone then sends the device proof that it holds that exact finalized ride, and the device commits and reads back that proof. Proof turns the synced indicator on. It is deliberately not a deletion trigger: routes and rides stay on the device until the rider removes them, and a full card offers the rider an age-based cleanup instead of deciding alone. The durable format is in the metadata contract.

Pairing and the Bluetooth controls

① First pairing — once device shows 428 913 rider reads it, types it on the phone LESC ECDH MITM-protected bond stored ② Every time after — silent device advertises stable address phone knows this identity re-encrypt · stored LTK resolve RPA · stored IRK connected encrypted no dialog bonded + powered + in range ⇒ connected + encrypted, no interaction ③ Another phone, while bonded — rejected a different phone tries to pair bond exists → no passkey, link dropped phone sees a generic failure Forget phone or Factory reset
One passkey creates one bond. Later connections use the stored keys. A bonded device rejects a second phone.

Pairing uses Secure Connections passkey entry: the device shows six digits and the rider types them on the phone. That produces one authenticated bond, and the device stores one phone. While a bond exists it refuses a second phone, so a bike computer in a group ride cannot be taken over from the next wheel.

Forget phone removes the stored keys. It writes and verifies the empty persistent slot first, then removes the host keys, because the order decides what a failure leaves behind. Controller cleanup has no receipt, so the screen asks for a restart to finish. A disconnect proves nothing about stored keys. A factory reset runs the same removal, so the setup that follows can pair a phone again. It also deletes personal card data. Maps stay installed, so setup does not require another map download. Reset waits for card deletion to finish. A card failure keeps the reset screen open for Retry.

Device information, the battery level, and the protocol version are readable before pairing. Everything else needs an authenticated, encrypted link. The clock is set by the phone or by a GPS fix; the local offset has to come from the phone or the rider.

Sensors: the device as central

Two roles, one radio — peripheral to the phone, central to the sensors companion app BLE central (iPhone) OBC device peripheral · to phone central · to sensors one radio · nRF54L the phone link heart-rate strap HRS · 0x180D power meter Cycling Power · 0x1818 cadence sensor CSC · 0x1816 scan · connect · subscribe one radio — MPSL time-slices the peripheral (phone) and central (sensor) roles; no second radio sensors are open GATT servers — connected by stored address, no bond, one saved slot per quantity
The device is a BLE peripheral for the phone and a BLE central for sensors.

For the phone the device is a peripheral. For sensors it is a central, on the same radio at the same time. It supports the standard heart rate, cycling power, speed and cadence, and battery services, with one saved slot each for heart rate, power, and cadence.

Sensors use their own saved address and have nothing to do with the phone bond. A reading older than a few seconds becomes unavailable rather than stale, and the recorder stores the fresh samples with the ride. The device does not stream live sensor values to the phone.

Where the two transports differ

PropertyBluetoothUSB
Protocol framesSameSame
AuthorizationAuthenticated bondPhysical cable access
Device-local controlsAvailableNot available
Device informationGATT servicesControl request

USB frames travel inside length-prefixed records, and a record can span packets. There is no mass-storage mode: the firmware stays the only owner of the card, so a computer can never leave the card in a state the firmware did not make.

Implementation