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

added the TileApi to control real colorful lifx tiles #15

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions example/tile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict';

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

const client = new LifxClient();

const LOOPTIME = 10000;
const TILE_LABEL = '*';

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

function getBits(light, idx, chain) {
if (idx >= chain.total_count) {
console.log('All Bits get');
setTimeout(() => setBits(light, 0, chain), LOOPTIME);
return;
}
light.getTileState64(idx, (err) => {
if (err) {
console.error('getTileState64:', err);
return;
}
getBits(light, idx + 1, chain);
});
}

function setBits(light, idx, chain) {
mabels marked this conversation as resolved.
Show resolved Hide resolved
if (idx >= chain.total_count) {
setTimeout(() => getBits(light, 0, chain), LOOPTIME);
return;
}
const ofs = ~~(Math.random() * (65536 - (65536 / 64)));
light.setTileState64(idx, {duration: 100},
(new Array(64)).fill(undefined).map((_, idx) => ({
hue: (ofs + (idx * (65536 / 64))) & 0xffff,
saturation: 50000,
brightness: 16384,
kelvin: 4096
})), () => setBits(light, idx + 1, chain));
}

client.on('light-new', (light) => {
if (!(TILE_LABEL === '*' || TILE_LABEL === light.id)) {
return;
}
console.log('New light found.');
console.log('ID: ' + light.id);

light.getDeviceChain(function(err, chain) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This would now attempt to call setTileState64 on any light. Add a const TILE_LABEL = 'MyTile'; and trigger only on a light matching the label.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Collaborator

Choose a reason for hiding this comment

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

This does not work as light label has not resolved at this point. It was partly my bad as I didn't remember this while reviewing this the first time. It would improve the example a lot if a label could be used but for this purpose it would be enough to just rename TILE_LABEL TO TILE_LIGHT_ID for example.

if (err) {
console.log(err);
}
setBits(light, 0, chain);
});
});

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

client.init();
155 changes: 155 additions & 0 deletions src/lifx/light.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,4 +529,159 @@ Light.prototype.colorZones = function(startIndex, endIndex, hue, saturation, bri
this.client.send(packetObj, callback);
};

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

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

/**
* Sets Tile Position
* @example light.setUserPosition(0, 12, 13, 0, () => {})
* @param {Number} tileIndex unsigned 8-bit integer
* @param {Number} userX 32-bit float
* @param {Number} userY 32-bit float
* @param {Number} reserved 16-bit integer
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is this reserved value and how would a user of this library know what to put there? This applies to all functions.

* @param {Function} callback called when light did receive message
*/
Light.prototype.setUserPosition = function(tileIndex, userX, userY, reserved, callback) {
validate.isUInt8(tileIndex, 'setUserPosition', 'tileIndex');
validate.isXY(userX, userY, 'setUserPosition', 'user');
validate.optionalCallback(callback, 'light setUserPosition method');

const packetObj = packet.create('setUserPositiion', {
tileIndex,
userX,
userY,
reserved: reserved || 0
}, this.client.source);
packetObj.target = this.id;
this.client.send(packetObj, callback);
};

function defaultOptionsTileState64(options) {
const ret = {
length: options.length,
x: options.x,
y: options.y,
width: options.width,
reserved: options.reserved,
duration: options.duration
};
if (typeof ret.length !== 'number') {
ret.length = 64;
}
if (typeof ret.width !== 'number') {
ret.length = 8;
}
if (typeof ret.x !== 'number') {
ret.x = 0;
}
if (typeof ret.y !== 'number') {
ret.y = 0;
}
if (typeof ret.duration !== 'number') {
ret.duration = 8;
}
return ret;
}

/**
* Requests tile GetTileState64 707
*
* Get the state of 64 pixels in the tile in a rectangle that has
* a starting point and width.
* The tileIndex is used to control the starting tile in the chain
* and length is used to get the state of that many tiles beginning
* from the tileIndex. This will result in a separate response from
* each tile.
* @param {Number} tileIndex unsigned 8-bit integer
* @param {Object} options - options passed to
* @param {Number} options.length unsigned 8-bit integer (default 64)
* @param {Number} options.x unsigned 8-bit integer (default 0)
* @param {Number} options.y unsigned 8-bit integer (default 0)
* @param {Number} options.width unsigned 8-bit integer (default 8)
* @param {Number} options.reserved unsigned 8-bit integer
* @param {Function} callback a function to accept the data
*/
Light.prototype.getTileState64 = function(tileIndex, options, callback) {
validate.isUInt8(tileIndex, 'getTileState64', 'tileIndex');
if (typeof options === 'function') {
callback = options;
options = {};
}
mabels marked this conversation as resolved.
Show resolved Hide resolved
validate.callback(callback, 'light getTileState64 method');
const {length, x, y, width, reserved} = defaultOptionsTileState64(options);
const packetObj = packet.create('getTileState64', {
tileIndex,
length,
reserved: reserved || 0,
x,
y,
width
},
this.client.source
);
packetObj.target = this.id;
const sqnNumber = this.client.send(packetObj);
this.client.addMessageHandler('stateTileState64', function(err, msg) {
if (err) {
return callback(err, null);
}
return callback(null, msg);
}, sqnNumber);
};

