njs-modbus 3.1.1 → 3.3.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 (44) hide show
  1. package/README.md +84 -35
  2. package/README.zh-CN.md +84 -35
  3. package/dist/index.cjs +1291 -497
  4. package/dist/index.d.ts +99 -31
  5. package/dist/index.mjs +1291 -498
  6. package/dist/utils.cjs +536 -0
  7. package/dist/utils.d.ts +163 -0
  8. package/dist/utils.mjs +522 -0
  9. package/package.json +22 -2
  10. package/dist/src/error-code.d.ts +0 -17
  11. package/dist/src/index.d.ts +0 -7
  12. package/dist/src/layers/application/abstract-application-layer.d.ts +0 -26
  13. package/dist/src/layers/application/ascii-application-layer.d.ts +0 -23
  14. package/dist/src/layers/application/index.d.ts +0 -6
  15. package/dist/src/layers/application/rtu-application-layer.d.ts +0 -34
  16. package/dist/src/layers/application/tcp-application-layer.d.ts +0 -16
  17. package/dist/src/layers/physical/abstract-physical-layer.d.ts +0 -50
  18. package/dist/src/layers/physical/index.d.ts +0 -12
  19. package/dist/src/layers/physical/serial-physical-layer.d.ts +0 -70
  20. package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +0 -20
  21. package/dist/src/layers/physical/tcp-physical-connection.d.ts +0 -16
  22. package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +0 -29
  23. package/dist/src/layers/physical/udp-client-physical-layer.d.ts +0 -34
  24. package/dist/src/layers/physical/udp-server-physical-layer.d.ts +0 -51
  25. package/dist/src/layers/physical/utils.d.ts +0 -39
  26. package/dist/src/layers/physical/vars.d.ts +0 -11
  27. package/dist/src/master/index.d.ts +0 -3
  28. package/dist/src/master/master-session.d.ts +0 -18
  29. package/dist/src/master/master.d.ts +0 -140
  30. package/dist/src/slave/index.d.ts +0 -2
  31. package/dist/src/slave/slave.d.ts +0 -119
  32. package/dist/src/types.d.ts +0 -54
  33. package/dist/src/utils/bitsToMs.d.ts +0 -13
  34. package/dist/src/utils/callback.d.ts +0 -8
  35. package/dist/src/utils/checkRange.d.ts +0 -1
  36. package/dist/src/utils/crc.d.ts +0 -1
  37. package/dist/src/utils/index.d.ts +0 -11
  38. package/dist/src/utils/isUint8.d.ts +0 -8
  39. package/dist/src/utils/lrc.d.ts +0 -1
  40. package/dist/src/utils/predictRtuFrameLength.d.ts +0 -17
  41. package/dist/src/utils/promisify-cb.d.ts +0 -4
  42. package/dist/src/utils/rtu-timing.d.ts +0 -63
  43. package/dist/src/utils/whitelist.d.ts +0 -11
  44. package/dist/src/vars.d.ts +0 -49
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ interface ApplicationDataUnit {
12
12
  data: Buffer;
13
13
  }
