ads-client 1.11.4 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +52 -47
- package/package.json +1 -1
- package/src/ads-client.js +494 -366
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.12.0] - 28.12.2021
|
|
8
|
+
### Changed
|
|
9
|
+
- Improving handling of connection losses ([See pull request #78](https://github.com/jisotalo/ads-client/pull/78))
|
|
10
|
+
- Thank you [Hopperpop](https://github.com/Hopperpop) for contribution!
|
|
11
|
+
- Re-design of connection, reconnection and disconnection system
|
|
12
|
+
- Re-design of automatic reconnection
|
|
13
|
+
- Calling `reconnect()` now automatically re-subscribes all existing subscriptions
|
|
14
|
+
|
|
7
15
|
## [1.11.4] - 17.10.2021
|
|
8
16
|
### Added
|
|
9
17
|
- Added optional `targetAdsPort` parameter to basic raw read and write methods. ([Pull request #79](https://github.com/jisotalo/ads-client/pull/79))
|
package/README.md
CHANGED
|
@@ -22,60 +22,65 @@ Check out the [node-red-contrib-ads-client](https://www.npmjs.com/package/node-r
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
# Table of contents
|
|
25
|
-
|
|
26
25
|
- [Installation](#installation)
|
|
27
|
-
- [Features](#
|
|
26
|
+
- [Features](#features)
|
|
28
27
|
- [Supported and tested platforms](#supported-and-tested-platforms)
|
|
29
28
|
- [Connection setups and possibilities](#connection-setups-and-possibilities)
|
|
30
|
-
- [Enabling localhost support](#enabling-localhost-support)
|
|
29
|
+
- [Enabling localhost support on TwinCAT 3](#enabling-localhost-support-on-twincat-3)
|
|
31
30
|
- [IMPORTANT: Writing STRUCT variables](#important-writing-struct-variables)
|
|
32
31
|
- [IMPORTANT: Things to know when using with TwinCAT 2](#important-things-to-know-when-using-with-twincat-2)
|
|
33
32
|
- [Getting started](#getting-started)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
33
|
+
* [Data types used in getting started](#data-types-used-in-getting-started)
|
|
34
|
+
* [Creating a new Client instance](#creating-a-new-client-instance)
|
|
35
|
+
* [Connecting and disconnecting](#connecting-and-disconnecting)
|
|
36
|
+
* [Reading any type PLC variable](#reading-any-type-plc-variable)
|
|
37
|
+
+ [Example: Reading `INT` type variable](#example-reading-int-type-variable)
|
|
38
|
+
+ [Example: Reading `STRING` type variable](#example-reading-string-type-variable)
|
|
39
|
+
+ [Example: Reading `ENUM` type variable](#example-reading-enum-type-variable)
|
|
40
|
+
+ [Example: Reading `STRUCT` type variable](#example-reading-struct-type-variable)
|
|
41
|
+
+ [Example: Reading `ARRAY OF INT` type variable](#example-reading-array-of-int-type-variable)
|
|
42
|
+
+ [Example: Reading `ARRAY OF STRUCT` type variable](#example-reading-array-of-struct-type-variable)
|
|
43
|
+
+ [Example: Reading `FUNCTION BLOCK` type variable](#example-reading-function-block-type-variable)
|
|
44
|
+
* [Writing any type PLC variable](#writing-any-type-plc-variable)
|
|
45
|
+
+ [Example: Writing `INT` type variable](#example-writing-int-type-variable)
|
|
46
|
+
+ [Example: Writing `STRING` type variable](#example-writing-string-type-variable)
|
|
47
|
+
+ [Example: Writing `ENUM` type variable](#example-writing-enum-type-variable)
|
|
48
|
+
+ [Example: Writing `STRUCT` type variable](#example-writing-struct-type-variable)
|
|
49
|
+
+ [Example: Writing `STRUCT` type variable (with autoFill parameter)](#example-writing-struct-type-variable-with-autofill-parameter-)
|
|
50
|
+
+ [Example: Writing `ARRAY OF INT` type variable](#example-writing-array-of-int-type-variable)
|
|
51
|
+
+ [Example: Writing `ARRAY of STRUCT` type variable](#example-writing-array-of-struct-type-variable)
|
|
52
|
+
+ [Example: Writing `FUNCTION BLOCK` type variable](#example-writing-function-block-type-variable)
|
|
53
|
+
* [Subscribing to PLC variables (device notifications)](#subscribing-to-plc-variables-device-notifications-)
|
|
54
|
+
+ [Subcribe to variable value (on-change)](#subcribe-to-variable-value-on-change-)
|
|
55
|
+
+ [Subcribe to variable value (cyclic)](#subcribe-to-variable-value-cyclic-)
|
|
56
|
+
* [Reading and writing raw data](#reading-and-writing-raw-data)
|
|
57
|
+
+ [Getting symbol index group, offset and size](#getting-symbol-index-group-offset-and-size)
|
|
58
|
+
+ [Reading a single raw value](#reading-a-single-raw-value)
|
|
59
|
+
+ [Writing a single raw value](#writing-a-single-raw-value)
|
|
60
|
+
+ [Reading multiple raw values](#reading-multiple-raw-values)
|
|
61
|
+
+ [Writing multiple raw values](#writing-multiple-raw-values)
|
|
62
|
+
+ [Creating a variable handle and reading a raw value](#creating-a-variable-handle-and-reading-a-raw-value)
|
|
63
|
+
+ [Creating a variable handle and writing a raw value](#creating-a-variable-handle-and-writing-a-raw-value)
|
|
64
|
+
+ [Creating and deleting multiple variable handles](#creating-and-deleting-multiple-variable-handles)
|
|
65
|
+
+ [Converting a raw value to Javascript object](#converting-a-raw-value-to-javascript-object)
|
|
66
|
+
+ [Converting a Javascript object to raw value](#converting-a-javascript-object-to-raw-value)
|
|
67
|
+
* [Reading and writing `POINTER TO` and `REFERENCE TO` variables](#reading-and-writing-pointer-to-and-reference-to-variables)
|
|
68
|
+
+ [Reading a `REFERENCE TO` value](#reading-a-reference-to-value)
|
|
69
|
+
+ [Writing a `REFERENCE TO` value](#writing-a-reference-to-value)
|
|
70
|
+
+ [Reading a `POINTER TO` value](#reading-a-pointer-to-value)
|
|
71
|
+
+ [Writing a `POINTER TO` value](#writing-a-pointer-to-value)
|
|
72
|
+
* [Calling a function block method with parameters using RPC (remote procedure call)](#calling-a-function-block-method-with-parameters-using-rpc-remote-procedure-call-)
|
|
73
|
+
+ [Calling a simple RPC method](#calling-a-simple-rpc-method)
|
|
74
|
+
+ [Using structs with RPC methods](#using-structs-with-rpc-methods)
|
|
75
|
+
* [Starting and stopping the PLC](#starting-and-stopping-the-plc)
|
|
76
|
+
* [Starting and stopping the TwinCAT system](#starting-and-stopping-the-twincat-system)
|
|
77
|
+
* [Sending custom ADS commands](#sending-custom-ads-commands)
|
|
78
|
+
- [Available ads-client events](#available-ads-client-events)
|
|
79
|
+
* [Example: Printing to console when PLC runtime state changes:](#example-printing-to-console-when-plc-runtime-state-changes-)
|
|
80
|
+
* [Example: Catching an error that is not directly from a method call](#example-catching-an-error-that-is-not-directly-from-a-method-call)
|
|
78
81
|
- [Debugging](#debugging)
|
|
82
|
+
* [Enabling debug from code](#enabling-debug-from-code)
|
|
83
|
+
* [Enabling debugging from terminal](#enabling-debugging-from-terminal)
|
|
79
84
|
- [FAQ](#faq)
|
|
80
85
|
- [Documentation](#documentation)
|
|
81
86
|
- [License](#license)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ads-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "Beckhoff TwinCAT ADS client library for Node.js (unofficial). Connects to Beckhoff TwinCAT automation systems using ADS protocol.",
|
|
5
5
|
"main": "./src/ads-client.js",
|
|
6
6
|
"scripts": {
|
package/src/ads-client.js
CHANGED
|
@@ -161,12 +161,12 @@ class Client extends EventEmitter {
|
|
|
161
161
|
activeAdsRequests: {}, //Active ADS requests that wait for answer from router
|
|
162
162
|
activeSubscriptions: {}, //Active device notifications
|
|
163
163
|
symbolVersionNotification: null, //Notification handle of the symbol version changed subscription (used to unsubscribe)
|
|
164
|
-
systemManagerStatePoller: null, //Timer that reads the system manager state (run/config) - This is not available through ADS notifications
|
|
164
|
+
systemManagerStatePoller: { id: 0, timer: null }, //Timer that reads the system manager state (run/config) - This is not available through ADS notifications
|
|
165
165
|
firstStateReadFaultTime: null, //Date when system manager state read failed for the first time
|
|
166
166
|
socketConnectionLostHandler: null, //Handler for socket connection lost event
|
|
167
167
|
socketErrorHandler: null, //Handler for socket error event
|
|
168
168
|
oldSubscriptions: null, //Old subscriptions that were active before connection was lost
|
|
169
|
-
reconnectionTimer: null, //Timer that tries to reconnect intervally
|
|
169
|
+
reconnectionTimer: { id: 0, timer: null }, //Timer that tries to reconnect intervally
|
|
170
170
|
portRegisterTimeoutTimer: null, //Timeout timer of registerAdsPort()
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -290,205 +290,7 @@ class Client extends EventEmitter {
|
|
|
290
290
|
* - If rejected, something went wrong and error info is returned (object)
|
|
291
291
|
*/
|
|
292
292
|
connect() {
|
|
293
|
-
|
|
294
|
-
return new Promise(async (resolve, reject) => {
|
|
295
|
-
|
|
296
|
-
if (this._internals.socket !== null) {
|
|
297
|
-
debug(`connect(): Socket already assigned`)
|
|
298
|
-
return reject(new ClientException(this, 'connect()', 'Connection is already opened. Close the connection first using disconnect()'))
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
debug(`connect(): Starting to connect ${this.settings.routerAddress}:${this.settings.routerTcpPort}`)
|
|
302
|
-
|
|
303
|
-
//Creating a socket and setting it up
|
|
304
|
-
let socket = new net.Socket()
|
|
305
|
-
socket.setNoDelay(true) //Sends data without delay
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
//----- Connecting error events -----
|
|
310
|
-
|
|
311
|
-
//Listening error event during connection
|
|
312
|
-
socket.once('error', err => {
|
|
313
|
-
debug('connect(): Socket connect failed: %O', err)
|
|
314
|
-
|
|
315
|
-
//Remove all events from socket
|
|
316
|
-
socket.removeAllListeners()
|
|
317
|
-
socket = null
|
|
318
|
-
|
|
319
|
-
//Reset connection flag
|
|
320
|
-
this.connection.connected = false
|
|
321
|
-
|
|
322
|
-
reject(new ClientException(this, 'connect()', `Connection to ${this.settings.routerAddress}:${this.settings.routerTcpPort} failed (socket error ${err.errno})`, err))
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
//Listening close event during connection
|
|
328
|
-
socket.once('close', hadError => {
|
|
329
|
-
debug(`connect(): Socket closed by remote, connection failed`)
|
|
330
|
-
|
|
331
|
-
//Remove all events from socket
|
|
332
|
-
socket.removeAllListeners()
|
|
333
|
-
socket = null
|
|
334
|
-
|
|
335
|
-
//Reset connection flag
|
|
336
|
-
this.connection.connected = false
|
|
337
|
-
|
|
338
|
-
reject(new ClientException(this, 'connect()', `Connection to ${this.settings.routerAddress}:${this.settings.routerTcpPort} failed - socket closed by remote (hadError = ${hadError})`))
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
//Listening end event during connection
|
|
343
|
-
socket.once('end', () => {
|
|
344
|
-
debug(`connect(): Socket connection ended by remote, connection failed.`)
|
|
345
|
-
|
|
346
|
-
//Remove all events from socket
|
|
347
|
-
socket.removeAllListeners()
|
|
348
|
-
socket = null
|
|
349
|
-
|
|
350
|
-
//Reset connection flag
|
|
351
|
-
this.connection.connected = false
|
|
352
|
-
|
|
353
|
-
if (this.settings.localAdsPort != null)
|
|
354
|
-
reject(new ClientException(this, 'connect()', `Connection to ${this.settings.routerAddress}:${this.settings.routerTcpPort} failed - socket ended by remote (is the given local ADS port ${this.settings.localAdsPort} already in use?)`))
|
|
355
|
-
else
|
|
356
|
-
reject(new ClientException(this, 'connect()', `Connection to ${this.settings.routerAddress}:${this.settings.routerTcpPort} failed - socket ended by remote`))
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
//Listening timeout event during connection
|
|
360
|
-
socket.once('timeout', () => {
|
|
361
|
-
debug(`connect(): Socket timeout`)
|
|
362
|
-
|
|
363
|
-
//No more timeout needed
|
|
364
|
-
socket.setTimeout(0);
|
|
365
|
-
socket.destroy()
|
|
366
|
-
|
|
367
|
-
//Remove all events from socket
|
|
368
|
-
socket.removeAllListeners()
|
|
369
|
-
socket = null
|
|
370
|
-
|
|
371
|
-
//Reset connection flag
|
|
372
|
-
this.connection.connected = false
|
|
373
|
-
|
|
374
|
-
reject(new ClientException(this, 'connect()', `Connection to ${this.settings.routerAddress}:${this.settings.routerTcpPort} failed (timeout) - No response from router in ${this.settings.timeoutDelay} ms`))
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
//----- Connecting error events end -----
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
//Listening for connect event
|
|
381
|
-
socket.once('connect', async () => {
|
|
382
|
-
debug(`connect(): Socket connection established to ${this.settings.routerAddress}:${this.settings.routerTcpPort}`)
|
|
383
|
-
|
|
384
|
-
//No more timeout needed
|
|
385
|
-
socket.setTimeout(0);
|
|
386
|
-
|
|
387
|
-
this._internals.socket = socket
|
|
388
|
-
|
|
389
|
-
//Try to register an ADS port
|
|
390
|
-
try {
|
|
391
|
-
let res = await _registerAdsPort.call(this)
|
|
392
|
-
|
|
393
|
-
this.connection.connected = true
|
|
394
|
-
this.connection.localAmsNetId = res.amsTcp.data.localAmsNetId
|
|
395
|
-
this.connection.localAdsPort = res.amsTcp.data.localAdsPort
|
|
396
|
-
this.connection.isLocal = this.settings.targetAmsNetId === '127.0.0.1.1.1' || this.settings.targetAmsNetId === this.connection.localAmsNetId
|
|
397
|
-
|
|
398
|
-
debug(`connect(): ADS port registered from router. We are ${this.connection.localAmsNetId}:${this.connection.localAdsPort}`)
|
|
399
|
-
} catch (err) {
|
|
400
|
-
|
|
401
|
-
if (socket) {
|
|
402
|
-
socket.destroy()
|
|
403
|
-
//Remove all events from socket
|
|
404
|
-
socket.removeAllListeners()
|
|
405
|
-
}
|
|
406
|
-
this.connection.connected = false
|
|
407
|
-
|
|
408
|
-
return reject(new ClientException(this, 'connect()', `Registering ADS port from router failed`, err))
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
//Remove the socket events that were used only during connect()
|
|
412
|
-
socket.removeAllListeners('error')
|
|
413
|
-
socket.removeAllListeners('close')
|
|
414
|
-
socket.removeAllListeners('end')
|
|
415
|
-
|
|
416
|
-
//When socket errors from now on, we will close the connection
|
|
417
|
-
this._internals.socketErrorHandler = _onSocketError.bind(this)
|
|
418
|
-
socket.on('error', this._internals.socketErrorHandler)
|
|
419
|
-
|
|
420
|
-
try {
|
|
421
|
-
//Try to read system manager state - If it's OK, connection is successful to the target
|
|
422
|
-
await this.readSystemManagerState()
|
|
423
|
-
_systemManagerStatePoller.call(this)
|
|
424
|
-
|
|
425
|
-
} catch (err) {
|
|
426
|
-
try {
|
|
427
|
-
await this.disconnect()
|
|
428
|
-
} catch (err) {
|
|
429
|
-
debug(`connect(): Reading target system manager failed -> Connection closed`)
|
|
430
|
-
}
|
|
431
|
-
this.connection.connected = false
|
|
432
|
-
|
|
433
|
-
return reject(new ClientException(this, 'connect()', `Connection failed: ${err.message}`, err))
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
try {
|
|
438
|
-
await _reInitializeInternals.call(this)
|
|
439
|
-
|
|
440
|
-
} catch (err) {
|
|
441
|
-
if (this.settings.allowHalfOpen !== true) {
|
|
442
|
-
try {
|
|
443
|
-
await this.disconnect()
|
|
444
|
-
} catch (err) {
|
|
445
|
-
debug(`connect(): Connecting to target PLC runtime failed -> Connection closed`)
|
|
446
|
-
}
|
|
447
|
-
this.connection.connected = false
|
|
448
|
-
|
|
449
|
-
return reject(new ClientException(this, 'connect()', `Target and system manager found but couldn't connect to the PLC runtime (see setting allowHalfOpen): ${err.message}`, err))
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
//Todo: Redesign this
|
|
453
|
-
if (this.metaData.systemManagerState.adsState !== ADS.ADS_STATE.Run)
|
|
454
|
-
_console.call(this, `WARNING: Target is connected but not in RUN mode (mode: ${this.metaData.systemManagerState.adsStateStr}) - connecting to runtime (ADS port ${this.settings.targetAdsPort}) failed`)
|
|
455
|
-
else
|
|
456
|
-
_console.call(this, `WARNING: Target is connected but connecting to runtime (ADS port ${this.settings.targetAdsPort}) failed - Check the port number and that the target system state (${this.metaData.systemManagerState.adsStateStr}) is valid.`)
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
//Listening connection lost events
|
|
460
|
-
this._internals.socketConnectionLostHandler = _onConnectionLost.bind(this, true)
|
|
461
|
-
socket.on('close', this._internals.socketConnectionLostHandler)
|
|
462
|
-
|
|
463
|
-
//We are connected to the target
|
|
464
|
-
this.emit('connect', this.connection)
|
|
465
|
-
|
|
466
|
-
resolve(this.connection)
|
|
467
|
-
})
|
|
468
|
-
|
|
469
|
-
//Listening data event
|
|
470
|
-
socket.on('data', data => {
|
|
471
|
-
_socketReceive.call(this, data)
|
|
472
|
-
})
|
|
473
|
-
|
|
474
|
-
//Timeout only during connecting, other timeouts are handled elsewhere
|
|
475
|
-
socket.setTimeout(this.settings.timeoutDelay);
|
|
476
|
-
|
|
477
|
-
//Finally, connect
|
|
478
|
-
try {
|
|
479
|
-
socket.connect({
|
|
480
|
-
port: this.settings.routerTcpPort,
|
|
481
|
-
host: this.settings.routerAddress,
|
|
482
|
-
localPort: (this.settings.localTcpPort ? this.settings.localTcpPort : null),
|
|
483
|
-
localAddress: (this.settings.localAddress ? this.settings.localAddress : null),
|
|
484
|
-
})
|
|
485
|
-
|
|
486
|
-
} catch (err) {
|
|
487
|
-
this.connection.connected = false
|
|
488
|
-
|
|
489
|
-
reject(new ClientException(this, 'connect()', `Opening socket connection to ${this.settings.routerAddress}:${this.settings.routerTcpPort} failed`, err))
|
|
490
|
-
}
|
|
491
|
-
})
|
|
293
|
+
return _connect.call(this)
|
|
492
294
|
}
|
|
493
295
|
|
|
494
296
|
|
|
@@ -511,96 +313,7 @@ class Client extends EventEmitter {
|
|
|
511
313
|
* - If rejected, connection is still closed but something went wrong during disconnecting and error info is returned
|
|
512
314
|
*/
|
|
513
315
|
disconnect(forceDisconnect = false) {
|
|
514
|
-
return
|
|
515
|
-
debug(`disconnect(): Starting to close connection (force: ${forceDisconnect})`)
|
|
516
|
-
|
|
517
|
-
try {
|
|
518
|
-
if (this._internals.socketConnectionLostHandler) {
|
|
519
|
-
this._internals.socket.off('close', this._internals.socketConnectionLostHandler)
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
} catch (err) {
|
|
523
|
-
//We probably have no socket anymore. Just quit.
|
|
524
|
-
forceDisconnect = true
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
//Clear system manager state poller
|
|
528
|
-
clearTimeout(this._internals.systemManagerStatePoller)
|
|
529
|
-
clearTimeout(this._internals.portRegisterTimeoutTimer)
|
|
530
|
-
|
|
531
|
-
//If forced, then just destroy the socket
|
|
532
|
-
if (forceDisconnect) {
|
|
533
|
-
|
|
534
|
-
if (this._internals.socket) {
|
|
535
|
-
this._internals.socket.removeAllListeners()
|
|
536
|
-
this._internals.socket.destroy()
|
|
537
|
-
}
|
|
538
|
-
this.connection.connected = false
|
|
539
|
-
this.connection.localAdsPort = null
|
|
540
|
-
this._internals.socket = null
|
|
541
|
-
|
|
542
|
-
this.emit('disconnect')
|
|
543
|
-
|
|
544
|
-
return resolve()
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
let error = null
|
|
549
|
-
|
|
550
|
-
try {
|
|
551
|
-
await this.unsubscribeAll()
|
|
552
|
-
} catch (err) {
|
|
553
|
-
error = new ClientException(this, 'disconnect()', err)
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
try {
|
|
557
|
-
await _unsubscribeAllInternals.call(this)
|
|
558
|
-
} catch (err) {
|
|
559
|
-
error = new ClientException(this, 'disconnect()', err)
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
try {
|
|
563
|
-
await _unregisterAdsPort.call(this)
|
|
564
|
-
|
|
565
|
-
//Done
|
|
566
|
-
this.connection.connected = false
|
|
567
|
-
this.connection.localAdsPort = null
|
|
568
|
-
|
|
569
|
-
//Socket should be null but check it anyways
|
|
570
|
-
if (this._internals.socket != null) {
|
|
571
|
-
this._internals.socket.removeAllListeners()
|
|
572
|
-
this._internals.socket.destroy() //Just incase
|
|
573
|
-
this._internals.socket = null
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
debug(`disconnect(): Connection closed successfully`)
|
|
577
|
-
|
|
578
|
-
} catch (err) {
|
|
579
|
-
//Force socket close
|
|
580
|
-
if (this._internals.socket) {
|
|
581
|
-
this._internals.socket.removeAllListeners()
|
|
582
|
-
this._internals.socket.destroy()
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
this.connection.connected = false
|
|
586
|
-
this.connection.localAdsPort = null
|
|
587
|
-
this._internals.socket = null
|
|
588
|
-
|
|
589
|
-
error = new ClientException(this, 'disconnect()', err)
|
|
590
|
-
|
|
591
|
-
debug(`disconnect(): Connection closing failed, connection forced to close`)
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
if (error !== null) {
|
|
595
|
-
error.message = `Disconnected but something failed: ${error.message}`
|
|
596
|
-
this.emit('disconnect')
|
|
597
|
-
|
|
598
|
-
return reject(error)
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
this.emit('disconnect')
|
|
602
|
-
resolve()
|
|
603
|
-
})
|
|
316
|
+
return _disconnect.call(this, forceDisconnect)
|
|
604
317
|
}
|
|
605
318
|
|
|
606
319
|
|
|
@@ -611,42 +324,18 @@ class Client extends EventEmitter {
|
|
|
611
324
|
|
|
612
325
|
|
|
613
326
|
/**
|
|
614
|
-
* Disconnects and reconnects again.
|
|
327
|
+
* Disconnects and reconnects again. Subscribes again to all active subscriptions.
|
|
328
|
+
* To prevent subscribing, call unsubscribeAll() before reconnecting.
|
|
615
329
|
*
|
|
616
330
|
* @param {boolean} [forceDisconnect] - If true, the connection is dropped immediately (default = false)
|
|
617
331
|
*
|
|
618
332
|
* @returns {Promise} Returns a promise (async function)
|
|
619
|
-
* - If resolved,
|
|
620
|
-
* - If rejected,
|
|
333
|
+
* - If resolved, reconnecting was successful
|
|
334
|
+
* - If rejected, reconnecting failed and error info is returned
|
|
621
335
|
*/
|
|
622
336
|
reconnect(forceDisconnect = false) {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
if (this._internals.socket != null) {
|
|
626
|
-
try {
|
|
627
|
-
debug(`reconnect(): Trying to disconnect`)
|
|
628
|
-
|
|
629
|
-
await this.disconnect(forceDisconnect)
|
|
630
|
-
|
|
631
|
-
} catch (err) {
|
|
632
|
-
debug(`reconnect(): Disconnecting failed: %o`, err)
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
debug(`reconnect(): Trying to connect`)
|
|
637
|
-
|
|
638
|
-
return this.connect()
|
|
639
|
-
.then(res => {
|
|
640
|
-
debug(`reconnect(): Connected!`)
|
|
641
|
-
this.emit('reconnect')
|
|
642
|
-
|
|
643
|
-
resolve(res)
|
|
644
|
-
})
|
|
645
|
-
.catch(err => {
|
|
646
|
-
debug(`reconnect(): Connecting failed`)
|
|
647
|
-
reject(err)
|
|
648
|
-
})
|
|
649
|
-
})
|
|
337
|
+
debug(`reconnect(): Reconnecting...`)
|
|
338
|
+
return _reconnect.call(this, forceDisconnect)
|
|
650
339
|
}
|
|
651
340
|
|
|
652
341
|
|
|
@@ -3566,6 +3255,408 @@ class ClientException extends Error {
|
|
|
3566
3255
|
|
|
3567
3256
|
|
|
3568
3257
|
|
|
3258
|
+
|
|
3259
|
+
|
|
3260
|
+
|
|
3261
|
+
/**
|
|
3262
|
+
* Connects to the target system using pre-defined Client::settings (at constructor or manually given)
|
|
3263
|
+
*
|
|
3264
|
+
* @param {boolean} [isReconnecting] - If true, the _connect() call is made during reconnection (-> affects disconnect() calls if connecting fails)
|
|
3265
|
+
*
|
|
3266
|
+
* @returns {Promise<object>} Returns a promise (async function)
|
|
3267
|
+
* - If resolved, client is connected successfully and connection info is returned (object)
|
|
3268
|
+
* - If rejected, something went wrong and error info is returned (object)
|
|
3269
|
+
*
|
|
3270
|
+
* @memberof _LibraryInternals
|
|
3271
|
+
*/
|
|
3272
|
+
function _connect(isReconnecting = false) {
|
|
3273
|
+
return new Promise(async (resolve, reject) => {
|
|
3274
|
+
|
|
3275
|
+
if (this._internals.socket !== null) {
|
|
3276
|
+
debug(`_connect(): Socket already assigned`)
|
|
3277
|
+
return reject(new ClientException(this, '_connect()', 'Connection is already opened. Close the connection first using disconnect()'))
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
debug(`_connect(): Starting to connect ${this.settings.routerAddress}:${this.settings.routerTcpPort}`)
|
|
3281
|
+
|
|
3282
|
+
//Creating a socket and setting it up
|
|
3283
|
+
let socket = new net.Socket()
|
|
3284
|
+
socket.setNoDelay(true) //Sends data without delay
|
|
3285
|
+
|
|
3286
|
+
|
|
3287
|
+
|
|
3288
|
+
//----- Connecting error events -----
|
|
3289
|
+
|
|
3290
|
+
//Listening error event during connection
|
|
3291
|
+
socket.once('error', err => {
|
|
3292
|
+
debug('_connect(): Socket connect failed: %O', err)
|
|
3293
|
+
|
|
3294
|
+
//Remove all events from socket
|
|
3295
|
+
socket.removeAllListeners()
|
|
3296
|
+
socket = null
|
|
3297
|
+
|
|
3298
|
+
//Reset connection flag
|
|
3299
|
+
this.connection.connected = false
|
|
3300
|
+
|
|
3301
|
+
reject(new ClientException(this, '_connect()', `Connection to ${this.settings.routerAddress}:${this.settings.routerTcpPort} failed (socket error ${err.errno})`, err))
|
|
3302
|
+
})
|
|
3303
|
+
|
|
3304
|
+
|
|
3305
|
+
|
|
3306
|
+
//Listening close event during connection
|
|
3307
|
+
socket.once('close', hadError => {
|
|
3308
|
+
debug(`_connect(): Socket closed by remote, connection failed`)
|
|
3309
|
+
|
|
3310
|
+
//Remove all events from socket
|
|
3311
|
+
socket.removeAllListeners()
|
|
3312
|
+
socket = null
|
|
3313
|
+
|
|
3314
|
+
//Reset connection flag
|
|
3315
|
+
this.connection.connected = false
|
|
3316
|
+
|
|
3317
|
+
reject(new ClientException(this, '_connect()', `Connection to ${this.settings.routerAddress}:${this.settings.routerTcpPort} failed - socket closed by remote (hadError = ${hadError})`))
|
|
3318
|
+
})
|
|
3319
|
+
|
|
3320
|
+
|
|
3321
|
+
//Listening end event during connection
|
|
3322
|
+
socket.once('end', () => {
|
|
3323
|
+
debug(`_connect(): Socket connection ended by remote, connection failed.`)
|
|
3324
|
+
|
|
3325
|
+
//Remove all events from socket
|
|
3326
|
+
socket.removeAllListeners()
|
|
3327
|
+
socket = null
|
|
3328
|
+
|
|
3329
|
+
//Reset connection flag
|
|
3330
|
+
this.connection.connected = false
|
|
3331
|
+
|
|
3332
|
+
if (this.settings.localAdsPort != null)
|
|
3333
|
+
reject(new ClientException(this, '_connect()', `Connection to ${this.settings.routerAddress}:${this.settings.routerTcpPort} failed - socket ended by remote (is the given local ADS port ${this.settings.localAdsPort} already in use?)`))
|
|
3334
|
+
else
|
|
3335
|
+
reject(new ClientException(this, '_connect()', `Connection to ${this.settings.routerAddress}:${this.settings.routerTcpPort} failed - socket ended by remote`))
|
|
3336
|
+
})
|
|
3337
|
+
|
|
3338
|
+
//Listening timeout event during connection
|
|
3339
|
+
socket.once('timeout', () => {
|
|
3340
|
+
debug(`_connect(): Socket timeout`)
|
|
3341
|
+
|
|
3342
|
+
//No more timeout needed
|
|
3343
|
+
socket.setTimeout(0);
|
|
3344
|
+
socket.destroy()
|
|
3345
|
+
|
|
3346
|
+
//Remove all events from socket
|
|
3347
|
+
socket.removeAllListeners()
|
|
3348
|
+
socket = null
|
|
3349
|
+
|
|
3350
|
+
//Reset connection flag
|
|
3351
|
+
this.connection.connected = false
|
|
3352
|
+
|
|
3353
|
+
reject(new ClientException(this, '_connect()', `Connection to ${this.settings.routerAddress}:${this.settings.routerTcpPort} failed (timeout) - No response from router in ${this.settings.timeoutDelay} ms`))
|
|
3354
|
+
})
|
|
3355
|
+
|
|
3356
|
+
//----- Connecting error events end -----
|
|
3357
|
+
|
|
3358
|
+
|
|
3359
|
+
//Listening for connect event
|
|
3360
|
+
socket.once('connect', async () => {
|
|
3361
|
+
debug(`_connect(): Socket connection established to ${this.settings.routerAddress}:${this.settings.routerTcpPort}`)
|
|
3362
|
+
|
|
3363
|
+
//No more timeout needed
|
|
3364
|
+
socket.setTimeout(0);
|
|
3365
|
+
|
|
3366
|
+
this._internals.socket = socket
|
|
3367
|
+
|
|
3368
|
+
//Try to register an ADS port
|
|
3369
|
+
try {
|
|
3370
|
+
let res = await _registerAdsPort.call(this)
|
|
3371
|
+
|
|
3372
|
+
this.connection.connected = true
|
|
3373
|
+
this.connection.localAmsNetId = res.amsTcp.data.localAmsNetId
|
|
3374
|
+
this.connection.localAdsPort = res.amsTcp.data.localAdsPort
|
|
3375
|
+
this.connection.isLocal = this.settings.targetAmsNetId === '127.0.0.1.1.1' || this.settings.targetAmsNetId === this.connection.localAmsNetId
|
|
3376
|
+
|
|
3377
|
+
debug(`_connect(): ADS port registered from router. We are ${this.connection.localAmsNetId}:${this.connection.localAdsPort}`)
|
|
3378
|
+
} catch (err) {
|
|
3379
|
+
|
|
3380
|
+
if (socket) {
|
|
3381
|
+
socket.destroy()
|
|
3382
|
+
//Remove all events from socket
|
|
3383
|
+
socket.removeAllListeners()
|
|
3384
|
+
}
|
|
3385
|
+
this.connection.connected = false
|
|
3386
|
+
|
|
3387
|
+
return reject(new ClientException(this, '_connect()', `Registering ADS port from router failed`, err))
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
//Remove the socket events that were used only during _connect()
|
|
3391
|
+
socket.removeAllListeners('error')
|
|
3392
|
+
socket.removeAllListeners('close')
|
|
3393
|
+
socket.removeAllListeners('end')
|
|
3394
|
+
|
|
3395
|
+
//When socket errors from now on, we will close the connection
|
|
3396
|
+
this._internals.socketErrorHandler = _onSocketError.bind(this)
|
|
3397
|
+
socket.on('error', this._internals.socketErrorHandler)
|
|
3398
|
+
|
|
3399
|
+
try {
|
|
3400
|
+
//Try to read system manager state - If it's OK, connection is successful to the target
|
|
3401
|
+
await this.readSystemManagerState()
|
|
3402
|
+
_systemManagerStatePoller.call(this)
|
|
3403
|
+
|
|
3404
|
+
} catch (err) {
|
|
3405
|
+
try {
|
|
3406
|
+
await _disconnect.call(this, false, isReconnecting)
|
|
3407
|
+
} catch (err) {
|
|
3408
|
+
debug(`_connect(): Reading target system manager failed -> Connection closed`)
|
|
3409
|
+
}
|
|
3410
|
+
this.connection.connected = false
|
|
3411
|
+
|
|
3412
|
+
return reject(new ClientException(this, '_connect()', `Connection failed: ${err.message}`, err))
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
|
|
3416
|
+
try {
|
|
3417
|
+
await _reInitializeInternals.call(this)
|
|
3418
|
+
|
|
3419
|
+
} catch (err) {
|
|
3420
|
+
if (this.settings.allowHalfOpen !== true) {
|
|
3421
|
+
try {
|
|
3422
|
+
await _disconnect.call(this, false, true)
|
|
3423
|
+
} catch (err) {
|
|
3424
|
+
debug(`_connect(): Connecting to target PLC runtime failed -> Connection closed`)
|
|
3425
|
+
}
|
|
3426
|
+
this.connection.connected = false
|
|
3427
|
+
|
|
3428
|
+
return reject(new ClientException(this, '_connect()', `Target and system manager found but couldn't connect to the PLC runtime (see setting allowHalfOpen): ${err.message}`, err))
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
//Todo: Redesign this
|
|
3432
|
+
if (this.metaData.systemManagerState.adsState !== ADS.ADS_STATE.Run)
|
|
3433
|
+
_console.call(this, `WARNING: Target is connected but not in RUN mode (mode: ${this.metaData.systemManagerState.adsStateStr}) - connecting to runtime (ADS port ${this.settings.targetAdsPort}) failed`)
|
|
3434
|
+
else
|
|
3435
|
+
_console.call(this, `WARNING: Target is connected but connecting to runtime (ADS port ${this.settings.targetAdsPort}) failed - Check the port number and that the target system state (${this.metaData.systemManagerState.adsStateStr}) is valid.`)
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
//Listening connection lost events
|
|
3439
|
+
this._internals.socketConnectionLostHandler = _onConnectionLost.bind(this, true)
|
|
3440
|
+
socket.on('close', this._internals.socketConnectionLostHandler)
|
|
3441
|
+
|
|
3442
|
+
//We are connected to the target
|
|
3443
|
+
this.emit('connect', this.connection)
|
|
3444
|
+
|
|
3445
|
+
resolve(this.connection)
|
|
3446
|
+
})
|
|
3447
|
+
|
|
3448
|
+
//Listening data event
|
|
3449
|
+
socket.on('data', data => {
|
|
3450
|
+
_socketReceive.call(this, data)
|
|
3451
|
+
})
|
|
3452
|
+
|
|
3453
|
+
//Timeout only during connecting, other timeouts are handled elsewhere
|
|
3454
|
+
socket.setTimeout(this.settings.timeoutDelay);
|
|
3455
|
+
|
|
3456
|
+
//Finally, connect
|
|
3457
|
+
try {
|
|
3458
|
+
socket.connect({
|
|
3459
|
+
port: this.settings.routerTcpPort,
|
|
3460
|
+
host: this.settings.routerAddress,
|
|
3461
|
+
localPort: (this.settings.localTcpPort ? this.settings.localTcpPort : null),
|
|
3462
|
+
localAddress: (this.settings.localAddress ? this.settings.localAddress : null),
|
|
3463
|
+
})
|
|
3464
|
+
|
|
3465
|
+
} catch (err) {
|
|
3466
|
+
this.connection.connected = false
|
|
3467
|
+
|
|
3468
|
+
reject(new ClientException(this, '_connect()', `Opening socket connection to ${this.settings.routerAddress}:${this.settings.routerTcpPort} failed`, err))
|
|
3469
|
+
}
|
|
3470
|
+
})
|
|
3471
|
+
}
|
|
3472
|
+
|
|
3473
|
+
|
|
3474
|
+
/**
|
|
3475
|
+
* Unsubscribes all notifications, unregisters ADS port from router (if it was registered)
|
|
3476
|
+
* and disconnects target system and ADS router
|
|
3477
|
+
*
|
|
3478
|
+
* @param {boolean} [forceDisconnect] - If true, the connection is dropped immediately (default = false)
|
|
3479
|
+
* @param {boolean} [isReconnecting] - If true, _disconnect() call is made during reconnecting
|
|
3480
|
+
*
|
|
3481
|
+
* @returns {Promise<object>} Returns a promise (async function)
|
|
3482
|
+
* - If resolved, disconnect was successful
|
|
3483
|
+
* - If rejected, connection is still closed but something went wrong during disconnecting and error info is returned
|
|
3484
|
+
*
|
|
3485
|
+
* @memberof _LibraryInternals
|
|
3486
|
+
*/
|
|
3487
|
+
function _disconnect(forceDisconnect = false, isReconnecting = false) {
|
|
3488
|
+
return new Promise(async (resolve, reject) => {
|
|
3489
|
+
|
|
3490
|
+
debug(`_disconnect(): Starting to close connection (force: ${forceDisconnect})`)
|
|
3491
|
+
|
|
3492
|
+
try {
|
|
3493
|
+
if (this._internals.socketConnectionLostHandler) {
|
|
3494
|
+
this._internals.socket.off('close', this._internals.socketConnectionLostHandler)
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
} catch (err) {
|
|
3498
|
+
//We probably have no socket anymore. Just quit.
|
|
3499
|
+
forceDisconnect = true
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
//Clear reconnection timer only when not reconnecting
|
|
3503
|
+
if (!isReconnecting) {
|
|
3504
|
+
_clearTimer(this._internals.reconnectionTimer)
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
//Clear other timers
|
|
3508
|
+
_clearTimer(this._internals.systemManagerStatePoller)
|
|
3509
|
+
clearTimeout(this._internals.portRegisterTimeoutTimer)
|
|
3510
|
+
|
|
3511
|
+
//If forced, then just destroy the socket
|
|
3512
|
+
if (forceDisconnect) {
|
|
3513
|
+
|
|
3514
|
+
if (this._internals.socket) {
|
|
3515
|
+
this._internals.socket.removeAllListeners()
|
|
3516
|
+
this._internals.socket.destroy()
|
|
3517
|
+
}
|
|
3518
|
+
this.connection.connected = false
|
|
3519
|
+
this.connection.localAdsPort = null
|
|
3520
|
+
this._internals.socket = null
|
|
3521
|
+
|
|
3522
|
+
this.emit('disconnect')
|
|
3523
|
+
|
|
3524
|
+
return resolve()
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
|
|
3528
|
+
let error = null
|
|
3529
|
+
|
|
3530
|
+
try {
|
|
3531
|
+
await this.unsubscribeAll()
|
|
3532
|
+
} catch (err) {
|
|
3533
|
+
error = new ClientException(this, 'disconnect()', err)
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3536
|
+
try {
|
|
3537
|
+
await _unsubscribeAllInternals.call(this)
|
|
3538
|
+
} catch (err) {
|
|
3539
|
+
error = new ClientException(this, 'disconnect()', err)
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
try {
|
|
3543
|
+
await _unregisterAdsPort.call(this)
|
|
3544
|
+
|
|
3545
|
+
//Done
|
|
3546
|
+
this.connection.connected = false
|
|
3547
|
+
this.connection.localAdsPort = null
|
|
3548
|
+
|
|
3549
|
+
//Socket should be null but check it anyways
|
|
3550
|
+
if (this._internals.socket != null) {
|
|
3551
|
+
this._internals.socket.removeAllListeners()
|
|
3552
|
+
this._internals.socket.destroy() //Just incase
|
|
3553
|
+
this._internals.socket = null
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
debug(`_disconnect(): Connection closed successfully`)
|
|
3557
|
+
|
|
3558
|
+
} catch (err) {
|
|
3559
|
+
//Force socket close
|
|
3560
|
+
if (this._internals.socket) {
|
|
3561
|
+
this._internals.socket.removeAllListeners()
|
|
3562
|
+
this._internals.socket.destroy()
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
this.connection.connected = false
|
|
3566
|
+
this.connection.localAdsPort = null
|
|
3567
|
+
this._internals.socket = null
|
|
3568
|
+
|
|
3569
|
+
error = new ClientException(this, 'disconnect()', err)
|
|
3570
|
+
|
|
3571
|
+
debug(`_disconnect(): Connection closing failed, connection forced to close`)
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
if (error !== null) {
|
|
3575
|
+
error.message = `Disconnected but something failed: ${error.message}`
|
|
3576
|
+
this.emit('disconnect')
|
|
3577
|
+
|
|
3578
|
+
return reject(error)
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
this.emit('disconnect')
|
|
3582
|
+
resolve()
|
|
3583
|
+
})
|
|
3584
|
+
}
|
|
3585
|
+
|
|
3586
|
+
|
|
3587
|
+
|
|
3588
|
+
/**
|
|
3589
|
+
* Disconnects and reconnects again. Subscribes again to all active subscriptions.
|
|
3590
|
+
* To prevent subscribing, call unsubscribeAll() before reconnecting.
|
|
3591
|
+
*
|
|
3592
|
+
* @param {boolean} [forceDisconnect] - If true, the connection is dropped immediately (default = false)
|
|
3593
|
+
* @param {boolean} [isReconnecting] - If true, _reconnect() call is made during reconnecting
|
|
3594
|
+
*
|
|
3595
|
+
* @returns {Promise<object>} Returns a promise (async function)
|
|
3596
|
+
* - If resolved, reconnecting was successful
|
|
3597
|
+
* - If rejected, reconnecting failed and error info is returned
|
|
3598
|
+
*
|
|
3599
|
+
* @memberof _LibraryInternals
|
|
3600
|
+
*/
|
|
3601
|
+
function _reconnect(forceDisconnect = false, isReconnecting = false) {
|
|
3602
|
+
return new Promise(async (resolve, reject) => {
|
|
3603
|
+
|
|
3604
|
+
//Clear all cached symbols and data types (might be incorrect)
|
|
3605
|
+
this.metaData.symbols = {}
|
|
3606
|
+
this.metaData.dataTypes = {}
|
|
3607
|
+
|
|
3608
|
+
//Save active subscriptions to memory and delete olds
|
|
3609
|
+
if (this._internals.oldSubscriptions == null) {
|
|
3610
|
+
this._internals.oldSubscriptions = {}
|
|
3611
|
+
Object.assign(this._internals.oldSubscriptions, this._internals.activeSubscriptions)
|
|
3612
|
+
|
|
3613
|
+
debug(`_reconnect(): Total of ${Object.keys(this._internals.activeSubscriptions).length} subcriptions saved for reinitializing`)
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
this._internals.activeSubscriptions = {}
|
|
3617
|
+
|
|
3618
|
+
if (this._internals.socket != null) {
|
|
3619
|
+
try {
|
|
3620
|
+
debug(`_reconnect(): Trying to disconnect`)
|
|
3621
|
+
|
|
3622
|
+
await _disconnect.call(this, forceDisconnect, isReconnecting)
|
|
3623
|
+
|
|
3624
|
+
} catch (err) {
|
|
3625
|
+
//debug(`_reconnect(): Disconnecting failed: %o`, err)
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
|
|
3629
|
+
debug(`_reconnect(): Trying to connect`)
|
|
3630
|
+
|
|
3631
|
+
return _connect.call(this, true)
|
|
3632
|
+
.then(res => {
|
|
3633
|
+
debug(`_reconnect(): Connected!`)
|
|
3634
|
+
|
|
3635
|
+
//Reconnected, try to subscribe again
|
|
3636
|
+
_reInitializeSubscriptions.call(this, this._internals.oldSubscriptions)
|
|
3637
|
+
.then(() => {
|
|
3638
|
+
_console.call(this, `PLC runtime reconnected successfully and all subscriptions were restored!`)
|
|
3639
|
+
|
|
3640
|
+
debug(`_reconnect(): Connection and subscriptions reinitialized. Connection is back.`)
|
|
3641
|
+
})
|
|
3642
|
+
.catch(err => {
|
|
3643
|
+
_console.call(this, `PLC runtime reconnected successfully but not all subscriptions were restored. Error info: ${err}`)
|
|
3644
|
+
|
|
3645
|
+
debug(`_reconnect(): Connection and some subscriptions reinitialized. Connection is back.`)
|
|
3646
|
+
})
|
|
3647
|
+
|
|
3648
|
+
this.emit('reconnect')
|
|
3649
|
+
|
|
3650
|
+
resolve(res)
|
|
3651
|
+
})
|
|
3652
|
+
.catch(err => {
|
|
3653
|
+
debug(`_reconnect(): Connecting failed`)
|
|
3654
|
+
reject(err)
|
|
3655
|
+
})
|
|
3656
|
+
})
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
|
|
3569
3660
|
/**
|
|
3570
3661
|
* Registers a new ADS port from used AMS router
|
|
3571
3662
|
*
|
|
@@ -3677,8 +3768,8 @@ function _unregisterAdsPort() {
|
|
|
3677
3768
|
return new Promise(async (resolve, reject) => {
|
|
3678
3769
|
debugD(`_unregisterAdsPort(): Unregister ads port ${this.connection.localAdsPort} from ${this.settings.routerAddress}:${this.settings.routerTcpPort}`)
|
|
3679
3770
|
|
|
3680
|
-
//If we have manually given ADS port, we only need to close the connection manually
|
|
3681
|
-
if (this.settings.localAdsPort) {
|
|
3771
|
+
//If we have manually given AmsNetId and ADS port, we only need to close the connection manually
|
|
3772
|
+
if (this.settings.localAmsNetId && this.settings.localAdsPort) {
|
|
3682
3773
|
debug(`_unregisterAdsPort(): Local AmsNetId and ADS port manually given so no need to unregister`)
|
|
3683
3774
|
|
|
3684
3775
|
|
|
@@ -3829,8 +3920,7 @@ async function _onSocketError(err) {
|
|
|
3829
3920
|
*/
|
|
3830
3921
|
async function _onConnectionLost(socketFailure = false) {
|
|
3831
3922
|
//Clear timers
|
|
3832
|
-
|
|
3833
|
-
clearTimeout(this._internals.reconnectionTimer)
|
|
3923
|
+
_clearTimer(this._internals.systemManagerStatePoller)
|
|
3834
3924
|
|
|
3835
3925
|
debug(`_onConnectionLost(): Connection was lost. Socket failure: ${socketFailure}`)
|
|
3836
3926
|
|
|
@@ -3851,53 +3941,70 @@ async function _onConnectionLost(socketFailure = false) {
|
|
|
3851
3941
|
|
|
3852
3942
|
_console.call(this, 'WARNING: Connection was lost. Trying to reconnect...')
|
|
3853
3943
|
|
|
3944
|
+
const tryToReconnect = async (firstTime, timerId) => {
|
|
3854
3945
|
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3946
|
+
//If the timer has changed, quit here
|
|
3947
|
+
if (this._internals.reconnectionTimer.id !== timerId) {
|
|
3948
|
+
return
|
|
3949
|
+
}
|
|
3858
3950
|
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3951
|
+
//Try to reconnect
|
|
3952
|
+
_reconnect.call(this, socketFailure, true)
|
|
3953
|
+
.then(res => {
|
|
3954
|
+
|
|
3955
|
+
//Success -> remove timer
|
|
3956
|
+
_clearTimer(this._internals.reconnectionTimer)
|
|
3863
3957
|
|
|
3864
|
-
|
|
3958
|
+
})
|
|
3959
|
+
.catch(err => {
|
|
3960
|
+
//Reconnecting failed
|
|
3961
|
+
if (firstTime)
|
|
3962
|
+
_console.call(this, `WARNING: Reconnecting failed. Keeping trying in the background every ${this.settings.reconnectInterval} ms...`)
|
|
3963
|
+
|
|
3964
|
+
//If this is still a valid timer, start over again
|
|
3965
|
+
if (this._internals.reconnectionTimer.id === timerId) {
|
|
3966
|
+
//Creating a new timer with the same id
|
|
3967
|
+
this._internals.reconnectionTimer.timer = setTimeout(
|
|
3968
|
+
() => tryToReconnect(false, timerId),
|
|
3969
|
+
this.settings.reconnectInterval
|
|
3970
|
+
)
|
|
3971
|
+
} else {
|
|
3972
|
+
debugD(`_onConnectionLost(): Timer is no more valid, quiting here`)
|
|
3973
|
+
}
|
|
3974
|
+
})
|
|
3865
3975
|
}
|
|
3866
3976
|
|
|
3867
|
-
|
|
3977
|
+
//Clearing old timer if there is one + increasing timer id
|
|
3978
|
+
_clearTimer(this._internals.reconnectionTimer)
|
|
3868
3979
|
|
|
3980
|
+
//Starting poller timer
|
|
3981
|
+
this._internals.reconnectionTimer.timer = setTimeout(
|
|
3982
|
+
() => tryToReconnect(true, this._internals.reconnectionTimer.id),
|
|
3983
|
+
this.settings.reconnectInterval
|
|
3984
|
+
)
|
|
3985
|
+
}
|
|
3869
3986
|
|
|
3870
|
-
const tryToReconnect = async (firstTime) => {
|
|
3871
|
-
clearTimeout(this._internals.reconnectionTimer)
|
|
3872
3987
|
|
|
3873
|
-
this.reconnect(socketFailure)
|
|
3874
|
-
.then(res => {
|
|
3875
3988
|
|
|
3876
|
-
_reInitializeSubscriptions.call(this, this._internals.oldSubscriptions)
|
|
3877
|
-
.then(() => {
|
|
3878
|
-
_console.call(this, `PLC runtime reconnected successfully and all subscriptions were restored!`)
|
|
3879
3989
|
|
|
3880
|
-
debug(`_onConnectionLost(): Connection and subscriptions reinitialized. Connection is back.`)
|
|
3881
|
-
})
|
|
3882
|
-
.catch(err => {
|
|
3883
|
-
_console.call(this, `PLC runtime reconnected successfully but not all subscriptions were restored. Error info: ${err}`)
|
|
3884
3990
|
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3991
|
+
/**
|
|
3992
|
+
* Clears given object timer if it's available
|
|
3993
|
+
* and increases the id
|
|
3994
|
+
*
|
|
3995
|
+
* @param {object} timerObject Timer object {id, timer}
|
|
3996
|
+
*
|
|
3997
|
+
* @memberof _LibraryInternals
|
|
3998
|
+
*/
|
|
3999
|
+
function _clearTimer(timerObject) {
|
|
4000
|
+
//Clearing timer
|
|
4001
|
+
clearTimeout(timerObject.timer)
|
|
4002
|
+
timerObject.timer = null
|
|
3897
4003
|
|
|
3898
|
-
|
|
3899
|
-
|
|
4004
|
+
//Increasing timer id
|
|
4005
|
+
timerObject.id = timerObject.id < Number.MAX_SAFE_INTEGER ? timerObject.id + 1 : 0;
|
|
3900
4006
|
}
|
|
4007
|
+
|
|
3901
4008
|
|
|
3902
4009
|
|
|
3903
4010
|
|
|
@@ -4167,10 +4274,15 @@ async function _onSymbolVersionChanged(data) {
|
|
|
4167
4274
|
* @memberof _LibraryInternals
|
|
4168
4275
|
*/
|
|
4169
4276
|
function _systemManagerStatePoller() {
|
|
4170
|
-
clearTimeout(this._internals.systemManagerStatePoller)
|
|
4171
4277
|
|
|
4172
|
-
const poller = async () => {
|
|
4173
|
-
let startAgain = true
|
|
4278
|
+
const poller = async (timerId) => {
|
|
4279
|
+
let startAgain = true
|
|
4280
|
+
let oldState = this.metaData.systemManagerState
|
|
4281
|
+
|
|
4282
|
+
//If the timer has changed, quit here
|
|
4283
|
+
if (this._internals.systemManagerStatePoller.id !== timerId){
|
|
4284
|
+
return
|
|
4285
|
+
}
|
|
4174
4286
|
|
|
4175
4287
|
try {
|
|
4176
4288
|
await this.readSystemManagerState()
|
|
@@ -4191,6 +4303,7 @@ function _systemManagerStatePoller() {
|
|
|
4191
4303
|
_onConnectionLost.call(this)
|
|
4192
4304
|
}
|
|
4193
4305
|
}
|
|
4306
|
+
|
|
4194
4307
|
} catch (err) {
|
|
4195
4308
|
debug(`_systemManagerStatePoller(): Reading system manager state failed: %o`, err)
|
|
4196
4309
|
|
|
@@ -4207,11 +4320,26 @@ function _systemManagerStatePoller() {
|
|
|
4207
4320
|
}
|
|
4208
4321
|
}
|
|
4209
4322
|
|
|
4210
|
-
if
|
|
4211
|
-
|
|
4323
|
+
//Try again if required AND this is still the valid timer
|
|
4324
|
+
if (startAgain && this._internals.systemManagerStatePoller.id === timerId) {
|
|
4325
|
+
//Creating a new timer with the same id
|
|
4326
|
+
this._internals.systemManagerStatePoller.timer = setTimeout(
|
|
4327
|
+
() => poller(timerId),
|
|
4328
|
+
this.settings.checkStateInterval
|
|
4329
|
+
)
|
|
4330
|
+
}
|
|
4212
4331
|
}
|
|
4213
4332
|
|
|
4214
|
-
|
|
4333
|
+
|
|
4334
|
+
//Clearing old timer if there is one + increasing timer id
|
|
4335
|
+
_clearTimer(this._internals.systemManagerStatePoller)
|
|
4336
|
+
|
|
4337
|
+
//Starting poller timer
|
|
4338
|
+
this._internals.systemManagerStatePoller.timer = setTimeout(
|
|
4339
|
+
() => poller(this._internals.systemManagerStatePoller.id),
|
|
4340
|
+
this.settings.checkStateInterval
|
|
4341
|
+
)
|
|
4342
|
+
|
|
4215
4343
|
}
|
|
4216
4344
|
|
|
4217
4345
|
|
|
@@ -4286,7 +4414,7 @@ async function _onRouterStateChanged(data) {
|
|
|
4286
4414
|
//If we have a local connection, connection needs to be reinitialized
|
|
4287
4415
|
if (this.connection.isLocal === true) {
|
|
4288
4416
|
//We should stop polling system manager, it will be reinitialized later
|
|
4289
|
-
|
|
4417
|
+
_clearTimer(this._internals.systemManagerStatePoller)
|
|
4290
4418
|
|
|
4291
4419
|
debug(`_onRouterStateChanged(): Local loopback connection active, monitoring router state`)
|
|
4292
4420
|
|