Blog

June 04, 2016 | Electronics |

IoT Triad Part II - Mobile (Cordova), Device (nRF51822) and a bit of Cloud (ThingSpeak)

nrf51-cordova

Introduction

Last year, I had written a rather ponderous article on the Device part of the IoT triad consisting of Device, Mobile and Cloud. This time, I want to focus on the Mobile part. In this article, I’ll go through the construction of a cross platform mobile app that displays heart rate measurement data sent by a Nordic nRF51822 device via BLE. I’ll throw in a bit of cloud as well - by periodically posting this data to thingspeak.

nRF51 Programming

There is no firmware development in this article. I’ll be assuming that you have an nRF51 device, and that you have programmed it with the ble_app_hrs example from Nordic.

If you are unfamiliar with nRF51 development, you might want to look through some of my previous articles on the subject:

Now let’s look at the mobile part.

Cross Platform Mobile Apps

At the beginning of my career in the late 90s, I spent most of my time porting applications from Unix to Windows. Cross Platform development was a mess, and my job felt like taking apart one watch to stuff it into another, losing parts in the process. In 2016, writing portable code is still a wretched affair, but there is a glimmer of hope as Web Tech, with JavaScript at the helm, is emerging as the winner. (I don’t know about you, but I am tired of learning syntax for yet another programming language.)

For the Mobile universe, there are various cross platform solutions out there - PhoneGap, React Native, Ionic/Angular, Xamarin - just to name a few. After reading through some of this technology (and doubling my number of grey hairs), I decided to go with plain Cordova - which is actually the basis for many of the frameworks above, and uses HTML5, CSS and JavaScript to help you develop cross platform mobile apps. (For folks who want a quick start for their IoT devices, I recommend exploring evothings, which is also based on cordova.)

Mobile App Development

Now let’s get into the nitty-gritty of developing the mobile app.

Objective

Our goal is to create a mobile app that does the following:

  1. Scan for BLE devices
  2. Connect to Heart Rate Service on a device
  3. Enable notifications
  4. Display current heart rate
  5. Plot heart rate as a function of time
  6. Post data periodically to ThingSpeak

Let’s get on with the setup.

Cordova setup

I won’t cover cordova here, but the official documentation is quite good, and you can start there. For this project, the most important thing is that you need to get the cordova-plugin-ble-central plugin. Your setup will roughly look like the following:

$cordova create nrf51HRM com.electronut.nrf51HRM nrf51HRM
$cd nrf51HRM
$cordova plugin add cordova-plugin-ble-central
$cordova platform add android

The above was done on OS X which required sudo on cordova commands. (Fixable, but life is short, and there’s too much to do.) Also, a long term iPhone user, I finally decided to ditch iOS and get an Android (Nexus 5X) using this project as an excuse - the main reason being that I am tired of paying developer fees to the demigods at Infinite Loop just so I can put apps on my own darn device. If you use iOS, you will need to add that platform above.

Next, you’ll need to setup your platform (iOS or Android) for development. That’s beyond the scope of this article, but there are many resources to help you out there. Again, the Cordova official documentation has nice sections on platform setup.

After the setup, here’s what the directory structure looks like:

$ tree -L 2 nrf51HRM/
nrf51HRM/
├── config.xml
├── hooks
│   └── README.md
├── platforms
│   ├── android
│   └── platforms.json
├── plugins
│   ├── android.json
│   ├── cordova-plugin-ble-central
│   ├── cordova-plugin-compat
│   ├── cordova-plugin-whitelist
│   └── fetch.json
└── www
    ├── css
    ├── index.html
    └── js

All our action is in the www directory. Once you are done with setup, you need to replace the contents of the www directory with my code which you can find in the Downloads section below.

Here’s what my www looks like:

$ tree -L 4 nrf51HRM/www
nrf51HRM/www
├── css
│   └── index.css
├── index.html
└── js
    ├── index.js
    └── jquery
        ├── jquery.js
        └── version.txt

index.html contains the layout of your app, which is styled by index.css. The logic for your app resides in index.js. We use jquery in this project, so you need that as well.

Now that you are setup, here’s how you build and upload the code to your mobile device:

$cordova build android
$cordova run android

I had trouble running this on the emulator, and BLE may not work with the emulator anyway. So I suggest that you just plug in the mobile and work with it right from start.

