UNPKG

node-aurora

Version:

Provides an interface to the Aurora Dreamband.

423 lines (292 loc) 14.8 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _keys = require('babel-runtime/core-js/object/keys'); var _keys2 = _interopRequireDefault(_keys); var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _events = require('events'); var _events2 = _interopRequireDefault(_events); var _AuroraConstants = require('./AuroraConstants'); var _AuroraCmdResponseParser = require('./AuroraCmdResponseParser'); var _AuroraCmdResponseParser2 = _interopRequireDefault(_AuroraCmdResponseParser); var _moment = require('moment'); var _moment2 = _interopRequireDefault(_moment); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var CmdStates = { NO_CMD: 0, CMD_HEADER: 1, CMD_RESPONSE: 2, CMD_OUTPUT: 3, CMD_INPUT: 4, CMD_ERROR: 5 }; var AuroraSerialParser = function (_EventEmitter) { (0, _inherits3.default)(AuroraSerialParser, _EventEmitter); function AuroraSerialParser() { (0, _classCallCheck3.default)(this, AuroraSerialParser); var _this = (0, _possibleConstructorReturn3.default)(this, (AuroraSerialParser.__proto__ || (0, _getPrototypeOf2.default)(AuroraSerialParser)).call(this)); _this._triggerCmdError = function (message) { _this._cmd.error = true; _this._cmd.response = { error: -64, message: message }; _this.emit('cmdResponse', _this._cmd); _this.reset(); }; _this._onCmdTimeout = function () { _this._triggerCmdError('Command timed out.'); }; _this._parseNonCmdResponseLine = function (line) { if (line.charAt(0) == '<') { var logParts = line.match(_this._regexLog); if (logParts && logParts.length == 4) { _this.emit('log', { typeId: _AuroraConstants.LogNamesToTypeIds[logParts[1].toUpperCase()], type: logParts[1].toUpperCase(), time: +(0, _moment2.default)(logParts[2], "HH:mm:ss.SSS", true), message: logParts[3] }); return; } } else if (line.slice(0, 6) == 'event-') { var eventParts = line.match(/^event-(\d{1,2}): (\d+) \[(\S*)\]$/i); if (eventParts && eventParts.length == 4) { var eventId = +eventParts[1]; if (!isNaN(eventId) && eventId <= _AuroraConstants.EVENT_ID_MAX) { _this.emit('auroraEvent', { eventId: eventId, event: _AuroraConstants.EventIdsToNames[eventId], flags: +eventParts[2], time: Date.now() }); return; } } } else { var dataParts = line.match(/^(\S*)-(\d{1,2}): ([\d\s,.-]+)$/i); if (dataParts && dataParts.length == 4) { var streamId = +dataParts[2]; if (!isNaN(streamId) && streamId <= _AuroraConstants.STREAM_ID_MAX) { _this.emit('streamData', { streamId: streamId, stream: _AuroraConstants.StreamIdsToNames[streamId], data: dataParts[3].split(',').map(Number), time: Date.now() }); return; } } } _this.emit('parseError', line); }; _this._cmdResponseParser = new _AuroraCmdResponseParser2.default(); _this._cmdWatchdogTimer = null; _this.reset(); _this._regexLog = new RegExp('^\\< (' + (0, _keys2.default)(_AuroraConstants.LogNamesToTypeIds).join('|') + ') \\| (\\d{2}:\\d{2}:\\d{2}\\.\\d{3}) \\> (.+)$'); return _this; } (0, _createClass3.default)(AuroraSerialParser, [{ key: 'reset', value: function reset() { clearTimeout(this._cmdWatchdogTimer); this._cmd = null; this._unparsedBuffer = null; this._cmdResponseParser.reset(); this._cmdState = CmdStates.NO_CMD; } }, { key: 'parseChunk', value: function parseChunk(chunk) { //received new data so clear any timeouts clearTimeout(this._cmdWatchdogTimer); //don't do anything if chunk is empty if (!chunk.length) { return; } //set new timeout, which will get cleared if this //line indicates that there is no more data expected this._cmdWatchdogTimer = setTimeout(this._onCmdTimeout, 10000); //pick up where we left off this._unparsedBuffer = Buffer.isBuffer(this._unparsedBuffer) ? Buffer.concat([this._unparsedBuffer, chunk]) : chunk; //if we aren't in the middle of processing output //lets break this chunk into lines if (this._cmdState != CmdStates.CMD_OUTPUT) { while (this._unparsedBuffer && this._unparsedBuffer.length) { //look for first newline var newlineIndex = this._unparsedBuffer.indexOf('\n'); //no newline, so wait for the next chunk if (newlineIndex == -1) { return; } //we must have a newline now so grab the line var bufferLine = this._unparsedBuffer.slice(0, newlineIndex).toString().trim(); //and remove it from the unparsed buffer this._unparsedBuffer = this._unparsedBuffer.slice(newlineIndex + 1); //after trim, if no data is in line, bail if (!bufferLine.length) { continue; } //we have a complete line, so pass it off this.parseLine(bufferLine.toString()); //if we are now receiving output, we need to exit //this loop if (this._cmdState == CmdStates.CMD_OUTPUT) break; } } //if we are here, we are currently receiving output. //this is where things get tricky since we need to //properly identify whether an output footer exists var outputFooter = '\r\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'; //while we have enough bytes to potentially identify the footer while (this._unparsedBuffer.length >= outputFooter.length) { //look for the footer var footerStartIndex = this._unparsedBuffer.indexOf(outputFooter); //didn't find full footer, so we consume as much as we can if (footerStartIndex == -1) { //determine where the first CR is var firstCRIndex = this._unparsedBuffer.indexOf('\r'); //if we don't have one, we can safely consume the entire buffer if (firstCRIndex == -1) { this.emit('cmdOutputReady', this._unparsedBuffer); this._unparsedBuffer = null; return; } //we have a CR, so see if we have enough bytes to guarantee the output footer //hasn't started yet if (this._unparsedBuffer.length >= firstCRIndex + outputFooter.length) { //consume everything up to and including the first CR this.emit('cmdOutputReady', this._unparsedBuffer.slice(0, firstCRIndex + 1)); this._unparsedBuffer = this._unparsedBuffer.slice(firstCRIndex + 1); continue; } return; } //we found the footer!! else { //if the footer doesn't start at the //beginning, we consume everything up to it if (footerStartIndex) { //consume everything up to the footer this.emit('cmdOutputReady', this._unparsedBuffer.slice(0, footerStartIndex)); this._unparsedBuffer = this._unparsedBuffer.slice(footerStartIndex); } //at this point we have the footer and it is at the beginning //of the unparsed buffer so now we have to look for the footer //end, which is the second set of \r\n var footerEndIndex = this._unparsedBuffer.indexOf('\r\n', 2); if (footerEndIndex == -1) return; //we found the entire footer, so consume it all (including trailing new lines) and change the state this._unparsedBuffer = this._unparsedBuffer.slice(footerEndIndex + 2); this._cmdState = CmdStates.CMD_RESPONSE; break; } } } }, { key: 'parseLine', value: function parseLine(line) { switch (this._cmdState) { case CmdStates.NO_CMD: //trim off whitespace line = line.trim(); //don't do anything if line is empty if (!line.length) { return; } //look for command prompt var match = line.match(/^# ([a-z-]+.*)$/i); if (match) { this._cmd = { command: match[1], error: false }; this._cmdState = CmdStates.CMD_HEADER; } else { clearTimeout(this._cmdWatchdogTimer); this._parseNonCmdResponseLine(line); } break; case CmdStates.CMD_HEADER: //look for response start if (line.match(/^[-]{64,}$/)) { this._cmdResponseParser.reset(); this._cmdState = CmdStates.CMD_RESPONSE; } else { this._triggerCmdError('Expected command header.'); } break; case CmdStates.CMD_INPUT: //look for input footer if (line[0] == '.' && line.match(/^[.]{64,}$/)) { this._cmdState = CmdStates.CMD_RESPONSE; } break; case CmdStates.CMD_ERROR: //look for error footer if (line[0] == '~' && line.match(/^[~]{64,}$/)) { this._cmdState = CmdStates.CMD_RESPONSE; } else { try { this._cmdResponseParser.parseDetect(line); } catch (error) { this._triggerCmdError('Invalid command error response: ' + error); } } break; case CmdStates.CMD_RESPONSE: //trim off whitespace line = line.trim(); //don't do anything if line is empty if (!line.length) { return; } //look for output header if (line[0] == '+' && line.match(/^[+]{64,}$/)) { this._cmdState = CmdStates.CMD_OUTPUT; } //look for input header else if (line[0] == '.' && line.match(/^[.]{64,}$/)) { this._cmdState = CmdStates.CMD_INPUT; clearTimeout(this._cmdWatchdogTimer); this._cmdWatchdogTimer = setTimeout(this._onCmdTimeout, 5 * 60 * 1000); this.emit('cmdInputRequested'); } //look for error header else if (line[0] == '~' && line.match(/^[~]{64,}$/)) { this._cmd.error = true; this._cmdResponseParser.reset(); this._cmdState = CmdStates.CMD_ERROR; } //look for end of response else if (line.match(/^[-]{64,}$/)) { clearTimeout(this._cmdWatchdogTimer); this._cmd.response = this._cmdResponseParser.getResponse(); this.emit('cmdResponse', this._cmd); this._cmdState = CmdStates.NO_CMD; } //neither, so this is normal command response else if (!this._cmd.error) { try { this._cmdResponseParser.parseDetect(line); } catch (error) { this._triggerCmdError('Invalid command response: ' + error); } } break; } } }]); return AuroraSerialParser; }(_events2.default); exports.default = AuroraSerialParser;