diff --git a/example/tile.js b/example/tile.js new file mode 100644 index 0000000..f72386c --- /dev/null +++ b/example/tile.js @@ -0,0 +1,74 @@ +'use strict'; + +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) { + console.log('All Bits get'); + setTimeout(() => setBits(light, 0, chain), LOOPTIME); + return; + } + light.getTileState64(idx, (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, {duration: 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) => { + if (!(TILE_LABEL === '*' || TILE_LABEL === light.id)) { + return; + } + 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..ed49d61 100644 --- a/src/lifx/light.js +++ b/src/lifx/light.js @@ -529,4 +529,159 @@ 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) { + validate.isUInt8(tileIndex, 'setUserPosition', 'tileIndex'); + validate.isXY(userX, userY, 'setUserPosition', 'user'); + 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); +}; + +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 + * + * 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. + * @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 {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 = {}; + } + 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 + ); + 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} 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 {Function} [callback] called when light did receive message + */ +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(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); + 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/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/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)))); + }); + }); +}); 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')); + }); });