Debugging

Having a good debugging tool saves you a lot of time on development. Midway through this project, I was delighted to discover this Chrome trick - with your mobile connected via USB to your computer and running the app, go to chrome://inspect/?#devices on your computer. It will list your app, and you can click on inspect to get a developer console. How cool is that?! Here’s a session:

debug cordova

Now let’s look at the code.

The Layout

Everything starts at index.html, which starts by setting up some parameters and loading scripts inside <head>.

<head>
  <meta charset="utf-8" />
  <meta name="format-detection" content="telephone=no" />
  <meta name="msapplication-tap-highlight" content="no" />
  <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
  <link rel="stylesheet" type="text/css" href="css/index.css" />
  <title>Heart Rate</title>

  <script type="text/javascript" src="cordova.js"></script>
  <script type="text/javascript" src="js/jquery/jquery.js"></script>
  <script type="text/javascript" src="js/index.js"></script>
</head>

You can see above where the JavaScript files are loaded. Now for the main content:

<body>
   <div>
       <h2>Heart Rate</h2>
       <div id="beatsPerMinute">...</div>
       <div id="statusDiv"></div>
       <button id="button-connect" onclick="app.connectBtn()">CONNECT</button>
   </div>
   <canvas id="canvas" width="600" height="200"></canvas>
 </body>

The layout above is simple - just a heading, <div>s for heart rate and status, a connect button, and an HTML5 canvas for drawing the graph.

Now let’s go to the action part.

JS Action

index.js is where the action happens. Let’s look at the important snippets within.

Talking to BLE

When you click the connect button, the BLE scan is started here:

app.scan = function() {
  app.status("Scanning for Heart Rate Monitor");

  // hanlder for scan success
  function onScan(peripheral) {

    // assume only one peripheral sending heart rate

    console.log("Found " + JSON.stringify(peripheral));
    app.status("Found " + peripheral.name);

    // save peripheral
    app.peripheral = peripheral;

    // on successful connection
    function onConnect(peripheral) {
      app.status("Connected to " + peripheral.name);
      // start heart rate notification
      ble.startNotification(peripheral.id, '180D', '2A37', app.onData, app.onError);
      // set flag
      app.connected = true;
    }

    // on connection failure
    function onFailure (reason) {
      beatsPerMinute.innerHTML = "...";
      console.log("disconnected: " + reason);
      app.status("Disconnected!");
      app.connected = false;
      $('#button-connect').html('CONNECT');
    }

    // connect to peripheral
    ble.connect(peripheral.id, onConnect, onFailure);

    // set button text
    $('#button-connect').html('DISCONNECT');
  }

  // handler for scan failure
  function scanFailure(reason) {
    app.status("Did not find a heart rate monitor.");
    $('#button-connect').html('CONNECT');
  }

  // scan for heartrate service, 0x180D
  // https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.heart_rate.xml
  ble.scan(['180D'], 5, onScan, scanFailure);
};

If you are new to JavaScript, the “functions within functions” style of writing code as seen above might look incomprehensible, till you realize as Douglas Crockford said, that JavaScript is Lisp in C’s clothing. I highly recommend that you read his book JavaScript: The Good Parts to get a feel for the actual power of this language.

In the above code, ble.scan() starts looking for BLE devices, and we’re filtering for the service id 0x180D, which is a predefined bluetooth service for sending heart rate information. Each time a device with this service is found, the onConnect() function is called, which calls ble.startNotification() on the heart rate characteristic with id 0x2A37 - this is the actual heart rate measurement. Setting the notification will make the device send the data periodically to the app. This data will be made available through the onData() function passed in. Here’s what it looks like:

// called on receiving characteristic data
app.onData = function(buffer) {

  var hrm = new Uint8Array(buffer);

  // parse heart rate
  // see:
  // https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml

  if(hrm[0] & 0x1) {
    // 16-bit
    app.heartRate = (hrm[2] << 8) + hrm[1];
  }
  else {
    // 8-bit
    app.heartRate = hrm[1];
  }

  // set heart rate display
  beatsPerMinute.innerHTML = app.heartRate;

  // draw graph
  app.plot(app.heartRate);
};

To understand the format of the data sent by the heart rate measurement characteristic, please take a look at the official specification. The Nordic ble_app_hrs firmware sends heart rate in 16-bit format, and the code above handles it by combining data from the two adjacent bytes. Once parsed, the value is set to the text display and then passed on to the app.plot() function.

