ads-client 2.0.0-beta.4 → 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 {
@@ -748,11 +752,9 @@ class Client extends events_1.default {
748
752
  this.socketWrite(buffer);
749
753
  }
750
754
  catch (err) {
751
- reject(err);
752
- }
753
- finally {
754
755
  this.amsTcpCallback = undefined;
755
756
  clearTimeout(this.portRegisterTimeoutTimer);
757
+ reject(err);
756
758
  }
757
759
  });
758
760
  }
@@ -878,29 +880,38 @@ class Client extends events_1.default {
878
880
  * @param subscription The subscription object (unused here)
879
881
  */
880
882
  onPlcRuntimeStateChanged(data, subscription) {
881
- const res = {};
883
+ const state = {};
882
884
  let pos = 0;
883
885
  //0..1 ADS state
884
- res.adsState = data.value.readUInt16LE(pos);
885
- res.adsStateStr = ADS.ADS_STATE.toString(res.adsState);
886
+ state.adsState = data.value.readUInt16LE(pos);
887
+ state.adsStateStr = ADS.ADS_STATE.toString(state.adsState);
886
888
  pos += 2;
887
889
  //2..3 Device state
888
- res.deviceState = data.value.readUInt16LE(pos);
890
+ state.deviceState = data.value.readUInt16LE(pos);
889
891
  pos += 2;
890
- //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) {
891
902
  let stateChanged = false;
892
- if (this.metaData.plcRuntimeState === undefined || this.metaData.plcRuntimeState.adsState !== res.adsState) {
893
- 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}`);
894
905
  stateChanged = true;
895
906
  }
896
- if (this.metaData.plcRuntimeState === undefined || this.metaData.plcRuntimeState.deviceState !== res.deviceState) {
897
- 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}`);
898
909
  stateChanged = true;
899
910
  }
900
911
  let oldState = this.metaData.plcRuntimeState !== undefined
901
912
  ? { ...this.metaData.plcRuntimeState }
902
913
  : undefined;
903
- this.metaData.plcRuntimeState = res;
914
+ this.metaData.plcRuntimeState = state;
904
915
  if (stateChanged) {
905
916
  this.emit('plcRuntimeStateChange', this.metaData.plcRuntimeState, oldState);
906
917
  }
@@ -1322,8 +1333,7 @@ class Client extends events_1.default {
1322
1333
  ads.payload.versionBuild = data.readUInt16LE(pos);
1323
1334
  pos += 2;
1324
1335
  //8..24 Device name
1325
- ads.payload.deviceName = ADS.decodePlcStringBuffer(data.subarray(pos, pos + 16));
1326
- ;
1336
+ ads.payload.deviceName = ADS.decodePlcStringBuffer(data.subarray(pos, pos + 16), this.metaData.adsSymbolsUseUtf8);
1327
1337
  }
1328
1338
  break;
1329
1339
  case ADS.ADS_COMMAND.ReadState:
@@ -1877,13 +1887,13 @@ class Client extends events_1.default {
1877
1887
  const commentLength = data.readUInt16LE(pos);
1878
1888
  pos += 2;
1879
1889
  //30.... Symbol name
1880
- symbol.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
1890
+ symbol.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
1881
1891
  pos += nameLength + 1;
1882
1892
  //.. Symbol type
1883
- symbol.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1));
1893
+ symbol.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1), this.metaData.adsSymbolsUseUtf8);
1884
1894
  pos += typeLength + 1;
1885
1895
  //.. Symbol comment
1886
- symbol.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1));
1896
+ symbol.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1), this.metaData.adsSymbolsUseUtf8);
1887
1897
  pos += commentLength + 1;
1888
1898
  //Array information
1889
1899
  symbol.arrayInfo = [];
@@ -1915,10 +1925,10 @@ class Client extends events_1.default {
1915
1925
  const valueLength = data.readUInt8(pos);
1916
1926
  pos += 1;
1917
1927
  //Name
1918
- attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
1928
+ attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
1919
1929
  pos += nameLength + 1;
1920
1930
  //Value
1921
- attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1));
1931
+ attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1), this.metaData.adsSymbolsUseUtf8);
1922
1932
  pos += valueLength + 1;
