Arduino BLE Shield + Spacebrew
/This tutorial is a more in-depth look at some of the code behind the SpaceBlue project. Specifically, it goes over some of the finer points in the node.js app that integrates information from the RedBear BLE Shield and then sends it via Spacebrew. This project uses the Noble library for connecting node to BLE.
The code for the entire project is available on Github here. This tutorial will explain certain points about the node app, spacebrew_and_BLE.js, which is here.
As a preliminary matter, you need to make sure certain files are in the same folder as your app. The spacebrew.js library needs to be in there. Additionally, you need to have the Spacebrew and the Noble modules installed in your node_modules folder.
Also note that this node app uses the public Spacebrew playground. If you want to use your local server, you can un-comment the line of code that reads: sb.server = "localhost";. In that case, you need to make sure any other apps also use the localhost, and that you're running the Spacebrew server on your localhost as well.
General setup:
First, note that the way the app works is that it connects to two specific BLE Shields, tests their RSSI (Received Signal Strength Indication) every 100th of a second, and then averages the last 100 values in order to send it to Processing. As a result, we set up two arrays to hold this information. Because these arrays will hold data from specific shields, we named them accordingly:
//variables for calculating average within Bluetooth function var numberArrayBLE1 = []; // for Gus BLE Shield var numberArrayBLE2 = []; // for Jennifer BLE Shield
ConnectSpacebrew():
Next, we define the ConnectSpacebrew() function.
Because we want to make sure that Spacebrew is connected before we start scanning for BLE devices, we included a separate function, InitializeBluetooth(), as a callback once Spacebrew actually connects:
sb.onOpen = function (){ console.log("Spacebrew is open"); // initialize Bluetooth connection only after Spacebrew is open InitializeBluetooth(); };
We have a placeholder for receiving messages next, onStringMessage. But because we don't actually receive any messages in this app, we don't actually use this function.
InitializeBluetooth():
Next we define the InitializeBluetooth() function. This function immediately scans for BLE devices in the vicinity. After that, if the status of the computer's Bluetooth receiver changes, the app sends messages to the console. In addition, if it's turned on, it scans again:
// if state changes to on, always start scanning; otherwise, stop if (state === 'poweredOn') { console.log('state is powered on'); noble.startScanning(); console.log('started scanning'); } else { noble.stopScanning(); console.log('scanning stopped'); }
For each device discovered, the app will read its advertised information and print certain pieces of it to the console:
noble.on('discover', function(peripheral) { console.log('peripheral discovered (' + peripheral.uuid+ '):'); console.log('\thello my local name is:'); console.log('\t\t' + peripheral.advertisement.localName); console.log('\tcan I interest you in any of the following advertised services:'); console.log('\t\t' + JSON.stringify(peripheral.advertisement.serviceUuids));
The UUID, or Universally Unique Identifier, is a number that is unique to a device. In our case, we knew that we wanted to connect to two very specific shields. Therefore, we included their UUIDs in the function so that we could make sure we would connect to the right devices and not to any others.
The method in the Noble library to connect is, conveniently, connect():
// if the device is either of the two RedBear BLE shields -- GUS or JGP -- connects if (peripheral.uuid === 'd49abe6bfb9b4bc8847238f760413d91' || peripheral.uuid === '9e2aab25f29d49078577c1559f8f343d') { peripheral.connect(function(error) { console.log('Connected to ', peripheral.advertisement.localName); ReadButtonPress(peripheral); UpdateRSSIAndAverage(peripheral); }); }
You can see that when we connect to the devices, two more functions get called: UpdateRSSIAndAverage() and ReadButtonPress().
Accessing the data sent from the BLE devices is fairly simple using the Noble library. Accessing the RSSI happens with one simple call:
peripheral.updateRssi(function(error, rssi) {
Reading the button requires subscribing to the appropriate characteristic, and then reading the data. It may be helpful to look through the full code here. We first discover the correct service, then discover the correct characteristic (these are both specific to the RedBear BLE Shields), and then read the data from that characteristic.
In order to subscribe to a characteristic, use the notify() method.
txCharacteristic.notify(true, function(error) {
After that, we set up an anonymous function that is called whenever the data changes.
txCharacteristic.on('read', function(data, isNotification) {
Custom data types:
Finally, we've set up custom data types to send the information to Processing. When we set up Spacebrew, we add two publishers. Because we're sending custom data types, we define the data as JSON objects in the third parameter of each addPublish function. Note that both send the name of the device, but the each one sends data appropriate for that type:
// add custom data types for rssi and for button sb.addPublish("rssi", "rssi_info", {deviceName:"", rssiValue:""} ); sb.addPublish("button", "button_info", {deviceName: "", buttonValue:""} );
When we actually send these messages, which we do within the UpdateRSSIAndAverage() and ReadButtonPress() functions, we need to create strings that fit the JSON format we defined above.
Such as here:
var rssiAvgData2 = '{\"deviceName\":\"' + peripheral.advertisement.localName + '\",\"rssiValue\":\"' + averageResults2.average + '\"}'; console.log('rssiAvgData2: ', rssiAvgData2); sb.send("rssi", "rssi_info", rssiAvgData2);
and here:
var buttonData = '{\"deviceName\":\"' + peripheral.advertisement.localName + '\", \"buttonValue\":' + data.readUInt8(0).toString() + '}'; console.log("buttonData: ", buttonData); sb.send("button", "button_info", buttonData);
So long as you're attached to an app on the other side that's set up to receive these data types, such as the Processing app in this project, you're in business!