ads-client 2.0.0-beta.3 → 2.0.0-beta.5

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.
@@ -148,6 +148,7 @@ class Client extends events_1.default {
148
148
  plcSymbols: {},
149
149
  allPlcDataTypesCached: false,
150
150
  plcDataTypes: {},
151
+ adsSymbolsUseUtf8: false
151
152
  };
152
153
  /**
153
154
  * Buffer for received data.
@@ -233,7 +234,8 @@ class Client extends events_1.default {
233
234
  allowHalfOpen: false,
234
235
  rawClient: false,
235
236
  disableCaching: false,
236
- deleteUnknownSubscriptions: true
237
+ deleteUnknownSubscriptions: true,
238
+ forceUtf8ForAdsSymbols: false
237
239
  };
238
240
  /**
239
241
  * Active connection information.
@@ -419,6 +421,8 @@ class Client extends events_1.default {
419
421
  //When socket errors from now on, we will close the connection
420
422
  this.socketErrorHandler = this.onSocketError.bind(this);
421
423
  socket.on("error", this.socketErrorHandler);
424
+ //Force ADS UTF-8 (note: this is also set later at readPlcUploadInfo() if target is a PLC)
425
+ this.metaData.adsSymbolsUseUtf8 = this.settings.forceUtf8ForAdsSymbols;
422
426
  //If rawClient setting is true, we are done here (just a connection is enough)
423
427
  if (this.settings.rawClient !== true) {
424
428
  try {
@@ -659,7 +663,7 @@ class Client extends events_1.default {
659
663
  };
660
664
  this.socket?.once('error', errorHandler);
661
665
  try {
662
- await this.socketWrite(packet);
666
+ this.socketWrite(packet);
663
667
  }
664
668
  catch (err) {
665
669
  this.socket?.off('error', errorHandler);
@@ -715,13 +719,41 @@ class Client extends events_1.default {
715
719
  //Sometimes close event is not received, so resolve already here
716
720
  this.socket.once('end', () => {
717
721
  this.debugD(`unregisterAdsPort(): Socket connection ended, connection closed.`);
722
+ clearTimeout(this.portRegisterTimeoutTimer);
723
+ this.amsTcpCallback = undefined;
718
724
  this.socket?.destroy();
719
725
  resolve();
720
726
  });
727
+ this.amsTcpCallback = (res) => {
728
+ this.amsTcpCallback = undefined;
729
+ clearTimeout(this.portRegisterTimeoutTimer);
730
+ if (res.amsTcp.command === ADS.AMS_HEADER_FLAG.AMS_TCP_PORT_CLOSE) {
731
+ this.debug(`unregisterAdsPort(): ADS port unregistered`);
732
+ this.socket?.destroy();
733
+ resolve();
734
+ }
735
+ };
736
+ //Timeout (if no answer from router)
737
+ this.portRegisterTimeoutTimer = setTimeout(() => {
738
+ //Callback is no longer needed, delete it
739
+ this.amsTcpCallback = undefined;
740
+ //Create a custom "ads error" so that the info is passed onwards
741
+ const adsError = {
742
+ ads: {
743
+ error: true,
744
+ errorCode: -1,
745
+ errorStr: `Timeout - no response in ${this.settings.timeoutDelay} ms`
746
+ }
747
+ };
748
+ this.debug(`unregisterAdsPort(): Failed to unregister ADS port: Timeout - no response in ${this.settings.timeoutDelay} ms`);
749
+ return reject(new client_error_1.default(`unregisterAdsPort(): Timeout - no response in ${this.settings.timeoutDelay} ms`, adsError));
750
+ }, this.settings.timeoutDelay);
721
751
  try {
722
- await this.socketWrite(buffer);
752
+ this.socketWrite(buffer);
723
753
  }
724
754
  catch (err) {
755
+ this.amsTcpCallback = undefined;
756
+ clearTimeout(this.portRegisterTimeoutTimer);
725
757
  reject(err);
726
758
  }
727
759
  });
@@ -848,29 +880,38 @@ class Client extends events_1.default {
848
880
  * @param subscription The subscription object (unused here)
849
881
  */
850
882
  onPlcRuntimeStateChanged(data, subscription) {
851
- const res = {};
883
+ const state = {};
852
884
  let pos = 0;
853
885
  //0..1 ADS state
854
- res.adsState = data.value.readUInt16LE(pos);
855
- res.adsStateStr = ADS.ADS_STATE.toString(res.adsState);
886
+ state.adsState = data.value.readUInt16LE(pos);
887
+ state.adsStateStr = ADS.ADS_STATE.toString(state.adsState);
856
888
  pos += 2;
857
889
  //2..3 Device state
858
- res.deviceState = data.value.readUInt16LE(pos);
890
+ state.deviceState = data.value.readUInt16LE(pos);
859
891
  pos += 2;
860
- //Checking if PLC runtime state has changed
892
+ this.handlePlcRuntimeStateChange(state);
893
+ }
894
+ /**
895
+ * Checks if PLC runtime state has changed, and if so, emits an event.
896
+ *
897
+ * Call from `onPlcRuntimeStateChanged()` and `readPlcRuntimeState()`.
898
+ *
899
+ * @param state Active PLC runtime state
900
+ */
901
+ handlePlcRuntimeStateChange(state) {
861
902
  let stateChanged = false;
862
- if (this.metaData.plcRuntimeState === undefined || this.metaData.plcRuntimeState.adsState !== res.adsState) {
863
- this.debug(`onPlcRuntimeStateChanged(): PLC runtime state (adsState) changed from ${this.metaData.plcRuntimeState === undefined ? 'UNKNOWN' : this.metaData.plcRuntimeState.adsStateStr} to ${res.adsStateStr}`);
903
+ if (this.metaData.plcRuntimeState === undefined || this.metaData.plcRuntimeState.adsState !== state.adsState) {
904
+ this.debug(`handlePlcRuntimeStateChange(): PLC runtime state (adsState) changed from ${this.metaData.plcRuntimeState === undefined ? 'UNKNOWN' : this.metaData.plcRuntimeState.adsStateStr} to ${state.adsStateStr}`);
864
905
  stateChanged = true;
865
906
  }
866
- if (this.metaData.plcRuntimeState === undefined || this.metaData.plcRuntimeState.deviceState !== res.deviceState) {
867
- this.debug(`onPlcRuntimeStateChanged(): PLC runtime state (deviceState) changed from ${this.metaData.plcRuntimeState === undefined ? 'UNKNOWN' : this.metaData.plcRuntimeState.deviceState} to ${res.deviceState}`);
907
+ if (this.metaData.plcRuntimeState === undefined || this.metaData.plcRuntimeState.deviceState !== state.deviceState) {
908
+ this.debug(`handlePlcRuntimeStateChange(): PLC runtime state (deviceState) changed from ${this.metaData.plcRuntimeState === undefined ? 'UNKNOWN' : this.metaData.plcRuntimeState.deviceState} to ${state.deviceState}`);
868
909
  stateChanged = true;
869
910
  }
870
911
  let oldState = this.metaData.plcRuntimeState !== undefined
871
912
  ? { ...this.metaData.plcRuntimeState }
872
913
  : undefined;
873
- this.metaData.plcRuntimeState = res;
914
+ this.metaData.plcRuntimeState = state;
874
915
  if (stateChanged) {
875
916
  this.emit('plcRuntimeStateChange', this.metaData.plcRuntimeState, oldState);
876
917
  }
@@ -1292,8 +1333,7 @@ class Client extends events_1.default {
1292
1333
  ads.payload.versionBuild = data.readUInt16LE(pos);
1293
1334
  pos += 2;
1294
1335
  //8..24 Device name
1295
- ads.payload.deviceName = ADS.decodePlcStringBuffer(data.subarray(pos, pos + 16));
1296
- ;
1336
+ ads.payload.deviceName = ADS.decodePlcStringBuffer(data.subarray(pos, pos + 16), this.metaData.adsSymbolsUseUtf8);
1297
1337
  }
1298
1338
  break;
1299
1339
  case ADS.ADS_COMMAND.ReadState:
@@ -1379,7 +1419,12 @@ class Client extends events_1.default {
1379
1419
  //AMS port unregister
1380
1420
  case ADS.AMS_HEADER_FLAG.AMS_TCP_PORT_CLOSE:
1381
1421
  packet.amsTcp.commandStr = 'Port unregister';
1382
- //No action at the moment
1422
+ if (this.amsTcpCallback) {
1423
+ this.amsTcpCallback(packet);
1424
+ }
1425
+ else {
1426
+ this.debug(`onAmsTcpPacketReceived(): Port unregister response received but no callback was assigned (${packet.amsTcp.commandStr})`);
1427
+ }
1383
1428
  break;
1384
1429
  //AMS port register
1385
1430
  case ADS.AMS_HEADER_FLAG.AMS_TCP_PORT_CONNECT:
@@ -1842,13 +1887,13 @@ class Client extends events_1.default {
1842
1887
  const commentLength = data.readUInt16LE(pos);
1843
1888
  pos += 2;
1844
1889
  //30.... Symbol name
1845
- symbol.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
1890
+ symbol.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
1846
1891
  pos += nameLength + 1;
1847
1892
  //.. Symbol type
1848
- symbol.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1));
1893
+ symbol.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1), this.metaData.adsSymbolsUseUtf8);
1849
1894
  pos += typeLength + 1;
1850
1895
  //.. Symbol comment
1851
- symbol.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1));
1896
+ symbol.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1), this.metaData.adsSymbolsUseUtf8);
1852
1897
  pos += commentLength + 1;