1923
1933
  symbol.attributes.push(attr);
1924
1934
  }
@@ -1986,13 +1996,13 @@ class Client extends events_1.default {
1986
1996
  const subItemCount = data.readUInt16LE(pos);
1987
1997
  pos += 2;
1988
1998
  //38.. Data type name
1989
- dataType.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
1999
+ dataType.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
1990
2000
  pos += nameLength + 1;
1991
2001
  //.. Data type type
1992
- dataType.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1));
2002
+ dataType.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1), this.metaData.adsSymbolsUseUtf8);
1993
2003
  pos += typeLength + 1;
1994
2004
  //.. Data type comment
1995
- dataType.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1));
2005
+ dataType.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1), this.metaData.adsSymbolsUseUtf8);
1996
2006
  pos += commentLength + 1;
1997
2007
  //Array information
1998
2008
  dataType.arrayInfos = [];
@@ -2076,13 +2086,13 @@ class Client extends events_1.default {
2076
2086
  const parameterCount = data.readUInt16LE(pos);
2077
2087
  pos += 2;
2078
2088
  //56.. Name
2079
- method.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2089
+ method.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2080
2090
  pos += nameLength + 1;
2081
2091
  //.. Return data type
2082
- method.retunDataType = ADS.decodePlcStringBuffer(data.subarray(pos, pos + returnTypeLength + 1));
2092
+ method.retunDataType = ADS.decodePlcStringBuffer(data.subarray(pos, pos + returnTypeLength + 1), this.metaData.adsSymbolsUseUtf8);
2083
2093
  pos += returnTypeLength + 1;
2084
2094
  //.. Comment
2085
- method.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1));
2095
+ method.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1), this.metaData.adsSymbolsUseUtf8);
2086
2096
  pos += commentLength + 1;
2087
2097
  //Parameters
2088
2098
  method.parameters = [];
@@ -2125,13 +2135,13 @@ class Client extends events_1.default {
2125
2135
  const commentLength = data.readUInt16LE(pos);
2126
2136
  pos += 2;
2127
2137
  //38.. Data type name
2128
- param.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2138
+ param.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2129
2139
  pos += nameLength + 1;
2130
2140
  //.. Data type type
2131
- param.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1));
2141
+ param.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1), this.metaData.adsSymbolsUseUtf8);
2132
2142
  pos += typeLength + 1;
2133
2143
  //.. Data type comment
2134
- param.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1));
2144
+ param.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1), this.metaData.adsSymbolsUseUtf8);
2135
2145
  pos += commentLength + 1;
2136
2146
  //Attributes
2137
2147
  param.attributes = [];
@@ -2148,18 +2158,19 @@ class Client extends events_1.default {
2148
2158
  const valueLength = data.readUInt8(pos);
2149
2159
  pos += 1;
2150
2160
  //Name
2151
- attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2161
+ attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2152
2162
  pos += nameLength + 1;
2153
2163
  //Value
2154
- attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1));
2164
+ attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1), this.metaData.adsSymbolsUseUtf8);
2155
2165
  pos += valueLength + 1;
2156
- method.attributes.push(attr);
2166
+ param.attributes.push(attr);
2157
2167
  }
2158
2168
  }
2159
2169
  if (pos - beginPosition > entryLength) {
2160
2170
  //There is some additional data left
2161
2171
  param.reserved2 = data.subarray(pos);
2162
2172
  }
2173
+ pos = beginPosition + entryLength;
2163
2174
  method.parameters.push(param);
2164
2175
  }
2165
2176
  //Attributes
@@ -2177,10 +2188,10 @@ class Client extends events_1.default {
2177
2188
  const valueLength = data.readUInt8(pos);
2178
2189
  pos += 1;
2179
2190
  //Name
2180
- attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2191
+ attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2181
2192
  pos += nameLength + 1;
2182
2193
  //Value
2183
- attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1));
2194
+ attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1), this.metaData.adsSymbolsUseUtf8);
2184
2195
  pos += valueLength + 1;
