UNPKG

thing-it-device-ibeacon

Version:
469 lines (395 loc) 16.1 kB
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 } } }