bleacon-fork
Version:
A Node.js library for creating, discovering, and configuring iBeacons
160 lines (121 loc) • 4.75 kB
JavaScript
var events = require('events');
var os = require('os');
var util = require('util');
var debug = require('debug')('estimote-sticker');
var noble = require('@abandonware/noble');
if(process.env.NOBLE_REPORT_ALL_HCI_EVENTS === undefined && os.platform() !== 'darwin') {
debug("NOBLE_REPORT_ALL_HCI_EVENTS env variable has to be set to 1");
process.env.NOBLE_REPORT_ALL_HCI_EVENTS = 1;
}
var EstimoteSticker = function() {
noble.on('discover', this.onDiscover.bind(this));
};
var EXPECTED_MANUFACTURER_DATA_LENGTH = 22;
util.inherits(EstimoteSticker, events.EventEmitter);
EstimoteSticker.prototype.startScanning = function() {
debug('startScanning');
function startScanning() {
noble.startScanning([], true);
}
if (noble.state === 'poweredOn') {
startScanning();
} else {
noble.once('stateChange', startScanning);
}
};
EstimoteSticker.prototype.stopScanning = function() {
debug('stopScanning');
noble.stopScanning();
};
EstimoteSticker.prototype.onDiscover = function(peripheral) {
debug('onDiscover: %s', peripheral);
var manufacturerData = peripheral.advertisement.manufacturerData;
var rssi = peripheral.rssi;
debug('onDiscover: manufacturerData = %s, rssi = %d', manufacturerData ? manufacturerData.toString('hex') : null, rssi);
if (manufacturerData &&
EXPECTED_MANUFACTURER_DATA_LENGTH <= manufacturerData.length &&
manufacturerData[0] === 0x5d && manufacturerData[1] === 0x01 && // company id
manufacturerData[2] === 0x01) { // nearable protocol version
debug('onDiscover: ' + peripheral.uuid + ' ' + manufacturerData.toString('hex'));
// id can be looked up: https://cloud.estimote.com/v1/stickers/<id>/info
// response: {"identifier":"<id>","type":"shoe","color":"blueberry","category":"shoe"}
var id = manufacturerData.slice(3, 11).toString('hex');
var major = parseInt(manufacturerData.slice(7, 9).toString('hex'), 16);
var minor = parseInt(manufacturerData.slice(9, 11).toString('hex'), 16);
var uuid = 'd0d3fa86ca7645ec9bd96af4' + manufacturerData.slice(3, 7).toString('hex');
var type = (manufacturerData[11] === 0x4) ? 'SB0' : 'unknown';
var firmware = null;
switch (manufacturerData[12]) {
case -127:
firmware = 'SA1.0.0';
break;
case -126:
firmware = 'SA1.0.1';
break;
default:
firmware = 'unknown';
break;
}
var bootloader = (manufacturerData[12] === 0x1) ? 'SB1.0.0' : 'unknown';
var rawTemperature = (manufacturerData.readUInt16LE(13) & 0x0fff) << 4;
var temperature = null;
if (rawTemperature & 0x8000) {
temperature = ((rawTemperature & 0x7fff) - 32768.0) / 256.0;
} else {
temperature = rawTemperature / 256.0;
}
var moving = (manufacturerData.readUInt8(15) & 0x40) !== 0;
var batteryVoltage = 0;
if ((manufacturerData.readUInt8(15) & 0x80) === 0) {
var rawBatteryLevel = (manufacturerData.readUInt8(15) << 8) + ((manufacturerData.readUInt8(14) >> 4) & 0x3ff);
batteryVoltage = 3.6 * rawBatteryLevel / 1023.0;
}
var batteryLevel = 'unknown';
if (batteryVoltage >= 2.95) {
batteryLevel = 'high';
} else if (batteryVoltage < 2.95 && batteryVoltage >= 2.7) {
batteryLevel = 'medium';
} else if (batteryVoltage > 0.0) {
batteryLevel = 'low';
}
var acceleration = {
x: manufacturerData.readInt8(16) * 15.625,
y: manufacturerData.readInt8(17) * 15.625,
z: manufacturerData.readInt8(18) * 15.625
};
var currentMotionStateDuration = convertMotionStateDuration(manufacturerData.readUInt8(19));
var previousMotionStateDuration = convertMotionStateDuration(manufacturerData.readUInt8(20));
var power = [1, 2, 3, 7, 5, 6, 4, 8][manufacturerData.readUInt8(21) & 0x0f] || 'unknown';
var firmwareState = (manufacturerData.readUInt8(21) & 0x40) ? 'app' : 'bootloader';
var sticker = {
id: id,
uuid: uuid,
major: major,
minor: minor,
type: type,
firmware: firmware,
bootloader: bootloader,
temperature: temperature,
moving: moving,
batteryLevel: batteryLevel,
acceleration: acceleration,
currentMotionStateDuration: currentMotionStateDuration,
previousMotionStateDuration: previousMotionStateDuration,
power: power,
firmwareState: firmwareState,
rssi: rssi
};
this.emit('discover', sticker);
}
};
function convertMotionStateDuration(raw) {
var unit = (raw >> 6) & 0x03;
var duration = (raw & 0x3f);
if (unit === 1) {
duration *= 60;
} else if (unit === 2) {
duration *= (60 * 60);
}
return duration;
}
module.exports = new EstimoteSticker();