modbus-webserial 0.9.2 → 0.10.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/README.md +10 -4
- package/dist/index.d.ts +66 -1
- package/dist/index.js +181 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ console.log('HR0=', data[0], 'HR1=', data[1]);
|
|
|
32
32
|
// Write values to holding registers 0x00 and 0x01
|
|
33
33
|
await client.writeRegisters(0, [0x0A, 0x0B]);
|
|
34
34
|
```
|
|
35
|
-
**Can also be used *without* WebSerial for building modbus frames in any
|
|
35
|
+
**Can also be used *without* WebSerial for building modbus frames in any environment**
|
|
36
36
|
```javascript
|
|
37
37
|
import {
|
|
38
38
|
buildReadHoldingRegisters,
|
|
@@ -67,7 +67,13 @@ The following Modbus-RTU function calls are implemented:
|
|
|
67
67
|
| `writeRegister(addr, value)` | FC 06 – Write single holding register |
|
|
68
68
|
| `writeCoils(addr, states)` | FC 15 – Write multiple coils |
|
|
69
69
|
| `writeRegisters(addr, values)` | FC 16 – Write multiple holding registers |
|
|
70
|
-
|
|
70
|
+
| `readFileRecord(file, rec, len)` | FC 20 – Read file record (single ref) |
|
|
71
|
+
| `writeFileRecord(file, rec, vals)`| FC 21 – Write file record (single ref) |
|
|
72
|
+
| `maskWriteRegister(addr, and, or)`| FC 22 – Mask write register |
|
|
73
|
+
| `readWriteRegisters(rAddr, rQty, wAddr, vals)` | FC 23 – Read/write multiple regs |
|
|
74
|
+
| `readFifoQueue(addr)` | FC 24 – Read FIFO queue |
|
|
75
|
+
> [!CAUTION]
|
|
76
|
+
> Not all slave libraries support file records, FIFO queues, mask writes or read-write calls
|
|
71
77
|
### Auxiliary Client Methods
|
|
72
78
|
|
|
73
79
|
Utility and configuration methods exposed on `ModbusRTU`:
|
|
@@ -91,8 +97,8 @@ The following demos are fully self‑contained HTML files, served via GitHub Pag
|
|
|
91
97
|
Automated loop testing read/write of 64 registers, coils, and discrete inputs with live counters and error logging.
|
|
92
98
|
|
|
93
99
|
## Current state
|
|
94
|
-
|
|
95
|
-
* **v0.9
|
|
100
|
+
* **v0.10**: Full modbus data-access coverage
|
|
101
|
+
* **v0.9**: Full passing tests, smoke test passed, complete README, build scripts in place
|
|
96
102
|
* **Beta**: Full Modbus RTU function‑code coverage
|
|
97
103
|
* **Alpha**: Basic structure and layout
|
|
98
104
|
|
package/dist/index.d.ts
CHANGED
|
@@ -34,6 +34,22 @@ interface WriteMultipleResult {
|
|
|
34
34
|
length: number;
|
|
35
35
|
raw: Uint8Array;
|
|
36
36
|
}
|
|
37
|
+
interface MaskWriteResult {
|
|
38
|
+
address: number;
|
|
39
|
+
andMask: number;
|
|
40
|
+
orMask: number;
|
|
41
|
+
raw: Uint8Array;
|
|
42
|
+
}
|
|
43
|
+
interface WriteFileResult {
|
|
44
|
+
file: number;
|
|
45
|
+
record: number;
|
|
46
|
+
length: number;
|
|
47
|
+
raw: Uint8Array;
|
|
48
|
+
}
|
|
49
|
+
interface ReadFifoResult {
|
|
50
|
+
data: number[];
|
|
51
|
+
raw: Uint8Array;
|
|
52
|
+
}
|
|
37
53
|
|
|
38
54
|
declare class ModbusRTU {
|
|
39
55
|
private id;
|
|
@@ -61,6 +77,16 @@ declare class ModbusRTU {
|
|
|
61
77
|
writeRegister(addr: number, value: number): Promise<WriteRegisterResult>;
|
|
62
78
|
/** FC 16 – multiple holding registers */
|
|
63
79
|
writeRegisters(addr: number, values: number[]): Promise<WriteMultipleResult>;
|
|
80
|
+
/** FC 22 – mask write register */
|
|
81
|
+
maskWriteRegister(addr: number, andMask: number, orMask: number): Promise<MaskWriteResult>;
|
|
82
|
+
/** FC 23 – read/write multiple registers */
|
|
83
|
+
readWriteRegisters(readAddr: number, readQty: number, writeAddr: number, values: number[]): Promise<ReadRegisterResult>;
|
|
84
|
+
/** FC 20 – read file record (single reference) */
|
|
85
|
+
readFileRecord(file: number, record: number, length: number): Promise<ReadRegisterResult>;
|
|
86
|
+
/** FC 21 – write file record (single reference) */
|
|
87
|
+
writeFileRecord(file: number, record: number, values: number[]): Promise<WriteFileResult>;
|
|
88
|
+
/** FC 24 – read FIFO queue */
|
|
89
|
+
readFifoQueue(addr: number): Promise<ReadFifoResult>;
|
|
64
90
|
}
|
|
65
91
|
|
|
66
92
|
/**
|
|
@@ -103,6 +129,33 @@ declare function buildReadInputRegisters(id: number, addr: number, qty: number):
|
|
|
103
129
|
* Modbus Application Protocol V1.1b3 §6.2
|
|
104
130
|
*/
|
|
105
131
|
declare function buildReadDiscreteInputs(id: number, addr: number, qty: number): Uint8Array;
|
|
132
|
+
/**
|
|
133
|
+
* FC_MASK_WRITE_REGISTER (0x16)
|
|
134
|
+
* Modbus Application Protocol V1.1b3 §6.16
|
|
135
|
+
*/
|
|
136
|
+
declare function buildMaskWriteRegister(id: number, addr: number, andMask: number, orMask: number): Uint8Array;
|
|
137
|
+
/**
|
|
138
|
+
* FC_READ_WRITE_MULTIPLE_REGISTERS (0x17)
|
|
139
|
+
* Modbus Application Protocol V1.1b3 §6.17
|
|
140
|
+
*/
|
|
141
|
+
declare function buildReadWriteMultiple(id: number, readAddr: number, readQty: number, writeAddr: number, values: number[]): Uint8Array;
|
|
142
|
+
/**
|
|
143
|
+
* FC_READ_FILE_RECORD (0x14)
|
|
144
|
+
* Single sub-request only (ref type 0x06)
|
|
145
|
+
* Modbus Application Protocol V1.1b3 §6.14
|
|
146
|
+
*/
|
|
147
|
+
declare function buildReadFileRecord(id: number, file: number, record: number, length: number): Uint8Array;
|
|
148
|
+
/**
|
|
149
|
+
* FC_WRITE_FILE_RECORD (0x15)
|
|
150
|
+
* Single sub-request only (ref type 0x06)
|
|
151
|
+
* Modbus Application Protocol V1.1b3 §6.15
|
|
152
|
+
*/
|
|
153
|
+
declare function buildWriteFileRecord(id: number, file: number, record: number, values: number[]): Uint8Array;
|
|
154
|
+
/**
|
|
155
|
+
* FC_READ_FIFO_QUEUE (0x18)
|
|
156
|
+
* Modbus Application Protocol V1.1b3 §6.18
|
|
157
|
+
*/
|
|
158
|
+
declare function buildReadFifoQueue(id: number, addr: number): Uint8Array;
|
|
106
159
|
/** FC 03 – Read Holding Registers: returns array of words */
|
|
107
160
|
declare function parseReadHolding(resp: Uint8Array): number[];
|
|
108
161
|
/** FC 06 – Write Single Holding Register: echo frame -> { address, value } */
|
|
@@ -121,6 +174,18 @@ declare function parseWriteSingleCoil(resp: Uint8Array): {
|
|
|
121
174
|
address: number;
|
|
122
175
|
state: boolean;
|
|
123
176
|
};
|
|
177
|
+
/** FC 16 – Mask Write Register: echo → { address, andMask, orMask } */
|
|
178
|
+
declare function parseMaskWriteRegister(resp: Uint8Array): {
|
|
179
|
+
address: number;
|
|
180
|
+
andMask: number;
|
|
181
|
+
orMask: number;
|
|
182
|
+
};
|
|
183
|
+
/** FC 23 – Read/Write Multiple Registers: returns array of words */
|
|
184
|
+
declare function parseReadWriteMultiple(resp: Uint8Array): number[];
|
|
185
|
+
/** FC 20 – Read File Record (single sub-response) */
|
|
186
|
+
declare function parseReadFileRecord(resp: Uint8Array): number[];
|
|
187
|
+
/** FC 24 – Read FIFO Queue */
|
|
188
|
+
declare function parseReadFifoQueue(resp: Uint8Array): number[];
|
|
124
189
|
|
|
125
190
|
/** Standard Modbus CRC-16 (poly 0xA001, little-endian) */
|
|
126
191
|
declare function crc16(buf: Uint8Array): number;
|
|
@@ -136,4 +201,4 @@ declare class ExceptionError extends Error {
|
|
|
136
201
|
constructor(code: number);
|
|
137
202
|
}
|
|
138
203
|
|
|
139
|
-
export { CrcError, ExceptionError, ModbusRTU, type ReadRegisterResult, TimeoutError, type WebSerialOptions, type WriteRegisterResult, buildReadCoils, buildReadDiscreteInputs, buildReadHolding, buildReadInputRegisters, buildWriteMultiple, buildWriteMultipleCoils, buildWriteSingle, buildWriteSingleCoil, crc16, parseReadCoils, parseReadDiscreteInputs, parseReadHolding, parseReadInputRegisters, parseWriteSingle, parseWriteSingleCoil };
|
|
204
|
+
export { CrcError, ExceptionError, type MaskWriteResult, ModbusRTU, type ReadFifoResult, type ReadRegisterResult, TimeoutError, type WebSerialOptions, type WriteFileResult, type WriteRegisterResult, buildMaskWriteRegister, buildReadCoils, buildReadDiscreteInputs, buildReadFifoQueue, buildReadFileRecord, buildReadHolding, buildReadInputRegisters, buildReadWriteMultiple, buildWriteFileRecord, buildWriteMultiple, buildWriteMultipleCoils, buildWriteSingle, buildWriteSingleCoil, crc16, parseMaskWriteRegister, parseReadCoils, parseReadDiscreteInputs, parseReadFifoQueue, parseReadFileRecord, parseReadHolding, parseReadInputRegisters, parseReadWriteMultiple, parseWriteSingle, parseWriteSingleCoil };
|
package/dist/index.js
CHANGED
|
@@ -152,6 +152,11 @@ var FC_WRITE_SINGLE_COIL = 5;
|
|
|
152
152
|
var FC_WRITE_MULTIPLE_COILS = 15;
|
|
153
153
|
var FC_READ_INPUT_REGISTERS = 4;
|
|
154
154
|
var FC_READ_DISCRETE_INPUTS = 2;
|
|
155
|
+
var FC_READ_FILE_RECORD = 20;
|
|
156
|
+
var FC_WRITE_FILE_RECORD = 21;
|
|
157
|
+
var FC_MASK_WRITE_REGISTER = 22;
|
|
158
|
+
var FC_READ_WRITE_MULTIPLE_REGISTERS = 23;
|
|
159
|
+
var FC_READ_FIFO_QUEUE = 24;
|
|
155
160
|
|
|
156
161
|
// src/core/frames.ts
|
|
157
162
|
function buildReadHolding(id, addr, len) {
|
|
@@ -281,6 +286,102 @@ function buildReadDiscreteInputs(id, addr, qty) {
|
|
|
281
286
|
frame[7] = crc >> 8;
|
|
282
287
|
return frame;
|
|
283
288
|
}
|
|
289
|
+
function buildMaskWriteRegister(id, addr, andMask, orMask) {
|
|
290
|
+
const frame = new Uint8Array(10);
|
|
291
|
+
frame[0] = id;
|
|
292
|
+
frame[1] = FC_MASK_WRITE_REGISTER;
|
|
293
|
+
frame[2] = addr >> 8;
|
|
294
|
+
frame[3] = addr & 255;
|
|
295
|
+
frame[4] = andMask >> 8;
|
|
296
|
+
frame[5] = andMask & 255;
|
|
297
|
+
frame[6] = orMask >> 8;
|
|
298
|
+
frame[7] = orMask & 255;
|
|
299
|
+
const crc = crc16(frame.subarray(0, 8));
|
|
300
|
+
frame[8] = crc & 255;
|
|
301
|
+
frame[9] = crc >> 8;
|
|
302
|
+
return frame;
|
|
303
|
+
}
|
|
304
|
+
function buildReadWriteMultiple(id, readAddr, readQty, writeAddr, values) {
|
|
305
|
+
const writeQty = values.length;
|
|
306
|
+
if (readQty < 1 || readQty > 125) throw new Error("Invalid read quantity");
|
|
307
|
+
if (writeQty < 1 || writeQty > 121) throw new Error("Invalid write quantity");
|
|
308
|
+
const byteCount = writeQty * 2;
|
|
309
|
+
const frame = new Uint8Array(11 + byteCount + 2);
|
|
310
|
+
frame[0] = id;
|
|
311
|
+
frame[1] = FC_READ_WRITE_MULTIPLE_REGISTERS;
|
|
312
|
+
frame[2] = readAddr >> 8;
|
|
313
|
+
frame[3] = readAddr & 255;
|
|
314
|
+
frame[4] = readQty >> 8;
|
|
315
|
+
frame[5] = readQty & 255;
|
|
316
|
+
frame[6] = writeAddr >> 8;
|
|
317
|
+
frame[7] = writeAddr & 255;
|
|
318
|
+
frame[8] = writeQty >> 8;
|
|
319
|
+
frame[9] = writeQty & 255;
|
|
320
|
+
frame[10] = byteCount;
|
|
321
|
+
for (let i = 0; i < writeQty; i++) {
|
|
322
|
+
const v = values[i] & 65535;
|
|
323
|
+
frame[11 + i * 2] = v >> 8;
|
|
324
|
+
frame[12 + i * 2] = v & 255;
|
|
325
|
+
}
|
|
326
|
+
const crc = crc16(frame.subarray(0, frame.length - 2));
|
|
327
|
+
frame[frame.length - 2] = crc & 255;
|
|
328
|
+
frame[frame.length - 1] = crc >> 8;
|
|
329
|
+
return frame;
|
|
330
|
+
}
|
|
331
|
+
function buildReadFileRecord(id, file, record, length) {
|
|
332
|
+
if (length < 1 || length > 120) throw new Error("Invalid record length");
|
|
333
|
+
const frame = new Uint8Array(12);
|
|
334
|
+
frame[0] = id;
|
|
335
|
+
frame[1] = FC_READ_FILE_RECORD;
|
|
336
|
+
frame[2] = 7;
|
|
337
|
+
frame[3] = 6;
|
|
338
|
+
frame[4] = file >> 8;
|
|
339
|
+
frame[5] = file & 255;
|
|
340
|
+
frame[6] = record >> 8;
|
|
341
|
+
frame[7] = record & 255;
|
|
342
|
+
frame[8] = length >> 8;
|
|
343
|
+
frame[9] = length & 255;
|
|
344
|
+
const crc = crc16(frame.subarray(0, 10));
|
|
345
|
+
frame[10] = crc & 255;
|
|
346
|
+
frame[11] = crc >> 8;
|
|
347
|
+
return frame;
|
|
348
|
+
}
|
|
349
|
+
function buildWriteFileRecord(id, file, record, values) {
|
|
350
|
+
const len = values.length;
|
|
351
|
+
if (len < 1 || len > 120) throw new Error("Invalid record length");
|
|
352
|
+
const byteCount = 7 + len * 2;
|
|
353
|
+
const frame = new Uint8Array(3 + byteCount + 2);
|
|
354
|
+
frame[0] = id;
|
|
355
|
+
frame[1] = FC_WRITE_FILE_RECORD;
|
|
356
|
+
frame[2] = byteCount;
|
|
357
|
+
frame[3] = 6;
|
|
358
|
+
frame[4] = file >> 8;
|
|
359
|
+
frame[5] = file & 255;
|
|
360
|
+
frame[6] = record >> 8;
|
|
361
|
+
frame[7] = record & 255;
|
|
362
|
+
frame[8] = len >> 8;
|
|
363
|
+
frame[9] = len & 255;
|
|
364
|
+
for (let i = 0; i < len; i++) {
|
|
365
|
+
const v = values[i] & 65535;
|
|
366
|
+
frame[10 + i * 2] = v >> 8;
|
|
367
|
+
frame[11 + i * 2] = v & 255;
|
|
368
|
+
}
|
|
369
|
+
const crc = crc16(frame.subarray(0, frame.length - 2));
|
|
370
|
+
frame[frame.length - 2] = crc & 255;
|
|
371
|
+
frame[frame.length - 1] = crc >> 8;
|
|
372
|
+
return frame;
|
|
373
|
+
}
|
|
374
|
+
function buildReadFifoQueue(id, addr) {
|
|
375
|
+
const frame = new Uint8Array(6);
|
|
376
|
+
frame[0] = id;
|
|
377
|
+
frame[1] = FC_READ_FIFO_QUEUE;
|
|
378
|
+
frame[2] = addr >> 8;
|
|
379
|
+
frame[3] = addr & 255;
|
|
380
|
+
const crc = crc16(frame.subarray(0, 4));
|
|
381
|
+
frame[4] = crc & 255;
|
|
382
|
+
frame[5] = crc >> 8;
|
|
383
|
+
return frame;
|
|
384
|
+
}
|
|
284
385
|
function parseReadHolding(resp) {
|
|
285
386
|
basicChecks(resp, FC_READ_HOLDING_REGISTERS);
|
|
286
387
|
const byteCount = resp[2];
|
|
@@ -314,6 +415,42 @@ function parseWriteSingleCoil(resp) {
|
|
|
314
415
|
const val = resp[4] << 8 | resp[5];
|
|
315
416
|
return { address: addr, state: val === 65280 };
|
|
316
417
|
}
|
|
418
|
+
function parseMaskWriteRegister(resp) {
|
|
419
|
+
basicChecks(resp, FC_MASK_WRITE_REGISTER);
|
|
420
|
+
const addr = resp[2] << 8 | resp[3];
|
|
421
|
+
const andMask = resp[4] << 8 | resp[5];
|
|
422
|
+
const orMask = resp[6] << 8 | resp[7];
|
|
423
|
+
return { address: addr, andMask, orMask };
|
|
424
|
+
}
|
|
425
|
+
function parseReadWriteMultiple(resp) {
|
|
426
|
+
basicChecks(resp, FC_READ_WRITE_MULTIPLE_REGISTERS);
|
|
427
|
+
const byteCount = resp[2];
|
|
428
|
+
const words = [];
|
|
429
|
+
for (let i = 0; i < byteCount; i += 2) {
|
|
430
|
+
words.push(resp[3 + i] << 8 | resp[4 + i]);
|
|
431
|
+
}
|
|
432
|
+
return words;
|
|
433
|
+
}
|
|
434
|
+
function parseReadFileRecord(resp) {
|
|
435
|
+
basicChecks(resp, FC_READ_FILE_RECORD);
|
|
436
|
+
const dataLen = resp[3] - 1;
|
|
437
|
+
const words = [];
|
|
438
|
+
for (let i = 0; i < dataLen; i += 2) {
|
|
439
|
+
words.push(resp[5 + i] << 8 | resp[6 + i]);
|
|
440
|
+
}
|
|
441
|
+
return words;
|
|
442
|
+
}
|
|
443
|
+
function parseReadFifoQueue(resp) {
|
|
444
|
+
basicChecks(resp, FC_READ_FIFO_QUEUE);
|
|
445
|
+
const count = resp[4] << 8 | resp[5];
|
|
446
|
+
const words = [];
|
|
447
|
+
for (let i = 0; i < count; i++) {
|
|
448
|
+
const hi = resp[6 + i * 2];
|
|
449
|
+
const lo = resp[7 + i * 2];
|
|
450
|
+
words.push(hi << 8 | lo);
|
|
451
|
+
}
|
|
452
|
+
return words;
|
|
453
|
+
}
|
|
317
454
|
function _parseBits(expectedFC) {
|
|
318
455
|
return (_frame) => {
|
|
319
456
|
basicChecks(_frame, expectedFC);
|
|
@@ -416,25 +553,69 @@ var ModbusRTU = class _ModbusRTU {
|
|
|
416
553
|
const length = values.length;
|
|
417
554
|
return { address: addr, length, raw };
|
|
418
555
|
}
|
|
556
|
+
/* ================== OPTIONAL DATA-ACCESS FUNCTIONS ================== */
|
|
557
|
+
/** FC 22 – mask write register */
|
|
558
|
+
async maskWriteRegister(addr, andMask, orMask) {
|
|
559
|
+
const raw = await this.transport.transact(buildMaskWriteRegister(this.id, addr, andMask, orMask));
|
|
560
|
+
const { address, andMask: a, orMask: o } = parseMaskWriteRegister(raw);
|
|
561
|
+
return { address, andMask: a, orMask: o, raw };
|
|
562
|
+
}
|
|
563
|
+
/** FC 23 – read/write multiple registers */
|
|
564
|
+
async readWriteRegisters(readAddr, readQty, writeAddr, values) {
|
|
565
|
+
const raw = await this.transport.transact(
|
|
566
|
+
buildReadWriteMultiple(this.id, readAddr, readQty, writeAddr, values)
|
|
567
|
+
);
|
|
568
|
+
return { data: parseReadWriteMultiple(raw), raw };
|
|
569
|
+
}
|
|
570
|
+
/** FC 20 – read file record (single reference) */
|
|
571
|
+
async readFileRecord(file, record, length) {
|
|
572
|
+
const raw = await this.transport.transact(
|
|
573
|
+
buildReadFileRecord(this.id, file, record, length)
|
|
574
|
+
);
|
|
575
|
+
return { data: parseReadFileRecord(raw), raw };
|
|
576
|
+
}
|
|
577
|
+
/** FC 21 – write file record (single reference) */
|
|
578
|
+
async writeFileRecord(file, record, values) {
|
|
579
|
+
const raw = await this.transport.transact(
|
|
580
|
+
buildWriteFileRecord(this.id, file, record, values)
|
|
581
|
+
);
|
|
582
|
+
return { file, record, length: values.length, raw };
|
|
583
|
+
}
|
|
584
|
+
/** FC 24 – read FIFO queue */
|
|
585
|
+
async readFifoQueue(addr) {
|
|
586
|
+
const raw = await this.transport.transact(
|
|
587
|
+
buildReadFifoQueue(this.id, addr)
|
|
588
|
+
);
|
|
589
|
+
return { data: parseReadFifoQueue(raw), raw };
|
|
590
|
+
}
|
|
419
591
|
};
|
|
420
592
|
export {
|
|
421
593
|
CrcError,
|
|
422
594
|
ExceptionError,
|
|
423
595
|
ModbusRTU,
|
|
424
596
|
TimeoutError,
|
|
597
|
+
buildMaskWriteRegister,
|
|
425
598
|
buildReadCoils,
|
|
426
599
|
buildReadDiscreteInputs,
|
|
600
|
+
buildReadFifoQueue,
|
|
601
|
+
buildReadFileRecord,
|
|
427
602
|
buildReadHolding,
|
|
428
603
|
buildReadInputRegisters,
|
|
604
|
+
buildReadWriteMultiple,
|
|
605
|
+
buildWriteFileRecord,
|
|
429
606
|
buildWriteMultiple,
|
|
430
607
|
buildWriteMultipleCoils,
|
|
431
608
|
buildWriteSingle,
|
|
432
609
|
buildWriteSingleCoil,
|
|
433
610
|
crc16,
|
|
611
|
+
parseMaskWriteRegister,
|
|
434
612
|
parseReadCoils,
|
|
435
613
|
parseReadDiscreteInputs,
|
|
614
|
+
parseReadFifoQueue,
|
|
615
|
+
parseReadFileRecord,
|
|
436
616
|
parseReadHolding,
|
|
437
617
|
parseReadInputRegisters,
|
|
618
|
+
parseReadWriteMultiple,
|
|
438
619
|
parseWriteSingle,
|
|
439
620
|
parseWriteSingleCoil
|
|
440
621
|
};
|
package/package.json
CHANGED