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,936 @@
1
+ /**
2
+ * Modbus Server Base Class module. Can only deal with modbus PDU.
3
+ * Implement the base class for a modbus server stack.
4
+ * @module protocol/modbus-server
5
+ * @author Hector E. Socarras.
6
+ * @version 1.0.0
7
+ */
8
+
9
+
10
+ const EventEmitter = require('node:events');
11
+ const { Buffer } = require('node:buffer');
12
+ const utils = require('./utils');
13
+
14
+ //define max number of coil, inputs or register
15
+ const MAX_ITEM_NUMBER = 65535;
16
+
17
+ //Default Server's Configuration object
18
+ const defaultCfg = {
19
+ inputs : 2048,
20
+ coils : 2048,
21
+ holdingRegisters : 2048,
22
+ inputRegisters : 2048
23
+ }
24
+
25
+ /**
26
+ * Class representing a modbus slave.
27
+ * @extends EventEmitter
28
+ */
29
+ class ModbusServer extends EventEmitter {
30
+ /**
31
+ * Create a Modbus Basic server.
32
+ * @param {object} mbServerCfg - Configuration object, must have the following properties:
33
+ * {
34
+ * inputs : {int} cuantity of inputs, 0 means that imputs uses same buffer thar input registers.
35
+ * coils : {int} cuantity of coils, 0 means that coils uses same buffer thar coils registers.
36
+ * holdingRegisters : {int} cuantity of holding registers with 2 bytes per register
37
+ * inputRegisters : {int} cuantity of input registers with 2 bytes per register
38
+ * }
39
+ */
40
+ constructor(mbServerCfg = defaultCfg){
41
+ super();
42
+
43
+ var self = this;
44
+
45
+ //arguments check
46
+ if(mbServerCfg.inputs == undefined){mbServerCfg.inputs = defaultCfg.inputs;}
47
+ if(mbServerCfg.coils == undefined){ mbServerCfg.coils = defaultCfg.coils;}
48
+ if(mbServerCfg.holdingRegisters == undefined){mbServerCfg.holdingRegisters = defaultCfg.holdingRegisters;}
49
+ if(mbServerCfg.inputRegisters == undefined){mbServerCfg.inputRegisters = defaultCfg.inputRegisters;}
50
+
51
+ /**
52
+ * Get server's modbus functions code supported
53
+ * @return {set object} a set objects with funcion codes suported for the server
54
+ *
55
+ */
56
+ this._internalFunctionCode = new Map();
57
+ this._internalFunctionCode.set(1, 'readCoilsService');
58
+ this._internalFunctionCode.set(2, 'readDiscreteInputsService');
59
+ this._internalFunctionCode.set(3, 'readHoldingRegistersService');
60
+ this._internalFunctionCode.set(4, 'readInputRegistersService');
61
+ this._internalFunctionCode.set(5, 'writeSingleCoilService');
62
+ this._internalFunctionCode.set(6, 'writeSingleRegisterService');
63
+ this._internalFunctionCode.set(15, 'writeMultipleCoilsService');
64
+ this._internalFunctionCode.set(16, 'writeMultipleRegistersService');
65
+ this._internalFunctionCode.set(22, 'maskWriteRegisterService');
66
+ this._internalFunctionCode.set(23, 'readWriteMultipleRegistersService');
67
+ Object.defineProperty(self, 'supportedFunctionCode',{
68
+ get: function(){
69
+ return this._internalFunctionCode.keys();
70
+ }
71
+ })
72
+
73
+
74
+ /**
75
+ * Holding Registers.
76
+ * @type {Object}
77
+ * @public
78
+ */
79
+ if(mbServerCfg.holdingRegisters <= MAX_ITEM_NUMBER & mbServerCfg.holdingRegisters > 0){
80
+ this.holdingRegisters = Buffer.alloc(mbServerCfg.holdingRegisters*2);
81
+ }
82
+ else if(mbServerCfg.holdingRegisters >= MAX_ITEM_NUMBER){
83
+ this.holdingRegisters = Buffer.alloc(MAX_ITEM_NUMBER*2);
84
+ }
85
+ else{
86
+ this.holdingRegisters = Buffer.alloc(defaultCfg.holdingRegisters*2);
87
+ }
88
+
89
+ /**
90
+ * Input Registers.
91
+ * @type {Buffer}
92
+ * @public
93
+ */
94
+ if(mbServerCfg.inputRegisters <= MAX_ITEM_NUMBER & mbServerCfg.inputRegisters > 0){
95
+ this.inputRegisters = Buffer.alloc(mbServerCfg.inputRegisters * 2);
96
+ }
97
+ else if(mbServerCfg.inputRegisters >= MAX_ITEM_NUMBER){
98
+ this.inputRegisters = Buffer.alloc(MAX_ITEM_NUMBER*2);
99
+ }
100
+ else{
101
+ this.inputRegisters = Buffer.alloc(defaultCfg.inputRegisters*2);
102
+ }
103
+
104
+ /**
105
+ * Inputs. Reference 1x.
106
+ * @type {Buffer}
107
+ * @public
108
+ */
109
+ if(mbServerCfg.inputs <= MAX_ITEM_NUMBER & mbServerCfg.inputs > 0){
110
+ this.inputs = Buffer.alloc(Math.ceil(mbServerCfg.inputs/8));
111
+ }
112
+ else if(mbServerCfg.inputs >= MAX_ITEM_NUMBER){
113
+ this.inputs = Buffer.alloc(Math.ceil(MAX_ITEM_NUMBER/8));
114
+ }
115
+ else if (mbServerCfg.inputs <= 0){
116
+ this.inputs = this.inputRegisters;
117
+ }
118
+ else{
119
+ this.inputs = Buffer.alloc(Math.ceil(defaultCfg.inputs/8));
120
+ }
121
+
122
+
123
+ /**
124
+ * Coils. Reference 0x
125
+ * @type {buffer}
126
+ * @public
127
+ */
128
+ if(mbServerCfg.coils <= MAX_ITEM_NUMBER & mbServerCfg.coils > 0){
129
+ this.coils = Buffer.alloc(Math.ceil(mbServerCfg.coils/8));
130
+ }
131
+ else if(mbServerCfg.coils >= MAX_ITEM_NUMBER){
132
+ this.coils = Buffer.alloc(Math.ceil(MAX_ITEM_NUMBER/8));
133
+ }
134
+ else if (mbServerCfg.coils <= 0){
135
+ this.coils = this.holdingRegisters;
136
+ }
137
+ else{
138
+ this.coils = Buffer.alloc(Math.ceil(defaultCfg.coils / 8));
139
+ }
140
+
141
+
142
+
143
+ }
144
+
145
+ /**
146
+ * @brief Main server function. Entry point for client request. Process request pdu, execute de service and return a response pdu.
147
+ * @param {Buffer} reqPduBuffe buffer containing a protocol data unit
148
+ * @fires ModbusServer#exception
149
+ * @fires ModbusServer#write
150
+ * @fires ModbusServer#error
151
+ * @return {Buffer} buffer containing a protocol data unit
152
+ */
153
+ processReqPdu(reqPduBuffer) {
154
+
155
+ let self = this;
156
+ let functionCode = reqPduBuffer[0];
157
+
158
+ //Check for function code
159
+ if(this._internalFunctionCode.has(functionCode)){
160
+
161
+ try {
162
+ //gets pdu data
163
+ let reqPduData = Buffer.alloc(reqPduBuffer.length - 1);
164
+ reqPduBuffer.copy(reqPduData,0,1);
165
+
166
+ let serviceName = this._internalFunctionCode.get(functionCode); //get de function code prossesing function
167
+ var resPduBuffer = this[serviceName](reqPduData); //execute service procesing
168
+
169
+ return resPduBuffer;
170
+ }
171
+ catch(e){
172
+ //reply modbus exception 4
173
+ resPduBuffer = this.makeExceptionResPdu(functionCode, 4); //Slave failure exception response
174
+ this.emit('error', e);
175
+ return resPduBuffer;
176
+ }
177
+ }
178
+ else{
179
+ //reply modbus exception 1
180
+ resPduBuffer = this.makeExceptionResPdu(functionCode, 1);
181
+ return resPduBuffer;
182
+ }
183
+
184
+ }
185
+
186
+
187
+ /**
188
+ * @brief Build a modbus exception response PDU
189
+ * @param {number} mbFunctionCode modbus function code
190
+ * @param {number} exceptionCode code of modbus exception
191
+ * @fires ModbusServer#exception
192
+ * @return {Buffer} Exception response pdu
193
+ */
194
+ makeExceptionResPdu(mbFunctionCode, exceptionCode){
195
+
196
+ //setting modbus function to exception
197
+ let excepFunctionCode = mbFunctionCode | 0x80;
198
+ //setting exeption code
199
+ let excepResBuffer = Buffer.alloc(2);
200
+ excepResBuffer[0] = excepFunctionCode;
201
+ excepResBuffer[1] = exceptionCode;
202
+
203
+ switch(exceptionCode){
204
+ case 1:
205
+ this.emit('exception', mbFunctionCode, exceptionCode, 'ILLEGAL FUNCTION');
206
+ break;
207
+ case 2:
208
+ this.emit('exception', mbFunctionCode, exceptionCode, 'ILLEGAL DATA ADDRESS');
209
+ break;
210
+ case 3:
211
+ this.emit('exception', mbFunctionCode, exceptionCode, 'ILLEGAL DATA VALUE');
212
+ break;
213
+ case 4:
214
+ this.emit('exception', mbFunctionCode, exceptionCode, 'SLAVE DEVICE FAILURE');
215
+ break;
216
+ case 5:
217
+ this.emit('exception', mbFunctionCode, exceptionCode, 'ACKNOWLEDGE');
218
+ break;
219
+ case 6:
220
+ this.emit('exception', mbFunctionCode, exceptionCode, 'SLAVE DEVICE BUSY');
221
+ break;
222
+ case 8:
223
+ this.emit('exception', mbFunctionCode, exceptionCode, 'MEMORY PARITY ERROR');
224
+ break;
225
+ case 0x0A:
226
+ this.emit('exception', mbFunctionCode, exceptionCode, 'GATEWAY PATH UNAVAILABLE');
227
+ break;
228
+ case 0x0B:
229
+ this.emit('exception', mbFunctionCode, exceptionCode, 'GATEWAY TARGET DEVICE FAILED TO RESPOND');
230
+ break;
231
+
232
+ }
233
+
234
+ return excepResBuffer;
235
+ }
236
+
237
+ /**
238
+ * @brief Function to implement Read Coil stauts service on server. Function code 01.
239
+ * @param {Buffer} pduReqData buffer containing only data from a request pdu
240
+ * @fires ModbusServer#exception
241
+ * @return {Buffer} resPduBuffer. Response pdu.
242
+ */
243
+ readCoilsService(pduReqData){
244
+
245
+ //Defining function code for this service
246
+ const FUNCTION_CODE = 1;
247
+
248
+ let resPduBuffer;
249
+
250
+ if(pduReqData.length == 4){
251
+ //registers to read
252
+ let registersToRead = pduReqData.readUInt16BE(2);
253
+
254
+ //Validating Data Value. Max number of coils to read is 2000 acording to Modbus Aplication Protocol V1.1b3 2006
255
+ if(registersToRead >=1 && registersToRead <= 2000){
256
+ //initial register. Example coil 20 addressing as 0x13 (19)
257
+ let startAddress = pduReqData.readUInt16BE(0);
258
+
259
+ //Validating data address
260
+ if(startAddress + registersToRead < this.coils.length * 8 & startAddress + registersToRead <= MAX_ITEM_NUMBER){
261
+
262
+
263
+ //Calculando cantidad de bytes de la respuesta 12%8=1
264
+ //ejemplo 12 coils necesitan 2 bytes
265
+ let byteCount = registersToRead % 8 ? Math.ceil(registersToRead/8) : (registersToRead/8);
266
+ let masks = [0x01, 0x02, 0x04, 0x08, 0x010, 0x20, 0x40, 0x80];
267
+ let values = Buffer.alloc(byteCount);
268
+ resPduBuffer = Buffer.alloc(byteCount + 2);
269
+ resPduBuffer[0] = FUNCTION_CODE;
270
+ resPduBuffer[1] = byteCount;
271
+
272
+ for(let i = 0; i < registersToRead; i++){
273
+ if(this.getBoolFromBuffer(this.coils, startAddress + i)){
274
+ values[Math.floor(i/8)] = values[Math.floor(i/8)] | masks[i%8];
275
+ }
276
+ }
277
+
278
+ values.copy(resPduBuffer, 2);
279
+
280
+
281
+ }
282
+ //Making modbus exeption 2
283
+ else{
284
+ //reply modbus exception 2
285
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 2);
286
+ }
287
+ }
288
+ else{
289
+ //reply modbus exception 3
290
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
291
+ }
292
+ }
293
+ else{
294
+ //reply modbus exception 3
295
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
296
+ }
297
+ return resPduBuffer;
298
+ }
299
+
300
+ /**
301
+ * @brief Function to implement Read Input status service on server. Function code 02.
302
+ * @param {Buffer} pduReqData buffer containing only data from a request pdu
303
+ * @fires ModbusServer#exception
304
+ * @return {Buffer} resPduBuffer. Response pdu.
305
+ */
306
+ readDiscreteInputsService(pduReqData){
307
+
308
+ //Defining function code for this service
309
+ const FUNCTION_CODE = 2;
310
+
311
+ let resPduBuffer;
312
+ if(pduReqData.length == 4){
313
+ //registers to read
314
+ let registersToRead = pduReqData.readUInt16BE(2);
315
+
316
+ //Validating Data Value. Max number of inputss to read is 2000 acording to Modbus Aplication Protocol V1.1b3 2006
317
+ if(registersToRead >=1 && registersToRead <= 2000){
318
+ //initial register. Example coil 20 addressing as 0x13 (19)
319
+ let startAddress = pduReqData.readUInt16BE(0);
320
+
321
+ //Validating data address
322
+ if(startAddress + registersToRead < this.inputs.length * 8 & startAddress + registersToRead <= MAX_ITEM_NUMBER){
323
+
324
+ //Calculando cantidad de bytes de la respuesta 12%8=1
325
+ //example 12 inputss needs 2 bytes
326
+ let byteCount = registersToRead % 8 ? Math.ceil(registersToRead/8) : (registersToRead/8);
327
+ let masks = [0x01, 0x02, 0x04, 0x08, 0x010, 0x20, 0x40, 0x80];
328
+ let values = Buffer.alloc(byteCount);
329
+ resPduBuffer = Buffer.alloc(byteCount + 2);
330
+ resPduBuffer[0] = FUNCTION_CODE;
331
+ resPduBuffer[1] = byteCount;
332
+
333
+ for(let i = 0; i < registersToRead; i++){
334
+ if(this.getBoolFromBuffer(this.inputs, startAddress + i)){
335
+ values[Math.floor(i/8)] = values[Math.floor(i/8)] | masks[i%8];
336
+ }
337
+ }
338
+
339
+ values.copy(resPduBuffer, 2);
340
+ }
341
+ //Making modbus exeption 2
342
+ else{
343
+ //reply modbus exception 2
344
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 2);
345
+ }
346
+ }
347
+ //Making modbus exeption 3
348
+ else{
349
+ //reply modbus exception 3
350
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
351
+ }
352
+ }
353
+ else{
354
+ //reply modbus exception 3
355
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
356
+ }
357
+ return resPduBuffer;
358
+ }
359
+
360
+ /**
361
+ * @brief Function to implement Read Holdings registers service on server. Function code 03.
362
+ * @param {Buffer} pduReqData buffer containing only data from a request pdu
363
+ * @fires ModbusServer#exception
364
+ * @return {Buffer} resPduBuffer. Response pdu.
365
+ */
366
+ readHoldingRegistersService(pduReqData){
367
+
368
+ //Defining function code for this service
369
+ const FUNCTION_CODE = 3;
370
+
371
+ let resPduBuffer;
372
+
373
+ if(pduReqData.length == 4){
374
+ //registers to read
375
+ let registersToRead = pduReqData.readUInt16BE(2);
376
+
377
+ //Validating Data Value. Max number of registers to read is 125 acording to Modbus Aplication Protocol V1.1b3 2006
378
+ if(registersToRead >=1 && registersToRead <= 0x007D){
379
+ //initial register.
380
+ let startAddress = pduReqData.readUInt16BE(0);
381
+
382
+ //Validating data address
383
+ if(startAddress + registersToRead < this.holdingRegisters.length / 2 ){
384
+
385
+ //Calculando cantidad de bytes de la respuesta
386
+ //example 12 registers needs 2 bytes
387
+ let byteCount = registersToRead * 2;
388
+ let values = Buffer.alloc(byteCount);
389
+ resPduBuffer = Buffer.alloc(byteCount + 2);
390
+ resPduBuffer[0] = FUNCTION_CODE;
391
+ resPduBuffer[1] = byteCount;
392
+
393
+ for(let i = 0; i < registersToRead; i++){
394
+ let word = this.getWordFromBuffer(this.holdingRegisters, startAddress + i);
395
+ word.copy(values, i * 2) ;
396
+ }
397
+
398
+ values.copy(resPduBuffer, 2);
399
+
400
+ }
401
+ //Making modbus exeption 2
402
+ else{
403
+ //reply modbus exception 3
404
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 2);
405
+ }
406
+ }
407
+ else{
408
+ //reply modbus exception 3
409
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
410
+ }
411
+ }
412
+ else{
413
+ //reply modbus exception 3
414
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
415
+ }
416
+ return resPduBuffer;
417
+ }
418
+
419
+ /**
420
+ * @brief Function to implement Read Input registers service on server. Function code 04.
421
+ * @param {Buffer} pduReqData buffer containing only data from a request pdu
422
+ * @fires ModbusServer#exception
423
+ * @return {Buffer} resPduBuffer. Response pdu.
424
+ */
425
+ readInputRegistersService(pduReqData){
426
+
427
+ //Defining function code for this service
428
+ const FUNCTION_CODE = 4;
429
+
430
+ let resPduBuffer;
431
+ if(pduReqData.length == 4){
432
+ //registers to read
433
+ let registersToRead = pduReqData.readUInt16BE(2);
434
+
435
+ //Validating Data Value. Max number of registers to read is 125 acording to Modbus Aplication Protocol V1.1b3 2006
436
+ if(registersToRead >=1 && registersToRead <= 0x007D){
437
+ //initial register.
438
+ let startAddress = pduReqData.readUInt16BE(0);
439
+
440
+ //Validating data address
441
+ if(startAddress + registersToRead < this.inputRegisters.length / 2 ){
442
+
443
+ //Calculando cantidad de bytes de la respuesta
444
+ //example 12 registers needs 2 bytes
445
+ let byteCount = registersToRead * 2;
446
+ let values = Buffer.alloc(byteCount);
447
+ resPduBuffer = Buffer.alloc(byteCount + 2);
448
+ resPduBuffer[0] = FUNCTION_CODE;
449
+ resPduBuffer[1] = byteCount;
450
+
451
+ for(let i = 0; i < registersToRead; i++){
452
+ let word = this.getWordFromBuffer(this.inputRegisters, startAddress + i);
453
+ word.copy(values, i * 2) ;
454
+ }
455
+
456
+ values.copy(resPduBuffer, 2);
457
+
458
+ }
459
+ //Making modbus exeption 2
460
+ else{
461
+ //reply modbus exception 3
462
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 2);
463
+ }
464
+ }
465
+ else{
466
+ //reply modbus exception 3
467
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
468
+ }
469
+ }
470
+ else{
471
+ //reply modbus exception 3
472
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
473
+ }
474
+ return resPduBuffer;
475
+ }
476
+
477
+ /**
478
+ * @brief Function to implement force single Coil service on server. Function code 05.
479
+ * @param {Buffer} pduReqData buffer containing only data from a request pdu
480
+ * @fires ModbusServer#exception
481
+ * @fires ModbusServer#write-coils
482
+ * @return {Buffer} resPduBuffer. Response pdu.
483
+ */
484
+ writeSingleCoilService(pduReqData){
485
+
486
+ //Defining function code for this service
487
+ const FUNCTION_CODE = 5;
488
+
489
+ let resPduBuffer;
490
+ if(pduReqData.length == 4){
491
+ //value to write
492
+ let value = pduReqData.readUInt16BE(2);
493
+
494
+ //Validating Data Value. output value must be 0x00 or 0xFF00 see Modbus Aplication Protocol V1.1b3 2006
495
+ if(value == 0x00 || value == 0xFF00){
496
+
497
+ //coil to write
498
+ let targetCoil = pduReqData.readUInt16BE(0);
499
+
500
+ //Validating data address
501
+ if(targetCoil < this.coils.length * 8 & targetCoil <= MAX_ITEM_NUMBER){
502
+
503
+ resPduBuffer = Buffer.alloc(5);
504
+ resPduBuffer[0] = FUNCTION_CODE;
505
+
506
+ //writing values on register
507
+ this.setBoolToBuffer(value, this.coils, targetCoil);
508
+ pduReqData.copy(resPduBuffer, 1);
509
+
510
+ //creating object of values writed
511
+ //let values = new Map();
512
+ //let coilValue = this.getBoolFromBuffer(this.coils, targetCoil);
513
+ //values.set(targetCoil, coilValue);
514
+ //telling user app that some coils was writed
515
+ this.emit('write-coils', targetCoil, 1);
516
+
517
+ }
518
+ //Making modbus exeption 2
519
+ else{
520
+ //reply modbus exception 3
521
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 2);
522
+ }
523
+ }
524
+ else{
525
+ //reply modbus exception 3
526
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
527
+ }
528
+ }
529
+ else{
530
+ //reply modbus exception 3
531
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
532
+ }
533
+
534
+ return resPduBuffer;
535
+ }
536
+
537
+ /**
538
+ * @brief Function to implement write single Register service on server. Function code 06.
539
+ * @param {Buffer} pduReqData buffer containing only data from a request pdu
540
+ * @fires ModbusServer#exception
541
+ * @fires ModbusServer#write-registers
542
+ * @return {Buffer} resPduBuffer. Response pdu.
543
+ */
544
+ writeSingleRegisterService(pduReqData){
545
+
546
+ //Defining function code for this service
547
+ const FUNCTION_CODE = 6;
548
+
549
+ let resPduBuffer;
550
+
551
+
552
+ //Validating Data Value. output value must be 0x00 or 0xFF00 see Modbus Aplication Protocol V1.1b3 2006
553
+ if(pduReqData.length == 4){
554
+
555
+ //registers to write
556
+ let value = Buffer.alloc(2);
557
+ value[0] = pduReqData[2];
558
+ value[1] = pduReqData[3];
559
+
560
+ //register to write
561
+ let targetRegister = pduReqData.readUInt16BE(0);
562
+
563
+ //Validating data address
564
+ if(targetRegister < this.holdingRegisters.length / 2){
565
+
566
+ resPduBuffer = Buffer.alloc(5);
567
+ resPduBuffer[0] = FUNCTION_CODE;
568
+
569
+ //writing values on register
570
+ this.setWordToBuffer(value, this.holdingRegisters, targetRegister);
571
+ pduReqData.copy(resPduBuffer, 1);
572
+
573
+ //creating object of values writed
574
+ //let values = new Map();
575
+ //let registerValue = this.getWordFromBuffer(this.holdingRegisters, targetRegister);
576
+ //values.set(targetRegister, registerValue);
577
+ //telling user app that some coils was writed
578
+ this.emit('write-registers', targetRegister, 1);
579
+
580
+ }
581
+ else{
582
+ //reply modbus exception 2
583
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 2);
584
+ }
585
+ }
586
+ else{
587
+ //reply modbus exception 3
588
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
589
+ }
590
+
591
+ return resPduBuffer;
592
+ }
593
+
594
+ /**
595
+ * @brief Function to implement write multiple Coils service on server. Function code 15.
596
+ * @param {Buffer} pduReqData buffer containing only data from a request pdu
597
+ * @fires ModbusServer#exception
598
+ * @fires ModbusServer#write-coils
599
+ * @return {Buffer} resPduBuffer. Response pdu.
600
+ */
601
+ writeMultipleCoilsService(pduReqData){
602
+
603
+ //Defining function code for this service
604
+ const FUNCTION_CODE = 15;
605
+
606
+ let resPduBuffer;
607
+ if(pduReqData.length >=6){
608
+ //amount coils to write
609
+ let cuantityOfOutputs = pduReqData.readUInt16BE(2);
610
+ //byte count
611
+ let byteCount = pduReqData[4];
612
+ //values to force
613
+ let outputValues = pduReqData.subarray(5);
614
+
615
+ //Validating Data Value. output value must be 0x00 or 0xFF00 see Modbus Aplication Protocol V1.1b3 2006
616
+ if(cuantityOfOutputs >= 1 && cuantityOfOutputs <= 0x07B0 && byteCount == Math.ceil(cuantityOfOutputs/8) && byteCount == outputValues.length){
617
+
618
+ //start
619
+ let startingAddress = pduReqData.readUInt16BE(0);
620
+
621
+ //Validating data address
622
+ if(startingAddress + cuantityOfOutputs < this.coils.length * 8 & (startingAddress + cuantityOfOutputs) <= MAX_ITEM_NUMBER){
623
+
624
+ resPduBuffer = Buffer.alloc(5);
625
+ resPduBuffer[0] = FUNCTION_CODE;
626
+
627
+ //writing values on register
628
+ for(let i=0; i < cuantityOfOutputs; i++){
629
+ let value = this.getBoolFromBuffer(outputValues, i);
630
+ this.setBoolToBuffer(value, this.coils, startingAddress + i);
631
+ }
632
+ pduReqData.copy(resPduBuffer, 1);
633
+
634
+ //creating object of values writed
635
+ /*let values = new Map();
636
+ for(let i = 0; i < cuantityOfOutputs; i++){
637
+ let coilValue = this.getBoolFromBuffer(this.coils, startingAddress + i);
638
+ values.set(startingAddress + i, coilValue);
639
+ } */
640
+ //telling user app that some coils was writed
641
+ this.emit('write-coils', startingAddress, cuantityOfOutputs);
642
+
643
+ }
644
+ //Making modbus exeption 2
645
+ else{
646
+ //reply modbus exception 3
647
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 2);
648
+ }
649
+ }
650
+ else{
651
+ //reply modbus exception 3
652
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
653
+ }
654
+ }
655
+ else{
656
+ //reply modbus exception 3
657
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
658
+ }
659
+ return resPduBuffer;
660
+ }
661
+
662
+ /**
663
+ * @brief Function to implement write multiple registers service on server. Function code 16.
664
+ * @param {Buffer} pduReqData buffer containing only data from a request pdu
665
+ * @fires ModbusServer#exception
666
+ * @fires ModbusServer#write
667
+ * @return {Buffer} resPduBuffer. Response pdu.
668
+ */
669
+ writeMultipleRegistersService(pduReqData){
670
+
671
+ //Defining function code for this service
672
+ const FUNCTION_CODE = 16;
673
+
674
+ let resPduBuffer;
675
+
676
+ if(pduReqData.length >=6){
677
+ //amount to write
678
+ let cuantityOfRegisters = pduReqData.readUInt16BE(2);
679
+ //byte count
680
+ let byteCount = pduReqData[4];;
681
+ //values to force
682
+ let registerValues = pduReqData.subarray(5);
683
+
684
+ //Validating Data Value. output value must be 0x00 or 0xFF00 see Modbus Aplication Protocol V1.1b3 2006
685
+ if(cuantityOfRegisters >= 1 && cuantityOfRegisters <= 0x07B0 && byteCount == cuantityOfRegisters*2 & byteCount == registerValues.length){
686
+
687
+ //start
688
+ let startingAddress = pduReqData.readUInt16BE(0);
689
+
690
+ //Validating data address
691
+ if(startingAddress + cuantityOfRegisters < this.holdingRegisters.length / 2 ){
692
+
693
+ resPduBuffer = Buffer.alloc(5);
694
+ resPduBuffer[0] = FUNCTION_CODE;
695
+
696
+ //writing values on register
697
+ for(let i=0; i < cuantityOfRegisters; i++){
698
+ let value = this.getWordFromBuffer(registerValues, i);
699
+ this.setWordToBuffer(value, this.holdingRegisters, startingAddress + i);
700
+ }
701
+ pduReqData.copy(resPduBuffer, 1);
702
+
703
+ //creating object of values writed
704
+ /*let values = new Map();
705
+ for(let i = 0; i < cuantityOfRegisters; i++){
706
+ let registerValue = this.getWordFromBuffer(this.holdingRegisters, startingAddress + i);
707
+ values.set(startingAddress + i, registerValue);
708
+ } */
709
+ //telling user app that some coils was writed
710
+ this.emit('write-registers', startingAddress, cuantityOfRegisters);
711
+
712
+ }
713
+ //Making modbus exeption 2
714
+ else{
715
+ //reply modbus exception 3
716
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 2);
717
+ }
718
+ }
719
+ else{
720
+ //reply modbus exception 3
721
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
722
+ }
723
+ }
724
+ else{
725
+ //reply modbus exception 3
726
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
727
+ }
728
+
729
+ return resPduBuffer;
730
+ }
731
+
732
+ /**
733
+ * @brief Function to implement mask holding register service on server. Function code 22.
734
+ * @param {Buffer} pduReqData buffer containing only data from a request pdu
735
+ * @fires ModbusServer#exception
736
+ * @fires ModbusServer#write-registers
737
+ * @return {Buffer} resPduBuffer. Response pdu.
738
+ */
739
+ maskWriteRegisterService(pduReqData){
740
+
741
+ //Defining function code for this service
742
+ const FUNCTION_CODE = 22;
743
+
744
+ let resPduBuffer;
745
+
746
+ //Validating data address
747
+ if(pduReqData.length == 6 ){
748
+
749
+ //register to mask
750
+ let referenceAddress = pduReqData.readUInt16BE(0);
751
+
752
+ if(referenceAddress < this.holdingRegisters.length / 2){
753
+
754
+ //masks
755
+ let andMask = pduReqData.readUInt16BE(2);
756
+ let orMask = pduReqData.readUInt16BE(4);
757
+
758
+ resPduBuffer = Buffer.alloc(7);
759
+ resPduBuffer[0] = FUNCTION_CODE;
760
+
761
+ //writing values on register
762
+ let actualValue = this.getWordFromBuffer(this.holdingRegisters, referenceAddress);
763
+ let maskValue = Buffer.alloc(2);
764
+ maskValue.writeUint16BE((actualValue.readUint16BE() & andMask) | (orMask & ~andMask));
765
+ this.setWordToBuffer(maskValue, this.holdingRegisters, referenceAddress)
766
+
767
+ pduReqData.copy(resPduBuffer, 1);
768
+
769
+ //creating object of values writed
770
+ //let values = new Map();
771
+ //let registerValue = this.getWordFromBuffer(this.holdingRegisters, referenceAddress);
772
+ //values.set(referenceAddress, registerValue);
773
+ //telling user app that some coils was writed
774
+ this.emit('write-registers', referenceAddress, 1);
775
+
776
+ }
777
+ else{
778
+ //reply modbus exception 2
779
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 2);
780
+ }
781
+ }
782
+ else{
783
+ //reply modbus exception 3
784
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
785
+ }
786
+
787
+ return resPduBuffer;
788
+ }
789
+
790
+ /**
791
+ * Function to implement read and write multiple registers service on server. Function code 23.
792
+ * @param {Buffer} pduReqData buffer containing only data from a request pdu
793
+ * @fires ModbusServer#exception
794
+ * @fires ModbusServer#write-registers
795
+ * @return {Buffer} resPduBuffer. Response pdu.
796
+ */
797
+ readWriteMultipleRegistersService(pduReqData){
798
+
799
+ //Defining function code for this service
800
+ const FUNCTION_CODE = 23;
801
+
802
+ let resPduBuffer;
803
+
804
+ if(pduReqData.length >= 11){
805
+
806
+ //values to read and write
807
+ let cuantityToRead = pduReqData.readUInt16BE(2);
808
+ let cuantityToWrite = pduReqData.readUInt16BE(6);
809
+ //byte count
810
+ let byteCount = pduReqData[8];
811
+ //values to force
812
+ let writeRegisterValues = pduReqData.subarray(9);
813
+
814
+ //Validating Data Value. See Modbus Aplication Protocol V1.1b3 2006
815
+ if(cuantityToRead > 0 && cuantityToRead <= 0x7D && cuantityToWrite > 0 && cuantityToWrite <= 0x79 && byteCount == cuantityToWrite*2 & byteCount == writeRegisterValues.length){
816
+
817
+ //starting addresses
818
+ let readStartingAddress = pduReqData.readUInt16BE(0);
819
+ let writeStartingAddress = pduReqData.readUInt16BE(4);
820
+
821
+ //Validating data address
822
+ if(readStartingAddress + cuantityToRead < this.holdingRegisters.length / 2 && writeStartingAddress + cuantityToWrite < this.holdingRegisters.length / 2){
823
+
824
+ //Calculando cantidad de bytes de la respuesta
825
+ //example 12 registers needs 2 bytes
826
+ let byteCount = cuantityToRead * 2;
827
+ let readValues = Buffer.alloc(byteCount);
828
+ resPduBuffer = Buffer.alloc(byteCount + 2);
829
+ resPduBuffer[0] = FUNCTION_CODE;
830
+ resPduBuffer[1] = byteCount;
831
+
832
+ for(let i = 0; i < cuantityToRead; i++){
833
+ let word = this.getWordFromBuffer(this.holdingRegisters, readStartingAddress + i);
834
+ word.copy(readValues, i * 2) ;
835
+ }
836
+
837
+ readValues.copy(resPduBuffer, 2);
838
+
839
+ //writing values on register
840
+ for(let i=0; i < cuantityToWrite; i++){
841
+ let value = this.getWordFromBuffer(writeRegisterValues, i);
842
+ this.setWordToBuffer(value, this.holdingRegisters, writeStartingAddress + i);
843
+ }
844
+
845
+ //creating object of values writed
846
+ /*let values = new Map();
847
+ for(let i = 0; i < cuantityToWrite; i++){
848
+ let registerValue = this.getWordFromBuffer(this.holdingRegisters, writeStartingAddress + i);
849
+ values.set(writeStartingAddress + i, registerValue);
850
+ } */
851
+ //telling user app that some coils was writed
852
+ this.emit('write-registers', writeStartingAddress, cuantityToWrite);
853
+
854
+ }
855
+ //Making modbus exeption 2
856
+ else{
857
+ //reply modbus exception 3
858
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 2);
859
+ }
860
+ }
861
+ else{
862
+ //reply modbus exception 3
863
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
864
+ }
865
+ }
866
+ else{
867
+ //reply modbus exception 3
868
+ resPduBuffer = this.makeExceptionResPdu(FUNCTION_CODE, 3);
869
+ }
870
+
871
+ return resPduBuffer;
872
+ }
873
+
874
+ /**
875
+ * Low level api function to get a boolean value from buffer.
876
+ * @param {Buffer} targetBuffer buffer object to read
877
+ * @param {number} offset integer value with bit address.
878
+ * @return {boolean} bit value
879
+ * @throws {RangeError} if offset is out of buffer's bound.
880
+ */
881
+ getBoolFromBuffer(targetBuffer, offset = 0){
882
+
883
+ if(offset < targetBuffer.length * 8){
884
+
885
+ let targetByte = targetBuffer[Math.floor(offset/8)]; //Byte where the bit is place inside the buffer
886
+ let byteOffset = offset % 8; //offset of bit inside the byte
887
+ let masks = [0x01, 0x02, 0x04, 0x08, 0x010, 0x20, 0x40, 0x80];
888
+
889
+ let value = (targetByte & masks[byteOffset]) > 0;
890
+
891
+ return value;
892
+
893
+ }
894
+ else{
895
+ throw new RangeError("offset is out of buffer bounds");
896
+ }
897
+ }
898
+
899
+ /**
900
+ * Low level api function to set a boolean value into a buffer.
901
+ * @param {bool} value boolean value to write
902
+ * @param {Buffer} targetBuffer buffer object to read
903
+ * @param {number} offset integer value with bit address.
904
+ * @throws {RangeError} if offset is out of buffer's bound.
905
+ */
906
+ setBoolToBuffer(value, targetBuffer, offset = 0){
907
+
908
+ if(offset < targetBuffer.length * 8){
909
+
910
+ let targetOffset = Math.floor(offset / 8); //byte inside the buffer where the bit is placed
911
+ let byteOffset = offset % 8; //offset inside the byte
912
+ let masks = [0x01, 0x02, 0x04, 0x08, 0x010, 0x20, 0x40, 0x80];
913
+
914
+ let previousValue = targetBuffer[targetOffset];
915
+
916
+ if(value){
917
+ targetBuffer[targetOffset] = previousValue | masks[byteOffset];
918
+ }
919
+ else{
920
+ targetBuffer[targetOffset] = previousValue & (~masks[byteOffset]);
921
+ }
922
+
923
+ }
924
+ else{
925
+ throw new RangeError("offset is out of buffer bounds");
926
+ }
927
+ }
928
+
929
+ }
930
+
931
+ ModbusServer.prototype.getWordFromBuffer = utils.getWordFromBuffer;
932
+
933
+ ModbusServer.prototype.setWordToBuffer = utils.setWordToBuffer;
934
+
935
+
936
+ module.exports = ModbusServer;