Developer Information

Technical documentation for developers who want to integrate CGM data into their own watchfaces.

Available Complications

The app provides 3 complications that can be read using Garmin's complication system.

Complication 1: Simple Value

Simple human readable current value, can be displayed directly.

  • shortLabel: CGM
  • longLabel: CGM-GB-value
  • value: (string) Numeric reading or error code (mg/dL or mmol/L, depending on user setting. Decimals are based on the user setting in the app)
  • unit: (string) mg/dL or mmol/L
  • ranges: Null

Complication 2: Extended Data

Meant for better integration with progress bars, trend icons, and range coloring.

  • shortLabel: D-DATA
  • longLabel: CGM-GB-data
  • value: (string) Numeric reading or error code (mg/dL or mmol/L, depending on user setting. Decimals are based on the user setting in the app))
  • unit: (string) mg/dL or mmol/L

ranges: Array of Floats (use toNumber() to convert)

Index Description
0 Unit (0 = mg/dL, 1 = mmol/L)
1 Trend Arrow (0-10, see below)
2 Low Target (for yellow coloring)
3 High Target (for yellow coloring)
4 Very Low (for red coloring)
5 Very High (for red coloring)
6 Timestamp of sample from Dexcom
7 Timestamp when sample should be considered old (add "-") to the value
8 Timestamp when sample should not be shown as it is too old.

⚠️ Important for developers: The "old" and "too old" timestamps (indices 7 and 8) are crucial for proper implementation. When a watch loses connection, complications don't get updated on most watch models, so your watchface needs to check these timestamps and handle stale data appropriately to prevent showing outdated glucose values.

Complication 3: Graph Data

Meant for drawing a graph for up to 24 hours of the Glucose readings.

  • shortLabel: D-GRAPH
  • longLabel: CGM-GB-graph
  • value: Numeric reading or error code (mg/dL or mmol/L)
  • unit: mg/dL or mmol/L

ranges: Array of 288 Floats

• Contains last 24 hours of readings (288 total if available)

• Each reading is 5 minutes apart

First value = most recent reading

• A value of 0 means no data at that time

Trend Arrow Values

The trend arrow values (index 1 in Complication 2 ranges array) indicate the direction and rate of glucose change.

Value Direction Arrow
0 None/Unknown -
1 Double Up
2 Single Up
3 Forty Five Up
4 Flat
5 Forty Five Down
6 Single Down
7 Double Down
8 Not Computeable -
9 Out of Range -

Watchface Integration

Developers can integrate CGM data into their watchfaces using the following approach:

Basic Integration Example

import Toybox.System;
import Toybox.Lang;
import Toybox.Complications;


module gbDexcomConsumer {

    var complicationId as Complications.Id?;

    // Find the complication by longLabel.
    function getComplicationId() as Complications.Id or Null {        
        var i = Complications.getComplications();
        var c = i.next();
        while (c != null) {    
            if ("CGM-GB-value".equals(c.longLabel)) {
                return c.complicationId;   
            }       
            c = i.next();
        }
        // Not installed?        
        return null;
    }

    function getData() as String {
        if (!(complicationId instanceof Complications.Id)) {
            complicationId = getComplicationId();
            if (!(complicationId instanceof Complications.Id)) {
                return "--";
            }
        }

        // Get the complication and return the value as string.
        var complication = Complications.getComplication(complicationId) as Complications.Complication?; 
        return complication == null || complication.value == null ? "--" : complication.value + "";
    }
}

// In your onUpdate method:

function onUpdate(dc as Dc) {
    System.println(gbDexcomConsumer.getData()); 
}

Advanced Integration Example (with Stale Data Handling)

This example shows how to properly handle stale data using Complication 2's timestamp data. This is essential because complications don't update when the watch loses connection.

import Toybox.System;
import Toybox.Lang;
import Toybox.Time;
import Toybox.Complications;

module gbDexcomConsumer {

