When the Raspberry Pi 3 was released I was thrilled, finally an capable Raspberry Pi board with both WiFi and BLE built in. No more issues with getting USB-dongles to work.
In this article you will learn how you easily can develop mobile applications that communicates via Bluetooth Low Energy with your Raspberry Pi 3. The application will be developed using nothing but web technologies such as JavaScript and HTML.
The article is an rewrite of the much appreciated article on the same theme but adapted for the C.H.I.P. development board. You can find the original article here.
In order to try this example you need the following tools:
Part 1 - Raspberry Pi Setup
Preparation
In order to follow this article I assume that you have super user/root access on your Raspberry Pi. I also assume that you know how to open a terminal on the development board. If you, like me, start out with a newly installed Raspbian (2016-03-18 +) you should be covered. This article is written with the assumption that you are using Raspbian. If you are running some other Linux distribution you should be able to get this example up and running by using the information found in this article.
Board Configuration
The application that we are going to run on the Raspberry Pi 3 is a Node.js application that makes use of the library Bleno. This library requires that you have installed the Bluetooth stack, Bluez. Lucky for you Bluez is pre-installed in the latest releases of Raspbian. The first thing we are going to do is to check that Bluez is installed and which version. Do this by running the following command:
hcitool | grep ver
Most likely you have a 5.xx version installed on your Raspberry Pi, which is fine. If the command does not work, you can try to install the package pi-bluetooth by running the following command:
sudo apt-get install pi-bluetooth
In order for the Bleno library to work you need to stop the bluetooth daemon. The following command will stop the daemon and print the status of the daemon to verify that it has been stopped.
sudo systemctl stop bluetooth
sudo systemctl status bluetooth
In the print out (seen below) verify that the row prefixed with Active: states that the service is inactive.
● bluetooth.service - Bluetooth service
Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled)
Active: inactive (dead) since Mon 2016-04-04 06:58:30 UTC; 4s ago
Docs: man:bluetoothd(8)
Main PID: 584 (code=exited, status=0/SUCCESS)
Status: "Quitting"
Note that this is temporary, if you reboot your Raspberry Pi 3, the daemon will start again. You can disable the daemon permanently by running the following command:
sudo systemctl disable bluetooth
This might cause you issues later on if you are trying to use the Bluetooth connection in another way. You also need to power up the Bluetooth adapter after stopping the bluetooth daemon.
sudo hciconfig hci0 up
The next step is to install the latest version of Node.js (as of today v5.9.1). Since the Raspberry Pi is based on an ARM7 core there is prebuild binaries available on Node.js homepage. You can find all node-releases, the files containing binaries that executes on the RPi are the ones that have a name that contains armv7l. Start by downloading and uncompressing the file:
cd ~wget https://nodejs.org/dist/v5.9.1/node-v5.9.1-linux-armv7l.tar.xztar xfv node-v5.9.1-linux-armv7l.tar.xzcd node-v5.9.1-linux-armv7l/
You install node and npm on your development board by running the following command:
sudo cp -R * /usr/local/
Verify that you succeeded to install node by trying the following commands:
node -v (should output v5.9.1)npm -v (should output 3.7.3)
The last step in the preparation process is to install some essential software that is needed in the upcoming chapter. Run the following commands in order to do that:
sudo apt-get update
sudo apt-get install git libudev-dev
You are now ready to move on to the next step and start developing the Node.js application that we will run on the Raspberry Pi 3.
Part 2 - Node.js ApplicationFirst step in getting the Node.js application up and running on the Raspberry Pi is to clone the application from the Github repository. Run the following command in a directory where you would like to store the application.
cd ~
git clone https://github.com/evothings/evothings-examples.git
The repository consists of two applications, the app is the mobile application that we will discuss in the following chapter and the rpi3-application that we will use in this chapter. The next step is to install the dependencies:
cd evothings-examples/examples/rpi3-system-information/rpi3-applicationnpm install
If you have followed the initial steps in this tutorial all libraries required to build the application dependencies should be in place and the installation should succeed. Before explaining the application lets try to run it:
sudo node index.js
Source Code
Let's have a look at the source code, open the file index.js in an editor of your choice.
var bleno = require('bleno');
var SystemInformationService = require('./systeminformationservice');
var systemInformationService = new SystemInformationService();
bleno.on('stateChange', function(state) {
console.log('on -> stateChange: ' + state);
if (state === 'poweredOn') {
bleno.startAdvertising(bleno.name, [systemInformationService.uuid]);
}
else {
bleno.stopAdvertising();
}
});
In the code above we ensure that the Raspberry Pi starts to advertise once the chip enters the state poweredOn. If it leaves that state the Raspberry Pi will stop to advertise its presence. We rely heavily on the Bleno library in this example, it enables us to define our own BLE-services in our node application
bleno.on('advertisingStart', function(error) {
console.log('on -> advertisingStart: ' +
(error ? 'error ' + error : 'success')
);
if (!error) {
bleno.setServices([
systemInformationService
]);
}
});
Once the BLE-chip has started to advertise it emits the event advertisingStart, upon which we configure the services that we will expose to the user. In this case we only have one service, the SystemInformationService.
Lets have a look at the SystemInformationService, it is implemented in the file systeminformationservice.js, open the file.
var bleno = require('bleno');
var util = require('util');
var LoadAverageCharacteristic = require('./characteristics/loadaverage');
var UptimeCharacteristic = require('./characteristics/uptime');
var MemoryCharacteristic = require('./characteristics/memory');
function SystemInformationService() {
bleno.PrimaryService.call(this, {
uuid: 'ff51b30e-d7e2-4d93-8842-a7c4a57dfb07',
characteristics: [
new LoadAverageCharacteristic(),
new UptimeCharacteristic(),
new MemoryCharacteristic()
]
});
};
util.inherits(SystemInformationService, bleno.PrimaryService);
module.exports = SystemInformationService;
In the code above we define the service to include the following three characteristics, LoadAverageCharacteristic, UptimeCharacteristic and MemoryCharacteristic. The service is given the UUID ff51b30e-d7e2-4d93-8842-a7c4a57dfb07 which we will use in the next chapter to connect to the service. The UUID was randomly generated using this web page.
In the next step we will look at one of the characteristics stated above, in this case the UptimeCharacteristic. Since all characteristics are implemented similarly, we will leave it to you to look at and understand how LoadAverageCharacteristic andMemoryCharacteristic are implemented. Open the file characteristics/uptime.js .
var bleno = require('bleno');
var os = require('os');
var util = require('util');
var BlenoCharacteristic = bleno.Characteristic;
var UptimeCharacteristic = function() {
UptimeCharacteristic.super_.call(this, {
uuid: 'ff51b30e-d7e2-4d93-8842-a7c4a57dfb09',
properties: ['read'],
});
this._value = new Buffer(0);
};
UptimeCharacteristic.prototype.onReadRequest = function(offset, callback) {
if(!offset) {
this._value = new Buffer(JSON.stringify({
'uptime' : os.uptime()
}));
}
console.log('UptimeCharacteristic - onReadRequest: value = ' +
this._value.slice(offset, offset + bleno.mtu).toString()
);
callback(this.RESULT_SUCCESS, this._value.slice(offset, this._value.length));
};
util.inherits(UptimeCharacteristic, BlenoCharacteristic);
module.exports = UptimeCharacteristic;
In this article we only make use of read characteristics, there are a lot of other possibilities available as well. The UptimeCharacteristic will upon read deliver the number of seconds that the board has been up and running wrapped in a JSON-object. The UptimeCharacteristic.prototype.onReadRequest contains the callback function that will be executed when a server tries to read the characteristic. In this case the method takes two parameters, offset & callback. The offset parameter contains a value that defines from what buffer offset data should be returned to the callback. If a new characteristic read is initiated the offset parameter is equal to zero and this initiates an update of the buffer (this._value).
Part 3 - Mobile ApplicationIn the last chapter you cloned the Github repository containing the applications. In this chapter we will have a look at the application in the evothings-applications directory. Evothings is a development tool that enables users to build cross platform IoT-applications using nothing but web technologies such as HTML and JavaScript.
In order to run the example provided you need to install the Evothings Workbench on your computer and the Evothings Viewer (iOS, Android) on your smartphone. There is more detailed instructions available on Evothings homepage.
Open the Evothings Workbench on your computer and press the Connect tab. In this tab you will connect your smartphone with your computer in order to enable live updates of your mobile application project. Press the GET KEY button to receive a key.
Open the Evothings Viewer on your smartphone and enter the key in the Connect Keyfield and press the Connect button. Now you have connected your workbench to your mobile phone and you are ready to start to develop your mobile application.
Download (or clone) the Github repository and unzip it to a folder of your choice. Browse to the generated/examples/rpi3-system-information/app directory and find the index.html file. Press the My Apps button in the Evothings Workbench and drag the file to the window. Now you should have an application named Raspberry Pi 3 - System Information in your window.
If you press the Run button the application should load on your smartphone, see the figure below.
Press the scan button to start looking for advertising BLE-devices nearby. If you have the node application running on the Raspberry Pi you should find your device among the detected devices. Push that device and wait. The application now tries to establish a BLE connection between the Raspberry Pi and your smartphone. When it succeeds it will try to read the three characteristics (uptime, memory, load average) and present them on the screen see the figure below.
Source Code
The application mainly consists of three files, index.html which contains all the code connected to the user interface, app.css which contains the style sheets of the application and last, app.js which contains all the logic of the application.
Open the file index.html in a code editor.
The user interface consists of four views, startView, scanResultView, loadingView and systemInformationView. The startView consists of the initial view that is displayed to the user when the applications is launched. In the view there is a button that, when pressed, executes the app.startScan method in defined in app.js. The scanResultView presents the user with the BLE-devices found during scan and enables the user to connect to one of them. During the connection establishment the loadingView is shown until, the systemInformationView containing the data and a disconnect button is presented to the user. When the user presses the disconnect button the method app.disconnect is executed. In the initial state of the application only the startView is visible, the other views are hidden.
Open the file app.js in a code editor. This file contains all application logic. There is a clear path of usage in this application, first the user initiates a scanning by pressing the scan button presented in the first view. This executes the method app.startScan
app.startScan = function() {
app.disconnect();
console.log('Scanning started...');
app.devices = {};
var htmlString =
'<img src="img/loader_small.gif" style="display:inline; vertical-align:middle">' +
'<p style="display:inline"> Scanning...</p>';
$('#scanResultView').append($(htmlString));
$('#scanResultView').show();
function onScanSuccess(device) {
if (device.name != null) {
app.devices[device.address] = device;
console.log('Found: ' + device.name + ', ' + device.address + ', ' + device.rssi);
var htmlString =
'<div class="deviceContainer" onclick="app.connectTo(\'' +
device.address + '\')">' +
'<p class="deviceName">' + device.name + '</p>' +
'<p class="deviceAddress">' + device.address + '</p>' +
'</div>';
$('#scanResultView').append($(htmlString));
}
};
function onScanFailure(errorCode) {
// Show an error message to the user
app.disconnect('Failed to scan for devices.');
// Write debug information to console.
console.log('Error ' + errorCode);
};
evothings.easyble.reportDeviceOnce(true);
evothings.easyble.startScan(onScanSuccess, onScanFailure);
$('#startView').hide();
};
Initially the method shows the scanResultView and hides the startView. The functiononScanSuccess is executed each time a new device is detected. The function extracts the relevant information about the device, builds a HTML and inserts the information in the scanResultView by appending a new HTML string. The function onScanFailure is executed if some error occurs during the scanning in which case the app.disconnect method is called. If a user presses any of the found devices the
app.connectTo = function(address) {
device = app.devices[address];
$('#loadingView').show();
app.setLoadingLabel('Trying to connect to ' + device.name);
function onConnectSuccess(device) {
function onServiceSuccess(device) {
// Application is now connected
app.connected = true;
app.device = device;
console.log('Connected to ' + device.name);
var htmlString = '<h2>' + device.name + '</h2>';
$('#hostname').append($(htmlString));
$('#scanResultView').hide();
$('#loadingView').hide();
$('#systemInformationView').show();
Object.keys(app.CHARACTERISTICS).map(
function(characteristic){
device.readCharacteristic(
characteristic,
app.CHARACTERISTICS[characteristic],
function(error){
console.log('Error occured')
});
});
}
function onServiceFailure(errorCode) {
// Disconnect and show an error message to the user.
app.disconnect('Wrong device!');
// Write debug information to console.
console.log('Error reading services: ' + errorCode);
}
app.setLoadingLabel('Identifying services...');
// Connect to the appropriate BLE service
device.readServices(
[app.SYSTEMINFORMATIONSERVICE],
onServiceSuccess,
onServiceFailure
);
}
function onConnectFailure(errorCode) {
app.disconnect('Disconnected from device');
// Show an error message to the user
console.log('Error ' + errorCode);
}
// Stop scanning
evothings.easyble.stopScan();
// Connect to our device
console.log('Identifying service for communication');
device.connect(onConnectSuccess, onConnectFailure);
};
The method above starts by displaying the loadingView and hiding the scanResultView, cancels the ongoing scanning and tries to connect to the device. If the connection fails the function onConnectFailure is executed, this function is basically a wrapper to theapp.disconnect method. If the connection attempt succeeds the functiononConnectSuccess is executed, this function immediately tries to read the serviceapp.SYSTEMINFORMATIONSERVICE. If the service can not be found it calls the functiononServiceFailure which, again, basically is a wrapper to app.disconnect. The functiononServiceSuccess is called if the read attempt succeeds. The application then immediately reads all the characteristics defined in the app.CHARACTERISTICS object. Each read is registered with a custom callback that handles the data and prints the data to the systemInformationView. The implementation of the callbacks can be found at the end of the file in the functions printUptime, printMemory and printLoadAverage.
app.disconnect = function(errorMessage) {
if (errorMessage) {
navigator.notification.alert(errorMessage, function() {});
}
app.connected = false;
app.device = null;
// Stop any ongoing scan and close devices.
evothings.easyble.stopScan();
evothings.easyble.closeConnectedDevices();
console.log('Disconnected');
$('#scanResultView').empty();
$('#hostname').empty();
$('#memory').empty();
$('#uptime').empty();
$('#loadaverage').empty();
$('#loadingView').hide();
$('#scanResultView').hide();
$('#systemInformationView').hide();
$('#startView').show();
};
The code above shows the implementation of the app.disconnect method. It is basically a wrapper for the evothings.easyble.closeConnectedDevices that executes the disconnect procedure. The last lines empties the dynamic data that the application added during the scanning and data fetching, hides all views but the startView that enables the user to start a new scan as described in previous parts of this article.
Part 4 - ConclusionThis is an article written with the intent of describing how to get started with building BLE applications for your Raspberry Pi. Using the provided source code as a starting point you should be able to implement applications that satisfy your requirements. Using the Node.js, the Bleno library and the tools from Evothings you can start developing your IoT-solutions using nothing but web technologies.
Comments