ads-client 2.0.0-beta.4 → 2.0.0-beta.6

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.
@@ -39,13 +39,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
39
39
  }) : function(o, v) {
40
40
  o["default"] = v;
41
41
  });
42
- var __importStar = (this && this.__importStar) || function (mod) {
43
- if (mod && mod.__esModule) return mod;
44
- var result = {};
45
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
46
- __setModuleDefault(result, mod);
47
- return result;
48
- };
42
+ var __importStar = (this && this.__importStar) || (function () {
43
+ var ownKeys = function(o) {
44
+ ownKeys = Object.getOwnPropertyNames || function (o) {
45
+ var ar = [];
46
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
47
+ return ar;
48
+ };
49
+ return ownKeys(o);
50
+ };
51
+ return function (mod) {
52
+ if (mod && mod.__esModule) return mod;
53
+ var result = {};
54
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
55
+ __setModuleDefault(result, mod);
56
+ return result;
57
+ };
58
+ })();
49
59
  var __importDefault = (this && this.__importDefault) || function (mod) {
50
60
  return (mod && mod.__esModule) ? mod : { "default": mod };
51
61
  };
@@ -148,6 +158,7 @@ class Client extends events_1.default {
148
158
  plcSymbols: {},
149
159
  allPlcDataTypesCached: false,
150
160
  plcDataTypes: {},
161
+ adsSymbolsUseUtf8: false
151
162
  };
152
163
  /**
153
164
  * Buffer for received data.
@@ -233,7 +244,8 @@ class Client extends events_1.default {
233
244
  allowHalfOpen: false,
234
245
  rawClient: false,
235
246
  disableCaching: false,
236
- deleteUnknownSubscriptions: true
247
+ deleteUnknownSubscriptions: true,
248
+ forceUtf8ForAdsSymbols: false
237
249
  };
238
250
  /**
239
251
  * Active connection information.
@@ -419,6 +431,8 @@ class Client extends events_1.default {
419
431
  //When socket errors from now on, we will close the connection
420
432
  this.socketErrorHandler = this.onSocketError.bind(this);
421
433
  socket.on("error", this.socketErrorHandler);
434
+ //Force ADS UTF-8 (note: this is also set later at readPlcUploadInfo() if target is a PLC)
435
+ this.metaData.adsSymbolsUseUtf8 = this.settings.forceUtf8ForAdsSymbols;
422
436
  //If rawClient setting is true, we are done here (just a connection is enough)
423
437
  if (this.settings.rawClient !== true) {
424
438
  try {
@@ -748,11 +762,9 @@ class Client extends events_1.default {
748
762
  this.socketWrite(buffer);
749
763
  }
750
764
  catch (err) {
751
- reject(err);
752
- }
753
- finally {
754
765
  this.amsTcpCallback = undefined;
755
766
  clearTimeout(this.portRegisterTimeoutTimer);
767
+ reject(err);
756
768
  }
757
769
  });
758
770
  }
@@ -878,29 +890,38 @@ class Client extends events_1.default {
878
890
  * @param subscription The subscription object (unused here)
879
891
  */
880
892
  onPlcRuntimeStateChanged(data, subscription) {
881
- const res = {};
893
+ const state = {};
882
894
  let pos = 0;
883
895
  //0..1 ADS state
884
- res.adsState = data.value.readUInt16LE(pos);
885
- res.adsStateStr = ADS.ADS_STATE.toString(res.adsState);
896
+ state.adsState = data.value.readUInt16LE(pos);
897
+ state.adsStateStr = ADS.ADS_STATE.toString(state.adsState);
886
898
  pos += 2;
887
899
  //2..3 Device state
888
- res.deviceState = data.value.readUInt16LE(pos);
900
+ state.deviceState = data.value.readUInt16LE(pos);
889
901
  pos += 2;
890
- //Checking if PLC runtime state has changed
902
+ this.handlePlcRuntimeStateChange(state);
903
+ }
904
+ /**
905
+ * Checks if PLC runtime state has changed, and if so, emits an event.
906
+ *
907
+ * Call from `onPlcRuntimeStateChanged()` and `readPlcRuntimeState()`.
908
+ *
909
+ * @param state Active PLC runtime state
910
+ */
911
+ handlePlcRuntimeStateChange(state) {
891
912
  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}`);
913
+ if (this.metaData.plcRuntimeState === undefined || this.metaData.plcRuntimeState.adsState !== state.adsState) {
914
+ this.debug(`handlePlcRuntimeStateChange(): PLC runtime state (adsState) changed from ${this.metaData.plcRuntimeState === undefined ? 'UNKNOWN' : this.metaData.plcRuntimeState.adsStateStr} to ${state.adsStateStr}`);
894
915
  stateChanged = true;
895
916
  }
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}`);
917
+ if (this.metaData.plcRuntimeState === undefined || this.metaData.plcRuntimeState.deviceState !== state.deviceState) {
918
+ this.debug(`handlePlcRuntimeStateChange(): PLC runtime state (deviceState) changed from ${this.metaData.plcRuntimeState === undefined ? 'UNKNOWN' : this.metaData.plcRuntimeState.deviceState} to ${state.deviceState}`);
898
919
  stateChanged = true;
899
920
  }
900
921
  let oldState = this.metaData.plcRuntimeState !== undefined
901
922
  ? { ...this.metaData.plcRuntimeState }
902
923
  : undefined;
903
- this.metaData.plcRuntimeState = res;
924
+ this.metaData.plcRuntimeState = state;
904
925
  if (stateChanged) {
905
926
  this.emit('plcRuntimeStateChange', this.metaData.plcRuntimeState, oldState);
906
927
  }
@@ -1322,8 +1343,7 @@ class Client extends events_1.default {
1322
1343
  ads.payload.versionBuild = data.readUInt16LE(pos);
1323
1344
  pos += 2;
1324
1345
  //8..24 Device name
1325
- ads.payload.deviceName = ADS.decodePlcStringBuffer(data.subarray(pos, pos + 16));
1326
- ;
1346
+ ads.payload.deviceName = ADS.decodePlcStringBuffer(data.subarray(pos, pos + 16), this.metaData.adsSymbolsUseUtf8);
1327
1347
  }
