How to Develop Application Metrics with Garmin Connect IQ


This is the second part of a 3-part post about creating an app for the active lifestyle using the Garmin Connect IQ app system; part 1 focused on page layout, while part 2 focuses on page flow and metrics, and part 3 focuses on recording metrics.

The Model-View-Controller (MVC) paradigm is a common UI design pattern. The idea is to separate the data and rules of the application from the visualization and manipulation.

The Connect IQ WatchUi enforces a separation between the view and handling input. The Connect IQ design does not impose an MVC design on your app, but it can be helpful to apply the pattern when developing apps. When publishing your UI to the screen, you provide two classes: a View instance and an InputDelegate instance. The View class provides the framework for drawing a page on the screen, while the InputDelegate provides the framework for handling screen taps, button presses and other inputs.

Connect IQ devices can vary in many ways, though especially with Input Handling. Some products only take button inputs, while other products have touchscreens, and some are mixtures of the two. In order to simplify input handling across devices, we encourage developers to use the BehaviorDelegate class. The BehaviorDelegate is a subclass of the InputDelegate that maps common actions to a single behavior. Rather than having to know how a particular device handles the start button, you can use the onSelect behavior to handle them all.
The Namaste app has a controller between the view and the model. This abstracts the page flow logic from the display and input handling logic.

Always Delegate

The NamasteDelegate handles input for the app by forwarding it to the NamasteController.

// The NamasteDelegate forwards the inputs to the
// NamasteController. One might say it _delegates_
// its responsibilities... what, okay, I'll stop.
class NamasteDelegate extends Ui.BehaviorDelegate {
   
    // Controller class
    var mController;

    // Constructor
    function initialize() {
       // Initialize the superclass
       BehaviorDelegate.initialize();
       // Get the controller from the application class
       mController = Application.getApp().controller;
    }

    // Input handling of start/stop is mapped to onSelect
    function onSelect() {
       // Pass the input to the controller
       mController.onStartStop();
       return true;
    }

    // Block access to the menu button
    function onMenu() {
       return true;
    }
}

A delegate can choose what inputs to handle by overriding the appropriate BehaviorDelegate and InputDelegate methods. If the input is handled, the method should return true; otherwise, the input will be passed on to the system.

We currently have a hole in our flow: if the user hits the back button from the main view, the app will immediately exit. If they are recording, we need to get user confirmation the user wants to exit. To do this, let’s add back button handling to the NamasteDelegate:

    // Handle the back action
    function onBack() {
       // If the timer is running, confirm they want to exit
       if(mController.isRunning()) {
           WatchUi.pushView(new WatchUi.Confirmation("Are you sure?"),
               new NamasteConfirmationDelegate(mController), WatchUi.SLIDE_IMMEDIATE );
           // Don't let the system handle the message!
           return true;
       }
       // Pass the message through to the system
       return false;
    }

This will capture the back button, and ask the controller if the timer is currently running. If it is running, push a confirmation dialog to the screen. Otherwise, pass the message down to the system for processing.

We also need to add the new NamasteConfirmationDelegate to the file. Put the following code over the NamasteDelegate:

// Confirmation delegate used by the NamasteDelegate
class NamasteConfirmationDelegate extends Ui.ConfirmationDelegate
{
   hidden var mController;

    // Hold onto the controller
   function initialize(controller) {
       ConfirmationDelegate.initialize();
       mController = controller;
    }

    // Handle the confirmation dialog response
   function onResponse(response) {
       if( response == WatchUi.CONFIRM_YES ) {
           mController.stop();
           mController.discard();
       }
    }
}

This delegate will handle a yes by stopping the timer and discarding the recording.

Quantitative Enlightenment

Unlike running or cycling, yoga does not have any traditional metrics to measure improvement. With yoga, there is no fastest time, longest ride or competition you are training for; instead, the goal is constant self-improvement through practice and meditation.

Since we are metric people, let’s invent one for yoga. As mentioned above, much of the challenge of yoga comes from holding a position. Our metric will measure the period of time the user is in this state.

    Quantitative Enlightenment (Namastes) = Minutes heart rate is in zone one or above and the body is not moving.

It’s a good start. Now how do we measure it?

Heart Rate

Connect IQ devices have access to the user’s heart rate either via the built-in Garmin Elevate WHR sensor (for devices that have it) or via the ANT+ chest strap.

The system requires us to ask for permission before we access the user’s heart rate. To do this, right click on your project in Eclipse and go to Properties. On the left side, select Connect IQ. If you scroll your pane to the bottom, you’ll see the permissions for your app:

We will need the Sensor permission enabled. This will give us access to the Toybox.Sensor module.

