Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Tile support #25

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion example/setMultiZoneEffect.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Searches for new lights, if one is found it sends a setMultiZoneEffect packet
*/

const LifxClient = require('../lib/lifx').Client;
const LifxClient = require('../src/lifx').Client;
const client = new LifxClient();

// Function running when packet was received by light
Expand Down
81 changes: 81 additions & 0 deletions example/tile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
'use strict';

const LifxClient = require('../src/lifx').Client;

const client = new LifxClient();

const LOOPTIME = 1000;
const TILE_LABEL = process.argv.length > 2 ? process.argv[process.argv.length - 1] : '*';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want this to come from the command line execution or could it be done differently? Also, there's a library yargs which makes it a lot easier to pull in command line parameters. If we're doing this in Node.js anyway, why not pull it in from a config when creating the LIFX client?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on this one. At least the script should fail if run without arguments and instruct on the correct usage. Example:

Usage: node tile.js <label>, e.g. node tile.js "My Tiles"


/*
* This example should show on all tiles of
* your chain a random pattern. After all bits
* of the tiles are set with the random pattern,
* the example reads back all set values
* from the tiles just to test the read back function.
* I could not compare the set values out of the reason
* that the setvalues are usally a bit modified.
*
* EASY: You should see an updating Pattern every two second
*/

function getBits(light, idx, chain) {
if (idx >= chain.totalCount) {
console.log('All Bits get');
setTimeout(() => setBits(light, 0, chain), LOOPTIME);
return;
}
console.log('getBits', idx);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this debugging info that needs to be removed? There are a few instances like these. Do we want to use console.info or console.debug instead? I always think of console.log statements as temporary for local development only.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it an test example programm, so why not 'log' that something is happens here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these are fine, they indicate at least what part of the code is currently executing. They could of course be more informative as I'm suspecting the API might be quite difficult to graps for many.

light.getTileState64(idx, (err) => {
if (err) {
console.error('getTileState64:', err);
return;
}
getBits(light, idx + 1, chain);
});
}

function setBits(light, idx, chain) {
if (idx >= chain.totalCount) {
setTimeout(() => getBits(light, 0, chain), LOOPTIME);
return;
}
console.log('setBits', idx, chain.totalCount);
const ofs = ~~(Math.random() * (65536 - (65536 / 64)));
light.setTileState64(idx,
Array(64).fill(undefined).map((_, idx) => ({
hue: (ofs + (idx * (65536 / 64))) & 0xffff,
saturation: 50000,
brightness: 16384,
kelvin: 4096
})), {duration: 100}, () => setBits(light, idx + 1, chain));
}

client.on('light-new', (light) => {
console.log(TILE_LABEL, light.id);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this console.log, it only prints out mysterious garbage of lights that aren't matching the given label (or in this case actually the id).

if (!(TILE_LABEL === '*' || TILE_LABEL === light.id)) {
return;
}
console.log('New light found.');
console.log('ID: ' + light.id);

light.getDeviceChain(function(err, chain) {
if (err) {
console.log(err);
}
light.on(0, () => {
setBits(light, 0, chain);
});
});
});

// Give feedback when running
client.on('listening', function() {
const address = client.address();
console.log(
'Started LIFX listening on ' +
address.address + ':' + address.port + '\n'
);
});

client.init();
117 changes: 117 additions & 0 deletions src/lifx/light.js
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,123 @@ Light.prototype.colorZones = function(startIndex, endIndex, hue, saturation, bri
};

/**
* Requests tile getDeviceChain 701
* @param {Function} callback a function to accept the data
*/
Light.prototype.getDeviceChain = function(callback) {
validate.callback(callback, 'light getDeviceChain method');

const packetObj = packet.create('getDeviceChain', {}, this.client.source);
packetObj.target = this.id;
const sqnNumber = this.client.send(packetObj);
this.client.addMessageHandler('stateDeviceChain', callback, sqnNumber);
};

/**
* Sets Tile Position
* @param {SetUserPosition} vals - value to set
* @param {Function} callback called when light did receive message
*/
Light.prototype.setUserPosition = function(vals, callback) {
vals = Object.assign({
tileIndex: 0,
userX: 0,
userY: 0,
reserved: 0
}, vals);
validate.isUInt8(vals.tileIndex, 'setUserPosition', 'tileIndex');
validate.isFloat(vals.userX, 'setUserPosition', 'userX');
validate.isFloat(vals.userY, 'setUserPosition', 'userX');
validate.isUInt16(vals.reserved || 0, 'setUserPosition', 'reserved');
validate.optionalCallback(callback, 'light setUserPosition method');
if (typeof callback !== 'function') {
callback = () => {};
}

const packetObj = packet.create('setUserPosition', vals, this.client.source);
packetObj.target = this.id;
this.client.send(packetObj, callback);
};

