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 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 enviorement**
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.0**: Full passing tests, smoke test passed, complete README, build scripts in place
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modbus-webserial",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "description": "Tiny TypeScript library for speaking Modbus-RTU from the browser via Web Serial",
5
5
  "type": "module",
6
6
  "author": "Antti Kotajärvi <antti.kotajarvi@hotmail.com>",