ads-client 1.14.1 → 1.14.3

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,13 +4,44 @@ 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.3] - 23.09.2023
8
+ ### Changed
9
+ - Bug fix: Some TwinCAT 2 devices (such as BK9050) do not send data length if answering with error code
10
+ - This caused `RangeError: Index out of range` exception as there wasn't enough bytes received
11
+ - See [issue #116](https://github.com/jisotalo/ads-client/issues/116)
12
+ - Bug fix: If using older Node.js versions such as 8.x, connection lost could have caused unhandled exception
13
+ - Reason was `catch {}` which isn't supported in old versions
14
+ - See [issue #116](https://github.com/jisotalo/ads-client/issues/116)
15
+
16
+ ### Added
17
+ - Updated readme to include information about TypeScript types
18
+ - Thanks to [Christian Rishøj](https://github.com/crishoj)
19
+ - Updated readme with FAQ about TwinCAT 2 low-end devices
20
+ - Updated readme about v2 development
21
+ - Added option to run tests with usermode runtime AmsNetId (`192.168.4.1.1.1`) instead of localhost (`npm run test-um`)
22
+
23
+ ## [1.14.2] - 02.05.2023
24
+ ### Changed
25
+ - Bug fix: `ADS_DATA_TYPE_FLAGS` (`dataType.flags`) were parsed incorrectly.
26
+ - See [issue #109](https://github.com/jisotalo/ads-client/issues/109)
27
+ - Thank you [Michael Croes](https://github.com/mycroes) for contribution!
28
+ - Bug fix: If calling `unsubcribeAll()` when client is already unsubcribing and disconnecting, an error might be thrown
29
+ - See [issue #103](https://github.com/jisotalo/ads-client/issues/103#issuecomment-1450640160)
30
+ - Bug fix: Check that subscription callback exists before calling it
31
+ - Fixes unecessary error if receiving notification data (for subscription) but it's being unsubscribed at the same time
32
+ - Bump json5 from 2.2.1 to 2.2.3
33
+ - See [pull request 101](https://github.com/jisotalo/ads-client/pull/101)
34
+ - Not used in production code, only when running tests
35
+
36
+ ### Added
37
+ - New test for [issue #103](https://github.com/jisotalo/ads-client/issues/103)
38
+
7
39
  ## [1.14.1] - 13.09.2022
8
40
  ### Changed
9
41
  - Bug fix: Connecting to local router failed with ECONNREFUSED error on Node.js version 17 and newer
10
42
  - See [https://github.com/nodejs/node/issues/40702](https://github.com/nodejs/node/issues/40702)
11
43
  - Fixed by using `127.0.0.1` instead of `localhost`
12
44
 
13
-
14
45
  ## [1.14.0] - 23.07.2022
15
46
  ### Added
16
47
  - Created end-to-end testing for the library using Jest
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
- [![Donate](https://img.shields.io/badge/Donate-PayPal-yellow)](https://www.paypal.com/donate/?business=KUWBXXCVGZZME&no_recurring=0&currency_code=EUR)
5
+ [![Donate](https://img.shields.io/badge/Support-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
 
@@ -10,16 +10,19 @@ Beckhoff TwinCAT ADS client library for Node.js (unofficial). Connects to Beckho
10
10
 
11
11
  Coded from scratch using [TwinCAT ADS specification](https://infosys.beckhoff.com/content/1033/tc3_ads_intro/116157835.html?id=124964102706356243) and [Beckhoff.TwinCAT.Ads nuget package](https://www.nuget.org/packages/Beckhoff.TwinCAT.Ads/5.0.0-preview6). Inspiration from similar projects like [node-ads](https://www.npmjs.com/package/node-ads), [beckhoff-js](https://www.npmjs.com/package/beckhoff-js) and [iecstruct](https://www.npmjs.com/package/iecstruct).
12
12
 
13
- There is automatically created documentation available at https://jisotalo.github.io/ads-client/
13
+ There is automatically created documentation available at https://jisotalo.fi/ads-client/
14
14
 
15
15
  # Project status
16
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 :)
20
+ If you want to support my work, you can do it using PayPal. I can provide you support in exchange.
21
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)
22
+ [![Donate](https://img.shields.io/badge/Support%20my%20work!-PayPal-yellow)](https://www.paypal.com/donate/?business=KUWBXXCVGZZME&no_recurring=0&currency_code=EUR)
23
+
24
+ ## Version 2
25
+ Version 2 is under development in [`v2-dev`](https://github.com/jisotalo/ads-client/tree/v2-dev) branch. It's written in TypeScript (including all types!) and will also be more optimized. At the moment basic functions *might* work but it's not ready for production use.
23
26
 
24
27
 
25
28
  # Using Node-RED?
@@ -101,6 +104,13 @@ Install the [npm package](https://www.npmjs.com/package/ads-client) using npm co
101
104
  npm i ads-client
102
105
  ```
103
106
 
107
+ If you are using TypeScript, install unofficial types using npm command (thanks [Christian Rishøj](https://github.com/crishoj)):
108
+ ```bash
109
+ npm install --save @types/ads-client
110
+ ```
111
+
112
+ *Note: Version 2 under development will be written in 100% TypeScript*
113
+
104
114
  Include the module in your code
105
115
  ```js
106
116
  const ads = require('ads-client')
@@ -1764,12 +1774,17 @@ Solution:
1764
1774
  * When closing application, first unsubscribe from all notifications using `unsubscribeAll()`
1765
1775
  * Use router instead of direct connection, see https://github.com/jisotalo/ads-client/issues/85#issuecomment-1193098519
1766
1776
 
1777
+ ### Issues with TwinCAT 2 low-end devices (BK9050, BC9050 etc.)
1778
+ * You can only use raw commands (such as `readRaw()`, `writeRaw()`, `subscribeRaw()`) as these devices provide no symbols
1779
+ * See [issue 114](https://github.com/jisotalo/ads-client/issues/114) and [issue 116](https://github.com/jisotalo/ads-client/issues/116) for starters
1780
+
1781
+
1767
1782
  # Automatic testing
1768
1783
  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...)
1769
1784
 
1770
1785
  Separate PLC project is required for testing, see https://github.com/jisotalo/ads-client-test-plc-project for more project and more info.
1771
1786
 
1772
- Tests are run with command `npm test` (not in npm version, please clone this repository).
1787
+ Tests are run with command `npm test` or `npm run test-um` (usermode runtime) (not in npm version, please clone this repository).
1773
1788
 
1774
1789
  # Documentation
1775
1790
 
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "ads-client",
3
- "version": "1.14.1",
3
+ "version": "1.14.3",
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
7
  "test": "jest --runInBand",
8
+ "test-um": "set ADS_CLIENT_TEST_AMS=192.168.4.1.1.1 && jest --runInBand",
8
9
  "generate-docs": "jsdoc ./src/ads-client.js ./README.md -c ./jsdoc-conf.json -d ./docs/ -t ./node_modules/docdash/"
9
10
  },
10
11
  "keywords": [
package/src/ads-client.js CHANGED
@@ -894,7 +894,7 @@ class Client extends EventEmitter {
894
894
  } catch (err) {
895
895
  return reject(new ClientException(this, 'readSymbol()', `Reading symbol ${variableName} failed: Reading data type failed`, err))
896
896
  }
897
-
897
+
898
898
  //4. Parse the data to javascript object
899
899
  let data = {}
900
900
  try {
@@ -1224,7 +1224,11 @@ class Client extends EventEmitter {
1224
1224
  unSubCount++
1225
1225
 
1226
1226
  } catch (err) {
1227
- debug(`unsubscribeAll(): Unsubscribing from notification ${JSON.stringify(this._internals.activeSubscriptions[sub].target)} failed`)
1227
+ if (this._internals.activeSubscriptions[sub] && this._internals.activeSubscriptions[sub].target !== undefined) {
1228
+ debug(`unsubscribeAll(): Unsubscribing from notification ${JSON.stringify(this._internals.activeSubscriptions[sub].target)} failed`)
1229
+ } else {
1230
+ debug(`unsubscribeAll(): Unsubscribing from notification with handle ${sub} failed`)
1231
+ }
1228
1232
 
1229
1233
  firstError = new ClientException(this, 'unsubscribeAll()', err)
1230
1234
  }
@@ -3654,7 +3658,7 @@ function _reconnect(forceDisconnect = false, isReconnecting = false) {
3654
3658
 
3655
3659
  debug(`_reconnect(): Connection and some subscriptions reinitialized. Connection is back.`)
3656
3660
  })
3657
-
3661
+
3658
3662
  this.emit('reconnect')
3659
3663
 
3660
3664
  resolve(res)
@@ -3941,7 +3945,9 @@ async function _onConnectionLost(socketFailure = false) {
3941
3945
  _console.call(this, 'WARNING: Connection was lost and setting autoReconnect=false. Quiting.')
3942
3946
  try {
3943
3947
  await this.disconnect(true)
3944
- } catch { }
3948
+ } catch (err) {
3949
+ debugD(`_onConnectionLost(): Error during disconnecting. Quiting.`)
3950
+ }
3945
3951
 
3946
3952
  return
3947
3953
  }
@@ -3961,7 +3967,7 @@ async function _onConnectionLost(socketFailure = false) {
3961
3967
  //Try to reconnect
3962
3968
  _reconnect.call(this, socketFailure, true)
3963
3969
  .then(res => {
3964
-
3970
+
3965
3971
  //Success -> remove timer
3966
3972
  _clearTimer(this._internals.reconnectionTimer)
3967
3973
 
@@ -3970,7 +3976,7 @@ async function _onConnectionLost(socketFailure = false) {
3970
3976
  //Reconnecting failed
3971
3977
  if (firstTime)
3972
3978
  _console.call(this, `WARNING: Reconnecting failed. Keeping trying in the background every ${this.settings.reconnectInterval} ms...`)
3973
-
3979
+
3974
3980
  //If this is still a valid timer, start over again
3975
3981
  if (this._internals.reconnectionTimer.id === timerId) {
3976
3982
  //Creating a new timer with the same id
@@ -4014,7 +4020,7 @@ function _clearTimer(timerObject) {
4014
4020
  //Increasing timer id
4015
4021
  timerObject.id = timerObject.id < Number.MAX_SAFE_INTEGER ? timerObject.id + 1 : 0;
4016
4022
  }
4017
-
4023
+
4018
4024
 
4019
4025
 
4020
4026
 
@@ -4290,7 +4296,7 @@ function _systemManagerStatePoller() {
4290
4296
  let oldState = this.metaData.systemManagerState
4291
4297
 
4292
4298
  //If the timer has changed, quit here
4293
- if (this._internals.systemManagerStatePoller.id !== timerId){
4299
+ if (this._internals.systemManagerStatePoller.id !== timerId) {
4294
4300
  return
4295
4301
  }
4296
4302
 
@@ -4349,7 +4355,7 @@ function _systemManagerStatePoller() {
4349
4355
  () => poller(this._internals.systemManagerStatePoller.id),
4350
4356
  this.settings.checkStateInterval
4351
4357
  )
4352
-
4358
+
4353
4359
  }
4354
4360
 
4355
4361
 
@@ -4722,32 +4728,32 @@ async function _parseDataType(data) {
4722
4728
  dataType.adsDataTypeStr = ADS.ADS_DATA_TYPES.toString(dataType.adsDataType)
4723
4729
  pos += 4
4724
4730
 
4725
- //27..30 Flags (AdsDataTypeFlags)
4726
- dataType.flags = data.readUInt16LE(pos)
4731
+ //28..31 Flags (AdsDataTypeFlags)
4732
+ dataType.flags = data.readUInt32LE(pos)
4727
4733
  dataType.flagsStr = ADS.ADS_DATA_TYPE_FLAGS.toStringArray(dataType.flags)
4728
4734
  pos += 4
4729
4735
 
4730
- //31..32 Name length
4736
+ //32..33 Name length
4731
4737
  dataType.nameLength = data.readUInt16LE(pos)
4732
4738
  pos += 2
4733
4739
 
4734
- //33..34 Type length
4740
+ //34..35 Type length
4735
4741
  dataType.typeLength = data.readUInt16LE(pos)
4736
4742
  pos += 2
4737
4743
 
4738
- //35..36 Comment length
4744
+ //36..37 Comment length
4739
4745
  dataType.commentLength = data.readUInt16LE(pos)
4740
4746
  pos += 2
4741
4747
 
4742
- //37..40 Array dimension
4748
+ //38..39 Array dimension
4743
4749
  dataType.arrayDimension = data.readUInt16LE(pos)
4744
4750
  pos += 2
4745
4751
 
4746
- //41..42 Subitem count
4752
+ //40..41 Subitem count
4747
4753
  dataType.subItemCount = data.readUInt16LE(pos)
4748
4754
  pos += 2
4749
4755
 
4750
- //43.... Name
4756
+ //42.... Name
4751
4757
  dataType.name = _trimPlcString(iconv.decode(data.slice(pos, pos + dataType.nameLength + 1), 'cp1252'))
4752
4758
  pos += dataType.nameLength + 1
4753
4759
 
@@ -5237,7 +5243,7 @@ function _parseJsObjectToBuffer(value, dataType, objectPathStr = '', isArraySubI
5237
5243
  //Struct or array subitem - Go through each subitem
5238
5244
  if ((dataType.arrayData.length === 0 || isArraySubItem) && dataType.subItems.length > 0) {
5239
5245
  buffer = Buffer.alloc(dataType.size)
5240
-
5246
+
5241
5247
  for (const subItem of dataType.subItems) {
5242
5248
  //Try the find the subitem from javascript object
5243
5249
  let key = null
@@ -6036,11 +6042,16 @@ function _parseAdsData(packet, data) {
6036
6042
  case ADS.ADS_COMMAND.ReadWrite:
6037
6043
  case ADS.ADS_COMMAND.Read:
6038
6044
 
6039
-
6040
6045
  //0..3 Ads error number
6041
6046
  ads.errorCode = data.readUInt32LE(pos)
6042
6047
  pos += 4
6043
6048
 
6049
+ if (data.byteLength <= 4) {
6050
+ ads.dataLength = 0
6051
+ ads.data = Buffer.alloc(0)
6052
+ break
6053
+ }
6054
+
6044
6055
  //4..7 Data length (bytes)
6045
6056
  ads.dataLength = data.readUInt32LE(pos)
6046
6057
  pos += 4
@@ -6051,8 +6062,6 @@ function _parseAdsData(packet, data) {
6051
6062
 
6052
6063
  break
6053
6064
 
6054
-
6055
-
6056
6065
  //-------------- Write ---------------
6057
6066
  case ADS.ADS_COMMAND.Write:
6058
6067
 
@@ -6062,8 +6071,6 @@ function _parseAdsData(packet, data) {
6062
6071
 
6063
6072
  break
6064
6073
 
6065
-
6066
-
6067
6074
  //-------------- Device info ---------------
6068
6075
  case ADS.ADS_COMMAND.ReadDeviceInfo:
6069
6076
 
@@ -6073,6 +6080,10 @@ function _parseAdsData(packet, data) {
6073
6080
 
6074
6081
  ads.data = {}
6075
6082
 
6083
+ if (data.byteLength <= 4) {
6084
+ break
6085
+ }
6086
+
6076
6087
  //4 Major version
6077
6088
  ads.data.majorVersion = data.readUInt8(pos)
6078
6089
  pos += 1
@@ -6090,10 +6101,6 @@ function _parseAdsData(packet, data) {
6090
6101
 
6091
6102
  break
6092
6103
 
6093
-
6094
-
6095
-
6096
-
6097
6104
  //-------------- Device status ---------------
6098
6105
  case ADS.ADS_COMMAND.ReadState:
6099
6106
 
@@ -6103,6 +6110,10 @@ function _parseAdsData(packet, data) {
6103
6110
 
6104
6111
  ads.data = {}
6105
6112
 
6113
+ if (data.byteLength <= 4) {
6114
+ break
6115
+ }
6116
+
6106
6117
  //4..5 ADS state
6107
6118
  ads.data.adsState = data.readUInt16LE(pos)
6108
6119
  ads.data.adsStateStr = ADS.ADS_STATE.toString(ads.data.adsState)
@@ -6114,9 +6125,6 @@ function _parseAdsData(packet, data) {
6114
6125
 
6115
6126
  break
6116
6127
 
6117
-
6118
-
6119
-
6120
6128
  //-------------- Add notification ---------------
6121
6129
  case ADS.ADS_COMMAND.AddNotification:
6122
6130
 
@@ -6126,15 +6134,16 @@ function _parseAdsData(packet, data) {
6126
6134
 
6127
6135
  ads.data = {}
6128
6136
 
6137
+ if (data.byteLength <= 4) {
6138
+ break
6139
+ }
6140
+
6129
6141
  //4..7 Notification handle
6130
6142
  ads.data.notificationHandle = data.readUInt32LE(pos)
6131
6143
  pos += 4
6132
6144
 
6133
6145
  break
6134
6146
 
6135
-
6136
-
6137
-
6138
6147
  //-------------- Delete notification ---------------
6139
6148
  case ADS.ADS_COMMAND.DeleteNotification:
6140
6149
 
@@ -6144,8 +6153,6 @@ function _parseAdsData(packet, data) {
6144
6153
 
6145
6154
  break
6146
6155
 
6147
-
6148
-
6149
6156
  //-------------- Notification ---------------
6150
6157
  case ADS.ADS_COMMAND.Notification:
6151
6158
 
@@ -6153,8 +6160,6 @@ function _parseAdsData(packet, data) {
6153
6160
 
6154
6161
  break
6155
6162
 
6156
-
6157
-
6158
6163
  //-------------- WriteControl ---------------
6159
6164
  case ADS.ADS_COMMAND.WriteControl:
6160
6165
 
@@ -6164,7 +6169,6 @@ function _parseAdsData(packet, data) {
6164
6169
 
6165
6170
  break
6166
6171
 
6167
-
6168
6172
  default:
6169
6173
  //Unknown command, return a custom error
6170
6174
  debug(`_parseAdsResponse: Unknown ads command in response: ${packet.ams.adsCommand}`)
@@ -6327,7 +6331,7 @@ async function _onAdsCommandReceived(packet) {
6327
6331
  parsedValue.timeStamp = stamp.timeStamp
6328
6332
 
6329
6333
  //Then lets call the users callback
6330
- sub.callback(
6334
+ sub.callback && sub.callback(
6331
6335
  parsedValue,
6332
6336
  sub
6333
6337
  )