ads-client 2.0.2 → 2.1.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,24 @@ 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
+ ## [2.1.0] - 27.01.2025
8
+ ### Added
9
+ - New method `readTcSystemExtendedState()`
10
+ - Reads [extended target TwinCAT system state](https://jisotalo.fi/ads-client/interfaces/AdsTcSystemExtendedState.html) (if available)
11
+ - Improved detection of TwinCAT system service restart
12
+ - Available if remote system supports `readTcSystemExtendedState()`
13
+ - Tested with TwinCAT 3.1.4022, 3.1.4024 and 3.1.4026
14
+ - Client detects a restart of TwinCAT system and reconnects to re-establish subscriptions
15
+ - See issue [issue #159](https://github.com/jisotalo/ads-client/issues/159)
16
+ - Added tests for subscription persistence during TwinCAT system restart
17
+
18
+ ### Changed
19
+ - Changed `activeSubscriptions` property visibility from private to public
20
+ - `disconnect` event is no longer emitted continuously each reconnect attempt (only during the initial disconnect / connection lost event)
21
+ - `disconnect` event parameter `isReconnecting` changed to `connectionLost`
22
+ - TwinCAT system state type (`metaData.tcSystemState`) changed from `AdsState` to `AdsTcSystemState`
23
+ - Not a breaking change - the new type extends `AdsState`
24
+
7
25
  ## [2.0.2] - 14.12.2024
8
26
  **IMPORTANT:** This is a major version update. There are lots of **breaking changes**!
9
27
 
package/README.md CHANGED
@@ -34,6 +34,7 @@ See [`legacy-v1` branch](https://github.com/jisotalo/ads-client/tree/legacy-v1)
34
34
  - Calling function block methods (RPC)
35
35
  - Automatic 32/64 bit variable support (PVOID, XINT, etc.)
36
36
  - Automatic byte alignment support (all pack-modes automatically supported)
37
+ - Handles TwinCAT restarts, configuration changes and PLC software updates automatically
37
38
 
38
39
  # Table of contents
39
40
  - [ads-client](#ads-client)
@@ -457,6 +458,7 @@ Click a method to open it's documentation.
457
458
  | [`readRawMulti()`](https://jisotalo.fi/ads-client/classes/Client.html#readRawMulti) | Sends multiple `readRaw()` commands in one ADS packet (ADS sum command). |
458
459
  | [`readState()`](https://jisotalo.fi/ads-client/classes/Client.html#readState) | Reads target ADS state. |
459
460
  | [`readTcSystemState()`](https://jisotalo.fi/ads-client/classes/Client.html#readTcSystemState) | Reads target TwinCAT system state from ADS port 10000 (usually `Run` or `Config`). |
461
+ | [`readTcSystemExtendedState()`](https://jisotalo.fi/ads-client/classes/Client.html#readTcSystemExtendedState) | Reads extended target TwinCAT system service state from ADS port 10000 if supported by target system. Tested to work with 3.1.4022 and newer. |
460
462
  | [`readValue()`](https://jisotalo.fi/ads-client/classes/Client.html#readValue) | Reads variable's value from the target system by a variable path (such as `GVL_Test.ExampleStruct`) and returns the value as a Javascript object. |
461
463
  | [`readValueBySymbol()`](https://jisotalo.fi/ads-client/classes/Client.html#readValueBySymbol) | Reads variable's value from the target system by a symbol object (acquired using `getSymbol()`) and returns the value as a Javascript object. |
462
464
  | [`readWriteRaw()`](https://jisotalo.fi/ads-client/classes/Client.html#readWriteRaw) | Writes raw data to the target system by a raw ADS address (index group, index offset) and reads the result as raw data. |
@@ -1,7 +1,7 @@
1
1
  import EventEmitter from "events";
2
2
  import * as ADS from './ads-commons';
3
- import type { ActiveSubscription, AdsClientConnection, AdsClientSettings, AdsCommandToSend, AdsDataTypeContainer, AdsSymbolContainer, ConnectionMetaData, SubscriptionSettings, ReadValueResult, WriteValueResult, VariableHandle, RpcMethodCallResult, CreateVariableHandleMultiResult, ReadRawMultiResult, ReadRawMultiCommand, WriteRawMultiResult, DeleteVariableHandleMultiResult, ReadWriteRawMultiResult, ReadWriteRawMultiCommand, WriteRawMultiCommand, SubscriptionCallback, DebugLevel, AdsClientEvents, SendAdsCommandWithFallbackResult } from "./types/ads-client-types";
4
- import { AdsAttributeEntry, AdsDataType, AdsDeviceInfo, AdsResponse, AdsState, AdsSymbol, AmsAddress, AmsTcpPacket, AdsUploadInfo } from "./types/ads-protocol-types";
3
+ import type { ActiveSubscription, ActiveSubscriptionContainer, AdsClientConnection, AdsClientSettings, AdsCommandToSend, AdsDataTypeContainer, AdsSymbolContainer, ConnectionMetaData, SubscriptionSettings, ReadValueResult, WriteValueResult, VariableHandle, RpcMethodCallResult, CreateVariableHandleMultiResult, ReadRawMultiResult, ReadRawMultiCommand, WriteRawMultiResult, DeleteVariableHandleMultiResult, ReadWriteRawMultiResult, ReadWriteRawMultiCommand, WriteRawMultiCommand, SubscriptionCallback, DebugLevel, AdsClientEvents, SendAdsCommandWithFallbackResult } from "./types/ads-client-types";
4
+ import { AdsAttributeEntry, AdsDataType, AdsDeviceInfo, AdsResponse, AdsState, AdsSymbol, AmsAddress, AmsTcpPacket, AdsUploadInfo, AdsTcSystemExtendedState } from "./types/ads-protocol-types";
5
5
  export type * from "./types/ads-client-types";
6
6
  export type * from './types/ads-protocol-types';
7
7
  export type * from './client-error';
@@ -94,10 +94,6 @@ export declare class Client extends EventEmitter<AdsClientEvents> {
94
94
  * Timer handle of the timer used for detecting ADS port registeration timeout.
95
95
  */
96
96
  private portRegisterTimeoutTimer?;
97
- /**
98
- * Container for all active subscriptions.
99
- */
100
- private activeSubscriptions;
101
97
  /**
102
98
  * Container for previous subscriptions that were active
103
99
  * before reconnecting or when PLC runtime symbol version changed.
@@ -164,6 +160,12 @@ export declare class Client extends EventEmitter<AdsClientEvents> {
164
160
  * Some properties might not be available in all connection setups and setting combinations.
165
161
  */
166
162
  metaData: ConnectionMetaData;
163
+ /**
164
+ * Container for all active subscriptions.
165
+ *
166
+ * Do not edit this directly.
167
+ */
168
+ activeSubscriptions: ActiveSubscriptionContainer;
167
169
  /**
168
170
  * Creates a new ADS client instance.
169
171
  *
@@ -976,6 +978,8 @@ export declare class Client extends EventEmitter<AdsClientEvents> {
976
978
  /**
977
979
  * Reads target TwinCAT system state from ADS port 10000 (usually `Run` or `Config`).
978
980
  *
981
+ * NOTE: You might want to use the extended {@link readTcSystemExtendedState}() instead.
982
+ *
979
983
  * If `targetOpts` is not used to override target, the state is also
980
984
  * saved to the `metaData.tcSystemState`.
981
985
  *
@@ -996,6 +1000,30 @@ export declare class Client extends EventEmitter<AdsClientEvents> {
996
1000
  * @throws Throws an error if sending the command fails or if the target responds with an error.
997
1001
  */
998
1002
  readTcSystemState(targetOpts?: Partial<AmsAddress>): Promise<AdsState>;
1003
+ /**
1004
+ * Reads extended target TwinCAT system service state from ADS port 10000
1005
+ * if supported by target system. Extended version of the {@link readTcSystemState}().
1006
+ *
1007
+ * If `targetOpts` is not used to override target, the state is also
1008
+ * saved to the `metaData.tcSystemState`.
1009
+ *
1010
+ * NOTE: Might not be supported in older TwinCAT versions. If so, use {@link readTcSystemState}() instead.
1011
+ * Tested to work with 3.1.4022 and newer.
1012
+ *
1013
+ * @example
1014
+ * ```js
1015
+ * try {
1016
+ * const tcSystemState = await client.readTcSystemServiceState();
1017
+ * } catch (err) {
1018
+ * console.log("Error:", err);
1019
+ * }
1020
+ * ```
1021
+ *
1022
+ * @param targetOpts Optional target settings that override values in `settings`
1023
+ *
1024
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
1025
+ */
1026
+ readTcSystemExtendedState(targetOpts?: Partial<AmsAddress>): Promise<AdsTcSystemExtendedState>;
999
1027
  /**
1000
1028
  * Reads target PLC runtime symbol version.
1001
1029
  *
@@ -39,23 +39,13 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
39
39
  }) : function(o, v) {
40
40
  o["default"] = v;
41
41
  });
42
- var __importStar = (this && this.__importStar) || (function () {
43
- var ownKeys = function(o) {
44
- ownKeys = Object.getOwnPropertyNames || function (o) {
45
- var ar = [];
46
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
47
- return ar;
48
- };
49
- return ownKeys(o);
50
- };
51
- return function (mod) {
52
- if (mod && mod.__esModule) return mod;
53
- var result = {};
54
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
55
- __setModuleDefault(result, mod);
56
- return result;
57
- };
58
- })();
42
+ var __importStar = (this && this.__importStar) || function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
59
49
  var __importDefault = (this && this.__importDefault) || function (mod) {
60
50
  return (mod && mod.__esModule) ? mod : { "default": mod };
61
51
  };
@@ -182,10 +172,6 @@ class Client extends events_1.default {
182
172
  * Timer handle of the timer used for detecting ADS port registeration timeout.
183
173
  */
184
174
  this.portRegisterTimeoutTimer = undefined;
185
- /**
186
- * Container for all active subscriptions.
187
- */
188
- this.activeSubscriptions = {};
189
175
  /**
190
176
  * Container for previous subscriptions that were active
191
177
  * before reconnecting or when PLC runtime symbol version changed.
@@ -263,6 +249,12 @@ class Client extends events_1.default {
263
249
  * Some properties might not be available in all connection setups and setting combinations.
264
250
  */
265
251
  this.metaData = { ...this.defaultMetaData };
252
+ /**
253
+ * Container for all active subscriptions.
254
+ *
255
+ * Do not edit this directly.
256
+ */
257
+ this.activeSubscriptions = {};
266
258
  //Taking the default settings and then updating the provided ones
267
259
  this.settings = {
268
260
  ...this.settings,
@@ -528,7 +520,9 @@ class Client extends events_1.default {
528
520
  this.socket?.removeAllListeners();
529
521
  this.socket?.destroy();
530
522
  this.socket = undefined;
531
- this.emit("disconnect", isReconnecting);
523
+ if (!isReconnecting) {
524
+ this.emit("disconnect", isReconnecting);
525
+ }
532
526
  return resolve();
533
527
  }
534
528
  let disconnectError = null;
@@ -550,7 +544,9 @@ class Client extends events_1.default {
550
544
  this.socket?.destroy();
551
545
  this.socket = undefined;
552
546
  this.debug(`disconnectFromTarget(): Connection closed successfully`);
553
- this.emit("disconnect", isReconnecting);
547
+ if (!isReconnecting) {
548
+ this.emit("disconnect", isReconnecting);
549
+ }
554
550
  return resolve();
555
551
  }
556
552
  catch (err) {
@@ -565,7 +561,9 @@ class Client extends events_1.default {
565
561
  this.metaData = { ...this.defaultMetaData };
566
562
  this.activeSubscriptions = {};
567
563
  this.debug(`disconnectFromTarget(): Connection closing failed, connection was forced to close`);
568
- this.emit("disconnect", isReconnecting);
564
+ if (isReconnecting) {
565
+ this.emit("disconnect", isReconnecting);
566
+ }
569
567
  return reject(new client_error_1.default(`disconnect(): Disconnected with errors: ${disconnectError.message}`, err));
570
568
  }
571
569
  });
@@ -775,8 +773,13 @@ class Client extends events_1.default {
775
773
  * This is not called if the `settings.rawClient` is `true`.
776
774
  */
777
775
  async setupPlcConnection() {
778
- //Read system state
779
- await this.readTcSystemState();
776
+ //Read system state - try the extended version first
777
+ try {
778
+ await this.readTcSystemExtendedState();
779
+ }
780
+ catch (err) {
781
+ await this.readTcSystemState();
782
+ }
780
783
  //Start system state poller
781
784
  await this.startTcSystemStatePoller();
782
785
  //Subscribe to runtime state changes (detect PLC run/stop etc.)
@@ -839,10 +842,27 @@ class Client extends events_1.default {
839
842
  }
840
843
  let startTimer = true;
841
844
  try {
842
- let oldState = this.metaData.tcSystemState !== undefined
845
+ const oldState = this.metaData.tcSystemState
843
846
  ? { ...this.metaData.tcSystemState }
844
847
  : undefined;
845
- const state = await this.readTcSystemState();
848
+ let state;
849
+ //If we have extended state, then use it for better restart detection
850
+ if (this.metaData.tcSystemState?.restartIndex !== undefined) {
851
+ state = await this.readTcSystemExtendedState();
852
+ }
853
+ else if (this.metaData.tcSystemState) {
854
+ state = await this.readTcSystemState();
855
+ }
856
+ else {
857
+ //We don't know yet so try the extended first
858
+ try {
859
+ state = await this.readTcSystemExtendedState();
860
+ }
861
+ catch (err) {
862
+ state = await this.readTcSystemState();
863
+ }
864
+ }
865
+ //State read successfully
846
866
  this.connectionDownSince = undefined;
847
867
  if (!oldState || state.adsState !== oldState.adsState) {
848
868
  this.debug(`checkTcSystemState(): TwinCAT system state has changed from ${oldState?.adsStateStr} to ${state.adsStateStr}`);
@@ -861,6 +881,12 @@ class Client extends events_1.default {
861
881
  this.onConnectionLost();
862
882
  }
863
883
  }
884
+ else if (state.restartIndex !== undefined && oldState?.restartIndex !== state.restartIndex) {
885
+ this.debug(`checkTcSystemState(): TwinCAT system service has restarted -> reconnecting`);
886
+ this.emit('tcSystemStateChange', state, oldState);
887
+ startTimer = false;
888
+ this.onConnectionLost();
889
+ }
864
890
  }
865
891
  catch (err) {
866
892
  //Reading state failed.
@@ -1011,6 +1037,7 @@ class Client extends events_1.default {
1011
1037
  this.debug(`onConnectionLost(): Connection was lost. Socket failure: ${socketFailure}`);
1012
1038
  this.connection.connected = false;
1013
1039
  this.emit('connectionLost', socketFailure);
1040
+ this.emit('disconnect', true);
1014
1041
  if (this.settings.autoReconnect !== true) {
1015
1042
  this.warn("Connection to target was lost and setting autoReconnect was false -> disconnecting");
1016
1043
  await this.disconnectFromTarget(true).catch();
@@ -3771,6 +3798,8 @@ class Client extends events_1.default {
3771
3798
  /**
3772
3799
  * Reads target TwinCAT system state from ADS port 10000 (usually `Run` or `Config`).
3773
3800
  *
3801
+ * NOTE: You might want to use the extended {@link readTcSystemExtendedState}() instead.
3802
+ *
3774
3803
  * If `targetOpts` is not used to override target, the state is also
3775
3804
  * saved to the `metaData.tcSystemState`.
3776
3805
  *
@@ -3813,6 +3842,85 @@ class Client extends events_1.default {
3813
3842
  throw new client_error_1.default(`readTcSystemState(): Reading TwinCAT system state failed`, err);
3814
3843
  }
3815
3844
  }
3845
+ /**
3846
+ * Reads extended target TwinCAT system service state from ADS port 10000
3847
+ * if supported by target system. Extended version of the {@link readTcSystemState}().
3848
+ *
3849
+ * If `targetOpts` is not used to override target, the state is also
3850
+ * saved to the `metaData.tcSystemState`.
3851
+ *
3852
+ * NOTE: Might not be supported in older TwinCAT versions. If so, use {@link readTcSystemState}() instead.
3853
+ * Tested to work with 3.1.4022 and newer.
3854
+ *
3855
+ * @example
3856
+ * ```js
3857
+ * try {
3858
+ * const tcSystemState = await client.readTcSystemServiceState();
3859
+ * } catch (err) {
3860
+ * console.log("Error:", err);
3861
+ * }
3862
+ * ```
3863
+ *
3864
+ * @param targetOpts Optional target settings that override values in `settings`
3865
+ *
3866
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
3867
+ */
3868
+ async readTcSystemExtendedState(targetOpts = {}) {
3869
+ if (!this.connection.connected) {
3870
+ throw new client_error_1.default(`readTcSystemServiceState(): Client is not connected. Use connect() to connect to the target first.`);
3871
+ }
3872
+ this.debug(`readTcSystemServiceState(): Reading TwinCAT system service state`);
3873
+ try {
3874
+ const target = {
3875
+ adsPort: 10000,
3876
+ ...targetOpts
3877
+ };
3878
+ const response = await this.readRaw(240, 0, 16, target);
3879
+ const result = {};
3880
+ let pos = 0;
3881
+ //0..1 ADS state
3882
+ result.adsState = response.readUInt16LE(pos);
3883
+ result.adsStateStr = ADS.ADS_STATE.toString(result.adsState);
3884
+ pos += 2;
3885
+ //2..3 Device state
3886
+ result.deviceState = response.readUInt16LE(pos);
3887
+ pos += 2;
3888
+ //4..5 Restart index
3889
+ result.restartIndex = response.readUInt16LE(pos);
3890
+ pos += 2;
3891
+ //6 Version
3892
+ result.version = response.readUInt8(pos);
3893
+ pos += 1;
3894
+ //7 Revision
3895
+ result.revision = response.readUInt8(pos);
3896
+ pos += 1;
3897
+ //8..9 Build
3898
+ result.build = response.readUInt16LE(pos);
3899
+ pos += 2;
3900
+ //10 Platform
3901
+ result.platform = response.readUInt8(pos);
3902
+ pos += 1;
3903
+ //11 OS type
3904
+ result.osType = response.readUInt8(pos);
3905
+ pos += 1;
3906
+ //12..13 Flags
3907
+ result.flags = response.readUInt16LE(pos);
3908
+ result.flagsStr = ADS.ADS_SYSTEM_SERVICE_STATE_FLAGS.toStringArray(result.flags);
3909
+ pos += 2;
3910
+ //14.. Reserved
3911
+ result.reserved = response.subarray(pos);
3912
+ this.debug(`readTcSystemServiceState(): TwinCAT system service state read successfully`);
3913
+ if (!targetOpts.adsPort && !targetOpts.amsNetId) {
3914
+ //Target is not overridden -> save to metadata
3915
+ this.metaData.tcSystemState = result;
3916
+ }
3917
+ return result;
3918
+ }
3919
+ catch (err) {
3920
+ this.debug(`readTcSystemServiceState(): Reading TwinCAT system service state failed: %o`, err);
3921
+ throw new client_error_1.default(`readTcSystemServiceState(): Reading TwinCAT system service state failed`, err);
3922
+ }
3923
+ }
3816
3924
  /**
3817
3925
  * Reads target PLC runtime symbol version.
3818
3926
  *