From c3ff5275302dccd4d78e282b1c8a02f7ac164a40 Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Fri, 13 Sep 2019 22:55:59 +0200 Subject: [PATCH 1/9] added the TileApi to control real colorful lifx tiles --- example/tile.js | 60 ++++++++++ src/lifx/light.js | 158 ++++++++++++++++++++++++++ src/lifx/packet.js | 8 +- src/lifx/packets/getDeviceChain.js | 7 ++ src/lifx/packets/getTileState64.js | 44 +++++++ src/lifx/packets/index.js | 7 ++ src/lifx/packets/setTileState64.js | 74 ++++++++++++ src/lifx/packets/stateDeviceChain.js | 76 +++++++++++++ src/lifx/packets/stateTileState64.js | 51 +++++++++ test/unit/packets/getDeviceChain.js | 14 +++ test/unit/packets/getTileState64.js | 27 +++++ test/unit/packets/setTileState64.js | 102 +++++++++++++++++ test/unit/packets/stateDeviceChain.js | 95 ++++++++++++++++ test/unit/packets/stateTileState64.js | 52 +++++++++ 14 files changed, 774 insertions(+), 1 deletion(-) create mode 100644 example/tile.js create mode 100644 src/lifx/packets/getDeviceChain.js create mode 100644 src/lifx/packets/getTileState64.js create mode 100644 src/lifx/packets/setTileState64.js create mode 100644 src/lifx/packets/stateDeviceChain.js create mode 100644 src/lifx/packets/stateTileState64.js create mode 100644 test/unit/packets/getDeviceChain.js create mode 100644 test/unit/packets/getTileState64.js create mode 100644 test/unit/packets/setTileState64.js create mode 100644 test/unit/packets/stateDeviceChain.js create mode 100644 test/unit/packets/stateTileState64.js diff --git a/example/tile.js b/example/tile.js new file mode 100644 index 0000000..5da1d0e --- /dev/null +++ b/example/tile.js @@ -0,0 +1,60 @@ +'use strict'; + +const LifxClient = require('../lib/lifx').Client; + +const client = new LifxClient(); + +const LOOPTIME = 10000; + +function getBits(light, idx, chain) { + if (idx >= chain.total_count) { + console.log('All Bits get'); + setTimeout(() => setBits(light, 0, chain), LOOPTIME); + return; + } + light.getTileState64(idx, 64, 0, 0, 8, (err) => { + if (err) { + console.error('getTileState64:', err); + return; + } + getBits(light, idx + 1, chain); + }); +} + +function setBits(light, idx, chain) { + if (idx >= chain.total_count) { + setTimeout(() => getBits(light, 0, chain), LOOPTIME); + return; + } + const ofs = ~~(Math.random() * (65536 - (65536 / 64))); + light.setTileState64(idx, 64, 0, 0, 8, 100, + (new Array(64)).fill(undefined).map((_, idx) => ({ + hue: (ofs + (idx * (65536 / 64))) & 0xffff, + saturation: 50000, + brightness: 16384, + kelvin: 4096 + })), () => setBits(light, idx + 1, chain)); +} + +client.on('light-new', (light) => { + console.log('New light found.'); + console.log('ID: ' + light.id); + + light.getDeviceChain(function(err, chain) { + if (err) { + console.log(err); + } + setBits(light, 0, chain); + }); +}); + +// Give feedback when running +client.on('listening', function() { + const address = client.address(); + console.log( + 'Started LIFX listening on ' + + address.address + ':' + address.port + '\n' + ); +}); + +client.init(); diff --git a/src/lifx/light.js b/src/lifx/light.js index aa63cfd..df83b09 100644 --- a/src/lifx/light.js +++ b/src/lifx/light.js @@ -529,4 +529,162 @@ Light.prototype.colorZones = function(startIndex, endIndex, hue, saturation, bri this.client.send(packetObj, callback); }; +/** + * Requests tile getDeviceChain 701 + * @param {Function} callback a function to accept the data + */ +Light.prototype.getDeviceChain = function(callback) { + validate.callback(callback, 'light getDeviceChain method'); + + const packetObj = packet.create('getDeviceChain', {}, this.client.source); + packetObj.target = this.id; + const sqnNumber = this.client.send(packetObj); + this.client.addMessageHandler('stateDeviceChain', function(err, msg) { + if (err) { + return callback(err, null); + } + return callback(null, msg); + }, sqnNumber); +}; + +/** + * Sets Tile Position + * @example light.setUserPosition(0, 12, 13, 0, () => {}) + * @param {Number} tileIndex unsigned 8-bit integer + * @param {Number} userX 32-bit float + * @param {Number} userY 32-bit float + * @param {Number} reserved 16-bit integer + * @param {Function} callback called when light did receive message + */ +Light.prototype.setUserPosition = function(tileIndex, userX, userY, reserved, callback) { + if (tileIndex === undefined || typeof tileIndex !== 'number') { + throw new TypeError('LIFX light setUserPosition tileIndex to be a number'); + } + if (userX === undefined || typeof userX !== 'number') { + throw new TypeError('LIFX light setUserPosition userX to be a number'); + } + if (userY === undefined || typeof userY !== 'number') { + throw new TypeError('LIFX light setUserPosition userY to be a number'); + } + validate.optionalCallback(callback, 'light setUserPosition method'); + + const packetObj = packet.create('setUserPositiion', { + tileIndex, + userX, + userY, + reserved: reserved || 0 + }, this.client.source); + packetObj.target = this.id; + this.client.send(packetObj, callback); +}; + +/** + * Requests tile GetTileState64 707 + * + * Get the state of 64 pixels in the tile in a rectangle that has + * a starting point and width. + * The tileIndex is used to control the starting tile in the chain + * and length is used to get the state of that many tiles beginning + * from the tileIndex. This will result in a separate response from + * each tile. + * For the LIFX Tile it really only makes sense to set x and y to + * zero, and width to 8. + * @param {Number} tileIndex unsigned 8-bit integer + * @param {Number} length unsigned 8-bit integer + * @param {Number} x unsigned 8-bit integer + * @param {Number} y unsigned 8-bit integer + * @param {Number} width unsigned 8-bit integer + * @param {Number} reserved unsigned 8-bit integer + * @param {Function} callback a function to accept the data + */ +Light.prototype.getTileState64 = function(tileIndex, length, x, y, width, reserved, callback) { + if (tileIndex === undefined || typeof tileIndex !== 'number') { + throw new TypeError('LIFX light getTileState64 tileIndex to be a number'); + } + if (length === undefined || typeof length !== 'number') { + throw new TypeError('LIFX light getTileState64 length to be a number'); + } + if (x === undefined || typeof x !== 'number') { + throw new TypeError('LIFX light getTileState64 x to be a number'); + } + if (y === undefined || typeof y !== 'number') { + throw new TypeError('LIFX light getTileState64 y to be a number'); + } + if (width === undefined || typeof width !== 'number') { + throw new TypeError('LIFX light getTileState64 width to be a number'); + } + validate.callback(callback, 'light getTileState64 method'); + + const packetObj = packet.create('getTileState64', { + tileIndex, + length, + reserved: reserved || 0, + x, + y, + width + }, + this.client.source + ); + packetObj.target = this.id; + const sqnNumber = this.client.send(packetObj); + this.client.addMessageHandler('stateTileState64', function(err, msg) { + if (err) { + return callback(err, null); + } + return callback(null, msg); + }, sqnNumber); +}; + +/** + * This lets you set 64 pixels from a starting x and y for + * a rectangle with the specified width. + * For the LIFX Tile it really only makes sense to set x + * and y to zero, and width to 8. + * @param {Number} tileIndex unsigned 8-bit integer + * @param {Number} length unsigned 8-bit integer + * @param {Number} x unsigned 8-bit integer + * @param {Number} y unsigned 8-bit integer + * @param {Number} width unsigned 8-bit integer + * @param {Number} duration unsigned 32-bit integer + * @param {Number} colors[64] 64 HSBK values + * @param {Number} reserved unsigned 8-bit integer + * @param {Function} [callback] called when light did receive message + */ +Light.prototype.setTileState64 = function(tileIndex, length, x, y, width, duration, colors, reserved, callback) { + if (tileIndex === undefined || typeof tileIndex !== 'number') { + throw new TypeError('LIFX light setTileState64 tileIndex to be a number'); + } + if (length === undefined || typeof length !== 'number') { + throw new TypeError('LIFX light setTileState64 length to be a number'); + } + if (x === undefined || typeof x !== 'number') { + throw new TypeError('LIFX light setTileState64 x to be a number'); + } + if (y === undefined || typeof y !== 'number') { + throw new TypeError('LIFX light setTileState64 y to be a number'); + } + if (width === undefined || typeof width !== 'number') { + throw new TypeError('LIFX light setTileState64 width to be a number'); + } + if (duration === undefined || typeof duration !== 'number') { + throw new TypeError('LIFX light setTileState64 duration to be a number'); + } + const set64colors = utils.buildColorsHsbk(colors, 64); + + validate.optionalCallback(callback, 'light setTileState64 method'); + + const packetObj = packet.create('setTileState64', { + tileIndex, + length, + reserved: reserved || 0, + x, + y, + width, + duration, + colors: set64colors + }, this.client.source); + packetObj.target = this.id; + this.client.send(packetObj, callback); +}; + exports.Light = Light; diff --git a/src/lifx/packet.js b/src/lifx/packet.js index 090ef05..2ca80c9 100644 --- a/src/lifx/packet.js +++ b/src/lifx/packet.js @@ -85,10 +85,16 @@ Packet.typeList = [ {id: 503, name: 'stateZone'}, {id: 504, name: 'getCountZone'}, {id: 505, name: 'stateCountZone'}, - {id: 506, name: 'stateMultiZone'} + {id: 506, name: 'stateMultiZone'}, // {id: 507, name: 'getEffectZone'}, // {id: 508, name: 'setEffectZone'}, // {id: 509, name: 'stateEffectZone'} + {id: 701, name: 'getDeviceChain'}, + {id: 702, name: 'stateDeviceChain'}, + {id: 706, name: 'setUserPosition'}, + {id: 707, name: 'getTileState64'}, + {id: 711, name: 'stateTileState64'}, + {id: 715, name: 'setTileState64'} ]; /** diff --git a/src/lifx/packets/getDeviceChain.js b/src/lifx/packets/getDeviceChain.js new file mode 100644 index 0000000..c1d22b2 --- /dev/null +++ b/src/lifx/packets/getDeviceChain.js @@ -0,0 +1,7 @@ +'use strict'; + +const Packet = { + size: 0 +}; + +module.exports = Packet; diff --git a/src/lifx/packets/getTileState64.js b/src/lifx/packets/getTileState64.js new file mode 100644 index 0000000..abb1189 --- /dev/null +++ b/src/lifx/packets/getTileState64.js @@ -0,0 +1,44 @@ +'use strict'; + +const {validate} = require('../../lifx'); + +const Packet = { + size: 6 +}; + +/** + * Converts the given packet specific object into a packet + * @param {Object} obj object with configuration data + * @param {Number} obj.tileIndex an 8bit value + * @param {Number} obj.length an 8bit value + * @param {Number} obj.reserved an 8bit value + * @param {Number} obj.x an 8bit value + * @param {Number} obj.y an 8bit value + * @param {Number} obj.width an 8bit value + * @return {Buffer} packet + */ +Packet.toBuffer = function(obj) { + const buf = Buffer.alloc(this.size); + buf.fill(0); + let offset = 0; + + ['tileIndex', 'length', 'reserved', 'x', 'y', 'width'].forEach((field) => { + validate.isUInt8(obj[field], `getTileState64:${field}`); + }); + buf.writeUInt8(obj.tileIndex, offset); + offset += 1; + buf.writeUInt8(obj.length, offset); + offset += 1; + buf.writeUInt8(obj.reserved, offset); + offset += 1; + buf.writeUInt8(obj.x, offset); + offset += 1; + buf.writeUInt8(obj.y, offset); + offset += 1; + buf.writeUInt8(obj.width, offset); + offset += 1; + + return buf; +}; + +module.exports = Packet; diff --git a/src/lifx/packets/index.js b/src/lifx/packets/index.js index 685f378..896d1e1 100644 --- a/src/lifx/packets/index.js +++ b/src/lifx/packets/index.js @@ -79,3 +79,10 @@ packets.setColorZones = require('./setColorZones'); packets.stateZone = require('./stateZone'); packets.stateMultiZone = require('./stateMultiZone'); + +packets.getDeviceChain = require('./getDeviceChain'); +packets.stateDeviceChain = require('./stateDeviceChain'); + +packets.setTileState64 = require('./setTileState64'); +packets.getTileState64 = require('./getTileState64'); +packets.stateTileState64 = require('./stateTileState64'); diff --git a/src/lifx/packets/setTileState64.js b/src/lifx/packets/setTileState64.js new file mode 100644 index 0000000..5c266a4 --- /dev/null +++ b/src/lifx/packets/setTileState64.js @@ -0,0 +1,74 @@ +'use strict'; + +const {validate} = require('../../lifx'); + +const Packet = { + size: (obj) => 10 + (obj.colors.length * (8)), + HSBK: { + toBuffer: (obj, buf, offset) => { + validate.isUInt16(obj.hue, 'setTileState64:HSBK:hue'); + buf.writeUInt16LE(obj.hue, offset); + offset += 2; + + validate.isUInt16(obj.saturation, 'setTileState64:HSBK:saturation'); + buf.writeUInt16LE(obj.saturation, offset); + offset += 2; + + validate.isUInt16(obj.brightness, 'setTileState64:HSBK:brightness'); + buf.writeUInt16LE(obj.brightness, offset); + offset += 2; + + validate.isUInt16(obj.kelvin, 'setTileState64:HSBK:kelvin'); + buf.writeUInt16LE(obj.kelvin, offset); + offset += 2; + + return offset; + } + } +}; + +/** + * Converts the given packet specific object into a packet + * @param {Object} obj object with configuration data + * @param {Number} obj.tileIndex 8bit value + * @param {Number} obj.length 8bit value + * @param {Number} obj.reserved 8bit value + * @param {Number} obj.x 8bit value + * @param {Number} obj.y 8bit value + * @param {Number} obj.width 8bit value + * @param {Number} obj.duration 8bit value + * @param {Number} [obj.duration] transition time in milliseconds + * @param {Array} obj.colors an array of HSBK values + * @return {Buffer} packet + */ +Packet.toBuffer = function(obj) { + const buf = Buffer.alloc(Packet.size(obj)); + buf.fill(0); + let offset = 0; + + ['tileIndex', 'length', 'reserved', 'x', 'y', 'width'].forEach((field) => { + validate.isUInt8(obj[field], `setTileState64:${field}`); + }); + // obj.stream field has unknown function so leave it as 0 + buf.writeUInt8(obj.tileIndex, offset); + offset += 1; + buf.writeUInt8(obj.length, offset); + offset += 1; + buf.writeUInt8(obj.reserved || 0, offset); + offset += 1; + buf.writeUInt8(obj.x, offset); + offset += 1; + buf.writeUInt8(obj.y, offset); + offset += 1; + buf.writeUInt8(obj.width, offset); + offset += 1; + validate.isUInt32(obj.duration, 'setTileState64:duration'); + buf.writeUInt32LE(obj.duration, offset); + offset += 4; + obj.colors.forEach((color) => { + offset = Packet.HSBK.toBuffer(color, buf, offset); + }); + return buf; +}; + +module.exports = Packet; diff --git a/src/lifx/packets/stateDeviceChain.js b/src/lifx/packets/stateDeviceChain.js new file mode 100644 index 0000000..192a331 --- /dev/null +++ b/src/lifx/packets/stateDeviceChain.js @@ -0,0 +1,76 @@ +'use strict'; + +const Packet = { + size: 882, + Tile: { + toObject: function(buf, offset) { + const tile = {}; + tile.accelMeasX = buf.readUInt16LE(offset); + offset += 2; + tile.accelMeasY = buf.readUInt16LE(offset); + offset += 2; + tile.accelMeasZ = buf.readUInt16LE(offset); + offset += 2; + tile.reserved0 = buf.readUInt16LE(offset); + offset += 2; + tile.userX = buf.readUInt32LE(offset); + offset += 4; + tile.userY = buf.readUInt32LE(offset); + offset += 4; + tile.width = buf.readUInt8(offset); + offset += 1; + tile.height = buf.readUInt8(offset); + offset += 1; + tile.reserved1 = buf.readUInt8(offset); + offset += 1; + tile.deviceVersionVendor = buf.readUInt32LE(offset); + offset += 4; + tile.deviceVersionProduct = buf.readUInt32LE(offset); + offset += 4; + tile.deviceVersionVersion = buf.readUInt32LE(offset); + offset += 4; + tile.firmwareBuild = { + low: buf.readUInt32LE(offset), + high: buf.readUInt32LE(offset + 4) + }; + offset += 8; + tile.reserved2 = { + low: buf.readUInt32LE(offset), + high: buf.readUInt32LE(offset + 4) + }; + offset += 8; + tile.firmwareVersionMinor = buf.readUInt16LE(offset); + offset += 2; + tile.firmwareVersionMajor = buf.readUInt16LE(offset); + offset += 2; + tile.reserved3 = buf.readUInt32LE(offset); + offset += 4; + return {offset, tile}; + } + } +}; + +/** + * Converts packet specific data from a buffer to an object + * @param {Buffer} buf Buffer containing only packet specific data no header + * @return {Object} Information contained in packet + */ +Packet.toObject = function(buf) { + if (buf.length !== this.size) { + throw new Error(`Invalid length given for stateDeviceChain LIFX packet:${buf.length}:${this.size}`); + } + let offset = 0; + const obj = {}; + obj.startIndex = buf.readUInt8(offset); + offset += 1; + obj.tileDevices = new Array(16).fill(undefined).map(() => { + const ret = Packet.Tile.toObject(buf, offset); + offset = ret.offset; + return ret.tile; + }); + obj.totalCount = buf.readUInt8(offset); + offset += 1; + return obj; +}; + +module.exports = Packet; diff --git a/src/lifx/packets/stateTileState64.js b/src/lifx/packets/stateTileState64.js new file mode 100644 index 0000000..a08b025 --- /dev/null +++ b/src/lifx/packets/stateTileState64.js @@ -0,0 +1,51 @@ +'use strict'; + +const Packet = { + size: 517, + HSBK: { + toObject: function(buf, offset) { + const hsbk = {}; + hsbk.hue = buf.readUInt16LE(offset); + offset += 2; + hsbk.saturation = buf.readUInt16LE(offset); + offset += 2; + hsbk.brightness = buf.readUInt16LE(offset); + offset += 2; + hsbk.kelvin = buf.readUInt16LE(offset); + offset += 2; + return {offset, hsbk}; + } + } +}; + +/** + * Converts packet specific data from a buffer to an object + * @param {Buffer} buf Buffer containing only packet specific data no header + * @return {Object} Information contained in packet + */ +Packet.toObject = function(buf) { + if (buf.length !== this.size) { + throw new Error(`Invalid length given for stateTileState64 LIFX packet:${buf.length}:${this.size}`); + } + let offset = 0; + const obj = {}; + obj.tileIndex = buf.readUInt8(offset); + offset += 1; + obj.reserved = buf.readUInt8(offset); + offset += 1; + obj.x = buf.readUInt8(offset); + offset += 1; + obj.y = buf.readUInt8(offset); + offset += 1; + obj.width = buf.readUInt8(offset); + offset += 1; + obj.colors = new Array(64).fill(undefined).map(() => { + const ret = Packet.HSBK.toObject(buf, offset); + offset = ret.offset; + return ret.hsbk; + }); + return obj; +}; + +module.exports = Packet; + diff --git a/test/unit/packets/getDeviceChain.js b/test/unit/packets/getDeviceChain.js new file mode 100644 index 0000000..56bc302 --- /dev/null +++ b/test/unit/packets/getDeviceChain.js @@ -0,0 +1,14 @@ +'use strict'; + +const Packet = require('../../../lib/lifx').packet; +const assert = require('chai').assert; + +describe('Packet getDeviceChain', () => { + describe('create', () => { + it('getDeviceChain', () => { + const packet = Packet.create('getDeviceChain'); + assert.equal(packet.size, 36); + assert.equal(packet.type, 701); + }); + }); +}); diff --git a/test/unit/packets/getTileState64.js b/test/unit/packets/getTileState64.js new file mode 100644 index 0000000..924fdb1 --- /dev/null +++ b/test/unit/packets/getTileState64.js @@ -0,0 +1,27 @@ +'use strict'; + +const Packet = require('../../../lib/lifx').packet; +const assert = require('chai').assert; + +describe('Packet getTileState64', () => { + describe('create', () => { + it('getTileState64', () => { + const packet = Packet.create('getTileState64', { + tileIndex: 1, + length: 2, + reserved: 3, + x: 4, + y: 5, + width: 6 + }); + assert.equal(packet.size, 42); + assert.equal(packet.type, 707); + assert.equal(packet.tileIndex, 1); + assert.equal(packet.length, 2); + assert.equal(packet.reserved, 3); + assert.equal(packet.x, 4); + assert.equal(packet.y, 5); + assert.equal(packet.width, 6); + }); + }); +}); diff --git a/test/unit/packets/setTileState64.js b/test/unit/packets/setTileState64.js new file mode 100644 index 0000000..a4002de --- /dev/null +++ b/test/unit/packets/setTileState64.js @@ -0,0 +1,102 @@ +'use strict'; + +const packet = require('../../../lib/lifx').packet; +// const Client = require('../../../lib/lifx').Client; +// const Light = require('../../../lib/lifx').Light; +// const constants = require('../../../lib/lifx').constants; +const assert = require('chai').assert; + +/* +function dummy() { +let client; + const packetSendCallback = (msg) => { + console.log('XXXXXXXXXX', msg); + done(); + }; +function getMsgQueueLength(queueAddress) { + return client.getMessageQueue(queueAddress).length; +} +const lightProps = { + client: client, + id: 'f37a4311b857', + address: '192.168.0.1', + port: constants.LIFX_DEFAULT_PORT, + seenOnDiscovery: 0 +}; +beforeEach(() => { + client = new Client(); + client.devices.f37a4311b857 = new Light(lightProps); +}); +console.log(Object.keys(packetObj)); + // const packetObj = packet.create('setPower', {level: 65535}, client.source); + const queueAddress = client.broadcastAddress; + +client.init({ + port: constants.LIFX_DEFAULT_PORT, + startDiscovery: false +}, () => { + client.socket.on('message', packetSendCallback); + let currMsgQueCnt = getMsgQueueLength(queueAddress); + client.send(packetObj); + assert.equal(getMsgQueueLength(queueAddress), currMsgQueCnt + 1, 'sends a packet to the queue'); + currMsgQueCnt += 1; + assert.isDefined(client.sendTimers[queueAddress]); + client.stopSendingProcess(); // We don't want automatic calling of sending + const sendingProcess = client.sendingProcess(queueAddress); + sendingProcess(); // Call sending it manualy + assert.equal(getMsgQueueLength(queueAddress), currMsgQueCnt - 1, 'removes the packet when send'); + currMsgQueCnt -= 1; +}); +} +*/ + +describe('Packet setTileState64', () => { + describe('toBuffer', () => { + it('setTileState64', () => { + const obj = { + type: 'setTileState64', + tileIndex: 1, + length: 2, + reserved: 3, + x: 4, + y: 5, + width: 6, + duration: 7, + colors: [ + { + saturation: 0x8000, + brightness: 0x8001, + kelvin: 3500, + hue: 49 + }, + { + saturation: 0x6000, + brightness: 0x6001, + kelvin: 4500, + hue: 59 + } + ] + }; + const buf = packet.toBuffer(obj); + assert.equal(buf.length, 62); + assert.equal(buf.readUInt32LE(32), 715); + assert.equal(buf.readUInt8(36), obj.tileIndex); + assert.equal(buf.readUInt8(37), obj.length); + assert.equal(buf.readUInt8(38), obj.reserved); + assert.equal(buf.readUInt8(39), obj.x); + assert.equal(buf.readUInt8(40), obj.y); + assert.equal(buf.readUInt8(41), obj.width); + assert.equal(buf.readUInt32LE(42), obj.duration); + + assert.equal(buf.readUInt16LE(46), obj.colors[0].hue); + assert.equal(buf.readUInt16LE(48), obj.colors[0].saturation); + assert.equal(buf.readUInt16LE(50), obj.colors[0].brightness); + assert.equal(buf.readUInt16LE(52), obj.colors[0].kelvin); + + assert.equal(buf.readUInt16LE(54), obj.colors[1].hue); + assert.equal(buf.readUInt16LE(56), obj.colors[1].saturation); + assert.equal(buf.readUInt16LE(58), obj.colors[1].brightness); + assert.equal(buf.readUInt16LE(60), obj.colors[1].kelvin); + }); + }); +}); diff --git a/test/unit/packets/stateDeviceChain.js b/test/unit/packets/stateDeviceChain.js new file mode 100644 index 0000000..a92ab7c --- /dev/null +++ b/test/unit/packets/stateDeviceChain.js @@ -0,0 +1,95 @@ +'use strict'; + +const Packet = require('../../../lib/lifx').packet; +const assert = require('chai').assert; + +function buildDevice(base) { + return { + accelMeasX: base + 0, + accelMeasY: base + 2, + accelMeasZ: base + 4, + reserved0: base + 6, + userX: base + 8, + userY: base + 12, + width: (base + 16) & 0xff, + height: (base + 17) & 0xff, + reserved1: (base + 18) & 0xff, + deviceVersionVendor: base + 19, + deviceVersionProduct: base + 23, + deviceVersionVersion: base + 27, + firmwareBuild: { + low: base + 31, + high: base + 35 + }, + reserved2: { + low: base + 39, + high: base + 43 + }, + firmwareVersionMinor: base + 47, + firmwareVersionMajor: base + 49, + reserved3: base + 51 + }; +} + +function buildDeviceMsg(msg, offset) { + msg.writeUInt16LE(offset, offset); + offset += 2; + msg.writeUInt16LE(offset, offset); + offset += 2; + msg.writeUInt16LE(offset, offset); + offset += 2; + msg.writeUInt16LE(offset, offset); + offset += 2; + msg.writeUInt32LE(offset, offset); + offset += 4; + msg.writeUInt32LE(offset, offset); + offset += 4; + msg.writeUInt8(offset & 0xff, offset); + offset += 1; + msg.writeUInt8(offset & 0xff, offset); + offset += 1; + msg.writeUInt8(offset & 0xff, offset); + offset += 1; + msg.writeUInt32LE(offset, offset); + offset += 4; + msg.writeUInt32LE(offset, offset); + offset += 4; + msg.writeUInt32LE(offset, offset); + offset += 4; + msg.writeUInt32LE(offset, offset); + offset += 4; + msg.writeUInt32LE(offset, offset); + offset += 4; + msg.writeUInt32LE(offset, offset); + offset += 4; + msg.writeUInt32LE(offset, offset); + offset += 4; + msg.writeUInt16LE(offset, offset); + offset += 2; + msg.writeUInt16LE(offset, offset); + offset += 2; + msg.writeUInt32LE(offset, offset); + offset += 4; + return offset; +} + +describe('Packet stateDeviceChain', () => { + describe('toObject', () => { + it('stateDeviceChain', () => { + const msg = Buffer.alloc(882 + 36); + msg.writeInt16LE(702, 32); + msg.writeInt8(1, 36 + 0); + msg.writeInt8(2, 36 + 881); + for (let offset = 36 + 1; offset < 36 + 881; offset += 0) { + offset = buildDeviceMsg(msg, offset); + } + const parsedMsg = Packet.toObject(msg); + assert.equal(parsedMsg.startIndex, 1); + assert.equal(parsedMsg.type, 702); + assert.equal(parsedMsg.totalCount, 2); + assert.deepEqual(parsedMsg.tileDevices, (new Array(16)) + .fill(undefined) + .map((_, idx) => buildDevice(37 + (idx * 55)))); + }); + }); +}); diff --git a/test/unit/packets/stateTileState64.js b/test/unit/packets/stateTileState64.js new file mode 100644 index 0000000..b5d4f3e --- /dev/null +++ b/test/unit/packets/stateTileState64.js @@ -0,0 +1,52 @@ +'use strict'; + +const Packet = require('../../../lib/lifx').packet; +const assert = require('chai').assert; + +function buildTileMsg(msg, offset) { + msg.writeUInt16LE(offset, offset); + offset += 2; + msg.writeUInt16LE(offset, offset); + offset += 2; + msg.writeUInt16LE(offset, offset); + offset += 2; + msg.writeUInt16LE(offset, offset); + offset += 2; + return offset; +} + +function buildTile(base) { + return { + hue: base, + saturation: base + 2, + brightness: base + 4, + kelvin: base + 6 + }; +} + +describe('Packet stateTileState64', () => { + describe('toBuffer', () => { + it('stateTileState64', () => { + const msg = Buffer.alloc(517 + 36); + msg.writeInt16LE(711, 32); + msg.writeInt8(1, 36 + 0); + msg.writeInt8(2, 36 + 1); + msg.writeInt8(3, 36 + 2); + msg.writeInt8(4, 36 + 3); + msg.writeInt8(5, 36 + 4); + for (let offset = 36 + 5; offset < 36 + 517; offset += 0) { + offset = buildTileMsg(msg, offset); + } + const parsedMsg = Packet.toObject(msg); + assert.equal(parsedMsg.type, 711); + assert.deepEqual(parsedMsg.tileIndex, 1); + assert.deepEqual(parsedMsg.reserved, 2); + assert.deepEqual(parsedMsg.x, 3); + assert.deepEqual(parsedMsg.y, 4); + assert.deepEqual(parsedMsg.width, 5); + assert.deepEqual(parsedMsg.colors, (new Array(64)) + .fill(undefined) + .map((_, idx) => buildTile(36 + 5 + (idx * 8)))); + }); + }); +}); From 30e90a53eada6b57889a4a6e16d42c4c245db143 Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Tue, 10 Dec 2019 07:41:11 +0100 Subject: [PATCH 2/9] changed api of set/getTileState64 to have an options object and callback. removed validation clutter from set/getTileState64 added some comments in the example --- example/tile.js | 18 +++++- src/lifx/light.js | 111 ++++++++++++++++++------------------- src/lifx/validate.js | 14 +++++ test/unit/validate-test.js | 7 +++ 4 files changed, 91 insertions(+), 59 deletions(-) diff --git a/example/tile.js b/example/tile.js index 5da1d0e..f72386c 100644 --- a/example/tile.js +++ b/example/tile.js @@ -5,6 +5,17 @@ const LifxClient = require('../lib/lifx').Client; const client = new LifxClient(); const LOOPTIME = 10000; +const TILE_LABEL = '*'; + +/* + * This example should show on all tiles of + * your chain a random pattern. After all bits + * of the tiles are set with the random pattern, + * the example reads back all set values + * from the tiles just to test the read back function. + * I could not compare the set values out of the reason + * that the setvalues are usally a bit modified. + */ function getBits(light, idx, chain) { if (idx >= chain.total_count) { @@ -12,7 +23,7 @@ function getBits(light, idx, chain) { setTimeout(() => setBits(light, 0, chain), LOOPTIME); return; } - light.getTileState64(idx, 64, 0, 0, 8, (err) => { + light.getTileState64(idx, (err) => { if (err) { console.error('getTileState64:', err); return; @@ -27,7 +38,7 @@ function setBits(light, idx, chain) { return; } const ofs = ~~(Math.random() * (65536 - (65536 / 64))); - light.setTileState64(idx, 64, 0, 0, 8, 100, + light.setTileState64(idx, {duration: 100}, (new Array(64)).fill(undefined).map((_, idx) => ({ hue: (ofs + (idx * (65536 / 64))) & 0xffff, saturation: 50000, @@ -37,6 +48,9 @@ function setBits(light, idx, chain) { } client.on('light-new', (light) => { + if (!(TILE_LABEL === '*' || TILE_LABEL === light.id)) { + return; + } console.log('New light found.'); console.log('ID: ' + light.id); diff --git a/src/lifx/light.js b/src/lifx/light.js index df83b09..f83a270 100644 --- a/src/lifx/light.js +++ b/src/lifx/light.js @@ -557,15 +557,8 @@ Light.prototype.getDeviceChain = function(callback) { * @param {Function} callback called when light did receive message */ Light.prototype.setUserPosition = function(tileIndex, userX, userY, reserved, callback) { - if (tileIndex === undefined || typeof tileIndex !== 'number') { - throw new TypeError('LIFX light setUserPosition tileIndex to be a number'); - } - if (userX === undefined || typeof userX !== 'number') { - throw new TypeError('LIFX light setUserPosition userX to be a number'); - } - if (userY === undefined || typeof userY !== 'number') { - throw new TypeError('LIFX light setUserPosition userY to be a number'); - } + validate.isUint8(tileIndex, 'setUserPosition', 'tileIndex'); + validate.isXY(userX, userY, 'setUserPosition', 'user'); validate.optionalCallback(callback, 'light setUserPosition method'); const packetObj = packet.create('setUserPositiion', { @@ -578,6 +571,33 @@ Light.prototype.setUserPosition = function(tileIndex, userX, userY, reserved, ca this.client.send(packetObj, callback); }; +function defaultOptionsTileState64(options) { + const ret = { + length: options.length, + x: options.x, + y: options.y, + width: options.width, + reserved: options.reserved, + duration: options.duration + }; + if (typeof ret.length !== 'number') { + ret.length = 64; + } + if (typeof ret.width !== 'number') { + ret.length = 8; + } + if (typeof ret.x !== 'number') { + ret.x = 0; + } + if (typeof ret.y !== 'number') { + ret.y = 0; + } + if (typeof ret.duration !== 'number') { + ret.duration = 8; + } + return ret; +} + /** * Requests tile GetTileState64 707 * @@ -587,34 +607,23 @@ Light.prototype.setUserPosition = function(tileIndex, userX, userY, reserved, ca * and length is used to get the state of that many tiles beginning * from the tileIndex. This will result in a separate response from * each tile. - * For the LIFX Tile it really only makes sense to set x and y to - * zero, and width to 8. * @param {Number} tileIndex unsigned 8-bit integer - * @param {Number} length unsigned 8-bit integer - * @param {Number} x unsigned 8-bit integer - * @param {Number} y unsigned 8-bit integer - * @param {Number} width unsigned 8-bit integer - * @param {Number} reserved unsigned 8-bit integer + * @param {Object} options - options passed to + * @param {Number} options.length unsigned 8-bit integer (default 64) + * @param {Number} options.x unsigned 8-bit integer (default 0) + * @param {Number} options.y unsigned 8-bit integer (default 0) + * @param {Number} options.width unsigned 8-bit integer (default 8) + * @param {Number} options.reserved unsigned 8-bit integer * @param {Function} callback a function to accept the data */ -Light.prototype.getTileState64 = function(tileIndex, length, x, y, width, reserved, callback) { - if (tileIndex === undefined || typeof tileIndex !== 'number') { - throw new TypeError('LIFX light getTileState64 tileIndex to be a number'); - } - if (length === undefined || typeof length !== 'number') { - throw new TypeError('LIFX light getTileState64 length to be a number'); - } - if (x === undefined || typeof x !== 'number') { - throw new TypeError('LIFX light getTileState64 x to be a number'); - } - if (y === undefined || typeof y !== 'number') { - throw new TypeError('LIFX light getTileState64 y to be a number'); - } - if (width === undefined || typeof width !== 'number') { - throw new TypeError('LIFX light getTileState64 width to be a number'); +Light.prototype.getTileState64 = function(tileIndex, options, callback) { + validate.isUint8(tileIndex, 'getTileState64', 'tileIndex'); + if (typeof options === 'function') { + callback = options; + options = {}; } validate.callback(callback, 'light getTileState64 method'); - + const {length, x, y, width, reserved} = defaultOptionsTileState64(options); const packetObj = packet.create('getTileState64', { tileIndex, length, @@ -641,36 +650,24 @@ Light.prototype.getTileState64 = function(tileIndex, length, x, y, width, reserv * For the LIFX Tile it really only makes sense to set x * and y to zero, and width to 8. * @param {Number} tileIndex unsigned 8-bit integer - * @param {Number} length unsigned 8-bit integer - * @param {Number} x unsigned 8-bit integer - * @param {Number} y unsigned 8-bit integer - * @param {Number} width unsigned 8-bit integer - * @param {Number} duration unsigned 32-bit integer * @param {Number} colors[64] 64 HSBK values - * @param {Number} reserved unsigned 8-bit integer + * @param {Object} options - options passed to + * @param {Number} options.length unsigned 8-bit integer (default 0) + * @param {Number} options.x unsigned 8-bit integer (default 8) + * @param {Number} options.y unsigned 8-bit integer (default 8) + * @param {Number} options.width unsigned 8-bit integer (default 8) + * @param {Number} options.duration unsigned 32-bit integer (default 0) + * @param {Number} options.reserved unsigned 8-bit integer * @param {Function} [callback] called when light did receive message */ -Light.prototype.setTileState64 = function(tileIndex, length, x, y, width, duration, colors, reserved, callback) { - if (tileIndex === undefined || typeof tileIndex !== 'number') { - throw new TypeError('LIFX light setTileState64 tileIndex to be a number'); - } - if (length === undefined || typeof length !== 'number') { - throw new TypeError('LIFX light setTileState64 length to be a number'); - } - if (x === undefined || typeof x !== 'number') { - throw new TypeError('LIFX light setTileState64 x to be a number'); - } - if (y === undefined || typeof y !== 'number') { - throw new TypeError('LIFX light setTileState64 y to be a number'); - } - if (width === undefined || typeof width !== 'number') { - throw new TypeError('LIFX light setTileState64 width to be a number'); - } - if (duration === undefined || typeof duration !== 'number') { - throw new TypeError('LIFX light setTileState64 duration to be a number'); +Light.prototype.setTileState64 = function(tileIndex, colors, options, callback) { + validate.isUint8(tileIndex, 'setTileState64', 'tileIndex'); + if (typeof options === 'function') { + callback = options; + options = {}; } + const {length, x, y, width, duration, reserved} = defaultOptionsTileState64({}); const set64colors = utils.buildColorsHsbk(colors, 64); - validate.optionalCallback(callback, 'light setTileState64 method'); const packetObj = packet.create('setTileState64', { diff --git a/src/lifx/validate.js b/src/lifx/validate.js index c181347..91ae838 100644 --- a/src/lifx/validate.js +++ b/src/lifx/validate.js @@ -171,6 +171,20 @@ validate.optionalZoneIndex = function(index, context) { return true; }; +/** + * Checks validity the userX and userY + * @param {Number} x the x value + * @param {Number} y the y value + * @param {String} context validation context + * @param {String} valueName prepended to the output + * @return {Boolean} const true or an exception + */ +validate.isXY = function(x, y, context, valueName) { + validate.isUInt8(x, context, (valueName || '') + 'X'); + validate.isUInt8(y, context, (valueName || '') + 'Y'); + return true; +}; + /** * test if the given value is an uint value * @param {Number} val the given uint value as number diff --git a/test/unit/validate-test.js b/test/unit/validate-test.js index 76a103d..c0d870e 100644 --- a/test/unit/validate-test.js +++ b/test/unit/validate-test.js @@ -37,4 +37,11 @@ describe('Validation', () => { assert.isTrue(validate.isUInt32(0xffffffff)); assert.throw(() => validate.isUInt32(0x100000000)); }); + + it('isXY', () => { + assert.isTrue(validate.isXY(1, 2)); + assert.throw(() => validate.isXY()); + assert.throw(() => validate.isXY('hallo', 4)); + assert.throw(() => validate.isXY(4, 'hallo')); + }); }); From a8fa1cecd5ca4ed73ed91aedcc0c8fd35601042a Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Sat, 14 Dec 2019 22:47:52 +0100 Subject: [PATCH 3/9] fix in setTileState64 the missing option passing --- src/lifx/light.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lifx/light.js b/src/lifx/light.js index f83a270..62c74cc 100644 --- a/src/lifx/light.js +++ b/src/lifx/light.js @@ -666,7 +666,7 @@ Light.prototype.setTileState64 = function(tileIndex, colors, options, callback) callback = options; options = {}; } - const {length, x, y, width, duration, reserved} = defaultOptionsTileState64({}); + const {length, x, y, width, duration, reserved} = defaultOptionsTileState64(options); const set64colors = utils.buildColorsHsbk(colors, 64); validate.optionalCallback(callback, 'light setTileState64 method'); From 240ec77d7d5735ce986618ca18b92d3fc48b9e74 Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Sat, 14 Dec 2019 22:54:45 +0100 Subject: [PATCH 4/9] fix: the typo of Uint8 to UInt8 --- src/lifx/light.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lifx/light.js b/src/lifx/light.js index 62c74cc..ed49d61 100644 --- a/src/lifx/light.js +++ b/src/lifx/light.js @@ -557,7 +557,7 @@ Light.prototype.getDeviceChain = function(callback) { * @param {Function} callback called when light did receive message */ Light.prototype.setUserPosition = function(tileIndex, userX, userY, reserved, callback) { - validate.isUint8(tileIndex, 'setUserPosition', 'tileIndex'); + validate.isUInt8(tileIndex, 'setUserPosition', 'tileIndex'); validate.isXY(userX, userY, 'setUserPosition', 'user'); validate.optionalCallback(callback, 'light setUserPosition method'); @@ -617,7 +617,7 @@ function defaultOptionsTileState64(options) { * @param {Function} callback a function to accept the data */ Light.prototype.getTileState64 = function(tileIndex, options, callback) { - validate.isUint8(tileIndex, 'getTileState64', 'tileIndex'); + validate.isUInt8(tileIndex, 'getTileState64', 'tileIndex'); if (typeof options === 'function') { callback = options; options = {}; @@ -661,7 +661,7 @@ Light.prototype.getTileState64 = function(tileIndex, options, callback) { * @param {Function} [callback] called when light did receive message */ Light.prototype.setTileState64 = function(tileIndex, colors, options, callback) { - validate.isUint8(tileIndex, 'setTileState64', 'tileIndex'); + validate.isUInt8(tileIndex, 'setTileState64', 'tileIndex'); if (typeof options === 'function') { callback = options; options = {}; From 7808c8af0a6f329d4e0d46bd496042c447a73e77 Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Thu, 26 Dec 2019 17:07:12 +0100 Subject: [PATCH 5/9] - make the example/tile.js working again - changed api of setUserPosition to args parsed by props object added full stack tests - added full stack tests to getDeviceChain - added full stack tests to setTileState64 - added full stack tests to getTileState64 --- babelhook.js | 3 +- example/tile.js | 21 ++- src/lifx/light.js | 31 ++-- src/lifx/packet.js | 2 +- src/lifx/packets/getTileState64.js | 43 +++++- src/lifx/packets/hsbk.js | 71 +++++++++ src/lifx/packets/index.js | 3 + src/lifx/packets/setTileState64.js | 89 ++++++----- src/lifx/packets/setUserPosition.js | 63 ++++++++ src/lifx/packets/stateDeviceChain.js | 85 +++++----- src/lifx/packets/stateTileState64.js | 63 ++++++-- src/lifx/packets/tile.js | 179 +++++++++++++++++++++ src/lifx/validate.js | 34 ++++ test/unit/light-test.js | 215 +++++++++++++++++++++++++- test/unit/packets/buildColors.js | 11 ++ test/unit/packets/buildTile.js | 29 ++++ test/unit/packets/getTileState64.js | 39 ++--- test/unit/packets/hsbk.js | 23 +++ test/unit/packets/setTileState64.js | 134 +++++----------- test/unit/packets/setUserPosition.js | 23 +++ test/unit/packets/stateDeviceChain.js | 107 +++---------- test/unit/packets/stateTileState64.js | 65 +++----- test/unit/packets/tile.js | 23 +++ test/unit/validate-test.js | 16 ++ 24 files changed, 995 insertions(+), 377 deletions(-) create mode 100644 src/lifx/packets/hsbk.js create mode 100644 src/lifx/packets/setUserPosition.js create mode 100644 src/lifx/packets/tile.js create mode 100644 test/unit/packets/buildColors.js create mode 100644 test/unit/packets/buildTile.js create mode 100644 test/unit/packets/hsbk.js create mode 100644 test/unit/packets/setUserPosition.js create mode 100644 test/unit/packets/tile.js diff --git a/babelhook.js b/babelhook.js index d5facd4..4a0533e 100644 --- a/babelhook.js +++ b/babelhook.js @@ -6,5 +6,6 @@ require('babel-core/register')({ // Only compile test files only: /-test.js$/, - presets: ['env'] + presets: ['env'], + sourceMaps: "both" }); diff --git a/example/tile.js b/example/tile.js index f72386c..08c9874 100644 --- a/example/tile.js +++ b/example/tile.js @@ -4,8 +4,8 @@ const LifxClient = require('../lib/lifx').Client; const client = new LifxClient(); -const LOOPTIME = 10000; -const TILE_LABEL = '*'; +const LOOPTIME = 1000; +const TILE_LABEL = process.argv.length > 2 ? process.argv[process.argv.length - 1] : '*'; /* * This example should show on all tiles of @@ -15,14 +15,17 @@ const TILE_LABEL = '*'; * from the tiles just to test the read back function. * I could not compare the set values out of the reason * that the setvalues are usally a bit modified. + * + * EASY: You should see an updating Pattern every two second */ function getBits(light, idx, chain) { - if (idx >= chain.total_count) { + if (idx >= chain.totalCount) { console.log('All Bits get'); setTimeout(() => setBits(light, 0, chain), LOOPTIME); return; } + console.log('getBits', idx); light.getTileState64(idx, (err) => { if (err) { console.error('getTileState64:', err); @@ -33,21 +36,23 @@ function getBits(light, idx, chain) { } function setBits(light, idx, chain) { - if (idx >= chain.total_count) { + if (idx >= chain.totalCount) { setTimeout(() => getBits(light, 0, chain), LOOPTIME); return; } + console.log('setBits', idx, chain.totalCount); const ofs = ~~(Math.random() * (65536 - (65536 / 64))); - light.setTileState64(idx, {duration: 100}, + light.setTileState64(idx, (new Array(64)).fill(undefined).map((_, idx) => ({ hue: (ofs + (idx * (65536 / 64))) & 0xffff, saturation: 50000, brightness: 16384, kelvin: 4096 - })), () => setBits(light, idx + 1, chain)); + })), {duration: 100}, () => setBits(light, idx + 1, chain)); } client.on('light-new', (light) => { + console.log(TILE_LABEL, light.id); if (!(TILE_LABEL === '*' || TILE_LABEL === light.id)) { return; } @@ -58,7 +63,9 @@ client.on('light-new', (light) => { if (err) { console.log(err); } - setBits(light, 0, chain); + light.on(0, () => { + setBits(light, 0, chain); + }); }); }); diff --git a/src/lifx/light.js b/src/lifx/light.js index ed49d61..e8d96f6 100644 --- a/src/lifx/light.js +++ b/src/lifx/light.js @@ -549,24 +549,23 @@ Light.prototype.getDeviceChain = function(callback) { /** * Sets Tile Position - * @example light.setUserPosition(0, 12, 13, 0, () => {}) - * @param {Number} tileIndex unsigned 8-bit integer - * @param {Number} userX 32-bit float - * @param {Number} userY 32-bit float - * @param {Number} reserved 16-bit integer + * @param {SetUserPosition} vals - value to set * @param {Function} callback called when light did receive message */ -Light.prototype.setUserPosition = function(tileIndex, userX, userY, reserved, callback) { - validate.isUInt8(tileIndex, 'setUserPosition', 'tileIndex'); - validate.isXY(userX, userY, 'setUserPosition', 'user'); +Light.prototype.setUserPosition = function(vals, callback) { + vals = Object.assign({ + tileIndex: 0, + userX: 0, + userY: 0, + reserved: 0 + }, vals); + validate.isUInt8(vals.tileIndex, 'setUserPosition', 'tileIndex'); + validate.isFloat(vals.userX, 'setUserPosition', 'userX'); + validate.isFloat(vals.userY, 'setUserPosition', 'userX'); + validate.isUInt16(vals.reserved || 0, 'setUserPosition', 'reserved'); validate.optionalCallback(callback, 'light setUserPosition method'); - const packetObj = packet.create('setUserPositiion', { - tileIndex, - userX, - userY, - reserved: reserved || 0 - }, this.client.source); + const packetObj = packet.create('setUserPosition', vals, this.client.source); packetObj.target = this.id; this.client.send(packetObj, callback); }; @@ -584,7 +583,7 @@ function defaultOptionsTileState64(options) { ret.length = 64; } if (typeof ret.width !== 'number') { - ret.length = 8; + ret.width = 8; } if (typeof ret.x !== 'number') { ret.x = 0; @@ -593,7 +592,7 @@ function defaultOptionsTileState64(options) { ret.y = 0; } if (typeof ret.duration !== 'number') { - ret.duration = 8; + ret.duration = 0; } return ret; } diff --git a/src/lifx/packet.js b/src/lifx/packet.js index 2ca80c9..abdc0d9 100644 --- a/src/lifx/packet.js +++ b/src/lifx/packet.js @@ -91,7 +91,7 @@ Packet.typeList = [ // {id: 509, name: 'stateEffectZone'} {id: 701, name: 'getDeviceChain'}, {id: 702, name: 'stateDeviceChain'}, - {id: 706, name: 'setUserPosition'}, + {id: 703, name: 'setUserPosition'}, {id: 707, name: 'getTileState64'}, {id: 711, name: 'stateTileState64'}, {id: 715, name: 'setTileState64'} diff --git a/src/lifx/packets/getTileState64.js b/src/lifx/packets/getTileState64.js index abb1189..d5b55eb 100644 --- a/src/lifx/packets/getTileState64.js +++ b/src/lifx/packets/getTileState64.js @@ -6,15 +6,19 @@ const Packet = { size: 6 }; +/** + * @typedef {Object} GetTileState64 + * @property {Number} tileIndex an 8bit value + * @property {Number} length an 8bit value + * @property {Number} reserved an 8bit value + * @property {Number} x an 8bit value + * @property {Number} y an 8bit value + * @property {Number} width an 8bit valu + */ + /** * Converts the given packet specific object into a packet - * @param {Object} obj object with configuration data - * @param {Number} obj.tileIndex an 8bit value - * @param {Number} obj.length an 8bit value - * @param {Number} obj.reserved an 8bit value - * @param {Number} obj.x an 8bit value - * @param {Number} obj.y an 8bit value - * @param {Number} obj.width an 8bit value + * @param {GetTileState64} obj object with configuration data * @return {Buffer} packet */ Packet.toBuffer = function(obj) { @@ -41,4 +45,29 @@ Packet.toBuffer = function(obj) { return buf; }; +/** + * Converts the given packet specific object into a packet + * @param {Buffer} buf object with configuration data + * @return {GetTileState64} packet + */ +Packet.toObject = function(buf) { + const obj = {}; + let offset = 0; + + obj.tileIndex = buf.readUInt8(offset); + offset += 1; + obj.length = buf.readUInt8(offset); + offset += 1; + obj.reserved = buf.readUInt8(offset); + offset += 1; + obj.x = buf.readUInt8(offset); + offset += 1; + obj.y = buf.readUInt8(offset); + offset += 1; + obj.width = buf.readUInt8(offset); + offset += 1; + + return obj; +}; + module.exports = Packet; diff --git a/src/lifx/packets/hsbk.js b/src/lifx/packets/hsbk.js new file mode 100644 index 0000000..740bcca --- /dev/null +++ b/src/lifx/packets/hsbk.js @@ -0,0 +1,71 @@ +'use strict'; + +const {validate} = require('../../lifx'); + +const Packet = { + size: 8 +}; + +/** + * @typedef {Object} HSBK + * @property {Number} hsbk.hue - hue value + * @property {Number} hsbk.saturation - saturation value + * @property {Number} hsbk.brightness - brightness value + * @property {Number} hsbk.kelvin - kelvin value + */ + +/** + * @typedef {Object} OffsetHSBK + * @property {Number} offset - offset after the buffer + * @property {HSBK} hsbk - HSBK value + * + */ + +/** + * Converts the given packet specific object into a packet + * @param {Buffer} buf hsbk as buffer + * @param {Number} offset offset to the buffer + * @return {OffsetHSBK} packet + */ +Packet.toObject = function(buf, offset) { + const hsbk = {}; + hsbk.hue = buf.readUInt16LE(offset); + offset += 2; + hsbk.saturation = buf.readUInt16LE(offset); + offset += 2; + hsbk.brightness = buf.readUInt16LE(offset); + offset += 2; + hsbk.kelvin = buf.readUInt16LE(offset); + offset += 2; + return {offset, hsbk}; +}; + +/** + * @typedef {Object} OffsetBuffer + * @property {number} offset - offset after the buffer + * @property {Buffer} buffer - buffer + */ +/** + * Converts the given packet specific object into a packet + * @param {Buffer} buffer output buffer + * @param {Number} offset offset in the output buffer + * @param {HSBK} hsbk offset in the output buffer + * @return {OffsetBuffer} packet + */ +Packet.toBuffer = function(buffer, offset, hsbk) { + validate.isUInt16(hsbk.hue); + validate.isUInt16(hsbk.saturation); + validate.isUInt16(hsbk.brightness); + validate.isUInt16(hsbk.kelvin); + buffer.writeUInt16LE(hsbk.hue, offset); + offset += 2; + buffer.writeUInt16LE(hsbk.saturation, offset); + offset += 2; + buffer.writeUInt16LE(hsbk.brightness, offset); + offset += 2; + buffer.writeUInt16LE(hsbk.kelvin, offset); + offset += 2; + return {offset, buffer}; +}; + +module.exports = Packet; diff --git a/src/lifx/packets/index.js b/src/lifx/packets/index.js index 896d1e1..a3f9090 100644 --- a/src/lifx/packets/index.js +++ b/src/lifx/packets/index.js @@ -80,9 +80,12 @@ packets.setColorZones = require('./setColorZones'); packets.stateZone = require('./stateZone'); packets.stateMultiZone = require('./stateMultiZone'); +packets.tile = require('./tile'); +packets.hsbk = require('./hsbk'); packets.getDeviceChain = require('./getDeviceChain'); packets.stateDeviceChain = require('./stateDeviceChain'); +packets.setUserPosition = require('./setUserPosition'); packets.setTileState64 = require('./setTileState64'); packets.getTileState64 = require('./getTileState64'); packets.stateTileState64 = require('./stateTileState64'); diff --git a/src/lifx/packets/setTileState64.js b/src/lifx/packets/setTileState64.js index 5c266a4..21b0512 100644 --- a/src/lifx/packets/setTileState64.js +++ b/src/lifx/packets/setTileState64.js @@ -1,55 +1,71 @@ 'use strict'; const {validate} = require('../../lifx'); +const HSBK = require('./hsbk'); const Packet = { - size: (obj) => 10 + (obj.colors.length * (8)), - HSBK: { - toBuffer: (obj, buf, offset) => { - validate.isUInt16(obj.hue, 'setTileState64:HSBK:hue'); - buf.writeUInt16LE(obj.hue, offset); - offset += 2; - - validate.isUInt16(obj.saturation, 'setTileState64:HSBK:saturation'); - buf.writeUInt16LE(obj.saturation, offset); - offset += 2; - - validate.isUInt16(obj.brightness, 'setTileState64:HSBK:brightness'); - buf.writeUInt16LE(obj.brightness, offset); - offset += 2; + size: 10 + (64 * HSBK.size), + HSBK +}; - validate.isUInt16(obj.kelvin, 'setTileState64:HSBK:kelvin'); - buf.writeUInt16LE(obj.kelvin, offset); - offset += 2; +/** + * @typedef {Object} SetTileState64 + * @property {Number} tileIndex an 8bit value + * @property {Number} length an 8bit value + * @property {Number} reserved an 8bit value + * @property {Number} x an 8bit value + * @property {Number} y an 8bit value + * @property {Number} width an 8bit value + * @property {HSBK[]} colors an array of HSBK values + */ - return offset; - } - } +/** + * Converts the given packet specific object into a packet + * @param {Buffer} buf Buffer of the data + * @return {SetTileState64} packet + */ +Packet.toObject = function(buf) { + const obj = {}; + let offset = 0; + obj.tileIndex = buf.readUInt8(offset); + offset += 1; + obj.length = buf.readUInt8(offset); + offset += 1; + obj.reserved = buf.readUInt8(offset); + offset += 1; + obj.x = buf.readUInt8(offset); + offset += 1; + obj.y = buf.readUInt8(offset); + offset += 1; + obj.width = buf.readUInt8(offset); + offset += 1; + obj.duration = buf.readUInt32LE(offset); + offset += 4; + obj.colors = Array(64).fill(undefined).map(() => { + const ret = Packet.HSBK.toObject(buf, offset); + offset = ret.offset; + return ret.hsbk; + }); + return obj; }; /** * Converts the given packet specific object into a packet - * @param {Object} obj object with configuration data - * @param {Number} obj.tileIndex 8bit value - * @param {Number} obj.length 8bit value - * @param {Number} obj.reserved 8bit value - * @param {Number} obj.x 8bit value - * @param {Number} obj.y 8bit value - * @param {Number} obj.width 8bit value - * @param {Number} obj.duration 8bit value - * @param {Number} [obj.duration] transition time in milliseconds - * @param {Array} obj.colors an array of HSBK values + * @param {SetTileState64} obj object with configuration data * @return {Buffer} packet */ Packet.toBuffer = function(obj) { - const buf = Buffer.alloc(Packet.size(obj)); - buf.fill(0); - let offset = 0; - + obj.reserved = obj.reserved || 0; + obj.duration = obj.duration || 0; ['tileIndex', 'length', 'reserved', 'x', 'y', 'width'].forEach((field) => { validate.isUInt8(obj[field], `setTileState64:${field}`); }); - // obj.stream field has unknown function so leave it as 0 + validate.isUInt32(obj.duration, 'setTileState64:duration'); + + const buf = Buffer.alloc(Packet.size); + buf.fill(0); + let offset = 0; + buf.writeUInt8(obj.tileIndex, offset); offset += 1; buf.writeUInt8(obj.length, offset); @@ -62,11 +78,10 @@ Packet.toBuffer = function(obj) { offset += 1; buf.writeUInt8(obj.width, offset); offset += 1; - validate.isUInt32(obj.duration, 'setTileState64:duration'); buf.writeUInt32LE(obj.duration, offset); offset += 4; obj.colors.forEach((color) => { - offset = Packet.HSBK.toBuffer(color, buf, offset); + offset = Packet.HSBK.toBuffer(buf, offset, color).offset; }); return buf; }; diff --git a/src/lifx/packets/setUserPosition.js b/src/lifx/packets/setUserPosition.js new file mode 100644 index 0000000..ca55e6d --- /dev/null +++ b/src/lifx/packets/setUserPosition.js @@ -0,0 +1,63 @@ +'use strict'; + +const {validate} = require('../../lifx'); + +const Packet = { + size: 11 +}; + +/** + * @typedef {Object} SetUserPosition + * @property {Number} - UInt8 tileIndex + * @property {Number} - UInt16 reserved + * @property {Number} - Float userX + * @property {Number} - Float userY + */ + +/** + * Converts packet specific data from a buffer to an object + * @param {Buffer} buf Buffer containing only packet specific data no header + * @return {SetUserPosition} Information contained in packet + */ +Packet.toObject = function(buf) { + if (buf.length !== this.size) { + throw new Error(`Invalid length given for stateTileState64 LIFX packet:${buf.length}:${this.size}`); + } + let offset = 0; + const obj = {}; + obj.tileIndex = buf.readUInt8(offset); + offset += 1; + obj.reserved = buf.readUInt16LE(offset); + offset += 2; + obj.userX = buf.readFloatLE(offset); + offset += 4; + obj.userY = buf.readFloatLE(offset); + offset += 4; + return obj; +}; + +/** + * Converts the given packet specific object into a packet + * @param {SetUserPosition} obj object with configuration data + * @return {Buffer} packet + */ +Packet.toBuffer = function(obj) { + validate.isUInt8(obj.tileIndex); + validate.isUInt16(obj.reserved); + validate.isFloat(obj.userX); + validate.isFloat(obj.userY); + const buf = Buffer.alloc(Packet.size); + buf.fill(0); + let offset = 0; + buf.writeUInt8(obj.tileIndex, offset); + offset += 1; + buf.writeUInt16LE(obj.reserved, offset); + offset += 2; + buf.writeFloatLE(obj.userX, offset); + offset += 4; + buf.writeFloatLE(obj.userY, offset); + offset += 4; + return buf; +}; + +module.exports = Packet; diff --git a/src/lifx/packets/stateDeviceChain.js b/src/lifx/packets/stateDeviceChain.js index 192a331..3706b14 100644 --- a/src/lifx/packets/stateDeviceChain.js +++ b/src/lifx/packets/stateDeviceChain.js @@ -1,59 +1,24 @@ 'use strict'; +const Tile = require('./tile'); +const {validate} = require('../../lifx'); + const Packet = { - size: 882, - Tile: { - toObject: function(buf, offset) { - const tile = {}; - tile.accelMeasX = buf.readUInt16LE(offset); - offset += 2; - tile.accelMeasY = buf.readUInt16LE(offset); - offset += 2; - tile.accelMeasZ = buf.readUInt16LE(offset); - offset += 2; - tile.reserved0 = buf.readUInt16LE(offset); - offset += 2; - tile.userX = buf.readUInt32LE(offset); - offset += 4; - tile.userY = buf.readUInt32LE(offset); - offset += 4; - tile.width = buf.readUInt8(offset); - offset += 1; - tile.height = buf.readUInt8(offset); - offset += 1; - tile.reserved1 = buf.readUInt8(offset); - offset += 1; - tile.deviceVersionVendor = buf.readUInt32LE(offset); - offset += 4; - tile.deviceVersionProduct = buf.readUInt32LE(offset); - offset += 4; - tile.deviceVersionVersion = buf.readUInt32LE(offset); - offset += 4; - tile.firmwareBuild = { - low: buf.readUInt32LE(offset), - high: buf.readUInt32LE(offset + 4) - }; - offset += 8; - tile.reserved2 = { - low: buf.readUInt32LE(offset), - high: buf.readUInt32LE(offset + 4) - }; - offset += 8; - tile.firmwareVersionMinor = buf.readUInt16LE(offset); - offset += 2; - tile.firmwareVersionMajor = buf.readUInt16LE(offset); - offset += 2; - tile.reserved3 = buf.readUInt32LE(offset); - offset += 4; - return {offset, tile}; - } - } + size: 2 + (Tile.size * 16), + Tile }; +/** + * @typedef {Object} StateDeviceChain + * @property {Number} startIndex - UInt8 startIndex + * @property {Number} totalCount - UInt8 totalCount + * @property {Tile[]} tileDevices- Array of Tiles + */ + /** * Converts packet specific data from a buffer to an object * @param {Buffer} buf Buffer containing only packet specific data no header - * @return {Object} Information contained in packet + * @return {StateDeviceChain} Information contained in packet */ Packet.toObject = function(buf) { if (buf.length !== this.size) { @@ -62,15 +27,35 @@ Packet.toObject = function(buf) { let offset = 0; const obj = {}; obj.startIndex = buf.readUInt8(offset); + obj.totalCount = Math.min(buf.readUInt8(buf.length - 1), 16); offset += 1; - obj.tileDevices = new Array(16).fill(undefined).map(() => { + obj.tileDevices = Array(obj.totalCount).fill(undefined).map(() => { const ret = Packet.Tile.toObject(buf, offset); offset = ret.offset; return ret.tile; }); - obj.totalCount = buf.readUInt8(offset); offset += 1; return obj; }; +/** + * Converts packet specific data from a buffer to an object + * @param {StateDeviceChain} obj - as Object + * @return {Buffer} - Buffer of the packet + */ +Packet.toBuffer = function(obj) { + const buf = Buffer.alloc(this.size); + buf.fill(0); + + validate.isUInt8(obj.startIndex, 'stateDeviceChain:start_index'); + + buf.writeUInt8(obj.startIndex, 0); + const len = Math.min(obj.tileDevices.length, obj.totalCount || 16); + buf.writeUInt8(len, Packet.size - 1); + obj.tileDevices.slice(0, len).reduce((ofs, tile) => { + return Packet.Tile.toBuffer(buf, ofs, tile).offset; + }, 1); + return buf; +}; + module.exports = Packet; diff --git a/src/lifx/packets/stateTileState64.js b/src/lifx/packets/stateTileState64.js index a08b025..dc93073 100644 --- a/src/lifx/packets/stateTileState64.js +++ b/src/lifx/packets/stateTileState64.js @@ -1,27 +1,27 @@ 'use strict'; +const {validate} = require('../../lifx'); +const HSBK = require('./hsbk'); + const Packet = { - size: 517, - HSBK: { - toObject: function(buf, offset) { - const hsbk = {}; - hsbk.hue = buf.readUInt16LE(offset); - offset += 2; - hsbk.saturation = buf.readUInt16LE(offset); - offset += 2; - hsbk.brightness = buf.readUInt16LE(offset); - offset += 2; - hsbk.kelvin = buf.readUInt16LE(offset); - offset += 2; - return {offset, hsbk}; - } - } + size: 5 + (HSBK.size * 64), + HSBK }; +/** + * @typedef {Object} StateTileState64 + * @property {Number} tileIndex an 8bit value + * @property {Number} reserved an 8bit value + * @property {Number} x an 8bit value + * @property {Number} y an 8bit value + * @property {Number} width an 8bit value + * @property {HSBK[]} colors an array of HSBK values + */ + /** * Converts packet specific data from a buffer to an object * @param {Buffer} buf Buffer containing only packet specific data no header - * @return {Object} Information contained in packet + * @return {StateTileState64} Information contained in packet */ Packet.toObject = function(buf) { if (buf.length !== this.size) { @@ -47,5 +47,36 @@ Packet.toObject = function(buf) { return obj; }; +/** + * Converts the given packet specific object into a packet + * @param {StateTileState64} obj object with configuration data + * @return {Buffer} packet + */ +Packet.toBuffer = function(obj) { + obj.reserved = obj.reserved || 0; + ['tileIndex', 'reserved', 'x', 'y', 'width'].forEach((field) => { + validate.isUInt8(obj[field], `setTileState64:${field}`); + }); + + const buf = Buffer.alloc(Packet.size); + buf.fill(0); + let offset = 0; + + buf.writeUInt8(obj.tileIndex, offset); + offset += 1; + buf.writeUInt8(obj.reserved || 0, offset); + offset += 1; + buf.writeUInt8(obj.x, offset); + offset += 1; + buf.writeUInt8(obj.y, offset); + offset += 1; + buf.writeUInt8(obj.width, offset); + offset += 1; + obj.colors.forEach((color) => { + offset = Packet.HSBK.toBuffer(buf, offset, color).offset; + }); + return buf; +}; + module.exports = Packet; diff --git a/src/lifx/packets/tile.js b/src/lifx/packets/tile.js new file mode 100644 index 0000000..71557ec --- /dev/null +++ b/src/lifx/packets/tile.js @@ -0,0 +1,179 @@ +'use strict'; + +const {validate} = require('../../lifx'); + +const Packet = { + size: 55 +}; + +/** + * @typedef {Object} Tile + * @property {Number} - UInt16 accelMeasX + * @property {Number} - UInt16 accelMeasY + * @property {Number} - UInt16 accelMeasZ + * @property {Number} - UInt16 reserved0 + * @property {Number} - Float userX + * @property {Number} - Float userY + * @property {Number} - UInt8 width + * @property {Number} - UInt8 height + * @property {Number} - UInt8 reserved1 + * @property {Number} - UInt32 deviceVersionVendor + * @property {Number} - UInt32 deviceVersionProduct + * @property {Number} - UInt32 deviceVersionVersion + * @property {UInt64LowHigh} - firmwareBuild + * @property {UInt64LowHigh} - reserved2 + * @property {Number} - UInt16 firmwareVersionMinor + * @property {Number} - UInt16 firmwareVersionMajor + * @property {Number} - UInt32 reserved3 + */ + +/** + * @typedef {Object} OffsetBuffer + * @property {Number} offset - offset after the buffer + * @property {Buffer} buffer - buffer + */ + +/** + * Converts the given packet specific object into a packet + * @param {Buffer} buffer output buffer + * @param {Number} offset offset in the output buffer + * @param {Tile} tile offset in the output buffer + * @return {OffsetBuffer} packet + */ +Packet.toBuffer = function(buffer, offset, tile) { + tile = Object.assign({ + accelMeasX: 0, + accelMeasY: 0, + accelMeasZ: 0, + reserved0: 0, + userX: 0, + userY: 0, + width: 0, + height: 0, + reserved1: 0, + deviceVersionVendor: 0, + deviceVersionProduct: 0, + deviceVersionVersion: 0, + firmwareBuild: {low: 0, high: 0}, + reserved2: {low: 0, high: 0}, + firmwareVersionMinor: 0, + firmwareVersionMajor: 0, + reserved3: 0 + }, tile); + validate.isUInt16(tile.accelMeasX, 'Tile:accelMeasX'); + validate.isUInt16(tile.accelMeasY, 'Tile:accelMeasY'); + validate.isUInt16(tile.accelMeasZ, 'Tile:accelMeasZ'); + validate.isUInt16(tile.reserved0, 'Tile:reserved0'); + validate.isFloat(tile.userX, 'Tile:userX'); + validate.isFloat(tile.userY, 'Tile:userY'); + validate.isUInt8(tile.width, 'Tile:width'); + validate.isUInt8(tile.height, 'Tile:height'); + validate.isUInt8(tile.reserved1, 'Tile:reserved1'); + validate.isUInt32(tile.deviceVersionVendor, 'Tile:deviceVersionVendor'); + validate.isUInt32(tile.deviceVersionProduct, 'Tile:deviceVersionProduct'); + validate.isUInt32(tile.deviceVersionVersion, 'Tile:deviceVersionVersion'); + validate.isUInt64LowHigh(tile.firmwareBuild, 'Tile:firmwareBuild'); + validate.isUInt64LowHigh(tile.reserved2, 'Tile:reserved2'); + validate.isUInt16(tile.firmwareVersionMinor, 'Tile:firmwareVersionMinor'); + validate.isUInt16(tile.firmwareVersionMajor, 'Tile:firmwareVersionMajor'); + validate.isUInt32(tile.reserved3, 'Tile:reserved3'); + + buffer.writeUInt16LE(tile.accelMeasX, offset); + offset += 2; + buffer.writeUInt16LE(tile.accelMeasY, offset); + offset += 2; + buffer.writeUInt16LE(tile.accelMeasZ, offset); + offset += 2; + buffer.writeUInt16LE(tile.reserved0, offset); + offset += 2; + buffer.writeFloatLE(tile.userX, offset); + offset += 4; + buffer.writeFloatLE(tile.userY, offset); + offset += 4; + buffer.writeUInt8(tile.width, offset); + offset += 1; + buffer.writeUInt8(tile.height, offset); + offset += 1; + buffer.writeUInt8(tile.reserved1, offset); + offset += 1; + buffer.writeUInt32LE(tile.deviceVersionVendor, offset); + offset += 4; + buffer.writeUInt32LE(tile.deviceVersionProduct, offset); + offset += 4; + buffer.writeUInt32LE(tile.deviceVersionVersion, offset); + offset += 4; + buffer.writeUInt32LE(tile.firmwareBuild.low, offset); + offset += 4; + buffer.writeUInt32LE(tile.firmwareBuild.high, offset); + offset += 4; + buffer.writeUInt32LE(tile.reserved2.low, offset); + offset += 4; + buffer.writeUInt32LE(tile.reserved2.high, offset); + offset += 4; + buffer.writeUInt16LE(tile.firmwareVersionMinor, offset); + offset += 2; + buffer.writeUInt16LE(tile.firmwareVersionMajor, offset); + offset += 2; + buffer.writeUInt32LE(tile.reserved3, offset); + offset += 4; + return {offset, buffer}; +}; + +/** + * @typedef {Object} OffsetTile + * @property {Number} offset - offset after the buffer + * @property {Tile} tile - Tile value + */ + +/** + * Converts the given packet specific object into a packet + * @param {Buffer} buf tile as buffer + * @param {Number} offset offset to the buffer + * @return {OffsetTile} packet + */ +Packet.toObject = function(buf, offset) { + const tile = {}; + tile.accelMeasX = buf.readUInt16LE(offset); + offset += 2; + tile.accelMeasY = buf.readUInt16LE(offset); + offset += 2; + tile.accelMeasZ = buf.readUInt16LE(offset); + offset += 2; + tile.reserved0 = buf.readUInt16LE(offset); + offset += 2; + tile.userX = buf.readFloatLE(offset); + offset += 4; + tile.userY = buf.readFloatLE(offset); + offset += 4; + tile.width = buf.readUInt8(offset); + offset += 1; + tile.height = buf.readUInt8(offset); + offset += 1; + tile.reserved1 = buf.readUInt8(offset); + offset += 1; + tile.deviceVersionVendor = buf.readUInt32LE(offset); + offset += 4; + tile.deviceVersionProduct = buf.readUInt32LE(offset); + offset += 4; + tile.deviceVersionVersion = buf.readUInt32LE(offset); + offset += 4; + tile.firmwareBuild = { + low: buf.readUInt32LE(offset), + high: buf.readUInt32LE(offset + 4) + }; + offset += 8; + tile.reserved2 = { + low: buf.readUInt32LE(offset), + high: buf.readUInt32LE(offset + 4) + }; + offset += 8; + tile.firmwareVersionMinor = buf.readUInt16LE(offset); + offset += 2; + tile.firmwareVersionMajor = buf.readUInt16LE(offset); + offset += 2; + tile.reserved3 = buf.readUInt32LE(offset); + offset += 4; + return {offset, tile}; +}; + +module.exports = Packet; diff --git a/src/lifx/validate.js b/src/lifx/validate.js index 91ae838..343cc0a 100644 --- a/src/lifx/validate.js +++ b/src/lifx/validate.js @@ -2,6 +2,7 @@ const {constants} = require('../lifx'); const {format} = require('util'); + const validate = exports; /** @@ -232,6 +233,39 @@ validate.isUInt32 = function(val, context) { return validate.isUIntRange(val, context, 0xffffffff); }; +/** + * @typedef {Object} UInt64LowHigh + * @property {Number} low - UInt16 accelMeasX + * @property {Number} high - UInt16 accelMeasX + */ +/** + * test if the given value is an uint32 value + * @param {UInt64LowHigh} val the given uint64 as low,high object + * @param {String} context the string for the error message + * @return {Boolean} const true or an exception + */ +validate.isUInt64LowHigh = function(val, context) { + if (typeof val !== 'object') { + throwTypeError('LIFX %s expects "%s" to be an object', context, val); + } + validate.isUInt32(val.low, context); + validate.isUInt32(val.high, context); + return true; +}; + +/** + * test if the given value is an float value + * @param {Number} val the given float value as number + * @param {String} context the string for the error message + * @return {Boolean} const true or an exception + */ +validate.isFloat = function(val, context) { + if (typeof val !== 'number') { + throwTypeError('LIFX %s expects "%s" to be a float', context, val); + } + return true; +}; + /** * Formats error message and throws a TypeError * @param {String} message Error message diff --git a/test/unit/light-test.js b/test/unit/light-test.js index 5b0f99f..398b34a 100644 --- a/test/unit/light-test.js +++ b/test/unit/light-test.js @@ -2,8 +2,11 @@ const Lifx = require('../../').Client; const Light = require('../../').Light; +const packet = require('../../lib/lifx').packet; const constant = require('../../').constants; const assert = require('chai').assert; +const buildTile = require('./packets/buildTile'); +const buildColors = require('./packets/buildColors'); describe('Light', () => { let client; @@ -57,7 +60,7 @@ describe('Light', () => { zoneIndex: 255 }; - beforeEach(() => { + beforeEach((done) => { client = new Lifx(); bulb = new Light({ client: client, @@ -66,6 +69,9 @@ describe('Light', () => { port: constant.LIFX_DEFAULT_PORT, seenOnDiscovery: 0 }); + client.init({ + startDiscovery: false + }, done); }); afterEach(() => { @@ -618,4 +624,211 @@ describe('Light', () => { assert.equal(getMsgHandlerLength(), currHandlerCnt + 1, 'adds a handler'); currHandlerCnt += 1; }); + + it('getDeviceChain', (done) => { + const ref = { + startIndex: 1, + tileDevices: Array(13).fill(0) + .map((_, i) => i * 103) + .map((i) => buildTile(i)) + }; + bulb.getDeviceChain((err, msg) => { + try { + assert.isFalse(Boolean(err)); + assert.equal(msg.startIndex, ref.startIndex); + msg.tileDevices.forEach((tile, i) => { + tile.userX = ref.tileDevices[i].userX; + tile.userY = ref.tileDevices[i].userY; + }); + assert.deepEqual(msg.tileDevices, ref.tileDevices); + done(); + } catch (e) { + done(e); + } + }); + const mq = client.getMessageQueue(); + assert.equal(36, mq[mq.length - 1].data.length); + assert.equal(701, mq[mq.length - 1].data.readUInt16LE(32)); + client.socket.emit('message', + packet.toBuffer(packet.create('stateDeviceChain', ref, client.source)), { + address: '127.0.47.11' + }); + }); + + it('setUserPosition', (done) => { + const ref = { + tileIndex: 4, + reserved: 0x5005, + userX: 6.0006, + userY: 7.0007 + }; + bulb.setUserPosition(ref, (err, msg) => { + try { + assert.isFalse(Boolean(err)); + assert.equal(msg.type, 'acknowledgement'); + done(); + } catch (e) { + done(e); + } + }); + const mq = client.getMessageQueue(); + // assert.equal(36, mq[mq.length - 1].data.length); + // assert.equal(706, mq[mq.length - 1].data.readUInt16LE(32)); + const msg = packet.toObject(mq[mq.length - 1].data); + assert.equal(msg.type, 703); + assert.equal(msg.tileIndex, ref.tileIndex); + assert.equal(msg.reserved, ref.reserved); + assert.isTrue(Math.abs(msg.userX - ref.userX) < 0.0001, 'userX'); + assert.isTrue(Math.abs(msg.userY - ref.userY) < 0.0001, 'userY'); + client.socket.emit('message', + packet.toBuffer(packet.create('acknowledgement', { + sequence: msg.sequence + }, client.source)), { + address: '127.0.47.11' + }); + }); + + it('getTileState64 without options', (done) => { + const ref = { + tileIndex: 4, + reserved: 0x0, + x: 0, + y: 0, + width: 8, + colors: buildColors() + }; + bulb.getTileState64(ref.tileIndex, (err, msg) => { + try { + assert.isFalse(Boolean(err)); + assert.deepEqual(msg.colors, ref.colors); + assert.equal(msg.tileIndex, ref.tileIndex); + assert.equal(msg.reserved, ref.reserved); + assert.equal(msg.x, ref.x); + assert.equal(msg.y, ref.y); + assert.equal(msg.width, ref.width); + done(); + } catch (e) { + done(e); + } + }); + const mq = client.getMessageQueue(); + const msg = packet.toObject(mq[mq.length - 1].data); + assert.equal(msg.type, 707); + assert.equal(msg.tileIndex, ref.tileIndex); + assert.equal(msg.reserved, ref.reserved); + assert.equal(msg.x, ref.x); + assert.equal(msg.y, ref.y); + assert.equal(msg.width, ref.width); + client.socket.emit('message', + packet.toBuffer(packet.create('stateTileState64', ref, client.source)), + {address: '127.0.47.11'}); + }); + + it('getTileState64 with options', (done) => { + const ref = { + tileIndex: 4, + reserved: 0x50, + x: 6, + y: 7, + width: 8, + colors: buildColors() + }; + bulb.getTileState64(ref.tileIndex, ref, (err, msg) => { + try { + assert.isFalse(Boolean(err)); + assert.deepEqual(msg.colors, ref.colors, 'colors'); + assert.equal(msg.tileIndex, ref.tileIndex, 'tileIndex'); + assert.equal(msg.reserved, ref.reserved, 'reserved'); + assert.equal(msg.x, ref.x, 'x'); + assert.equal(msg.y, ref.y, 'y'); + assert.equal(msg.width, ref.width, 'width'); + done(); + } catch (e) { + done(e); + } + }); + const mq = client.getMessageQueue(); + // assert.equal(36, mq[mq.length - 1].data.length); + // assert.equal(706, mq[mq.length - 1].data.readUInt16LE(32)); + const msg = packet.toObject(mq[mq.length - 1].data); + assert.equal(msg.type, 707); + assert.equal(msg.tileIndex, ref.tileIndex); + assert.equal(msg.reserved, ref.reserved); + assert.equal(msg.x, ref.x); + assert.equal(msg.y, ref.y); + assert.equal(msg.width, ref.width); + client.socket.emit('message', + packet.toBuffer(packet.create('stateTileState64', ref, client.source)), + {address: '127.0.47.11'}); + }); + + it('setTileState64 without options', (done) => { + const colors = buildColors(); + bulb.setTileState64(1, colors, (err, msg) => { + try { + assert.isFalse(Boolean(err)); + assert.equal(msg.type, 'acknowledgement'); + done(); + } catch (e) { + done(e); + } + }); + const mq = client.getMessageQueue(); + const msg = packet.toObject(mq[mq.length - 1].data); + assert.equal(msg.type, 715); + // console.log(msg); + assert.equal(msg.tileIndex, 1); + assert.equal(msg.length, 64); + assert.equal(msg.reserved, 0); + assert.equal(msg.x, 0); + assert.equal(msg.y, 0); + assert.equal(msg.width, 8, 'width'); + assert.equal(msg.duration, 0, 'duration'); + assert.deepEqual(msg.colors, colors); + client.socket.emit('message', + packet.toBuffer(packet.create('acknowledgement', { + sequence: msg.sequence + }, client.source)), { + address: '127.0.47.11' + }); + }); + + it('setTileState64 with options', (done) => { + const ref = { + reserved: 0x50, + x: 6, + y: 7, + width: 8, + duration: 0x9009 + }; + const colors = buildColors(); + + bulb.setTileState64(4, colors, ref, (err, msg) => { + try { + assert.isFalse(Boolean(err)); + assert.equal(msg.type, 'acknowledgement'); + done(); + } catch (e) { + done(e); + } + }); + const mq = client.getMessageQueue(); + const msg = packet.toObject(mq[mq.length - 1].data); + assert.equal(msg.type, 715); + // console.log(msg); + assert.equal(msg.tileIndex, 4); + assert.equal(msg.length, 64); + assert.equal(msg.reserved, 0x50); + assert.equal(msg.x, 6); + assert.equal(msg.y, 7); + assert.equal(msg.width, 8, 'width'); + assert.equal(msg.duration, 0x9009, 'duration'); + assert.deepEqual(msg.colors, colors); + client.socket.emit('message', + packet.toBuffer(packet.create('acknowledgement', { + sequence: msg.sequence + }, client.source)), { + address: '127.0.47.11' + }); + }); }); diff --git a/test/unit/packets/buildColors.js b/test/unit/packets/buildColors.js new file mode 100644 index 0000000..30c50ec --- /dev/null +++ b/test/unit/packets/buildColors.js @@ -0,0 +1,11 @@ + +function buildColors() { + return Array(64).fill(undefined).map((_, i) => ({ + hue: (i << 8) | 1, + saturation: (i << 8) | 2, + brightness: (i << 8) | 3, + kelvin: (i << 8) | 4 + })); +} + +module.exports = buildColors; diff --git a/test/unit/packets/buildTile.js b/test/unit/packets/buildTile.js new file mode 100644 index 0000000..7ca10e3 --- /dev/null +++ b/test/unit/packets/buildTile.js @@ -0,0 +1,29 @@ +function buildTile(base) { + return { + accelMeasX: base + 0, + accelMeasY: base + 2, + accelMeasZ: base + 4, + reserved0: base + 6, + userX: base + 8 + 0.000123, + userY: base + 12 + 0.000123, + width: (base + 16) & 0xff, + height: (base + 17) & 0xff, + reserved1: (base + 18) & 0xff, + deviceVersionVendor: base + 19, + deviceVersionProduct: base + 23, + deviceVersionVersion: base + 27, + firmwareBuild: { + low: base + 31, + high: base + 35 + }, + reserved2: { + low: base + 39, + high: base + 43 + }, + firmwareVersionMinor: base + 47, + firmwareVersionMajor: base + 49, + reserved3: base + 51 + }; +} + +module.exports = buildTile; diff --git a/test/unit/packets/getTileState64.js b/test/unit/packets/getTileState64.js index 924fdb1..2f1b5c2 100644 --- a/test/unit/packets/getTileState64.js +++ b/test/unit/packets/getTileState64.js @@ -4,24 +4,27 @@ const Packet = require('../../../lib/lifx').packet; const assert = require('chai').assert; describe('Packet getTileState64', () => { - describe('create', () => { - it('getTileState64', () => { - const packet = Packet.create('getTileState64', { - tileIndex: 1, - length: 2, - reserved: 3, - x: 4, - y: 5, - width: 6 - }); - assert.equal(packet.size, 42); - assert.equal(packet.type, 707); - assert.equal(packet.tileIndex, 1); - assert.equal(packet.length, 2); - assert.equal(packet.reserved, 3); - assert.equal(packet.x, 4); - assert.equal(packet.y, 5); - assert.equal(packet.width, 6); + it('toBuffer->toObject', () => { + const ref = Packet.create('getTileState64', { + tileIndex: 1, + length: 2, + reserved: 3, + x: 4, + y: 5, + width: 6 }); + + const msg = Packet.create('getTileState64', ref); + const buf = Packet.toBuffer(msg); + const packet = Packet.toObject(buf); + + assert.equal(packet.size, 42); + assert.equal(packet.type, 707); + assert.equal(packet.tileIndex, ref.tileIndex); + assert.equal(packet.length, ref.length); + assert.equal(packet.reserved, ref.reserved); + assert.equal(packet.x, ref.x); + assert.equal(packet.y, ref.y); + assert.equal(packet.width, ref.width); }); }); diff --git a/test/unit/packets/hsbk.js b/test/unit/packets/hsbk.js new file mode 100644 index 0000000..974e641 --- /dev/null +++ b/test/unit/packets/hsbk.js @@ -0,0 +1,23 @@ +'use strict'; + +const hsbk = require('../../../lib/lifx/packets').hsbk; +const assert = require('chai').assert; + +describe('hsbk', () => { + it('toBuffer -> toObject', () => { + // debugger; + const ref = { + hue: 0x0102, + saturation: 0x0304, + brightness: 0x0506, + kelvin: 0x0708 + }; + const buf = Buffer.alloc(hsbk.size); + const out = hsbk.toBuffer(buf, 0, ref); + assert.equal(out.offset, 8); + assert.equal(out.buffer, buf); + const msg = hsbk.toObject(buf, 0); + assert.equal(msg.offset, 8); + assert.deepEqual(msg.hsbk, ref); + }); +}); diff --git a/test/unit/packets/setTileState64.js b/test/unit/packets/setTileState64.js index a4002de..c3c1fde 100644 --- a/test/unit/packets/setTileState64.js +++ b/test/unit/packets/setTileState64.js @@ -1,102 +1,50 @@ 'use strict'; -const packet = require('../../../lib/lifx').packet; -// const Client = require('../../../lib/lifx').Client; -// const Light = require('../../../lib/lifx').Light; -// const constants = require('../../../lib/lifx').constants; +const Packet = require('../../../lib/lifx').packet; const assert = require('chai').assert; -/* -function dummy() { -let client; - const packetSendCallback = (msg) => { - console.log('XXXXXXXXXX', msg); - done(); - }; -function getMsgQueueLength(queueAddress) { - return client.getMessageQueue(queueAddress).length; -} -const lightProps = { - client: client, - id: 'f37a4311b857', - address: '192.168.0.1', - port: constants.LIFX_DEFAULT_PORT, - seenOnDiscovery: 0 -}; -beforeEach(() => { - client = new Client(); - client.devices.f37a4311b857 = new Light(lightProps); -}); -console.log(Object.keys(packetObj)); - // const packetObj = packet.create('setPower', {level: 65535}, client.source); - const queueAddress = client.broadcastAddress; - -client.init({ - port: constants.LIFX_DEFAULT_PORT, - startDiscovery: false -}, () => { - client.socket.on('message', packetSendCallback); - let currMsgQueCnt = getMsgQueueLength(queueAddress); - client.send(packetObj); - assert.equal(getMsgQueueLength(queueAddress), currMsgQueCnt + 1, 'sends a packet to the queue'); - currMsgQueCnt += 1; - assert.isDefined(client.sendTimers[queueAddress]); - client.stopSendingProcess(); // We don't want automatic calling of sending - const sendingProcess = client.sendingProcess(queueAddress); - sendingProcess(); // Call sending it manualy - assert.equal(getMsgQueueLength(queueAddress), currMsgQueCnt - 1, 'removes the packet when send'); - currMsgQueCnt -= 1; -}); -} -*/ - describe('Packet setTileState64', () => { - describe('toBuffer', () => { - it('setTileState64', () => { - const obj = { - type: 'setTileState64', - tileIndex: 1, - length: 2, - reserved: 3, - x: 4, - y: 5, - width: 6, - duration: 7, - colors: [ - { - saturation: 0x8000, - brightness: 0x8001, - kelvin: 3500, - hue: 49 - }, - { - saturation: 0x6000, - brightness: 0x6001, - kelvin: 4500, - hue: 59 - } - ] - }; - const buf = packet.toBuffer(obj); - assert.equal(buf.length, 62); - assert.equal(buf.readUInt32LE(32), 715); - assert.equal(buf.readUInt8(36), obj.tileIndex); - assert.equal(buf.readUInt8(37), obj.length); - assert.equal(buf.readUInt8(38), obj.reserved); - assert.equal(buf.readUInt8(39), obj.x); - assert.equal(buf.readUInt8(40), obj.y); - assert.equal(buf.readUInt8(41), obj.width); - assert.equal(buf.readUInt32LE(42), obj.duration); + it('toBuffer->toObject', () => { + const ref = { + tileIndex: 1, + length: 2, + reserved: 3, + x: 4, + y: 5, + width: 6, + duration: 7, + colors: [ + { + saturation: 0x8000, + brightness: 0x8001, + kelvin: 3500, + hue: 49 + }, + { + saturation: 0x6000, + brightness: 0x6001, + kelvin: 4500, + hue: 59 + } + ] + }; - assert.equal(buf.readUInt16LE(46), obj.colors[0].hue); - assert.equal(buf.readUInt16LE(48), obj.colors[0].saturation); - assert.equal(buf.readUInt16LE(50), obj.colors[0].brightness); - assert.equal(buf.readUInt16LE(52), obj.colors[0].kelvin); + const msg = Packet.create('setTileState64', ref); + const buf = Packet.toBuffer(msg); + const parsedMsg = Packet.toObject(buf); - assert.equal(buf.readUInt16LE(54), obj.colors[1].hue); - assert.equal(buf.readUInt16LE(56), obj.colors[1].saturation); - assert.equal(buf.readUInt16LE(58), obj.colors[1].brightness); - assert.equal(buf.readUInt16LE(60), obj.colors[1].kelvin); - }); + assert.equal(parsedMsg.type, 715); + assert.equal(parsedMsg.tileIndex, ref.tileIndex); + assert.equal(parsedMsg.length, ref.length); + assert.equal(parsedMsg.reserved, ref.reserved); + assert.equal(parsedMsg.x, ref.x); + assert.equal(parsedMsg.y, ref.y); + assert.equal(parsedMsg.width, ref.width); + assert.equal(parsedMsg.duration, ref.duration); + assert.deepEqual(parsedMsg.colors, + ref.colors.concat(Array(64 - ref.colors.length).fill({ + hue: 0, saturation: 0, brightness: 0, kelvin: 0 + })) + ); }); }); diff --git a/test/unit/packets/setUserPosition.js b/test/unit/packets/setUserPosition.js new file mode 100644 index 0000000..924d65d --- /dev/null +++ b/test/unit/packets/setUserPosition.js @@ -0,0 +1,23 @@ +'use strict'; + +const Packet = require('../../../lib/lifx').packet; +const assert = require('chai').assert; + +describe('Packet setUserPosition', () => { + it('toBuffer -> toObject', () => { + const ref = { + tileIndex: 17, + reserved: 19, + userX: 4.0004, + userY: 5.0005 + }; + const msg = Packet.create('setUserPosition', ref); + const buf = Packet.toBuffer(msg); + const parsedMsg = Packet.toObject(buf); + assert.equal(parsedMsg.type, 703); + assert.equal(parsedMsg.tileIndex, ref.tileIndex); + assert.equal(parsedMsg.reserved, ref.reserved); + assert.isTrue(Math.abs(parsedMsg.userX - ref.userX) < 0.0001); + assert.isTrue(Math.abs(parsedMsg.userY - ref.userY) < 0.0001); + }); +}); diff --git a/test/unit/packets/stateDeviceChain.js b/test/unit/packets/stateDeviceChain.js index a92ab7c..17034fd 100644 --- a/test/unit/packets/stateDeviceChain.js +++ b/test/unit/packets/stateDeviceChain.js @@ -2,94 +2,29 @@ const Packet = require('../../../lib/lifx').packet; const assert = require('chai').assert; - -function buildDevice(base) { - return { - accelMeasX: base + 0, - accelMeasY: base + 2, - accelMeasZ: base + 4, - reserved0: base + 6, - userX: base + 8, - userY: base + 12, - width: (base + 16) & 0xff, - height: (base + 17) & 0xff, - reserved1: (base + 18) & 0xff, - deviceVersionVendor: base + 19, - deviceVersionProduct: base + 23, - deviceVersionVersion: base + 27, - firmwareBuild: { - low: base + 31, - high: base + 35 - }, - reserved2: { - low: base + 39, - high: base + 43 - }, - firmwareVersionMinor: base + 47, - firmwareVersionMajor: base + 49, - reserved3: base + 51 - }; -} - -function buildDeviceMsg(msg, offset) { - msg.writeUInt16LE(offset, offset); - offset += 2; - msg.writeUInt16LE(offset, offset); - offset += 2; - msg.writeUInt16LE(offset, offset); - offset += 2; - msg.writeUInt16LE(offset, offset); - offset += 2; - msg.writeUInt32LE(offset, offset); - offset += 4; - msg.writeUInt32LE(offset, offset); - offset += 4; - msg.writeUInt8(offset & 0xff, offset); - offset += 1; - msg.writeUInt8(offset & 0xff, offset); - offset += 1; - msg.writeUInt8(offset & 0xff, offset); - offset += 1; - msg.writeUInt32LE(offset, offset); - offset += 4; - msg.writeUInt32LE(offset, offset); - offset += 4; - msg.writeUInt32LE(offset, offset); - offset += 4; - msg.writeUInt32LE(offset, offset); - offset += 4; - msg.writeUInt32LE(offset, offset); - offset += 4; - msg.writeUInt32LE(offset, offset); - offset += 4; - msg.writeUInt32LE(offset, offset); - offset += 4; - msg.writeUInt16LE(offset, offset); - offset += 2; - msg.writeUInt16LE(offset, offset); - offset += 2; - msg.writeUInt32LE(offset, offset); - offset += 4; - return offset; -} +const buildTile = require('./buildTile'); describe('Packet stateDeviceChain', () => { - describe('toObject', () => { - it('stateDeviceChain', () => { - const msg = Buffer.alloc(882 + 36); - msg.writeInt16LE(702, 32); - msg.writeInt8(1, 36 + 0); - msg.writeInt8(2, 36 + 881); - for (let offset = 36 + 1; offset < 36 + 881; offset += 0) { - offset = buildDeviceMsg(msg, offset); - } - const parsedMsg = Packet.toObject(msg); - assert.equal(parsedMsg.startIndex, 1); - assert.equal(parsedMsg.type, 702); - assert.equal(parsedMsg.totalCount, 2); - assert.deepEqual(parsedMsg.tileDevices, (new Array(16)) - .fill(undefined) - .map((_, idx) => buildDevice(37 + (idx * 55)))); + it('toObject->toBuffer', () => { + const ref = { + startIndex: 1, + tileDevices: Array(13).fill(0) + .map((_, i) => i * 103) + .map((i) => buildTile(i)) + }; + const msg = Packet.create('stateDeviceChain', ref); + const buf = Packet.toBuffer(msg); + const parsedMsg = Packet.toObject(buf); + + assert.equal(parsedMsg.startIndex, 1); + assert.equal(parsedMsg.type, 702); + assert.equal(parsedMsg.totalCount, ref.tileDevices.length); + parsedMsg.tileDevices.forEach((tile, idx) => { + assert.isTrue(Math.abs(tile.userX - ref.tileDevices[idx].userX) < 0.0001); + assert.isTrue(Math.abs(tile.userY - ref.tileDevices[idx].userY) < 0.0001); + tile.userX = ref.tileDevices[idx].userX; + tile.userY = ref.tileDevices[idx].userY; }); + assert.deepEqual(parsedMsg.tileDevices, ref.tileDevices); }); }); diff --git a/test/unit/packets/stateTileState64.js b/test/unit/packets/stateTileState64.js index b5d4f3e..5b3cf9a 100644 --- a/test/unit/packets/stateTileState64.js +++ b/test/unit/packets/stateTileState64.js @@ -2,51 +2,28 @@ const Packet = require('../../../lib/lifx').packet; const assert = require('chai').assert; - -function buildTileMsg(msg, offset) { - msg.writeUInt16LE(offset, offset); - offset += 2; - msg.writeUInt16LE(offset, offset); - offset += 2; - msg.writeUInt16LE(offset, offset); - offset += 2; - msg.writeUInt16LE(offset, offset); - offset += 2; - return offset; -} - -function buildTile(base) { - return { - hue: base, - saturation: base + 2, - brightness: base + 4, - kelvin: base + 6 - }; -} +const buildColors = require('./buildColors'); describe('Packet stateTileState64', () => { - describe('toBuffer', () => { - it('stateTileState64', () => { - const msg = Buffer.alloc(517 + 36); - msg.writeInt16LE(711, 32); - msg.writeInt8(1, 36 + 0); - msg.writeInt8(2, 36 + 1); - msg.writeInt8(3, 36 + 2); - msg.writeInt8(4, 36 + 3); - msg.writeInt8(5, 36 + 4); - for (let offset = 36 + 5; offset < 36 + 517; offset += 0) { - offset = buildTileMsg(msg, offset); - } - const parsedMsg = Packet.toObject(msg); - assert.equal(parsedMsg.type, 711); - assert.deepEqual(parsedMsg.tileIndex, 1); - assert.deepEqual(parsedMsg.reserved, 2); - assert.deepEqual(parsedMsg.x, 3); - assert.deepEqual(parsedMsg.y, 4); - assert.deepEqual(parsedMsg.width, 5); - assert.deepEqual(parsedMsg.colors, (new Array(64)) - .fill(undefined) - .map((_, idx) => buildTile(36 + 5 + (idx * 8)))); - }); + it('toObject->toBuffer', () => { + const ref = { + tileIndex: 1, + reserved: 2, + x: 3, + y: 4, + width: 5, + colors: buildColors() + }; + const msg = Packet.create('stateTileState64', ref); + const buf = Packet.toBuffer(msg); + const parsedMsg = Packet.toObject(buf); + + assert.equal(parsedMsg.type, 711); + assert.deepEqual(parsedMsg.tileIndex, ref.tileIndex); + assert.deepEqual(parsedMsg.reserved, ref.reserved); + assert.deepEqual(parsedMsg.x, ref.x); + assert.deepEqual(parsedMsg.y, ref.y); + assert.deepEqual(parsedMsg.width, ref.width); + assert.deepEqual(parsedMsg.colors, ref.colors); }); }); diff --git a/test/unit/packets/tile.js b/test/unit/packets/tile.js new file mode 100644 index 0000000..4a974c9 --- /dev/null +++ b/test/unit/packets/tile.js @@ -0,0 +1,23 @@ +'use strict'; + +const tile = require('../../../lib/lifx/packets').tile; +const assert = require('chai').assert; +const buildTile = require('./buildTile'); + +describe('tile', () => { + it('toBuffer -> toObject', () => { + // debugger; + const ref = buildTile(213); + const buf = Buffer.alloc(tile.size); + const out = tile.toBuffer(buf, 0, ref); + assert.equal(out.offset, 55); + assert.equal(out.buffer, buf); + const msg = tile.toObject(buf, 0); + assert.equal(msg.offset, 55); + assert.isTrue(Math.abs(ref.userX - msg.tile.userX) < 0.00001); + assert.isTrue(Math.abs(ref.userY - msg.tile.userY) < 0.00001); + ref.userX = msg.tile.userX; + ref.userY = msg.tile.userY; + assert.deepEqual(msg.tile, ref); + }); +}); diff --git a/test/unit/validate-test.js b/test/unit/validate-test.js index c0d870e..626b846 100644 --- a/test/unit/validate-test.js +++ b/test/unit/validate-test.js @@ -44,4 +44,20 @@ describe('Validation', () => { assert.throw(() => validate.isXY('hallo', 4)); assert.throw(() => validate.isXY(4, 'hallo')); }); + + it('isUInt64LowHigh', () => { + assert.isTrue(validate.isUInt64LowHigh({ + low: 0x10002000, + high: 0x20001000 + })); + assert.throw(() => validate.isUInt64LowHigh()); + assert.throw(() => validate.isUInt64LowHigh('hallo')); + assert.throw(() => validate.isUInt64LowHigh({ })); + assert.throw(() => validate.isUInt64LowHigh({ + low: 1 + })); + assert.throw(() => validate.isUInt64LowHigh({ + high: 1 + })); + }); }); From 670c0912b39ae017a40e75ec676e13cd1b41b9df Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Fri, 27 Dec 2019 00:16:43 +0100 Subject: [PATCH 6/9] chore: cleanup the defaultOptionsTileState64 method --- src/lifx/light.js | 115 ++++++++++++++++------------------------------ 1 file changed, 40 insertions(+), 75 deletions(-) diff --git a/src/lifx/light.js b/src/lifx/light.js index e8d96f6..0c84a19 100644 --- a/src/lifx/light.js +++ b/src/lifx/light.js @@ -571,29 +571,22 @@ Light.prototype.setUserPosition = function(vals, callback) { }; function defaultOptionsTileState64(options) { - const ret = { - length: options.length, - x: options.x, - y: options.y, - width: options.width, - reserved: options.reserved, - duration: options.duration - }; - if (typeof ret.length !== 'number') { - ret.length = 64; - } - if (typeof ret.width !== 'number') { - ret.width = 8; - } - if (typeof ret.x !== 'number') { - ret.x = 0; - } - if (typeof ret.y !== 'number') { - ret.y = 0; - } - if (typeof ret.duration !== 'number') { - ret.duration = 0; - } + const ret = Object.assign({ + tileIndex: 0, + length: 64, + width: 8, + x: 0, + y: 0, + duration: 0, + reserved: 0 + }, options); + validate.isUInt8(ret.tileIndex, 'TileState64:tileIndex'); + validate.isUInt8(ret.length, 'TileState64:length'); + validate.isUInt8(ret.width, 'TileState64:width'); + validate.isUInt8(ret.x, 'TileState64:x'); + validate.isUInt8(ret.y, 'TileState64:y'); + validate.isUInt32(ret.duration, 'TileState64:duration'); + validate.isUInt8(ret.reserved, 'TileState64:length'); return ret; } @@ -607,40 +600,22 @@ function defaultOptionsTileState64(options) { * from the tileIndex. This will result in a separate response from * each tile. * @param {Number} tileIndex unsigned 8-bit integer - * @param {Object} options - options passed to - * @param {Number} options.length unsigned 8-bit integer (default 64) - * @param {Number} options.x unsigned 8-bit integer (default 0) - * @param {Number} options.y unsigned 8-bit integer (default 0) - * @param {Number} options.width unsigned 8-bit integer (default 8) - * @param {Number} options.reserved unsigned 8-bit integer + * @param {GetTileState64} optionsOrCallback - tileState ignore tileIndex * @param {Function} callback a function to accept the data */ -Light.prototype.getTileState64 = function(tileIndex, options, callback) { - validate.isUInt8(tileIndex, 'getTileState64', 'tileIndex'); - if (typeof options === 'function') { - callback = options; - options = {}; +Light.prototype.getTileState64 = function(tileIndex, optionsOrCallback, callback) { + const options = {tileIndex}; + if (typeof optionsOrCallback === 'function') { + callback = optionsOrCallback; + } else { + Object.assign(options, optionsOrCallback); } validate.callback(callback, 'light getTileState64 method'); - const {length, x, y, width, reserved} = defaultOptionsTileState64(options); - const packetObj = packet.create('getTileState64', { - tileIndex, - length, - reserved: reserved || 0, - x, - y, - width - }, - this.client.source - ); + const packetObj = packet.create('getTileState64', + defaultOptionsTileState64(options), this.client.source); packetObj.target = this.id; const sqnNumber = this.client.send(packetObj); - this.client.addMessageHandler('stateTileState64', function(err, msg) { - if (err) { - return callback(err, null); - } - return callback(null, msg); - }, sqnNumber); + this.client.addMessageHandler('stateTileState64', callback, sqnNumber); }; /** @@ -650,35 +625,25 @@ Light.prototype.getTileState64 = function(tileIndex, options, callback) { * and y to zero, and width to 8. * @param {Number} tileIndex unsigned 8-bit integer * @param {Number} colors[64] 64 HSBK values - * @param {Object} options - options passed to - * @param {Number} options.length unsigned 8-bit integer (default 0) - * @param {Number} options.x unsigned 8-bit integer (default 8) - * @param {Number} options.y unsigned 8-bit integer (default 8) - * @param {Number} options.width unsigned 8-bit integer (default 8) - * @param {Number} options.duration unsigned 32-bit integer (default 0) - * @param {Number} options.reserved unsigned 8-bit integer + * @param {GetTileState64} optionsOrCallback - tileState ignore tileIndex * @param {Function} [callback] called when light did receive message */ -Light.prototype.setTileState64 = function(tileIndex, colors, options, callback) { +Light.prototype.setTileState64 = function(tileIndex, colors, optionsOrCallback, callback) { validate.isUInt8(tileIndex, 'setTileState64', 'tileIndex'); - if (typeof options === 'function') { - callback = options; - options = {}; + const options = {tileIndex}; + if (typeof optionsOrCallback === 'function') { + callback = optionsOrCallback; + } else { + Object.assign(options, optionsOrCallback); } - const {length, x, y, width, duration, reserved} = defaultOptionsTileState64(options); - const set64colors = utils.buildColorsHsbk(colors, 64); validate.optionalCallback(callback, 'light setTileState64 method'); - - const packetObj = packet.create('setTileState64', { - tileIndex, - length, - reserved: reserved || 0, - x, - y, - width, - duration, - colors: set64colors - }, this.client.source); + if (typeof callback !== 'function') { + callback = () => {}; + } + const packetObj = packet.create('setTileState64', + Object.assign(defaultOptionsTileState64(options), { + colors: utils.buildColorsHsbk(colors, 64) + }), this.client.source); packetObj.target = this.id; this.client.send(packetObj, callback); }; From 043f0be6b3f07aebcfa20bafdb037a8e0f352000 Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Fri, 27 Dec 2019 00:23:31 +0100 Subject: [PATCH 7/9] chore: remove new from Array(number) --- example/tile.js | 2 +- src/lifx/packets/stateTileState64.js | 2 +- src/lifx/utils.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/tile.js b/example/tile.js index 08c9874..4bfe1b8 100644 --- a/example/tile.js +++ b/example/tile.js @@ -43,7 +43,7 @@ function setBits(light, idx, chain) { console.log('setBits', idx, chain.totalCount); const ofs = ~~(Math.random() * (65536 - (65536 / 64))); light.setTileState64(idx, - (new Array(64)).fill(undefined).map((_, idx) => ({ + Array(64).fill(undefined).map((_, idx) => ({ hue: (ofs + (idx * (65536 / 64))) & 0xffff, saturation: 50000, brightness: 16384, diff --git a/src/lifx/packets/stateTileState64.js b/src/lifx/packets/stateTileState64.js index dc93073..a52ef6d 100644 --- a/src/lifx/packets/stateTileState64.js +++ b/src/lifx/packets/stateTileState64.js @@ -39,7 +39,7 @@ Packet.toObject = function(buf) { offset += 1; obj.width = buf.readUInt8(offset); offset += 1; - obj.colors = new Array(64).fill(undefined).map(() => { + obj.colors = Array(64).fill(undefined).map(() => { const ret = Packet.HSBK.toObject(buf, offset); offset = ret.offset; return ret.hsbk; diff --git a/src/lifx/utils.js b/src/lifx/utils.js index 67b95d1..9e0a109 100644 --- a/src/lifx/utils.js +++ b/src/lifx/utils.js @@ -138,7 +138,7 @@ utils.buildColorsHsbk = function(colors, size) { if (typeof size !== 'number') { size = 0; } - return (new Array(size)) + return Array(size) .fill(undefined) .map((_, idx) => this.toColorHsbk(colors[idx] || {})); }; From 65e73abe4a3f20d10c8f8b375cc68ed6b6394000 Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Fri, 27 Dec 2019 00:29:15 +0100 Subject: [PATCH 8/9] chore: cleanup the callback processing --- src/lifx/light.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lifx/light.js b/src/lifx/light.js index 0c84a19..31bca64 100644 --- a/src/lifx/light.js +++ b/src/lifx/light.js @@ -539,12 +539,7 @@ Light.prototype.getDeviceChain = function(callback) { const packetObj = packet.create('getDeviceChain', {}, this.client.source); packetObj.target = this.id; const sqnNumber = this.client.send(packetObj); - this.client.addMessageHandler('stateDeviceChain', function(err, msg) { - if (err) { - return callback(err, null); - } - return callback(null, msg); - }, sqnNumber); + this.client.addMessageHandler('stateDeviceChain', callback, sqnNumber); }; /** @@ -564,6 +559,9 @@ Light.prototype.setUserPosition = function(vals, callback) { validate.isFloat(vals.userY, 'setUserPosition', 'userX'); validate.isUInt16(vals.reserved || 0, 'setUserPosition', 'reserved'); validate.optionalCallback(callback, 'light setUserPosition method'); + if (typeof callback !== 'function') { + callback = () => {}; + } const packetObj = packet.create('setUserPosition', vals, this.client.source); packetObj.target = this.id; From 9ba76a1b483f92f13872918037082c86269d58b2 Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Fri, 27 Dec 2019 00:41:13 +0100 Subject: [PATCH 9/9] chore: use toObject instead of direct addressing chore: remove some comment clutter --- test/unit/light-test.js | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/test/unit/light-test.js b/test/unit/light-test.js index 398b34a..9ffa5fd 100644 --- a/test/unit/light-test.js +++ b/test/unit/light-test.js @@ -647,8 +647,8 @@ describe('Light', () => { } }); const mq = client.getMessageQueue(); - assert.equal(36, mq[mq.length - 1].data.length); - assert.equal(701, mq[mq.length - 1].data.readUInt16LE(32)); + const msg = packet.toObject(mq[mq.length - 1].data); + assert.equal(msg.type, 701); client.socket.emit('message', packet.toBuffer(packet.create('stateDeviceChain', ref, client.source)), { address: '127.0.47.11' @@ -672,8 +672,6 @@ describe('Light', () => { } }); const mq = client.getMessageQueue(); - // assert.equal(36, mq[mq.length - 1].data.length); - // assert.equal(706, mq[mq.length - 1].data.readUInt16LE(32)); const msg = packet.toObject(mq[mq.length - 1].data); assert.equal(msg.type, 703); assert.equal(msg.tileIndex, ref.tileIndex); @@ -748,8 +746,6 @@ describe('Light', () => { } }); const mq = client.getMessageQueue(); - // assert.equal(36, mq[mq.length - 1].data.length); - // assert.equal(706, mq[mq.length - 1].data.readUInt16LE(32)); const msg = packet.toObject(mq[mq.length - 1].data); assert.equal(msg.type, 707); assert.equal(msg.tileIndex, ref.tileIndex); @@ -776,7 +772,6 @@ describe('Light', () => { const mq = client.getMessageQueue(); const msg = packet.toObject(mq[mq.length - 1].data); assert.equal(msg.type, 715); - // console.log(msg); assert.equal(msg.tileIndex, 1); assert.equal(msg.length, 64); assert.equal(msg.reserved, 0); @@ -786,9 +781,7 @@ describe('Light', () => { assert.equal(msg.duration, 0, 'duration'); assert.deepEqual(msg.colors, colors); client.socket.emit('message', - packet.toBuffer(packet.create('acknowledgement', { - sequence: msg.sequence - }, client.source)), { + packet.toBuffer(packet.create('acknowledgement', { }, client.source)), { address: '127.0.47.11' }); }); @@ -815,7 +808,6 @@ describe('Light', () => { const mq = client.getMessageQueue(); const msg = packet.toObject(mq[mq.length - 1].data); assert.equal(msg.type, 715); - // console.log(msg); assert.equal(msg.tileIndex, 4); assert.equal(msg.length, 64); assert.equal(msg.reserved, 0x50); @@ -825,9 +817,7 @@ describe('Light', () => { assert.equal(msg.duration, 0x9009, 'duration'); assert.deepEqual(msg.colors, colors); client.socket.emit('message', - packet.toBuffer(packet.create('acknowledgement', { - sequence: msg.sequence - }, client.source)), { + packet.toBuffer(packet.create('acknowledgement', { }, client.source)), { address: '127.0.47.11' }); });