157 lines
4.7 KiB
JavaScript
157 lines
4.7 KiB
JavaScript
'use strict';
|
|
const stampit = require('stampit');
|
|
const Put = require('put');
|
|
const Net = require('net');
|
|
const ModbusCore = require('../modbus-client-core.js');
|
|
|
|
module.exports = stampit()
|
|
.compose(ModbusCore)
|
|
.init(function () {
|
|
|
|
let reqId = 0;
|
|
let currentRequestId = reqId;
|
|
let closedOnPurpose = false;
|
|
let reconnect = false;
|
|
let buffer = new Buffer(0);
|
|
let trashRequestId;
|
|
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('trashCurrentRequest', onTrashCurrentRequest);
|
|
//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);
|
|
};
|
|
|
|
let onSocketData = data => {
|
|
buffer = Buffer.concat([buffer, data]);
|
|
|
|
while (buffer.length > 8) {
|
|
// http://www.simplymodbus.ca/TCP.htm
|
|
// 1. extract mbap
|
|
const id = buffer.readUInt16BE(0);
|
|
//const protId = buffer.readUInt16BE(2);
|
|
const len = buffer.readUInt16BE(4);
|
|
const unitId = buffer.readUInt8(6);
|
|
|
|
// 2. extract pdu
|
|
if (buffer.length < 7 + len - 1) {
|
|
break;
|
|
}
|
|
|
|
const pdu = buffer.slice(7, 7 + len - 1);
|
|
|
|
if (id === trashRequestId) {
|
|
this.log.debug('current mbap contains trashed request id.');
|
|
} else {
|
|
// emit data event and let the
|
|
// listener handle the pdu
|
|
this.emit('data', pdu, unitId);
|
|
}
|
|
|
|
buffer = buffer.slice(pdu.length + 7, buffer.length);
|
|
}
|
|
};
|
|
|
|
let onError = err => {
|
|
this.log.error('Client in error state.');
|
|
socket.destroy();
|
|
};
|
|
|
|
|
|
let onSend = (pdu, unitId) => {
|
|
reqId = (reqId + 1) % 0xffff;
|
|
|
|
let pkt = Put()
|
|
.word16be(reqId) // transaction id
|
|
.word16be(this.options.tcp.protocolVersion) // protocol version
|
|
.word16be(pdu.length + 1) // pdu length
|
|
.word8((unitId === undefined ? this.options.unitId : unitId) || 0) // unit id
|
|
.put(pdu) // the actual pdu
|
|
.buffer();
|
|
|
|
currentRequestId = reqId;
|
|
|
|
socket.write(pkt);
|
|
};
|
|
|
|
let onTrashCurrentRequest = () => trashRequestId = currentRequestId;
|
|
|
|
this.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();
|
|
});
|