Connect IQ SDK

Positioning and Sensors

Monkey C provides access to the wearable’s available sensors, which may include the GPS, altimeter, thermometer, and supported ANT sensors.

Location

A Location is an abstraction of a coordinate. It exposes the ability to retrieve the coordinates in radians or decimal degrees and then provides a method to convert to coordinate formats supported by the Garmin system. The Position module also exposes string parsing interface to convert from various coordinate formats to a Location object.

// The GEO enum is used to retrieve coordinates in various String representations.
enum
{
    GEO_DEG,    // Degree Format, ddd.dddddd: 38.278652
    GEO_DM,     // Degree/Minute Format, dddmm.mmm: 38 27.865'
    GEO_DMS,    // Degree/Minute/Seconds Format, dddmmss: 38 27' 8"
    GEO_MGRS    // Military Grid Reference System (MGRS): 4QFJ12345678
}

// The Location object represents a position. It provides accessor
// methods for retrieving the coordinates in various formats.
class Location
{
    // Constructor: create a coordinate based off an options hash table
    // @param [Dictionary] options Hash table of options
    // @option options [Number] :latitude The latitude
    // @option options [Number] :longitude The longitude
    // @option options [Symbol] :format The format of lat/long (possible
    //        values are :degrees, :radians, or :semicircles)
    function initialize( options );

    // Use toDegrees() to retrieve the coordinate back as an Array of degree values.
    // @return [Array] An Array of the latitude and the longitude in degree format
    function toDegrees();

    // Use toRadians() to retrieve the coordinate back as an Array of radian values.
    // @return [Array] An Array of the latitude and the longitude in radian format
    function toRadians();

    // Use toGeoString() to get a String representation of the coordinate.
    // @param format Coordinate format to which coordinate should be
    //      converted (GEO constant)
    // @return [String] Formatted coordinate String
    function toGeoString( format );
}

Location Events

To enable the GPS call the enableLocationEvents() method. The parameters are outlined below:

enum
{
    LOCATION_ONE_SHOT,      // One-time retrieval of Location
    LOCATION_CONTINUOUS,    // Register for Location updates
    LOCATION_DISABLE        // Unregister for Location updates
}

// Request a location event with enableLocationEvents().
// @param type LOCATION_ONE_SHOT for a single location request,
//       LOCATION_CONTINUOUS to enable location tracking, and
//       LOCATION_DISABLE to turn off location tracking
// @param [Method] listener Method object to call with location updates
function enableLocationEvents( type, listener );

To register a position listener, use the method() call to create a Method callback:

function onPosition( info ) {
    Sys.println( "Position " + info.position.toGeoString( Position.GEO_DM ) );
}

function initializeListener() {
    Position.enableLocationEvents( Position.LOCATION_CONTINUOUS, method( :onPosition ) );
}

All of the location information will be sent in an Info object:

// The Location.Info class contains all information necessary for the Location.
// It can be passed on the update or it can be retrieved on demand.
class Info
{
    var position;   // Lat/lon
    var speed;      // Speed in meters per second
    var altitude;   // Altitude in meters, mean sea level
    var accuracy;   // Accuracy - good, usable, poor, not available
    var heading;    // Heading in radians
    var when;       // Moment Object: GPS time stamp of fix
}

// Use getInfo() to retrieve the current Location.Info
// @return [Location.Info] The Info object containing the current information
function getInfo();

Sensors

The Sensor module allows the app to enable and receive information from Garmin ANT+ sensors. To receive information, a listener method must be assigned and the sensors enabled:

function initialize() {
    Sensor.setEnabledSensors( [Sensor.SENSOR_HEARTRATE] );
    Sensor.enableSensorEvents( method( :onSensor ) );
}

function onSensor(sensorInfo) {
    System.println( "Heart Rate: " + sensorInfo.heartRate );
}

Sensor information is packaged in the Sensor.Info object:

// The Sensor.Info class contains all information necessary for the Sensor.
// It can be passed on the update or it can be retrieved on demand.
class Info
{
    var speed;          // Speed in meters per second
    var cadence;        // Cadence in revolutions per minute
    var heartRate;      // HR in beats per minute
    var temperature;    // Temperature in degrees Centigrade
    var altitude;       // Altitude in meters
    var pressure;       // Pressure in Pa
    var heading;        // Heading in Radians
}

The simulator can simulate sensor data via the Simulation menu by selecting Fit Data > Simulate Data. This generates valid but random values that can be read in via the sensor interface. For more acuate simulation, the simulator can play back a FIT file and feed the input into the Sensor module. To do this, select Simulation > Fit Data > Playback File and choose a FIT file from the dialog.

Sensor History (2.1)

The SensorHistory module allows the app to access saved sensor history on the device. Data from sensors can be accessed by getting an iterator.

function getHeartRateHistory(options);
function getTemperatureHistory(options);
function getPressureHistory(options);
function getElevationHistory(options);

