(Blue)mixing IoT and GPS services for your health

12 August 2014
PDF (675 KB)
Share:
Phil Estes

Phil Estes

IBM STSM and Technical Leader, SWG Cloud Labs

@estesp on Twitter

Sign up for IBM Bluemix™
This cloud platform is stocked with free services, runtimes, and infrastructure to help you quickly build and deploy your next mobile or web application.

Seems everyone I know has a fitness band or tracker of some sort these days. While the steps, calories, and miles tick by, these small devices generate mountains of new data — some of it intelligently connected to apps and websites that analyze and track the user's every move. Ideally, this data helps band wearers increase their activity levels, eat better, and possibly lose unwanted weight.

If you're a geek like me, and own a Fitbit, you might have gotten excited to see in the Fitbit web dashboard that your device and its data are accessible via an API. Wouldn't it be fun to write your own app to do interesting things with your Fitbit data? But you'll need to put together or select libraries for your language of choice for HTTP communication, REST API interactions, some form of mobile support or SDK, or libraries for whatever GUI or interaction method you want to support. The excitement fades as you realize you might never be able to fit all those tasks into your busy schedule.

IBM SoftLayer CEO Lance Crosby announced at IBM Pulse 2014 that he lost 210 pounds over 24 months by using Fitbit — whose web presence is handled by IBM SoftLayer — for awareness and tracking of his activity.

Before you give up on your idea, though, take a look at IBM Bluemix. With Bluemix, you can bind your apps efficiently to existing services and APIs, and Bluemix offers a growing body of popular, ready-to-use language frameworks and runtimes. You needn't spend any time acquiring hardware, installing middleware or other software, or suffering setbacks in getting your apps up and running.

This article shows how to assemble a web application quickly that gives a status update on your Fitbit statistics for the day, provides encouragement, and helps you find places to increase your step count. You'll start with a simple Node.js application and bind two preexisting Bluemix services to it — the Wearable Fitness service (a community-developed service from IBM IoT Labs) and the Pitney Bowes Travel Boundary Service — and also tap into external APIs for map data.

READ:Getting Started with IBM Bluemix and IBM DevOps Services using Node.js

What you'll need

 

Where to start

 

To follow along and try out Bluemix as you read:

  1. Click the  Get the code button at the end of this section and fork the application into a new DevOps Services project under your own login ID. You can edit the manifest.yml file in your forked project to change the name and host parameters to a name of your own choosing; then, using the DevOps Services interface, deploy your application to Bluemix to set up your newly forked application in the Bluemix UI.
  2. Go to the Bluemix services catalog and add the Pitney Bowes Travel Boundary Service (Web and Application category) and the Wearable Fitness service (Internet of Things category) to your app. Details about the credentials you need for these services are in this article's "Bluemix service binding" section. (Environment variable setup for external services is covered later in the article.)
  3. Restart your application in the Bluemix UI if you haven't already since binding the two services. Verify that your new Bluemix application is available at the Bluemix application URL (normally applicationname.mybluemix.net).

If you aren't up for creating your own forked project at this point, you can follow along for now and use these buttons to view the code or run the existing application. For an informative look at starting your own Bluemix application from scratch, read "Getting Started with IBM Bluemix and IBM DevOps Services using Node.js."

After you create an application in Bluemix and bind it to the necessary services, at runtime you can easily access the details needed to use these services.

The Get Moar Steps app

 

Every application in Bluemix starts with a name and, optionally, an icon. With the intent to humorously nudge users to increase their fitness activity, I included the Internet meme MOAR in the app name. For ease of use in the cf command-line tool, the actual app name is getmoarsteps.

Much more important than the application's name is its architecture. This diagram shows the app's basic components, especially where it binds to other Bluemix-provided services:

Architectural diagram of the GetMoarSteps Bluemix application

The diagram gives a fairly comprehensive overview of the application's flow. Because the application uses Node.js, the basic control flow is through the routes. You can see three main routes in use in the diagram:

  • The main GET / route to retrieve the basic index (home) page, which simply contains a login form
  • The POST /iot route, which passes the login credentials to the route handling the meat of the application's processing
  • The GET /tbs route, which handles (via an asynchronous browser call, or Ajax) the travel boundary service interface based on current location

