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,290 @@
1
+ /**
2
+ ** Tcp server module.
3
+ * @module net/tcpserver.
4
+ * @author Hector E. Socarras.
5
+ * @version 1.0.0
6
+ */
7
+
8
+ const net = require('node:net');
9
+
10
+ //Default Server's Configuration object
11
+ const defaultCfg = {
12
+ port : 502,
13
+ maxConnections : 32,
14
+ accessControlEnable: false,
15
+ tcpCoalescingDetection : false
16
+ //connectionTimeout : 0
17
+ }
18
+
19
+ //No operation default function for listeners
20
+ const noop = () => {};
21
+
22
+ /**
23
+ * Class to wrap a node net server.
24
+ */
25
+ class TcpServer {
26
+ /**
27
+ * Create a Tcp Server.
28
+ * @param {object} netCfg configuration object.
29
+ */
30
+ constructor(netCfg = defaultCfg){
31
+
32
+ if(netCfg.port == undefined){ netCfg.port = defaultCfg.port}
33
+ if(netCfg.maxConnections == undefined){ netCfg.maxConnections = defaultCfg.maxConnections}
34
+ if(netCfg.accessControlEnable == undefined){ netCfg.accessControlEnable = defaultCfg.accessControlEnable}
35
+ if(netCfg.tcpCoalescingDetection == undefined){ netCfg.tcpCoalescingDetection = defaultCfg.tcpCoalescingDetection}
36
+
37
+ let self = this;
38
+
39
+ /**
40
+ * tcp server
41
+ * @type {Object}
42
+ */
43
+ this.coreServer = net.createServer();
44
+
45
+ //array whit connections
46
+ this.activeConnections = [];
47
+
48
+ /**
49
+ * port
50
+ * @type {number}
51
+ */
52
+ this.port = netCfg.port;
53
+
54
+ this.maxConnections = netCfg.maxConnections;
55
+
56
+ this.accessControlEnable = true;
57
+
58
+ this.tcpCoalescingDetection = netCfg.tcpCoalescingDetection;
59
+
60
+ /**
61
+ * Inactivity time to close a connection
62
+ * @type {number}
63
+ */
64
+ //this.connectionTimeout = netCfg.connectionTimeout;
65
+
66
+ //API Callback interfaces****************************************************************************************************************************************
67
+
68
+ /**
69
+ * function to executed when event data is emited
70
+ * @param {Buffer} data
71
+ */
72
+ this.onDataHook = noop;
73
+
74
+ /**
75
+ * function to executed when modbus message is detected
76
+ * @param {Buffer} data
77
+ */
78
+ this.onMbAduHook = noop;
79
+
80
+ /**
81
+ * function to executed when event listening is emited
82
+ */
83
+ this.onListeningHook = noop;
84
+ this.coreServer.on('listening', () => {
85
+ self.onListeningHook();
86
+ });
87
+
88
+ /**
89
+ * function to executed access control
90
+ * @param {Object} net.socket
91
+ */
92
+ this.onConnectionRequestHook = noop;
93
+
94
+ /**
95
+ * function to executed when event connection is emited
96
+ * @param {Object} net.socket
97
+ */
98
+ this.onConnectionAcceptedHook = noop;
99
+
100
+ /**
101
+ * function to executed when event error is emited
102
+ * @param {Object} error
103
+ */
104
+ this.onErrorHook = noop;
105
+ this.coreServer.on('error', (e) => {
106
+ self.onErrorHook(e);
107
+ });
108
+
109
+ /**
110
+ * function to executed when event close is emited
111
+ */
112
+ this.onServerCloseHook = noop;
113
+ this.coreServer.on('close', function(){
114
+
115
+ self.activeConnections = [];
116
+ self.onServerCloseHook();
117
+
118
+ });
119
+
120
+ /**
121
+ * function to executed when event socket#close is emited
122
+ */
123
+ this.onConnectionCloseHook = noop;
124
+
125
+ /**
126
+ * function to executed when event write is emited
127
+ * @param {Buffer} buff
128
+ */
129
+ this.onWriteHook = noop;
130
+ //******************************************************************************************************************************************************
131
+
132
+ //function for validating data****************************************************************
133
+ this.validateFrame = ()=>{ return false}
134
+
135
+ this.coreServer.on('connection', function(socket) {
136
+
137
+
138
+ //disabling nagle algorithm
139
+ socket.setNoDelay(true);
140
+
141
+ socket.on('data',function(data){
142
+
143
+
144
+ if(self.onDataHook instanceof Function){
145
+ self.onDataHook(socket, data);
146
+ }
147
+
148
+ //Active tcp coalesing detection for modbus tcp
149
+ if(self.tcpCoalescingDetection){
150
+ //one tcp message can have more than one modbus indication.
151
+ //each modbus tcp message have a length field
152
+ let messages = self.resolveTcpCoalescing(data);
153
+
154
+ messages.forEach((message) => {
155
+ if(self.onMbAduHook instanceof Function & self.validateFrame(message)){
156
+ self.onMbAduHook(socket, message);
157
+ }
158
+ })
159
+
160
+ }
161
+ //Non active tcp coalesing detection for modbus serial
162
+ else{
163
+
164
+ if(self.onMbAduHook instanceof Function & self.validateFrame(data)){
165
+ self.onMbAduHook(socket, data);
166
+ }
167
+
168
+ }
169
+
170
+
171
+ });
172
+
173
+ socket.on('error', (e) => {
174
+ if(self.onErrorHook instanceof Function){
175
+ self.onErrorHook(e);
176
+ }
177
+ });
178
+
179
+ socket.on('close',function(had_error){
180
+
181
+ var index = self.activeConnections.indexOf(socket);
182
+ //deleting socket from pool
183
+ self.activeConnections.splice(index,1);
184
+
185
+ if(self.onConnectionCloseHook instanceof Function){
186
+ self.onConnectionCloseHook(had_error);
187
+ }
188
+
189
+ });
190
+
191
+ //adding sockets to connections pool
192
+ self.activeConnections.push(socket)
193
+
194
+ if(self.onConnectionAcceptedHook instanceof Function){
195
+ self.onConnectionAcceptedHook(socket);
196
+ }
197
+
198
+ });
199
+
200
+ }
201
+
202
+ get maxConnections(){
203
+ return this.coreServer.maxConnections;
204
+ }
205
+
206
+ set maxConnections(max){
207
+ this.coreServer.maxConnections = max;
208
+ }
209
+
210
+ /**
211
+ * listening status
212
+ * @type {boolean}
213
+ */
214
+ get isListening(){
215
+ return this.coreServer.listening;
216
+ }
217
+
218
+
219
+ /**
220
+ * Start the tcp server
221
+ */
222
+ start (){
223
+ try {
224
+ this.coreServer.listen(this.port)
225
+ }
226
+ catch(error){
227
+ this.onErrorHook(error);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Stop the tcp server
233
+ */
234
+ stop (){
235
+ //cerrando el server
236
+ this.activeConnections.forEach(function(element){
237
+ element.destroy();
238
+ });
239
+
240
+ this.coreServer.close();
241
+ }
242
+
243
+ /**
244
+ * function to write in a conection. Callback to onWriteHook hook function.
245
+ * @param {number} socketIndex. Index to socket in connections array
246
+ * @param {buffer} data
247
+ */
248
+ write (socket, frame){
249
+ let self = this;
250
+
251
+ socket.write(frame, 'utf8', function(){
252
+ self.onWriteHook(socket, frame);
253
+ });
254
+ }
255
+
256
+ /**
257
+ * Split the input buffer in several indication buffer baset on length property
258
+ * of modbus tcp header. The goal of this funcion is suport several modbus indication
259
+ * on same tcp frame due to tcp coalesing.
260
+ * @param {Buffer Object} dataFrame
261
+ * @return {Buffer array}
262
+ */
263
+ resolveTcpCoalescing(dataFrame){
264
+ //get de first tcp header length
265
+ let indicationsList = [];
266
+
267
+ if (dataFrame.length > 7){
268
+ let expectedlength = dataFrame.readUInt16BE(4);
269
+ let indication = Buffer.alloc(expectedlength + 6);
270
+
271
+ if(dataFrame.length == expectedlength + 6){
272
+ dataFrame.copy(indication);
273
+ indicationsList.push(indication);
274
+ }
275
+ else if (dataFrame.length > expectedlength + 6){
276
+ dataFrame.copy(indication, 0, 0, expectedlength + 6);
277
+ indicationsList.push(indication);
278
+ indicationsList = indicationsList.concat(this.resolveTcpCoalescing(dataFrame.slice(expectedlength + 6)));
279
+ }
280
+ }
281
+
282
+ return indicationsList;
283
+ }
284
+
285
+
286
+ }
287
+
288
+ module.exports = TcpServer;
289
+
290
+
@@ -0,0 +1,242 @@
1
+ /**
2
+ ** UDP server module.
3
+ * @module net/udpnet.
4
+ * @author Hector E. Socarras.
5
+ * @version 1.0.0
6
+ */
7
+
8
+ const dgram = require('node:dgram');
9
+
10
+ //Default Server's Configuration object
11
+ const defaultCfg = {
12
+ port : 502,
13
+ maxConnections : 32,
14
+ accessControlEnable: false,
15
+ //connectionTimeout : 0,
16
+ udpType: 'udp4'
17
+ }
18
+
19
+ //No operation default function for listeners
20
+ const noop = () => {};
21
+
22
+ /**
23
+ * Class to wrap a node udp datagram socket.
24
+ */
25
+ class UdpServer {
26
+ /**
27
+ * Create a UdpServer.
28
+ * @param {object} netCfg configuration object.
29
+ */
30
+ constructor(netCfg = defaultCfg, type){
31
+
32
+ if(netCfg.port == undefined){ netCfg.port = defaultCfg.port}
33
+ if(netCfg.maxConnections == undefined){ netCfg.maxConnections = defaultCfg.maxConnections}
34
+ if(netCfg.accessControlEnable == undefined){ netCfg.accessControlEnable = defaultCfg.accessControlEnable}
35
+ if(netCfg.udpType != 'udp4' & netCfg.udpType != 'udp6'){ netCfg.udpType = defaultCfg.udpType}
36
+
37
+
38
+ var self = this;
39
+
40
+ /**
41
+ * udp server
42
+ * @type {Object}
43
+ */
44
+ this.coreServer = dgram.createSocket(netCfg.udpType);
45
+
46
+ //array whit connections
47
+ this.activeConnections = [];
48
+
49
+ /**
50
+ * port
51
+ * @type {number}
52
+ */
53
+ this.port = netCfg.port;
54
+
55
+ this.maxConnections = netCfg.maxConnections;
56
+
57
+ this.accessControlEnable = true;
58
+
59
+ this.tcpCoalescingDetection = netCfg.tcpCoalescingDetection;
60
+
61
+ /**
62
+ * listening status
63
+ * @type {boolean}
64
+ */
65
+ this.isListening = false;
66
+
67
+ //Hoocks functions***************************************************************************************************
68
+
69
+ /**
70
+ * function to executed when event data is emited
71
+ * @param {Buffer} data
72
+ */
73
+ this.onDataHook = noop;
74
+
75
+ /**
76
+ * function to executed when modbus message is detected
77
+ * @param {Buffer} data
78
+ */
79
+ this.onMbAduHook = noop;
80
+
81
+ this.coreServer.on('message', function(msg, rinfo){
82
+
83
+ //udp is conexionless protocol.
84
+
85
+ rinfo.remoteAddress = rinfo.address;
86
+ rinfo.remotePort = rinfo.port;
87
+
88
+ self.onDataHook(rinfo, msg);
89
+
90
+
91
+ //Active tcp coalesing detection for modbus tcp
92
+ if(self.tcpCoalescingDetection){
93
+ //one tcp message can have more than one modbus indication.
94
+ //each modbus tcp message have a length field
95
+ let messages = self.resolveTcpCoalescing(msg);
96
+
97
+ messages.forEach((message) => {
98
+ if(self.onMbAduHook instanceof Function & self.validateFrame(message)){
99
+ self.onMbAduHook(rinfo, message);
100
+ }
101
+ })
102
+
103
+ }
104
+ //Non active tcp coalesing detection for modbus serial
105
+ else{
106
+
107
+ if(self.onMbAduHook instanceof Function & self.validateFrame(msg)){
108
+ self.onMbAduHook(rinfo, msg);
109
+ }
110
+ }
111
+
112
+
113
+ });
114
+
115
+ /**
116
+ * function to executed when event listening is emited
117
+ */
118
+ this.onListeningHook = noop;
119
+ this.coreServer.on('listening', () => {
120
+ self.isListening = true;
121
+ self.onListeningHook();
122
+ });
123
+
124
+ /**
125
+ * function to executed access control
126
+ * @param {Object} net.socket
127
+ */
128
+ this.onConnectionRequestHook = noop;
129
+
130
+ /**
131
+ * function to executed when event connection is emited
132
+ * @param {Object} net.socket
133
+ */
134
+ this.onConnectionAcceptedHook = noop;
135
+
136
+ /**
137
+ * function to executed when event error is emited
138
+ * @param {Object} error
139
+ */
140
+ this.onErrorHook = noop;
141
+ this.coreServer.on('error', (e) => {
142
+ self.onErrorHook(e);
143
+ });
144
+
145
+ /**
146
+ * function to executed when event close is emited
147
+ */
148
+ this.onServerCloseHook = noop;
149
+ this.coreServer.on('close', function(){
150
+ self.isListening = false;
151
+ self.onServerCloseHook();
152
+
153
+ });
154
+
155
+ /**
156
+ * function to executed when event socket#close is emited
157
+ */
158
+ this.onConnectionCloseHook = noop;
159
+
160
+ /**
161
+ * function to executed when event write is emited
162
+ * @param {Buffer} buff
163
+ */
164
+ this.onWriteHook = noop;
165
+
166
+ //function for validating data****************************************************************
167
+ this.validateFrame = ()=>{ return false}
168
+
169
+ }
170
+
171
+ /**
172
+ * Start the tcp server
173
+ */
174
+ start (){
175
+ var self = this;
176
+
177
+ try {
178
+ this.coreServer.bind(this.port)
179
+ }
180
+ catch(error){
181
+ this.onError(error);
182
+ }
183
+
184
+ }
185
+
186
+ /**
187
+ * Stop the tcp server
188
+ */
189
+ stop (){
190
+ //cerrando el server
191
+ this.coreServer.close();
192
+ }
193
+
194
+ /**
195
+ * function to write in a conection
196
+ * @param {number} socketIndex. Index to socket in connections array
197
+ * @param {buffer} data
198
+ */
199
+ write (rinfo, frame){
200
+ let self = this;
201
+
202
+ self.coreServer.send(frame, 0, frame.length, rinfo.port, rinfo.address, function(){
203
+ if(self.onWrite){
204
+ self.onWrite(rinfo, frame);
205
+ }
206
+ })
207
+ }
208
+
209
+ /**
210
+ * Split the input buffer in several indication buffer baset on length property
211
+ * of modbus tcp header. The goal of this funcion is suport several modbus indication
212
+ * on same tcp frame due to tcp coalesing.
213
+ * @param {Buffer Object} dataFrame
214
+ * @return {Buffer array}
215
+ */
216
+ resolveTcpCoalescing(dataFrame){
217
+ //get de first tcp header length
218
+ let indicationsList = [];
219
+
220
+ if (dataFrame.length > 7){
221
+ let expectedlength = dataFrame.readUInt16BE(4);
222
+ let indication = Buffer.alloc(expectedlength + 6);
223
+
224
+ if(dataFrame.length == expectedlength + 6){
225
+ dataFrame.copy(indication);
226
+ indicationsList.push(indication);
227
+ }
228
+ else if (dataFrame.length > expectedlength + 6){
229
+ dataFrame.copy(indication, 0, 0, expectedlength + 6);
230
+ indicationsList.push(indication);
231
+ indicationsList = indicationsList.concat(this.resolveTcpCoalescing(dataFrame.slice(expectedlength + 6)));
232
+ }
233
+ }
234
+
235
+ return indicationsList;
236
+ }
237
+ }
238
+
239
+ //Old pattern for static members
240
+ UdpServer.prototype.validateFrame = require('./tcpserver').prototype.validateFrame;
241
+
242
+ module.exports = UdpServer;