All Sensor History types are not available on all devices. Capabilities should be validated using the ‘has’ operator. The get functions will return a SensorHistoryIterator type.

class SensorHistoryIterator {
    function next();
    function getMax();
    function getMin();
    function getNewestSampleTime();
    function getOldestSampleTime();
}

Calling the next() function will iterate through the history values until the end of the data is reached. When there is no more data the iterator will return null. This function returns a SensorSample:

class SensorSample {

    var data; // Sample data. Can be null if not valuid
    var when; // The time this sample occurred
}

The iterator can be adjusted to provide the newest data values first or the oldest values first by using the enumeration values ORDER_NEWEST_FIRST and ORDER_OLDEST_FIRST

Fit File Recording

Imagine you are trying to create a Yoga app. You’d like the app to record heart rate and calories burned during your Yoga workout, just like other Garmin apps. You’d also like the recording to be represented on Garmin Connect. Monkey C allows for apps to start and stop recording of FIT files. Controlling the FIT file recording requires a few steps:

  1. Enable the sensors to be recorded
  2. Use Fit.createSession(options) to create a session object
  3. Use the start() method of the FIT session to begin recording. Data from the enabled sensors will be recorded into the FIT file.
  4. Use stop() to pause the recording
  5. Use save() to save the recording, or discard() to delete the recording

The FIT file will sync with Garmin Connect. You can use the Garmin Connect API to process the FIT file from a web service.

Recording FIT Files (CIQ 1.3)

In addition to being able to play back existing FIT files, the simulator can also record files using data obtained via the Fit Data>Simulate Data menu option. To begin recording a session you must start the timer; this can be done via the Data Fields>Timer>Start Activity menu option, or by clicking the corresponding start button on the device you have selected. The following table describes the different options available for activity recording and provides instructions for how to access them via the menu bar, or the device buttons (where available).

Option Menu Button Device Button Notes
Start Activity Data Fields>Timer>Start Activity Start Button The device start button starts and stops an activity.
Stop Activity Data Fields>Timer>Stop Activity Start Button The device start button starts and stops an activity, and the Start Activity menu item is renamed to Stop Activity when recording is active.
Lap Activity Data Fields>Timer>Lap Activity Back Button Records a lap record in the FIT file.
Pause Activity Data Fields>Timer>Pause Activity N/A Pauses activity recording. Note this option is only available when FIT data simulation is enabled.
Resume Activity Data Fields>Timer>Resume Activity N/A Resumes activity recording. Note this option is only available when FIT data simulation is enabled and activity recording is paused.
Discard Activity Data Fields>Timer>Discard Activity N/A Discards the current recording and deletes the corresponding .fit file. Note this is only enabled if activity recording has been started and subsequently stopped.
Save Activity Data Fields>Timer>Save Activity N/A Saves the recorded activity into a .fit file and resets the timer state. Note this is only enabled if activity recording has been started and subsequently stopped.

Note: the device simulator will not automatically start activity recording when a data field is being run. You must excplitily start and stop activity recording via the menu options described here.

FIT Developer Fields (CIQ 1.3)

Now imagine you want to add a new metric - Namastes - to the Yoga app[1]. This metric will combine heart rate, accelerometer, and other sensor data into a single value, and does not have an analog in any Garmin recording metric. To do this we need the FitContributor API, which allows you to add new metrics to a FIT recording and display it on Garmin Connect.

First, you must enable the FitContributor permission in the manifest file. Next, you need to add your field definitions in your resources using the fitContributions block:

    <strings>
        <string id="namaste_label">Namastes</string>
        <string id="namaste_graph_label">Namastes</string>
        <string id="namaste_units">N(s)</string>
    </strings>
    <fitContributions>
        <fitField id="0" displayInChart="true" sortOrder = "0" precision="2"
        chartTitle="@Strings.namaste_graph_label" dataLabel="@Strings.namaste_label"
        unitLabel="@Strings.namaste_units" fillColor="#FF0000" />
    </fitContributions>

The fitField block has a number of configurable options:

Attribute Value Notes
id A numeric value from 0 to 255 used to refer to your field No duplicates within an app are allowed
displayInChart Indicates whether or not record level connect IQ data should be rendered in a chart true if you want this entry to be displayed as a chart, false otherwise. Graph fields can only support numeric data.
displayInActivityLaps Indicates whether or not lap level connect IQ data should be rendered in the Activity Laps section in the Garmin Connect Activity Details page true if you want this entry to be displayed in the activity laps, false otherwise
displayInActivitySummary Indicates whether or not activity (fit session) level connect IQ data should be rendered in the Activity Summary section in the Garmin Connect Activity Details page true if you want this entry to be displayed in the activity summary data, false otherwise
sortOrder Determines the order in which the Connect IQ data will appear in the Summary or Lap Sections of the Activity Details page and what order charts will be displayed on the Activity Details page No duplicates are allowed
precision Decimal point precision for numeric data 0 for integer, 1 for one decimal point, 2 for two decimal points. Without this attribute the default is no rounding
chartTitle This is the resources string key to use to render the title of the chart Optional if displayInChart is false. Must be a string resource.
dataLabel This is the resources string key to use to render the label of the data field in the Activity Summary or Activity Laps section of the Activity Details page (ex. Cadence, or Heart Rate). Must be a string resource
unitLabel This is the key to use to render the unit of the data field in the Activity Summary or Activity Laps section of the Activity Details page (ex. kph, or miles). Must be a string resource
fillColor RRGGBB value of color to use for chart Optional if displayInChart is false

