@teamcymbeline/node-red-contrib-mcprotocol 0.0.0-stage → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -2
- package/connection_pool.js +161 -0
- package/examples/demo.json +1 -0
- package/mcprotocol/LICENSE +22 -0
- package/mcprotocol/README.md +192 -0
- package/mcprotocol/mcprotocol.js +3980 -0
- package/nodes/connection.html +155 -0
- package/nodes/connection.js +59 -0
- package/nodes/icons/connection.png +0 -0
- package/nodes/icons/read.png +0 -0
- package/nodes/icons/read2.png +0 -0
- package/nodes/icons/write.png +0 -0
- package/nodes/read.html +179 -0
- package/nodes/read.js +307 -0
- package/nodes/write.html +185 -0
- package/nodes/write.js +284 -0
- package/package.json +28 -3
|
@@ -0,0 +1,3980 @@
|
|
|
1
|
+
// MCPROTOCOL - A library for communication to Mitsubishi PLCs over Ethernet from node.js.
|
|
2
|
+
// Currently only FX3U CPUs using FX3U-ENET and FX3U-ENET-ADP modules (Ethernet modules) tested.
|
|
3
|
+
// Please report experiences with others.
|
|
4
|
+
|
|
5
|
+
// The MIT License (MIT)
|
|
6
|
+
|
|
7
|
+
// Copyright (c) 2015 Dana Moffit
|
|
8
|
+
|
|
9
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
// in the Software without restriction, including without limitation the rights
|
|
12
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
// furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
// The above copyright notice and this permission notice shall be included in
|
|
17
|
+
// all copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
25
|
+
// THE SOFTWARE.
|
|
26
|
+
|
|
27
|
+
// EXTRA WARNING - This is BETA software and as such, be careful, especially when
|
|
28
|
+
// writing values to programmable controllers.
|
|
29
|
+
//
|
|
30
|
+
// Some actions or errors involving programmable controllers can cause injury or death,
|
|
31
|
+
// and YOU are indicating that you understand the risks, including the
|
|
32
|
+
// possibility that the wrong address will be overwritten with the wrong value,
|
|
33
|
+
// when using this library. Test thoroughly in a laboratory environment.
|
|
34
|
+
|
|
35
|
+
var net = require("net");
|
|
36
|
+
var dgram = require('dgram');
|
|
37
|
+
var EventEmitter = require('events').EventEmitter;
|
|
38
|
+
var util = require("util");
|
|
39
|
+
var inherits = require('util').inherits
|
|
40
|
+
var effectiveDebugLevel = 0; // intentionally global, shared between connections
|
|
41
|
+
var monitoringTime = 10;
|
|
42
|
+
|
|
43
|
+
module.exports = MCProtocol;
|
|
44
|
+
|
|
45
|
+
function MCProtocol() {
|
|
46
|
+
if (!(this instanceof MCProtocol)) return new MCProtocol();
|
|
47
|
+
EventEmitter.call(this);
|
|
48
|
+
|
|
49
|
+
var self = this;
|
|
50
|
+
|
|
51
|
+
//self.data = {};//for data access
|
|
52
|
+
self.readReq = Buffer.alloc(1000);//not calculated
|
|
53
|
+
self.writeReq;// = new Buffer(1500);//size depends on PLC type! As Q/L can read/write 950 WDs
|
|
54
|
+
self.queue = [];
|
|
55
|
+
self.resetPending = false;
|
|
56
|
+
self.resetTimeout = undefined;
|
|
57
|
+
|
|
58
|
+
self.maxPDU = 255;
|
|
59
|
+
self.netClient = undefined;
|
|
60
|
+
self.connectionState = 0;
|
|
61
|
+
self.requestMaxParallel = 1;
|
|
62
|
+
self.maxParallel = 1; // MC protocol is read/response. Parallel jobs not supported.
|
|
63
|
+
self.isAscii = false;
|
|
64
|
+
self.octalInputOutput;
|
|
65
|
+
self.parallelJobsNow = 0;
|
|
66
|
+
self.maxGap = 5;
|
|
67
|
+
self.doNotOptimize = false;
|
|
68
|
+
self.connectCallback = undefined;
|
|
69
|
+
self.readDoneCallback = undefined;
|
|
70
|
+
self.writeDoneCallback = undefined;
|
|
71
|
+
self.connectTimeout = undefined;
|
|
72
|
+
self.PDUTimeout = undefined;
|
|
73
|
+
self.globalTimeout = 4500;
|
|
74
|
+
self.lastPacketSent = undefined;
|
|
75
|
+
self.readPacketArray = [];
|
|
76
|
+
self.writePacketArray = [];
|
|
77
|
+
self.polledReadBlockList = [];
|
|
78
|
+
self.globalReadBlockList = [];
|
|
79
|
+
self.globalWriteBlockList = [];
|
|
80
|
+
self.masterSequenceNumber = 1;
|
|
81
|
+
self.translationCB = function (tag) { return tag };
|
|
82
|
+
self.connectionParams = undefined;
|
|
83
|
+
self.connectionID = 'UNDEF';
|
|
84
|
+
self.addRemoveArray = [];
|
|
85
|
+
self.readPacketValid = false;
|
|
86
|
+
self.connectCBIssued = false;
|
|
87
|
+
self.queueTimer = undefined;
|
|
88
|
+
self.queuePollTime = 50;
|
|
89
|
+
self.queueMaxLength = 50;//avg time on good connection is 20ms. 20 * 50 = 1000ms to process.
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
inherits(MCProtocol, EventEmitter);
|
|
95
|
+
|
|
96
|
+
MCProtocol.prototype.isConnected = function () {
|
|
97
|
+
var self = this;
|
|
98
|
+
return self.connectionState == 4;
|
|
99
|
+
}
|
|
100
|
+
MCProtocol.prototype.setDebugLevel = function (level) {
|
|
101
|
+
var l = (level + "").toUpperCase();
|
|
102
|
+
switch (l) {
|
|
103
|
+
case 'TRACE':
|
|
104
|
+
effectiveDebugLevel = 4;
|
|
105
|
+
break;
|
|
106
|
+
case 'DEBUG':
|
|
107
|
+
effectiveDebugLevel = 3;
|
|
108
|
+
break;
|
|
109
|
+
case 'INFO':
|
|
110
|
+
effectiveDebugLevel = 2;
|
|
111
|
+
break;
|
|
112
|
+
case 'WARN':
|
|
113
|
+
effectiveDebugLevel = 1;
|
|
114
|
+
break;
|
|
115
|
+
case 'ERROR':
|
|
116
|
+
effectiveDebugLevel = 0;
|
|
117
|
+
break;
|
|
118
|
+
case 'NONE':
|
|
119
|
+
effectiveDebugLevel = -1;
|
|
120
|
+
break;
|
|
121
|
+
|
|
122
|
+
default:
|
|
123
|
+
effectiveDebugLevel = level;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
MCProtocol.prototype.nextSequenceNumber = function () {
|
|
129
|
+
var self = this;
|
|
130
|
+
self.masterSequenceNumber += 1;
|
|
131
|
+
if (self.masterSequenceNumber > 32767) {
|
|
132
|
+
self.masterSequenceNumber = 1;
|
|
133
|
+
}
|
|
134
|
+
return self.masterSequenceNumber;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
MCProtocol.prototype.setTranslationCB = function (cb) {
|
|
138
|
+
var self = this;
|
|
139
|
+
if (typeof cb === "function") {
|
|
140
|
+
outputLog('Translation OK', "TRACE");
|
|
141
|
+
self.translationCB = cb;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
MCProtocol.prototype.initiateConnection = function (cParam, callback) {
|
|
146
|
+
var self = this;
|
|
147
|
+
if (cParam === undefined) { cParam = { port: 10000, host: '192.168.8.106', ascii: false }; }
|
|
148
|
+
outputLog('Initiate Called - Connecting to PLC with address and parameters...', "DEBUG");
|
|
149
|
+
outputLog(cParam, "DEBUG");
|
|
150
|
+
if (typeof (cParam.name) === 'undefined') {
|
|
151
|
+
self.connectionID = cParam.host;
|
|
152
|
+
} else {
|
|
153
|
+
self.connectionID = cParam.name;
|
|
154
|
+
}
|
|
155
|
+
if (typeof (cParam.ascii) === 'undefined') {
|
|
156
|
+
self.isAscii = false;
|
|
157
|
+
} else {
|
|
158
|
+
self.isAscii = cParam.ascii;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (typeof (cParam.octalInputOutput) === 'undefined') {
|
|
162
|
+
self.octalInputOutput = false;
|
|
163
|
+
} else {
|
|
164
|
+
self.octalInputOutput = cParam.octalInputOutput;
|
|
165
|
+
}
|
|
166
|
+
if (typeof (cParam.plcType) === 'undefined') {
|
|
167
|
+
self.plcType = MCProtocol.prototype.enumPLCTypes.Q.name;
|
|
168
|
+
self.enumDeviceCodeSpec = MCProtocol.prototype.enumDeviceCodeSpecQ;//default to Q/L series
|
|
169
|
+
outputLog(`plcType not provided, defaulting to Q series PLC`,"WARN");
|
|
170
|
+
} else {
|
|
171
|
+
self.plcType = cParam.plcType;
|
|
172
|
+
if(!MCProtocol.prototype.enumPLCTypes[cParam.plcType]){
|
|
173
|
+
self.plcType = MCProtocol.prototype.enumPLCTypes.Q.name;
|
|
174
|
+
outputLog(`plcType '${cParam.plcType}' unknown. Currently supported types are '${MCProtocol.prototype.enumPLCTypes.keys.join("|")}', defaulting to Q series PLC`,"WARN");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
self.plcSeries = MCProtocol.prototype.enumPLCTypes[self.plcType];
|
|
178
|
+
//not sure how best to handle A/QnA series - not even sure A series can do 3E/4E frames!
|
|
179
|
+
//for now, default to Q (will be overwritten below if user choses 1E frames)
|
|
180
|
+
self.enumDeviceCodeSpec = MCProtocol.prototype['enumDeviceCodeSpec' + self.plcType] || MCProtocol.prototype.enumDeviceCodeSpecQ;
|
|
181
|
+
|
|
182
|
+
outputLog(`'plcType' set is ${self.plcType}`,"INFO");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (typeof (cParam.frame) === 'undefined') {
|
|
186
|
+
outputLog(`'frame' not provided, defaulting '3E'. Valid options are 1E, 3E, 4E.`,"WARN");
|
|
187
|
+
self.frame = '3E';
|
|
188
|
+
} else {
|
|
189
|
+
switch (cParam.frame.toUpperCase()) {
|
|
190
|
+
case '1E':
|
|
191
|
+
self.frame = '1E';
|
|
192
|
+
self.enumDeviceCodeSpec = MCProtocol.prototype.enumDeviceCodeSpec1E;
|
|
193
|
+
break;
|
|
194
|
+
case '3E':
|
|
195
|
+
self.frame = '3E';
|
|
196
|
+
break;
|
|
197
|
+
case '4E':
|
|
198
|
+
self.frame = '4E';
|
|
199
|
+
break;
|
|
200
|
+
default:
|
|
201
|
+
self.frame = '3E';
|
|
202
|
+
outputLog(`'frame' ${cParam.frame} is unknown. Defaulting to 3E. Valid options are 1E, 3E, 4E.`,"WARN");
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
self.frame = cParam.frame;
|
|
206
|
+
outputLog(`'frame' set is ${self.frame}`,"DEBUG");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if(!self.enumDeviceCodeSpec){
|
|
210
|
+
throw new Error("Error determining device code specification. Check combination of PLC Type and Frame Type are valid");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (typeof (cParam.PLCStation) !== 'undefined') {
|
|
214
|
+
self.PLCStation = cParam.PLCStation;
|
|
215
|
+
}
|
|
216
|
+
if (typeof (cParam.PCStation) !== 'undefined') {
|
|
217
|
+
self.PCStation = cParam.PCStation;
|
|
218
|
+
}
|
|
219
|
+
if (typeof (cParam.network) !== 'undefined') {
|
|
220
|
+
self.network = cParam.network;
|
|
221
|
+
}
|
|
222
|
+
if (typeof (cParam.PLCModuleNo) !== 'undefined') {
|
|
223
|
+
self.PLCModuleNo = cParam.PLCModuleNo;
|
|
224
|
+
}
|
|
225
|
+
if (typeof (cParam.queuePollTime) !== 'undefined') {
|
|
226
|
+
self.queuePollTime = cParam.queuePollTime;
|
|
227
|
+
}
|
|
228
|
+
if (typeof (cParam.queueMaxAge) !== 'undefined') {
|
|
229
|
+
self.queueMaxAge = cParam.queueMaxAge;
|
|
230
|
+
} else {
|
|
231
|
+
self.queueMaxAge = 2000;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
self.plcSeries = MCProtocol.prototype.enumPLCTypes[self.plcType];
|
|
235
|
+
self.writeReq = Buffer.alloc(self.plcSeries.requiredWriteBufferSize);//size depends on PLC type! As Q/L can read/write 950 WDs
|
|
236
|
+
self.connectionParams = cParam;
|
|
237
|
+
self.connectCallback = callback;
|
|
238
|
+
self.connectCBIssued = false;
|
|
239
|
+
if (self.fakeTheConnection)//debug
|
|
240
|
+
self.connectCallback();
|
|
241
|
+
else
|
|
242
|
+
self.connectNow(self.connectionParams, false);
|
|
243
|
+
|
|
244
|
+
self.startQueueTimer = function (ms) {
|
|
245
|
+
self.queueTimer = setTimeout(() => {
|
|
246
|
+
self._processQueue()
|
|
247
|
+
}, ms);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
self.startQueueTimer(self.queuePollTime);
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
self.processQueueASAP = function () {
|
|
254
|
+
setImmediate(() => {
|
|
255
|
+
outputLog(`🛢️ setImmediate Calling _processQueue() --> `, "TRACE");
|
|
256
|
+
self._processQueue();
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
self._processQueue = function () {
|
|
261
|
+
clearTimeout(self.queueTimer);
|
|
262
|
+
try {
|
|
263
|
+
if (!self.queue.length) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// {arg: , cb: , dt: }
|
|
267
|
+
var queueItem = self.queue[0];
|
|
268
|
+
var itemAge = Date.now() - queueItem.dt;
|
|
269
|
+
if (itemAge > self.queueMaxAge) {
|
|
270
|
+
outputLog(`🛢️➡🗑️ Discarding queued '${queueItem.fn}' item ${queueItem.arg} (item age is ${itemAge}ms, max age is ${self.queueMaxAge}ms)`, "WARN")
|
|
271
|
+
self.queue.shift();
|
|
272
|
+
return;//too old - discard
|
|
273
|
+
}
|
|
274
|
+
outputLog(`🛢️➡⚙️ Sending queued '${queueItem.fn}' item ${queueItem.arg}`, "DEBUG");
|
|
275
|
+
var result;
|
|
276
|
+
if (queueItem.fn == "read") {
|
|
277
|
+
result = _readItems(self, queueItem.arg, queueItem.cb, true);
|
|
278
|
+
} else if (queueItem.fn == "write") {
|
|
279
|
+
result = _writeItems(self, queueItem.arg, queueItem.value, queueItem.cb, true);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
let allSent = result.every(function (r) {
|
|
284
|
+
return r.sendStatus == MCProtocol.prototype.enumSendResult.sent;
|
|
285
|
+
});
|
|
286
|
+
if (allSent) {
|
|
287
|
+
outputLog(`🛢️➡⚙️ Successfully sent queued '${queueItem.fn}' item '${queueItem.arg}'. (queue will be shifted to remove this item)`, "DEBUG");
|
|
288
|
+
self.queue.shift();//all sent shift the queued item - its done :)
|
|
289
|
+
return; //no need to continue
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let noneSent = result.every(function (r) {
|
|
293
|
+
return r.sendStatus == MCProtocol.prototype.enumSendResult.notSent;
|
|
294
|
+
});
|
|
295
|
+
if (noneSent) {
|
|
296
|
+
outputLog(`🛢️➡X Queued '${queueItem.fn}' item ${queueItem.arg} NOT sent - will try again soon`, "DEBUG");
|
|
297
|
+
return; //no need to continue
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
//by default, if !allSent and !nonSent, then _some_ were sent!
|
|
301
|
+
outputLog(`🛢️➡☠️ Something failed to send '${queueItem.fn}' item '${queueItem.arg}'. Queue will be shifted to remove this item.`, "WARN");
|
|
302
|
+
self.queue.shift();//some sent / some bad - shift the queued item regardless
|
|
303
|
+
|
|
304
|
+
} catch (error) {
|
|
305
|
+
outputLog(`Something went wrong polling the queue: ${error}. Queue will be shifted to remove this item.`, "ERROR");
|
|
306
|
+
self.queue.shift();
|
|
307
|
+
} finally {
|
|
308
|
+
self.startQueueTimer();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
} //_processQueue()
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
MCProtocol.prototype.dropConnection = function () {
|
|
317
|
+
var self = this;
|
|
318
|
+
outputLog(`dropConnection() called`, "TRACE", self.connectionID);
|
|
319
|
+
if(self.connectionParams.protocol == "UDP"){
|
|
320
|
+
//TODO - implement UDP
|
|
321
|
+
try {
|
|
322
|
+
if(self.netClient){
|
|
323
|
+
self.netClient.close();
|
|
324
|
+
}
|
|
325
|
+
} catch (error) {
|
|
326
|
+
outputLog(`dropConnection() caused an error error: ${error}`, "ERROR", self.connectionID);
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
try {
|
|
330
|
+
if (typeof (self.netClient) !== 'undefined') {
|
|
331
|
+
self.netClient.end();
|
|
332
|
+
}
|
|
333
|
+
} catch (error) {
|
|
334
|
+
outputLog(`dropConnection() caused an error error: ${error}`, "ERROR", self.connectionID);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
self.connectionCleanup();
|
|
338
|
+
self.connected = false;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
MCProtocol.prototype.close = function () {
|
|
342
|
+
this.dropConnection();
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
MCProtocol.prototype.connectNow = function (cParam, suppressCallback) { // TODO - implement or remove suppressCallback
|
|
346
|
+
var self = this;
|
|
347
|
+
|
|
348
|
+
if (self.connectionParams.protocol == "UDP") {
|
|
349
|
+
//TODO - implement UDP
|
|
350
|
+
|
|
351
|
+
// Track the connection state
|
|
352
|
+
self.connectionState = 1 // 1 = trying to connect
|
|
353
|
+
|
|
354
|
+
if (self.netClient) {
|
|
355
|
+
self.connectionState = 0
|
|
356
|
+
self.netClient.removeAllListeners();
|
|
357
|
+
delete self.netClient;
|
|
358
|
+
}
|
|
359
|
+
self.netClient = dgram.createSocket('udp4');
|
|
360
|
+
self.connected = false;
|
|
361
|
+
self.requests = {};
|
|
362
|
+
|
|
363
|
+
function close() {
|
|
364
|
+
self.connectionState = 0;
|
|
365
|
+
self.emit('close');
|
|
366
|
+
self.connected = false;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
// self.netClient.on('listening', function () {
|
|
374
|
+
// self.onUDPConnect.apply(self, arguments);
|
|
375
|
+
// });
|
|
376
|
+
self.netClient.on('close', close);
|
|
377
|
+
|
|
378
|
+
//self.netClient.connect();
|
|
379
|
+
|
|
380
|
+
self.netClient.write = function(buffer){
|
|
381
|
+
self.netClient.send( buffer, 0, buffer.length, cParam.port, cParam.host, function (err) {
|
|
382
|
+
if (err) {
|
|
383
|
+
self.emit('error');//??
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
//{
|
|
390
|
+
outputLog('UDP Connection Setup to ' + cParam.host + ' on port ' + cParam.port, "DEBUG", self.connectionID);
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
self.netClient.removeAllListeners('data');
|
|
394
|
+
self.netClient.removeAllListeners('message');
|
|
395
|
+
self.netClient.removeAllListeners('error');
|
|
396
|
+
|
|
397
|
+
self.netClient.on('message', function () {
|
|
398
|
+
self.onResponse.apply(self, arguments);
|
|
399
|
+
}); // We need to make sure we don't add this event every time if we call it on data.
|
|
400
|
+
self.netClient.on('error', function () {
|
|
401
|
+
self.readWriteError.apply(self, arguments);
|
|
402
|
+
}); // Might want to remove the connecterror listener
|
|
403
|
+
|
|
404
|
+
self.emit('open');
|
|
405
|
+
if ((!self.connectCBIssued) && (typeof (self.connectCallback) === "function")) {
|
|
406
|
+
self.connectCBIssued = true;
|
|
407
|
+
self.connectCallback();
|
|
408
|
+
}
|
|
409
|
+
//}
|
|
410
|
+
|
|
411
|
+
self.connectionState = 4;
|
|
412
|
+
|
|
413
|
+
} else {
|
|
414
|
+
// Don't re-trigger.
|
|
415
|
+
if (self.connectionState >= 1) { return; }
|
|
416
|
+
self.connectionCleanup();
|
|
417
|
+
self.netClient = net.connect(cParam, function () {
|
|
418
|
+
self.netClient.setKeepAlive(true, 2500); // For reliable unplug detection in most cases - although it takes 10 minutes to notify
|
|
419
|
+
self.onTCPConnect.apply(self, arguments);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
self.connectionState = 1; // 1 = trying to connect
|
|
423
|
+
|
|
424
|
+
self.netClient.on('error', function () {
|
|
425
|
+
self.connectError.apply(self, arguments);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
self.netClient.on('close', function () {
|
|
429
|
+
self.onClientDisconnect.apply(self, arguments);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
outputLog('<initiating a new connection>', "INFO", self.connectionID);
|
|
434
|
+
outputLog('Attempting to connect to host...', "DEBUG", self.connectionID);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
MCProtocol.prototype.connectError = function (e) {
|
|
441
|
+
var self = this;
|
|
442
|
+
self.emit('error',e);
|
|
443
|
+
// Note that a TCP connection timeout error will appear here. An MC connection timeout error is a packet timeout.
|
|
444
|
+
outputLog('We Caught a connect error ' + e.code, "ERROR", self.connectionID);
|
|
445
|
+
if ((!self.connectCBIssued) && (typeof (self.connectCallback) === "function")) {
|
|
446
|
+
self.connectCBIssued = true;
|
|
447
|
+
self.connectCallback(e);
|
|
448
|
+
}
|
|
449
|
+
self.connectionState = 0;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
MCProtocol.prototype.readWriteError = function (e) {
|
|
453
|
+
var self = this;
|
|
454
|
+
outputLog('We Caught a read/write error ' + e.code + ' - resetting connection', "ERROR", self.connectionID);
|
|
455
|
+
self.emit('error', e);
|
|
456
|
+
self.connectionState = 0;
|
|
457
|
+
self.connectionReset();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
MCProtocol.prototype.packetTimeout = function (packetType, packetSeqNum) {
|
|
461
|
+
var self = this;
|
|
462
|
+
outputLog('PacketTimeout called with type ' + packetType + ' and seq ' + packetSeqNum, "WARN", self.connectionID);
|
|
463
|
+
if (packetType === "read") {
|
|
464
|
+
outputLog("READ TIMEOUT on sequence number " + packetSeqNum, "WARN", self.connectionID);
|
|
465
|
+
self.readResponse(undefined); //, self.findReadIndexOfSeqNum(packetSeqNum));
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
if (packetType === "write") {
|
|
469
|
+
outputLog("WRITE TIMEOUT on sequence number " + packetSeqNum, "WARN", self.connectionID);
|
|
470
|
+
self.writeResponse(undefined); //, self.findWriteIndexOfSeqNum(packetSeqNum));
|
|
471
|
+
return undefined;
|
|
472
|
+
}
|
|
473
|
+
outputLog("Unknown timeout error. Nothing was done - this shouldn't happen.", "ERROR", self.connectionID);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
MCProtocol.prototype.onTCPConnect = function () {
|
|
477
|
+
var self = this;
|
|
478
|
+
outputLog('TCP Connection Established to ' + self.netClient.remoteAddress + ' on port ' + self.netClient.remotePort, "DEBUG", self.connectionID);
|
|
479
|
+
|
|
480
|
+
// Track the connection state
|
|
481
|
+
self.connectionState = 4; // 4 = all connected, simple with MC protocol. Other protocols have a negotiation/session packet as well.
|
|
482
|
+
|
|
483
|
+
self.netClient.removeAllListeners('data');
|
|
484
|
+
self.netClient.removeAllListeners('message');
|
|
485
|
+
self.netClient.removeAllListeners('error');
|
|
486
|
+
|
|
487
|
+
self.netClient.on('data', function () {
|
|
488
|
+
self.onResponse.apply(self, arguments);
|
|
489
|
+
}); // We need to make sure we don't add this event every time if we call it on data.
|
|
490
|
+
self.netClient.on('error', function () {
|
|
491
|
+
self.readWriteError.apply(self, arguments);
|
|
492
|
+
}); // Might want to remove the connecterror listener
|
|
493
|
+
|
|
494
|
+
self.emit('open');
|
|
495
|
+
if ((!self.connectCBIssued) && (typeof (self.connectCallback) === "function")) {
|
|
496
|
+
self.connectCBIssued = true;
|
|
497
|
+
self.connectCallback();
|
|
498
|
+
}
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
MCProtocol.prototype.onUDPConnect = function () {
|
|
504
|
+
var self = this;
|
|
505
|
+
outputLog('UDP Connection Established to ' + self.netClient.remoteAddress + ' on port ' + self.netClient.remotePort, "DEBUG", self.connectionID);
|
|
506
|
+
|
|
507
|
+
// Track the connection state
|
|
508
|
+
self.connectionState = 4; // 4 = all connected, simple with MC protocol. Other protocols have a negotiation/session packet as well.
|
|
509
|
+
|
|
510
|
+
self.netClient.removeAllListeners('data');
|
|
511
|
+
self.netClient.removeAllListeners('message');
|
|
512
|
+
self.netClient.removeAllListeners('error');
|
|
513
|
+
|
|
514
|
+
self.netClient.on('message', function () {
|
|
515
|
+
self.onResponse.apply(self, arguments);
|
|
516
|
+
}); // We need to make sure we don't add this event every time if we call it on data.
|
|
517
|
+
self.netClient.on('error', function () {
|
|
518
|
+
self.readWriteError.apply(self, arguments);
|
|
519
|
+
}); // Might want to remove the connecterror listener
|
|
520
|
+
|
|
521
|
+
self.emit('open');
|
|
522
|
+
if ((!self.connectCBIssued) && (typeof (self.connectCallback) === "function")) {
|
|
523
|
+
self.connectCBIssued = true;
|
|
524
|
+
self.connectCallback();
|
|
525
|
+
}
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
MCProtocol.prototype.writeItems = function (arg, value, cb) {
|
|
531
|
+
return _writeItems(this, arg, value, cb, false);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function _writeItems(self, arg, value, cb, queuedItem) {
|
|
535
|
+
//var self = this;
|
|
536
|
+
var i;
|
|
537
|
+
var reply = [];
|
|
538
|
+
outputLog("Preparing to WRITE " + arg, "DEBUG", self.connectionID);
|
|
539
|
+
|
|
540
|
+
//ensure arg is an array regardless of count
|
|
541
|
+
let argArr = arg;
|
|
542
|
+
let valueArr = value;
|
|
543
|
+
|
|
544
|
+
if (Array.isArray(arg) != true) {
|
|
545
|
+
argArr = [arg];
|
|
546
|
+
valueArr = [value];
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (self.isWaiting()) {
|
|
550
|
+
|
|
551
|
+
let sendStatus = MCProtocol.prototype.enumSendResult.unknown;
|
|
552
|
+
|
|
553
|
+
if (queuedItem) {
|
|
554
|
+
sendStatus = MCProtocol.prototype.enumSendResult.notSent;
|
|
555
|
+
outputLog(`️🛢️➡🚧 queued writeItem '${arg}' still not sent (isWaiting)`, "DEBUG")
|
|
556
|
+
|
|
557
|
+
} else if (self.queue.length >= self.queueMaxLength) {
|
|
558
|
+
outputLog(`️🛢️➡🗑️ writeItem '${arg}' discarded, queue full`, "WARN")
|
|
559
|
+
sendStatus = MCProtocol.prototype.enumSendResult.queueFull;
|
|
560
|
+
} else {
|
|
561
|
+
sendStatus = MCProtocol.prototype.enumSendResult.queued;
|
|
562
|
+
self.queue.push({
|
|
563
|
+
arg: arg,
|
|
564
|
+
value: value,
|
|
565
|
+
cb: cb,
|
|
566
|
+
fn: "write",
|
|
567
|
+
dt: Date.now()
|
|
568
|
+
});
|
|
569
|
+
outputLog(`️✏️➡🛢️ writeItem '${arg}' pushed to queue`, "DEBUG")
|
|
570
|
+
}
|
|
571
|
+
reply.push({ TAG: arg, sendStatus: sendStatus });//[item.useraddr] = MCProtocol.prototype.enumSendResult.badRequest;
|
|
572
|
+
return reply;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
let plcitems = [];
|
|
576
|
+
for (i = 0; i < argArr.length; i++) {
|
|
577
|
+
if (typeof argArr[i] === "string") {
|
|
578
|
+
let plcitem = new PLCItem(self);
|
|
579
|
+
plcitem.init(self.translationCB(argArr[i]), argArr[i], self.octalInputOutput, self.frame, self.plcType, valueArr[i]);
|
|
580
|
+
plcitem._instance = "original";
|
|
581
|
+
if (Array.isArray(cb))
|
|
582
|
+
plcitem.cb = cb[i];
|
|
583
|
+
else
|
|
584
|
+
plcitem.cb = cb;
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
plcitems.push(plcitem);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
//do callback for non initialised (bad) items
|
|
592
|
+
plcitems.map(function (item) {
|
|
593
|
+
if (item.initialised == false) {
|
|
594
|
+
if (item.cb) {
|
|
595
|
+
var cbd = new PLCWriteResult(item.useraddr, item.addr, MCProtocol.prototype.enumOPCQuality.badDeviceFailure.value, 0);
|
|
596
|
+
cbd.error = item.initError;
|
|
597
|
+
cbd.problem = true;
|
|
598
|
+
item.cb(true, cbd);
|
|
599
|
+
}
|
|
600
|
+
item.extraInfo = item.initError;
|
|
601
|
+
reply.problem = true;
|
|
602
|
+
let r = {
|
|
603
|
+
TAG: item.useraddr,
|
|
604
|
+
sendStatus: MCProtocol.prototype.enumSendResult.badRequest
|
|
605
|
+
};
|
|
606
|
+
reply.push(r);//[item.useraddr] = MCProtocol.prototype.enumSendResult.badRequest;
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
//filter OK items
|
|
611
|
+
var plcitemsInitialised = plcitems.filter(function (item) {
|
|
612
|
+
return item.initialised;
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
var preparedCount = self.prepareWritePacket(plcitemsInitialised);
|
|
616
|
+
|
|
617
|
+
var plcitemsBuffered = plcitemsInitialised.filter(function (item) {
|
|
618
|
+
return item.bufferized;
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
//do callback for items not buffered
|
|
622
|
+
plcitemsInitialised.map(function (item) {
|
|
623
|
+
if (!item.bufferized) {
|
|
624
|
+
if (item.cb) {
|
|
625
|
+
var cbd = new PLCWriteResult(item.useraddr, item.addr, MCProtocol.prototype.enumOPCQuality.bad.value, 0);
|
|
626
|
+
cbd.error = item.lastError;
|
|
627
|
+
cbd.problem = true;
|
|
628
|
+
item.cb(true, cbd);
|
|
629
|
+
}
|
|
630
|
+
item.extraInfo = item.lastError;
|
|
631
|
+
reply.problem = true;
|
|
632
|
+
let r = {
|
|
633
|
+
TAG: item.useraddr,
|
|
634
|
+
sendStatus: MCProtocol.prototype.enumSendResult.badRequest
|
|
635
|
+
};
|
|
636
|
+
reply.push(r);//[item.useraddr] = MCProtocol.prototype.enumSendResult.badRequest;
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
let sentCount = 0;
|
|
641
|
+
if (plcitemsBuffered.length) {
|
|
642
|
+
sentCount = self.sendWritePacket();
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
plcitemsBuffered.map(function (item) {
|
|
646
|
+
let s = sentCount ? MCProtocol.prototype.enumSendResult.sent : MCProtocol.prototype.enumSendResult.notSent;
|
|
647
|
+
if (s != MCProtocol.prototype.enumSendResult.sent) {
|
|
648
|
+
reply.problem = true;
|
|
649
|
+
}
|
|
650
|
+
let r = {
|
|
651
|
+
TAG: item.useraddr,
|
|
652
|
+
sendStatus: s
|
|
653
|
+
};
|
|
654
|
+
reply.push(r);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
return reply;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
MCProtocol.prototype.findItem = function (useraddr) {
|
|
661
|
+
var self = this;
|
|
662
|
+
var i;
|
|
663
|
+
var commstate = { value: self.connectionState !== 4, quality: 'OK' };
|
|
664
|
+
if (useraddr === '_COMMERR') { return commstate; }
|
|
665
|
+
for (i = 0; i < self.polledReadBlockList.length; i++) {
|
|
666
|
+
if (self.polledReadBlockList[i].useraddr === useraddr) { return self.polledReadBlockList[i]; }
|
|
667
|
+
}
|
|
668
|
+
return undefined;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
MCProtocol.prototype.addItems = function (arg, cb) {
|
|
672
|
+
var self = this;
|
|
673
|
+
self.addRemoveArray.push({ arg: arg, cb: cb, action: 'poll' });
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
MCProtocol.prototype.addItemsNow = function (arg, action, cb) {
|
|
677
|
+
var self = this;
|
|
678
|
+
var i;
|
|
679
|
+
outputLog("Adding " + arg, "DEBUG", self.connectionID);
|
|
680
|
+
addItemsFlag = false;
|
|
681
|
+
var addedCount = 0;
|
|
682
|
+
var expectedCount = Array.isArray(arg) ? arg.length : 1;
|
|
683
|
+
if (typeof arg === "string" && arg !== "_COMMERR") {
|
|
684
|
+
//plcitem = stringToMCAddr(self.translationCB(arg), arg, self.octalInputOutput, self.frame, self.plcType);
|
|
685
|
+
let plcitem = new PLCItem(self);
|
|
686
|
+
plcitem.init(self.translationCB(arg), arg, self.octalInputOutput, self.frame, self.plcType, undefined /*not writing*/);
|
|
687
|
+
if (plcitem.initialised) {
|
|
688
|
+
plcitem.action = action;
|
|
689
|
+
plcitem.cb = cb;
|
|
690
|
+
self.polledReadBlockList.push(plcitem);
|
|
691
|
+
addedCount++;
|
|
692
|
+
} else {
|
|
693
|
+
outputLog(`Dropping bad request item '${arg}'`, "WARN");
|
|
694
|
+
}
|
|
695
|
+
} else if (Array.isArray(arg)) {
|
|
696
|
+
for (i = 0; i < arg.length; i++) {
|
|
697
|
+
if (typeof arg[i] === "string" && arg[i] !== "_COMMERR") {
|
|
698
|
+
//plcitem = stringToMCAddr(self.translationCB(arg[i]), arg[i], self.octalInputOutput, self.frame, self.plcType);
|
|
699
|
+
let plcitem = new PLCItem(self);
|
|
700
|
+
plcitem.init(self.translationCB(arg[i]), arg[i], self.octalInputOutput, self.frame, self.plcType, undefined /*not writing*/);
|
|
701
|
+
if (plcitem.initialised) {
|
|
702
|
+
if (Array.isArray(cb))
|
|
703
|
+
plcitem.cb = cb[i];
|
|
704
|
+
else
|
|
705
|
+
plcitem.cb = cb;
|
|
706
|
+
if (Array.isArray(action))
|
|
707
|
+
plcitem.action = action[i];
|
|
708
|
+
else
|
|
709
|
+
plcitem.action = action;
|
|
710
|
+
self.polledReadBlockList.push(plcitem);
|
|
711
|
+
addedCount++;
|
|
712
|
+
} else {
|
|
713
|
+
outputLog(`Dropping bad request item '${arg[i]}'`, "WARN");
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Validity check.
|
|
720
|
+
for (i = self.polledReadBlockList.length - 1; i >= 0; i--) {
|
|
721
|
+
if (self.polledReadBlockList[i] === undefined) {
|
|
722
|
+
self.polledReadBlockList.splice(i, 1);
|
|
723
|
+
outputLog("Dropping an undefined request item.", "WARN");
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
// prepareReadPacket();
|
|
727
|
+
self.readPacketValid = false;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
MCProtocol.prototype.removeItems = function (arg) {
|
|
731
|
+
var self = this;
|
|
732
|
+
self.addRemoveArray.push({ arg: arg, action: 'remove' });
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
MCProtocol.prototype.removeItemsNow = function (arg) {
|
|
736
|
+
var self = this;
|
|
737
|
+
var i;
|
|
738
|
+
self.removeItemsFlag = false;
|
|
739
|
+
if (typeof arg === "undefined") {
|
|
740
|
+
self.polledReadBlockList = [];
|
|
741
|
+
} else if (typeof arg === "string") {
|
|
742
|
+
for (i = 0; i < self.polledReadBlockList.length; i++) {
|
|
743
|
+
outputLog('TCBA ' + self.translationCB(arg), "TRACE");
|
|
744
|
+
if (self.polledReadBlockList[i].addr === self.translationCB(arg)) {
|
|
745
|
+
outputLog('Splicing', "TRACE");
|
|
746
|
+
self.polledReadBlockList.splice(i, 1);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
} else if (Array.isArray(arg)) {
|
|
750
|
+
for (i = 0; i < self.polledReadBlockList.length; i++) {
|
|
751
|
+
for (j = 0; j < arg.length; j++) {
|
|
752
|
+
if (self.polledReadBlockList[i].addr === self.translationCB(arg[j])) {
|
|
753
|
+
self.polledReadBlockList.splice(i, 1);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
self.readPacketValid = false;
|
|
759
|
+
// prepareReadPacket();
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
MCProtocol.prototype.readAllItems = function (arg) {
|
|
763
|
+
var self = this;
|
|
764
|
+
var i;
|
|
765
|
+
|
|
766
|
+
outputLog("Reading All Items (readAllItems was called)", "TRACE", self.connectionID);
|
|
767
|
+
|
|
768
|
+
if (typeof arg === "function") {
|
|
769
|
+
self.readDoneCallback = arg;
|
|
770
|
+
} else {
|
|
771
|
+
self.readDoneCallback = doNothing;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (self.connectionState !== 4) {
|
|
775
|
+
outputLog("Unable to read when not connected. Return bad values.", "WARN", self.connectionID);
|
|
776
|
+
} // For better behaviour when auto-reconnecting - don't return now
|
|
777
|
+
|
|
778
|
+
// 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.
|
|
779
|
+
if (self.isWaiting()) {
|
|
780
|
+
outputLog("Waiting to read for all R/W operations to complete. Will re-trigger readAllItems in 100ms.", "DEBUG");
|
|
781
|
+
setTimeout(function () {
|
|
782
|
+
self.readAllItems.apply(self, arguments);
|
|
783
|
+
}, 100, arg);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Now we check the array of adding and removing things. Only now is it really safe to do this.
|
|
788
|
+
self.addRemoveArray.forEach(function (element) {
|
|
789
|
+
outputLog('Adding or Removing ' + util.format(element), "DEBUG", self.connectionID);
|
|
790
|
+
if (element.action === 'remove') {
|
|
791
|
+
self.removeItemsNow(element.arg);
|
|
792
|
+
}
|
|
793
|
+
if (element.action === 'poll' || element.action === 'read') {
|
|
794
|
+
self.addItemsNow(element.arg, element.action, element.cb);
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
self.addRemoveArray = []; // Clear for next time.
|
|
799
|
+
|
|
800
|
+
if (!self.readPacketValid) {
|
|
801
|
+
self.prepareReadPacket();
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
outputLog("Calling SRP from RAI", "TRACE", self.connectionID);
|
|
805
|
+
self.sendReadPacket(); // Note this sends the first few read packets depending on parallel connection restrictions.
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
MCProtocol.prototype.readItems = function (arg, cb) {
|
|
809
|
+
return _readItems(this, arg, cb, false);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function _readItems(self, arg, cb, queuedItem) {
|
|
813
|
+
//var self = this;
|
|
814
|
+
var i;
|
|
815
|
+
var reply = [];
|
|
816
|
+
outputLog("#readItems() was called)", "TRACE", self.connectionID);
|
|
817
|
+
|
|
818
|
+
//ensure arg is an array regardless of count
|
|
819
|
+
let argArr = arg;
|
|
820
|
+
if (Array.isArray(arg) != true) {
|
|
821
|
+
argArr = [arg];
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (self.connectionState !== 4) {
|
|
825
|
+
outputLog("Unable to read when not connected. Return bad values.", "WARN", self.connectionID);
|
|
826
|
+
//self.queue = [];//empty the queue
|
|
827
|
+
} // For better behaviour when auto-reconnecting - don't return now
|
|
828
|
+
|
|
829
|
+
if (self.isWaiting()) {
|
|
830
|
+
|
|
831
|
+
let sendStatus = MCProtocol.prototype.enumSendResult.unknown;
|
|
832
|
+
|
|
833
|
+
if (queuedItem) {
|
|
834
|
+
sendStatus = MCProtocol.prototype.enumSendResult.notSent;
|
|
835
|
+
outputLog(`️🛢️➡🚧 queued readItem '${arg}' still not sent (isWaiting)`, "DEBUG")
|
|
836
|
+
|
|
837
|
+
} else if (self.queue.length >= self.queueMaxLength) {
|
|
838
|
+
outputLog(`️🛢️➡🗑️ readItem '${arg}' discarded, queue full`, "WARN")
|
|
839
|
+
sendStatus = MCProtocol.prototype.enumSendResult.queueFull;
|
|
840
|
+
} else {
|
|
841
|
+
sendStatus = MCProtocol.prototype.enumSendResult.queued;
|
|
842
|
+
self.queue.push({
|
|
843
|
+
arg: arg,
|
|
844
|
+
cb: cb,
|
|
845
|
+
fn: "read",
|
|
846
|
+
dt: Date.now()
|
|
847
|
+
});
|
|
848
|
+
outputLog(`️📒➡🛢️ readItem '${arg}' pushed to queue`, "DEBUG")
|
|
849
|
+
}
|
|
850
|
+
reply.push({ TAG: arg, sendStatus: sendStatus });//[item.useraddr] = MCProtocol.prototype.enumSendResult.badRequest;
|
|
851
|
+
return reply;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
let plcitems = [];
|
|
855
|
+
for (i = 0; i < argArr.length; i++) {
|
|
856
|
+
if (typeof argArr[i] === "string" && argArr[i] !== "_COMMERR") {
|
|
857
|
+
let plcitem = new PLCItem(self);
|
|
858
|
+
plcitem.init(self.translationCB(argArr[i]), argArr[i], self.octalInputOutput, self.frame, self.plcType, undefined /*not writing*/);
|
|
859
|
+
if (Array.isArray(cb))
|
|
860
|
+
plcitem.cb = cb[i];
|
|
861
|
+
else
|
|
862
|
+
plcitem.cb = cb;
|
|
863
|
+
plcitem.action = 'read';
|
|
864
|
+
plcitems.push(plcitem);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
//do callback for non initialised (bad) items
|
|
869
|
+
plcitems.map(function (item) {
|
|
870
|
+
if (item.initialised == false) {
|
|
871
|
+
var cbd = new PLCReadResult(item.useraddr, item.addr, MCProtocol.prototype.enumOPCQuality.badConfigErrInServer.value, 0, undefined, undefined);
|
|
872
|
+
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");
|
|
873
|
+
item.cb(true, cbd);
|
|
874
|
+
item.extraInfo = item.initError;
|
|
875
|
+
reply.problem = true;
|
|
876
|
+
let r = {
|
|
877
|
+
TAG: item.useraddr,
|
|
878
|
+
sendStatus: MCProtocol.prototype.enumSendResult.badRequest
|
|
879
|
+
};
|
|
880
|
+
reply.push(r);
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
//filter OK items
|
|
885
|
+
var plcitemsInitialised = plcitems.filter(function (item) {
|
|
886
|
+
return item.initialised;
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
//check how many good items - return if none
|
|
890
|
+
if (!plcitemsInitialised.length) {
|
|
891
|
+
outputLog("Nothing to send!", "WARN");
|
|
892
|
+
return reply;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
let pp = self.prepareReadPacket(plcitemsInitialised);
|
|
896
|
+
|
|
897
|
+
outputLog("Calling sendReadPacket()", "TRACE", self.connectionID);
|
|
898
|
+
var sentCount = self.sendReadPacket(); // Note this sends the first few read packets depending on parallel connection restrictions.
|
|
899
|
+
|
|
900
|
+
plcitemsInitialised.map(function (item) {
|
|
901
|
+
let s = sentCount ? MCProtocol.prototype.enumSendResult.sent : MCProtocol.prototype.enumSendResult.notSent;
|
|
902
|
+
if (s != MCProtocol.prototype.enumSendResult.sent) {
|
|
903
|
+
reply.problem = true;
|
|
904
|
+
}
|
|
905
|
+
let r = {
|
|
906
|
+
TAG: item.useraddr,
|
|
907
|
+
sendStatus: s
|
|
908
|
+
};
|
|
909
|
+
reply.push(r);
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
return reply;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
MCProtocol.prototype.isWaiting = function () {
|
|
916
|
+
var self = this;
|
|
917
|
+
return (self.isReading() || self.isWriting());
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
MCProtocol.prototype.isReading = function () {
|
|
921
|
+
return this.readPacketArray.some(function(el){
|
|
922
|
+
return el.sent;
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
MCProtocol.prototype.isWriting = function () {
|
|
927
|
+
return this.writePacketArray.some(function(el){
|
|
928
|
+
return el.sent;
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
MCProtocol.prototype.clearReadPacketTimeouts = function () {
|
|
933
|
+
var self = this;
|
|
934
|
+
clearPacketTimeouts(self.readPacketArray);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
MCProtocol.prototype.clearWritePacketTimeouts = function () {
|
|
938
|
+
var self = this;
|
|
939
|
+
outputLog('Clearing write PacketTimeouts', "DEBUG", self.connectionID);
|
|
940
|
+
// Before we initialize the readPacketArray, we need to loop through all of them and clear timeouts.
|
|
941
|
+
for (i = 0; i < self.writePacketArray.length; i++) {
|
|
942
|
+
clearTimeout(self.writePacketArray[i].timeout);
|
|
943
|
+
self.writePacketArray[i].sent = false;
|
|
944
|
+
self.writePacketArray[i].rcvd = false;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
MCProtocol.prototype.prepareWritePacket = function (itemList) {
|
|
949
|
+
outputLog("#################### prepareWritePacket() ####################", "TRACE");
|
|
950
|
+
|
|
951
|
+
var self = this;
|
|
952
|
+
var requestList = []; // The request list consists of the block list, split into chunks readable by PDU.
|
|
953
|
+
var requestNumber = 0, thisBlock = 0, thisRequest = 0;
|
|
954
|
+
var itemsThisPacket;
|
|
955
|
+
var numItems;
|
|
956
|
+
// Sort the items using the sort function, by type and offset.
|
|
957
|
+
itemList.sort(itemListSorter);
|
|
958
|
+
|
|
959
|
+
// Just exit if there are no items.
|
|
960
|
+
if (itemList.length == 0) {
|
|
961
|
+
return undefined;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
self.globalWriteBlockList = [];
|
|
965
|
+
itemList[0].block = thisBlock;
|
|
966
|
+
|
|
967
|
+
// Just push the items into blocks and figure out the write buffers
|
|
968
|
+
for (i = 0; i < itemList.length; i++) {
|
|
969
|
+
if (itemList[i].prepareWriteData()) {
|
|
970
|
+
itemList[i].writeBuffer._instance = itemList[i]._instance;
|
|
971
|
+
self.globalWriteBlockList.push(itemList[i]); // Remember - by reference.
|
|
972
|
+
var bli = self.globalWriteBlockList[self.globalWriteBlockList.length-1];
|
|
973
|
+
bli.isOptimized = false;
|
|
974
|
+
bli.itemReference = [];
|
|
975
|
+
bli.itemReference.push(itemList[i]);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// Split the blocks into requests, if they're too large.
|
|
980
|
+
for (i = 0; i < self.globalWriteBlockList.length; i++) {
|
|
981
|
+
let block = self.globalWriteBlockList[i];
|
|
982
|
+
var startElement = block.offset;
|
|
983
|
+
var remainingLength = block.byteLengthWrite;
|
|
984
|
+
var remainingTotalArrayLength = block.totalArrayLength;
|
|
985
|
+
var maxByteRequest = block.maxWordLength() * 2;
|
|
986
|
+
var lengthOffset = 0;
|
|
987
|
+
block.partsBufferized = 0;
|
|
988
|
+
|
|
989
|
+
// How many parts?
|
|
990
|
+
block.parts = Math.ceil(block.byteLengthWrite / maxByteRequest);
|
|
991
|
+
outputLog(`globalWriteBlockList[${i}].parts == ${block.parts} for request '${block.useraddr}', .offset (device number) == ${block.offset}, maxByteRequest==${maxByteRequest}`, "DEBUG");
|
|
992
|
+
|
|
993
|
+
block.requestReference = [];
|
|
994
|
+
|
|
995
|
+
// If we need to spread the sending/receiving over multiple packets...
|
|
996
|
+
for (j = 0; j < block.parts; j++) {
|
|
997
|
+
// create a request for a globalWriteBlockList.
|
|
998
|
+
requestList[thisRequest] = block.clone();
|
|
999
|
+
let reqItem = requestList[thisRequest];
|
|
1000
|
+
reqItem._instance = "block clone (request item)";
|
|
1001
|
+
reqItem.part = j+1;
|
|
1002
|
+
//reqItem.updateSeqNum(self.nextSequenceNumber());
|
|
1003
|
+
reqItem.offset = startElement;
|
|
1004
|
+
reqItem.byteLengthWrite = Math.min(maxByteRequest, remainingLength);
|
|
1005
|
+
|
|
1006
|
+
if (reqItem.bitNative) {
|
|
1007
|
+
reqItem.totalArrayLength = Math.min(maxByteRequest * 2, remainingTotalArrayLength, block.totalArrayLength);
|
|
1008
|
+
} else {
|
|
1009
|
+
// I think we should be dividing by dtypelen here
|
|
1010
|
+
reqItem.totalArrayLength = Math.min(maxByteRequest / block.dtypelen, remainingLength / block.dtypelen, block.totalArrayLength);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
remainingTotalArrayLength -= reqItem.totalArrayLength;
|
|
1014
|
+
reqItem.byteLengthWithFill = reqItem.byteLengthWrite;
|
|
1015
|
+
reqItem.writeBuffer = block.writeBuffer.slice(lengthOffset, lengthOffset + reqItem.byteLengthWithFill);
|
|
1016
|
+
reqItem.writeQualityBuffer = block.writeQualityBuffer.slice(lengthOffset, lengthOffset + reqItem.byteLengthWithFill);
|
|
1017
|
+
lengthOffset += reqItem.byteLengthWrite;
|
|
1018
|
+
|
|
1019
|
+
//if we have split a request into parts, then we need to update the device count before creating the comm buffer
|
|
1020
|
+
if (block.parts > 1) {
|
|
1021
|
+
//reqItem.datatype = 'BYTE';//why???
|
|
1022
|
+
//reqItem.dtypelen = 1;//why???
|
|
1023
|
+
if (reqItem.bitNative) {
|
|
1024
|
+
reqItem.arrayLength = reqItem.totalArrayLength;//globalReadBlockList[thisBlock].byteLength;
|
|
1025
|
+
} else {
|
|
1026
|
+
reqItem.arrayLength = reqItem.byteLengthWrite / 2;//globalReadBlockList[thisBlock].byteLength;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
//generate the comm buffer for this part (with new address offset & length etc)
|
|
1030
|
+
reqItem.toBuffer(self.isAscii, self.frame, self.plcType, self.nextSequenceNumber(), self.network, self.PCStation, self.PLCStation, self.PLCModuleNo);
|
|
1031
|
+
block.requestReference.push(reqItem);
|
|
1032
|
+
block.partsBufferized++;
|
|
1033
|
+
|
|
1034
|
+
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");
|
|
1035
|
+
|
|
1036
|
+
remainingLength -= maxByteRequest;
|
|
1037
|
+
if (block.bitNative) {
|
|
1038
|
+
startElement += maxByteRequest * 2;
|
|
1039
|
+
} else {
|
|
1040
|
+
startElement += maxByteRequest / 2;
|
|
1041
|
+
}
|
|
1042
|
+
thisRequest++;
|
|
1043
|
+
}
|
|
1044
|
+
block.bufferized = (block.partsBufferized == block.parts);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
self.clearWritePacketTimeouts();
|
|
1048
|
+
self.writePacketArray = [];
|
|
1049
|
+
|
|
1050
|
+
// Set up the write packet
|
|
1051
|
+
while (requestNumber < requestList.length) {
|
|
1052
|
+
numItems = 0;
|
|
1053
|
+
self.writePacketArray.push(new PLCPacket());
|
|
1054
|
+
var thisPacketNumber = self.writePacketArray.length - 1;
|
|
1055
|
+
self.writePacketArray[thisPacketNumber].itemList = []; // Initialize as array.
|
|
1056
|
+
for (var i = requestNumber; i < requestList.length; i++) {
|
|
1057
|
+
if (numItems == 1) {
|
|
1058
|
+
break; // Used to break when packet was full. Now break when we can't fit this packet in here.
|
|
1059
|
+
}
|
|
1060
|
+
requestNumber++;
|
|
1061
|
+
numItems++;
|
|
1062
|
+
self.writePacketArray[thisPacketNumber].seqNum = requestList[i].seqNum;
|
|
1063
|
+
self.writePacketArray[thisPacketNumber].itemList.push(requestList[i]);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
outputLog("writePacketArray Length = " + self.writePacketArray.length, "DEBUG");
|
|
1067
|
+
return thisRequest; //return count of prepared requests.
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
MCProtocol.prototype.prepareReadPacket = function (items) {
|
|
1071
|
+
outputLog("#################### prepareReadPacket() ####################", "TRACE");
|
|
1072
|
+
|
|
1073
|
+
const fnstarttime = process.hrtime();
|
|
1074
|
+
|
|
1075
|
+
var self = this;
|
|
1076
|
+
var itemList = items || self.polledReadBlockList; // The items are the actual items requested by the user
|
|
1077
|
+
var requestList = []; // The request list consists of the block list, split into chunks readable by PDU.
|
|
1078
|
+
var startOfSlice, endOfSlice, oldEndCoil, demandEndCoil;
|
|
1079
|
+
let blocklist = []; //self.globalReadBlockList
|
|
1080
|
+
|
|
1081
|
+
// Validity check.
|
|
1082
|
+
for (i = itemList.length - 1; i >= 0; i--) {
|
|
1083
|
+
if (itemList[i] === undefined) {
|
|
1084
|
+
itemList.splice(i, 1);
|
|
1085
|
+
outputLog("Dropping an undefined request item.", "WARN", self.connectionID);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Sort the items using the sort function, by type and offset.
|
|
1090
|
+
itemList.sort(itemListSorter);
|
|
1091
|
+
|
|
1092
|
+
// Just exit if there are no items.
|
|
1093
|
+
if (itemList.length == 0) {
|
|
1094
|
+
return undefined;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/* 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
|
|
1098
|
+
|
|
1099
|
+
//in order to optimise, cache read packets - where the items addresses are used to generate a key
|
|
1100
|
+
var packetCacheKey = 'key:';
|
|
1101
|
+
for (const item in itemList) {
|
|
1102
|
+
if (itemList.hasOwnProperty(item)) {
|
|
1103
|
+
packetCacheKey = packetCacheKey + itemList[item].addr + ',';
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
outputLog("packetCacheKey = " + packetCacheKey, 3, self.connectionID);
|
|
1107
|
+
if (!this.readPacketArrayCache)
|
|
1108
|
+
this.readPacketArrayCache = [];
|
|
1109
|
+
|
|
1110
|
+
var cache = this.readPacketArrayCache.find(obj => obj.key == packetCacheKey)
|
|
1111
|
+
if (cache) {
|
|
1112
|
+
outputLog(`------ ⚡⚡ ------ YEY ------ ⚡⚡ ------- Found cache item packetCacheKey:${packetCacheKey} - returning this (nice speed up) -------- ⚡⚡⚡`, 2, self.connectionID);
|
|
1113
|
+
cache.lastUsed = Date.now();
|
|
1114
|
+
self.readPacketArray = cache.packetArray;
|
|
1115
|
+
self.globalReadBlockList = cache.blocklist;
|
|
1116
|
+
self.readPacketValid = true;
|
|
1117
|
+
|
|
1118
|
+
clearPacketTimeouts(self.readPacketArray);
|
|
1119
|
+
// //increment seq number
|
|
1120
|
+
// for (let p in self.readPacketArray) {
|
|
1121
|
+
// if (self.readPacketArray.hasOwnProperty(p)) {
|
|
1122
|
+
// self.readPacketArray[p].seqNum = self.nextSequenceNumber();
|
|
1123
|
+
// }
|
|
1124
|
+
// }
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
let fntimetaken = process.hrtime(fnstarttime);
|
|
1128
|
+
outputLog(`function '${getFuncName()}()' - time taken ${fntimetaken[0] * 1e9 + fntimetaken[1]} nanoseconds ⌛`, 3);
|
|
1129
|
+
|
|
1130
|
+
return cache;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
outputLog("Cache item not found :(. packetCacheKey : " + packetCacheKey, 3, self.connectionID);
|
|
1134
|
+
|
|
1135
|
+
//cache maintenance...
|
|
1136
|
+
//IMPORTANT: each item cached is approx 99kb (well stringified it is anyhow - so keep cache size reasonable)
|
|
1137
|
+
if (this.readPacketArrayCache.length >= 10) {
|
|
1138
|
+
//cleanup - delete oldest
|
|
1139
|
+
this.readPacketArrayCache.sort((a, b) => a.lastUsed < b.lastUsed);
|
|
1140
|
+
let deleteditem = this.readPacketArrayCache.pop();
|
|
1141
|
+
outputLog("--------BOO!------- Cache item clean up (Cache exhausted) - removing item packetCacheKey : " + deleteditem.key + ' ---------cache item removed--- ✨✨✨ ', 3, self.connectionID);
|
|
1142
|
+
}
|
|
1143
|
+
*/
|
|
1144
|
+
|
|
1145
|
+
// ...because you have to start your optimization somewhere.
|
|
1146
|
+
blocklist[0] = itemList[0];
|
|
1147
|
+
blocklist[0].itemReference = [];
|
|
1148
|
+
blocklist[0].itemReference.push(itemList[0]);
|
|
1149
|
+
|
|
1150
|
+
var maxByteRequest, thisBlock = 0;
|
|
1151
|
+
itemList[0].block = thisBlock;
|
|
1152
|
+
|
|
1153
|
+
// Optimize the items into blocks
|
|
1154
|
+
for (i = 1; i < itemList.length; i++) {
|
|
1155
|
+
maxByteRequest = itemList[i].maxWordLength() * 2;
|
|
1156
|
+
|
|
1157
|
+
if ((itemList[i].areaMCCode !== blocklist[thisBlock].areaMCCode) || // Can't optimize between areas
|
|
1158
|
+
(!self.isOptimizableArea(itemList[i].areaMCCode)) || // May as well try to optimize everything.
|
|
1159
|
+
((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.
|
|
1160
|
+
((itemList[i].offset - (blocklist[thisBlock].offset + blocklist[thisBlock].byteLength) > self.maxGap) && !itemList[i].bitNative) ||
|
|
1161
|
+
((itemList[i].offset - (blocklist[thisBlock].offset + blocklist[thisBlock].byteLength) > self.maxGap * 8) && itemList[i].bitNative)) { // If our gap is large, create a new block.
|
|
1162
|
+
// At this point we give up and create a new block.
|
|
1163
|
+
thisBlock = thisBlock + 1;
|
|
1164
|
+
blocklist[thisBlock] = itemList[i]; // By reference.
|
|
1165
|
+
// itemList[i].block = thisBlock; // Don't need to do this.
|
|
1166
|
+
blocklist[thisBlock].isOptimized = false;
|
|
1167
|
+
blocklist[thisBlock].itemReference = [];
|
|
1168
|
+
blocklist[thisBlock].itemReference.push(itemList[i]);
|
|
1169
|
+
// outputLog("Not optimizing.");
|
|
1170
|
+
} else {
|
|
1171
|
+
outputLog("Performing optimization of item " + itemList[i].addr + " with " + blocklist[thisBlock].addr, "DEBUG");
|
|
1172
|
+
// This next line checks the maximum.
|
|
1173
|
+
// Think of this situation - we have a large request of 40 bytes starting at byte 10.
|
|
1174
|
+
// Then someone else wants one byte starting at byte 12. The block length doesn't change.
|
|
1175
|
+
//
|
|
1176
|
+
// 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.
|
|
1177
|
+
|
|
1178
|
+
if (itemList[i].bitNative) { // Coils and inputs must be special-cased
|
|
1179
|
+
blocklist[thisBlock].byteLength =
|
|
1180
|
+
Math.max(
|
|
1181
|
+
blocklist[thisBlock].byteLength,
|
|
1182
|
+
(Math.floor((itemList[i].requestOffset - blocklist[thisBlock].requestOffset) / 8) + itemList[i].byteLength)
|
|
1183
|
+
);
|
|
1184
|
+
if (blocklist[thisBlock].byteLength % 2) { // shouldn't be necessary
|
|
1185
|
+
blocklist[thisBlock].byteLength += 1;
|
|
1186
|
+
}
|
|
1187
|
+
} else {
|
|
1188
|
+
blocklist[thisBlock].byteLength =
|
|
1189
|
+
Math.max(
|
|
1190
|
+
blocklist[thisBlock].byteLength,
|
|
1191
|
+
((itemList[i].offset - blocklist[thisBlock].offset) * 2 + Math.ceil(itemList[i].byteLength / itemList[i].multidtypelen)) * itemList[i].multidtypelen
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
outputLog("Optimized byte length is now " + blocklist[thisBlock].byteLength, "DEBUG");
|
|
1195
|
+
|
|
1196
|
+
// Point the buffers (byte and quality) to a sliced version of the optimized block. This is by reference (same area of memory)
|
|
1197
|
+
if (itemList[i].bitNative) { // Again a special case.
|
|
1198
|
+
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;
|
|
1199
|
+
} else {
|
|
1200
|
+
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;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
endOfSlice = startOfSlice + itemList[i].byteLength;
|
|
1204
|
+
itemList[i].byteBuffer = blocklist[thisBlock].byteBuffer.slice(startOfSlice, endOfSlice);
|
|
1205
|
+
itemList[i].qualityBuffer = blocklist[thisBlock].qualityBuffer.slice(startOfSlice, endOfSlice);
|
|
1206
|
+
|
|
1207
|
+
// For now, change the request type here, and fill in some other things.
|
|
1208
|
+
|
|
1209
|
+
// I am not sure we want to do these next two steps.
|
|
1210
|
+
// It seems like things get screwed up when we do this.
|
|
1211
|
+
// Since globalReadBlockList[thisBlock] exists already at this point, and our buffer is already set, let's not do this now.
|
|
1212
|
+
// globalReadBlockList[thisBlock].datatype = 'BYTE';
|
|
1213
|
+
// globalReadBlockList[thisBlock].dtypelen = 1;
|
|
1214
|
+
blocklist[thisBlock].isOptimized = true;
|
|
1215
|
+
blocklist[thisBlock].itemReference.push(itemList[i]);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
var thisRequest = 0;
|
|
1220
|
+
|
|
1221
|
+
// Split the blocks into requests, if they're too large.
|
|
1222
|
+
for (i = 0; i < blocklist.length; i++) {
|
|
1223
|
+
// Always create a request for a globalReadBlockList.
|
|
1224
|
+
let blockListItem = blocklist[i];
|
|
1225
|
+
// How many parts?
|
|
1226
|
+
maxByteRequest = blockListItem.maxWordLength() * 2;
|
|
1227
|
+
blockListItem.parts = Math.ceil(blockListItem.byteLength / maxByteRequest);
|
|
1228
|
+
var startElement = blockListItem.requestOffset; // try to ignore the offset
|
|
1229
|
+
var remainingLength = blockListItem.byteLength;
|
|
1230
|
+
var remainingTotalArrayLength = blockListItem.totalArrayLength;
|
|
1231
|
+
|
|
1232
|
+
//initialise the buffers
|
|
1233
|
+
blockListItem.byteBuffer.fill(0)// = new Buffer(blockListItem.byteLength);
|
|
1234
|
+
blockListItem.qualityBuffer.fill(0)// = new Buffer(blockListItem.byteLength);
|
|
1235
|
+
|
|
1236
|
+
blockListItem.requestReference = [];
|
|
1237
|
+
|
|
1238
|
+
// If we need to spread the sending/receiving over multiple packets...
|
|
1239
|
+
for (j = 0; j < blockListItem.parts; j++) {
|
|
1240
|
+
requestList[thisRequest] = blockListItem.clone();
|
|
1241
|
+
let thisReqItem = requestList[thisRequest];
|
|
1242
|
+
thisReqItem._instance = "block clone (request item)";
|
|
1243
|
+
|
|
1244
|
+
blockListItem.requestReference.push(thisReqItem);
|
|
1245
|
+
thisReqItem.requestOffset = startElement;
|
|
1246
|
+
thisReqItem.byteLength = Math.min(maxByteRequest, remainingLength);
|
|
1247
|
+
if (thisReqItem.bitNative) {
|
|
1248
|
+
thisReqItem.totalArrayLength = Math.min(maxByteRequest * 8, remainingLength * 8, blockListItem.totalArrayLength);
|
|
1249
|
+
} else {
|
|
1250
|
+
thisReqItem.totalArrayLength = Math.min(maxByteRequest / blockListItem.dtypelen, remainingLength / blockListItem.dtypelen, blockListItem.totalArrayLength);
|
|
1251
|
+
}
|
|
1252
|
+
thisReqItem.byteLengthWithFill = thisReqItem.byteLength;
|
|
1253
|
+
if (thisReqItem.byteLengthWithFill % 2) { thisReqItem.byteLengthWithFill += 1; };
|
|
1254
|
+
// Just for now... I am not sure if we really want to do this in this case.
|
|
1255
|
+
if (blockListItem.parts > 1) {
|
|
1256
|
+
thisReqItem.datatype = 'BYTE';
|
|
1257
|
+
thisReqItem.dtypelen = 1;
|
|
1258
|
+
if (thisReqItem.bitNative) {
|
|
1259
|
+
thisReqItem.arrayLength = thisReqItem.totalArrayLength;//globalReadBlockList[thisBlock].byteLength;
|
|
1260
|
+
} else {
|
|
1261
|
+
thisReqItem.arrayLength = thisReqItem.byteLength / 2;//globalReadBlockList[thisBlock].byteLength;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
outputLog(`Created block part (reqItem) ${j+1} of ${blockListItem.parts} for request '${blockListItem.useraddr}', .offset (device number) == ${thisReqItem.offset}, byteLength==${thisReqItem.byteLength}`, "DEBUG");
|
|
1265
|
+
|
|
1266
|
+
remainingLength -= maxByteRequest;
|
|
1267
|
+
if (blockListItem.bitNative) {
|
|
1268
|
+
// startElement += maxByteRequest/thisReqItem.multidtypelen;
|
|
1269
|
+
startElement += maxByteRequest * 8;
|
|
1270
|
+
} else {
|
|
1271
|
+
startElement += maxByteRequest / 2;
|
|
1272
|
+
}
|
|
1273
|
+
thisRequest++;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// The packetizer...
|
|
1280
|
+
var requestNumber = 0;
|
|
1281
|
+
var itemsThisPacket;
|
|
1282
|
+
|
|
1283
|
+
self.clearReadPacketTimeouts();
|
|
1284
|
+
let packetArray = [];
|
|
1285
|
+
packetArray = [];
|
|
1286
|
+
|
|
1287
|
+
|
|
1288
|
+
while (requestNumber < requestList.length) {
|
|
1289
|
+
// Set up the read packet
|
|
1290
|
+
|
|
1291
|
+
var numItems = 0;
|
|
1292
|
+
|
|
1293
|
+
packetArray.push(new PLCPacket());
|
|
1294
|
+
var thisPacketNumber = packetArray.length - 1;
|
|
1295
|
+
//packetArray[thisPacketNumber].seqNum = self.nextSequenceNumber();
|
|
1296
|
+
packetArray[thisPacketNumber].itemList = []; // Initialize as array.
|
|
1297
|
+
|
|
1298
|
+
for (var i = requestNumber; i < requestList.length; i++) {
|
|
1299
|
+
if (numItems >= 1) {
|
|
1300
|
+
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.
|
|
1301
|
+
}
|
|
1302
|
+
requestNumber++;
|
|
1303
|
+
numItems++;
|
|
1304
|
+
packetArray[thisPacketNumber].itemList.push(requestList[i]);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
self.readPacketArray = packetArray;
|
|
1308
|
+
self.globalReadBlockList = blocklist;
|
|
1309
|
+
self.readPacketValid = true;
|
|
1310
|
+
let rpa = {
|
|
1311
|
+
lastUsed: Date.now(),
|
|
1312
|
+
/* DISABLED cache for now key: packetCacheKey, */
|
|
1313
|
+
blocklist: blocklist,
|
|
1314
|
+
packetArray: packetArray,
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
let fntimetaken = process.hrtime(fnstarttime);
|
|
1318
|
+
outputLog(`function '${getFuncName()}()' - time taken ${fntimetaken[0] * 1e9 + fntimetaken[1]} nanoseconds ⌛`, "TRACE");
|
|
1319
|
+
/* DISABLED cache for now
|
|
1320
|
+
outputLog("------------------ that took aaaaaggggeeeeessss adding packetCacheKey : " + packetCacheKey + ' to the cache for optimisation --------------- 🏺🏺🏺', 3, self.connectionID);
|
|
1321
|
+
self.readPacketArrayCache.push(rpa);
|
|
1322
|
+
*/
|
|
1323
|
+
|
|
1324
|
+
return rpa;
|
|
1325
|
+
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
function clearPacketTimeouts(packetArray) {
|
|
1329
|
+
outputLog('Clearing read PacketTimeouts', 3);
|
|
1330
|
+
// Before we initialize the readPacketArray, we need to loop through all of them and clear timeouts.
|
|
1331
|
+
for (i = 0; i < packetArray.length; i++) {
|
|
1332
|
+
clearTimeout(packetArray[i].timeout);
|
|
1333
|
+
packetArray[i].sent = false;
|
|
1334
|
+
packetArray[i].rcvd = false;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
function getFuncName() {
|
|
1339
|
+
return getFuncName.caller.name
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
MCProtocol.prototype.sendReadPacket = function (arg) {
|
|
1343
|
+
var self = this;
|
|
1344
|
+
var i, j, curLength, returnedBfr, routerLength;
|
|
1345
|
+
var flagReconnect = false;
|
|
1346
|
+
var sentCount = 0;
|
|
1347
|
+
outputLog("#################### sendReadPacket() ####################", 3, self.connectionID);
|
|
1348
|
+
|
|
1349
|
+
for (i = 0; i < self.readPacketArray.length; i++) {
|
|
1350
|
+
let readPacket = self.readPacketArray[i];
|
|
1351
|
+
if (readPacket.sent) { continue; }
|
|
1352
|
+
if (self.parallelJobsNow >= self.maxParallel) { continue; }
|
|
1353
|
+
// From here down is SENDING the packet
|
|
1354
|
+
readPacket.reqTime = process.hrtime();
|
|
1355
|
+
|
|
1356
|
+
curLength = 0;
|
|
1357
|
+
routerLength = 0;
|
|
1358
|
+
|
|
1359
|
+
// The FOR loop is left in here for now, but really we are only doing one request per packet for now.
|
|
1360
|
+
for (j = 0; j < readPacket.itemList.length; j++) {
|
|
1361
|
+
var item = readPacket.itemList[j];
|
|
1362
|
+
item.sendTime = Date.now();
|
|
1363
|
+
readPacket.seqNum = self.nextSequenceNumber();// readPacket.seqNum;
|
|
1364
|
+
item.toBuffer(self.isAscii, self.frame, self.plcType, readPacket.seqNum, self.network, self.PCStation, self.PLCStation, self.PLCModuleNo);
|
|
1365
|
+
returnedBfr = item.buffer.data;
|
|
1366
|
+
returnedBfr.copy(self.readReq, curLength);
|
|
1367
|
+
curLength += returnedBfr.length;
|
|
1368
|
+
outputLog(`The buffer.length of read item '${item.useraddr} [offset:${item.offset}] (seqNum:${item.seqNum})' is '${returnedBfr.length}'...`, "DEBUG");
|
|
1369
|
+
outputLog(returnedBfr, "DEBUG");
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
outputLog("The final send buffer is...", "DEBUG");
|
|
1373
|
+
var sendBuffer = self.readReq.slice(0, curLength);
|
|
1374
|
+
if (self.isAscii) {
|
|
1375
|
+
outputLog(asciize(sendBuffer), "DEBUG");
|
|
1376
|
+
outputLog(binarize(asciize(sendBuffer)), "DEBUG");
|
|
1377
|
+
} else {
|
|
1378
|
+
outputLog(sendBuffer, "DEBUG");
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
if (self.connectionState == 4) {
|
|
1382
|
+
readPacket.timeout = setTimeout(function () {
|
|
1383
|
+
self.packetTimeout.apply(self, arguments);
|
|
1384
|
+
},
|
|
1385
|
+
self.globalTimeout, "read", readPacket.seqNum);
|
|
1386
|
+
if (self.isAscii) {
|
|
1387
|
+
self.netClient.write(asciize(sendBuffer));
|
|
1388
|
+
} else {
|
|
1389
|
+
self.netClient.write(sendBuffer); // was 31
|
|
1390
|
+
}
|
|
1391
|
+
self.lastPacketSent = readPacket;
|
|
1392
|
+
self.lastPacketSent.isWritePacket = false;
|
|
1393
|
+
self.lastPacketSent.isReadPacket = true;
|
|
1394
|
+
sentCount++;
|
|
1395
|
+
readPacket.sent = true;
|
|
1396
|
+
readPacket.rcvd = false;
|
|
1397
|
+
readPacket.timeoutError = false;
|
|
1398
|
+
self.parallelJobsNow += 1;
|
|
1399
|
+
|
|
1400
|
+
outputLog('Sent Read Packet SEQ ' + readPacket.seqNum, "DEBUG");
|
|
1401
|
+
} else {
|
|
1402
|
+
// outputLog('Somehow got into read block without proper connectionState of 4. Disconnect.');
|
|
1403
|
+
// connectionReset();
|
|
1404
|
+
// setTimeout(connectNow, 2000, connectionParams);
|
|
1405
|
+
// Note we aren't incrementing maxParallel so we are actually going to time out on all our packets all at once.
|
|
1406
|
+
readPacket.sent = true;
|
|
1407
|
+
readPacket.rcvd = false;
|
|
1408
|
+
readPacket.timeoutError = true;
|
|
1409
|
+
if (!flagReconnect) {
|
|
1410
|
+
// Prevent duplicates
|
|
1411
|
+
outputLog(`Not Sending Read Packet (seqNum==${readPacket.seqNum}) because we are not connected - connection state is ${self.connectionState}`, 0, self.connectionID);
|
|
1412
|
+
}
|
|
1413
|
+
// This is essentially an instantTimeout.
|
|
1414
|
+
if (self.connectionState == 0) {
|
|
1415
|
+
flagReconnect = true;
|
|
1416
|
+
}
|
|
1417
|
+
outputLog('Requesting PacketTimeout Due to connection state != 4. readPacket SeqNum == ' + readPacket.seqNum, 1, self.connectionID);
|
|
1418
|
+
readPacket.timeout = setTimeout(function () {
|
|
1419
|
+
self.packetTimeout.apply(self, arguments);
|
|
1420
|
+
}, 0, "read", readPacket.seqNum);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
if (flagReconnect) {
|
|
1425
|
+
setTimeout(function () {
|
|
1426
|
+
outputLog("The scheduled reconnect from sendReadPacket is happening now", 1, self.connectionID);
|
|
1427
|
+
self.connectNow(self.connectionParams); // We used to do this NOW - not NextTick() as we need to mark connectionState as 1 right now. Otherwise we queue up LOTS of connects and crash.
|
|
1428
|
+
}, 0);
|
|
1429
|
+
}
|
|
1430
|
+
return sentCount;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
MCProtocol.prototype.sendWritePacket = function () {
|
|
1434
|
+
var self = this;
|
|
1435
|
+
var curLength, flagReconnect = false;
|
|
1436
|
+
var sentCount = 0
|
|
1437
|
+
outputLog("#################### sendWritePacket() ####################", 3, self.connectionID);
|
|
1438
|
+
|
|
1439
|
+
//self.writeInQueue = false;
|
|
1440
|
+
|
|
1441
|
+
for (i = 0; i < self.writePacketArray.length; i++) {
|
|
1442
|
+
let writePacket = self.writePacketArray[i];
|
|
1443
|
+
if (writePacket.sent) { continue; }
|
|
1444
|
+
if (self.parallelJobsNow >= self.maxParallel) { continue; }
|
|
1445
|
+
// From here down is SENDING the packet
|
|
1446
|
+
writePacket.reqTime = process.hrtime();
|
|
1447
|
+
|
|
1448
|
+
curLength = 0;
|
|
1449
|
+
// With MC we generate the simple header inside the packet generator as well
|
|
1450
|
+
for (var j = 0; j < writePacket.itemList.length; j++) {
|
|
1451
|
+
var seqNum = writePacket.seqNum;
|
|
1452
|
+
var item = writePacket.itemList[j];
|
|
1453
|
+
item.sendTime = Date.now();
|
|
1454
|
+
item.buffer.data.copy(self.writeReq, curLength);
|
|
1455
|
+
curLength += item.buffer.data.length;
|
|
1456
|
+
//debug point
|
|
1457
|
+
// if(item.useraddr == "D2000,1000"){
|
|
1458
|
+
// curLength = curLength;
|
|
1459
|
+
// }
|
|
1460
|
+
outputLog(`The buffer.length of write item '${item.useraddr} [offset:${item.offset}] (seqNum:${item.seqNum})' is '${item.buffer.data.length}'...`, "DEBUG");
|
|
1461
|
+
outputLog(item.buffer.data, "DEBUG");
|
|
1462
|
+
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
|
|
1466
|
+
if (self.connectionState === 4) {
|
|
1467
|
+
writePacket.timeout = setTimeout(function () {
|
|
1468
|
+
self.packetTimeout.apply(self, arguments);
|
|
1469
|
+
}, self.globalTimeout, "write", writePacket.seqNum);
|
|
1470
|
+
outputLog("Actual Send Packet:", "DEBUG");
|
|
1471
|
+
outputLog(self.writeReq.slice(0, curLength), "DEBUG");
|
|
1472
|
+
if (self.isAscii) {
|
|
1473
|
+
self.netClient.write(asciize(self.writeReq.slice(0, curLength))); // was 31
|
|
1474
|
+
sentCount++;
|
|
1475
|
+
} else {
|
|
1476
|
+
self.netClient.write(self.writeReq.slice(0, curLength)); // was 31
|
|
1477
|
+
sentCount++;
|
|
1478
|
+
}
|
|
1479
|
+
self.lastPacketSent = writePacket;
|
|
1480
|
+
self.lastPacketSent.isWritePacket = true;
|
|
1481
|
+
self.lastPacketSent.isReadPacket = false;
|
|
1482
|
+
writePacket.sent = true;
|
|
1483
|
+
writePacket.rcvd = false;
|
|
1484
|
+
writePacket.timeoutError = false;
|
|
1485
|
+
self.parallelJobsNow += 1;
|
|
1486
|
+
outputLog('Sent Write Packet With Sequence Number ' + writePacket.seqNum, "DEBUG", self.connectionID);
|
|
1487
|
+
} else {
|
|
1488
|
+
// This is essentially an instantTimeout.
|
|
1489
|
+
writePacket.sent = true;
|
|
1490
|
+
writePacket.rcvd = false;
|
|
1491
|
+
writePacket.timeoutError = true;
|
|
1492
|
+
|
|
1493
|
+
// Without the scopePlaceholder, this doesn't work. writePacketArray[i] becomes undefined.
|
|
1494
|
+
// The reason is that the value i is part of a closure and when seen "nextTick" has the same value
|
|
1495
|
+
// it would have just after the FOR loop is done.
|
|
1496
|
+
// (The FOR statement will increment it to beyond the array, then exit after the condition fails)
|
|
1497
|
+
// scopePlaceholder works as the array is de-referenced NOW, not "nextTick".
|
|
1498
|
+
var scopePlaceholder = writePacket.seqNum;
|
|
1499
|
+
process.nextTick(function () {
|
|
1500
|
+
self.packetTimeout("write", scopePlaceholder);
|
|
1501
|
+
});
|
|
1502
|
+
if (self.connectionState == 0) {
|
|
1503
|
+
flagReconnect = true;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
if (flagReconnect) {
|
|
1508
|
+
setTimeout(function () {
|
|
1509
|
+
outputLog("The scheduled reconnect from sendWritePacket is happening now", "DEBUG", self.connectionID);
|
|
1510
|
+
self.connectNow(self.connectionParams); // We used to do this NOW - not NextTick() as we need to mark connectionState as 1 right now. Otherwise we queue up LOTS of connects and crash.
|
|
1511
|
+
}, 0);
|
|
1512
|
+
}
|
|
1513
|
+
return sentCount;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
MCProtocol.prototype.isOptimizableArea = function (area) {
|
|
1517
|
+
var self = this;
|
|
1518
|
+
// For MC protocol always say yes.
|
|
1519
|
+
if (self.doNotOptimize) { return false; } // Are we skipping all optimization due to user request?
|
|
1520
|
+
|
|
1521
|
+
return true;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
MCProtocol.prototype.onResponse = function (rawdata, rinfo) {
|
|
1525
|
+
var self = this;
|
|
1526
|
+
var isReadResponse, isWriteResponse, data;
|
|
1527
|
+
// Packet Validity Check.
|
|
1528
|
+
|
|
1529
|
+
if (!self.isAscii) {
|
|
1530
|
+
data = rawdata;
|
|
1531
|
+
} else {
|
|
1532
|
+
data = binarize(rawdata);
|
|
1533
|
+
if (typeof (data) === 'undefined') {
|
|
1534
|
+
outputLog('Failed ASCII conversion to binary on reply. Ignoring packet.', "WARN");
|
|
1535
|
+
outputLog(data, 0);
|
|
1536
|
+
return null;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
outputLog("onResponse called with length " + data.length, "TRACE");
|
|
1541
|
+
|
|
1542
|
+
if (data.length < 2) {
|
|
1543
|
+
outputLog('DATA LESS THAN 2 BYTES RECEIVED. NO PROCESSING WILL OCCUR - CONNECTION RESET.', "WARN");
|
|
1544
|
+
outputLog(data, 0);
|
|
1545
|
+
self.connectionReset();
|
|
1546
|
+
return null;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
outputLog('Valid MC Response Received (not yet checked for error)', "DEBUG");
|
|
1550
|
+
|
|
1551
|
+
// Log the receive
|
|
1552
|
+
outputLog('Received ' + data.length + ' bytes of data from PLC.', "DEBUG");
|
|
1553
|
+
outputLog(data, "DEBUG");
|
|
1554
|
+
|
|
1555
|
+
//1E / 3E frame...
|
|
1556
|
+
// On a lot of other industrial protocols the sequence number is coded as part of the
|
|
1557
|
+
// packet and read in the response which is used as a check.
|
|
1558
|
+
// On the MC protocol, we can't do that - so we need to either give up on tracking sequence
|
|
1559
|
+
// numbers (this is what we've done) or fake sequence numbers (adds code complexity for no perceived benefit)
|
|
1560
|
+
let _isReading = self.isReading();
|
|
1561
|
+
let _isWriting = self.isWriting();
|
|
1562
|
+
let rh = decodeResponseHeader(self,data)
|
|
1563
|
+
|
|
1564
|
+
//TODO: Consider data having multiple responses (e.g. 4E send multiple commands with seq - multiple responses may arrive in same packet)
|
|
1565
|
+
|
|
1566
|
+
if(!_isReading && !_isWriting){
|
|
1567
|
+
outputLog("Unexpected data received " + JSON.stringify(data) + " dropping packet! ", "WARN");
|
|
1568
|
+
outputLog(rh,"TRACE");
|
|
1569
|
+
return null;
|
|
1570
|
+
}
|
|
1571
|
+
if(self.frame != "4E"){
|
|
1572
|
+
//1E and 3E dont have seq number. Ideally for 4E frame, we would send all frames (upto buffer capability) & match up the responses to the sequence numbers
|
|
1573
|
+
rh.seqNum = self.lastPacketSent.seqNum;
|
|
1574
|
+
}
|
|
1575
|
+
if(rh.valid == false && rh.lastError) {
|
|
1576
|
+
outputLog(rh.lastError + " - dropping packet", "ERROR");
|
|
1577
|
+
outputLog(rh,"TRACE");
|
|
1578
|
+
return null;
|
|
1579
|
+
}
|
|
1580
|
+
if(rh.seqNum != self.lastPacketSent.seqNum){
|
|
1581
|
+
outputLog(`Unexpected response. Expected sequence number ${self.lastPacketSent.seqNum}, received ${rh.seqNum} - dropping packet`, "WARN");
|
|
1582
|
+
outputLog(rh,"TRACE");
|
|
1583
|
+
return null;
|
|
1584
|
+
}
|
|
1585
|
+
if(rh.valid){
|
|
1586
|
+
if (_isReading) {
|
|
1587
|
+
isReadResponse = true;
|
|
1588
|
+
outputLog("Received Read Response", "DEBUG");
|
|
1589
|
+
self.readResponse(data, rh);
|
|
1590
|
+
}
|
|
1591
|
+
if (_isWriting) {
|
|
1592
|
+
isWriteResponse = true;
|
|
1593
|
+
outputLog("Received Write Response", "DEBUG");
|
|
1594
|
+
self.writeResponse(data, rh);
|
|
1595
|
+
}
|
|
1596
|
+
} else {
|
|
1597
|
+
outputLog("Response is not valid - dropping", "WARN");
|
|
1598
|
+
outputLog(rh,"TRACE");
|
|
1599
|
+
return null;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
MCProtocol.prototype.writeResponse = function (data, decodedHeader) {
|
|
1604
|
+
var self = this;
|
|
1605
|
+
var dataPointer = 2, i, sentPacketNum;
|
|
1606
|
+
|
|
1607
|
+
for (packetCounter = 0; packetCounter < self.writePacketArray.length; packetCounter++) {
|
|
1608
|
+
if (self.writePacketArray[packetCounter].sent && !(self.writePacketArray[packetCounter].rcvd)) {
|
|
1609
|
+
sentPacketNum = packetCounter;
|
|
1610
|
+
break; // Done with the FOR loop
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
if (typeof (sentPacketNum) === 'undefined') {
|
|
1615
|
+
outputLog('WARNING: Received a write packet when none marked as sent', "WARN", self.connectionID);
|
|
1616
|
+
return null;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
if (self.writePacketArray[sentPacketNum].rcvd) {
|
|
1620
|
+
outputLog('WARNING: Received a write packet that was already marked as received', "WARN", self.connectionID);
|
|
1621
|
+
return null;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
for (itemCount = 0; itemCount < self.writePacketArray[sentPacketNum].itemList.length; itemCount++) {
|
|
1625
|
+
//dataPointer = processMBWriteItemORIG(data, self.writePacketArray[sentPacketNum].itemList[itemCount], dataPointer, self.frame);
|
|
1626
|
+
dataPointer = processMBWriteItem(decodedHeader, self.writePacketArray[sentPacketNum].itemList[itemCount], dataPointer, self.frame);
|
|
1627
|
+
if (!dataPointer) {
|
|
1628
|
+
outputLog('Stopping Processing Write Response Packet due to unrecoverable packet error', "ERROR");
|
|
1629
|
+
break;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// Make a note of the time it took the PLC to process the request.
|
|
1634
|
+
self.writePacketArray[sentPacketNum].reqTime = process.hrtime(self.writePacketArray[sentPacketNum].reqTime);
|
|
1635
|
+
let wtms = (self.writePacketArray[sentPacketNum].reqTime[0] * 1000) + Math.round(self.writePacketArray[sentPacketNum].reqTime[1] * 10 / 1e6) / 10;
|
|
1636
|
+
outputLog('Write Time is ' + wtms + 'ms.', "TRACE");
|
|
1637
|
+
|
|
1638
|
+
if (!self.writePacketArray[sentPacketNum].rcvd) {
|
|
1639
|
+
self.writePacketArray[sentPacketNum].rcvd = true;
|
|
1640
|
+
self.parallelJobsNow--;
|
|
1641
|
+
}
|
|
1642
|
+
clearTimeout(self.writePacketArray[sentPacketNum].timeout);
|
|
1643
|
+
|
|
1644
|
+
if (!self.writePacketArray.every(doneSending)) {
|
|
1645
|
+
outputLog("writePacketArray not done sending - calling sendWritePacket() again", "WARN");
|
|
1646
|
+
self.sendWritePacket();
|
|
1647
|
+
} else {
|
|
1648
|
+
for (i = 0; i < self.writePacketArray.length; i++) {
|
|
1649
|
+
self.writePacketArray[i].sent = false;
|
|
1650
|
+
self.writePacketArray[i].rcvd = false;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
anyBadQualities = false;
|
|
1654
|
+
let results = {};//items to send to callback
|
|
1655
|
+
|
|
1656
|
+
for (i = 0; i < self.globalWriteBlockList.length; i++) {
|
|
1657
|
+
// Post-process the write code and apply the quality.
|
|
1658
|
+
// Loop through the global block list...
|
|
1659
|
+
let theItem = self.globalWriteBlockList[i];
|
|
1660
|
+
writePostProcess(theItem);
|
|
1661
|
+
if (!isQualityOK(theItem.writeQuality)) {
|
|
1662
|
+
anyBadQualities = true;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
for (i = 0; i < self.globalWriteBlockList.length; i++) {
|
|
1666
|
+
// Post-Post-process the write code, colate results and send callbacks
|
|
1667
|
+
// Loop through the global block list...
|
|
1668
|
+
let theItem = self.globalWriteBlockList[i];
|
|
1669
|
+
var ttms = theItem.recvTime - theItem.initTime;
|
|
1670
|
+
let result = new PLCWriteResult(theItem.useraddr, theItem.addr, theItem.writeQuality, ttms);
|
|
1671
|
+
result.deviceCode = theItem.deviceCode;
|
|
1672
|
+
result.deviceCodeNotation = theItem.deviceCodeSpec.notation;
|
|
1673
|
+
result.deviceCodeType = theItem.deviceCodeSpec.type;
|
|
1674
|
+
result.digitSpec = theItem.digitSpec;
|
|
1675
|
+
result.dataType = theItem.datatype;
|
|
1676
|
+
result.dataTypeByteLength = theItem.dtypelen;
|
|
1677
|
+
result.deviceNo = theItem.offset;
|
|
1678
|
+
result.bitOffset = theItem.bitOffset;
|
|
1679
|
+
results[theItem.useraddr] = result;
|
|
1680
|
+
|
|
1681
|
+
if (typeof (theItem.cb) === 'function') {
|
|
1682
|
+
outputLog("Now calling back item.cb().", "DEBUG", self.connectionID);
|
|
1683
|
+
outputLog(util.format(result), "DEBUG");
|
|
1684
|
+
theItem.cb(!result.isGood, result)
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
self.emit('message',anyBadQualities, results);
|
|
1688
|
+
if (typeof (self.writeDoneCallback) === 'function') {
|
|
1689
|
+
outputLog("Now calling back readDoneCallback(). Sending the following values...", "DEBUG", self.connectionID);
|
|
1690
|
+
outputLog(util.format(results), "DEBUG");
|
|
1691
|
+
self.writeDoneCallback(anyBadQualities, results);
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
self.processQueueASAP();
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
|
|
1700
|
+
|
|
1701
|
+
MCProtocol.prototype.readResponse = function (data, decodedHeader) {
|
|
1702
|
+
|
|
1703
|
+
//4E: Subheader (6 bytes), Access route (5 bytes), Response data length (2 bytes), End code (2 bytes), Response data (variable)
|
|
1704
|
+
// Data starts at [15]
|
|
1705
|
+
//3E: Subheader (2 bytes), Access route (5 bytes), Response data length (2 bytes), End code (2 bytes), Response data (variable)
|
|
1706
|
+
// Data starts at [11]
|
|
1707
|
+
//error information : Access route, Command, Subcommand.
|
|
1708
|
+
var self = this;
|
|
1709
|
+
var anyBadQualities, dataPointer, rcvdPacketNum; // For non-routed packets we start at byte 21 of the packet. If we do routing it will be more than this.
|
|
1710
|
+
let results = {};//items to send to callback
|
|
1711
|
+
|
|
1712
|
+
outputLog("ReadResponse called", "DEBUG", self.connectionID);
|
|
1713
|
+
|
|
1714
|
+
for (packetCounter = 0; packetCounter < self.readPacketArray.length; packetCounter++) {
|
|
1715
|
+
if (self.readPacketArray[packetCounter].sent && !(self.readPacketArray[packetCounter].rcvd)) {
|
|
1716
|
+
rcvdPacketNum = packetCounter;
|
|
1717
|
+
break; // Done with the FOR loop
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
if (typeof (rcvdPacketNum) === 'undefined') {
|
|
1722
|
+
outputLog('WARNING: Received a read response packet that was not marked as sent', "WARN", self.connectionID);
|
|
1723
|
+
//TODO - fix the network unreachable error that made us do this
|
|
1724
|
+
return null;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
if (self.readPacketArray[rcvdPacketNum].rcvd) {
|
|
1728
|
+
outputLog('WARNING: Received a read response packet that was already marked as received', "WARN", self.connectionID);
|
|
1729
|
+
return null;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
for (itemCount = 0; itemCount < self.readPacketArray[rcvdPacketNum].itemList.length; itemCount++) {
|
|
1733
|
+
//dataPointer = processMBPacketORIG(data, self.readPacketArray[rcvdPacketNum].itemList[itemCount], dataPointer, self.frame);
|
|
1734
|
+
dataPointer = processMBPacket(decodedHeader, data, self.readPacketArray[rcvdPacketNum].itemList[itemCount], dataPointer, self.frame);
|
|
1735
|
+
if (!dataPointer && typeof (data) !== "undefined") {
|
|
1736
|
+
// Don't bother showing this message on timeout.
|
|
1737
|
+
outputLog('Received a ZERO RESPONSE Processing Read Packet due to unrecoverable packet error', "ERROR");
|
|
1738
|
+
// break; // We rely on this for our timeout now.
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
// Make a note of the time it took the PLC to process the request.
|
|
1743
|
+
self.readPacketArray[rcvdPacketNum].reqTime = process.hrtime(self.readPacketArray[rcvdPacketNum].reqTime);
|
|
1744
|
+
let rtms = (self.readPacketArray[rcvdPacketNum].reqTime[0] * 1000) + Math.round(self.readPacketArray[rcvdPacketNum].reqTime[1] * 10 / 1e6) / 10;
|
|
1745
|
+
outputLog('Read Time is ' + rtms + 'ms.', "DEBUG", self.connectionID);
|
|
1746
|
+
|
|
1747
|
+
// Do the bookkeeping for packet and timeout.
|
|
1748
|
+
if (!self.readPacketArray[rcvdPacketNum].rcvd) {
|
|
1749
|
+
self.readPacketArray[rcvdPacketNum].rcvd = true;
|
|
1750
|
+
self.parallelJobsNow--;
|
|
1751
|
+
if (self.parallelJobsNow < 0) { self.parallelJobsNow = 0; }
|
|
1752
|
+
}
|
|
1753
|
+
clearTimeout(self.readPacketArray[rcvdPacketNum].timeout);
|
|
1754
|
+
|
|
1755
|
+
if (self.readPacketArray.every(doneSending)) { // if sendReadPacket returns true we're all done.
|
|
1756
|
+
// Mark our packets unread for next time.
|
|
1757
|
+
outputLog('Every packet done sending', "DEBUG", self.connectionID);
|
|
1758
|
+
for (i = 0; i < self.readPacketArray.length; i++) {
|
|
1759
|
+
self.readPacketArray[i].sent = false;
|
|
1760
|
+
self.readPacketArray[i].rcvd = false;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
anyBadQualities = false;
|
|
1764
|
+
|
|
1765
|
+
// Loop through the global block list...
|
|
1766
|
+
for (var i = 0; i < self.globalReadBlockList.length; i++) {
|
|
1767
|
+
var lengthOffset = 0;
|
|
1768
|
+
let block = self.globalReadBlockList[i];
|
|
1769
|
+
// For each block, we loop through all the requests. Remember, for all but large arrays, there will only be one.
|
|
1770
|
+
for (var j = 0; j < block.requestReference.length; j++) {
|
|
1771
|
+
// Now that our request is complete, we reassemble the BLOCK byte buffer as a
|
|
1772
|
+
//copy of each and every request byte buffer.
|
|
1773
|
+
let reqRef = block.requestReference[j];
|
|
1774
|
+
reqRef.byteBuffer.copy(block.byteBuffer, lengthOffset, 0, reqRef.byteLength);
|
|
1775
|
+
reqRef.qualityBuffer.copy(block.qualityBuffer, lengthOffset, 0, reqRef.byteLength);
|
|
1776
|
+
lengthOffset += reqRef.byteLength;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
// For each ITEM reference pointed to by the block, we process the item.
|
|
1780
|
+
for (var k = 0; k < block.itemReference.length; k++) {
|
|
1781
|
+
let itemRef = block.itemReference[k];
|
|
1782
|
+
processMCReadItem(itemRef, self.isAscii, self.frame);
|
|
1783
|
+
var ttms = itemRef.recvTime - itemRef.initTime;
|
|
1784
|
+
let result = new PLCReadResult(itemRef.useraddr, itemRef.addr, itemRef.quality, ttms, itemRef.value, itemRef.datatype);
|
|
1785
|
+
result.deviceCode = itemRef.deviceCode;
|
|
1786
|
+
result.deviceCodeNotation = itemRef.deviceCodeSpec.notation;
|
|
1787
|
+
result.deviceCodeType = itemRef.deviceCodeSpec.type;
|
|
1788
|
+
result.digitSpec = itemRef.digitSpec;
|
|
1789
|
+
result.dataType = itemRef.datatype;
|
|
1790
|
+
result.dataTypeByteLength = itemRef.dtypelen;
|
|
1791
|
+
result.deviceNo = itemRef.offset;
|
|
1792
|
+
result.bitOffset = itemRef.bitOffset;
|
|
1793
|
+
|
|
1794
|
+
if (typeof (itemRef.cb) === 'function') {
|
|
1795
|
+
outputLog("Now calling back item.cb(). Sending the following values...", "DEBUG", self.connectionID);
|
|
1796
|
+
outputLog(util.format(result), "DEBUG");
|
|
1797
|
+
itemRef.cb(!result.isGood, result)
|
|
1798
|
+
}
|
|
1799
|
+
if (itemRef.action == "read") {
|
|
1800
|
+
self.removeItems(itemRef.useraddr);
|
|
1801
|
+
}
|
|
1802
|
+
results[itemRef.useraddr] = result;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// Inform our user that we are done and that the values are ready for pickup.
|
|
1807
|
+
self.emit('message',anyBadQualities, results);
|
|
1808
|
+
if (typeof (self.readDoneCallback) === 'function') {
|
|
1809
|
+
outputLog("Now calling back readDoneCallback(). Sending the following values...", "DEBUG", self.connectionID);
|
|
1810
|
+
outputLog(util.format(results), "DEBUG");
|
|
1811
|
+
|
|
1812
|
+
self.readDoneCallback(anyBadQualities, results);
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
if (self.resetPending) {
|
|
1816
|
+
self.resetNow();
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
self.processQueueASAP();
|
|
1820
|
+
|
|
1821
|
+
} else {
|
|
1822
|
+
outputLog("Read Request not done - more packets to send - Calling sendReadPacket() from readResponse()", "DEBUG", self.connectionID);
|
|
1823
|
+
self.sendReadPacket();
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
MCProtocol.prototype.onClientDisconnect = function (err) {
|
|
1829
|
+
var self = this;
|
|
1830
|
+
self.emit('close',err);
|
|
1831
|
+
outputLog('EIP/TCP DISCONNECTED.');
|
|
1832
|
+
self.connectionCleanup();
|
|
1833
|
+
self.tryingToConnectNow = false;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
MCProtocol.prototype.connectionReset = function () {
|
|
1837
|
+
var self = this;
|
|
1838
|
+
//self.connectionState = 0;
|
|
1839
|
+
self.resetPending = true;
|
|
1840
|
+
outputLog('ConnectionReset is happening', "DEBUG");
|
|
1841
|
+
// The problem is that if we are interrupted before a read can be completed, say we get a bogus packet - we'll never recover.
|
|
1842
|
+
if (!self.isReading() && typeof (self.resetTimeout) === 'undefined') { // For now - ignore writes. && !isWriting()) {
|
|
1843
|
+
self.resetTimeout = setTimeout(function () {
|
|
1844
|
+
self.resetNow.apply(self, arguments);
|
|
1845
|
+
}, 1500);
|
|
1846
|
+
}
|
|
1847
|
+
// For now we wait until read() is called again to re-connect.
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
MCProtocol.prototype.resetNow = function () {
|
|
1851
|
+
var self = this;
|
|
1852
|
+
outputLog('resetNow is happening', "DEBUG");
|
|
1853
|
+
if(self.connectionState == 4){
|
|
1854
|
+
if(self.connectionParams.protocol == "UDP"){
|
|
1855
|
+
self.netClient.close();
|
|
1856
|
+
} else {
|
|
1857
|
+
self.netClient.end();
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
self.connectionState = 0;
|
|
1861
|
+
self.resetPending = false;
|
|
1862
|
+
// In some cases, we can have a timeout scheduled for a reset, but we don't want to call it again in that case.
|
|
1863
|
+
// We only want to call a reset just as we are returning values. Otherwise, we will get asked to read // more values and we will "break our promise" to always return something when asked.
|
|
1864
|
+
if (typeof (self.resetTimeout) !== 'undefined') {
|
|
1865
|
+
clearTimeout(self.resetTimeout);
|
|
1866
|
+
self.resetTimeout = undefined;
|
|
1867
|
+
outputLog('Clearing an earlier scheduled reset', "DEBUG");
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
MCProtocol.prototype.connectionCleanup = function () {
|
|
1872
|
+
var self = this;
|
|
1873
|
+
self.connectionState = 0;
|
|
1874
|
+
outputLog('Connection cleanup is happening', "DEBUG");
|
|
1875
|
+
if (typeof (self.netClient) !== "undefined") {
|
|
1876
|
+
self.netClient.removeAllListeners('message');
|
|
1877
|
+
self.netClient.removeAllListeners('data');
|
|
1878
|
+
self.netClient.removeAllListeners('error');
|
|
1879
|
+
self.netClient.removeAllListeners('connect');
|
|
1880
|
+
self.netClient.removeAllListeners('end');
|
|
1881
|
+
self.netClient.removeAllListeners();
|
|
1882
|
+
}
|
|
1883
|
+
clearTimeout(self.connectTimeout);
|
|
1884
|
+
clearTimeout(self.PDUTimeout);
|
|
1885
|
+
self.clearReadPacketTimeouts(); // Note this clears timeouts.
|
|
1886
|
+
self.clearWritePacketTimeouts(); // Note this clears timeouts.
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
function outputLog(txt, debugLevel, id) {
|
|
1890
|
+
if(effectiveDebugLevel < 0){
|
|
1891
|
+
return;//NONE
|
|
1892
|
+
}
|
|
1893
|
+
var idtext;
|
|
1894
|
+
if (typeof (id) === 'undefined') {
|
|
1895
|
+
idtext = '';
|
|
1896
|
+
} else {
|
|
1897
|
+
idtext = ' ' + id;
|
|
1898
|
+
}
|
|
1899
|
+
var t = process.hrtime();
|
|
1900
|
+
var s = new Date().toISOString() + " " + Math.round(t[1] / 1000) + " ";
|
|
1901
|
+
|
|
1902
|
+
let level = 3;//debug by default
|
|
1903
|
+
if(typeof debugLevel == "number"){
|
|
1904
|
+
level = debugLevel;
|
|
1905
|
+
} else {
|
|
1906
|
+
if(!debugLevel){
|
|
1907
|
+
debugLevel = "DEBUG";
|
|
1908
|
+
}
|
|
1909
|
+
switch (debugLevel) {
|
|
1910
|
+
case "TRACE":
|
|
1911
|
+
level = 4;
|
|
1912
|
+
break;
|
|
1913
|
+
case "DEBUG":
|
|
1914
|
+
level = 3;
|
|
1915
|
+
break;
|
|
1916
|
+
case "INFO":
|
|
1917
|
+
level = 2;
|
|
1918
|
+
break;
|
|
1919
|
+
case "WARN":
|
|
1920
|
+
level = 1;
|
|
1921
|
+
break;
|
|
1922
|
+
case "ERROR":
|
|
1923
|
+
level = 0;
|
|
1924
|
+
break;
|
|
1925
|
+
case "NONE":
|
|
1926
|
+
level = 65535;
|
|
1927
|
+
break;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
if (effectiveDebugLevel >= level) { console.log('[' + s + idtext + '] ' + util.format(txt)); }
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
function doneSending(element) {
|
|
1934
|
+
return ((element.sent && element.rcvd) ? true : false);
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
function processMBPacket(decodedHeader, theData, theItem, thePointer, frame) {
|
|
1938
|
+
|
|
1939
|
+
// Create a new buffer for the quality.
|
|
1940
|
+
theItem.qualityBuffer = Buffer.alloc(theItem.byteLength);
|
|
1941
|
+
theItem.qualityBuffer.fill(MCProtocol.prototype.enumOPCQuality.unknown.value);
|
|
1942
|
+
//reset some flags...#
|
|
1943
|
+
theItem.endCode = null;
|
|
1944
|
+
theItem.endDetail = null;
|
|
1945
|
+
theItem.lastError = null;
|
|
1946
|
+
|
|
1947
|
+
if(!decodedHeader || !decodedHeader.valid){
|
|
1948
|
+
theItem.valid = false;
|
|
1949
|
+
if(decodedHeader)
|
|
1950
|
+
theItem.lastError = decodedHeader.lastError || "Response Header is not valid";
|
|
1951
|
+
else
|
|
1952
|
+
theItem.lastError = "Response Header is not valid";
|
|
1953
|
+
outputLog(theItem.lastError, "WARN"); // Can't log more info here as we dont have "self" info
|
|
1954
|
+
return 0; // Hard to increment the pointer so we call it a malformed packet and we're done.
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
// There is no reported data length to check here -
|
|
1958
|
+
// reportedDataLength = theData[9];
|
|
1959
|
+
if (theItem.bitNative && theItem.writeOperation)
|
|
1960
|
+
expectedLength = Math.ceil(theItem.arrayLength / 2);//2 bits are return per byte (pg91)
|
|
1961
|
+
else
|
|
1962
|
+
expectedLength = theItem.byteLength;
|
|
1963
|
+
|
|
1964
|
+
if (decodedHeader.dataLength !== expectedLength) {
|
|
1965
|
+
decodedHeader.valid = false;
|
|
1966
|
+
theItem.valid = false;
|
|
1967
|
+
theItem.lastError = 'Invalid "Response Data Length" - Expected ' + expectedLength + ' but got ' + (decodedHeader.dataLength) + ' bytes.';
|
|
1968
|
+
decodedHeader.lastError = theItem.lastError;
|
|
1969
|
+
outputLog(theItem.lastError, "ERROR");
|
|
1970
|
+
return 1;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
// Looks good so far.
|
|
1974
|
+
// set the data pointer to start of data.
|
|
1975
|
+
thePointer = decodedHeader.dataStart;
|
|
1976
|
+
|
|
1977
|
+
var arrayIndex = 0;
|
|
1978
|
+
|
|
1979
|
+
theItem.valid = true;
|
|
1980
|
+
theItem.byteBuffer = theData.slice(thePointer); // This means take to end.
|
|
1981
|
+
|
|
1982
|
+
outputLog('Byte Buffer is:', "TRACE");
|
|
1983
|
+
outputLog(theItem.byteBuffer, "TRACE");
|
|
1984
|
+
|
|
1985
|
+
outputLog('Marking quality as good.', "TRACE");
|
|
1986
|
+
theItem.qualityBuffer.fill(MCProtocol.prototype.enumOPCQuality.good.value); //
|
|
1987
|
+
|
|
1988
|
+
return -1; //thePointer;
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
|
|
1992
|
+
|
|
1993
|
+
|
|
1994
|
+
/**
|
|
1995
|
+
* Decodes a reply from PLC. NOTE: Different properties will be set depeding on the frame used. e.g. `.seq` is only valid for 4E frames.
|
|
1996
|
+
*
|
|
1997
|
+
* @param {*} mcp - this MCProtocol object (required for access to frame)
|
|
1998
|
+
* @param {*} theData - the data as it comes direct from the PLC
|
|
1999
|
+
* @returns an object containing the decoded header. check `.valid` before using values
|
|
2000
|
+
*/
|
|
2001
|
+
function decodeResponseHeader(mcp, theData){
|
|
2002
|
+
|
|
2003
|
+
//18.1 1E Message Format pg 389
|
|
2004
|
+
// Subheader, End code, Response data
|
|
2005
|
+
// Subheader is 1 byte. Value should be between Original Req Subheader + 0x80
|
|
2006
|
+
// End Code is 1 byte. Value should be 0 for normal
|
|
2007
|
+
// Response Data. Non for write op, Data for Read op, Abnormal Code if End Code != 0
|
|
2008
|
+
|
|
2009
|
+
//5.2 3E,4E Message Format pg 41
|
|
2010
|
+
// Subheader, Access route, Response data length, End code, Response data
|
|
2011
|
+
|
|
2012
|
+
// Subheader
|
|
2013
|
+
// 4E 6byes 0x0054 + serial number + 0x0000 (6b) 1234H (5400 = 4E frame, 3412=1234h, 0000 fixed)
|
|
2014
|
+
// 3E 2bytes 5000
|
|
2015
|
+
// Access route
|
|
2016
|
+
// 5bytes
|
|
2017
|
+
//* (Byte) Network No , Specify the network No. of an access target.
|
|
2018
|
+
//* (Byte) PC No. (byte) Specify the network module station No. of an access target
|
|
2019
|
+
//* (UINT16 LE) Request destination module I/O No
|
|
2020
|
+
//* (Byte) Request destination module station No.
|
|
2021
|
+
// Response data length
|
|
2022
|
+
// 2bytes
|
|
2023
|
+
// End code
|
|
2024
|
+
// 2bytes
|
|
2025
|
+
// Response data - Non for write op, Data for Read op, Error Information if End Code != 0
|
|
2026
|
+
|
|
2027
|
+
|
|
2028
|
+
//TODO: put these into an enum of frame eg. ("1E":{minResponseLength:2, other:xxx})
|
|
2029
|
+
let frame = mcp.frame;
|
|
2030
|
+
var minLength = 0;
|
|
2031
|
+
switch (frame) {
|
|
2032
|
+
case '1E':
|
|
2033
|
+
minLength = 2;
|
|
2034
|
+
break;
|
|
2035
|
+
case '3E':
|
|
2036
|
+
minLength = 11;
|
|
2037
|
+
break;
|
|
2038
|
+
case '4E':
|
|
2039
|
+
minLength = 15;
|
|
2040
|
+
break;
|
|
2041
|
+
default:
|
|
2042
|
+
break;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
var reply = {
|
|
2046
|
+
expectedResponse: undefined,
|
|
2047
|
+
response: undefined,
|
|
2048
|
+
endCode: undefined,
|
|
2049
|
+
accessRouteNetworkNo: undefined,
|
|
2050
|
+
accessPCNo: undefined,
|
|
2051
|
+
accessRouteModuleIONo: undefined,
|
|
2052
|
+
accessRouteModuleStationNo: undefined,
|
|
2053
|
+
seq: undefined,
|
|
2054
|
+
length: undefined,
|
|
2055
|
+
dataLength: undefined, //length: Noof bytes in "Response data" deduct 2 from length (to remove endCode)
|
|
2056
|
+
dataStart: undefined,
|
|
2057
|
+
lastError: "",
|
|
2058
|
+
valid: false
|
|
2059
|
+
};
|
|
2060
|
+
|
|
2061
|
+
try {
|
|
2062
|
+
if (theData.length < minLength) {
|
|
2063
|
+
if (typeof (theData) !== "undefined") {
|
|
2064
|
+
throw new Error(`Malformed MC Packet - Expected at least '${minLength}' Bytes, however, only received '${theData.length}' Bytes(s)`)
|
|
2065
|
+
} else {
|
|
2066
|
+
throw new Error("Timeout error - zero length packet");
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
if (frame == '1E') {
|
|
2071
|
+
reply.expectedResponse = theData[0];//set the expected to same as received. As this is a 1E frame, we dont know expected response at this time. This will be checked later.
|
|
2072
|
+
//0x80=128 PG401 Batch read in bit units (command: 00) OK response
|
|
2073
|
+
//0x81=129 PG403 Batch read in word units (command: 01) OK response
|
|
2074
|
+
//0x82=130 PG405 Batch write in bit units (command: 02) OK response
|
|
2075
|
+
//0x83=131 PG407 Batch write in word units (command: 03) OK response
|
|
2076
|
+
reply.response = theData[0];
|
|
2077
|
+
reply.endCode = theData[1];
|
|
2078
|
+
reply.dataLength = theData.length -2; //length: Noof bytes in "Response data" deduct 2 for length of errcode
|
|
2079
|
+
reply.dataStart = 2;
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
if (frame == '3E') {
|
|
2083
|
+
reply.expectedResponse = 0x00D0;
|
|
2084
|
+
reply.response = theData.readUInt16LE(0);
|
|
2085
|
+
reply.accessRouteNetworkNo = theData[2];
|
|
2086
|
+
reply.accessPCNo = theData[3];
|
|
2087
|
+
reply.accessRouteModuleIONo = theData.readUInt16LE(4);
|
|
2088
|
+
reply.accessRouteModuleStationNo = theData[6];
|
|
2089
|
+
reply.length = theData.readUInt16LE(7); //length Noof bytes from start of "end code" ~ end of "Response data"
|
|
2090
|
+
reply.endCode = theData.readUInt16LE(9);
|
|
2091
|
+
//rExtraInfo = theData.readUInt16LE(11);
|
|
2092
|
+
reply.dataLength = reply.length -2; //length: Noof bytes in "Response data" deduct 2 for length of errcode
|
|
2093
|
+
reply.dataStart = 11;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
if (frame == '4E') {
|
|
2097
|
+
reply.expectedResponse = 0x00D4;
|
|
2098
|
+
reply.response = theData.readUInt16LE(0);
|
|
2099
|
+
reply.seqNum = theData.readUInt16LE(2);
|
|
2100
|
+
let xxx = theData.readUInt16LE(4); //dummy
|
|
2101
|
+
reply.accessRouteNetworkNo = theData[6];
|
|
2102
|
+
reply.accessPCNo = theData[7];
|
|
2103
|
+
reply.accessRouteModuleIONo = theData.readUInt16LE(8);
|
|
2104
|
+
reply.accessRouteModuleStationNo = theData[10];
|
|
2105
|
+
reply.length = theData.readUInt16LE(11);//length Noof bytes from start of "end code" ~ end of "Response data"
|
|
2106
|
+
reply.endCode = theData.readUInt16LE(13);
|
|
2107
|
+
//rExtraInfo = theData.readUInt16LE(15);
|
|
2108
|
+
reply.dataLength = reply.length -2; //length: Noof bytes in "Response data" deduct 2 for length of errcode
|
|
2109
|
+
reply.dataStart = 15;
|
|
2110
|
+
|
|
2111
|
+
}
|
|
2112
|
+
if (reply.response !== reply.expectedResponse) {
|
|
2113
|
+
reply.lastError = 'Invalid MC - Expected first part to be ' + decimalToHexString(reply.expectedResponse) + ' - got ' + decimalToHexString(reply.response) + " (" + reply.response + ")";
|
|
2114
|
+
return reply;
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
if (reply.endCode !== 0) {
|
|
2118
|
+
reply.endDetail = theData.slice(reply.dataStart);//all response data is error info when endcode is not 0
|
|
2119
|
+
reply.lastError = `Reply End code '${reply.endCode} is not zero. For extra info, see "endDetail" `;
|
|
2120
|
+
return reply;
|
|
2121
|
+
}
|
|
2122
|
+
} catch (error) {
|
|
2123
|
+
reply.lastError = `Exception - ${error}`;
|
|
2124
|
+
return reply;
|
|
2125
|
+
}
|
|
2126
|
+
reply.valid = true;
|
|
2127
|
+
return reply;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
|
|
2131
|
+
function processMBWriteItem(decodedHeader, theItem, thePointer, frame) {
|
|
2132
|
+
// Create a new buffer for the quality.
|
|
2133
|
+
// theItem.writeQualityBuffer = new Buffer(theItem.byteLength);
|
|
2134
|
+
theItem.writeQualityBuffer.fill(MCProtocol.prototype.enumOPCQuality.unknown.value);
|
|
2135
|
+
//reset some flags...#
|
|
2136
|
+
theItem.endCode = null;
|
|
2137
|
+
theItem.endDetail = null;
|
|
2138
|
+
theItem.lastError = null;
|
|
2139
|
+
if(!decodedHeader){
|
|
2140
|
+
theItem.valid = false;
|
|
2141
|
+
theItem.endCode = -1; //TODO: Determine a suitable endCode (or none!)
|
|
2142
|
+
theItem.lastError = "Response Header is not valid / empty (should not happen)";
|
|
2143
|
+
theItem.writeQualityBuffer.fill(MCProtocol.prototype.enumOPCQuality.bad.value);
|
|
2144
|
+
outputLog(theItem.lastError, "WARN");
|
|
2145
|
+
return 0;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
if(!decodedHeader.valid){
|
|
2149
|
+
theItem.valid = false;
|
|
2150
|
+
theItem.lastError = decodedHeader.lastError || "Response Header is not valid";
|
|
2151
|
+
theItem.endCode = decodedHeader.endCode;
|
|
2152
|
+
theItem.endDetail = decodedHeader.endDetail;
|
|
2153
|
+
//TODO: Fill quality with more specific / appropriate value (based on endCode)
|
|
2154
|
+
theItem.writeQualityBuffer.fill(MCProtocol.prototype.enumOPCQuality.bad.value);
|
|
2155
|
+
outputLog(theItem.lastError, "WARN");
|
|
2156
|
+
return 0;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
//success
|
|
2160
|
+
theItem.writeQualityBuffer.fill(MCProtocol.prototype.enumOPCQuality.good.value);
|
|
2161
|
+
return -1;
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
function writePostProcess(theItem) {
|
|
2165
|
+
var thePointer = 0;
|
|
2166
|
+
theItem.recvTime = Date.now();
|
|
2167
|
+
|
|
2168
|
+
if (theItem.arrayLength === 1) {
|
|
2169
|
+
let qual = theItem.writeQualityBuffer[0];
|
|
2170
|
+
let qualItem = MCProtocol.prototype.enumOPCQuality[qual];
|
|
2171
|
+
if (!qualItem)
|
|
2172
|
+
qualItem = MCProtocol.prototype.enumOPCQuality.uncertain;
|
|
2173
|
+
theItem.writeQuality = qualItem.desc;
|
|
2174
|
+
} else {
|
|
2175
|
+
// Array value.
|
|
2176
|
+
theItem.writeQuality = [];
|
|
2177
|
+
for (arrayIndex = 0; arrayIndex < theItem.arrayLength; arrayIndex++) {
|
|
2178
|
+
|
|
2179
|
+
let qual = theItem.writeQualityBuffer[thePointer];
|
|
2180
|
+
let qualItem = MCProtocol.prototype.enumOPCQuality[qual];
|
|
2181
|
+
if (!qualItem)
|
|
2182
|
+
qualItem = MCProtocol.prototype.enumOPCQuality.uncertain;
|
|
2183
|
+
|
|
2184
|
+
theItem.writeQuality[arrayIndex] = qualItem.desc;
|
|
2185
|
+
|
|
2186
|
+
if (theItem.datatype == 'BIT') {
|
|
2187
|
+
// For bit arrays, we have to do some tricky math to get the pointer to equal the byte offset.
|
|
2188
|
+
// Note that we add the bit offset here for the rare case of an array starting at other than zero. We either have to
|
|
2189
|
+
// drop support for this at the request level or support it here.
|
|
2190
|
+
if ((((arrayIndex + theItem.bitOffset + 1) % 8) == 0) || (arrayIndex == theItem.arrayLength - 1)) {
|
|
2191
|
+
thePointer += theItem.dtypelen;
|
|
2192
|
+
}
|
|
2193
|
+
} else {
|
|
2194
|
+
// Add to the pointer every time.
|
|
2195
|
+
thePointer += theItem.dtypelen;
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
|
|
2202
|
+
function processMCReadItem(theItem, isAscii, frame) {
|
|
2203
|
+
|
|
2204
|
+
var thePointer = 0, tempBuffer = Buffer.alloc(4);
|
|
2205
|
+
theItem.recvTime = Date.now();
|
|
2206
|
+
let quals = MCProtocol.prototype.enumOPCQuality;
|
|
2207
|
+
if (theItem.arrayLength > 1) {
|
|
2208
|
+
// Array value.
|
|
2209
|
+
if (theItem.datatype != 'C' && theItem.datatype != 'CHAR') {
|
|
2210
|
+
theItem.value = [];
|
|
2211
|
+
theItem.quality = [];
|
|
2212
|
+
} else {
|
|
2213
|
+
theItem.value = '';
|
|
2214
|
+
theItem.quality = '';
|
|
2215
|
+
}
|
|
2216
|
+
var bitShiftAmount = theItem.bitOffset;
|
|
2217
|
+
if (theItem.bitNative) {
|
|
2218
|
+
bitShiftAmount = theItem.remainder;
|
|
2219
|
+
}
|
|
2220
|
+
var stringNullFound = false;
|
|
2221
|
+
|
|
2222
|
+
for (arrayIndex = 0; arrayIndex < theItem.arrayLength; arrayIndex++) {
|
|
2223
|
+
|
|
2224
|
+
let qual = theItem.qualityBuffer[thePointer];
|
|
2225
|
+
let qualItem = quals[qual];
|
|
2226
|
+
if (!qualItem)
|
|
2227
|
+
qualItem = quals.uncertain;
|
|
2228
|
+
if (qualItem.value !== quals.good.value && qualItem.value !== quals.goodForced.value) {
|
|
2229
|
+
outputLog("Logging a Bad Quality thePointer " + thePointer, "WARN");
|
|
2230
|
+
// If we're a string, quality is not an array.
|
|
2231
|
+
let dts = MCProtocol.prototype.enumDataTypes;
|
|
2232
|
+
let dt = dts[theItem.datatype];
|
|
2233
|
+
if (dt && dt == dts.CHAR || dt == dts.STR) {
|
|
2234
|
+
theItem.quality = qualItem.desc;
|
|
2235
|
+
theItem.value = theItem.badValue();
|
|
2236
|
+
break;
|
|
2237
|
+
} else {
|
|
2238
|
+
theItem.quality.push(qualItem.desc);
|
|
2239
|
+
theItem.value.push(theItem.badValue());
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
} else {
|
|
2243
|
+
// If we're a string, quality is not an array.
|
|
2244
|
+
if (theItem.quality instanceof Array) {
|
|
2245
|
+
theItem.quality.push(qualItem.desc);
|
|
2246
|
+
} else {
|
|
2247
|
+
theItem.quality = qualItem.desc;
|
|
2248
|
+
}
|
|
2249
|
+
switch (theItem.datatype) {
|
|
2250
|
+
case "FLOAT":
|
|
2251
|
+
case "REAL":
|
|
2252
|
+
if (isAscii) {
|
|
2253
|
+
theItem.value.push(theItem.byteBuffer.getFloatBESwap(thePointer));
|
|
2254
|
+
} else {
|
|
2255
|
+
theItem.value.push(theItem.byteBuffer.readFloatLE(thePointer));
|
|
2256
|
+
}
|
|
2257
|
+
break;
|
|
2258
|
+
case "DWORD":
|
|
2259
|
+
if (isAscii) {
|
|
2260
|
+
theItem.value.push(theItem.byteBuffer.getUInt32BESwap(thePointer));
|
|
2261
|
+
} else {
|
|
2262
|
+
theItem.value.push(theItem.byteBuffer.readUInt32LE(thePointer));
|
|
2263
|
+
}
|
|
2264
|
+
break;
|
|
2265
|
+
case "DINT":
|
|
2266
|
+
if (isAscii) {
|
|
2267
|
+
theItem.value.push(theItem.byteBuffer.getInt32BESwap(thePointer));
|
|
2268
|
+
} else {
|
|
2269
|
+
theItem.value.push(theItem.byteBuffer.readInt32LE(thePointer));
|
|
2270
|
+
}
|
|
2271
|
+
break;
|
|
2272
|
+
case "INT":
|
|
2273
|
+
if (isAscii) {
|
|
2274
|
+
theItem.value.push(theItem.byteBuffer.readInt16BE(thePointer));
|
|
2275
|
+
} else {
|
|
2276
|
+
theItem.value.push(theItem.byteBuffer.readInt16LE(thePointer));
|
|
2277
|
+
}
|
|
2278
|
+
break;
|
|
2279
|
+
case "UINT":
|
|
2280
|
+
case "WORD":
|
|
2281
|
+
if (isAscii) {
|
|
2282
|
+
theItem.value.push(theItem.byteBuffer.readUInt16BE(thePointer));
|
|
2283
|
+
} else {
|
|
2284
|
+
theItem.value.push(theItem.byteBuffer.readUInt16LE(thePointer));
|
|
2285
|
+
}
|
|
2286
|
+
break;
|
|
2287
|
+
case "BIT":
|
|
2288
|
+
if (theItem.bitNative) {
|
|
2289
|
+
if (isAscii) {
|
|
2290
|
+
theItem.value.push(((theItem.byteBuffer.readUInt16BE(thePointer) >> (bitShiftAmount)) & 1) ? true : false);
|
|
2291
|
+
} else {
|
|
2292
|
+
theItem.value.push(((theItem.byteBuffer.readUInt16LE(thePointer) >> (bitShiftAmount)) & 1) ? true : false);
|
|
2293
|
+
}
|
|
2294
|
+
} else {
|
|
2295
|
+
if (isAscii) {
|
|
2296
|
+
theItem.value.push(((theItem.byteBuffer.readUInt16BE(thePointer) >> (bitShiftAmount)) & 1) ? true : false);
|
|
2297
|
+
} else {
|
|
2298
|
+
theItem.value.push(((theItem.byteBuffer.readUInt16LE(thePointer) >> (bitShiftAmount)) & 1) ? true : false);
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
break;
|
|
2302
|
+
case "B":
|
|
2303
|
+
case "BYTE":
|
|
2304
|
+
if (isAscii) {
|
|
2305
|
+
if (arrayIndex % 2) {
|
|
2306
|
+
theItem.value.push(theItem.byteBuffer.readUInt8(thePointer - 1));
|
|
2307
|
+
} else {
|
|
2308
|
+
theItem.value.push(theItem.byteBuffer.readUInt8(thePointer + 1));
|
|
2309
|
+
}
|
|
2310
|
+
} else {
|
|
2311
|
+
theItem.value.push(theItem.byteBuffer.readUInt8(thePointer));
|
|
2312
|
+
}
|
|
2313
|
+
break;
|
|
2314
|
+
|
|
2315
|
+
case "C":
|
|
2316
|
+
case "CHAR":
|
|
2317
|
+
// Convert to string.
|
|
2318
|
+
if (isAscii) {
|
|
2319
|
+
if (arrayIndex % 2) {
|
|
2320
|
+
theItem.value += String.fromCharCode(theItem.byteBuffer.readUInt8(thePointer - 1));
|
|
2321
|
+
} else {
|
|
2322
|
+
theItem.value += String.fromCharCode(theItem.byteBuffer.readUInt8(thePointer + 1));
|
|
2323
|
+
}
|
|
2324
|
+
} else {
|
|
2325
|
+
var ch = theItem.byteBuffer.readUInt8(thePointer);
|
|
2326
|
+
if (ch > 0 && !stringNullFound)
|
|
2327
|
+
theItem.value += String.fromCharCode(ch)
|
|
2328
|
+
else
|
|
2329
|
+
stringNullFound = true;
|
|
2330
|
+
}
|
|
2331
|
+
break;
|
|
2332
|
+
|
|
2333
|
+
default:
|
|
2334
|
+
outputLog(`data type ${theItem.datatype} is not valid.`, "WARN");
|
|
2335
|
+
return 0;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
if (theItem.datatype == 'BIT') {
|
|
2339
|
+
// For bit arrays, we have to do some tricky math to get the pointer to equal the byte offset.
|
|
2340
|
+
// Note that we add the bit offset here for the rare case of an array starting at other than zero. We either have to
|
|
2341
|
+
// drop support for this at the request level or support it here.
|
|
2342
|
+
bitShiftAmount++;
|
|
2343
|
+
if (theItem.bitNative) {
|
|
2344
|
+
if ((((arrayIndex + theItem.remainder + 1) % 16) == 0) || (arrayIndex == theItem.arrayLength - 1)) { // NOTE: The second or case is for the case of the end of an array where we increment for next read - not important for MC protocol
|
|
2345
|
+
thePointer += theItem.dtypelen;
|
|
2346
|
+
bitShiftAmount = 0;
|
|
2347
|
+
}
|
|
2348
|
+
} else {
|
|
2349
|
+
// Never tested
|
|
2350
|
+
if ((((arrayIndex + theItem.bitOffset + 1) % 16) == 0) || (arrayIndex == theItem.arrayLength - 1)) {
|
|
2351
|
+
thePointer += theItem.dtypelen; // I guess this is 1 for bits.
|
|
2352
|
+
bitShiftAmount = 0;
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
} else {
|
|
2356
|
+
// Add to the pointer every time.
|
|
2357
|
+
thePointer += theItem.dtypelen;
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
} else {
|
|
2361
|
+
// Single value.
|
|
2362
|
+
let qual = theItem.qualityBuffer[thePointer];
|
|
2363
|
+
let qualItem = quals[qual];
|
|
2364
|
+
if (!qualItem)
|
|
2365
|
+
qualItem = quals.uncertain;
|
|
2366
|
+
if (qualItem.value !== quals.good.value && qualItem.value !== quals.goodForced.value) {
|
|
2367
|
+
theItem.value = theItem.badValue();
|
|
2368
|
+
theItem.quality = (qualItem.desc);
|
|
2369
|
+
outputLog("Item Quality is Bad", "WARN");
|
|
2370
|
+
} else {
|
|
2371
|
+
theItem.quality = (qualItem.desc);
|
|
2372
|
+
outputLog("Item Datatype (single value) is " + theItem.datatype, "TRACE");
|
|
2373
|
+
switch (theItem.datatype) {
|
|
2374
|
+
case "FLOAT":
|
|
2375
|
+
case "REAL":
|
|
2376
|
+
if (isAscii) {
|
|
2377
|
+
theItem.value = theItem.byteBuffer.getFloatBESwap(thePointer);
|
|
2378
|
+
} else {
|
|
2379
|
+
theItem.value = theItem.byteBuffer.readFloatLE(thePointer);
|
|
2380
|
+
}
|
|
2381
|
+
break;
|
|
2382
|
+
case "DWORD":
|
|
2383
|
+
if (isAscii) {
|
|
2384
|
+
theItem.value = theItem.byteBuffer.getUInt32BESwap(thePointer);
|
|
2385
|
+
} else {
|
|
2386
|
+
theItem.value = theItem.byteBuffer.readUInt32LE(thePointer);
|
|
2387
|
+
}
|
|
2388
|
+
break;
|
|
2389
|
+
case "DINT":
|
|
2390
|
+
if (isAscii) {
|
|
2391
|
+
theItem.value = theItem.byteBuffer.getInt32BESwap(thePointer);
|
|
2392
|
+
} else {
|
|
2393
|
+
theItem.value = theItem.byteBuffer.readInt32LE(thePointer);
|
|
2394
|
+
}
|
|
2395
|
+
break;
|
|
2396
|
+
case "INT":
|
|
2397
|
+
if (isAscii) {
|
|
2398
|
+
theItem.value = theItem.byteBuffer.readInt16BE(thePointer);
|
|
2399
|
+
} else {
|
|
2400
|
+
theItem.value = theItem.byteBuffer.readInt16LE(thePointer);
|
|
2401
|
+
}
|
|
2402
|
+
break;
|
|
2403
|
+
case "UINT":
|
|
2404
|
+
case "WORD":
|
|
2405
|
+
if (isAscii) {
|
|
2406
|
+
theItem.value = theItem.byteBuffer.readUInt16BE(thePointer);
|
|
2407
|
+
} else {
|
|
2408
|
+
theItem.value = theItem.byteBuffer.readUInt16LE(thePointer);
|
|
2409
|
+
}
|
|
2410
|
+
break;
|
|
2411
|
+
case "BIT":
|
|
2412
|
+
if (theItem.bitNative) {
|
|
2413
|
+
if (isAscii) {
|
|
2414
|
+
theItem.value = (((theItem.byteBuffer.readUInt16BE(thePointer) >> (theItem.remainder)) & 1) ? true : false);
|
|
2415
|
+
} else {
|
|
2416
|
+
theItem.value = (((theItem.byteBuffer.readUInt16LE(thePointer) >> (theItem.remainder)) & 1) ? true : false);
|
|
2417
|
+
}
|
|
2418
|
+
} else {
|
|
2419
|
+
if (isAscii) {
|
|
2420
|
+
theItem.value = (((theItem.byteBuffer.readUInt16BE(thePointer) >> (theItem.bitOffset)) & 1) ? true : false);
|
|
2421
|
+
} else {
|
|
2422
|
+
theItem.value = (((theItem.byteBuffer.readUInt16LE(thePointer) >> (theItem.bitOffset)) & 1) ? true : false);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
break;
|
|
2426
|
+
case "B":
|
|
2427
|
+
case "BYTE":
|
|
2428
|
+
// No support as of yet for signed 8 bit. This isn't that common.
|
|
2429
|
+
if (isAscii) {
|
|
2430
|
+
theItem.value = theItem.byteBuffer.readUInt8(thePointer + 1);
|
|
2431
|
+
} else {
|
|
2432
|
+
theItem.value = theItem.byteBuffer.readUInt8(thePointer);
|
|
2433
|
+
}
|
|
2434
|
+
break;
|
|
2435
|
+
case "C":
|
|
2436
|
+
case "CHAR":
|
|
2437
|
+
// No support as of yet for signed 8 bit. This isn't that common.
|
|
2438
|
+
if (isAscii) {
|
|
2439
|
+
theItem.value = String.fromCharCode(theItem.byteBuffer.readUInt8(thePointer + 1));
|
|
2440
|
+
} else {
|
|
2441
|
+
var ch = theItem.byteBuffer.readUInt8(thePointer);
|
|
2442
|
+
if (ch > 0)
|
|
2443
|
+
theItem.value = String.fromCharCode(ch);
|
|
2444
|
+
else
|
|
2445
|
+
theItem = "";
|
|
2446
|
+
}
|
|
2447
|
+
break;
|
|
2448
|
+
default:
|
|
2449
|
+
outputLog(`data type ${theItem.datatype} is not valid.`);
|
|
2450
|
+
return 0;
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
thePointer += theItem.dtypelen;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
if (((thePointer) % 2)) { // Odd number.
|
|
2457
|
+
thePointer += 1;
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
return thePointer; // Should maybe return a value now???
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
|
|
2464
|
+
function isQualityOK(obj) {
|
|
2465
|
+
|
|
2466
|
+
var isGood = function (q) {
|
|
2467
|
+
if (typeof q === "object") {
|
|
2468
|
+
if (q.value == MCProtocol.prototype.enumOPCQuality.good.value || obj.value == MCProtocol.prototype.enumOPCQuality.goodForced.value) { return true; }
|
|
2469
|
+
} else if (typeof q === "string") {
|
|
2470
|
+
if (q == 'OK' || q == MCProtocol.prototype.enumOPCQuality.good.desc || q == MCProtocol.prototype.enumOPCQuality.goodForced.desc) { return true; }
|
|
2471
|
+
} else if (typeof obj === "number") {
|
|
2472
|
+
if (q == MCProtocol.prototype.enumOPCQuality.good.value || q == MCProtocol.prototype.enumOPCQuality.goodForced.value) { return true; }
|
|
2473
|
+
}
|
|
2474
|
+
return false;
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
if (Array.isArray(obj)) {
|
|
2478
|
+
for (i = 0; i < obj.length; i++) {
|
|
2479
|
+
if (isGood(obj[i]) == false) {
|
|
2480
|
+
return false;
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
} else {
|
|
2484
|
+
return isGood(obj);
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
return true;
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
Buffer.prototype.setPointer = function (pos) {
|
|
2491
|
+
this.pointer = pos;
|
|
2492
|
+
return ptr;
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
Buffer.prototype.setFloatBESwap = function (val, ptr) {
|
|
2496
|
+
var newBuf = Buffer.alloc(4);
|
|
2497
|
+
newBuf.writeFloatBE(val, 0);
|
|
2498
|
+
this[ptr + 2] = newBuf[0];
|
|
2499
|
+
this[ptr + 3] = newBuf[1];
|
|
2500
|
+
this[ptr + 0] = newBuf[2];
|
|
2501
|
+
this[ptr + 1] = newBuf[3];
|
|
2502
|
+
return ptr + 4;
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
|
|
2506
|
+
Buffer.prototype.getFloatBESwap = function(ptr) {
|
|
2507
|
+
var newBuf = Buffer.alloc(4);
|
|
2508
|
+
newBuf[0] = this[ptr + 2];
|
|
2509
|
+
newBuf[1] = this[ptr + 3];
|
|
2510
|
+
newBuf[2] = this[ptr + 0];
|
|
2511
|
+
newBuf[3] = this[ptr + 1];
|
|
2512
|
+
return newBuf.readFloatBE(0);
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
|
|
2516
|
+
Buffer.prototype.getInt32BESwap = function(ptr) {
|
|
2517
|
+
var newBuf = Buffer.alloc(4);
|
|
2518
|
+
newBuf[0] = this[ptr + 2];
|
|
2519
|
+
newBuf[1] = this[ptr + 3];
|
|
2520
|
+
newBuf[2] = this[ptr + 0];
|
|
2521
|
+
newBuf[3] = this[ptr + 1];
|
|
2522
|
+
return newBuf.readInt32BE(0);
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
Buffer.prototype.setInt32BESwap = function(val,ptr) {
|
|
2526
|
+
var newBuf = Buffer.alloc(4);
|
|
2527
|
+
newBuf.writeInt32BE(Math.round(val), 0);
|
|
2528
|
+
this[ptr + 2] = newBuf[0];
|
|
2529
|
+
this[ptr + 3] = newBuf[1];
|
|
2530
|
+
this[ptr + 0] = newBuf[2];
|
|
2531
|
+
this[ptr + 1] = newBuf[3];
|
|
2532
|
+
return ptr + 4;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
Buffer.prototype.getUInt32BESwap = function(ptr) {
|
|
2536
|
+
var newBuf = Buffer.alloc(4);
|
|
2537
|
+
newBuf[0] = this[ptr + 2];
|
|
2538
|
+
newBuf[1] = this[ptr + 3];
|
|
2539
|
+
newBuf[2] = this[ptr + 0];
|
|
2540
|
+
newBuf[3] = this[ptr + 1];
|
|
2541
|
+
return newBuf.readUInt32BE(0);
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
Buffer.prototype.setUInt32BESwap = function(val,ptr) {
|
|
2545
|
+
var newBuf = Buffer.alloc(4);
|
|
2546
|
+
newBuf.writeUInt32BE(Math.round(val), 0);
|
|
2547
|
+
this[ptr + 2] = newBuf[0];
|
|
2548
|
+
this[ptr + 3] = newBuf[1];
|
|
2549
|
+
this[ptr + 0] = newBuf[2];
|
|
2550
|
+
this[ptr + 1] = newBuf[3];
|
|
2551
|
+
return ptr + 4;
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
Buffer.prototype.addByte = function (v) {
|
|
2555
|
+
if (this.pointer == undefined)
|
|
2556
|
+
this.pointer = 0;
|
|
2557
|
+
this.writeUInt8(v, this.pointer);
|
|
2558
|
+
this.pointer += 1;
|
|
2559
|
+
return this.pointer;
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
Buffer.prototype.addUint16LE = function (v) {
|
|
2563
|
+
if (this.pointer == undefined)
|
|
2564
|
+
this.pointer = 0;
|
|
2565
|
+
this.writeUInt16LE(v, this.pointer);
|
|
2566
|
+
this.pointer += 2;
|
|
2567
|
+
return this.pointer;
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
Buffer.prototype.addUint32LE = function (v) {
|
|
2571
|
+
if (this.pointer == undefined)
|
|
2572
|
+
this.pointer = 0;
|
|
2573
|
+
this.writeUInt32LE(v, this.pointer);
|
|
2574
|
+
this.pointer += 4;
|
|
2575
|
+
return this.pointer;
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
//https://dl.mitsubishielectric.com/dl/fa/document/manual/plc/sh080008/sh080008x.pdf PG 464
|
|
2579
|
+
MCProtocol.prototype.enumMaxWordLength = _enum({
|
|
2580
|
+
batchReadWordUnits04010000: { //Batch read in word units (command: 0401) PG86
|
|
2581
|
+
description: "Batch read in word units",
|
|
2582
|
+
command: 0x0401, subCommand: 0x0000,
|
|
2583
|
+
"BITDevice": {A:64, QnA:480, Q:960, L:960 /*, R:960 does iQR support subCommand 0x0? */},
|
|
2584
|
+
"WORDDevice": {A:32, QnA:480, Q:960, L:960/*, R:960 does iQR support subCommand 0x0? */},
|
|
2585
|
+
},
|
|
2586
|
+
batchReadWordUnits04010002: { //Batch read in word units (command: 0401) PG86
|
|
2587
|
+
description: "Batch read in word units",
|
|
2588
|
+
command: 0x0401, subCommand: 0x0002,
|
|
2589
|
+
"BITDevice": {R:960},
|
|
2590
|
+
"WORDDevice": {R:960},
|
|
2591
|
+
},
|
|
2592
|
+
batchReadBitUnits04010001: { //Batch read in bit units (command: 0401) PG90
|
|
2593
|
+
description: "Batch read in word units",
|
|
2594
|
+
command: 0x0401, subCommand: 0x0001,
|
|
2595
|
+
"BITDevice": {A:64, QnA:896, Q:1792, L:1792 /*, R:1792 does iQR support subCommand 0x1? */},
|
|
2596
|
+
"WORDDevice": {A:64, QnA:896, Q:1792, L:1792/*, R:1792 does iQR support subCommand 0x1? */},
|
|
2597
|
+
},
|
|
2598
|
+
batchReadBitUnits04010003: { //Batch read in bit units (command: 0401) PG90
|
|
2599
|
+
description: "Batch read in word units",
|
|
2600
|
+
command: 0x0401, subCommand: 0x0003,
|
|
2601
|
+
"BITDevice": {R:1792},
|
|
2602
|
+
"WORDDevice": {R:1792},
|
|
2603
|
+
},
|
|
2604
|
+
batchWriteWordUnits14010000: { //Batch Write in word units (command: 1401) PG92
|
|
2605
|
+
description: "Batch Write in word units",
|
|
2606
|
+
command: 0x1401, subCommand: 0x0000,
|
|
2607
|
+
"BITDevice": {A:10, QnA:480, Q:960, L:960 /*, R:960 does iQR support subCommand 0x0? */},
|
|
2608
|
+
"WORDDevice": {A:64, QnA:480, Q:960, L:960/*, R:960 does iQR support subCommand 0x0? */},
|
|
2609
|
+
"DWORDDevice": {Q:960, L:960/*, R:960 does iQR support subCommand 0x0? */},
|
|
2610
|
+
},
|
|
2611
|
+
batchWriteWordUnits14010002: { //Batch Write in word units (command: 1401) PG92
|
|
2612
|
+
description: "Batch Write in word units",
|
|
2613
|
+
command: 0x1401, subCommand: 0x0002,
|
|
2614
|
+
"BITDevice": {R:960},
|
|
2615
|
+
"WORDDevice": {R:960},
|
|
2616
|
+
"DWORDDevice": {R:960},
|
|
2617
|
+
},
|
|
2618
|
+
batchWriteBitUnits14010001: { //Batch Write in Bit units (command: 1401) PG95
|
|
2619
|
+
description: "Batch Write in Bit units",
|
|
2620
|
+
command: 0x1401, subCommand: 0x0001,
|
|
2621
|
+
"BITDevice": {A:10, QnA:896, Q:1792, L:1792 /*, R:1792 does iQR support subCommand 0x0? */},
|
|
2622
|
+
"WORDDevice": {A:64, QnA:896, Q:1792, L:1792/*, R:1792 does iQR support subCommand 0x0? */},
|
|
2623
|
+
"DWORDDevice": {Q:960, L:960/*, R:960 does iQR support subCommand 0x0? */},
|
|
2624
|
+
},
|
|
2625
|
+
batchWriteBitUnits14010003: { //Batch Write in Bit units (command: 1401) PG95
|
|
2626
|
+
description: "Batch Write in Bit units",
|
|
2627
|
+
command: 0x1401, subCommand: 0x0003,
|
|
2628
|
+
"BITDevice": {R:1792},
|
|
2629
|
+
"WORDDevice": {R:1792},
|
|
2630
|
+
"DWORDDevice": {R:1792},
|
|
2631
|
+
},
|
|
2632
|
+
|
|
2633
|
+
});
|
|
2634
|
+
|
|
2635
|
+
MCProtocol.prototype.enumPLCTypes = _enum({
|
|
2636
|
+
"A": {wordDeviceMax: 64, bitWriteDeviceMax : 32 /*160 points PG96*/, bitReadDeviceMax : 64 /*256 points PG91*/, requiredWriteBufferSize: ((64*2) + 30/*for frame etc*/) },
|
|
2637
|
+
"QnA": {wordDeviceMax: 480, bitWriteDeviceMax: 480, bitReadDeviceMax: 480, requiredWriteBufferSize: ((480*2) + 30 /*for frame etc*/)},
|
|
2638
|
+
"Q": {wordDeviceMax: 960, bitWriteDeviceMax: 960, bitReadDeviceMax: 960, doubleWordDeviceMax: 960, requiredWriteBufferSize: ((960*2) + 30 /*for frame etc*/)},
|
|
2639
|
+
"L": {wordDeviceMax: 960, bitWriteDeviceMax: 960, bitReadDeviceMax: 960, doubleWordDeviceMax: 960, requiredWriteBufferSize: ((960*2) + 30 /*for frame etc*/)},
|
|
2640
|
+
'R': {wordDeviceMax: 960, bitWriteDeviceMax: 960, bitReadDeviceMax: 960, doubleWordDeviceMax: 960, requiredWriteBufferSize: ((960*2) + 30 /*for frame etc*/)},
|
|
2641
|
+
});
|
|
2642
|
+
|
|
2643
|
+
MCProtocol.prototype.enumOPCQuality = _enum({
|
|
2644
|
+
good: { name: 'good', desc: 'Good', value: 0xC0 }, //(192)Good
|
|
2645
|
+
goodForced: { name: 'goodForced', desc: 'Good - Local Override, Value Forced', value: 0xD8 }, //(216)Good - Local Override, Value Forced
|
|
2646
|
+
bad: { name: 'bad', desc: 'Bad', value: 0x0 }, //(0)Bad
|
|
2647
|
+
badConfigErrInServer: { name: 'badConfigErrInServer', desc: 'Bad - Configuration Error in Server', value: 0x4 }, //(4)Bad - Configuration Error in Server
|
|
2648
|
+
badNotConnected: { name: 'badNotConnected', desc: 'Bad - Not Connected', value: 0x8 }, //(8)Bad - Not Connected
|
|
2649
|
+
badDeviceFailure: { name: 'badDeviceFailure', desc: 'Bad - Device Failure', value: 0xC }, //(12)Bad - Device Failure
|
|
2650
|
+
badSensorFailure: { name: 'badSensorFailure', desc: 'Bad - Sensor Failure', value: 0x10 }, //(16)Bad - Sensor Failure
|
|
2651
|
+
badLastKnownValPassed: { name: 'badLastKnownValPassed', desc: 'Bad - Last Know Value Passed', value: 0x14 }, //(20)Bad - Last Know Value Passed
|
|
2652
|
+
badCommFailure: { name: 'badCommFailure', desc: 'Bad - Comm Failure', value: 0x18 }, //(24)Bad - Comm Failure
|
|
2653
|
+
badItemInactive: { name: 'badItemInactive', desc: 'Bad - Item Set InActive', value: 0x1C }, //(28)Bad - Item Set InActive
|
|
2654
|
+
uncertain: { name: 'uncertain', desc: 'Uncertain', value: 0x40 }, //(64)Uncertain
|
|
2655
|
+
uncertainLastUsableValue: { name: 'uncertainLastUsableValue', desc: 'Uncertain - Last Usable Value - timeout of some kind', value: 0x44 }, //(68)Uncertain - Last Usable Value - timeout of some kind
|
|
2656
|
+
uncertainSensorNotAccurate: { name: 'uncertainSensorNotAccurate', desc: 'Uncertain - Sensor not Accurate - outside of limits', value: 0x50 }, //(80)Uncertain - Sensor not Accurate - outside of limits
|
|
2657
|
+
uncertainEngUnitsExceeded: { name: 'uncertainEngUnitsExceeded', desc: 'Uncertain - Engineering Units exceeded', value: 0x54 }, //(84)Uncertain - Engineering Units exceeded
|
|
2658
|
+
uncertainMultipleSources: { name: 'uncertainMultipleSources', desc: 'Uncertain - Value from multiple sources - with less then required good values', value: 0x58 }, //(88)Uncertain - Value from multiple sources - with less then required good values
|
|
2659
|
+
unknown: { name: 'unknown', desc: 'Unknown', value: 0xff }, //(255)Unknown
|
|
2660
|
+
});
|
|
2661
|
+
|
|
2662
|
+
//https://dl.mitsubishielectric.com/dl/fa/document/manual/plc/sh080008/sh080008x.pdf PG 68
|
|
2663
|
+
MCProtocol.prototype.enumDeviceCodeSpecQ = _enum({
|
|
2664
|
+
SM: {symbol: 'SM', type: 'BIT', notation: 'Decimal', binary: 0x91, ascii: 'SM', description: 'Special relay'},
|
|
2665
|
+
SD: {symbol: 'SD', type: 'WORD', notation: 'Decimal', binary: 0xA9, ascii: 'SD', description: 'Special register'},
|
|
2666
|
+
TS: {symbol: 'TS', type: 'BIT', notation: 'Decimal', binary: 0xC1, ascii: 'TS', description: 'Timer Contact'},
|
|
2667
|
+
TC: {symbol: 'TC', type: 'BIT', notation: 'Decimal', binary: 0xC0, ascii: '', description: 'Timer Coil'},
|
|
2668
|
+
TN: {symbol: 'TN', type: 'WORD', notation: 'Decimal', binary: 0xC2, ascii: '', description: 'Timer Current value'},
|
|
2669
|
+
STS: {symbol: 'STS', type: 'BIT', notation: 'Decimal', binary: 0xC7, ascii: 'SS', description: 'Retentive timer Contact'},
|
|
2670
|
+
STC: {symbol: 'STC', type: 'BIT', notation: 'Decimal', binary: 0xC6, ascii: 'SC', description: 'Retentive timer Coil'},
|
|
2671
|
+
STN: {symbol: 'STN', type: 'WORD', notation: 'Decimal', binary: 0xC8, ascii: 'SN', description: 'Retentive timer Current value'},
|
|
2672
|
+
CS: {symbol: 'CS', type: 'BIT', notation: 'Decimal', binary: 0xC4, ascii: 'CS', description: 'Counter Contact CS'},
|
|
2673
|
+
CC: {symbol: 'CC', type: 'BIT', notation: 'Decimal', binary: 0xC3, ascii: 'CC', description: 'Counter Coil CC'},
|
|
2674
|
+
CN: {symbol: 'CN', type: 'WORD', notation: 'Decimal', binary: 0xC5, ascii: 'CN', description: 'Counter Current value CN'},
|
|
2675
|
+
SB: {symbol: 'SB', type: 'BIT', notation: 'Hexadecimal', binary: 0xA1, ascii: 'SB', description: 'Link special relay'},
|
|
2676
|
+
SW: {symbol: 'SW', type: 'WORD', notation: 'Hexadecimal', binary: 0xB5, ascii: 'SW', description: 'Link special register'},
|
|
2677
|
+
DX: {symbol: 'DX', type: 'BIT', notation: 'Hexadecimal', binary: 0xA2, ascii: 'DX', description: 'Direct access input'},
|
|
2678
|
+
DY: {symbol: 'DY', type: 'BIT', notation: 'Hexadecimal', binary: 0xA3, ascii: 'DY', description: 'Direct access output'},
|
|
2679
|
+
//ZR: {symbol: 'ZR', type: 'WORD', notation: 'Hexadecimal', binary: 0xB0, ascii: 'ZR', description: 'File register'}, << Manual says hex, its actually decimal!!!
|
|
2680
|
+
ZR: {symbol: 'ZR', type: 'WORD', notation: 'Decimal', binary: 0xB0, ascii: 'ZR', description: 'File register'},
|
|
2681
|
+
X: {symbol: 'X', type: 'BIT', notation: 'Hexadecimal', binary: 0x9C, ascii: 'X*', description: 'Input'},
|
|
2682
|
+
Y: {symbol: 'Y', type: 'BIT', notation: 'Hexadecimal', binary: 0x9D, ascii: 'Y*', description: 'Output'},
|
|
2683
|
+
M: {symbol: 'M', type: 'BIT', notation: 'Decimal', binary: 0x90, ascii: 'M*', description: 'Internal relay'},
|
|
2684
|
+
L: {symbol: 'L', type: 'BIT', notation: 'Decimal', binary: 0x92, ascii: 'L*', description: 'Latch relay'},
|
|
2685
|
+
F: {symbol: 'F', type: 'BIT', notation: 'Decimal', binary: 0x93, ascii: 'F*', description: 'Annunciator'},
|
|
2686
|
+
V: {symbol: 'V', type: 'BIT', notation: 'Decimal', binary: 0x94, ascii: 'V*', description: 'Edge relay'},
|
|
2687
|
+
B: {symbol: 'B', type: 'BIT', notation: 'Hexadecimal', binary: 0xA0, ascii: 'B*', description: 'Link relay'},
|
|
2688
|
+
D: {symbol: 'D', type: 'WORD', notation: 'Decimal', binary: 0xA8, ascii: 'D*', description: 'Data register'},
|
|
2689
|
+
W: {symbol: 'W', type: 'WORD', notation: 'Hexadecimal', binary: 0xB4, ascii: 'W*', description: 'Link register'},
|
|
2690
|
+
Z: {symbol: 'Z', type: 'WORD', notation: 'Decimal', binary: 0xCC, ascii: 'Z*', description: 'Index register'},
|
|
2691
|
+
R: {symbol: 'R', type: 'WORD', notation: 'Decimal', binary: 0xAF, ascii: 'R*', description: 'File register'},
|
|
2692
|
+
//D: {symbol: 'D', type: 'WORD', notation: 'Decimal', binary: 0xA8, ascii: 'D*', description: 'Extended data register*4'},
|
|
2693
|
+
//W: {symbol: 'W', type: 'WORD', notation: 'Hexadecimal', binary: 0xB4, ascii: 'W*', description: 'Extended link register*4'},
|
|
2694
|
+
});
|
|
2695
|
+
MCProtocol.prototype.enumDeviceCodeSpecR = _enum({
|
|
2696
|
+
SM: {symbol: 'SM', type: 'BIT', notation: 'Decimal', binary: 0x0091, ascii: 'SM**', description: 'Special relay'},
|
|
2697
|
+
SD: {symbol: 'SD', type: 'WORD', notation: 'Decimal', binary: 0x00A9, ascii: 'SD**', description: 'Special register'},
|
|
2698
|
+
TS: {symbol: 'TS', type: 'BIT', notation: 'Decimal', binary: 0x00C1, ascii: 'TS**', description: 'Timer Contact'},
|
|
2699
|
+
TC: {symbol: 'TC', type: 'BIT', notation: 'Decimal', binary: 0x00C0, ascii: 'TC**', description: 'Timer Coil'},
|
|
2700
|
+
TN: {symbol: 'TN', type: 'WORD', notation: 'Decimal', binary: 0x00C2, ascii: 'TN**', description: 'Timer Current value'},
|
|
2701
|
+
LTS: {symbol: 'LTS', type: 'BIT', notation: 'Decimal', binary: 0x0051, ascii: 'LTS*', description: 'Long timer, Contact'},
|
|
2702
|
+
LTC: {symbol: 'LTC', type: 'BIT', notation: 'Decimal', binary: 0x0050, ascii: 'LTC*', description: 'Long timer, Coil'},
|
|
2703
|
+
LTN: {symbol: 'LTN', type: 'DWORD', notation: 'Decimal', binary: 0x0052, ascii: 'LTN*', description: 'Long timer, Current value'},
|
|
2704
|
+
STS: {symbol: 'STS', type: 'BIT', notation: 'Decimal', binary: 0x00C7, ascii: 'STS*', description: 'Retentive timer Contact'},
|
|
2705
|
+
STC: {symbol: 'STC', type: 'BIT', notation: 'Decimal', binary: 0x00C6, ascii: 'STC*', description: 'Retentive timer Coil'},
|
|
2706
|
+
STN: {symbol: 'STN', type: 'WORD', notation: 'Decimal', binary: 0x00C8, ascii: 'STN*', description: 'Retentive timer Current value'},
|
|
2707
|
+
LSTS: {symbol: 'LSTS', type: 'BIT', notation: 'Decimal', binary: 0x0059, ascii: 'LSTS', description: 'Long retentive timer, Contact'},
|
|
2708
|
+
LSTC: {symbol: 'LSTC', type: 'BIT', notation: 'Decimal', binary: 0x0058, ascii: 'LSTC', description: 'Long retentive timer, Coil'},
|
|
2709
|
+
LSTN: {symbol: 'LSTN', type: 'DWORD', notation: 'Decimal', binary: 0x005A, ascii: 'LSTN', description: 'Long retentive timer, Current value'},
|
|
2710
|
+
CS: {symbol: 'CS', type: 'BIT', notation: 'Decimal', binary: 0x00C4, ascii: 'CS**', description: 'Counter Contact CS'},
|
|
2711
|
+
CC: {symbol: 'CC', type: 'BIT', notation: 'Decimal', binary: 0x00C3, ascii: 'CC**', description: 'Counter Coil CC'},
|
|
2712
|
+
CN: {symbol: 'CN', type: 'WORD', notation: 'Decimal', binary: 0x00C5, ascii: 'CN**', description: 'Counter Current value CN'},
|
|
2713
|
+
LCS: {symbol: 'LCS', type: 'BIT', notation: 'Decimal', binary: 0x0055, ascii: 'LCS*', description: 'Long counter, Contact'},
|
|
2714
|
+
LCC: {symbol: 'LCC', type: 'BIT', notation: 'Decimal', binary: 0x0054, ascii: 'LCC*', description: 'Long counter, Coil'},
|
|
2715
|
+
LCN: {symbol: 'LCN', type: 'DWORD', notation: 'Decimal', binary: 0x0056, ascii: 'LCN*', description: 'Long counter, Current value'},
|
|
2716
|
+
SB: {symbol: 'SB', type: 'BIT', notation: 'Hexadecimal', binary: 0x00A1, ascii: 'SB**', description: 'Link special relay'},
|
|
2717
|
+
SW: {symbol: 'SW', type: 'WORD', notation: 'Hexadecimal', binary: 0x00B5, ascii: 'SW**', description: 'Link special register'},
|
|
2718
|
+
DX: {symbol: 'DX', type: 'BIT', notation: 'Hexadecimal', binary: 0x00A2, ascii: 'DX**', description: 'Direct access input'},
|
|
2719
|
+
DY: {symbol: 'DY', type: 'BIT', notation: 'Hexadecimal', binary: 0x00A3, ascii: 'DY**', description: 'Direct access output'},
|
|
2720
|
+
LZ: {symbol: 'LZ', type: 'DWORD', notation: 'Decimal', binary: 0x0062, ascii: 'LZ**', description: 'Long index register'},
|
|
2721
|
+
ZR: {symbol: 'ZR', type: 'WORD', notation: 'Hexadecimal', binary: 0x00B0, ascii: 'ZR**', description: 'File register'},
|
|
2722
|
+
Z: {symbol: 'Z', type: 'WORD', notation: 'Decimal', binary: 0x00CC, ascii: 'Z***', description: 'Index register'},
|
|
2723
|
+
R: {symbol: 'R', type: 'WORD', notation: 'Decimal', binary: 0x00AF, ascii: 'R***', description: 'File register'},
|
|
2724
|
+
X: {symbol: 'X', type: 'BIT', notation: 'Hexadecimal', binary: 0x009C, ascii: 'X***', description: 'Input'},
|
|
2725
|
+
Y: {symbol: 'Y', type: 'BIT', notation: 'Hexadecimal', binary: 0x009D, ascii: 'Y***', description: 'Output'},
|
|
2726
|
+
M: {symbol: 'M', type: 'BIT', notation: 'Decimal', binary: 0x0090, ascii: 'M***', description: 'Internal relay'},
|
|
2727
|
+
L: {symbol: 'L', type: 'BIT', notation: 'Decimal', binary: 0x0092, ascii: 'L***', description: 'Latch relay'},
|
|
2728
|
+
F: {symbol: 'F', type: 'BIT', notation: 'Decimal', binary: 0x0093, ascii: 'F***', description: 'Annunciator'},
|
|
2729
|
+
V: {symbol: 'V', type: 'BIT', notation: 'Decimal', binary: 0x0094, ascii: 'V***', description: 'Edge relay'},
|
|
2730
|
+
B: {symbol: 'B', type: 'BIT', notation: 'Hexadecimal', binary: 0x00A0, ascii: 'B***', description: 'Link relay'},
|
|
2731
|
+
D: {symbol: 'D', type: 'WORD', notation: 'Decimal', binary: 0x00A8, ascii: 'D***', description: 'Data register'},
|
|
2732
|
+
W: {symbol: 'W', type: 'WORD', notation: 'Hexadecimal', binary: 0x00B4, ascii: 'W***', description: 'Link register'},
|
|
2733
|
+
});
|
|
2734
|
+
|
|
2735
|
+
MCProtocol.prototype.enumDeviceCodeSpec1E = _enum({
|
|
2736
|
+
X: {symbol: 'X', type: 'BIT', notation: 'Hexadecimal', binary: 0x5820, ascii: 'X ', description: 'Input'},
|
|
2737
|
+
Y: {symbol: 'Y', type: 'BIT', notation: 'Hexadecimal', binary: 0x5920, ascii: 'Y ', description: 'Output'},
|
|
2738
|
+
M: {symbol: 'M', type: 'BIT', notation: 'Decimal', binary: 0x4D20, ascii: 'M ', description: 'Internal relay'},
|
|
2739
|
+
L: {symbol: 'L', type: 'BIT', notation: 'Decimal', binary: 0x4D20, ascii: 'M ', description: 'Internal relay'},
|
|
2740
|
+
S: {symbol: 'S', type: 'BIT', notation: 'Decimal', binary: 0x4D20, ascii: 'M ', description: 'Internal relay'},
|
|
2741
|
+
F: {symbol: 'F', type: 'BIT', notation: 'Decimal', binary: 0x4620, ascii: 'F ', description: 'Annunciator'},
|
|
2742
|
+
B: {symbol: 'B', type: 'BIT', notation: 'Hexadecimal', binary: 0x4220, ascii: 'B ', description: 'Link relay'},
|
|
2743
|
+
TN: {symbol: 'TN', type: 'WORD', notation: 'Decimal', binary: 0x544E, ascii: 'TN', description: 'Timer Current value'},
|
|
2744
|
+
TS: {symbol: 'TS', type: 'BIT', notation: 'Decimal', binary: 0x5453, ascii: 'TS', description: 'Timer Contact'},
|
|
2745
|
+
TC: {symbol: 'TC', type: 'BIT', notation: 'Decimal', binary: 0x5443, ascii: 'TC', description: 'Timer Coil'},
|
|
2746
|
+
CN: {symbol: 'CN', type: 'WORD', notation: 'Decimal', binary: 0x434E, ascii: 'CN', description: 'Counter Current value'},
|
|
2747
|
+
CS: {symbol: 'CS', type: 'BIT', notation: 'Decimal', binary: 0x4353, ascii: 'CS', description: 'Counter Contact'},
|
|
2748
|
+
CC: {symbol: 'CC', type: 'BIT', notation: 'Decimal', binary: 0x4343, ascii: 'CC', description: 'Counter Coil'},
|
|
2749
|
+
D: {symbol: 'D', type: 'WORD', notation: 'Decimal', binary: 0x4420, ascii: 'D ', description: 'Data register'},
|
|
2750
|
+
W: {symbol: 'W', type: 'WORD', notation: 'Hexadecimal', binary: 0x5720, ascii: 'W ', description: 'Link register'},
|
|
2751
|
+
R: {symbol: 'R', type: 'WORD', notation: 'Decimal', binary: 0x5220, ascii: 'R ', description: 'File register'},
|
|
2752
|
+
});
|
|
2753
|
+
|
|
2754
|
+
MCProtocol.prototype.enumDeviceCodeSpecL = MCProtocol.prototype.enumDeviceCodeSpecQ;
|
|
2755
|
+
//MCProtocol.prototype.enumDeviceCodeSpec = MCProtocol.prototype.enumDeviceCodeSpecQ;//default to Q/L series
|
|
2756
|
+
|
|
2757
|
+
MCProtocol.prototype.enumDataTypes = _enum({
|
|
2758
|
+
REAL: { name: "REAL", dataLength: 4, badValue: 0.0 },
|
|
2759
|
+
FLOAT: { name: "FLOAT", dataLength: 4, badValue: 0.0 },
|
|
2760
|
+
DWORD: { name: "DWORD", dataLength: 4, badValue: 0 },
|
|
2761
|
+
DINT: { name: "DINT", dataLength: 4, badValue: 0 },
|
|
2762
|
+
WORD: { name: "WORD", dataLength: 2, badValue: 0 },
|
|
2763
|
+
UINT: { name: "UINT", dataLength: 2, badValue: 0 },
|
|
2764
|
+
INT: { name: "INT", dataLength: 2, badValue: 0 },
|
|
2765
|
+
STR: { name: "STR", dataLength: 1, badValue: '' },
|
|
2766
|
+
CHAR: { name: "CHAR", dataLength: 1, badValue: '' },
|
|
2767
|
+
BYTE: { name: "BYTE", dataLength: 1, badValue: 0 },
|
|
2768
|
+
BIT: { name: "BIT", dataLength: 1, badValue: false },
|
|
2769
|
+
});
|
|
2770
|
+
|
|
2771
|
+
|
|
2772
|
+
MCProtocol.prototype.enumSendResult = _enum({
|
|
2773
|
+
unknown: 0,
|
|
2774
|
+
notSent: -1,
|
|
2775
|
+
queueFull: -2,
|
|
2776
|
+
badRequest: -3,
|
|
2777
|
+
sent: 1,
|
|
2778
|
+
queued: 2,
|
|
2779
|
+
});
|
|
2780
|
+
|
|
2781
|
+
function decimalToHexString(number) {
|
|
2782
|
+
if (number < 0) {
|
|
2783
|
+
number = 0xFFFFFFFF + number + 1;
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
return "0x" + number.toString(16).toUpperCase();
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
function PLCPacket() {
|
|
2790
|
+
this.seqNum = undefined; // Made-up sequence number to watch for.
|
|
2791
|
+
this.itemList = undefined; // This will be assigned the object that details what was in the request.
|
|
2792
|
+
this.reqTime = undefined;
|
|
2793
|
+
this.sent = false; // Have we sent the packet yet?
|
|
2794
|
+
this.rcvd = false; // Are we waiting on a reply?
|
|
2795
|
+
this.timeoutError = undefined; // The packet is marked with error on timeout so we don't then later switch to good data.
|
|
2796
|
+
this.timeout = undefined; // The timeout for use with clearTimeout()
|
|
2797
|
+
this.isWritePacket = false;
|
|
2798
|
+
this.isReadPacket = false;
|
|
2799
|
+
this.command = undefined;
|
|
2800
|
+
this.subCommand = undefined;
|
|
2801
|
+
this.subHeader = undefined;
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
|
|
2805
|
+
function PLCReadResult(TAG, addr, quality, timeTaken, value, valueType) {
|
|
2806
|
+
this.isGood = isQualityOK(quality);
|
|
2807
|
+
this.quality = quality;
|
|
2808
|
+
this.TAG = TAG;
|
|
2809
|
+
this.addr = addr;
|
|
2810
|
+
this.timeTaken = timeTaken;
|
|
2811
|
+
this.timeStamp = Date.now();
|
|
2812
|
+
this.value = value;//TODO: consider sending clone?
|
|
2813
|
+
this.valueType = valueType;
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
function PLCWriteResult(TAG, addr, quality, timeTaken) {
|
|
2817
|
+
this.isGood = isQualityOK(quality);
|
|
2818
|
+
this.quality = quality;
|
|
2819
|
+
this.TAG = TAG;
|
|
2820
|
+
this.addr = addr;
|
|
2821
|
+
this.timeTaken = timeTaken;
|
|
2822
|
+
this.timeStamp = Date.now();
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
function PLCItem(owner) { // Object
|
|
2826
|
+
this.owner = owner;
|
|
2827
|
+
this.initialised = false;
|
|
2828
|
+
|
|
2829
|
+
// MC only
|
|
2830
|
+
this.areaMCCode = undefined;
|
|
2831
|
+
this.bitNative = undefined;
|
|
2832
|
+
this.startRegister = undefined;
|
|
2833
|
+
this.byteLengthWrite = undefined;
|
|
2834
|
+
|
|
2835
|
+
// Save the original address
|
|
2836
|
+
this.addr = undefined;
|
|
2837
|
+
this.useraddr = undefined;
|
|
2838
|
+
|
|
2839
|
+
// First group is properties to do with PLC item - these alone define the address.
|
|
2840
|
+
this.deviceCode = undefined;
|
|
2841
|
+
this.datatype = undefined;
|
|
2842
|
+
this.bitOffset = undefined;
|
|
2843
|
+
this.byteOffset = undefined;
|
|
2844
|
+
this.offset = undefined;
|
|
2845
|
+
this.arrayLength = undefined;
|
|
2846
|
+
this.totalArrayLength = undefined;
|
|
2847
|
+
|
|
2848
|
+
|
|
2849
|
+
|
|
2850
|
+
this.maxWordLength1E = function (subHeader) {
|
|
2851
|
+
/* PG 468 */
|
|
2852
|
+
var plcType = this.plcType;
|
|
2853
|
+
switch (subHeader) {
|
|
2854
|
+
case 0x00: // Batch read Bit units 256 points (4 bits per WD... 256/4=64 WDs)
|
|
2855
|
+
if(plcType == "A" && this.deviceCode == "X") //
|
|
2856
|
+
return 32; //*1 For ACPU ... If only X is specified, the number of points processed per one communication will be one half the value
|
|
2857
|
+
return 64;
|
|
2858
|
+
case 0x01: // Batch read Word units, Bit device 128 words (2048 points), Word device 256 points
|
|
2859
|
+
if(this.isBitDevice()){
|
|
2860
|
+
return 128; //Bit device 128 words (2048 points)
|
|
2861
|
+
} else {
|
|
2862
|
+
return 255;//256; //Word device 256 points but as the send data for this element is BYTE, 256 will cause overflow. We could handle this but its easier to just reduce maxsize by 1
|
|
2863
|
+
}
|
|
2864
|
+
case 0x02: // Batch write Bit units 256 points (4 bits per WD... 256/4=64 WDs)
|
|
2865
|
+
if(plcType == "A" && this.deviceCode == "X") //
|
|
2866
|
+
return 32; //*1 For ACPU ... If only X is specified, the number of points processed per one communication will be one half the value
|
|
2867
|
+
return 64;
|
|
2868
|
+
case 0x03: // Batch write "Word units Bit device 40 words (640 points)" / "Word device 256 points"
|
|
2869
|
+
if(plcType == "A" && this.deviceCode == "X") //
|
|
2870
|
+
return 20; //*1 For ACPU ... If only X is specified, the number of points processed per one communication will be one half the value
|
|
2871
|
+
return 40;
|
|
2872
|
+
case 0x17: // Extended file register Batch read 256 points
|
|
2873
|
+
return 256;
|
|
2874
|
+
case 0x18: // Extended file register Batch write 256 points
|
|
2875
|
+
return 256;
|
|
2876
|
+
default:
|
|
2877
|
+
outputLog('Failed to find a match for 1E subheader ' + subHeader + ' possibly because that function is not supported yet.', "WARN");
|
|
2878
|
+
return undefined;
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
this.maxWordLength = function () {
|
|
2884
|
+
/* PG87, 464
|
|
2885
|
+
Target Word Device Bit Device Double Word Device
|
|
2886
|
+
A series 1 to 64 points 1 to 32 words (1 to 512 points)
|
|
2887
|
+
QnA 1 to 480 points 1 to 480 words (1 to 7680 points)
|
|
2888
|
+
Q/L/iQR 1 to 960 points 1 to 960 words (1 to 15360 points) 1 to 960 words (LCN: 1 to 480 points) (LTN, LSTN: 1 to 240 points)
|
|
2889
|
+
*/
|
|
2890
|
+
|
|
2891
|
+
if(this.frame == "1E"){
|
|
2892
|
+
var subHeader = this.subHeader;
|
|
2893
|
+
return this.maxWordLength1E(subHeader);
|
|
2894
|
+
} else {
|
|
2895
|
+
try {
|
|
2896
|
+
var maxWordLengthSpec;
|
|
2897
|
+
var command = this.command;
|
|
2898
|
+
var subCommand = this.subCommand;
|
|
2899
|
+
var filteredKey = MCProtocol.prototype.enumMaxWordLength.keys.filter(function (key) {
|
|
2900
|
+
let item = MCProtocol.prototype.enumMaxWordLength[key];
|
|
2901
|
+
return item.command == command && item.subCommand == subCommand;
|
|
2902
|
+
});
|
|
2903
|
+
maxWordLengthSpec = MCProtocol.prototype.enumMaxWordLength[filteredKey];
|
|
2904
|
+
var deviceType = this.deviceCodeSpec.type + "Device";
|
|
2905
|
+
|
|
2906
|
+
if(maxWordLengthSpec && maxWordLengthSpec[deviceType] && maxWordLengthSpec[deviceType][this.plcType] ){
|
|
2907
|
+
return maxWordLengthSpec[deviceType][this.plcType];
|
|
2908
|
+
} else {
|
|
2909
|
+
throw new Error(`Failed to find a max device count for frame '${this.frame}' command '${command}', subCommand '${subCommand}' possibly because that function is not supported yet.`);
|
|
2910
|
+
}
|
|
2911
|
+
} catch (error) {
|
|
2912
|
+
outputLog(`Error: ${error}`, "ERROR");
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
return undefined;
|
|
2916
|
+
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
|
|
2920
|
+
this.dataTypeByteLength = function () {
|
|
2921
|
+
if (typeof (this.deviceCode) === 'undefined') {
|
|
2922
|
+
return 1;
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
let dt = MCProtocol.prototype.enumDataTypes[this.datatype];
|
|
2926
|
+
if (dt) {
|
|
2927
|
+
return dt.dataLength;
|
|
2928
|
+
} else {
|
|
2929
|
+
outputLog(`Failed to find a match for '${this.addr}' possibly because the data type '${this.datatype}' is not supported yet.`, "WARN");
|
|
2930
|
+
return undefined;
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
|
|
2936
|
+
// These next properties can be calculated from the above properties, and may be converted to functions.
|
|
2937
|
+
this.dtypelen = undefined;
|
|
2938
|
+
this.multidtypelen = undefined; // multi-datatype length. Different than dtypelen when requesting a timer preset, for example, which has width two but dtypelen of 2.
|
|
2939
|
+
this.areaMCCode = undefined;
|
|
2940
|
+
this.byteLength = undefined;
|
|
2941
|
+
this.byteLengthWithFill = undefined;
|
|
2942
|
+
|
|
2943
|
+
// Note that read transport codes and write transport codes will be the same except for bits which are read as bytes but written as bits
|
|
2944
|
+
this.readTransportCode = undefined;
|
|
2945
|
+
this.writeTransportCode = undefined;
|
|
2946
|
+
|
|
2947
|
+
// // This is where the data can go that arrives in the packet, before calculating the value.
|
|
2948
|
+
// this.byteBuffer = undefined;//new Buffer(8192); //now initialised to correct size - when needed
|
|
2949
|
+
// this.writeBuffer = undefined;//new Buffer(8192); //now initialised to correct size - when needed
|
|
2950
|
+
|
|
2951
|
+
// // We use the "quality buffer" to keep track of whether or not the requests were successful.
|
|
2952
|
+
// // Otherwise, it is too easy to lose track of arrays that may only be partially complete.
|
|
2953
|
+
// this.qualityBuffer = undefined;//new Buffer(8192); //now initialised to correct size - when needed
|
|
2954
|
+
// this.writeQualityBuffer = undefined;//new Buffer(8192); //now initialised to correct size - when needed
|
|
2955
|
+
|
|
2956
|
+
// This is where the data can go that arrives in the packet, before calculating the value.
|
|
2957
|
+
this.byteBuffer = Buffer.alloc(8192);
|
|
2958
|
+
this.writeBuffer = Buffer.alloc(8192);
|
|
2959
|
+
|
|
2960
|
+
// We use the "quality buffer" to keep track of whether or not the requests were successful.
|
|
2961
|
+
// Otherwise, it is too easy to lose track of arrays that may only be partially complete.
|
|
2962
|
+
this.qualityBuffer = Buffer.alloc(8192);
|
|
2963
|
+
this.writeQualityBuffer = Buffer.alloc(8192);
|
|
2964
|
+
|
|
2965
|
+
|
|
2966
|
+
// Then we have item properties
|
|
2967
|
+
this.value = undefined;
|
|
2968
|
+
this.writeValue = undefined;
|
|
2969
|
+
this.valid = false;
|
|
2970
|
+
this.lastError = undefined;
|
|
2971
|
+
this.frame = undefined;
|
|
2972
|
+
this.plcType = undefined;
|
|
2973
|
+
|
|
2974
|
+
|
|
2975
|
+
// Then we have result properties
|
|
2976
|
+
this.part = undefined;
|
|
2977
|
+
this.maxPart = undefined;
|
|
2978
|
+
|
|
2979
|
+
// Block properties
|
|
2980
|
+
this.isOptimized = false;
|
|
2981
|
+
this.resultReference = undefined;
|
|
2982
|
+
this.itemReference = undefined;
|
|
2983
|
+
|
|
2984
|
+
this.isWORDDevice = function() {
|
|
2985
|
+
return _isWORDDevice(this);
|
|
2986
|
+
}
|
|
2987
|
+
function _isWORDDevice(plcitem) {
|
|
2988
|
+
return plcitem.deviceCodeSpec.type == 'WORD';
|
|
2989
|
+
}
|
|
2990
|
+
this.isDWORDDevice = function() {
|
|
2991
|
+
return _isDWORDDevice(this);
|
|
2992
|
+
}
|
|
2993
|
+
function _isDWORDDevice(plcitem) {
|
|
2994
|
+
return plcitem.deviceCodeSpec.type == 'DWORD';
|
|
2995
|
+
}
|
|
2996
|
+
this.isBitDevice = function () {
|
|
2997
|
+
return _isBitDevice(this);
|
|
2998
|
+
}
|
|
2999
|
+
function _isBitDevice(plcitem) {
|
|
3000
|
+
return plcitem.deviceCodeSpec.type == 'BIT';
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
this.getDeviceCodeSpec = function(){
|
|
3004
|
+
var theItem = this;
|
|
3005
|
+
|
|
3006
|
+
if(theItem.owner && theItem.owner.enumDeviceCodeSpec){
|
|
3007
|
+
return theItem.owner.enumDeviceCodeSpec;
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
if(theItem.enumDeviceCodeSpec){
|
|
3011
|
+
return theItem.enumDeviceCodeSpec;
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
if (typeof (theItem.plcType) === 'undefined') {
|
|
3015
|
+
theItem.enumDeviceCodeSpec = MCProtocol.prototype.enumDeviceCodeSpecQ;//default to Q/L series
|
|
3016
|
+
} else {
|
|
3017
|
+
theItem.enumDeviceCodeSpec = MCProtocol.prototype['enumDeviceCodeSpec' + self.plcType];
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
if (typeof (theItem.frame) === 'undefined') {
|
|
3021
|
+
//
|
|
3022
|
+
} else if(theItem.frame.toUpperCase() == '1E'){
|
|
3023
|
+
theItem.enumDeviceCodeSpec = MCProtocol.prototype.enumDeviceCodeSpec1E;
|
|
3024
|
+
}
|
|
3025
|
+
return theItem.enumDeviceCodeSpec;
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
this.init = function _stringToMCAddr(addr, TAG, octalInputOutput, frame, plcType, writeValue) {
|
|
3029
|
+
"use strict";
|
|
3030
|
+
var theItem = this;
|
|
3031
|
+
var matches, postDotNumeric, forceBitDtype;
|
|
3032
|
+
|
|
3033
|
+
// Save arguments for later use and reference
|
|
3034
|
+
theItem.addr = addr;
|
|
3035
|
+
theItem.useraddr = TAG ? TAG : addr;
|
|
3036
|
+
theItem.frame = frame;
|
|
3037
|
+
theItem.plcType = plcType;
|
|
3038
|
+
|
|
3039
|
+
//initialsiation
|
|
3040
|
+
theItem.initError = "";
|
|
3041
|
+
theItem.initialised = false;
|
|
3042
|
+
theItem.initTime = Date.now();
|
|
3043
|
+
theItem.writeValue = writeValue;
|
|
3044
|
+
|
|
3045
|
+
|
|
3046
|
+
if (TAG === '_COMMERR') {
|
|
3047
|
+
// Special-case for communication error status - this variable returns true when there is a communications error
|
|
3048
|
+
theItem.initError = "_COMMERR";
|
|
3049
|
+
return false;
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
if (!addr) {
|
|
3053
|
+
theItem.initError = "addr parameter is empty";
|
|
3054
|
+
//outputLog("Error - addr is empty.");
|
|
3055
|
+
return false;
|
|
3056
|
+
}
|
|
3057
|
+
//breakdown the address format [DS] DEV [DT] DA [.BIT] [,CNT] ...
|
|
3058
|
+
/*
|
|
3059
|
+
[1] DS - digit specifier (e.g. K4) [optional]
|
|
3060
|
+
[2] DEV - Device (Y|X|D|F|W|B|R|etc)
|
|
3061
|
+
[3] DT - data type (REAL|FLOAT|STR|DWORD|DINT) [optional]
|
|
3062
|
+
[4] DA - Device Address
|
|
3063
|
+
[5] BIT - bit numbeer [optional]
|
|
3064
|
+
[6] CNT - count of items [optional]
|
|
3065
|
+
*/
|
|
3066
|
+
try {
|
|
3067
|
+
addr = addr.toUpperCase();
|
|
3068
|
+
var allowedDigitSpecs = "K2|K4|K8";
|
|
3069
|
+
//var allowedDeviceTypes = MCProtocol.prototype.enumDeviceCodeSpec.keys.join("|");
|
|
3070
|
+
var deviceCodeSpec = this.getDeviceCodeSpec();
|
|
3071
|
+
var allowedDeviceTypes = deviceCodeSpec.keys.join("|");
|
|
3072
|
+
var allowedDataTypes = MCProtocol.prototype.enumDataTypes.keys.join("|");
|
|
3073
|
+
var AltAddressStyle = false; //Alt style combines DS and DT at position [1] e.g. [DS/DT] DEV DA [.BIT] [,CNT]
|
|
3074
|
+
var strRegex = undefined;
|
|
3075
|
+
if(AltAddressStyle)
|
|
3076
|
+
strRegex = `(${allowedDigitSpecs}|${allowedDataTypes})?(${allowedDeviceTypes})(\\w+)(?:\\.?)?(.?)?(?:,)?(\\w+)?(?::)?({.*})?`;
|
|
3077
|
+
else
|
|
3078
|
+
strRegex = `(${allowedDigitSpecs})?(${allowedDeviceTypes})(${allowedDataTypes})?(\\w+)(?:\\.?)?(.?)?(?:,)?(\\w+)?(?::)?({.*})?`;
|
|
3079
|
+
//(K2|K4|K8)?(SM|SD|TS|TC|TN|STS|STC|STN|CS|CC|CN|SB|SW|DX|DY|ZR|X|Y|M|L|F|V|B|D|W|Z|R)(REAL|FLOAT|DWORD|DINT|WORD|UINT|INT|STR|CHAR|BYTE|BIT)?(\w+)(?:\.?)?(.?)?(?:,)?(\w+)?(?::)?({.*})?
|
|
3080
|
+
|
|
3081
|
+
outputLog(`Matching address '${addr}' to regex '${strRegex}'.`, "DEBUG");
|
|
3082
|
+
|
|
3083
|
+
var matches = addr.match(strRegex);
|
|
3084
|
+
if (!matches) {
|
|
3085
|
+
throw new Error(`Unable to match addr '${addr} to a valid config`);
|
|
3086
|
+
}
|
|
3087
|
+
if (matches[0] != addr) {
|
|
3088
|
+
throw new Error(`addr is invalid`);
|
|
3089
|
+
}
|
|
3090
|
+
var spec;
|
|
3091
|
+
if(AltAddressStyle)
|
|
3092
|
+
spec = {
|
|
3093
|
+
dataType: matches[1] ? matches[1] : "", //e.g. K4 | DWORD | STR etc
|
|
3094
|
+
deviceCode: matches[2], //e.g. Y | X | D | M
|
|
3095
|
+
deviceNo: matches[3], //e.g. 1000 | FF1 | E1A etc
|
|
3096
|
+
bitNo: (!matches[4] || matches[4] == ",") ? "" : matches[4], //0 ~ F
|
|
3097
|
+
deviceCount: matches[5] ? matches[5] : 1,
|
|
3098
|
+
}
|
|
3099
|
+
else
|
|
3100
|
+
spec = {
|
|
3101
|
+
digitSpec: matches[1] ? matches[1] : "", //e.g. K4
|
|
3102
|
+
deviceCode: matches[2], //e.g. Y | X | D | M
|
|
3103
|
+
dataType: matches[3] ? matches[3] : "", //e.g. DWORD | STR etc
|
|
3104
|
+
deviceNo: matches[4], //e.g. 1000 | FF1 | E1A etc
|
|
3105
|
+
bitNo: (!matches[5] || matches[5] == ",") ? "" : matches[5], //0 ~ F
|
|
3106
|
+
deviceCount: matches[6] ? matches[6] : 1,
|
|
3107
|
+
options: (matches[7]) ? JSON.parse(matches[7].replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": ')) || {} : {}
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
outputLog(`${addr} == ${util.format(spec)}`, "DEBUG");
|
|
3111
|
+
|
|
3112
|
+
//determine the device area
|
|
3113
|
+
theItem.digitSpec = spec.digitSpec;
|
|
3114
|
+
theItem.datatype = spec.dataType; // eg "DINT"
|
|
3115
|
+
theItem.deviceCode = spec.deviceCode;
|
|
3116
|
+
theItem.prefix = theItem.digitSpec + theItem.deviceCode + theItem.datatype;
|
|
3117
|
+
theItem.options = spec.options;
|
|
3118
|
+
|
|
3119
|
+
//get device number (if X / Y, then convert HEX to DEC)
|
|
3120
|
+
let devNo = spec.deviceNo, radix = 10;
|
|
3121
|
+
//theItem.deviceCodeSpec = MCProtocol.prototype.enumDeviceCodeSpec[theItem.deviceCode];
|
|
3122
|
+
theItem.deviceCodeSpec = deviceCodeSpec[theItem.deviceCode];
|
|
3123
|
+
switch (theItem.deviceCodeSpec.notation) {
|
|
3124
|
+
case 'Decimal':
|
|
3125
|
+
radix = 10;
|
|
3126
|
+
break;
|
|
3127
|
+
case 'Hexadecimal':
|
|
3128
|
+
radix = 16;
|
|
3129
|
+
devNo = "0x" + spec.deviceNo;
|
|
3130
|
+
break;
|
|
3131
|
+
case 'Octal':
|
|
3132
|
+
radix = 8;
|
|
3133
|
+
break;
|
|
3134
|
+
default:
|
|
3135
|
+
throw new Error(`Device notation '${theItem.deviceCodeSpec.notation}' is not supported.`);
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
|
|
3139
|
+
if(!isNumeric(devNo)){
|
|
3140
|
+
throw new Error(`Device Number '${spec.deviceNo}' is not numeric.`);
|
|
3141
|
+
}
|
|
3142
|
+
theItem.offset = parseInt(spec.deviceNo, radix);
|
|
3143
|
+
if (isNaN(theItem.offset)) {
|
|
3144
|
+
throw new Error(`Device Number '${spec.deviceNo}' is NG.`);
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
//determine BIT
|
|
3148
|
+
if (spec.bitNo != "") {
|
|
3149
|
+
postDotNumeric = parseInt("0x" + spec.bitNo);
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
//determine Array Len
|
|
3153
|
+
theItem.arrayLength = parseInt(spec.deviceCount);
|
|
3154
|
+
if (isNaN(theItem.arrayLength)) {
|
|
3155
|
+
throw new Error(`Count '${spec.deviceCount}' is invalid`);
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
} catch (error) {
|
|
3159
|
+
theItem.initError = `Unable to parse address '${addr}'. ${error}`;
|
|
3160
|
+
//outputLog(`Unable to parse address '${addr}'. Error : ${error}`);
|
|
3161
|
+
return false;
|
|
3162
|
+
}
|
|
3163
|
+
|
|
3164
|
+
theItem.areaMCCode = theItem.deviceCodeSpec.binary;
|
|
3165
|
+
|
|
3166
|
+
if (!theItem.areaMCCode) {
|
|
3167
|
+
theItem.initError = 'Failed to find a match for ' + theItem.deviceCode + ' possibly because that type is not supported yet.';
|
|
3168
|
+
//outputLog('Failed to find a match for ' + theItem.deviceCode + ' possibly because that type is not supported yet.');
|
|
3169
|
+
return false;
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
if(theItem.digitSpec == "K2"){
|
|
3173
|
+
theItem.datatype = "BYTE";
|
|
3174
|
+
} else if(theItem.digitSpec == "K4"){
|
|
3175
|
+
theItem.datatype = "INT";
|
|
3176
|
+
} else if(theItem.digitSpec == "K8"){
|
|
3177
|
+
theItem.datatype = "DINT";
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
if (theItem.datatype == "") {
|
|
3181
|
+
if (theItem.isBitDevice()) {
|
|
3182
|
+
theItem.datatype = "BIT";
|
|
3183
|
+
}else{
|
|
3184
|
+
theItem.datatype = "INT";
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
if (theItem.deviceCode === "CN" && theItem.offset >= 200) {
|
|
3188
|
+
theItem.datatype = "DINT";
|
|
3189
|
+
}
|
|
3190
|
+
let dts = MCProtocol.prototype.enumDataTypes;
|
|
3191
|
+
let dt = dts[theItem.datatype];
|
|
3192
|
+
switch (dt.name) {
|
|
3193
|
+
case dts.REAL.name:
|
|
3194
|
+
case dts.FLOAT.name:
|
|
3195
|
+
case dts.DINT.name:
|
|
3196
|
+
case dts.DWORD.name:
|
|
3197
|
+
// These are the double-byte types
|
|
3198
|
+
theItem.remainder = 0;
|
|
3199
|
+
theItem.dtypelen = 4;
|
|
3200
|
+
theItem.multidtypelen = 4;
|
|
3201
|
+
theItem.requestOffset = theItem.offset;
|
|
3202
|
+
break;
|
|
3203
|
+
case dts.WORD.name:
|
|
3204
|
+
case dts.INT.name:
|
|
3205
|
+
case dts.UINT.name:
|
|
3206
|
+
theItem.remainder = 0;
|
|
3207
|
+
theItem.dtypelen = 2;
|
|
3208
|
+
theItem.multidtypelen = 2;
|
|
3209
|
+
if (typeof (postDotNumeric) !== 'undefined') {
|
|
3210
|
+
theItem.datatype = 'BIT';
|
|
3211
|
+
theItem.bitOffset = postDotNumeric;
|
|
3212
|
+
theItem.remainder = theItem.bitOffset % 16;//bug fix. If D1000.1,16 is requested, we need to get 2 WDs as the addr range is D1000.1 ~ D1001.1
|
|
3213
|
+
}
|
|
3214
|
+
if (theItem.deviceCode === "CN" && theItem.offset >= 200) {
|
|
3215
|
+
theItem.dtypelen = 4;
|
|
3216
|
+
theItem.multidtypelen = 4;
|
|
3217
|
+
}
|
|
3218
|
+
theItem.requestOffset = theItem.offset;
|
|
3219
|
+
break;
|
|
3220
|
+
|
|
3221
|
+
case dts.CHAR.name:
|
|
3222
|
+
case dts.STR.name:
|
|
3223
|
+
// These are the double-byte types
|
|
3224
|
+
theItem.datatype = "CHAR";
|
|
3225
|
+
theItem.multidtypelen = 1;
|
|
3226
|
+
theItem.remainder = 0;
|
|
3227
|
+
theItem.requestOffset = theItem.offset;
|
|
3228
|
+
theItem.dtypelen = 1;
|
|
3229
|
+
break;
|
|
3230
|
+
case dts.BIT.name:
|
|
3231
|
+
//theItem.deviceCode = theItem.prefix;
|
|
3232
|
+
theItem.datatype = "BIT";
|
|
3233
|
+
theItem.multidtypelen = 2;
|
|
3234
|
+
theItem.remainder = theItem.offset % 16;
|
|
3235
|
+
theItem.requestOffset = theItem.offset - theItem.remainder;
|
|
3236
|
+
theItem.dtypelen = 2; // was 1, not sure why, we read 1 word at a time. SMc-If we request X0,2 we get a 1byte reply!
|
|
3237
|
+
theItem.bitNative = theItem.isBitDevice();
|
|
3238
|
+
break;
|
|
3239
|
+
default:
|
|
3240
|
+
theItem.initError = 'Failed to find a match for ' + theItem.addr + ' possibly because type is not supported yet.';
|
|
3241
|
+
//outputLog('Failed to find a match for ' + theItem.addr + ' possibly because ' + theItem.prefix + ' type is not supported yet.');
|
|
3242
|
+
return false;
|
|
3243
|
+
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3246
|
+
if (forceBitDtype) {
|
|
3247
|
+
theItem.datatype = "BIT";
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
|
|
3251
|
+
|
|
3252
|
+
if (theItem.datatype === 'BIT') {
|
|
3253
|
+
theItem.wordLength = Math.ceil((theItem.remainder + theItem.arrayLength) / 16); // used tadd request offset here but not right
|
|
3254
|
+
// if(!theItem.bitNative){
|
|
3255
|
+
// var wdOverflow = ((theItem.bitOffset + theItem.arrayLength) % 16) > 0;
|
|
3256
|
+
// if(wdOverflow)
|
|
3257
|
+
// theItem.wordLength += 1;//need to consider max req len!
|
|
3258
|
+
// }
|
|
3259
|
+
} else {
|
|
3260
|
+
theItem.wordLength = Math.ceil(theItem.arrayLength * theItem.dataTypeByteLength() / 2);
|
|
3261
|
+
}
|
|
3262
|
+
if (isNaN(theItem.wordLength)) {
|
|
3263
|
+
//console.log("theItem.byteLength is NAN");
|
|
3264
|
+
theItem.initError = "wordLength is NAN!";
|
|
3265
|
+
}
|
|
3266
|
+
theItem.byteLength = theItem.wordLength * 2;
|
|
3267
|
+
if (isNaN(theItem.byteLength)) {
|
|
3268
|
+
//console.log("theItem.byteLength is NAN");
|
|
3269
|
+
theItem.initError = "byteLength is NAN!";
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
theItem.byteLength = theItem.wordLength*2;
|
|
3273
|
+
theItem.byteLengthWrite = (theItem.bitNative) ? Math.ceil(theItem.arrayLength/2) : theItem.byteLength;
|
|
3274
|
+
theItem.totalArrayLength = theItem.arrayLength;
|
|
3275
|
+
|
|
3276
|
+
// Counter check - can't think of a better way to handle this.
|
|
3277
|
+
if (theItem.deviceCode === "CN" && theItem.requestOffset < 200 && (theItem.requestOffset + theItem.arrayLength > 200)) {
|
|
3278
|
+
theItem.initError = "You can't have a counter array that crosses the 200-point boundary.";
|
|
3279
|
+
//outputLog(theItem.initError);
|
|
3280
|
+
return false;
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
let writeOperation = writeValue == undefined ? false : true;
|
|
3284
|
+
if (frame === '1E') {
|
|
3285
|
+
|
|
3286
|
+
// Hard code the header. Note that for bit devices, we use the less-efficient, but easier to program, bit device read.
|
|
3287
|
+
if (theItem.bitNative) {
|
|
3288
|
+
if (writeOperation) {
|
|
3289
|
+
theItem.subHeader = 0x02; //Batch Write BIT units
|
|
3290
|
+
} else {
|
|
3291
|
+
theItem.subHeader = 0x01; // For now we read words. (MC command 0x00 is bit read)
|
|
3292
|
+
}
|
|
3293
|
+
} else {
|
|
3294
|
+
if (writeOperation) {
|
|
3295
|
+
theItem.subHeader = 0x03; //batch write WORD units
|
|
3296
|
+
} else {
|
|
3297
|
+
theItem.subHeader = 0x01; //batch read WORD units
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
} else {
|
|
3301
|
+
// * Command
|
|
3302
|
+
theItem.command = (writeOperation ? 0x1401 : 0x0401);
|
|
3303
|
+
if (theItem.bitNative) {
|
|
3304
|
+
if (writeOperation)
|
|
3305
|
+
theItem.subCommand = (plcType == MCProtocol.prototype.enumPLCTypes.R.name ? 0x0003 : 0x0001);
|
|
3306
|
+
else //reading - use subcommand 0/2 (batch read words) instead of 1/3 (batch read bits)
|
|
3307
|
+
theItem.subCommand = (plcType == MCProtocol.prototype.enumPLCTypes.R.name ? 0x0002 : 0x0000);
|
|
3308
|
+
} else {
|
|
3309
|
+
theItem.subCommand = (plcType == MCProtocol.prototype.enumPLCTypes.R.name ? 0x0002 : 0x0000);//subcommand 0 or 2 (batch read/write words)
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
theItem.initialised = theItem.initError == "";
|
|
3314
|
+
return theItem.initialised;
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
this.updateSeqNum = function(seqNum){
|
|
3318
|
+
var self = this;
|
|
3319
|
+
self.seqNum = seqNum;
|
|
3320
|
+
if(self.frame == "4E"){
|
|
3321
|
+
self.buffer.writeUInt16LE(seqNum,2); // SEQ
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
this.toBuffer = function _MCAddrToBuffer(isAscii, frame, plcType, seqNum, network, PCStation, PLCStation, PLCModuleNo) {
|
|
3326
|
+
var self = this;
|
|
3327
|
+
var writeOperation = self.writeValue === undefined ? false : true;
|
|
3328
|
+
var writeLength, MCCommand = Buffer.alloc(2500);
|
|
3329
|
+
self.buffer = undefined;
|
|
3330
|
+
self.isAscii = isAscii;
|
|
3331
|
+
self.frame = frame;
|
|
3332
|
+
self.plcType = plcType;
|
|
3333
|
+
self.bufferized = false;
|
|
3334
|
+
self.seqNum = seqNum;
|
|
3335
|
+
|
|
3336
|
+
var result = {
|
|
3337
|
+
data: undefined,
|
|
3338
|
+
subHeader: undefined,
|
|
3339
|
+
command: undefined,
|
|
3340
|
+
subCommand: undefined
|
|
3341
|
+
}
|
|
3342
|
+
/*
|
|
3343
|
+
|
|
3344
|
+
The MC Buffer for 'D1234,55' is:
|
|
3345
|
+
0 1
|
|
3346
|
+
0 1 2 3 4 5 6 7 8 9 0 1
|
|
3347
|
+
<Buffer 01 ff 0a 00 d2 04 00 00 20 44 37 00>
|
|
3348
|
+
|
|
3349
|
+
1E frame... Subheader, PC No., ACPU monitoring timer, Request data
|
|
3350
|
+
|
|
3351
|
+
0 - Sub header : 01 = read
|
|
3352
|
+
1 - PC number : ff = default or Specify the network module station No.01H to 40H (1 to 64) of the target.
|
|
3353
|
+
2 - ACPU mon timer: 0a 00 = 10 Waiting time (unit: 250 ms) ~ 10x250=2.5s (UINT16 - Little Endian (BA))
|
|
3354
|
+
4 - Device Number : d2 04 00 00 = 1234 (UINT32 - Little Endian (DCBA))
|
|
3355
|
+
8 - Device Area : 20 44 = 4420 == D area (UINT16 - Little Endian (BA))
|
|
3356
|
+
10- Device Count : 37 00 = 55 == device count (UINT16 - Little Endian (BA))
|
|
3357
|
+
|
|
3358
|
+
*/
|
|
3359
|
+
|
|
3360
|
+
writeLength = writeOperation ? (self.byteLengthWrite) : 0;
|
|
3361
|
+
|
|
3362
|
+
if (frame === '1E') {
|
|
3363
|
+
|
|
3364
|
+
headerLength = 4;
|
|
3365
|
+
|
|
3366
|
+
MCCommand[0] = self.subHeader ;
|
|
3367
|
+
MCCommand[1] = 0xff;
|
|
3368
|
+
|
|
3369
|
+
if (isAscii) {
|
|
3370
|
+
outputLog("We're Ascii", "DEBUG");
|
|
3371
|
+
MCCommand.writeUInt16BE(monitoringTime, 2);
|
|
3372
|
+
} else {
|
|
3373
|
+
outputLog("We're Binary", "DEBUG");
|
|
3374
|
+
MCCommand.writeUInt16LE(monitoringTime, 2);
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3377
|
+
// Write the data type code
|
|
3378
|
+
if (isAscii) {
|
|
3379
|
+
MCCommand.writeUInt16BE(self.areaMCCode, 4);
|
|
3380
|
+
} else {
|
|
3381
|
+
MCCommand.writeUInt16LE(self.areaMCCode, 8);
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
// Write the data request offset
|
|
3385
|
+
if (isAscii) {
|
|
3386
|
+
if (writeOperation) {
|
|
3387
|
+
MCCommand.writeUInt32BE(self.offset, 6);
|
|
3388
|
+
} else {
|
|
3389
|
+
MCCommand.writeUInt32BE(self.requestOffset, 6);
|
|
3390
|
+
}
|
|
3391
|
+
} else {
|
|
3392
|
+
if (writeOperation) {
|
|
3393
|
+
MCCommand.writeUInt32LE(self.offset, 4);
|
|
3394
|
+
} else {
|
|
3395
|
+
// RequestOffset ensures bit-native types are read as a word
|
|
3396
|
+
MCCommand.writeUInt32LE(self.requestOffset, 4);
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
// Number of elements in request - for single-bit, 16-bit, 32-bit, this is always the number of WORDS
|
|
3401
|
+
if (self.bitNative && writeOperation) {
|
|
3402
|
+
// set to bit length
|
|
3403
|
+
MCCommand.writeUInt8(self.arrayLength, 10); // fails when asking for >256 items because we call toBuffer() before breaking into "parts"
|
|
3404
|
+
} else if (self.bitNative && !writeOperation) {
|
|
3405
|
+
MCCommand.writeUInt8(Math.ceil((self.arrayLength + self.remainder) / 16), 10);
|
|
3406
|
+
} else {
|
|
3407
|
+
// doesn't work with optimized blocks where array length isn't right MCCommand.writeUInt8(self.arrayLength*self.dataTypeByteLength()/2, 10);
|
|
3408
|
+
if (writeOperation) {
|
|
3409
|
+
MCCommand.writeUInt8(self.byteLengthWrite / 2, 10);
|
|
3410
|
+
} else {
|
|
3411
|
+
MCCommand.writeUInt8(self.byteLength / 2, 10);
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
// Spec says to write 0 here
|
|
3416
|
+
MCCommand.writeUInt8(0, 11);
|
|
3417
|
+
|
|
3418
|
+
if (writeOperation) {
|
|
3419
|
+
//self.prepareWriteData();
|
|
3420
|
+
self.writeBuffer.copy(MCCommand, 12, 0, self.byteLengthWrite);
|
|
3421
|
+
reqDataLen += writeLength;
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
//return 1E frame
|
|
3425
|
+
result.data = MCCommand.slice(0, 12 + writeLength);
|
|
3426
|
+
self.buffer = result;
|
|
3427
|
+
self.bufferized = true;
|
|
3428
|
+
outputLog(`MCAddrToBuffer generated the below data for ${self.addr} (frame ${frame}, plcType ${plcType})...`, "TRACE");
|
|
3429
|
+
outputLog(' 0 1 2 ', "TRACE");
|
|
3430
|
+
outputLog(' 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 ', "TRACE");
|
|
3431
|
+
outputLog(result.data, "TRACE")
|
|
3432
|
+
|
|
3433
|
+
return result;
|
|
3434
|
+
} else {
|
|
3435
|
+
//frame 3E / 4E
|
|
3436
|
+
|
|
3437
|
+
if (isAscii) {
|
|
3438
|
+
throw new Error("ASCII Mode not implimented for 3E/4E frames");
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
/*
|
|
3442
|
+
4E/3E frame... Subheader, Access route, Request data length, Monitoring timer, Request data
|
|
3443
|
+
Subheader The value to be set according to type of message is defined.
|
|
3444
|
+
4E frame: Set a serial No.
|
|
3445
|
+
3E frame: Fixed value (Request message '5000', Response message 'D000')
|
|
3446
|
+
Page 42 Subheader
|
|
3447
|
+
Access route
|
|
3448
|
+
Specify the access route.
|
|
3449
|
+
Page 45 ACCESS ROUTE SETTINGS
|
|
3450
|
+
Request data length (UINT16 LE)
|
|
3451
|
+
Specify the data length from the monitoring timer --> request data.
|
|
3452
|
+
Page 43 Request data length and response data length
|
|
3453
|
+
Monitoring timer (UINT16 LE 0 or 1~40 = 1=250ms, 40=10s )
|
|
3454
|
+
Set the wait time up to the completion of reading and writing processing.
|
|
3455
|
+
Page 43 Monitoring timer
|
|
3456
|
+
Request data
|
|
3457
|
+
COMMAND + SUBCOMMAND + DEVICE
|
|
3458
|
+
For the request data, set the command that indicates the request content.
|
|
3459
|
+
Refer to "Request data" rows of each command.
|
|
3460
|
+
Page 60 COMMANDS AND FUNCTIONS
|
|
3461
|
+
* Batch Read Words: 04 01 (00 00 | 02 00) (Q/L = 00 00, iQR = 02 00)
|
|
3462
|
+
* Batch Read Bits: 04 01 (00 01 | 03 00) (Q/L = 00 00, iQR = 02 00)
|
|
3463
|
+
* Batch Write Words: 14 01 (00 00 | 02 00) (Q/L = 00 00, iQR = 02 00)
|
|
3464
|
+
* Batch Write Bits: 14 01 (00 01 | 03 00) (Q/L = 00 00, iQR = 02 00)
|
|
3465
|
+
|
|
3466
|
+
|
|
3467
|
+
3E Q/L example
|
|
3468
|
+
0 1 2 3
|
|
3469
|
+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
|
|
3470
|
+
50 00 00 ff ff 03 00 0a 00 10 00 01 04 00 00 d2 04 00 A8 37 00
|
|
3471
|
+
|
|
3472
|
+
4E Q/L example
|
|
3473
|
+
0 1 2 3
|
|
3474
|
+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
|
|
3475
|
+
54 00 02 00 00 00 00 ff ff 03 00 0a 00 10 00 01 04 00 00 d2 04 00 A8 37 00
|
|
3476
|
+
|
|
3477
|
+
Subheader 4E 0x0054 + serial number + 0x0000 (6b) 1234H (5400 = 4E frame, 0200=2h, 0000 fixed)
|
|
3478
|
+
0: 54 00 34 12 00 00
|
|
3479
|
+
|
|
3480
|
+
Subheader 3E 5000 fixed
|
|
3481
|
+
0: 50 00
|
|
3482
|
+
|
|
3483
|
+
access route (5b)- connect to local example net0, pcno ff,
|
|
3484
|
+
4E:6: 00 ff ff 03 00
|
|
3485
|
+
3E:2: 00 ff ff 03 00
|
|
3486
|
+
* (Byte) Network No , Specify the network No. of an access target.
|
|
3487
|
+
* (Byte) PC No. (byte) Specify the network module station No. of an access target
|
|
3488
|
+
* (UINT16 LE) Request destination module I/O No
|
|
3489
|
+
0000H to 01FFH: Values obtained by dividing the start input/output number by 16
|
|
3490
|
+
* (Byte) Request destination module station No.
|
|
3491
|
+
When accessing a multidrop connection station, specify the station No. of an access target module
|
|
3492
|
+
00H to 1FH (0 to 31): Station No
|
|
3493
|
+
ex. Accessing the connected station (host station) FFH 03H 00H
|
|
3494
|
+
|
|
3495
|
+
Req Data length (2b) (UINT16 LE) = noof bytes from Monitoring timer --> Request data
|
|
3496
|
+
4E:11: 0a 00 (10 bytes)
|
|
3497
|
+
3E:07: 0a 00 (10 bytes)
|
|
3498
|
+
|
|
3499
|
+
Monitoring timer (2b) (0= wait forever, 01H=250ms ~ 28H=40x250=10s )
|
|
3500
|
+
13: 10 00
|
|
3501
|
+
|
|
3502
|
+
Request data (10b Q/L, 12b iQR)
|
|
3503
|
+
15: 01 04 00 00 d2 04 00 00 20 44 37 00
|
|
3504
|
+
|
|
3505
|
+
Command (4b)
|
|
3506
|
+
15: 01 04 00 00
|
|
3507
|
+
01 04 = 0401=read
|
|
3508
|
+
00 00 = subcommand 0 (Q/L series)
|
|
3509
|
+
|
|
3510
|
+
Device (6b Q/L, 8b iQR)
|
|
3511
|
+
19: d2 04 00 A8 37 00
|
|
3512
|
+
Q/L (6 bytes)
|
|
3513
|
+
(UINT16 LE) - Device Number : d2 04 = 1234
|
|
3514
|
+
(BYTE) - Device Number
|
|
3515
|
+
(UINT8) - Device code : A8 = 0xa8 == D area
|
|
3516
|
+
(UINT16 LE) - Device Count : 37 00 = 55
|
|
3517
|
+
iQ-R (8 bytes)
|
|
3518
|
+
(UINT32 LE) - Device Number : d2 04 00 00 = 1234
|
|
3519
|
+
(UINT16 LE) - Device code : A8 00 = 0xA8 == D area
|
|
3520
|
+
(UINT16 LE) - Device Count : 37 00 = 55
|
|
3521
|
+
|
|
3522
|
+
*/
|
|
3523
|
+
|
|
3524
|
+
|
|
3525
|
+
//Subheader: serial number (4E) or fixed (3E)
|
|
3526
|
+
//example 1234H (5400 = 4E frame, 3412=1234h, 0000 fixed)
|
|
3527
|
+
// 0 1 2 3 4 5
|
|
3528
|
+
// 54 00 34 12 00 00
|
|
3529
|
+
if (frame === '4E') {
|
|
3530
|
+
MCCommand.addByte(0x54); // 4E frame
|
|
3531
|
+
MCCommand.addByte(0x00); // 4E frame
|
|
3532
|
+
MCCommand.addUint16LE(seqNum); // SEQ
|
|
3533
|
+
MCCommand.addUint16LE(0x0); // fixed
|
|
3534
|
+
pos = 6;
|
|
3535
|
+
} else {
|
|
3536
|
+
MCCommand.addByte(0x50); // 3E frame
|
|
3537
|
+
MCCommand.addByte(0x00); // 3E frame
|
|
3538
|
+
pos = 2;
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
//access route - connect to local example net0, pcno ff,
|
|
3542
|
+
// 00 ff ff 03 00
|
|
3543
|
+
/*
|
|
3544
|
+
* (Byte) Network No , Specify the network No. of an access target.
|
|
3545
|
+
* (Byte) PC No. (byte) Specify the network module station No. of an access target
|
|
3546
|
+
* (UINT16 LE) Request destination module I/O No
|
|
3547
|
+
0000H to 01FFH: Values obtained by dividing the start input/output number by 16
|
|
3548
|
+
0x03FF = CPU
|
|
3549
|
+
* (Byte) Request destination module station No.
|
|
3550
|
+
*/
|
|
3551
|
+
let networkNo = network ? network : (this.options.N || 0);
|
|
3552
|
+
let stationNo = PCStation ? PCStation : (this.options.S || 0xff);
|
|
3553
|
+
|
|
3554
|
+
MCCommand.addByte(networkNo); // net
|
|
3555
|
+
MCCommand.addByte(stationNo); // PC No
|
|
3556
|
+
MCCommand.addUint16LE(PLCModuleNo ? PLCModuleNo : 0x3FF); //03ff // Request destination module I/O No //for multidrop/routing
|
|
3557
|
+
MCCommand.addByte(PLCStation ? PLCStation : 0); //0x0 // Request destination module station No //for multidrop/routing
|
|
3558
|
+
pos += 5;
|
|
3559
|
+
|
|
3560
|
+
//Request data length (UINT16 LE)
|
|
3561
|
+
var reqDataLenPos = MCCommand.pointer;
|
|
3562
|
+
var reqDataLen = 0;
|
|
3563
|
+
//Update Data Length after its understood below - for now, set length as zero
|
|
3564
|
+
MCCommand.addUint16LE(0);
|
|
3565
|
+
pos += 2;
|
|
3566
|
+
|
|
3567
|
+
//Monitoring timer (0= wait forever, 01H=250ms ~ 28H=40x250=10s )
|
|
3568
|
+
MCCommand.addUint16LE(monitoringTime);
|
|
3569
|
+
pos += 2;
|
|
3570
|
+
reqDataLen += 2;
|
|
3571
|
+
|
|
3572
|
+
// //Request data...
|
|
3573
|
+
// * Command
|
|
3574
|
+
MCCommand.addUint16LE(self.command);
|
|
3575
|
+
MCCommand.addUint16LE(self.subCommand);
|
|
3576
|
+
|
|
3577
|
+
|
|
3578
|
+
pos += 4;
|
|
3579
|
+
reqDataLen += 4;
|
|
3580
|
+
// * Device
|
|
3581
|
+
/*
|
|
3582
|
+
Q/L
|
|
3583
|
+
(UINT16 LE) - Device Number : d2 04 = 1234
|
|
3584
|
+
(BYTE) - Device Number
|
|
3585
|
+
(UINT8) - Device code : A8 = 0xa8 == D area
|
|
3586
|
+
(UINT16 LE) - Device Count : 37 00 = 55
|
|
3587
|
+
iQ-R
|
|
3588
|
+
(UINT32 LE) - Device Number : d2 04 00 00 = 1234
|
|
3589
|
+
(UINT16 LE) - Device code : A8 00 = 0xA8 == D area
|
|
3590
|
+
(UINT16 LE) - Device Count : 37 00 = 55
|
|
3591
|
+
|
|
3592
|
+
*/
|
|
3593
|
+
// Device Number
|
|
3594
|
+
var offset = writeOperation ? self.offset : self.requestOffset;
|
|
3595
|
+
MCCommand.addByte(offset & 0xff);
|
|
3596
|
+
MCCommand.addByte((offset >> 8) & 0xff);
|
|
3597
|
+
MCCommand.addByte((offset >> 16) & 0xff);
|
|
3598
|
+
pos += 3;
|
|
3599
|
+
reqDataLen += 3;
|
|
3600
|
+
|
|
3601
|
+
|
|
3602
|
+
if (plcType == 'R') {
|
|
3603
|
+
MCCommand.addByte((offset >> 24) & 0xff);
|
|
3604
|
+
pos += 1;
|
|
3605
|
+
reqDataLen += 1;
|
|
3606
|
+
}
|
|
3607
|
+
|
|
3608
|
+
|
|
3609
|
+
// Device Code
|
|
3610
|
+
if (plcType == 'R') {
|
|
3611
|
+
MCCommand.addUint16LE(self.areaMCCode);
|
|
3612
|
+
pos += 2;
|
|
3613
|
+
reqDataLen += 2;
|
|
3614
|
+
} else {
|
|
3615
|
+
MCCommand.addByte(self.areaMCCode);
|
|
3616
|
+
reqDataLen += 1;
|
|
3617
|
+
pos += 1;
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3620
|
+
//NOTES:
|
|
3621
|
+
// 1E Number of elements in request - for single-bit, 16-bit, 32-bit, this is always the number of WORDS
|
|
3622
|
+
// 3E,4E Number of elements in request -
|
|
3623
|
+
// for batch bit read each bit is returned in 1 digit.
|
|
3624
|
+
// for batch word read, this is always the number of WORDS
|
|
3625
|
+
if (self.bitNative && writeOperation) {
|
|
3626
|
+
MCCommand.addUint16LE(self.arrayLength);// set to bit length
|
|
3627
|
+
} else if (self.bitNative && !writeOperation) {
|
|
3628
|
+
//for now, we dont use Batch Read Bits function - so we need to request noof WORDs
|
|
3629
|
+
MCCommand.addUint16LE(Math.ceil((self.arrayLength + self.remainder) / 16));
|
|
3630
|
+
} else {
|
|
3631
|
+
if (writeOperation) {
|
|
3632
|
+
MCCommand.addUint16LE(self.byteLengthWrite / 2);//noof words
|
|
3633
|
+
} else {
|
|
3634
|
+
MCCommand.addUint16LE(self.byteLength / 2);//noof words
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
pos += 2;
|
|
3638
|
+
reqDataLen += 2;
|
|
3639
|
+
|
|
3640
|
+
|
|
3641
|
+
//add write data
|
|
3642
|
+
if (writeOperation) {
|
|
3643
|
+
//self.prepareWriteData();
|
|
3644
|
+
self.writeBuffer.copy(MCCommand, pos, 0, self.byteLengthWrite);
|
|
3645
|
+
reqDataLen += writeLength;
|
|
3646
|
+
}
|
|
3647
|
+
|
|
3648
|
+
//update the Request Data Length
|
|
3649
|
+
MCCommand.writeInt16LE(reqDataLen, reqDataLenPos);
|
|
3650
|
+
|
|
3651
|
+
//return 3E/4E frame
|
|
3652
|
+
result.data = MCCommand.slice(0, pos + writeLength);
|
|
3653
|
+
outputLog(`MCAddrToBuffer generated the below data for ${self.addr} (frame ${frame}, plcType ${plcType})...`, "TRACE");
|
|
3654
|
+
outputLog(' 0 1 2 ', "TRACE");
|
|
3655
|
+
outputLog(' 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 ', "TRACE");
|
|
3656
|
+
outputLog(result.data, "TRACE");
|
|
3657
|
+
self.buffer = result;
|
|
3658
|
+
self.bufferized = true;
|
|
3659
|
+
return result;
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
|
|
3663
|
+
this.prepareWriteData = function _prepareWriteData() {
|
|
3664
|
+
var thePointer, theByte, problem = false;
|
|
3665
|
+
var theItem = this;
|
|
3666
|
+
var isAscii = theItem.isAscii;
|
|
3667
|
+
|
|
3668
|
+
theByte = 0;
|
|
3669
|
+
thePointer = 0; // After length and header
|
|
3670
|
+
|
|
3671
|
+
try {
|
|
3672
|
+
|
|
3673
|
+
//if(!theItem.writeBuffer || !theItem.writeQualityBuffer || !(theItem.writeBuffer.length == theItem.byteLengthWrite)){
|
|
3674
|
+
theItem.writeBuffer.fill(0) //= new Buffer(theItem.byteLengthWrite+1);
|
|
3675
|
+
theItem.writeQualityBuffer.fill(0) //= new Buffer(theItem.byteLengthWrite+1);
|
|
3676
|
+
//}
|
|
3677
|
+
// if(!theItem.writeBuffer){
|
|
3678
|
+
// theItem.writeBuffer = new Buffer(theItem.byteLengthWrite);
|
|
3679
|
+
// theItem.writeQualityBuffer = new Buffer(theItem.byteLengthWrite);
|
|
3680
|
+
// }
|
|
3681
|
+
|
|
3682
|
+
|
|
3683
|
+
if (theItem.arrayLength > 1) {
|
|
3684
|
+
// Array value.
|
|
3685
|
+
var bitShiftAmount = theItem.bitOffset;
|
|
3686
|
+
var stringNullFound = false;
|
|
3687
|
+
for (arrayIndex = 0; arrayIndex < theItem.arrayLength; arrayIndex++) {
|
|
3688
|
+
switch (theItem.datatype) {
|
|
3689
|
+
case "REAL":
|
|
3690
|
+
if (isAscii) {
|
|
3691
|
+
theItem.writeBuffer.setFloatBESwap(theItem.writeValue[arrayIndex], thePointer)
|
|
3692
|
+
} else {
|
|
3693
|
+
theItem.writeBuffer.writeFloatLE(theItem.writeValue[arrayIndex], thePointer);
|
|
3694
|
+
}
|
|
3695
|
+
break;
|
|
3696
|
+
case "DWORD":
|
|
3697
|
+
if (isAscii) {
|
|
3698
|
+
theItem.writeBuffer.setUInt32BESwap(theItem.writeValue[arrayIndex], thePointer);
|
|
3699
|
+
} else {
|
|
3700
|
+
theItem.writeBuffer.writeUInt32LE(theItem.writeValue[arrayIndex], thePointer);
|
|
3701
|
+
}
|
|
3702
|
+
break;
|
|
3703
|
+
case "DINT":
|
|
3704
|
+
if (isAscii) {
|
|
3705
|
+
theItem.writeBuffer.setInt32BESwap(theItem.writeValue[arrayIndex], thePointer);
|
|
3706
|
+
} else {
|
|
3707
|
+
theItem.writeBuffer.writeInt32LE(theItem.writeValue[arrayIndex], thePointer);
|
|
3708
|
+
}
|
|
3709
|
+
break;
|
|
3710
|
+
case "INT":
|
|
3711
|
+
if (isAscii) {
|
|
3712
|
+
theItem.writeBuffer.writeInt16BE(theItem.writeValue[arrayIndex], thePointer);
|
|
3713
|
+
} else {
|
|
3714
|
+
theItem.writeBuffer.writeInt16LE(theItem.writeValue[arrayIndex], thePointer);
|
|
3715
|
+
}
|
|
3716
|
+
break;
|
|
3717
|
+
case "UINT":
|
|
3718
|
+
case "WORD":
|
|
3719
|
+
if (isAscii) {
|
|
3720
|
+
theItem.writeBuffer.writeUInt16BE(theItem.writeValue[arrayIndex], thePointer);
|
|
3721
|
+
} else {
|
|
3722
|
+
theItem.writeBuffer.writeUInt16LE(theItem.writeValue[arrayIndex], thePointer);
|
|
3723
|
+
}
|
|
3724
|
+
break;
|
|
3725
|
+
case "BIT":
|
|
3726
|
+
if (arrayIndex % 2) {
|
|
3727
|
+
theByte = theByte | ((theItem.writeValue[arrayIndex] == true) ? 1 : 0);
|
|
3728
|
+
} else {
|
|
3729
|
+
theByte = theByte | (((theItem.writeValue[arrayIndex] == true) ? 1 : 0) << 4);
|
|
3730
|
+
}
|
|
3731
|
+
// Maybe not so efficient to do this every time when we only need to do it every 8. Need to be careful with optimizations here for odd requests.
|
|
3732
|
+
theItem.writeBuffer.writeUInt8(theByte, thePointer);
|
|
3733
|
+
//theItem.writeBuffer.writeUInt8(0, thePointer + 1); // Zero out the pad byte
|
|
3734
|
+
//bitShiftAmount++;
|
|
3735
|
+
break;
|
|
3736
|
+
case "B":
|
|
3737
|
+
case "BYTE":
|
|
3738
|
+
if (isAscii) {
|
|
3739
|
+
if (arrayIndex % 2) {
|
|
3740
|
+
theItem.writeBuffer.writeUInt8(theItem.writeValue[arrayIndex], thePointer - 1);
|
|
3741
|
+
} else {
|
|
3742
|
+
theItem.writeBuffer.writeUInt8(theItem.writeValue[arrayIndex], thePointer + 1);
|
|
3743
|
+
}
|
|
3744
|
+
} else {
|
|
3745
|
+
theItem.writeBuffer.writeUInt8(Math.round(theItem.writeValue[arrayIndex]), thePointer);
|
|
3746
|
+
}
|
|
3747
|
+
break;
|
|
3748
|
+
case "C":
|
|
3749
|
+
case "CHAR":
|
|
3750
|
+
// Convert to string.
|
|
3751
|
+
if (isAscii) {
|
|
3752
|
+
//todo: stop at null
|
|
3753
|
+
if (arrayIndex % 2) {
|
|
3754
|
+
theItem.writeBuffer.writeUInt8(theItem.writeValue.charCodeAt(arrayIndex), thePointer - 1);
|
|
3755
|
+
} else {
|
|
3756
|
+
theItem.writeBuffer.writeUInt8(theItem.writeValue.charCodeAt(arrayIndex), thePointer + 1);
|
|
3757
|
+
}
|
|
3758
|
+
} else {
|
|
3759
|
+
var ch = theItem.writeValue.charCodeAt(arrayIndex);
|
|
3760
|
+
if (ch > 0 && !stringNullFound)
|
|
3761
|
+
theItem.writeBuffer.writeUInt8(ch, thePointer);
|
|
3762
|
+
else
|
|
3763
|
+
stringNullFound = true;
|
|
3764
|
+
}
|
|
3765
|
+
break;
|
|
3766
|
+
default:
|
|
3767
|
+
throw new Error(`Unknown data type '${theItem.datatype}'`);
|
|
3768
|
+
}
|
|
3769
|
+
if (theItem.datatype == 'BIT') {
|
|
3770
|
+
// Increment the pointer "sometimes" - only when we cross byte boundaries, then set to zero as we "AND" things together to build the byte.
|
|
3771
|
+
if (arrayIndex % 2) {
|
|
3772
|
+
thePointer += 1;
|
|
3773
|
+
theByte = 0;
|
|
3774
|
+
}
|
|
3775
|
+
} else {
|
|
3776
|
+
// Add to the pointer every time.
|
|
3777
|
+
thePointer += theItem.dtypelen;
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
} else {
|
|
3781
|
+
// Single value.
|
|
3782
|
+
switch (theItem.datatype) {
|
|
3783
|
+
|
|
3784
|
+
case "REAL":
|
|
3785
|
+
if (isAscii) {
|
|
3786
|
+
theItem.writeBuffer.setFloatBESwap(theItem.writeValue, thePointer);
|
|
3787
|
+
} else {
|
|
3788
|
+
theItem.writeBuffer.writeFloatLE(theItem.writeValue, thePointer);
|
|
3789
|
+
}
|
|
3790
|
+
break;
|
|
3791
|
+
case "DWORD":
|
|
3792
|
+
if (isAscii) {
|
|
3793
|
+
theItem.writeBuffer.setUInt32BESwap(theItem.writeValue, thePointer);
|
|
3794
|
+
} else {
|
|
3795
|
+
theItem.writeBuffer.writeUInt32LE(theItem.writeValue, thePointer);
|
|
3796
|
+
}
|
|
3797
|
+
break;
|
|
3798
|
+
case "DINT":
|
|
3799
|
+
if (isAscii) {
|
|
3800
|
+
theItem.writeBuffer.setInt32BESwap(theItem.writeValue, thePointer);
|
|
3801
|
+
} else {
|
|
3802
|
+
theItem.writeBuffer.writeInt32LE(theItem.writeValue, thePointer);
|
|
3803
|
+
}
|
|
3804
|
+
break;
|
|
3805
|
+
case "INT":
|
|
3806
|
+
if (isAscii) {
|
|
3807
|
+
theItem.writeBuffer.writeInt16BE(theItem.writeValue, thePointer);
|
|
3808
|
+
} else {
|
|
3809
|
+
theItem.writeBuffer.writeInt16LE(theItem.writeValue, thePointer);
|
|
3810
|
+
}
|
|
3811
|
+
break;
|
|
3812
|
+
case "WORD":
|
|
3813
|
+
if (isAscii) {
|
|
3814
|
+
theItem.writeBuffer.writeUInt16BE(theItem.writeValue, thePointer);
|
|
3815
|
+
} else {
|
|
3816
|
+
theItem.writeBuffer.writeUInt16LE(theItem.writeValue, thePointer);
|
|
3817
|
+
}
|
|
3818
|
+
break;
|
|
3819
|
+
case "BIT":
|
|
3820
|
+
if (theItem.bitNative) {
|
|
3821
|
+
//e.g. 0x10 = Turn on first bit, turn off 2nd bit.
|
|
3822
|
+
//e.g. 0x01 0x10 = Turn on off 1st bit, turn on 2nd bit. , turn on 3rd bit. turn off 4th bit.
|
|
3823
|
+
theItem.writeBuffer.writeUInt8(((theItem.writeValue) ? 0x10 : 0x00), thePointer); // checked ===true but this caused problems if you write 1
|
|
3824
|
+
theItem.writeBuffer.writeUInt8(0x00, thePointer + 1);
|
|
3825
|
+
outputLog("Datatype is X writing " + theItem.writeValue + " tpi " + theItem.writeBuffer[0], "DEBUG");
|
|
3826
|
+
} else {
|
|
3827
|
+
throw new Error(`We don't support writing individual bits of non-native types - write the whole word externally please`);
|
|
3828
|
+
}
|
|
3829
|
+
break;
|
|
3830
|
+
case "B":
|
|
3831
|
+
case "BYTE":
|
|
3832
|
+
// No support as of yet for signed 8 bit. This isn't that common.
|
|
3833
|
+
if (isAscii) {
|
|
3834
|
+
theItem.writeBuffer.writeUInt8(Math.round(theItem.writeValue), thePointer + 1);
|
|
3835
|
+
theItem.writeBuffer.writeUInt8(0, thePointer);
|
|
3836
|
+
} else {
|
|
3837
|
+
theItem.writeBuffer.writeUInt8(Math.round(theItem.writeValue), thePointer);
|
|
3838
|
+
}
|
|
3839
|
+
break;
|
|
3840
|
+
case "C":
|
|
3841
|
+
case "CHAR":
|
|
3842
|
+
// No support as of yet for signed 8 bit. This isn't that common.
|
|
3843
|
+
if (isAscii) {
|
|
3844
|
+
theItem.writeBuffer.writeUInt8(String.toCharCode(theItem.writeValue), thePointer + 1);
|
|
3845
|
+
theItem.writeBuffer.writeUInt8(0, thePointer);
|
|
3846
|
+
} else {
|
|
3847
|
+
theItem.writeBuffer.writeUInt8(String.toCharCode(theItem.writeValue), thePointer);
|
|
3848
|
+
}
|
|
3849
|
+
break;
|
|
3850
|
+
default:
|
|
3851
|
+
throw new Error(`Unknown data type '${theItem.datatype}'`);
|
|
3852
|
+
}
|
|
3853
|
+
thePointer += theItem.dtypelen;
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
} catch (error) {
|
|
3857
|
+
theItem.lastError = `Exception preparing the write buffer for item '${theItem.useraddr}': ${error}`;
|
|
3858
|
+
outputLog(theItem.lastError, "ERROR");
|
|
3859
|
+
problem = true;
|
|
3860
|
+
}
|
|
3861
|
+
return !problem;
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
|
|
3865
|
+
// And functions...
|
|
3866
|
+
this.clone = function () {
|
|
3867
|
+
var newObj = new PLCItem(this.owner);
|
|
3868
|
+
for (var i in this) {
|
|
3869
|
+
if (i == 'clone')
|
|
3870
|
+
continue;
|
|
3871
|
+
// if (i == 'qualityBuffer')
|
|
3872
|
+
// continue;
|
|
3873
|
+
// if (i == 'writeQualityBuffer')
|
|
3874
|
+
// continue;
|
|
3875
|
+
newObj[i] = this[i];
|
|
3876
|
+
}
|
|
3877
|
+
newObj._instance = "clone";
|
|
3878
|
+
return newObj;
|
|
3879
|
+
};
|
|
3880
|
+
|
|
3881
|
+
|
|
3882
|
+
// Bad value function definition
|
|
3883
|
+
this.badValue = function () {
|
|
3884
|
+
let dt = MCProtocol.prototype.enumDataTypes[this.datatype];
|
|
3885
|
+
if (!dt) {
|
|
3886
|
+
outputLog("Unknown data type when figuring out bad value - should never happen. Should have been caught earlier. " + this.datatype, "ERROR");
|
|
3887
|
+
return 0;
|
|
3888
|
+
}
|
|
3889
|
+
return dt.badValue;
|
|
3890
|
+
}
|
|
3891
|
+
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
function itemListSorter(a, b) {
|
|
3895
|
+
// Feel free to manipulate these next two lines...
|
|
3896
|
+
if (a.areaMCCode < b.areaMCCode) { return -1; }
|
|
3897
|
+
if (a.areaMCCode > b.areaMCCode) { return 1; }
|
|
3898
|
+
|
|
3899
|
+
// But for byte offset we need to start at 0.
|
|
3900
|
+
if (a.offset < b.offset) { return -1; }
|
|
3901
|
+
if (a.offset > b.offset) { return 1; }
|
|
3902
|
+
|
|
3903
|
+
// Then bit offset
|
|
3904
|
+
if (a.bitOffset < b.bitOffset) { return -1; }
|
|
3905
|
+
if (a.bitOffset > b.bitOffset) { return 1; }
|
|
3906
|
+
|
|
3907
|
+
// Then item length - most first. This way smaller items are optimized into bigger ones if they have the same starting value.
|
|
3908
|
+
if (a.byteLength > b.byteLength) { return -1; }
|
|
3909
|
+
if (a.byteLength < b.byteLength) { return 1; }
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3912
|
+
function doNothing(arg) {
|
|
3913
|
+
return arg;
|
|
3914
|
+
}
|
|
3915
|
+
function binarize(buf) {
|
|
3916
|
+
var i, newBuf;
|
|
3917
|
+
if (buf && !(buf.length % 2)) {
|
|
3918
|
+
newBuf = Buffer.alloc(buf.length / 2);
|
|
3919
|
+
for (i = 0; i < buf.length; i += 2) {
|
|
3920
|
+
newBuf[i / 2] = parseInt("0x" + buf.toString('ascii', i, i + 2));
|
|
3921
|
+
if (isNaN(newBuf[i / 2])) {
|
|
3922
|
+
return undefined;
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
return newBuf;
|
|
3926
|
+
}
|
|
3927
|
+
return undefined;
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
function asciize(buf) {
|
|
3931
|
+
var i, newBuf;
|
|
3932
|
+
if (buf) {
|
|
3933
|
+
newBuf = Buffer.alloc(buf.length * 2);
|
|
3934
|
+
for (i = 0; i < buf.length; i += 1) {
|
|
3935
|
+
newBuf.write(zeroPad(buf[i], 2), i * 2, 2, 'ascii');
|
|
3936
|
+
}
|
|
3937
|
+
return newBuf;
|
|
3938
|
+
}
|
|
3939
|
+
return undefined;
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
function zeroPad(num, places) {
|
|
3943
|
+
var zero = places - num.toString(16).length + 1;
|
|
3944
|
+
return Array(+(zero > 0 && zero)).join("0") + num.toString(16);
|
|
3945
|
+
}
|
|
3946
|
+
|
|
3947
|
+
|
|
3948
|
+
function _enum(list) {
|
|
3949
|
+
list.keys = [];
|
|
3950
|
+
//list.values = [];
|
|
3951
|
+
for (var key in list) {
|
|
3952
|
+
if (key != "keys" && key != "values") {
|
|
3953
|
+
let v = list[key] = list[key];
|
|
3954
|
+
let t = typeof (v);
|
|
3955
|
+
let alt = undefined;
|
|
3956
|
+
if (t == "string" || t == "number")
|
|
3957
|
+
alt = v;
|
|
3958
|
+
else if (t == "object"){
|
|
3959
|
+
if(v.hasOwnProperty('value'))
|
|
3960
|
+
alt = v.value;
|
|
3961
|
+
if(!v.name)
|
|
3962
|
+
v.name = key;
|
|
3963
|
+
}
|
|
3964
|
+
|
|
3965
|
+
if (alt !== undefined)
|
|
3966
|
+
list[alt] = v;
|
|
3967
|
+
|
|
3968
|
+
list.keys.push(key);
|
|
3969
|
+
//list.values.push(list[key]);
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
list.toString = function (intVal) {
|
|
3973
|
+
return list[intVal];
|
|
3974
|
+
}
|
|
3975
|
+
return Object.freeze(list);
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3978
|
+
function isNumeric(n) {
|
|
3979
|
+
return !isNaN(parseFloat(n)) && isFinite(n);
|
|
3980
|
+
}
|