ads-client 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.14.0] - 23.07.2022
8
+ ### Added
9
+ - Created end-to-end testing for the library using Jest
10
+ - Separate PLC project required for testing: https://github.com/jisotalo/ads-client-test-plc-project
11
+ - Test is started using command `npm test`
12
+ - First version tests
13
+ - Connecting
14
+ - Reading
15
+ - Writing
16
+ - RPC methods
17
+
18
+ ### Changed
19
+ - Bug fix: Value converted to Javascript variable incorrectly in some edge cases [See issue #94](https://github.com/jisotalo/ads-client/issues/94)
20
+
21
+ ## [1.13.2] - 17.05.2022
22
+ ### Changed
23
+ - Bug fix: Incorrect parsing of ARRAY OF ARRAY, now works as should [See issue #91](https://github.com/jisotalo/ads-client/issues/91)
24
+ - Added settings descriptions to README
25
+
26
+ ## [1.13.1] - 04.04.2022
27
+ ### Changed
28
+ - Bug fix: Undefined variable `message` [See issue #89](https://github.com/jisotalo/ads-client/issues/89)
29
+ - Updated ADS error codes with new ones [See issue #88](https://github.com/jisotalo/ads-client/issues/88)
30
+ - Updated README
31
+
7
32
  ## [1.13.0] - 27.02.2022
8
33
  ### Added
9
34
  - Added new setting `bareClient`. If it's set, the client will only connect to the target, nothing else
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
 
4
4
  [![npm version](https://img.shields.io/npm/v/ads-client)](https://www.npmjs.org/package/ads-client)
5
- [![GitHub milestone](https://img.shields.io/github/milestones/progress-percent/jisotalo/ads-client/1)](https://github.com/jisotalo/ads-client/milestone/1)
5
+ [![Donate](https://img.shields.io/badge/Donate-PayPal-yellow)](https://www.paypal.com/donate/?business=KUWBXXCVGZZME&no_recurring=0&currency_code=EUR)
6
6
  [![GitHub](https://img.shields.io/badge/View%20on-GitHub-brightgreen)](https://github.com/jisotalo/ads-client)
7
7
  [![License](https://img.shields.io/github/license/jisotalo/ads-client)](https://choosealicense.com/licenses/mit/)
8
8
 
@@ -13,10 +13,15 @@ Coded from scratch using [TwinCAT ADS specification](https://infosys.beckhoff.co
13
13
  There is automatically created documentation available at https://jisotalo.github.io/ads-client/
14
14
 
15
15
  # Project status
16
- This project is currently "ready". It's maintained actively and used in projects by the author and others.
16
+ This project is currently "ready". It's maintained actively and used in projects by the author and others (also lot's of commercial projects)
17
17
 
18
18
  Bugs are fixed if found and new features can be added. Please let me know if you have any ideas!
19
19
 
20
+ And if you want you can buy me a beer using PayPal :)
21
+
22
+ [![Donate](https://img.shields.io/badge/Donate%20a%20beer!-PayPal-yellow)](https://www.paypal.com/donate/?business=KUWBXXCVGZZME&no_recurring=0&currency_code=EUR)
23
+
24
+
20
25
  # Using Node-RED?
21
26
  Check out the [node-red-contrib-ads-client](https://www.npmjs.com/package/node-red-contrib-ads-client) package. It's an `ads-client` wrapper for Node-RED to get the same functionality.
22
27
 
@@ -33,6 +38,7 @@ Check out the [node-red-contrib-ads-client](https://www.npmjs.com/package/node-r
33
38
  - [Getting started](#getting-started)
34
39
  * [Data types used in getting started](#data-types-used-in-getting-started)
35
40
  * [Creating a new Client instance](#creating-a-new-client-instance)
41
+ * [Available settings](#available-settings)
36
42
  * [Connecting and disconnecting](#connecting-and-disconnecting)
37
43
  * [Reading any type PLC variable](#reading-any-type-plc-variable)
38
44
  + [Example: Reading `INT` type variable](#example-reading-int-type-variable)
@@ -83,6 +89,7 @@ Check out the [node-red-contrib-ads-client](https://www.npmjs.com/package/node-r
83
89
  * [Enabling debug from code](#enabling-debug-from-code)
84
90
  * [Enabling debugging from terminal](#enabling-debugging-from-terminal)
85
91
  - [FAQ](#faq)
92
+ - [Automatic testing](#automatic-testing)
86
93
  - [Documentation](#documentation)
87
94
  - [License](#license)
88
95
 
@@ -406,8 +413,6 @@ END_TYPE
406
413
 
407
414
  The constructor takes settings as its parameter. Two settings are mandatory: `targetAmsNetId` and `targetAdsPort`. The first is the target PLC system AmsNetId (like *127.0.0.1.1.1* or *192.168.1.10.1.1*) and the latter is target system ADS port (*like 851 for TwinCAT 3 runtime 1*).
408
415
 
409
- See all settings from the [Settings documentation](https://jisotalo.github.io/ads-client/global.html#Settings)
410
-
411
416
  ```js
412
417
  const ads = require('ads-client')
413
418
 
@@ -417,6 +422,84 @@ const client = new ads.Client({
417
422
  targetAdsPort: 851,
418
423
  })
419
424
  ```
425
+ ### Available settings
426
+ **REQUIRED:**
427
+ * `targetAmsNetId`
428
+ * Target system AmsNetId
429
+ * Use `127.0.0.1.1.1` or `localhost` if connecting to a local system
430
+ * `targetAdsPort`
431
+ * Target system ADS port.
432
+ * TwinCAT 3: 851 (1st runtime), 852 (2nd runtime) and so on
433
+ * TwinCAT 2: 801 (1st runtime)
434
+
435
+ **Optional:**
436
+ * `objectifyEnumerations`
437
+ * Default value: `true`
438
+ * If true, read ENUM data types are converted to objects instead of numbers, e.g. `{name: 'enumValue', value: 5}` instead of 5
439
+ * `convertDatesToJavascript`
440
+ * Default value: `true`
441
+ * If true, PLC DT (DATE_AND_TIME) and DATE types are converted to Javascript dates
442
+ * `readAndCacheSymbols`
443
+ * Default value: `false`
444
+ * If true, **all** PLC symbols are cached during connecting. Otherwise they are read and cached only when needed
445
+ * `readAndCacheDataTypes`
446
+ * Default value: `false`
447
+ * If true, **all** PLC data types are cached during connecting. Otherwise they are read and cached only when needed
448
+ * `disableSymbolVersionMonitoring`
449
+ * Default value: `false`
450
+ * If true, PLC symbol version changes aren't monitored and cached symbols and datatypes won't be updated after PLC program download
451
+ * `routerTcpPort`
452
+ * Default value: `48898`
453
+ * Target ADS router TCP port
454
+ * `routerAddress`
455
+ * Default value: `localhost`
456
+ * Target ADS router IP address/hostname
457
+ * `localAddress`
458
+ * Default value: `system default`
459
+ * Local IP address to use, use this to change used network interface if required
460
+ * `localTcpPort`
461
+ * Default value: `system default`
462
+ * Local TCP port to use for outgoing connections
463
+ * `localAmsNetId`
464
+ * Default value: `AMS router provides`
465
+ * Local AmsNetId to use
466
+ * Used especially when connecting from systems without own AMS router, like Raspberry Pi
467
+ * See: [Connecting from any Node.js supported system to the PLC](#setup-3---connecting-from-any-nodejs-supported-system-to-the-plc)
468
+ * `localAdsPort`
469
+ * Default value: `AMS router provides`
470
+ * Local ADS port to use
471
+ * Used especially when connecting from systems without own AMS router, like Raspberry Pi
472
+ * See: [Connecting from any Node.js supported system to the PLC](#setup-3---connecting-from-any-nodejs-supported-system-to-the-plc)
473
+ * `timeoutDelay`
474
+ * Default value: `2000`
475
+ * Time (milliseconds) after connecting to the router or waiting for command response is canceled to a timeout
476
+ * `hideConsoleWarnings`
477
+ * Default value: `false`
478
+ * If true, no warnings are written to console (=nothing is **ever** written to console)
479
+ * `autoReconnect`
480
+ * Default value: `true`
481
+ * If true and connection is lost, the client tries to reconnect automatically
482
+ * `reconnectInterval`
483
+ * Default value: `2000`
484
+ * Time (milliseconds) how often the lost connection is tried to re-establish
485
+ * `checkStateInterval`
486
+ * Default value: `1000`
487
+ * Time (milliseconds) how often the system manager state is read to see if connection is OK
488
+ * `connectionDownDelay`
489
+ * Default value: `5000`
490
+ * Time (milliseconds) after no successful reading of the system manager state the connection is determined to be lost
491
+ * `allowHalfOpen`
492
+ * Default value: `false`
493
+ * If true, connect() is successful even if no PLC runtime is found (but target and system manager are available) - Can be useful if it's ok that after connecting the PLC runtime is not immediately available (example: connecting before uploading PLC code and reading data later) -
494
+ * WARNING: If true, reinitializing subscriptions might fail after connection loss.
495
+ * `disableBigInt`
496
+ * Default value: `false`
497
+ * If true, 64-bit integer PLC variables are kept as `Buffer` objects instead of converting to Javascript `BigInt` variables (JSON.strigify and libraries that use it have no BigInt support)
498
+ * `bareClient`
499
+ * Default value: `false`
500
+ * If true, only direct ads connection is established (no system manager)
501
+ * Can be used to connect to systems without PLC runtime
502
+ * See [Connecting to systems without PLC runtime](#connecting-to-systems-without-plc-runtime)
420
503
 
421
504
 
422
505
  ## Connecting and disconnecting
@@ -1662,6 +1745,24 @@ See https://github.com/jisotalo/ads-client/issues/82
1662
1745
 
1663
1746
  - EADDRNOTAVAIL: See above and https://github.com/jisotalo/ads-client/issues/82
1664
1747
 
1748
+ ### How to connect to PLC that is in CONFIG mode?
1749
+ As default, the ads-client checks if the target has PLC runtime at given port. However, when target system manager is at config mode, there is none. The client will throw an error during connecting.
1750
+
1751
+ `Target and system manager found but couldn't connect to the PLC runtime (see settings allowHalfOpen and bareClient)`
1752
+
1753
+ You can disable the check by providing setting `allowHalfOpen: true`. After that, it's possible to start the PLC by `setSystemManagerToRun()`. However, when using this setting internal subscription like symbol version etc. might not work properly.
1754
+
1755
+ Another option is to use setting `bareClient: true` (since v.1.13.0). However, when using this option, the ads-client does not handle anything automatically - it's just a bare client (duh).
1756
+
1757
+ I would suggest to use ads-client normally without any special settings. If the target is at config mode, use separate client instance to start it, and the again the normal instance to connect to a running system. This way the client works the best.
1758
+
1759
+ # Automatic testing
1760
+ Since version 1.14.0 the library has automatic testing using Jest. Idea is to run the tests before updates to make sure everything works OK (this should have been done much earlier...)
1761
+
1762
+ Separate PLC project is required for testing, see https://github.com/jisotalo/ads-client-test-plc-project for more project and more info.
1763
+
1764
+ Tests are run with command `npm test` (not in npm version, please clone this repository).
1765
+
1665
1766
  # Documentation
1666
1767
 
1667
1768
  You can find the full html documentation from the project [GitHub home page](https://jisotalo.github.io/ads-client/) as well as from `./docs/` folder in the repository.
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "ads-client",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "Beckhoff TwinCAT ADS client library for Node.js (unofficial). Connects to Beckhoff TwinCAT automation systems using ADS protocol.",
5
5
  "main": "./src/ads-client.js",
6
6
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1",
7
+ "test": "jest --runInBand",
8
8
  "generate-docs": "jsdoc ./src/ads-client.js ./README.md -c ./jsdoc-conf.json -d ./docs/ -t ./node_modules/docdash/"
9
9
  },
10
10
  "keywords": [
@@ -39,6 +39,7 @@
39
39
  },
40
40
  "devDependencies": {
41
41
  "docdash": "^1.2.0",
42
+ "jest": "^28.1.3",
42
43
  "jsdoc": "^3.6.7"
43
44
  },
44
45
  "files": [
@@ -352,6 +352,8 @@ const ADS_ERROR = {
352
352
  26: 'TCP send error',
353
353
  27: 'Host unreachable',
354
354
  28: 'Invalid AMS fragment',
355
+ 29: 'TLS send error – secure ADS connection failed.',
356
+ 30: 'Access denied – secure ADS access denied.',
355
357
  1280: 'No locked memory can be allocated',
356
358
  1281: 'The size of the router memory could not be changed',
357
359
  1282: 'The mailbox has reached the maximum number of possible messages. The current sent message was rejected',
@@ -363,6 +365,9 @@ const ADS_ERROR = {
363
365
  1288: 'The maximum number of Ports reached',
364
366
  1289: 'Invalid port',
365
367
  1290: 'TwinCAT Router not active',
368
+ 1291: 'The mailbox has reached the maximum number for fragmented messages.',
369
+ 1292: 'A fragment timeout has occurred.',
370
+ 1293: 'The port is removed.',
366
371
  1792: 'General device error',
367
372
  1793: 'Service is not supported by server',
368
373
  1794: 'Invalid index group',
@@ -420,6 +425,7 @@ const ADS_ERROR = {
420
425
  1862: 'Error in win32 subsystem',
421
426
  1863: 'Invalid client timeout value',
422
427
  1864: 'Ads-port not opened',
428
+ 1865: 'No AMS address.',
423
429
  1872: 'Internal error in ads sync',
424
430
  1873: 'Hash table overflow',
425
431
  1874: 'Key not found in hash',
package/src/ads-client.js CHANGED
@@ -888,7 +888,7 @@ class Client extends EventEmitter {
888
888
  } catch (err) {
889
889
  return reject(new ClientException(this, 'readSymbol()', `Reading symbol ${variableName} failed: Reading data type failed`, err))
890
890
  }
891
-
891
+
892
892
  //4. Parse the data to javascript object
893
893
  let data = {}
894
894
  try {
@@ -3128,7 +3128,7 @@ class ClientException extends Error {
3128
3128
  if (typeof Error.captureStackTrace === 'function') {
3129
3129
  Error.captureStackTrace(this, this.constructor)
3130
3130
  } else {
3131
- this.stack = (new Error(message)).stack
3131
+ this.stack = (new Error(messageOrError)).stack
3132
3132
  }
3133
3133
 
3134
3134
  /**
@@ -5230,8 +5230,8 @@ function _parseJsObjectToBuffer(value, dataType, objectPathStr = '', isArraySubI
5230
5230
 
5231
5231
  //Struct or array subitem - Go through each subitem
5232
5232
  if ((dataType.arrayData.length === 0 || isArraySubItem) && dataType.subItems.length > 0) {
5233
- buffer = Buffer.alloc(dataType.offset + dataType.size)
5234
-
5233
+ buffer = Buffer.alloc(dataType.size)
5234
+
5235
5235
  for (const subItem of dataType.subItems) {
5236
5236
  //Try the find the subitem from javascript object
5237
5237
  let key = null
@@ -5684,8 +5684,8 @@ function _getDataTypeRecursive(dataTypeName, firstLevel = true, size = null) {
5684
5684
  const arrayType = await _getDataTypeRecursive.call(this, dataType.type, false)
5685
5685
 
5686
5686
  parsedDataType = arrayType
5687
- parsedDataType.arrayData = dataType.arrayData
5688
-
5687
+ //Combining array information (for ARRAY OF ARRAY support)
5688
+ parsedDataType.arrayData = dataType.arrayData.concat(parsedDataType.arrayData)
5689
5689
 
5690
5690
  //If the data type has flag "DataType" and it's enum
5691
5691
  } else if (dataType.flagsStr.includes('DataType') && dataType.flagsStr.includes('EnumInfos')) {