@ya-modbus/transport 0.9.1 → 0.10.1
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 +12 -0
- package/README.md +74 -0
- package/dist/cjs/src/client-config.d.ts +15 -0
- package/dist/cjs/src/client-config.d.ts.map +1 -1
- package/dist/cjs/src/client-config.js +26 -0
- package/dist/cjs/src/client-config.js.map +1 -1
- package/dist/cjs/src/connection.d.ts +90 -0
- package/dist/cjs/src/connection.d.ts.map +1 -0
- package/dist/cjs/src/connection.js +217 -0
- package/dist/cjs/src/connection.js.map +1 -0
- package/dist/cjs/src/create-modbus-transport.d.ts +4 -2
- package/dist/cjs/src/create-modbus-transport.d.ts.map +1 -1
- package/dist/cjs/src/create-modbus-transport.js +29 -28
- package/dist/cjs/src/create-modbus-transport.js.map +1 -1
- package/dist/cjs/src/index.d.ts +1 -0
- package/dist/cjs/src/index.d.ts.map +1 -1
- package/dist/cjs/src/index.js +4 -1
- package/dist/cjs/src/index.js.map +1 -1
- package/dist/cjs/src/manager.d.ts +10 -0
- package/dist/cjs/src/manager.d.ts.map +1 -1
- package/dist/cjs/src/manager.js +19 -7
- package/dist/cjs/src/manager.js.map +1 -1
- package/dist/cjs/src/rtu-transport.d.ts +4 -0
- package/dist/cjs/src/rtu-transport.d.ts.map +1 -1
- package/dist/cjs/src/rtu-transport.js +19 -10
- package/dist/cjs/src/rtu-transport.js.map +1 -1
- package/dist/cjs/src/slave-transport.d.ts +10 -5
- package/dist/cjs/src/slave-transport.d.ts.map +1 -1
- package/dist/cjs/src/slave-transport.js +42 -39
- package/dist/cjs/src/slave-transport.js.map +1 -1
- package/dist/cjs/src/tcp-transport.d.ts +4 -1
- package/dist/cjs/src/tcp-transport.d.ts.map +1 -1
- package/dist/cjs/src/tcp-transport.js +14 -5
- package/dist/cjs/src/tcp-transport.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/client-config.d.ts +15 -0
- package/dist/esm/src/client-config.d.ts.map +1 -1
- package/dist/esm/src/client-config.js +25 -0
- package/dist/esm/src/client-config.js.map +1 -1
- package/dist/esm/src/connection.d.ts +90 -0
- package/dist/esm/src/connection.d.ts.map +1 -0
- package/dist/esm/src/connection.js +211 -0
- package/dist/esm/src/connection.js.map +1 -0
- package/dist/esm/src/create-modbus-transport.d.ts +4 -2
- package/dist/esm/src/create-modbus-transport.d.ts.map +1 -1
- package/dist/esm/src/create-modbus-transport.js +29 -28
- package/dist/esm/src/create-modbus-transport.js.map +1 -1
- package/dist/esm/src/index.d.ts +1 -0
- package/dist/esm/src/index.d.ts.map +1 -1
- package/dist/esm/src/index.js +1 -0
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/manager.d.ts +10 -0
- package/dist/esm/src/manager.d.ts.map +1 -1
- package/dist/esm/src/manager.js +20 -8
- package/dist/esm/src/manager.js.map +1 -1
- package/dist/esm/src/rtu-transport.d.ts +4 -0
- package/dist/esm/src/rtu-transport.d.ts.map +1 -1
- package/dist/esm/src/rtu-transport.js +19 -10
- package/dist/esm/src/rtu-transport.js.map +1 -1
- package/dist/esm/src/slave-transport.d.ts +10 -5
- package/dist/esm/src/slave-transport.d.ts.map +1 -1
- package/dist/esm/src/slave-transport.js +42 -39
- package/dist/esm/src/slave-transport.js.map +1 -1
- package/dist/esm/src/tcp-transport.d.ts +4 -1
- package/dist/esm/src/tcp-transport.d.ts.map +1 -1
- package/dist/esm/src/tcp-transport.js +15 -6
- package/dist/esm/src/tcp-transport.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [0.10.1](https://github.com/groupsky/ya-modbus/compare/@ya-modbus/transport@0.10.0...@ya-modbus/transport@0.10.1) (2026-10-12)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **transport:** time out TCP connects that get no answer ([#588](https://github.com/groupsky/ya-modbus/issues/588)) ([2eeaf08](https://github.com/groupsky/ya-modbus/commit/2eeaf08e1c04c17953591bb3000a63339823bffc)), closes [#569](https://github.com/groupsky/ya-modbus/issues/569)
|
|
11
|
+
|
|
12
|
+
# [0.10.0](https://github.com/groupsky/ya-modbus/compare/@ya-modbus/transport@0.9.1...@ya-modbus/transport@0.10.0) (2026-10-12)
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- **transport:** survive a lost serial port and reopen it after a backoff ([#549](https://github.com/groupsky/ya-modbus/issues/549)) ([b5b3e34](https://github.com/groupsky/ya-modbus/commit/b5b3e34e3f219b6600cd975f98680adebafe3093)), closes [#517](https://github.com/groupsky/ya-modbus/issues/517)
|
|
17
|
+
|
|
6
18
|
## [0.9.1](https://github.com/groupsky/ya-modbus/compare/@ya-modbus/transport@0.9.0...@ya-modbus/transport@0.9.1) (2026-10-12)
|
|
7
19
|
|
|
8
20
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ Modbus transport implementations for RTU (serial) and TCP connections.
|
|
|
7
7
|
- RTU (RS-485/serial) transport
|
|
8
8
|
- TCP/IP transport
|
|
9
9
|
- Automatic retry on transient failures
|
|
10
|
+
- Survives a lost port (unplugged USB adapter) and opens it again
|
|
10
11
|
- Factory for auto-detecting transport type
|
|
11
12
|
- TypeScript support
|
|
12
13
|
|
|
@@ -69,6 +70,79 @@ try {
|
|
|
69
70
|
|
|
70
71
|
`normalizeModbusError(error)` applies the same conversion to any caught value.
|
|
71
72
|
|
|
73
|
+
### Lost connections
|
|
74
|
+
|
|
75
|
+
When a USB serial adapter is unplugged or a TCP connection closes, `modbus-serial` emits a
|
|
76
|
+
`close` event, and an `error` event when a write fails. The transports listen for these events,
|
|
77
|
+
so the process does not crash. The connection is then lost:
|
|
78
|
+
|
|
79
|
+
- A request in flight rejects at once. It does not wait for the response timeout.
|
|
80
|
+
- Later requests reject at once, without using the port.
|
|
81
|
+
- The error has the name `ConnectionLostError` and a message like
|
|
82
|
+
`Connection to /dev/ttyUSB0 lost: port closed`, or
|
|
83
|
+
`Connection to /dev/ttyUSB0 lost: EIO: i/o error, write` when a write failed. The underlying
|
|
84
|
+
error is in `cause`. `code` and `errno` are set only when the underlying error is a Node.js
|
|
85
|
+
system error, for example `ECONNREFUSED` when a TCP connection cannot be opened again.
|
|
86
|
+
|
|
87
|
+
After a TCP socket error, `modbus-serial` emits no event. The next request fails with the error of
|
|
88
|
+
`modbus-serial` (for example `Port Not Open`), and the connection is lost from then on.
|
|
89
|
+
|
|
90
|
+
Opening a TCP connection takes at most `timeout` ms (the response timeout, default 1000 ms).
|
|
91
|
+
When the host does not answer, the open fails with `TCP Connection Timed Out`. Without this
|
|
92
|
+
limit it would wait for the TCP timeout of the system (about 2 minutes), and the other devices
|
|
93
|
+
on the connection would wait too. On a slow network, or one that loses packets, use a larger
|
|
94
|
+
`timeout` (for example 3000).
|
|
95
|
+
|
|
96
|
+
`createTransport` and `TransportManager` open the port again on a later request. There are no
|
|
97
|
+
attempts in the background. The first request 1 second after the loss opens the port again. If
|
|
98
|
+
this fails, the next attempt waits 2 seconds, then 4 seconds, and so on, up to 60 seconds.
|
|
99
|
+
Requests between the attempts reject with the last error. The delay also doubles when the port
|
|
100
|
+
opens but is lost again before a request succeeds. A successful request sets the delay back to
|
|
101
|
+
1 second. `TransportManager` opens one new client for all devices on the bus.
|
|
102
|
+
|
|
103
|
+
A short-lived program, like a CLI command, can stop at the first `ConnectionLostError`.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { isConnectionLostError } from '@ya-modbus/transport'
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
await transport.readHoldingRegisters(0, 10)
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (isConnectionLostError(error)) {
|
|
112
|
+
// port is gone, the transport opens it again on a later request
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
A USB adapter can get a new name when it is plugged in again (`/dev/ttyUSB0` becomes
|
|
118
|
+
`/dev/ttyUSB1`), but the port is opened again with the same path. Use a stable path:
|
|
119
|
+
|
|
120
|
+
- `/dev/serial/by-id/...` for adapters with a unique serial number.
|
|
121
|
+
- `/dev/serial/by-path/...` for adapters without a serial number (many CH340 adapters). This name
|
|
122
|
+
is the physical USB port, so it changes when the adapter moves to another USB port.
|
|
123
|
+
|
|
124
|
+
To use your own `modbus-serial` client with the same handling, wrap it in a `ModbusConnection`:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { createModbusTransport, ModbusConnection, type OpenClient } from '@ya-modbus/transport'
|
|
128
|
+
import ModbusRTU from 'modbus-serial'
|
|
129
|
+
|
|
130
|
+
// Returns a new connected client
|
|
131
|
+
const open: OpenClient = async () => {
|
|
132
|
+
const client = new ModbusRTU()
|
|
133
|
+
await client.connectRTUBuffered('/dev/ttyUSB0', { baudRate: 9600 })
|
|
134
|
+
client.setID(1)
|
|
135
|
+
client.setTimeout(1000)
|
|
136
|
+
return client
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const connection = new ModbusConnection(await open(), '/dev/ttyUSB0', open)
|
|
140
|
+
const transport = createModbusTransport(connection)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Without the `open` function, a lost connection works again only when you open the same client
|
|
144
|
+
again.
|
|
145
|
+
|
|
72
146
|
## License
|
|
73
147
|
|
|
74
148
|
GPL-3.0-or-later
|
|
@@ -12,4 +12,19 @@ export declare const DEFAULT_TIMEOUT_MS = 1000;
|
|
|
12
12
|
* @param timeout - Response timeout in milliseconds (default: DEFAULT_TIMEOUT_MS)
|
|
13
13
|
*/
|
|
14
14
|
export declare function configureClient(client: ModbusRTU, slaveId: number, timeout?: number): void;
|
|
15
|
+
/**
|
|
16
|
+
* Connect a modbus-serial client to a Modbus TCP server, and give up after `timeout` ms when
|
|
17
|
+
* the host does not answer
|
|
18
|
+
*
|
|
19
|
+
* Without a timeout, the connect waits for the TCP timeout of the system (about 2 minutes on
|
|
20
|
+
* Linux). modbus-serial uses the `timeout` option as the socket timeout, so a connect that
|
|
21
|
+
* takes longer fails with 'TCP Connection Timed Out'. After the connect, the socket keeps
|
|
22
|
+
* the timeout, but then it does not close the socket.
|
|
23
|
+
*
|
|
24
|
+
* @param client - New modbus-serial client
|
|
25
|
+
* @param host - TCP host (IP address or hostname)
|
|
26
|
+
* @param port - TCP port
|
|
27
|
+
* @param timeout - Connect timeout in milliseconds (default: DEFAULT_TIMEOUT_MS)
|
|
28
|
+
*/
|
|
29
|
+
export declare function connectTCP(client: ModbusRTU, host: string, port: number, timeout?: number): Promise<void>;
|
|
15
30
|
//# sourceMappingURL=client-config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-config.d.ts","sourceRoot":"","sources":["../../../src/client-config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,SAAS,MAAM,eAAe,CAAA;AAE1C,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,OAAO,CAAA;AAEtC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAG1F"}
|
|
1
|
+
{"version":3,"file":"client-config.d.ts","sourceRoot":"","sources":["../../../src/client-config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,SAAS,MAAM,eAAe,CAAA;AAE1C,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,OAAO,CAAA;AAEtC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAG1F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,SAAqB,GAC3B,OAAO,CAAC,IAAI,CAAC,CASf"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.DEFAULT_TIMEOUT_MS = void 0;
|
|
7
7
|
exports.configureClient = configureClient;
|
|
8
|
+
exports.connectTCP = connectTCP;
|
|
8
9
|
/** Default response timeout for Modbus operations in milliseconds */
|
|
9
10
|
exports.DEFAULT_TIMEOUT_MS = 1000;
|
|
10
11
|
/**
|
|
@@ -18,4 +19,29 @@ function configureClient(client, slaveId, timeout) {
|
|
|
18
19
|
client.setID(slaveId);
|
|
19
20
|
client.setTimeout(timeout ?? exports.DEFAULT_TIMEOUT_MS);
|
|
20
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Connect a modbus-serial client to a Modbus TCP server, and give up after `timeout` ms when
|
|
24
|
+
* the host does not answer
|
|
25
|
+
*
|
|
26
|
+
* Without a timeout, the connect waits for the TCP timeout of the system (about 2 minutes on
|
|
27
|
+
* Linux). modbus-serial uses the `timeout` option as the socket timeout, so a connect that
|
|
28
|
+
* takes longer fails with 'TCP Connection Timed Out'. After the connect, the socket keeps
|
|
29
|
+
* the timeout, but then it does not close the socket.
|
|
30
|
+
*
|
|
31
|
+
* @param client - New modbus-serial client
|
|
32
|
+
* @param host - TCP host (IP address or hostname)
|
|
33
|
+
* @param port - TCP port
|
|
34
|
+
* @param timeout - Connect timeout in milliseconds (default: DEFAULT_TIMEOUT_MS)
|
|
35
|
+
*/
|
|
36
|
+
async function connectTCP(client, host, port, timeout = exports.DEFAULT_TIMEOUT_MS) {
|
|
37
|
+
try {
|
|
38
|
+
await client.connectTCP(host, { port, timeout });
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
// After a timeout the socket still tries to connect. A connection that it opens later
|
|
42
|
+
// would stay open with nothing using it.
|
|
43
|
+
client.destroy(() => undefined);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
21
47
|
//# sourceMappingURL=client-config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-config.js","sourceRoot":"","sources":["../../../src/client-config.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAcH,0CAGC;
|
|
1
|
+
{"version":3,"file":"client-config.js","sourceRoot":"","sources":["../../../src/client-config.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAcH,0CAGC;AAgBD,gCAcC;AA3CD,qEAAqE;AACxD,QAAA,kBAAkB,GAAG,IAAI,CAAA;AAEtC;;;;;;GAMG;AACH,SAAgB,eAAe,CAAC,MAAiB,EAAE,OAAe,EAAE,OAAgB;IAClF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACrB,MAAM,CAAC,UAAU,CAAC,OAAO,IAAI,0BAAkB,CAAC,CAAA;AAClD,CAAC;AAED;;;;;;;;;;;;;GAaG;AACI,KAAK,UAAU,UAAU,CAC9B,MAAiB,EACjB,IAAY,EACZ,IAAY,EACZ,OAAO,GAAG,0BAAkB;IAE5B,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;IAClD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,sFAAsF;QACtF,yCAAyC;QACzC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;QAC/B,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection of lost connections and reopening of modbus-serial clients
|
|
3
|
+
*/
|
|
4
|
+
import type ModbusRTU from 'modbus-serial';
|
|
5
|
+
import { type ModbusError } from './normalize-error.js';
|
|
6
|
+
/** Delay before the first attempt to reopen a lost connection, in milliseconds */
|
|
7
|
+
export declare const REOPEN_DELAY_MS = 1000;
|
|
8
|
+
/** Longest delay between attempts to reopen a lost connection, in milliseconds */
|
|
9
|
+
export declare const MAX_REOPEN_DELAY_MS = 60000;
|
|
10
|
+
/**
|
|
11
|
+
* Opens a new connected and configured client
|
|
12
|
+
*/
|
|
13
|
+
export type OpenClient = () => Promise<ModbusRTU>;
|
|
14
|
+
/**
|
|
15
|
+
* Check if an error means that the connection is lost (see ModbusConnection)
|
|
16
|
+
*
|
|
17
|
+
* @param error - Value that was thrown or used to reject a promise
|
|
18
|
+
* @returns True for a ConnectionLostError
|
|
19
|
+
*/
|
|
20
|
+
export declare function isConnectionLostError(error: unknown): error is ModbusError;
|
|
21
|
+
/** Marks ModbusConnection objects, the same for all copies of this package */
|
|
22
|
+
declare const CONNECTION: unique symbol;
|
|
23
|
+
/**
|
|
24
|
+
* A modbus-serial client together with the state of its connection.
|
|
25
|
+
*
|
|
26
|
+
* modbus-serial emits 'error' (for example when a USB serial adapter is unplugged)
|
|
27
|
+
* and 'close' when the port fails. Node.js throws an 'error' event that has no
|
|
28
|
+
* listener, which crashes the process. ModbusConnection listens for both events
|
|
29
|
+
* and for clients that are no longer open, and then marks the connection as lost:
|
|
30
|
+
* - requests in flight reject at once, without waiting for the response timeout
|
|
31
|
+
* - later requests reject with the same ConnectionLostError without using the port
|
|
32
|
+
* - with an `open` function, the first request after a delay opens a new client.
|
|
33
|
+
* The delay starts at REOPEN_DELAY_MS and doubles after each attempt that does
|
|
34
|
+
* not lead to a successful request, up to MAX_REOPEN_DELAY_MS.
|
|
35
|
+
* - without an `open` function, requests reject until the owner of the client
|
|
36
|
+
* opens it again
|
|
37
|
+
*/
|
|
38
|
+
export declare class ModbusConnection {
|
|
39
|
+
private readonly target?;
|
|
40
|
+
private readonly open?;
|
|
41
|
+
readonly [CONNECTION] = true;
|
|
42
|
+
private client;
|
|
43
|
+
private lostError;
|
|
44
|
+
private reopenDelay;
|
|
45
|
+
private reopenAt;
|
|
46
|
+
private reopening;
|
|
47
|
+
private closed;
|
|
48
|
+
private readonly inFlight;
|
|
49
|
+
/**
|
|
50
|
+
* @param client - Connected modbus-serial client
|
|
51
|
+
* @param target - Port path or host:port, used in error messages
|
|
52
|
+
* @param open - Opens a new client after the connection is lost
|
|
53
|
+
*/
|
|
54
|
+
constructor(client: ModbusRTU, target?: string | undefined, open?: OpenClient | undefined);
|
|
55
|
+
/**
|
|
56
|
+
* Run a request with the client
|
|
57
|
+
*
|
|
58
|
+
* @param request - Request to run
|
|
59
|
+
* @returns Result of the request
|
|
60
|
+
* @throws ConnectionLostError when the connection is lost before or during the request
|
|
61
|
+
*/
|
|
62
|
+
run<T>(request: (client: ModbusRTU) => Promise<T>): Promise<T>;
|
|
63
|
+
/**
|
|
64
|
+
* Close the client and stop reopening it
|
|
65
|
+
*/
|
|
66
|
+
close(): Promise<void>;
|
|
67
|
+
private watch;
|
|
68
|
+
private lose;
|
|
69
|
+
/** Set the time of the next reopen attempt, and wait longer after that one */
|
|
70
|
+
private delayReopen;
|
|
71
|
+
/**
|
|
72
|
+
* Get a working client for a lost connection, unless it is too early to try again
|
|
73
|
+
*
|
|
74
|
+
* @param lostError - The error that the connection was lost with
|
|
75
|
+
*/
|
|
76
|
+
private reopen;
|
|
77
|
+
private openClient;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Use a ModbusConnection as it is, or wrap a plain client in a new one
|
|
81
|
+
*
|
|
82
|
+
* Checks the mark instead of `instanceof`, because the CommonJS and ES module
|
|
83
|
+
* builds of this package each have their own ModbusConnection class.
|
|
84
|
+
*
|
|
85
|
+
* @param client - A ModbusConnection or a connected modbus-serial client
|
|
86
|
+
* @returns The connection
|
|
87
|
+
*/
|
|
88
|
+
export declare function toConnection(client: ModbusRTU | ModbusConnection): ModbusConnection;
|
|
89
|
+
export {};
|
|
90
|
+
//# sourceMappingURL=connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../src/connection.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,SAAS,MAAM,eAAe,CAAA;AAE1C,OAAO,EAAwB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAE7E,kFAAkF;AAClF,eAAO,MAAM,eAAe,OAAO,CAAA;AAEnC,kFAAkF;AAClF,eAAO,MAAM,mBAAmB,QAAS,CAAA;AAEzC;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAAA;AAEjD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAE1E;AAeD,8EAA8E;AAC9E,QAAA,MAAM,UAAU,eAAsD,CAAA;AAqBtE;;;;;;;;;;;;;;GAcG;AACH,qBAAa,gBAAgB;IAiBzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IAjBxB,QAAQ,CAAC,CAAC,UAAU,CAAC,QAAO;IAC5B,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0C;IAEnE;;;;OAIG;gBAED,MAAM,EAAE,SAAS,EACA,MAAM,CAAC,EAAE,MAAM,YAAA,EACf,IAAI,CAAC,EAAE,UAAU,YAAA;IAMpC;;;;;;OAMG;IACG,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IA0BpE;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,OAAO,CAAC,KAAK;IAOb,OAAO,CAAC,IAAI;IAYZ,8EAA8E;IAC9E,OAAO,CAAC,WAAW;IAKnB;;;;OAIG;IACH,OAAO,CAAC,MAAM;YAkBA,UAAU;CAqBzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,gBAAgB,GAAG,gBAAgB,CAEnF"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Detection of lost connections and reopening of modbus-serial clients
|
|
4
|
+
*/
|
|
5
|
+
var _a;
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.ModbusConnection = exports.MAX_REOPEN_DELAY_MS = exports.REOPEN_DELAY_MS = void 0;
|
|
8
|
+
exports.isConnectionLostError = isConnectionLostError;
|
|
9
|
+
exports.toConnection = toConnection;
|
|
10
|
+
const node_events_1 = require("node:events");
|
|
11
|
+
const normalize_error_js_1 = require("./normalize-error.js");
|
|
12
|
+
/** Delay before the first attempt to reopen a lost connection, in milliseconds */
|
|
13
|
+
exports.REOPEN_DELAY_MS = 1000;
|
|
14
|
+
/** Longest delay between attempts to reopen a lost connection, in milliseconds */
|
|
15
|
+
exports.MAX_REOPEN_DELAY_MS = 60000;
|
|
16
|
+
/**
|
|
17
|
+
* Check if an error means that the connection is lost (see ModbusConnection)
|
|
18
|
+
*
|
|
19
|
+
* @param error - Value that was thrown or used to reject a promise
|
|
20
|
+
* @returns True for a ConnectionLostError
|
|
21
|
+
*/
|
|
22
|
+
function isConnectionLostError(error) {
|
|
23
|
+
return error instanceof Error && error.name === 'ConnectionLostError';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Close a client so that a late 'error' event cannot crash the process
|
|
27
|
+
*
|
|
28
|
+
* modbus-serial stops forwarding the 'error' events of its port when the client
|
|
29
|
+
* closes. An error that the port emits after that (for example from an unplugged
|
|
30
|
+
* adapter) would have no listener, so the port keeps one that ignores it.
|
|
31
|
+
*/
|
|
32
|
+
function closeClient(client, callback) {
|
|
33
|
+
const port = Reflect.get(client, '_port');
|
|
34
|
+
if (port instanceof node_events_1.EventEmitter)
|
|
35
|
+
port.on('error', () => undefined);
|
|
36
|
+
client.close(callback);
|
|
37
|
+
}
|
|
38
|
+
/** Marks ModbusConnection objects, the same for all copies of this package */
|
|
39
|
+
const CONNECTION = Symbol.for('@ya-modbus/transport/ModbusConnection');
|
|
40
|
+
/**
|
|
41
|
+
* Error for a lost connection, with code and errno of a Node.js system error cause
|
|
42
|
+
*/
|
|
43
|
+
function connectionLostError(target, cause) {
|
|
44
|
+
const reason = (0, normalize_error_js_1.normalizeModbusError)(cause);
|
|
45
|
+
const error = new Error(`Connection ${target === undefined ? '' : `to ${target} `}lost: ${reason.message}`, { cause: reason });
|
|
46
|
+
error.name = 'ConnectionLostError';
|
|
47
|
+
// modbus-serial gives all its port errors errno 'ECONNREFUSED', also an unplugged
|
|
48
|
+
// serial adapter. Only Node.js system errors (they have a code) give a real errno.
|
|
49
|
+
if (reason.code !== undefined) {
|
|
50
|
+
error.code = reason.code;
|
|
51
|
+
error.errno = reason.errno;
|
|
52
|
+
}
|
|
53
|
+
return error;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A modbus-serial client together with the state of its connection.
|
|
57
|
+
*
|
|
58
|
+
* modbus-serial emits 'error' (for example when a USB serial adapter is unplugged)
|
|
59
|
+
* and 'close' when the port fails. Node.js throws an 'error' event that has no
|
|
60
|
+
* listener, which crashes the process. ModbusConnection listens for both events
|
|
61
|
+
* and for clients that are no longer open, and then marks the connection as lost:
|
|
62
|
+
* - requests in flight reject at once, without waiting for the response timeout
|
|
63
|
+
* - later requests reject with the same ConnectionLostError without using the port
|
|
64
|
+
* - with an `open` function, the first request after a delay opens a new client.
|
|
65
|
+
* The delay starts at REOPEN_DELAY_MS and doubles after each attempt that does
|
|
66
|
+
* not lead to a successful request, up to MAX_REOPEN_DELAY_MS.
|
|
67
|
+
* - without an `open` function, requests reject until the owner of the client
|
|
68
|
+
* opens it again
|
|
69
|
+
*/
|
|
70
|
+
class ModbusConnection {
|
|
71
|
+
/**
|
|
72
|
+
* @param client - Connected modbus-serial client
|
|
73
|
+
* @param target - Port path or host:port, used in error messages
|
|
74
|
+
* @param open - Opens a new client after the connection is lost
|
|
75
|
+
*/
|
|
76
|
+
constructor(client, target, open) {
|
|
77
|
+
this.target = target;
|
|
78
|
+
this.open = open;
|
|
79
|
+
this[_a] = true;
|
|
80
|
+
this.reopenDelay = exports.REOPEN_DELAY_MS;
|
|
81
|
+
this.reopenAt = 0;
|
|
82
|
+
this.closed = false;
|
|
83
|
+
this.inFlight = new Set();
|
|
84
|
+
this.client = client;
|
|
85
|
+
this.watch(client);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Run a request with the client
|
|
89
|
+
*
|
|
90
|
+
* @param request - Request to run
|
|
91
|
+
* @returns Result of the request
|
|
92
|
+
* @throws ConnectionLostError when the connection is lost before or during the request
|
|
93
|
+
*/
|
|
94
|
+
async run(request) {
|
|
95
|
+
// No await while the connection works, so the request starts at once
|
|
96
|
+
const client = this.lostError === undefined ? this.client : await this.reopen(this.lostError);
|
|
97
|
+
// Assigned at once: the promise executor runs synchronously
|
|
98
|
+
let rejectOnLoss;
|
|
99
|
+
const lost = new Promise((_resolve, reject) => {
|
|
100
|
+
rejectOnLoss = reject;
|
|
101
|
+
});
|
|
102
|
+
// Handled here too, in case request() throws before Promise.race() listens
|
|
103
|
+
lost.catch(() => undefined);
|
|
104
|
+
this.inFlight.add(rejectOnLoss);
|
|
105
|
+
try {
|
|
106
|
+
const value = await Promise.race([request(client), lost]);
|
|
107
|
+
// The port works, so the next loss starts again with the shortest delay
|
|
108
|
+
this.reopenDelay = exports.REOPEN_DELAY_MS;
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
// modbus-serial clears isOpen on some failures without an event (TCP socket errors)
|
|
113
|
+
if (!client.isOpen)
|
|
114
|
+
this.lose(client, error);
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
this.inFlight.delete(rejectOnLoss);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Close the client and stop reopening it
|
|
123
|
+
*/
|
|
124
|
+
async close() {
|
|
125
|
+
this.closed = true;
|
|
126
|
+
// A lost client or a client that is not open needs no close. modbus-serial never
|
|
127
|
+
// calls back when it closes a TCP client whose socket is closed.
|
|
128
|
+
if (this.lostError !== undefined || !this.client.isOpen)
|
|
129
|
+
return;
|
|
130
|
+
const client = this.client;
|
|
131
|
+
await new Promise((resolve) => {
|
|
132
|
+
closeClient(client, () => resolve());
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
watch(client) {
|
|
136
|
+
// The listeners stay for the life of the client: a late 'error' from a lost
|
|
137
|
+
// client must not crash the process
|
|
138
|
+
client.on('error', (error) => this.lose(client, error));
|
|
139
|
+
client.on('close', () => this.lose(client, 'port closed'));
|
|
140
|
+
}
|
|
141
|
+
lose(client, cause) {
|
|
142
|
+
// Ignore events from clients that were replaced, and repeated events
|
|
143
|
+
if (client !== this.client || this.lostError !== undefined || this.closed)
|
|
144
|
+
return;
|
|
145
|
+
this.lostError = connectionLostError(this.target, cause);
|
|
146
|
+
this.delayReopen();
|
|
147
|
+
for (const reject of this.inFlight)
|
|
148
|
+
reject(this.lostError);
|
|
149
|
+
// serialport closes the port on a disconnect. A port that is still open keeps
|
|
150
|
+
// its lock, and the reopen would fail.
|
|
151
|
+
if (client.isOpen)
|
|
152
|
+
closeClient(client, () => undefined);
|
|
153
|
+
}
|
|
154
|
+
/** Set the time of the next reopen attempt, and wait longer after that one */
|
|
155
|
+
delayReopen() {
|
|
156
|
+
this.reopenAt = performance.now() + this.reopenDelay;
|
|
157
|
+
this.reopenDelay = Math.min(this.reopenDelay * 2, exports.MAX_REOPEN_DELAY_MS);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get a working client for a lost connection, unless it is too early to try again
|
|
161
|
+
*
|
|
162
|
+
* @param lostError - The error that the connection was lost with
|
|
163
|
+
*/
|
|
164
|
+
reopen(lostError) {
|
|
165
|
+
if (this.open === undefined) {
|
|
166
|
+
// The owner of the client can open it again
|
|
167
|
+
if (this.client.isOpen && !this.closed) {
|
|
168
|
+
this.lostError = undefined;
|
|
169
|
+
return Promise.resolve(this.client);
|
|
170
|
+
}
|
|
171
|
+
return Promise.reject(lostError);
|
|
172
|
+
}
|
|
173
|
+
if (this.closed || performance.now() < this.reopenAt)
|
|
174
|
+
return Promise.reject(lostError);
|
|
175
|
+
// Requests that come while the client opens wait for the same attempt
|
|
176
|
+
this.reopening ?? (this.reopening = this.openClient(this.open, lostError).finally(() => {
|
|
177
|
+
this.reopening = undefined;
|
|
178
|
+
}));
|
|
179
|
+
return this.reopening;
|
|
180
|
+
}
|
|
181
|
+
async openClient(open, lostError) {
|
|
182
|
+
let client;
|
|
183
|
+
try {
|
|
184
|
+
client = await open();
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
const openError = connectionLostError(this.target, error);
|
|
188
|
+
this.lostError = openError;
|
|
189
|
+
this.delayReopen();
|
|
190
|
+
throw openError;
|
|
191
|
+
}
|
|
192
|
+
if (this.closed) {
|
|
193
|
+
// close() was called while the client was opening
|
|
194
|
+
closeClient(client, () => undefined);
|
|
195
|
+
throw lostError;
|
|
196
|
+
}
|
|
197
|
+
this.client = client;
|
|
198
|
+
this.lostError = undefined;
|
|
199
|
+
this.watch(client);
|
|
200
|
+
return client;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
exports.ModbusConnection = ModbusConnection;
|
|
204
|
+
_a = CONNECTION;
|
|
205
|
+
/**
|
|
206
|
+
* Use a ModbusConnection as it is, or wrap a plain client in a new one
|
|
207
|
+
*
|
|
208
|
+
* Checks the mark instead of `instanceof`, because the CommonJS and ES module
|
|
209
|
+
* builds of this package each have their own ModbusConnection class.
|
|
210
|
+
*
|
|
211
|
+
* @param client - A ModbusConnection or a connected modbus-serial client
|
|
212
|
+
* @returns The connection
|
|
213
|
+
*/
|
|
214
|
+
function toConnection(client) {
|
|
215
|
+
return CONNECTION in client ? client : new ModbusConnection(client);
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../../src/connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import type { Transport } from '@ya-modbus/driver-types';
|
|
2
2
|
import type ModbusRTU from 'modbus-serial';
|
|
3
|
+
import { type ModbusConnection } from './connection.js';
|
|
3
4
|
import { type RetryLogger } from './retry.js';
|
|
4
5
|
/**
|
|
5
6
|
* Create a Transport implementation from a ModbusRTU client
|
|
6
7
|
*
|
|
7
8
|
* This factory wraps all modbus-serial client methods with retry logic
|
|
8
9
|
* and converts them to the Transport interface expected by device drivers.
|
|
10
|
+
* Requests on a lost connection reject with a ConnectionLostError (see ModbusConnection).
|
|
9
11
|
*
|
|
10
|
-
* @param client - Configured ModbusRTU client instance
|
|
12
|
+
* @param client - Configured ModbusRTU client instance, or a ModbusConnection that can reopen it
|
|
11
13
|
* @param maxRetries - Maximum retry attempts (default: 3, use 1 for discovery to disable retries)
|
|
12
14
|
* @param logger - Optional callback to log retry attempts for debugging
|
|
13
15
|
* @returns Transport implementation
|
|
14
16
|
*/
|
|
15
|
-
export declare function createModbusTransport(client: ModbusRTU, maxRetries?: number, logger?: RetryLogger): Transport;
|
|
17
|
+
export declare function createModbusTransport(client: ModbusRTU | ModbusConnection, maxRetries?: number, logger?: RetryLogger): Transport;
|
|
16
18
|
//# sourceMappingURL=create-modbus-transport.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-modbus-transport.d.ts","sourceRoot":"","sources":["../../../src/create-modbus-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,SAAS,MAAM,eAAe,CAAA;AAE1C,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,YAAY,CAAA;AAExD
|
|
1
|
+
{"version":3,"file":"create-modbus-transport.d.ts","sourceRoot":"","sources":["../../../src/create-modbus-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,SAAS,MAAM,eAAe,CAAA;AAE1C,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACrE,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,YAAY,CAAA;AAExD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,GAAG,gBAAgB,EACpC,UAAU,GAAE,MAAU,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,SAAS,CA4GX"}
|
|
@@ -1,61 +1,64 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createModbusTransport = createModbusTransport;
|
|
4
|
+
const connection_js_1 = require("./connection.js");
|
|
4
5
|
const retry_js_1 = require("./retry.js");
|
|
5
6
|
/**
|
|
6
7
|
* Create a Transport implementation from a ModbusRTU client
|
|
7
8
|
*
|
|
8
9
|
* This factory wraps all modbus-serial client methods with retry logic
|
|
9
10
|
* and converts them to the Transport interface expected by device drivers.
|
|
11
|
+
* Requests on a lost connection reject with a ConnectionLostError (see ModbusConnection).
|
|
10
12
|
*
|
|
11
|
-
* @param client - Configured ModbusRTU client instance
|
|
13
|
+
* @param client - Configured ModbusRTU client instance, or a ModbusConnection that can reopen it
|
|
12
14
|
* @param maxRetries - Maximum retry attempts (default: 3, use 1 for discovery to disable retries)
|
|
13
15
|
* @param logger - Optional callback to log retry attempts for debugging
|
|
14
16
|
* @returns Transport implementation
|
|
15
17
|
*/
|
|
16
18
|
function createModbusTransport(client, maxRetries = 3, logger) {
|
|
19
|
+
const connection = (0, connection_js_1.toConnection)(client);
|
|
17
20
|
return {
|
|
18
21
|
async readHoldingRegisters(address, count) {
|
|
19
|
-
return (0, retry_js_1.withRetry)(async () => {
|
|
20
|
-
const result = await
|
|
22
|
+
return (0, retry_js_1.withRetry)(() => connection.run(async (modbus) => {
|
|
23
|
+
const result = await modbus.readHoldingRegisters(address, count);
|
|
21
24
|
return result.buffer;
|
|
22
|
-
}, maxRetries, logger);
|
|
25
|
+
}), maxRetries, logger);
|
|
23
26
|
},
|
|
24
27
|
async readInputRegisters(address, count) {
|
|
25
|
-
return (0, retry_js_1.withRetry)(async () => {
|
|
26
|
-
const result = await
|
|
28
|
+
return (0, retry_js_1.withRetry)(() => connection.run(async (modbus) => {
|
|
29
|
+
const result = await modbus.readInputRegisters(address, count);
|
|
27
30
|
return result.buffer;
|
|
28
|
-
}, maxRetries, logger);
|
|
31
|
+
}), maxRetries, logger);
|
|
29
32
|
},
|
|
30
33
|
async readCoils(address, count) {
|
|
31
|
-
return (0, retry_js_1.withRetry)(async () => {
|
|
32
|
-
const result = await
|
|
34
|
+
return (0, retry_js_1.withRetry)(() => connection.run(async (modbus) => {
|
|
35
|
+
const result = await modbus.readCoils(address, count);
|
|
33
36
|
return result.buffer;
|
|
34
|
-
}, maxRetries, logger);
|
|
37
|
+
}), maxRetries, logger);
|
|
35
38
|
},
|
|
36
39
|
async readDiscreteInputs(address, count) {
|
|
37
|
-
return (0, retry_js_1.withRetry)(async () => {
|
|
38
|
-
const result = await
|
|
40
|
+
return (0, retry_js_1.withRetry)(() => connection.run(async (modbus) => {
|
|
41
|
+
const result = await modbus.readDiscreteInputs(address, count);
|
|
39
42
|
return result.buffer;
|
|
40
|
-
}, maxRetries, logger);
|
|
43
|
+
}), maxRetries, logger);
|
|
41
44
|
},
|
|
42
45
|
async writeSingleRegister(address, value) {
|
|
43
|
-
return (0, retry_js_1.withRetry)(async () => {
|
|
44
|
-
await
|
|
45
|
-
}, maxRetries, logger);
|
|
46
|
+
return (0, retry_js_1.withRetry)(() => connection.run(async (modbus) => {
|
|
47
|
+
await modbus.writeRegister(address, value);
|
|
48
|
+
}), maxRetries, logger);
|
|
46
49
|
},
|
|
47
50
|
async writeMultipleRegisters(address, values) {
|
|
48
|
-
return (0, retry_js_1.withRetry)(async () => {
|
|
49
|
-
await
|
|
50
|
-
}, maxRetries, logger);
|
|
51
|
+
return (0, retry_js_1.withRetry)(() => connection.run(async (modbus) => {
|
|
52
|
+
await modbus.writeRegisters(address, values);
|
|
53
|
+
}), maxRetries, logger);
|
|
51
54
|
},
|
|
52
55
|
async writeSingleCoil(address, value) {
|
|
53
|
-
return (0, retry_js_1.withRetry)(async () => {
|
|
54
|
-
await
|
|
55
|
-
}, maxRetries, logger);
|
|
56
|
+
return (0, retry_js_1.withRetry)(() => connection.run(async (modbus) => {
|
|
57
|
+
await modbus.writeCoil(address, value);
|
|
58
|
+
}), maxRetries, logger);
|
|
56
59
|
},
|
|
57
60
|
async writeMultipleCoils(address, values) {
|
|
58
|
-
return (0, retry_js_1.withRetry)(async () => {
|
|
61
|
+
return (0, retry_js_1.withRetry)(() => connection.run(async (modbus) => {
|
|
59
62
|
// Convert Buffer to boolean array
|
|
60
63
|
const bools = [];
|
|
61
64
|
for (let i = 0; i < values.length * 8; i++) {
|
|
@@ -64,13 +67,11 @@ function createModbusTransport(client, maxRetries = 3, logger) {
|
|
|
64
67
|
const byte = values[byteIndex]; // byteIndex < values.length due to loop condition
|
|
65
68
|
bools.push((byte & (1 << bitIndex)) !== 0);
|
|
66
69
|
}
|
|
67
|
-
await
|
|
68
|
-
}, maxRetries, logger);
|
|
70
|
+
await modbus.writeCoils(address, bools);
|
|
71
|
+
}), maxRetries, logger);
|
|
69
72
|
},
|
|
70
73
|
async close() {
|
|
71
|
-
return
|
|
72
|
-
client.close(resolve);
|
|
73
|
-
});
|
|
74
|
+
return connection.close();
|
|
74
75
|
},
|
|
75
76
|
};
|
|
76
77
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-modbus-transport.js","sourceRoot":"","sources":["../../../src/create-modbus-transport.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"create-modbus-transport.js","sourceRoot":"","sources":["../../../src/create-modbus-transport.ts"],"names":[],"mappings":"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"}
|