@ya-modbus/emulator 0.4.1-refactor-scope-driver-packages.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 (53) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/LICENSE +674 -0
  3. package/README.md +226 -0
  4. package/bin/ya-modbus-emulator.js +10 -0
  5. package/dist/behaviors/function-codes.d.ts +12 -0
  6. package/dist/behaviors/function-codes.d.ts.map +1 -0
  7. package/dist/behaviors/function-codes.js +139 -0
  8. package/dist/behaviors/function-codes.js.map +1 -0
  9. package/dist/behaviors/timing.d.ts +46 -0
  10. package/dist/behaviors/timing.d.ts.map +1 -0
  11. package/dist/behaviors/timing.js +90 -0
  12. package/dist/behaviors/timing.js.map +1 -0
  13. package/dist/cli.d.ts +10 -0
  14. package/dist/cli.d.ts.map +1 -0
  15. package/dist/cli.js +124 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/device.d.ts +25 -0
  18. package/dist/device.d.ts.map +1 -0
  19. package/dist/device.js +93 -0
  20. package/dist/device.js.map +1 -0
  21. package/dist/emulator.d.ts +24 -0
  22. package/dist/emulator.d.ts.map +1 -0
  23. package/dist/emulator.js +116 -0
  24. package/dist/emulator.js.map +1 -0
  25. package/dist/index.d.ts +8 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +6 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/transports/base.d.ts +10 -0
  30. package/dist/transports/base.d.ts.map +1 -0
  31. package/dist/transports/base.js +6 -0
  32. package/dist/transports/base.js.map +1 -0
  33. package/dist/transports/memory.d.ts +16 -0
  34. package/dist/transports/memory.d.ts.map +1 -0
  35. package/dist/transports/memory.js +30 -0
  36. package/dist/transports/memory.js.map +1 -0
  37. package/dist/transports/rtu.d.ts +44 -0
  38. package/dist/transports/rtu.d.ts.map +1 -0
  39. package/dist/transports/rtu.js +121 -0
  40. package/dist/transports/rtu.js.map +1 -0
  41. package/dist/types/config.d.ts +54 -0
  42. package/dist/types/config.d.ts.map +1 -0
  43. package/dist/types/config.js +5 -0
  44. package/dist/types/config.js.map +1 -0
  45. package/dist/types/device.d.ts +27 -0
  46. package/dist/types/device.d.ts.map +1 -0
  47. package/dist/types/device.js +5 -0
  48. package/dist/types/device.js.map +1 -0
  49. package/dist/utils/config-loader.d.ts +21 -0
  50. package/dist/utils/config-loader.d.ts.map +1 -0
  51. package/dist/utils/config-loader.js +53 -0
  52. package/dist/utils/config-loader.js.map +1 -0
  53. package/package.json +51 -0
