'use strict'; const Stampit = require('stampit'); const StateMachine = require('stampit-state-machine'); const EventBus = require('stampit-event-bus'); const ExceptionMessage = { 0x01: 'ILLEGAL FUNCTION', 0x02: 'ILLEGAL DATA ADDRESS', 0x03: 'ILLEGAL DATA VALUE', 0x04: 'SLAVE DEVICE FAILURE', 0x05: 'ACKNOWLEDGE', 0x06: 'SLAVE DEVICE BUSY', 0x08: 'MEMORY PARITY ERROR', 0x0A: 'GATEWAY PATH UNAVAILABLE', 0x0B: 'GATEWAY TARGET DEVICE FAILED TO RESPOND' }; module.exports = Stampit() .compose(StateMachine, EventBus) .init(function () { this.log = this.options && this.options.log; if (!this.log) { this.log = { log: () => console.log.apply(console, arguments), error: () => console.error.apply(console, arguments), warn: () => console.warn.apply(console, arguments), debug: () => console.log.apply(console, arguments) }; } let responseHandler = {}; let currentRequest = null; let reqFifo = []; let init = () => { this.options.timeout = this.options.timeout || (5 * 1000); // 5s this.on('data', onData); this.on('newState_ready', flush); this.on('newState_closed', onClosed); }; let flush = () => { if (reqFifo.length) { currentRequest = reqFifo.shift(); currentRequest.timeout = setTimeout(() => { currentRequest.cb && currentRequest.cb({err: 'timeout'}); this.emit('trashCurrentRequest'); this.log.error('Request timed out.'); this.setState('error'); }, this.options.timeout); this.setState('waiting'); this.emit('send', currentRequest.pdu, currentRequest.unitId); } }; let onClosed = () => { if (currentRequest) { this.log.debug('Clearing timeout of the current request.'); clearTimeout(currentRequest.timeout); } this.log.debug('Cleaning up request fifo.'); reqFifo = []; }; let handleErrorPDU = (pdu) => { const errorCode = pdu.readUInt8(0); // if error code is smaller than 0x80 // ths pdu describes no error if (errorCode < 0x80) { return false; } // pdu describes an error const exceptionCode = pdu.readUInt8(1); const message = ExceptionMessage[exceptionCode]; const err = { errorCode: errorCode, exceptionCode: exceptionCode, message: message }; // call the desired deferred currentRequest.cb && currentRequest.cb(err); return true; }; /** * Handle the incoming data, cut out the mbap * packet and send the pdu to the listener */ let onData = (pdu, unitId) => { if (!currentRequest) { this.log.debug('No current request.'); return; } clearTimeout(currentRequest.timeout); // check pdu for error if (handleErrorPDU(pdu)) { this.log.debug('Received pdu describes an error.'); currentRequest = null; this.setState('ready'); return; } // handle pdu const handler = responseHandler[currentRequest.fc]; if (!handler) { this.log.debug(`Found no handler for fc ${currentRequest.fc}`); throw new Error(`No handler implemented for fc ${currentRequest.fc}`); } handler(unitId, pdu, currentRequest.cb); this.setState('ready'); }; this.addResponseHandler = (fc, handler) => { responseHandler[fc] = handler; return this; }; this.queueRequest = (unitId, fc, pdu, cb) => { reqFifo.push({unitId, fc, pdu, cb}); if (this.inState('ready')) { flush(); } }; init(); });