/**
* This lets you set 64 pixels from a starting x and y for
* a rectangle with the specified width.
* For the LIFX Tile it really only makes sense to set x
* and y to zero, and width to 8.
* @param {Number} tileIndex unsigned 8-bit integer
* @param {Number} colors[64] 64 HSBK values
* @param {Object} options - options passed to
* @param {Number} options.length unsigned 8-bit integer (default 0)
* @param {Number} options.x unsigned 8-bit integer (default 8)
* @param {Number} options.y unsigned 8-bit integer (default 8)
* @param {Number} options.width unsigned 8-bit integer (default 8)
* @param {Number} options.duration unsigned 32-bit integer (default 0)
* @param {Number} options.reserved unsigned 8-bit integer
* @param {Function} [callback] called when light did receive message
*/
Light.prototype.setTileState64 = function(tileIndex, colors, options, callback) {
validate.isUInt8(tileIndex, 'setTileState64', 'tileIndex');
if (typeof options === 'function') {
callback = options;
options = {};
}
mabels marked this conversation as resolved.
Show resolved Hide resolved
const {length, x, y, width, duration, reserved} = defaultOptionsTileState64(options);
const set64colors = utils.buildColorsHsbk(colors, 64);
validate.optionalCallback(callback, 'light setTileState64 method');

const packetObj = packet.create('setTileState64', {
tileIndex,
length,
reserved: reserved || 0,
x,
y,
width,
duration,
colors: set64colors
}, this.client.source);
packetObj.target = this.id;
this.client.send(packetObj, callback);
};

exports.Light = Light;
8 changes: 7 additions & 1 deletion src/lifx/packet.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,16 @@ Packet.typeList = [
{id: 503, name: 'stateZone'},
{id: 504, name: 'getCountZone'},
{id: 505, name: 'stateCountZone'},
{id: 506, name: 'stateMultiZone'}
{id: 506, name: 'stateMultiZone'},
// {id: 507, name: 'getEffectZone'},
// {id: 508, name: 'setEffectZone'},
// {id: 509, name: 'stateEffectZone'}
{id: 701, name: 'getDeviceChain'},
{id: 702, name: 'stateDeviceChain'},
{id: 706, name: 'setUserPosition'},
{id: 707, name: 'getTileState64'},
{id: 711, name: 'stateTileState64'},
{id: 715, name: 'setTileState64'}
];

/**
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;
44 changes: 44 additions & 0 deletions src/lifx/packets/getTileState64.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';

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

const Packet = {
size: 6
};

/**
* Converts the given packet specific object into a packet
* @param {Object} obj object with configuration data
* @param {Number} obj.tileIndex an 8bit value
* @param {Number} obj.length an 8bit value
* @param {Number} obj.reserved an 8bit value
* @param {Number} obj.x an 8bit value
* @param {Number} obj.y an 8bit value
* @param {Number} obj.width an 8bit value
* @return {Buffer} packet
*/
Packet.toBuffer = function(obj) {
const buf = Buffer.alloc(this.size);
buf.fill(0);
let offset = 0;

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

return buf;
};

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

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

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

packets.setTileState64 = require('./setTileState64');
packets.getTileState64 = require('./getTileState64');
packets.stateTileState64 = require('./stateTileState64');
74 changes: 74 additions & 0 deletions src/lifx/packets/setTileState64.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict';

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

const Packet = {
size: (obj) => 10 + (obj.colors.length * (8)),
HSBK: {
toBuffer: (obj, buf, offset) => {
validate.isUInt16(obj.hue, 'setTileState64:HSBK:hue');
buf.writeUInt16LE(obj.hue, offset);
offset += 2;

validate.isUInt16(obj.saturation, 'setTileState64:HSBK:saturation');
buf.writeUInt16LE(obj.saturation, offset);
offset += 2;

validate.isUInt16(obj.brightness, 'setTileState64:HSBK:brightness');
buf.writeUInt16LE(obj.brightness, offset);
offset += 2;

validate.isUInt16(obj.kelvin, 'setTileState64:HSBK:kelvin');
buf.writeUInt16LE(obj.kelvin, offset);
offset += 2;

return offset;
}
}
};

/**
* Converts the given packet specific object into a packet
* @param {Object} obj object with configuration data
* @param {Number} obj.tileIndex 8bit value
* @param {Number} obj.length 8bit value
* @param {Number} obj.reserved 8bit value
* @param {Number} obj.x 8bit value
* @param {Number} obj.y 8bit value
* @param {Number} obj.width 8bit value
* @param {Number} obj.duration 8bit value
* @param {Number} [obj.duration] transition time in milliseconds
* @param {Array} obj.colors an array of HSBK values
* @return {Buffer} packet
*/
Packet.toBuffer = function(obj) {
const buf = Buffer.alloc(Packet.size(obj));
buf.fill(0);
let offset = 0;

['tileIndex', 'length', 'reserved', 'x', 'y', 'width'].forEach((field) => {
validate.isUInt8(obj[field], `setTileState64:${field}`);
});
// obj.stream field has unknown function so leave it as 0
buf.writeUInt8(obj.tileIndex, offset);
offset += 1;
buf.writeUInt8(obj.length, offset);
offset += 1;
buf.writeUInt8(obj.reserved || 0, offset);
offset += 1;
buf.writeUInt8(obj.x, offset);
offset += 1;
buf.writeUInt8(obj.y, offset);
offset += 1;
buf.writeUInt8(obj.width, offset);
offset += 1;
validate.isUInt32(obj.duration, 'setTileState64:duration');
buf.writeUInt32LE(obj.duration, offset);
offset += 4;
obj.colors.forEach((color) => {
offset = Packet.HSBK.toBuffer(color, buf, offset);
});
return buf;
};

module.exports = Packet;
Loading