@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.
- package/CHANGELOG.md +23 -0
- package/LICENSE +674 -0
- package/README.md +226 -0
- package/bin/ya-modbus-emulator.js +10 -0
- package/dist/behaviors/function-codes.d.ts +12 -0
- package/dist/behaviors/function-codes.d.ts.map +1 -0
- package/dist/behaviors/function-codes.js +139 -0
- package/dist/behaviors/function-codes.js.map +1 -0
- package/dist/behaviors/timing.d.ts +46 -0
- package/dist/behaviors/timing.d.ts.map +1 -0
- package/dist/behaviors/timing.js +90 -0
- package/dist/behaviors/timing.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +124 -0
- package/dist/cli.js.map +1 -0
- package/dist/device.d.ts +25 -0
- package/dist/device.d.ts.map +1 -0
- package/dist/device.js +93 -0
- package/dist/device.js.map +1 -0
- package/dist/emulator.d.ts +24 -0
- package/dist/emulator.d.ts.map +1 -0
- package/dist/emulator.js +116 -0
- package/dist/emulator.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/transports/base.d.ts +10 -0
- package/dist/transports/base.d.ts.map +1 -0
- package/dist/transports/base.js +6 -0
- package/dist/transports/base.js.map +1 -0
- package/dist/transports/memory.d.ts +16 -0
- package/dist/transports/memory.d.ts.map +1 -0
- package/dist/transports/memory.js +30 -0
- package/dist/transports/memory.js.map +1 -0
- package/dist/transports/rtu.d.ts +44 -0
- package/dist/transports/rtu.d.ts.map +1 -0
- package/dist/transports/rtu.js +121 -0
- package/dist/transports/rtu.js.map +1 -0
- package/dist/types/config.d.ts +54 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +5 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/device.d.ts +27 -0
- package/dist/types/device.d.ts.map +1 -0
- package/dist/types/device.js +5 -0
- package/dist/types/device.js.map +1 -0
- package/dist/utils/config-loader.d.ts +21 -0
- package/dist/utils/config-loader.d.ts.map +1 -0
- package/dist/utils/config-loader.js +53 -0
- package/dist/utils/config-loader.js.map +1 -0
- package/package.json +51 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI interface for Modbus emulator
|
|
4
|
+
*/
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { ModbusEmulator } from './emulator.js';
|
|
7
|
+
import { loadConfig } from './utils/config-loader.js';
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program
|
|
10
|
+
.name('ya-modbus-emulator')
|
|
11
|
+
.description('Modbus device emulator for testing drivers without physical hardware')
|
|
12
|
+
.version('0.1.0');
|
|
13
|
+
program
|
|
14
|
+
.option('-c, --config <file>', 'Configuration file (YAML or JSON)')
|
|
15
|
+
.option('-t, --transport <type>', 'Transport type: tcp|rtu|memory')
|
|
16
|
+
.option('-p, --port <port>', 'TCP port number or serial port path')
|
|
17
|
+
.option('-H, --host <host>', 'TCP host address (default: 0.0.0.0)')
|
|
18
|
+
.option('-b, --baud-rate <rate>', 'Serial baud rate (default: 9600)', parseInt)
|
|
19
|
+
.option('--parity <type>', 'Serial parity: none|even|odd (default: none)')
|
|
20
|
+
.option('-s, --slave-id <id>', 'Slave ID (required if no config file)', parseInt)
|
|
21
|
+
.option('-v, --verbose', 'Enable verbose logging')
|
|
22
|
+
.option('-q, --quiet', 'Suppress all output except errors')
|
|
23
|
+
.option('--log-requests', 'Log all Modbus requests/responses');
|
|
24
|
+
program.parse();
|
|
25
|
+
// Type assertion needed for TypeScript strict mode with noUncheckedIndexedAccess
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
27
|
+
const options = program.opts();
|
|
28
|
+
/**
|
|
29
|
+
* Main CLI entry point
|
|
30
|
+
*/
|
|
31
|
+
async function main() {
|
|
32
|
+
try {
|
|
33
|
+
// Load configuration
|
|
34
|
+
let config;
|
|
35
|
+
let devices = [];
|
|
36
|
+
if (options.config !== undefined) {
|
|
37
|
+
// Load from config file
|
|
38
|
+
const fileConfig = await loadConfig(options.config);
|
|
39
|
+
// Build config object only with defined properties (exactOptionalPropertyTypes)
|
|
40
|
+
config = {
|
|
41
|
+
transport: fileConfig.transport.type,
|
|
42
|
+
...(fileConfig.transport.port !== undefined && { port: fileConfig.transport.port }),
|
|
43
|
+
...(fileConfig.transport.host !== undefined && { host: fileConfig.transport.host }),
|
|
44
|
+
...(fileConfig.transport.baudRate !== undefined && {
|
|
45
|
+
baudRate: fileConfig.transport.baudRate,
|
|
46
|
+
}),
|
|
47
|
+
...(fileConfig.transport.parity !== undefined && { parity: fileConfig.transport.parity }),
|
|
48
|
+
...(fileConfig.transport.dataBits !== undefined && {
|
|
49
|
+
dataBits: fileConfig.transport.dataBits,
|
|
50
|
+
}),
|
|
51
|
+
...(fileConfig.transport.stopBits !== undefined && {
|
|
52
|
+
stopBits: fileConfig.transport.stopBits,
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
devices = fileConfig.devices;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// Build from CLI args
|
|
59
|
+
if (options.transport === undefined) {
|
|
60
|
+
console.error('Error: --transport is required when not using --config');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
if (options.slaveId === undefined) {
|
|
64
|
+
console.error('Error: --slave-id is required when not using --config');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
config = {
|
|
68
|
+
transport: options.transport,
|
|
69
|
+
...(options.port !== undefined && { port: options.port }),
|
|
70
|
+
...(options.host !== undefined && { host: options.host }),
|
|
71
|
+
...(options.baudRate !== undefined && { baudRate: options.baudRate }),
|
|
72
|
+
...(options.parity !== undefined && {
|
|
73
|
+
parity: options.parity,
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
devices = [{ slaveId: options.slaveId }];
|
|
77
|
+
}
|
|
78
|
+
// Create emulator
|
|
79
|
+
const emulator = new ModbusEmulator(config);
|
|
80
|
+
// Add devices
|
|
81
|
+
for (const deviceConfig of devices) {
|
|
82
|
+
emulator.addDevice(deviceConfig);
|
|
83
|
+
}
|
|
84
|
+
// Start emulator
|
|
85
|
+
await emulator.start();
|
|
86
|
+
if (options.quiet !== true) {
|
|
87
|
+
console.log('[INFO] Starting Modbus emulator...');
|
|
88
|
+
console.log(`[INFO] Transport: ${config.transport.toUpperCase()} on ${config.port ?? 'memory'}`);
|
|
89
|
+
console.log(`[INFO] Devices: ${devices.length}`);
|
|
90
|
+
devices.forEach((device) => {
|
|
91
|
+
console.log(`[INFO] - Slave ID ${device.slaveId}`);
|
|
92
|
+
});
|
|
93
|
+
console.log('[INFO] Emulator started successfully');
|
|
94
|
+
console.log('[INFO] Press Ctrl+C to stop');
|
|
95
|
+
}
|
|
96
|
+
// Handle shutdown
|
|
97
|
+
const shutdown = async () => {
|
|
98
|
+
if (options.quiet !== true) {
|
|
99
|
+
console.log('\n[INFO] Shutting down...');
|
|
100
|
+
}
|
|
101
|
+
await emulator.stop();
|
|
102
|
+
if (options.quiet !== true) {
|
|
103
|
+
console.log('[INFO] Emulator stopped');
|
|
104
|
+
}
|
|
105
|
+
process.exit(0);
|
|
106
|
+
};
|
|
107
|
+
process.on('SIGINT', () => {
|
|
108
|
+
void shutdown();
|
|
109
|
+
});
|
|
110
|
+
process.on('SIGTERM', () => {
|
|
111
|
+
void shutdown();
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error('[ERROR]', error instanceof Error ? error.message : String(error));
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
main().catch((error) => {
|
|
120
|
+
console.error('[ERROR]', error instanceof Error ? error.message : String(error));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
});
|
|
123
|
+
export { main };
|
|
124
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/device.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmulatedDevice class implementation
|
|
3
|
+
*/
|
|
4
|
+
import { TimingSimulator } from './behaviors/timing.js';
|
|
5
|
+
import type { DeviceConfig } from './types/config.js';
|
|
6
|
+
import type { EmulatedDevice as IEmulatedDevice } from './types/device.js';
|
|
7
|
+
export declare class EmulatedDevice implements IEmulatedDevice {
|
|
8
|
+
readonly slaveId: number;
|
|
9
|
+
private holdingRegisters;
|
|
10
|
+
private inputRegisters;
|
|
11
|
+
private coils;
|
|
12
|
+
private discreteInputs;
|
|
13
|
+
private timingSimulator?;
|
|
14
|
+
constructor(config: DeviceConfig);
|
|
15
|
+
getHoldingRegister(address: number): number;
|
|
16
|
+
setHoldingRegister(address: number, value: number): void;
|
|
17
|
+
getInputRegister(address: number): number;
|
|
18
|
+
setInputRegister(address: number, value: number): void;
|
|
19
|
+
getCoil(address: number): boolean;
|
|
20
|
+
setCoil(address: number, value: boolean): void;
|
|
21
|
+
getDiscreteInput(address: number): boolean;
|
|
22
|
+
setDiscreteInput(address: number, value: boolean): void;
|
|
23
|
+
getTimingSimulator(): TimingSimulator | undefined;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=device.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAE,cAAc,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAoB1E,qBAAa,cAAe,YAAW,eAAe;IACpD,SAAgB,OAAO,EAAE,MAAM,CAAA;IAC/B,OAAO,CAAC,gBAAgB,CAAiC;IACzD,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,cAAc,CAAkC;IACxD,OAAO,CAAC,eAAe,CAAC,CAAiB;gBAE7B,MAAM,EAAE,YAAY;IAwChC,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI3C,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAMxD,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIzC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAMtD,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAIjC,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAI9C,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAI1C,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAIvD,kBAAkB,IAAI,eAAe,GAAG,SAAS;CAGlD"}
|
package/dist/device.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmulatedDevice class implementation
|
|
3
|
+
*/
|
|
4
|
+
import { TimingSimulator } from './behaviors/timing.js';
|
|
5
|
+
// Modbus register address and value ranges
|
|
6
|
+
const MIN_REGISTER_ADDRESS = 0;
|
|
7
|
+
const MAX_REGISTER_ADDRESS = 65535;
|
|
8
|
+
const MIN_REGISTER_VALUE = 0;
|
|
9
|
+
const MAX_REGISTER_VALUE = 65535;
|
|
10
|
+
function validateRegisterAddress(address) {
|
|
11
|
+
if (address < MIN_REGISTER_ADDRESS || address > MAX_REGISTER_ADDRESS) {
|
|
12
|
+
throw new Error(`Register address ${address} out of range (0-65535)`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function validateRegisterValue(value, address) {
|
|
16
|
+
if (value < MIN_REGISTER_VALUE || value > MAX_REGISTER_VALUE) {
|
|
17
|
+
throw new Error(`Register value ${value} at address ${address} out of range (0-65535)`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class EmulatedDevice {
|
|
21
|
+
slaveId;
|
|
22
|
+
holdingRegisters = new Map();
|
|
23
|
+
inputRegisters = new Map();
|
|
24
|
+
coils = new Map();
|
|
25
|
+
discreteInputs = new Map();
|
|
26
|
+
timingSimulator;
|
|
27
|
+
constructor(config) {
|
|
28
|
+
this.slaveId = config.slaveId;
|
|
29
|
+
// Initialize timing behavior
|
|
30
|
+
if (config.timing) {
|
|
31
|
+
this.timingSimulator = new TimingSimulator(config.timing);
|
|
32
|
+
}
|
|
33
|
+
// Initialize registers from config
|
|
34
|
+
if (config.registers?.holding) {
|
|
35
|
+
for (const [address, value] of Object.entries(config.registers.holding)) {
|
|
36
|
+
const addr = Number(address);
|
|
37
|
+
validateRegisterAddress(addr);
|
|
38
|
+
validateRegisterValue(value, addr);
|
|
39
|
+
this.holdingRegisters.set(addr, value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (config.registers?.input) {
|
|
43
|
+
for (const [address, value] of Object.entries(config.registers.input)) {
|
|
44
|
+
const addr = Number(address);
|
|
45
|
+
validateRegisterAddress(addr);
|
|
46
|
+
validateRegisterValue(value, addr);
|
|
47
|
+
this.inputRegisters.set(addr, value);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (config.registers?.coils) {
|
|
51
|
+
for (const [address, value] of Object.entries(config.registers.coils)) {
|
|
52
|
+
this.coils.set(Number(address), value);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (config.registers?.discreteInputs) {
|
|
56
|
+
for (const [address, value] of Object.entries(config.registers.discreteInputs)) {
|
|
57
|
+
this.discreteInputs.set(Number(address), value);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
getHoldingRegister(address) {
|
|
62
|
+
return this.holdingRegisters.get(address) ?? 0;
|
|
63
|
+
}
|
|
64
|
+
setHoldingRegister(address, value) {
|
|
65
|
+
validateRegisterAddress(address);
|
|
66
|
+
validateRegisterValue(value, address);
|
|
67
|
+
this.holdingRegisters.set(address, value);
|
|
68
|
+
}
|
|
69
|
+
getInputRegister(address) {
|
|
70
|
+
return this.inputRegisters.get(address) ?? 0;
|
|
71
|
+
}
|
|
72
|
+
setInputRegister(address, value) {
|
|
73
|
+
validateRegisterAddress(address);
|
|
74
|
+
validateRegisterValue(value, address);
|
|
75
|
+
this.inputRegisters.set(address, value);
|
|
76
|
+
}
|
|
77
|
+
getCoil(address) {
|
|
78
|
+
return this.coils.get(address) ?? false;
|
|
79
|
+
}
|
|
80
|
+
setCoil(address, value) {
|
|
81
|
+
this.coils.set(address, value);
|
|
82
|
+
}
|
|
83
|
+
getDiscreteInput(address) {
|
|
84
|
+
return this.discreteInputs.get(address) ?? false;
|
|
85
|
+
}
|
|
86
|
+
setDiscreteInput(address, value) {
|
|
87
|
+
this.discreteInputs.set(address, value);
|
|
88
|
+
}
|
|
89
|
+
getTimingSimulator() {
|
|
90
|
+
return this.timingSimulator;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=device.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.js","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main ModbusEmulator class
|
|
3
|
+
*/
|
|
4
|
+
import { EmulatedDevice } from './device.js';
|
|
5
|
+
import type { BaseTransport } from './transports/base.js';
|
|
6
|
+
import type { EmulatorConfig, DeviceConfig } from './types/config.js';
|
|
7
|
+
export declare class ModbusEmulator {
|
|
8
|
+
private devices;
|
|
9
|
+
private transport;
|
|
10
|
+
private started;
|
|
11
|
+
constructor(config: EmulatorConfig);
|
|
12
|
+
start(): Promise<void>;
|
|
13
|
+
stop(): Promise<void>;
|
|
14
|
+
private handleRequest;
|
|
15
|
+
/**
|
|
16
|
+
* Parse register count from Modbus request
|
|
17
|
+
*/
|
|
18
|
+
private parseRegisterCount;
|
|
19
|
+
getTransport(): BaseTransport;
|
|
20
|
+
addDevice(config: DeviceConfig): EmulatedDevice;
|
|
21
|
+
removeDevice(slaveId: number): void;
|
|
22
|
+
getDevice(slaveId: number): EmulatedDevice | undefined;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=emulator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emulator.d.ts","sourceRoot":"","sources":["../src/emulator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAGzD,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErE,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAyC;IACxD,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,OAAO,CAAQ;gBAEX,MAAM,EAAE,cAAc;IA+B5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAQb,aAAa;IA2B3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAyB1B,YAAY,IAAI,aAAa;IAI7B,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc;IAU/C,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAOnC,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;CAGvD"}
|
package/dist/emulator.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main ModbusEmulator class
|
|
3
|
+
*/
|
|
4
|
+
import { EmulatedDevice } from './device.js';
|
|
5
|
+
import { MemoryTransport } from './transports/memory.js';
|
|
6
|
+
import { RtuTransport } from './transports/rtu.js';
|
|
7
|
+
export class ModbusEmulator {
|
|
8
|
+
devices = new Map();
|
|
9
|
+
transport;
|
|
10
|
+
started = false;
|
|
11
|
+
constructor(config) {
|
|
12
|
+
// Create transport based on config
|
|
13
|
+
if (config.transport === 'memory') {
|
|
14
|
+
this.transport = new MemoryTransport();
|
|
15
|
+
}
|
|
16
|
+
else if (config.transport === 'rtu') {
|
|
17
|
+
if (!config.port || typeof config.port !== 'string') {
|
|
18
|
+
throw new Error('RTU transport requires port (serial port path)');
|
|
19
|
+
}
|
|
20
|
+
// Build RTU config with only defined properties (exactOptionalPropertyTypes)
|
|
21
|
+
const rtuConfig = {
|
|
22
|
+
port: config.port,
|
|
23
|
+
...(config.baudRate !== undefined && { baudRate: config.baudRate }),
|
|
24
|
+
...(config.parity !== undefined && { parity: config.parity }),
|
|
25
|
+
...(config.dataBits !== undefined && { dataBits: config.dataBits }),
|
|
26
|
+
...(config.stopBits !== undefined && { stopBits: config.stopBits }),
|
|
27
|
+
};
|
|
28
|
+
this.transport = new RtuTransport(rtuConfig);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
throw new Error(`Unsupported transport: ${config.transport}`);
|
|
32
|
+
}
|
|
33
|
+
// Set up request handler
|
|
34
|
+
this.transport.onRequest(this.handleRequest.bind(this));
|
|
35
|
+
}
|
|
36
|
+
async start() {
|
|
37
|
+
if (this.started) {
|
|
38
|
+
throw new Error('Emulator already started');
|
|
39
|
+
}
|
|
40
|
+
await this.transport.start();
|
|
41
|
+
this.started = true;
|
|
42
|
+
}
|
|
43
|
+
async stop() {
|
|
44
|
+
if (!this.started) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
await this.transport.stop();
|
|
48
|
+
this.started = false;
|
|
49
|
+
}
|
|
50
|
+
async handleRequest(slaveId, request) {
|
|
51
|
+
// Get the device for this slave ID
|
|
52
|
+
const device = this.devices.get(slaveId);
|
|
53
|
+
if (!device) {
|
|
54
|
+
// No device found - return exception
|
|
55
|
+
const response = Buffer.alloc(3);
|
|
56
|
+
response[0] = slaveId;
|
|
57
|
+
const functionCode = request[1];
|
|
58
|
+
response[1] = functionCode !== undefined ? functionCode | 0x80 : 0x80;
|
|
59
|
+
response[2] = 0x0b; // Gateway Target Device Failed to Respond
|
|
60
|
+
return response;
|
|
61
|
+
}
|
|
62
|
+
// Apply timing delay if configured
|
|
63
|
+
const timingSimulator = device.getTimingSimulator();
|
|
64
|
+
if (timingSimulator !== undefined) {
|
|
65
|
+
// Parse register count from request
|
|
66
|
+
const registerCount = this.parseRegisterCount(request);
|
|
67
|
+
await timingSimulator.delay(request, registerCount);
|
|
68
|
+
}
|
|
69
|
+
// Import dynamically to avoid circular dependency issues
|
|
70
|
+
const { handleModbusRequest } = await import('./behaviors/function-codes.js');
|
|
71
|
+
return handleModbusRequest(device, request);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Parse register count from Modbus request
|
|
75
|
+
*/
|
|
76
|
+
parseRegisterCount(request) {
|
|
77
|
+
if (request.length < 6) {
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
const functionCode = request[1];
|
|
81
|
+
// For read operations, register count is in bytes 4-5
|
|
82
|
+
if (functionCode === 0x03 || functionCode === 0x04) {
|
|
83
|
+
return request.readUInt16BE(4);
|
|
84
|
+
}
|
|
85
|
+
// For write single register, count is 1
|
|
86
|
+
if (functionCode === 0x06) {
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
// For write multiple registers, count is in bytes 4-5
|
|
90
|
+
if (functionCode === 0x10) {
|
|
91
|
+
return request.readUInt16BE(4);
|
|
92
|
+
}
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
getTransport() {
|
|
96
|
+
return this.transport;
|
|
97
|
+
}
|
|
98
|
+
addDevice(config) {
|
|
99
|
+
if (this.devices.has(config.slaveId)) {
|
|
100
|
+
throw new Error(`Device with slave ID ${config.slaveId} already exists`);
|
|
101
|
+
}
|
|
102
|
+
const device = new EmulatedDevice(config);
|
|
103
|
+
this.devices.set(config.slaveId, device);
|
|
104
|
+
return device;
|
|
105
|
+
}
|
|
106
|
+
removeDevice(slaveId) {
|
|
107
|
+
if (!this.devices.has(slaveId)) {
|
|
108
|
+
throw new Error(`Device with slave ID ${slaveId} not found`);
|
|
109
|
+
}
|
|
110
|
+
this.devices.delete(slaveId);
|
|
111
|
+
}
|
|
112
|
+
getDevice(slaveId) {
|
|
113
|
+
return this.devices.get(slaveId);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=emulator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emulator.js","sourceRoot":"","sources":["../src/emulator.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ya-modbus/emulator - Software Modbus device emulator
|
|
3
|
+
*/
|
|
4
|
+
export { ModbusEmulator } from './emulator.js';
|
|
5
|
+
export { EmulatedDevice } from './device.js';
|
|
6
|
+
export type { EmulatorConfig, DeviceConfig, RegisterStorage } from './types/config.js';
|
|
7
|
+
export type { EmulatedDevice as IEmulatedDevice } from './types/device.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACtF,YAAY,EAAE,cAAc,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base transport abstract class
|
|
3
|
+
*/
|
|
4
|
+
export declare abstract class BaseTransport {
|
|
5
|
+
abstract start(): Promise<void>;
|
|
6
|
+
abstract stop(): Promise<void>;
|
|
7
|
+
abstract send(slaveId: number, response: Buffer): Promise<void>;
|
|
8
|
+
abstract onRequest(handler: (slaveId: number, request: Buffer) => Promise<Buffer>): void;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/transports/base.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,8BAAsB,aAAa;IACjC,QAAQ,CAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAC/B,QAAQ,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAC9B,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC/D,QAAQ,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI;CACzF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/transports/base.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,OAAgB,aAAa;CAKlC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory transport for testing
|
|
3
|
+
*/
|
|
4
|
+
import { BaseTransport } from './base.js';
|
|
5
|
+
export declare class MemoryTransport extends BaseTransport {
|
|
6
|
+
private requestHandler?;
|
|
7
|
+
start(): Promise<void>;
|
|
8
|
+
stop(): Promise<void>;
|
|
9
|
+
send(_slaveId: number, _response: Buffer): Promise<void>;
|
|
10
|
+
onRequest(handler: (slaveId: number, request: Buffer) => Promise<Buffer>): void;
|
|
11
|
+
/**
|
|
12
|
+
* Send a request and get response (for testing)
|
|
13
|
+
*/
|
|
14
|
+
sendRequest(slaveId: number, request: Buffer): Promise<Buffer>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/transports/memory.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAEzC,qBAAa,eAAgB,SAAQ,aAAa;IAChD,OAAO,CAAC,cAAc,CAAC,CAAuD;IAExE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9D,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI;IAI/E;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAMrE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory transport for testing
|
|
3
|
+
*/
|
|
4
|
+
import { BaseTransport } from './base.js';
|
|
5
|
+
export class MemoryTransport extends BaseTransport {
|
|
6
|
+
requestHandler;
|
|
7
|
+
async start() {
|
|
8
|
+
// Nothing to do for memory transport
|
|
9
|
+
}
|
|
10
|
+
async stop() {
|
|
11
|
+
// Nothing to do for memory transport
|
|
12
|
+
}
|
|
13
|
+
async send(_slaveId, _response) {
|
|
14
|
+
// In memory transport, send is not used directly
|
|
15
|
+
// Responses are returned from sendRequest
|
|
16
|
+
}
|
|
17
|
+
onRequest(handler) {
|
|
18
|
+
this.requestHandler = handler;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Send a request and get response (for testing)
|
|
22
|
+
*/
|
|
23
|
+
async sendRequest(slaveId, request) {
|
|
24
|
+
if (!this.requestHandler) {
|
|
25
|
+
throw new Error('No request handler set');
|
|
26
|
+
}
|
|
27
|
+
return this.requestHandler(slaveId, request);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/transports/memory.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAEzC,MAAM,OAAO,eAAgB,SAAQ,aAAa;IACxC,cAAc,CAAwD;IAE9E,KAAK,CAAC,KAAK;QACT,qCAAqC;IACvC,CAAC;IAED,KAAK,CAAC,IAAI;QACR,qCAAqC;IACvC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,QAAgB,EAAE,SAAiB;QAC5C,iDAAiD;QACjD,0CAA0C;IAC5C,CAAC;IAED,SAAS,CAAC,OAA8D;QACtE,IAAI,CAAC,cAAc,GAAG,OAAO,CAAA;IAC/B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,OAAe,EAAE,OAAe;QAChD,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAA;QAC3C,CAAC;QACD,OAAO,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAC9C,CAAC;CACF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RTU (serial) transport for Modbus emulator
|
|
3
|
+
*
|
|
4
|
+
* This implementation uses modbus-serial for protocol handling.
|
|
5
|
+
* Serial port communication will be added in future iterations.
|
|
6
|
+
*/
|
|
7
|
+
import { BaseTransport } from './base.js';
|
|
8
|
+
export interface RtuTransportConfig {
|
|
9
|
+
port: string;
|
|
10
|
+
baudRate?: number;
|
|
11
|
+
parity?: 'none' | 'even' | 'odd';
|
|
12
|
+
dataBits?: 7 | 8;
|
|
13
|
+
stopBits?: 1 | 2;
|
|
14
|
+
}
|
|
15
|
+
export declare class RtuTransport extends BaseTransport {
|
|
16
|
+
private requestHandler?;
|
|
17
|
+
private started;
|
|
18
|
+
constructor(_config: RtuTransportConfig);
|
|
19
|
+
start(): Promise<void>;
|
|
20
|
+
stop(): Promise<void>;
|
|
21
|
+
send(_slaveId: number, _response: Buffer): Promise<void>;
|
|
22
|
+
onRequest(handler: (slaveId: number, request: Buffer) => Promise<Buffer>): void;
|
|
23
|
+
/**
|
|
24
|
+
* Handle complete RTU frame (for future serial port implementation)
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
handleFrame(frame: Buffer): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Calculate Modbus RTU CRC-16 (Modbus variant)
|
|
30
|
+
*
|
|
31
|
+
* Returns CRC as a 16-bit value that can be written with writeUInt16LE
|
|
32
|
+
*/
|
|
33
|
+
private calculateCRC;
|
|
34
|
+
/**
|
|
35
|
+
* Verify CRC of RTU frame
|
|
36
|
+
*/
|
|
37
|
+
private verifyCRC;
|
|
38
|
+
/**
|
|
39
|
+
* Add CRC to buffer
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
addCRC(buffer: Buffer): Buffer;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=rtu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rtu.d.ts","sourceRoot":"","sources":["../../src/transports/rtu.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAEzC,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAA;IAChC,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;CACjB;AAED,qBAAa,YAAa,SAAQ,aAAa;IAC7C,OAAO,CAAC,cAAc,CAAC,CAAuD;IAC9E,OAAO,CAAC,OAAO,CAAQ;gBAEX,OAAO,EAAE,kBAAkB;IAKvC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IASrB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQxD,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI;IAI/E;;;OAGG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmC/C;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAqBpB;;OAEG;IACH,OAAO,CAAC,SAAS;IAejB;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;CAO/B"}
|