node-red-contrib-mcprotocol
Version:
Mode-red nodes to Read from & Write to MITSUBISHI PLC over Ethernet using MC Protocol
1,411 lines (1,203 loc) β’ 150 kB
JavaScript
// MCPROTOCOL - A library for communication to Mitsubishi PLCs over Ethernet from node.js.
// Currently only FX3U CPUs using FX3U-ENET and FX3U-ENET-ADP modules (Ethernet modules) tested.
// Please report experiences with others.
// The MIT License (MIT)
// Copyright (c) 2015 Dana Moffit
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// EXTRA WARNING - This is BETA software and as such, be careful, especially when
// writing values to programmable controllers.
//
// Some actions or errors involving programmable controllers can cause injury or death,
// and YOU are indicating that you understand the risks, including the
// possibility that the wrong address will be overwritten with the wrong value,
// when using this library. Test thoroughly in a laboratory environment.
var net = require("net");
var dgram = require('dgram');
var EventEmitter = require('events').EventEmitter;
var util = require("util");
var inherits = require('util').inherits
var effectiveDebugLevel = 0; // intentionally global, shared between connections
var monitoringTime = 10;
module.exports = MCProtocol;
function MCProtocol() {
if (!(this instanceof MCProtocol)) return new MCProtocol();
EventEmitter.call(this);
var self = this;
//self.data = {};//for data access
self.readReq = Buffer.alloc(1000);//not calculated
self.writeReq;// = new Buffer(1500);//size depends on PLC type! As Q/L can read/write 950 WDs
self.queue = [];
self.resetPending = false;
self.resetTimeout = undefined;
self.maxPDU = 255;
self.netClient = undefined;
self.connectionState = 0;
self.requestMaxParallel = 1;
self.maxParallel = 1; // MC protocol is read/response. Parallel jobs not supported.
self.isAscii = false;
self.octalInputOutput;
self.parallelJobsNow = 0;
self.maxGap = 5;
self.doNotOptimize = false;
self.connectCallback = undefined;
self.readDoneCallback = undefined;
self.writeDoneCallback = undefined;
self.connectTimeout = undefined;
self.PDUTimeout = undefined;
self.globalTimeout = 4500;
self.lastPacketSent = undefined;
self.readPacketArray = [];
self.writePacketArray = [];
self.polledReadBlockList = [];
self.globalReadBlockList = [];
self.globalWriteBlockList = [];
self.masterSequenceNumber = 1;
self.translationCB = function (tag) { return tag };
self.connectionParams = undefined;
self.connectionID = 'UNDEF';
self.addRemoveArray = [];
self.readPacketValid = false;
self.connectCBIssued = false;
self.queueTimer = undefined;
self.queuePollTime = 50;
self.queueMaxLength = 50;//avg time on good connection is 20ms. 20 * 50 = 1000ms to process.
}
inherits(MCProtocol, EventEmitter);
MCProtocol.prototype.isConnected = function () {
var self = this;
return self.connectionState == 4;
}
MCProtocol.prototype.setDebugLevel = function (level) {
var l = (level + "").toUpperCase();
switch (l) {
case 'TRACE':
effectiveDebugLevel = 4;
break;
case 'DEBUG':
effectiveDebugLevel = 3;
break;
case 'INFO':
effectiveDebugLevel = 2;
break;
case 'WARN':
effectiveDebugLevel = 1;
break;
case 'ERROR':
effectiveDebugLevel = 0;
break;
case 'NONE':
effectiveDebugLevel = -1;
break;
default:
effectiveDebugLevel = level;
}
}
MCProtocol.prototype.nextSequenceNumber = function () {
var self = this;
self.masterSequenceNumber += 1;
if (self.masterSequenceNumber > 32767) {
self.masterSequenceNumber = 1;
}
return self.masterSequenceNumber;
}
MCProtocol.prototype.setTranslationCB = function (cb) {
var self = this;
if (typeof cb === "function") {
outputLog('Translation OK', "TRACE");
self.translationCB = cb;
}
}
MCProtocol.prototype.initiateConnection = function (cParam, callback) {
var self = this;
if (cParam === undefined) { cParam = { port: 10000, host: '192.168.8.106', ascii: false }; }
outputLog('Initiate Called - Connecting to PLC with address and parameters...', "DEBUG");
outputLog(cParam, "DEBUG");
if (typeof (cParam.name) === 'undefined') {
self.connectionID = cParam.host;
} else {
self.connectionID = cParam.name;
}
if (typeof (cParam.ascii) === 'undefined') {
self.isAscii = false;
} else {
self.isAscii = cParam.ascii;
}
if (typeof (cParam.octalInputOutput) === 'undefined') {
self.octalInputOutput = false;
} else {
self.octalInputOutput = cParam.octalInputOutput;
}
if (typeof (cParam.plcType) === 'undefined') {
self.plcType = MCProtocol.prototype.enumPLCTypes.Q.name;
self.enumDeviceCodeSpec = MCProtocol.prototype.enumDeviceCodeSpecQ;//default to Q/L series
outputLog(`plcType not provided, defaulting to Q series PLC`,"WARN");
} else {
self.plcType = cParam.plcType;
if(!MCProtocol.prototype.enumPLCTypes[cParam.plcType]){
self.plcType = MCProtocol.prototype.enumPLCTypes.Q.name;
outputLog(`plcType '${cParam.plcType}' unknown. Currently supported types are '${MCProtocol.prototype.enumPLCTypes.keys.join("|")}', defaulting to Q series PLC`,"WARN");
}
self.plcSeries = MCProtocol.prototype.enumPLCTypes[self.plcType];
//not sure how best to handle A/QnA series - not even sure A series can do 3E/4E frames!
//for now, default to Q (will be overwritten below if user choses 1E frames)
self.enumDeviceCodeSpec = MCProtocol.prototype['enumDeviceCodeSpec' + self.plcType] || MCProtocol.prototype.enumDeviceCodeSpecQ;
outputLog(`'plcType' set is ${self.plcType}`,"INFO");
}
if (typeof (cParam.frame) === 'undefined') {
outputLog(`'frame' not provided, defaulting '3E'. Valid options are 1E, 3E, 4E.`,"WARN");
self.frame = '3E';
} else {
switch (cParam.frame.toUpperCase()) {
case '1E':
self.frame = '1E';
self.enumDeviceCodeSpec = MCProtocol.prototype.enumDeviceCodeSpec1E;
break;
case '3E':
self.frame = '3E';
break;
case '4E':
self.frame = '4E';
break;
default:
self.frame = '3E';
outputLog(`'frame' ${cParam.frame} is unknown. Defaulting to 3E. Valid options are 1E, 3E, 4E.`,"WARN");
break;
}
self.frame = cParam.frame;
outputLog(`'frame' set is ${self.frame}`,"DEBUG");
}
if(!self.enumDeviceCodeSpec){
throw new Error("Error determining device code specification. Check combination of PLC Type and Frame Type are valid");
}
if (typeof (cParam.PLCStation) !== 'undefined') {
self.PLCStation = cParam.PLCStation;
}
if (typeof (cParam.PCStation) !== 'undefined') {
self.PCStation = cParam.PCStation;
}
if (typeof (cParam.network) !== 'undefined') {
self.network = cParam.network;
}
if (typeof (cParam.PLCModuleNo) !== 'undefined') {
self.PLCModuleNo = cParam.PLCModuleNo;
}
if (typeof (cParam.queuePollTime) !== 'undefined') {
self.queuePollTime = cParam.queuePollTime;
}
if (typeof (cParam.queueMaxAge) !== 'undefined') {
self.queueMaxAge = cParam.queueMaxAge;
} else {
self.queueMaxAge = 2000;
}
self.plcSeries = MCProtocol.prototype.enumPLCTypes[self.plcType];
self.writeReq = Buffer.alloc(self.plcSeries.requiredWriteBufferSize);//size depends on PLC type! As Q/L can read/write 950 WDs
self.connectionParams = cParam;
self.connectCallback = callback;
self.connectCBIssued = false;
if (self.fakeTheConnection)//debug
self.connectCallback();
else
self.connectNow(self.connectionParams, false);
self.startQueueTimer = function (ms) {
self.queueTimer = setTimeout(() => {
self._processQueue()
}, ms);
}
self.startQueueTimer(self.queuePollTime);
self.processQueueASAP = function () {
setImmediate(() => {
outputLog(`π’οΈ setImmediate Calling _processQueue() --> `, "TRACE");
self._processQueue();
});
}
self._processQueue = function () {
clearTimeout(self.queueTimer);
try {
if (!self.queue.length) {
return;
}
// {arg: , cb: , dt: }
var queueItem = self.queue[0];
var itemAge = Date.now() - queueItem.dt;
if (itemAge > self.queueMaxAge) {
outputLog(`π’οΈβ‘ποΈ Discarding queued '${queueItem.fn}' item ${queueItem.arg} (item age is ${itemAge}ms, max age is ${self.queueMaxAge}ms)`, "WARN")
self.queue.shift();
return;//too old - discard
}
outputLog(`π’οΈβ‘βοΈ Sending queued '${queueItem.fn}' item ${queueItem.arg}`, "DEBUG");
var result;
if (queueItem.fn == "read") {
result = _readItems(self, queueItem.arg, queueItem.cb, true);
} else if (queueItem.fn == "write") {
result = _writeItems(self, queueItem.arg, queueItem.value, queueItem.cb, true);
}
let allSent = result.every(function (r) {
return r.sendStatus == MCProtocol.prototype.enumSendResult.sent;
});
if (allSent) {
outputLog(`π’οΈβ‘βοΈ Successfully sent queued '${queueItem.fn}' item '${queueItem.arg}'. (queue will be shifted to remove this item)`, "DEBUG");
self.queue.shift();//all sent shift the queued item - its done :)
return; //no need to continue
}
let noneSent = result.every(function (r) {
return r.sendStatus == MCProtocol.prototype.enumSendResult.notSent;
});
if (noneSent) {
outputLog(`π’οΈβ‘X Queued '${queueItem.fn}' item ${queueItem.arg} NOT sent - will try again soon`, "DEBUG");
return; //no need to continue
}
//by default, if !allSent and !nonSent, then _some_ were sent!
outputLog(`π’οΈβ‘β οΈ Something failed to send '${queueItem.fn}' item '${queueItem.arg}'. Queue will be shifted to remove this item.`, "WARN");
self.queue.shift();//some sent / some bad - shift the queued item regardless
} catch (error) {
outputLog(`Something went wrong polling the queue: ${error}. Queue will be shifted to remove this item.`, "ERROR");
self.queue.shift();
} finally {
self.startQueueTimer();
}
} //_processQueue()
}
MCProtocol.prototype.dropConnection = function () {
var self = this;
outputLog(`dropConnection() called`, "TRACE", self.connectionID);
if(self.connectionParams.protocol == "UDP"){
//TODO - implement UDP
try {
if(self.netClient){
self.netClient.close();
}
} catch (error) {
outputLog(`dropConnection() caused an error error: ${error}`, "ERROR", self.connectionID);
}
} else {
try {
if (typeof (self.netClient) !== 'undefined') {
self.netClient.end();
}
} catch (error) {
outputLog(`dropConnection() caused an error error: ${error}`, "ERROR", self.connectionID);
}
}
self.connectionCleanup();
self.connected = false;
}
MCProtocol.prototype.close = function () {
this.dropConnection();
};
MCProtocol.prototype.connectNow = function (cParam, suppressCallback) { // TODO - implement or remove suppressCallback
var self = this;
if (self.connectionParams.protocol == "UDP") {
//TODO - implement UDP
// Track the connection state
self.connectionState = 1 // 1 = trying to connect
if (self.netClient) {
self.connectionState = 0
self.netClient.removeAllListeners();
delete self.netClient;
}
self.netClient = dgram.createSocket('udp4');
self.connected = false;
self.requests = {};
function close() {
self.connectionState = 0;
self.emit('close');
self.connected = false;
}
// self.netClient.on('listening', function () {
// self.onUDPConnect.apply(self, arguments);
// });
self.netClient.on('close', close);
//self.netClient.connect();
self.netClient.write = function(buffer){
self.netClient.send( buffer, 0, buffer.length, cParam.port, cParam.host, function (err) {
if (err) {
self.emit('error');//??
}
});
}
//{
outputLog('UDP Connection Setup to ' + cParam.host + ' on port ' + cParam.port, "DEBUG", self.connectionID);
self.netClient.removeAllListeners('data');
self.netClient.removeAllListeners('message');
self.netClient.removeAllListeners('error');
self.netClient.on('message', function () {
self.onResponse.apply(self, arguments);
}); // We need to make sure we don't add this event every time if we call it on data.
self.netClient.on('error', function () {
self.readWriteError.apply(self, arguments);
}); // Might want to remove the connecterror listener
self.emit('open');
if ((!self.connectCBIssued) && (typeof (self.connectCallback) === "function")) {
self.connectCBIssued = true;
self.connectCallback();
}
//}
self.connectionState = 4;
} else {
// Don't re-trigger.
if (self.connectionState >= 1) { return; }
self.connectionCleanup();
self.netClient = net.connect(cParam, function () {
self.netClient.setKeepAlive(true, 2500); // For reliable unplug detection in most cases - although it takes 10 minutes to notify
self.onTCPConnect.apply(self, arguments);
});
self.connectionState = 1; // 1 = trying to connect
self.netClient.on('error', function () {
self.connectError.apply(self, arguments);
});
self.netClient.on('close', function () {
self.onClientDisconnect.apply(self, arguments);
});
outputLog('<initiating a new connection>', "INFO", self.connectionID);
outputLog('Attempting to connect to host...', "DEBUG", self.connectionID);
}
}
MCProtocol.prototype.connectError = function (e) {
var self = this;
self.emit('error',e);
// Note that a TCP connection timeout error will appear here. An MC connection timeout error is a packet timeout.
outputLog('We Caught a connect error ' + e.code, "ERROR", self.connectionID);
if ((!self.connectCBIssued) && (typeof (self.connectCallback) === "function")) {
self.connectCBIssued = true;
self.connectCallback(e);
}
self.connectionState = 0;
}
MCProtocol.prototype.readWriteError = function (e) {
var self = this;
outputLog('We Caught a read/write error ' + e.code + ' - resetting connection', "ERROR", self.connectionID);
self.emit('error', e);
self.connectionState = 0;
self.connectionReset();
}
MCProtocol.prototype.packetTimeout = function (packetType, packetSeqNum) {
var self = this;
outputLog('PacketTimeout called with type ' + packetType + ' and seq ' + packetSeqNum, "WARN", self.connectionID);
if (packetType === "read") {
outputLog("READ TIMEOUT on sequence number " + packetSeqNum, "WARN", self.connectionID);
self.readResponse(undefined); //, self.findReadIndexOfSeqNum(packetSeqNum));
return undefined;
}
if (packetType === "write") {
outputLog("WRITE TIMEOUT on sequence number " + packetSeqNum, "WARN", self.connectionID);
self.writeResponse(undefined); //, self.findWriteIndexOfSeqNum(packetSeqNum));
return undefined;
}
outputLog("Unknown timeout error. Nothing was done - this shouldn't happen.", "ERROR", self.connectionID);
}
MCProtocol.prototype.onTCPConnect = function () {
var self = this;
outputLog('TCP Connection Established to ' + self.netClient.remoteAddress + ' on port ' + self.netClient.remotePort, "DEBUG", self.connectionID);
// Track the connection state
self.connectionState = 4; // 4 = all connected, simple with MC protocol. Other protocols have a negotiation/session packet as well.
self.netClient.removeAllListeners('data');
self.netClient.removeAllListeners('message');
self.netClient.removeAllListeners('error');
self.netClient.on('data', function () {
self.onResponse.apply(self, arguments);
}); // We need to make sure we don't add this event every time if we call it on data.
self.netClient.on('error', function () {
self.readWriteError.apply(self, arguments);
}); // Might want to remove the connecterror listener
self.emit('open');
if ((!self.connectCBIssued) && (typeof (self.connectCallback) === "function")) {
self.connectCBIssued = true;
self.connectCallback();
}
return;
}
MCProtocol.prototype.onUDPConnect = function () {
var self = this;
outputLog('UDP Connection Established to ' + self.netClient.remoteAddress + ' on port ' + self.netClient.remotePort, "DEBUG", self.connectionID);
// Track the connection state
self.connectionState = 4; // 4 = all connected, simple with MC protocol. Other protocols have a negotiation/session packet as well.
self.netClient.removeAllListeners('data');
self.netClient.removeAllListeners('message');
self.netClient.removeAllListeners('error');
self.netClient.on('message', function () {
self.onResponse.apply(self, arguments);
}); // We need to make sure we don't add this event every time if we call it on data.
self.netClient.on('error', function () {
self.readWriteError.apply(self, arguments);
}); // Might want to remove the connecterror listener
self.emit('open');
if ((!self.connectCBIssued) && (typeof (self.connectCallback) === "function")) {
self.connectCBIssued = true;
self.connectCallback();
}
return;
}
MCProtocol.prototype.writeItems = function (arg, value, cb) {
return _writeItems(this, arg, value, cb, false);
}
function _writeItems(self, arg, value, cb, queuedItem) {
//var self = this;
var i;
var reply = [];
outputLog("Preparing to WRITE " + arg, "DEBUG", self.connectionID);
//ensure arg is an array regardless of count
let argArr = arg;
let valueArr = value;
if (Array.isArray(arg) != true) {
argArr = [arg];
valueArr = [value];
}
if (self.isWaiting()) {
let sendStatus = MCProtocol.prototype.enumSendResult.unknown;
if (queuedItem) {
sendStatus = MCProtocol.prototype.enumSendResult.notSent;
outputLog(`οΈπ’οΈβ‘π§ queued writeItem '${arg}' still not sent (isWaiting)`, "DEBUG")
} else if (self.queue.length >= self.queueMaxLength) {
outputLog(`οΈπ’οΈβ‘ποΈ writeItem '${arg}' discarded, queue full`, "WARN")
sendStatus = MCProtocol.prototype.enumSendResult.queueFull;
} else {
sendStatus = MCProtocol.prototype.enumSendResult.queued;
self.queue.push({
arg: arg,
value: value,
cb: cb,
fn: "write",
dt: Date.now()
});
outputLog(`οΈβοΈβ‘π’οΈ writeItem '${arg}' pushed to queue`, "DEBUG")
}
reply.push({ TAG: arg, sendStatus: sendStatus });//[item.useraddr] = MCProtocol.prototype.enumSendResult.badRequest;
return reply;
}
let plcitems = [];
for (i = 0; i < argArr.length; i++) {
if (typeof argArr[i] === "string") {
let plcitem = new PLCItem(self);
plcitem.init(self.translationCB(argArr[i]), argArr[i], self.octalInputOutput, self.frame, self.plcType, valueArr[i]);
plcitem._instance = "original";
if (Array.isArray(cb))
plcitem.cb = cb[i];
else
plcitem.cb = cb;
plcitems.push(plcitem);
}
}
//do callback for non initialised (bad) items
plcitems.map(function (item) {
if (item.initialised == false) {
if (item.cb) {
var cbd = new PLCWriteResult(item.useraddr, item.addr, MCProtocol.prototype.enumOPCQuality.badDeviceFailure.value, 0);
cbd.error = item.initError;
cbd.problem = true;
item.cb(true, cbd);
}
item.extraInfo = item.initError;
reply.problem = true;
let r = {
TAG: item.useraddr,
sendStatus: MCProtocol.prototype.enumSendResult.badRequest
};
reply.push(r);//[item.useraddr] = MCProtocol.prototype.enumSendResult.badRequest;
}
});
//filter OK items
var plcitemsInitialised = plcitems.filter(function (item) {
return item.initialised;
});
var preparedCount = self.prepareWritePacket(plcitemsInitialised);
var plcitemsBuffered = plcitemsInitialised.filter(function (item) {
return item.bufferized;
});
//do callback for items not buffered
plcitemsInitialised.map(function (item) {
if (!item.bufferized) {
if (item.cb) {
var cbd = new PLCWriteResult(item.useraddr, item.addr, MCProtocol.prototype.enumOPCQuality.bad.value, 0);
cbd.error = item.lastError;
cbd.problem = true;
item.cb(true, cbd);
}
item.extraInfo = item.lastError;
reply.problem = true;
let r = {
TAG: item.useraddr,
sendStatus: MCProtocol.prototype.enumSendResult.badRequest
};
reply.push(r);//[item.useraddr] = MCProtocol.prototype.enumSendResult.badRequest;
}
});
let sentCount = 0;
if (plcitemsBuffered.length) {
sentCount = self.sendWritePacket();
}
plcitemsBuffered.map(function (item) {
let s = sentCount ? MCProtocol.prototype.enumSendResult.sent : MCProtocol.prototype.enumSendResult.notSent;
if (s != MCProtocol.prototype.enumSendResult.sent) {
reply.problem = true;
}
let r = {
TAG: item.useraddr,
sendStatus: s
};
reply.push(r);
});
return reply;
}
MCProtocol.prototype.findItem = function (useraddr) {
var self = this;
var i;
var commstate = { value: self.connectionState !== 4, quality: 'OK' };
if (useraddr === '_COMMERR') { return commstate; }
for (i = 0; i < self.polledReadBlockList.length; i++) {
if (self.polledReadBlockList[i].useraddr === useraddr) { return self.polledReadBlockList[i]; }
}
return undefined;
}
MCProtocol.prototype.addItems = function (arg, cb) {
var self = this;
self.addRemoveArray.push({ arg: arg, cb: cb, action: 'poll' });
}
MCProtocol.prototype.addItemsNow = function (arg, action, cb) {
var self = this;
var i;
outputLog("Adding " + arg, "DEBUG", self.connectionID);
addItemsFlag = false;
var addedCount = 0;
var expectedCount = Array.isArray(arg) ? arg.length : 1;
if (typeof arg === "string" && arg !== "_COMMERR") {
//plcitem = stringToMCAddr(self.translationCB(arg), arg, self.octalInputOutput, self.frame, self.plcType);
let plcitem = new PLCItem(self);
plcitem.init(self.translationCB(arg), arg, self.octalInputOutput, self.frame, self.plcType, undefined /*not writing*/);
if (plcitem.initialised) {
plcitem.action = action;
plcitem.cb = cb;
self.polledReadBlockList.push(plcitem);
addedCount++;
} else {
outputLog(`Dropping bad request item '${arg}'`, "WARN");
}
} else if (Array.isArray(arg)) {
for (i = 0; i < arg.length; i++) {
if (typeof arg[i] === "string" && arg[i] !== "_COMMERR") {
//plcitem = stringToMCAddr(self.translationCB(arg[i]), arg[i], self.octalInputOutput, self.frame, self.plcType);
let plcitem = new PLCItem(self);
plcitem.init(self.translationCB(arg[i]), arg[i], self.octalInputOutput, self.frame, self.plcType, undefined /*not writing*/);
if (plcitem.initialised) {
if (Array.isArray(cb))
plcitem.cb = cb[i];
else
plcitem.cb = cb;
if (Array.isArray(action))
plcitem.action = action[i];
else
plcitem.action = action;
self.polledReadBlockList.push(plcitem);
addedCount++;
} else {
outputLog(`Dropping bad request item '${arg[i]}'`, "WARN");
}
}
}
}
// Validity check.
for (i = self.polledReadBlockList.length - 1; i >= 0; i--) {
if (self.polledReadBlockList[i] === undefined) {
self.polledReadBlockList.splice(i, 1);
outputLog("Dropping an undefined request item.", "WARN");
}
}
// prepareReadPacket();
self.readPacketValid = false;
}
MCProtocol.prototype.removeItems = function (arg) {
var self = this;
self.addRemoveArray.push({ arg: arg, action: 'remove' });
}
MCProtocol.prototype.removeItemsNow = function (arg) {
var self = this;
var i;
self.removeItemsFlag = false;
if (typeof arg === "undefined") {
self.polledReadBlockList = [];
} else if (typeof arg === "string") {
for (i = 0; i < self.polledReadBlockList.length; i++) {
outputLog('TCBA ' + self.translationCB(arg), "TRACE");
if (self.polledReadBlockList[i].addr === self.translationCB(arg)) {
outputLog('Splicing', "TRACE");
self.polledReadBlockList.splice(i, 1);
}
}
} else if (Array.isArray(arg)) {
for (i = 0; i < self.polledReadBlockList.length; i++) {
for (j = 0; j < arg.length; j++) {
if (self.polledReadBlockList[i].addr === self.translationCB(arg[j])) {
self.polledReadBlockList.splice(i, 1);
}
}
}
}
self.readPacketValid = false;
// prepareReadPacket();
}
MCProtocol.prototype.readAllItems = function (arg) {
var self = this;
var i;
outputLog("Reading All Items (readAllItems was called)", "TRACE", self.connectionID);
if (typeof arg === "function") {
self.readDoneCallback = arg;
} else {
self.readDoneCallback = doNothing;
}
if (self.connectionState !== 4) {
outputLog("Unable to read when not connected. Return bad values.", "WARN", self.connectionID);
} // For better behaviour when auto-reconnecting - don't return now
// Check if ALL are done... You might think we could look at parallel jobs, and for the most part we can, but if one just finished and we end up here before starting another, it's bad.
if (self.isWaiting()) {
outputLog("Waiting to read for all R/W operations to complete. Will re-trigger readAllItems in 100ms.", "DEBUG");
setTimeout(function () {
self.readAllItems.apply(self, arguments);
}, 100, arg);
return;
}
// Now we check the array of adding and removing things. Only now is it really safe to do this.
self.addRemoveArray.forEach(function (element) {
outputLog('Adding or Removing ' + util.format(element), "DEBUG", self.connectionID);
if (element.action === 'remove') {
self.removeItemsNow(element.arg);
}
if (element.action === 'poll' || element.action === 'read') {
self.addItemsNow(element.arg, element.action, element.cb);
}
});
self.addRemoveArray = []; // Clear for next time.
if (!self.readPacketValid) {
self.prepareReadPacket();
}
outputLog("Calling SRP from RAI", "TRACE", self.connectionID);
self.sendReadPacket(); // Note this sends the first few read packets depending on parallel connection restrictions.
}
MCProtocol.prototype.readItems = function (arg, cb) {
return _readItems(this, arg, cb, false);
}
function _readItems(self, arg, cb, queuedItem) {
//var self = this;
var i;
var reply = [];
outputLog("#readItems() was called)", "TRACE", self.connectionID);
//ensure arg is an array regardless of count
let argArr = arg;
if (Array.isArray(arg) != true) {
argArr = [arg];
}
if (self.connectionState !== 4) {
outputLog("Unable to read when not connected. Return bad values.", "WARN", self.connectionID);
//self.queue = [];//empty the queue
} // For better behaviour when auto-reconnecting - don't return now
if (self.isWaiting()) {
let sendStatus = MCProtocol.prototype.enumSendResult.unknown;
if (queuedItem) {
sendStatus = MCProtocol.prototype.enumSendResult.notSent;
outputLog(`οΈπ’οΈβ‘π§ queued readItem '${arg}' still not sent (isWaiting)`, "DEBUG")
} else if (self.queue.length >= self.queueMaxLength) {
outputLog(`οΈπ’οΈβ‘ποΈ readItem '${arg}' discarded, queue full`, "WARN")
sendStatus = MCProtocol.prototype.enumSendResult.queueFull;
} else {
sendStatus = MCProtocol.prototype.enumSendResult.queued;
self.queue.push({
arg: arg,
cb: cb,
fn: "read",
dt: Date.now()
});
outputLog(`οΈπβ‘π’οΈ readItem '${arg}' pushed to queue`, "DEBUG")
}
reply.push({ TAG: arg, sendStatus: sendStatus });//[item.useraddr] = MCProtocol.prototype.enumSendResult.badRequest;
return reply;
}
let plcitems = [];
for (i = 0; i < argArr.length; i++) {
if (typeof argArr[i] === "string" && argArr[i] !== "_COMMERR") {
let plcitem = new PLCItem(self);
plcitem.init(self.translationCB(argArr[i]), argArr[i], self.octalInputOutput, self.frame, self.plcType, undefined /*not writing*/);
if (Array.isArray(cb))
plcitem.cb = cb[i];
else
plcitem.cb = cb;
plcitem.action = 'read';
plcitems.push(plcitem);
}
}
//do callback for non initialised (bad) items
plcitems.map(function (item) {
if (item.initialised == false) {
var cbd = new PLCReadResult(item.useraddr, item.addr, MCProtocol.prototype.enumOPCQuality.badConfigErrInServer.value, 0, undefined, undefined);
outputLog(`Failed to initialise PLC item. Addr '${item.addr}' may be invalid for this type of PLC and frame setting - item will be dropped`, "ERROR");
item.cb(true, cbd);
item.extraInfo = item.initError;
reply.problem = true;
let r = {
TAG: item.useraddr,
sendStatus: MCProtocol.prototype.enumSendResult.badRequest
};
reply.push(r);
}
});
//filter OK items
var plcitemsInitialised = plcitems.filter(function (item) {
return item.initialised;
});
//check how many good items - return if none
if (!plcitemsInitialised.length) {
outputLog("Nothing to send!", "WARN");
return reply;
}
let pp = self.prepareReadPacket(plcitemsInitialised);
outputLog("Calling sendReadPacket()", "TRACE", self.connectionID);
var sentCount = self.sendReadPacket(); // Note this sends the first few read packets depending on parallel connection restrictions.
plcitemsInitialised.map(function (item) {
let s = sentCount ? MCProtocol.prototype.enumSendResult.sent : MCProtocol.prototype.enumSendResult.notSent;
if (s != MCProtocol.prototype.enumSendResult.sent) {
reply.problem = true;
}
let r = {
TAG: item.useraddr,
sendStatus: s
};
reply.push(r);
});
return reply;
}
MCProtocol.prototype.isWaiting = function () {
var self = this;
return (self.isReading() || self.isWriting());
}
MCProtocol.prototype.isReading = function () {
return this.readPacketArray.some(function(el){
return el.sent;
});
}
MCProtocol.prototype.isWriting = function () {
return this.writePacketArray.some(function(el){
return el.sent;
});
}
MCProtocol.prototype.clearReadPacketTimeouts = function () {
var self = this;
clearPacketTimeouts(self.readPacketArray);
}
MCProtocol.prototype.clearWritePacketTimeouts = function () {
var self = this;
outputLog('Clearing write PacketTimeouts', "DEBUG", self.connectionID);
// Before we initialize the readPacketArray, we need to loop through all of them and clear timeouts.
for (i = 0; i < self.writePacketArray.length; i++) {
clearTimeout(self.writePacketArray[i].timeout);
self.writePacketArray[i].sent = false;
self.writePacketArray[i].rcvd = false;
}
}
MCProtocol.prototype.prepareWritePacket = function (itemList) {
outputLog("#################### prepareWritePacket() ####################", "TRACE");
var self = this;
var requestList = []; // The request list consists of the block list, split into chunks readable by PDU.
var requestNumber = 0, thisBlock = 0, thisRequest = 0;
var itemsThisPacket;
var numItems;
// Sort the items using the sort function, by type and offset.
itemList.sort(itemListSorter);
// Just exit if there are no items.
if (itemList.length == 0) {
return undefined;
}
self.globalWriteBlockList = [];
itemList[0].block = thisBlock;
// Just push the items into blocks and figure out the write buffers
for (i = 0; i < itemList.length; i++) {
if (itemList[i].prepareWriteData()) {
itemList[i].writeBuffer._instance = itemList[i]._instance;
self.globalWriteBlockList.push(itemList[i]); // Remember - by reference.
var bli = self.globalWriteBlockList[self.globalWriteBlockList.length-1];
bli.isOptimized = false;
bli.itemReference = [];
bli.itemReference.push(itemList[i]);
}
}
// Split the blocks into requests, if they're too large.
for (i = 0; i < self.globalWriteBlockList.length; i++) {
let block = self.globalWriteBlockList[i];
var startElement = block.offset;
var remainingLength = block.byteLengthWrite;
var remainingTotalArrayLength = block.totalArrayLength;
var maxByteRequest = block.maxWordLength() * 2;
var lengthOffset = 0;
block.partsBufferized = 0;
// How many parts?
block.parts = Math.ceil(block.byteLengthWrite / maxByteRequest);
outputLog(`globalWriteBlockList[${i}].parts == ${block.parts} for request '${block.useraddr}', .offset (device number) == ${block.offset}, maxByteRequest==${maxByteRequest}`, "DEBUG");
block.requestReference = [];
// If we need to spread the sending/receiving over multiple packets...
for (j = 0; j < block.parts; j++) {
// create a request for a globalWriteBlockList.
requestList[thisRequest] = block.clone();
let reqItem = requestList[thisRequest];
reqItem._instance = "block clone (request item)";
reqItem.part = j+1;
//reqItem.updateSeqNum(self.nextSequenceNumber());
reqItem.offset = startElement;
reqItem.byteLengthWrite = Math.min(maxByteRequest, remainingLength);
if (reqItem.bitNative) {
reqItem.totalArrayLength = Math.min(maxByteRequest * 2, remainingTotalArrayLength, block.totalArrayLength);
} else {
// I think we should be dividing by dtypelen here
reqItem.totalArrayLength = Math.min(maxByteRequest / block.dtypelen, remainingLength / block.dtypelen, block.totalArrayLength);
}
remainingTotalArrayLength -= reqItem.totalArrayLength;
reqItem.byteLengthWithFill = reqItem.byteLengthWrite;
reqItem.writeBuffer = block.writeBuffer.slice(lengthOffset, lengthOffset + reqItem.byteLengthWithFill);
reqItem.writeQualityBuffer = block.writeQualityBuffer.slice(lengthOffset, lengthOffset + reqItem.byteLengthWithFill);
lengthOffset += reqItem.byteLengthWrite;
//if we have split a request into parts, then we need to update the device count before creating the comm buffer
if (block.parts > 1) {
//reqItem.datatype = 'BYTE';//why???
//reqItem.dtypelen = 1;//why???
if (reqItem.bitNative) {
reqItem.arrayLength = reqItem.totalArrayLength;//globalReadBlockList[thisBlock].byteLength;
} else {
reqItem.arrayLength = reqItem.byteLengthWrite / 2;//globalReadBlockList[thisBlock].byteLength;
}
}
//generate the comm buffer for this part (with new address offset & length etc)
reqItem.toBuffer(self.isAscii, self.frame, self.plcType, self.nextSequenceNumber(), self.network, self.PCStation, self.PLCStation, self.PLCModuleNo);
block.requestReference.push(reqItem);
block.partsBufferized++;
outputLog(`Created block part (reqItem) ${reqItem.part} of ${block.parts} for request '${block.useraddr}', .offset (device number) == ${reqItem.offset}, byteLengthWrite==${reqItem.byteLengthWrite}, SeqNum == ${reqItem.seqNum}`, "DEBUG");
remainingLength -= maxByteRequest;
if (block.bitNative) {
startElement += maxByteRequest * 2;
} else {
startElement += maxByteRequest / 2;
}
thisRequest++;
}
block.bufferized = (block.partsBufferized == block.parts);
}
self.clearWritePacketTimeouts();
self.writePacketArray = [];
// Set up the write packet
while (requestNumber < requestList.length) {
numItems = 0;
self.writePacketArray.push(new PLCPacket());
var thisPacketNumber = self.writePacketArray.length - 1;
self.writePacketArray[thisPacketNumber].itemList = []; // Initialize as array.
for (var i = requestNumber; i < requestList.length; i++) {
if (numItems == 1) {
break; // Used to break when packet was full. Now break when we can't fit this packet in here.
}
requestNumber++;
numItems++;
self.writePacketArray[thisPacketNumber].seqNum = requestList[i].seqNum;
self.writePacketArray[thisPacketNumber].itemList.push(requestList[i]);
}
}
outputLog("writePacketArray Length = " + self.writePacketArray.length, "DEBUG");
return thisRequest; //return count of prepared requests.
}
MCProtocol.prototype.prepareReadPacket = function (items) {
outputLog("#################### prepareReadPacket() ####################", "TRACE");
const fnstarttime = process.hrtime();
var self = this;
var itemList = items || self.polledReadBlockList; // The items are the actual items requested by the user
var requestList = []; // The request list consists of the block list, split into chunks readable by PDU.
var startOfSlice, endOfSlice, oldEndCoil, demandEndCoil;
let blocklist = []; //self.globalReadBlockList
// Validity check.
for (i = itemList.length - 1; i >= 0; i--) {
if (itemList[i] === undefined) {
itemList.splice(i, 1);
outputLog("Dropping an undefined request item.", "WARN", self.connectionID);
}
}
// Sort the items using the sort function, by type and offset.
itemList.sort(itemListSorter);
// Just exit if there are no items.
if (itemList.length == 0) {
return undefined;
}
/* DISABLED for now as I need to match the items[x] with the cacheitem[x] and update cacheitem[x].cb to new items[x].cb callback
//in order to optimise, cache read packets - where the items addresses are used to generate a key
var packetCacheKey = 'key:';
for (const item in itemList) {
if (itemList.hasOwnProperty(item)) {
packetCacheKey = packetCacheKey + itemList[item].addr + ',';
}
}
outputLog("packetCacheKey = " + packetCacheKey, 3, self.connectionID);
if (!this.readPacketArrayCache)
this.readPacketArrayCache = [];
var cache = this.readPacketArrayCache.find(obj => obj.key == packetCacheKey)
if (cache) {
outputLog(`------ β‘β‘ ------ YEY ------ β‘β‘ ------- Found cache item packetCacheKey:${packetCacheKey} - returning this (nice speed up) -------- β‘β‘β‘`, 2, self.connectionID);
cache.lastUsed = Date.now();
self.readPacketArray = cache.packetArray;
self.globalReadBlockList = cache.blocklist;
self.readPacketValid = true;
clearPacketTimeouts(self.readPacketArray);
// //increment seq number
// for (let p in self.readPacketArray) {
// if (self.readPacketArray.hasOwnProperty(p)) {
// self.readPacketArray[p].seqNum = self.nextSequenceNumber();
// }
// }
let fntimetaken = process.hrtime(fnstarttime);
outputLog(`function '${getFuncName()}()' - time taken ${fntimetaken[0] * 1e9 + fntimetaken[1]} nanoseconds β`, 3);
return cache;
}
outputLog("Cache item not found :(. packetCacheKey : " + packetCacheKey, 3, self.connectionID);
//cache maintenance...
//IMPORTANT: each item cached is approx 99kb (well stringified it is anyhow - so keep cache size reasonable)
if (this.readPacketArrayCache.length >= 10) {
//cleanup - delete oldest
this.readPacketArrayCache.sort((a, b) => a.lastUsed < b.lastUsed);
let deleteditem = this.readPacketArrayCache.pop();
outputLog("--------BOO!------- Cache item clean up (Cache exhausted) - removing item packetCacheKey : " + deleteditem.key + ' ---------cache item removed--- β¨β¨β¨ ', 3, self.connectionID);
}
*/
// ...because you have to start your optimization somewhere.
blocklist[0] = itemList[0];
blocklist[0].itemReference = [];
blocklist[0].itemReference.push(itemList[0]);
var maxByteRequest, thisBlock = 0;
itemList[0].block = thisBlock;
// Optimize the items into blocks
for (i = 1; i < itemList.length; i++) {
maxByteRequest = itemList[i].maxWordLength() * 2;
if ((itemList[i].areaMCCode !== blocklist[thisBlock].areaMCCode) || // Can't optimize between areas
(!self.isOptimizableArea(itemList[i].areaMCCode)) || // May as well try to optimize everything.
((itemList[i].offset - blocklist[thisBlock].offset + itemList[i].byteLength) > maxByteRequest) || // If this request puts us over our max byte length, create a new block for consistency reasons.
((itemList[i].offset - (blocklist[thisBlock].offset + blocklist[thisBlock].byteLength) > self.maxGap) && !itemList[i].bitNative) ||
((itemList[i].offset - (blocklist[thisBlock].offset + blocklist[thisBlock].byteLength) > self.maxGap * 8) && itemList[i].bitNative)) { // If our gap is large, create a new block.
// At this point we give up and create a new block.
thisBlock = thisBlock + 1;
blocklist[thisBlock] = itemList[i]; // By reference.
// itemList[i].block = thisBlock; // Don't need to do this.
blocklist[thisBlock].isOptimized = false;
blocklist[thisBlock].itemReference = [];
blocklist[thisBlock].itemReference.push(itemList[i]);
// outputLog("Not optimizing.");
} else {
outputLog("Performing optimization of item " + itemList[i].addr + " with " + blocklist[thisBlock].addr, "DEBUG");
// This next line checks the maximum.
// Think of this situation - we have a large request of 40 bytes starting at byte 10.
// Then someone else wants one byte starting at byte 12. The block length doesn't change.
//
// But if we had 40 bytes starting at byte 10 (which gives us byte 10-49) and we want byte 50, our byte length is 50-10 + 1 = 41.
if (itemList[i].bitNative) { // Coils and inputs must be special-cased
blocklist[thisBlock].byteLength =
Math.max(
blocklist[thisBlock].byteLength,
(Math.floor((itemList[i].requestOffset - blocklist[thisBlock].requestOffset) / 8) + itemList[i].byteLength)
);
if (blocklist[thisBlock].byteLength % 2) { // shouldn't be necessary
blocklist[thisBlock].byteLength += 1;
}
} else {
blocklist[thisBlock].byteLength =
Math.max(
blocklist[thisBlock].byteLength,
((itemList[i].offset - blocklist[thisBlock].offset) * 2 + Math.ceil(itemList[i].byteLength / itemList[i].multidtypelen)) * itemList[i].multidtypelen
);
}
outputLog("Optimized byte length is now " + blocklist[thisBlock].byteLength, "DEBUG");
// Point the buffers (byte and quality) to a sliced version of the optimized block. This is by reference (same area of memory)
if (itemList[i].bitNative) { // Again a special case.
startOfSlice = (itemList[i].requestOffset - blocklist[thisBlock].requestOffset) / 8; // NO, NO, NO - not the dtype length - start of slice varies with register width. itemList[i].multidtypelen;
} else {
startOfSlice = (itemList[i].requestOffset - blocklist[thisBlock].requestOffset) * 2; // NO, NO, NO - not the dtype length - start of slice varies with register width. itemList[i].multidtypelen;
}
endOfSlice = startOfSlice + itemList[i].byteLength;
itemList[i].byteBuffer = blocklist[thisBlock].byteBuffer.slice(startOfSlice, endOfSlice);
itemList[i].qualityBuffer = blocklist[thisBlock].qualityBuffer.slice(startOfSlice, endOfSlice);
// For now, change the request type here, and fill in some other things.
// I am not sure we want to do these next two steps.
// It seems like things get screwed up when we do this.
// Since globalReadBlockList[thisBlock] exists already at this point, and our buffer is already set, let's not do this now.
// globalReadBlockList[thisBlock].datatype = 'BYTE';
// globalReadBlockList[thisBlock].dtypelen = 1;
blocklist[thisBlock].isOptimized = true;
blocklist[thisBlock].itemReference.push(itemList[i]);
}
}
var thisRequest = 0;
// Split the blocks into requests, if they're too large.
for (i = 0; i < blocklist.length; i++) {
// Always create a request for a globalReadBlockList.
let blockListItem = blocklist[i];
// How many parts?
maxByteRequest = blockListItem.maxWordLength() * 2;
blockListItem.parts = Math.ceil(blockListItem.byteLength / maxByteRequest);
var startElement = blockListItem.requestOffset; // try to ignore the offset
var remainingLength = blockListItem.byteLength;
var remainingTotalArrayLength = blockListItem.totalArrayLength;
//initialise the buffers
blockListItem.byteBuffer.fill(0)// = new Buffer(blockListItem.byteLength);
blockListItem.qualityBuffer.fill(0)// = new Buffer(blockListItem.byteLength);
blockListItem.requestReference = [];
// If we need to spread the sending/receiving over multiple packets...
for (j = 0; j < blockListItem.parts; j++) {
requestList[thisRequest] = blockListItem.clone();
let thisReqItem = requestList[thisRequest];
thisReqItem._instance = "block clone (request item)";
blockListItem.requestReference.push(thisReqItem);
thisReqItem.requestOffset = startElement;
thisReqItem.byteLength = Math.min(maxByteRequest, remainingLength);
if (thisReqItem.bitNative) {
thisReqItem.totalArrayLength = Math.min(maxByteRequest * 8, remainingLength * 8, blockListItem.totalArrayLength);
} else {
thisReqItem.totalArrayLength = Math.min(maxByteRequest / blockListItem.dtypelen, remainingLength / blockListItem.dtypelen, blockListItem.totalArrayLength);
}
thisReqItem.byteLengthWithFill = thisReqItem.byteLength;
if (thisReqItem.byteLengthWithFill % 2) { thisReqItem.byteLengthWithFill += 1; };
// Just for now... I am not sure if we really want to do this in this case.
if (blockListItem.parts > 1) {
thisReqItem.datatype = 'BYTE';
thisReqItem.dtypelen = 1;
if (thisReqItem.bitNative) {
thisReqItem.arrayLength = thisReqItem.totalArrayLength;//globalReadBlockList[thisBlock].byteLength;
} else {
thisReqItem.arrayLength = thisReqItem.byteLength / 2;//globalReadBlockList[thisBlock].byteLength;
}
}
outputLog(`Created block part (reqItem) ${j+1} of ${blockListItem.parts} for request '${blockListItem.useraddr}', .offset (device number) == ${thisReqItem.offset}, byteLength==${thisReqItem.byteLength}`, "DEBUG");
remainingLength -= maxByteRequest;
if (blockListItem.bitNative) {
// startElement += maxByteRequest/thisReqItem.multidtypelen;
startElement += maxByteRequest * 8;
} else {
startElement += maxByteRequest / 2;
}
thisRequest++;
}
}
// The packetizer...
var requestNumber = 0;
var itemsThisPacket;
self.clearReadPacketTimeouts();
let packetArray = [];
packetArray = [];
while (requestNumber < requestList.length) {
// Set up the read packet
var numItems = 0;
packetArray.push(new PLCPacket());
var thisPacketNumber = packetArray.length - 1;
//packetArray[thisPacketNumber].seqNum = self.nextSequenceNumber();
packetArray[thisPacketNumber].itemList = []; // Initialize as array.
for (var i = requestNumber; i < requestList.length; i++) {
if (numItems >= 1) {
break; // We can't fit this packet in here. For now, this is always the case as we only have one item in MC protocol.
}
requestNumber++;
numItems++;
packetArray[thisPacketNumber].itemList.push(requestList[i]);
}
}
self.readPacketArray = packetArray;
self.globalReadBlockList = blocklist;
self.readPacketValid = true;
let rpa = {
lastUsed: Date.now(),
/* DISABLED cache for now key: packetCacheKey, */
blocklist: blocklist,
packetArray: packetArray,
}
let fntimetaken = process.hrtime(fnstarttime);
outputLog(`function '${getFuncName()}()' - time taken ${fntimetaken[0] * 1e9 + fntimetaken[1]} nanoseconds β`, "TRACE");
/* DISABLED cache for now
outputLog("------------------ that took aaaaaggggeeeeessss adding packetCacheKey : " + packetCacheKey + ' to the cache for optimisation --------------- πΊπΊπΊ', 3, self.connectionID);
self.readPacketArrayCache.push(rpa);
*/
return rpa;
}
function clearPacketTimeouts(packetArray) {
outputLog('Clearing read PacketTimeouts', 3);
// Before we initialize the readPacketArray, we need to loop through all of them and clear timeouts.
for (i = 0; i < packetArray.length; i++) {
clearTimeout(packetArray[i].timeout);
packetArray[i].sent = false;
packetArray[i].rcvd = false;
}
}
function getFuncName() {
return getFuncName.caller.name
}
MCProtocol.prototype.sendReadPacket = function (arg) {
var self = this;
var i, j, curLength, returnedBfr, routerLength;
var flagReconnect = false;
var sentCount = 0;
outputLog("#################### sendReadPacket() ####################", 3, self.connectionID);
for (i = 0; i < self.readPacketArray.length; i++) {
let readPacket = self.readPacketArray[i];
if (readPacket.sent) { continue; }
if (self.parallelJobsNow >= self.maxParallel) { continue; }
// From here down is SENDING the packet
readPacket.reqTime = process.hrtime();
curLength = 0;
routerLength = 0;
// The FOR loop is left in here for now, but really we are only doing one request per packet for now.
for (j = 0; j < readPacket.itemList.length; j++) {
var item = readPacket.itemList[j];
item.sendTime = Date.now();
readPacket.seqNum = self.nextSequenceNumber();// readPacket.seqNum;
item.toBuffer(self.isAscii, self.frame, self.plcType, readPacket.seqNum, self.network, self.PCStation, self.PLCStation, self.PLCModuleNo);
returnedBfr = item.buffer.data;
returnedBfr.copy(self.readReq, curLength);
curLength += returnedBfr.length;
outputLog(`The buffer.length of read item '${item.useraddr} [offset:${item.offset}] (seqNum:${item.seqNum})' is '${returnedBfr.length}'...`, "DEBUG");
outputLog(returnedBfr, "DEBUG");
}
outputLog("The final send buffer is...", "DEBUG");
var sendBuffer = self.readReq.slice(0, curLength);
if (self.isAscii) {
outputLog(asciize(sendBuffer), "DEBUG");
outputLog(binarize(asciize(sendBuffer)), "DEBUG");
} else {
outputLog(sendBuffer, "DEBUG");
}
if (self.connectionState == 4) {
readPacket.timeout = setTimeout(function () {
self.packetTimeout.apply(self, arguments);
},
self.globalTimeout, "read", readPacket.seqNum);
if (self.isAscii) {
self.netClient.write(asciize(sendBuffer));
} else {
self.netClient.write(sendBuffer); // was 31
}
self.lastPacketSent = readPacket;
self.lastPacketSent.isWritePacket = false;
self.lastPacketSent.isReadPacket = true;
sentCount++;
readPacket.sent = true;
readPacket.rcvd = false;
readPacket.timeoutError = false;
self.parallelJobsNow += 1;
outputLog('Sent Read Packet SEQ ' + readPacket.seqNum, "DEBUG");
} else {
// outputLog('Somehow got into read block without proper connectionState of 4. Disconnect.');
// connectionReset();
// setTimeout(connectNow, 2000, connectionParams);
// Note we aren't incrementing maxParallel so we are actually going to time out on all our packets all at once.
readPacket.sent = true;
readPacket.rcvd = false;
readPacket.timeoutError = true;
if (!flagReconnect) {
// Prevent duplicates
outputLog(`Not Sending Read Packet (seqNum==${readPacket