Key source files — where the bulk of the interesting code you'll want to understand is located — are highlighted:

  • The server-side route implementation files (routes/iot.js and routes/tbs.js) are in red.
  • The largest client-side JavaScript file (js/iot.js), which handles the Ajax and mapping APIs, is in green.
  • The embedded JavaScript views (EJS files index.ejs and iotview.ejs), rendered via the routes, are in blue.

Finally, green arrows represent REST API calls to the two Bluemix services, and a red arrow notes the use of mapping services and APIs external to Bluemix (the MapQuest and Google Maps APIs). The blue arrows generally denote the flow of Node.js/Express routes as you would expect in any standard Node.js application.

Bluemix service binding

 

The interesting work that the application performs involves interacting with the services it uses in Bluemix. So I'll give you a brief tour of how to connect to and use the service APIs from a Bluemix application. (The code to do so with this app is JavaScript-based, but the concepts apply to all runtime options within Bluemix.)

You can use the cf command-line tool to replicate all the same tasks that you can perform via the Bluemix web interface, including creating and binding to services in the Bluemix catalog. If you are using the IBM DevOps Services web-based IDE, many of these same capabilities are also accessible directly from the rich web interface provided there.

Because the Wearable Fitness API and the Pitney Bowes Travel Boundary Service API require credentials for API access, you must first register with those external systems to receive an application key. Your app will use the key to authenticate to these services at runtime:

If you are using the Bluemix web interface, as soon as you add the Wearable Fitness service and the Travel Boundary Service to your application, you are guided through the steps for registering for API access with these external endpoints, as well as plugging in your resultant key details so that Bluemix can provide this information to you at runtime:

Screenshot of the dialog box for supplying IoT credentials to Bluemix

Accessing service credentials

 

After you create an application in Bluemix and bind it to the necessary services, at runtime you can easily access the details needed to use these services via an environment variable named VCAP_SERVICES. An easy way to view the current contents of this variable is to look at your application's runtime status page via the Bluemix web dashboard.

From the dashboard, click your application icon to view the overview page, then click Runtime in the left navigation bar to see all your application's details. Scroll down to the Environment Variables section:

Screenshot of the environment variables section in the Bluemix app overview page

Accessing these important credentials from your Node.js application is easy because the format of VCAP_SERVICES is already in JSON and is packed in an easily accessible environment variable. This code shows a simple access pattern to parse the credentials for any named service:

//Bluemix hands us our service connection data in the VCAP_SERVICES variable
var envVCAP_SVC = JSON.parse(process.env.VCAP_SERVICES);

var wearableCredentials = getCredentials(envVCAP_SVC, "Wearable Fitness");

//Take the JSON environment variable contents and find the named service, returning
//the credentials JSON object
function getCredentials(vcapEnv, serviceNameStr) {

    var credentials = undefined;

    vcapEnv['user-provided'].forEach(function(service) {
      if (service.name.indexOf(serviceNameStr) === 0) {
          credentials = service.credentials;
      }
    });
    return credentials;
}

Note that with this lazy method, you can specify any prefix of the service name. If you had multiple services that started with the same string and you weren't careful with your calling convention, you might not get back the credentials you meant to. Hence, caller beware with this kind of implementation. But for a simple example with few services, it is a reasonable choice.

Stepping into step data

 

You have built the basic application framework in Node.js and bound it to the services, and you understand how to access the credentials to use the service APIs. The next step is to do something interesting in the Get Moar Steps application: display a user's Fitbit data.

The login page (the default GET / response from your Node.js app) requests the user's credentials and also provides a link for registering a Fitbit with the Wearable Fitness service:

Screenshot of the Get Moar Steps login page

The form controls in the login page validate that you have entries for username and password. They also query the local time-zone information from the client browser. That data combination is sent (via HTTP POST) to the POST /iot route in the application.

At this point, you have all the necessary details to make a REST call to the Wearable Fitness API and get back some Fitbit data. Note that each service in Bluemix has a nice reference link to the documentation, and specifically API docs, right within the Bluemix dashboard. You've already done your homework and decided that you want to query the "today's date" document of Fitbit activity from the /iot/doc?id= endpoint of the Wearable Fitness API.

