Sitemap

CGM App Development

Continuous Glucose Monitoring (CGM) BLE GATT

15 min readApr 23, 2026

--

Press enter or click to view image in full size

The landscape of metabolic health monitoring has undergone a paradigm shift, transitioning from episodic capillary blood glucose measurements to continuous, real-time sensing of glucose concentrations in the interstitial fluid (ISF). This evolution is underpinned by the proliferation of Bluetooth Low Energy (BLE) technology, which has replaced legacy radio frequency (RF) protocols to provide a energy-efficient, standardized communication layer between wearable sensors and consumer electronics. In the current marketplace, the ability to access raw data directly from these devices via the Generic Attribute Profile (GATT) is a critical requirement for developers building independent health applications, closed-loop insulin delivery systems, and research platforms.

Physiological and Technical Foundations of Continuous Sensing

To understand the engineering requirements for a BLE-connected glucose monitor, one must first appreciate the physiological environment in which these devices operate. A Continuous Glucose Monitor (CGM) does not measure blood glucose directly; instead, it utilizes a glucose oxidase-coated filament inserted into the subcutaneous tissue to measure the glucose concentration in the interstitial fluid. This physiological mechanism introduces a delay, known as “lag time,” between the systemic blood glucose level and the value reported by the CGM, typically ranging from 5 to 15 minutes. During periods of rapid glucose fluctuation, such as post-prandial spikes or intensive exercise, this lag can result in significant discrepancies between CGM readings and traditional finger-stick blood glucose monitor (BGM) values.

Accuracy in these systems is mathematically quantified using the Mean Absolute Relative Difference (MADR), calculated as:

Press enter or click to view image in full size

A lower MARD indicates higher precision. The engineering of a direct GATT connection must account for these physiological variations, particularly when implementing features like calibration or trend forecasting. Modern sensors like the SiBionics GS1 and Dexcom G7 have achieved single-digit MARD values, making them suitable for therapy-adjusting decisions.

Press enter or click to view image in full size

Evolution of Wireless Communication in CGMs

The transition to BLE was not instantaneous. Early generation devices, such as the Medtronic MiniMed series, utilized the 2.4 GHz Industrial, Scientific, and Medical (ISM) radio band with proprietary protocols. These systems often required a separate handheld receiver or a “bridge” device to translate RF signals into a format compatible with mobile phones. The inherent power consumption and interference issues of these custom RF protocols led to device recalls and limited the interoperability of metabolic data. With the adoption of BLE, manufacturers have moved toward a standardized protocol stack. BLE operates in the same 2.4GHz band but uses a frequency-hopping spread spectrum (FHSS) and a highly optimized GATT structure to minimize “on-air” time, thereby extending the battery life of a wearable sensor to up to 14 days on a single coin-cell or silver-oxide battery.

Press enter or click to view image in full size

Comprehensive Manufacturer Analysis and Device Identification

The selection of a device for direct BLE GATT server connection requires a detailed evaluation of fifteen major manufacturers. The primary criteria for the “best” device in this context are the accessibility of the BLE API, the lack of restrictive encryption barriers, the reliability of real-time data streaming, and the availability of community-driven documentation or reverse-engineering resources.

Press enter or click to view image in full size

The SiBionics GS1: Best Candidate for Direct GATT Integration

Following a rigorous comparison of the manufacturers listed above, the SiBionics GS1 is identified as the optimal device for direct real-time GATT server connection. Unlike the FreeStyle Libre series, which requires a manual NFC “scan” to initiate a BLE session or exchange encryption keys, the GS1 is designed as a truly “active” CGM.

The GS1’s “scan-free” architecture allows it to automatically broadcast glucose data every five minutes to a paired central device. From an engineering perspective, this is achieved through a proprietary low-power radio chip that initiates a connection with the central device without user intervention once the initial pairing — facilitated by a one-time QR code scan — is complete. The device provides 288 readings per day, and the communication protocol does not utilize the restrictive “DMCA-protected” encryption layers found in Abbott’s products, making it the most accessible high-fidelity sensor for third-party development.

