yunkong2.rsonoff/lib/jsmodbus/modbus-client-core.js
2019-01-16 12:38:53 +08:00

146 lines
4.2 KiB
JavaScript

'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();
});