Repository navigation
feat(sensor): add fail-closed live CBio/SIBIONICS GS1 driver - #127
Closed
shroominic wants to merge 5 commits into
Closed
shroominic wants to merge 5 commits into
shroominic wants to merge 5 commits into
Conversation
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.
This was referenced Sep 17, 2026
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 recordsevery scan result, connect, service/characteristic discovery, subscribe, write
and notification byte.
packages/cgm_cbio— the live driver, registered behind its own pure FF30classifier in the platform registry.
openhealth/integration_test/cbio_live_capture_test.dart— bounded,device-backed capture. It drives the production
FlutterBluePlusTransportbehind the repository's
RecordingBleTransport, so the durable trace inapp-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 F1read, 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.
1800, 1801, FF30, 180A, FE59, 180F; FF30 exposes FF31 (notify) and FF32 (write, write-without-response)notify.enabled=[FF31]06 08 01 00 00 00 F1read (write.count=1,write.allowed_only=true)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=[]andCbioProtocolException(missingNotifyCharacteristic).Root-cause fix
_locateCharacteristicscompared the discovered service UUID against the full128-bit base constant, while Android reports the short form (
FF30). Everyreal 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, sodisconnecting on the first packet could silently drop later fragments. Nothing
further arrives in that window.
Explicit non-claim
No
mg/dLvalue is validated and none is claimed. There is no plaintextframe, no opcode, no packed record and no decoded glucose field. The single
on-air reply fails
parseCbioPlaintextFrameonlength, and its determinismplus 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 test25/25,dart analyzecleanopenhealth:flutter test --exclude-tags integration473/473,flutter analyzeclean (including the harness)--dart-define, and passed end-to-endThe 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.