diff --git a/example/setMultiZoneEffect.js b/example/setMultiZoneEffect.js index 105dc4b..1be2a26 100644 --- a/example/setMultiZoneEffect.js +++ b/example/setMultiZoneEffect.js @@ -4,7 +4,7 @@ * Searches for new lights, if one is found it sends a setMultiZoneEffect packet */ -const LifxClient = require('../lib/lifx').Client; +const LifxClient = require('../src/lifx').Client; const client = new LifxClient(); // Function running when packet was received by light diff --git a/example/tile.js b/example/tile.js new file mode 100644 index 0000000..48e4f67 --- /dev/null +++ b/example/tile.js @@ -0,0 +1,81 @@ +'use strict'; + +const LifxClient = require('../src/lifx').Client; + +const client = new LifxClient(); + +const LOOPTIME = 1000; +const TILE_LABEL = process.argv.length > 2 ? process.argv[process.argv.length - 1] : '*'; + +/* + * 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. + * + * EASY: You should see an updating Pattern every two second + */ + +function getBits(light, idx, chain) { + 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); + return; + } + getBits(light, idx + 1, chain); + }); +} + +function setBits(light, idx, chain) { + 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, + Array(64).fill(undefined).map((_, idx) => ({ + hue: (ofs + (idx * (65536 / 64))) & 0xffff, + saturation: 50000, + brightness: 16384, + kelvin: 4096 + })), {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; + } + console.log('New light found.'); + console.log('ID: ' + light.id); + + light.getDeviceChain(function(err, chain) { + if (err) { + console.log(err); + } + light.on(0, () => { + 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 b2858fd..ed17e1e 100644 --- a/src/lifx/light.js +++ b/src/lifx/light.js @@ -577,6 +577,123 @@ Light.prototype.colorZones = function(startIndex, endIndex, hue, saturation, bri }; /** + * 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', callback, sqnNumber); +}; + +/** + * Sets Tile Position + * @param {SetUserPosition} vals - value to set + * @param {Function} callback called when light did receive message + */ +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'); + if (typeof callback !== 'function') { + callback = () => {}; + } + + const packetObj = packet.create('setUserPosition', vals, this.client.source); + packetObj.target = this.id; + this.client.send(packetObj, callback); +}; + +function defaultOptionsTileState64(options) { + 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; +} + +/** + * 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 {GetTileState64} optionsOrCallback - tileState ignore tileIndex + * @param {Function} callback a function to accept the data + */ +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 packetObj = packet.create('getTileState64', + defaultOptionsTileState64(options), this.client.source); + packetObj.target = this.id; + const sqnNumber = this.client.send(packetObj); + this.client.addMessageHandler('stateTileState64', callback, 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 {GetTileState64} optionsOrCallback - tileState ignore tileIndex + * @param {Function} [callback] called when light did receive message + */ +Light.prototype.setTileState64 = function(tileIndex, colors, optionsOrCallback, callback) { + validate.isUInt8(tileIndex, 'setTileState64', 'tileIndex'); + const options = {tileIndex}; + if (typeof optionsOrCallback === 'function') { + callback = optionsOrCallback; + } else { + Object.assign(options, optionsOrCallback); + } + validate.optionalCallback(callback, 'light setTileState64 method'); + 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); +}; + +/* * Changes a color zone range to the given HSBK value * @param {String} effectName sets the desired effect, currently available options are: MOVE, OFF * @param {Number} speed sets duration of one cycle of the effect, the higher the value the slower the effect animation diff --git a/src/lifx/packet.js b/src/lifx/packet.js index 0b62aa6..6c6911b 100644 --- a/src/lifx/packet.js +++ b/src/lifx/packet.js @@ -87,8 +87,14 @@ Packet.typeList = [ {id: 505, name: 'stateCountZone'}, {id: 506, name: 'stateMultiZone'}, // {id: 507, name: 'getEffectZone'}, - {id: 508, name: 'setMultiZoneEffect'} - // {id: 509, name: 'stateEffectZone'} + {id: 508, name: 'setMultiZoneEffect'}, + // {id: 509, name: 'stateEffectZone'}, + {id: 701, name: 'getDeviceChain'}, + {id: 702, name: 'stateDeviceChain'}, + {id: 703, 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..d5b55eb --- /dev/null +++ b/src/lifx/packets/getTileState64.js @@ -0,0 +1,73 @@ +'use strict'; + +const {validate} = require('../../lifx'); + +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 {GetTileState64} obj object with configuration data + * @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; +}; + +/** + * 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 f8ac9c2..dd60b62 100644 --- a/src/lifx/packets/index.js +++ b/src/lifx/packets/index.js @@ -80,3 +80,13 @@ 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 new file mode 100644 index 0000000..21b0512 --- /dev/null +++ b/src/lifx/packets/setTileState64.js @@ -0,0 +1,89 @@ +'use strict'; + +const {validate} = require('../../lifx'); +const HSBK = require('./hsbk'); + +const Packet = { + size: 10 + (64 * HSBK.size), + HSBK +}; + +/** + * @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 + */ + +/** + * 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 {SetTileState64} obj object with configuration data + * @return {Buffer} packet + */ +Packet.toBuffer = function(obj) { + obj.reserved = obj.reserved || 0; + obj.duration = obj.duration || 0; + ['tileIndex', 'length', 'reserved', 'x', 'y', 'width'].forEach((field) => { + validate.isUInt8(obj[field], `setTileState64:${field}`); + }); + 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); + 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; + buf.writeUInt32LE(obj.duration, offset); + offset += 4; + obj.colors.forEach((color) => { + offset = Packet.HSBK.toBuffer(buf, offset, color).offset; + }); + return buf; +}; + +module.exports = Packet; 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 new file mode 100644 index 0000000..3706b14 --- /dev/null +++ b/src/lifx/packets/stateDeviceChain.js @@ -0,0 +1,61 @@ +'use strict'; + +const Tile = require('./tile'); +const {validate} = require('../../lifx'); + +const Packet = { + 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 {StateDeviceChain} 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); + obj.totalCount = Math.min(buf.readUInt8(buf.length - 1), 16); + offset += 1; + obj.tileDevices = Array(obj.totalCount).fill(undefined).map(() => { + const ret = Packet.Tile.toObject(buf, offset); + offset = ret.offset; + return ret.tile; + }); + 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 new file mode 100644 index 0000000..a52ef6d --- /dev/null +++ b/src/lifx/packets/stateTileState64.js @@ -0,0 +1,82 @@ +'use strict'; + +const {validate} = require('../../lifx'); +const HSBK = require('./hsbk'); + +const Packet = { + 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 {StateTileState64} 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 = 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 {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/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] || {})); }; diff --git a/src/lifx/validate.js b/src/lifx/validate.js index 7ce2916..9a769c4 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; /** @@ -202,6 +203,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 @@ -249,6 +264,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 cf1779b..8fa0b3b 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('../../src/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; @@ -60,7 +63,7 @@ describe('Light', () => { zoneIndex: 255 }; - beforeEach(() => { + beforeEach((done) => { client = new Lifx(); bulb = new Light({ client: client, @@ -69,6 +72,9 @@ describe('Light', () => { port: constant.LIFX_DEFAULT_PORT, seenOnDiscovery: 0 }); + client.init({ + startDiscovery: false + }, done); }); afterEach(() => { @@ -711,6 +717,203 @@ describe('Light', () => { 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(); + 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' + }); + }); + + 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(); + 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(); + 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); + 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', { }, 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); + 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', { }, client.source)), { + address: '127.0.47.11' + }); + }); + it('setting multizone effect', () => { let currMsgQueCnt = getMsgQueueLength(); // eslint-disable-next-line prefer-const 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/getDeviceChain.js b/test/unit/packets/getDeviceChain.js new file mode 100644 index 0000000..430168f --- /dev/null +++ b/test/unit/packets/getDeviceChain.js @@ -0,0 +1,14 @@ +'use strict'; + +const Packet = require('../../../src/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..3de4ce0 --- /dev/null +++ b/test/unit/packets/getTileState64.js @@ -0,0 +1,30 @@ +'use strict'; + +const Packet = require('../../../src/lifx').packet; +const assert = require('chai').assert; + +describe('Packet getTileState64', () => { + 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..39640c4 --- /dev/null +++ b/test/unit/packets/hsbk.js @@ -0,0 +1,23 @@ +'use strict'; + +const hsbk = require('../../../src/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 new file mode 100644 index 0000000..4e01fd1 --- /dev/null +++ b/test/unit/packets/setTileState64.js @@ -0,0 +1,50 @@ +'use strict'; + +const Packet = require('../../../src/lifx').packet; +const assert = require('chai').assert; + +describe('Packet setTileState64', () => { + 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 + } + ] + }; + + const msg = Packet.create('setTileState64', ref); + const buf = Packet.toBuffer(msg); + const parsedMsg = Packet.toObject(buf); + + 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..cc6429a --- /dev/null +++ b/test/unit/packets/setUserPosition.js @@ -0,0 +1,23 @@ +'use strict'; + +const Packet = require('../../../src/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 new file mode 100644 index 0000000..c1ea369 --- /dev/null +++ b/test/unit/packets/stateDeviceChain.js @@ -0,0 +1,30 @@ +'use strict'; + +const Packet = require('../../../src/lifx').packet; +const assert = require('chai').assert; +const buildTile = require('./buildTile'); + +describe('Packet stateDeviceChain', () => { + 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 new file mode 100644 index 0000000..cc0ad33 --- /dev/null +++ b/test/unit/packets/stateTileState64.js @@ -0,0 +1,29 @@ +'use strict'; + +const Packet = require('../../../src/lifx').packet; +const assert = require('chai').assert; +const buildColors = require('./buildColors'); + +describe('Packet stateTileState64', () => { + 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..5443e92 --- /dev/null +++ b/test/unit/packets/tile.js @@ -0,0 +1,23 @@ +'use strict'; + +const tile = require('../../../src/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 76a103d..626b846 100644 --- a/test/unit/validate-test.js +++ b/test/unit/validate-test.js @@ -37,4 +37,27 @@ 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')); + }); + + 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 + })); + }); });