This will communicate the metadata to display our chart to Garmin Connect. Now we need to create our Field in the code. You do this by using the createField method of the Session object within your source:

// Field ID from resources.
const NAMASTE_FIELD_ID = 0;
hidden var mNamasteField;

// Initializes the new Namaste field in the activity file
function setupField(session) {
    // Create a new field in the session.
    // Current namastes provides an file internal definition of the field
    // Field id _must_ match the fitField id in resources or your data will not display!
    // The field type specifies the kind of data we are going to store. For Record data this must be numeric, for others it can also be a string.
    // The mesgType allows us to say what kind of FIT record we are writing.
    //    FitContributor.MESG_TYPE_RECORD for graph information
    //    FitContributor.MESG_TYPE_LAP for lap information
    //    FitContributor.MESG_TYPE_SESSION` for summary information.
    // Units provides a file internal units field.
    mNamasteField = session.createField("current_namastes", NAMASTE_FIELD_ID, FitContributor.DATA_TYPE_FLOAT, { :mesgType=>Fit.MESG_TYPE_RECORD, :units=>"N" });
}

Now when you call setData on mNamasteField the value will be recorded into either the Record, Lap, or Session information of the FIT file based on the message type specified when calling createField. If you are creating a Record (graph), you should update this value once a second. For lap and summary, you should provide constant updates to the current lap or workout value for the metric.

After setting this all up, you will want to preview how this will look on Garmin Connect. We can use the monkeygraph tool to create a preview. You can start the monkeygraph tool with following from the command line:

$ monkeygraph

The monkeygraph tool requires the following: a FIT file with the recorded developer data, and an IQ file of the app. The tool will allow you to test how charts will look before uploading your app for review.

Communicating With ANT/ANT+ Sensors

Generic ANT channels

Connect IQ provides a low level interface for communication with ANT and ANT+ sensors. With this interface, an ANT channel can be created to send and receive ANT packets. The MO2Display sample provides a sample application that implements the Muscle Oxygen ANT profile. The ANT Generic interface is not available to watch faces. Low and High priority search timeout for sensors differs from the basic ANT radio specification to allow for interoperation with native ANT behavior on devices. These are limited to a maximum timeout of 30 seconds and 5 seconds respectively.

Burst Data (CIQ 2.2)

Burst data transmission provides a mechanism for large amounts of data to be sent between devices over an ANT Generic Channel. Developers are notified through a listener of the success/failure of burst transmit/receive events. Burst data transmission is limited to up to 8Kb of data at a time.

Common use cases for this include passkey authentication or sending/receiving configuration data between devices.

The GenericChannelBurst sample provides a demonstration of transmitting and receiving burst data.

ANT+ Profiles (CIQ 2.2)

The AntPlus module allows access to information about ANT+ sensors that are paired to a user’s device without requiring you to set up and manage the ANT channel yourself. All management of ANT+ sensors such as adding, removing, enabling, disabling, and calibrating is managed by the user via the device’s regular sensor menus.

An extension of a sensor-specific listener is passed into the constructor for a sensor-specific extension of Device. If there is a sensor of the given type paired to the user’s device, information about that sensor can be retrieved using the sensor-specific getters, or through the common data getters such as GetBatteryStatus(identifier). Null can be passed in as the identifier for sensor types (like most) that do not support multi-components.

Callbacks in the DeviceListener and extensions of it will be called automatically if a sensor of the given type is paired and the corresponding information is updated via ANT. For example, onDeviceStateUpdate() will be called if a sensor’s ANT channel goes from connected to searching, or if the user switches the sensor ID of a given type that their device is connected to. Callbacks like onCalculatedPowerUpdate() will be called when new pieces of information about a power sensor are received via ANT.

Certain ANT+ sensors, such as bike lights, have special callbacks. For example, the onLightNetworkStateUpdate() callback should be used to understand the light network’s state rather than onDeviceStateUpdate(). The LightNetwork class will allow you to make changes to bike light modes, given there are bike lights paired to the user’s device and a light network is fully formed.

Not all ANT+ profiles provisioned by Monkey C will be supported by every Connect IQ-compatible device.


  1. How can we possibly tell who is more at peace with the universe if we don’t have a metric?  ↩