1853
1898
  //Array information
1854
1899
  symbol.arrayInfo = [];
@@ -1880,10 +1925,10 @@ class Client extends events_1.default {
1880
1925
  const valueLength = data.readUInt8(pos);
1881
1926
  pos += 1;
1882
1927
  //Name
1883
- attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
1928
+ attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
1884
1929
  pos += nameLength + 1;
1885
1930
  //Value
1886
- attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1));
1931
+ attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1), this.metaData.adsSymbolsUseUtf8);
1887
1932
  pos += valueLength + 1;
1888
1933
  symbol.attributes.push(attr);
1889
1934
  }
@@ -1951,13 +1996,13 @@ class Client extends events_1.default {
1951
1996
  const subItemCount = data.readUInt16LE(pos);
1952
1997
  pos += 2;
1953
1998
  //38.. Data type name
1954
- dataType.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
1999
+ dataType.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
1955
2000
  pos += nameLength + 1;
1956
2001
  //.. Data type type
1957
- dataType.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1));
2002
+ dataType.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1), this.metaData.adsSymbolsUseUtf8);
1958
2003
  pos += typeLength + 1;
1959
2004
  //.. Data type comment
1960
- dataType.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1));
2005
+ dataType.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1), this.metaData.adsSymbolsUseUtf8);
1961
2006
  pos += commentLength + 1;
1962
2007
  //Array information
1963
2008
  dataType.arrayInfos = [];
@@ -2041,13 +2086,13 @@ class Client extends events_1.default {
2041
2086
  const parameterCount = data.readUInt16LE(pos);
2042
2087
  pos += 2;
2043
2088
  //56.. Name
2044
- method.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2089
+ method.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2045
2090
  pos += nameLength + 1;
2046
2091
  //.. Return data type
2047
- method.retunDataType = ADS.decodePlcStringBuffer(data.subarray(pos, pos + returnTypeLength + 1));
2092
+ method.retunDataType = ADS.decodePlcStringBuffer(data.subarray(pos, pos + returnTypeLength + 1), this.metaData.adsSymbolsUseUtf8);
2048
2093
  pos += returnTypeLength + 1;
2049
2094
  //.. Comment
2050
- method.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1));
2095
+ method.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1), this.metaData.adsSymbolsUseUtf8);
2051
2096
  pos += commentLength + 1;
2052
2097
  //Parameters
2053
2098
  method.parameters = [];
@@ -2090,13 +2135,13 @@ class Client extends events_1.default {
2090
2135
  const commentLength = data.readUInt16LE(pos);
2091
2136
  pos += 2;
2092
2137
  //38.. Data type name
2093
- param.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2138
+ param.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2094
2139
  pos += nameLength + 1;
2095
2140
  //.. Data type type
2096
- param.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1));
2141
+ param.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1), this.metaData.adsSymbolsUseUtf8);
2097
2142
  pos += typeLength + 1;
2098
2143
  //.. Data type comment
2099
- param.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1));
2144
+ param.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1), this.metaData.adsSymbolsUseUtf8);
2100
2145
  pos += commentLength + 1;
2101
2146
  //Attributes
2102
2147
  param.attributes = [];
@@ -2113,18 +2158,19 @@ class Client extends events_1.default {
2113
2158
  const valueLength = data.readUInt8(pos);
2114
2159
  pos += 1;
2115
2160
  //Name
2116
- attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2161
+ attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2117
2162
  pos += nameLength + 1;
2118
2163
  //Value
2119
- attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1));
2164
+ attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1), this.metaData.adsSymbolsUseUtf8);
2120
2165
  pos += valueLength + 1;
2121
- method.attributes.push(attr);
2166
+ param.attributes.push(attr);
2122
2167
  }
2123
2168
  }
2124
2169
  if (pos - beginPosition > entryLength) {
2125
2170
  //There is some additional data left
2126
2171
  param.reserved2 = data.subarray(pos);
2127
2172
  }
2173
+ pos = beginPosition + entryLength;
2128
2174
  method.parameters.push(param);
2129
2175
  }
2130
2176
  //Attributes
@@ -2142,10 +2188,10 @@ class Client extends events_1.default {
2142
2188
  const valueLength = data.readUInt8(pos);
2143
2189
  pos += 1;
2144
2190
  //Name
2145
- attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2191
+ attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2146
2192
  pos += nameLength + 1;
2147
2193
  //Value
2148
- attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1));
2194
+ attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1), this.metaData.adsSymbolsUseUtf8);
2149
2195
  pos += valueLength + 1;
2150
2196
  method.attributes.push(attr);
2151
2197
  }
@@ -2170,18 +2216,19 @@ class Client extends events_1.default {
2170
2216
  const valueLength = data.readUInt8(pos);
2171
2217
  pos += 1;
2172
2218
  //Name
2173
- attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2219
+ attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2174
2220
  pos += nameLength + 1;
2175
2221
  //Value
2176
- attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1));
2222
+ attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1), this.metaData.adsSymbolsUseUtf8);
2177
2223
  pos += valueLength + 1;
2178
2224
  dataType.attributes.push(attr);
2179
2225
  }
2180
2226
  }
2181
2227
  //If flag EnumInfos set
2182
2228
  dataType.enumInfos = [];
2229
+ let enumInfoCount = 0;
2183
2230
  if ((dataType.flags & ADS.ADS_DATA_TYPE_FLAGS.EnumInfos) === ADS.ADS_DATA_TYPE_FLAGS.EnumInfos) {
2184
- const enumInfoCount = data.readUInt16LE(pos);
2231
+ enumInfoCount = data.readUInt16LE(pos);
2185
2232
  pos += 2;
2186
2233
  for (let i = 0; i < enumInfoCount; i++) {
2187
2234
  let enumInfo = {};
@@ -2189,7 +2236,7 @@ class Client extends events_1.default {
2189
2236
  const nameLength = data.readUInt8(pos);
2190
2237
  pos += 1;
2191
2238
  //Enumeration name
2192
- enumInfo.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2239
+ enumInfo.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2193
2240
  pos += nameLength + 1;
2194
2241
  //Enumeration value
2195
2242
  enumInfo.value = data.subarray(pos, pos + dataType.size);
@@ -2216,9 +2263,43 @@ class Client extends events_1.default {
2216
2263
  }
2217
2264
  //If flag ExtendedEnumInfos set
2218
2265
  if ((dataType.flags & ADS.ADS_DATA_TYPE_FLAGS.ExtendedEnumInfos) === ADS.ADS_DATA_TYPE_FLAGS.ExtendedEnumInfos) {
2219
- //TODO: this is not working now
2220
- //Things probably break now
2221
- this.warn(`Data type ${dataType.name} (${dataType.type}) has flag "ExtendedEnumInfos" which is not supported. Things might not work now. Please open an issue at Github`);
2266
+ for (let i = 0; i < enumInfoCount; i++) {
2267
+ let beginPosition = pos;
2268
+ //Total entry length (bytes)
2269
+ const entryLength = data.readUInt16LE(pos);
2270
+ pos += 2;
2271
+ //Comment length
2272
+ const commentLength = data.readUInt8(pos);
2273
+ pos += 1;
2274
+ //Attribute count
2275
+ const attributeCount = data.readUInt8(pos);
2276
+ pos += 1;
2277
+ //Enumeration comment
2278
+ dataType.enumInfos[i].comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1), this.metaData.adsSymbolsUseUtf8);
2279
+ pos += commentLength + 1;
2280
+ //Enumeration attributes
2281
+ dataType.enumInfos[i].attributes = [];
2282
+ //console.log(enumInfoCount, num1, commentLength, attributeCount, test, attributes);
2283
+ //Attributes
2284
+ for (let i = 0; i < attributeCount; i++) {
2285
+ const attr = {};
2286
+ //Name length
2287
+ const nameLength = data.readUInt8(pos);
2288
+ pos += 1;
2289
+ //Value length
2290
+ const valueLength = data.readUInt8(pos);
2291
+ pos += 1;
2292
+ //Name
2293
+ attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2294
+ pos += nameLength + 1;
2295
+ //Value
2296
+ attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1), this.metaData.adsSymbolsUseUtf8);
2297
+ pos += valueLength + 1;
2298
+ dataType.enumInfos[i].attributes.push(attr);
2299
+ }
2300
+ //If there are some reserved bytes -> skip
2301
+ pos = beginPosition + entryLength;
2302
+ }
2222
2303
  }
