@flux-control/effect-modbus-rs 0.8.0 → 0.9.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 +499 -832
- package/dist/index.d.ts +56 -113
- package/dist/index.js +234 -116
- 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 +6 -5
- package/dist/src/WasmAsciiTransportService.d.ts +13 -7
- package/dist/src/WasmRtuTransportService.d.ts +13 -7
- 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 +9 -5
- 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 +3 -2
- 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 +25 -5
- 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 +20 -11
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 {
|
|
@@ -1273,17 +1279,68 @@ var createMockTransport = (devices) => {
|
|
|
1273
1279
|
});
|
|
1274
1280
|
};
|
|
1275
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
|
+
|
|
1276
1322
|
// src/shared-transport.ts
|
|
1277
|
-
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";
|
|
1278
1335
|
var makeInFlight = () => Ref.make(Option.none());
|
|
1279
|
-
var singleFlight = (inFlight, work) =>
|
|
1336
|
+
var singleFlight = (inFlight, work) => Effect9.uninterruptibleMask((restore) => Effect9.gen(function* () {
|
|
1280
1337
|
const fresh = yield* Deferred4.make();
|
|
1281
1338
|
const [deferred, isLeader] = yield* Ref.modify(inFlight, (current) => Option.match(current, {
|
|
1282
1339
|
onSome: (existing) => [[existing, false], current],
|
|
1283
1340
|
onNone: () => [[fresh, true], Option.some(fresh)]
|
|
1284
1341
|
}));
|
|
1285
1342
|
if (isLeader) {
|
|
1286
|
-
yield*
|
|
1343
|
+
yield* Effect9.forkDetach(Effect9.interruptible(work).pipe(Effect9.onExit((exit) => Effect9.andThen(Ref.set(inFlight, Option.none()), Deferred4.done(deferred, exit)))));
|
|
1287
1344
|
}
|
|
1288
1345
|
return yield* restore(Deferred4.await(deferred));
|
|
1289
1346
|
}));
|
|
@@ -1314,44 +1371,67 @@ var loadTransportConstructor = async (transportKey, moduleSpecifier) => {
|
|
|
1314
1371
|
}
|
|
1315
1372
|
};
|
|
1316
1373
|
function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
1317
|
-
return
|
|
1374
|
+
return Effect9.fnUntraced(function* (options) {
|
|
1318
1375
|
const { retry: transportRetry, reconnect: reconnectOptions } = options;
|
|
1319
1376
|
const connectTimeout = options.connectTimeout === undefined ? undefined : Duration6.fromInputUnsafe(options.connectTimeout);
|
|
1320
1377
|
const openOptions = { ...options };
|
|
1321
1378
|
Reflect.deleteProperty(openOptions, "retry");
|
|
1322
1379
|
Reflect.deleteProperty(openOptions, "reconnect");
|
|
1323
1380
|
Reflect.deleteProperty(openOptions, "connectTimeout");
|
|
1324
|
-
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"));
|
|
1325
1394
|
let transport = null;
|
|
1326
1395
|
const opening = yield* makeInFlight();
|
|
1327
1396
|
const reconnecting = yield* makeInFlight();
|
|
1328
1397
|
const connectionState = yield* SubscriptionRef4.make(ConnectionState.Disconnected());
|
|
1329
1398
|
const resolvedReconnect = resolveReconnect(reconnectOptions ?? {});
|
|
1330
1399
|
const supervised = reconnectOptions ? resolvedReconnect : null;
|
|
1331
|
-
const serviceScope = yield*
|
|
1400
|
+
const serviceScope = yield* Effect9.scope;
|
|
1332
1401
|
const clientSet = new Map;
|
|
1402
|
+
const touchedUnits = new Set;
|
|
1333
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
|
+
});
|
|
1334
1410
|
const transportClosed = () => new ModbusNotConnectedError({
|
|
1335
1411
|
cause: new Error("Transport has been closed"),
|
|
1336
1412
|
message: "Transport has been closed"
|
|
1337
1413
|
});
|
|
1338
|
-
const closeOrphan = (t) =>
|
|
1414
|
+
const closeOrphan = (t) => Effect9.forkDetach(Effect9.ignore(Effect9.tryPromise(() => t.close())));
|
|
1339
1415
|
const limitConnect = (wait) => {
|
|
1340
1416
|
if (connectTimeout === undefined)
|
|
1341
1417
|
return wait;
|
|
1342
1418
|
const message = `${serviceName}: connect did not complete within ${Duration6.format(connectTimeout)}`;
|
|
1343
|
-
return
|
|
1419
|
+
return Effect9.timeoutOrElse(wait, {
|
|
1344
1420
|
duration: connectTimeout,
|
|
1345
|
-
orElse: () =>
|
|
1421
|
+
orElse: () => Effect9.fail(new ModbusTimeoutError({ cause: new Error(message), message }))
|
|
1346
1422
|
});
|
|
1347
1423
|
};
|
|
1348
|
-
const openTransport =
|
|
1424
|
+
const openTransport = Effect9.tryPromise({
|
|
1349
1425
|
try: () => openMethod(TC, openOptions),
|
|
1350
1426
|
catch: (cause) => toModbusError(cause instanceof Error ? cause : new Error(String(cause)))
|
|
1351
|
-
}).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);
|
|
1352
1432
|
transport = t;
|
|
1353
1433
|
}), SubscriptionRef4.set(connectionState, ConnectionState.Connected()))));
|
|
1354
|
-
const ensureOpen =
|
|
1434
|
+
const ensureOpen = Effect9.fnUntraced(function* () {
|
|
1355
1435
|
if (closed)
|
|
1356
1436
|
return yield* transportClosed();
|
|
1357
1437
|
if (transport)
|
|
@@ -1361,56 +1441,65 @@ function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
1361
1441
|
return yield* transportClosed();
|
|
1362
1442
|
return t;
|
|
1363
1443
|
});
|
|
1364
|
-
const closeTransport =
|
|
1444
|
+
const closeTransport = Effect9.suspend(() => {
|
|
1365
1445
|
if (closed)
|
|
1366
|
-
return
|
|
1446
|
+
return Effect9.void;
|
|
1367
1447
|
closed = true;
|
|
1368
1448
|
const t = transport;
|
|
1369
1449
|
if (!t)
|
|
1370
1450
|
return SubscriptionRef4.set(connectionState, ConnectionState.Disconnected());
|
|
1371
|
-
return
|
|
1451
|
+
return Effect9.andThen(Effect9.logDebug(`Closing ${serviceName}`), Effect9.andThen(Effect9.tryPromise({
|
|
1372
1452
|
try: () => t.close(),
|
|
1373
1453
|
catch: (cause) => toModbusError(cause instanceof Error ? cause : new Error(String(cause)))
|
|
1374
1454
|
}), SubscriptionRef4.set(connectionState, ConnectionState.Disconnected())));
|
|
1375
1455
|
});
|
|
1376
|
-
yield*
|
|
1456
|
+
yield* Effect9.addFinalizer(() => Effect9.orDie(closeTransport));
|
|
1377
1457
|
const notConnectedMsg = "Transport is not connected. Call withClient() first.";
|
|
1378
|
-
const
|
|
1458
|
+
const withNativeLock = (effect) => nativeLock === undefined ? effect : nativeLock.withPermits(1)(effect);
|
|
1459
|
+
const reconnectTransport = (t) => limitConnect(singleFlight(reconnecting, withNativeLock(Effect9.tryPromise({
|
|
1379
1460
|
try: () => t.reconnect(),
|
|
1380
1461
|
catch: (cause) => toModbusError(cause instanceof Error ? cause : new Error(String(cause)))
|
|
1381
|
-
}).pipe(
|
|
1382
|
-
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(() => {
|
|
1383
1464
|
const t = transport;
|
|
1384
1465
|
if (closed || !t)
|
|
1385
1466
|
return transportClosed();
|
|
1386
1467
|
return reconnectTransport(t);
|
|
1387
1468
|
});
|
|
1388
1469
|
const report = (error) => {
|
|
1389
|
-
if (closed
|
|
1390
|
-
return
|
|
1470
|
+
if (closed)
|
|
1471
|
+
return Effect9.void;
|
|
1472
|
+
if (!resolvedReconnect.triggers(error))
|
|
1473
|
+
return Effect9.void;
|
|
1391
1474
|
if (!supervised) {
|
|
1392
1475
|
return SubscriptionRef4.update(connectionState, (current) => ConnectionState.$is("Connected")(current) ? ConnectionState.Down({ cause: error }) : current);
|
|
1393
1476
|
}
|
|
1394
|
-
return claimReconnect(reconnectOnce, connectionState, supervised, serviceScope,
|
|
1477
|
+
return claimReconnect(reconnectOnce, connectionState, supervised, serviceScope, Effect9.logDebug(`${serviceName}: reconnecting after ${error.message}`));
|
|
1395
1478
|
};
|
|
1396
1479
|
const resilience = {
|
|
1397
|
-
guard: supervised ? guardCircuit(connectionState) :
|
|
1480
|
+
guard: supervised ? Effect9.suspend(() => transport === null ? Effect9.void : guardCircuit(connectionState)) : Effect9.void,
|
|
1481
|
+
aroundAttempt: aroundNative,
|
|
1398
1482
|
onSuccess: supervised ? undefined : SubscriptionRef4.update(connectionState, (current) => ConnectionState.$is("Down")(current) ? ConnectionState.Connected() : current),
|
|
1399
1483
|
report
|
|
1400
1484
|
};
|
|
1401
|
-
const
|
|
1485
|
+
const resolveClient = Effect9.fnUntraced(function* (unitId) {
|
|
1402
1486
|
const t = yield* ensureOpen();
|
|
1403
1487
|
let client = clientSet.get(unitId);
|
|
1404
1488
|
if (!client) {
|
|
1405
|
-
client = yield*
|
|
1489
|
+
client = yield* Effect9.try({
|
|
1406
1490
|
try: () => t.createClient({ unitId }),
|
|
1407
1491
|
catch: (cause) => toModbusError(cause instanceof Error ? cause : new Error(String(cause)))
|
|
1408
1492
|
});
|
|
1409
1493
|
clientSet.set(unitId, client);
|
|
1494
|
+
touchedUnits.add(unitId);
|
|
1410
1495
|
}
|
|
1411
|
-
return
|
|
1496
|
+
return client;
|
|
1497
|
+
});
|
|
1498
|
+
const makeOperations = Effect9.fnUntraced(function* (unitId) {
|
|
1499
|
+
yield* withNativeLock(resolveClient(unitId));
|
|
1500
|
+
return createEffectModbusClient(resolveClient(unitId));
|
|
1412
1501
|
});
|
|
1413
|
-
const makeClient =
|
|
1502
|
+
const makeClient = Effect9.fnUntraced(function* (unitId, clientOptions) {
|
|
1414
1503
|
const operations = yield* makeOperations(unitId);
|
|
1415
1504
|
return withResilience(operations, {
|
|
1416
1505
|
...resilience,
|
|
@@ -1422,7 +1511,7 @@ function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
1422
1511
|
connectionState,
|
|
1423
1512
|
scope: serviceScope
|
|
1424
1513
|
});
|
|
1425
|
-
const withClient =
|
|
1514
|
+
const withClient = Effect9.fnUntraced(function* (unitId, clientOptions) {
|
|
1426
1515
|
const operations = yield* makeOperations(unitId);
|
|
1427
1516
|
return withResilience(batching.guardRawWrites(unitId, operations), {
|
|
1428
1517
|
...resilience,
|
|
@@ -1432,7 +1521,7 @@ function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
1432
1521
|
return {
|
|
1433
1522
|
connectionState,
|
|
1434
1523
|
withClient,
|
|
1435
|
-
setRequestTimeout:
|
|
1524
|
+
setRequestTimeout: Effect9.fnUntraced(function* (timeoutMs) {
|
|
1436
1525
|
const t = transport;
|
|
1437
1526
|
if (!t || closed) {
|
|
1438
1527
|
return yield* new ModbusNotConnectedError({
|
|
@@ -1441,8 +1530,10 @@ function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
1441
1530
|
});
|
|
1442
1531
|
}
|
|
1443
1532
|
t.setRequestTimeout(timeoutMs);
|
|
1533
|
+
nativeLimit = timeoutMs;
|
|
1534
|
+
requestLimitOverride = timeoutMs;
|
|
1444
1535
|
}),
|
|
1445
|
-
clearRequestTimeout:
|
|
1536
|
+
clearRequestTimeout: Effect9.fnUntraced(function* () {
|
|
1446
1537
|
const t = transport;
|
|
1447
1538
|
if (!t || closed) {
|
|
1448
1539
|
return yield* new ModbusNotConnectedError({
|
|
@@ -1451,8 +1542,10 @@ function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
1451
1542
|
});
|
|
1452
1543
|
}
|
|
1453
1544
|
t.clearRequestTimeout();
|
|
1545
|
+
nativeLimit = undefined;
|
|
1546
|
+
requestLimitOverride = null;
|
|
1454
1547
|
}),
|
|
1455
|
-
reconnect:
|
|
1548
|
+
reconnect: Effect9.fnUntraced(function* () {
|
|
1456
1549
|
if (closed)
|
|
1457
1550
|
return yield* transportClosed();
|
|
1458
1551
|
const t = transport;
|
|
@@ -1465,15 +1558,15 @@ function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
1465
1558
|
return yield* transportClosed();
|
|
1466
1559
|
yield* SubscriptionRef4.set(connectionState, ConnectionState.Connected());
|
|
1467
1560
|
}),
|
|
1468
|
-
close:
|
|
1561
|
+
close: Effect9.fnUntraced(function* (scope) {
|
|
1469
1562
|
if (closed)
|
|
1470
1563
|
return;
|
|
1471
|
-
yield* Scope3.close(scope, Exit4.void).pipe(
|
|
1564
|
+
yield* Scope3.close(scope, Exit4.void).pipe(Effect9.onExit(() => closeTransport));
|
|
1472
1565
|
}),
|
|
1473
1566
|
withBatchingClient: batching.withBatchingClient,
|
|
1474
1567
|
batchingClient: batching.batchingClient,
|
|
1475
1568
|
get touchedUnits() {
|
|
1476
|
-
return new Set(
|
|
1569
|
+
return new Set(touchedUnits);
|
|
1477
1570
|
},
|
|
1478
1571
|
hasPendingRequests: () => {
|
|
1479
1572
|
if (closed)
|
|
@@ -1489,9 +1582,19 @@ function createTransportScoped(transportKey, openMethod, serviceName, config) {
|
|
|
1489
1582
|
|
|
1490
1583
|
// src/AsciiTransportService.ts
|
|
1491
1584
|
class AsciiTransportService extends Context.Service()("AsciiTransportService") {
|
|
1492
|
-
static makeScoped =
|
|
1493
|
-
|
|
1494
|
-
|
|
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
|
+
});
|
|
1495
1598
|
static make = (options) => Layer.effect(AsciiTransportService, AsciiTransportService.makeScoped(options));
|
|
1496
1599
|
static makeMockTransport = (devices) => {
|
|
1497
1600
|
const factory = createMockTransport(devices);
|
|
@@ -1502,11 +1605,21 @@ class AsciiTransportService extends Context.Service()("AsciiTransportService") {
|
|
|
1502
1605
|
import { Context as Context3, Layer as Layer3 } from "effect";
|
|
1503
1606
|
|
|
1504
1607
|
// src/RtuTransportService.ts
|
|
1505
|
-
import { Context as Context2, Layer as Layer2 } from "effect";
|
|
1608
|
+
import { Context as Context2, Effect as Effect11, Layer as Layer2 } from "effect";
|
|
1506
1609
|
class RtuTransportService extends Context2.Service()("RtuTransportService") {
|
|
1507
|
-
static makeScoped =
|
|
1508
|
-
|
|
1509
|
-
|
|
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
|
+
});
|
|
1510
1623
|
static make = (options) => Layer2.effect(RtuTransportService, RtuTransportService.makeScoped(options));
|
|
1511
1624
|
static makeMockTransport = (devices) => {
|
|
1512
1625
|
const factory = createMockTransport(devices);
|
|
@@ -1531,7 +1644,10 @@ class SerialTransportService extends Context3.Service()("SerialTransportService"
|
|
|
1531
1644
|
import { Context as Context4, Layer as Layer4 } from "effect";
|
|
1532
1645
|
class TcpTransportService extends Context4.Service()("TcpTransportService") {
|
|
1533
1646
|
static makeScoped = createTransportScoped("AsyncTcpTransport", (transportConstructor, options) => {
|
|
1534
|
-
return transportConstructor.connect(
|
|
1647
|
+
return transportConstructor.connect({
|
|
1648
|
+
...options,
|
|
1649
|
+
responseTimeoutMs: options.responseTimeoutMs ?? options.requestTimeoutMs
|
|
1650
|
+
});
|
|
1535
1651
|
}, "TcpTransportService");
|
|
1536
1652
|
static make = (options) => Layer4.effect(TcpTransportService, TcpTransportService.makeScoped(options));
|
|
1537
1653
|
static makeMockTransport = (devices) => {
|
|
@@ -1540,58 +1656,58 @@ class TcpTransportService extends Context4.Service()("TcpTransportService") {
|
|
|
1540
1656
|
};
|
|
1541
1657
|
}
|
|
1542
1658
|
// src/SerialModbusServerService.ts
|
|
1543
|
-
import { Effect as
|
|
1544
|
-
var serialRtuServerLayer = (options, handlers) => Layer5.effectDiscard(
|
|
1545
|
-
const { AsyncSerialModbusServer } = yield*
|
|
1546
|
-
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({
|
|
1547
1663
|
try: () => AsyncSerialModbusServer.bindRtu(options, handlers),
|
|
1548
1664
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1549
1665
|
});
|
|
1550
|
-
yield*
|
|
1551
|
-
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({
|
|
1552
1668
|
try: () => server.shutdown(),
|
|
1553
1669
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1554
|
-
})),
|
|
1670
|
+
})), Effect12.catch(() => Effect12.void)));
|
|
1555
1671
|
}));
|
|
1556
|
-
var serialAsciiServerLayer = (options, handlers) => Layer5.effectDiscard(
|
|
1557
|
-
const { AsyncSerialModbusServer } = yield*
|
|
1558
|
-
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({
|
|
1559
1675
|
try: () => AsyncSerialModbusServer.bindAscii(options, handlers),
|
|
1560
1676
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1561
1677
|
});
|
|
1562
|
-
yield*
|
|
1563
|
-
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({
|
|
1564
1680
|
try: () => server.shutdown(),
|
|
1565
1681
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1566
|
-
})),
|
|
1682
|
+
})), Effect12.catch(() => Effect12.void)));
|
|
1567
1683
|
}));
|
|
1568
1684
|
// src/TcpModbusServerService.ts
|
|
1569
|
-
import { Effect as
|
|
1570
|
-
var tcpServerLayer = (options, handlers) => Layer6.effectDiscard(
|
|
1571
|
-
const { AsyncTcpModbusServer } = yield*
|
|
1572
|
-
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({
|
|
1573
1689
|
try: () => AsyncTcpModbusServer.bind(options, handlers),
|
|
1574
1690
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1575
1691
|
});
|
|
1576
|
-
yield*
|
|
1577
|
-
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({
|
|
1578
1694
|
try: () => server.shutdown(),
|
|
1579
1695
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1580
|
-
})),
|
|
1696
|
+
})), Effect13.catch(() => Effect13.void)));
|
|
1581
1697
|
}));
|
|
1582
1698
|
// src/TcpGatewayService.ts
|
|
1583
|
-
import { Effect as
|
|
1584
|
-
var tcpGatewayLayer = (options, gatewayConfig) => Layer7.effectDiscard(
|
|
1585
|
-
const { AsyncTcpGateway } = yield*
|
|
1586
|
-
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({
|
|
1587
1703
|
try: () => AsyncTcpGateway.bind(options, gatewayConfig),
|
|
1588
1704
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1589
1705
|
});
|
|
1590
|
-
yield*
|
|
1591
|
-
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({
|
|
1592
1708
|
try: () => gateway.shutdown(),
|
|
1593
1709
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1594
|
-
})),
|
|
1710
|
+
})), Effect14.catch(() => Effect14.void)));
|
|
1595
1711
|
}));
|
|
1596
1712
|
// src/WasmWsTransportService.ts
|
|
1597
1713
|
import { Context as Context5, Layer as Layer8 } from "effect";
|
|
@@ -1644,8 +1760,8 @@ class WasmSerialTransportService extends Context8.Service()("WasmSerialTransport
|
|
|
1644
1760
|
};
|
|
1645
1761
|
}
|
|
1646
1762
|
// src/WasmSerialPort.ts
|
|
1647
|
-
import { Effect as
|
|
1648
|
-
var requestSerialPort = () =>
|
|
1763
|
+
import { Effect as Effect15 } from "effect";
|
|
1764
|
+
var requestSerialPort = () => Effect15.tryPromise({
|
|
1649
1765
|
try: async () => {
|
|
1650
1766
|
const mod = await import("modbus-rs/web");
|
|
1651
1767
|
return mod.requestSerialPort();
|
|
@@ -1653,60 +1769,61 @@ var requestSerialPort = () => Effect12.tryPromise({
|
|
|
1653
1769
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1654
1770
|
});
|
|
1655
1771
|
// src/WasmTcpServerService.ts
|
|
1656
|
-
import { Effect as
|
|
1657
|
-
var wasmWsServerLayer = (options, handlers) => Layer12.effectDiscard(
|
|
1658
|
-
const { WasmWsModbusServer } = yield*
|
|
1659
|
-
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({
|
|
1660
1776
|
try: () => WasmWsModbusServer.bind(options, handlers),
|
|
1661
1777
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1662
1778
|
});
|
|
1663
|
-
yield*
|
|
1664
|
-
yield*
|
|
1779
|
+
yield* Effect16.logDebug(`WASM WS server bound to ${options.wsUrl}`);
|
|
1780
|
+
yield* Effect16.forkScoped(Effect16.tryPromise({
|
|
1665
1781
|
try: () => server.serve(),
|
|
1666
1782
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1667
|
-
}).pipe(
|
|
1668
|
-
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({
|
|
1669
1785
|
try: () => server.shutdown(),
|
|
1670
1786
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1671
|
-
})),
|
|
1787
|
+
})), Effect16.catch(() => Effect16.void)));
|
|
1672
1788
|
}));
|
|
1673
1789
|
// src/WasmSerialModbusServerService.ts
|
|
1674
|
-
import { Effect as
|
|
1675
|
-
var wasmSerialRtuServerLayer = (options, handlers) => Layer13.effectDiscard(
|
|
1676
|
-
const { WasmSerialModbusServer } = yield*
|
|
1677
|
-
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({
|
|
1678
1794
|
try: () => WasmSerialModbusServer.bindRtu(options, handlers),
|
|
1679
1795
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1680
1796
|
});
|
|
1681
|
-
yield*
|
|
1682
|
-
yield*
|
|
1797
|
+
yield* Effect17.logDebug("WASM serial RTU server bound");
|
|
1798
|
+
yield* Effect17.forkScoped(Effect17.tryPromise({
|
|
1683
1799
|
try: () => server.serve(),
|
|
1684
1800
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1685
|
-
}).pipe(
|
|
1686
|
-
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({
|
|
1687
1803
|
try: () => server.shutdown(),
|
|
1688
1804
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1689
|
-
})),
|
|
1805
|
+
})), Effect17.catch(() => Effect17.void)));
|
|
1690
1806
|
}));
|
|
1691
|
-
var wasmSerialAsciiServerLayer = (options, handlers) => Layer13.effectDiscard(
|
|
1692
|
-
const { WasmSerialModbusServer } = yield*
|
|
1693
|
-
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({
|
|
1694
1810
|
try: () => WasmSerialModbusServer.bindAscii(options, handlers),
|
|
1695
1811
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1696
1812
|
});
|
|
1697
|
-
yield*
|
|
1698
|
-
yield*
|
|
1813
|
+
yield* Effect17.logDebug("WASM serial ASCII server bound");
|
|
1814
|
+
yield* Effect17.forkScoped(Effect17.tryPromise({
|
|
1699
1815
|
try: () => server.serve(),
|
|
1700
1816
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1701
|
-
}).pipe(
|
|
1702
|
-
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({
|
|
1703
1819
|
try: () => server.shutdown(),
|
|
1704
1820
|
catch: (error) => toModbusError(error instanceof Error ? error : new Error(String(error)))
|
|
1705
|
-
})),
|
|
1821
|
+
})), Effect17.catch(() => Effect17.void)));
|
|
1706
1822
|
}));
|
|
1707
1823
|
export {
|
|
1708
1824
|
AsciiTransportService,
|
|
1709
1825
|
ConnectionState,
|
|
1826
|
+
MAX_SERIAL_PORT_PATH,
|
|
1710
1827
|
MODBUS_MAX_READ_REGISTERS,
|
|
1711
1828
|
MODBUS_MAX_WRITE_REGISTERS,
|
|
1712
1829
|
ModbusCircuitOpenError,
|
|
@@ -1737,6 +1854,7 @@ export {
|
|
|
1737
1854
|
planReads,
|
|
1738
1855
|
planWrites,
|
|
1739
1856
|
requestSerialPort,
|
|
1857
|
+
resolveSerialPortPath,
|
|
1740
1858
|
retryModbus,
|
|
1741
1859
|
retryableExceptionCodes,
|
|
1742
1860
|
serialAsciiServerLayer,
|