lumen
Version:
Node.js lib for the Tabu Lumen
259 lines (192 loc) • 6.49 kB
JavaScript
var util = require('util');
var NobleDevice = require('noble-device');
var SERVICE_UUID = 'fff0';
var DEVICE_NAME_UUID = '2a00';
var SYSTEM_ID_UUID = '2a23';
var MODEL_NUMBER_UUID = '2a24';
var SERIAL_NUMBER_UUID = '2a25';
var FIRMWARE_REVISION_UUID = '2a26';
var HARDWARE_REVISION_UUID = '2a27';
var SOFTWARE_REVISION_UUID = '2a28';
var MANUFACTURER_NAME_UUID = '2a29';
var REGULATORY_CERTIFICATE_DATA_LIST_UUID = '2a2a';
var PNP_ID_UUID = '2a50';
var BATTERY_LEVEL_UUID = '2a19';
var SERVICE_1_UUID = 'fff1';
var SERVICE_2_UUID = 'fff2';
var SERVICE_3_UUID = 'fff3';
var SERVICE_4_UUID = 'fff4';
var SERVICE_5_UUID = 'fff5';
var SERVICE_KEYADD = [0, 244, 229, 214, 163, 178, 163, 178, 193, 244, 229, 214, 163, 178, 193, 244, 229, 214, 163, 178];
var SERVICE_KEYXOR = [0, 43, 60, 77, 94, 111, 247, 232, 217, 202, 187, 172, 157, 142, 127, 94, 111, 247, 232, 217];
var Lumen = function(peripheral) {
NobleDevice.call(this, peripheral);
var manufacturerData = peripheral.advertisement.manufacturerData;
if (manufacturerData && manufacturerData.length === 8) {
this.id = peripheral.advertisement.manufacturerData.toString('hex').match(/.{1,2}/g).reverse().join(':');
this.address = this.id.substring(0, 17);
}
this._keepAliveTimer = null;
this.on('disconnect', this._onDisconnect.bind(this));
};
Lumen.SCAN_UUIDS = [SERVICE_UUID];
NobleDevice.Util.inherits(Lumen, NobleDevice);
NobleDevice.Util.mixin(Lumen, NobleDevice.BatteryService);
NobleDevice.Util.mixin(Lumen, NobleDevice.DeviceInformationService);
Lumen.prototype._onDisconnect = function() {
if (this._keepAliveTimer) {
clearTimeout(this._keepAliveTimer);
}
};
Lumen.prototype.toString = function() {
return JSON.stringify({
uuid: this.uuid,
id: this.id,
address: this.address
});
};
Lumen.prototype.connectAndSetUp = function(callback) {
NobleDevice.prototype.connectAndSetup.call(this, function(error) {
if (error) {
return callback(error);
}
// send auth codes
this._service1Data = new Buffer('08610766a7680f5a183e5e7a3e3cbeaa8a214b6b', 'hex');
this._writeService1(this._service1Data, function(error) {
if (error) {
return callback(error);
}
this._readService2(function(error, data) {
if (error) {
return callback(error);
}
this._service1Data = new Buffer('07dfd99bfddd545a183e5e7a3e3cbeaa8a214b6b', 'hex');
this._writeService1(this._service1Data, function(error) {
if (error) {
return callback(error);
}
this._readService2(function(error, data) {
if (error) {
return callback(error);
}
this.keepAlive();
callback();
}.bind(this));
}.bind(this));
}.bind(this));
}.bind(this));
}.bind(this));
};
Lumen.prototype._readService1 = function(callback) {
this.readDataCharacteristic(SERVICE_UUID, SERVICE_1_UUID, callback);
};
Lumen.prototype._writeService1 = function(data, callback) {
this.writeDataCharacteristic(SERVICE_UUID, SERVICE_1_UUID, data, function() {
callback();
});
};
Lumen.prototype._readService2 = function(callback) {
this.readDataCharacteristic(SERVICE_UUID, SERVICE_2_UUID, callback);
};
Lumen.prototype.keepAlive = function() {
this._keepAliveTimer = setTimeout(function() {
this.readBatteryLevel(function(error, batteryLevel) {
this.keepAlive();
}.bind(this));
}.bind(this), 5000);
};
Lumen.prototype.turnOff = function(callback) {
this._writeCommand([0x00], callback);
};
Lumen.prototype.coolMode = function(callback) {
this._writeCommand([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04], callback);
};
Lumen.prototype.warmMode = function(callback) {
this._writeCommand([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03], callback);
};
Lumen.prototype.disco1Mode = function(callback) {
this._writeCommand([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02], callback);
};
Lumen.prototype.disco2Mode = function(callback) {
this._writeCommand([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], callback);
};
Lumen.prototype.white = function(percentage, callback) {
this._writeCommand([0x01, 0x00, 0x00, 0x00, this._0to99(percentage)], callback);
};
Lumen.prototype.color = function (r, g, b, callback) {
this._writeCommand([0x01, this._0to99(r), this._0to99(g), this._0to99(b)], callback);
};
Lumen.prototype._0to99 = function(value) {
if (value < 0) {
value = 0;
} else if (value > 99) {
value = 99;
}
return value;
};
Lumen.prototype._writeCommand = function(cmd, callback) {
cmd = new Buffer(cmd);
this._writeService1(this._encryptCommand(cmd), callback);
};
Lumen.prototype._encryptCommand = function(buffer) {
var data = new Buffer(20);
// Set buffer data
for (var i = 0; i < data.length; i++) {
if (i < buffer.length) {
data[i] = buffer[i];
} else {
data[i] = 0;
}
}
// Encrypt data
add(data, SERVICE_KEYADD);
xor(data, SERVICE_KEYXOR);
// Set 'on' bit
data[0] = 0x01 & buffer[0];
return data;
};
Lumen.prototype._decryptResponse = function(response) {
var data = new Buffer(20);
response.copy(data);
// Decrypt data
xor(data, SERVICE_KEYXOR);
subtract(data, SERVICE_KEYADD);
// Set 'on' bit
data[0] = 0x01 & response[0];
return data;
};
/*----------------- Utility functions -----------------*/
function add(array, key) {
var i = 0;
for(var j = array.length - 1; j >= 0; j--) {
var k = i + array[j] + key[j];
if (k >= 256) {
i = k >> 8;
k -= 256;
} else {
i = 0;
}
array[j] = k;
}
}
function subtract(array, key) {
var a1 = new Buffer(array);
var a2 = new Buffer(key);
var c = (a1[0] < a2[0]) ? 256 : 0;
for(var i = 0; i < a1.length - 1; i++) {
var c1 = 0;
if (a1[i + 1] < a2[i + 1]) {
a1[i] = (-1 + a1[i]);
c1 = 256;
}
array[i] = c + a1[i] - a2[i];
c = c1;
}
array[i] = c + a1[i] - a2[i];
}
function xor(array, key) {
for (var i = 0; i < array.length; i++) {
array[i] = array[i] ^ key[i];
}
}
module.exports = Lumen;