Now open the file NamasteModel.mc in your project. The sensor module provides access to a number of the sensors on a Connect IQ device. We can tell the system which sensors our app wants to use with the setEnabledSensors call. In our case, we want to enable reading from the heart rate sensor in our initializer:

Sensor.setEnabledSensors([Sensor.SENSOR_HEARTRATE]);

To get the current sensor readings, we can get the latest SensorInfo instance using the following:

// Get the current Sensor info
var info = Sensor.getInfo();

This POMO (Plain Old Monkey Object) provides readings from sensors we have enabled.

Stillness

We only want to give credit for time in which the user has an elevated heart rate while holding still. To measure stillness, we will use the accelerometer. The accelerometer gives us a reading of the direction of the vector of gravity.

You can read the value of the accelerometer in the Sensor Info POMO. The measurement of gravity in each direction is given in milligravities.

Let’s take a simple approach to measure stillness. Let’s treat each reading from the accelerometer as a vector in three-space.

We want to see if the vectors across one second stay within a certain bound. To do this, let’s take the angle between the vectors into account. We can use the dot product of the vectors to get the cosine of the angle between our vectors.
 

We want to measure the angle between our vectors. If the angle goes over a 10-degree threshold, then the user has had significant movement.

The accelCallback is called at 4 Hz. Three times out of four, it captures the current vector and buffers it away. Every fourth sample, it runs through the four vectors to see if the angle has significantly changed across them.

           var a, b, magA;
           var result = true;

           // Calculate the first vector and magnitude
           a = mSamples[0];
           magA = Math.sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2] );

           // Use the dot product to get the angle between the four vectors
           // If the angle is greater than cos(10 degrees), then they have
           // moved in the last second significantly.
           for(var i = 1; i < 4; i++) {
               // Compute the second vector and magnitude
               b = mSamples[i];
               var magB = Math.sqrt( b[0]*b[0] + b[1]*b[1] + b[2]*b[2] );
               // Compute the dot product of a . b
               var dot = ( a[0]*b[0] + a[1]*b[1] + a[2]*b[2] ).toFloat() /
                   (magA * magB);

               // If the angle is more than cos(10), then it is
               // outside range
               if( dot < .987) {
                   result = false;
               }

               // Move the current vector into a
               a = b;
               magA = magB;
           }

For now, we can ignore the magnitude of the sample (except for the purposes of getting the cosine). We can compare the cosine directly against the cosine of 10 degrees. Every time the loop runs, we use test the last computed vector against the current one.

Now we can test the user’s stillness and the user’s heart rate. How can we test what heart rate zone they are in?

User Profile

The UserProfile gives the developer access to the user’s fitness profile. Because this contains personal information, you need to set a permission to access it. In our case, we need the user’s heart rate zones they have configured in Garmin Connect.

mZones = UserProfile.getHeartRateZones(
          UserProfile.HR_ZONE_SPORT_GENERIC);

Garmin Connect allows for configuring different heart rate zones for different sports, so we need to specify what kind of sport we need the zones for. The heart rate zones are provided as an array of six values. The first value is the minimum of the first heart rate zone. The second value is the maximum value of the first heart rate zone. The subsequent values are the maximum values of the second, third, fourth and fifth heart rate zones.

Putting It Together

Now that we have the stillness computation, we can do a simple test to see if the user’s heart rate is sufficient.

           // If they have been still, and their heart rate is in
           // HR zone 2, give them a Namaste
           if( result && info.heartRate != null ) {
               if( info.heartRate > mZones[0]) {
                   mNamaste = 1;
                   mTotalNamastes++;                   
               }
           } else {
               mNamaste = 0;
           }

Our model class is going to capture instantaneous and total namastes. This will be captured by the NamasteController class.

To get started with Connect IQ and see where your innovation takes you, go to the developer site  and download the free SDK.

About Connect IQ:
Connect IQ launched in early 2015 with the goal of making Garmin devices open to the developer ecosystem and subsequently to consumers for personalizing their devices. Since the launch of Connect IQ last year, Garmin has launched many more Connect IQ-enabled wearables in addition to the first compatible bike computer and handheld GPS unit. The new SDK just launched last month as well. Check it out here! For more information on Connect IQ, please see this overview page.

About Garmin:
For decades, Garmin has pioneered new GPS navigation and wireless devices and applications that are designed for people who live an active lifestyle.  Garmin serves 5 primary business units, including automotive, aviation, fitness, marine and outdoor recreation. For more information about Garmin, visit our virtual pressroom at garmin.com/newsroom, contact the Media Relations department at 913-397-8200, or follow us at facebook.com/garmin, twitter.com/garmin, or youtube.com/garmin.

Nicolas Kral

Comments