server/api.js

var net = Npm.require('net');
var future = Npm.require('fibers/future');

var SOH = String.fromCharCode(1);
var ETX = String.fromCharCode(3);

var MAX_RETRIES = 3;

/**
 * TLS API Interface
 * @constructor
 * @param {String} ip TLS IP Address
 * @param {Number} port TLS Port
 * @param {Array} tankNames Product List
 */
TLS.connect = function TLS(ip, port, tankNames) {
	var fut = new future();

	this.ip = ip;
	this.port = port;
	this.offset = 0;

	this.retryInterval = {};

	if (typeof tankNames === 'undefined' || tankNames.length === 0) {
		this.tankNames = this.getTankNames(function() { fut.return() });
	} else {
		this.tankNames = tankNames;
		fut.return();
	}

	return fut.wait();
}

/**
 * Returns an array of tank names
 * @param  {Array} tanks TLS tanks
 * @param  {Function} [callback] callback function
 */
TLS.connect.prototype.getTankNames = function getTankNames(cb) {
	var res = this.api('200', cb);
	this.offset = TLS.utils.time.offset(res);
  res = TLS.utils.tanks.names(res);
  return res;
}

/**
 * Returns an array of tanks objects
 * @param  {Array} tanks TLS tanks
 * @param  {Function} [callback] callback function
 */
TLS.connect.prototype.getTanks = function getTanks(cb) {
	var res = this.api('i20100', cb);
	this.offset = TLS.utils.time.offset(res);
	res = TLS.utils.tanks.extract(res, this.tankNames);
	return res;
}

/**
 * Send TLS API request
 * @param  {String} command TLS function code
* @param  {Function} [completed] Connection close callback
 */
TLS.connect.prototype.request = function setup(command, completed) {
	var fut	= new future(),
		timer = new Date(),
		_tls 	= this;

	command = SOH + command;
	var responseString = '';

	var c = net.connect(_tls.port, _tls.ip);
	c.setEncoding('ascii');
	c.setTimeout(2000);

	c.on('connect', function() {
		console.log(_tls.ip + ' connected (' + command + ')');
		clearInterval(_tls.retryInterval);
		this.write(command);
	});

	c.on('error', function(err) {
		if (err.code == 'ECONNREFUSED') {
			console.log(_tls.ip + ' retrying (' + err.code + ')');
		} else {
			console.log(_tls.ip + ' error (' + err + ')');
			fut.throw(err);
		}
	});

	c.on('readable', function() {
		var chunk;
		while (null !== (chunk = c.read())) {
			var s = chunk.toString();
			if (s.indexOf(SOH) != -1) {
				s = s.substr(1);
			}
			if (s.indexOf(ETX) != -1) {
				c.end();
				break;
			} else {
				if (s != 'undefined') {
					responseString += s;
				}
			}
		}
	});

	c.on('close', function(hadError) {
		console.log(_tls.ip + ' closed ([' + c.bytesRead + '] err: ' + hadError + ')');
		if (c.bytesRead > 0 && responseString.length > 0) {
			fut.return(responseString);
			typeof completed === 'function' && completed();
		} else {
			fut.return(false);
		}
	});

	c.on('end', function() {
		console.log(_tls.ip + ' disconnected (' + (new Date() - timer) + 'ms)');
	});

	return fut.wait();
}

/**
 * Process TLS command
 * @param  {String} command TLS function code
 * @param  {Object} [options] Set call options
* @param  {Function} [callback] Function to execute on completion
 */
TLS.connect.prototype.api = function api(command, options, callback) {
	var response = new future(),
					_tls = this,
			 retries = 0;

	if (typeof options === 'function' && !callback) {
		callback = options;
		options = undefined;
	}

	var request = _tls.request(command);

	if (!request) {
		_tls.retryInterval = Meteor.setInterval(function() {
			request = _tls.request(command, function() {
				Meteor.clearInterval(_tls.retryInterval);

				typeof callback === 'function' && callback();
				response.return(request);
			});

			if (retries >= MAX_RETRIES) {
				Meteor.clearInterval(_tls.retryInterval);
				retries = 0;
			}

			retries++;
		}, 1000);
	} else {
		typeof callback === 'function' && callback();
		response.return(request);
	}

  return response.wait();
};