Commit 168ba20c authored by Donald Chen's avatar Donald Chen
Browse files

add scanning functionality

parent a834ea5f
<!--
Copyright (c) 2014. Knowledge Media Institute - The Open University
Licensed 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
http://www.apache.org/licenses/LICENSE-2.0
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.
-->
<!--
NodeRed node with support for BLE interaction
@author <a href="mailto:carlos.pedrinaci@open.ac.uk">Carlos Pedrinaci</a> (KMi - The Open University)
-->
<script type="text/x-red" data-template-name="scan ble">
<!-- data-template-name identifies the node type this is for -->
<!-- Each of the following divs creates a field in the edit dialog. -->
<!-- Generally, there should be an input for each property of the node. -->
<!-- The for and id attributes identify the corresponding property -->
<!-- (with the 'node-input-' prefix). -->
<!-- The available icon classes are defined Twitter Bootstrap glyphicons -->
<!-- Limit to list of UUIDs -->
<div class="form-row">
<label for="node-input-uuids"><i class="fa fa-tasks"></i> Restrict by Service UUID</label>
<input type="text" id="node-input-uuids" placeholder="Comma separated list">
</div>
<div class="form-tips">Leave empty for scanning all devices offering any service.</div>
<!-- Allow duplicates -->
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-duplicates" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-duplicates" style="width: 70%;">Allow duplicates?</label>
</div>
<!-- By convention, most nodes have a 'name' property. The following div -->
<!-- provides the necessary field. Should always be the last option -->
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<!-- Next, some simple help text is provided for the node. -->
<script type="text/x-red" data-help-name="scan ble">
<p>This node allows scanning for BLE devices, restricted if necessary by the services they offer.</p>
<p>This node will generate one message per BLE device detected according to the node configuration.</p>
<p>The node will also generate events according to the status of the bluetooth interface when it changes. Those status messages include:</p>
<ul>
<li>Whether there was an <b>msg.error</b></li>
<li>Whether there was a <b>msg.stateChange</b></li>
<li>The current <b>msg.state</b> of the interface</li>
<li>The <b>msg.payload</b> will be empty in this case to prevent mistreating status messages as BLE detection messages.</li>
</ul>
<p>The node will begin scanning after it is deployed, and will not stop until it is deployed again. It can be useful to turn on and off scanning to conserve energy and force scan requests to be sent triggering devices to respond. To control scanning, you may send a message containing <code>msg.payload.scan=false</code> to turn off scanning, and <code>msg.payload.scan=true</code> to turn it on.</p>
<p>The output will contain in <b>msg.payload</b> the <i>peripheral uuid</i> and the <i>local name</i> of the BLE device detected.</p>
<p>Additionally the node will set the following values:</p>
<ul>
<li>Sets the <b>msg.peripheralUuid</b> to the <i>peripheral uuid</i> of the BLE device detected.</li>
<li>Sets the <b>msg.rssi</b> to the <i>received signal strength indicator (RSSI)</i> of the BLE device detected.</li>
<li>Sets <b>msg.localName</b> to the name of the device if any.</li>
<li>Sets <b>msg.detectedAt</b> to the moment the device was detected on milliseconds since 1 Jan 1970.</li>
<li>Sets <b>msg.detectedBy</b> to the name of the machine that detected the BLE device.</li>
<li>Sets <b>msg.advertisement</b> to the full advertisement of the BLE device (see Noble documentation).</li>
</ul>
<p>If the device follows the iBeacon specification, the message will also contain the following properties</p>
<ul>
<li>Sets the <b>msg.manufacturerUuid</b> to the <i>uuid</i> of the manufacturer of the BLE device detected.</li>
<li>Sets the <b>msg.major</b> to the <i>major number</i> of the manufacturer of the BLE device detected.</li>
<li>Sets the <b>msg.minor</b> to the <i>minor number</i> of the manufacturer of the BLE device detected.</li>
<li>Sets the <b>msg.measuredPower</b> to the <i>measured power at one meter</i> of the BLE device detected.</li>
<li>Sets the <b>msg.accuracy</b> to the <i>accuracy</i> estimated for this measurement.</li>
<li>Sets the <b>msg.proximity</b> to the <i>proximity</i> guessed for this device. The value will be one of: 'unknown', 'immediate', 'near', 'far' </li>
</ul>
</script>
<!-- Finally, the node type is registered along with all of its properties -->
<!-- The example below shows a small subset of the properties that can be set-->
<script type="text/javascript">
RED.nodes.registerType('scan ble',{
category: 'advanced', // the palette category
color:"#C0DEED",
defaults: { // defines the editable properties of the node
uuids: {value:"", validate:RED.validators.regex(/^([a-fA-F0-9]{32}){0,1}(?:,[a-fA-F0-9]{32})*$/)},
duplicates: {value:false},
name: {value:""}
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n
// set the icon (held in icons dir below where you save the node)
icon: "bluetooth.png", // saved in icons/myicon.png
label: function() { // sets the default label contents
if (this.name) {
return this.name;
} else {
return "Scan BLEs";
}
},
labelStyle: function() { // sets the class to apply to the label
return this.name ? "node_label_italic" : "";
}
});
</script>
/*
* Copyright (c) 2014. Knowledge Media Institute - The Open University
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
/**
* NodeRed node with support for interaction with BLEs
*
* @author <a href="mailto:carlos.pedrinaci@open.ac.uk">Carlos Pedrinaci</a> (KMi - The Open University)
* based on the initial node by Charalampos Doukas http://blog.buildinginternetofthings.com/2013/10/12/using-node-red-to-scan-for-ble-devices/
*/
module.exports = function(RED) {
"use strict";
var noble = require('noble');
var os = require('os');
// The main node definition - most things happen in here
function NobleScan(n) {
// Create a RED node
RED.nodes.createNode(this,n);
// Store local copies of the node configuration (as defined in the .html)
this.duplicates = n.duplicates;
this.uuids = [];
if (n.uuids != undefined && n.uuids !== "") {
this.uuids = n.uuids.split(','); //obtain array of uuids
}
var node = this;
var machineId = os.hostname();
var scanning = false;
noble.on('discover', function(peripheral) {
var msg = { payload:{peripheralUuid:peripheral.uuid, localName: peripheral.advertisement.localName} };
msg.peripheralUuid = peripheral.uuid;
msg.localName = peripheral.advertisement.localName;
msg.detectedAt = new Date().getTime();
msg.detectedBy = machineId;
msg.advertisement = peripheral.advertisement;
msg.rssi = peripheral.rssi;
// Check the BLE follows iBeacon spec
if (peripheral.manufacturerData) {
// http://www.theregister.co.uk/2013/11/29/feature_diy_apple_ibeacons/
if (peripheral.manufacturerData.length >= 25) {
var proxUuid = peripheral.manufacturerData.slice(4, 20).toString('hex');
var major = peripheral.manufacturerData.readUInt16BE(20);
var minor = peripheral.manufacturerData.readUInt16BE(22);
var measuredPower = peripheral.manufacturerData.readInt8(24);
var accuracy = Math.pow(12.0, 1.5 * ((rssi / measuredPower) - 1));
var proximity = null;
if (accuracy < 0) {
proximity = 'unknown';
} else if (accuracy < 0.5) {
proximity = 'immediate';
} else if (accuracy < 4.0) {
proximity = 'near';
} else {
proximity = 'far';
}
msg.manufacturerUuid = proxUuid;
msg.major = major;
msg.minor = minor;
msg.measuredPower = measuredPower;
msg.accuracy = accuracy;
msg.proximity = proximity;
}
}
// Generate output event
node.send(msg);
});
// Take care of starting the scan and sending the status message
function startScan(stateChange, error) {
if (!node.scanning) {
// send status message
var msg = {
statusUpdate: true,
error: error,
stateChange: stateChange,
state: noble.state
};
node.send(msg);
// start the scan
noble.startScanning(node.uuids, node.duplicates, function() {
node.log("Scanning for BLEs started. UUIDs: " + node.uuids + " - Duplicates allowed: " + node.duplicates);
node.status({fill:"green",shape:"dot",text:"started"});
node.scanning = true;
});
}
}
// Take care of stopping the scan and sending the status message
function stopScan(stateChange, error) {
if (node.scanning) {
// send status message
var msg = {
statusUpdate: true,
error: error,
stateChange: stateChange,
state: noble.state
};
node.send(msg);
// stop the scan
noble.stopScanning(function() {
node.log('BLE scanning stopped.');
node.status({fill:"red",shape:"ring",text:"stopped"});
node.scanning = false;
});
if (error) {
node.warn('BLE scanning stopped due to change in adapter state.');
}
}
}
// deal with state changes
noble.on('stateChange', function(state) {
if (state === 'poweredOn') {
startScan(true, false);
} else {
if (node.scanning) {
stopScan(true, true);
}
}
});
// start initially
if (noble.state === 'poweredOn') {
startScan(false, false);
} else {
// send status message
var msg = {
statusUpdate: true,
error: true,
stateChange: false,
state: noble.state
};
// TODO: Catch a global event instead eventually
setTimeout(function(){
node.send(msg);
}, 3000);
node.warn('Unable to start BLE scan. Adapter state: ' + noble.state);
}
// control scanning
node.on('input', function (msg) {
if (msg.hasOwnProperty("payload") && typeof msg.payload == "object" && msg.payload.hasOwnProperty("scan")) {
if (msg.payload.scan === true) {
startScan(false, false);
return;
} else if (msg.payload.scan === false) {
stopScan(false, false);
return;
}
}
node.warn("Incorrect input, ignoring. See the documentation in the info tab. ");
});
node.on("close", function() {
// Called when the node is shutdown - eg on redeploy.
// Allows ports to be closed, connections dropped etc.
// eg: this.client.disconnect();
stopScan(false, false);
// remove listeners since they get added again on deploy
noble.removeAllListeners();
});
}
// Register the node by name. This must be called before overriding any of the
// Node functions.
RED.nodes.registerType("scan ble",NobleScan);
}
......@@ -8,7 +8,8 @@
},
"node-red": {
"nodes": {
"noble-connect": "noble-connect/node-red-noble-connect.js"
"noble-connect": "noble-connect/node-red-noble-connect.js",
"noble": "noble-connect/node-red-contrib-noble.js"
}
},
"keywords": [],
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment