var dgram = require('dgram'); var protocol = require('./protocol'); var BufferReader = require('buffer-reader'); var responseHandlers = {}; function registerResponseHandler(command, callback) { if (!responseHandlers.hasOwnProperty(command)) responseHandlers[command] = [callback]; else responseHandlers[command].push(callback); } function callResponseHandlers(command, reader, error) { if (!responseHandlers.hasOwnProperty(command)) return; newHandlers = []; responseHandlers[command].forEach(function(callback) { if (!callback(reader, error)) newHandlers.push(callback); }); responseHandlers[command] = newHandlers; } var serverHost = ''; var serverPort = 0; var client = dgram.createSocket('udp4'); client.on('message', function (message, remote) { console.log(message.toString('hex')); if (message.length < 2) return; var reader = new BufferReader(message); if (reader.nextInt8() !== protocol.Command.Reply) return; var command = reader.nextInt8(); if (command === protocol.Command.Error) callResponseHandlers(reader.nextInt8(), reader, true) else callResponseHandlers(command, reader, false); }); function requestResponse(buffer, callback, withTimeout) { if (buffer === null || buffer.length == 0) return; console.log('> ' + buffer.toString('hex')); var command = buffer.readInt8(0); var cancelled = false; if (typeof(withTimeout) == 'undefined') withTimeout = true; if (withTimeout) { var timeout = setTimeout(function() { cancelled = true; callback(null, true); clearTimeout(timeout); }, 2000); } registerResponseHandler(command, function(reader, error) { if (cancelled) return; if (withTimeout) clearTimeout(timeout); callback(reader, error); return true; }); client.send(buffer, 0, buffer.length, serverPort, serverHost, function(err, bytes) { if (err) onError(); }); } function readModeData(mode, reader) { switch (mode) { case protocol.Mode.Static: return { brightness: reader.nextInt16LE(), easeTime: reader.nextInt16LE() }; case protocol.Mode.Custom: var values = []; while (reader.tell() < reader.buf.length) values.push(reader.nextInt16LE()); return { brightness: values }; case protocol.Mode.Alternate: return { interval: reader.nextInt16LE(), brightness: reader.nextInt16LE() }; case protocol.Mode.Slide: return { interval: reader.nextInt16LE(), brightness: reader.nextInt16LE(), direction: reader.nextInt8(), fadeOutTime: reader.nextInt16LE() }; } return null; } function readRangeData(reader) { var data = { useScaling: reader.nextInt8() == 1, values: [] }; while (reader.tell() < reader.buf.length) data.values.push({ start: reader.nextInt16LE(), end: reader.nextInt16LE() }); return data; } function lsb(value) { return value & 0xFF; } function msb(value) { return (value >> 8) & 0xFF; } function getBrightness(value) { if (typeof(value) == 'string' && value.substr(-1) === '%') return (Number(value.substr(0, value.length - 1)) * 4096 / 100); return Number(value) || 0; } function writeModeData(mode, data) { switch (mode) { case protocol.Mode.Static: var brightness = getBrightness(data.brightness); return new Buffer([protocol.Command.SetMode, mode, lsb(brightness), msb(brightness), lsb(500), msb(500)]); case protocol.Mode.Custom: var brightness = typeof(data.brightness) !== 'undefined' ? data.brightness.split(',') : []; var valueCount = Math.min(16, brightness.length); var buffer = Buffer.alloc(2 + (valueCount * 2)); buffer.writeUInt8(protocol.Command.SetMode, 0); buffer.writeUInt8(mode, 1); for (var index = 0; index < valueCount; index++) buffer.writeUInt16LE(getBrightness(brightness[index]), 2 + (index * 2)); return buffer; case protocol.Mode.Alternate: var brightness = getBrightness(data.brightness); if (typeof(data.interval) == 'undefined') data.interval = 500; return new Buffer([protocol.Command.SetMode, mode, lsb(data.interval), msb(data.interval), lsb(brightness), msb(brightness)]); case protocol.Mode.Slide: var brightness = getBrightness(data.brightness); if (typeof(data.interval) == 'undefined') data.interval = 500; if (typeof(data.direction) == 'undefined') data.direction = 0; if (typeof(data.fadeOutTime) == 'undefined') data.fadeOutTime = 0; return new Buffer([protocol.Command.SetMode, mode, lsb(data.interval), msb(data.interval), lsb(brightness), msb(brightness), data.direction, lsb(data.fadeOutTime), msb(data.fadeOutTime)]); } } function writeRangeData(data) { var start = typeof(data.start) !== 'undefined' ? data.start.split(',') : []; var end = typeof(data.end) !== 'undefined' ? data.end.split(',') : []; var valueCount = Math.min(16, start.length, end.length); var buffer = Buffer.alloc(2 + (valueCount * 4)); buffer.writeUInt8(protocol.Command.SetRange, 0); buffer.writeUInt8(data.useScaling ? 1 : 0, 1); for (var index = 0; index < valueCount; index++) { buffer.writeUInt16LE(getBrightness(start[index]), 2 + (index * 4)); buffer.writeUInt16LE(getBrightness(end[index]), 4 + (index * 4)); } return buffer; } module.exports = { init: function(host, port) { serverHost = host; serverPort = port; }, ping: function(callback) { requestResponse(new Buffer([protocol.Command.Ping]), function(reader, error) { if (!error) { callback( { stepCount: reader.nextInt8() }, false); } else callback(null, true); }); }, getMode: function(callback) { requestResponse(new Buffer([protocol.Command.GetMode]), function(reader, error) { if (!error) { var data = { mode: reader.nextInt8() }; data.data = readModeData(data.mode, reader); callback(data, false); } else callback(null, true); }); }, setMode: function(mode, data, callback) { if (!protocol.Mode.hasOwnProperty(mode)) return; requestResponse(writeModeData(protocol.Mode[mode], data), function(reader, error) { if (!error) { var data = { mode: reader.nextInt8() }; data.data = readModeData(data.mode, reader); callback(data, false); } else callback(null, true); }); }, getRange: function(callback) { requestResponse(new Buffer([protocol.Command.GetRange]), function(reader, error) { if (!error) { callback(readRangeData(reader), false); } else callback(null, true); }); }, setRange: function(data, callback) { requestResponse(writeRangeData(data), function(reader, error) { if (!error) { callback(readRangeData(reader), false); } else callback(null, true); }); }, updateFirmware: function(data, callback) { if (typeof(data.host) == 'undefined') data.host = ''; if (typeof(data.port) == 'undefined') data.port = 80; if (typeof(data.path) == 'undefined') data.path = ''; var buffer = Buffer.alloc(1 + (data.host.length + 1) + 2 + (data.path.length + 1)); buffer.writeUInt8(protocol.Command.UpdateFirmware, 0); var position = 1; buffer.writeUInt16LE(data.port, position); position += 2; buffer.write(data.host, position); position += data.host.length; buffer.writeUInt8(0, position); position++; buffer.write(data.path, position); position += data.path.length; buffer.writeUInt8(0, position); requestResponse(buffer, function(reader, error) { if (!error) { var data = { hasUpdates: reader.nextInt8() == 1 }; callback(data, false); } else callback(null, true); }, false); } }