1328
1348
  break;
1329
1349
  case ADS.ADS_COMMAND.ReadState:
@@ -1698,7 +1718,7 @@ class Client extends events_1.default {
1698
1718
  };
1699
1719
  if (this.symbol) {
1700
1720
  const dataType = await clientRef.getDataType(this.symbol.type, this.targetOpts);
1701
- result.value = clientRef.convertBufferToObject(data, dataType);
1721
+ result.value = clientRef.convertBufferToObject(data, dataType, this.symbol.attributes);
1702
1722
  }
1703
1723
  this.latestData = result;
1704
1724
  return result;
@@ -1877,13 +1897,13 @@ class Client extends events_1.default {
1877
1897
  const commentLength = data.readUInt16LE(pos);
1878
1898
  pos += 2;
1879
1899
  //30.... Symbol name
1880
- symbol.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
1900
+ symbol.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
1881
1901
  pos += nameLength + 1;
1882
1902
  //.. Symbol type
1883
- symbol.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1));
1903
+ symbol.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1), this.metaData.adsSymbolsUseUtf8);
1884
1904
  pos += typeLength + 1;
1885
1905
  //.. Symbol comment
1886
- symbol.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1));
1906
+ symbol.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1), this.metaData.adsSymbolsUseUtf8);
1887
1907
  pos += commentLength + 1;
1888
1908
  //Array information
1889
1909
  symbol.arrayInfo = [];
@@ -1915,10 +1935,10 @@ class Client extends events_1.default {
1915
1935
  const valueLength = data.readUInt8(pos);
1916
1936
  pos += 1;
1917
1937
  //Name
1918
- attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
1938
+ attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
1919
1939
  pos += nameLength + 1;
1920
1940
  //Value
1921
- attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1));
1941
+ attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1), this.metaData.adsSymbolsUseUtf8);
1922
1942
  pos += valueLength + 1;
1923
1943
  symbol.attributes.push(attr);
1924
1944
  }
@@ -1986,13 +2006,13 @@ class Client extends events_1.default {
1986
2006
  const subItemCount = data.readUInt16LE(pos);
1987
2007
  pos += 2;
1988
2008
  //38.. Data type name
1989
- dataType.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2009
+ dataType.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
1990
2010
  pos += nameLength + 1;
1991
2011
  //.. Data type type
1992
- dataType.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1));
2012
+ dataType.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1), this.metaData.adsSymbolsUseUtf8);
1993
2013
  pos += typeLength + 1;
1994
2014
  //.. Data type comment
1995
- dataType.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1));
2015
+ dataType.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1), this.metaData.adsSymbolsUseUtf8);
1996
2016
  pos += commentLength + 1;
1997
2017
  //Array information
1998
2018
  dataType.arrayInfos = [];
@@ -2076,13 +2096,13 @@ class Client extends events_1.default {
2076
2096
  const parameterCount = data.readUInt16LE(pos);
2077
2097
  pos += 2;
2078
2098
  //56.. Name
2079
- method.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2099
+ method.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2080
2100
  pos += nameLength + 1;
2081
2101
  //.. Return data type
2082
- method.retunDataType = ADS.decodePlcStringBuffer(data.subarray(pos, pos + returnTypeLength + 1));
2102
+ method.retunDataType = ADS.decodePlcStringBuffer(data.subarray(pos, pos + returnTypeLength + 1), this.metaData.adsSymbolsUseUtf8);
2083
2103
  pos += returnTypeLength + 1;
2084
2104
  //.. Comment
2085
- method.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1));
2105
+ method.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1), this.metaData.adsSymbolsUseUtf8);
2086
2106
  pos += commentLength + 1;
2087
2107
  //Parameters
2088
2108
  method.parameters = [];
@@ -2125,13 +2145,13 @@ class Client extends events_1.default {
2125
2145
  const commentLength = data.readUInt16LE(pos);
2126
2146
  pos += 2;
2127
2147
  //38.. Data type name
2128
- param.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2148
+ param.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2129
2149
  pos += nameLength + 1;
2130
2150
  //.. Data type type
2131
- param.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1));
2151
+ param.type = ADS.decodePlcStringBuffer(data.subarray(pos, pos + typeLength + 1), this.metaData.adsSymbolsUseUtf8);
2132
2152
  pos += typeLength + 1;
2133
2153
  //.. Data type comment
2134
- param.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1));
2154
+ param.comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1), this.metaData.adsSymbolsUseUtf8);
2135
2155
  pos += commentLength + 1;
2136
2156
  //Attributes
2137
2157
  param.attributes = [];
@@ -2148,18 +2168,19 @@ class Client extends events_1.default {
2148
2168
  const valueLength = data.readUInt8(pos);
2149
2169
  pos += 1;
2150
2170
  //Name
2151
- attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2171
+ attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2152
2172
  pos += nameLength + 1;
2153
2173
  //Value
2154
- attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1));
2174
+ attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1), this.metaData.adsSymbolsUseUtf8);
2155
2175
  pos += valueLength + 1;
2156
- method.attributes.push(attr);
2176
+ param.attributes.push(attr);
2157
2177
  }
2158
2178
  }
2159
2179
  if (pos - beginPosition > entryLength) {
2160
2180
  //There is some additional data left
2161
2181
  param.reserved2 = data.subarray(pos);
2162
2182
  }
2183
+ pos = beginPosition + entryLength;
2163
2184
  method.parameters.push(param);
2164
2185
  }
2165
2186
  //Attributes
@@ -2177,10 +2198,10 @@ class Client extends events_1.default {
2177
2198
  const valueLength = data.readUInt8(pos);
2178
2199
  pos += 1;
2179
2200
  //Name
2180
- attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2201
+ attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2181
2202
  pos += nameLength + 1;
2182
2203
  //Value
2183
- attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1));
2204
+ attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1), this.metaData.adsSymbolsUseUtf8);
2184
2205
  pos += valueLength + 1;
2185
2206
  method.attributes.push(attr);
2186
2207
  }
