Introduction: Using Cordova to Create Bluetooth Mobile App (for Temperature Sensor)

In my previous Instructables

https://www.instructables.com/id/Bluetooth-Temperature-Sensor-for-SmartPhone/

I've showed how to connect a temperature sensor MCP9808 to Bluetooth nRF51822 device to transmit temperature measurements to your cellphone


Now that you have a working Bluetooth temperature sensor connected to your cellphone you may want to display temperature measurements in a nice way that just using nRFconnect utility from Nordic Semi.

Please note the current application is for demonstration only and should not be used in Production systems

Picture above shows the final application running on my Android phone

Step 1: Installing Development Environment for Cordova

To create a mobile app we are going to use “Apache CORDOVA” an amazing open source project.

Download and install cordova as shown in “getting started guide” here:

https://cordova.apache.org/#getstarted

Follow instruction for your specific OS and for the mobile OS you are planning to target.

Another good (easier) list of installation steps can be found here:

https://evothings.com/doc/build/cordova-install-windows.html

In my case I’m using Linux OS for development and I’m targeting Android OS for my mobile app.

Here’s my development final configuration

> lsb_release -a

Distributor ID: Ubuntu

Description: Ubuntu 16.04.1 LTS

Release: 16.04

Codename: xenial

>uname -i

x86_64

>node –version

v7.2.0

>cordova –version

6.4.0

> javac -version

javac 1.8.0_111

>ant -version

Apache Ant(TM) version 1.9.7 compiled on April 9 2016

>adb version

Android Debug Bridge version 1.0.36

Revision 302830efc153-android

If you don’t have the same configuration it’s most probably fine but you may need to adjust things accordingly along the way.

Step 2: Create a Basic Cordova App

Once your development environment is complete you can create the skeleton for Bluetooth application.

$ cordova create SoliditekBLE com.soliditek.marco.SoliditekBLE SoliditekBLE

Using detached cordova-create

Creating a new cordova project.

Check created SoliditekBLE app has the following directory structure

$ ls SoliditekBLE


Here's the directory listing resulting on my machine

config.
xml
hooks
platforms
plugins
www

Now you need to configure the BLE plugin for Cordova
Use the following command

$ cordova plugin add cordova-plugin-ble-central


OK we are now ready to personalize our app

Step 3: Replace “www” Folder in Cordova App

Rename ./SoliditekBLE/www to something like ./SoliditekBLE/www.bak
Download and unzip in ./SoliditekBLE/www the following archive:

http://www.soliditek.com/wp-content/uploads/2016/1...

Archive contains a complete set of html, css and js to control and display data received from Soliditek Bluetooth temperature sensor

Following here a quick explanation of main files

Index.html

The file creates our application with 2 tabs

First tab (PageMain) shows a gauge with temperature received from Bluetooth sensor

Second tab (PageSetup) shows a setup page to scan and connect to Bluetooth sensor


index.js