In addition to the above, the status messages are set based on various conditions, like connect, disconnect, failures, etc.

Now let’s look at plotting a graph using the heart rate data.

Drawing a Graph

For display the heart rate data, we create a “rolling graph” of points (represented as filled circles). As new data comes in, old data moves to the right.

Here’s the app.plot() function where the drawing to the HTML5 canvas happens.

app.plot = function(heartRate) {

  var canvas = document.getElementById('canvas');
  var context = canvas.getContext('2d');
  var dataPoints = app.dataPoints;
  var maxLen = 50;

  // add data
  dataPoints.push(heartRate);
  // cap length
  if (dataPoints.length > maxLen) {
    // remove first
    dataPoints.splice(0, 1);
  }

  // maximum value
  var maxVal = 400;

  function drawPoints(color)
  {
    // draw dots
    context.fillStyle = color;
    context.strokeStyle = color;
    var x = 0;
    for (var i = dataPoints.length-1; i> 0; i--) {
      context.beginPath();
      var y = canvas.height - (dataPoints[i] * canvas.height) / maxVal;
      context.arc(x, y, 4, 0, 2 * Math.PI);
      context.fill();
      x += 10;
    }
  }

  // clear previous
  context.clearRect(0, 0, canvas.width, canvas.height);

  drawPoints("green");
};

In the above code, new data is added to the dataPoints array as they come in. A splice is done when the count exceeds maxLen to keep the number of points drawn constant. The function drawPoints() does the actual drawing, using the arc() method of the canvas context. x+=10; ensures a certain distance between the points on the horizontal axis.

Now for some cloud mischief.

Posting Data to ThingSpeak

Thingspeak is a great platform for plotting your IoT data. It’s free, and lets you create channels for your data, and provides convenient export options for all your data. You’re allowed to update your channel every 15 seconds only - something to remember. Here’s the code that posts data there, from the $(document).ready() function:

// AJAX callback
function onDataReceived(jsonData) {
  app.status("Thingspeak post: " + JSON.stringify(jsonData));
}
// AJAX error handler
function onError(){
  app.status("Ajax error!");
}

// get data from server
function getData() {
  if(app.connected) {
    // prepare thingspeak URL
    // set up a thingspeak channel and change the write key below
    var key = 'IKYH9WWZLG5TVYF2'
    var urlTS = 'https://api.thingspeak.com/update?api_key=' + key + '&field1=' + app.heartRate;
    // make the AJAX call
    $.ajax({
      url: urlTS,
      type: "GET",
      dataType: "json",
      success: onDataReceived,
      error: onError
      });
    }
  }
  // define an update function
  var count = 0;
  function update() {
    // get data
    getData();
    // set timeout - thingspeak can only update every 15 seconds
    setTimeout(update, 16000);
  }
  // call update
  update();

In the above code, we’re using a simple AJAX setup to make a ‘GET’ call into the thingspeak channel to update our channel. jquery makes this job easy. We use the setTimeout() method to call this code every 16 seconds. The data returned from the GET call is displayed in the status text.

You’ll need to setup your own thingspeak channel for testing, as I’ve changed the write API key to mine - no naughty business. :-)

Here’s a sample output:

nrf51-thingspeak

And thus we have our BLE device not only talking to our own mobile app, but using it as a conduit to post data on to the cloud yonder.

Downloads

You can get the complete source code for this project here:

https://github.com/electronut/nrf51-codova

In Action

Here’s the app in action:

Conclusion

We’ve touched on the Mobile part of the IoT triad by building a cross platform mobile application that talks to a BLE device. I think cordova is a good place to start - especially in an IoT context. The concepts learned here can be leveraged as you move to other platforms like Ionic/Angular.


Questions/Comments

We love hearing from our readers. Email us at info@electronut.in for questions or comments on this article. If you found this article useful, please support us by buying some of our Open Source hardware products - like ZeroDriver - an Arduino Zero compatible motor driver for robotics. ZeroDriver is crowdfunding right now. Please click here to support our campaign. and bring this product to market!


Please sign up for updates

Once in a while, we will send you an email update on the latest Electronut Labs projects and products. Your email address will never be shared or abused, ever.

2016 Electronut Labs. All rights reserved.