thing-it-device-ibeacon
Version:
[thing-it-node] Device Plugin for iBeacon Devices.
469 lines (395 loc) • 16.1 kB
JavaScript
module.exports = {
metadata: {
family: "beaconScanner",
plugin: "beaconScanner",
label: "Beacon Scanner",
manufacturer: "Generic",
tangible: true,
discoverable: false,
dataTypes: {
scannerType: {
family: "enumeration",
values: [{
id: "BUILTIN",
label: "Built In"
}, {
id: "BLUEUP",
label: "BlueUp"
}]
}
},
state: [{
id: 'scannedBeacons',
label: 'Scanned Beacons in Interval',
type: {
id: 'integer'
}
}, {
id: 'ignoreRangingThrottling',
label: 'Ignore Ranging Throttling',
type: {
id: 'boolean'
}
}],
actorTypes: [],
sensorTypes: [],
services: [{
id: "deactivateRangeThrottling",
label: "Deactivate Range Throttling"
}, {
id: "activateRangeThrottling",
label: "Activate Range Throttling"
}],
events: [{}],
configuration: [{
id: "uuidFilter",
label: "UUID Filter",
type: {
id: "string"
}
}, {
id: 'rangeInterval', label: 'Range Interval (s)', type: { id: 'integer' },
defaultValue: 30
}, {
id: 'rssiThreshold', label: 'RSSI Threshold (dB)', type: { id: 'integer' },
defaultValue: 20
}, {
id: 'rssiAbsoluteThreshold', label: 'RSSI Absolute Threshold (dB)', type: { id: 'integer' },
defaultValue: -80
}, {
id: 'excludeFilter', label: 'Exclude Filter', type: { id: 'string' }
}, {
id: 'includeFilter', label: 'Include Filter', type: { id: 'string' }
}, {
id: 'countInterval', label: 'Count Interval (s)', type: { id: 'integer' },
defaultValue: 30
}, {
id: 'cumulationInterval', label: 'Cumulation Interval (s)', type: { id: 'integer' },
defaultValue: 60
}, {
id: 'ip', label: 'IP address', type: { id: 'string' }
}, {
id: 'scannerType', label: 'Scanner Type',
type: {
family: "reference",
id: "scannerType"
},
defaultValue: "BUILTIN"
}, {
id: 'updateFrequencySeconds', label: 'Update Frequency (seconds)', type: { id: 'integer' },
defaultValue: 10
}, {
id: 'publishSingleRangingEvents', label: 'Publish single ranging events',
type: { id: "boolean"},
defaultValue: false
}]
},
create: function () {
return new BeaconScanner();
},
discovery: function (options) {
return null;
}
};
var q = require('q');
var moment = require('moment');
var bleacon;
/**
*
*/
function BeaconScanner() {
/**
*
*/
BeaconScanner.prototype.start = function () {
var deferred = q.defer();
this.logInfo('Start Beacon Scanner, Scanner Type ' + this.configuration.scannerType);
if (!this.configuration.eventInterval) {
this.configuration.eventInterval = 60;
}
if (!this.configuration.rssiThreshold) {
this.configuration.rssiThreshold = 20;
}
if (this.configuration.rssiThreshold < 10) {
this.logInfo('Value of rssiThreshold is too low. Setting to 10.');
this.configuration.rssiThreshold = 10;
}
if (!this.configuration.countInterval) {
this.configuration.countInterval = 60;
}
if (!this.configuration.scannerType) {
this.configuration.scannerType = "BUILTIN";
}
if (!this.configuration.updateFrequencySeconds) {
this.configuration.updateFrequencySeconds = 10;
}
if (!this.configuration.publishSingleRangingEvents) {
this.configuration.publishSingleRangingEvents = false;
}
this.state = {
scannedBeacons: 0, ignoreRangingThrottling: false
};
this.buffer = {};
this.countBuffer = {};
this.cumulationBuffer = {};
if (this.configuration.scannerType == "BUILTIN") {
this.handleBuiltInScanner();
}
else if (this.configuration.scannerType == "BLUEUP") {
this.handleBlueUpScanner();
this.updateInterval = setInterval(function () {
this.handleBlueUpScanner();
}.bind(this), this.configuration.updateFrequencySeconds * 1000);
}
this.countInterval = setInterval(function () {
this.state = { scannedBeacons: Object.keys(this.countBuffer).length };
this.countBuffer = {};
this.publishStateChange();
}.bind(this), this.configuration.countInterval * 1000);
this.cumulationInterval = setInterval(function () {
for (var beaconId in this.cumulationBuffer) {
var buffer = this.cumulationBuffer[beaconId];
var sum = buffer.rssiAverage;
buffer.rssiAverage = sum / buffer.count;
if (buffer.count == 1) {
buffer.rssiDeviation = 0;
} else {
var squareSum = buffer.rssiDeviation;
buffer.rssiDeviation = Math.sqrt(1 / (buffer.count - 1) *
(squareSum - 2 * buffer.rssiAverage * sum + buffer.count * buffer.rssiAverage * buffer.rssiAverage));
}
this.publishEvent('cumulatedBeaconRangingEvent', {
rssiAverage: buffer.rssiAverage,
rssiDeviation: buffer.rssiDeviation,
count: buffer.count,
}, beaconId);
}
this.cumulationBuffer = {};
}.bind(this), this.configuration.cumulationInterval * 1000);
deferred.resolve();
return deferred.promise;
};
/**
*
*/
BeaconScanner.prototype.stop = function () {
if (!this.isSimulated()) {
if (this.countInterval) {
clearInterval(this.countInterval);
}
if (this.cumulationInterval) {
clearInterval(this.cumulationInterval);
}
} else {
if (this.simulationInterval) {
clearInterval(this.simulationInterval);
}
}
};
/**
*
*/
BeaconScanner.prototype.setState = function (state) {
this.state.ignoreRangingThrottling = state.ignoreRangingThrottling;
this.publishStateChange();
};
/**
*
*/
BeaconScanner.prototype.getState = function () {
return this.state;
};
/**
*
*/
BeaconScanner.prototype.deactivateRangeThrottling = function () {
this.state.ignoreRangingThrottling = true;
this.publishStateChange();
};
/**
*
*/
BeaconScanner.prototype.activateRangeThrottling = function () {
this.state.ignoreRangingThrottling = false;
this.publishStateChange();
};
BeaconScanner.prototype.isFilterActive = function (identifier) {
if (this.configuration.uuidFilter) {
var str = this.configuration.uuidFilter.replace(/-/g, '');
try {
if (identifier.toLowerCase().indexOf(str.toLowerCase()) === -1) {
return true;
}
} catch (error) {
// Do nothing
}
}
if (this.configuration.excludeFilter) {
try {
var regExp = new RegExp(this.configuration.excludeFilter);
if (identifier.match(regExp)) {
return true;
}
} catch (error) {
// Do nothing
}
}
if (this.configuration.includeFilter) {
try {
var regExp = new RegExp(this.configuration.includeFilter);
if (!identifier.match(regExp)) {
return true;
}
} catch (error) {
// Do nothing
}
}
}
BeaconScanner.prototype.publishRangingEvent = function (beacon) {
if (this.configuration.publishSingleRangingEvents == true) {
this.publishEvent('beaconRangingEvent', {
measuredPower: beacon.measuredPower,
rssi: beacon.rssi,
accuracy: beacon.accuracy,
proximity: beacon.proximity
}, beacon.uuid + '/' + beacon.major + '/' + beacon.minor);
}
}
BeaconScanner.prototype.handleBuiltInScanner = function () {
if (!this.isSimulated()) {
if (!bleacon) {
bleacon = require('bleacon-fork');
}
bleacon.on('discover', function (beacon) {
this.logDebug("Received beacon", beacon);
var identifier = beacon.uuid + '/' + beacon.major + '/' + beacon.minor;
if (this.isFilterActive(identifier)) {
return;
}
if (!this.countBuffer[identifier]) {
this.countBuffer[identifier] = beacon;
}
if (!this.buffer[identifier] ||
this.state.ignoreRangingThrottling ||
Math.abs(this.buffer[identifier].rssi - beacon.rssi) > this.configuration.rssiThreshold ||
this.buffer[identifier].timestamp.clone().add(this.configuration.eventInterval, 's').isSameOrBefore(moment())) {
if (beacon.rssi > this.configuration.rssiAbsoluteThreshold) {
this.publishRangingEvent(beacon);
this.buffer[identifier] = { rssi: beacon.rssi, timestamp: moment() };
}
}
if (!this.cumulationBuffer[identifier]) {
this.cumulationBuffer[identifier] = {
rssiAverage: beacon.rssi,
rssiDeviation: beacon.rssi * beacon.rssi,
count: 1
};
} else {
this.cumulationBuffer[identifier].rssiAverage += beacon.rssi
this.cumulationBuffer[identifier].rssiDeviation += (beacon.rssi * beacon.rssi);
this.cumulationBuffer[identifier].count += 1;
}
}.bind(this));
this.logDebug('Start scanning');
bleacon.startScanning();
} else {
this.simulateBuiltInScanner();
}
}
BeaconScanner.prototype.simulateBuiltInScanner = function () {
this.simulationInterval = setInterval(function () {
var count = Math.floor(Math.random() * 6) + 1;
this.state = {
scannedBeacons: count
};
this.publishStateChange();
for (var n = 0; n < count; ++n) {
this.publishRangingEvent({
uuid: '74278BDA-B644-4520-8F0C-720EAF059935',
major: 0,
minor: n,
measuredPower: 10,
rssi: -1 * Math.floor(Math.random() * 30) + 50,
accuracy: 1,
proximity: 'near'
});
}
}.bind(this), 20000);
}
BeaconScanner.prototype.handleBlueUpScanner = function () {
if (!this.isSimulated()) {
var request = require('request');
var message = '';
this.logDebug("Request data from BlueUpScanner IP: ", this.configuration.ip);
request.get('http://' + this.configuration.ip + '/api/beacons', function (error, response, body) {
if (error) {
this.logInfo('Error during access to blueup gateway: ' + error)
}
}.bind(this)).on('data', function (data) {
var message = '';
message = data.toString('utf8');
try {
message = JSON.parse(message);
}
catch (err) {
this.logInfo("Received incomplete message from Blueup API");
this.logDebug(message);
}
for (var b in message.beacons) {
if (!message.beacons[b].ibeacon) {
this.logInfo("Detected beacon without ibeacon support");
continue;
}
if (message.beacons[b].ibeacon.length > 1) {
this.logInfo("Multiple ibeacons for one beacon, count: " + message.beacons[b].ibeacon.length);
}
var beacon = message.beacons[b].ibeacon[0];
this.logDebug("Received beacon ", beacon);
var identifier = beacon.uuid + '/' + beacon.major + '/' + beacon.minor;
if (this.isFilterActive(identifier)) {
continue;
}
if (!this.countBuffer[identifier]) {
this.countBuffer[identifier] = beacon;
}
if (!this.buffer[identifier] ||
this.state.ignoreRangingThrottling ||
Math.abs(this.buffer[identifier].rssi - beacon.rssi) > this.configuration.rssiThreshold ||
this.buffer[identifier].timestamp.clone().add(this.configuration.eventInterval, 's').isSameOrBefore(moment())) {
if (beacon.rssi > this.configuration.rssiAbsoluteThreshold) {
var accuracy = Math.pow(12.0, 1.5 * ((beacon.rssi / beacon.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';
}
beacon.proximity = proximity;
beacon.accuracy = accuracy;
this.publishRangingEvent(beacon);
this.buffer[identifier] = { rssi: beacon.rssi, timestamp: moment() };
}
}
if (!this.cumulationBuffer[identifier]) {
this.cumulationBuffer[identifier] = {
rssiAverage: beacon.rssi,
rssiDeviation: beacon.rssi * beacon.rssi,
count: 1
};
} else {
this.cumulationBuffer[identifier].rssiAverage += beacon.rssi
this.cumulationBuffer[identifier].rssiDeviation += (beacon.rssi * beacon.rssi);
this.cumulationBuffer[identifier].count += 1;
}
}
return this;
}.bind(this));
} else {
//simulate BlueUp Scanner
}
}
}