The Medtrum TouchCare Nano: Open-Source Compatibility

The Medtrum TouchCare Nano is a strong secondary candidate, particularly for applications involving automated insulin delivery (AID). It is one of the few devices that has been integrated into open-source platforms like AndroidAPS. The Medtrum system utilizes a pump-base and reservoir model where the pump-base maintains a persistent BLE connection to the controller. The protocol allows for real-time status updates, including reservoir levels, battery voltage, and basal rate configurations, all accessible via GATT characteristics

The BLE GATT Hierarchy in Metabolic Wearables

To implement a direct connection to a CGM, developers must navigate the Generic Attribute Profile (GATT) hierarchy. This structure is the foundation of all BLE interactions and defines how data is discovered and accessed.

Profiles, Services, and Characteristics

A GATT server (the CGM) organizes its data into a hierarchy of Services and Characteristics.

  1. Profiles: A collection of services required to fulfill a use case (e.g., the Glucose Profile).
  2. Services: Groupings of related data points. A CGM will typically offer a Glucose Service (standardized as 0x1808 by the Bluetooth SIG) and several vendor-specific services for proprietary data.
  3. Characteristics: The lowest level of the hierarchy, representing a single piece of data, such as a glucose value, a timestamp, or a sensor status flag.
  4. Descriptors: Optional attributes that provide metadata about a characteristic. The most important for CGMs is the Client Characteristic Configuration Descriptor (CCCD), which has a 16-bit UUID of 0x2902. Writing to the CCCD allows a client to subscribe to notifications, which are the mechanism for real-time data streaming.

Attributes and UUIDs

Every element in the GATT database is an “Attribute.” An attribute consists of four components:

  • Attribute Handle: A 16-bit identifier that acts as the address of the attribute on the server.
  • Attribute Type (UUID): A 16-bit (SIG-adopted) or 128-bit (custom) identifier that defines what the attribute is.
  • Attribute Value: The actual data payload.
  • Permissions: Defines whether the attribute can be read, written, or if it requires authentication/encryption.

For the SiBionics GS1 and similar devices, identifying the 128-bit vendor-specific UUID is the first step in protocol integration. While standard services use a 16-bit representation, they are internally expanded to a 128-bit “Base UUID”: 0000xxxx-0000-1000-8000-00805F9B34FB.

Press enter or click to view image in full size

Technical Implementation Guide: Android (Kotlin)

Developing a robust CGM client on Android requires managing the complexities of the BluetoothGatt API, foreground services for persistence, and handling the specific threading requirements of the Bluetooth stack.

Foreground Service and Permissions

Medical applications must remain active in the background to receive glucose alerts. This necessitates a Foreground Service.

class CGMForegroundService : Service() {
private var bluetoothGatt: BluetoothGatt? = null

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = createNotification()
startForeground(NOTIFICATION_ID, notification)
initiateScan()
return START_STICKY
}
//... notification and lifecycle management
}

The application must request BLUETOOTH_SCAN, BLUETOOTH_CONNECT, and ACCESS_FINE_LOCATION (required for BLE on most Android versions).

Device Scanning with Filters

To connect to the GS1, the app should scan for the specific service UUID using ScanFilter. This reduces power consumption and ensures the app only interacts with relevant hardware.

val scanFilter = ScanFilter.Builder()
.setServiceUuid(ParcelUuid.fromString(SI_BIONICS_SERVICE_UUID))
.build()

val scanSettings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()

bluetoothLeScanner.startScan(listOf(scanFilter), scanSettings, scanCallback)

GATT Connection and Service Discovery

Once a device is found, the app establishes a connection. It is critical to perform service discovery only after the connection state has transitioned to STATE_CONNECTED.

private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
}
}

override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
enableGlucoseNotifications(gatt)
}
}
}

Subscribing to Real-Time Data

To receive data every five minutes, the app must enable notifications on the glucose characteristic. This involves a two-step process: enabling local notifications in the Android stack and writing to the remote CCCD.