@@ -2205,18 +2226,19 @@ class Client extends events_1.default {
2205
2226
  const valueLength = data.readUInt8(pos);
2206
2227
  pos += 1;
2207
2228
  //Name
2208
- attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2229
+ attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2209
2230
  pos += nameLength + 1;
2210
2231
  //Value
2211
- attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1));
2232
+ attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1), this.metaData.adsSymbolsUseUtf8);
2212
2233
  pos += valueLength + 1;
2213
2234
  dataType.attributes.push(attr);
2214
2235
  }
2215
2236
  }
2216
2237
  //If flag EnumInfos set
2217
2238
  dataType.enumInfos = [];
2239
+ let enumInfoCount = 0;
2218
2240
  if ((dataType.flags & ADS.ADS_DATA_TYPE_FLAGS.EnumInfos) === ADS.ADS_DATA_TYPE_FLAGS.EnumInfos) {
2219
- const enumInfoCount = data.readUInt16LE(pos);
2241
+ enumInfoCount = data.readUInt16LE(pos);
2220
2242
  pos += 2;
2221
2243
  for (let i = 0; i < enumInfoCount; i++) {
2222
2244
  let enumInfo = {};
@@ -2224,7 +2246,7 @@ class Client extends events_1.default {
2224
2246
  const nameLength = data.readUInt8(pos);
2225
2247
  pos += 1;
2226
2248
  //Enumeration name
2227
- enumInfo.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1));
2249
+ enumInfo.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2228
2250
  pos += nameLength + 1;
2229
2251
  //Enumeration value
2230
2252
  enumInfo.value = data.subarray(pos, pos + dataType.size);
@@ -2251,9 +2273,42 @@ class Client extends events_1.default {
2251
2273
  }
2252
2274
  //If flag ExtendedEnumInfos set
2253
2275
  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`);
2276
+ for (let i = 0; i < enumInfoCount; i++) {
2277
+ let beginPosition = pos;
2278
+ //Total entry length (bytes)
2279
+ const entryLength = data.readUInt16LE(pos);
2280
+ pos += 2;
2281
+ //Comment length
2282
+ const commentLength = data.readUInt8(pos);
2283
+ pos += 1;
2284
+ //Attribute count
2285
+ const attributeCount = data.readUInt8(pos);
2286
+ pos += 1;
2287
+ //Enumeration comment
2288
+ dataType.enumInfos[i].comment = ADS.decodePlcStringBuffer(data.subarray(pos, pos + commentLength + 1), this.metaData.adsSymbolsUseUtf8);
2289
+ pos += commentLength + 1;
2290
+ //Enumeration attributes
2291
+ dataType.enumInfos[i].attributes = [];
2292
+ //Attributes
2293
+ for (let i = 0; i < attributeCount; i++) {
2294
+ const attr = {};
2295
+ //Name length
2296
+ const nameLength = data.readUInt8(pos);
2297
+ pos += 1;
2298
+ //Value length
2299
+ const valueLength = data.readUInt8(pos);
2300
+ pos += 1;
2301
+ //Name
2302
+ attr.name = ADS.decodePlcStringBuffer(data.subarray(pos, pos + nameLength + 1), this.metaData.adsSymbolsUseUtf8);
2303
+ pos += nameLength + 1;
2304
+ //Value
2305
+ attr.value = ADS.decodePlcStringBuffer(data.subarray(pos, pos + valueLength + 1), this.metaData.adsSymbolsUseUtf8);
2306
+ pos += valueLength + 1;
2307
+ dataType.enumInfos[i].attributes.push(attr);
2308
+ }
2309
+ //If there are some reserved bytes -> skip
2310
+ pos = beginPosition + entryLength;
2311
+ }
2257
2312
  }
2258
2313
  //If flag SoftwareProtectionLevels set
2259
2314
  if ((dataType.flags & ADS.ADS_DATA_TYPE_FLAGS.SoftwareProtectionLevels) === ADS.ADS_DATA_TYPE_FLAGS.SoftwareProtectionLevels) {
@@ -2437,6 +2492,8 @@ class Client extends events_1.default {
2437
2492
  subItemType.hashValue = dataType.subItems[i].hashValue;
2438
2493
  subItemType.offset = dataType.subItems[i].offset;
2439
2494
  subItemType.comment = dataType.subItems[i].comment;
2495
+ //Let's keep attributes from parent as well as subitem's own attributes
2496
+ subItemType.attributes = [...subItemType.attributes, ...dataType.subItems[i].attributes];
2440
2497
  }
2441
2498
  builtType.subItems.push(subItemType);
2442
2499
  }
@@ -2507,10 +2564,11 @@ class Client extends events_1.default {
2507
2564
  *
2508
2565
  * @param buffer The raw data to convert
2509
2566
  * @param dataType Target data type
2567
+ * @param attributes Additional attributes of the symbol or data type to use for conversion
2510
2568
  */
2511
- convertBufferToPrimitiveType(buffer, dataType) {
2569
+ convertBufferToPrimitiveType(buffer, dataType, attributes) {
2512
2570
  if (dataType.adsDataType === ADS.ADS_DATA_TYPES.ADST_STRING) {
2513
- return ADS.decodePlcStringBuffer(buffer);
2571
+ return ADS.decodePlcStringBuffer(buffer, this.getStringDataTypeEncoding(dataType, attributes) === 'UTF-8');
2514
2572
  }
2515
2573
  else if (dataType.adsDataType === ADS.ADS_DATA_TYPES.ADST_WSTRING) {
2516
2574
  return ADS.decodePlcWstringBuffer(buffer);
@@ -2519,6 +2577,16 @@ class Client extends events_1.default {
2519
2577
  return ADS.BASE_DATA_TYPES.fromBuffer(dataType.type, buffer, this.settings.convertDatesToJavascript);
2520
2578
  }
2521
2579
  }
2580
+ /**
2581
+ * Extracts TcEncoding attribute from the data datatype attributes or additional attributes
2582
+ *
2583
+ * @param dataType The data type
2584
+ * @param attributes Additional attributes of the symbol or data type
2585
+ */
2586
+ getStringDataTypeEncoding(dataType, attributes) {
2587
+ return dataType.attributes.find((attr) => attr.name === 'TcEncoding')?.value
2588
+ || attributes?.find((attr) => attr.name === 'TcEncoding')?.value;
2589
+ }
2522
2590
  /**
2523
2591
  * Converts raw data to Javascript object.
2524
2592
  *
@@ -2526,9 +2594,10 @@ class Client extends events_1.default {
2526
2594
  *
2527
2595
  * @param data The raw data to convert
2528
2596
  * @param dataType Target data type
2597
+ * @param attributes Additional attributes of the symbol or data type used for conversion
2529
2598
  * @param isArrayItem If `true`, this is an array item (default: `false`)
2530
2599
  */
2531
- convertBufferToObject(data, dataType, isArrayItem = false) {
2600
+ convertBufferToObject(data, dataType, attributes, isArrayItem = false) {
2532
2601
  let result;
2533
2602
  if ((dataType.arrayInfos.length === 0 || isArrayItem) && dataType.subItems.length > 0) {
2534
2603
  //Struct or array item
@@ -2550,7 +2619,7 @@ class Client extends events_1.default {
2550
2619
  }
2551
2620
  else {
2552
2621
  //Final dimension
2553
- temp.push(this.convertBufferToObject(data, dataType, true));
2622
+ temp.push(this.convertBufferToObject(data, dataType, undefined, true));
2554
2623
  data = data.subarray(dataType.size);
2555
2624
  }
2556
2625
  }
@@ -2596,7 +2665,7 @@ class Client extends events_1.default {
2596
2665
  }
2597
2666
  else {
2598
2667
  //Primitive type
2599
- result = this.convertBufferToPrimitiveType(data.subarray(dataType.offset, dataType.offset + dataType.size), dataType);
2668
+ result = this.convertBufferToPrimitiveType(data.subarray(dataType.offset, dataType.offset + dataType.size), dataType, attributes);
2600
2669
  }
2601
2670
  return result;
2602
2671
  }
@@ -2605,10 +2674,11 @@ class Client extends events_1.default {
2605
2674
  *
2606
2675
  * @param value Javascript object to convert
2607
2676
  * @param dataType Target data type
2677
+ * @param attributes Additional attributes of the symbol or data type used for conversion
2608
2678
  * @param objectPath Object path that is passed forward when calling recursively. This is used for error reporting if a property is missing
2609
2679
  * @param isArrayItem If `true`, this is an array item (default: `false`)
2610
2680
  */
2611
- convertObjectToBuffer(value, dataType, objectPath = '', isArrayItem = false) {
2681
+ convertObjectToBuffer(value, dataType, attributes, objectPath = '', isArrayItem = false) {
2612
2682
  let rawValue = Buffer.alloc(0);
2613
2683
  const pathInfo = objectPath === '' && dataType.name === ''
2614
2684
  ? ''
@@ -2642,7 +2712,7 @@ class Client extends events_1.default {
2642
2712
  };
2643
2713
  }
2644
2714
  //Converting subItem
2645
- let res = this.convertObjectToBuffer(value[key], subItem, `${pathInfo}.${subItem.name}`);
2715
+ let res = this.convertObjectToBuffer(value[key], subItem, undefined, `${pathInfo}.${subItem.name}`);
2646
2716
  if (res.missingProperty) {
2647
2717
  //We have a missing property somewhere -> quit
2648
2718
  return res;
@@ -2697,7 +2767,7 @@ class Client extends events_1.default {
2697
2767
  return `${pathInfo}${path}[${child}]`;
2698
2768
  }
2699
2769
  //Converting array item
2700
- let res = this.convertObjectToBuffer(value[child], dataType, `${pathInfo}.${path}[${child}]`, true);
2770
+ let res = this.convertObjectToBuffer(value[child], dataType, undefined, `${pathInfo}.${path}[${child}]`, true);
2701
2771
  if (res.missingProperty) {
2702
2772
  //We have a missing property somewhere -> quit
2703
2773
  return res.missingProperty;
@@ -2748,11 +2818,11 @@ class Client extends events_1.default {
2748
2818
  else {
2749
2819
  throw new client_error_1.default(`Provided value ${JSON.stringify(value)}${pathInfo} is not valid value for this ENUM. Allowed values are a number and any of the following: ${allowedEnumValuesStr}`);
2750
2820
  }
2751
- this.convertPrimitiveTypeToBuffer(enumEntry.value, dataType, rawValue);
2821
+ this.convertPrimitiveTypeToBuffer(enumEntry.value, dataType, rawValue, attributes);
2752
2822
  }
2753
2823
  else if (ADS.BASE_DATA_TYPES.isKnownType(dataType.type)) {
2754
2824
  rawValue = Buffer.alloc(dataType.size);
2755
- this.convertPrimitiveTypeToBuffer(value, dataType, rawValue);
2825
+ this.convertPrimitiveTypeToBuffer(value, dataType, rawValue, attributes);
2756
2826
  }
2757
2827
  else if (dataType.adsDataType === ADS.ADS_DATA_TYPES.ADST_BIGTYPE && dataType.subItems.length === 0) {
2758
2828
  //If size is 0, then it's empty struct -> rawValue is already OK
@@ -2787,15 +2857,17 @@ class Client extends events_1.default {
2787
2857
  * @param value Javascript object to convert
2788
2858
  * @param dataType Data type
2789
2859
  * @param buffer Reference to Buffer object where to write the raw value
2860
+ * @param attributes Additional attributes of the symbol or data type used for conversion
2790
2861
  */
2791
- convertPrimitiveTypeToBuffer(value, dataType, buffer) {
2862
+ convertPrimitiveTypeToBuffer(value, dataType, buffer, attributes) {
2792
2863
  if (dataType.size === 0) {
2793
2864
  return;
2794
2865
  }
2795
2866
  if (dataType.adsDataType === ADS.ADS_DATA_TYPES.ADST_STRING) {
2867
+ const encoded = ADS.encodeStringToPlcStringBuffer(value, this.getStringDataTypeEncoding(dataType, attributes) === 'UTF-8');
2796
2868
  //If string is too long for target data type, truncate it
2797
- const length = Math.min(dataType.size - 1, value.length);
2798
- ADS.encodeStringToPlcStringBuffer(value).copy(buffer, 0, 0, length);
2869
+ const length = Math.min(dataType.size - 1, encoded.length);
2870
+ encoded.copy(buffer, 0, 0, length);
2799
2871
  }
2800
2872
  else if (dataType.adsDataType === ADS.ADS_DATA_TYPES.ADST_WSTRING) {
2801
2873
  ADS.encodeStringToPlcWstringBuffer(value).copy(buffer);
@@ -3015,6 +3087,8 @@ class Client extends events_1.default {
3015
3087
  * }
3016
3088
  * ```
3017
3089
  *
3090
+ * @param command The ADS command to send
3091
+ *
3018
3092
  * @template T In Typescript, the type of the ADS response. If omitted, generic {@link AdsResponse} type is used.
3019
3093
  *
3020
3094
  * @throws Throws an error if sending the command fails or if target responds with an error
@@ -3100,6 +3174,82 @@ class Client extends events_1.default {
3100
3174
  }
3101
3175
  });
