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

176 lines
5.8 KiB
JavaScript

'use strict';
const stampit = require('stampit');
const Put = require('put');
const crc = require('crc');
const Net = require('net');
const ModbusCore = require('../modbus-client-core.js');
module.exports = stampit()
.compose(ModbusCore)
.init(function () {
let closedOnPurpose = false;
let reconnect = false;
let buffer = new Buffer(0);
let socket;
let init = () => {
this.setState('init');
let tcp = this.options.tcp;
tcp.protocolVersion = tcp.protocolVersion || 0;
tcp.port = tcp.port || 502;
tcp.host = tcp.host || 'localhost';
tcp.autoReconnect = tcp.autoReconnect || false;
tcp.reconnectTimeout = tcp.reconnectTimeout || 0;
this.on('send', onSend);
this.on('newState_error', onError);
//this.on('stateChanged', this.log.debug);
};
let connect = () => {
this.setState('connect');
if (!socket) {
socket = new Net.Socket();
socket.on('connect', onSocketConnect);
socket.on('close', onSocketClose);
socket.on('error', onSocketError);
socket.on('data', onSocketData);
}
socket.connect(this.options.tcp.port, this.options.tcp.host);
};
let onSocketConnect = () => {
this.emit('connect');
this.setState('ready');
};
let onSocketClose = hadErrors => {
this.log.debug('Socket closed with error', hadErrors);
this.setState('closed');
this.emit('close');
if (!closedOnPurpose && (this.options.tcp.autoReconnect || reconnect)) {
setTimeout(() => {
reconnect = false;
connect();
}, this.options.tcp.reconnectTimeout);
}
};
let onSocketError = err => {
this.log.error('Socket Error', err);
this.setState('error');
this.emit('error', err);
};
function toStrArray(buf) {
if (!buf || !buf.length) return '';
let text = '';
for (let i = 0; i < buf.length; i++) {
text += (text ? ',' : '') + buf[i];
}
return text;
}
let onSocketData = data => {
buffer = Buffer.concat([buffer, data]);
while (buffer.length > 4) {
// 1. there is no mbap
// 2. extract pdu
// 0 - device ID
// 1 - Function CODE
// 2 - Bytes length
// 3.. Data
// checksum.(2 bytes
let len;
let pdu;
// if response for write
if (buffer[1] === 5 || buffer[1] === 6 || buffer[1] === 15 || buffer[1] === 16) {
if (buffer.length < 8) break;
pdu = buffer.slice(0, 8); // 1 byte device ID + 1 byte FC + 2 bytes address + 2 bytes value + 2 bytes CRC
} else if (buffer[1] > 0 && buffer[1] < 5){
len = buffer[2];
if (buffer.length < len + 5) break;
pdu = buffer.slice(0, len + 5); // 1 byte deviceID + 1 byte FC + 1 byte length + 2 bytes CRC
} else {
// unknown function code
this.log.error('unknown function code: ' + buffer[1]);
// reset buffer and try again
buffer = new Buffer(0);
break;
}
if (crc.crc16modbus(pdu) === 0) { /* PDU is valid if CRC across whole PDU equals 0, else ignore and do nothing */
if (this.options.unitId !== undefined && pdu[0] !== this.options.unitId) {
// answer for wrong device
this.log.debug('received answer for wrong ID ' + buffer[0] + ', expected ' + this.options.unitId);
}
// emit data event and let the
// listener handle the pdu
this.emit('data', pdu.slice(1, pdu.length - 2), pdu[0]);
} else {
this.log.error('Wrong CRC for frame: ' + toStrArray(pdu));
// reset buffer and try again
buffer = new Buffer(0);
break;
}
buffer = buffer.slice(pdu.length, buffer.length);
}
};
let onError = () => {
this.log.error('Client in error state.');
socket.destroy();
};
let onSend = (pdu, unitId) => {
this.log.debug('Sending pdu to the socket.');
let pkt = Put()
.word8((unitId === undefined ? this.options.unitId : unitId) || 0) // unit id
.put(pdu); // the actual pdu
let buf = pkt.buffer();
let crc16 = crc.crc16modbus(buf);
pkt = pkt.word16le(crc16).buffer();
socket.write(pkt);
};
this.connect = () => {
this.setState('connect');
connect();
return this;
};
this.reconnect = () => {
if (!this.inState('closed')) {
return this;
}
closedOnPurpose = false;
reconnect = true;
this.log.debug('Reconnecting client.');
socket.end();
return this;
};
this.close = () => {
closedOnPurpose = true;
this.log.debug('Closing client on purpose.');
socket.end();
return this;
};
init();
});