Skip to content

Commit

Permalink
Improved and simplified synchronization of command sequences
Browse files Browse the repository at this point in the history
Added repeat mode to send each sequences multiple times
  • Loading branch information
mwittig committed May 10, 2015
1 parent 995ec86 commit d58cbb1
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 50 deletions.
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
# node-milight-promise

A node module to control Milight LED bulbs and OEM equivalents auch as Rocket LED, Limitless LED Applamp,
Easybulb, s`luce, iLight, iBulb, and Kreuzer

NOTE: This is work in progress.
Easybulb, s`luce, iLight, iBulb, and Kreuzer. This library use Promises to automatically synchronize the command
sequences. So there is no need for nesting commands using callback. Of course, each API call returns a promise which
can be used to wait for the call to be resolved or rejected.

## Introduction

Milight uses a very primitive three-byte-sequence one-way communication proptocol where each command must be sent in a
single UDP packet. It is fire & forget really similar to simply RF protocols fro garage door openers and such.
Compared to other Milight libraries I am using a more more aggressive timing for the delay between sending UDP command
packets (```delayBetweenCommands``` property).
Generally, the delay is to reduce the chances of UDP package loss on the network. A longer delay may lower the risk of
data loss, however, data loss is likely to occur occasionally on a wireless network. Keep in mind, that apart from your
Wifi network there is another lossy communications channel between the Milight Controller and the bulbs. My strategy
against loss is to repeat each command send three times (```commandRepeat``` property).

## Usage Example

Expand All @@ -12,7 +23,9 @@ NOTE: This is work in progress.


var light = new Milight({
ip: "255.255.255.255"
ip: "255.255.255.255",
delayBetweenCommands: 35,
commandRepeat: 3
}),
zone = 1;

Expand All @@ -23,4 +36,16 @@ NOTE: This is work in progress.
light.pause(1000);
light.sendCommands(commands.rgbw.on(zone), commands.rgbw.whiteMode(zone));

light.close();
light.close();

Instead of providing the broadcast IP address which is the default, you should provide the IP address
of the Milight Controller for unicast mode.

## History

* 20150426, V0.0.1
* Initial Version

* 20150510, V0.0.2
* Improved and simplified synchronization of command sequences
* Added repeat mode to send each sequences multiple times
25 changes: 20 additions & 5 deletions example/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,31 @@ var commands = require('../src/index').commands;


var light = new Milight({
ip: "255.255.255.255"
ip: "255.255.255.255",
delayBetweenCommands: 30,
commandRepeat: 3
}),
zone = 1;