3102
3176
  }
3177
+ /**
3178
+ * Sends a raw ADS command to the target with fallback. A wrapper for {@link Client.sendAdsCommand}().
3179
+ *
3180
+ * Calls `sendAdsCommand(command)` and if it fails with
3181
+ * ADS error 1793 or 1808 then calls the `sendAdsCommand(fallback)`.
3182
+ *
3183
+ * See {@link Client.readPlcUploadInfo}() for use case.
3184
+ *
3185
+ * The ideas is copied from TwinCAT.Ads.dll (`TwinCAT.Ads.AdsClientExtensions.ReadWithFallbackAsync()`).
3186
+ *
3187
+ * @example
3188
+ * ```js
3189
+ * try {
3190
+ * const data = Buffer.alloc(12);
3191
+ * //...code omitted...
3192
+ *
3193
+ * const command = {
3194
+ * adsCommand: ADS.ADS_COMMAND.Read,
3195
+ * targetAmsNetId: targetOpts.amsNetId,
3196
+ * targetAdsPort: targetOpts.adsPort,
3197
+ * payload: data
3198
+ * };
3199
+ *
3200
+ * const fbData = Buffer.alloc(12);
3201
+ * //...code omitted...
3202
+ *
3203
+ * const fallback = {
3204
+ * adsCommand: ADS.ADS_COMMAND.Read,
3205
+ * targetAmsNetId: targetOpts.amsNetId,
3206
+ * targetAdsPort: targetOpts.adsPort,
3207
+ * payload: fbData
3208
+ * };
3209
+ *
3210
+ * const { response: res, fallbackUsed } = await this.sendAdsCommandWithFallback<AdsReadResponse>(command, fallback);
3211
+ *
3212
+ * //If we are here, one of those commands was succcesful
3213
+ * if(fallbackUsed) {
3214
+ * console.log("Fallback was used. Result:", res.ads.payload);
3215
+ * } else {
3216
+ * console.log("Fallback was not used. Result:", res.ads.payload);
3217
+ * }
3218
+ *
3219
+ * } catch (err) {
3220
+ * console.log("Error:", err);
3221
+ * }
3222
+ * ```
3223
+ *
3224
+ * @param command The main ADS command to send
3225
+ * @param fallback The fallback ADS command to send
3226
+ *
3227
+ * @template T In Typescript, the type of the ADS response. If omitted, generic {@link AdsResponse} type is used.
3228
+ *
3229
+ * @throws Throws an error if sending the command fails or if target responds with an error
3230
+ */
3231
+ async sendAdsCommandWithFallback(command, fallback) {
3232
+ try {
3233
+ this.debug(`sendAdsCommandWithFallback(): Sending ADS command with a fallback`);
3234
+ return {
3235
+ fallbackUsed: false,
3236
+ response: await this.sendAdsCommand(command)
3237
+ };
3238
+ }
3239
+ catch (err) {
3240
+ const clientErr = err;
3241
+ //If error is "Service is not supported by server" (1793) or "Symbol not found" (1808) then we should try the fallback
3242
+ if (clientErr.adsError?.errorCode === 1793 || clientErr.adsError?.errorCode === 1808) {
3243
+ this.debug(`sendAdsCommandWithFallback(): Sending ADS command failed with ADS error ${clientErr.adsError.errorCode} - trying fallback...`);
3244
+ return {
3245
+ fallbackUsed: true,
3246
+ response: await this.sendAdsCommand(fallback)
3247
+ };
3248
+ }
3249
+ this.debug(`sendAdsCommandWithFallback(): Sending ADS command failed - skipping fallback (ADS error is not 1793 or 1808)`);
3250
+ throw err;
3251
+ }
3252
+ }
3103
3253
  /**
3104
3254
  * Sends an ADS `WriteControl` command to the target.
3105
3255
  *
@@ -3191,6 +3341,8 @@ class Client extends events_1.default {
3191
3341
  const state = await this.readPlcRuntimeState(targetOpts);
3192
3342
  await this.writeControl("Run", state.deviceState, undefined, targetOpts);
3193
3343
  this.debug(`startPlc(): PLC runtime started at ${this.targetToString(targetOpts)}`);
3344
+ //Reading runtime state to make sure metaData is updated asap
3345
+ await this.readPlcRuntimeState(targetOpts);
3194
3346
  }
3195
3347
  catch (err) {
3196
3348
  this.debug(`startPlc(): Starting PLC runtime at ${this.targetToString(targetOpts)} failed: %o`, err);
@@ -3223,6 +3375,8 @@ class Client extends events_1.default {
3223
3375
  const state = await this.readPlcRuntimeState(targetOpts);
3224
3376
  await this.writeControl("Reset", state.deviceState, undefined, targetOpts);
3225
3377
  this.debug(`resetPlc(): PLC runtime reset at ${this.targetToString(targetOpts)}`);
3378
+ //Reading runtime state to make sure metaData is updated asap
3379
+ await this.readPlcRuntimeState(targetOpts);
3226
3380
  }
3227
3381
  catch (err) {
3228
3382
  this.debug(`resetPlc(): Resetting PLC runtime at ${this.targetToString(targetOpts)} failed: %o`, err);
@@ -3255,6 +3409,8 @@ class Client extends events_1.default {
3255
3409
  const state = await this.readPlcRuntimeState(targetOpts);
3256
3410
  await this.writeControl("Stop", state.deviceState, undefined, targetOpts);
3257
3411
  this.debug(`stopPlc(): PLC runtime stopped at ${this.targetToString(targetOpts)}`);
3412
+ //Reading runtime state to make sure metaData is updated asap
3413
+ await this.readPlcRuntimeState(targetOpts);
3258
3414
  }
3259
3415
  catch (err) {
3260
3416
  this.debug(`stopPlc(): Stopping PLC runtime at ${this.targetToString(targetOpts)} failed: %o`, err);
@@ -3602,8 +3758,8 @@ class Client extends events_1.default {
3602
3758
  });
3603
3759
  this.debug(`readPlcRuntimeState(): Runtime state read successfully. State is %o`, res.ads.payload);
3604
3760
  if (!targetOpts.adsPort && !targetOpts.amsNetId) {
3605
- //Target is not overridden -> save to metadata
3606
- this.metaData.plcRuntimeState = res.ads.payload;
3761
+ //Target is not overridden -> handle the result (check for change and update metadata)
3762
+ this.handlePlcRuntimeStateChange(res.ads.payload);
3607
3763
  }
3608
3764
  return res.ads.payload;
3609
3765
  }
@@ -4096,36 +4252,90 @@ class Client extends events_1.default {
4096
4252
  data.writeUInt32LE(0, pos);
4097
4253
  pos += 4;
4098
4254
  //8..11 Read data length
4099
- data.writeUInt32LE(24, pos);
4255
+ data.writeUInt32LE(64, pos); //64 = upload info version 3 size (works also for lower versions)
4256
+ pos += 4;
4257
+ const command = {
4258
+ adsCommand: ADS.ADS_COMMAND.Read,
4259
+ targetAmsNetId: targetOpts.amsNetId,
4260
+ targetAdsPort: targetOpts.adsPort,
4261
+ payload: data
4262
+ };
4263
+ //Fallback read command for version 1
4264
+ //Allocating bytes for request
4265
+ const fbData = Buffer.alloc(12);
4266
+ pos = 0;
4267
+ //0..3 IndexGroup
4268
+ fbData.writeUInt32LE(ADS.ADS_RESERVED_INDEX_GROUPS.SymbolUploadInfo, pos);
4269
+ pos += 4;
4270
+ //4..7 IndexOffset
4271
+ fbData.writeUInt32LE(0, pos);
4272
+ pos += 4;
4273
+ //8..11 Read data length
4274
+ fbData.writeUInt32LE(8, pos); //8 = upload info version 1 size
4100
4275
  pos += 4;
4276
+ const fallback = {
4277
+ adsCommand: ADS.ADS_COMMAND.Read,
4278
+ targetAmsNetId: targetOpts.amsNetId,
4279
+ targetAdsPort: targetOpts.adsPort,
4280
+ payload: fbData
4281
+ };
4101
4282
  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
- });
4283
+ const { response: res, fallbackUsed } = await this.sendAdsCommandWithFallback(command, fallback);
4284
+ this.debug(`readPlcUploadInfo(): Upload info read - fallback was needed: ${fallbackUsed}`);
4108
4285
  const uploadInfo = {};
4109
4286
  let pos = 0;
4110
4287
  const response = res.ads.payload;
4288
+ //If we are here, either the command or fallback was successful
4289
+ //Detect version from byte count
4290
+ if (response.byteLength === 8) {
4291
+ uploadInfo.version = 1;
4292
+ }
4293
+ else if (response.byteLength === 24) {
4294
+ uploadInfo.version = 2;
4295
+ }
4296
+ else if (response.byteLength === 64) {
4297
+ uploadInfo.version = 3;
4298
+ }
4299
+ else {
4300
+ //Shouldn't happen as we request max 64 bytes
4301
+ throw new Error(`Upload info version is unknown (received ${response.byteLength} bytes) - create a GitHub issue`);
4302
+ }
4111
4303
  //0..3 Symbol count
4112
4304
  uploadInfo.symbolCount = response.readUInt32LE(pos);
4113
4305
  pos += 4;
4114
4306
  //4..7 Symbol length
4115
4307
  uploadInfo.symbolLength = response.readUInt32LE(pos);
4116
4308
  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;
4309
+ if (uploadInfo.version >= 2) {
4310
+ //8..11 Data type count
4311
+ uploadInfo.dataTypeCount = response.readUInt32LE(pos);
4312
+ pos += 4;
4313
+ //12..15 Data type length
4314
+ uploadInfo.dataTypeLength = response.readUInt32LE(pos);
4315
+ pos += 4;
4316
+ //16..19 Max. allowed dynamic symbol count
4317
+ uploadInfo.maxDynamicSymbolCount = response.readUInt32LE(pos);
4318
+ pos += 4;
4319
+ //20..23 Number of dynamic symbols used (version >= 2)
4320
+ uploadInfo.dynamicSymbolCount = response.readUInt32LE(pos);
4321
+ pos += 4;
4322
+ if (uploadInfo.version >= 3) {
4323
+ //24..27 Invalid dynamic symbol count
4324
+ uploadInfo.invalidDynamicSymbolCount = response.readUInt32LE(pos);
4325
+ pos += 4;
4326
+ //28..31 Encoding code page used for STRING encoding
4327
+ uploadInfo.encodingCodePage = response.readUInt32LE(pos);
4328
+ pos += 4;
4329
+ //32..35 Upload info flags
4330
+ uploadInfo.flags = response.readUInt32LE(pos);
4331
+ uploadInfo.flagsStr = ADS.ADS_UPLOAD_INFO_FLAGS.toStringArray(uploadInfo.flags);
4332
+ pos += 4;
4333
+ //36..63 Reserved
4334
+ uploadInfo.reserved = response.subarray(pos);
4335
+ }
4336
+ }
4337
+ //Target has UTF-8 encoded ADS symbols if encodingCodePage is 65001 (or if forced from settings)
4338
+ this.metaData.adsSymbolsUseUtf8 = uploadInfo.encodingCodePage === 65001 || this.settings.forceUtf8ForAdsSymbols;
4129
4339
  if (!targetOpts.adsPort && !targetOpts.amsNetId) {
4130
4340
  //Target is not overridden -> save to metadata
4131
4341
  this.metaData.plcUploadInfo = uploadInfo;
@@ -4314,7 +4524,7 @@ class Client extends events_1.default {
4314
4524
  data.writeUInt32LE(0, pos);
4315
4525
  pos += 4;
4316
4526
  //8..11 Read data length
4317
- data.writeUInt32LE(uploadInfo.dataTypeLength, pos);
4527
+ data.writeUInt32LE(uploadInfo.dataTypeLength ?? 0xFFFFFFFF, pos); //Using 0xFFFFFFFF is upload info is version 1 (= we don't know the length)
4318
4528
  pos += 4;
4319
4529
  try {
4320
4530
  const res = await this.sendAdsCommand({
@@ -4826,11 +5036,11 @@ class Client extends events_1.default {
4826
5036
  *
4827
5037
  * //Writing a POINTER value (Note the dereference operator ^)
4828
5038
  * const ptrValue = ...
4829
- * await client.writeRawByPath('GVL_Read.ComplexTypes.POINTER_^', ptrValue);
5039
+ * await client.writeRawByPath('GVL_Write.ComplexTypes.POINTER_^', ptrValue);
4830
5040
  *
4831
5041
  * //Writing a REFERENCE value
4832
5042
  * const refValue = ...
4833
- * await client.readRawByPath('GVL_Read.ComplexTypes.REFERENCE_');
5043
+ * await client.writeRawByPath('GVL_Write.ComplexTypes.REFERENCE_');
4834
5044
  *
4835
5045
  * } catch (err) {
4836
5046
  * console.log("Error:", err);
@@ -5133,11 +5343,11 @@ class Client extends events_1.default {
5133
5343
  this.debug(`readValue(): Reading raw value from ${path} failed: %o`, err);
5134
5344
  throw new client_error_1.default(`readValue(): Reading raw value from ${path} failed`, err);
5135
5345
  }
5136
- //Converting byte data to javascript object
5346
+ //Converting byte data to javascript object
5137
5347
  let value;
5138
5348
  try {
5139
5349
  this.debugD(`readValue(): Converting raw value to object for ${path}`);
5140
- value = await this.convertBufferToObject(rawValue, dataType);
5350
+ value = await this.convertBufferToObject(rawValue, dataType, symbol.attributes);
5141
5351
  }
5142
5352
  catch (err) {
5143
5353
  this.debug(`readValue(): Converting raw value to object for ${path} failed: %o`, err);
@@ -5204,11 +5414,9 @@ class Client extends events_1.default {
5204
5414
  * @example
5205
5415
  * ```js
5206
5416
  * try {
5207
- * const value = {
5208
- * example: true
5209
- * };
5417
+ * const res = await client.writeValue('GVL_Write.StandardTypes.INT_', 32767);
5418
+ * console.log('Value written:', res.value);
5210
5419
  *
5211
- * const res = await client.writeValue('GVL_Read.StandardTypes.INT_', value);
5212
5420
  * } catch (err) {
5213
5421
  * console.log("Error:", err);
5214
5422
  * }
@@ -5251,7 +5459,7 @@ class Client extends events_1.default {
5251
5459
  //Creating raw data from object
5252
5460
  let rawValue;
5253
5461
  try {
5254
- let res = this.convertObjectToBuffer(value, dataType);
5462
+ let res = this.convertObjectToBuffer(value, dataType, symbol.attributes);
5255
5463
  if (res.missingProperty && autoFill) {
5256
5464
  //Some fields are missing and autoFill is used -> try to auto fill missing values
5257
5465
  this.debug(`writeValue(): Autofilling missing fields with active values`);
@@ -5260,7 +5468,7 @@ class Client extends events_1.default {
5260
5468
  this.debugD(`writeValue(): Merging objects (adding missing fields)`);
5261
5469
  value = this.deepMergeObjects(false, valueNow.value, value);
5262
5470
  //Try conversion again - should work now
5263
- res = this.convertObjectToBuffer(value, dataType);
5471
+ res = this.convertObjectToBuffer(value, dataType, symbol.attributes);
5264
5472
  if (res.missingProperty) {
5265
5473
  //This shouldn't really happen
5266
5474
  //However, if it happened, the merge isn't working as it should
@@ -5356,12 +5564,13 @@ class Client extends events_1.default {
5356
5564
  * ```
5357
5565
  *
5358
5566
  * @param dataType Data type name in the PLC as string (such as `ST_Struct`) or data type object (acquired using {@link getDataType}())
5567
+ * @param attributes Additional attributes of the symbol or data type used for conversion
5359
5568
  * @param targetOpts Optional target settings that override values in `settings`
5360
5569
  *
5361
5570
  * @template T Typescript data type of the PLC data, for example `getDefaultPlcObject<number>(...)` or `getDefaultPlcObject<ST_TypedStruct>(...)`
5362
5571
  * @throws Throws an error if sending the command fails or if the target responds with an error.
5363
5572
  */
