From d58cbb149f216089ab8fbb3c0159b92e5e5c7622 Mon Sep 17 00:00:00 2001 From: marcus Date: Sun, 10 May 2015 21:00:22 +0200 Subject: [PATCH] Improved and simplified synchronization of command sequences Added repeat mode to send each sequences multiple times --- README.md | 35 ++++++++++++++++--- example/example.js | 25 +++++++++++--- package.json | 9 +++-- src/milight.js | 86 ++++++++++++++++++++++++++-------------------- 4 files changed, 105 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 108cadf..80932ab 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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; @@ -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(); \ No newline at end of file + 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 \ No newline at end of file diff --git a/example/example.js b/example/example.js index b721c47..d15c6f6 100644 --- a/example/example.js +++ b/example/example.js @@ -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"); +}); diff --git a/package.json b/package.json index d14cce8..3fdfc35 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "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", @@ -8,7 +8,12 @@ }, "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" diff --git a/src/milight.js b/src/milight.js index 40035a8..19ff6b5 100644 --- a/src/milight.js +++ b/src/milight.js @@ -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 @@ -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 + })); }; // @@ -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); + }) }) }) }; @@ -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) }); };