The mention of "today's date" should make it obvious why you pass in the client browser's time zone from the login page form. Depending on where your code is hosted, the server's notion of the current time and date are potentially different from the time and date where the Fitbit is in use. By using the data queried from the client browser, the app can at least show users the proper day based on their current location. (There's still a loophole in that Fitbit devices themselves have a time zone set, and if the user is currently not in the same time zone as the device — and hasn't set it to autoupdate or updated it manually — the results might not match. But for the example, I'll leave that hole wide open for simplicity's sake.)

You can view the complete code in the DevOps Services web IDE (see routes/iot.js), including the handling of all the date parsing and computation. To keep moving through the example, here's a shorter view of building up the REST call from your service credentials, your end-user user ID and password, and the document name and date to query. Note that I've standardized the REST call/response functionality into a separate module named restcall.js:

function queryFitbitData(username, password, datestr, callbackFn) {

    var restcall = require('../restcall');
    var url = require('url');

    //Because the Bluemix service detail includes a URL with the /iot endpoint already
    //postfixed to the "url", we use the Node.js url module to break it back down so
    //we can have the host portion for our options JSON object below
    var iotURLObj = url.parse(iotprops.url);
    var host = iotURLObj.host;
    var authStr = username+":"+password;
    var endpoint = "/iot/doc?id=fb_activity_"+datestr+"&appId="+iotprops.appId;

    var options = {
      host: host,
      path: endpoint,
      method: "GET",
      auth: authStr
    };

    console.log("URL: "+host+" / Endpoint: "+endpoint);

    //send the request to the IoT API
    restcall.get(options, true, callbackFn);
}

Barring any API service disruptions, the callback function will be triggered with the resulting JSON response from the Wearable Fitness API, including many details from your Fitbit device statistics for the day. Much more data is available than what you have access to on the Fitbit website, including a more detailed breakdown of time spent (and distance) in various activity levels, such as sedentary, fairly active, and highly active.

Here is a representative piece of the JSON data returned when you call the Wearable Fitness /iot/doc API:

"summary": {
    "distances": [
      {
        "distance": 5.56,
        "activity": "total"
      },
      {
        "distance": 5.56,
        "activity": "tracker"
      },
      {
        "distance": 23.01,
        "activity": "loggedActivities"
      },
      {
        "distance": 2.44,
        "activity": "veryActive"
      },
      {
        "distance": 2.17,
        "activity": "moderatelyActive"
      },
      {
        "distance": 0.94,
        "activity": "lightlyActive"
      },
      {
        "distance": 0,
        "activity": "sedentaryActive"
      },
      {
        "distance": 23.01,
        "activity": "Cycling"
      }
    ],
    "sedentaryMinutes": 733,
    "lightlyActiveMinutes": 113,
    "caloriesOut": 3120,
    "caloriesBMR": 1836,
    "marginalCalories": 1107,
    "fairlyActiveMinutes": 77,
    "veryActiveMinutes": 49,
    "activityCalories": 1489,
    "steps": 8404,
    "activeScore": -1
  },
  "iot_source": "fitbit",
  "goals": {
    "activeMinutes": 30,
    "distance": 8.05,
    "caloriesOut": 2925,
    "steps": 10000
  }

The API returns distances in kilometers. To display any distance data in miles (or any other unit) in your application, you must perform unit conversion. Note that you are provided with both the number of minutes in various levels of activity and the distance (in km) traveled at those activity levels.

Augmenting the data

 

Much of the data you want to use in your displayed page is already in the JSON response. But you want to augment this raw Fitbit data with a few calculated additions. Rather than create another JSON object, you can add to this response and then use the Embedded JS (EJS) render function to marry your HTML template and this data into the view that the user sees. The bulk of that response augmentation is done in this callback function from the REST call in the preceding section.

