@yarkivaev/source-to-sink 1.3.0 → 1.3.2
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/index.js +4 -1
- package/package.json +1 -1
- package/src/modbusRtuBusSource.js +118 -0
- package/src/modbusSource.js +131 -35
- package/test/test_modbusRtuBusSource.js +47 -0
- package/test/test_modbusRtuSource.js +45 -0
- package/test/test_modbusSource.js +4 -2
package/index.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* - stompSend: one-shot STOMP publish (accepts URL)
|
|
15
15
|
* - lokiSource: Loki polling source (accepts URL)
|
|
16
16
|
* - modbusSource: Modbus TCP polling source (accepts host/port)
|
|
17
|
+
* - modbusRtuSource: Modbus RTU polling source (accepts serial path)
|
|
18
|
+
* - modbusRtuBusSource: Modbus RTU multi-slave bus on one serial path
|
|
17
19
|
* - mqttSink: MQTT publishing sink (accepts URL)
|
|
18
20
|
*
|
|
19
21
|
* @example
|
|
@@ -37,6 +39,7 @@ export { default as mqttSource } from './src/mqttSource.js';
|
|
|
37
39
|
export { default as stompSource } from './src/stompSource.js';
|
|
38
40
|
export { default as stompSend } from './src/stompSend.js';
|
|
39
41
|
export { default as lokiSource } from './src/lokiSource.js';
|
|
40
|
-
export { default as modbusSource } from './src/modbusSource.js';
|
|
42
|
+
export { default as modbusSource, modbusRtuSource } from './src/modbusSource.js';
|
|
43
|
+
export { default as modbusRtuBusSource } from './src/modbusRtuBusSource.js';
|
|
41
44
|
export { default as mqttSink } from './src/mqttSink.js';
|
|
42
45
|
export { default as sqliteSink } from './src/sqliteSink.js';
|
package/package.json
CHANGED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import ModbusRTU from 'modbus-serial';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validates RTU bus source arguments.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} path - Serial device path
|
|
7
|
+
* @param {object} serial - Serial options with baudRate
|
|
8
|
+
* @param {Array} slaves - Slave descriptors
|
|
9
|
+
*/
|
|
10
|
+
function assertBusArgs(path, serial, slaves) {
|
|
11
|
+
if (typeof path !== 'string' || path.length === 0) {
|
|
12
|
+
throw new Error('Serial path must be a non-empty string');
|
|
13
|
+
}
|
|
14
|
+
if (!serial || typeof serial.baudRate !== 'number' || serial.baudRate <= 0) {
|
|
15
|
+
throw new Error('Serial baudRate must be a positive number');
|
|
16
|
+
}
|
|
17
|
+
if (!Array.isArray(slaves) || slaves.length === 0) {
|
|
18
|
+
throw new Error('Slaves must be a non-empty array');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Reads one slave holding-register block and forwards it to its collector.
|
|
24
|
+
*
|
|
25
|
+
* @param {object} client - ModbusRTU client
|
|
26
|
+
* @param {object} slave - slaveId and collector
|
|
27
|
+
* @param {number} address - PDU start address
|
|
28
|
+
* @param {number} count - Register count
|
|
29
|
+
* @returns {Promise<void>}
|
|
30
|
+
*/
|
|
31
|
+
function readSlave(client, slave, address, count) {
|
|
32
|
+
client.setID(slave.slaveId);
|
|
33
|
+
return client.readHoldingRegisters(address, count).then((result) => {
|
|
34
|
+
slave.collector.accept(result.data);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Polls all slaves on the bus sequentially.
|
|
40
|
+
*
|
|
41
|
+
* @param {object} client - ModbusRTU client
|
|
42
|
+
* @param {Array} slaves - Slave descriptors
|
|
43
|
+
* @param {number} address - PDU start address
|
|
44
|
+
* @param {number} count - Register count
|
|
45
|
+
* @returns {Promise<void>}
|
|
46
|
+
*/
|
|
47
|
+
function pollSlaves(client, slaves, address, count) {
|
|
48
|
+
return slaves.reduce((chain, slave) => {
|
|
49
|
+
return chain.then(() => {
|
|
50
|
+
return readSlave(client, slave, address, count);
|
|
51
|
+
});
|
|
52
|
+
}, Promise.resolve());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Modbus RTU bus source: one serial port, several slave IDs polled in sequence.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const source = modbusRtuBusSource(
|
|
60
|
+
* '/dev/ttyUSB0',
|
|
61
|
+
* { baudRate: 9600, stopBits: 2, parity: 'none' },
|
|
62
|
+
* [{ slaveId: 1, collector: t1 }, { slaveId: 2, collector: t2 }],
|
|
63
|
+
* { address: 1280, count: 30, interval: 5 }
|
|
64
|
+
* );
|
|
65
|
+
* source.start();
|
|
66
|
+
*
|
|
67
|
+
* @param {string} path - Serial device path
|
|
68
|
+
* @param {object} serial - baudRate, dataBits, stopBits, parity
|
|
69
|
+
* @param {Array<{slaveId: number, collector: object}>} slaves - Slave poll targets
|
|
70
|
+
* @param {object} poll - address, count, interval, optional log
|
|
71
|
+
* @returns {object} Source with start() and stop()
|
|
72
|
+
*/
|
|
73
|
+
export default function modbusRtuBusSource(path, serial, slaves, poll) {
|
|
74
|
+
assertBusArgs(path, serial, slaves);
|
|
75
|
+
const log = poll.log || console;
|
|
76
|
+
const client = new ModbusRTU();
|
|
77
|
+
const line = {
|
|
78
|
+
baudRate: serial.baudRate,
|
|
79
|
+
dataBits: serial.dataBits || 8,
|
|
80
|
+
stopBits: serial.stopBits || 1,
|
|
81
|
+
parity: serial.parity || 'none'
|
|
82
|
+
};
|
|
83
|
+
let handle = null;
|
|
84
|
+
let opened = false;
|
|
85
|
+
function runPoll() {
|
|
86
|
+
pollSlaves(client, slaves, poll.address, poll.count).catch((err) => {
|
|
87
|
+
log.error(`RTU bus poll on ${path} failed: ${err.message}`);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function attempt() {
|
|
91
|
+
client.connectRTUBuffered(path, line).then(() => {
|
|
92
|
+
opened = true;
|
|
93
|
+
handle = setInterval(runPoll, poll.interval * 1000);
|
|
94
|
+
runPoll();
|
|
95
|
+
}).catch((err) => {
|
|
96
|
+
log.error(`Connection to ${path} failed, retrying: ${err.message}`);
|
|
97
|
+
handle = setTimeout(attempt, poll.interval * 1000);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
start() {
|
|
102
|
+
if (!(opened || handle)) {
|
|
103
|
+
attempt();
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
stop() {
|
|
107
|
+
if (handle) {
|
|
108
|
+
clearInterval(handle);
|
|
109
|
+
clearTimeout(handle);
|
|
110
|
+
handle = null;
|
|
111
|
+
}
|
|
112
|
+
if (opened) {
|
|
113
|
+
client.close();
|
|
114
|
+
opened = false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
package/src/modbusSource.js
CHANGED
|
@@ -58,42 +58,23 @@ function connected(client) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
* Modbus
|
|
61
|
+
* Shared Modbus polling lifecycle for TCP and RTU transports.
|
|
62
62
|
*
|
|
63
|
-
*
|
|
64
|
-
* at a specified interval. Forwards register arrays to the
|
|
65
|
-
* collector via pollingSource.
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* const source = modbusSource('192.168.2.148', 502, 4000, 14, 5, collector, clock);
|
|
69
|
-
* source.start();
|
|
70
|
-
* // ... later
|
|
71
|
-
* source.stop();
|
|
72
|
-
*
|
|
73
|
-
* @param {string} host - Modbus TCP host address
|
|
74
|
-
* @param {number} port - Modbus TCP port
|
|
75
|
-
* @param {number} address - Starting register address
|
|
76
|
-
* @param {number} count - Number of registers to read
|
|
77
|
-
* @param {number} interval - Polling interval in seconds
|
|
78
|
-
* @param {object} collector - Collector with accept() method
|
|
79
|
-
* @param {object} clk - Clock with millis() method
|
|
80
|
-
* @param {object} [log] - Logger with error() method for connection failures
|
|
63
|
+
* @param {object} options - connect, target, address, count, interval, collector, clk, client, log
|
|
81
64
|
* @returns {object} Source with start() and stop() methods
|
|
82
65
|
*/
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
const client = new ModbusRTU();
|
|
66
|
+
function modbusPollingSource(options) {
|
|
67
|
+
const {
|
|
68
|
+
connect,
|
|
69
|
+
target,
|
|
70
|
+
address,
|
|
71
|
+
count,
|
|
72
|
+
interval,
|
|
73
|
+
collector,
|
|
74
|
+
clk,
|
|
75
|
+
client,
|
|
76
|
+
log
|
|
77
|
+
} = options;
|
|
97
78
|
let state = idle();
|
|
98
79
|
async function fetch() {
|
|
99
80
|
const result = await client.readHoldingRegisters(address, count);
|
|
@@ -103,12 +84,12 @@ export default function modbusSource(host, port, address, count, interval, colle
|
|
|
103
84
|
const source = pollingSource(fetch, interval, collector, clk);
|
|
104
85
|
let delay = interval;
|
|
105
86
|
function attempt() {
|
|
106
|
-
client
|
|
87
|
+
connect(client).then(() => {
|
|
107
88
|
delay = interval;
|
|
108
89
|
state = connected(client);
|
|
109
90
|
source.start();
|
|
110
91
|
}).catch((err) => {
|
|
111
|
-
log.error(`Connection to ${
|
|
92
|
+
log.error(`Connection to ${target} failed, retrying in ${delay}s: ${err.message}`);
|
|
112
93
|
const handle = setTimeout(attempt, delay * 1000);
|
|
113
94
|
state = retrying(handle);
|
|
114
95
|
delay = Math.min(delay * 2, maxDelay);
|
|
@@ -140,3 +121,118 @@ export default function modbusSource(host, port, address, count, interval, colle
|
|
|
140
121
|
}
|
|
141
122
|
};
|
|
142
123
|
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Modbus TCP polling source for reading holding registers.
|
|
127
|
+
*
|
|
128
|
+
* Connects to a Modbus TCP device and polls holding registers
|
|
129
|
+
* at a specified interval. Forwards register arrays to the
|
|
130
|
+
* collector via pollingSource.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* const source = modbusSource('192.168.2.148', 502, 4000, 14, 5, collector, clock);
|
|
134
|
+
* source.start();
|
|
135
|
+
*
|
|
136
|
+
* @param {string} host - Modbus TCP host address
|
|
137
|
+
* @param {number} port - Modbus TCP port
|
|
138
|
+
* @param {number} address - Starting register address
|
|
139
|
+
* @param {number} count - Number of registers to read
|
|
140
|
+
* @param {number} interval - Polling interval in seconds
|
|
141
|
+
* @param {object} collector - Collector with accept() method
|
|
142
|
+
* @param {object} clk - Clock with millis() method
|
|
143
|
+
* @param {object} [log] - Logger with error() method for connection failures
|
|
144
|
+
* @returns {object} Source with start() and stop() methods
|
|
145
|
+
*/
|
|
146
|
+
export default function modbusSource(host, port, address, count, interval, collector, clk, log = console) {
|
|
147
|
+
if (typeof host !== 'string' || host.length === 0) {
|
|
148
|
+
throw new Error('Host must be a non-empty string');
|
|
149
|
+
}
|
|
150
|
+
if (typeof port !== 'number' || port <= 0) {
|
|
151
|
+
throw new Error(`Port must be a positive number, got: ${port}`);
|
|
152
|
+
}
|
|
153
|
+
if (typeof address !== 'number' || address < 0) {
|
|
154
|
+
throw new Error(`Address must be a non-negative number, got: ${address}`);
|
|
155
|
+
}
|
|
156
|
+
if (typeof count !== 'number' || count <= 0) {
|
|
157
|
+
throw new Error(`Count must be a positive number, got: ${count}`);
|
|
158
|
+
}
|
|
159
|
+
const client = new ModbusRTU();
|
|
160
|
+
function connect(modbusClient) {
|
|
161
|
+
return modbusClient.connectTCP(host, { port });
|
|
162
|
+
}
|
|
163
|
+
return modbusPollingSource({
|
|
164
|
+
connect,
|
|
165
|
+
target: `${host}:${port}`,
|
|
166
|
+
address,
|
|
167
|
+
count,
|
|
168
|
+
interval,
|
|
169
|
+
collector,
|
|
170
|
+
clk,
|
|
171
|
+
client,
|
|
172
|
+
log
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Modbus RTU polling source for reading holding registers over serial.
|
|
178
|
+
*
|
|
179
|
+
* Connects to a Modbus RTU device on a serial port and polls holding registers
|
|
180
|
+
* at a specified interval. Forwards register arrays to the collector via pollingSource.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* const source = modbusRtuSource('/dev/ttyUSB0', { baudRate: 9600, slaveId: 1 }, 4000, 44, 5, collector, clock);
|
|
184
|
+
* source.start();
|
|
185
|
+
*
|
|
186
|
+
* @param {string} path - Serial device path (e.g. /dev/ttyUSB0)
|
|
187
|
+
* @param {object} serial - baudRate, dataBits, stopBits, parity, slaveId
|
|
188
|
+
* @param {number} address - Starting register address
|
|
189
|
+
* @param {number} count - Number of registers to read
|
|
190
|
+
* @param {number} interval - Polling interval in seconds
|
|
191
|
+
* @param {object} collector - Collector with accept() method
|
|
192
|
+
* @param {object} clk - Clock with millis() method
|
|
193
|
+
* @param {object} [log] - Logger with error() method for connection failures
|
|
194
|
+
* @returns {object} Source with start() and stop() methods
|
|
195
|
+
*/
|
|
196
|
+
export function modbusRtuSource(path, serial, address, count, interval, collector, clk, log = console) {
|
|
197
|
+
if (typeof path !== 'string' || path.length === 0) {
|
|
198
|
+
throw new Error('Serial path must be a non-empty string');
|
|
199
|
+
}
|
|
200
|
+
if (!serial || typeof serial !== 'object') {
|
|
201
|
+
throw new Error('Serial config must be an object');
|
|
202
|
+
}
|
|
203
|
+
if (typeof serial.baudRate !== 'number' || serial.baudRate <= 0) {
|
|
204
|
+
throw new Error(`Baud rate must be a positive number, got: ${serial.baudRate}`);
|
|
205
|
+
}
|
|
206
|
+
if (typeof address !== 'number' || address < 0) {
|
|
207
|
+
throw new Error(`Address must be a non-negative number, got: ${address}`);
|
|
208
|
+
}
|
|
209
|
+
if (typeof count !== 'number' || count <= 0) {
|
|
210
|
+
throw new Error(`Count must be a positive number, got: ${count}`);
|
|
211
|
+
}
|
|
212
|
+
const client = new ModbusRTU();
|
|
213
|
+
const slaveId = serial.slaveId || 1;
|
|
214
|
+
const dataBits = serial.dataBits || 8;
|
|
215
|
+
const stopBits = serial.stopBits || 1;
|
|
216
|
+
const parity = serial.parity || 'none';
|
|
217
|
+
function connect(modbusClient) {
|
|
218
|
+
return modbusClient.connectRTUBuffered(path, {
|
|
219
|
+
baudRate: serial.baudRate,
|
|
220
|
+
dataBits,
|
|
221
|
+
stopBits,
|
|
222
|
+
parity
|
|
223
|
+
}).then(() => {
|
|
224
|
+
modbusClient.setID(slaveId);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return modbusPollingSource({
|
|
228
|
+
connect,
|
|
229
|
+
target: path,
|
|
230
|
+
address,
|
|
231
|
+
count,
|
|
232
|
+
interval,
|
|
233
|
+
collector,
|
|
234
|
+
clk,
|
|
235
|
+
client,
|
|
236
|
+
log
|
|
237
|
+
});
|
|
238
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { describe, it } from 'mocha';
|
|
3
|
+
import modbusRtuBusSource from '../src/modbusRtuBusSource.js';
|
|
4
|
+
|
|
5
|
+
describe('modbusRtuBusSource', () => {
|
|
6
|
+
it('throws on empty serial path', () => {
|
|
7
|
+
assert.throws(
|
|
8
|
+
() => {
|
|
9
|
+
return modbusRtuBusSource('', { baudRate: 9600 }, [{ slaveId: 1, collector: { accept() {} } }], {
|
|
10
|
+
address: 1280,
|
|
11
|
+
count: 30,
|
|
12
|
+
interval: 5
|
|
13
|
+
});
|
|
14
|
+
},
|
|
15
|
+
/Serial path must be a non-empty string/u,
|
|
16
|
+
'Should reject empty serial path'
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('throws on empty slaves list', () => {
|
|
21
|
+
assert.throws(
|
|
22
|
+
() => {
|
|
23
|
+
return modbusRtuBusSource(`/dev/ttyUSB${Math.floor(Math.random() * 9)}`, { baudRate: 9600 }, [], {
|
|
24
|
+
address: 1280,
|
|
25
|
+
count: 30,
|
|
26
|
+
interval: 5
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
/Slaves must be a non-empty array/u,
|
|
30
|
+
'Should reject empty slaves array'
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('creates source with start method for multiple slaves', () => {
|
|
35
|
+
const path = `/dev/ttyUSB${Math.floor(Math.random() * 9)}`;
|
|
36
|
+
const source = modbusRtuBusSource(
|
|
37
|
+
path,
|
|
38
|
+
{ baudRate: 9600, stopBits: 2 },
|
|
39
|
+
[
|
|
40
|
+
{ slaveId: 1, collector: { accept() {} } },
|
|
41
|
+
{ slaveId: 2, collector: { accept() {} } }
|
|
42
|
+
],
|
|
43
|
+
{ address: 1280, count: 30, interval: 5 }
|
|
44
|
+
);
|
|
45
|
+
assert.strictEqual(typeof source.start, 'function', 'Should expose start for RTU bus source');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { describe, it } from 'mocha';
|
|
3
|
+
import { modbusRtuSource } from '../src/modbusSource.js';
|
|
4
|
+
import fakeClock from './fakeClock.js';
|
|
5
|
+
|
|
6
|
+
describe('modbusRtuSource', () => {
|
|
7
|
+
it('throws on empty serial path', () => {
|
|
8
|
+
const collector = { accept: () => {} };
|
|
9
|
+
const clk = fakeClock(Math.floor(Math.random() * 10000));
|
|
10
|
+
assert.throws(
|
|
11
|
+
() => {return modbusRtuSource('', { baudRate: 9600 }, 4000, 44, 5, collector, clk)},
|
|
12
|
+
/Serial path must be a non-empty string/u,
|
|
13
|
+
'Should reject empty serial path'
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('throws on invalid baud rate', () => {
|
|
18
|
+
const collector = { accept: () => {} };
|
|
19
|
+
const clk = fakeClock(Math.floor(Math.random() * 10000));
|
|
20
|
+
assert.throws(
|
|
21
|
+
() => {return modbusRtuSource('/dev/ttyUSB0', { baudRate: 0 }, 4000, 44, 5, collector, clk)},
|
|
22
|
+
/Baud rate must be a positive number/u,
|
|
23
|
+
'Should reject zero baud rate'
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('creates source with start method', () => {
|
|
28
|
+
const collector = { accept: () => {} };
|
|
29
|
+
const clk = fakeClock(Math.floor(Math.random() * 10000));
|
|
30
|
+
const source = modbusRtuSource(`/dev/ttyUSB${Math.floor(Math.random() * 9)}`, { baudRate: 9600 }, 4000, 44, 5, collector, clk);
|
|
31
|
+
assert.strictEqual(typeof source.start, 'function', 'Should expose start method for RTU source');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('retries connection on failure', async () => {
|
|
35
|
+
let logged = false;
|
|
36
|
+
const log = { error: () => { logged = true; } };
|
|
37
|
+
const collector = { accept: () => {} };
|
|
38
|
+
const clk = fakeClock(Math.floor(Math.random() * 10000));
|
|
39
|
+
const source = modbusRtuSource(`/dev/tty\u044b${Math.random()}`, { baudRate: 9600 }, 4000, 44, 5, collector, clk, log);
|
|
40
|
+
source.start();
|
|
41
|
+
await new Promise(resolve => { setTimeout(resolve, 200); });
|
|
42
|
+
source.stop();
|
|
43
|
+
assert.strictEqual(logged, true, 'Should log RTU connection failure and retry');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -99,7 +99,8 @@ describe('modbusSource', () => {
|
|
|
99
99
|
const log = { error: () => { logged = true; } };
|
|
100
100
|
const collector = { accept: () => {} };
|
|
101
101
|
const clk = fakeClock(Math.floor(Math.random() * 10000));
|
|
102
|
-
const
|
|
102
|
+
const port = 65000 + Math.floor(Math.random() * 500);
|
|
103
|
+
const source = modbusSource('127.0.0.1', port, 4000, 14, 5, collector, clk, log);
|
|
103
104
|
source.start();
|
|
104
105
|
await new Promise(resolve => { setTimeout(resolve, 200); });
|
|
105
106
|
source.stop();
|
|
@@ -110,7 +111,8 @@ describe('modbusSource', () => {
|
|
|
110
111
|
const log = { error: () => {} };
|
|
111
112
|
const collector = { accept: () => {} };
|
|
112
113
|
const clk = fakeClock(Math.floor(Math.random() * 10000));
|
|
113
|
-
const
|
|
114
|
+
const port = 65000 + Math.floor(Math.random() * 500);
|
|
115
|
+
const source = modbusSource('127.0.0.1', port, 4000, 14, 5, collector, clk, log);
|
|
114
116
|
source.start();
|
|
115
117
|
await new Promise(resolve => { setTimeout(resolve, 200); });
|
|
116
118
|
source.stop();
|