light.sendCommands(commands.rgbw.on(zone), commands.rgbw.brightness(100));
for (var x=0; x<256; x++) {
for (var x=0; x<256; x+=5) {
light.sendCommands( commands.rgbw.on(zone), commands.rgbw.hue(x));
}
light.pause(1000);
light.sendCommands(commands.rgbw.on(zone), commands.rgbw.whiteMode(zone));

light.close();
light.sendCommands(commands.rgbw.off(zone));
light.pause(1000);
light.sendCommands(commands.rgbw.on(zone));
light.pause(1000);
light.sendCommands(commands.rgbw.off(zone));
light.pause(1000);
light.sendCommands(commands.rgbw.whiteMode(zone), commands.rgbw.on(zone));
light.pause(1000);
light.sendCommands( commands.rgbw.on(zone), commands.rgbw.hue(255));
light.pause(1000);
light.sendCommands( commands.rgbw.on(zone), commands.rgbw.hue(126));
light.pause(1000);
light.sendCommands( commands.rgbw.off(zone));
light.close().then(function() {
console.log("Finished");
});

9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
{
"name": "node-milight-promise",
"version": "0.0.1",
"version": "0.0.2",
"description": "A node module to control Milight LED bulbs and OEM equivalents auch as Rocket LED, Limitless LED Applamp, Easybulb, s`luce, iLight, iBulb, and Kreuzer",
"author": {
"name": "Marcus Wittig",
"url": "https://github.com/mwittig/node-milight-promise"
},
"main": "src/index",
"homepage": "https://github.com/mwittig/node-milight-promise",
"license": "MIT",
"licenses": [
{
"type": "MIT",
"url": "https://github.com/mwittig/node-milight/blob/master/LICENSE"
}
],
"repository": {
"type": "git",
"url": "https://github.com/mwittig/node-milight-promise"
Expand Down
86 changes: 48 additions & 38 deletions src/milight.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ var Promise = require('bluebird'),
const
DEFAULT_IP = '255.255.255.255',
DEFAULT_PORT = 8899,
DEFAULT_SEND_MESSAGE_DELAY = 100,
DEFAULT_COMMAND_DELAY = 1;
DEFAULT_COMMAND_DELAY = 30,
DEFAULT_COMMAND_REPEAT=3;

//
// Local helper functions
Expand Down Expand Up @@ -41,10 +41,17 @@ var MilightController = function (options) {
this.ip = options.ip || DEFAULT_IP;
this._broadcastMode = this.ip === DEFAULT_IP;
this.port = options.port || DEFAULT_PORT;
this._delayBetweenMessages = options.delayBetweenMessages || DEFAULT_SEND_MESSAGE_DELAY;
this._delayBetweenCommands = options.delayBetweenCommands || DEFAULT_COMMAND_DELAY;
this._commandRepeat = options.commandRepeat || DEFAULT_COMMAND_REPEAT;
this._socketInit = Promise.resolve();
this._lastRequest = this._createSocket();
this._sendRequest = Promise.resolve();
debug("Milight:" + JSON.stringify({
ip: this.ip,
port: this.port,
delayBetweenCommands: this._delayBetweenCommands,
commandRepeat: this._commandRepeat
}));
};

//
Expand Down Expand Up @@ -90,28 +97,31 @@ MilightController.prototype._sendThreeByteArray = function (threeByteArray) {
var buffer = new Buffer(threeByteArray),
self = this;

return new Promise(function (resolve, reject) {
self._createSocket().then(function() {
self.clientSocket.send(buffer
, 0
, buffer.length
, self.port
, self.ip
, function (err, bytes) {
if (err) {
debug("UDP socket error:" + err);
return reject(err);
return self._sendRequest = Promise.settle([self._sendRequest]).then(function () {

return new Promise(function (resolve, reject) {
self._createSocket().then(function() {
self.clientSocket.send(buffer
, 0
, buffer.length
, self.port
, self.ip
, function (err, bytes) {
if (err) {
debug("UDP socket error:" + err);
return reject(err);
}
else {
debug('Milight: bytesSent=' + bytes +', buffer=[' + buffer2hex(buffer) + ']');
return Promise.delay(self._delayBetweenCommands).then(function () {
return resolve();
});
}
}
else {
debug('Milight: bytesSent=' + bytes +', buffer=[' + buffer2hex(buffer) + ']');
Promise.delay(self._delayBetweenCommands).then(function () {
return resolve();
});
}
}
);
}).catch(function(error) {
reject(error);
);
}).catch(function(error) {
return reject(error);
})
})
})
};
Expand All @@ -132,25 +142,25 @@ MilightController.prototype.sendCommands = function (varArgArray) {

return self._lastRequest = Promise.settle([self._lastRequest]).then(function () {

for (var i = 0; i < varArgs.length; i++) {
if (!varArgs[i] instanceof Array) {
return Promise.reject(new Error("Array arguments required"));
}
else {
var arg = varArgs[i];
if (((arg.length) > 0) && (arg[0] instanceof Array)) {
for (var j = 0; j < arg.length; j++) {
stackedCommands.push(self._sendThreeByteArray(arg[j]));
}
for (var r=0; r < self._commandRepeat; r++) {
for (var i = 0; i < varArgs.length; i++) {
if (!varArgs[i] instanceof Array) {
return Promise.reject(new Error("Array arguments required"));
}
else {
stackedCommands.push(self._sendThreeByteArray(arg));
var arg = varArgs[i];
if (((arg.length) > 0) && (arg[0] instanceof Array)) {
for (var j = 0; j < arg.length; j++) {
stackedCommands.push(self._sendThreeByteArray(arg[j]));
}
}
else {
stackedCommands.push(self._sendThreeByteArray(arg));
}
}
}
}
return Promise.settle(stackedCommands).then(function () {
return Promise.delay(self._delayBetweenMessages);
});
return Promise.settle(stackedCommands)
});
};

Expand Down

0 comments on commit d58cbb1

Please sign in to comment.