2223
2304
  //If flag SoftwareProtectionLevels set
2224
2305
  if ((dataType.flags & ADS.ADS_DATA_TYPE_FLAGS.SoftwareProtectionLevels) === ADS.ADS_DATA_TYPE_FLAGS.SoftwareProtectionLevels) {
@@ -2325,6 +2406,7 @@ class Client extends events_1.default {
2325
2406
  if (err.adsError && err.adsError?.errorCode === 1808) {
2326
2407
  //Type wasn't found.
2327
2408
  //Might be TwinCAT 2 system that doesn't provide pseudo or base data types by ADS --> check if we know the type ourselves
2409
+ const originalName = name;
2328
2410
  if (ADS.BASE_DATA_TYPES.isPseudoType(name)) {
2329
2411
  //This converts e.g. PVOID to a primitive type
2330
2412
  if (knownSize === undefined) {
@@ -2358,6 +2440,11 @@ class Client extends events_1.default {
2358
2440
  extendedFlags: 0,
2359
2441
  reserved: Buffer.alloc(0)
2360
2442
  };
2443
+ //Adding to cache, even though it's not read from the target
2444
+ //With TwinCAT 2, this prevents trying to read base types again and again from the target (as there aren't any)
2445
+ if (!this.settings.disableCaching && !targetOpts.adsPort && !targetOpts.amsNetId) {
2446
+ this.metaData.plcDataTypes[originalName.toLowerCase()] = dataType;
2447
+ }
2361
2448
  }
2362
2449
  else {
2363
2450
  //Type is unknown - we can't do anything
@@ -2401,7 +2488,7 @@ class Client extends events_1.default {
2401
2488
  }
2402
2489
  }
2403
2490
  else if ((((dataType.type === '' && !ADS.BASE_DATA_TYPES.isPseudoType(dataType.name)) || ADS.BASE_DATA_TYPES.isKnownType(dataType.name))
2404
- && dataType.flagsStr.includes('DataType')
2491
+ && (dataType.flagsStr.includes('DataType') || ADS.BASE_DATA_TYPES.isKnownType(dataType.name)) //isKnownType() call is for TwinCAT 2 support, as base types (like INT16) do not have DataType flag (but it's the final base type)
2405
2492
  && !dataType.flagsStr.includes('EnumInfos')
2406
2493
  && dataType.arrayDimension === 0)) {
2407
2494
  //This is final form (no need to go deeper)
@@ -2409,8 +2496,10 @@ class Client extends events_1.default {
2409
2496
  }
2410
2497
  else if (dataType.arrayDimension > 0) {
2411
2498
  //Data type is an array - get array subtype
2499
+ //TwinCAT 2 support - calculate size if it's array of pointer etc.
2500
+ const size = dataType.size / dataType.arrayInfos.reduce((total, dimension) => total * dimension.length, 1);
2412
2501
  builtType = {
2413
- ...(await this.buildDataType(dataType.type, targetOpts, false))
2502
+ ...(await this.buildDataType(dataType.type, targetOpts, false, size))
2414
2503
  };
2415
2504
  builtType.arrayInfos = [...dataType.arrayInfos, ...builtType.arrayInfos];
2416
2505
  }
@@ -2467,7 +2556,7 @@ class Client extends events_1.default {
2467
2556
  */
2468
2557
  convertBufferToPrimitiveType(buffer, dataType) {
2469
2558
  if (dataType.adsDataType === ADS.ADS_DATA_TYPES.ADST_STRING) {
2470
- return ADS.decodePlcStringBuffer(buffer);
2559
+ return ADS.decodePlcStringBuffer(buffer); //TODO: If symbol has {attribute 'TcEncoding':='UTF-8'}
2471
2560
  }
2472
2561
  else if (dataType.adsDataType === ADS.ADS_DATA_TYPES.ADST_WSTRING) {
2473
2562
  return ADS.decodePlcWstringBuffer(buffer);
@@ -2972,6 +3061,8 @@ class Client extends events_1.default {
2972
3061
  * }
2973
3062
  * ```
2974
3063
  *
3064
+ * @param command The ADS command to send
3065
+ *
2975
3066
  * @template T In Typescript, the type of the ADS response. If omitted, generic {@link AdsResponse} type is used.
2976
3067
  *
2977
3068
  * @throws Throws an error if sending the command fails or if target responds with an error
@@ -3057,6 +3148,82 @@ class Client extends events_1.default {
3057
3148
  }
3058
3149
  });
3059
3150
  }
3151
+ /**
3152
+ * Sends a raw ADS command to the target with fallback. A wrapper for {@link Client.sendAdsCommand}().
3153
+ *
3154
+ * Calls `sendAdsCommand(command)` and if it fails with
3155
+ * ADS error 1793 or 1808 then calls the `sendAdsCommand(fallback)`.
3156
+ *
3157
+ * See {@link Client.readPlcUploadInfo}() for use case.
3158
+ *
3159
+ * The ideas is copied from TwinCAT.Ads.dll (`TwinCAT.Ads.AdsClientExtensions.ReadWithFallbackAsync()`).
3160
+ *
3161
+ * @example
3162
+ * ```js
3163
+ * try {
3164
+ * const data = Buffer.alloc(12);
3165
+ * //...code omitted...
3166
+ *
3167
+ * const command = {
3168
+ * adsCommand: ADS.ADS_COMMAND.Read,
3169
+ * targetAmsNetId: targetOpts.amsNetId,
3170
+ * targetAdsPort: targetOpts.adsPort,
3171
+ * payload: data
3172
+ * };
3173
+ *
3174
+ * const fbData = Buffer.alloc(12);
3175
+ * //...code omitted...
3176
+ *
3177
+ * const fallback = {
3178
+ * adsCommand: ADS.ADS_COMMAND.Read,
3179
+ * targetAmsNetId: targetOpts.amsNetId,
3180
+ * targetAdsPort: targetOpts.adsPort,
3181
+ * payload: fbData
3182
+ * };
3183
+ *
3184
+ * const { response: res, fallbackUsed } = await this.sendAdsCommandWithFallback<AdsReadResponse>(command, fallback);
3185
+ *
3186
+ * //If we are here, one of those commands was succcesful
3187
+ * if(fallbackUsed) {
3188
+ * console.log("Fallback was used. Result:", res.ads.payload);
3189
+ * } else {
3190
+ * console.log("Fallback was not used. Result:", res.ads.payload);
3191
+ * }
3192
+ *
3193
+ * } catch (err) {
3194
+ * console.log("Error:", err);
3195
+ * }
3196
+ * ```
3197
+ *
3198
+ * @param command The main ADS command to send
3199
+ * @param fallback The fallback ADS command to send
3200
+ *
3201
+ * @template T In Typescript, the type of the ADS response. If omitted, generic {@link AdsResponse} type is used.
3202
+ *
3203
+ * @throws Throws an error if sending the command fails or if target responds with an error
3204
+ */
3205
+ async sendAdsCommandWithFallback(command, fallback) {
3206
+ try {
3207
+ this.debug(`sendAdsCommandWithFallback(): Sending ADS command with a fallback`);
3208
+ return {
3209
+ fallbackUsed: false,
3210
+ response: await this.sendAdsCommand(command)
3211
+ };
3212
+ }
3213
+ catch (err) {
3214
+ const clientErr = err;
3215
+ //If error is "Service is not supported by server" (1793) or "Symbol not found" (1808) then we should try the fallback
3216
+ if (clientErr.adsError?.errorCode === 1793 || clientErr.adsError?.errorCode === 1808) {
3217
+ this.debug(`sendAdsCommandWithFallback(): Sending ADS command failed with ADS error ${clientErr.adsError.errorCode} - trying fallback...`);
3218
+ return {
3219
+ fallbackUsed: true,
3220
+ response: await this.sendAdsCommand(fallback)
3221
+ };
3222
+ }
3223
+ this.debug(`sendAdsCommandWithFallback(): Sending ADS command failed - skipping fallback (ADS error is not 1793 or 1808)`);
3224
+ throw err;
3225
+ }
3226
+ }
3060
3227
  /**
3061
3228
  * Sends an ADS `WriteControl` command to the target.
3062
3229
  *
@@ -3148,6 +3315,8 @@ class Client extends events_1.default {
3148
3315
  const state = await this.readPlcRuntimeState(targetOpts);
3149
3316
  await this.writeControl("Run", state.deviceState, undefined, targetOpts);
3150
3317
  this.debug(`startPlc(): PLC runtime started at ${this.targetToString(targetOpts)}`);
3318
+ //Reading runtime state to make sure metaData is updated asap
3319
+ await this.readPlcRuntimeState(targetOpts);
3151
3320
  }
3152
3321
  catch (err) {
3153
3322
  this.debug(`startPlc(): Starting PLC runtime at ${this.targetToString(targetOpts)} failed: %o`, err);
@@ -3180,6 +3349,8 @@ class Client extends events_1.default {
3180
3349
  const state = await this.readPlcRuntimeState(targetOpts);
3181
3350
  await this.writeControl("Reset", state.deviceState, undefined, targetOpts);
3182
3351
  this.debug(`resetPlc(): PLC runtime reset at ${this.targetToString(targetOpts)}`);
3352
+ //Reading runtime state to make sure metaData is updated asap
3353
+ await this.readPlcRuntimeState(targetOpts);
3183
3354
  }
3184
3355
  catch (err) {
3185
3356
  this.debug(`resetPlc(): Resetting PLC runtime at ${this.targetToString(targetOpts)} failed: %o`, err);
@@ -3212,6 +3383,8 @@ class Client extends events_1.default {
3212
3383
  const state = await this.readPlcRuntimeState(targetOpts);
3213
3384
  await this.writeControl("Stop", state.deviceState, undefined, targetOpts);
3214
3385
  this.debug(`stopPlc(): PLC runtime stopped at ${this.targetToString(targetOpts)}`);
3386
+ //Reading runtime state to make sure metaData is updated asap
3387
+ await this.readPlcRuntimeState(targetOpts);
3215
3388
  }
3216
3389
  catch (err) {
3217
3390
  this.debug(`stopPlc(): Stopping PLC runtime at ${this.targetToString(targetOpts)} failed: %o`, err);
@@ -3381,7 +3554,7 @@ class Client extends events_1.default {
3381
3554
  * @example
3382
3555
  * ```js
3383
3556
  * try {
3384
- * const symbol = await client.getSymbol('GVL_Test.ExampleStruct');
3557
+ * const symbol = await client.getSymbol('GVL_Read.StandardTypes.INT_');
3385
3558
  * } catch (err) {
3386
3559
  * console.log("Error:", err);
3387
3560
  * }
@@ -3559,8 +3732,8 @@ class Client extends events_1.default {
3559
3732
  });
3560
3733
  this.debug(`readPlcRuntimeState(): Runtime state read successfully. State is %o`, res.ads.payload);
3561
3734
  if (!targetOpts.adsPort && !targetOpts.amsNetId) {
3562
- //Target is not overridden -> save to metadata
3563
- this.metaData.plcRuntimeState = res.ads.payload;
3735
+ //Target is not overridden -> handle the result (check for change and update metadata)
3736
+ this.handlePlcRuntimeStateChange(res.ads.payload);
3564
3737
  }
3565
3738
  return res.ads.payload;
3566
3739
  }
@@ -3677,7 +3850,7 @@ class Client extends events_1.default {
3677
3850
  * //Checks if value has changed every 100ms
3678
3851
  * //Callback is called only when the value has changed
3679
3852
  * await client.subscribeValue(
3680
- * 'GVL_Test.ExampleStruct.',
3853
+ * 'GVL_Subscription.NumericValue_10ms',
3681
3854
  * (data, subscription) => {
3682
3855
  * console.log(`Value of ${subscription.symbol.name} has changed: ${data.value}`);
3683
3856
  * },
@@ -3825,7 +3998,7 @@ class Client extends events_1.default {
3825
3998
  * //Checks if value has changed every 100ms
3826
3999
  * //Callback is called only when the value has changed
3827
4000
  * await client.subscribe({
3828
- * target: 'GVL_Test.ExampleStruct',
4001
+ * target: 'GVL_Subscription.NumericValue_10ms',
3829
4002
  * callback: (data, subscription) => {
3830
4003
  * console.log(`Value of ${subscription.symbol.name} has changed: ${data.value}`);
3831
4004
  * },
@@ -4053,36 +4226,90 @@ class Client extends events_1.default {
4053
4226
  data.writeUInt32LE(0, pos);
4054
4227
  pos += 4;
4055
4228
  //8..11 Read data length
4056
- data.writeUInt32LE(24, pos);
4229
+ data.writeUInt32LE(64, pos); //64 = upload info version 3 size (works also for lower versions)
4230
+ pos += 4;
4231
+ const command = {
4232
+ adsCommand: ADS.ADS_COMMAND.Read,
4233
+ targetAmsNetId: targetOpts.amsNetId,
4234
+ targetAdsPort: targetOpts.adsPort,
4235
+ payload: data
4236
+ };
4237
+ //Fallback read command for version 1
4238
+ //Allocating bytes for request
4239
+ const fbData = Buffer.alloc(12);
4240
+ pos = 0;
4241
+ //0..3 IndexGroup
4242
+ fbData.writeUInt32LE(ADS.ADS_RESERVED_INDEX_GROUPS.SymbolUploadInfo, pos);
4057
4243
  pos += 4;
4244
+ //4..7 IndexOffset
4245
+ fbData.writeUInt32LE(0, pos);
4246
+ pos += 4;
4247
+ //8..11 Read data length
4248
+ fbData.writeUInt32LE(8, pos); //8 = upload info version 1 size
4249
+ pos += 4;
4250
+ const fallback = {
4251
+ adsCommand: ADS.ADS_COMMAND.Read,
4252
+ targetAmsNetId: targetOpts.amsNetId,
4253
+ targetAdsPort: targetOpts.adsPort,
4254
+ payload: fbData
4255
+ };
4058
4256
  try {
4059
- const res = await this.sendAdsCommand({
4060
- adsCommand: ADS.ADS_COMMAND.Read,
4061
- targetAmsNetId: targetOpts.amsNetId,
4062
- targetAdsPort: targetOpts.adsPort,
4063
- payload: data
4064
- });
4257
+ const { response: res, fallbackUsed } = await this.sendAdsCommandWithFallback(command, fallback);
4258
+ this.debug(`readPlcUploadInfo(): Upload info read - fallback was needed: ${fallbackUsed}`);
4065
4259
  const uploadInfo = {};
4066
4260
  let pos = 0;
4067
4261
  const response = res.ads.payload;
4262
+ //If we are here, either the command or fallback was successful
4263
+ //Detect version from byte count
4264
+ if (response.byteLength === 8) {
4265
+ uploadInfo.version = 1;
4266
+ }
4267
+ else if (response.byteLength === 24) {
4268
+ uploadInfo.version = 2;
4269
+ }
4270
+ else if (response.byteLength === 64) {
4271
+ uploadInfo.version = 3;
4272
+ }
4273
+ else {
4274
+ //Shouldn't happen as we request max 64 bytes
4275
+ throw new Error(`Upload info version is unknown (received ${response.byteLength} bytes) - create a GitHub issue`);
4276
+ }
4068
4277
  //0..3 Symbol count
4069
4278
  uploadInfo.symbolCount = response.readUInt32LE(pos);
4070
4279
  pos += 4;
4071
4280
  //4..7 Symbol length
4072
4281
  uploadInfo.symbolLength = response.readUInt32LE(pos);
4073
4282
  pos += 4;
4074
- //8..11 Data type count
4075
- uploadInfo.dataTypeCount = response.readUInt32LE(pos);
4076
- pos += 4;
4077
- //12..15 Data type length
4078
- uploadInfo.dataTypeLength = response.readUInt32LE(pos);
4079
- pos += 4;
4080
- //16..19 Extra count
4081
- uploadInfo.extraCount = response.readUInt32LE(pos);
4082
- pos += 4;
4083
- //20..23 Extra length
4084
- uploadInfo.extraLength = response.readUInt32LE(pos);
4085
- pos += 4;
4283
+ if (uploadInfo.version >= 2) {
4284
+ //8..11 Data type count
4285
+ uploadInfo.dataTypeCount = response.readUInt32LE(pos);
4286
+ pos += 4;
4287
+ //12..15 Data type length
4288
+ uploadInfo.dataTypeLength = response.readUInt32LE(pos);
4289
+ pos += 4;
4290
+ //16..19 Max. allowed dynamic symbol count
4291
+ uploadInfo.maxDynamicSymbolCount = response.readUInt32LE(pos);
4292
+ pos += 4;
4293
+ //20..23 Number of dynamic symbols used (version >= 2)
4294
+ uploadInfo.dynamicSymbolCount = response.readUInt32LE(pos);
4295
+ pos += 4;
4296
+ if (uploadInfo.version >= 3) {
4297
+ //24..27 Invalid dynamic symbol count
4298
+ uploadInfo.invalidDynamicSymbolCount = response.readUInt32LE(pos);
4299
+ pos += 4;
4300
+ //28..31 Encoding code page used for STRING encoding
4301
+ uploadInfo.encodingCodePage = response.readUInt32LE(pos);
4302
+ pos += 4;
4303
+ //32..35 Upload info flags
4304
+ uploadInfo.flags = response.readUInt32LE(pos);
4305
+ uploadInfo.flagsStr = ADS.ADS_UPLOAD_INFO_FLAGS.toStringArray(uploadInfo.flags);
4306
+ pos += 4;
4307
+ //36..63 Reserved
4308
+ uploadInfo.reserved = response.subarray(pos);
4309
+ }
4310
+ }
4311
+ //Target has UTF-8 encoded ADS symbols if encodingCodePage is 65001 (or if forced from settings)
4312
+ this.metaData.adsSymbolsUseUtf8 = uploadInfo.encodingCodePage === 65001 || this.settings.forceUtf8ForAdsSymbols;
4086
4313
  if (!targetOpts.adsPort && !targetOpts.amsNetId) {
4087
4314
  //Target is not overridden -> save to metadata
4088
4315
  this.metaData.plcUploadInfo = uploadInfo;
@@ -4271,7 +4498,7 @@ class Client extends events_1.default {
4271
4498
  data.writeUInt32LE(0, pos);
4272
4499
  pos += 4;
4273
4500
  //8..11 Read data length
4274
- data.writeUInt32LE(uploadInfo.dataTypeLength, pos);
4501
+ data.writeUInt32LE(uploadInfo.dataTypeLength ?? 0xFFFFFFFF, pos); //Using 0xFFFFFFFF is upload info is version 1 (= we don't know the length)
4275
4502
  pos += 4;
4276
4503
  try {
4277
4504
  const res = await this.sendAdsCommand({
@@ -4773,7 +5000,6 @@ class Client extends events_1.default {
4773
5000
  *
4774
5001
  * NOTE: Unlike with {@link readRawByPath}(), this command uses multiple ADS requests for the operation.
4775
5002
  *
4776
- *
4777
5003
  * @example
4778
5004
  * ```js
4779
5005
  * try {
@@ -4784,11 +5010,11 @@ class Client extends events_1.default {
4784
5010
  *
4785
5011
  * //Writing a POINTER value (Note the dereference operator ^)
4786
5012
  * const ptrValue = ...
4787
- * await client.writeRawByPath('GVL_Read.ComplexTypes.POINTER_^', ptrValue);
5013
+ * await client.writeRawByPath('GVL_Write.ComplexTypes.POINTER_^', ptrValue);
4788
5014
  *
4789
5015
  * //Writing a REFERENCE value
4790
5016
  * const refValue = ...
4791
- * await client.readRawByPath('GVL_Read.ComplexTypes.REFERENCE_');
5017
+ * await client.writeRawByPath('GVL_Write.ComplexTypes.REFERENCE_');
4792
5018
  *
4793
5019
  * } catch (err) {
4794
5020
  * console.log("Error:", err);
@@ -5042,7 +5268,7 @@ class Client extends events_1.default {
5042
5268
  * @example
5043
5269
  * ```js
5044
5270
  * try {
5045
- * const res = await client.readValue('GVL_Test.ExampleStruct');
5271
+ * const res = await client.readValue('GVL_Read.StandardTypes.INT_');
5046
5272
  * console.log(res.value);
5047
5273
  * } catch (err) {
5048
5274
  * console.log("Error:", err);
@@ -5053,6 +5279,7 @@ class Client extends events_1.default {
5053
5279
  * @param targetOpts Optional target settings that override values in `settings`
5054
5280
  *
5055
5281
  * @template T In Typescript, the data type of the value, for example `readValue<number>(...)` or `readValue<ST_TypedStruct>(...)` (default: `any`)
5282
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5056
5283
  */
5057
5284
  async readValue(path, targetOpts = {}) {
5058
5285
  if (!this.connection.connected) {
@@ -5073,7 +5300,8 @@ class Client extends events_1.default {
5073
5300
  let dataType;
5074
5301
  try {
5075
5302
  this.debugD(`readValue(): Getting data type for ${path}`);
5076
- dataType = await this.buildDataType(symbol.type, targetOpts);
5303
+ //Also passing size for TC2 pointer/pseudo type support
5304
+ dataType = await this.buildDataType(symbol.type, targetOpts, true, symbol.size);
5077
5305
  }
5078
5306
  catch (err) {
5079
5307
  this.debug(`readValue(): Getting data type for ${path} failed: %o`, err);
@@ -5108,7 +5336,7 @@ class Client extends events_1.default {
5108
5336
  };
5109
5337
  }
5110
5338
  /**
5111
- * Reads variable's value from the target system by a symbol object
5339
+ * Reads variable's value from the target system by a symbol object (acquired using `getSymbol()`)
5112
5340
  * and returns the value as a Javascript object.
5113
5341
  *
5114
5342
  * Returns variable's
@@ -5122,7 +5350,7 @@ class Client extends events_1.default {
5122
5350
  * @example
5123
5351
  * ```js
5124
5352
  * try {
5125
- * const symbol = await client.getSymbol('GVL_Test.ExampleStruct');
5353
+ * const symbol = await client.getSymbol('GVL_Read.StandardTypes.INT_');
5126
5354
  *
5127
5355
  * const res = await client.readValueBySymbol(symbol);
5128
5356
  * console.log(res.value);
@@ -5131,10 +5359,11 @@ class Client extends events_1.default {
5131
5359
  * }
5132
5360
  * ```
5133
5361
  *
5134
- * @param symbol Symbol object (acquired using {@link getSymbol}())
5362
+ * @param symbol Symbol object
5135
5363
  * @param targetOpts Optional target settings that override values in `settings`
5136
5364
  *
5137
5365
  * @template T In Typescript, the data type of the value, for example `readValueBySymbol<number>(...)` or `readValueBySymbol<ST_TypedStruct>(...)` (default: `any`)
5366
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5138
5367
  */
5139
5368
  async readValueBySymbol(symbol, targetOpts = {}) {
5140
5369
  if (!this.connection.connected) {
@@ -5159,11 +5388,9 @@ class Client extends events_1.default {
5159
5388
  * @example
5160
5389
  * ```js
5161
5390
  * try {
5162
- * const value = {
5163
- * example: true
5164
- * };
5391
+ * const res = await client.writeValue('GVL_Write.StandardTypes.INT_', 32767);
5392
+ * console.log('Value written:', res.value);
5165
5393
  *
5166
- * const res = await client.writeValue('GVL_Test.ExampleStruct', value);
5167
5394
  * } catch (err) {
5168
5395
  * console.log("Error:", err);
5169
5396
  * }
@@ -5172,9 +5399,10 @@ class Client extends events_1.default {
5172
5399
  * @param path Variable path in the PLC (such as `GVL_Test.ExampleStruct`)
5173
5400
  * @param value Value to write
5174
5401
  * @param targetOpts Optional target settings that override values in `settings`
5175
- * @param autoFill If set and the data type is a container (`STRUCT`, `FUNCTION_BLOCK` etc.), missing properties are automatically set to default values (usually `0` or `''`).
5402
+ * @param autoFill If set and the data type is a container (`STRUCT`, `FUNCTION_BLOCK` etc.), missing properties are automatically set to active values read from target (kept as-is).
5176
5403
  *
5177
5404
  * @template T In Typescript, the data type of the value, for example `writeValue<number>(...)` or `writeValue<ST_TypedStruct>(...)`
5405
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5178
5406
  */
5179
5407
  async writeValue(path, value, autoFill = false, targetOpts = {}) {
5180
5408
  if (!this.connection.connected) {
@@ -5195,7 +5423,8 @@ class Client extends events_1.default {
5195
5423
  let dataType;
5196
5424
  try {
5197
5425
  this.debugD(`writeValue(): Getting data type for ${path}`);
5198
- dataType = await this.buildDataType(symbol.type, targetOpts);
5426
+ //Also passing size for TC2 pointer/pseudo type support
5427
+ dataType = await this.buildDataType(symbol.type, targetOpts, true, symbol.size);
5199
5428
  }
5200
5429
  catch (err) {
5201
5430
  this.debug(`writeValue(): Getting data type for ${path} failed: %o`, err);
@@ -5253,20 +5482,37 @@ class Client extends events_1.default {
5253
5482
  };
5254
5483
  }
5255
5484
  /**
5256
- * **TODO - DOCUMENTATION ONGOING**
5485
+ * Writes variable's value to the target system by a symbol object (acquired using `getSymbol()`).
5486
+ * Converts the value from a Javascript object to a raw value.
5257
5487
  *
5258
- * Writes a value by symbol. Converts the value from a Javascript object to a raw value.
5488
+ * Returns variable's
5489
+ * - converted value
5490
+ * - raw value
5491
+ * - data type
5492
+ * - symbol
5259
5493
  *
5260
- * Returns the converted value, the raw value, the data type and the symbol.
5494
+ * **NOTE:** Do not use `autoFill` for `UNION` types, it doesn't work correctly.
5495
+ *
5496
+ * **NOTE:** This requires that the target is a PLC runtime or has equivalent ADS protocol support.
5261
5497
  *
5262
- * **NOTE:** Do not use `autoFill` for `UNION` types, it works without errors but the result isn't probably the desired one
5498
+ * @example
5499
+ * ```js
5500
+ * try {
5501
+ * const symbol = await client.getSymbol('GVL_Read.StandardTypes.INT_');
5263
5502
  *
5264
- * @param symbol Symbol (acquired using `getSymbol()`)
5503
+ * const res = await client.writeValueBySymbol(symbol, 32767);
5504
+ * } catch (err) {
5505
+ * console.log("Error:", err);
5506
+ * }
5507
+ * ```
5508
+ *
5509
+ * @param symbol Symbol object
5265
5510
  * @param value Value to write
5266
5511
  * @param targetOpts Optional target settings that override values in `settings`
5267
- * @param autoFill If true and data type is a container (`STRUCT`, `FUNCTION_BLOCK` etc.), missing properties are automatically **set to default values** (usually `0` or `''`)
5512
+ * @param autoFill If set and the data type is a container (`STRUCT`, `FUNCTION_BLOCK` etc.), missing properties are automatically set to active values read from target (kept as-is).
5268
5513
  *
5269
5514
  * @template T In Typescript, the data type of the value, for example `writeValue<number>(...)` or `writeValue<ST_TypedStruct>(...)`
5515
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5270
5516
  */
5271
5517
  async writeValueBySymbol(symbol, value, autoFill = false, targetOpts = {}) {
5272
5518
  if (!this.connection.connected) {
@@ -5275,14 +5521,27 @@ class Client extends events_1.default {
5275
5521
  return this.writeValue(symbol.name, value, autoFill, targetOpts);
5276
5522
  }
5277
5523
  /**
5278
- * **TODO - DOCUMENTATION ONGOING**
5279
- *
5280
5524
  * Returns a default (empty) Javascript object representing provided PLC data type.
5281
5525
  *
5282
- * @param dataType Data type name in the PLC as string (such as `ST_Struct`) or `AdsDataType` object
5526
+ * @example
5527
+ * ```js
5528
+ * try {
5529
+ * const res = await client.getDefaultPlcObject('INT');
5530
+ * console.log(res); //0
5531
+
5532
+ * const res2 = await client.getDefaultPlcObject('Tc2_Standard.TON');
5533
+ * console.log(res2); //{ IN: false, PT: 0, Q: false, ET: 0, M: false, StartTime: 0 }
5534
+ *
5535
+ * } catch (err) {
5536
+ * console.log("Error:", err);
5537
+ * }
5538
+ * ```
5539
+ *
5540
+ * @param dataType Data type name in the PLC as string (such as `ST_Struct`) or data type object (acquired using {@link getDataType}())
5283
5541
  * @param targetOpts Optional target settings that override values in `settings`
5284
5542
  *
5285
- * @template T Typescript data type of the PLC data, for example `readValue<number>(...)` or `readValue<ST_TypedStruct>(...)`
5543
+ * @template T Typescript data type of the PLC data, for example `getDefaultPlcObject<number>(...)` or `getDefaultPlcObject<ST_TypedStruct>(...)`
5544
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5286
5545
  */
5287
5546
  async getDefaultPlcObject(dataType, targetOpts = {}) {
5288
5547
  if (!this.connection.connected) {
@@ -5306,15 +5565,28 @@ class Client extends events_1.default {
5306
5565
  return value;
5307
5566
  }
5308
5567
  /**
5309
- * **TODO - DOCUMENTATION ONGOING**
5568
+ * Converts raw data to a Javascript object by using the provided data type.
5569
+ *
5570
+ * @example
5571
+ * ```js
5572
+ * try {
5573
+ * const data = await client.readRaw(16448, 414816, 2);
5574
+ * console.log(data); //<Buffer ff 7f>
5310
5575
  *
5311
- * Converts a raw byte value to a Javascript object.
5576
+ * const converted = await client.convertFromRaw(data, 'INT');
5577
+ * console.log(converted); //32767
5578
+ *
5579
+ * } catch (err) {
5580
+ * console.log("Error:", err);
5581
+ * }
5582
+ * ```
5312
5583
  *
5313
- * @param data Raw PLC data as Buffer (read for example with `readRaw()`)
5314
- * @param dataType Data type name in the PLC as string (such as `ST_Struct`) or `AdsDataType` object
5584
+ * @param data Raw data (acquired for example using {@link readRaw}())
5585
+ * @param dataType Data type name in the PLC as string (such as `ST_Struct`) or data type object (acquired using {@link getDataType}())
5315
5586
  * @param targetOpts Optional target settings that override values in `settings`
5316
5587
  *
5317
5588
  * @template T Typescript data type of the PLC data, for example `convertFromRaw<number>(...)` or `convertFromRaw<ST_TypedStruct>(...)`
5589
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5318
5590
  */
5319
5591
  async convertFromRaw(data, dataType, targetOpts = {}) {
5320
5592
  if (!this.connection.connected) {
@@ -5346,18 +5618,27 @@ class Client extends events_1.default {
5346
5618
  return value;
5347
5619
  }
5348
5620
  /**
5349
- * **TODO - DOCUMENTATION ONGOING**
5621
+ * Converts a Javascript object to raw data by using the provided data type.
5350
5622
  *
5351
- * Converts a Javascript object to raw byte data.
5623
+ * **NOTE:** Do not use `autoFill` for `UNION` types, it doesn't work correctly.
5624
+
5625
+ * @example
5626
+ * ```js
5627
+ * try {
5628
+ * const data = await client.convertToRaw(32767, 'INT');
5629
+ * console.log(data); //<Buffer ff 7f>
5352
5630
  *
5353
- * **NOTE:** Do not use `autoFill` for `UNION` types, it works without errors but the result isn't probably the desired one
5631
+ * } catch (err) {
5632
+ * console.log("Error:", err);
5633
+ * }
5634
+ * ```
5354
5635
  *
5355
- * @param value Javascript object that represents the `dataType` in target system
5356
- * @param dataType Data type name in the PLC as string (such as `ST_Struct`) or `AdsDataType` object
5357
- * @param autoFill If true and data type is a container (`STRUCT`, `FUNCTION_BLOCK` etc.), missing properties are automatically **set to default values** (usually `0` or `''`)
5636
+ * @param value Value to convert
5637
+ * @param dataType Data type name in the PLC as string (such as `ST_Struct`) or data type object (acquired using {@link getDataType}())
5638
+ * @param autoFill autoFill If set and the data type is a container (`STRUCT`, `FUNCTION_BLOCK` etc.), missing properties are automatically set to default values (`0` or empty string).
5358
5639
  * @param targetOpts Optional target settings that override values in `settings`
5359
5640
  *
5360
- * @template T Typescript data type of the PLC data, for example `convertFromRaw<number>(...)` or `convertFromRaw<ST_TypedStruct>(...)`
5641
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5361
5642
  */
5362
5643
  async convertToRaw(value, dataType, autoFill = false, targetOpts = {}) {
5363
5644
  if (!this.connection.connected) {
@@ -5413,14 +5694,39 @@ class Client extends events_1.default {
5413
5694
  }
5414
5695
  }
5415
5696
  /**
5416
- * **TODO - DOCUMENTATION ONGOING**
5697
+ * Creates a handle to a variable at the target system by variable path (such as `GVL_Test.ExampleStruct`).
5417
5698
  *
5418
- * Creates a variable handle for a PLC symbol by given variable path
5699
+ * The handle can be then used for reading and writing the value.
5419
5700
  *
5420
- * Variable value can be accessed by using the handle with `readRawByHandle()` and `writeRawByHandle()`
5701
+ * Reading and writing dereferenced `POINTER` and `REFERENCE` values is also possible.
5702
+ * See {@link readRawByHandle}() and {@link writeRawByHandle}().
5421
5703
  *
5422
- * @param path Full variable path in the PLC (such as `GVL_Test.ExampleStruct`)
5423
- * @param targetOpts Optional target settings that override values in `settings` (**NOTE:** If used, no caching is available -> possible performance impact)
5704
+ * NOTE: The handle should be deleted after it's no longer needed,
5705
+ * as there is a limited amount of handles available. See {@link deleteVariableHandle}().
5706
+ *
5707
+ * @example
5708
+ * ```js
5709
+ * try {
5710
+ * //POINTER value (Note the dereference operator ^)
5711
+ * const handle1 = await client.createVariableHandle('GVL_Read.ComplexTypes.POINTER_^');
5712
+ * const value = await client.readRawByHandle(handle1);
5713
+ * await client.deleteVariableHandle(handle1);
5714
+ *
5715
+ * const handle2 = await client.createVariableHandle('GVL_Read.StandardTypes.INT_');
5716
+ * const value2 = await client.readRawByHandle(handle2);
5717
+ * await client.deleteVariableHandle(handle2);
5718
+ *
5719
+ * //Now you use convertFromRaw() to get actual values
5720
+ *
5721
+ * } catch (err) {
5722
+ * console.log("Error:", err);
5723
+ * }
5724
+ * ```
5725
+ *
5726
+ * @param path Variable path in the PLC to read (such as `GVL_Test.ExampleStruct`)
5727
+ * @param targetOpts Optional target settings that override values in `settings`
5728
+ *
5729
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5424
5730
  */
5425
5731
  async createVariableHandle(path, targetOpts = {}) {
5426
5732
  if (!this.connection.connected) {
@@ -5468,7 +5774,7 @@ class Client extends events_1.default {
5468
5774
  let dataTypeLength = response.readUInt16LE(pos);
5469
5775
  pos += 2;
5470
5776
  //10..n Data type
5471
- result.dataType = ADS.decodePlcStringBuffer(response.subarray(pos, pos + dataTypeLength + 1));
5777
+ result.dataType = ADS.decodePlcStringBuffer(response.subarray(pos, pos + dataTypeLength + 1), this.metaData.adsSymbolsUseUtf8);
5472
5778
  this.debug(`createVariableHandle(): Variable handle created to ${path}`);
5473
5779
  return result;
5474
5780
  }
@@ -5478,18 +5784,44 @@ class Client extends events_1.default {
5478
5784
  }
5479
5785
  }
5480
5786
  /**
5481
- * **TODO - DOCUMENTATION ONGOING**
5787
+ * Sends multiple {@link createVariableHandle}() commands in one ADS packet.
5788
+ *
5789
+ * Creates a handle to a variable at the target system by variable path (such as `GVL_Test.ExampleStruct`).
5790
+ *
5791
+ * The handle can be then used for reading and writing the value.
5482
5792
  *
5483
- * Sends multiple createVariableHandle() commands in one ADS packet
5793
+ * Reading and writing dereferenced `POINTER` and `REFERENCE` values is also possible.
5794
+ * See {@link readRawByHandle}() and {@link writeRawByHandle}().
5484
5795
  *
5485
- * Creates a variable handle for a PLC symbol by given variable path
5796
+ * NOTE: The handle should be deleted after it's no longer needed,
5797
+ * as there is a limited amount of handles available. See {@link deleteVariableHandle}().
5486
5798
  *
5487
- * Variable value can be accessed by using the handle with `readRawByHandle()` and `writeRawByHandle()`
5799
+ * Uses ADS sum command under the hood (better and faster performance).
5800
+ * See [Beckhoff Information System](https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_adsdll2/9007199379576075.html&id=9180083787138954512) for more info.
5801
+ *
5802
+ * @example
5803
+ * ```js
5804
+ * try {
5805
+ * const results = await client.createVariableHandleMulti([
5806
+ * 'GVL_Read.StandardTypes.INT_',
5807
+ * 'GVL_Read.StandardTypes.REAL_'
5808
+ * ]);
5488
5809
  *
5489
- * Uses ADS sum command under the hood (better perfomance)
5810
+ * if(results[0].success) {
5811
+ * console.log(`First handle: ${results[0].handle}`);
5812
+ * } else {
5813
+ * console.log(`Creating first handle failed: ${results[0].errorStr}`);
5814
+ * }
5490
5815
  *
5491
- * @param paths Array of full variable paths in the PLC (such as `GVL_Test.ExampleStruct`)
5816
+ * } catch (err) {
5817
+ * console.log("Error:", err);
5818
+ * }
5819
+ * ```
5820
+ *
5821
+ * @param paths Array of variable paths in the PLC to read (such as `GVL_Test.ExampleStruct`)
5492
5822
  * @param targetOpts Optional target settings that override values in `settings`
5823
+ *
5824
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5493
5825
  */
5494
5826
  async createVariableHandleMulti(paths, targetOpts = {}) {
5495
5827
  if (!this.connection.connected) {
@@ -5580,7 +5912,7 @@ class Client extends events_1.default {
5580
5912
  let dataTypeLength = response.readUInt16LE(pos);
5581
5913
  pos += 2;
5582
5914
  //10..n Data type
5583
- result.dataType = ADS.decodePlcStringBuffer(response.subarray(pos, pos + dataTypeLength + 1));
5915
+ result.dataType = ADS.decodePlcStringBuffer(response.subarray(pos, pos + dataTypeLength + 1), this.metaData.adsSymbolsUseUtf8);
5584
5916
  pos += dataTypeLength + 1;
5585
5917
  results[i].handle = result;
5586
5918
  }
@@ -5594,12 +5926,26 @@ class Client extends events_1.default {
5594
5926
  }
5595
5927
  }
5596
5928
  /**
5597
- * **TODO - DOCUMENTATION ONGOING**
5929
+ * Deletes a variable handle that was previously created
5930
+ * using {@link createVariableHandle}().
5598
5931
  *
5599
- * Deletes a variable handle that was previously created using `createVariableHandle()`
5932
+ * @example
5933
+ * ```js
5934
+ * try {
5935
+ * const handle = createVariableHandle(...);
5936
+ *
5937
+ * //After use, deleting the handle
5938
+ * await client.deleteVariableHandle(handle);
5939
+ *
5940
+ * } catch (err) {
5941
+ * console.log("Error:", err);
5942
+ * }
5943
+ * ```
5600
5944
  *
5601
5945
  * @param handle Variable handle to delete
5602
- * @param targetOpts Optional target settings that override values in `settings` (**NOTE:** If used, no caching is available -> possible performance impact)
5946
+ * @param targetOpts Optional target settings that override values in `settings`
5947
+ *
5948
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5603
5949
  */
5604
5950
  async deleteVariableHandle(handle, targetOpts = {}) {
5605
5951
  if (!this.connection.connected) {
@@ -5637,16 +5983,38 @@ class Client extends events_1.default {
5637
5983
  }
5638
5984
  }
5639
5985
  /**
5640
- * **TODO - DOCUMENTATION ONGOING**
5986
+ * Sends multiple {@link deleteVariableHandle}() commands in one ADS packet.
5987
+ *
5988
+ * Deletes a variable handle that was previously created
5989
+ * using {@link createVariableHandle}().
5990
+ *
5991
+ * Uses ADS sum command under the hood (better and faster performance).
5992
+ * See [Beckhoff Information System](https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_adsdll2/9007199379576075.html&id=9180083787138954512) for more info.
5641
5993
  *
5642
- * Sends multiple deleteVariableHandle() commands in one ADS packet
5994
+ * @example
5995
+ * ```js
5996
+ * try {
5997
+ * const handle1 = createVariableHandle(...);
5998
+ * const handle2 = createVariableHandle(...);
5643
5999
  *
5644
- * Deletes a variable handle that was previously created using `createVariableHandle()`
6000
+ * //After use, deleting the handles
6001
+ * const results = await client.deleteVariableHandleMulti([handle1, handle2]);
6002
+ *
6003
+ * if(results[0].success) {
6004
+ * console.log(`First deleted`);
6005
+ * } else {
6006
+ * console.log(`Deleting first handle failed: ${results[0].errorStr}`);
6007
+ * }
5645
6008
  *
5646
- * Uses ADS sum command under the hood (better performance)
6009
+ * } catch (err) {
6010
+ * console.log("Error:", err);
6011
+ * }
6012
+ * ```
5647
6013
  *
5648
6014
  * @param handles Array of variable handles to delete
5649
6015
  * @param targetOpts Optional target settings that override values in `settings`
6016
+ *
6017
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5650
6018
  */
5651
6019
  async deleteVariableHandleMulti(handles, targetOpts = {}) {
5652
6020
  if (!this.connection.connected) {
@@ -5718,13 +6086,24 @@ class Client extends events_1.default {
5718
6086
  }
5719
6087
  }
5720
6088
  /**
5721
- * **TODO - DOCUMENTATION ONGOING**
6089
+ * Reads raw data from the target system by a previously created variable handle (acquired using {@link createVariableHandle}())
5722
6090
  *
5723
- * Reads raw byte data from the target system by previously created variable handle
6091
+ * @example
6092
+ * ```js
6093
+ * try {
6094
+ * const handle = await client.createVariableHandle('GVL_Read.StandardTypes.INT_'');
6095
+ * const value = await client.readRawByHandle(handle);
6096
+ * await client.deleteVariableHandle(handle);
5724
6097
  *
6098
+ * } catch (err) {
6099
+ * console.log("Error:", err);
6100
+ * }
6101
+ * ```
5725
6102
  * @param handle Variable handle
5726
6103
  * @param size Optional data length to read (bytes) - as default, size in handle is used if available. Uses 0xFFFFFFFF as fallback.
5727
- * @param targetOpts Optional target settings that override values in `settings` (**NOTE:** If used, no caching is available -> possible performance impact)
6104
+ * @param targetOpts Optional target settings that override values in `settings`
6105
+ *
6106
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5728
6107
  */
5729
6108
  async readRawByHandle(handle, size, targetOpts = {}) {
5730
6109
  if (!this.connection.connected) {
@@ -5748,13 +6127,28 @@ class Client extends events_1.default {
5748
6127
  }
5749
6128
  }
5750
6129
  /**
5751
- * **TODO - DOCUMENTATION ONGOING**
6130
+ * Writes raw data to the target system by a previously created variable handle (acquired using {@link createVariableHandle}())
5752
6131
  *
5753
- * Writes raw byte data to the target system by previously created variable handle
6132
+ * @example
6133
+ * ```js
6134
+ * try {
6135
+ * const value = await client.convertToRaw(32767, 'INT');
6136
+ * console.log(value); //<Buffer ff 7f>
6137
+ *
6138
+ * const handle = await client.createVariableHandle('GVL_Read.StandardTypes.INT_');
6139
+ * await client.writeRawByHandle(handle, value);
6140
+ * await client.deleteVariableHandle(handle);
6141
+ *
6142
+ * } catch (err) {
6143
+ * console.log("Error:", err);
6144
+ * }
6145
+ * ```
5754
6146
  *
5755
6147
  * @param handle Variable handle
5756
6148
  * @param value Data to write
5757
- * @param targetOpts Optional target settings that override values in `settings` (**NOTE:** If used, no caching is available -> possible performance impact)
6149
+ * @param targetOpts Optional target settings that override values in `settings`
6150
+ *
6151
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5758
6152
  */
5759
6153
  async writeRawByHandle(handle, value, targetOpts = {}) {
5760
6154
  if (!this.connection.connected) {
@@ -5772,12 +6166,23 @@ class Client extends events_1.default {
5772
6166
  }
5773
6167
  }
5774
6168
  /**
5775
- * **TODO - DOCUMENTATION ONGOING**
6169
+ * Reads raw data from the target system by a symbol object (acquired using `getSymbol()`)
5776
6170
  *
5777
- * Reads raw data by symbol
6171
+ * @example
6172
+ * ```js
6173
+ * try {
6174
+ * const symbol = await client.getSymbol('GVL_Read.StandardTypes.INT_');
6175
+ * const value = await client.readRawBySymbol(symbol);
5778
6176
  *
5779
- * @param symbol Symbol (acquired using `getSymbol()`)
5780
- * @param targetOpts Optional target settings that override values in `settings` (**NOTE:** If used, no caching is available -> possible performance impact)
6177
+ * } catch (err) {
6178
+ * console.log("Error:", err);
6179
+ * }
6180
+ * ```
6181
+ *
6182
+ * @param symbol Symbol
6183
+ * @param targetOpts Optional target settings that override values in `settings`
6184
+ *
6185
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5781
6186
  */
5782
6187
  async readRawBySymbol(symbol, targetOpts = {}) {
5783
6188
  if (!this.connection.connected) {
@@ -5795,13 +6200,27 @@ class Client extends events_1.default {
5795
6200
  }
5796
6201
  }
5797
6202
  /**
5798
- * **TODO - DOCUMENTATION ONGOING**
6203
+ * Writes raw data to the target system by a symbol object (acquired using `getSymbol()`)
6204
+ *
6205
+ * @example
6206
+ * ```js
6207
+ * try {
6208
+ * const value = await client.convertToRaw(32767, 'INT');
6209
+ * console.log(value); //<Buffer ff 7f>
5799
6210
  *
5800
- * Writes raw data by symbol
6211
+ * const symbol = await client.getSymbol('GVL_Read.StandardTypes.INT_');
6212
+ * await client.writeRawBySymbol(symbol, value);
5801
6213
  *
5802
- * @param symbol Symbol (acquired using `getSymbol()`)
6214
+ * } catch (err) {
6215
+ * console.log("Error:", err);
6216
+ * }
6217
+ * ```
6218
+ *
6219
+ * @param symbol Symbol
5803
6220
  * @param value Data to write
5804
6221
  * @param targetOpts Optional target settings that override values in `settings`
6222
+ *
6223
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5805
6224
  */
5806
6225
  async writeRawBySymbol(symbol, value, targetOpts = {}) {
5807
6226
  if (!this.connection.connected) {
@@ -5818,21 +6237,40 @@ class Client extends events_1.default {
5818
6237
  }
5819
6238
  }
5820
6239
  /**
5821
- * **TODO - DOCUMENTATION ONGOING**
6240
+ * Invokes a function block RPC method on the target system.
5822
6241
  *
5823
- * Invokes/calls a function block RPC method from PLC
6242
+ * Returns the return value of the method and outputs (if any).
5824
6243
  *
5825
- * Returns method return value and/or outputs (if any)
6244
+ * NOTE: In the PLC, `{attribute 'TcRpcEnable'}` is required above the `METHOD` definition.
5826
6245
  *
5827
- * For RPC support, the method needs to have `{attribute 'TcRpcEnable'}` attribute above the `METHOD` definition
6246
+ * @example
6247
+ * ```js
6248
+ * try {
6249
+ * const res = await client.invokeRpcMethod('GVL_RPC.RpcBlock', 'Calculator', {
6250
+ * Value1: 1,
6251
+ * Value2: 123
6252
+ * });
6253
+ *
6254
+ * console.log(res);
6255
+ * //{
6256
+ * // returnValue: true,
6257
+ * // outputs: { Sum: 124, Product: 123, Division: 0.008130080997943878 }
6258
+ * //}
5828
6259
  *
5829
- * @param path Full function block instance path in the PLC (such as `GVL_Test.ExampleBlock`)
5830
- * @param method Function block method name to call
5831
- * @param parameters Function block method parameters (inputs) (if any)
6260
+ * } catch (err) {
6261
+ * console.log("Error:", err);
6262
+ * }
6263
+ * ```
6264
+ *
6265
+ * @param path Variable path in the PLC of the function block instance (such as `GVL_Test.ExampleBlock`)
6266
+ * @param method Function block method name
6267
+ * @param parameters Method parameters (inputs) (if any)
5832
6268
  * @param targetOpts Optional target settings that override values in `settings`
5833
6269
  *
5834
6270
  * @template T Typescript data type of the method return value, for example `invokeRpcMethod<number>(...)` or `invokeRpcMethod<ST_TypedStruct>(...)`
5835
6271
  * @template U Typescript data type of the method outputs object, for example `invokeRpcMethod<number, ST_TypedStruct>(...)`
6272
+ *
6273
+ * @throws Throws an error if sending the command fails or if the target responds with an error.
5836
6274
  */
5837
6275
  async invokeRpcMethod(path, method, parameters = {}, targetOpts = {}) {
5838
6276
  if (!this.connection.connected) {
@@ -5867,8 +6305,8 @@ class Client extends events_1.default {
5867
6305
  throw new client_error_1.default(`invokeRpcMethod(): Method ${method} was not found from symbol ${path}. Available RPC methods are: ${dataType.rpcMethods.map(m => m.name).join(', ')}`);
5868
6306
  }
5869
6307
  //Method inputs and outputs
5870
- const inputs = rpcMethod.parameters.filter(p => p.flags === ADS.ADS_RCP_METHOD_PARAM_FLAGS.In);
5871
- const outputs = rpcMethod.parameters.filter(p => p.flags === ADS.ADS_RCP_METHOD_PARAM_FLAGS.Out);
6308
+ const inputs = rpcMethod.parameters.filter(p => (p.flags & ADS.ADS_RCP_METHOD_PARAM_FLAGS.In) === ADS.ADS_RCP_METHOD_PARAM_FLAGS.In);
6309
+ const outputs = rpcMethod.parameters.filter(p => (p.flags & ADS.ADS_RCP_METHOD_PARAM_FLAGS.Out) === ADS.ADS_RCP_METHOD_PARAM_FLAGS.Out);
5872
6310
  //Creating data buffer for inputs
5873
6311
  let inputsData = Buffer.alloc(0);
5874
6312
  for (const input of inputs) {