@teamcymbeline/node-red-contrib-mcprotocol 0.0.0-stage → 1.4.1

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.
@@ -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
+ }