UNPKG

globalcache

Version:

Module to connect to Global Cache iTach devices and send IR strings.

274 lines (245 loc) 8.51 kB
var util = require('util'), request = require('request'), net = require('net'), _ = require('underscore'), EventEmitter = require('events').EventEmitter, ERRORCODES = { '001': 'Invalid command. Command not found.', '002': 'Invalid module address (does not exist).', '003': 'Invalid connector address (does not exist).', '004': 'Invalid ID value.', '005': 'Invalid frequency value.', '006': 'Invalid repeat value.', '007': 'Invalid offset value.', '008': 'Invalid pulse count.', '009': 'Invalid pulse data.', '010': 'Uneven amount of <on|off> statements.', '011': 'No carriage return found.', '012': 'Repeat count exceeded.', '013': 'IR command sent to input connector.', '014': 'Blaster command sent to non-blaster connector.', '015': 'No carriage return before buffer full.', '016': 'No carriage return.', '017': 'Bad command syntax.', '018': 'Sensor command sent to non-input connector.', '019': 'Repeated IR transmission failure.', '020': 'Above designated IR <on|off> pair limit.', '021': 'Symbol odd boundary.', '022': 'Undefined symbol.', '023': 'Unknown option.', '024': 'Invalid baud rate setting.', '025': 'Invalid flow control setting.', '026': 'Invalid parity setting.', '027': 'Settings are locked' }; const DELAY_BETWEEN_COMMANDS = 100; function iTach(config) { config = _.extend({ port: 4998, timeout: 20000, module: 1 }, config); if (!config.host) { throw new Error('Host is required for this module to function'); } var self = this; var isSending = false; var debug = config.debug; var sendTimeout = null; var callbacks = {}, messageQueue = [], _currentRequestID = 0; var _addToCallbacks = function (done, predefinedId, debug) { var id; if (!predefinedId) { debug && console.log('node-itach :: generating new id for IR transmittion'); debug && console.log('node-itach :: currently callbacks hash contains %d', Object.keys(callbacks).length); _currentRequestID++; id = _currentRequestID; } else id = predefinedId; callbacks[id] = done; return id; }, _resolveCallback = function (id, err, debug, send) { if (callbacks[id]) { debug && console.log('node-itach :: status:%s resolving callback with id %s', err ? 'error' : 'success', id); callbacks[id](err || false); delete callbacks[id]; send && clearSendTimeoutAndSendRightNow(); } else { console.error('node-itach :: cannot find callback with id %s in callbacks hash', id); } }; this.learn = function (done) { var options = { method: 'GET', uri: "http://" + config.host + '/api/v1/irlearn', json: true }; return request(options, function (error, response, learnObject) { if (error) { done && done(JSON.stringify(error)); } else if (response.statusCode != 200) { done && done(JSON.stringify(learnObject)); } else { done && done(false, learnObject); } }); }; function clearSendTimeoutAndSendRightNow() { if (sendTimeout) { clearTimeout(sendTimeout); sendTimeout = null; } sendFromQueue_(); } function sendFromQueue_(){ if (!messageQueue.length) { debug && console.log('Message queue is empty. returning...') return; } isSending = true; debug && console.log('Taking next message from the queue.') var message = messageQueue.shift(); send_(message); } function send_(message) { var id = message[0], data = message[1]; var socket = net.connect(config.port, config.host); socket.setTimeout(config.timeout); debug && console.log('Connecting to ' + config.host + ':' + config.port); self.emit('connecting'); socket.on('connect', function () { debug && console.log('node-itach :: connected to ' + config.host + ':' + config.port); debug && console.log('node-itach :: sending data', data); self.emit('connected'); socket.write(data + "\r\n"); self.emit('send'); }); socket.on('close', function () { debug && console.log('node-itach :: disconnected from ' + config.host + ':' + config.port); self.emit('disconnected'); }); socket.on('error', function (err) { console.error('node-itach :: error :: ', err); socket.destroy(); for (var key in callbacks) callbacks[key](err); //self.emit('error', err); sendNextMessage(); }); socket.on('timeout', function (err) { console.error('node-itach :: error :: ', 'Timeout'); socket.destroy(); for (var key in callbacks) callbacks[key](err); //self.emit('error', err); sendNextMessage(); }); socket.on('data', function (data) { var wholeData = data.toString().replace(/[\n\r]$/, ""); self.emit(data, wholeData); wholeData = wholeData.split(/\r/); debug && console.log("node-itach :: received data: " + data); for (var key in wholeData) { data = wholeData[key].toString().replace(/[\n]*/, ""); if (!data) continue; var parts = data.split(','), status = parts[0], id = parts[2]; if (status === 'busyIR') { // This shoud not happen if this script is the only device connected to the iTach // add rate limiter return _resolveCallback(id, 'Add Rate Limiter to the blaster', debug, true); } else if (status.match(/^ERR/)) { var tmpArr = (parts[1] || parts[0]).split('IR'); if(tmpArr.length===1) tmpArr = tmpArr[0].split(' '); var errCode = tmpArr.length >= 2 ? tmpArr[1] : tmpArr[0]; var err = ERRORCODES[errCode]; console.error('node-itach :: error :: ' + data + ': ' + err); return _resolveCallback(parts[2] || parts[1], err, debug, true); } else if (parts[0] === 'setstate' || parts[0] === 'sendir') { _resolveCallback(parts[1], null, debug, (parts[0] === 'setstate')); } else { _resolveCallback(id, null, debug); } } socket.destroy(); debug && console.log('Delay before going to another item in a queue...'); setTimeout(sendNextMessage, DELAY_BETWEEN_COMMANDS); }); }; function sendNextMessage(){ isSending = false; // go to the next message in the queue if any if (messageQueue.length){ sendFromQueue_(); } } this.disconnect = this.end = this.destroy = function (callback) { if (this.socket) this.socket.end(); messageQueue = []; callbacks = {}; } this.send = function (input, now, done) { if (!input) throw new Error('Missing input'); if (now===true && (messageQueue.length || isSending)){ debug && console.log("queue is not empty"); return; } if(done === undefined && typeof now === ' function') { done = now; } var data, ir; if (typeof(input) === 'string') { if (input.indexOf('sendir') !== -1) input = {ir: input}; else if (input.indexOf('setstate') !== -1) input = {serial: input}; else throw new Error('Unexpected command[' + input + '], expectind for sendir or setstat (serial cmd)'); } ir = (input.ir != null); if (ir) parts = input.ir.split(','); else parts = input.serial.split(','); if (parts[0].indexOf('sendir') !== -1 && !ir) throw new Error('Trying to send ir command, but it passed as ir command: ' + util.inspect(input)); else if (parts[0].indexOf('setstate') !== -1 && ir) throw new Error('Trying to send serial command, but it passed as serial command: ' + util.inspect(input)); if (!ir && typeof input.module !== 'undefined') { parts[1] = '1:' + input.module; } var id; if (ir) { id = _addToCallbacks(done, null, debug); parts[2] = id; } else { id = parts[1]; _addToCallbacks(done, id, debug); } if (ir && typeof input.repeat !== 'undefined') { parts[4] = input.repeat; } data = parts.join(','); if (now === true && !isSending) send_([id, data]); else { // add to queue messageQueue.push([id, data]); if (!isSending) sendFromQueue_(); } } iTach.super_.call(this, config); } util.inherits(iTach, EventEmitter); module.exports = {iTach: iTach};