From 5ce6676ac49587fdd8f96fac9d14c46fc0ba4d52 Mon Sep 17 00:00:00 2001 From: Ristomatti Airo Date: Sun, 29 Oct 2017 23:46:39 +0200 Subject: [PATCH 1/7] Add Babel plugin transform-class-properties --- .babelrc | 1 + package-lock.json | 18 ++++++++++++++++++ package.json | 1 + 3 files changed, 20 insertions(+) diff --git a/.babelrc b/.babelrc index 7ead2dd..886d1ab 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,5 @@ { + "plugins": ["transform-class-properties"], "presets": [ ["env", { "targets": { diff --git a/package-lock.json b/package-lock.json index 1d8b6cb..6c8eaf7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -727,6 +727,12 @@ "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", "dev": true }, + "babel-plugin-syntax-class-properties": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", + "dev": true + }, "babel-plugin-syntax-exponentiation-operator": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", @@ -750,6 +756,18 @@ "babel-runtime": "6.23.0" } }, + "babel-plugin-transform-class-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", + "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-plugin-syntax-class-properties": "6.13.0", + "babel-runtime": "6.23.0", + "babel-template": "6.24.1" + } + }, "babel-plugin-transform-es2015-arrow-functions": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", diff --git a/package.json b/package.json index 1fc6984..1c704c1 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "babel-cli": "^6.26.0", "babel-core": "^6.26.0", "babel-eslint": "^8.0.1", + "babel-plugin-transform-class-properties": "^6.24.1", "babel-preset-env": "^1.6.1", "chai": "^4.1.2", "codecov.io": "^0.1.6", From d052a53437f4437e25c6d5b85381b3de211b3ef3 Mon Sep 17 00:00:00 2001 From: Ristomatti Airo Date: Sun, 29 Oct 2017 23:49:03 +0200 Subject: [PATCH 2/7] Refactor Packet into a class --- src/lifx.js | 2 +- src/lifx/packet.js | 568 +++++++++++++-------------- test/unit/client-test.js | 14 +- test/unit/packet-test.js | 2 +- test/unit/packets/getService-test.js | 2 +- test/unit/packets/setColor-test.js | 2 +- test/unit/packets/setPower-test.js | 2 +- 7 files changed, 296 insertions(+), 296 deletions(-) diff --git a/src/lifx.js b/src/lifx.js index ff0857b..f36e381 100644 --- a/src/lifx.js +++ b/src/lifx.js @@ -10,7 +10,7 @@ lifx.validate = require('./lifx/validate'); lifx.utils = require('./lifx/utils'); // Export packet parser -lifx.packet = require('./lifx/packet'); +lifx.Packet = require('./lifx/packet'); // Export light device object lifx.Light = require('./lifx/light').Light; diff --git a/src/lifx/packet.js b/src/lifx/packet.js index de62bb5..fa64316 100644 --- a/src/lifx/packet.js +++ b/src/lifx/packet.js @@ -20,324 +20,324 @@ const {result, find, extend, assign} = require('lodash'); type - 2 bit 00 00 */ -const Packet = {}; - -/** - * Mapping for types - * @type {Array} - */ -Packet.typeList = [ - {id: 2, name: 'getService'}, - {id: 3, name: 'stateService'}, - {id: 12, name: 'getHostInfo'}, - {id: 13, name: 'stateHostInfo'}, - {id: 14, name: 'getHostFirmware'}, - {id: 15, name: 'stateHostFirmware'}, - {id: 16, name: 'getWifiInfo'}, - {id: 17, name: 'stateWifiInfo'}, - {id: 18, name: 'getWifiFirmware'}, - {id: 19, name: 'stateWifiFirmware'}, - // {id: 20, name: 'getPower'}, // These are for device level - // {id: 21, name: 'setPower'}, // and do not support duration value - // {id: 22, name: 'statePower'}, // since that we don't use them - {id: 23, name: 'getLabel'}, - {id: 24, name: 'setLabel'}, - {id: 25, name: 'stateLabel'}, - {id: 32, name: 'getVersion'}, - {id: 33, name: 'stateVersion'}, - {id: 45, name: 'acknowledgement'}, - {id: 48, name: 'getLocation'}, - {id: 50, name: 'stateLocation'}, - {id: 51, name: 'getGroup'}, - {id: 53, name: 'stateGroup'}, - {id: 54, name: 'getOwner'}, - {id: 56, name: 'stateOwner'}, - {id: 58, name: 'echoRequest'}, - {id: 59, name: 'echoResponse'}, - // {id: 60, name: 'getStatistic'}, - // {id: 61, name: 'stateStatistic'}, - {id: 101, name: 'getLight'}, - {id: 102, name: 'setColor'}, - {id: 103, name: 'setWaveform'}, - {id: 107, name: 'stateLight'}, - {id: 110, name: 'getTemperature'}, - {id: 111, name: 'stateTemperature'}, - // {id: 113, name: 'setSimpleEvent'}, - // {id: 114, name: 'getSimpleEvent'}, - // {id: 115, name: 'stateSimpleEvent'}, - {id: 116, name: 'getPower'}, - {id: 117, name: 'setPower'}, - {id: 118, name: 'statePower'}, - // {id: 119, name: 'setWaveformOptional'}, - {id: 120, name: 'getInfrared'}, - {id: 121, name: 'stateInfrared'}, - {id: 122, name: 'setInfrared'}, - {id: 401, name: 'getAmbientLight'}, - {id: 402, name: 'stateAmbientLight'}, - // {id: 403, name: 'getDimmerVoltage'}, - // {id: 404, name: 'stateDimmerVoltage'}, - {id: 501, name: 'setColorZones'}, - {id: 502, name: 'getColorZones'}, - {id: 503, name: 'stateZone'}, - {id: 504, name: 'getCountZone'}, - {id: 505, name: 'stateCountZone'}, - {id: 506, name: 'stateMultiZone'} - // {id: 507, name: 'getEffectZone'}, - // {id: 508, name: 'setEffectZone'}, - // {id: 509, name: 'stateEffectZone'} -]; - -/** - * Parses a lifx packet header - * @param {Buffer} buf Buffer containg lifx packet including header - * @return {Object} parsed packet header - */ -Packet.headerToObject = function(buf) { - const obj = {}; - let offset = 0; - - // Frame - obj.size = buf.readUInt16LE(offset); - offset += 2; - - const frameDescription = buf.readUInt16LE(offset); - obj.addressable = (frameDescription & constants.ADDRESSABLE_BIT) !== 0; - obj.tagged = (frameDescription & constants.TAGGED_BIT) !== 0; - obj.origin = ((frameDescription & constants.ORIGIN_BITS) >> 14) !== 0; - obj.protocolVersion = (frameDescription & constants.PROTOCOL_VERSION_BITS); - offset += 2; - - obj.source = buf.toString('hex', offset, offset + 4); - offset += 4; - - // Frame address - obj.target = buf.toString('hex', offset, offset + 6); - offset += 6; +class Packet { + /** + * Mapping for types + * @type {Array} + */ + static typeList = [ + {id: 2, name: 'getService'}, + {id: 3, name: 'stateService'}, + {id: 12, name: 'getHostInfo'}, + {id: 13, name: 'stateHostInfo'}, + {id: 14, name: 'getHostFirmware'}, + {id: 15, name: 'stateHostFirmware'}, + {id: 16, name: 'getWifiInfo'}, + {id: 17, name: 'stateWifiInfo'}, + {id: 18, name: 'getWifiFirmware'}, + {id: 19, name: 'stateWifiFirmware'}, + // {id: 20, name: 'getPower'}, // These are for device level + // {id: 21, name: 'setPower'}, // and do not support duration value + // {id: 22, name: 'statePower'}, // since that we don't use them + {id: 23, name: 'getLabel'}, + {id: 24, name: 'setLabel'}, + {id: 25, name: 'stateLabel'}, + {id: 32, name: 'getVersion'}, + {id: 33, name: 'stateVersion'}, + {id: 45, name: 'acknowledgement'}, + {id: 48, name: 'getLocation'}, + {id: 50, name: 'stateLocation'}, + {id: 51, name: 'getGroup'}, + {id: 53, name: 'stateGroup'}, + {id: 54, name: 'getOwner'}, + {id: 56, name: 'stateOwner'}, + {id: 58, name: 'echoRequest'}, + {id: 59, name: 'echoResponse'}, + // {id: 60, name: 'getStatistic'}, + // {id: 61, name: 'stateStatistic'}, + {id: 101, name: 'getLight'}, + {id: 102, name: 'setColor'}, + {id: 103, name: 'setWaveform'}, + {id: 107, name: 'stateLight'}, + {id: 110, name: 'getTemperature'}, + {id: 111, name: 'stateTemperature'}, + // {id: 113, name: 'setSimpleEvent'}, + // {id: 114, name: 'getSimpleEvent'}, + // {id: 115, name: 'stateSimpleEvent'}, + {id: 116, name: 'getPower'}, + {id: 117, name: 'setPower'}, + {id: 118, name: 'statePower'}, + // {id: 119, name: 'setWaveformOptional'}, + {id: 120, name: 'getInfrared'}, + {id: 121, name: 'stateInfrared'}, + {id: 122, name: 'setInfrared'}, + {id: 401, name: 'getAmbientLight'}, + {id: 402, name: 'stateAmbientLight'}, + // {id: 403, name: 'getDimmerVoltage'}, + // {id: 404, name: 'stateDimmerVoltage'}, + {id: 501, name: 'setColorZones'}, + {id: 502, name: 'getColorZones'}, + {id: 503, name: 'stateZone'}, + {id: 504, name: 'getCountZone'}, + {id: 505, name: 'stateCountZone'}, + {id: 506, name: 'stateMultiZone'} + // {id: 507, name: 'getEffectZone'}, + // {id: 508, name: 'setEffectZone'}, + // {id: 509, name: 'stateEffectZone'} + ]; + + /** + * Parses a lifx packet header + * @param {Buffer} buf Buffer containg lifx packet including header + * @return {Object} parsed packet header + */ + static headerToObject(buf) { + const obj = {}; + let offset = 0; + + // Frame + obj.size = buf.readUInt16LE(offset); + offset += 2; + + const frameDescription = buf.readUInt16LE(offset); + obj.addressable = (frameDescription & constants.ADDRESSABLE_BIT) !== 0; + obj.tagged = (frameDescription & constants.TAGGED_BIT) !== 0; + obj.origin = ((frameDescription & constants.ORIGIN_BITS) >> 14) !== 0; + obj.protocolVersion = (frameDescription & constants.PROTOCOL_VERSION_BITS); + offset += 2; + + obj.source = buf.toString('hex', offset, offset + 4); + offset += 4; + + // Frame address + obj.target = buf.toString('hex', offset, offset + 6); + offset += 6; + + obj.reserved1 = buf.slice(offset, offset + 2); + offset += 2; + + obj.site = buf.toString('utf8', offset, offset + 6); + obj.site = obj.site.replace(/\0/g, ''); + offset += 6; + + const frameAddressDescription = buf.readUInt8(offset); + obj.ackRequired = (frameAddressDescription & constants.ACK_REQUIRED_BIT) !== 0; + obj.resRequired = (frameAddressDescription & constants.RESPONSE_REQUIRED_BIT) !== 0; + offset += 1; + + obj.sequence = buf.readUInt8(offset); + offset += 1; + + // Protocol header + obj.time = utils.readUInt64LE(buf, offset); + offset += 8; + + obj.type = buf.readUInt16LE(offset); + offset += 2; + + obj.reserved2 = buf.slice(offset, offset + 2); + offset += 2; + + return obj; + } - obj.reserved1 = buf.slice(offset, offset + 2); - offset += 2; + /** + * Parses a lifx packet + * @param {Buffer} buf Buffer with lifx packet + * @return {Object} parsed packet + */ + static toObject(buf) { + let obj = {}; + + // Try to read header of packet + try { + obj = this.headerToObject(buf); + } catch (err) { + // If this fails return with error + return err; + } - obj.site = buf.toString('utf8', offset, offset + 6); - obj.site = obj.site.replace(/\0/g, ''); - offset += 6; + if (obj.type !== undefined) { + const typeName = result(find(this.typeList, {id: obj.type}), 'name'); + if (packets[typeName] !== undefined) { + if (typeof packets[typeName].toObject === 'function') { + const specificObj = packets[typeName].toObject(buf.slice(constants.PACKET_HEADER_SIZE)); + obj = extend(obj, specificObj); + } + } + } - const frameAddressDescription = buf.readUInt8(offset); - obj.ackRequired = (frameAddressDescription & constants.ACK_REQUIRED_BIT) !== 0; - obj.resRequired = (frameAddressDescription & constants.RESPONSE_REQUIRED_BIT) !== 0; - offset += 1; + return obj; + } - obj.sequence = buf.readUInt8(offset); - offset += 1; + /** + * Creates a lifx packet header from a given object + * @param {Object} obj Object containg header configuration for packet + * @return {Buffer} packet header buffer + */ + static headerToBuffer(obj) { + const buf = new Buffer(36); + buf.fill(0); + let offset = 0; + + // Frame + buf.writeUInt16LE(obj.size, offset); + offset += 2; + + if (obj.protocolVersion === undefined) { + obj.protocolVersion = constants.PROTOCOL_VERSION_CURRENT; + } + let frameDescription = obj.protocolVersion; - // Protocol header - obj.time = utils.readUInt64LE(buf, offset); - offset += 8; + if (obj.addressable !== undefined && obj.addressable === true) { + frameDescription |= constants.ADDRESSABLE_BIT; + } else if (obj.source !== undefined && obj.source.length > 0 && obj.source !== '00000000') { + frameDescription |= constants.ADDRESSABLE_BIT; + } - obj.type = buf.readUInt16LE(offset); - offset += 2; + if (obj.tagged !== undefined && obj.tagged === true) { + frameDescription |= constants.TAGGED_BIT; + } - obj.reserved2 = buf.slice(offset, offset + 2); - offset += 2; + if (obj.origin !== undefined && obj.origin === true) { + // 0 or 1 to the 14 bit + frameDescription |= (1 << 14); + } - return obj; -}; + buf.writeUInt16LE(frameDescription, offset); + offset += 2; -/** - * Parses a lifx packet - * @param {Buffer} buf Buffer with lifx packet - * @return {Object} parsed packet - */ -Packet.toObject = function(buf) { - let obj = {}; - - // Try to read header of packet - try { - obj = this.headerToObject(buf); - } catch (err) { - // If this fails return with error - return err; - } - - if (obj.type !== undefined) { - const typeName = result(find(this.typeList, {id: obj.type}), 'name'); - if (packets[typeName] !== undefined) { - if (typeof packets[typeName].toObject === 'function') { - const specificObj = packets[typeName].toObject(buf.slice(constants.PACKET_HEADER_SIZE)); - obj = extend(obj, specificObj); + if (obj.source !== undefined && obj.source.length > 0) { + if (obj.source.length === 8) { + buf.write(obj.source, offset, 4, 'hex'); + } else { + throw new RangeError('LIFX source must be given in 8 characters'); } } - } - - return obj; -}; - -/** - * Creates a lifx packet header from a given object - * @param {Object} obj Object containg header configuration for packet - * @return {Buffer} packet header buffer - */ -Packet.headerToBuffer = function(obj) { - const buf = new Buffer(36); - buf.fill(0); - let offset = 0; + offset += 4; - // Frame - buf.writeUInt16LE(obj.size, offset); - offset += 2; - - if (obj.protocolVersion === undefined) { - obj.protocolVersion = constants.PROTOCOL_VERSION_CURRENT; - } - let frameDescription = obj.protocolVersion; - - if (obj.addressable !== undefined && obj.addressable === true) { - frameDescription |= constants.ADDRESSABLE_BIT; - } else if (obj.source !== undefined && obj.source.length > 0 && obj.source !== '00000000') { - frameDescription |= constants.ADDRESSABLE_BIT; - } + // Frame address + if (obj.target !== undefined && obj.target !== null) { + buf.write(obj.target, offset, 6, 'hex'); + } + offset += 6; - if (obj.tagged !== undefined && obj.tagged === true) { - frameDescription |= constants.TAGGED_BIT; - } + // reserved1 + offset += 2; - if (obj.origin !== undefined && obj.origin === true) { - // 0 or 1 to the 14 bit - frameDescription |= (1 << 14); - } + if (obj.site !== undefined && obj.site !== null) { + buf.write(obj.site, offset, 6, 'utf8'); + } + offset += 6; - buf.writeUInt16LE(frameDescription, offset); - offset += 2; + let frameAddressDescription = 0; + if (obj.ackRequired !== undefined && obj.ackRequired === true) { + frameAddressDescription |= constants.ACK_REQUIRED_BIT; + } - if (obj.source !== undefined && obj.source.length > 0) { - if (obj.source.length === 8) { - buf.write(obj.source, offset, 4, 'hex'); - } else { - throw new RangeError('LIFX source must be given in 8 characters'); + if (obj.resRequired !== undefined && obj.resRequired === true) { + frameAddressDescription |= constants.RESPONSE_REQUIRED_BIT; } - } - offset += 4; + buf.writeUInt8(frameAddressDescription, offset); + offset += 1; - // Frame address - if (obj.target !== undefined && obj.target !== null) { - buf.write(obj.target, offset, 6, 'hex'); - } - offset += 6; + if (typeof obj.sequence === 'number') { + buf.writeUInt8(obj.sequence, offset); + } + offset += 1; - // reserved1 - offset += 2; + // Protocol header + if (obj.time !== undefined) { + utils.writeUInt64LE(buf, offset, obj.time); + } + offset += 8; - if (obj.site !== undefined && obj.site !== null) { - buf.write(obj.site, offset, 6, 'utf8'); - } - offset += 6; + if (typeof obj.type === 'number') { + obj.type = result(find(this.typeList, {id: obj.type}), 'id'); + } else if (typeof obj.type === 'string' || obj.type instanceof String) { + obj.type = result(find(this.typeList, {name: obj.type}), 'id'); + } + if (obj.type === undefined) { + throw new Error('Unknown lifx packet of type: ' + obj.type); + } + buf.writeUInt16LE(obj.type, offset); + offset += 2; - let frameAddressDescription = 0; - if (obj.ackRequired !== undefined && obj.ackRequired === true) { - frameAddressDescription |= constants.ACK_REQUIRED_BIT; - } + // reserved2 + offset += 2; - if (obj.resRequired !== undefined && obj.resRequired === true) { - frameAddressDescription |= constants.RESPONSE_REQUIRED_BIT; + return buf; } - buf.writeUInt8(frameAddressDescription, offset); - offset += 1; - if (typeof obj.sequence === 'number') { - buf.writeUInt8(obj.sequence, offset); - } - offset += 1; + /** + * Creates a packet from a configuration object + * @param {Object} obj Object with configuration for packet + * @return {Buffer|Boolean} the packet or false in case of error + */ + static toBuffer(obj) { + if (obj.type !== undefined) { + // Map id to string if needed + if (typeof obj.type === 'number') { + obj.type = result(find(this.typeList, {id: obj.type}), 'name'); + } else if (typeof obj.type === 'string' || obj.type instanceof String) { + obj.type = result(find(this.typeList, {name: obj.type}), 'name'); + } - // Protocol header - if (obj.time !== undefined) { - utils.writeUInt64LE(buf, offset, obj.time); - } - offset += 8; + if (obj.type !== undefined) { + if (typeof packets[obj.type].toBuffer === 'function') { + const packetTypeData = packets[obj.type].toBuffer(obj); + return Buffer.concat([ + this.headerToBuffer(obj), + packetTypeData + ]); + } + return this.headerToBuffer(obj); + } + } - if (typeof obj.type === 'number') { - obj.type = result(find(this.typeList, {id: obj.type}), 'id'); - } else if (typeof obj.type === 'string' || obj.type instanceof String) { - obj.type = result(find(this.typeList, {name: obj.type}), 'id'); - } - if (obj.type === undefined) { - throw new Error('Unknown lifx packet of type: ' + obj.type); + return false; } - buf.writeUInt16LE(obj.type, offset); - offset += 2; - - // reserved2 - offset += 2; - return buf; -}; - -/** - * Creates a packet from a configuration object - * @param {Object} obj Object with configuration for packet - * @return {Buffer|Boolean} the packet or false in case of error - */ -Packet.toBuffer = function(obj) { - if (obj.type !== undefined) { - // Map id to string if needed - if (typeof obj.type === 'number') { - obj.type = result(find(this.typeList, {id: obj.type}), 'name'); - } else if (typeof obj.type === 'string' || obj.type instanceof String) { - obj.type = result(find(this.typeList, {name: obj.type}), 'name'); - } - - if (obj.type !== undefined) { - if (typeof packets[obj.type].toBuffer === 'function') { - const packetTypeData = packets[obj.type].toBuffer(obj); - return Buffer.concat([ - this.headerToBuffer(obj), - packetTypeData - ]); + /** + * Creates a new packet by the given type + * Note: This does not validate the given params + * @param {String|Number} type the type of packet to create as number or string + * @param {Object} params further settings to pass + * @param {String} [source] the source of the packet, length 8 + * @param {String} [target] the target of the packet, length 12 + * @return {Object} The prepared packet object including header + */ + static create(type, params, source, target) { + const obj = {}; + if (type !== undefined) { + // Check if type is valid + if (typeof type === 'string' || type instanceof String) { + obj.type = result(find(this.typeList, {name: type}), 'id'); + } else if (typeof type === 'number') { + const typeMatch = find(this.typeList, {id: type}); + obj.type = result(typeMatch, 'id'); + type = result(typeMatch, 'name'); } - return this.headerToBuffer(obj); + if (obj.type === undefined) { + return false; + } + } else { + return false; } - } + obj.size = constants.PACKET_HEADER_SIZE + packets[type].size; - return false; -}; - -/** - * Creates a new packet by the given type - * Note: This does not validate the given params - * @param {String|Number} type the type of packet to create as number or string - * @param {Object} params further settings to pass - * @param {String} [source] the source of the packet, length 8 - * @param {String} [target] the target of the packet, length 12 - * @return {Object} The prepared packet object including header - */ -Packet.create = function(type, params, source, target) { - const obj = {}; - if (type !== undefined) { - // Check if type is valid - if (typeof type === 'string' || type instanceof String) { - obj.type = result(find(this.typeList, {name: type}), 'id'); - } else if (typeof type === 'number') { - const typeMatch = find(this.typeList, {id: type}); - obj.type = result(typeMatch, 'id'); - type = result(typeMatch, 'name'); + if (source !== undefined) { + obj.source = source; } - if (obj.type === undefined) { - return false; + if (target !== undefined) { + obj.target = target; + } + if (packets[type].tagged !== undefined) { + obj.tagged = packets[type].tagged; } - } else { - return false; - } - obj.size = constants.PACKET_HEADER_SIZE + packets[type].size; - if (source !== undefined) { - obj.source = source; - } - if (target !== undefined) { - obj.target = target; + return assign(obj, params); } - if (packets[type].tagged !== undefined) { - obj.tagged = packets[type].tagged; - } - - return assign(obj, params); -}; +} module.exports = Packet; diff --git a/test/unit/client-test.js b/test/unit/client-test.js index 33a352f..2a36d4f 100644 --- a/test/unit/client-test.js +++ b/test/unit/client-test.js @@ -2,7 +2,7 @@ const Client = require('../../').Client; const Light = require('../../').Light; -const packet = require('../../').packet; +const Packet = require('../../').Packet; const constants = require('../../').constants; const assert = require('chai').assert; const lolex = require('lolex'); @@ -326,17 +326,17 @@ suite('Client', () => { }, () => { assert.equal(client.sequenceNumber, 0, 'starts sequence with 0'); assert.lengthOf(client.messagesQueue, 0, 'is empty'); - client.send(packet.create('getService', {}, '12345678')); + client.send(Packet.create('getService', {}, '12345678')); assert.equal(client.sequenceNumber, 0, 'sequence is the same after broadcast'); assert.lengthOf(client.messagesQueue, 1, 'added to message queue'); assert.property(client.messagesQueue[0], 'data', 'has data'); assert.notProperty(client.messagesQueue[0], 'address', 'broadcast has no target address'); - client.send(packet.create('setPower', {level: 65535, duration: 0, target: 'f37a4311b857'}, '12345678')); + client.send(Packet.create('setPower', {level: 65535, duration: 0, target: 'f37a4311b857'}, '12345678')); assert.equal(client.sequenceNumber, 1, 'sequence increased after specific targeting'); client.sequenceNumber = constants.PACKET_HEADER_SEQUENCE_MAX; - client.send(packet.create('setPower', {level: 65535, duration: 0, target: 'f37a4311b857'}, '12345678')); + client.send(Packet.create('setPower', {level: 65535, duration: 0, target: 'f37a4311b857'}, '12345678')); assert.equal(client.sequenceNumber, 0, 'sequence starts over after maximum'); done(); }); @@ -550,7 +550,7 @@ suite('Client', () => { } done(); }; - const packetObj = packet.create('setPower', {level: 65535}, client.source); + const packetObj = Packet.create('setPower', {level: 65535}, client.source); client.init({ port: constants.LIFX_DEFAULT_PORT, @@ -578,7 +578,7 @@ suite('Client', () => { } done(); }; - const packetObj = packet.create('setPower', {level: 65535}, client.source); + const packetObj = Packet.create('setPower', {level: 65535}, client.source); client.init({ port: constants.LIFX_DEFAULT_PORT, @@ -608,7 +608,7 @@ suite('Client', () => { assert.isNull(rinfo); done(); }; - const packetObj = packet.create('setPower', {level: 65535}, client.source); + const packetObj = Packet.create('setPower', {level: 65535}, client.source); client.init({ startDiscovery: false diff --git a/test/unit/packet-test.js b/test/unit/packet-test.js index bfdd66d..4fcc64c 100644 --- a/test/unit/packet-test.js +++ b/test/unit/packet-test.js @@ -1,6 +1,6 @@ 'use strict'; -const Packet = require('../../').packet; +const Packet = require('../../').Packet; const assert = require('chai').assert; suite('Packet', () => { diff --git a/test/unit/packets/getService-test.js b/test/unit/packets/getService-test.js index 69972ed..211715f 100644 --- a/test/unit/packets/getService-test.js +++ b/test/unit/packets/getService-test.js @@ -1,6 +1,6 @@ 'use strict'; -const Packet = require('../../../').packet; +const Packet = require('../../../').Packet; const assert = require('chai').assert; suite('Packet getService', () => { diff --git a/test/unit/packets/setColor-test.js b/test/unit/packets/setColor-test.js index 572976c..35c4f8a 100644 --- a/test/unit/packets/setColor-test.js +++ b/test/unit/packets/setColor-test.js @@ -1,6 +1,6 @@ 'use strict'; -const Packet = require('../../../').packet; +const Packet = require('../../../').Packet; const assert = require('chai').assert; suite('Packet setColor', () => { diff --git a/test/unit/packets/setPower-test.js b/test/unit/packets/setPower-test.js index f5b74a1..6372c92 100644 --- a/test/unit/packets/setPower-test.js +++ b/test/unit/packets/setPower-test.js @@ -1,6 +1,6 @@ 'use strict'; -const Packet = require('../../../').packet; +const Packet = require('../../../').Packet; const assert = require('chai').assert; suite('Packet setPower', () => { From fe9e2745c209eb7f3211bff19a1145d6e5042a6a Mon Sep 17 00:00:00 2001 From: Ristomatti Airo Date: Sun, 29 Oct 2017 23:48:01 +0200 Subject: [PATCH 3/7] Refactor Client into a class --- src/lifx/client.js | 1047 ++++++++++++++++++++++---------------------- 1 file changed, 527 insertions(+), 520 deletions(-) diff --git a/src/lifx/client.js b/src/lifx/client.js index 759446d..82f6f3a 100644 --- a/src/lifx/client.js +++ b/src/lifx/client.js @@ -4,618 +4,625 @@ const util = require('util'); const dgram = require('dgram'); const EventEmitter = require('eventemitter3'); const {defaults, isArray, result, find, bind, forEach} = require('lodash'); -const Packet = require('../lifx').packet; +const Packet = require('../lifx').Packet; const {Light, constants, utils} = require('../lifx'); /** * Creates a lifx client * @extends EventEmitter */ -function Client() { - EventEmitter.call(this); - - this.debug = false; - this.socket = dgram.createSocket('udp4'); - this.isSocketBound = false; - this.devices = {}; - this.port = null; - this.messagesQueue = []; - this.sendTimer = null; - this.discoveryTimer = null; - this.discoveryPacketSequence = 0; - this.messageHandlers = [{ - type: 'stateService', - callback: this.processDiscoveryPacket.bind(this) - }, { - type: 'stateLabel', - callback: this.processLabelPacket.bind(this) - }, { - type: 'stateLight', - callback: this.processLabelPacket.bind(this) - }]; - this.sequenceNumber = 0; - this.lightOfflineTolerance = 3; - this.messageHandlerTimeout = 45000; // 45 sec - this.resendPacketDelay = 150; - this.resendMaxTimes = 5; - this.source = utils.getRandomHexString(8); - this.broadcastAddress = '255.255.255.255'; -} -util.inherits(Client, EventEmitter); - -/** - * Creates a new socket and starts discovery - * @example - * init({debug: true}, function() { - * console.log('Client started'); - * }) - * @param {Object} [options] Configuration to use - * @param {String} [options.address] The IPv4 address to bind to - * @param {Number} [options.port] The port to bind to - * @param {Boolean} [options.debug] Show debug output - * @param {Number} [options.lightOfflineTolerance] If light hasn't answered for amount of discoveries it is set offline - * @param {Number} [options.messageHandlerTimeout] Message handlers not called will be removed after this delay in ms - * @param {String} [options.source] The source to send to light, must be 8 chars lowercase or digit - * @param {Boolean} [options.startDiscovery] Weather to start discovery after initialization or not - * @param {Array} [options.lights] Pre set list of ip addresses of known addressable lights - * @param {String} [options.broadcast] The broadcast address to use for light discovery - * @param {Number} [options.sendPort] The port to send messages to - * @param {Function} [callback] Called after initialation - */ -Client.prototype.init = function(options, callback) { - const defaultOpts = { - address: '0.0.0.0', - port: 0, - debug: false, - lightOfflineTolerance: 3, - messageHandlerTimeout: 45000, - source: '', - startDiscovery: true, - lights: [], - broadcast: '255.255.255.255', - sendPort: constants.LIFX_DEFAULT_PORT, - resendPacketDelay: 150, - resendMaxTimes: 3 - }; - - options = options || {}; - const opts = defaults(options, defaultOpts); - - if (typeof opts.port !== 'number') { - throw new TypeError('LIFX Client port option must be a number'); - } else if (opts.port > 65535 || opts.port < 0) { - throw new RangeError('LIFX Client port option must be between 0 and 65535'); - } +class Client { + constructor() { + EventEmitter.call(this); - if (typeof opts.debug !== 'boolean') { - throw new TypeError('LIFX Client debug option must be a boolean'); - } - this.debug = opts.debug; + this.debug = false; + this.socket = dgram.createSocket('udp4'); + this.isSocketBound = false; + this.devices = {}; + this.port = null; + this.messagesQueue = []; + this.sendTimer = null; + this.discoveryTimer = null; + this.discoveryPacketSequence = 0; + this.messageHandlers = [{ + type: 'stateService', + callback: this.processDiscoveryPacket.bind(this) + }, { + type: 'stateLabel', + callback: this.processLabelPacket.bind(this) + }, { + type: 'stateLight', + callback: this.processLabelPacket.bind(this) + }]; + this.sequenceNumber = 0; + this.lightOfflineTolerance = 3; + this.messageHandlerTimeout = 45000; // 45 sec + this.resendPacketDelay = 150; + this.resendMaxTimes = 5; + this.source = utils.getRandomHexString(8); + this.broadcastAddress = '255.255.255.255'; + } + + /** + * Creates a new socket and starts discovery + * @example + * init({debug: true}, function() { + * console.log('Client started'); + * }) + * @param {Object} [options] Configuration to use + * @param {String} [options.address] The IPv4 address to bind to + * @param {Number} [options.port] The port to bind to + * @param {Boolean} [options.debug] Show debug output + * @param {Number} [options.lightOfflineTolerance] If light hasn't answered for amount of discoveries it is set offline + * @param {Number} [options.messageHandlerTimeout] Message handlers not called will be removed after this delay in ms + * @param {String} [options.source] The source to send to light, must be 8 chars lowercase or digit + * @param {Boolean} [options.startDiscovery] Weather to start discovery after initialization or not + * @param {Array} [options.lights] Pre set list of ip addresses of known addressable lights + * @param {String} [options.broadcast] The broadcast address to use for light discovery + * @param {Number} [options.sendPort] The port to send messages to + * @param {Function} [callback] Called after initialation + */ + init(options, callback) { + const defaultOpts = { + address: '0.0.0.0', + port: 0, + debug: false, + lightOfflineTolerance: 3, + messageHandlerTimeout: 45000, + source: '', + startDiscovery: true, + lights: [], + broadcast: '255.255.255.255', + sendPort: constants.LIFX_DEFAULT_PORT, + resendPacketDelay: 150, + resendMaxTimes: 3 + }; + + options = options || {}; + const opts = defaults(options, defaultOpts); + + if (typeof opts.port !== 'number') { + throw new TypeError('LIFX Client port option must be a number'); + } else if (opts.port > 65535 || opts.port < 0) { + throw new RangeError('LIFX Client port option must be between 0 and 65535'); + } - if (typeof opts.lightOfflineTolerance !== 'number') { - throw new TypeError('LIFX Client lightOfflineTolerance option must be a number'); - } - this.lightOfflineTolerance = opts.lightOfflineTolerance; + if (typeof opts.debug !== 'boolean') { + throw new TypeError('LIFX Client debug option must be a boolean'); + } + this.debug = opts.debug; - if (typeof opts.messageHandlerTimeout !== 'number') { - throw new TypeError('LIFX Client messageHandlerTimeout option must be a number'); - } - this.messageHandlerTimeout = opts.messageHandlerTimeout; + if (typeof opts.lightOfflineTolerance !== 'number') { + throw new TypeError('LIFX Client lightOfflineTolerance option must be a number'); + } + this.lightOfflineTolerance = opts.lightOfflineTolerance; - if (typeof opts.resendPacketDelay !== 'number') { - throw new TypeError('LIFX Client resendPacketDelay option must be a number'); - } - this.resendPacketDelay = opts.resendPacketDelay; + if (typeof opts.messageHandlerTimeout !== 'number') { + throw new TypeError('LIFX Client messageHandlerTimeout option must be a number'); + } + this.messageHandlerTimeout = opts.messageHandlerTimeout; - if (typeof opts.resendMaxTimes !== 'number') { - throw new TypeError('LIFX Client resendMaxTimes option must be a number'); - } - this.resendMaxTimes = opts.resendMaxTimes; + if (typeof opts.resendPacketDelay !== 'number') { + throw new TypeError('LIFX Client resendPacketDelay option must be a number'); + } + this.resendPacketDelay = opts.resendPacketDelay; - if (typeof opts.broadcast !== 'string') { - throw new TypeError('LIFX Client broadcast option must be a string'); - } else if (!utils.isIpv4Format(opts.broadcast)) { - throw new TypeError('LIFX Client broadcast option does only allow IPv4 address format'); - } - this.broadcastAddress = opts.broadcast; + if (typeof opts.resendMaxTimes !== 'number') { + throw new TypeError('LIFX Client resendMaxTimes option must be a number'); + } + this.resendMaxTimes = opts.resendMaxTimes; - if (typeof opts.sendPort !== 'number') { - throw new TypeError('LIFX Client sendPort option must be a number'); - } else if (opts.sendPort > 65535 || opts.sendPort < 1) { - throw new RangeError('LIFX Client sendPort option must be between 1 and 65535'); - } - this.sendPort = opts.sendPort; - - if (!isArray(opts.lights)) { - throw new TypeError('LIFX Client lights option must be an array'); - } else { - opts.lights.forEach(function(light) { - if (!utils.isIpv4Format(light)) { - throw new TypeError('LIFX Client lights option array element \'' + light + '\' is not expected IPv4 format'); - } - }); - } + if (typeof opts.broadcast !== 'string') { + throw new TypeError('LIFX Client broadcast option must be a string'); + } else if (!utils.isIpv4Format(opts.broadcast)) { + throw new TypeError('LIFX Client broadcast option does only allow IPv4 address format'); + } + this.broadcastAddress = opts.broadcast; - if (opts.source !== '') { - if (typeof opts.source === 'string') { - if (/^[0-9A-F]{8}$/.test(opts.source)) { - this.source = opts.source; - } else { - throw new RangeError('LIFX Client source option must be 8 hex chars'); - } - } else { - throw new TypeError('LIFX Client source option must be given as string'); + if (typeof opts.sendPort !== 'number') { + throw new TypeError('LIFX Client sendPort option must be a number'); + } else if (opts.sendPort > 65535 || opts.sendPort < 1) { + throw new RangeError('LIFX Client sendPort option must be between 1 and 65535'); } - } + this.sendPort = opts.sendPort; - this.socket.on('error', function(err) { - this.isSocketBound = false; - console.error('LIFX Client UDP error'); - console.trace(err); - this.socket.close(); - this.emit('error', err); - }.bind(this)); - - this.socket.on('message', function(msg, rinfo) { - // Ignore own messages and false formats - if (utils.getHostIPs().indexOf(rinfo.address) >= 0 || !Buffer.isBuffer(msg)) { - return; + if (!isArray(opts.lights)) { + throw new TypeError('LIFX Client lights option must be an array'); + } else { + opts.lights.forEach(function(light) { + if (!utils.isIpv4Format(light)) { + throw new TypeError('LIFX Client lights option array element \'' + light + '\' is not expected IPv4 format'); + } + }); } - /* istanbul ignore if */ - if (this.debug) { - console.log('DEBUG - ' + msg.toString('hex') + ' from ' + rinfo.address); + if (opts.source !== '') { + if (typeof opts.source === 'string') { + if (/^[0-9A-F]{8}$/.test(opts.source)) { + this.source = opts.source; + } else { + throw new RangeError('LIFX Client source option must be 8 hex chars'); + } + } else { + throw new TypeError('LIFX Client source option must be given as string'); + } } - // Parse packet to object - const parsedMsg = Packet.toObject(msg); + this.socket.on('error', function(err) { + this.isSocketBound = false; + console.error('LIFX Client UDP error'); + console.trace(err); + this.socket.close(); + this.emit('error', err); + }.bind(this)); + + this.socket.on('message', function(msg, rinfo) { + // Ignore own messages and false formats + if (utils.getHostIPs().indexOf(rinfo.address) >= 0 || !Buffer.isBuffer(msg)) { + return; + } - // Check if packet is read successfully - if (parsedMsg instanceof Error) { - console.error('LIFX Client invalid packet header error'); - console.error('Packet: ', msg.toString('hex')); - console.trace(parsedMsg); - } else { - // Convert type before emitting - const messageTypeName = result(find(Packet.typeList, {id: parsedMsg.type}), 'name'); - if (messageTypeName !== undefined) { - parsedMsg.type = messageTypeName; + /* istanbul ignore if */ + if (this.debug) { + console.log('DEBUG - ' + msg.toString('hex') + ' from ' + rinfo.address); } - // Check for handlers of given message and rinfo - this.processMessageHandlers(parsedMsg, rinfo); - this.emit('message', parsedMsg, rinfo); - } - }.bind(this)); + // Parse packet to object + const parsedMsg = Packet.toObject(msg); - this.socket.bind(opts.port, opts.address, function() { - this.isSocketBound = true; - this.socket.setBroadcast(true); - this.emit('listening'); - this.port = opts.port; + // Check if packet is read successfully + if (parsedMsg instanceof Error) { + console.error('LIFX Client invalid packet header error'); + console.error('Packet: ', msg.toString('hex')); + console.trace(parsedMsg); + } else { + // Convert type before emitting + const messageTypeName = result(find(Packet.typeList, { + id: parsedMsg.type + }), 'name'); + if (messageTypeName !== undefined) { + parsedMsg.type = messageTypeName; + } + // Check for handlers of given message and rinfo + this.processMessageHandlers(parsedMsg, rinfo); - // Start scanning - if (opts.startDiscovery) { - this.startDiscovery(opts.lights); - } - if (typeof callback === 'function') { - return callback(); - } - }.bind(this)); -}; + this.emit('message', parsedMsg, rinfo); + } + }.bind(this)); -/** - * Destroy an instance - */ -Client.prototype.destroy = function() { - this.stopDiscovery(); - this.stopSendingProcess(); - if (this.isSocketBound) { - this.socket.close(); + this.socket.bind(opts.port, opts.address, function() { + this.isSocketBound = true; + this.socket.setBroadcast(true); + this.emit('listening'); + this.port = opts.port; + + // Start scanning + if (opts.startDiscovery) { + this.startDiscovery(opts.lights); + } + if (typeof callback === 'function') { + return callback(); + } + }.bind(this)); } -}; -/** - * Sends a packet from the messages queue or stops the sending process - * if queue is empty - **/ -Client.prototype.sendingProcess = function() { - if (!this.isSocketBound) { + /** + * Destroy an instance + */ + destroy() { + this.stopDiscovery(); this.stopSendingProcess(); - console.log('LIFX Client stopped sending due to unbound socket'); - return; + if (this.isSocketBound) { + this.socket.close(); + } } - if (this.messagesQueue.length > 0) { - const msg = this.messagesQueue.pop(); - if (msg.address === undefined) { - msg.address = this.broadcastAddress; + /** + * Sends a packet from the messages queue or stops the sending process + * if queue is empty + **/ + sendingProcess() { + if (!this.isSocketBound) { + this.stopSendingProcess(); + console.log('LIFX Client stopped sending due to unbound socket'); + return; } - if (msg.transactionType === constants.PACKET_TRANSACTION_TYPES.ONE_WAY) { - this.socket.send(msg.data, 0, msg.data.length, this.sendPort, msg.address); - /* istanbul ignore if */ - if (this.debug) { - console.log('DEBUG - ' + msg.data.toString('hex') + ' to ' + msg.address); + + if (this.messagesQueue.length > 0) { + const msg = this.messagesQueue.pop(); + if (msg.address === undefined) { + msg.address = this.broadcastAddress; } - } else if (msg.transactionType === constants.PACKET_TRANSACTION_TYPES.REQUEST_RESPONSE) { - if (msg.timesSent < this.resendMaxTimes) { - if (Date.now() > (msg.timeLastSent + this.resendPacketDelay)) { - this.socket.send(msg.data, 0, msg.data.length, this.sendPort, msg.address); - msg.timesSent += 1; - msg.timeLastSent = Date.now(); - /* istanbul ignore if */ - if (this.debug) { - console.log( - 'DEBUG - ' + msg.data.toString('hex') + ' to ' + msg.address + - ', send ' + msg.timesSent + ' time(s)' - ); - } + if (msg.transactionType === constants.PACKET_TRANSACTION_TYPES.ONE_WAY) { + this.socket.send(msg.data, 0, msg.data.length, this.sendPort, msg.address); + /* istanbul ignore if */ + if (this.debug) { + console.log('DEBUG - ' + msg.data.toString('hex') + ' to ' + msg.address); } - // Add to the end of the queue again - this.messagesQueue.unshift(msg); - } else { - this.messageHandlers.forEach(function(handler, hdlrIndex) { - if (handler.type === 'acknowledgement' && handler.sequenceNumber === msg.sequence) { - this.messageHandlers.splice(hdlrIndex, 1); - const err = new Error('No LIFX response after max resend limit of ' + this.resendMaxTimes); - return handler.callback(err, null, null); + } else if (msg.transactionType === constants.PACKET_TRANSACTION_TYPES.REQUEST_RESPONSE) { + if (msg.timesSent < this.resendMaxTimes) { + if (Date.now() > (msg.timeLastSent + this.resendPacketDelay)) { + this.socket.send(msg.data, 0, msg.data.length, this.sendPort, msg.address); + msg.timesSent += 1; + msg.timeLastSent = Date.now(); + /* istanbul ignore if */ + if (this.debug) { + console.log( + 'DEBUG - ' + msg.data.toString('hex') + ' to ' + msg.address + + ', send ' + msg.timesSent + ' time(s)' + ); + } } - }.bind(this)); + // Add to the end of the queue again + this.messagesQueue.unshift(msg); + } else { + this.messageHandlers.forEach(function(handler, hdlrIndex) { + if (handler.type === 'acknowledgement' && handler.sequenceNumber === msg.sequence) { + this.messageHandlers.splice(hdlrIndex, 1); + const err = new Error('No LIFX response after max resend limit of ' + this.resendMaxTimes); + return handler.callback(err, null, null); + } + }.bind(this)); + } } + } else { + this.stopSendingProcess(); } - } else { - this.stopSendingProcess(); } -}; -/** - * Starts the sending of all packages in the queue - */ -Client.prototype.startSendingProcess = function() { - if (this.sendTimer === null) { // Already running? - this.sendTimer = setInterval(this.sendingProcess.bind(this), constants.MESSAGE_RATE_LIMIT); + /** + * Starts the sending of all packages in the queue + */ + startSendingProcess() { + if (this.sendTimer === null) { // Already running? + this.sendTimer = setInterval(this.sendingProcess.bind(this), constants.MESSAGE_RATE_LIMIT); + } } -}; -/** - * Stops sending of all packages in the queue - */ -Client.prototype.stopSendingProcess = function() { - if (this.sendTimer !== null) { - clearInterval(this.sendTimer); - this.sendTimer = null; + /** + * Stops sending of all packages in the queue + */ + stopSendingProcess() { + if (this.sendTimer !== null) { + clearInterval(this.sendTimer); + this.sendTimer = null; + } } -}; -/** - * Start discovery of lights - * This will keep the list of lights updated, finds new lights and sets lights - * offline if no longer found - * @param {Array} [lights] Pre set list of ip addresses of known addressable lights to request directly - */ -Client.prototype.startDiscovery = function(lights) { - lights = lights || []; - const sendDiscoveryPacket = function() { - // Sign flag on inactive lights - forEach(this.devices, bind(function(info, deviceId) { - if (this.devices[deviceId].status !== 'off') { - const diff = this.discoveryPacketSequence - info.seenOnDiscovery; - if (diff >= this.lightOfflineTolerance) { - this.devices[deviceId].status = 'off'; - this.emit('bulb-offline', info); // deprecated - this.emit('light-offline', info); + /** + * Start discovery of lights + * This will keep the list of lights updated, finds new lights and sets lights + * offline if no longer found + * @param {Array} [lights] Pre set list of ip addresses of known addressable lights to request directly + */ + startDiscovery(lights) { + lights = lights || []; + const sendDiscoveryPacket = function() { + // Sign flag on inactive lights + forEach(this.devices, bind(function(info, deviceId) { + if (this.devices[deviceId].status !== 'off') { + const diff = this.discoveryPacketSequence - info.seenOnDiscovery; + if (diff >= this.lightOfflineTolerance) { + this.devices[deviceId].status = 'off'; + this.emit('bulb-offline', info); // deprecated + this.emit('light-offline', info); + } } - } - }, this)); - - // Send a discovery packet broadcast - this.send(Packet.create('getService', {}, this.source)); + }, this)); - // Send a discovery packet to each light given directly - lights.forEach(function(lightAddress) { - const msg = Packet.create('getService', {}, this.source); - msg.address = lightAddress; - this.send(msg); - }, this); + // Send a discovery packet broadcast + this.send(Packet.create('getService', {}, this.source)); - // Keep track of a sequent number to find not answering lights - if (this.discoveryPacketSequence >= Number.MAX_VALUE) { - this.discoveryPacketSequence = 0; - } else { - this.discoveryPacketSequence += 1; - } - }.bind(this); + // Send a discovery packet to each light given directly + lights.forEach(function(lightAddress) { + const msg = Packet.create('getService', {}, this.source); + msg.address = lightAddress; + this.send(msg); + }, this); - this.discoveryTimer = setInterval( - sendDiscoveryPacket, - constants.DISCOVERY_INTERVAL - ); + // Keep track of a sequent number to find not answering lights + if (this.discoveryPacketSequence >= Number.MAX_VALUE) { + this.discoveryPacketSequence = 0; + } else { + this.discoveryPacketSequence += 1; + } + }.bind(this); - sendDiscoveryPacket(); -}; + this.discoveryTimer = setInterval( + sendDiscoveryPacket, + constants.DISCOVERY_INTERVAL + ); -/** - * Checks all registered message handlers if they request the given message - * @param {Object} msg message to check handler for - * @param {Object} rinfo rinfo address info to check handler for - */ -Client.prototype.processMessageHandlers = function(msg, rinfo) { - // Process only packages for us - if (msg.source.toLowerCase() !== this.source.toLowerCase()) { - return; + sendDiscoveryPacket(); } - // We check our message handler if the answer received is requested - this.messageHandlers.forEach(function(handler, hdlrIndex) { - if (msg.type === handler.type) { - if (handler.sequenceNumber !== undefined) { - if (handler.sequenceNumber === msg.sequence) { - // Remove if specific packet was request, since it should only be called once - this.messageHandlers.splice(hdlrIndex, 1); - this.messagesQueue.forEach(function(packet, packetIndex) { - if (packet.transactionType === constants.PACKET_TRANSACTION_TYPES.REQUEST_RESPONSE && + + /** + * Checks all registered message handlers if they request the given message + * @param {Object} msg message to check handler for + * @param {Object} rinfo rinfo address info to check handler for + */ + processMessageHandlers(msg, rinfo) { + // Process only packages for us + if (msg.source.toLowerCase() !== this.source.toLowerCase()) { + return; + } + // We check our message handler if the answer received is requested + this.messageHandlers.forEach(function(handler, hdlrIndex) { + if (msg.type === handler.type) { + if (handler.sequenceNumber !== undefined) { + if (handler.sequenceNumber === msg.sequence) { + // Remove if specific packet was request, since it should only be called once + this.messageHandlers.splice(hdlrIndex, 1); + this.messagesQueue.forEach(function(packet, packetIndex) { + if (packet.transactionType === constants.PACKET_TRANSACTION_TYPES.REQUEST_RESPONSE && packet.sequence === msg.sequence) { - this.messagesQueue.splice(packetIndex, 1); - } - }.bind(this)); + this.messagesQueue.splice(packetIndex, 1); + } + }.bind(this)); + // Call the function requesting the packet + return handler.callback(null, msg, rinfo); + } + } else { // Call the function requesting the packet return handler.callback(null, msg, rinfo); } - } else { - // Call the function requesting the packet - return handler.callback(null, msg, rinfo); } - } - // We want to call expired request handlers for specific packages after the - // messageHandlerTimeout set in options, to specify an error - if (handler.sequenceNumber !== undefined) { - if (Date.now() > (handler.timestamp + this.messageHandlerTimeout)) { - this.messageHandlers.splice(hdlrIndex, 1); + // We want to call expired request handlers for specific packages after the + // messageHandlerTimeout set in options, to specify an error + if (handler.sequenceNumber !== undefined) { + if (Date.now() > (handler.timestamp + this.messageHandlerTimeout)) { + this.messageHandlers.splice(hdlrIndex, 1); - const err = new Error('No LIFX response in time'); - return handler.callback(err, null, null); + const err = new Error('No LIFX response in time'); + return handler.callback(err, null, null); + } } - } - }, this); -}; - -/** - * Processes a discovery report packet to update internals - * @param {Object} err Error if existant - * @param {Object} msg The discovery report package - * @param {Object} rinfo Remote host details - */ -Client.prototype.processDiscoveryPacket = function(err, msg, rinfo) { - if (err) { - return; + }, this); } - if (msg.service === 'udp' && msg.port === constants.LIFX_DEFAULT_PORT) { - // Add / update the found gateway - if (!this.devices[msg.target]) { - const lightDevice = new Light({ - client: this, - id: msg.target, - address: rinfo.address, - port: msg.port, - seenOnDiscovery: this.discoveryPacketSequence - }); - this.devices[msg.target] = lightDevice; - - // Request label - const labelRequest = Packet.create('getLabel', {}, this.source); - labelRequest.target = msg.target; - this.send(labelRequest); - this.emit('bulb-new', lightDevice); // deprecated - this.emit('light-new', lightDevice); - } else { - if (this.devices[msg.target].status === 'off') { - this.devices[msg.target].status = 'on'; - this.emit('bulb-online', this.devices[msg.target]); // deprecated - this.emit('light-online', this.devices[msg.target]); + /** + * Processes a discovery report packet to update internals + * @param {Object} err Error if existant + * @param {Object} msg The discovery report package + * @param {Object} rinfo Remote host details + */ + processDiscoveryPacket(err, msg, rinfo) { + if (err) { + return; + } + if (msg.service === 'udp' && msg.port === constants.LIFX_DEFAULT_PORT) { + // Add / update the found gateway + if (!this.devices[msg.target]) { + const lightDevice = new Light({ + client: this, + id: msg.target, + address: rinfo.address, + port: msg.port, + seenOnDiscovery: this.discoveryPacketSequence + }); + this.devices[msg.target] = lightDevice; + + // Request label + const labelRequest = Packet.create('getLabel', {}, this.source); + labelRequest.target = msg.target; + this.send(labelRequest); + + this.emit('bulb-new', lightDevice); // deprecated + this.emit('light-new', lightDevice); + } else { + if (this.devices[msg.target].status === 'off') { + this.devices[msg.target].status = 'on'; + this.emit('bulb-online', this.devices[msg.target]); // deprecated + this.emit('light-online', this.devices[msg.target]); + } + this.devices[msg.target].address = rinfo.address; + this.devices[msg.target].seenOnDiscovery = this.discoveryPacketSequence; } - this.devices[msg.target].address = rinfo.address; - this.devices[msg.target].seenOnDiscovery = this.discoveryPacketSequence; } } -}; -/** - * Processes a state label packet to update internals - * @param {Object} err Error if existant - * @param {Object} msg The state label package - */ -Client.prototype.processLabelPacket = function(err, msg) { - if (err) { - return; - } - if (this.devices[msg.target] !== undefined) { - this.devices[msg.target].label = msg.label; + /** + * Processes a state label packet to update internals + * @param {Object} err Error if existant + * @param {Object} msg The state label package + */ + processLabelPacket(err, msg) { + if (err) { + return; + } + if (this.devices[msg.target] !== undefined) { + this.devices[msg.target].label = msg.label; + } } -}; - -/** - * This stops the discovery process - * The client will be no longer updating the state of lights or find lights - */ -Client.prototype.stopDiscovery = function() { - clearInterval(this.discoveryTimer); - this.discoveryTimer = null; -}; -/** - * Send a LIFX message objects over the network - * @param {Object} msg A message object or multiple with data to send - * @param {Function} [callback] Function to handle error and success after send - * @return {Number} The sequence number of the request - */ -Client.prototype.send = function(msg, callback) { - const packet = { - timeCreated: Date.now(), - timeLastSent: 0, - timesSent: 0, - transactionType: constants.PACKET_TRANSACTION_TYPES.ONE_WAY - }; - - // Add the target ip address if target given - if (msg.address !== undefined) { - packet.address = msg.address; - } - if (msg.target !== undefined) { - const targetBulb = this.light(msg.target); - if (targetBulb) { - packet.address = targetBulb.address; - // If we would exceed the max value for the int8 field start over again - if (this.sequenceNumber >= constants.PACKET_HEADER_SEQUENCE_MAX) { - this.sequenceNumber = 0; - } else { - this.sequenceNumber += 1; + /** + * This stops the discovery process + * The client will be no longer updating the state of lights or find lights + */ + stopDiscovery() { + clearInterval(this.discoveryTimer); + this.discoveryTimer = null; + } + + /** + * Send a LIFX message objects over the network + * @param {Object} msg A message object or multiple with data to send + * @param {Function} [callback] Function to handle error and success after send + * @return {Number} The sequence number of the request + */ + send(msg, callback) { + const packet = { + timeCreated: Date.now(), + timeLastSent: 0, + timesSent: 0, + transactionType: constants.PACKET_TRANSACTION_TYPES.ONE_WAY + }; + + // Add the target ip address if target given + if (msg.address !== undefined) { + packet.address = msg.address; + } + if (msg.target !== undefined) { + const targetBulb = this.light(msg.target); + if (targetBulb) { + packet.address = targetBulb.address; + // If we would exceed the max value for the int8 field start over again + if (this.sequenceNumber >= constants.PACKET_HEADER_SEQUENCE_MAX) { + this.sequenceNumber = 0; + } else { + this.sequenceNumber += 1; + } } } - } - - msg.sequence = this.sequenceNumber; - packet.sequence = this.sequenceNumber; - if (typeof callback === 'function') { - msg.ackRequired = true; - this.addMessageHandler('acknowledgement', callback, msg.sequence); - packet.transactionType = constants.PACKET_TRANSACTION_TYPES.REQUEST_RESPONSE; - } - packet.data = Packet.toBuffer(msg); - this.messagesQueue.unshift(packet); - this.startSendingProcess(); - return this.sequenceNumber; -}; + msg.sequence = this.sequenceNumber; + packet.sequence = this.sequenceNumber; + if (typeof callback === 'function') { + msg.ackRequired = true; + this.addMessageHandler('acknowledgement', callback, msg.sequence); + packet.transactionType = constants.PACKET_TRANSACTION_TYPES.REQUEST_RESPONSE; + } + packet.data = Packet.toBuffer(msg); + this.messagesQueue.unshift(packet); + this.startSendingProcess(); + + return this.sequenceNumber; + } + + /** + * Get network address data from connection + * @return {Object} Network address data + */ + address() { + let address = null; + try { + address = this.socket.address(); + } catch (e) {} + return address; + } + + /** + * Sets debug on or off at runtime + * @param {Boolean} debug debug messages on + */ + setDebug(debug) { + if (typeof debug !== 'boolean') { + throw new TypeError('LIFX Client setDebug expects boolean as parameter'); + } + this.debug = debug; + } + + /** + * Adds a message handler that calls a function when the requested + * info was received + * @param {String} type A type of packet to listen for, like stateLight + * @param {Function} callback the function to call if the packet was received, + * this will be called with parameters msg and rinfo + * @param {Number} [sequenceNumber] Expects a specific sequenceNumber on which will + * be called, this will call it only once. If not + * given the callback handler is permanent + */ + addMessageHandler(type, callback, sequenceNumber) { + if (typeof type !== 'string') { + throw new TypeError('LIFX Client addMessageHandler expects type parameter to be string'); + } + if (typeof callback !== 'function') { + throw new TypeError('LIFX Client addMessageHandler expects callback parameter to be a function'); + } -/** - * Get network address data from connection - * @return {Object} Network address data - */ -Client.prototype.address = function() { - let address = null; - try { - address = this.socket.address(); - } catch (e) {} - return address; -}; + const typeName = find(Packet.typeList, { + name: type + }); + if (typeName === undefined) { + throw new RangeError('LIFX Client addMessageHandler unknown packet type: ' + type); + } -/** - * Sets debug on or off at runtime - * @param {Boolean} debug debug messages on - */ -Client.prototype.setDebug = function(debug) { - if (typeof debug !== 'boolean') { - throw new TypeError('LIFX Client setDebug expects boolean as parameter'); - } - this.debug = debug; -}; + const handler = { + type: type, + callback: callback.bind(this), + timestamp: Date.now() + }; -/** - * Adds a message handler that calls a function when the requested - * info was received - * @param {String} type A type of packet to listen for, like stateLight - * @param {Function} callback the function to call if the packet was received, - * this will be called with parameters msg and rinfo - * @param {Number} [sequenceNumber] Expects a specific sequenceNumber on which will - * be called, this will call it only once. If not - * given the callback handler is permanent - */ -Client.prototype.addMessageHandler = function(type, callback, sequenceNumber) { - if (typeof type !== 'string') { - throw new TypeError('LIFX Client addMessageHandler expects type parameter to be string'); - } - if (typeof callback !== 'function') { - throw new TypeError('LIFX Client addMessageHandler expects callback parameter to be a function'); - } + if (sequenceNumber !== undefined) { + if (typeof sequenceNumber !== 'number') { + throw new TypeError('LIFX Client addMessageHandler expects sequenceNumber to be a integer'); + } else { + handler.sequenceNumber = sequenceNumber; + } + } - const typeName = find(Packet.typeList, {name: type}); - if (typeName === undefined) { - throw new RangeError('LIFX Client addMessageHandler unknown packet type: ' + type); + this.messageHandlers.push(handler); } - const handler = { - type: type, - callback: callback.bind(this), - timestamp: Date.now() - }; - - if (sequenceNumber !== undefined) { - if (typeof sequenceNumber !== 'number') { - throw new TypeError('LIFX Client addMessageHandler expects sequenceNumber to be a integer'); - } else { - handler.sequenceNumber = sequenceNumber; + /** + * Returns the list of all known lights + * @example client.lights() + * @param {String} [status='on'] Status to filter for, empty string for all + * @return {Array} Lights + */ + lights(status) { + if (status === undefined) { + status = 'on'; + } else if (typeof status !== 'string') { + throw new TypeError('LIFX Client lights expects status to be a string'); } - } - this.messageHandlers.push(handler); -}; - -/** - * Returns the list of all known lights - * @example client.lights() - * @param {String} [status='on'] Status to filter for, empty string for all - * @return {Array} Lights - */ -Client.prototype.lights = function(status) { - if (status === undefined) { - status = 'on'; - } else if (typeof status !== 'string') { - throw new TypeError('LIFX Client lights expects status to be a string'); - } + if (status.length > 0) { + if (status !== 'on' && status !== 'off') { + throw new TypeError('Lifx Client lights expects status to be \'on\', \'off\' or \'\''); + } - if (status.length > 0) { - if (status !== 'on' && status !== 'off') { - throw new TypeError('Lifx Client lights expects status to be \'on\', \'off\' or \'\''); + const result = []; + forEach(this.devices, function(light) { + if (light.status === status) { + result.push(light); + } + }); + return result; } - const result = []; - forEach(this.devices, function(light) { - if (light.status === status) { - result.push(light); - } - }); - return result; + return this.devices; } - return this.devices; -}; + /** + * Find a light by label, id or ip + * @param {String} identifier label, id or ip to search for + * @return {Object|Boolean} the light object or false if not found + */ + light(identifier) { + let result; + if (typeof identifier !== 'string') { + throw new TypeError('LIFX Client light expects identifier for LIFX light to be a string'); + } -/** - * Find a light by label, id or ip - * @param {String} identifier label, id or ip to search for - * @return {Object|Boolean} the light object or false if not found - */ -Client.prototype.light = function(identifier) { - let result; - if (typeof identifier !== 'string') { - throw new TypeError('LIFX Client light expects identifier for LIFX light to be a string'); - } + // There is no ip or id longer than 45 chars, no label longer than 32 bit + if (identifier.length > 45 && Buffer.byteLength(identifier, 'utf8') > 32) { + return false; + } - // There is no ip or id longer than 45 chars, no label longer than 32 bit - if (identifier.length > 45 && Buffer.byteLength(identifier, 'utf8') > 32) { - return false; - } + // Dots or colons is high likely an ip + if (identifier.indexOf('.') >= 0 || identifier.indexOf(':') >= 0) { + result = find(this.devices, {address: identifier}) || false; + if (result !== false) { + return result; + } + } - // Dots or colons is high likely an ip - if (identifier.indexOf('.') >= 0 || identifier.indexOf(':') >= 0) { - result = find(this.devices, {address: identifier}) || false; + // Search id + result = find(this.devices, {id: identifier}) || false; if (result !== false) { return result; } - } - // Search id - result = find(this.devices, {id: identifier}) || false; - if (result !== false) { + // Search label + result = find(this.devices, {label: identifier}) || false; + return result; } +} - // Search label - result = find(this.devices, {label: identifier}) || false; - - return result; -}; +util.inherits(Client, EventEmitter); exports.Client = Client; From cdfc242ffc85026f768a262ed55cd8504fefdd87 Mon Sep 17 00:00:00 2001 From: Ristomatti Airo Date: Sun, 29 Oct 2017 23:48:28 +0200 Subject: [PATCH 4/7] Refactor Light into a class --- src/lifx/light.js | 936 +++++++++++++++++++++++----------------------- 1 file changed, 470 insertions(+), 466 deletions(-) diff --git a/src/lifx/light.js b/src/lifx/light.js index 51dae7b..e62e676 100644 --- a/src/lifx/light.js +++ b/src/lifx/light.js @@ -1,425 +1,164 @@ 'use strict'; -const {packet, constants, validate, utils} = require('../lifx'); +const {Packet, constants, validate, utils} = require('../lifx'); const {assign, pick} = require('lodash'); -/** - * A representation of a light bulb - * @class - * @param {Obj} constr constructor object - * @param {Lifx/Client} constr.client the client the light belongs to - * @param {String} constr.id the id used to target the light - * @param {String} constr.address ip address of the light - * @param {Number} constr.port port of the light - * @param {Number} constr.seenOnDiscovery on which discovery the light was last seen - */ -function Light(constr) { - this.client = constr.client; - this.id = constr.id; // Used to target the light - this.address = constr.address; - this.port = constr.port; - this.label = null; - this.status = 'on'; - - this.seenOnDiscovery = constr.seenOnDiscovery; -} +class Light { + constructor(constr) { + this.client = constr.client; + this.id = constr.id; // Used to target the light + this.address = constr.address; + this.port = constr.port; + this.label = null; + this.status = 'on'; -/** - * Turns the light off - * @example light('192.168.2.130').off() - * @param {Number} [duration] transition time in milliseconds - * @param {Function} [callback] called when light did receive message - */ -Light.prototype.off = function(duration, callback) { - validate.optionalDuration(duration, 'light off method'); - validate.optionalCallback(callback, 'light off method'); - - const packetObj = packet.create('setPower', {level: 0, duration: duration}, this.client.source); - packetObj.target = this.id; - this.client.send(packetObj, callback); -}; - -/** - * Turns the light on - * @example light('192.168.2.130').on() - * @param {Number} [duration] transition time in milliseconds - * @param {Function} [callback] called when light did receive message - */ -Light.prototype.on = function(duration, callback) { - validate.optionalDuration(duration, 'light on method'); - validate.optionalCallback(callback, 'light on method'); - - const packetObj = packet.create('setPower', {level: 65535, duration: duration}, this.client.source); - packetObj.target = this.id; - this.client.send(packetObj, callback); -}; - -/** - * Changes the color to the given HSBK value - * @param {Number} hue color hue from 0 - 360 (in °) - * @param {Number} saturation color saturation from 0 - 100 (in %) - * @param {Number} brightness color brightness from 0 - 100 (in %) - * @param {Number} [kelvin=3500] color kelvin between 2500 and 9000 - * @param {Number} [duration] transition time in milliseconds - * @param {Function} [callback] called when light did receive message - */ -Light.prototype.color = function(hue, saturation, brightness, kelvin, duration, callback) { - validate.colorHsb(hue, saturation, brightness, 'light color method'); - - validate.optionalKelvin(kelvin, 'light color method'); - validate.optionalDuration(duration, 'light color method'); - validate.optionalCallback(callback, 'light color method'); - - // Convert HSB values to packet format - hue = Math.round(hue / constants.HSBK_MAXIMUM_HUE * 65535); - saturation = Math.round(saturation / constants.HSBK_MAXIMUM_SATURATION * 65535); - brightness = Math.round(brightness / constants.HSBK_MAXIMUM_BRIGHTNESS * 65535); - - const packetObj = packet.create('setColor', { - hue: hue, - saturation: saturation, - brightness: brightness, - kelvin: kelvin, - duration: duration - }, this.client.source); - packetObj.target = this.id; - this.client.send(packetObj, callback); -}; - -/** - * Changes the color to the given rgb value - * Note RGB poorly represents the color of light, prefer setting HSBK values with the color method - * @example light.colorRgb(255, 0, 0) - * @param {Integer} red value between 0 and 255 representing amount of red in color - * @param {Integer} green value between 0 and 255 representing amount of green in color - * @param {Integer} blue value between 0 and 255 representing amount of blue in color - * @param {Number} [duration] transition time in milliseconds - * @param {Function} [callback] called when light did receive message - */ -Light.prototype.colorRgb = function(red, green, blue, duration, callback) { - validate.colorRgb(red, green, blue, 'light colorRgb method'); - validate.optionalDuration(duration, 'light colorRgb method'); - validate.optionalCallback(callback, 'light colorRgb method'); - - const hsbObj = utils.rgbToHsb({r: red, g: green, b: blue}); - this.color(hsbObj.h, hsbObj.s, hsbObj.b, 3500, duration, callback); -}; - -/** - * Changes the color to the given rgb value - * Note RGB poorly represents the color of light, prefer setting HSBK values with the color method - * @example light.colorRgb('#FF0000') - * @param {String} hexString rgb hex string starting with # char - * @param {Number} [duration] transition time in milliseconds - * @param {Function} [callback] called when light did receive message - */ -Light.prototype.colorRgbHex = function(hexString, duration, callback) { - if (typeof hexString !== 'string') { - throw new TypeError('LIFX light colorRgbHex method expects first parameter hexString to a string'); + this.seenOnDiscovery = constr.seenOnDiscovery; } - validate.optionalDuration(duration, 'light colorRgbHex method'); - validate.optionalCallback(callback, 'light colorRgbHex method'); - - const rgbObj = utils.rgbHexStringToObject(hexString); - const hsbObj = utils.rgbToHsb(rgbObj); - this.color(hsbObj.h, hsbObj.s, hsbObj.b, 3500, duration, callback); -}; - -/** - * Sets the Maximum Infrared brightness - * @param {Number} brightness infrared brightness from 0 - 100 (in %) - * @param {Function} [callback] called when light did receive message - */ -Light.prototype.maxIR = function(brightness, callback) { - validate.irBrightness(brightness, 'light setMaxIR method'); + /** + * Turns the light off + * @example light('192.168.2.130').off() + * @param {Number} [duration] transition time in milliseconds + * @param {Function} [callback] called when light did receive message + */ + off(duration, callback) { + validate.optionalDuration(duration, 'light off method'); + validate.optionalCallback(callback, 'light off method'); + + const packetObj = Packet.create('setPower', { + level: 0, + duration: duration + }, this.client.source); + packetObj.target = this.id; + this.client.send(packetObj, callback); + } - brightness = Math.round(brightness / constants.IR_MAXIMUM_BRIGHTNESS * 65535); + /** + * Turns the light on + * @example light('192.168.2.130').on() + * @param {Number} [duration] transition time in milliseconds + * @param {Function} [callback] called when light did receive message + */ + on(duration, callback) { + validate.optionalDuration(duration, 'light on method'); + validate.optionalCallback(callback, 'light on method'); + + const packetObj = Packet.create('setPower', { + level: 65535, + duration: duration + }, this.client.source); + packetObj.target = this.id; + this.client.send(packetObj, callback); + } - if (callback !== undefined && typeof callback !== 'function') { - throw new TypeError('LIFX light setMaxIR method expects callback to be a function'); + /** + * Changes the color to the given HSBK value + * @param {Number} hue color hue from 0 - 360 (in °) + * @param {Number} saturation color saturation from 0 - 100 (in %) + * @param {Number} brightness color brightness from 0 - 100 (in %) + * @param {Number} [kelvin=3500] color kelvin between 2500 and 9000 + * @param {Number} [duration] transition time in milliseconds + * @param {Function} [callback] called when light did receive message + */ + color(hue, saturation, brightness, kelvin, duration, callback) { + validate.colorHsb(hue, saturation, brightness, 'light color method'); + + validate.optionalKelvin(kelvin, 'light color method'); + validate.optionalDuration(duration, 'light color method'); + validate.optionalCallback(callback, 'light color method'); + + // Convert HSB values to packet format + hue = Math.round(hue / constants.HSBK_MAXIMUM_HUE * 65535); + saturation = Math.round(saturation / constants.HSBK_MAXIMUM_SATURATION * 65535); + brightness = Math.round(brightness / constants.HSBK_MAXIMUM_BRIGHTNESS * 65535); + + const packetObj = Packet.create('setColor', { + hue: hue, + saturation: saturation, + brightness: brightness, + kelvin: kelvin, + duration: duration + }, this.client.source); + packetObj.target = this.id; + this.client.send(packetObj, callback); } - const packetObj = packet.create('setInfrared', { - brightness: brightness - }, this.client.source); - packetObj.target = this.id; - this.client.send(packetObj, callback); -}; - -/** - * Requests the current state of the light - * @param {Function} callback a function to accept the data - */ -Light.prototype.getState = function(callback) { - validate.callback(callback, 'light getState method'); - - const packetObj = packet.create('getLight', {}, this.client.source); - packetObj.target = this.id; - const sqnNumber = this.client.send(packetObj); - this.client.addMessageHandler('stateLight', function(err, msg) { - if (err) { - return callback(err, null); - } - // Convert HSB to readable format - msg.color.hue = Math.round(msg.color.hue * (constants.HSBK_MAXIMUM_HUE / 65535)); - msg.color.saturation = Math.round(msg.color.saturation * (constants.HSBK_MAXIMUM_SATURATION / 65535)); - msg.color.brightness = Math.round(msg.color.brightness * (constants.HSBK_MAXIMUM_BRIGHTNESS / 65535)); - // Convert power to readable format - if (msg.power === 65535) { - msg.power = 1; - } - callback(null, { - color: msg.color, - power: msg.power, - label: msg.label + /** + * Changes the color to the given rgb value + * Note RGB poorly represents the color of light, prefer setting HSBK values with the color method + * @example light.colorRgb(255, 0, 0) + * @param {Integer} red value between 0 and 255 representing amount of red in color + * @param {Integer} green value between 0 and 255 representing amount of green in color + * @param {Integer} blue value between 0 and 255 representing amount of blue in color + * @param {Number} [duration] transition time in milliseconds + * @param {Function} [callback] called when light did receive message + */ + colorRgb(red, green, blue, duration, callback) { + validate.colorRgb(red, green, blue, 'light colorRgb method'); + validate.optionalDuration(duration, 'light colorRgb method'); + validate.optionalCallback(callback, 'light colorRgb method'); + + const hsbObj = utils.rgbToHsb({ + r: red, + g: green, + b: blue }); - }, sqnNumber); -}; - -/** - * Requests the current maximum setting for the infrared channel - * @param {Function} callback a function to accept the data - */ -Light.prototype.getMaxIR = function(callback) { - validate.callback(callback, 'light getMaxIR method'); - - const packetObj = packet.create('getInfrared', {}, this.client.source); - packetObj.target = this.id; - const sqnNumber = this.client.send(packetObj); - this.client.addMessageHandler('stateInfrared', function(err, msg) { - if (err) { - return callback(err, null); - } - - msg.brightness = Math.round(msg.brightness * (constants.HSBK_MAXIMUM_BRIGHTNESS / 65535)); - - callback(null, msg.brightness); - }, sqnNumber); -}; - -/** - * Requests hardware info from the light - * @param {Function} callback a function to accept the data with error and - * message as parameters - */ -Light.prototype.getHardwareVersion = function(callback) { - validate.callback(callback, 'light getHardwareVersion method'); - - const packetObj = packet.create('getVersion', {}, this.client.source); - packetObj.target = this.id; - const sqnNumber = this.client.send(packetObj); - this.client.addMessageHandler('stateVersion', function(err, msg) { - if (err) { - return callback(err, null); - } - const versionInfo = pick(msg, [ - 'vendorId', - 'productId', - 'version' - ]); - callback(null, assign( - versionInfo, - utils.getHardwareDetails(versionInfo.vendorId, versionInfo.productId) - )); - }, sqnNumber); -}; - -/** - * Requests used version from the microcontroller unit of the light - * @param {Function} callback a function to accept the data - */ -Light.prototype.getFirmwareVersion = function(callback) { - validate.callback(callback, 'light getFirmwareIgetFirmwareVersion method'); - - const packetObj = packet.create('getHostFirmware', {}, this.client.source); - packetObj.target = this.id; - const sqnNumber = this.client.send(packetObj); - this.client.addMessageHandler('stateHostFirmware', function(err, msg) { - if (err) { - return callback(err, null); - } - callback(null, pick(msg, [ - 'majorVersion', - 'minorVersion' - ])); - }, sqnNumber); -}; - -/** - * Requests infos from the microcontroller unit of the light - * @param {Function} callback a function to accept the data - */ -Light.prototype.getFirmwareInfo = function(callback) { - validate.callback(callback, 'light getFirmwareInfo method'); - - const packetObj = packet.create('getHostInfo', {}, this.client.source); - packetObj.target = this.id; - const sqnNumber = this.client.send(packetObj); - this.client.addMessageHandler('stateHostInfo', function(err, msg) { - if (err) { - return callback(err, null); - } - callback(null, pick(msg, [ - 'signal', - 'tx', - 'rx' - ])); - }, sqnNumber); -}; - -/** - * Requests wifi infos from for the light - * @param {Function} callback a function to accept the data - */ -Light.prototype.getWifiInfo = function(callback) { - validate.callback(callback, 'light getWifiInfo method'); - - const packetObj = packet.create('getWifiInfo', {}, this.client.source); - packetObj.target = this.id; - const sqnNumber = this.client.send(packetObj); - this.client.addMessageHandler('stateWifiInfo', function(err, msg) { - if (err) { - return callback(err, null); - } - callback(null, pick(msg, [ - 'signal', - 'tx', - 'rx' - ])); - }, sqnNumber); -}; - -/** - * Requests used version from the wifi controller unit of the light (wifi firmware version) - * @param {Function} callback a function to accept the data - */ -Light.prototype.getWifiVersion = function(callback) { - validate.callback(callback, 'light getWifiVersion method'); - - const packetObj = packet.create('getWifiFirmware', {}, this.client.source); - packetObj.target = this.id; - const sqnNumber = this.client.send(packetObj); - this.client.addMessageHandler('stateWifiFirmware', function(err, msg) { - if (err) { - return callback(err, null); - } - return callback(null, pick(msg, [ - 'majorVersion', - 'minorVersion' - ])); - }, sqnNumber); -}; - -/** - * Requests the label of the light - * @param {Function} callback a function to accept the data - * @param {Boolean} [cache=false] return cached result if existent - * @return {Function} callback(err, label) - */ -Light.prototype.getLabel = function(callback, cache) { - validate.callback(callback, 'light getLabel method'); - - if (cache !== undefined && typeof cache !== 'boolean') { - throw new TypeError('LIFX light getLabel method expects cache to be a boolean'); + this.color(hsbObj.h, hsbObj.s, hsbObj.b, 3500, duration, callback); } - if (cache === true) { - if (typeof this.label === 'string' && this.label.length > 0) { - return callback(null, this.label); + + /** + * Changes the color to the given rgb value + * Note RGB poorly represents the color of light, prefer setting HSBK values with the color method + * @example light.colorRgb('#FF0000') + * @param {String} hexString rgb hex string starting with # char + * @param {Number} [duration] transition time in milliseconds + * @param {Function} [callback] called when light did receive message + */ + colorRgbHex(hexString, duration, callback) { + if (typeof hexString !== 'string') { + throw new TypeError('LIFX light colorRgbHex method expects first parameter hexString to a string'); } + + validate.optionalDuration(duration, 'light colorRgbHex method'); + validate.optionalCallback(callback, 'light colorRgbHex method'); + + const rgbObj = utils.rgbHexStringToObject(hexString); + const hsbObj = utils.rgbToHsb(rgbObj); + this.color(hsbObj.h, hsbObj.s, hsbObj.b, 3500, duration, callback); } - const packetObj = packet.create('getLabel', { - target: this.id - }, this.client.source); - const sqnNumber = this.client.send(packetObj); - this.client.addMessageHandler('stateLabel', function(err, msg) { - if (err) { - return callback(err, null); + + /** + * Sets the Maximum Infrared brightness + * @param {Number} brightness infrared brightness from 0 - 100 (in %) + * @param {Function} [callback] called when light did receive message + */ + maxIR(brightness, callback) { + validate.irBrightness(brightness, 'light setMaxIR method'); + + brightness = Math.round(brightness / constants.IR_MAXIMUM_BRIGHTNESS * 65535); + + if (callback !== undefined && typeof callback !== 'function') { + throw new TypeError('LIFX light setMaxIR method expects callback to be a function'); } - return callback(null, msg.label); - }, sqnNumber); -}; - -/** - * Sets the label of light - * @example light.setLabel('Kitchen') - * @param {String} label new label to be set, maximum 32 bytes - * @param {Function} [callback] called when light did receive message - */ -Light.prototype.setLabel = function(label, callback) { - if (label === undefined || typeof label !== 'string') { - throw new TypeError('LIFX light setLabel method expects label to be a string'); - } - if (Buffer.byteLength(label, 'utf8') > 32) { - throw new RangeError('LIFX light setLabel method expects a maximum of 32 bytes as label'); - } - if (label.length < 1) { - throw new RangeError('LIFX light setLabel method expects a minimum of one char as label'); + + const packetObj = Packet.create('setInfrared', { + brightness: brightness + }, this.client.source); + packetObj.target = this.id; + this.client.send(packetObj, callback); } - validate.optionalCallback(callback, 'light setLabel method'); - - const packetObj = packet.create('setLabel', {label: label}, this.client.source); - packetObj.target = this.id; - this.client.send(packetObj, callback); -}; - -/** - * Requests ambient light value of the light - * @param {Function} callback a function to accept the data - */ -Light.prototype.getAmbientLight = function(callback) { - validate.callback(callback, 'light getAmbientLight method'); - - const packetObj = packet.create('getAmbientLight', {}, this.client.source); - packetObj.target = this.id; - const sqnNumber = this.client.send(packetObj); - this.client.addMessageHandler('stateAmbientLight', function(err, msg) { - if (err) { - return callback(err, null); - } - return callback(null, msg.flux); - }, sqnNumber); -}; - -/** - * Requests the power level of the light - * @param {Function} callback a function to accept the data - */ -Light.prototype.getPower = function(callback) { - validate.callback(callback, 'light getPower method'); - - const packetObj = packet.create('getPower', {}, this.client.source); - packetObj.target = this.id; - const sqnNumber = this.client.send(packetObj); - this.client.addMessageHandler('statePower', function(err, msg) { - if (err) { - return callback(err, null); - } - if (msg.level === 65535) { - msg.level = 1; - } - return callback(null, msg.level); - }, sqnNumber); -}; - -/** - * Requests the current color zone states from a light - * @param {Number} startIndex start color zone index - * @param {Number} [endIndex] end color zone index - * @param {Function} callback a function to accept the data - */ -Light.prototype.getColorZones = function(startIndex, endIndex, callback) { - validate.zoneIndex(startIndex, 'light getColorZones method'); - validate.optionalZoneIndex(endIndex, 'light getColorZones method'); - validate.optionalCallback(callback, 'light getColorZones method'); - - const packetObj = packet.create('getColorZones', {}, this.client.source); - packetObj.target = this.id; - packetObj.startIndex = startIndex; - packetObj.endIndex = endIndex; - const sqnNumber = this.client.send(packetObj); - if (endIndex === undefined || startIndex === endIndex) { - this.client.addMessageHandler('stateZone', function(err, msg) { + + /** + * Requests the current state of the light + * @param {Function} callback a function to accept the data + */ + getState(callback) { + validate.callback(callback, 'light getState method'); + + const packetObj = Packet.create('getLight', {}, this.client.source); + packetObj.target = this.id; + const sqnNumber = this.client.send(packetObj); + this.client.addMessageHandler('stateLight', function(err, msg) { if (err) { return callback(err, null); } @@ -427,72 +166,337 @@ Light.prototype.getColorZones = function(startIndex, endIndex, callback) { msg.color.hue = Math.round(msg.color.hue * (constants.HSBK_MAXIMUM_HUE / 65535)); msg.color.saturation = Math.round(msg.color.saturation * (constants.HSBK_MAXIMUM_SATURATION / 65535)); msg.color.brightness = Math.round(msg.color.brightness * (constants.HSBK_MAXIMUM_BRIGHTNESS / 65535)); + // Convert power to readable format + if (msg.power === 65535) { + msg.power = 1; + } callback(null, { - count: msg.count, - index: msg.index, - color: msg.color + color: msg.color, + power: msg.power, + label: msg.label }); }, sqnNumber); - } else { - this.client.addMessageHandler('stateMultiZone', function(err, msg) { + } + + /** + * Requests the current maximum setting for the infrared channel + * @param {Function} callback a function to accept the data + */ + getMaxIR(callback) { + validate.callback(callback, 'light getMaxIR method'); + + const packetObj = Packet.create('getInfrared', {}, this.client.source); + packetObj.target = this.id; + const sqnNumber = this.client.send(packetObj); + this.client.addMessageHandler('stateInfrared', function(err, msg) { if (err) { return callback(err, null); } - // Convert HSB values to readable format - msg.color.forEach(function(color) { - color.hue = Math.round(color.hue * (constants.HSBK_MAXIMUM_HUE / 65535)); - color.saturation = Math.round(color.saturation * (constants.HSBK_MAXIMUM_SATURATION / 65535)); - color.brightness = Math.round(color.brightness * (constants.HSBK_MAXIMUM_BRIGHTNESS / 65535)); - }); - callback(null, { - count: msg.count, - index: msg.index, - color: msg.color - }); + + msg.brightness = Math.round(msg.brightness * (constants.HSBK_MAXIMUM_BRIGHTNESS / 65535)); + + callback(null, msg.brightness); + }, sqnNumber); + } + + /** + * Requests hardware info from the light + * @param {Function} callback a function to accept the data with error and + * message as parameters + */ + getHardwareVersion(callback) { + validate.callback(callback, 'light getHardwareVersion method'); + + const packetObj = Packet.create('getVersion', {}, this.client.source); + packetObj.target = this.id; + const sqnNumber = this.client.send(packetObj); + this.client.addMessageHandler('stateVersion', function(err, msg) { + if (err) { + return callback(err, null); + } + const versionInfo = pick(msg, [ + 'vendorId', + 'productId', + 'version' + ]); + callback(null, assign( + versionInfo, + utils.getHardwareDetails(versionInfo.vendorId, versionInfo.productId) + )); + }, sqnNumber); + } + + /** + * Requests used version from the microcontroller unit of the light + * @param {Function} callback a function to accept the data + */ + getFirmwareVersion(callback) { + validate.callback(callback, 'light getFirmwareIgetFirmwareVersion method'); + + const packetObj = Packet.create('getHostFirmware', {}, this.client.source); + packetObj.target = this.id; + const sqnNumber = this.client.send(packetObj); + this.client.addMessageHandler('stateHostFirmware', function(err, msg) { + if (err) { + return callback(err, null); + } + callback(null, pick(msg, [ + 'majorVersion', + 'minorVersion' + ])); + }, sqnNumber); + } + + /** + * Requests infos from the microcontroller unit of the light + * @param {Function} callback a function to accept the data + */ + getFirmwareInfo(callback) { + validate.callback(callback, 'light getFirmwareInfo method'); + + const packetObj = Packet.create('getHostInfo', {}, this.client.source); + packetObj.target = this.id; + const sqnNumber = this.client.send(packetObj); + this.client.addMessageHandler('stateHostInfo', function(err, msg) { + if (err) { + return callback(err, null); + } + callback(null, pick(msg, [ + 'signal', + 'tx', + 'rx' + ])); + }, sqnNumber); + } + + /** + * Requests wifi infos from for the light + * @param {Function} callback a function to accept the data + */ + getWifiInfo(callback) { + validate.callback(callback, 'light getWifiInfo method'); + + const packetObj = Packet.create('getWifiInfo', {}, this.client.source); + packetObj.target = this.id; + const sqnNumber = this.client.send(packetObj); + this.client.addMessageHandler('stateWifiInfo', function(err, msg) { + if (err) { + return callback(err, null); + } + callback(null, pick(msg, [ + 'signal', + 'tx', + 'rx' + ])); + }, sqnNumber); + } + + /** + * Requests used version from the wifi controller unit of the light (wifi firmware version) + * @param {Function} callback a function to accept the data + */ + getWifiVersion(callback) { + validate.callback(callback, 'light getWifiVersion method'); + + const packetObj = Packet.create('getWifiFirmware', {}, this.client.source); + packetObj.target = this.id; + const sqnNumber = this.client.send(packetObj); + this.client.addMessageHandler('stateWifiFirmware', function(err, msg) { + if (err) { + return callback(err, null); + } + return callback(null, pick(msg, [ + 'majorVersion', + 'minorVersion' + ])); + }, sqnNumber); + } + + /** + * Requests the label of the light + * @param {Function} callback a function to accept the data + * @param {Boolean} [cache=false] return cached result if existent + * @return {Function} callback(err, label) + */ + getLabel(callback, cache) { + validate.callback(callback, 'light getLabel method'); + + if (cache !== undefined && typeof cache !== 'boolean') { + throw new TypeError('LIFX light getLabel method expects cache to be a boolean'); + } + if (cache === true) { + if (typeof this.label === 'string' && this.label.length > 0) { + return callback(null, this.label); + } + } + const packetObj = Packet.create('getLabel', { + target: this.id + }, this.client.source); + const sqnNumber = this.client.send(packetObj); + this.client.addMessageHandler('stateLabel', function(err, msg) { + if (err) { + return callback(err, null); + } + return callback(null, msg.label); + }, sqnNumber); + } + + /** + * Sets the label of light + * @example light.setLabel('Kitchen') + * @param {String} label new label to be set, maximum 32 bytes + * @param {Function} [callback] called when light did receive message + */ + setLabel(label, callback) { + if (label === undefined || typeof label !== 'string') { + throw new TypeError('LIFX light setLabel method expects label to be a string'); + } + if (Buffer.byteLength(label, 'utf8') > 32) { + throw new RangeError('LIFX light setLabel method expects a maximum of 32 bytes as label'); + } + if (label.length < 1) { + throw new RangeError('LIFX light setLabel method expects a minimum of one char as label'); + } + validate.optionalCallback(callback, 'light setLabel method'); + + const packetObj = Packet.create('setLabel', { + label: label + }, this.client.source); + packetObj.target = this.id; + this.client.send(packetObj, callback); + } + + /** + * Requests ambient light value of the light + * @param {Function} callback a function to accept the data + */ + getAmbientLight(callback) { + validate.callback(callback, 'light getAmbientLight method'); + + const packetObj = Packet.create('getAmbientLight', {}, this.client.source); + packetObj.target = this.id; + const sqnNumber = this.client.send(packetObj); + this.client.addMessageHandler('stateAmbientLight', function(err, msg) { + if (err) { + return callback(err, null); + } + return callback(null, msg.flux); }, sqnNumber); } -}; - -/** - * Changes a color zone range to the given HSBK value - * @param {Number} startIndex start zone index from 0 - 255 - * @param {Number} endIndex start zone index from 0 - 255 - * @param {Number} hue color hue from 0 - 360 (in °) - * @param {Number} saturation color saturation from 0 - 100 (in %) - * @param {Number} brightness color brightness from 0 - 100 (in %) - * @param {Number} [kelvin=3500] color kelvin between 2500 and 9000 - * @param {Number} [duration] transition time in milliseconds - * @param {Boolean} [apply=true] apply changes immediately or leave pending for next apply - * @param {Function} [callback] called when light did receive message - */ -Light.prototype.colorZones = function(startIndex, endIndex, hue, saturation, brightness, kelvin, duration, apply, callback) { - validate.zoneIndex(startIndex, 'color zones method'); - validate.zoneIndex(endIndex, 'color zones method'); - validate.colorHsb(hue, saturation, brightness, 'color zones method'); - - validate.optionalKelvin(kelvin, 'color zones method'); - validate.optionalDuration(duration, 'color zones method'); - validate.optionalBoolean(apply, 'apply', 'color zones method'); - validate.optionalCallback(callback, 'color zones method'); - - // Convert HSB values to packet format - hue = Math.round(hue / constants.HSBK_MAXIMUM_HUE * 65535); - saturation = Math.round(saturation / constants.HSBK_MAXIMUM_SATURATION * 65535); - brightness = Math.round(brightness / constants.HSBK_MAXIMUM_BRIGHTNESS * 65535); - - const appReq = apply === false ? constants.APPLICATION_REQUEST_VALUES.NO_APPLY : constants.APPLICATION_REQUEST_VALUES.APPLY; - const packetObj = packet.create('setColorZones', { - startIndex: startIndex, - endIndex: endIndex, - hue: hue, - saturation: saturation, - brightness: brightness, - kelvin: kelvin, - duration: duration, - apply: appReq - }, this.client.source); - packetObj.target = this.id; - this.client.send(packetObj, callback); -}; - -exports.Light = Light; + + /** + * Requests the power level of the light + * @param {Function} callback a function to accept the data + */ + getPower(callback) { + validate.callback(callback, 'light getPower method'); + + const packetObj = Packet.create('getPower', {}, this.client.source); + packetObj.target = this.id; + const sqnNumber = this.client.send(packetObj); + this.client.addMessageHandler('statePower', function(err, msg) { + if (err) { + return callback(err, null); + } + if (msg.level === 65535) { + msg.level = 1; + } + return callback(null, msg.level); + }, sqnNumber); + } + + /** + * Requests the current color zone states from a light + * @param {Number} startIndex start color zone index + * @param {Number} [endIndex] end color zone index + * @param {Function} callback a function to accept the data + */ + getColorZones(startIndex, endIndex, callback) { + validate.zoneIndex(startIndex, 'light getColorZones method'); + validate.optionalZoneIndex(endIndex, 'light getColorZones method'); + validate.optionalCallback(callback, 'light getColorZones method'); + + const packetObj = Packet.create('getColorZones', {}, this.client.source); + packetObj.target = this.id; + packetObj.startIndex = startIndex; + packetObj.endIndex = endIndex; + const sqnNumber = this.client.send(packetObj); + if (endIndex === undefined || startIndex === endIndex) { + this.client.addMessageHandler('stateZone', function(err, msg) { + if (err) { + return callback(err, null); + } + // Convert HSB to readable format + msg.color.hue = Math.round(msg.color.hue * (constants.HSBK_MAXIMUM_HUE / 65535)); + msg.color.saturation = Math.round(msg.color.saturation * (constants.HSBK_MAXIMUM_SATURATION / 65535)); + msg.color.brightness = Math.round(msg.color.brightness * (constants.HSBK_MAXIMUM_BRIGHTNESS / 65535)); + callback(null, { + count: msg.count, + index: msg.index, + color: msg.color + }); + }, sqnNumber); + } else { + this.client.addMessageHandler('stateMultiZone', function(err, msg) { + if (err) { + return callback(err, null); + } + // Convert HSB values to readable format + msg.color.forEach(function(color) { + color.hue = Math.round(color.hue * (constants.HSBK_MAXIMUM_HUE / 65535)); + color.saturation = Math.round(color.saturation * (constants.HSBK_MAXIMUM_SATURATION / 65535)); + color.brightness = Math.round(color.brightness * (constants.HSBK_MAXIMUM_BRIGHTNESS / 65535)); + }); + callback(null, { + count: msg.count, + index: msg.index, + color: msg.color + }); + }, sqnNumber); + } + } + + /** + * Changes a color zone range to the given HSBK value + * @param {Number} startIndex start zone index from 0 - 255 + * @param {Number} endIndex start zone index from 0 - 255 + * @param {Number} hue color hue from 0 - 360 (in °) + * @param {Number} saturation color saturation from 0 - 100 (in %) + * @param {Number} brightness color brightness from 0 - 100 (in %) + * @param {Number} [kelvin=3500] color kelvin between 2500 and 9000 + * @param {Number} [duration] transition time in milliseconds + * @param {Boolean} [apply=true] apply changes immediately or leave pending for next apply + * @param {Function} [callback] called when light did receive message + */ + colorZones(startIndex, endIndex, hue, saturation, brightness, kelvin, duration, apply, callback) { + validate.zoneIndex(startIndex, 'color zones method'); + validate.zoneIndex(endIndex, 'color zones method'); + validate.colorHsb(hue, saturation, brightness, 'color zones method'); + + validate.optionalKelvin(kelvin, 'color zones method'); + validate.optionalDuration(duration, 'color zones method'); + validate.optionalBoolean(apply, 'apply', 'color zones method'); + validate.optionalCallback(callback, 'color zones method'); + + // Convert HSB values to packet format + hue = Math.round(hue / constants.HSBK_MAXIMUM_HUE * 65535); + saturation = Math.round(saturation / constants.HSBK_MAXIMUM_SATURATION * 65535); + brightness = Math.round(brightness / constants.HSBK_MAXIMUM_BRIGHTNESS * 65535); + + const appReq = apply === false ? constants.APPLICATION_REQUEST_VALUES.NO_APPLY : constants.APPLICATION_REQUEST_VALUES.APPLY; + const packetObj = Packet.create('setColorZones', { + startIndex: startIndex, + endIndex: endIndex, + hue: hue, + saturation: saturation, + brightness: brightness, + kelvin: kelvin, + duration: duration, + apply: appReq + }, this.client.source); + packetObj.target = this.id; + this.client.send(packetObj, callback); + } +} + +module.exports.Light = Light; From ab410eed51b9f556d23764f82dcac62cf6cc2133 Mon Sep 17 00:00:00 2001 From: Ristomatti Airo Date: Sun, 29 Oct 2017 23:54:05 +0200 Subject: [PATCH 5/7] Unify module.exports use --- src/lifx.js | 4 ++-- src/lifx/client.js | 2 +- src/lifx/light.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lifx.js b/src/lifx.js index f36e381..e205ae0 100644 --- a/src/lifx.js +++ b/src/lifx.js @@ -13,7 +13,7 @@ lifx.utils = require('./lifx/utils'); lifx.Packet = require('./lifx/packet'); // Export light device object -lifx.Light = require('./lifx/light').Light; +lifx.Light = require('./lifx/light'); // Export client -lifx.Client = require('./lifx/client').Client; +lifx.Client = require('./lifx/client'); diff --git a/src/lifx/client.js b/src/lifx/client.js index 82f6f3a..d02d608 100644 --- a/src/lifx/client.js +++ b/src/lifx/client.js @@ -625,4 +625,4 @@ class Client { util.inherits(Client, EventEmitter); -exports.Client = Client; +module.exports = Client; diff --git a/src/lifx/light.js b/src/lifx/light.js index e62e676..9028b4c 100644 --- a/src/lifx/light.js +++ b/src/lifx/light.js @@ -499,4 +499,4 @@ class Light { } } -module.exports.Light = Light; +module.exports = Light; From de7f1820ff5fdd961c6d21000cd5c0f91ba80b96 Mon Sep 17 00:00:00 2001 From: Ristomatti Airo Date: Sun, 29 Oct 2017 23:56:54 +0200 Subject: [PATCH 6/7] Rename Client constructor params to options --- src/lifx/light.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lifx/light.js b/src/lifx/light.js index 9028b4c..6af80b9 100644 --- a/src/lifx/light.js +++ b/src/lifx/light.js @@ -4,15 +4,15 @@ const {Packet, constants, validate, utils} = require('../lifx'); const {assign, pick} = require('lodash'); class Light { - constructor(constr) { - this.client = constr.client; - this.id = constr.id; // Used to target the light - this.address = constr.address; - this.port = constr.port; + constructor(options) { + this.client = options.client; + this.id = options.id; // Used to target the light + this.address = options.address; + this.port = options.port; this.label = null; this.status = 'on'; - this.seenOnDiscovery = constr.seenOnDiscovery; + this.seenOnDiscovery = options.seenOnDiscovery; } /** From 03f371f44d6ca41432e544208ae7bdadb53d624b Mon Sep 17 00:00:00 2001 From: Ristomatti Airo Date: Mon, 30 Oct 2017 00:16:22 +0200 Subject: [PATCH 7/7] Use arrow functions to avoid explicit bind --- src/lifx/client.js | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/lifx/client.js b/src/lifx/client.js index d02d608..b5c410f 100644 --- a/src/lifx/client.js +++ b/src/lifx/client.js @@ -26,13 +26,13 @@ class Client { this.discoveryPacketSequence = 0; this.messageHandlers = [{ type: 'stateService', - callback: this.processDiscoveryPacket.bind(this) + callback: this.processDiscoveryPacket }, { type: 'stateLabel', - callback: this.processLabelPacket.bind(this) + callback: this.processLabelPacket }, { type: 'stateLight', - callback: this.processLabelPacket.bind(this) + callback: this.processLabelPacket }]; this.sequenceNumber = 0; this.lightOfflineTolerance = 3; @@ -148,15 +148,15 @@ class Client { } } - this.socket.on('error', function(err) { + this.socket.on('error', (err) => { this.isSocketBound = false; console.error('LIFX Client UDP error'); console.trace(err); this.socket.close(); this.emit('error', err); - }.bind(this)); + }); - this.socket.on('message', function(msg, rinfo) { + this.socket.on('message', (msg, rinfo) => { // Ignore own messages and false formats if (utils.getHostIPs().indexOf(rinfo.address) >= 0 || !Buffer.isBuffer(msg)) { return; @@ -188,9 +188,9 @@ class Client { this.emit('message', parsedMsg, rinfo); } - }.bind(this)); + }); - this.socket.bind(opts.port, opts.address, function() { + this.socket.bind(opts.port, opts.address, () => { this.isSocketBound = true; this.socket.setBroadcast(true); this.emit('listening'); @@ -203,7 +203,7 @@ class Client { if (typeof callback === 'function') { return callback(); } - }.bind(this)); + }); } /** @@ -221,7 +221,7 @@ class Client { * Sends a packet from the messages queue or stops the sending process * if queue is empty **/ - sendingProcess() { + sendingProcess = () => { if (!this.isSocketBound) { this.stopSendingProcess(); console.log('LIFX Client stopped sending due to unbound socket'); @@ -256,13 +256,13 @@ class Client { // Add to the end of the queue again this.messagesQueue.unshift(msg); } else { - this.messageHandlers.forEach(function(handler, hdlrIndex) { + this.messageHandlers.forEach((handler, hdlrIndex) => { if (handler.type === 'acknowledgement' && handler.sequenceNumber === msg.sequence) { this.messageHandlers.splice(hdlrIndex, 1); const err = new Error('No LIFX response after max resend limit of ' + this.resendMaxTimes); return handler.callback(err, null, null); } - }.bind(this)); + }); } } } else { @@ -275,7 +275,7 @@ class Client { */ startSendingProcess() { if (this.sendTimer === null) { // Already running? - this.sendTimer = setInterval(this.sendingProcess.bind(this), constants.MESSAGE_RATE_LIMIT); + this.sendTimer = setInterval(this.sendingProcess, constants.MESSAGE_RATE_LIMIT); } } @@ -297,7 +297,7 @@ class Client { */ startDiscovery(lights) { lights = lights || []; - const sendDiscoveryPacket = function() { + const sendDiscoveryPacket = () => { // Sign flag on inactive lights forEach(this.devices, bind(function(info, deviceId) { if (this.devices[deviceId].status !== 'off') { @@ -326,7 +326,7 @@ class Client { } else { this.discoveryPacketSequence += 1; } - }.bind(this); + }; this.discoveryTimer = setInterval( sendDiscoveryPacket, @@ -353,12 +353,12 @@ class Client { if (handler.sequenceNumber === msg.sequence) { // Remove if specific packet was request, since it should only be called once this.messageHandlers.splice(hdlrIndex, 1); - this.messagesQueue.forEach(function(packet, packetIndex) { + this.messagesQueue.forEach((packet, packetIndex) => { if (packet.transactionType === constants.PACKET_TRANSACTION_TYPES.REQUEST_RESPONSE && packet.sequence === msg.sequence) { this.messagesQueue.splice(packetIndex, 1); } - }.bind(this)); + }); // Call the function requesting the packet return handler.callback(null, msg, rinfo); @@ -388,7 +388,7 @@ class Client { * @param {Object} msg The discovery report package * @param {Object} rinfo Remote host details */ - processDiscoveryPacket(err, msg, rinfo) { + processDiscoveryPacket = (err, msg, rinfo) => { if (err) { return; } @@ -428,7 +428,7 @@ class Client { * @param {Object} err Error if existant * @param {Object} msg The state label package */ - processLabelPacket(err, msg) { + processLabelPacket = (err, msg) => { if (err) { return; }