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