ads-client 1.11.2 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +54 -47
- package/package.json +5 -5
- package/src/ads-client-ads.js +6 -7
- package/src/ads-client.js +513 -380
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@ 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.1] - 17.01.2022
|
|
8
|
+
### Changed
|
|
9
|
+
- Updated dependecies to latest versions
|
|
10
|
+
- ([See issue #80](https://github.com/jisotalo/ads-client/issues/80))
|
|
11
|
+
- Updated `toString()` methods from `ads-client-ads.js` (problem with undefined variable)
|
|
12
|
+
|
|
13
|
+
## [1.12.0] - 28.12.2021
|
|
14
|
+
### Changed
|
|
15
|
+
- Improving handling of connection losses ([See pull request #78](https://github.com/jisotalo/ads-client/pull/78))
|
|
16
|
+
- Thank you [Hopperpop](https://github.com/Hopperpop) for contribution!
|
|
17
|
+
- Re-design of connection, reconnection and disconnection system
|
|
18
|
+
- Re-design of automatic reconnection
|
|
19
|
+
- Calling `reconnect()` now automatically re-subscribes all existing subscriptions
|
|
20
|
+
|
|
21
|
+
## [1.11.4] - 17.10.2021
|
|
22
|
+
### Added
|
|
23
|
+
- Added optional `targetAdsPort` parameter to basic raw read and write methods. ([Pull request #79](https://github.com/jisotalo/ads-client/pull/79))
|
|
24
|
+
- Thank you [Hopperpop](https://github.com/Hopperpop) for contribution!
|
|
25
|
+
|
|
26
|
+
## [1.11.3] - 08.08.2021
|
|
27
|
+
### Changed
|
|
28
|
+
- Minor bug fix for unhandled exception ([node-red-contrib-ads-client, issue #12](https://github.com/jisotalo/node-red-contrib-ads-client/issues/12#issuecomment-894711952))
|
|
29
|
+
|
|
7
30
|
## [1.11.2] - 01.08.2021
|
|
8
31
|
### Changed
|
|
9
32
|
- Mainly minor bug fixes / adjustments that have been found during Node-RED development
|
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ Beckhoff TwinCAT ADS client library for Node.js (unofficial). Connects to Beckho
|
|
|
10
10
|
|
|
11
11
|
Coded from scratch using [TwinCAT ADS specification](https://infosys.beckhoff.com/content/1033/tc3_ads_intro/116157835.html?id=124964102706356243) and [Beckhoff.TwinCAT.Ads nuget package](https://www.nuget.org/packages/Beckhoff.TwinCAT.Ads/5.0.0-preview6). Inspiration from similar projects like [node-ads](https://www.npmjs.com/package/node-ads), [beckhoff-js](https://www.npmjs.com/package/beckhoff-js) and [iecstruct](https://www.npmjs.com/package/iecstruct).
|
|
12
12
|
|
|
13
|
+
There is automatically created documentation available at https://jisotalo.github.io/ads-client/
|
|
14
|
+
|
|
13
15
|
# Project status
|
|
14
16
|
This project is currently "ready". It's maintained actively and used in projects by the author and others.
|
|
15
17
|
|
|
@@ -20,60 +22,65 @@ Check out the [node-red-contrib-ads-client](https://www.npmjs.com/package/node-r
|
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
# Table of contents
|
|
23
|
-
|
|
24
25
|
- [Installation](#installation)
|
|
25
|
-
- [Features](#
|
|
26
|
+
- [Features](#features)
|
|
26
27
|
- [Supported and tested platforms](#supported-and-tested-platforms)
|
|
27
28
|
- [Connection setups and possibilities](#connection-setups-and-possibilities)
|
|
28
|
-
- [Enabling localhost support](#enabling-localhost-support)
|
|
29
|
+
- [Enabling localhost support on TwinCAT 3](#enabling-localhost-support-on-twincat-3)
|
|
29
30
|
- [IMPORTANT: Writing STRUCT variables](#important-writing-struct-variables)
|
|
30
31
|
- [IMPORTANT: Things to know when using with TwinCAT 2](#important-things-to-know-when-using-with-twincat-2)
|
|
31
32
|
- [Getting started](#getting-started)
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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)
|
|
76
81
|
- [Debugging](#debugging)
|
|
82
|
+
* [Enabling debug from code](#enabling-debug-from-code)
|
|
83
|
+
* [Enabling debugging from terminal](#enabling-debugging-from-terminal)
|
|
77
84
|
- [FAQ](#faq)
|
|
78
85
|
- [Documentation](#documentation)
|
|
79
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.1",
|
|
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": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"author": "Jussi Isotalo <j.isotalo91@gmail.com> (https://github.com/jisotalo)",
|
|
26
26
|
"license": "MIT",
|
|
27
|
-
"homepage": "https://
|
|
27
|
+
"homepage": "https://github.com/jisotalo/ads-client/",
|
|
28
28
|
"bugs": {
|
|
29
29
|
"url": "https://github.com/jisotalo/ads-client/issues"
|
|
30
30
|
},
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"url": "https://github.com/jisotalo/ads-client.git"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"debug": "^4.3.
|
|
37
|
-
"iconv-lite": "^0.
|
|
38
|
-
"long": "^
|
|
36
|
+
"debug": "^4.3.3",
|
|
37
|
+
"iconv-lite": "^0.6.3",
|
|
38
|
+
"long": "^5.2.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"docdash": "^1.2.0",
|
package/src/ads-client-ads.js
CHANGED
|
@@ -255,9 +255,9 @@ const ADS_COMMAND = {
|
|
|
255
255
|
Notification: 8,
|
|
256
256
|
//ReadWrite Command
|
|
257
257
|
ReadWrite: 9,
|
|
258
|
-
|
|
258
|
+
|
|
259
259
|
toString: function (value) {
|
|
260
|
-
return (
|
|
260
|
+
return (Object.keys(this).find(key => this[key] == value)) || 'UNKNOWN';
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
exports.ADS_COMMAND = ADS_COMMAND
|
|
@@ -489,7 +489,7 @@ const ADS_STATE = {
|
|
|
489
489
|
Exception: 19,
|
|
490
490
|
|
|
491
491
|
toString: function (value) {
|
|
492
|
-
return (
|
|
492
|
+
return (Object.keys(this).find(key => this[key] == value)) || 'UNKNOWN';
|
|
493
493
|
}
|
|
494
494
|
}
|
|
495
495
|
exports.ADS_STATE = ADS_STATE
|
|
@@ -602,9 +602,8 @@ const ADS_RESERVED_INDEX_GROUPS = {
|
|
|
602
602
|
//DeviceData (0xF100,61696)
|
|
603
603
|
DeviceData: 61696, // 0x0000F100
|
|
604
604
|
|
|
605
|
-
|
|
606
605
|
toString: function (value) {
|
|
607
|
-
return (
|
|
606
|
+
return (Object.keys(this).find(key => this[key] == value)) || 'UNKNOWN'
|
|
608
607
|
}
|
|
609
608
|
}
|
|
610
609
|
exports.ADS_RESERVED_INDEX_GROUPS = ADS_RESERVED_INDEX_GROUPS
|
|
@@ -795,7 +794,7 @@ const ADS_DATA_TYPES = {
|
|
|
795
794
|
ADST_BIGTYPE: 65, // 0x00000041
|
|
796
795
|
|
|
797
796
|
toString: function (value) {
|
|
798
|
-
return (
|
|
797
|
+
return (Object.keys(this).find(key => this[key] == value)) || 'UNKNOWN'
|
|
799
798
|
}
|
|
800
799
|
}
|
|
801
800
|
exports.ADS_DATA_TYPES = ADS_DATA_TYPES
|
|
@@ -847,7 +846,7 @@ const AMS_ROUTER_STATE = {
|
|
|
847
846
|
REMOVED: 2,
|
|
848
847
|
|
|
849
848
|
toString: function (value) {
|
|
850
|
-
return (
|
|
849
|
+
return (Object.keys(this).find(key => this[key] == value)) || 'UNKNOWN'
|
|
851
850
|
}
|
|
852
851
|
}
|
|
853
852
|
exports.AMS_ROUTER_STATE = AMS_ROUTER_STATE
|
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
|
|
|
@@ -1694,6 +1383,7 @@ class Client extends EventEmitter {
|
|
|
1694
1383
|
* @param {number} indexGroup - Variable index group in the PLC
|
|
1695
1384
|
* @param {number} indexOffset - Variable index offset in the PLC
|
|
1696
1385
|
* @param {number} size - Variable size in the PLC (bytes)
|
|
1386
|
+
* @param {number} [targetAdsPort] - Target ADS port (optional) - default is this.settings.targetAdsPort
|
|
1697
1387
|
*
|
|
1698
1388
|
* @returns {Promise<Buffer>} Returns a promise (async function)
|
|
1699
1389
|
* - If resolved, reading was successful and data is returned (Buffer)
|
|
@@ -1701,7 +1391,7 @@ class Client extends EventEmitter {
|
|
|
1701
1391
|
*
|
|
1702
1392
|
|
|
1703
1393
|
*/
|
|
1704
|
-
readRaw(indexGroup, indexOffset, size) {
|
|
1394
|
+
readRaw(indexGroup, indexOffset, size, targetAdsPort = null) {
|
|
1705
1395
|
return new Promise(async (resolve, reject) => {
|
|
1706
1396
|
|
|
1707
1397
|
if (!this.connection.connected)
|
|
@@ -1729,7 +1419,7 @@ class Client extends EventEmitter {
|
|
|
1729
1419
|
data.writeUInt32LE(size, pos)
|
|
1730
1420
|
pos += 4
|
|
1731
1421
|
|
|
1732
|
-
_sendAdsCommand.call(this, ADS.ADS_COMMAND.Read, data)
|
|
1422
|
+
_sendAdsCommand.call(this, ADS.ADS_COMMAND.Read, data, targetAdsPort)
|
|
1733
1423
|
.then((res) => {
|
|
1734
1424
|
debug(`readRaw(): Data read - ${res.ads.data.byteLength} bytes from ${JSON.stringify({ indexGroup, indexOffset })}`)
|
|
1735
1425
|
|
|
@@ -1791,12 +1481,13 @@ class Client extends EventEmitter {
|
|
|
1791
1481
|
* All required parameters can be read for example with getSymbolInfo() method, **see also readRawBySymbol()**
|
|
1792
1482
|
*
|
|
1793
1483
|
* @param {ReadRawMultiParam[]} targetArray - Targets to read from
|
|
1484
|
+
* @param {number} [targetAdsPort] - Target ADS port (optional) - default is this.settings.targetAdsPort
|
|
1794
1485
|
*
|
|
1795
1486
|
* @returns {Promise<Array.<ReadRawMultiResult>>} Returns a promise (async function)
|
|
1796
1487
|
* - If resolved, reading was successful and data is returned (object)
|
|
1797
1488
|
* - If rejected, reading failed and error info is returned (object)
|
|
1798
1489
|
*/
|
|
1799
|
-
readRawMulti(targetArray) {
|
|
1490
|
+
readRawMulti(targetArray, targetAdsPort = null) {
|
|
1800
1491
|
return new Promise(async (resolve, reject) => {
|
|
1801
1492
|
|
|
1802
1493
|
if (!this.connection.connected)
|
|
@@ -1851,7 +1542,7 @@ class Client extends EventEmitter {
|
|
|
1851
1542
|
pos += 4
|
|
1852
1543
|
})
|
|
1853
1544
|
|
|
1854
|
-
_sendAdsCommand.call(this, ADS.ADS_COMMAND.ReadWrite, data)
|
|
1545
|
+
_sendAdsCommand.call(this, ADS.ADS_COMMAND.ReadWrite, data, targetAdsPort)
|
|
1855
1546
|
.then(res => {
|
|
1856
1547
|
debug(`readRawMulti(): Data read - ${res.ads.data.byteLength} bytes received`)
|
|
1857
1548
|
|
|
@@ -1912,13 +1603,14 @@ class Client extends EventEmitter {
|
|
|
1912
1603
|
* @param {number} indexOffset - Index offset in the PLC
|
|
1913
1604
|
* @param {number} readLength - Read data length in the PLC (bytes)
|
|
1914
1605
|
* @param {Buffer} dataBuffer - Data to write
|
|
1915
|
-
*
|
|
1606
|
+
* @param {number} [targetAdsPort] - Target ADS port (optional) - default is this.settings.targetAdsPort
|
|
1607
|
+
*
|
|
1916
1608
|
* @returns {Promise<Buffer>} Returns a promise (async function)
|
|
1917
1609
|
* - If resolved, writing and reading was successful and data is returned (Buffer)
|
|
1918
1610
|
* - If rejected, command failed and error info is returned (object)
|
|
1919
1611
|
*
|
|
1920
1612
|
*/
|
|
1921
|
-
readWriteRaw(indexGroup, indexOffset, readLength, dataBuffer) {
|
|
1613
|
+
readWriteRaw(indexGroup, indexOffset, readLength, dataBuffer, targetAdsPort = null) {
|
|
1922
1614
|
return new Promise(async (resolve, reject) => {
|
|
1923
1615
|
|
|
1924
1616
|
if (!this.connection.connected)
|
|
@@ -1956,7 +1648,7 @@ class Client extends EventEmitter {
|
|
|
1956
1648
|
dataBuffer.copy(data, pos)
|
|
1957
1649
|
|
|
1958
1650
|
|
|
1959
|
-
_sendAdsCommand.call(this, ADS.ADS_COMMAND.ReadWrite, data)
|
|
1651
|
+
_sendAdsCommand.call(this, ADS.ADS_COMMAND.ReadWrite, data, targetAdsPort)
|
|
1960
1652
|
.then(res => {
|
|
1961
1653
|
debug(`readWriteRaw(): Data written and ${res.ads.data.byteLength} bytes response received`)
|
|
1962
1654
|
|
|
@@ -2049,12 +1741,13 @@ class Client extends EventEmitter {
|
|
|
2049
1741
|
* @param {number} indexGroup - Variable index group in the PLC
|
|
2050
1742
|
* @param {number} indexOffset - Variable index offset in the PLC
|
|
2051
1743
|
* @param {Buffer} dataBuffer - Buffer object that contains the data (and byteLength is acceptable)
|
|
1744
|
+
* @param {number} [targetAdsPort] - Target ADS port (optional) - default is this.settings.targetAdsPort
|
|
2052
1745
|
*
|
|
2053
1746
|
* @returns {Promise<object>} Returns a promise (async function)
|
|
2054
1747
|
* - If resolved, writing was successful
|
|
2055
1748
|
* - If rejected, writing failed and error info is returned (object)
|
|
2056
1749
|
*/
|
|
2057
|
-
writeRaw(indexGroup, indexOffset, dataBuffer) {
|
|
1750
|
+
writeRaw(indexGroup, indexOffset, dataBuffer, targetAdsPort = null) {
|
|
2058
1751
|
return new Promise(async (resolve, reject) => {
|
|
2059
1752
|
|
|
2060
1753
|
if (!this.connection.connected)
|
|
@@ -2089,7 +1782,7 @@ class Client extends EventEmitter {
|
|
|
2089
1782
|
dataBuffer.copy(data, pos)
|
|
2090
1783
|
|
|
2091
1784
|
|
|
2092
|
-
_sendAdsCommand.call(this, ADS.ADS_COMMAND.Write, data)
|
|
1785
|
+
_sendAdsCommand.call(this, ADS.ADS_COMMAND.Write, data, targetAdsPort)
|
|
2093
1786
|
.then((res) => {
|
|
2094
1787
|
debug(`writeRaw(): Data written: ${dataBuffer.byteLength} bytes of data to ${JSON.stringify({ indexGroup, indexOffset })}`)
|
|
2095
1788
|
|
|
@@ -2131,12 +1824,13 @@ class Client extends EventEmitter {
|
|
|
2131
1824
|
* All required parameters can be read for example with getSymbolInfo() method, **see also readRawBySymbol()**
|
|
2132
1825
|
*
|
|
2133
1826
|
* @param {WriteRawMultiParam[]} targetArray - Targets to write to
|
|
1827
|
+
* @param {number} [targetAdsPort] - Target ADS port (optional) - default is this.settings.targetAdsPort
|
|
2134
1828
|
*
|
|
2135
1829
|
* @returns {Promise<Array.<WriteRawMultiResult>>} Returns a promise (async function)
|
|
2136
1830
|
* - If resolved, writing was successful and data is returned (Buffer)
|
|
2137
1831
|
* - If rejected, reading failed and error info is returned (object)
|
|
2138
1832
|
*/
|
|
2139
|
-
writeRawMulti(targetArray) {
|
|
1833
|
+
writeRawMulti(targetArray, targetAdsPort = null) {
|
|
2140
1834
|
return new Promise(async (resolve, reject) => {
|
|
2141
1835
|
|
|
2142
1836
|
if (!this.connection.connected)
|
|
@@ -2197,7 +1891,7 @@ class Client extends EventEmitter {
|
|
|
2197
1891
|
pos += target.data.byteLength
|
|
2198
1892
|
})
|
|
2199
1893
|
|
|
2200
|
-
_sendAdsCommand.call(this, ADS.ADS_COMMAND.ReadWrite, data)
|
|
1894
|
+
_sendAdsCommand.call(this, ADS.ADS_COMMAND.ReadWrite, data, targetAdsPort)
|
|
2201
1895
|
.then(res => {
|
|
2202
1896
|
debug(`writeRawMulti(): Data written - ${res.ads.data.byteLength} bytes received`)
|
|
2203
1897
|
|
|
@@ -3561,6 +3255,408 @@ class ClientException extends Error {
|
|
|
3561
3255
|
|
|
3562
3256
|
|
|
3563
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
|
+
|
|
3564
3660
|
/**
|
|
3565
3661
|
* Registers a new ADS port from used AMS router
|
|
3566
3662
|
*
|
|
@@ -3672,8 +3768,8 @@ function _unregisterAdsPort() {
|
|
|
3672
3768
|
return new Promise(async (resolve, reject) => {
|
|
3673
3769
|
debugD(`_unregisterAdsPort(): Unregister ads port ${this.connection.localAdsPort} from ${this.settings.routerAddress}:${this.settings.routerTcpPort}`)
|
|
3674
3770
|
|
|
3675
|
-
//If we have manually given ADS port, we only need to close the connection manually
|
|
3676
|
-
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) {
|
|
3677
3773
|
debug(`_unregisterAdsPort(): Local AmsNetId and ADS port manually given so no need to unregister`)
|
|
3678
3774
|
|
|
3679
3775
|
|
|
@@ -3824,8 +3920,7 @@ async function _onSocketError(err) {
|
|
|
3824
3920
|
*/
|
|
3825
3921
|
async function _onConnectionLost(socketFailure = false) {
|
|
3826
3922
|
//Clear timers
|
|
3827
|
-
|
|
3828
|
-
clearTimeout(this._internals.reconnectionTimer)
|
|
3923
|
+
_clearTimer(this._internals.systemManagerStatePoller)
|
|
3829
3924
|
|
|
3830
3925
|
debug(`_onConnectionLost(): Connection was lost. Socket failure: ${socketFailure}`)
|
|
3831
3926
|
|
|
@@ -3841,58 +3936,75 @@ async function _onConnectionLost(socketFailure = false) {
|
|
|
3841
3936
|
return
|
|
3842
3937
|
}
|
|
3843
3938
|
|
|
3844
|
-
if (this._internals.socketConnectionLostHandler)
|
|
3939
|
+
if (this._internals.socket && this._internals.socketConnectionLostHandler)
|
|
3845
3940
|
this._internals.socket.off('close', this._internals.socketConnectionLostHandler)
|
|
3846
3941
|
|
|
3847
3942
|
_console.call(this, 'WARNING: Connection was lost. Trying to reconnect...')
|
|
3848
3943
|
|
|
3944
|
+
const tryToReconnect = async (firstTime, timerId) => {
|
|
3849
3945
|
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3946
|
+
//If the timer has changed, quit here
|
|
3947
|
+
if (this._internals.reconnectionTimer.id !== timerId) {
|
|
3948
|
+
return
|
|
3949
|
+
}
|
|
3853
3950
|
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3951
|
+
//Try to reconnect
|
|
3952
|
+
_reconnect.call(this, socketFailure, true)
|
|
3953
|
+
.then(res => {
|
|
3954
|
+
|
|
3955
|
+
//Success -> remove timer
|
|
3956
|
+
_clearTimer(this._internals.reconnectionTimer)
|
|
3858
3957
|
|
|
3859
|
-
|
|
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
|
+
})
|
|
3860
3975
|
}
|
|
3861
3976
|
|
|
3862
|
-
|
|
3863
|
-
|
|
3977
|
+
//Clearing old timer if there is one + increasing timer id
|
|
3978
|
+
_clearTimer(this._internals.reconnectionTimer)
|
|
3864
3979
|
|
|
3865
|
-
|
|
3866
|
-
|
|
3980
|
+
//Starting poller timer
|
|
3981
|
+
this._internals.reconnectionTimer.timer = setTimeout(
|
|
3982
|
+
() => tryToReconnect(true, this._internals.reconnectionTimer.id),
|
|
3983
|
+
this.settings.reconnectInterval
|
|
3984
|
+
)
|
|
3985
|
+
}
|
|
3867
3986
|
|
|
3868
|
-
this.reconnect(socketFailure)
|
|
3869
|
-
.then(res => {
|
|
3870
3987
|
|
|
3871
|
-
_reInitializeSubscriptions.call(this, this._internals.oldSubscriptions)
|
|
3872
|
-
.then(() => {
|
|
3873
|
-
_console.call(this, `PLC runtime reconnected successfully and all subscriptions were restored!`)
|
|
3874
3988
|
|
|
3875
|
-
debug(`_onConnectionLost(): Connection and subscriptions reinitialized. Connection is back.`)
|
|
3876
|
-
})
|
|
3877
|
-
.catch(err => {
|
|
3878
|
-
_console.call(this, `PLC runtime reconnected successfully but not all subscriptions were restored. Error info: ${err}`)
|
|
3879
3989
|
|
|
3880
|
-
debug(`_onConnectionLost(): Connection and some subscriptions reinitialized. Connection is back.`)
|
|
3881
|
-
})
|
|
3882
|
-
})
|
|
3883
|
-
.catch(err => {
|
|
3884
|
-
if (firstTime)
|
|
3885
|
-
_console.call(this, `WARNING: Reconnecting failed. Keeping trying in the background every ${this.settings.reconnectInterval} ms...`)
|
|
3886
3990
|
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
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
|
|
3892
4003
|
|
|
3893
|
-
|
|
3894
|
-
|
|
4004
|
+
//Increasing timer id
|
|
4005
|
+
timerObject.id = timerObject.id < Number.MAX_SAFE_INTEGER ? timerObject.id + 1 : 0;
|
|
3895
4006
|
}
|
|
4007
|
+
|
|
3896
4008
|
|
|
3897
4009
|
|
|
3898
4010
|
|
|
@@ -4162,10 +4274,15 @@ async function _onSymbolVersionChanged(data) {
|
|
|
4162
4274
|
* @memberof _LibraryInternals
|
|
4163
4275
|
*/
|
|
4164
4276
|
function _systemManagerStatePoller() {
|
|
4165
|
-
clearTimeout(this._internals.systemManagerStatePoller)
|
|
4166
4277
|
|
|
4167
|
-
const poller = async () => {
|
|
4168
|
-
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
|
+
}
|
|
4169
4286
|
|
|
4170
4287
|
try {
|
|
4171
4288
|
await this.readSystemManagerState()
|
|
@@ -4186,6 +4303,7 @@ function _systemManagerStatePoller() {
|
|
|
4186
4303
|
_onConnectionLost.call(this)
|
|
4187
4304
|
}
|
|
4188
4305
|
}
|
|
4306
|
+
|
|
4189
4307
|
} catch (err) {
|
|
4190
4308
|
debug(`_systemManagerStatePoller(): Reading system manager state failed: %o`, err)
|
|
4191
4309
|
|
|
@@ -4202,11 +4320,26 @@ function _systemManagerStatePoller() {
|
|
|
4202
4320
|
}
|
|
4203
4321
|
}
|
|
4204
4322
|
|
|
4205
|
-
if
|
|
4206
|
-
|
|
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
|
+
}
|
|
4207
4331
|
}
|
|
4208
4332
|
|
|
4209
|
-
|
|
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
|
+
|
|
4210
4343
|
}
|
|
4211
4344
|
|
|
4212
4345
|
|
|
@@ -4281,7 +4414,7 @@ async function _onRouterStateChanged(data) {
|
|
|
4281
4414
|
//If we have a local connection, connection needs to be reinitialized
|
|
4282
4415
|
if (this.connection.isLocal === true) {
|
|
4283
4416
|
//We should stop polling system manager, it will be reinitialized later
|
|
4284
|
-
|
|
4417
|
+
_clearTimer(this._internals.systemManagerStatePoller)
|
|
4285
4418
|
|
|
4286
4419
|
debug(`_onRouterStateChanged(): Local loopback connection active, monitoring router state`)
|
|
4287
4420
|
|
|
@@ -6310,8 +6443,8 @@ function _parseAdsNotification(data) {
|
|
|
6310
6443
|
*
|
|
6311
6444
|
* @param {number} adsCommand - ADS command to send (see ADS.ADS_COMMAND)
|
|
6312
6445
|
* @param {Buffer} adsData - Buffer object that contains the data to send
|
|
6313
|
-
* @param {number} [targetAdsPort] - Target ADS port - default is this.settings.targetAdsPort
|
|
6314
|
-
* @param {string} [targetAmsNetId] - Target AmsNetID - default is this.settings.targetAmsNetId
|
|
6446
|
+
* @param {number} [targetAdsPort] - Target ADS port (optional) - default is this.settings.targetAdsPort
|
|
6447
|
+
* @param {string} [targetAmsNetId] - Target AmsNetID (optional) - default is this.settings.targetAmsNetId
|
|
6315
6448
|
*
|
|
6316
6449
|
* @returns {Promise<object>} Returns a promise (async function)
|
|
6317
6450
|
* - If resolved, command was sent successfully and response was received. The received reponse is parsed and returned (object)
|