nodbus-plus 0.8.2 → 1.0.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.
Files changed (101) hide show
  1. package/.readthedocs.yaml +35 -0
  2. package/LICENSE.md +21 -21
  3. package/README.md +163 -48
  4. package/docs/Makefile +20 -0
  5. package/docs/client/channel.rst +209 -0
  6. package/docs/client/nodbus_master_serial.rst +559 -0
  7. package/docs/client/nodbus_master_tcp.rst +573 -0
  8. package/docs/conf.py +41 -0
  9. package/docs/images/01-readcoils.png +0 -0
  10. package/docs/images/02-readinputs.png +0 -0
  11. package/docs/images/03-readholding.png +0 -0
  12. package/docs/images/04-readinputsreg.png +0 -0
  13. package/docs/images/05-writecoil.png +0 -0
  14. package/docs/images/06-writeregister.png +0 -0
  15. package/docs/images/15-writecoil.png +0 -0
  16. package/docs/images/16.png +0 -0
  17. package/docs/images/22-mask.png +0 -0
  18. package/docs/images/23.png +0 -0
  19. package/docs/images/7.png +0 -0
  20. package/docs/images/modbus_pdu.png +0 -0
  21. package/docs/images/serial_adu.png +0 -0
  22. package/docs/images/tcp_adu.png +0 -0
  23. package/docs/index.rst +30 -0
  24. package/docs/make.bat +35 -0
  25. package/docs/protocol/modbus_master.rst +276 -0
  26. package/docs/protocol/modbus_master_serial.rst +290 -0
  27. package/docs/protocol/modbus_master_tcp.rst +296 -0
  28. package/docs/protocol/modbus_server.rst +469 -0
  29. package/docs/protocol/modbus_server_serial.rst +543 -0
  30. package/docs/protocol/modbus_server_tcp.rst +365 -0
  31. package/docs/requirements.txt +4 -0
  32. package/docs/server/net_server.rst +242 -0
  33. package/docs/server/nodbus_serial_server.rst +652 -0
  34. package/docs/server/nodbus_tcp_server.rst +505 -0
  35. package/docs/starting.rst +192 -0
  36. package/docs/static/simple logo.jpg +0 -0
  37. package/package.json +39 -30
  38. package/samples/mb_serial_server.js +77 -0
  39. package/samples/mb_tcp_client.js +114 -0
  40. package/samples/mb_tcp_server.js +58 -0
  41. package/src/client/net/serialchannel.js +195 -0
  42. package/src/client/net/tcpchannel.js +233 -0
  43. package/src/client/net/udpchannel.js +243 -0
  44. package/src/client/nodbus_serial_client.js +577 -0
  45. package/src/client/nodbus_tcp_client.js +542 -0
  46. package/src/nodbus-plus.js +157 -110
  47. package/src/protocol/modbus_master.js +298 -961
  48. package/src/protocol/modbus_master_serial.js +247 -0
  49. package/src/protocol/modbus_master_tcp.js +219 -0
  50. package/src/protocol/modbus_server.js +936 -0
  51. package/src/protocol/modbus_server_serial.js +368 -0
  52. package/src/protocol/modbus_server_tcp.js +129 -0
  53. package/src/protocol/utils.js +296 -0
  54. package/src/server/net/serialserver.js +184 -0
  55. package/src/server/net/tcpserver.js +290 -0
  56. package/src/server/net/udpserver.js +242 -0
  57. package/src/server/nodbus_serial_server.js +238 -0
  58. package/src/server/nodbus_tcp_server.js +249 -0
  59. package/test/modbus_master.test.js +279 -0
  60. package/test/modbus_master_serial.test.js +124 -0
  61. package/test/modbus_master_tcp.test.js +178 -0
  62. package/test/modbus_server.test.js +506 -0
  63. package/test/modbus_server_serial.test.js +328 -0
  64. package/test/modbus_server_tcp.test.js +91 -0
  65. package/test/nodbus_client_serial.test.js +307 -0
  66. package/test/nodbus_client_tcp.test.js +334 -0
  67. package/test/nodbus_server_serial.test.js +255 -0
  68. package/test/nodbus_server_tcp.test.js +216 -0
  69. package/CHANGELOG.md +0 -27
  70. package/src/client/m_stcp_client.js +0 -214
  71. package/src/client/m_tcp_client.js +0 -234
  72. package/src/net/tcpclient.js +0 -173
  73. package/src/net/tcpserver.js +0 -329
  74. package/src/protocol/adu.js +0 -40
  75. package/src/protocol/ascii_adu.js +0 -139
  76. package/src/protocol/boolean_register.js +0 -78
  77. package/src/protocol/functions/Force_Multiple_Coils.js +0 -76
  78. package/src/protocol/functions/Force_Single_Coil.js +0 -54
  79. package/src/protocol/functions/Mask_Holding_Register.js +0 -47
  80. package/src/protocol/functions/Preset_Multiple_Registers.js +0 -53
  81. package/src/protocol/functions/Preset_Single_Register.js +0 -39
  82. package/src/protocol/functions/Read_Coil_Status.js +0 -59
  83. package/src/protocol/functions/Read_Holding_Registers.js +0 -52
  84. package/src/protocol/functions/Read_Input_Registers.js +0 -52
  85. package/src/protocol/functions/Read_Input_Status.js +0 -58
  86. package/src/protocol/mbap.js +0 -60
  87. package/src/protocol/modbus_device.js +0 -35
  88. package/src/protocol/modbus_slave.js +0 -522
  89. package/src/protocol/pdu.js +0 -70
  90. package/src/protocol/rtu_adu.js +0 -122
  91. package/src/protocol/serial_adu.js +0 -29
  92. package/src/protocol/tcp_adu.js +0 -84
  93. package/src/protocol/word_register.js +0 -122
  94. package/src/server/m_stcp_server.js +0 -310
  95. package/src/server/m_tcp_server.js +0 -295
  96. package/test/modbus-stcp-server-test.js +0 -72
  97. package/test/modbus-stcp-server-test1.js +0 -72
  98. package/test/modbus-tcp-client-test.js +0 -159
  99. package/test/modbus-tcp-server-test.js +0 -75
  100. package/test/modbus-tcp-server-test2.js +0 -75
  101. package/test/modbus_stcp_client.js +0 -149
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Tcp Client module.
3
+ * @module net/tcpclient.
4
+ * @author Hector E. Socarras.
5
+ * @version 1.0.0
6
+ */
7
+
8
+ const net = require('node:net');
9
+
10
+ //No operation default function for listeners
11
+ const noop = () => {};
12
+
13
+ //default config
14
+ defaultCfg = {
15
+
16
+ ip: '127.0.0.1',
17
+ port: 502,
18
+ tcpCoalescingDetection : false,
19
+ }
20
+
21
+ /**
22
+ * Class representing a tcp channel.
23
+ */
24
+ class TcpChannel {
25
+ /**
26
+ * Create a tcp client.
27
+ */
28
+ constructor(channelCfg){
29
+
30
+ let self = this;
31
+
32
+ if(channelCfg.port == undefined){ channelCfg.port = defaultCfg.port}
33
+ if(channelCfg.ip == undefined){ channelCfg.ip = defaultCfg.ip}
34
+ if(channelCfg.tcpCoalescingDetection == undefined){ channelCfg.tcpCoalescingDetection = defaultCfg.tcpCoalescingDetection}
35
+
36
+ /**
37
+ * prevent than server close de connection for idle time
38
+ * @type {bool}
39
+ */
40
+ this.keepAliveConnection = true;
41
+
42
+ this.name = channelCfg.name;
43
+
44
+ this.ip = channelCfg.ip;
45
+
46
+ this.port = channelCfg.port;
47
+
48
+ this.tcpCoalescingDetection = channelCfg.tcpCoalescingDetection;
49
+
50
+ /**
51
+ *net.socket Object
52
+ * @type {object}
53
+ */
54
+ this.coreChannel = new net.Socket();
55
+
56
+ //Hooks functions *****************************************************************************************************************************
57
+
58
+ /**
59
+ * function to executed when modbus message is detected
60
+ * @param {Buffer} data
61
+ */
62
+ this.onMbAduHook = noop;
63
+ this.onDataHook = noop;
64
+ this.coreChannel.on('data', (data)=>{
65
+
66
+ self.onDataHook(data);
67
+
68
+ //Active tcp coalesing detection for modbus tcp
69
+ if(self.tcpCoalescingDetection){
70
+
71
+ //one tcp message can have more than one modbus indication.
72
+ //each modbus tcp message have a length field
73
+ let messages = self.resolveTcpCoalescing(data);
74
+
75
+ messages.forEach((message, index) => {
76
+ if(self.validateFrame(message)){
77
+ self.onMbAduHook(message);
78
+ }
79
+ })
80
+
81
+ }
82
+ //Non active tcp coalesing detection for modbus serial
83
+ else{
84
+
85
+ if(self.validateFrame(data)){
86
+ self.onMbAduHook(data);
87
+ }
88
+
89
+ }
90
+ });
91
+
92
+ this.onConnectHook = noop;
93
+ this.coreChannel.on('connect', ()=>{
94
+ self.onConnectHook();
95
+ })
96
+
97
+ this.onErrorHook = noop;
98
+ this.coreChannel.on('error', (e)=>{
99
+ this.onErrorHook(e);
100
+ })
101
+
102
+ /*
103
+ this.onEndHook = noop;
104
+ this.coreChannel.on('end', () =>{
105
+
106
+ })
107
+
108
+ this.onTimeOutHook = noop;
109
+ */
110
+ this.onCloseHook = noop;
111
+ this.coreChannel.on('close', (e) =>{
112
+ self.onCloseHook();
113
+ })
114
+
115
+ this.onWriteHook = noop;
116
+
117
+ this.validateFrame = noop;
118
+
119
+ }
120
+
121
+ isConnected(){
122
+
123
+ if(this.coreChannel.pending == false){
124
+ return true;
125
+ }
126
+ else {
127
+ return false;
128
+ }
129
+
130
+ }
131
+
132
+ /**
133
+ * Init a connection with a server
134
+ * @returns {Promise} A promise that will be resolve once the connection is stablished with the socket as argument, or will
135
+ * be rejected with ip and port as parameters.
136
+ */
137
+ connect(){
138
+
139
+ let self = this;
140
+ let promise = new Promise(function(resolve, reject){
141
+
142
+ try{
143
+
144
+ function rejectConnection(e){
145
+
146
+ reject(self.ip, self.port);
147
+ }
148
+
149
+ self.coreChannel.once('error', rejectConnection)
150
+
151
+ self.coreChannel.connect(self.port,self.ip, ()=>{
152
+
153
+ self.coreChannel.removeListener('error', rejectConnection);
154
+ resolve(self.coreChannel);
155
+ });
156
+
157
+
158
+ }
159
+ catch(e){
160
+ self.onErrorHook(e);
161
+ reject(self.ip, self.port);
162
+ }
163
+ })
164
+
165
+ return promise;
166
+ }
167
+
168
+ disconnect(){
169
+ let self = this;
170
+
171
+ let promise = new Promise(function(resolve, reject){
172
+
173
+ self.coreChannel.end(()=>{
174
+ resolve()
175
+ });
176
+
177
+ })
178
+
179
+ return promise;
180
+
181
+ }
182
+
183
+ /**
184
+ * Write data to a server
185
+ * @param {Buffer} frame data to send to server.
186
+ * @returns {bool} True if success, otherwise false
187
+ * be rejected with ip and por as parameters.
188
+ */
189
+ write(frame){
190
+
191
+ let self = this;
192
+ if(self.isConnected() == false){
193
+ return false;
194
+ }
195
+ else{
196
+
197
+ let isSuccesfull
198
+
199
+ isSuccesfull = this.coreChannel.write(frame, 'utf8', function(){
200
+
201
+ self.onWriteHook(frame);
202
+
203
+ });
204
+
205
+ return isSuccesfull;
206
+ }
207
+ }
208
+
209
+ resolveTcpCoalescing(dataFrame){
210
+ //get de first tcp header length
211
+ let responseList = [];
212
+
213
+ if (dataFrame.length > 7){
214
+ let expectedlength = dataFrame.readUInt16BE(4);
215
+ let indication = Buffer.alloc(expectedlength + 6);
216
+
217
+ if(dataFrame.length == expectedlength + 6){
218
+ dataFrame.copy(indication);
219
+ responseList.push(indication);
220
+ }
221
+ else if (dataFrame.length > expectedlength + 6){
222
+ dataFrame.copy(indication, 0, 0, expectedlength + 6);
223
+ responseList.push(indication);
224
+ responseList = responseList.concat(this.resolveTcpCoalescing(dataFrame.slice(expectedlength + 6)));
225
+ }
226
+ }
227
+
228
+ return responseList;
229
+ }
230
+
231
+ }
232
+
233
+ module.exports = TcpChannel
@@ -0,0 +1,243 @@
1
+ /**
2
+ * UDP Client module.
3
+ * @module net/udpChannel.
4
+ * @author Hector E. Socarras.
5
+ * @version 1.0.0
6
+ */
7
+
8
+ const dgram = require('node:dgram');
9
+
10
+ //No operation default function for listeners
11
+ const noop = () => {};
12
+
13
+ //default config
14
+ defaultCfg = {
15
+
16
+ ip: '127.0.0.1',
17
+ port: 502,
18
+ tcpCoalescingDetection : false,
19
+ udpType: 'udp4'
20
+ }
21
+
22
+ /**
23
+ * Class representing a udp channel.
24
+ */
25
+ class UdpChannel {
26
+ constructor(channelCfg){
27
+
28
+ let self = this;
29
+
30
+ if(channelCfg.port == undefined){ channelCfg.port = defaultCfg.port}
31
+ if(channelCfg.ip == undefined){ channelCfg.ip = defaultCfg.ip}
32
+ if(channelCfg.tcpCoalescingDetection == undefined){ channelCfg.tcpCoalescingDetection = defaultCfg.tcpCoalescingDetection}
33
+ if(channelCfg.udpType == undefined){channelCfg.udpType = defaultCfg.udpType};
34
+
35
+ /**
36
+ * prevent than server close de connection for idle time
37
+ * @type {bool}
38
+ */
39
+ this.name = channelCfg.name;
40
+
41
+ this.ip = channelCfg.ip;
42
+
43
+ this.port = channelCfg.port;
44
+
45
+ this.tcpCoalescingDetection = channelCfg.tcpCoalescingDetection;
46
+
47
+
48
+ /**
49
+ *net.socket Object
50
+ * @type {object}
51
+ */
52
+ this.coreChannel = dgram.createSocket(channelCfg.udpType);
53
+
54
+ //Hooks functions *****************************************************************************************************************************
55
+ /**
56
+ * function to executed when modbus message is detected
57
+ * @param {Buffer} data
58
+ */
59
+ this.onMbAduHook = noop;
60
+ this.onDataHook = noop;
61
+ this.coreChannel.on('message', (data, rinfo)=>{
62
+
63
+ self.onDataHook(data);
64
+
65
+ //Active tcp coalesing detection for modbus tcp
66
+ if(self.tcpCoalescingDetection){
67
+
68
+ //one tcp message can have more than one modbus indication.
69
+ //each modbus tcp message have a length field
70
+ let messages = self.resolveTcpCoalescing(data);
71
+
72
+ messages.forEach((message, index) => {
73
+ if(self.validateFrame(message)){
74
+ self.onMbAduHook(message);
75
+ }
76
+ })
77
+
78
+ }
79
+ //Non active tcp coalesing detection for modbus serial
80
+ else{
81
+
82
+ if(self.validateFrame(data)){
83
+ self.onMbAduHook(data);
84
+ }
85
+
86
+ }
87
+ });
88
+
89
+ this.onConnectHook = noop;
90
+ this.coreChannel.on('connect', ()=>{
91
+ self.onConnectHook();
92
+ })
93
+
94
+ this.onErrorHook = noop;
95
+ this.coreChannel.on('error', (e)=>{
96
+ this.onErrorHook(e);
97
+ })
98
+
99
+ this.onCloseHook = noop;
100
+ /*
101
+ this.coreChannel.on('close', (e) =>{
102
+ self.onCloseHook();
103
+ })*/
104
+
105
+ this.onWriteHook = noop;
106
+
107
+ this.validateFrame = noop;
108
+
109
+ }
110
+
111
+ isConnected(){
112
+
113
+ try{
114
+
115
+ let remoteSocket = this.coreChannel.remoteAddress()
116
+
117
+ if (remoteSocket.address == this.ip){
118
+ return true;
119
+ }
120
+ else{
121
+ return false;
122
+ }
123
+ }
124
+ catch(e){
125
+ return false;
126
+ }
127
+ }
128
+
129
+
130
+ /**
131
+ * Init an asociation to a remote address
132
+ * @returns {Promise} A promise that will be resolve once the connection is stablished with the socket as argument, or will
133
+ * be rejected with ip and port as parameters.
134
+ */
135
+ connect(){
136
+
137
+ let self = this;
138
+
139
+ let promise = new Promise(function(resolve, reject){
140
+
141
+ try{
142
+
143
+ self.coreChannel.connect(self.port, self.ip, (e)=>{
144
+
145
+ if(e){
146
+ reject(self.ip, self.port);
147
+ }
148
+ else{
149
+ resolve(self.coreChannel);
150
+ }
151
+ })
152
+
153
+ }
154
+ catch(e){
155
+ self.onErrorHook(e);
156
+ reject(self.ip, self.port);
157
+ }
158
+ })
159
+
160
+ return promise;
161
+ }
162
+
163
+ disconnect(){
164
+
165
+ let self = this;
166
+
167
+ let promise = new Promise(function(resolve,reject){
168
+
169
+ if(self.isConnected() == true){
170
+ try{
171
+
172
+ self.coreChannel.disconnect();
173
+ self.onCloseHook();
174
+ resolve();
175
+ }
176
+ catch(e){
177
+ resolve()
178
+ }
179
+ }
180
+ else{
181
+ resolve();
182
+ }
183
+ })
184
+
185
+ return promise;
186
+
187
+ }
188
+
189
+ /**
190
+ * Write data to a server
191
+ * @param {Buffer} frame data to send to server.
192
+ * @returns {bool} True if success, otherwise false
193
+ * be rejected with ip and por as parameters.
194
+ */
195
+ write(frame){
196
+
197
+ let self = this;
198
+
199
+ if(self.isConnected() == false){
200
+ return false;
201
+ }
202
+ else{
203
+
204
+ this.coreChannel.send(frame, function(e){
205
+
206
+ if(e){
207
+ self.onErrorHook(e)
208
+ }
209
+ else{
210
+ self.onWriteHook(frame);
211
+ }
212
+
213
+ });
214
+
215
+ return true;
216
+ }
217
+
218
+ }
219
+
220
+ resolveTcpCoalescing(dataFrame){
221
+ //get de first tcp header length
222
+ let responseList = [];
223
+
224
+ if (dataFrame.length > 7){
225
+ let expectedlength = dataFrame.readUInt16BE(4);
226
+ let indication = Buffer.alloc(expectedlength + 6);
227
+
228
+ if(dataFrame.length == expectedlength + 6){
229
+ dataFrame.copy(indication);
230
+ responseList.push(indication);
231
+ }
232
+ else if (dataFrame.length > expectedlength + 6){
233
+ dataFrame.copy(indication, 0, 0, expectedlength + 6);
234
+ responseList.push(indication);
235
+ responseList = responseList.concat(this.resolveTcpCoalescing(dataFrame.slice(expectedlength + 6)));
236
+ }
237
+ }
238
+
239
+ return responseList;
240
+ }
241
+ }
242
+
243
+ module.exports = UdpChannel;