private fun enableGlucoseNotifications(gatt: BluetoothGatt) {
val service = gatt.getService(UUID.fromString(GS1_SERVICE_UUID))
val characteristic = service.getCharacteristic(UUID.fromString(GS1_MEASUREMENT_UUID))

gatt.setCharacteristicNotification(characteristic, true)

val descriptor = characteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
}

MTU Negotiation and Threading

Android’s default MTU is 23 bytes. If the CGM sends larger packets (e.g., history backfill), requesting a higher MTU is necessary. All GATT operations should be serialized on a background thread to avoid blocking the main UI thread.

gatt.requestMtu(517)
Press enter or click to view image in full size

Android Source:

Technical Implementation Guide: iOS (Swift)

To build a modern CGM stack on iOS in 2027+, you shouldn’t just be reacting to delegate callbacks. You should be architecting a thread-safe, stream-based system that leverages Swift Structured Concurrency to handle the high-frequency data bursts typical of real-time monitoring.

Since CoreBluetooth remains a delegate-based framework, the "latest tech" approach involves bridging those delegates into the async/await world.

The Actor-Centric BLE Stack

For CGM, thread safety is non-negotiable. Using a Swift Actor to wrap your CBCentralManager ensures that all Bluetooth state changes—scanning, connecting, and GATT discovery—happen on a dedicated, isolated thread, preventing the "race conditions" common in old-school delegate implementations.

  • withCheckedContinuation: Use this for one-shot operations like connecting. It pauses the Task until the delegate returns success or failure, making your code look linear.
  • Actor Isolation: By moving your BLE logic into an actor, you eliminate the need for manual DispatchQueue management.

Bridging Notifications to AsyncStream

The heart of real-time monitoring is the notification from the wearable’s GATT server. Instead of handling these in a scattered didUpdateValueFor method, you should wrap them in an AsyncStream.

Get YLabZ’s stories in your inbox

Join Medium for free to get updates from this writer.

This creates a “pipe” where the BLE delegate pushes data in one end, and your UI (or data processor) consumes it at the other end using a simple for await loop.

// A modern approach to consuming glucose readings
actor GlucoseMonitor {
private var streamContinuation: AsyncStream<GlucoseReading>.Continuation?

var glucoseData: AsyncStream<GlucoseReading> {
AsyncStream { continuation in
self.streamContinuation = continuation
}
}

// Inside CBCentralManagerDelegate
nonisolated func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
Task {
await self.processAndPush(characteristic.value)
}
}
}

Live Activities & Dynamic Island

The “Gold Standard” for modern CGM apps is no longer just a chart inside the app; it’s a Live Activity. Using ActivityKit, you can push real-time glucose numbers and trend arrows directly to the Dynamic Island and Lock Screen.

  • Frequent Updates: In iOS 17.2+, Apple optimized Live Activities to support more frequent updates via push notifications or local triggers, perfect for a device sending data every minute.
  • Stale Data Protection: You can implement a “stale data” UI state if the BLE connection drops, ensuring the user doesn’t rely on a flatline reading.

HealthKit & SwiftData Integration

For long-term storage and cross-device syncing, HealthKit is mandatory. However, for your app’s local high-resolution history, SwiftData (the successor to CoreData) is the latest way to handle persistence.

  • HealthKit Background Delivery: Register for background updates so your app can process data even when minimized.
  • SwiftData Modeling: Use the @Model macro to store raw BLE frames for debugging or proprietary algorithm refinement.

Recommended Libraries

While many senior architects (like you dear reader) prefer a thin custom wrapper to avoid dependency bloat, these libraries are currently leading the “Async-First” movement:

Press enter or click to view image in full size

~~~~~~~~~~~~~~~~The old BAD way … only for historical use ~~~~~~~~

iOS provides a high-level abstraction through the CoreBluetooth framework. Unlike Android, iOS handles much of the underlying GATT state machine automatically but imposes stricter background execution limits.