14
14
  interface ServerId {
15
- /** Server ID; may be 1 byte (number) or multi-byte (number[]) per device spec. */
15
+ /** Server ID as a byte array (e.g. single-byte IDs use `[id]`). */
16
16
  serverId?: number[];
17
17
  runIndicatorStatus?: boolean;
18
18
  additionalData?: number[];
@@ -44,10 +44,32 @@ interface DeviceIdentification {
44
44
  */
45
45
  interface CustomFunctionCode {
46
46
  fc: number;
47
- /** Predict total RTU frame length for an incoming request (slave-side framing). */
48
- predictRequestLength: (buffer: Buffer) => number | null;
49
- /** Predict total RTU frame length for an incoming response (master-side framing). */
50
- predictResponseLength: (buffer: Buffer) => number | null;
47
+ /**
48
+ * Predict total RTU frame length for an incoming request (slave-side framing).
49
+ *
50
+ * @param buffer The raw frame buffer (a shared pool or incoming chunk). Do NOT
51
+ * modify it; the framing layer owns the memory.
52
+ * @param start — Byte offset in `buffer` where the current candidate frame begins
53
+ * (the unit ID byte). Read the function code at `buffer[start + 1]` and derive
54
+ * the total frame length from the trailing bytes.
55
+ * @param end — Byte offset one past the last valid byte. Bounds checks must use
56
+ * `end - start` as available bytes, NOT `buffer.length` (pool capacity).
57
+ * @returns Total frame length (>= 4, <= 256), or `null` if more bytes are needed.
58
+ */
59
+ predictRequestLength: (buffer: Buffer, start: number, end: number) => number | null;
60
+ /**
61
+ * Predict total RTU frame length for an incoming response (master-side framing).
62
+ *
63
+ * @param buffer — The raw frame buffer (a shared pool or incoming chunk). Do NOT
64
+ * modify it; the framing layer owns the memory.
65
+ * @param start — Byte offset in `buffer` where the current candidate frame begins
66
+ * (the unit ID byte). Read the function code at `buffer[start + 1]` and derive
67
+ * the total frame length from the trailing bytes.
68
+ * @param end — Byte offset one past the last valid byte. Bounds checks must use
69
+ * `end - start` as available bytes, NOT `buffer.length` (pool capacity).
70
+ * @returns Total frame length (>= 4, <= 256), or `null` if more bytes are needed.
71
+ */
72
+ predictResponseLength: (buffer: Buffer, start: number, end: number) => number | null;
51
73
  /**
52
74
  * Slave-side handler. Receives PDU payload (bytes after `fc`, before CRC) and
53
75
  * the unit ID being addressed; must return the PDU payload of the response.
@@ -115,6 +137,8 @@ declare enum ConformityLevel {
115
137
  }
116
138
  /** Shared empty Buffer to avoid repeated allocations. */
117
139
  declare const EMPTY_BUFFER: Buffer<ArrayBuffer>;
140
+ /** Shared no-op function to avoid repeated allocations. */
141
+ declare const NOOP: () => void;
118
142
  /** Modbus V1.1b3 PDU quantity limits. */
119
143
  declare const LIMITS: {
120
144
  readonly READ_COILS_MIN: 1;
@@ -353,25 +377,37 @@ type PhysicalConfig = {
353
377
  type: 'CUSTOM';
354
378
  layer: AbstractPhysicalLayer;
355
379
  };
380
+ /**
381
+ * User-facing arguments for `Master.open()` / `Slave.open()`.
382
+ *
383
+ * These match the physical layer's `open()` signatures **without** the trailing
384
+ * callback — `promisifyCb` appends it internally so the user gets a `Promise`.
385
+ *
386
+ * | Config | User-facing args |
387
+ * |---------------|--------------------------------------|
388
+ * | SERIAL | `()` — configured at construction |
389
+ * | TCP_CLIENT | `(opts?)` — `SocketConnectOpts` |
390
+ * | TCP_SERVER | `(opts?)` — `ListenOptions` |
391
+ * | UDP_CLIENT | `(remote?)` — `{ port?, address? }` |
392
+ * | UDP_SERVER | `(opts?)` — `BindOptions` |
393
+ * | CUSTOM | `never` — call `layer.open()` directly |
394
+ */
356
395
  type OpenArgs<T extends PhysicalConfig> = T extends {
357
396
  type: 'SERIAL';
358
- } ? Parameters<SerialPhysicalLayer['open']> : T extends {
397
+ } ? [] : T extends {
359
398
  type: 'TCP_CLIENT';
360
- } ? Parameters<TcpClientPhysicalLayer['open']> : T extends {
399
+ } ? [options?: SocketConnectOpts] : T extends {
361
400
  type: 'TCP_SERVER';
362
- } ? Parameters<TcpServerPhysicalLayer['open']> : T extends {
401
+ } ? [options?: ListenOptions] : T extends {
363
402
  type: 'UDP_CLIENT';
364
- } ? Parameters<UdpClientPhysicalLayer['open']> : T extends {
403
+ } ? [remote?: {
404
+ port?: number;
405
+ address?: string;
406
+ }] : T extends {
365
407
  type: 'UDP_SERVER';
366
- } ? Parameters<UdpServerPhysicalLayer['open']> : never;
408
+ } ? [options?: BindOptions] : never;
367
409
  declare function createPhysicalLayer(config: PhysicalConfig): AbstractPhysicalLayer;
368
410
 
369
- interface AbstractApplicationLayerEvents {
370
- framing: [frame: ApplicationDataUnit & {
371
- buffer: Buffer;
372
- }];
373
- 'framing-error': [error: Error];
374
- }
375
411
  /**
376
412
  * Application-layer protocol handler bound to a single physical connection.
377
413
  *
@@ -379,10 +415,16 @@ interface AbstractApplicationLayerEvents {
379
415
  * established and discarded when the connection closes. Subclasses implement
380
416
  * ASCII, RTU, or TCP framing rules.
381
417
  */
382
- declare abstract class AbstractApplicationLayer extends EventEmitter<AbstractApplicationLayerEvents> {
418
+ declare abstract class AbstractApplicationLayer {
383
419
  abstract readonly PROTOCOL: 'ASCII' | 'RTU' | 'TCP';
384
420
  abstract ROLE: 'MASTER' | 'SLAVE';
385
421
  abstract readonly connection: AbstractPhysicalConnection;
422
+ /** Called when a complete frame is decoded. Defaults to no-op. */
423
+ onFraming: (frame: ApplicationDataUnit & {
424
+ buffer: Buffer;
425
+ }) => void;
426
+ /** Called when a framing error is detected. Defaults to no-op. */
427
+ onFramingError: (error: Error) => void;
386
428
  flush(): void;
387
429
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
388
430
  removeCustomFunctionCode(fc: number): void;
@@ -403,7 +445,7 @@ declare class AsciiApplicationLayer extends AbstractApplicationLayer {
403
445
  readonly lenientHex: boolean;
404
446
  private _connection;
405
447
  private _state;
406
- private _cleanupFns;
448
+ private _cleanupCbs;
407
449
  get connection(): AbstractPhysicalConnection;
408
450
  constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection, options?: AsciiApplicationLayerOptions);
409
451
  private framing;
@@ -432,10 +474,18 @@ declare class RtuApplicationLayer extends AbstractApplicationLayer {
432
474
  private _threePointFiveT;
433
475
  private _onePointFiveT;
434
476
  private _customFunctionCodes;
435
- private _cleanupFns;
477
+ private _cleanupCbs;
436
478
  get connection(): AbstractPhysicalConnection;
437
479
  constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection, options?: RtuApplicationLayerOptions);
438
480
  private clearStateTimers;
481
+ /**
482
+ * Shared handler for every "frame is not yet complete" exit in `flushBuffer`.
483
+ * Returns `true` when the caller should `return` (strict reset), `false` to
484
+ * `break` the parse loop. Hot path never reaches here — only error/incomplete
485
+ * edges. Extracted as a method so it is not recreated on every `flushBuffer`
486
+ * call.
487
+ */
488
+ private _handleIncomplete;
439
489
  private flushBuffer;
440
490
  flush(): void;
441
491
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
@@ -449,7 +499,7 @@ declare class TcpApplicationLayer extends AbstractApplicationLayer {
449
499
  private _connection;
450
500
  private _transactionId;
451
501
  private _buffer;
452
- private _cleanupFns;
502
+ private _cleanupCbs;
453
503
  get connection(): AbstractPhysicalConnection;
454
504
  constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection);
455
505
  private tryExtract;
@@ -546,19 +596,21 @@ declare class ModbusMaster<T extends ModbusMasterOptions = ModbusMasterOptions>
546
596
  private _queueDatas;
547
597
  private _queueTimeouts;
548
598
  private _queueBroadcasts;
549
- private _queueResolves;
550
- private _queueRejects;
599
+ private _queueCallbacks;
551
600
  private _queueHead;
552
601
  private _queueLen;
553
602
  private _draining;
554
603
  private _nextTid;
555
604
  private _cleanupFns;
556
605
  private _closePromise;
606
+ private _nextExchangeId;
607
+ private _pendingExchanges;
608
+ private _timerHeap;
557
609
  get state(): PhysicalState;
558
610
  get physicalLayer(): AbstractPhysicalLayer;
559
611
  constructor(options: T);
560
612
  private _createAppLayer;
561
- private send;
613
+ private _send;
562
614
  private _drain;
563
615
  private _processNext;
564
616
  private _exchange;
@@ -620,7 +672,7 @@ declare class ModbusMaster<T extends ModbusMasterOptions = ModbusMasterOptions>
620
672
  sendCustomFC(unit: 0, fc: number, data: Buffer | number[], timeout?: number): Promise<void>;
621
673
  sendCustomFC(unit: number, fc: number, data: Buffer | number[], timeout?: number): Promise<Buffer>;
622
674
  /**
623
- * Open the underlying physical layer and begin accepting connections.
675
+ * Open the underlying physical layer and establish a connection.
624
676
  *
625
677
  * A `ModbusMaster` instance can only be opened once. Once {@link close}
626
678
  * is called — explicitly or because the physical layer disconnected —
@@ -665,15 +717,19 @@ interface ModbusSlaveModel {
665
717
  * Otherwise keep the default read and write behavior.
666
718
  */
667
719
  interceptor?: MaybeAsyncFunction<(fc: number, data: Buffer) => Buffer | undefined>;
668
- readDiscreteInputs?: MaybeAsyncFunction<(address: number, length: number) => boolean[]>;
669
- readCoils?: MaybeAsyncFunction<(address: number, length: number) => boolean[]>;
720
+ /** Return `Uint8Array` to avoid the `Array.from` allocation overhead. */
721
+ readDiscreteInputs?: MaybeAsyncFunction<(address: number, length: number) => boolean[] | Uint8Array>;
722
+ /** Return `Uint8Array` to avoid the `Array.from` allocation overhead. */
723
+ readCoils?: MaybeAsyncFunction<(address: number, length: number) => boolean[] | Uint8Array>;
670
724
  writeSingleCoil?: MaybeAsyncFunction<(address: number, value: boolean) => void>;
671
725
  /**
672
726
  * If omitted, defaults to loop and call `writeSingleCoil`.
673
727
  */
674
728
  writeMultipleCoils?: MaybeAsyncFunction<(address: number, value: boolean[]) => void>;
675
- readInputRegisters?: MaybeAsyncFunction<(address: number, length: number) => number[]>;
676
- readHoldingRegisters?: MaybeAsyncFunction<(address: number, length: number) => number[]>;
729
+ /** Return `Uint16Array` to avoid the `Array.from` allocation overhead. */
730
+ readInputRegisters?: MaybeAsyncFunction<(address: number, length: number) => number[] | Uint16Array>;
731
+ /** Return `Uint16Array` to avoid the `Array.from` allocation overhead. */
732
+ readHoldingRegisters?: MaybeAsyncFunction<(address: number, length: number) => number[] | Uint16Array>;
677
733
  writeSingleRegister?: MaybeAsyncFunction<(address: number, value: number) => void>;
678
734
  /**
679
735
  * If omitted, defaults to loop and call `writeSingleRegister`.
@@ -719,7 +775,7 @@ declare class ModbusSlave<T extends ModbusSlaveOptions = ModbusSlaveOptions> ext
719
775
  private _protocol;
720
776
  private _appLayers;
721
777
  private _customFunctionCodes;
722
- private _locks;
778
+ private _intervalLocks;
723
779
  private _cleanupFns;
724
780
  private _closePromise;
725
781
  get state(): PhysicalState;
@@ -742,7 +798,19 @@ declare class ModbusSlave<T extends ModbusSlaveOptions = ModbusSlaveOptions> ext
742
798
  private _drain;
743
799
  private _processFrame;
744
800
  private _intercept;
745
- private _withAddressLock;
801
+ /**
802
+ * Serialize a code block over the half-open address interval `[lo, hi)`.
803
+ * The block runs after all previously-installed locks whose intervals
804
+ * overlap with this one have completed. Two non-overlapping intervals
805
+ * execute in parallel.
806
+ *
807
+ * Locks are tracked in a flat array (`_intervalLocks`); the typical depth
808
+ * is 0-2 entries, so the linear overlap scan is sub-µs. Compare with the
809
+ * old per-address Map design, where FC23 writing 121 registers allocated
810
+ * ~125 objects per request (one Promise.resolve / address + Set + sort +
811
+ * Promise.all); this version allocates 1-3.
812
+ */
813
+ private _withIntervalLock;
746
814
  private _handleFC;
747
815
  private _handleCustomFC;
748
816
  add(model: ModbusSlaveModel): void;
@@ -770,5 +838,5 @@ declare class ModbusSlave<T extends ModbusSlaveOptions = ModbusSlaveOptions> ext
770
838
  close(): Promise<void>;
771
839
  }
772
840
 
773
- export { AbstractApplicationLayer, AbstractPhysicalConnection, AbstractPhysicalLayer, AsciiApplicationLayer, COIL_OFF, COIL_ON, ConformityLevel, EMPTY_BUFFER, EXCEPTION_OFFSET, ErrorCode, FunctionCode, LIMITS, MEI_READ_DEVICE_ID, MasterSession, ModbusError, ModbusMaster, ModbusSlave, PhysicalConnectionState, PhysicalState, ReadDeviceIDCode, RtuApplicationLayer, SerialPhysicalLayer, TcpApplicationLayer, TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpClientPhysicalLayer, UdpServerPhysicalLayer, createPhysicalLayer, getCodeByError, getErrorByCode };
841
+ export { AbstractApplicationLayer, AbstractPhysicalConnection, AbstractPhysicalLayer, AsciiApplicationLayer, COIL_OFF, COIL_ON, ConformityLevel, EMPTY_BUFFER, EXCEPTION_OFFSET, ErrorCode, FunctionCode, LIMITS, MEI_READ_DEVICE_ID, MasterSession, ModbusError, ModbusMaster, ModbusSlave, NOOP, PhysicalConnectionState, PhysicalState, ReadDeviceIDCode, RtuApplicationLayer, SerialPhysicalLayer, TcpApplicationLayer, TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpClientPhysicalLayer, UdpServerPhysicalLayer, createPhysicalLayer, getCodeByError, getErrorByCode };
774
842
  export type { AbstractPhysicalLayerEvents, ApplicationDataUnit, AsciiApplicationLayerOptions, Callback$1 as Callback, CustomFunctionCode, DeviceIdentification, MaybeAsyncFunction, ModbusMasterOptions, ModbusSlaveModel, ModbusSlaveOptions, OpenArgs, PhysicalConfig, RtuApplicationLayerOptions, ServerId, TcpServerPhysicalLayerOptions, UdpServerPhysicalLayerOptions };