@flux-control/effect-modbus-rs 0.1.0 → 0.2.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/README.md +147 -86
- package/dist/index.d.ts +31 -11
- package/dist/index.js +326 -251
- package/dist/src/AsciiTransportService.d.ts +3 -3
- package/dist/src/RtuTransportService.d.ts +3 -3
- package/dist/src/SerialModbusServerService.d.ts +5 -5
- package/dist/src/SerialTransportService.d.ts +4 -4
- package/dist/src/TcpGatewayService.d.ts +4 -4
- package/dist/src/TcpModbusServerService.d.ts +4 -4
- package/dist/src/TcpTransportService.d.ts +3 -3
- package/dist/src/WasmAsciiTransportService.d.ts +56 -0
- package/dist/src/WasmRtuTransportService.d.ts +56 -0
- package/dist/src/WasmSerialModbusServerService.d.ts +54 -0
- package/dist/src/WasmSerialPort.d.ts +27 -0
- package/dist/src/WasmSerialTransportService.d.ts +48 -0
- package/dist/src/WasmTcpServerService.d.ts +38 -0
- package/dist/src/WasmWsTransportService.d.ts +45 -0
- package/dist/src/mocks.d.ts +6 -5
- package/dist/src/modbus-client.d.ts +29 -21
- package/dist/src/modbus-client.wasm.test.d.ts +1 -0
- package/dist/src/shared-transport.d.ts +8 -4
- package/dist/src/wasm-mocks.test.d.ts +1 -0
- package/package.json +27 -28
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { SlaveDeviceDefinitions } from
|
|
1
|
+
import { Effect, Layer } from 'effect';
|
|
2
|
+
import type { AsciiTransportOptions } from 'modbus-rs';
|
|
3
|
+
import { SlaveDeviceDefinitions } from './mocks';
|
|
4
4
|
declare const AsciiTransportService_base: Effect.Service.Class<AsciiTransportService, "AsciiTransportService", {
|
|
5
5
|
readonly scoped: (options: AsciiTransportOptions) => Effect.Effect<{
|
|
6
6
|
withClient: (unitId: number) => Effect.Effect<import("./modbus-client").EffectModbusClient, import("./errors").ModbusError, never>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import type { SlaveDeviceDefinitions } from
|
|
1
|
+
import { Effect, Layer } from 'effect';
|
|
2
|
+
import type { RtuTransportOptions } from 'modbus-rs';
|
|
3
|
+
import type { SlaveDeviceDefinitions } from './mocks';
|
|
4
4
|
declare const RtuTransportService_base: Effect.Service.Class<RtuTransportService, "RtuTransportService", {
|
|
5
5
|
readonly scoped: (options: RtuTransportOptions) => Effect.Effect<{
|
|
6
6
|
withClient: (unitId: number) => Effect.Effect<import("./modbus-client").EffectModbusClient, import("./errors").ModbusError, never>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import type { ModbusError } from
|
|
1
|
+
import { Layer } from 'effect';
|
|
2
|
+
import type { SerialServerOptions, ServerHandlers } from 'modbus-rs';
|
|
3
|
+
import type { ModbusError } from './errors';
|
|
4
4
|
/**
|
|
5
5
|
* A scoped {@link Layer} that starts a Modbus serial RTU server.
|
|
6
6
|
*
|
|
@@ -11,7 +11,7 @@ import type { ModbusError } from "./errors";
|
|
|
11
11
|
* @example
|
|
12
12
|
* ```ts
|
|
13
13
|
* import { Effect, Layer } from "effect";
|
|
14
|
-
* import { serialRtuServerLayer } from "effect-modbus-rs";
|
|
14
|
+
* import { serialRtuServerLayer } from "@flux-control/effect-modbus-rs";
|
|
15
15
|
*
|
|
16
16
|
* const ServerLive = serialRtuServerLayer(
|
|
17
17
|
* { portPath: "/dev/ttyUSB0", baudRate: 9600, unitId: 1 },
|
|
@@ -35,7 +35,7 @@ export declare const serialRtuServerLayer: (options: SerialServerOptions, handle
|
|
|
35
35
|
* @example
|
|
36
36
|
* ```ts
|
|
37
37
|
* import { Effect, Layer } from "effect";
|
|
38
|
-
* import { serialAsciiServerLayer } from "effect-modbus-rs";
|
|
38
|
+
* import { serialAsciiServerLayer } from "@flux-control/effect-modbus-rs";
|
|
39
39
|
*
|
|
40
40
|
* const ServerLive = serialAsciiServerLayer(
|
|
41
41
|
* { portPath: "/dev/ttyUSB0", baudRate: 9600, unitId: 1 },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Context, Layer } from
|
|
2
|
-
import type { AsciiTransportOptions, RtuTransportOptions } from
|
|
3
|
-
import type
|
|
4
|
-
import {
|
|
1
|
+
import { Context, Layer } from 'effect';
|
|
2
|
+
import type { AsciiTransportOptions, RtuTransportOptions } from 'modbus-rs';
|
|
3
|
+
import { type SlaveDeviceDefinitions } from './mocks';
|
|
4
|
+
import type { TransportServiceApi } from './shared-transport';
|
|
5
5
|
declare const SerialTransportService_base: Context.TagClass<SerialTransportService, "SerialTransportService", TransportServiceApi>;
|
|
6
6
|
/**
|
|
7
7
|
* Abstract serial Modbus transport service tag.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import type { ModbusError } from
|
|
1
|
+
import { Layer } from 'effect';
|
|
2
|
+
import type { GatewayBindOptions, GatewayConfig } from 'modbus-rs';
|
|
3
|
+
import type { ModbusError } from './errors';
|
|
4
4
|
/**
|
|
5
5
|
* A scoped {@link Layer} that starts a Modbus TCP gateway.
|
|
6
6
|
*
|
|
@@ -17,7 +17,7 @@ import type { ModbusError } from "./errors";
|
|
|
17
17
|
* @example
|
|
18
18
|
* ```ts
|
|
19
19
|
* import { Effect, Layer } from "effect";
|
|
20
|
-
* import { tcpGatewayLayer } from "effect-modbus-rs";
|
|
20
|
+
* import { tcpGatewayLayer } from "@flux-control/effect-modbus-rs";
|
|
21
21
|
*
|
|
22
22
|
* const GatewayLive = tcpGatewayLayer(
|
|
23
23
|
* { host: "0.0.0.0", port: 8502 },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import type { ModbusError } from
|
|
1
|
+
import { Layer } from 'effect';
|
|
2
|
+
import type { ServerHandlers, TcpServerOptions } from 'modbus-rs';
|
|
3
|
+
import type { ModbusError } from './errors';
|
|
4
4
|
/**
|
|
5
5
|
* A scoped {@link Layer} that starts a Modbus TCP server.
|
|
6
6
|
*
|
|
@@ -15,7 +15,7 @@ import type { ModbusError } from "./errors";
|
|
|
15
15
|
* @example
|
|
16
16
|
* ```ts
|
|
17
17
|
* import { Effect, Layer } from "effect";
|
|
18
|
-
* import { tcpServerLayer } from "effect-modbus-rs";
|
|
18
|
+
* import { tcpServerLayer } from "@flux-control/effect-modbus-rs";
|
|
19
19
|
*
|
|
20
20
|
* const ServerLive = tcpServerLayer(
|
|
21
21
|
* { host: "0.0.0.0", port: 502, unitId: 1 },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { SlaveDeviceDefinitions } from
|
|
1
|
+
import { Effect, Layer } from 'effect';
|
|
2
|
+
import type { TcpTransportOptions } from 'modbus-rs';
|
|
3
|
+
import { SlaveDeviceDefinitions } from './mocks';
|
|
4
4
|
declare const TcpTransportService_base: Effect.Service.Class<TcpTransportService, "TcpTransportService", {
|
|
5
5
|
readonly scoped: (options: TcpTransportOptions) => Effect.Effect<{
|
|
6
6
|
withClient: (unitId: number) => Effect.Effect<import("./modbus-client").EffectModbusClient, import("./errors").ModbusError, never>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Effect, Layer } from 'effect';
|
|
2
|
+
import type { WasmSerialPortHandle, WasmSerialTransportOptions } from 'modbus-rs/web';
|
|
3
|
+
import { SlaveDeviceDefinitions } from './mocks';
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link WasmAsciiTransportService}. `WasmAsciiTransport.open()` takes the
|
|
6
|
+
* serial port handle and the connection options as two separate arguments; this
|
|
7
|
+
* combines them into one object so it fits {@link makeTransportScoped}'s single-options
|
|
8
|
+
* shape, with `port` destructured back out inside the service's `openMethod`.
|
|
9
|
+
*
|
|
10
|
+
* @see requestSerialPort — Obtains the `port` handle (must be called from a user gesture).
|
|
11
|
+
*/
|
|
12
|
+
export type WasmAsciiTransportOpenOptions = WasmSerialTransportOptions & {
|
|
13
|
+
port: WasmSerialPortHandle;
|
|
14
|
+
};
|
|
15
|
+
declare const WasmAsciiTransportService_base: Effect.Service.Class<WasmAsciiTransportService, "WasmAsciiTransportService", {
|
|
16
|
+
readonly scoped: (options: WasmAsciiTransportOpenOptions) => Effect.Effect<{
|
|
17
|
+
withClient: (unitId: number) => Effect.Effect<import("./modbus-client").EffectModbusClient, import("./errors").ModbusError, never>;
|
|
18
|
+
setRequestTimeout: (timeoutMs: number) => Effect.Effect<undefined, import("./errors").ModbusNotConnectedError, never>;
|
|
19
|
+
clearRequestTimeout: () => Effect.Effect<undefined, import("./errors").ModbusNotConnectedError, never>;
|
|
20
|
+
reconnect: () => Effect.Effect<undefined, import("./errors").ModbusError, never>;
|
|
21
|
+
close: () => Effect.Effect<void, import("./errors").ModbusError, import("effect/Scope").Scope>;
|
|
22
|
+
hasPendingRequests: () => boolean;
|
|
23
|
+
}, never, import("effect/Scope").Scope>;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Scoped Effect service wrapping `modbus-rs`'s browser {@link WasmAsciiTransport}
|
|
27
|
+
* for Modbus ASCII over the Web Serial API.
|
|
28
|
+
*
|
|
29
|
+
* The transport connection is opened lazily on the first call to
|
|
30
|
+
* `withClient(unitId)` and automatically closed when the consuming
|
|
31
|
+
* {@link Effect.Scope | Scope} finalizes.
|
|
32
|
+
*
|
|
33
|
+
* Clients are created per `unitId` via {@link WasmAsciiTransport.createClient} and
|
|
34
|
+
* cached, so repeated requests for the same unit ID reuse the same client.
|
|
35
|
+
*
|
|
36
|
+
* @see WasmAsciiTransport — Upstream `modbus-rs` browser Web Serial ASCII transport.
|
|
37
|
+
* @see requestSerialPort — Obtains the serial port handle this service's `port` option needs.
|
|
38
|
+
* @see makeTransportScoped — Generic lifecycle logic from shared-transport.
|
|
39
|
+
*/
|
|
40
|
+
export declare class WasmAsciiTransportService extends WasmAsciiTransportService_base {
|
|
41
|
+
/**
|
|
42
|
+
* Creates a {@link Layer} providing an in-memory mock
|
|
43
|
+
* {@link WasmAsciiTransportService} for testing or development.
|
|
44
|
+
*
|
|
45
|
+
* Accepts an array of {@link SlaveDeviceDefinition} describing the
|
|
46
|
+
* simulated Modbus slaves and their register/coil maps.
|
|
47
|
+
*
|
|
48
|
+
* @param devices - Slave device definitions for the mock.
|
|
49
|
+
* @returns A function that takes {@link WasmAsciiTransportOpenOptions} and
|
|
50
|
+
* returns a scoped {@link Layer} providing the mock service.
|
|
51
|
+
*
|
|
52
|
+
* @see makeMockTransport — The underlying mock factory.
|
|
53
|
+
*/
|
|
54
|
+
static makeMockTransport: (devices: SlaveDeviceDefinitions) => (options: WasmAsciiTransportOpenOptions) => Layer.Layer<WasmAsciiTransportService, never, never>;
|
|
55
|
+
}
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Effect, Layer } from 'effect';
|
|
2
|
+
import type { WasmSerialPortHandle, WasmSerialTransportOptions } from 'modbus-rs/web';
|
|
3
|
+
import { SlaveDeviceDefinitions } from './mocks';
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link WasmRtuTransportService}. `WasmRtuTransport.open()` takes the
|
|
6
|
+
* serial port handle and the connection options as two separate arguments; this
|
|
7
|
+
* combines them into one object so it fits {@link makeTransportScoped}'s single-options
|
|
8
|
+
* shape, with `port` destructured back out inside the service's `openMethod`.
|
|
9
|
+
*
|
|
10
|
+
* @see requestSerialPort — Obtains the `port` handle (must be called from a user gesture).
|
|
11
|
+
*/
|
|
12
|
+
export type WasmRtuTransportOpenOptions = WasmSerialTransportOptions & {
|
|
13
|
+
port: WasmSerialPortHandle;
|
|
14
|
+
};
|
|
15
|
+
declare const WasmRtuTransportService_base: Effect.Service.Class<WasmRtuTransportService, "WasmRtuTransportService", {
|
|
16
|
+
readonly scoped: (options: WasmRtuTransportOpenOptions) => Effect.Effect<{
|
|
17
|
+
withClient: (unitId: number) => Effect.Effect<import("./modbus-client").EffectModbusClient, import("./errors").ModbusError, never>;
|
|
18
|
+
setRequestTimeout: (timeoutMs: number) => Effect.Effect<undefined, import("./errors").ModbusNotConnectedError, never>;
|
|
19
|
+
clearRequestTimeout: () => Effect.Effect<undefined, import("./errors").ModbusNotConnectedError, never>;
|
|
20
|
+
reconnect: () => Effect.Effect<undefined, import("./errors").ModbusError, never>;
|
|
21
|
+
close: () => Effect.Effect<void, import("./errors").ModbusError, import("effect/Scope").Scope>;
|
|
22
|
+
hasPendingRequests: () => boolean;
|
|
23
|
+
}, never, import("effect/Scope").Scope>;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Scoped Effect service wrapping `modbus-rs`'s browser {@link WasmRtuTransport}
|
|
27
|
+
* for Modbus RTU over the Web Serial API.
|
|
28
|
+
*
|
|
29
|
+
* The transport connection is opened lazily on the first call to
|
|
30
|
+
* `withClient(unitId)` and automatically closed when the consuming
|
|
31
|
+
* {@link Effect.Scope | Scope} finalizes.
|
|
32
|
+
*
|
|
33
|
+
* Clients are created per `unitId` via {@link WasmRtuTransport.createClient} and
|
|
34
|
+
* cached, so repeated requests for the same unit ID reuse the same client.
|
|
35
|
+
*
|
|
36
|
+
* @see WasmRtuTransport — Upstream `modbus-rs` browser Web Serial RTU transport.
|
|
37
|
+
* @see requestSerialPort — Obtains the serial port handle this service's `port` option needs.
|
|
38
|
+
* @see makeTransportScoped — Generic lifecycle logic from shared-transport.
|
|
39
|
+
*/
|
|
40
|
+
export declare class WasmRtuTransportService extends WasmRtuTransportService_base {
|
|
41
|
+
/**
|
|
42
|
+
* Creates a {@link Layer} providing an in-memory mock
|
|
43
|
+
* {@link WasmRtuTransportService} for testing or development.
|
|
44
|
+
*
|
|
45
|
+
* Accepts an array of {@link SlaveDeviceDefinition} describing the
|
|
46
|
+
* simulated Modbus slaves and their register/coil maps.
|
|
47
|
+
*
|
|
48
|
+
* @param devices - Slave device definitions for the mock.
|
|
49
|
+
* @returns A function that takes {@link WasmRtuTransportOpenOptions} and
|
|
50
|
+
* returns a scoped {@link Layer} providing the mock service.
|
|
51
|
+
*
|
|
52
|
+
* @see makeMockTransport — The underlying mock factory.
|
|
53
|
+
*/
|
|
54
|
+
static makeMockTransport: (devices: SlaveDeviceDefinitions) => (options: WasmRtuTransportOpenOptions) => Layer.Layer<WasmRtuTransportService, never, never>;
|
|
55
|
+
}
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Layer } from 'effect';
|
|
2
|
+
import type { ServerHandlers } from 'modbus-rs';
|
|
3
|
+
import type { WasmSerialServerOptions } from 'modbus-rs/web';
|
|
4
|
+
import type { ModbusError } from './errors';
|
|
5
|
+
/**
|
|
6
|
+
* A scoped {@link Layer} that starts a browser Modbus RTU server over the Web
|
|
7
|
+
* Serial API (experimental upstream surface).
|
|
8
|
+
*
|
|
9
|
+
* `options.serialPort` is the raw `SerialPort` object obtained from the
|
|
10
|
+
* consumer's own app code via `navigator.serial.requestPort()` (which has the
|
|
11
|
+
* `dom` lib types available) — **not** this package's {@link requestSerialPort}
|
|
12
|
+
* helper, which returns the `WasmSerialPortHandle` wrapper used only by the
|
|
13
|
+
* client-side transports (`WasmRtuTransportService`/`WasmAsciiTransportService`).
|
|
14
|
+
*
|
|
15
|
+
* Like {@link wasmWsServerLayer}, the WASM server requires a continuously-awaited
|
|
16
|
+
* `serve()` call to drive its request loop; this layer forks that call into the
|
|
17
|
+
* scope automatically so the returned `Layer` "just works".
|
|
18
|
+
*
|
|
19
|
+
* @param options - Serial port, baud rate, unit ID, etc.
|
|
20
|
+
* @param handlers - Callback functions that handle incoming Modbus requests
|
|
21
|
+
* (same {@link ServerHandlers} shape as the native TCP/serial servers).
|
|
22
|
+
* @returns A `Layer` that fails with {@link ModbusError} on bind failure.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { Effect, Layer } from "effect";
|
|
27
|
+
* import { wasmSerialRtuServerLayer } from "@flux-control/effect-modbus-rs";
|
|
28
|
+
*
|
|
29
|
+
* const port = await navigator.serial.requestPort(); // user-gesture gated
|
|
30
|
+
* const ServerLive = wasmSerialRtuServerLayer(
|
|
31
|
+
* { serialPort: port, unitId: 1, baudRate: 19200 },
|
|
32
|
+
* { onReadCoils: (req) => [false, false] },
|
|
33
|
+
* );
|
|
34
|
+
*
|
|
35
|
+
* Layer.launch(ServerLive).pipe(Effect.runPromise);
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @see WasmSerialServerOptions — Options accepted by the upstream WASM serial server.
|
|
39
|
+
* @see ServerHandlers — Interface for request handler callbacks.
|
|
40
|
+
*/
|
|
41
|
+
export declare const wasmSerialRtuServerLayer: (options: WasmSerialServerOptions, handlers: ServerHandlers) => Layer.Layer<never, ModbusError>;
|
|
42
|
+
/**
|
|
43
|
+
* A scoped {@link Layer} that starts a browser Modbus ASCII server over the Web
|
|
44
|
+
* Serial API. See {@link wasmSerialRtuServerLayer} for shared details (the
|
|
45
|
+
* `serialPort` source, and the `serve()`-forking behavior).
|
|
46
|
+
*
|
|
47
|
+
* @param options - Serial port, baud rate, unit ID, etc.
|
|
48
|
+
* @param handlers - Callback functions that handle incoming Modbus requests.
|
|
49
|
+
* @returns A `Layer` that fails with {@link ModbusError} on bind failure.
|
|
50
|
+
*
|
|
51
|
+
* @see WasmSerialServerOptions — Options accepted by the upstream WASM serial server.
|
|
52
|
+
* @see ServerHandlers — Interface for request handler callbacks.
|
|
53
|
+
*/
|
|
54
|
+
export declare const wasmSerialAsciiServerLayer: (options: WasmSerialServerOptions, handlers: ServerHandlers) => Layer.Layer<never, ModbusError>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import type { WasmSerialPortHandle } from 'modbus-rs/web';
|
|
3
|
+
import type { ModbusError } from './errors';
|
|
4
|
+
/**
|
|
5
|
+
* Requests a browser serial port handle via the Web Serial API, for use with
|
|
6
|
+
* {@link WasmSerialTransportService.fromRtu} / `.fromAscii` (or
|
|
7
|
+
* `WasmRtuTransportService.Default` / `WasmAsciiTransportService.Default` directly).
|
|
8
|
+
*
|
|
9
|
+
* **Must be called synchronously from within a user-gesture event handler**
|
|
10
|
+
* (e.g. a `click` listener) — this is a hard Web Serial API / browser security
|
|
11
|
+
* constraint (`navigator.serial.requestPort()` semantics), not a library-imposed
|
|
12
|
+
* restriction. Calling it outside a gesture will reject.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* button.addEventListener("click", () => {
|
|
17
|
+
* Effect.runPromise(
|
|
18
|
+
* requestSerialPort().pipe(
|
|
19
|
+
* Effect.flatMap((port) => Effect.provide(program, WasmRtuTransportService.Default({ port, baudRate: 19200 }))),
|
|
20
|
+
* ),
|
|
21
|
+
* );
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @see WasmSerialPortHandle — Opaque handle returned by `modbus-rs`'s WASM bindings.
|
|
26
|
+
*/
|
|
27
|
+
export declare const requestSerialPort: () => Effect.Effect<WasmSerialPortHandle, ModbusError>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Context, Layer } from 'effect';
|
|
2
|
+
import { type SlaveDeviceDefinitions } from './mocks';
|
|
3
|
+
import type { TransportServiceApi } from './shared-transport';
|
|
4
|
+
import { type WasmAsciiTransportOpenOptions } from './WasmAsciiTransportService';
|
|
5
|
+
import { type WasmRtuTransportOpenOptions } from './WasmRtuTransportService';
|
|
6
|
+
declare const WasmSerialTransportService_base: Context.TagClass<WasmSerialTransportService, "WasmSerialTransportService", TransportServiceApi>;
|
|
7
|
+
/**
|
|
8
|
+
* Abstract browser (WASM) serial Modbus transport service tag.
|
|
9
|
+
*
|
|
10
|
+
* Represents a Web Serial-based Modbus transport backed by either ASCII or RTU
|
|
11
|
+
* framing. Use this tag when you need a browser serial transport but don't care
|
|
12
|
+
* about the specific framing protocol.
|
|
13
|
+
*
|
|
14
|
+
* Consumers `yield* WasmSerialTransportService` to obtain a
|
|
15
|
+
* {@link TransportServiceApi} and satisfy the tag via one of the static
|
|
16
|
+
* provider methods:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* // Provide with ASCII framing
|
|
20
|
+
* Layer.provide(WasmSerialTransportService.fromAscii({ port, baudRate: 9600 }))
|
|
21
|
+
*
|
|
22
|
+
* // Provide with RTU framing
|
|
23
|
+
* Layer.provide(WasmSerialTransportService.fromRtu({ port, baudRate: 9600 }))
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @see requestSerialPort — Obtains the `port` handle both providers need (must be called from a user gesture).
|
|
27
|
+
*/
|
|
28
|
+
export declare class WasmSerialTransportService extends WasmSerialTransportService_base {
|
|
29
|
+
/**
|
|
30
|
+
* Creates a {@link Layer} providing {@link WasmSerialTransportService}
|
|
31
|
+
* backed by an ASCII transport.
|
|
32
|
+
*/
|
|
33
|
+
static fromAscii(options: WasmAsciiTransportOpenOptions): Layer.Layer<WasmSerialTransportService>;
|
|
34
|
+
/**
|
|
35
|
+
* Creates a {@link Layer} providing {@link WasmSerialTransportService}
|
|
36
|
+
* backed by an RTU transport.
|
|
37
|
+
*/
|
|
38
|
+
static fromRtu(options: WasmRtuTransportOpenOptions): Layer.Layer<WasmSerialTransportService>;
|
|
39
|
+
/**
|
|
40
|
+
* Creates a mock {@link Layer} providing {@link WasmSerialTransportService}
|
|
41
|
+
* for testing or development.
|
|
42
|
+
*
|
|
43
|
+
* Accepts an array of {@link SlaveDeviceDefinition} describing the
|
|
44
|
+
* simulated Modbus slaves and their register/coil maps.
|
|
45
|
+
*/
|
|
46
|
+
static makeMockTransport: (devices: SlaveDeviceDefinitions) => (options: WasmAsciiTransportOpenOptions | WasmRtuTransportOpenOptions) => Layer.Layer<WasmSerialTransportService>;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Layer } from 'effect';
|
|
2
|
+
import type { ServerHandlers } from 'modbus-rs';
|
|
3
|
+
import type { WasmTcpServerOptions } from 'modbus-rs/web';
|
|
4
|
+
import type { ModbusError } from './errors';
|
|
5
|
+
/**
|
|
6
|
+
* A scoped {@link Layer} that starts a browser Modbus server proxied over a
|
|
7
|
+
* WebSocket gateway (experimental upstream surface — browsers can't accept raw
|
|
8
|
+
* TCP connections, so this binds via `modbus-gateway` or an equivalent WS-to-TCP
|
|
9
|
+
* proxy).
|
|
10
|
+
*
|
|
11
|
+
* Unlike the native {@link tcpServerLayer}, the WASM server does not start
|
|
12
|
+
* serving on `bind()` — it requires an explicit, continuously-awaited `serve()`
|
|
13
|
+
* call to drive its request loop. This layer forks that call into the scope in
|
|
14
|
+
* the background so the returned `Layer` behaves the same as the native one
|
|
15
|
+
* from the consumer's perspective (no extra step needed).
|
|
16
|
+
*
|
|
17
|
+
* @param options - WebSocket gateway URL and unit ID.
|
|
18
|
+
* @param handlers - Callback functions that handle incoming Modbus requests
|
|
19
|
+
* (same {@link ServerHandlers} shape as the native TCP/serial servers).
|
|
20
|
+
* @returns A `Layer` that fails with {@link ModbusError} on bind failure.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { Effect, Layer } from "effect";
|
|
25
|
+
* import { wasmWsServerLayer } from "@flux-control/effect-modbus-rs";
|
|
26
|
+
*
|
|
27
|
+
* const ServerLive = wasmWsServerLayer(
|
|
28
|
+
* { wsUrl: "ws://localhost:8080", unitId: 1 },
|
|
29
|
+
* { onReadCoils: (req) => [false, false] },
|
|
30
|
+
* );
|
|
31
|
+
*
|
|
32
|
+
* Layer.launch(ServerLive).pipe(Effect.runPromise);
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @see WasmTcpServerOptions — Options accepted by the upstream WASM WS server.
|
|
36
|
+
* @see ServerHandlers — Interface for request handler callbacks.
|
|
37
|
+
*/
|
|
38
|
+
export declare const wasmWsServerLayer: (options: WasmTcpServerOptions, handlers: ServerHandlers) => Layer.Layer<never, ModbusError>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Effect, Layer } from 'effect';
|
|
2
|
+
import type { WasmWsTransportOptions } from 'modbus-rs/web';
|
|
3
|
+
import { SlaveDeviceDefinitions } from './mocks';
|
|
4
|
+
declare const WasmWsTransportService_base: Effect.Service.Class<WasmWsTransportService, "WasmWsTransportService", {
|
|
5
|
+
readonly scoped: (options: WasmWsTransportOptions) => Effect.Effect<{
|
|
6
|
+
withClient: (unitId: number) => Effect.Effect<import("./modbus-client").EffectModbusClient, import("./errors").ModbusError, never>;
|
|
7
|
+
setRequestTimeout: (timeoutMs: number) => Effect.Effect<undefined, import("./errors").ModbusNotConnectedError, never>;
|
|
8
|
+
clearRequestTimeout: () => Effect.Effect<undefined, import("./errors").ModbusNotConnectedError, never>;
|
|
9
|
+
reconnect: () => Effect.Effect<undefined, import("./errors").ModbusError, never>;
|
|
10
|
+
close: () => Effect.Effect<void, import("./errors").ModbusError, import("effect/Scope").Scope>;
|
|
11
|
+
hasPendingRequests: () => boolean;
|
|
12
|
+
}, never, import("effect/Scope").Scope>;
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* Scoped Effect service wrapping `modbus-rs`'s browser {@link WasmWsTransport}
|
|
16
|
+
* for Modbus TCP over a WebSocket gateway (browsers can't open raw TCP sockets).
|
|
17
|
+
*
|
|
18
|
+
* The transport connection is opened lazily on the first call to
|
|
19
|
+
* `withClient(unitId)` and automatically closed when the consuming
|
|
20
|
+
* {@link Effect.Scope | Scope} finalizes.
|
|
21
|
+
*
|
|
22
|
+
* Clients are created per `unitId` via {@link WasmWsTransport.createClient} and
|
|
23
|
+
* cached, so repeated requests for the same unit ID reuse the same client.
|
|
24
|
+
*
|
|
25
|
+
* @see WasmWsTransport — Upstream `modbus-rs` browser WebSocket transport.
|
|
26
|
+
* @see WasmWsTransportOptions — Configuration for the WebSocket gateway connection.
|
|
27
|
+
* @see makeTransportScoped — Generic lifecycle logic from shared-transport.
|
|
28
|
+
*/
|
|
29
|
+
export declare class WasmWsTransportService extends WasmWsTransportService_base {
|
|
30
|
+
/**
|
|
31
|
+
* Creates a {@link Layer} providing an in-memory mock
|
|
32
|
+
* {@link WasmWsTransportService} for testing or development.
|
|
33
|
+
*
|
|
34
|
+
* Accepts an array of {@link SlaveDeviceDefinition} describing the
|
|
35
|
+
* simulated Modbus slaves and their register/coil maps.
|
|
36
|
+
*
|
|
37
|
+
* @param devices - Slave device definitions for the mock.
|
|
38
|
+
* @returns A function that takes {@link WasmWsTransportOptions} and
|
|
39
|
+
* returns a scoped {@link Layer} providing the mock service.
|
|
40
|
+
*
|
|
41
|
+
* @see makeMockTransport — The underlying mock factory.
|
|
42
|
+
*/
|
|
43
|
+
static makeMockTransport: (devices: SlaveDeviceDefinitions) => (options: WasmWsTransportOptions) => Layer.Layer<WasmWsTransportService, never, never>;
|
|
44
|
+
}
|
|
45
|
+
export {};
|
package/dist/src/mocks.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Effect, Schema } from
|
|
2
|
-
import type
|
|
3
|
-
import {
|
|
4
|
-
import type
|
|
1
|
+
import { Effect, Schema } from 'effect';
|
|
2
|
+
import { type AsciiTransportOptions, type RtuTransportOptions, type TcpTransportOptions } from 'modbus-rs';
|
|
3
|
+
import type { WasmWsTransportOptions, WasmSerialTransportOptions } from 'modbus-rs/web';
|
|
4
|
+
import { ModbusInvalidArgumentError, type ModbusError } from './errors';
|
|
5
|
+
import type { EffectModbusClient } from './modbus-client';
|
|
5
6
|
/**
|
|
6
7
|
* Schema for a single coil (digital output) definition.
|
|
7
8
|
*
|
|
@@ -124,7 +125,7 @@ export type SlaveDeviceDefinitions = Schema.Schema.Type<typeof SlaveDeviceDefini
|
|
|
124
125
|
* @returns A transport factory function that returns a scoped Effect
|
|
125
126
|
* providing the mock transport.
|
|
126
127
|
*/
|
|
127
|
-
export declare const makeMockTransport: (devices: SlaveDeviceDefinitions) => (_options: RtuTransportOptions | AsciiTransportOptions | TcpTransportOptions) => Effect.Effect<{
|
|
128
|
+
export declare const makeMockTransport: (devices: SlaveDeviceDefinitions) => (_options: RtuTransportOptions | AsciiTransportOptions | TcpTransportOptions | WasmWsTransportOptions | WasmSerialTransportOptions) => Effect.Effect<{
|
|
128
129
|
withClient: (unitId: number) => Effect.Effect<EffectModbusClient, ModbusInvalidArgumentError, never>;
|
|
129
130
|
setRequestTimeout: (_timeoutMs: number) => Effect.Effect<void, never, never>;
|
|
130
131
|
clearRequestTimeout: () => Effect.Effect<void, never, never>;
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import type { ReadRegistersOptions, WriteSingleRegisterOptions, WriteMultipleRegistersOptions, ReadWriteMultipleRegistersOptions, ReadBitsOptions, WriteSingleCoilOptions, WriteMultipleCoilsOptions, ReadFifoQueueOptions, ReadFileRecordOptions, WriteFileRecordOptions, DiagnosticsOptions, ReadDeviceIdentificationOptions, FifoQueueResponse, DiagnosticsResponse, DeviceIdentificationResponse, AsyncSerialModbusClient, AsyncTcpModbusClient, CoilState } from 'modbus-rs';
|
|
3
|
+
import type { WasmWsModbusClient, WasmSerialModbusClient } from 'modbus-rs/web';
|
|
4
|
+
import type { ModbusError } from './errors';
|
|
5
|
+
/** The two native (napi) clients — same method surface, sharing one factory. */
|
|
6
|
+
export type NativeModbusClient = AsyncSerialModbusClient | AsyncTcpModbusClient;
|
|
7
|
+
/** The two browser (WASM) clients — same method surface, sharing one factory. */
|
|
8
|
+
export type WasmModbusClient = WasmSerialModbusClient | WasmWsModbusClient;
|
|
9
|
+
/** Any client this package knows how to wrap into an {@link EffectModbusClient}. */
|
|
10
|
+
export type AnyModbusClient = NativeModbusClient | WasmModbusClient;
|
|
5
11
|
/**
|
|
6
12
|
* Effect-ified Modbus client wrapping a `modbus-rs` transport client.
|
|
7
13
|
*
|
|
8
|
-
* Each method delegates to the equivalent
|
|
9
|
-
*
|
|
10
|
-
*
|
|
14
|
+
* Each method delegates to the equivalent method on the underlying native
|
|
15
|
+
* or WASM client, converting the Promise-based API into an
|
|
16
|
+
* {@link Effect.Effect} with typed {@link ModbusError} failures.
|
|
11
17
|
*
|
|
12
18
|
* Thrown errors are classified using {@link toModbusError}, mapping
|
|
13
19
|
* `modbus-rs` error codes (timeout, transport, exception, etc.) into
|
|
@@ -27,7 +33,7 @@ export interface EffectModbusClient {
|
|
|
27
33
|
* @see ReadRegistersOptions — Options shape from `modbus-rs`.
|
|
28
34
|
* @see AsyncSerialModbusClient.readHoldingRegisters — Upstream implementation.
|
|
29
35
|
*/
|
|
30
|
-
readHoldingRegisters(opts: ReadRegistersOptions): Effect.Effect<
|
|
36
|
+
readHoldingRegisters(opts: ReadRegistersOptions): Effect.Effect<Uint16Array, ModbusError>;
|
|
31
37
|
/**
|
|
32
38
|
* Reads input registers from the Modbus device (FC04).
|
|
33
39
|
*
|
|
@@ -37,7 +43,7 @@ export interface EffectModbusClient {
|
|
|
37
43
|
* @see ReadRegistersOptions — Options shape from `modbus-rs`.
|
|
38
44
|
* @see AsyncSerialModbusClient.readInputRegisters — Upstream implementation.
|
|
39
45
|
*/
|
|
40
|
-
readInputRegisters(opts: ReadRegistersOptions): Effect.Effect<
|
|
46
|
+
readInputRegisters(opts: ReadRegistersOptions): Effect.Effect<Uint16Array, ModbusError>;
|
|
41
47
|
/**
|
|
42
48
|
* Writes a single holding register (FC06).
|
|
43
49
|
*
|
|
@@ -67,7 +73,7 @@ export interface EffectModbusClient {
|
|
|
67
73
|
*
|
|
68
74
|
* @see ReadWriteMultipleRegistersOptions — Options shape from `modbus-rs`.
|
|
69
75
|
*/
|
|
70
|
-
readWriteMultipleRegisters(opts: ReadWriteMultipleRegistersOptions): Effect.Effect<
|
|
76
|
+
readWriteMultipleRegisters(opts: ReadWriteMultipleRegistersOptions): Effect.Effect<Uint16Array, ModbusError>;
|
|
71
77
|
/**
|
|
72
78
|
* Reads coils (digital outputs) from the Modbus device (FC01).
|
|
73
79
|
*
|
|
@@ -76,7 +82,7 @@ export interface EffectModbusClient {
|
|
|
76
82
|
*
|
|
77
83
|
* @see ReadBitsOptions — Options shape from `modbus-rs`.
|
|
78
84
|
*/
|
|
79
|
-
readCoils(opts: ReadBitsOptions): Effect.Effect<
|
|
85
|
+
readCoils(opts: ReadBitsOptions): Effect.Effect<CoilState[], ModbusError>;
|
|
80
86
|
/**
|
|
81
87
|
* Writes a single coil (digital output) (FC05).
|
|
82
88
|
*
|
|
@@ -103,7 +109,7 @@ export interface EffectModbusClient {
|
|
|
103
109
|
*
|
|
104
110
|
* @see ReadBitsOptions — Options shape from `modbus-rs`.
|
|
105
111
|
*/
|
|
106
|
-
readDiscreteInputs(opts: ReadBitsOptions): Effect.Effect<
|
|
112
|
+
readDiscreteInputs(opts: ReadBitsOptions): Effect.Effect<CoilState[], ModbusError>;
|
|
107
113
|
/**
|
|
108
114
|
* Reads the FIFO queue from the Modbus device (FC24).
|
|
109
115
|
*
|
|
@@ -122,7 +128,7 @@ export interface EffectModbusClient {
|
|
|
122
128
|
*
|
|
123
129
|
* @see ReadFileRecordOptions — Options shape from `modbus-rs`.
|
|
124
130
|
*/
|
|
125
|
-
readFileRecord(opts: ReadFileRecordOptions): Effect.Effect<
|
|
131
|
+
readFileRecord(opts: ReadFileRecordOptions): Effect.Effect<Uint16Array[], ModbusError>;
|
|
126
132
|
/**
|
|
127
133
|
* Writes file records to the Modbus device (FC21).
|
|
128
134
|
*
|
|
@@ -162,21 +168,23 @@ export interface EffectModbusClient {
|
|
|
162
168
|
readDeviceIdentification(opts: ReadDeviceIdentificationOptions): Effect.Effect<DeviceIdentificationResponse, ModbusError>;
|
|
163
169
|
}
|
|
164
170
|
/**
|
|
165
|
-
* Wraps a raw `modbus-rs` client into an
|
|
171
|
+
* Wraps a raw `modbus-rs` client — native (napi) or browser (WASM) — into an
|
|
172
|
+
* {@link EffectModbusClient}.
|
|
166
173
|
*
|
|
167
174
|
* Each method converts a Promise-based call from the upstream client
|
|
168
175
|
* into an `Effect` via {@link Effect.tryPromise}, routing errors through
|
|
169
176
|
* {@link toModbusError} for typed error discrimination.
|
|
170
177
|
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
178
|
+
* The native and WASM clients share the same method surface (same options
|
|
179
|
+
* shapes, same resolved value shapes — `CoilState[]`, full `FifoQueueResponse`,
|
|
180
|
+
* full `DeviceIdentificationResponse`), so one factory covers both; no
|
|
181
|
+
* transport-specific reshaping is needed.
|
|
174
182
|
*
|
|
175
|
-
* @param client - The upstream `modbus-rs` client instance.
|
|
183
|
+
* @param client - The upstream `modbus-rs` or `modbus-rs/web` client instance.
|
|
176
184
|
* @returns An `EffectModbusClient` that can be used within Effect
|
|
177
185
|
* workflows.
|
|
178
186
|
*
|
|
179
|
-
* @see AsyncSerialModbusClient — Upstream serial client API.
|
|
180
|
-
* @see AsyncTcpModbusClient — Upstream TCP client API.
|
|
187
|
+
* @see AsyncSerialModbusClient — Upstream native serial client API.
|
|
188
|
+
* @see AsyncTcpModbusClient — Upstream native TCP client API.
|
|
181
189
|
*/
|
|
182
|
-
export declare const makeEffectModbusClient: (client:
|
|
190
|
+
export declare const makeEffectModbusClient: (client: AnyModbusClient) => EffectModbusClient;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Effect, Scope } from
|
|
2
|
-
import { type ModbusError, ModbusNotConnectedError } from
|
|
3
|
-
import { type AnyModbusClient, type EffectModbusClient } from
|
|
1
|
+
import { Effect, Scope } from 'effect';
|
|
2
|
+
import { type ModbusError, ModbusNotConnectedError } from './errors';
|
|
3
|
+
import { type AnyModbusClient, type EffectModbusClient } from './modbus-client';
|
|
4
4
|
/**
|
|
5
5
|
* Shared API surface that every transport service exposes to consumers.
|
|
6
6
|
*
|
|
@@ -51,9 +51,13 @@ interface TransportHandle<TClient> {
|
|
|
51
51
|
* @param openMethod - A function that takes the transport constructor and options,
|
|
52
52
|
* returning a promise for the opened transport.
|
|
53
53
|
* @param serviceName - Logical name used in log messages and the finalizer guard.
|
|
54
|
+
* @param config - Optional module specifier override for browser WASM transports.
|
|
54
55
|
* @returns An `Effect` that produces a {@link TransportServiceApi}.
|
|
55
56
|
*/
|
|
56
|
-
export declare function makeTransportScoped<TOptions, TClient extends AnyModbusClient, TTransport extends TransportHandle<TClient>>(transportKey: string, openMethod: (TC: unknown, options: TOptions) => Promise<TTransport>, serviceName: string
|
|
57
|
+
export declare function makeTransportScoped<TOptions, TClient extends AnyModbusClient, TTransport extends TransportHandle<TClient>>(transportKey: string, openMethod: (TC: unknown, options: TOptions) => Promise<TTransport>, serviceName: string, config?: {
|
|
58
|
+
/** Which `modbus-rs` conditional export to import from. Defaults to `"modbus-rs"` (native). */
|
|
59
|
+
moduleSpecifier?: 'modbus-rs' | 'modbus-rs/web';
|
|
60
|
+
}): (options: TOptions) => Effect.Effect<{
|
|
57
61
|
withClient: (unitId: number) => Effect.Effect<EffectModbusClient, ModbusError, never>;
|
|
58
62
|
setRequestTimeout: (timeoutMs: number) => Effect.Effect<undefined, ModbusNotConnectedError, never>;
|
|
59
63
|
clearRequestTimeout: () => Effect.Effect<undefined, ModbusNotConnectedError, never>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|