Central Manager Setup and Background Modes

The CBCentralManager is the primary entry point. The application must include the bluetooth-central background mode in its Info.plist to receive updates while the phone is locked.

class CoreBluetoothManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
var centralManager: CBCentralManager!
var sensorPeripheral: CBPeripheral?

override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil, options:)
}
}

State restoration ensures that if the app is terminated by the system due to memory pressure, it can be re-instantiated when the CGM sends a new notification.

Scanning and Connecting

iOS allows scanning for specific CBUUIDs, which is more power-efficient than broad scans.

func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state ==.poweredOn {
centralManager.scanForPeripherals(withServices:, options: nil)
}
}

Service and Characteristic Discovery

The delegate-based architecture of CoreBluetooth requires several callbacks to map the sensor's services and characteristics.

func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices()
}

func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
peripheral.discoverCharacteristics(, for: service)
}
}

Enabling Notifications

iOS simplifies the CCCD write process. Calling setNotifyValue(true, for: characteristic) handles both the local stack and the remote descriptor write.

func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
if characteristic.uuid == CBUUID(string: GS1_MEASUREMENT_UUID) {
peripheral.setNotifyValue(true, for: characteristic)
}
}
}

Data Parsing and Endianness

When data arrives via didUpdateValueFor, it is received as a Data object (byte array). Because BLE uses little-endian byte ordering for multi-octet fields, developers must carefully reconstruct values.

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
guard let data = characteristic.value else { return }
let glucoseRaw = data.subdata(in: 2..<4).withUnsafeBytes { $0.load(as: UInt16.self) }
let glucoseValue = UInt16(littleEndian: glucoseRaw)
// Notify application observers
}
Press enter or click to view image in full size

Authentication and Cybersecurity Challenges

While the SiBionics GS1 offers a relatively straightforward GATT connection, other major manufacturers have implemented advanced security measures that represent the “frontier” of medical device engineering.

J-PAKE Authentication in Dexcom G7

The Dexcom G7 utilizes the Password Authenticated Key Exchange by Juggling (J-PAKE) protocol. This protocol allows the sensor and the phone to establish a secure, encrypted channel based on a shared secret (the pairing code) without that secret ever being transmitted over the air.

The J-PAKE process involves two phases:

  1. Phase 1: Both devices exchange zero-knowledge proofs and public keys derived from the pairing code.
  2. Phase 2: Both devices derive a final session key using the mbedtls framework or equivalent cryptographic libraries.

For developers, this means a simple GATT connection is insufficient; the client must implement a state machine that handles these challenge-response opcodes (0x06, 0x07, etc.) before the sensor will grant access to glucose characteristics.

Encryption and “App Hooking” in Abbott FreeStyle Libre 3

Abbott’s Libre 3 employs a more restrictive model. The sensor utilizes AES-128 CCM encryption and ECDH key agreement. The encryption keys are exchanged via NFC during sensor initialization, meaning a third-party app cannot simply “connect” to a Libre 3 without first having the keys.

In the open-source community, this has led to the use of “hooking” tools like Frida. By hooking the official Libre 3 Android application, developers can intercept the unWrapDBEncryptionKey function calls to extract the keys from the device's memory. This highlights a growing tension in the industry between manufacturer "walled gardens" and the user's right to their own physiological data.

L2CAP Connection-Oriented Channels

A recent trend in BLE development is the move toward L2CAP Connection-Oriented Channels (CoC) for large data transfers, such as firmware updates or historical backfill. Unlike GATT, which is limited by the overhead of attribute protocol (ATT) headers, L2CAP CoC provides a direct, stream-like socket between devices. However, this introduces new technical hurdles, as seen in recent Android updates where L2CAP connections timed out after 30 seconds, causing pairing failures in medical-grade accessories.

Comparative Analysis of Data Payloads and Packet Formats

Integrating multiple manufacturers into a single platform requires a flexible parsing engine. Most devices follow a variant of the IEEE-11073 20601 Personal Health Device Communication standard.

