@flux-control/effect-modbus-rs 0.7.0 → 0.8.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/README.md +498 -831
- package/dist/index.d.ts +56 -113
- package/dist/index.js +235 -119
- package/dist/src/AsciiTransportService.d.ts +15 -14
- package/dist/src/RtuTransportService.d.ts +15 -14
- package/dist/src/SerialModbusServerService.d.ts +3 -2
- package/dist/src/SerialTransportService.d.ts +8 -2
- package/dist/src/TcpGatewayService.d.ts +2 -1
- package/dist/src/TcpModbusServerService.d.ts +2 -1
- package/dist/src/TcpTransportService.d.ts +7 -6
- package/dist/src/WasmAsciiTransportService.d.ts +14 -8
- package/dist/src/WasmRtuTransportService.d.ts +14 -8
- package/dist/src/WasmSerialModbusServerService.d.ts +4 -2
- package/dist/src/WasmSerialPort.d.ts +6 -4
- package/dist/src/WasmSerialTransportService.d.ts +9 -2
- package/dist/src/WasmTcpServerService.d.ts +2 -1
- package/dist/src/WasmWsTransportService.d.ts +10 -6
- package/dist/src/batching-client.d.ts +61 -28
- package/dist/src/connection.d.ts +6 -2
- package/dist/src/errors.d.ts +11 -12
- package/dist/src/mocks.d.ts +4 -3
- package/dist/src/modbus-client.d.ts +14 -10
- package/dist/src/read-debouncer.d.ts +8 -22
- package/dist/src/register-cache.d.ts +1 -7
- package/dist/src/register-plan.d.ts +2 -12
- package/dist/src/retry.d.ts +1 -0
- package/dist/src/serial-port-path.d.ts +21 -0
- package/dist/src/shared-transport.d.ts +33 -8
- package/dist/src/span-attributes.d.ts +1 -1
- package/dist/src/write-debouncer.d.ts +15 -49
- package/dist/test/native-transport.test.d.ts +1 -0
- package/dist/test/serial-port-path.test.d.ts +1 -0
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -930,7 +930,7 @@ var createBatchingRegistry = (deps) => {
|
|
|
930
930
|
};
|
|
931
931
|
};
|
|
932
932
|
// src/AsciiTransportService.ts
|
|
933
|
-
import { Context, Layer } from "effect";
|
|
933
|
+
import { Context, Effect as Effect10, Layer } from "effect";
|
|
934
934
|
|
|
935
935
|
// src/mocks.ts
|
|
936
936
|
import { Effect as Effect7, Exit as Exit3, Schema, Scope as Scope2, SubscriptionRef as SubscriptionRef3 } from "effect";
|
|
@@ -940,30 +940,36 @@ import {
|
|
|
940
940
|
|
|
941
941
|
// src/modbus-client.ts
|
|
942
942
|
import { Effect as Effect6 } from "effect";
|
|
943
|
+
var identityWrapper = (call) => call;
|
|
943
944
|
var wrap = (try_) => Effect6.tryPromise({
|
|
944
945
|
try: try_,
|
|
945
946
|
catch: (cause) => toModbusError(cause instanceof Error ? cause : new Error(String(cause)))
|
|
946
947
|
});
|
|
947
|
-
var createEffectModbusClient = (client) =>
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
948
|
+
var createEffectModbusClient = (client, around = identityWrapper) => {
|
|
949
|
+
const resolve = Effect6.isEffect(client) ? client : Effect6.succeed(client);
|
|
950
|
+
const call = (try_) => around(Effect6.flatMap(resolve, (current) => wrap(() => try_(current))));
|
|
951
|
+
return {
|
|
952
|
+
readHoldingRegisters: (opts) => call((current) => current.readHoldingRegisters(opts)),
|
|
953
|
+
readInputRegisters: (opts) => call((current) => current.readInputRegisters(opts)),
|
|
954
|
+
writeSingleRegister: (opts) => call((current) => current.writeSingleRegister(opts)),
|
|
955
|
+
writeMultipleRegisters: (opts) => call((current) => current.writeMultipleRegisters(opts)),
|
|
956
|
+
readWriteMultipleRegisters: (opts) => call((current) => current.readWriteMultipleRegisters(opts)),
|
|
957
|
+
readCoils: (opts) => call((current) => current.readCoils(opts)),
|
|
958
|
+
writeSingleCoil: (opts) => call((current) => current.writeSingleCoil(opts)),
|
|
959
|
+
writeMultipleCoils: (opts) => call((current) => current.writeMultipleCoils(opts)),
|
|
960
|
+
readDiscreteInputs: (opts) => call((current) => current.readDiscreteInputs(opts)),
|
|
961
|
+
readFifoQueue: (opts) => call((current) => current.readFifoQueue(opts)),
|
|
962
|
+
readFileRecord: (opts) => call((current) => current.readFileRecord(opts)),
|
|
963
|
+
writeFileRecord: (opts) => call((current) => current.writeFileRecord(opts)),
|
|
964
|
+
readExceptionStatus: () => call((current) => current.readExceptionStatus()),
|
|
965
|
+
diagnostics: (opts) => call((current) => current.diagnostics(opts)),
|
|
966
|
+
readDeviceIdentification: (opts) => call((current) => current.readDeviceIdentification(opts))
|
|
967
|
+
};
|
|
968
|
+
};
|
|
964
969
|
var withResilience = (operations, resilience) => {
|
|
965
970
|
const run = (operation) => {
|
|
966
|
-
const
|
|
971
|
+
const guarded = Effect6.andThen(resilience.guard, Effect6.suspend(operation)).pipe(Effect6.tap(() => resilience.onSuccess ?? Effect6.void), Effect6.tapError((error) => resilience.report(error)));
|
|
972
|
+
const attempt = resilience.aroundAttempt?.(guarded) ?? guarded;
|
|
967
973
|
return resilience.policy ? retryModbus(resilience.policy)(attempt) : attempt;
|
|
968
974
|
};
|
|
969
975
|
return {
|
|
@@ -1263,10 +1269,9 @@ var createMockTransport = (devices) => {
|
|
|
1263
1269
|
setRequestTimeout: (_timeoutMs) => ensureOpen,
|
|
1264
1270
|
clearRequestTimeout: () => ensureOpen,
|
|
1265
1271
|
reconnect: () => Effect7.andThen(reconnectOnce, SubscriptionRef3.set(connectionState, ConnectionState.Connected())),
|
|
1266
|
-
close: () => Effect7.gen(function* () {
|
|
1272
|
+
close: (scope) => Effect7.gen(function* () {
|
|
1267
1273
|
if (closed)
|
|
1268
1274
|
return;
|
|
1269
|
-
const scope = yield* Effect7.scope;
|
|
1270
1275
|
yield* Scope2.close(scope, Exit3.void).pipe(Effect7.onExit(() => closeTransport));
|
|
1271
1276
|
}),
|
|
1272
1277
|
hasPendingRequests: () => false
|
|
@@ -1274,17 +1279,68 @@ var createMockTransport = (devices) => {
|
|
|
1274
1279
|
});
|
|
1275
1280
|
};
|
|
1276
1281
|
|
|
1282
|
+
// src/serial-port-path.ts
|
|
1283
|
+
import { Effect as Effect8 } from "effect";
|
|
1284
|
+
var MAX_SERIAL_PORT_PATH = 128;
|
|
1285
|
+
var resolveSerialPortPath = async (portPath) => {
|
|
1286
|
+
if (portPath.length <= MAX_SERIAL_PORT_PATH)
|
|
1287
|
+
return portPath;
|
|
1288
|
+
const { realpath } = await import("node:fs/promises");
|
|
1289
|
+
const target = await realpath(portPath).catch(() => portPath);
|
|
1290
|
+
if (target.length <= MAX_SERIAL_PORT_PATH)
|
|
1291
|
+
return target;
|
|
1292
|
+
throw new Error(`[MODBUS_INVALID_ARGUMENT] Serial port path is ${portPath.length} characters long. modbus-rs accepts at most ${MAX_SERIAL_PORT_PATH} characters. Use a shorter path or a shorter link to the device: ${portPath}`);
|
|
1293
|
+
};
|
|
1294
|
+
var serialPortPathScoped = Effect8.fnUntraced(function* (portPath) {
|
|
1295
|
+
let directory;
|
|
1296
|
+
let alias;
|
|
1297
|
+
yield* Effect8.addFinalizer(() => Effect8.promise(async () => {
|
|
1298
|
+
if (directory === undefined)
|
|
1299
|
+
return;
|
|
1300
|
+
const { rm } = await import("node:fs/promises");
|
|
1301
|
+
await rm(directory, { recursive: true, force: true });
|
|
1302
|
+
}));
|
|
1303
|
+
return async () => {
|
|
1304
|
+
if (portPath.length <= MAX_SERIAL_PORT_PATH)
|
|
1305
|
+
return portPath;
|
|
1306
|
+
if (alias !== undefined)
|
|
1307
|
+
return alias;
|
|
1308
|
+
await resolveSerialPortPath(portPath);
|
|
1309
|
+
const { mkdtemp, symlink } = await import("node:fs/promises");
|
|
1310
|
+
const { tmpdir } = await import("node:os");
|
|
1311
|
+
const { join, resolve } = await import("node:path");
|
|
1312
|
+
directory = await mkdtemp(join(tmpdir(), "modbus-port-"));
|
|
1313
|
+
const path = join(directory, "port");
|
|
1314
|
+
if (path.length > MAX_SERIAL_PORT_PATH)
|
|
1315
|
+
throw new Error("[MODBUS_INVALID_ARGUMENT] The temporary serial alias path exceeds the native limit");
|
|
1316
|
+
await symlink(resolve(portPath), path);
|
|
1317
|
+
alias = path;
|
|
1318
|
+
return alias;
|
|
1319
|
+
};
|
|
1320
|
+
});
|
|
1321
|
+
|
|
1277
1322
|
// src/shared-transport.ts
|
|
1278
|
-
import {
|
|
1323
|
+
import {
|
|
1324
|
+
Deferred as Deferred4,
|
|
1325
|
+
Duration as Duration6,
|
|
1326
|
+
Effect as Effect9,
|
|
1327
|
+
Exit as Exit4,
|
|
1328
|
+
Fiber as Fiber4,
|
|
1329
|
+
Option,
|
|
1330
|
+
Ref,
|
|
1331
|
+
Scope as Scope3,
|
|
1332
|
+
Semaphore as Semaphore4,
|
|
1333
|
+
SubscriptionRef as SubscriptionRef4
|
|
1334
|
+
} from "effect";
|
|
1279
1335
|
var makeInFlight = () => Ref.make(Option.none());
|
|
1280
|
-
var singleFlight = (inFlight, work) =>
|
|
1336
|
+
var singleFlight = (inFlight, work) => Effect9.uninterruptibleMask((restore) => Effect9.gen(function* () {
|
|
1281
1337
|
const fresh = yield* Deferred4.make();
|
|
1282
1338
|
const [deferred, isLeader] = yield* Ref.modify(inFlight, (current) => Option.match(current, {
|
|
1283
1339
|
onSome: (existing) => [[existing, false], current],
|
|
1284
1340
|
onNone: () => [[fresh, true], Option.some(fresh)]
|
|
1285
1341
|
}));
|
|
1286
1342
|
if (isLeader) {
|
|
1287
|
-
yield*
|
|
1343
|
+
yield* Effect9.forkDetach(Effect9.interruptible(work).pipe(Effect9.onExit((exit) => Effect9.andThen(Ref.set(inFlight, Option.none()), Deferred4.done(deferred, exit)))));
|
|
1288
1344
|
}
|
|
1289
1345
|
return yield* restore(Deferred4.await(deferred));
|
|
1290
1346
|
}));
|
|
@@ -1315,44 +1371,67 @@ var loadTransportConstructor = async (transportKey, moduleSpecifier) => {
|
|
|
1315
1371
|
}
|
|
1316
1372
|
};
|
|
1317
1373
|
function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
1318
|
-
return
|
|
1374
|
+
return Effect9.fnUntraced(function* (options) {
|
|
1319
1375
|
const { retry: transportRetry, reconnect: reconnectOptions } = options;
|
|
1320
1376
|
const connectTimeout = options.connectTimeout === undefined ? undefined : Duration6.fromInputUnsafe(options.connectTimeout);
|
|
1321
1377
|
const openOptions = { ...options };
|
|
1322
1378
|
Reflect.deleteProperty(openOptions, "retry");
|
|
1323
1379
|
Reflect.deleteProperty(openOptions, "reconnect");
|
|
1324
1380
|
Reflect.deleteProperty(openOptions, "connectTimeout");
|
|
1325
|
-
const
|
|
1381
|
+
const nativeLock = config?.serializeRequests ? yield* Semaphore4.make(1) : undefined;
|
|
1382
|
+
let nativeLimit = config?.responseTimeoutMs?.(options) ?? 1000;
|
|
1383
|
+
let requestLimitOverride;
|
|
1384
|
+
const aroundNative = nativeLock === undefined ? undefined : (call) => Effect9.uninterruptibleMask((restore) => Effect9.gen(function* () {
|
|
1385
|
+
yield* restore(nativeLock.take(1));
|
|
1386
|
+
const work = call.pipe(Effect9.ensuring(nativeLock.release(1)));
|
|
1387
|
+
if (nativeLimit !== undefined)
|
|
1388
|
+
return yield* work;
|
|
1389
|
+
const worker = yield* Effect9.forkDetach(Effect9.exit(work));
|
|
1390
|
+
const exit = yield* restore(Fiber4.join(worker)).pipe(Effect9.onInterrupt(() => invalidateNative));
|
|
1391
|
+
return yield* exit;
|
|
1392
|
+
}));
|
|
1393
|
+
const TC = yield* Effect9.promise(() => loadTransportConstructor(transportKey, config?.moduleSpecifier ?? "modbus-rs"));
|
|
1326
1394
|
let transport = null;
|
|
1327
1395
|
const opening = yield* makeInFlight();
|
|
1328
1396
|
const reconnecting = yield* makeInFlight();
|
|
1329
1397
|
const connectionState = yield* SubscriptionRef4.make(ConnectionState.Disconnected());
|
|
1330
1398
|
const resolvedReconnect = resolveReconnect(reconnectOptions ?? {});
|
|
1331
1399
|
const supervised = reconnectOptions ? resolvedReconnect : null;
|
|
1332
|
-
const serviceScope = yield*
|
|
1400
|
+
const serviceScope = yield* Effect9.scope;
|
|
1333
1401
|
const clientSet = new Map;
|
|
1402
|
+
const touchedUnits = new Set;
|
|
1334
1403
|
let closed = false;
|
|
1404
|
+
const invalidateNative = Effect9.suspend(() => {
|
|
1405
|
+
const current = transport;
|
|
1406
|
+
transport = null;
|
|
1407
|
+
clientSet.clear();
|
|
1408
|
+
return Effect9.andThen(SubscriptionRef4.set(connectionState, ConnectionState.Disconnected()), current === null ? Effect9.void : Effect9.ignore(Effect9.tryPromise(() => current.close())));
|
|
1409
|
+
});
|
|
1335
1410
|
const transportClosed = () => new ModbusNotConnectedError({
|
|
1336
1411
|
cause: new Error("Transport has been closed"),
|
|
1337
1412
|
message: "Transport has been closed"
|
|
1338
1413
|
});
|
|
1339
|
-
const closeOrphan = (t) =>
|
|
1414
|
+
const closeOrphan = (t) => Effect9.forkDetach(Effect9.ignore(Effect9.tryPromise(() => t.close())));
|
|
1340
1415
|
const limitConnect = (wait) => {
|
|
1341
1416
|
if (connectTimeout === undefined)
|
|
1342
1417
|
return wait;
|
|
1343
1418
|
const message = `${serviceName}: connect did not complete within ${Duration6.format(connectTimeout)}`;
|
|
1344
|
-
return
|
|
1419
|
+
return Effect9.timeoutOrElse(wait, {
|
|
1345
1420
|
duration: connectTimeout,
|
|
1346
|
-
orElse: () =>
|
|
1421
|
+
orElse: () => Effect9.fail(new ModbusTimeoutError({ cause: new Error(message), message }))
|
|
1347
1422
|
});
|
|
1348
1423
|
};
|
|
1349
|
-
const openTransport =
|
|
1424
|
+
const openTransport = Effect9.tryPromise({
|
|
1350
1425
|
try: () => openMethod(TC, openOptions),
|
|
1351
1426
|
catch: (cause) => toModbusError(cause instanceof Error ? cause : new Error(String(cause)))
|
|
1352
|
-
}).pipe(
|
|
1427
|
+
}).pipe(Effect9.tap((t) => closed ? closeOrphan(t) : Effect9.andThen(Effect9.sync(() => {
|
|
1428
|
+
if (requestLimitOverride === null)
|
|
1429
|
+
t.clearRequestTimeout();
|
|
1430
|
+
else if (requestLimitOverride !== undefined)
|
|
1431
|
+
t.setRequestTimeout(requestLimitOverride);
|
|
1353
1432
|
transport = t;
|
|
1354
1433
|
}), SubscriptionRef4.set(connectionState, ConnectionState.Connected()))));
|
|
1355
|
-
const ensureOpen =
|
|
1434
|
+
const ensureOpen = Effect9.fnUntraced(function* () {
|
|
1356
1435
|
if (closed)
|
|
1357
1436
|
return yield* transportClosed();
|
|
1358
1437
|
if (transport)
|
|
@@ -1362,56 +1441,65 @@ function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
1362
1441
|
return yield* transportClosed();
|
|
1363
1442
|
return t;
|
|
1364
1443
|
});
|
|
1365
|
-
const closeTransport =
|
|
1444
|
+
const closeTransport = Effect9.suspend(() => {
|
|
1366
1445
|
if (closed)
|
|
1367
|
-
return
|
|
1446
|
+
return Effect9.void;
|
|
1368
1447
|
closed = true;
|
|
1369
1448
|
const t = transport;
|
|
1370
1449
|
if (!t)
|
|
1371
1450
|
return SubscriptionRef4.set(connectionState, ConnectionState.Disconnected());
|
|
1372
|
-
return
|
|
1451
|
+
return Effect9.andThen(Effect9.logDebug(`Closing ${serviceName}`), Effect9.andThen(Effect9.tryPromise({
|
|
1373
1452
|
try: () => t.close(),
|
|
1374
1453
|
catch: (cause) => toModbusError(cause instanceof Error ? cause : new Error(String(cause)))
|
|
1375
1454
|
}), SubscriptionRef4.set(connectionState, ConnectionState.Disconnected())));
|
|
1376
1455
|
});
|
|
1377
|
-
yield*
|
|
1456
|
+
yield* Effect9.addFinalizer(() => Effect9.orDie(closeTransport));
|
|
1378
1457
|
const notConnectedMsg = "Transport is not connected. Call withClient() first.";
|
|
1379
|
-
const
|
|
1458
|
+
const withNativeLock = (effect) => nativeLock === undefined ? effect : nativeLock.withPermits(1)(effect);
|
|
1459
|
+
const reconnectTransport = (t) => limitConnect(singleFlight(reconnecting, withNativeLock(Effect9.tryPromise({
|
|
1380
1460
|
try: () => t.reconnect(),
|
|
1381
1461
|
catch: (cause) => toModbusError(cause instanceof Error ? cause : new Error(String(cause)))
|
|
1382
|
-
}).pipe(
|
|
1383
|
-
const reconnectOnce =
|
|
1462
|
+
})).pipe(Effect9.tap(() => closed ? closeOrphan(t) : SubscriptionRef4.update(connectionState, (current) => ConnectionState.$is("Down")(current) ? ConnectionState.Connected() : current)))));
|
|
1463
|
+
const reconnectOnce = Effect9.suspend(() => {
|
|
1384
1464
|
const t = transport;
|
|
1385
1465
|
if (closed || !t)
|
|
1386
1466
|
return transportClosed();
|
|
1387
1467
|
return reconnectTransport(t);
|
|
1388
1468
|
});
|
|
1389
1469
|
const report = (error) => {
|
|
1390
|
-
if (closed
|
|
1391
|
-
return
|
|
1470
|
+
if (closed)
|
|
1471
|
+
return Effect9.void;
|
|
1472
|
+
if (!resolvedReconnect.triggers(error))
|
|
1473
|
+
return Effect9.void;
|
|
1392
1474
|
if (!supervised) {
|
|
1393
1475
|
return SubscriptionRef4.update(connectionState, (current) => ConnectionState.$is("Connected")(current) ? ConnectionState.Down({ cause: error }) : current);
|
|
1394
1476
|
}
|
|
1395
|
-
return claimReconnect(reconnectOnce, connectionState, supervised, serviceScope,
|
|
1477
|
+
return claimReconnect(reconnectOnce, connectionState, supervised, serviceScope, Effect9.logDebug(`${serviceName}: reconnecting after ${error.message}`));
|
|
1396
1478
|
};
|
|
1397
1479
|
const resilience = {
|
|
1398
|
-
guard: supervised ? guardCircuit(connectionState) :
|
|
1480
|
+
guard: supervised ? Effect9.suspend(() => transport === null ? Effect9.void : guardCircuit(connectionState)) : Effect9.void,
|
|
1481
|
+
aroundAttempt: aroundNative,
|
|
1399
1482
|
onSuccess: supervised ? undefined : SubscriptionRef4.update(connectionState, (current) => ConnectionState.$is("Down")(current) ? ConnectionState.Connected() : current),
|
|
1400
1483
|
report
|
|
1401
1484
|
};
|
|
1402
|
-
const
|
|
1485
|
+
const resolveClient = Effect9.fnUntraced(function* (unitId) {
|
|
1403
1486
|
const t = yield* ensureOpen();
|
|
1404
1487
|
let client = clientSet.get(unitId);
|
|
1405
1488
|
if (!client) {
|
|
1406
|
-
client = yield*
|
|
1489
|
+
client = yield* Effect9.try({
|
|
1407
1490
|
try: () => t.createClient({ unitId }),
|
|
1408
1491
|
catch: (cause) => toModbusError(cause instanceof Error ? cause : new Error(String(cause)))
|
|
1409
1492
|
});
|
|
1410
1493
|
clientSet.set(unitId, client);
|
|
1494
|
+
touchedUnits.add(unitId);
|
|
1411
1495
|
}
|
|
1412
|
-
return
|
|
1496
|
+
return client;
|
|
1497
|
+
});
|
|
1498
|
+
const makeOperations = Effect9.fnUntraced(function* (unitId) {
|
|
1499
|
+
yield* withNativeLock(resolveClient(unitId));
|
|
1500
|
+
return createEffectModbusClient(resolveClient(unitId));
|
|
1413
1501
|
});
|
|
1414
|
-
const makeClient =
|
|
1502
|
+
const makeClient = Effect9.fnUntraced(function* (unitId, clientOptions) {
|
|
1415
1503
|
const operations = yield* makeOperations(unitId);
|
|
1416
1504
|
return withResilience(operations, {
|
|
1417
1505
|
...resilience,
|
|
@@ -1423,7 +1511,7 @@ function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
1423
1511
|
connectionState,
|
|
1424
1512
|
scope: serviceScope
|
|
1425
1513
|
});
|
|
1426
|
-
const withClient =
|
|
1514
|
+
const withClient = Effect9.fnUntraced(function* (unitId, clientOptions) {
|
|
1427
1515
|
const operations = yield* makeOperations(unitId);
|
|
1428
1516
|
return withResilience(batching.guardRawWrites(unitId, operations), {
|
|
1429
1517
|
...resilience,
|
|
@@ -1433,7 +1521,7 @@ function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
1433
1521
|
return {
|
|
1434
1522
|
connectionState,
|
|
1435
1523
|
withClient,
|
|
1436
|
-
setRequestTimeout:
|
|
1524
|
+
setRequestTimeout: Effect9.fnUntraced(function* (timeoutMs) {
|
|
1437
1525
|
const t = transport;
|
|
1438
1526
|
if (!t || closed) {
|
|
1439
1527
|
return yield* new ModbusNotConnectedError({
|
|
@@ -1442,8 +1530,10 @@ function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
1442
1530
|
});
|
|
1443
1531
|
}
|
|
1444
1532
|
t.setRequestTimeout(timeoutMs);
|
|
1533
|
+
nativeLimit = timeoutMs;
|
|
1534
|
+
requestLimitOverride = timeoutMs;
|
|
1445
1535
|
}),
|
|
1446
|
-
clearRequestTimeout:
|
|
1536
|
+
clearRequestTimeout: Effect9.fnUntraced(function* () {
|
|
1447
1537
|
const t = transport;
|
|
1448
1538
|
if (!t || closed) {
|
|
1449
1539
|
return yield* new ModbusNotConnectedError({
|
|
@@ -1452,8 +1542,10 @@ function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
1452
1542
|
});
|
|
1453
1543
|
}
|
|
1454
1544
|
t.clearRequestTimeout();
|
|
1545
|
+
nativeLimit = undefined;
|
|
1546
|
+
requestLimitOverride = null;
|
|
1455
1547
|
}),
|
|
1456
|
-
reconnect:
|
|
1548
|
+
reconnect: Effect9.fnUntraced(function* () {
|
|
1457
1549
|
if (closed)
|
|
1458
1550
|
return yield* transportClosed();
|
|
1459
1551
|
const t = transport;
|
|
@@ -1466,16 +1558,15 @@ function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
1466
1558
|
return yield* transportClosed();
|
|
1467
1559
|
yield* SubscriptionRef4.set(connectionState, ConnectionState.Connected());
|
|
1468
1560
|
}),
|
|
1469
|
-
close:
|
|
1561
|
+
close: Effect9.fnUntraced(function* (scope) {
|
|
1470
1562
|
if (closed)
|
|
1471
1563
|
return;
|
|
1472
|
-
|
|
1473
|
-
yield* Scope3.close(scope, Exit4.void).pipe(Effect8.onExit(() => closeTransport));
|
|
1564
|
+
yield* Scope3.close(scope, Exit4.void).pipe(Effect9.onExit(() => closeTransport));
|
|
1474
1565
|
}),
|
|
1475
1566
|
withBatchingClient: batching.withBatchingClient,
|
|
1476
1567
|
batchingClient: batching.batchingClient,
|
|
1477
1568
|
get touchedUnits() {
|
|
1478
|
-
return new Set(
|
|
1569
|
+
return new Set(touchedUnits);
|
|
1479
1570
|
},
|
|
1480
1571
|
hasPendingRequests: () => {
|
|
1481
1572
|
if (closed)
|
|
@@ -1491,9 +1582,19 @@ function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
1491
1582
|
|
|
1492
1583
|
// src/AsciiTransportService.ts
|
|
1493
1584
|
class AsciiTransportService extends Context.Service()("AsciiTransportService") {
|
|
1494
|
-
static makeScoped =
|
|
1495
|
-
|
|
1496
|
-
|
|
1585
|
+
static makeScoped = Effect10.fnUntraced(function* (options) {
|
|
1586
|
+
const portPath = yield* serialPortPathScoped(options.portPath);
|
|
1587
|
+
return yield* createTransportScoped("AsyncAsciiTransport", async (transportConstructor, options2) => {
|
|
1588
|
+
return transportConstructor.open({
|
|
1589
|
+
...options2,
|
|
1590
|
+
portPath: await portPath(),
|
|
1591
|
+
responseTimeoutMs: options2.responseTimeoutMs ?? options2.requestTimeoutMs
|
|
1592
|
+
});
|
|
1593
|
+
}, "AsciiTransportService", {
|
|
1594
|
+
serializeRequests: true,
|
|
1595
|
+
responseTimeoutMs: (options2) => options2.responseTimeoutMs ?? options2.requestTimeoutMs
|
|
1596
|
+
})(options);
|
|
1597
|
+
});
|
|
1497
1598
|
static make = (options) => Layer.effect(AsciiTransportService, AsciiTransportService.makeScoped(options));
|
|
1498
1599
|
static makeMockTransport = (devices) => {
|
|
1499
1600
|
const factory = createMockTransport(devices);
|
|
@@ -1504,11 +1605,21 @@ class AsciiTransportService extends Context.Service()("AsciiTransportService") {
|
|
|
1504
1605
|
import { Context as Context3, Layer as Layer3 } from "effect";
|
|
1505
1606
|
|
|
1506
1607
|
// src/RtuTransportService.ts
|
|
1507
|
-
import { Context as Context2, Layer as Layer2 } from "effect";
|
|
1608
|
+
import { Context as Context2, Effect as Effect11, Layer as Layer2 } from "effect";
|
|
1508
1609
|
class RtuTransportService extends Context2.Service()("RtuTransportService") {
|
|
1509
|
-
static makeScoped =
|
|
1510
|
-
|
|
1511
|
-
|
|
1610
|
+
static makeScoped = Effect11.fnUntraced(function* (options) {
|
|
1611
|
+
const portPath = yield* serialPortPathScoped(options.portPath);
|
|
1612
|
+
return yield* createTransportScoped("AsyncRtuTransport", async (transportConstructor, options2) => {
|
|
1613
|
+
return transportConstructor.open({
|
|
1614
|
+
...options2,
|
|
1615
|
+
portPath: await portPath(),
|
|
1616
|
+
responseTimeoutMs: options2.responseTimeoutMs ?? options2.requestTimeoutMs
|
|
1617
|
+
});
|
|
1618
|
+
}, "RtuTransportService", {
|
|
1619
|
+
serializeRequests: true,
|
|
1620
|
+
responseTimeoutMs: (options2) => options2.responseTimeoutMs ?? options2.requestTimeoutMs
|
|
1621
|
+
})(options);
|
|
1622
|
+
});
|
|
1512
1623
|
static make = (options) => Layer2.effect(RtuTransportService, RtuTransportService.makeScoped(options));
|
|
1513
1624
|
static makeMockTransport = (devices) => {
|
|
1514
1625
|
const factory = createMockTransport(devices);
|
|
@@ -1533,7 +1644,10 @@ class SerialTransportService extends Context3.Service()("SerialTransportService"
|
|
|
1533
1644
|
import { Context as Context4, Layer as Layer4 } from "effect";
|
|
1534
1645
|
class TcpTransportService extends Context4.Service()("TcpTransportService") {
|
|
1535
1646
|
static makeScoped = createTransportScoped("AsyncTcpTransport", (transportConstructor, options) => {
|
|
1536
|
-
return transportConstructor.connect(
|
|
1647
|
+
return transportConstructor.connect({
|
|
1648
|
+
...options,
|
|
1649
|
+
responseTimeoutMs: options.responseTimeoutMs ?? options.requestTimeoutMs
|
|
1650
|
+
});
|
|
1537
1651
|
}, "TcpTransportService");
|
|
1538
1652
|
static make = (options) => Layer4.effect(TcpTransportService, TcpTransportService.makeScoped(options));
|
|
1539
1653
|
static makeMockTransport = (devices) => {
|
|
@@ -1542,58 +1656,58 @@ class TcpTransportService extends Context4.Service()("TcpTransportService") {
|
|
|
1542
1656
|
};
|
|
1543
1657
|
}
|
|
1544
1658
|
// src/SerialModbusServerService.ts
|
|
1545
|
-
import { Effect as
|
|
1546
|
-
var serialRtuServerLayer = (options, handlers) => Layer5.effectDiscard(
|
|
1547
|
-
const { AsyncSerialModbusServer } = yield*
|
|
1548
|
-
const server = yield*
|
|
1659
|
+
import { Effect as Effect12, Layer as Layer5 } from "effect";
|
|
1660
|
+
var serialRtuServerLayer = (options, handlers) => Layer5.effectDiscard(Effect12.gen(function* () {
|
|
1661
|
+
const { AsyncSerialModbusServer } = yield* Effect12.promise(() => import("modbus-rs"));
|
|
1662
|
+
const server = yield* Effect12.tryPromise({
|
|
1549
1663
|
try: () => AsyncSerialModbusServer.bindRtu(options, handlers),
|
|
1550
1664
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1551
1665
|
});
|
|
1552
|
-
yield*
|
|
1553
|
-
yield*
|
|
1666
|
+
yield* Effect12.logDebug(`Serial RTU server bound to ${options.portPath}`);
|
|
1667
|
+
yield* Effect12.addFinalizer(() => Effect12.logDebug("Serial RTU server shutting down").pipe(Effect12.andThen(Effect12.tryPromise({
|
|
1554
1668
|
try: () => server.shutdown(),
|
|
1555
1669
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1556
|
-
})),
|
|
1670
|
+
})), Effect12.catch(() => Effect12.void)));
|
|
1557
1671
|
}));
|
|
1558
|
-
var serialAsciiServerLayer = (options, handlers) => Layer5.effectDiscard(
|
|
1559
|
-
const { AsyncSerialModbusServer } = yield*
|
|
1560
|
-
const server = yield*
|
|
1672
|
+
var serialAsciiServerLayer = (options, handlers) => Layer5.effectDiscard(Effect12.gen(function* () {
|
|
1673
|
+
const { AsyncSerialModbusServer } = yield* Effect12.promise(() => import("modbus-rs"));
|
|
1674
|
+
const server = yield* Effect12.tryPromise({
|
|
1561
1675
|
try: () => AsyncSerialModbusServer.bindAscii(options, handlers),
|
|
1562
1676
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1563
1677
|
});
|
|
1564
|
-
yield*
|
|
1565
|
-
yield*
|
|
1678
|
+
yield* Effect12.logDebug(`Serial ASCII server bound to ${options.portPath}`);
|
|
1679
|
+
yield* Effect12.addFinalizer(() => Effect12.logDebug("Serial ASCII server shutting down").pipe(Effect12.andThen(Effect12.tryPromise({
|
|
1566
1680
|
try: () => server.shutdown(),
|
|
1567
1681
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1568
|
-
})),
|
|
1682
|
+
})), Effect12.catch(() => Effect12.void)));
|
|
1569
1683
|
}));
|
|
1570
1684
|
// src/TcpModbusServerService.ts
|
|
1571
|
-
import { Effect as
|
|
1572
|
-
var tcpServerLayer = (options, handlers) => Layer6.effectDiscard(
|
|
1573
|
-
const { AsyncTcpModbusServer } = yield*
|
|
1574
|
-
const server = yield*
|
|
1685
|
+
import { Effect as Effect13, Layer as Layer6 } from "effect";
|
|
1686
|
+
var tcpServerLayer = (options, handlers) => Layer6.effectDiscard(Effect13.gen(function* () {
|
|
1687
|
+
const { AsyncTcpModbusServer } = yield* Effect13.promise(() => import("modbus-rs"));
|
|
1688
|
+
const server = yield* Effect13.tryPromise({
|
|
1575
1689
|
try: () => AsyncTcpModbusServer.bind(options, handlers),
|
|
1576
1690
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1577
1691
|
});
|
|
1578
|
-
yield*
|
|
1579
|
-
yield*
|
|
1692
|
+
yield* Effect13.logDebug(`TCP server bound to ${options.host}:${options.port}`);
|
|
1693
|
+
yield* Effect13.addFinalizer(() => Effect13.logDebug("TCP server shutting down").pipe(Effect13.andThen(Effect13.tryPromise({
|
|
1580
1694
|
try: () => server.shutdown(),
|
|
1581
1695
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1582
|
-
})),
|
|
1696
|
+
})), Effect13.catch(() => Effect13.void)));
|
|
1583
1697
|
}));
|
|
1584
1698
|
// src/TcpGatewayService.ts
|
|
1585
|
-
import { Effect as
|
|
1586
|
-
var tcpGatewayLayer = (options, gatewayConfig) => Layer7.effectDiscard(
|
|
1587
|
-
const { AsyncTcpGateway } = yield*
|
|
1588
|
-
const gateway = yield*
|
|
1699
|
+
import { Effect as Effect14, Layer as Layer7 } from "effect";
|
|
1700
|
+
var tcpGatewayLayer = (options, gatewayConfig) => Layer7.effectDiscard(Effect14.gen(function* () {
|
|
1701
|
+
const { AsyncTcpGateway } = yield* Effect14.promise(() => import("modbus-rs"));
|
|
1702
|
+
const gateway = yield* Effect14.tryPromise({
|
|
1589
1703
|
try: () => AsyncTcpGateway.bind(options, gatewayConfig),
|
|
1590
1704
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1591
1705
|
});
|
|
1592
|
-
yield*
|
|
1593
|
-
yield*
|
|
1706
|
+
yield* Effect14.logDebug(`TCP gateway bound to ${options.host}:${options.port}`);
|
|
1707
|
+
yield* Effect14.addFinalizer(() => Effect14.logDebug("TCP gateway shutting down").pipe(Effect14.andThen(Effect14.tryPromise({
|
|
1594
1708
|
try: () => gateway.shutdown(),
|
|
1595
1709
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1596
|
-
})),
|
|
1710
|
+
})), Effect14.catch(() => Effect14.void)));
|
|
1597
1711
|
}));
|
|
1598
1712
|
// src/WasmWsTransportService.ts
|
|
1599
1713
|
import { Context as Context5, Layer as Layer8 } from "effect";
|
|
@@ -1646,8 +1760,8 @@ class WasmSerialTransportService extends Context8.Service()("WasmSerialTransport
|
|
|
1646
1760
|
};
|
|
1647
1761
|
}
|
|
1648
1762
|
// src/WasmSerialPort.ts
|
|
1649
|
-
import { Effect as
|
|
1650
|
-
var requestSerialPort = () =>
|
|
1763
|
+
import { Effect as Effect15 } from "effect";
|
|
1764
|
+
var requestSerialPort = () => Effect15.tryPromise({
|
|
1651
1765
|
try: async () => {
|
|
1652
1766
|
const mod = await import("modbus-rs/web");
|
|
1653
1767
|
return mod.requestSerialPort();
|
|
@@ -1655,60 +1769,61 @@ var requestSerialPort = () => Effect12.tryPromise({
|
|
|
1655
1769
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1656
1770
|
});
|
|
1657
1771
|
// src/WasmTcpServerService.ts
|
|
1658
|
-
import { Effect as
|
|
1659
|
-
var wasmWsServerLayer = (options, handlers) => Layer12.effectDiscard(
|
|
1660
|
-
const { WasmWsModbusServer } = yield*
|
|
1661
|
-
const server = yield*
|
|
1772
|
+
import { Effect as Effect16, Layer as Layer12 } from "effect";
|
|
1773
|
+
var wasmWsServerLayer = (options, handlers) => Layer12.effectDiscard(Effect16.gen(function* () {
|
|
1774
|
+
const { WasmWsModbusServer } = yield* Effect16.promise(() => import("modbus-rs/web"));
|
|
1775
|
+
const server = yield* Effect16.tryPromise({
|
|
1662
1776
|
try: () => WasmWsModbusServer.bind(options, handlers),
|
|
1663
1777
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1664
1778
|
});
|
|
1665
|
-
yield*
|
|
1666
|
-
yield*
|
|
1779
|
+
yield* Effect16.logDebug(`WASM WS server bound to ${options.wsUrl}`);
|
|
1780
|
+
yield* Effect16.forkScoped(Effect16.tryPromise({
|
|
1667
1781
|
try: () => server.serve(),
|
|
1668
1782
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1669
|
-
}).pipe(
|
|
1670
|
-
yield*
|
|
1783
|
+
}).pipe(Effect16.catch((error) => Effect16.logError("WASM WS server loop ended", error))));
|
|
1784
|
+
yield* Effect16.addFinalizer(() => Effect16.logDebug("WASM WS server shutting down").pipe(Effect16.andThen(Effect16.tryPromise({
|
|
1671
1785
|
try: () => server.shutdown(),
|
|
1672
1786
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1673
|
-
})),
|
|
1787
|
+
})), Effect16.catch(() => Effect16.void)));
|
|
1674
1788
|
}));
|
|
1675
1789
|
// src/WasmSerialModbusServerService.ts
|
|
1676
|
-
import { Effect as
|
|
1677
|
-
var wasmSerialRtuServerLayer = (options, handlers) => Layer13.effectDiscard(
|
|
1678
|
-
const { WasmSerialModbusServer } = yield*
|
|
1679
|
-
const server = yield*
|
|
1790
|
+
import { Effect as Effect17, Layer as Layer13 } from "effect";
|
|
1791
|
+
var wasmSerialRtuServerLayer = (options, handlers) => Layer13.effectDiscard(Effect17.gen(function* () {
|
|
1792
|
+
const { WasmSerialModbusServer } = yield* Effect17.promise(() => import("modbus-rs/web"));
|
|
1793
|
+
const server = yield* Effect17.tryPromise({
|
|
1680
1794
|
try: () => WasmSerialModbusServer.bindRtu(options, handlers),
|
|
1681
1795
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1682
1796
|
});
|
|
1683
|
-
yield*
|
|
1684
|
-
yield*
|
|
1797
|
+
yield* Effect17.logDebug("WASM serial RTU server bound");
|
|
1798
|
+
yield* Effect17.forkScoped(Effect17.tryPromise({
|
|
1685
1799
|
try: () => server.serve(),
|
|
1686
1800
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1687
|
-
}).pipe(
|
|
1688
|
-
yield*
|
|
1801
|
+
}).pipe(Effect17.catch((error) => Effect17.logError("WASM serial RTU server loop ended", error))));
|
|
1802
|
+
yield* Effect17.addFinalizer(() => Effect17.logDebug("WASM serial RTU server shutting down").pipe(Effect17.andThen(Effect17.tryPromise({
|
|
1689
1803
|
try: () => server.shutdown(),
|
|
1690
1804
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1691
|
-
})),
|
|
1805
|
+
})), Effect17.catch(() => Effect17.void)));
|
|
1692
1806
|
}));
|
|
1693
|
-
var wasmSerialAsciiServerLayer = (options, handlers) => Layer13.effectDiscard(
|
|
1694
|
-
const { WasmSerialModbusServer } = yield*
|
|
1695
|
-
const server = yield*
|
|
1807
|
+
var wasmSerialAsciiServerLayer = (options, handlers) => Layer13.effectDiscard(Effect17.gen(function* () {
|
|
1808
|
+
const { WasmSerialModbusServer } = yield* Effect17.promise(() => import("modbus-rs/web"));
|
|
1809
|
+
const server = yield* Effect17.tryPromise({
|
|
1696
1810
|
try: () => WasmSerialModbusServer.bindAscii(options, handlers),
|
|
1697
1811
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1698
1812
|
});
|
|
1699
|
-
yield*
|
|
1700
|
-
yield*
|
|
1813
|
+
yield* Effect17.logDebug("WASM serial ASCII server bound");
|
|
1814
|
+
yield* Effect17.forkScoped(Effect17.tryPromise({
|
|
1701
1815
|
try: () => server.serve(),
|
|
1702
1816
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1703
|
-
}).pipe(
|
|
1704
|
-
yield*
|
|
1817
|
+
}).pipe(Effect17.catch((error) => Effect17.logError("WASM serial ASCII server loop ended", error))));
|
|
1818
|
+
yield* Effect17.addFinalizer(() => Effect17.logDebug("WASM serial ASCII server shutting down").pipe(Effect17.andThen(Effect17.tryPromise({
|
|
1705
1819
|
try: () => server.shutdown(),
|
|
1706
1820
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1707
|
-
})),
|
|
1821
|
+
})), Effect17.catch(() => Effect17.void)));
|
|
1708
1822
|
}));
|
|
1709
1823
|
export {
|
|
1710
1824
|
AsciiTransportService,
|
|
1711
1825
|
ConnectionState,
|
|
1826
|
+
MAX_SERIAL_PORT_PATH,
|
|
1712
1827
|
MODBUS_MAX_READ_REGISTERS,
|
|
1713
1828
|
MODBUS_MAX_WRITE_REGISTERS,
|
|
1714
1829
|
ModbusCircuitOpenError,
|
|
@@ -1739,6 +1854,7 @@ export {
|
|
|
1739
1854
|
planReads,
|
|
1740
1855
|
planWrites,
|
|
1741
1856
|
requestSerialPort,
|
|
1857
|
+
resolveSerialPortPath,
|
|
1742
1858
|
retryModbus,
|
|
1743
1859
|
retryableExceptionCodes,
|
|
1744
1860
|
serialAsciiServerLayer,
|