Skip to content

Commit f9cbd91

Browse files
committed
feat: update BLE integration guide with pairing flow and flutter_blue_plus examples
1 parent 7c858a3 commit f9cbd91

File tree

1 file changed

+37
-19
lines changed

1 file changed

+37
-19
lines changed

docs/en/companion-ble-integration.md

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,8 @@ Offset Len Field
234234
5. Inspect `pkt_type` to route to the appropriate UI handler.
235235

236236
```dart
237-
// flutter_reactive_ble example
238-
final sub = _ble.subscribeToCharacteristic(txChar).listen((data) {
237+
// flutter_blue_plus example
238+
txChar.onValueReceived.listen((data) {
239239
final bytes = Uint8List.fromList(data);
240240
if (!validateRivrFrame(bytes)) return; // check magic + CRC
241241
final pktType = bytes[3];
@@ -246,6 +246,7 @@ final sub = _ble.subscribeToCharacteristic(txChar).listen((data) {
246246
// ...
247247
}
248248
});
249+
await txChar.setNotifyValue(true);
249250
```
250251

251252
---
@@ -282,6 +283,11 @@ App Rivr node (BLE active)
282283
│ │
283284
│── connect ────────────────────────►│ GAP CONNECT event
284285
│ │ g_metrics.ble_connections++
286+
│ │ (new peer) triggers MITM pairing
287+
│◄── passkey notification ──────────│ node displays 6-digit PIN
288+
│ [user enters PIN on phone] │
289+
│── pairing complete ───────────────►│ bond persisted on both sides
290+
│ │
285291
│── request MTU (247) ──────────────►│ MTU event logged
286292
│ │
287293
│── subscribe TX notify ────────────►│ SUBSCRIBE event logged
@@ -314,6 +320,24 @@ phone must complete passkey pairing before the link is treated as data-ready. Bo
314320
phones reconnect silently and the bond is persisted on-device. Open BLE builds are
315321
still possible only if a board environment explicitly sets `RIVR_BLE_PASSKEY=0`.
316322

323+
### First-time connection — pairing flow
324+
325+
On the **first** connection to a new node, pairing must complete before the app can
326+
subscribe to notifications or send frames:
327+
328+
1. Immediately after the GAP connect event, the node triggers MITM authentication.
329+
2. The node generates and displays a **random 6-digit passkey** on its screen (logged as
330+
`BLE pairing: show passkey XXXXXX`).
331+
3. On Android the OS presents a passkey entry dialog; the user must type the 6-digit code
332+
shown on the node within **60 seconds**.
333+
4. On success the bond is persisted; future reconnections are silent (no PIN needed).
334+
5. On failure or timeout, the connection is dropped.
335+
336+
**App-side implementation**: block all GATT operations (MTU request, `discoverServices`,
337+
`setNotifyValue`) until the Android bond state reaches `bonded`. Only start normal
338+
operations (subscribe, send frames) after bonding completes. A 60-second timeout is
339+
recommended. Once the bond exists, reconnections require no extra delay.
340+
317341
---
318342

319343
## 11. Metrics the app can monitor
@@ -339,33 +363,26 @@ UART0.
339363

340364
| Package | Notes |
341365
|---|---|
342-
| [`flutter_reactive_ble`](https://pub.dev/packages/flutter_reactive_ble) | Recommended. Reactive streams, good MTU support, works on Android + iOS. |
343-
| [`flutter_blue_plus`](https://pub.dev/packages/flutter_blue_plus) | Also suitable. Broader platform support (Android, iOS, macOS, Linux, Windows). |
366+
| [`flutter_blue_plus`](https://pub.dev/packages/flutter_blue_plus) | **Used by Rivr Companion.** Android, iOS, macOS, Linux, Windows. |
367+
| [`flutter_reactive_ble`](https://pub.dev/packages/flutter_reactive_ble) | Alternative. Reactive streams, Android + iOS only. |
344368

345-
Both packages support Nordic NUS UUIDs out of the box. For `flutter_reactive_ble`:
369+
Both packages support Nordic NUS UUIDs. The companion app uses `flutter_blue_plus`:
346370

347371
```dart
348372
const kServiceUuid = '6E400001-B5A3-F393-E0A9-E50E24DCCA9E';
349373
const kRxCharUuid = '6E400002-B5A3-F393-E0A9-E50E24DCCA9E';
350374
const kTxCharUuid = '6E400003-B5A3-F393-E0A9-E50E24DCCA9E';
351375
352-
final rxChar = QualifiedCharacteristic(
353-
serviceId: Uuid.parse(kServiceUuid),
354-
characteristicId: Uuid.parse(kRxCharUuid),
355-
deviceId: deviceId,
356-
);
357-
358-
final txChar = QualifiedCharacteristic(
359-
serviceId: Uuid.parse(kServiceUuid),
360-
characteristicId: Uuid.parse(kTxCharUuid),
361-
deviceId: deviceId,
362-
);
376+
final svc = device.servicesList.firstWhere((s) => s.uuid == Guid(kServiceUuid));
377+
final rxChar = svc.characteristics.firstWhere((c) => c.uuid == Guid(kRxCharUuid));
378+
final txChar = svc.characteristics.firstWhere((c) => c.uuid == Guid(kTxCharUuid));
363379
364380
// Subscribe to incoming mesh frames
365-
_ble.subscribeToCharacteristic(txChar).listen(onFrame);
381+
txChar.onValueReceived.listen(onFrame);
382+
await txChar.setNotifyValue(true);
366383
367384
// Send a frame to the mesh
368-
await _ble.writeCharacteristicWithoutResponse(rxChar, value: frameBytes);
385+
await rxChar.write(frameBytes, withoutResponse: true);
369386
```
370387

371388
---
@@ -411,5 +428,6 @@ support (`flutter_reactive_ble` does not support Windows as of 2026).
411428
| **One client at a time** | Rivr currently tracks a single active BLE connection. A second phone cannot connect while one is already connected. |
412429
| **Fragmentation overhead** | Oversize payloads are fragmented automatically, but each fragment carries a 6-byte Rivr BLE transport header. |
413430
| **Activation window** | BLE is not always on. See Section 4. |
431+
| **First-connection PIN entry** | First-time pairing requires entering a 6-digit PIN shown on the node's display. Subsequent connections are silent. See Section 10. |
414432
| **No phone↔phone relay** | Frames injected via BLE are processed by the connected node only; they do not bypass the node's relay policy. A PKT_CHAT written by the phone is subject to the same duty-cycle and relay rules as any LoRa frame. |
415-
| **ESP32-S3 BLE stability** | Heltec V3 and LilyGo T3-S3 use ESP32-S3. BLE on ESP32-S3 with IDF 5.x has less community testing than ESP32 classic. Report regressions. |
433+
| **ESP32-S3 BLE stability** | Heltec V3 and LilyGo T3-S3 use ESP32-S3. Static-random BLE addressing is used on these boards; advertising and reconnection are stable from IDF 5.5.0. |

0 commit comments

Comments
 (0)