    var complicationId as Complications.Id?;

    // Find the complication by longLabel for extended data
    function getComplicationId() as Complications.Id or Null {        
        var i = Complications.getComplications();
        var c = i.next();
        while (c != null) {    
            if ("CGM-GB-data".equals(c.longLabel)) {
                return c.complicationId;   
            }       
            c = i.next();
        }
        return null;
    }

    // Get glucose value with proper stale data handling
    function getValue() as Number {
        if (!(complicationId instanceof Complications.Id)) {
            complicationId = getComplicationId();
            if (!(complicationId instanceof Complications.Id)) {
                return -1;  // Not installed
            }
        }

        var complication = Complications.getComplication(complicationId);
        if (complication == null || complication.value == null) {
            return -1;  // No data
        }

        var val = complication.value.toNumber();
        
        // Check if ranges array has stale data timestamps
        if (complication.ranges instanceof Array && complication.ranges.size() >= 9) {
            var oldTimestamp = complication.ranges[7];
            var tooOldTimestamp = complication.ranges[8];
            
            if (oldTimestamp instanceof Float && tooOldTimestamp instanceof Float) {
                var nowTimestamp = Time.now().value();
                
                // Data is too old - show -1
                if (nowTimestamp > tooOldTimestamp) {
                    val = -1;
                } 
                // Data is stale - negate value (adds minus prefix)
                else if (nowTimestamp > oldTimestamp && val > 0) {
                    val = -val;
                }
            }
        }
        
        return val;
    }

    // Get trend arrow value
    function getTrendArrow() as Number {
        if (!(complicationId instanceof Complications.Id)) {
            return 0;
        }
        
        var complication = Complications.getComplication(complicationId);
        if (complication != null && complication.ranges instanceof Array && complication.ranges.size() >= 2) {
            return complication.ranges[1].toNumber();
        }
        return 0;
    }

    // Get target ranges for color coding
    function getTargetRanges() as [Number, Number, Number, Number]? {
        if (!(complicationId instanceof Complications.Id)) {
            return null;
        }
        
        var complication = Complications.getComplication(complicationId);
        if (complication != null && complication.ranges instanceof Array && complication.ranges.size() >= 6) {
            return [
                complication.ranges[2].toNumber(),  // Low Target
                complication.ranges[3].toNumber(),  // High Target
                complication.ranges[4].toNumber(),  // Very Low
                complication.ranges[5].toNumber()   // Very High
            ];
        }
        return null;
    }
}

// In your onUpdate method:

function onUpdate(dc as Dc) {
    var glucoseValue = gbDexcomConsumer.getValue();
    var trendArrow = gbDexcomConsumer.getTrendArrow();
    var ranges = gbDexcomConsumer.getTargetRanges();
    
    System.println("Glucose: " + glucoseValue);
    System.println("Trend: " + trendArrow);
    System.println("Ranges: " + ranges);
}

💡 Key Implementation Notes:

  • • Always check if ranges array exists and has enough elements before accessing indices
  • • Compare current time with timestamps at indices 7 and 8 to detect stale data
  • • Return -1 when data is too old (past index 8 timestamp)
  • • Negate the value when data is stale (past index 7 timestamp)
  • • This prevents displaying outdated glucose values when the watch is disconnected

⚠️ Important Disclaimer

  • NOT FOR MEDICAL DECISIONS: This app is for informational purposes only and should NOT be used to make medical decisions.
  • Always verify readings: Confirm glucose values with your official Dexcom receiver or app before taking action.
  • Emergency situations: In case of extreme glucose levels or medical emergencies, use your primary CGM device.
  • Delayed data: Readings may be delayed by several minutes due to how often the app can sync.
  • No liability: The developer assumes no responsibility for health outcomes related to the use of this app.
  • Not affiliated: This app is not approved by, endorsed by, or affiliated with Dexcom, Abbott (Libre), or any other medical device company.