package/README.md ADDED
@@ -0,0 +1,226 @@
1
+ # @ya-modbus/emulator
2
+
3
+ Software Modbus device emulator for testing device drivers without physical hardware.
4
+
5
+ ## Features
6
+
7
+ - **Realistic device simulation**: Mimic actual device constraints and timing characteristics
8
+ - **Test acceleration**: Enable fast, deterministic testing without hardware
9
+ - **Edge case coverage**: Simulate error conditions and edge cases
10
+ - **Multiple transports**: TCP, RTU (virtual/real serial ports), and in-memory
11
+ - **Custom function codes**: Support vendor-specific Modbus extensions
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @ya-modbus/emulator
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```typescript
22
+ import { ModbusEmulator } from '@ya-modbus/emulator'
23
+
24
+ // Create emulator with TCP transport
25
+ const emulator = new ModbusEmulator({
26
+ transport: 'tcp',
27
+ port: 5502,
28
+ })
29
+
30
+ // Add a device
31
+ emulator.addDevice({
32
+ slaveId: 1,
33
+ registers: {
34
+ holding: {
35
+ 0: 230, // Voltage * 10 = 23.0V
36
+ 1: 52, // Current * 10 = 5.2A
37
+ },
38
+ },
39
+ })
40
+
41
+ // Start emulator
42
+ await emulator.start()
43
+
44
+ // Use with your driver tests
45
+ // ...
46
+
47
+ // Stop emulator
48
+ await emulator.stop()
49
+ ```
50
+
51
+ ## CLI Usage
52
+
53
+ The emulator can be used from the command line for quick testing:
54
+
55
+ ### Basic Usage
56
+
57
+ ```bash
58
+ # Start with config file
59
+ ya-modbus-emulator --config config.yaml
60
+
61
+ # Or specify parameters directly
62
+ ya-modbus-emulator --transport rtu --port /dev/ttyUSB0 --slave-id 1
63
+ ```
64
+
65
+ ### Command Line Options
66
+
67
+ ```
68
+ Options:
69
+ -c, --config <file> Configuration file (YAML or JSON)
70
+ -t, --transport <type> Transport type: tcp|rtu|memory
71
+ -p, --port <port> TCP port number or serial port path
72
+ -H, --host <host> TCP host address (default: 0.0.0.0)
73
+ -b, --baud-rate <rate> Serial baud rate (default: 9600)
74
+ --parity <type> Serial parity: none|even|odd (default: none)
75
+ -s, --slave-id <id> Slave ID (required if no config file)
76
+ -v, --verbose Enable verbose logging
77
+ -q, --quiet Suppress all output except errors
78
+ --log-requests Log all Modbus requests/responses
79
+ -V, --version output the version number
80
+ -h, --help display help for command
81
+ ```
82
+
83
+ ### Configuration Files
84
+
85
+ Create a YAML or JSON configuration file to define devices and behaviors:
86
+
87
+ **Basic RTU Example** (`basic-rtu.yaml`):
88
+
89
+ ```yaml
90
+ transport:
91
+ type: rtu
92
+ port: /dev/ttyUSB0
93
+ baudRate: 9600
94
+ parity: none
95
+
96
+ devices:
97
+ - slaveId: 1
98
+ registers:
99
+ holding:
100
+ 0: 230 # Voltage * 10 = 23.0V
101
+ 1: 52 # Current * 10 = 5.2A
102
+ ```
103
+
104
+ **Power Meter with Timing** (`power-meter.yaml`):
105
+
106
+ ```yaml
107
+ transport:
108
+ type: rtu
109
+ port: /dev/ttyUSB0
110
+
111
+ devices:
112
+ - slaveId: 1
113
+ registers:
114
+ holding:
115
+ 0: 2300 # Voltage * 10 = 230.0V
116
+ 1: 52 # Current * 10 = 5.2A
117
+ 2: 11960 # Power = 1196W
118
+ timing:
119
+ pollingInterval: 10
120
+ commandDetectionDelay: [3, 8]
121
+ processingDelay: [2, 5]
122
+ perRegisterDelay: 0.1
123
+ ```
124
+
125
+ **Multiple Devices** (`multi-device.yaml`):
126
+
127
+ ```yaml
128
+ transport:
129
+ type: rtu
130
+ port: /dev/ttyUSB0
131
+
132
+ devices:
133
+ - slaveId: 1
134
+ registers:
135
+ holding: { 0: 100, 1: 200 }
136
+ - slaveId: 2
137
+ registers:
138
+ holding: { 0: 300, 1: 400 }
139
+ - slaveId: 3
140
+ registers:
141
+ holding: { 0: 500, 1: 600 }
142
+ ```
143
+
144
+ See `examples/config-files/` for more examples.
145
+
146
+ ### Testing with Virtual Serial Ports
147
+
148
+ > **⚠️ Note**: RTU transport is currently a placeholder for v0.1.0. Serial port communication will be implemented in v0.2.0. Use the memory transport for testing in this version.
149
+
150
+ For testing without physical hardware, create virtual serial port pairs:
151
+
152
+ **Linux/macOS** (using socat):
153
+
154
+ ```bash
155
+ # Terminal 1: Create virtual serial port pair
156
+ socat -d -d pty,raw,echo=0 pty,raw,echo=0
157
+ # Note the output: /dev/pts/3 <-> /dev/pts/4
158
+
159
+ # Terminal 2: Start emulator on first port
160
+ ya-modbus-emulator --transport rtu --port /dev/pts/3 --slave-id 1
161
+
162
+ # Terminal 3: Run your driver tests against second port
163
+ node test-driver.js --port /dev/pts/4
164
+ ```
165
+
166
+ **Windows** (using com0com):
167
+
168
+ 1. Install [com0com](https://sourceforge.net/projects/com0com/)
169
+ 2. Create a pair: COM10 <-> COM11
170
+ 3. Start emulator: `ya-modbus-emulator --transport rtu --port COM10 --slave-id 1`
171
+ 4. Run tests: `node test-driver.js --port COM11`
172
+
173
+ See `examples/virtual-serial-test.md` for detailed virtual serial port testing guide.
174
+
175
+ ## Configuration
176
+
177
+ ### Timing Behaviors
178
+
179
+ Simulate realistic device response times:
180
+
181
+ ```typescript
182
+ emulator.addDevice({
183
+ slaveId: 1,
184
+ timing: {
185
+ pollingInterval: 10, // Device checks for commands every 10ms
186
+ commandDetectionDelay: [3, 8], // 3-8ms to notice command
187
+ processingDelay: [2, 5], // 2-5ms to process
188
+ perRegisterDelay: 0.1, // 0.1ms per register
189
+ },
190
+ })
191
+ ```
192
+
193
+ ### Register Constraints
194
+
195
+ > **🚧 Planned for v0.2.0**: Define forbidden ranges and batch size limits
196
+
197
+ ```typescript
198
+ // Coming in v0.2.0
199
+ emulator.addDevice({
200
+ slaveId: 1,
201
+ constraints: {
202
+ maxReadRegisters: 80,
203
+ maxWriteRegisters: 50,
204
+ forbiddenReadRanges: [{ type: 'holding', start: 100, end: 199, reason: 'Protected' }],
205
+ },
206
+ })
207
+ ```
208
+
209
+ ### Error Simulation
210
+
211
+ > **🚧 Planned for v0.2.0**: Inject errors for testing error handling
212
+
213
+ ```typescript
214
+ // Coming in v0.2.0
215
+ emulator.addDevice({
216
+ slaveId: 1,
217
+ errors: {
218
+ timeoutProbability: 0.05, // 5% timeout rate
219
+ crcErrorProbability: 0.01, // 1% CRC error rate
220
+ },
221
+ })
222
+ ```
223
+
224
+ ## License
225
+
226
+ GPL-3.0-or-later
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Entry point for CLI - delegates to compiled TypeScript
4
+ import { main } from '../dist/cli.js'
5
+
6
+ // Run the CLI
7
+ main().catch((error) => {
8
+ console.error('[ERROR]', error instanceof Error ? error.message : String(error))
9
+ process.exit(1)
10
+ })
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Modbus function code handlers
3
+ */
4
+ import type { EmulatedDevice } from '../device.js';
5
+ export declare const ILLEGAL_FUNCTION = 1;
6
+ export declare const ILLEGAL_DATA_ADDRESS = 2;
7
+ export declare const ILLEGAL_DATA_VALUE = 3;
8
+ /**
9
+ * Handle a Modbus request and return the response
10
+ */
11
+ export declare function handleModbusRequest(device: EmulatedDevice, request: Buffer): Buffer;
12
+ //# sourceMappingURL=function-codes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"function-codes.d.ts","sourceRoot":"","sources":["../../src/behaviors/function-codes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD,eAAO,MAAM,gBAAgB,IAAO,CAAA;AACpC,eAAO,MAAM,oBAAoB,IAAO,CAAA;AACxC,eAAO,MAAM,kBAAkB,IAAO,CAAA;AAEtC;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CA4BnF"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Modbus function code handlers
3
+ */
4
+ // Modbus exception codes
5
+ export const ILLEGAL_FUNCTION = 0x01;
6
+ export const ILLEGAL_DATA_ADDRESS = 0x02;
7
+ export const ILLEGAL_DATA_VALUE = 0x03;
8
+ /**
9
+ * Handle a Modbus request and return the response
10
+ */
11
+ export function handleModbusRequest(device, request) {
12
+ if (request.length < 2) {
13
+ return createExceptionResponse(request[0] ?? 0, 0x00, ILLEGAL_DATA_VALUE);
14
+ }
15
+ // Buffer access is safe after length check
16
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17
+ const slaveId = request[0];
18
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
19
+ const functionCode = request[1];
20
+ try {
21
+ switch (functionCode) {
22
+ case 0x03:
23
+ return handleReadHoldingRegisters(device, request);
24
+ case 0x04:
25
+ return handleReadInputRegisters(device, request);
26
+ case 0x06:
27
+ return handleWriteSingleRegister(device, request);
28
+ case 0x10:
29
+ return handleWriteMultipleRegisters(device, request);
30
+ default:
31
+ return createExceptionResponse(slaveId, functionCode, ILLEGAL_FUNCTION);
32
+ }
33
+ }
34
+ catch {
35
+ // If error occurs, return illegal data value exception
36
+ return createExceptionResponse(slaveId, functionCode, ILLEGAL_DATA_VALUE);
37
+ }
38
+ }
39
+ /**
40
+ * 0x03 - Read Holding Registers
41
+ */
42
+ function handleReadHoldingRegisters(device, request) {
43
+ if (request.length < 6) {
44
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
45
+ return createExceptionResponse(request[0], 0x03, ILLEGAL_DATA_VALUE);
46
+ }
47
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
48
+ const slaveId = request[0];
49
+ const startAddress = request.readUInt16BE(2);
50
+ const quantity = request.readUInt16BE(4);
51
+ const byteCount = quantity * 2;
52
+ const response = Buffer.alloc(3 + byteCount);
53
+ response[0] = slaveId;
54
+ response[1] = 0x03;
55
+ response[2] = byteCount;
56
+ for (let i = 0; i < quantity; i++) {
57
+ const value = device.getHoldingRegister(startAddress + i);
58
+ response.writeUInt16BE(value, 3 + i * 2);
59
+ }
60
+ return response;
61
+ }
62
+ /**
63
+ * 0x04 - Read Input Registers
64
+ */
65
+ function handleReadInputRegisters(device, request) {
66
+ if (request.length < 6) {
67
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
68
+ return createExceptionResponse(request[0], 0x04, ILLEGAL_DATA_VALUE);
69
+ }
70
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
71
+ const slaveId = request[0];
72
+ const startAddress = request.readUInt16BE(2);
73
+ const quantity = request.readUInt16BE(4);
74
+ const byteCount = quantity * 2;
75
+ const response = Buffer.alloc(3 + byteCount);
76
+ response[0] = slaveId;
77
+ response[1] = 0x04;
78
+ response[2] = byteCount;
79
+ for (let i = 0; i < quantity; i++) {
80
+ const value = device.getInputRegister(startAddress + i);
81
+ response.writeUInt16BE(value, 3 + i * 2);
82
+ }
83
+ return response;
84
+ }
85
+ /**
86
+ * 0x06 - Write Single Register
87
+ */
88
+ function handleWriteSingleRegister(device, request) {
89
+ if (request.length < 6) {
90
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
91
+ return createExceptionResponse(request[0], 0x06, ILLEGAL_DATA_VALUE);
92
+ }
93
+ const address = request.readUInt16BE(2);
94
+ const value = request.readUInt16BE(4);
95
+ device.setHoldingRegister(address, value);
96
+ // Echo the request as response
97
+ return request;
98
+ }
99
+ /**
100
+ * 0x10 - Write Multiple Registers
101
+ */
102
+ function handleWriteMultipleRegisters(device, request) {
103
+ if (request.length < 7) {
104
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
105
+ return createExceptionResponse(request[0], 0x10, ILLEGAL_DATA_VALUE);
106
+ }
107
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
108
+ const slaveId = request[0];
109
+ const startAddress = request.readUInt16BE(2);
110
+ const quantity = request.readUInt16BE(4);
111
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
112
+ const byteCount = request[6];
113
+ if (request.length < 7 + byteCount) {
114
+ return createExceptionResponse(slaveId, 0x10, ILLEGAL_DATA_VALUE);
115
+ }
116
+ // Write registers
117
+ for (let i = 0; i < quantity; i++) {
118
+ const value = request.readUInt16BE(7 + i * 2);
119
+ device.setHoldingRegister(startAddress + i, value);
120
+ }
121
+ // Response: slave_id + function_code + start_address + quantity
122
+ const response = Buffer.alloc(6);
123
+ response[0] = slaveId;
124
+ response[1] = 0x10;
125
+ response.writeUInt16BE(startAddress, 2);
126
+ response.writeUInt16BE(quantity, 4);
127
+ return response;
128
+ }
129
+ /**
130
+ * Create a Modbus exception response
131
+ */
132
+ function createExceptionResponse(slaveId, functionCode, exceptionCode) {
133
+ const response = Buffer.alloc(3);
134
+ response[0] = slaveId;
135
+ response[1] = functionCode | 0x80; // Set error bit
136
+ response[2] = exceptionCode;
137
+ return response;
138
+ }
139
+ //# sourceMappingURL=function-codes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"function-codes.js","sourceRoot":"","sources":["../../src/behaviors/function-codes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Timing behavior simulation
3
+ */
4
+ import type { TimingBehavior } from '../types/config.js';
5
+ /**
6
+ * Calculate a delay value from a fixed number or random range
7
+ */
8
+ export declare function calculateDelay(delay: number | [number, number] | undefined): number;
9
+ /**
10
+ * Calculate transmission delay based on baud rate and frame size
11
+ * Formula: (frameBytes * 11 bits per byte) / (baudRate / 1000)
12
+ */
13
+ export declare function calculateTransmissionDelay(frameBytes: number, baudRate: number): number;
14
+ /**
15
+ * Thin abstraction over setTimeout for dependency injection
16
+ */
17
+ export interface DelayFunction {
18
+ (ms: number): Promise<void>;
19
+ }
20
+ /**
21
+ * Default delay implementation using setTimeout
22
+ */
23
+ export declare const defaultDelay: DelayFunction;
24
+ /**
25
+ * Timing simulator that calculates and applies realistic device delays
26
+ */
27
+ export declare class TimingSimulator {
28
+ private config;
29
+ private delayFn;
30
+ constructor(config: TimingBehavior, delayFn?: DelayFunction);
31
+ /**
32
+ * Calculate total delay for a request
33
+ * @param request - The Modbus request buffer
34
+ * @param registerCount - Number of registers being accessed
35
+ * @returns Total delay in milliseconds
36
+ */
37
+ calculateTotalDelay(request: Buffer, registerCount: number): number;
38
+ /**
39
+ * Apply timing delay for a request
40
+ * @param request - The Modbus request buffer
41
+ * @param registerCount - Number of registers being accessed
42
+ * @returns Promise that resolves after the calculated delay
43
+ */
44
+ delay(request: Buffer, registerCount: number): Promise<void>;
45
+ }
46
+ //# sourceMappingURL=timing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../../src/behaviors/timing.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAAG,MAAM,CAYnF;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMvF;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5B;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,aAK1B,CAAA;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,OAAO,CAAe;gBAElB,MAAM,EAAE,cAAc,EAAE,OAAO,GAAE,aAA4B;IAKzE;;;;;OAKG;IACH,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM;IA8BnE;;;;;OAKG;IACG,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAInE"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Timing behavior simulation
3
+ */
4
+ /**
5
+ * Calculate a delay value from a fixed number or random range
6
+ */
7
+ export function calculateDelay(delay) {
8
+ if (delay === undefined) {
9
+ return 0;
10
+ }
11
+ if (typeof delay === 'number') {
12
+ return delay;
13
+ }
14
+ // Random value in range [min, max]
15
+ const [min, max] = delay;
16
+ return min + Math.random() * (max - min);
17
+ }
18
+ /**
19
+ * Calculate transmission delay based on baud rate and frame size
20
+ * Formula: (frameBytes * 11 bits per byte) / (baudRate / 1000)
21
+ */
22
+ export function calculateTransmissionDelay(frameBytes, baudRate) {
23
+ // Each byte requires 11 bits (1 start + 8 data + 1 parity + 1 stop)
24
+ const totalBits = frameBytes * 11;
25
+ // Convert baud rate to bits per millisecond
26
+ const bitsPerMs = baudRate / 1000;
27
+ return totalBits / bitsPerMs;
28
+ }
29
+ /**
30
+ * Default delay implementation using setTimeout
31
+ */
32
+ export const defaultDelay = (ms) => {
33
+ if (ms <= 0) {
34
+ return Promise.resolve();
35
+ }
36
+ return new Promise((resolve) => setTimeout(resolve, ms));
37
+ };
38
+ /**
39
+ * Timing simulator that calculates and applies realistic device delays
40
+ */
41
+ export class TimingSimulator {
42
+ config;
43
+ delayFn;
44
+ constructor(config, delayFn = defaultDelay) {
45
+ this.config = config;
46
+ this.delayFn = delayFn;
47
+ }
48
+ /**
49
+ * Calculate total delay for a request
50
+ * @param request - The Modbus request buffer
51
+ * @param registerCount - Number of registers being accessed
52
+ * @returns Total delay in milliseconds
53
+ */
54
+ calculateTotalDelay(request, registerCount) {
55
+ let totalDelay = 0;
56
+ // 1. Command detection delay (or random value in [0, pollingInterval])
57
+ if (this.config.commandDetectionDelay !== undefined) {
58
+ totalDelay += calculateDelay(this.config.commandDetectionDelay);
59
+ }
60
+ else if (this.config.pollingInterval !== undefined) {
61
+ // Use random value in range [0, pollingInterval] as detection time
62
+ totalDelay += calculateDelay([0, this.config.pollingInterval]);
63
+ }
64
+ // 2. Processing delay
65
+ if (this.config.processingDelay !== undefined) {
66
+ totalDelay += calculateDelay(this.config.processingDelay);
67
+ }
68
+ // 3. Per-register delay
69
+ if (this.config.perRegisterDelay !== undefined) {
70
+ totalDelay += registerCount * this.config.perRegisterDelay;
71
+ }
72
+ // 4. Transmission delay (RTU only, when enabled)
73
+ if (this.config.autoCalculateTransmissionDelay && this.config.baudRate !== undefined) {
74
+ const frameSize = request.length;
75
+ totalDelay += calculateTransmissionDelay(frameSize, this.config.baudRate);
76
+ }
77
+ return totalDelay;
78
+ }
79
+ /**
80
+ * Apply timing delay for a request
81
+ * @param request - The Modbus request buffer
82
+ * @param registerCount - Number of registers being accessed
83
+ * @returns Promise that resolves after the calculated delay
84
+ */
85
+ async delay(request, registerCount) {
86
+ const delayMs = this.calculateTotalDelay(request, registerCount);
87
+ return this.delayFn(delayMs);
88
+ }
89
+ }
90
+ //# sourceMappingURL=timing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timing.js","sourceRoot":"","sources":["../../src/behaviors/timing.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,KAA4C;IACzE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,CAAC,CAAA;IACV,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,mCAAmC;IACnC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,KAAK,CAAA;IACxB,OAAO,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,CAAA;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CAAC,UAAkB,EAAE,QAAgB;IAC7E,oEAAoE;IACpE,MAAM,SAAS,GAAG,UAAU,GAAG,EAAE,CAAA;IACjC,4CAA4C;IAC5C,MAAM,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAA;IACjC,OAAO,SAAS,GAAG,SAAS,CAAA;AAC9B,CAAC;AASD;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAkB,CAAC,EAAU,EAAiB,EAAE;IACvE,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;QACZ,OAAO,OAAO,CAAC,OAAO,EAAE,CAAA;IAC1B,CAAC;IACD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAC1D,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,OAAO,eAAe;IAClB,MAAM,CAAgB;IACtB,OAAO,CAAe;IAE9B,YAAY,MAAsB,EAAE,UAAyB,YAAY;QACvE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;IAED;;;;;OAKG;IACH,mBAAmB,CAAC,OAAe,EAAE,aAAqB;QACxD,IAAI,UAAU,GAAG,CAAC,CAAA;QAElB,uEAAuE;QACvE,IAAI,IAAI,CAAC,MAAM,CAAC,qBAAqB,KAAK,SAAS,EAAE,CAAC;YACpD,UAAU,IAAI,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAA;QACjE,CAAC;aAAM,IAAI,IAAI,CAAC,MAAM,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;YACrD,mEAAmE;YACnE,UAAU,IAAI,cAAc,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAA;QAChE,CAAC;QAED,sBAAsB;QACtB,IAAI,IAAI,CAAC,MAAM,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;YAC9C,UAAU,IAAI,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,CAAA;QAC3D,CAAC;QAED,wBAAwB;QACxB,IAAI,IAAI,CAAC,MAAM,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;YAC/C,UAAU,IAAI,aAAa,GAAG,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAA;QAC5D,CAAC;QAED,iDAAiD;QACjD,IAAI,IAAI,CAAC,MAAM,CAAC,8BAA8B,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YACrF,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAA;YAChC,UAAU,IAAI,0BAA0B,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC3E,CAAC;QAED,OAAO,UAAU,CAAA;IACnB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,KAAK,CAAC,OAAe,EAAE,aAAqB;QAChD,MAAM,OAAO,GAAG,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;QAChE,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAC9B,CAAC;CACF"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI interface for Modbus emulator
4
+ */
5
+ /**
6
+ * Main CLI entry point
7
+ */
8
+ declare function main(): Promise<void>;
9
+ export { main };
10
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;GAEG;AA8CH;;GAEG;AACH,iBAAe,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAmGnC;AAOD,OAAO,EAAE,IAAI,EAAE,CAAA"}