In one of my previous posts, [PiIoT#03]: Cheap BLE Beacons with nRF24L01+, I discussed how to create cheap BLE beacons with nRF24L01+ modules. In this post, I'm going to use the inbuilt BLE functionality of Pi3 to detect the presence of such BLE tags and update it a UI. This is done is two steps. First part, I uses a nodeJS script to monitor the presence of such BLE tags around Pi and send MQTT messages of specific topics to a connected broker. Next, these messages are subscribed by a freeboard UI running inside a browser and updated to the dashboard.
Setting up the beacons
A detailed description of faking a BLE beacon is already discussed previously. All you have to do is to upload it with different MAC address to different beacons. A modified version of example sketch is given below:
// RF24 Beacon #include <SPI.h> #include "RF24Beacon.h" RF24Beacon beacon( 9,10 ); uint8_t customData[] = { 0x01, 0x02, 0x03 }; void setup() { beacon.begin(); beacon.setMAC( 0x01, 0x02, 0x03, 0x04, 0x05, 0xF6 ); // Change this uint8_t temp = beacon.setName( "beacon1" ); // Change this beacon.setData( customData, 2 ); } void loop() { // beacon.sendData( customData, sizeof(customData) ); beacon.beep(); delay( 1000 ); }
The code is very similar to the one discussed in previous post, except you need to take care a few things here:
- In line#14, change the MAC id for each of the beacons you create. This is used to uniquely identify the beacon by nodejs script.
- At line#16, change the beacon name to some thing more meaningful for your use - like 'car keys' or 'jimmy' for each beacons.
- At line#26, appropriately set the delay for advertising.
Now you can upload the code to you beacons and verify the advertising packets with hcitool as mentioned in previous post.
Scanning for beacons with Pi
Next part is a nodejs script to scan for the beacons and publish mqtt messages. For scanning for beacons, I use 'noble' library and for mqtt I use MQTT.js. Both can be installed through npm as below:
$ npm install noble $ npm install mqtt
Once these packages are installed, we can start writing the script. My script is loosely based on one of the example scripts available with noble package for continuously checking for beacons. Basically the script does this:
- Connects to MQTT broker and publish a message on 'helloTopic' which indicates the scanner is online
- Switches on the bluetooth and start scanning
- Once it founds a valid advertisement, it check whether that tag is new
- If new, then add this to the list of online tags 'onlineTags' along with it's uuid and last seen time stamp
- If this tag is already in list, update lastSeen time and RSSI value
- With a period, run a function
- which checks for the lastSeen time of all available beacons and see if anyone went offline
- If some tag is missing, publish a message indicating 'offline' status with -100dB rssi
- If online, publish a message 'online' status and last known RSSI
- Also handle App exits by publishing a disconnect message to broker on 'helloTopic'
The script is given below:
// Scan for BLE beacons and send MQTT messages for each beacon status var noble = require('noble'); var mqtt = require( 'mqtt' ) var util = require('util'); // var RSSI_THRESHOLD = -90; var EXIT_GRACE_PERIOD = 4000; // milliseconds var onlineTags = []; // ---------------- MQTT Connection -------------- var helloTopic = 'user/vish/devices/jupiter' var beaconBaseTopic = 'user/vish/devices/ble_tags' // Configuration of broker var connectOptions = { host: '192.168.1.54', port: 1883 } // -------------- MQTT Event Handling function -------------------- function onConnect() { console.log( 'Connected to broker ' + connectOptions.host +':' + connectOptions.port ) // Send a hello message to network client.publish(helloTopic, '{"connected":"true"}' ) } function onMessage( topic, message ) { console.log( " New message: [" + topic + "] > " + message ) } client = mqtt.connect( connectOptions ) client.on( 'connect', onConnect ) client.on( 'message', onMessage ) // ------------------ BLE Scanner functions ---------------------- function onDiscover( peripheral ) { console.log( new Date() + ' ' + peripheral.advertisement.localName + ' ' + peripheral.uuid + ' ' + peripheral.rssi ) if( peripheral.rssi < RSSI_THRESHOLD ) { return; } var id = peripheral.id; var newTag = !onlineTags[id]; if( newTag ) { onlineTags[id] = { tag: peripheral }; console.log( new Date() + ': "' + peripheral.advertisement.localName + '" ONLINE (RSSI ' + peripheral.rssi + 'dB) ') // publish a message for beacon msg = { uuid: peripheral.uuid, rssi: peripheral.rssi, adv : peripheral.advertisement, online: true, lastSeen: Date.now() } client.publish( beaconBaseTopic + "/" + peripheral.advertisement.localName, JSON.stringify(msg) ) } // Update rssi & last seen time onlineTags[id].rssi = peripheral.rssi onlineTags[id].lastSeen = Date.now(); } noble.on('discover', onDiscover ); noble.on('stateChange', function(state) { if (state === 'poweredOn') { noble.startScanning([], true); } else { noble.stopScanning(); } }); // ------ Timed function to check whether the devices are online and publish MQTT packets accordingly ---- function checkNUpdate() { // for each device in range for( var tagId in onlineTags ) { var tag = onlineTags[tagId].tag // prepare message var msg = { uuid: tag.uuid, adv: tag.advertisement, lastSeen: onlineTags[tagId].lastSeen } if( onlineTags[tagId].lastSeen < (Date.now()-EXIT_GRACE_PERIOD) ) { // Device went offline msg.online = false msg.rssi = -100 // delete from the list of visible tags delete onlineTags[tagId]; console.log( new Date() + ': "' + tag.advertisement.localName + '" OFFLINE (RSSI ' + tag.rssi + 'dB) ') } else { // device in with in range msg.online = true msg.rssi = tag.rssi } client.publish( beaconBaseTopic + "/" + tag.advertisement.localName, JSON.stringify(msg) ) } } // Function to be called at the end of script function handleAppExit (options, err) { if( err ){ console.log(err.stack) } if( options.cleanup ){ client.publish( helloTopic, '{"connected":"false"}' ) client.end( true ) } if( options.exit ){ process.exit() } } // Handle the different ways an application can shutdown process.on('exit', handleAppExit.bind(null, { cleanup: true })) process.on('SIGINT', handleAppExit.bind(null, { exit: true })) process.on('uncaughtException', handleAppExit.bind(null, { exit: true })) // Create the checkNUpdate function to run preiodically setInterval( checkNUpdate, EXIT_GRACE_PERIOD/2 );
You can save this as 'scanner.js' and then use
$ sudo node scanner.js
to start the script.
Here, my 'hello' topic is of the form 'user/vish/devices/<pi_name>' to identify from which Pi the scanner is working. Once you start the script, you can use any MQTT message viewer like MQTT-Spy to monitor the messages. For each BLE tag in the visibility, Pi will publish a MQTT message on topic 'user/vish/devices/ble_tags/<beacon_name>' with the scan info and last seen time stamp.
Creating a dashboard
Creating dashboard is almost same as the one I explained in my previous post. But before going to freeboard, make sure that you started the scanner.js script. Then only you will be able to find your beacons in the datasource list in freeboard. The procedure of creating new dashboard is as follows:
- In Freeboard, add your MQTT broker as a data source subscribing to 'beaconBaseTopic/#'. In my case, my MQTT datasource is named as 'Mercury' and beaconBaseTopic as 'user/vish/devices/ble_tags'
- Create a pane for each of the beacons
- Inside pane, create a Text widget with title 'Name' and value selected as the "datasources[\"Mercury\"][\"user/vish/devices/ble_tags/beacon1\"][\"adv\"][\"localName\"]".
- Create another text field with title as 'Last Seen'. For selecting the value, click on JS Editor option and enter
return new Date(datasources[\"Mercury\"][\"user/vish/devices/ble_tags/beacon1\"][\"lastSeen\"]).toTimeString()
- Next create a Guage widget with title 'RSSI' and as data source select "datasources[\"Mercury\"][\"user/vish/devices/ble_tags/beacon1\"][\"rssi\"]". Put max value as 0 and min value as -100.
- Create an Indicator light widget with title 'Online'. For value,enter "datasources[\"Mercury\"][\"user/vish/devices/ble_tags/beacon1\"][\"online\"]"
- Like this you can create as many as panes for beacons. Don't forget to change the mqtt topic names to match with your beacon names.
Result will be some thing like below: Here I put monitoring for 4 beacons.
Since your scanner.js script is running, you can see that the status is updated online.
Test
Now we are ready to test. For testing, I modified the arduino sketch to emit four different beacon advertisements from same tag. A video of the test is given below:
In the video, I'm using two raspberry pi's - one serves as my MQTT broker (B+) and Pi3 is where i'm running the freeboard server and tag scanner codes. Both are connected to my Wifi.
Happy Hacking,
vish