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 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](#installation)
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
- - [Data types used in getting started](data-types-used-in-getting-started)
35
- - [Creating a new Client instance](#creating-a-new-client-instance)
36
- - [Connecting and disconnecting](#connecting-and-disconnecting)
37
- - [Reading any type PLC variable](#reading-any-type-plc-variable)
38
- - [Example: Reading INT type variable](#example-reading-int-type-variable)
39
- - [Example: Reading STRING type variable](#example-reading-string-type-variable)
40
- - [Example: Reading ENUM type variable](#example-reading-enum-type-variable)
41
- - [Example: Reading STRUCT type variable](#example-reading-struct-type-variable)
42
- - [Example: Reading ARRAY OF INT type variable](#example-reading-array-of-int-type-variable)
43
- - [Example: Reading ARRAY OF STRUCT type variable](#example-reading-array-of-struct-type-variable)
44
- - [Example: Reading FUNCTION BLOCK type variable](#example-reading-function-block-type-variable)
45
- - [Writing any type PLC variable](#writing-any-type-plc-variable)
46
- - [Example: Writing INT type variable](#example-writing-int-type-variable)
47
- - [Example: Writing STRING type variable](#example-writing-string-type-variable)
48
- - [Example: Writing ENUM type variable](#example-writing-enum-type-variable)
49
- - [Example: Writing STRUCT type variable](#example-writing-struct-type-variable)
50
- - [Example: Writing STRUCT type variable (with autoFill parameter)](#example-writing-struct-type-variable-with-autofill-parameter)
51
- - [Example: Writing ARRAY OF INT type variable](#example-writing-array-of-int-type-variable)
52
- - [Example: Writing ARRAY of STRUCT type variable](#example-writing-array-of-struct-type-variable)
53
- - [Example: Writing FUNCTION BLOCK type variable](#example-writing-function-block-type-variable)
54
- - [Subscribing to PLC variables (device notifications)](#subscribing-to-plc-variables-device-notifications)
55
- - [Subcribe to variable value (on-change)](#subcribe-to-variable-value-on-change)
56
- - [Subcribe to variable value (cyclic)](#subcribe-to-variable-value-cyclic)
57
- - [Reading and writing raw data](#reading-and-writing-raw-data)
58
- - [Getting symbol index group, offset and size](#getting-symbol-index-group-offset-and-size)
59
- - [Reading a single raw value](#reading-a-single-raw-value)
60
- - [Writing a single raw value](#writing-a-single-raw-value)
61
- - [Reading multiple raw values](#reading-multiple-raw-values)
62
- - [Writing multiple raw values](#writing-multiple-raw-values)
63
- - [Creating a variable handle and reading a raw value](#creating-a-variable-handle-and-reading-a-raw-value)
64
- - [Creating a variable handle and writing a raw value](#creating-a-variable-handle-and-writing-a-raw-value)
65
- - [Creating and deleting multiple variable handles](#creating-and-deleting-multiple-variable-handles)
66
- - [Converting a raw value to Javascript object](#converting-a-raw-value-to-javascript-object)
67
- - [Converting a Javascript object to raw value](#converting-a-javascript-object-to-raw-value)
68
- - [Reading and writing POINTER TO and REFERENCE TO variables](#reading-and-writing-pointer-to-and-reference-to-variables)
69
- - [Reading a REFERENCE TO value](#reading-a-reference-to-value)
70
- - [Writing a REFERENCE TO value](#writing-a-reference-to-value)
71
- - [Reading a POINTER TO value](#reading-a-pointer-to-value)
72
- - [Writing a POINTER TO value](#writing-a-pointer-to-value)
73
- - [Calling a function block method with parameters using RPC (remote procedure call)](#calling-a-function-block-method-with-parameters-using-rpc-remote-procedure-call)
74
- - [Starting and stopping the PLC](#starting-and-stopping-the-plc)
75
- - [Starting and stopping the TwinCAT system](#starting-and-stopping-the-twincat-system)
76
- - [Sending custom ADS commands](#sending-custom-ads-commands)
77
- - [Available ads-client events](#available-ads-client-events)
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.11.4",
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 new Promise(async (resolve, reject) => {
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. At the moment does NOT reinitialize subscriptions, everything is lost
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, connection to PLC runtime is successful
620
- * - If rejected, connection to PLC runtime failed
333
+ * - If resolved, reconnecting was successful
334
+ * - If rejected, reconnecting failed and error info is returned
621
335
  */
622
336
  reconnect(forceDisconnect = false) {
623
- return new Promise(async (resolve, reject) => {
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
- clearTimeout(this._internals.systemManagerStatePoller)
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
- //Clear all cached symbols and data types (might be incorrect)
3856
- this.metaData.symbols = {}
3857
- this.metaData.dataTypes = {}
3946
+ //If the timer has changed, quit here
3947
+ if (this._internals.reconnectionTimer.id !== timerId) {
3948
+ return
3949
+ }
3858
3950
 
3859
- //Save active subscriptions to memory and delete olds
3860
- if (this._internals.oldSubscriptions == null) {
3861
- this._internals.oldSubscriptions = {}
3862
- Object.assign(this._internals.oldSubscriptions, this._internals.activeSubscriptions)
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
- debugD(`_onConnectionLost(): Total of ${Object.keys(this._internals.activeSubscriptions).length} subcriptions saved for reinitializing`)
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
- this._internals.activeSubscriptions = {}
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
- debug(`_onConnectionLost(): Connection and some subscriptions reinitialized. Connection is back.`)
3886
- })
3887
- })
3888
- .catch(err => {
3889
- if (firstTime)
3890
- _console.call(this, `WARNING: Reconnecting failed. Keeping trying in the background every ${this.settings.reconnectInterval} ms...`)
3891
-
3892
- //Clear timer again just incase
3893
- clearTimeout(this._internals.reconnectionTimer)
3894
- this._internals.reconnectionTimer = setTimeout(() => tryToReconnect(false), this.settings.reconnectInterval)
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
- clearTimeout(this._internals.reconnectionTimer)
3899
- this._internals.reconnectionTimer = setTimeout(() => tryToReconnect(true), this.settings.reconnectInterval)
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, oldState = this.metaData.systemManagerState
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 (startAgain)
4211
- this._internals.systemManagerStatePoller = setTimeout(poller, this.settings.checkStateInterval)
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
- this._internals.systemManagerStatePoller = setTimeout(poller, this.settings.checkStateInterval)
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
- clearTimeout(this._internals.systemManagerStatePoller)
4417
+ _clearTimer(this._internals.systemManagerStatePoller)
4290
4418
 
4291
4419
  debug(`_onRouterStateChanged(): Local loopback connection active, monitoring router state`)
4292
4420