2185
2196
  method.attributes.push(attr);
2186
2197
  }
@@ -2205,18 +2216,19 @@ class Client extends events_1.default {
2205
2216
  const valueLength = data.readUInt8(pos);
2206
2217
  pos += 1;
2207
2218
  //Name
2208
- attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2219
+ attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2209
2220
  pos += nameLength + 1;
2210
2221
  //Value
2211
- attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1));
2222
+ attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1), this.metaData.adsSymbolsUseUtf8);
2212
2223
  pos += valueLength + 1;
2213
2224
  dataType.attributes.push(attr);
2214
2225
  }
2215
2226
  }
2216
2227
  //If flag EnumInfos set
2217
2228
  dataType.enumInfos = [];
2229
+ let enumInfoCount = 0;
2218
2230
  if ((dataType.flags & ADS.ADS_DATA_TYPE_FLAGS.EnumInfos) === ADS.ADS_DATA_TYPE_FLAGS.EnumInfos) {
2219
- const enumInfoCount = data.readUInt16LE(pos);
2231
+ enumInfoCount = data.readUInt16LE(pos);
2220
2232
  pos += 2;
2221
2233
  for (let i = 0; i < enumInfoCount; i++) {
2222
2234
  let enumInfo = {};
@@ -2224,7 +2236,7 @@ class Client extends events_1.default {
2224
2236
  const nameLength = data.readUInt8(pos);
2225
2237
  pos += 1;
2226
2238
  //Enumeration name
2227
- enumInfo.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2239
+ enumInfo.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2228
2240
  pos += nameLength + 1;
2229
2241
  //Enumeration value
2230
2242
  enumInfo.value = data.subarray(pos, pos + dataType.size);
@@ -2251,9 +2263,43 @@ class Client extends events_1.default {
2251
2263
  }
2252
2264
  //If flag ExtendedEnumInfos set
2253
2265
  if ((dataType.flags & ADS.ADS_DATA_TYPE_FLAGS.ExtendedEnumInfos) === ADS.ADS_DATA_TYPE_FLAGS.ExtendedEnumInfos) {
2254
- //TODO: this is not working now
2255
- //Things probably break now
2256
- 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
+ }
2257
2303
  }
2258
2304
  //If flag SoftwareProtectionLevels set
2259
2305
  if ((dataType.flags & ADS.ADS_DATA_TYPE_FLAGS.SoftwareProtectionLevels) === ADS.ADS_DATA_TYPE_FLAGS.SoftwareProtectionLevels) {
@@ -2510,7 +2556,7 @@ class Client extends events_1.default {
2510
2556
  */
2511
2557
  convertBufferToPrimitiveType(buffer, dataType) {
2512
2558
  if (dataType.adsDataType === ADS.ADS_DATA_TYPES.ADST_STRING) {
2513
- return ADS.decodePlcStringBuffer(buffer);
2559
+ return ADS.decodePlcStringBuffer(buffer); //TODO: If symbol has {attribute 'TcEncoding':='UTF-8'}
2514
2560
  }
2515
2561
  else if (dataType.adsDataType === ADS.ADS_DATA_TYPES.ADST_WSTRING) {
2516
2562
  return ADS.decodePlcWstringBuffer(buffer);
@@ -3015,6 +3061,8 @@ class Client extends events_1.default {
3015
3061
  * }
3016
3062
  * ```
3017
3063
  *
3064
+ * @param command The ADS command to send
3065
+ *
3018
3066
  * @template T In Typescript, the type of the ADS response. If omitted, generic {@link AdsResponse} type is used.
3019
3067
  *
3020
3068
  * @throws Throws an error if sending the command fails or if target responds with an error
@@ -3100,6 +3148,82 @@ class Client extends events_1.default {
3100
3148
  }
3101
3149
  });
3102
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
+ }
3103
3227
  /**
3104
3228
  * Sends an ADS `WriteControl` command to the target.
3105
3229
  *
@@ -3191,6 +3315,8 @@ class Client extends events_1.default {
3191
3315
  const state = await this.readPlcRuntimeState(targetOpts);
3192
3316
  await this.writeControl("Run", state.deviceState, undefined, targetOpts);
3193
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);
3194
3320
  }
3195
3321
  catch (err) {
3196
3322
  this.debug(`startPlc(): Starting PLC runtime at ${this.targetToString(targetOpts)} failed: %o`, err);
@@ -3223,6 +3349,8 @@ class Client extends events_1.default {
3223
3349
  const state = await this.readPlcRuntimeState(targetOpts);
3224
3350
  await this.writeControl("Reset", state.deviceState, undefined, targetOpts);
3225
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);
3226
3354
  }
3227
3355
  catch (err) {
3228
3356
  this.debug(`resetPlc(): Resetting PLC runtime at ${this.targetToString(targetOpts)} failed: %o`, err);
@@ -3255,6 +3383,8 @@ class Client extends events_1.default {
3255
3383
  const state = await this.readPlcRuntimeState(targetOpts);
3256
3384
  await this.writeControl("Stop", state.deviceState, undefined, targetOpts);
3257
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);
3258
3388
  }
3259
3389
  catch (err) {
3260
3390
  this.debug(`stopPlc(): Stopping PLC runtime at ${this.targetToString(targetOpts)} failed: %o`, err);
@@ -3602,8 +3732,8 @@ class Client extends events_1.default {
3602
3732
  });
3603
3733
  this.debug(`readPlcRuntimeState(): Runtime state read successfully. State is %o`, res.ads.payload);
3604
3734
  if (!targetOpts.adsPort && !targetOpts.amsNetId) {
3605
- //Target is not overridden -> save to metadata
3606
- 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);
3607
3737
  }
3608
3738
  return res.ads.payload;
3609
3739
  }
@@ -4096,36 +4226,90 @@ class Client extends events_1.default {
4096
4226
  data.writeUInt32LE(0, pos);
4097
4227
  pos += 4;
4098
4228
  //8..11 Read data length
4099
- 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);
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
4100
4249
  pos += 4;
4250
+ const fallback = {
4251
+ adsCommand: ADS.ADS_COMMAND.Read,
4252
+ targetAmsNetId: targetOpts.amsNetId,
4253
+ targetAdsPort: targetOpts.adsPort,
4254
+ payload: fbData
4255
+ };
4101
4256
  try {
4102
- const res = await this.sendAdsCommand({
4103
- adsCommand: ADS.ADS_COMMAND.Read,
4104
- targetAmsNetId: targetOpts.amsNetId,
4105
- targetAdsPort: targetOpts.adsPort,
4106
- payload: data
4107
- });
4257
+ const { response: res, fallbackUsed } = await this.sendAdsCommandWithFallback(command, fallback);
4258
+ this.debug(`readPlcUploadInfo(): Upload info read - fallback was needed: ${fallbackUsed}`);
4108
4259
  const uploadInfo = {};
4109
4260
  let pos = 0;
4110
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
+ }
4111
4277
  //0..3 Symbol count
4112
4278
  uploadInfo.symbolCount = response.readUInt32LE(pos);
4113
4279
  pos += 4;
4114
4280
  //4..7 Symbol length
4115
4281
  uploadInfo.symbolLength = response.readUInt32LE(pos);
4116
4282
  pos += 4;
4117
- //8..11 Data type count
4118
- uploadInfo.dataTypeCount = response.readUInt32LE(pos);
4119
- pos += 4;
4120
- //12..15 Data type length
4121
- uploadInfo.dataTypeLength = response.readUInt32LE(pos);
4122
- pos += 4;
4123
- //16..19 Extra count
4124
- uploadInfo.extraCount = response.readUInt32LE(pos);
4125
- pos += 4;
4126
- //20..23 Extra length
4127
- uploadInfo.extraLength = response.readUInt32LE(pos);
4128
- 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;
4129
4313
  if (!targetOpts.adsPort && !targetOpts.amsNetId) {
4130
4314
  //Target is not overridden -> save to metadata
4131
4315
  this.metaData.plcUploadInfo = uploadInfo;
@@ -4314,7 +4498,7 @@ class Client extends events_1.default {
4314
4498
  data.writeUInt32LE(0, pos);
4315
4499
  pos += 4;
4316
4500
  //8..11 Read data length
4317
- 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)
4318
4502
  pos += 4;
4319
4503
  try {
4320
4504
  const res = await this.sendAdsCommand({
@@ -4826,11 +5010,11 @@ class Client extends events_1.default {
4826
5010
  *
4827
5011
  * //Writing a POINTER value (Note the dereference operator ^)
4828
5012
  * const ptrValue = ...
4829
- * await client.writeRawByPath('GVL_Read.ComplexTypes.POINTER_^', ptrValue);
5013
+ * await client.writeRawByPath('GVL_Write.ComplexTypes.POINTER_^', ptrValue);
4830
5014
  *
4831
5015
  * //Writing a REFERENCE value
4832
5016
  * const refValue = ...
4833
- * await client.readRawByPath('GVL_Read.ComplexTypes.REFERENCE_');
5017
+ * await client.writeRawByPath('GVL_Write.ComplexTypes.REFERENCE_');
4834
5018
  *
4835
5019
  * } catch (err) {
4836
5020
  * console.log("Error:", err);
@@ -5204,11 +5388,9 @@ class Client extends events_1.default {
5204
5388
  * @example
5205
5389
  * ```js
5206
5390
  * try {
5207
- * const value = {
5208
- * example: true
5209
- * };
5391
+ * const res = await client.writeValue('GVL_Write.StandardTypes.INT_', 32767);
5392
+ * console.log('Value written:', res.value);
5210
5393
  *
5211
- * const res = await client.writeValue('GVL_Read.StandardTypes.INT_', value);
5212
5394
  * } catch (err) {
5213
5395
  * console.log("Error:", err);
5214
5396
  * }
@@ -5592,7 +5774,7 @@ class Client extends events_1.default {
5592
5774
  let dataTypeLength = response.readUInt16LE(pos);
5593
5775
  pos += 2;
5594
5776
  //10..n Data type
5595
- result.dataType = ADS.decodePlcStringBuffer(response.subarray(pos, pos + dataTypeLength + 1));
5777
+ result.dataType = ADS.decodePlcStringBuffer(response.subarray(pos, pos + dataTypeLength + 1), this.metaData.adsSymbolsUseUtf8);
5596
5778
  this.debug(`createVariableHandle(): Variable handle created to ${path}`);
5597
5779
  return result;
5598
5780
  }
@@ -5730,7 +5912,7 @@ class Client extends events_1.default {
5730
5912
  let dataTypeLength = response.readUInt16LE(pos);
5731
5913
  pos += 2;
5732
5914
  //10..n Data type
5733
- result.dataType = ADS.decodePlcStringBuffer(response.subarray(pos, pos + dataTypeLength + 1));
5915
+ result.dataType = ADS.decodePlcStringBuffer(response.subarray(pos, pos + dataTypeLength + 1), this.metaData.adsSymbolsUseUtf8);
5734
5916
  pos += dataTypeLength + 1;
5735
5917
  results[i].handle = result;
5736
5918
  }
@@ -6123,8 +6305,8 @@ class Client extends events_1.default {
6123
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(', ')}`);
6124
6306
  }
6125
6307
  //Method inputs and outputs
6126
- const inputs = rpcMethod.parameters.filter(p => p.flags === ADS.ADS_RCP_METHOD_PARAM_FLAGS.In);
6127
- 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);
6128
6310
  //Creating data buffer for inputs
6129
6311
  let inputsData = Buffer.alloc(0);
6130
6312
  for (const input of inputs) {