/*<br> * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * <a href="http://www.apache.org/licenses/LICENSE-2.0" rel="nofollow">  http://www.apache.org/licenses/LICENSE-2.0

</a>
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

var IDRY_UUID_SERVICE = "00001546-1212-efde-1523-785feabcd123";
var IDRY_UUID_TEMPERATURE_CHAR = "00001548-1212-efde-1523-785feabcd123";
 
var looperVar;
var iDry;

var idry_struct = {
  temperature: 20.0,
//  humidity: 20.0,
};

 
var app = {
    // Application Constructor
    initialize: function() {
        this.bindEvents();
        document.getElementById("button-scan").disabled = false; 
    },
  
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // 'load', 'deviceready', 'offline', and 'online'.
    bindEvents: function() {
        document.addEventListener('deviceready', this.onDeviceReady, false);
    },
  
    // deviceready Event Handler
    //
    // The scope of 'this' is the event. In order to call the 'receivedEvent'
    // function, we must explicitly call 'app.receivedEvent(...);'
    onDeviceReady: function() {
        console.log('exec onDeviceReady');
        app.receivedEvent('deviceready');
    },
  
    refreshDeviceList: function() {
        console.log('exec refreshDeviceList');
        document.getElementById("BLE-table-body").innerHTML = ''; // empties the list
        document.getElementById("info").innerHTML = "Scanning!";
        // scan for all devices
        ble.scan([], 5, app.onDiscoverDevice, app.onError);
    },

    onDiscoverDevice: function(device) {
        console.log(JSON.stringify(device));
        var listItem = document.createElement('tr'),
          html = '' + device.name + '' + '' + device.rssi + '' + '' + device.id + '';

        listItem.setAttribute('onclick',"app.bleConnectionRequest('" + device.id + "')" ); 
        listItem.dataset.deviceId = device.id;  // TODO
        listItem.innerHTML = html;
        document.getElementById("BLE-table-body").appendChild(listItem);
    },
  
    bleConnectionRequest: function(dev_id) {
//      document.getElementById("BLE-table").disabled = true;
      document.getElementById("BLE-table-body").innerHTML = ''; // empties the list
      document.getElementById("info").innerHTML = "Connecting!!!";
      ble.connect(dev_id, app.bleConnectionSuccess, app.bleConnectionFailure);
    },
  
    bleConnectionSuccess: function(device) {

      iDry = device;  // store iDry data in global var

      document.getElementById("info").innerHTML = "SoliditekBLE Connected!";
      document.getElementById("debug-info").innerHTML = "id = " + iDry.id;
//      document.getElementById("debug-area").innerHTML = JSON.stringify(device);
      //enable button to "disconnect" 
      document.getElementById("button-scan").disabled = true;
      document.getElementById("button-disconnect").disabled = false;

      ble.startNotification(iDry.id, IDRY_UUID_SERVICE, IDRY_UUID_TEMPERATURE_CHAR, app.idry_temp_notification_handler, null);
      // if connected then start periodic reading of data
      looperVar = setInterval(looper,2000);
    },

    idry_temp_notification_handler: function(buffer) {
    
      // Decode the ArrayBuffer into a typed Array based on the data you expect
      var data = new Uint8Array(buffer);
      idry_struct.temperature = data[0] + data[1] * 255;
      document.getElementById("debug-area").innerHTML = data[1] + "  " + data[0];
      updateFancyGauges();
    },
    
    bleConnectionFailure: function(device) {

      //enable button to "scan" 
      document.getElementById("button-scan").disabled = false;
      document.getElementById("button-disconnect").disabled = true;
      document.getElementById("BLE-table").disabled = false;
      document.getElementById("info").innerHTML = "Not Connected";
      // stop periodic task execution
      clearInterval(looperVar);
    },
  
    // Update DOM on a Received Event
    receivedEvent: function(id) {
      document.getElementById("button-scan").disabled = false;
      document.getElementById("button-scan").innerHTML = "Scan BLE";
      console.log('Received Event: ' + id);
    }
};

setTimeout(function () {
  app.initialize(); 
  } ,2000);
  

var looperCnt = 0;

function looper() {
  document.getElementById("info").innerHTML = "iDry Connected!" + looperCnt++;
}

// tab menu helper

function openPage(evt, pageName) {
    var i, tabcontent, tablinks;
    tabcontent = document.getElementsByClassName("tabcontent");
    for (i = 0; i < tabcontent.length; i++) {
        tabcontent[i].style.display = "none";
    }
    tablinks = document.getElementsByClassName("tablinks");
    for (i = 0; i < tablinks.length; i++) {
        tablinks[i].className = tablinks[i].className.replace(" active", "");
    }
    document.getElementById(pageName).style.display = "block";
    evt.currentTarget.className += " active";   
}



// Gauge helper

function createCharts() {

 var basicGaugeSettings  = {
    id: "tbd",
    title: "Temperature",
    label: "C",
    value: getRandomInt(0, 100),
    gaugeWidthScale: 0.6,
    min: 0,
    max: 100,
    decimals: 1,
    counter: true,
    donut: false,
    relativeGaugeSize: true,
  }
  
  basicGaugeSettings.id = "temperature-gauge";
  basicGaugeSettings.title = "Temperature";
  basicGaugeSettings.label = "C";
  temperatureGauge = new JustGage(basicGaugeSettings);
  updateTemperatureGauge();
  
}

function updateFancyGauges() {
 
    // update Stat Symple
    updateTemperatureGauge();
}

function setGaugeSize(id){
//  var size = Math.min(window.innerHeight, window.innerWidth) / 2;
  var size = (window.innerHeight * 0.9) / 2;
  document.getElementById(id).style.width =  window.innerWidth + 'px';
  document.getElementById(id).style.height = size + 'px';
}
// Draw or Redraw meterial (desicant, tank) chart
function updateTemperatureGauge() {
  setGaugeSize("temperature-gauge");
  temperatureGauge.refresh(idry_struct.temperature / 100.0);
}

“app” class contains all Bluetooth functions
section “tab menu helper” contains the 2 tab menu helper functions

section “gauge helper” contains the functions required to draw and visualize the temperature gauge

Step 4: Compile and Upload App to Android Phone

Compile Cordova app using the following command:

$ cordova build android

Cordova compile successfully with the following output

ANDROID_HOME=/home/marco/android-sdk-linux/
JAVA_HOME=/home/marco/java/jdk1.8.0_111 Subproject Path: CordovaLib Incremental java compilation is an incubating feature. :preBuild UP-TO-DATE :preDebugBuild UP-TO-DATE :checkDebugManifest :CordovaLib:preBuild UP-TO-DATE :CordovaLib:preDebugBuild UP-TO-DATE :CordovaLib:checkDebugManifest :CordovaLib:prepareDebugDependencies :CordovaLib:compileDebugAidl UP-TO-DATE :CordovaLib:compileDebugNdk UP-TO-DATE :CordovaLib:compileLint UP-TO-DATE :CordovaLib:copyDebugLint UP-TO-DATE :CordovaLib:mergeDebugShaders UP-TO-DATE :CordovaLib:compileDebugShaders UP-TO-DATE :CordovaLib:generateDebugAssets UP-TO-DATE :CordovaLib:mergeDebugAssets UP-TO-DATE :CordovaLib:mergeDebugProguardFiles UP-TO-DATE :CordovaLib:packageDebugRenderscript UP-TO-DATE :CordovaLib:compileDebugRenderscript UP-TO-DATE :CordovaLib:generateDebugResValues UP-TO-DATE :CordovaLib:generateDebugResources UP-TO-DATE :CordovaLib:packageDebugResources UP-TO-DATE :CordovaLib:processDebugManifest UP-TO-DATE :CordovaLib:generateDebugBuildConfig UP-TO-DATE :CordovaLib:processDebugResources UP-TO-DATE :CordovaLib:generateDebugSources UP-TO-DATE :CordovaLib:incrementalDebugJavaCompilationSafeguard UP-TO-DATE :CordovaLib:compileDebugJavaWithJavac UP-TO-DATE :CordovaLib:processDebugJavaRes UP-TO-DATE :CordovaLib:transformResourcesWithMergeJavaResForDebug UP-TO-DATE :CordovaLib:transformClassesAndResourcesWithSyncLibJarsForDebug UP-TO-DATE :CordovaLib:mergeDebugJniLibFolders UP-TO-DATE :CordovaLib:transformNative_libsWithMergeJniLibsForDebug UP-TO-DATE :CordovaLib:transformNative_libsWithSyncJniLibsForDebug UP-TO-DATE :CordovaLib:bundleDebug UP-TO-DATE :prepareAndroidCordovaLibUnspecifiedDebugLibrary UP-TO-DATE :prepareDebugDependencies :compileDebugAidl UP-TO-DATE :compileDebugRenderscript UP-TO-DATE :generateDebugBuildConfig UP-TO-DATE :generateDebugResValues UP-TO-DATE :generateDebugResources UP-TO-DATE :mergeDebugResources UP-TO-DATE :processDebugManifest UP-TO-DATE :processDebugResources UP-TO-DATE :generateDebugSources UP-TO-DATE :incrementalDebugJavaCompilationSafeguard UP-TO-DATE :compileDebugJavaWithJavac UP-TO-DATE :compileDebugNdk UP-TO-DATE :compileDebugSources UP-TO-DATE :mergeDebugShaders UP-TO-DATE :compileDebugShaders UP-TO-DATE :generateDebugAssets UP-TO-DATE :mergeDebugAssets UP-TO-DATE :transformClassesWithDexForDebug UP-TO-DATE :mergeDebugJniLibFolders UP-TO-DATE :transformNative_libsWithMergeJniLibsForDebug UP-TO-DATE :processDebugJavaRes UP-TO-DATE :transformResourcesWithMergeJavaResForDebug UP-TO-DATE :validateSigningDebug :packageDebug UP-TO-DATE :assembleDebug UP-TO-DATE :cdvBuildDebug UP-TO-DATE BUILD SUCCESSFUL Total time: 1.054 secs Built the following apk(s): /home/marco/SoliditekBLE/platforms/android/build/outputs/apk/android-debug.apk

Output file is available under the following folder
~/SoliditekBLE/platforms/android/build/outputs/apk

$ ls

android-debug.apk

copy android-debug.apk to your android device

You can used several methods to share your app with your android device; check this page for a complete list:

https://developer.android.com/distribute/tools/open-distribution.html

In my case I’m using a shared folder on my Google Drive account

Downloading the file with extension “apk” automagically start the installation process

NOTE:

This file is not a final production file

It is not signed and require that your Android device has “install app from unknown sources” enabled

Step 5: Install and Run SoliditekBLE App on Your Android Device

Open Google Drive folder where you have copied android-debug.apk file

click on file

File should automagically install

Successful installed

Touch “OPEN” button on the bottom-right and app starts showing the following screen

Click on “SETUP” tab

Setup page shows up

Turn on nRF51822 device (if already powered – reboot or power cycle to enable BLE advertising)

Click on “Scan BLE” button

On Android 6 a message asking to allow SoliditecBLE to access Location or Bluetooth is showing the first time the app is running

Click allow (otherwise your app won’t be able to access Bluetooth interface)

A list of Bluetooth device is populated on page (if you have Bluetooth device around like I do)

Choose “SOLIDITEC” device touching the line on the list

“Scan BLE” button is now disabled and “Disconnect Soliditeck” is now enabled to disconnect BLE device

Here’s few screen shots of temperature changing

Congratulations you have now a complete Bluetooth temperature sensor connected to Android device