function defaultOptionsTileState64(options) {
const ret = Object.assign({
tileIndex: 0,
length: 64,
width: 8,
x: 0,
y: 0,
duration: 0,
reserved: 0
}, options);
validate.isUInt8(ret.tileIndex, 'TileState64:tileIndex');
validate.isUInt8(ret.length, 'TileState64:length');
validate.isUInt8(ret.width, 'TileState64:width');
validate.isUInt8(ret.x, 'TileState64:x');
validate.isUInt8(ret.y, 'TileState64:y');
validate.isUInt32(ret.duration, 'TileState64:duration');
validate.isUInt8(ret.reserved, 'TileState64:length');
return ret;
}

/**
* Requests tile GetTileState64 707
*
* Get the state of 64 pixels in the tile in a rectangle that has
* a starting point and width.
* The tileIndex is used to control the starting tile in the chain
* and length is used to get the state of that many tiles beginning
* from the tileIndex. This will result in a separate response from
* each tile.
* @param {Number} tileIndex unsigned 8-bit integer
* @param {GetTileState64} optionsOrCallback - tileState ignore tileIndex
* @param {Function} callback a function to accept the data
*/
Light.prototype.getTileState64 = function(tileIndex, optionsOrCallback, callback) {
const options = {tileIndex};
if (typeof optionsOrCallback === 'function') {
callback = optionsOrCallback;
} else {
Object.assign(options, optionsOrCallback);
}
validate.callback(callback, 'light getTileState64 method');
const packetObj = packet.create('getTileState64',
defaultOptionsTileState64(options), this.client.source);
packetObj.target = this.id;
const sqnNumber = this.client.send(packetObj);
this.client.addMessageHandler('stateTileState64', callback, sqnNumber);
};

/**
* This lets you set 64 pixels from a starting x and y for
* a rectangle with the specified width.
* For the LIFX Tile it really only makes sense to set x
* and y to zero, and width to 8.
* @param {Number} tileIndex unsigned 8-bit integer
* @param {Number} colors[64] 64 HSBK values
* @param {GetTileState64} optionsOrCallback - tileState ignore tileIndex
* @param {Function} [callback] called when light did receive message
*/
Light.prototype.setTileState64 = function(tileIndex, colors, optionsOrCallback, callback) {
validate.isUInt8(tileIndex, 'setTileState64', 'tileIndex');
const options = {tileIndex};
if (typeof optionsOrCallback === 'function') {
callback = optionsOrCallback;
} else {
Object.assign(options, optionsOrCallback);
}
validate.optionalCallback(callback, 'light setTileState64 method');
if (typeof callback !== 'function') {
callback = () => {};
}
const packetObj = packet.create('setTileState64',
Object.assign(defaultOptionsTileState64(options), {
colors: utils.buildColorsHsbk(colors, 64)
}), this.client.source);
packetObj.target = this.id;
this.client.send(packetObj, callback);
};

/*
* Changes a color zone range to the given HSBK value
* @param {String} effectName sets the desired effect, currently available options are: MOVE, OFF
* @param {Number} speed sets duration of one cycle of the effect, the higher the value the slower the effect animation
Expand Down
10 changes: 8 additions & 2 deletions src/lifx/packet.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,14 @@ Packet.typeList = [
{id: 505, name: 'stateCountZone'},
{id: 506, name: 'stateMultiZone'},
// {id: 507, name: 'getEffectZone'},
{id: 508, name: 'setMultiZoneEffect'}
// {id: 509, name: 'stateEffectZone'}
{id: 508, name: 'setMultiZoneEffect'},
// {id: 509, name: 'stateEffectZone'},
{id: 701, name: 'getDeviceChain'},
{id: 702, name: 'stateDeviceChain'},
{id: 703, name: 'setUserPosition'},
{id: 707, name: 'getTileState64'},
{id: 711, name: 'stateTileState64'},
{id: 715, name: 'setTileState64'}
];

/**
Expand Down
7 changes: 7 additions & 0 deletions src/lifx/packets/getDeviceChain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

const Packet = {
size: 0
};

module.exports = Packet;
73 changes: 73 additions & 0 deletions src/lifx/packets/getTileState64.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use strict';

const {validate} = require('../../lifx');

const Packet = {
size: 6
};

/**
* @typedef {Object} GetTileState64
* @property {Number} tileIndex an 8bit value
* @property {Number} length an 8bit value
* @property {Number} reserved an 8bit value
* @property {Number} x an 8bit value
* @property {Number} y an 8bit value
* @property {Number} width an 8bit valu
*/

