8000 feat(sensor): add fail-closed live CBio/SIBIONICS GS1 driver by shroominic · Pull Request #127 · shroominic/OpenGlucose · GitHub
[go: up one dir, main page]

Skip to content

feat(sensor): add fail-closed live CBio/SIBIONICS GS1 driver - #127

Closed
shroominic wants to merge 5 commits into
feature/codex-cbio-gs1from
feature/cbio-gs1-live
Closed

shroominic wants to merge 5 commits into
feature/codex-cbio-gs1from
feature/cbio-gs1-live

Conversation

@shroominic
Copy link
Copy Markdown
Owner

What this adds

The live, fail-closed CBio/SIBIONICS GS1 session on top of the offline
evidence lane: FF30 discovery, connect, FF31 notify, decode through
parseCbioPlaintextFrame, and a device-backed capture harness that records
every scan result, connect, service/characteristic discovery, subscribe, write
and notification byte.

  • packages/cgm_cbio — the live driver, registered behind its own pure FF30
    classifier in the platform registry.
  • openhealth/integration_test/cbio_live_capture_test.dart — bounded,
    device-backed capture. It drives the production FlutterBluePlusTransport
    behind the repository's RecordingBleTransport, so the durable trace in
    app-private storage is the evidence, and it never goes through the Nearby
    sensors UI.

Its only possible write is the bounded, non-activating 06 08 01 00 00 00 F1
read, and only after a passive window with no notification. Activation 0x07,
clock 0x03, reset, thresholds and calibration writes are never issued.

Home issue: #117.

Verified live evidence (real GS1 sensor)

Four bounded sessions against the actual peripheral; address, name and packet
bytes stayed in the restricted local trace and are not reproduced here.

phase result
discovery (raw plugin stream, 6 s window) 60–70 advertisements per run, exactly 1 FF30 candidate
connect succeeded in every session
discoverServices 1800, 1801, FF30, 180A, FE59, 180F; FF30 exposes FF31 (notify) and FF32 (write, write-without-response)
setNotify FF31 enabled — notify.enabled=[FF31]
passive window, 12 s with notify on and no write zero spontaneous notifications — the sensor does not stream on its own
bounded write exactly one, the allowed 06 08 01 00 00 00 F1 read (write.count=1, write.allowed_only=true)
FF31 reply exactly one 5-byte notification, byte-identical across three independent sessions
teardown clean disconnect; harness All tests passed! (rc=0)

The first session is the control for the root-cause fix below: identical live
link, FF30/FF31/FF32 discovered, yet notify.enabled=[] and
CbioProtocolException(missingNotifyCharacteristic).

Root-cause fix

_locateCharacteristics compared the discovered service UUID against the full
128-bit base constant, while Android reports the short form (FF30). Every
real session therefore failed closed before it could subscribe, even with FF31
present on the link. Both sides are now canonicalised onto the Bluetooth base
UUID before comparison (1f71f6c, with the RED→GREEN unit test).

The capture harness also lingers for a bounded 6 s settle window after the
first packet (fcb751c): the reply's leading byte reads as a frame length, so
disconnecting on the first packet could silently drop later fragments. Nothing
further arrives in that window.

Explicit non-claim

No mg/dL value is validated and none is claimed. There is no plaintext
frame, no opcode, no packed record and no decoded glucose field. The single
on-air reply fails parseCbioPlaintextFrame on length, and its determinism
plus the absence of follow-up fragments suggests an encrypted or differently
framed vendor reply rather than the plaintext V120 layout the offline codec
handles. Resolving that is the open question for any later decode work — the
sensor's on-air framing/key is not established, and this PR does not guess it.

Verification

  • packages/cgm_cbio: dart test 25/25, dart analyze clean
  • openhealth: flutter test --exclude-tags integration 473/473,
    flutter analyze clean (including the harness)
  • The documented reproduction was run verbatim, with no recovery file and no
    --dart-define, and passed end-to-end

The independent HCI cross-check was not available: the device's persisted
snoop buffer did not cover these sessions, so the app-level trace is the
authoritative record here.

Known dependency, tracked separately

The production transport's scan path still surfaces 0 candidates while the
raw plugin stream sees 69 advertisements and 1 FF30 candidate in the same
6 s window. This harness works today because its acquisition loop subscribes to
the raw stream after the scan is live; the normal discovery path is gated by
the separate scan-surfacing fix.

Stacked on #120 (offline evidence and gates); #120 stays open and separate.

Add a live Cbio/SIBIONICS GS1 session on top of the existing offline frame
contract: discover the FF30 service, connect, enable FF31 notifications, and
decode every received frame with parseCbioPlaintextFrame.

The session stays fail-closed. The only write it can emit is the bounded
`06 08 01 00 00 00 F1` read the protocol evidence supports, and only after a
passive window with no notification. Activation (0x07), clock (0x03), reset,
threshold, and calibration writes are never issued; calibration submission
reports unsupported instead of attempting a write.

Register the driver in the platform registry behind its own pure
advertisement classifier so an FF30 candidate routes to the FF30 driver, and
declare the package dependency plus the device-test dev dependencies the
capture harness needs.

Verified: packages/cgm_cbio `dart test` 24/24, `dart analyze` clean,
openhealth driver registry/connection-flow tests green.
Add an integration_test entry point that drives the production
FlutterBluePlusTransport through RecordingBleTransport and the unmodified
CbioSensorDriver against the live sensor, without going through the Nearby
sensors UI. Every phase is bounded, so a silent radio cannot hang the harness,
and all evidence lands in the app-private trace sink that already backs the
protocol capture lane.

Discovery runs three ways: the driver's own FF30-filtered scan, an unfiltered
transport scan, then a bounded raw-plugin acquisition loop that waits for an
intermittent advertisement. The raw path also primes the platform's address
type for the peripheral. An address recovered from advertisement evidence can
be supplied at run time via --dart-define=CBIO_TARGET_DEVICE_ID so a capture is
possible while the Dart scan surfacing defect is open.

The harness asserts the safety contract from the trace itself: at most one
write, and that write must be the allowed 0x08 read. It prints only redacted
identifiers and never prints payload bytes.

Pin the debug-only androidx.test espresso artifacts that the integration_test
package's pinned 3.2.0 release makes ambiguous for AGP 8 namespace validation.
The platform registry now registers the Cbio driver alongside AiDEX, so the
OG_DEMO selection test's expected driver set has to include it.

Verified: openhealth `flutter test --exclude-tags integration` 473/473.
Android hands the app short-form UUIDs (FF30/FF31/FF32) while the driver
declared the full 128-bit base form, so _locateCharacteristics never
matched the FF30 service on a live link and every real session failed
closed with missingNotifyCharacteristic.

Canonicalise both sides onto the Bluetooth base UUID before comparing.
Observed on the GS1 sensor: connect ok, service FF30 with FF31 (notify)
and FF32 (write), and the previous code produced notify.enabled=[].
The first live FF31 packet is five bytes whose leading byte is the size a
full frame would need, so disconnecting on the first packet could drop
every later fragment. Linger for six seconds after the first packet.
@shroominic
Copy link
Copy Markdown
Owner Author

Carried by the GS1 consolidation in PR #209 (branch feature/cbio-gs1, head 3eca4f4).

This PR is contained in the consolidated line; the merge that carries it is c85f827.

Closing as subsumed by the consolidation - not as won't fix. The per-defect evidence stays in this thread, and the carrying commit above is where the change now lives.

@shroominic shroominic closed this Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0