The code includes some math functions, so a short explanation might help clarify. I created a two-dimensional array, welcome_messages. The array is based on two variables — how far into the day it is right now, and how close the user is to his or her step goal — that become indexes into a list of messages that encourage, challenge, or sarcastically spur on the user. Because I created only four different messages for four "quadrants" of the day (0-25 percent, 25-50 percent, 50-75 percent, and 75-100 percent), you must convert the percentage of the day gone by into the digit 0, 1, 2, or 3 to index into the array.

Similarly, how far the user is toward his or her Fitbit step goal must be converted to the same four indexes. Putting the two indexes together generates the appropriate message. The math function shown here converts those percentages into this index, with the additional "ceiling" calculation on the percentage of the step goal reached — because you can be well over your step goal for the day but only the four messages are available, so the highest array index must be 3:

function(respData) {
  //check for error codes:
  if (!handleError(respData, curDateTime)) {
    //if no errors, augment data with added display info
    var dayMsgIndex = Math.ceil(percentOfDay/100*4 - 1);
    var percentGoal = respData.summary.steps/respData.goals.steps;
    //since you can surpass the goal in steps, max at "100%" response
    var goalMsgIndex = Math.min(Math.ceil(percentGoal*4 - 1), 3);
    //add one of our canned messages to the JSON data
    respData["welcome_msg"] = messages.welcome_messages[dayMsgIndex][goalMsgIndex];
    respData["daypercent"] = percentOfDay;
  }

  res.render('iotview', respData)
}

The result of rendering the response data with the EJS template (named iotview.ejs) is a dashboard showing the user's step count and a possibly helpful (or sarcastic) message about the user's progress, based on time of day and number of steps completed:

Screenshot of the GetMoarSteps dashboard with step count and message

You've made it through querying and displaying some basic information by using the Wearable Fitness service API, but what about the promise to help users "get moar steps"? The second part of the application focuses on that aspect.

All about maps

 

How can you help users find locations where they can walk, hike, or do a trail run? Using the HTML5 capability to access location services — either via a standard PC browser that supports location, or via a mobile device's GPS function — you'll now add to the dashboard display an embedded Google Map showing the user's current location. Below this map view, you'll provide a search capability to find local parks based on travel distance — a feature partially provided by the third-party Travel Boundary Service from Pitney Bowes.

Travel time versus radius searching

 

The Travel Boundary Service is only a partial answer to the quest for places to walk because it focuses only on answering the question, "What part of the map is reachable within the specified amount of travel time from a specific point?" This service returns a polygon of points on the map that meet your criteria for travel time. Other steps are required to use this polygon with an actual database of places to generate a list of nearby parks.

The travel boundary service is a useful location-search method because distance can be disconnected from reachability and travel time. A restaurant could be 500 feet from my current GPS location, but if it's across a river and the nearest bridge crossing is 10 miles away, the actual travel distance is much longer than the distance to a restaurant that's 1,000 feet down the street on my side of the river. This inherent problem with radius searching is solved by the travel boundary service available in Bluemix.

Knowing that users might not have time to make a long trek to get to a park, I combined the travel boundary service with four user-selectable fixed time options for travel time distance: 5, 10, 15, or 20 minutes:

Screenshot of travel-time selection dialog box

The REST call to the Pitney Bowes service uses the restcall module, building up the API endpoint and parameters:

function queryBoundaryData(distance, lat, lon, callbackFn) {

    var url = require('url');

    var iotURLObj = url.parse(tbsprops.url);
    var host = iotURLObj.host;
    var endpoint = iotURLObj.pathname;
    endpoint += "?latitude=" + lat + "&longitude=" + lon +
                "&cost=" + distance + "&units=Minutes&appId="+tbsprops.appId;

    //appears to be a potential issue with the SSL cert on the PitneyBowes
    //boundary service; using "rejectUnauthorized: false" as option to the
    //HTTPS Node.js module until this problem is corrected
    var options = {
      host: host,
      path: endpoint,
      method: "GET",
      rejectUnauthorized: false
    };
    console.log("URL: "+host+" / Endpoint: "+endpoint);

    //send the request to the Travel Boundary Service API
    restcall.get(options, true, callbackFn);
}