Standard Glucose Measurement Packet (0x2A18)

A typical measurement packet from a device like the CareSens Air or Dexcom includes:

  • Flags (8–16 bits): Indicates presence of timestamp, concentration, status, etc.
  • Sequence Number (16 bits): Increments with every reading to detect packet loss.
  • Base Time (7 bytes): Year, Month, Day, Hour, Minute, Second.
  • Glucose Concentration (16-bit SFLOAT): IEEE-11073 16-bit float.

The SFLOAT format is critical for cross-platform compatibility and is defined as:

Press enter or click to view image in full size

Where the exponent is a 4-bit signed integer and the mantissa is a 12-bit signed integer. For example, if the CGM reports a mantissa of 127 and an exponent of 0, the glucose value is 127 mg/ dL.

SiBionics GS1 Proprietary Packet

The GS1 broadcasts every 5 minutes with a payload that includes:

  • Encrypted Body: Requires decryption using the key derived from the QR code/Serial number.
  • Timestamp/Minute Counter: Number of minutes since sensor activation.
  • Glucose Value: Direct mg/dL representation.
  • Status Byte: Indicates if the sensor is in the 60-minute warm-up period or near expiration.

Interoperability Strategy

The Unified Implementation Model

The “best” device identification (SiBionics GS1) is only as useful as the implementation’s ability to handle the “edge cases” of metabolic monitoring. A production-grade GATT client must account for:

1. Connection Robustness and Backfill

CGM connections are inherently unstable due to the wearable nature of the device. Body blocking (the human body absorbing 2.4GHz signals) is a common cause of disconnection. A robust implementation must:

  • Use autoConnect = true on Android to allow the system to reconnect whenever the sensor is in range.
  • Implement backfill logic to request missed data points once a connection is re-established. On Dexcom G7, this involves writing opcode 0x02 to the Control Point characteristic.

2. Time Synchronization

Sensors operate on an internal clock (minutes since activation), while phones operate on wall-clock time. The implementation must map the sensor’s “tick” counter to a UTC timestamp, adjusting for any clock drift that occurs over the 14-day wear time.

3. Error Handling and Alarms

Direct GATT connections place the responsibility for safety on the developer. The app must distinguish between a “signal loss” (BLE disconnected) and a “sensor error” (BLE connected, but data indicates hardware failure). Manufacturers like Medtrum provide detailed alarm characteristics to differentiate these states.

Future Outlook

Standardized Medical BLE and Regulatory Evolution

The current state of “reverse-engineering” direct GATT connections is likely a transitional phase. Regulatory bodies like the FDA are increasingly pushing for “interoperable CGM” (iCGM) standards, which mandate that devices must be capable of communicating with other compatible devices, such as insulin pumps and mobile apps, in a standardized way.

The next generation of devices from manufacturers like Biolinq and Nemaura are expected to adopt more standardized BLE profiles from the outset. Furthermore, the integration of multiple sensors (e.g., glucose, ketone, and lactate) into a single wearable will require more complex GATT databases with multiple service instances.

Conclusion

Engineering the Path to Data Liberation

The identification of the SiBionics GS1 as the premier device for direct BLE GATT server connection is predicated on its “active transmission” architecture, which prioritizes real-time data accessibility over the restrictive scanning models used by traditional market leaders. However, a successful technical implementation for Android and iOS requires more than just establishing a connection; it demands a deep understanding of the GATT hierarchy, the nuances of medical-grade cybersecurity like J-PAKE, and the physiological realities of interstitial sensing.

By leveraging the provided Kotlin and Swift implementation guides, developers can build metabolic monitoring platforms that are not only high-performing but also resilient to the physical and technical challenges of wearable technology. The future of personalized health lies in this ability to “liberate” data from proprietary silos and integrate it into a broader, interconnected ecosystem of metabolic care. The engineering strategies detailed in this report provide the necessary blueprint for achieving this interoperability in the modern digital health landscape.

Android App:

Press enter or click to view image in full size

Android Source:

--

--