IoT · 2 min read
Designing offline-first BLE sync for hardware that goes underwater
Resumable chunked uploads, conflict rules, and why the app should never care whether there’s signal.
scoi.io · Jun 22, 2026

A dive computer does not get a polite HTTP timeout. It disappears for forty minutes, comes back with a burst of samples, and the phone that is supposed to receive them may be in a locker.
AquaPlane forced that constraint into the product, not into a “sync later” footnote. The app’s job is to look like it was always connected. The radio’s job is to catch up when it can.
The phone is not the source of truth
The device writes a local log. The phone holds a cache. The cloud holds the durable record. Those three copies disagree on purpose for long stretches of time. The mistake is pretending they don’t.
We treat every sample as an immutable append with a device id, a monotonic sequence, and a checksum. The phone never “edits” a dive. It only acknowledges ranges it has fully received.
That sounds obvious until a user pairs a second phone. Then you need a rule: the device is the authority for what happened in the water; the cloud is the authority for what has been stored; phones are caches that can be thrown away.
Chunked, resumable, boring
BLE payloads are small. A dive log is not. We split uploads into chunks the radio can finish even if the swimmer walks out of range mid-transfer. Each chunk is acknowledged. A reconnect starts at the first missing range, not at byte zero.
The UI never shows a spinner that means “hope.” It shows “3 of 11 segments on this phone” or “uploaded.” Those are different states. Collapsing them is how you get support tickets that say “it synced” when it didn’t.

Conflict rules you can explain
When two phones have overlapping caches, last-write-wins is the wrong instinct. The log did not change. One phone is simply behind. We merge by sequence number and refuse to invent samples.
If a firmware update changes the schema, the phone stores the raw payload and a parser version. Old apps still upload. New apps still read. The cloud migrates.
The app should never care whether there is signal. It should care whether the last acknowledged range is complete.
That sentence is the whole architecture. Everything else — pairing, OTA, the pretty charts — sits on top of it.
If your hardware leaves coverage as a feature, not a bug, design the log first. The radio is just a way to catch the log up.