Notice in the comments that to get around an SSL certificate issue at the time of this writing, lax connection security on the SSL validation is allowed so that you can make the REST call ( a reasonable temporary workaround because the data you're passing has no inherent privacy issue). If you were working with data requiring strong privacy and security, a better option would be to work with the API provider to correct the certificate validation/signing issue.

Finding parks

 

Now you have your polygon (handed to the callback function) from the travel boundary service, but how can you find a list of parks within this area? You have a limited set of options for access to place data. The Google Maps API (Google Places, more accurately) — doesn't offer a polygon search API. The MapQuest API does have a polygon search capability, albeit with a weaker points of interest (POI) database than Google.

Neither of these mapping/search services is a Bluemix service. But you can call external REST APIs from your application in a similar fashion to calling a service (without the integrated service binding, support, and SLA promises of the Bluemix ecosystem). One benefit is that instead of hardcoding any service information in your source code, you can use the "User Defined" environment area within Bluemix to add the service endpoint URL and any application key/ID details in a variable that's accessible from your application.

To create user-defined environment variables from the Bluemix dashboard:

  1. Click your application icon.
  2. Click the Runtime link in the left navigation pane.
  3. Scroll to Environment Variables and click USER-DEFINED to switch to the user-defined variables page.
  4. Use the Add and Save buttons to create new entries, as shown here: Screenshot of user-defined variable configuration dialog box

    As you can see, I set the mqHost variable to www.mapquestapi.com and the mqAPIKey variable to the API key provided after I created a community ID at the MapQuest developer site.

You can also perform these variable additions by using the cf command-line tool:

cf set-env appnamevariable value

Constructing the MapQuest REST call is fairly simple now that you have all the required components: the polygon to search, a MapQuest API key (available for limited API calls for free), and the MapQuest category code for "Parks" (found in MapQuest's online POI documentation).

In what should be a now-familiar code pattern, the API call to search for parks looks like this:

function findParksInPolygon(polygonData, callbackFn) {

    //MapQuest API host - added in Bluemix user-defined env data
    var host = process.env.mqHost;
    //community version API key to MapQuest services - from user env data
    var mq_appKey = process.env.mqAPIKey;

    //search USA dataset with group == Parks code
    var searchParksOnly = "&hostedData=mqap.ntpois|group_sic_code=?|799951"
    //polygon search endpoint + API key and above Parks restriction
    var endpoint = "/search/v2/polygon?key="+mq_appKey+searchParksOnly;
    endpoint += "&polygon=";

    //iterate through the polygon from the PitneyBowes service and add to
    //the API call to MapQuest
    var polygonResult = polygonData.Output.IsoPolygonResponse.Polygon[0];
    (polygonResult.Exterior.LineString[0].Pos).forEach(function (point) {
      endpoint += ""+point.Y+","+point.X+",";
    });
    //trim the ending comma
    endpoint.replace(/\,$/,"");

    var options = {
      host: host,
      path: endpoint,
      method: "GET"
    };

    console.log("URL: "+host+" / Endpoint: "+endpoint);

    //send the request to the MapQuest Polygon Search API
    restcall.get(options, false, callbackFn);
}

For locations outside the United States and Canada, you can switch the database to MapQuest's international POI database by changing the hostedData= parameter in the searchParksOnly variable to mqap.internationalpois. A potential improvement to the application would be to assess the user's location data and make a determination about which database to use.

The most tedious part of the code takes the travel boundary service polygon data and massages it into the form that the MapQuest API expects. Because the original call to this route (GET /tbs back in the architecture diagram) is coming from the browser-side JavaScript as an Ajax call — driven by the user clicking the Search button with a travel time selected — the response isn't a rendered HTML page as it is for the other routes; it simply passes back the JSON via the HTTP connection. The entire sequence of this route's actions are summarized in this snippet:

   queryBoundaryData(dist, lat, lon, function(polygonData) {
      //now use the polygon response to search MapQuest Polygon search API
      findParksInPolygon(polygonData, function(resultData) {
        res.send(resultData);
      });
    });

Note that the callback functions are implemented as anonymous inline functions for simplicity. The result is that the browser-side JavaScript (see the searchNearMe() function in the public/js/iot.js file) receives the raw JSON from the MapQuest polygon POI data search and is responsible for displaying it to the end user.

Rather than write a large loop to render all the results in prettier HTML, I used the mustache.js template library. An inline template handles the conversion from raw data to a nice table-formatted display of parks. With an embedded Google Map of the user's location already in place, it was a fairly simple exercise also to cycle through the results and add markers to the map for each result with a link to Google Maps Directions service:

Screenshot of map with marker

For more details on using Google's Map and Marker classes, view the code in public/js/iot.js in the searchNearMe() function. It uses the google.maps.Marker class with the position and name information already provided from the MapQuest query to add markers to the map. This is additionally useful with access on a mobile device: Clicking the link in the marker's information window automatically enters navigation mode for the selected target park.

Challenges and improvements

 

You've successfully created an example Node.js application that combines the capabilities of the Wearable Fitness API service and the third-party Travel Boundary Service within Bluemix. What extra challenges did you face, and what can be improved in the future? Here's a short list that varies from trivial to potentially important, were you to try to make this application a viable offering for Fitbit users:

  • Sleep versus sedentary time: Fitbit users can tell their device when they are sleeping, or add in a "sleep record" on fitbit.com. If the user uses this capability correctly, the sedentary time from the Wearable Fitness API truly matches "awake" time spent with nearly no movement. If a user ignores this feature, the sedentary time includes many hours during which you can assume that the user was sleeping. Because the app doesn't capture whether the user is using sleep logging, sedentary time is marked "Sleep+sedentary:". The direct Fitbit API could be used to pull sleep records; if they exist, the app would know to mark truly sedentary time as "Sedentary" in the UI. (However, using both APIs seems like a hack; a potential improvement of the Wearable Fitness API dataset would add detail if sleep time was recorded by the user.)
  • What is a park? From the MapQuest dataset, it seems clear the category code for "Parks" has undergone little validation. For example, the headquarters of a park service (in the middle of a city) might be categorized under "Parks." And a place that is properly represented as a park might not be exactly the kind of place for a human to walk for exercise — certain dog parks, for example. Some sanitization of data might be required so that the results are useful for the purpose intended, if MapQuest remains the dataset source for the application.
  • Better park data: Looking for "parks I can take a walk in" is potentially a larger problem than finding another data source can solve. What if beyond finding points on a map categorized or named "park" you want to identify facilities for hiking or walking, or paved paths for disabled persons, and so on? Some progress in the right direction has been made, some of which is possibly available via API, but no worldwide and fully featured solution exists today. You can look at the PRORAGIS system created by the National Recreation and Park Association as a start as far as the GIS data and feature information is concerned, but at this point the data set is small and has no API accessibility. From an API perspective, thanks to the data.gov initiative, the National Park Service has a comprehensive API— complete with a GitHub project area— but at this point it covers only US national park information.
  • A mobile app: Taking a simple example path with Node.js ignores the fact the application might make better sense as a true mobile app. Bluemix already has a great set of services for mobile application development and support (see "Build an Android app using the MobileData cloud service" and "Build an iOS app using the MobileData cloud service," for example.)
  • Support for more devices: The Wearable Fitness API supports more device types and application sources than just the Fitbit. The application fails with an error message if you use credentials from the Wearable Fitness site for which a Fitbit is not the linked device. A truly functional application would support all the data types and devices provided via the Wearable Fitness API.

If you have other ideas for improvements, add them in the article comments or — even better — join my getmoarsteps project and update the code.

A walk in the park

 

I've refrained for nearly this entire article from picking some low-hanging fruit: Saying that developing with Bluemix is like taking a walk in the park. But with the Get Moar Steps application and a bit of help from Bluemix services, the simile might just lead you to take that walk. Enjoy Bluemix, and happy hacking.

RELATED TOPICS:Node.jsInternet of Things


BLUEMIX SERVICE USED IN THIS TUTORIAL:The Internet of Things service provides simple but powerful application access to IoT devices and data.

Add a comment

Note: HTML elements are not supported within comments.


1000 characters left

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Cloud computing, Web development
ArticleID=980064
ArticleTitle=(Blue)mixing IoT and GPS services for your health
publish-date=08122014