5364
- async getDefaultPlcObject(dataType, targetOpts = {}) {
5573
+ async getDefaultPlcObject(dataType, attributes, targetOpts = {}) {
5365
5574
  if (!this.connection.connected) {
5366
5575
  throw new client_error_1.default(`getDefaultPlcObject(): Client is not connected. Use connect() to connect to the target first.`);
5367
5576
  }
@@ -5378,7 +5587,7 @@ class Client extends events_1.default {
5378
5587
  }
5379
5588
  }
5380
5589
  //Converting raw byte data to Javascript object
5381
- let value = await this.convertFromRaw(Buffer.alloc(dataType.size), dataType, targetOpts);
5590
+ let value = await this.convertFromRaw(Buffer.alloc(dataType.size), dataType, attributes, targetOpts);
5382
5591
  this.debug(`getDefaultPlcObject(): Empty default object created for ${dataType.type}`);
5383
5592
  return value;
5384
5593
  }
@@ -5401,12 +5610,13 @@ class Client extends events_1.default {
5401
5610
  *
5402
5611
  * @param data Raw data (acquired for example using {@link readRaw}())
5403
5612
  * @param dataType Data type name in the PLC as string (such as `ST_Struct`) or data type object (acquired using {@link getDataType}())
5613
+ * @param attributes Additional attributes of the symbol or data type used for conversion
5404
5614
  * @param targetOpts Optional target settings that override values in `settings`
5405
5615
  *
5406
5616
  * @template T Typescript data type of the PLC data, for example `convertFromRaw<number>(...)` or `convertFromRaw<ST_TypedStruct>(...)`
5407
5617
  * @throws Throws an error if sending the command fails or if the target responds with an error.
5408
5618
  */
5409
- async convertFromRaw(data, dataType, targetOpts = {}) {
5619
+ async convertFromRaw(data, dataType, attributes, targetOpts = {}) {
5410
5620
  if (!this.connection.connected) {
5411
5621
  throw new client_error_1.default(`convertFromRaw(): Client is not connected. Use connect() to connect to the target first.`);
5412
5622
  }
@@ -5426,7 +5636,7 @@ class Client extends events_1.default {
5426
5636
  let value;
5427
5637
  try {
5428
5638
  this.debugD(`convertFromRaw(): Converting raw value to object for ${dataType.type}`);
5429
- value = await this.convertBufferToObject(data, dataType);
5639
+ value = await this.convertBufferToObject(data, dataType, attributes);
5430
5640
  }
5431
5641
  catch (err) {
5432
5642
  this.debug(`convertFromRaw(): Converting raw value to object for ${dataType.type} failed: %o`, err);
@@ -5454,11 +5664,12 @@ class Client extends events_1.default {
5454
5664
  * @param value Value to convert
5455
5665
  * @param dataType Data type name in the PLC as string (such as `ST_Struct`) or data type object (acquired using {@link getDataType}())
5456
5666
  * @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).
5667
+ * @param attributes Additional attributes of the symbol or data type used for conversion
5457
5668
  * @param targetOpts Optional target settings that override values in `settings`
5458
5669
  *
5459
5670
  * @throws Throws an error if sending the command fails or if the target responds with an error.
5460
5671
  */
5461
- async convertToRaw(value, dataType, autoFill = false, targetOpts = {}) {
5672
+ async convertToRaw(value, dataType, autoFill = false, attributes, targetOpts = {}) {
5462
5673
  if (!this.connection.connected) {
5463
5674
  throw new client_error_1.default(`convertToRaw(): Client is not connected. Use connect() to connect to the target first.`);
5464
5675
  }
@@ -5477,16 +5688,16 @@ class Client extends events_1.default {
5477
5688
  //Converting Javascript object to raw byte data
5478
5689
  let rawValue;
5479
5690
  try {
5480
- let res = this.convertObjectToBuffer(value, dataType);
5691
+ let res = this.convertObjectToBuffer(value, dataType, attributes);
5481
5692
  if (res.missingProperty && autoFill) {
5482
5693
  //Some fields are missing and autoFill is used -> try to auto fill missing values
5483
5694
  this.debug(`convertToRaw(): Autofilling missing fields with default/zero values`);
5484
5695
  this.debugD(`convertToRaw(): Creating default object`);
5485
- const defaultValue = await this.getDefaultPlcObject(dataType, targetOpts);
5696
+ const defaultValue = await this.getDefaultPlcObject(dataType, attributes, targetOpts);
5486
5697
  this.debugD(`convertToRaw(): Merging objects (adding missing fields)`);
5487
5698
  value = this.deepMergeObjects(false, defaultValue, value);
5488
5699
  //Try conversion again - should work now
5489
- res = this.convertObjectToBuffer(value, dataType);
5700
+ res = this.convertObjectToBuffer(value, dataType, attributes);
5490
5701
  if (res.missingProperty) {
5491
5702
  //This shouldn't really happen
5492
5703
  //However, if it happened, the merge isn't working as it should
@@ -5592,7 +5803,7 @@ class Client extends events_1.default {
5592
5803
  let dataTypeLength = response.readUInt16LE(pos);
5593
5804
  pos += 2;
5594
5805
  //10..n Data type
5595
- result.dataType = ADS.decodePlcStringBuffer(response.subarray(pos, pos + dataTypeLength + 1));
5806
+ result.dataType = ADS.decodePlcStringBuffer(response.subarray(pos, pos + dataTypeLength + 1), this.metaData.adsSymbolsUseUtf8);
5596
5807
  this.debug(`createVariableHandle(): Variable handle created to ${path}`);
5597
5808
  return result;
5598
5809
  }
@@ -5730,7 +5941,7 @@ class Client extends events_1.default {
5730
5941
  let dataTypeLength = response.readUInt16LE(pos);
5731
5942
  pos += 2;
5732
5943
  //10..n Data type
5733
- result.dataType = ADS.decodePlcStringBuffer(response.subarray(pos, pos + dataTypeLength + 1));
5944
+ result.dataType = ADS.decodePlcStringBuffer(response.subarray(pos, pos + dataTypeLength + 1), this.metaData.adsSymbolsUseUtf8);
5734
5945
  pos += dataTypeLength + 1;
5735
5946
  results[i].handle = result;
5736
5947
  }
@@ -6123,8 +6334,8 @@ class Client extends events_1.default {
6123
6334
  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
6335
  }
6125
6336
  //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);
6337
+ const inputs = rpcMethod.parameters.filter(p => (p.flags & ADS.ADS_RCP_METHOD_PARAM_FLAGS.In) === ADS.ADS_RCP_METHOD_PARAM_FLAGS.In);
6338
+ const outputs = rpcMethod.parameters.filter(p => (p.flags & ADS.ADS_RCP_METHOD_PARAM_FLAGS.Out) === ADS.ADS_RCP_METHOD_PARAM_FLAGS.Out);
6128
6339
  //Creating data buffer for inputs
6129
6340
  let inputsData = Buffer.alloc(0);
6130
6341
  for (const input of inputs) {