LibreCRKit is the clean-room Swift package for Libre 3 pairing, BLE transport,
authorization, sensor recovery, and post-auth data-plane decoding. It is built
as a reusable foundation for any iOS app that wants to integrate a Libre
sensor. The standalone Apps/LibreCR app is the live-device harness around
this package.
This repo contains:
Package.swift: the reusableLibreCRKitSwift package.Sources/LibreCRKit: BLE, NFC activation, pairing, crypto, persistence, and data-plane code.Apps/LibreCR: an iOS PoC app that exercises pairing, state persistence, reconnect, backfill, and decoded realtime glucose/status display.protocol.md: a protocol-level summary of the NFC, BLE, authorization, and data-plane behavior currently modeled by the package.
The public app does not ship captured per-sensor state. Local files such as
Libre3PatchContext.xml and Libre3SensorState.json are ignored.
swift testTo work on the iOS PoC app, open Apps/LibreCR/LibreCR.xcodeproj in Xcode.
The app target uses the package at the repo root as a local Swift package.
The package currently owns:
- NFC activation payload construction and response parsing.
- Receiver identity generation, display, and recovery parsing
(
Libre3ReceiverID). - BLE scanning, connecting, GATT discovery, notification subscription, writes,
reads, CoreBluetooth restoration hooks, and iOS connection-event registration
(
SensorScanner,SensorSession). - First-pair authorization primitives through Phase 6, including standard ECDSA-P256(SHA-256) verification of the sensor certificate with bundled Abbott patch-signing public keys.
- Post-auth data-plane framing, CCM decrypt,
patchStatus, and realtimeglucoseDataparsing. - Lifecycle and backfill helpers:
SensorLifecycle, raw quality evidence,Libre3GlucoseQualityAssessment,Libre3DataPlaneState,PatchControlCommand.backfillGreaterEqual,HistoricalReadingPage,HistoricalBackfillcoverage/gap summaries, clinical-data records, factory data command access, and bounded reconnect backfill command planning from the last accepted glucose life count. - Persistence bridges between
Libre3SensorStateandLibre3DataPlaneStatefor seeding reconnect backfill after app relaunch.
An integrating app should own:
- User-facing pairing and recovery workflows.
- Persistence in app settings, keychain, cloud/device backup, or other durable state chosen by the app.
- Conversion from
RealtimeGlucoseReadinginto the app's glucose sample model. - Connection state policy, retry cadence, and UI error state.
- Background launch orchestration from app lifecycle callbacks.
- Protocol conformance and conversion into the app's own glucose sample types.
A Libre 3 receiver ID is the 4-byte value placed into the NFC activation/switch payload as:
timestamp_LE || receiverID_LE || abbott_crc16
The sensor-facing value is the receiver ID itself. A LibreView account is not a protocol requirement for the observed first-pair and active-sensor recovery paths.
Apps that support sensor recovery should persist and expose at least:
receiverIDas 4-byte little-endian hex.- Sensor serial number.
- BLE address returned by NFC.
- Latest BLE PIN returned by NFC.
- Sensor start / lifecycle metadata once the remaining fields are named.
If a user loses the original phone, a new install can accept the saved
receiverID before scanning the active sensor. Active-sensor recovery then uses
the switch-receiver NFC command and the normal BLE authorization flow.
Each RealtimeGlucoseReading carries the sensor's own quality channels (data
quality error, sensor condition, displayable-range status, and actionability).
currentGlucoseQualityAssessment(lifecycle:) folds these into a
Libre3GlucoseQualityAssessment with an overall isUsable flag plus the
contributing issues.
Issues are split into two classes:
- Blocking issues suppress the reading: sensor warmup, expiry, a data-quality
error, an unavailable/out-of-range value, or an abnormal sensor condition.
These clear
isUsableand are exposed viablockingIssues. - Advisory issues are surfaced for visibility but do not suppress the reading.
Currently the only advisory is
notActionable, exposed viaadvisories.
Actionability (bit 3 of the realtime status byte) is intentionally advisory.
Abbott's own app still displays the glucose value for a non-actionable reading;
the bit only optionally drives a "non-actionable" overlay icon. A reading whose
other quality channels are clean therefore stays usable even when the sensor
reports it as non-actionable. Integrating apps that want stricter behavior can
inspect reading.actionability or the advisories list directly.
The Apps/LibreCR NFC tab is currently the live-device harness for the public
integration surface. It uses product-facing "pairing" language:
- Initial pairing: a new sensor that accepts activation/switch NFC and BLE authorization with the current receiver ID.
- Sensor recovery pairing: a post-A8 active sensor that accepts the known saved receiver ID, then completes BLE authorization and resumes realtime data.
The harness now displays decoded glucoseData and patchStatus, persists
Libre3SensorState.json, records the last realtime glucose life count/value for
bounded reconnect backfill, can reconnect from that saved state, exposes an
explicit BLE disconnect button, registers CoreBluetooth connection-event wake
hooks, keeps a model-owned post-auth listener alive after the initial bootstrap,
and records scene/restoration/connection lifecycle events in-app. Temperature is
shown as the currently grounded raw field (tempRaw) until its unit/calibration
is confirmed.
The four live-device behaviors that the library is shaped around have been exercised end-to-end in real use:
- Background while connected: minute-spaced
glucoseDatanotifications wake the app and are decrypted from the locked/backgrounded state. - iOS termination restoration:
willRestoreStatedelivers the peripheral and subscribed characteristics after system-driven termination. - Disconnect recovery: registered connection events wake the app on reconnect or service match.
- Long idle run: locked-phone sessions sustain sample continuity against the sensor's history/backfill channel.
The library still deliberately exposes low-level hooks rather than owning a finished CGM lifecycle policy, because the host app's connection-state policy, retry cadence, persistence, and UI belong in the integrating app.
Two backfill channels exist, with different resolution and cadence:
- Paged historical backfill (
HistoricalReadingPage/PatchControlCommand.backfillGreaterEqual) commits only at 5-minute boundaries and lags the current life count by ~17 minutes. - The clinical stream (
ClinicalReadingRecord, char0x08981ab8) emits one per-minute record while connected. Its current-minute glucose (currentGlucose, decoded from word[5]) is keyed at the record's ownlifeCountwith no offset. The sensor buffers these records while the host is disconnected and replays the buffered window in a burst on resubscribe — field testing has seen 38+ contiguous per-minute records arrive within seconds of reconnect after a multi-tens-of-minutes outage.
The clinical stream is therefore the only published way to recover
minute-resolution glucose across a disconnect window. Apps that care about
gap-fill should subscribe to the clinical CCCD and forward currentGlucose
keyed at lifeCount, deduping against samples already received from the
realtime stream.
historicGlucoseRaw (word[6]) is the same 5-minute committed value the
realtime frame carries as its embedded historical and should not be keyed at
the clinical record's own lifeCount. When only a clinical record is in hand,
historicLifeCountEstimate snaps to the last 5-minute boundary at
lifeCount − 17; when the realtime frame is available, prefer its
authoritative historicalLifeCount.
After a sensor is initially paired and saved, an app can skip onboarding, avoid
repeating NFC activation/switch unless the saved state is missing, seed
Libre3DataPlaneState from the saved last glucose point, and request bounded
backfill instead of draining all available history.
Pristine post-pairing captures show two reconnect shapes:
- Cached/direct reconnect:
0x11 StartAuthorization, R1/nonce notify, Phase 5 write,0x08 SendChallengeLoadDone, then0843+ Phase 6. LibreCRKit exposes this asrunCachedReconnectPreambleandrunCachedReconnectHandshake. Callers that persist the raw kAuth blob can derive the cached Phase 5 raw key withChild23KAuthImport.phase5RawKey(forKAuthBlob:). - Full fallback authorization: certificate exchange, ephemeral exchange,
StartAuthorization, Phase 5, and Phase 6. LibreCRKit exposes this asrunCommandGatedAuthorizationPreambleandrunCommandGatedAuthorizationHandshake. The older first-pair method names remain for compatibility.
The cached/direct path is the preferred saved-state reconnect attempt when the integration has accepted Phase 5 material for that sensor/receiver state. If it is rejected before Phase 6, fall back to the full authorization path.
LibreCRKit is available under the MIT License. See LICENSE.