8000 fix(cbio): read the raw payload word, not the empty packed field (G1) by shroominic · Pull Request #210 · shroominic/OpenGlucose · GitHub
[go: up one dir, main page]

Skip to content

fix(cbio): read the raw payload word, not the empty packed field (G1) - #210

Closed
shroominic wants to merge 2 commits into
fix/cbio-gs1-harness-discoveryfrom
fix/cbio-gs1-raw-glucose-payload
Closed

shroominic wants to merge 2 commits into
fix/cbio-gs1-harness-discoveryfrom
fix/cbio-gs1-raw-glucose-payload

Conversation

@shroominic
Copy link
Copy Markdown
Owner

Fixes #207. Gate G1: the decoded-value question is a wrong field, not a
missing one.

The arithmetic

One real captured 08 record (session 2026-09-18 13:02, first record after the
batch header) is 3a 01 6a 0b 40 00 00 00:

offset bytes LE16 field value
0 3a 01 314 rawTemperature control: the stride and offsets are right independently of glucose
2 6a 0b 2922 rawDump no established meaning
4 40 00 64 rawPayload the reading-bearing word; /10 → 6.4 (scale unverified)
6 00 00 0 processed packed (b >> 6) | (b1 << 2) → 0

The evidence path read offset 6, the firmware's processed word in the 0A
packed layout. That word is zero in every record of every captured session, so
"every raw glucose field reads 0" was the decoder reading the empty field, not
a sensor that reports nothing.

Proof over one window

make cbio-gs1-decode-comparison replays one captured log through two decoders
over the same bytes and writes evidence/gs1-decode-comparison-20260918T102533.json.
For index 1..1520 (25.3 h):

  • payload: 51..95, non-zero in 1520/1520 records; hourly median 5.3 at 05:00Z
    to 8.6 at 12:00Z — dips overnight, rises through the day.
  • processed: 0..0, non-zero in 0/1520 records.
  • App path (CbioHistoryArchive, what the installed app renders) vs frame path:
    compared 1520, agreeing 1520, missing 0, disagreeing 0.

/10 is still unverified: nothing here is a reference measurement, and no
value is presented as a physical glucose reading.

Changes

  • parseCbioRawDataFrame is the single owner of the eight-byte 08 layout;
    record fields are rawTemperature, rawDump, rawPayload, processed.
  • CbioHistoryArchive decodes through it instead of duplicating offsets.
  • New cbio_decode_comparison.dart: per-record agreement between the app's live
    path and the harness path, with a byte-arithmetic block.
  • New cbio_log_replay.dart + tool/replay_gs1_decode.dart + make cbio-gs1-decode-comparison to reproduce the comparison from a captured log.
  • Session evidence schema → cbio.session-evidence/2: rawPayload and
    processedGlucose are separate {count, minimum, maximum, nonZero} blocks,
    and payload nonZero == 0 or a payload/record count disagreement fails
    validation.
  • The device harness now asserts payload non-zero and app-path agreement, so a
    run that reports an empty payload field fails loudly instead of passing.

Verification

dart format --set-exit-if-changed clean, flutter analyze --fatal-infos clean
on every workspace project, cgm_cbio packages green (81 tests) and the app
suite green at 472/473 — the one failure is the pre-existing
protocol_capture_status_test event-loop-budget flake, already owned by
fix/protocol-capture-status-flake (#203) and untouched here.

Not in this PR

  • No fresh on-device window: the sensor has stopped advertising to the test
    phone (repeated aborted_no_target scans), so the hardware form of this proof
    is queued behind sensor availability.
  • No unit verification. That needs one fingerstick or one vendor-app reading at
    a known index; it stays an owner decision.

The evidence artifact reported the packed ten-bit field of an `08` record as
the raw glucose, and on this firmware that field is `0x0000` in every record of
every captured session. The artifact therefore showed `rawGlucoseMinimum` and
`rawGlucoseMaximum` of zero while the app's live path rendered a moving value
from the same frames: two decoders, two fields, one reported as the other.

The same bytes carry both fields, so the disagreement was never an offset or a
width. It is field identity:

* `08` record offsets 0, 2, 4, 6 are `temperature, dump, payload, processed`.
* The payload word (offset 4, LE16) is what the app's live path renders and the
  only field with content on this firmware; `parseCbioRawDataFrame` now exposes
  it as `rawPayload`.
* The processed word (offset 6) keeps the recovered `0A` packed layout and is
  exposed as `processed`, with the record's `rawWord` alongside the ten bits so
  the span is visible.
* `CbioHistoryArchive` no longer repeats the record layout: it decodes through
  `parseCbioRawDataFrame`, so the two paths cannot drift again.

The evidence artifact moves to `cbio.session-evidence/2` and reports both series
separately, each as `{count, minimum, maximum, nonZero}`. `nonZero` is what
separates "this field is empty" from "this measured zero", and a payload count
that disagrees with the raw record count fails validation. The authenticated
harness now decodes every notification through the app path and the frame path
in one session, emits the comparison, and fails the run when the payload field
is empty everywhere or the two paths disagree.

`tool/replay_gs1_decode.dart`, reachable as `make cbio-gs1-decode-comparison`,
turns a captured log into a durable per-record comparison without a radio.
@shroominic
Copy link
Copy Markdown
Owner Author

CI position, for the record: Quality, Android Build, Web Build, Secret Scan, Dependency Review pass. Unit Tests, Integration Tests, iOS Build, macOS preview fail for reasons that already fail on the base branch (fix/cbio-gs1-harness-discovery, PR #205, which also fails Quality): the unit failures are all test/private_bench_test.dart host-scope cases owned by #179, the integration job collects device-only harnesses onto a host runner, and the iOS job is the missing integration_test pod owned by #180. Nothing in this PR's diff touches those surfaces.

@shroominic
Copy link
Copy Markdown
Owner Author

Absorbed into #209 (head be9b47a). The two commits here are ported onto the consolidated branch:

  • bb2619d — the reading-bearing word is the 08 record's payload at offset 4 (LE16); the word at offset 6 is the firmware's processed field and reads 0x0000 in every capture, so the previous rawGlucoseMinimum/Maximum: 0 was a wrong-field decoder error, not an empty sensor.
  • 6355da4 — the two-decoder comparison record.

The absorption keeps this line's unit-free accessor policy: the record exposes rawPayload / rawProcessed and rawPayloadScaled (rawPayload / 10) with isUnitVerified == false, so no surface publishes a unit. G1 is proven offline only (index 1..1520: payload 51..95, non-zero 1520/1520; processed 0/1520; app vs frame 1520 agreeing / 0 disagreeing); hardware re-proof is pending sensor availability.

Carrying PR: #209.

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