/**
* Converts the given packet specific object into a packet
* @param {GetTileState64} obj object with configuration data
* @return {Buffer} packet
*/
Packet.toBuffer = function(obj) {
const buf = Buffer.alloc(this.size);
buf.fill(0);
let offset = 0;

['tileIndex', 'length', 'reserved', 'x', 'y', 'width'].forEach((field) => {
validate.isUInt8(obj[field], `getTileState64:${field}`);
});
buf.writeUInt8(obj.tileIndex, offset);
offset += 1;
buf.writeUInt8(obj.length, offset);
offset += 1;
buf.writeUInt8(obj.reserved, offset);
offset += 1;
buf.writeUInt8(obj.x, offset);
offset += 1;
buf.writeUInt8(obj.y, offset);
offset += 1;
buf.writeUInt8(obj.width, offset);
offset += 1;

return buf;
};

/**
* Converts the given packet specific object into a packet
* @param {Buffer} buf object with configuration data
* @return {GetTileState64} packet
*/
Packet.toObject = function(buf) {
const obj = {};
let offset = 0;

obj.tileIndex = buf.readUInt8(offset);
offset += 1;
obj.length = buf.readUInt8(offset);
offset += 1;
obj.reserved = buf.readUInt8(offset);
offset += 1;
obj.x = buf.readUInt8(offset);
offset += 1;
obj.y = buf.readUInt8(offset);
offset += 1;
obj.width = buf.readUInt8(offset);
offset += 1;

return obj;
};

module.exports = Packet;
71 changes: 71 additions & 0 deletions src/lifx/packets/hsbk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use strict';

const {validate} = require('../../lifx');

const Packet = {
size: 8
};

/**
* @typedef {Object} HSBK
* @property {Number} hsbk.hue - hue value
* @property {Number} hsbk.saturation - saturation value
* @property {Number} hsbk.brightness - brightness value
* @property {Number} hsbk.kelvin - kelvin value
*/

/**
* @typedef {Object} OffsetHSBK
* @property {Number} offset - offset after the buffer
* @property {HSBK} hsbk - HSBK value
*
*/

/**
* Converts the given packet specific object into a packet
* @param {Buffer} buf hsbk as buffer
* @param {Number} offset offset to the buffer
* @return {OffsetHSBK} packet
*/
Packet.toObject = function(buf, offset) {
const hsbk = {};
hsbk.hue = buf.readUInt16LE(offset);
offset += 2;
hsbk.saturation = buf.readUInt16LE(offset);
offset += 2;
hsbk.brightness = buf.readUInt16LE(offset);
offset += 2;
hsbk.kelvin = buf.readUInt16LE(offset);
offset += 2;
return {offset, hsbk};
};

/**
* @typedef {Object} OffsetBuffer
* @property {number} offset - offset after the buffer
* @property {Buffer} buffer - buffer
*/
/**
* Converts the given packet specific object into a packet
* @param {Buffer} buffer output buffer
* @param {Number} offset offset in the output buffer
* @param {HSBK} hsbk offset in the output buffer
* @return {OffsetBuffer} packet
*/
Packet.toBuffer = function(buffer, offset, hsbk) {
validate.isUInt16(hsbk.hue);
validate.isUInt16(hsbk.saturation);
validate.isUInt16(hsbk.brightness);
validate.isUInt16(hsbk.kelvin);
buffer.writeUInt16LE(hsbk.hue, offset);
offset += 2;
buffer.writeUInt16LE(hsbk.saturation, offset);
offset += 2;
buffer.writeUInt16LE(hsbk.brightness, offset);
offset += 2;
buffer.writeUInt16LE(hsbk.kelvin, offset);
offset += 2;
return {offset, buffer};
};

module.exports = Packet;
10 changes: 10 additions & 0 deletions src/lifx/packets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,13 @@ packets.setColorZones = require('./setColorZones');

packets.stateZone = require('./stateZone');
packets.stateMultiZone = require('./stateMultiZone');

packets.tile = require('./tile');
packets.hsbk = require('./hsbk');
packets.getDeviceChain = require('./getDeviceChain');
packets.stateDeviceChain = require('./stateDeviceChain');

packets.setUserPosition = require('./setUserPosition');
packets.setTileState64 = require('./setTileState64');
packets.getTileState64 = require('./getTileState64');
packets.stateTileState64 = require('./stateTileState64');
Loading