As a computer engineer, I spend most of my time in the embedded hardware and software worlds. I love hardware because when I'm finished building something, I have something tangible to show for my work. More and more though, I find myself branching out to learn skills in new fields. Much of this exploration is driven by the IoT. In order to be an effective engineer in this new field, one must not only have a grasp of embedded technology but also web technologies and how the two can be linked.
In this guide I'll walk you through how to build a smartphone application using a really cool tool called Cordova. Cordova allows users to build apps using modern web technologies like HTML5, Javascript, and CSS. Additionally, through the use of plugins, the phone's hardware resources (like Bluetooth) can be accessed in Javascript. This makes it very easy for just about anyone to build their own app to talk to Bluetooth devices. But wait, it gets even better! With a single codebase, Cordova allows you to build apps for both Android and iOS!
Let's get started by installing the requisite software tools...
InstallationCordova
Cordova is built on top of a javascript framework called Node.js, so first we'll need to install Node. You can download and install it from here:
Once you have Node installed you can install Cordova using NPM (Node Package Manager). Open a terminal and run:
$ npm install -g cordova
If you're on a *nix OS you may need to run this with sudo permissions (so instead run "sudo npm install -g cordova").
Platforms
After Cordova is installed, we'll need to install support for the platforms we'd like to deploy to, namely Android and/or iOS. In this guide, I'll focus on Android because applications for Android can be built on any host computer. iOS applications must be built on a Mac. If you do wish to build for iOS, follow the platform guide here to install the requisite tools:
https://cordova.apache.org/docs/en/latest/guide/platforms/ios/index.html
To build Android applications, we'll need a few additional tools:
- Android Platform (Platform SDK, SDK Build Tools, Support Repository)
The JDK install should be rather straightforward, so I won't spend any time on it. The Android SDK tools come in a zip for most operating systems and can simply be extracted. Once we have the SDK tools we can use them to download the Android Platform. Open the extracted SDK folder. If you're on Windows you can double click "SDK Manager.exe", or if you're on Linux/Mac go into the "tools" directory and run "android". You should now see this window:
We need to select three things Cordova will use to build our Android app:
- Android SDK Build-tools
- SDK Platform (choose the one under Android 6.0 (API 23))
- Android Support Repository
Once selected, click the install button and take a break while these items are downloaded. After the download completes you can close the Android SDK Manager.
The final step of installation is to set a few paths so Cordova can find these newly installed Android tools. On Linux/Mac you can do so by adding the following lines to ~/.bash_profile:
export ANDROID_HOME=/Development/android-sdk/
export PATH=${PATH}:/Development/android-sdk/platform-tools:/Development/android-sdk/tools
Of course you will need to adjust the paths to match where you extracted the android sdk to.
On Windows you can accomplish the same thing by editing your Environmental Variables. Click on Start and search for Environmental Variables. Select Edit the system Environment Variables when it appears. In the window that opens select Environmental Variables. Now you can add a new variable for ANDROID_HOME, and edit the PATH variable to add the path to the android platform-tools and tools directories.
Drivers
The final step of the install process involves making sure we can push our new apps to a connected phone or tablet. We'll start this by seeing if we may already have drivers installed. On your Android device open the Settings menu and go to the About phone page (it's typically near the bottom of settings). On this page scroll to the bottom and find the Build number. Tap on the Build number seven times and a message should appear on screen letting you know that you are now a developer. Now, go back to the main settings menu and click on the new menu option Developer options. In this menu scroll down a bit and enable USB Debugging. At this point the phone is setup and ready to go.
Plug in the phone to your computer and open a terminal window. Run the following command:
$ adb devices
There are a couple things that could happen here:
- you get an error that this command cannot be found
- the command runs but no devices are found
- the command runs and devices are found
If the command can't be found, go back to the previous section and double check the path to the Android platform-tools directory.
If the command runs and devices are found, CONGRATULATIONS! You're done with setup. You can skip the rest of this section and proceed to "Creating Your First App".
If the command runs, but devices are not found you're exactly where you should be. At this point we'll need to install drivers for the debug connection of the phone. Nexus phones are by far the easiest to find drivers for because they are available in the Android SDK Manager (down at the bottom under Extras). Other phones should have drivers available from their manufacturer. Because there are so many I can't cover exact steps for how to install these drivers. Use your Google skills to find an appropriate driver for your phone. After installing a driver try running "adb devices" again to see if the phone shows up. Once it does, you're ready to proceed.
Creating Your First AppJust to prove that our setup is working correctly, let's create a demo app to check. Start by opening a command terminal and navigating to the directory you'd like your app to reside. I created mine in my home directory. Once you're ready to create the app, run the following commands
$ cordova create TutorialApp
$ cd TutorialApp
$ cordova platform add browser
Once you've done this the console should look like this:
The first command tells Cordova to go ahead and create a new Cordova app using a basic demo template. After we change directory (cd) into the app's directory, we then tell Cordova that we would like to package this application for use with a web browser.
At this point, the app is complete and we can run the app by typing:
$ cordova run browser
This will start a mini web-server locally on your machine, open a browser, and show you your application.
While this is cool, it's not what you came here to do...So let's add another platform and package this app to run on your cell phone. To do this simply run:
$ cordova platform add android
Assuming you've installed all of the Android requirements correctly, this will add the ability to build this app for Android. You can now run it on a phone by connecting it via USB and running:
$ cordova run android
This will build an Android Package (APK), push it to the phone with ADB, and then run it.
If you look in your device's app drawer you should also see an icon for this app titled "Hello Cordova".
Debugging Your First AppNow that we've created our first app, let's dig under the hood to find out how it works.
With your phone connected and the app running, open a new tab in chrome and type "chrome://inspect" in the URL bar. You should see this page appear:
Next you'll want to click on "Inspect" under the "Hello World" app. This will open a new window that looks like this:
This is the Chrome debugger. Normally you would use this to debug web pages and web based applications. Because we are building a webapp, we can also use it to debug the application running on our phone! Pretty cool!!
If you've ever used a debugger for an embedded device, you should feel right at home. Breakpoints can be added by clicking on a line number, variables can be watched using the watch window, and execution can be stepped using the execution buttons at the top right of the debugger window.
There are two main files we want to take a look at: index.html and index.js. Index.html sets up the overall layout of the page, while index.js controls the content and any interactive elements.
If you look at index.html, you'll notice there are two messages that can be displayed: "Device is Ready" and "Connecting to Device". We however have only seen "Device is Ready" because the app loads so quickly.
Let's try to pause execution of the app so that we can ensure "Connecting to Device" text is displayed properly. In the Chrome debugger, open index.js and add a breakpoint to line 36 by clicking on the line number.
Because the app has already run to completion, we need to effectively reset the app so that we can hit the breakpoint. To do this, click on the refresh button in the top left of the debugger window. You should now see this:
The debugger will halt execution at the breakpoint and because the 'deviceReady' event hasn't fully propagated through the javascript, we should see "Connecting to Device in the app's display.
If you wish to keep going, try stepping into the receivedEvent function and finding the lines that actual change what message is displayed. Given that both messages are in the HTML, how are these elements individually turned on and off?
The javascript accomplishes this by changing the CSS style associated with each element. First it goes and find the elements in the html by searching by their class (listening and received). Then it changes a style attribute called display, which has the effect of changing which text is displayed and which is hidden.
Using Plug-InsNow that we understand the basics of how to build a real app with Cordova, let's add some connectivity. By far one of the most popular connectivity options for makers is Bluetooth Low Energy (BLE), and writing apps that use it couldn't be easier. Cordova supplies plugins that allow developers to access the underlying hardware of the phone through javascript. This makes it extremely easy to do just about everything you could with a native application in a much easier to develop cross platform HTML5/JS environment.
To get started with this next part of the guide you'll want to download the CC2650 SensorTag Cordova Example from the bottom of this page. After you download it, extract the zip file and navigate to the extracted project with a terminal. Once there, we need to add a platform, add the BLE plugin, and then run the project.
$ cordova platform add android
$ cordova plugin add cordova-plugin-ble-central
$ cordova run android
Make sure you have bluetooth enabled on your phone and a CC2650 SensorTag nearby and turned on (Green LED should be blinking). Refresh the device list in the app and your SensorTag should appear. To connect to the SensorTag, click on the hardware identifier number. It should look like "68:C9:0B:04:44:80", but your numbers will be different. If you get an error message it is because you touched something other than the hardware ID. Just click ok on the error message and try again. Once connected, the app will begin displaying data from the accelerometers, gyroscopes, magnetometers, barometer, and thermometer.
If we take a look at the code in the debugger, we'll find its actually pretty simple. Our index.html simply sets up a template for the data with a few divs:
SensorTag
Refresh
waiting...
waiting...
Disconnect
All of the real work of generating content for these divs is done in index.js. The app starts by registering handlers for the control buttons and page events as well as hiding the detailPage div:
initialize: function() {
this.bindEvents();
detailPage.hidden = true;
},
bindEvents: function() {
document.addEventListener('deviceready', this.onDeviceReady, false);
refreshButton.addEventListener('touchstart', this.refreshDeviceList, false);
disconnectButton.addEventListener('touchstart', this.disconnect, false);
deviceList.addEventListener('touchstart', this.connect, false);
},
Just like in the example app, we are adding an event listener for the 'deviceready' event so that we can know when all of the underlying services and plugins of Cordova are ready. In this case we are calling the function onDeviceReady when this occurs:
onDeviceReady: function() {
app.refreshDeviceList();
},
refreshDeviceList: function() {
deviceList.innerHTML = ''; // empties the list
// scan for all devices
ble.scan([], 5, app.onDiscoverDevice, app.onError);
},
onDiscoverDevice: function(device) {
// we're not limiting scanning by services, so filter
// the list for devices with "Sensor" in the name
if (device.name.match(/sensor/i)) {
var listItem = document.createElement('li'),
html = '' + device.name + '' +
'RSSI: ' + device.rssi + ' | ' +
device.id;
listItem.dataset.deviceId = device.id; // TODO
listItem.innerHTML = html;
deviceList.appendChild(listItem);
}
},
This function in turn calls refreshDeviceList which tries to scan for BLE devices using a call to ble.scan() from the BLE-Central plugin we added earlier. When a device is found, onDiscoverDevice is called which actually adds this new device to the HTML so the user can see it.
Now that we know how the list is populated, what happens when we touch that hardware ID to connect to the device?
Well when the app first started, we bound some functions to certain events. One of those was:
deviceList.addEventListener('touchstart', this.connect, false);
This will cause the connect function to be called whenever a touch starts on the deviceList.
connect: function(e) {
var deviceId = e.target.dataset.deviceId,
onConnect = function() {
//Subscribe to button service
ble.startNotification(deviceId, button.service, button.data, app.onButtonData, app.onError);
//Subscribe to accelerometer service
ble.startNotification(deviceId, accelerometer.service, accelerometer.data, app.onAccelerometerData, app.onError);
//Subscribe to barometer service
ble.startNotification(deviceId, barometer.service, barometer.data, app.onBarometerData, app.onError);
// turn accelerometer on
var configData = new Uint16Array(1);
//Turn on gyro, accel, and mag, 2G range, Disable wake on motion
configData[0] = 0x007F;
ble.write(deviceId, accelerometer.service, accelerometer.configuration, configData.buffer,
function() { console.log("Started accelerometer."); },app.onError);
var periodData = new Uint8Array(1);
periodData[0] = 0x0A;
ble.write(deviceId, accelerometer.service, accelerometer.period, periodData.buffer,
function() { console.log("Configured accelerometer period."); },app.onError);
//Turn on barometer
var barometerConfig = new Uint8Array(1);
barometerConfig[0] = 0x01;
ble.write(deviceId, barometer.service, barometer.configuration, barometerConfig.buffer,
function() { console.log("Started barometer."); },app.onError);
//Associate the deviceID with the disconnect button
disconnectButton.dataset.deviceId = deviceId;
app.showDetailPage();
};
ble.connect(deviceId, onConnect, app.onError);
},
This function is a little tricky to understand at first because there is a whole other function inside of it. Let's walk through it and try to understand what is going on here.
The first thing the function does is tries to get the hardware ID from the event. This is why it is critical that you click on the hardware ID in order to connect. If you don't click on the hardware ID, this line of code isn't able to properly extract the ID and the connection fails.
The next thing that happens is all the way at the bottom of the function. The last line is executed. ble.connect() tries to connect to the requested deviceId. If it's successful, the onConnect function which is inside the connect function will be called.
The onConnect function is what configures the SensorTag and registers callback functions for when data is received. I won't spend too much time on how the device is configured, but if you want to do some more reading the SensorTag User's Guide is a great place to start. As a final step this function turns off the mainPage and turns on the detailPage by calling showDetailPage().
When data is actually received by the app, callback functions take care of processing this data and displaying it on the detailPage. Let's take a quick look at one of these functions:
sensorBarometerConvert: function(data){
return (data / 100);
},
onBarometerData: function(data) {
console.log(data);
var message;
var a = new Uint8Array(data);
//0-2 Temp
//3-5 Pressure
message = "Temperature " +
app.sensorBarometerConvert( a[0] | (a[1] << 8) | (a[2] << 16)) + "Degrees C " +
"Pressure " +
app.sensorBarometerConvert( a[3] | (a[4] << 8) | (a[5] << 16)) + "hPa " ;
barometerData.innerHTML = message;
},
From the earlier call to ble.startNotification, we know that the onBarometerData function will be called whenever new data is available from the barometer service. By looking at the sensortag user's guide we know that the data consists of two 24 bit unsigned integers. By taking the data and processing it as a uint8_t array (just like we would in the embedded world), we can easily assemble the bytes into two 24 bit unsigned integers, convert their value, and then add it to the HTML which is displayed to the user. The callback functions for the other sensors work in much the same way.
Visualizing Sensor DataNow that we understand the basics of how to build an app using HTML5/JS and collect data using plugins, we probably want to do something a little more meaningful to the user than simply display a bunch of numbers. Thanks to the open source nature of the web, we can easily accomplish this using any number of javascript libraries that are available for free. For my app I chose to graph the SensorTag data using SmoothieCharts.js.
To get started visualizing our data, we need to add the smoothiecharts.js file to our project. To do this, right click on this link and save it in your project's /www/js directory. You can also find documentation on the basics of Smoothie Charts here.
We'll now need to edit the source code of the app to include the chart. Open index.html in your projects www directory using a text editor of your choice. The first step is to add this new javascript library to the source. Add the following line near the bottom of the file where the other javascript files are included but make sure this line is before the include of index.js:
It should look like this when you're done:
Next, we need to add a canvas (which is a special type of HTML5 graphic) to our html page. Let's add this somewhere inside the detailPage div. We'll add the following line:
Once added the detailPage div should look something like this:
That said, this line can go anywhere inside the div. Changing the order of these lines will effectively change the order in which the data is shown on the detail page.
At this point, we are done modifying the HTML and need to add some javascript code to initialize and populate the graph with data from the SensorTag. Go ahead and open index.js in the www/js directory of your project using your favorite text editor.
The first step of initialization is to create a new Smoothie Charts object and associate it with the canvas we created in the last step. We can do this by adding the following code to index.js:
var smoothie = new SmoothieChart();smoothie.streamTo(document.getElementById("mycanvas"));
Your next question should be: "Where do I put this in index.js?!?". Good question, I'm glad you asked. Depending on where we put this code, the scope of what can access the chart will change. To keep things simple, I'm going to add this line outside of the var app object such that the chart has global scope and can easily be accessed from inside the var app object where our data is coming in. After I added the code my file looked like this:
The last step is of course adding data to the graph so that we can visualize it. Just like adding the chart and initializing it, this is a pretty easy task. We'll start by creating a time series which we can add our data to and then we'll add it to our Smoothie Chart. Add these lines after the call to smoothie.streamTo():
var line1 = new TimeSeries();smoothie.addTimeSeries(line1);
It will look like this when complete:
Finally, as data comes in over Bluetooth we need to append it to the TimeSeries we just created. In this example, I'm going to use pressure data from the on board barometer, but the same principles can be used to graph data from any sensor. Add the following line in the onBarometerData function:
line1.append(new Date().getTime(), app.sensorBarometerConvert( a[3] | (a[4] << 8) | (a[5] << 16)));
This will create a new time/value pair using the current time and data we just received as well as add it to the time series. Once added it should look something like this:
Now we can test our changes by making sure everything is saved before running:
$ cordova run android
Once the app builds and pushes to the device try connecting to your SensorTag as we did before to see if the barometer data is now graphed. If you're not seeing the graph, try connecting to the app with the Chrome debugger to see if there are any errors. Because HTML5/JS are not compiled, spelling and capitalization errors are typically only caught at runtime.
That concludes the Getting Started with Cordova guide, but I hope that the learning you'll do on this topic is only just beginning. This guide was meant to be a crash course in building apps with embedded connectivity, but there is so much more you can do from here very easily.
Try graphing the accelerometer, gyroscope, or magnetometer data.
Try fusing the sensor data in javascript to get attitude and heading data.
You could even display a 3D model that moves based on sensor data using WebGL.
You're really only limited by your imagination, and because we are working in Javascript (the most popular language in the world right now), there are tons of libraries and experts that can help you on your journey.
If you're new to the web world, I highly recommend spending some time learning the basics of HTML5/JS/CSS. W3Schools is a great free resource but there are many other teaching sites that can help get you up to speed quickly.
Now get out there and build some apps!
Comments