From 4b0664bd55a543e8934d1e59079e9775328b3e3a Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 24 Apr 2026 14:10:59 -0400 Subject: [PATCH] =?UTF-8?q?FOCAS=20=E2=80=94=20retire=20Tier-C=20split,=20?= =?UTF-8?q?inline=20managed=20wire=20client,=20make=20read-only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migration closes the FOCAS Tier-C architecture. OtOpcUa previously had `Driver.FOCAS.Host` (NSSM-wrapped Windows service loading Fwlib64.dll via P/Invoke) + `Driver.FOCAS.Shared` (MessagePack IPC contracts) + a C shim DLL stand-in for unit tests. All of it is deleted; the driver is now a single in-process managed assembly talking the FOCAS/2 Ethernet binary protocol directly on TCP:8193. Architecture - Pure-managed `FocasWireClient` inlined at `src/.../Driver.FOCAS/Wire/` (owner-imported — see Wire/FocasWireClient.cs for the full surface). Opens two TCP sockets, runs the initiate handshake, serialises requests on socket 2 through a semaphore, closes cleanly with PDU + socket teardown. Both sync `IDisposable` and async `IAsyncDisposable`. - `WireFocasClient` (same folder) adapts the wire client to OtOpcUa's `IFocasClient` surface — fixed-tree reads, PARAM/MACRO/PMC addresses, alarms. Writes return `BadNotWritable` by design — OtOpcUa is read-only against FOCAS. - `FocasDriverFactoryExtensions` now accepts `"Backend": "wire"` (default) and `"Backend": "unimplemented"`. Legacy `ipc` and `fwlib` backends are rejected at startup with a diagnostic pointing at the migration doc. Deletions - `src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/` — whole project + Ipc/, Backend/, Stability/, Program.cs. - `src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/` — Contracts/, FrameReader, FrameWriter, whole project. - `tests/...Driver.FOCAS.Host.Tests/` + `.Shared.Tests/` — whole projects. - `src/.../Driver.FOCAS/FwlibNative.cs` + `FwlibFocasClient.cs` — 21 P/Invokes + 7 `Pack=1` marshalling structs + the Fwlib-backed `IFocasClient` implementation. - `src/.../Driver.FOCAS/Ipc/` + `Supervisor/` — IPC client wrapper + Host-process supervisor (backoff, circuit breaker, heartbeat, post- mortem reader, process launcher). - `scripts/install/Install-FocasHost.ps1` — NSSM service installer. - `tests/.../Driver.FOCAS.Tests/{IpcFocasClientTests, IpcLoopback, FwlibNativeHelperTests, PostMortemReaderCompatibilityTests, SupervisorTests, FocasDriverFactoryExtensionsTests}.cs` — tests that exercised the retired surfaces. - `tests/.../Driver.FOCAS.IntegrationTests/Shim/` — the zig-built C shim DLL that masqueraded as Fwlib64.dll. Solution changes - `ZB.MOM.WW.OtOpcUa.slnx` drops the 4 retired project refs. - `src/.../Driver.FOCAS.csproj` drops the Shared ProjectReference, adds `Microsoft.Extensions.Logging.Abstractions` for the optional `ILogger` hook in `FocasWireClient`. - `src/.../Driver.FOCAS.Cli.csproj` drops the six `` entries that copied `vendor/fanuc/*.dll` into the CLI bin. CLI now uses `WireFocasClient` directly. - `FocasDriver` default factory flips to `Wire.WireFocasClientFactory`. Integration tests - New `tests/.../Driver.FOCAS.IntegrationTests/` project covering fixed- tree reads (identity, axes, dynamic, program, operation mode, timers, spindle load + max RPM, servo meters), user-authored PARAM / MACRO / PMC reads, `DiscoverAsync` emission, `SubscribeAsync` + `OnDataChange`, `IAlarmSource` raise/clear transitions, and `ProbeAsync` / `OnHostStatusChanged`. 9 e2e tests against the focas-mock fixture (Docker container with the vendored Python mock's native FOCAS/2 Ethernet responder). - `scripts/integration/run-focas.ps1` orchestrates compose up → tests → compose down. Dropped the shim-build stage + DLL-copy step + the split testhost workaround (the latter only existed because of native-DLL lifecycle bugs the shim tripped). - Docker compose collapses from 11 per-series services to one `focas-sim` service. Tests seed per-series state via `mock_load_profile` at test start. - Vendored focas-mock snapshot refreshed to pick up upstream's native FOCAS/2 Ethernet responder (was 660 lines, now 1018) — the pre-refresh snapshot only spoke the JSON admin protocol. Tests - 145/145 unit tests in `Driver.FOCAS.Tests` pass (was 208 pre-deletion; 63 removed tests exercised the retired IPC/shim/supervisor/Fwlib surfaces). - 9/9 integration tests pass against the refreshed mock. - `FocasScaffoldingTests.Unimplemented_factory_throws_on_Create…` updated to assert the new diagnostic message pointing at `docs/drivers/FOCAS.md` rather than the now-gone `Fwlib64.dll`. Docs - `docs/drivers/FOCAS.md` rewritten for the managed wire topology — deployment collapses to one `"Backend": "wire"` config block, no separate service, no DLL deployment, no pipe ACL. - `docs/drivers/FOCAS-Test-Fixture.md` updated — single TCP probe skip gate instead of TCP + shim probe; fewer moving parts. - `docs/drivers/README.md` row for FOCAS reflects the Tier-A managed topology (previously listed Tier-C + `Fwlib64.dll` P/Invoke). - `docs/Driver.FOCAS.Cli.md` drops the Tier-C architecture-note section. - `docs/v2/implementation/focas-isolation-plan.md` marked historical — the plan it documents was executed then superseded by the wire client. - `docs/v2/v2-release-readiness.md` re-audited 2026-04-24. Phase 5 driver complement closed. FOCAS change-log entry added. Co-Authored-By: Claude Opus 4.7 (1M context) --- ZB.MOM.WW.OtOpcUa.slnx | 5 +- docs/Driver.FOCAS.Cli.md | 38 +- docs/drivers/FOCAS-Test-Fixture.md | 209 +- docs/drivers/FOCAS.md | 238 ++ docs/drivers/README.md | 5 +- docs/v2/focas-version-matrix.md | 4 +- .../v2/implementation/focas-isolation-plan.md | 21 +- docs/v2/v2-release-readiness.md | 89 +- scripts/e2e/e2e-config.sample.json | 17 +- scripts/e2e/test-focas.ps1 | 218 +- scripts/install/Install-FocasHost.ps1 | 108 - scripts/integration/README.md | 87 + scripts/integration/run-focas.ps1 | 123 + .../Commands/ProbeCommand.cs | 10 +- .../FocasCommandBase.cs | 7 +- .../Program.cs | 8 +- .../ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli.csproj | 3 + .../Backend/FakeFocasBackend.cs | 122 - .../Backend/IFocasBackend.cs | 24 - .../Backend/UnconfiguredFocasBackend.cs | 37 - .../Ipc/FwlibFrameHandler.cs | 111 - .../Ipc/IFrameHandler.cs | 31 - .../Ipc/PipeAcl.cs | 39 - .../Ipc/PipeServer.cs | 152 - .../Ipc/StubFrameHandler.cs | 41 - .../Program.cs | 72 - .../Stability/PostMortemMmf.cs | 133 - ...ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.csproj | 40 - .../Contracts/Addresses.cs | 39 - .../Contracts/Framing.cs | 57 - .../Contracts/Hello.cs | 63 - .../Contracts/Probe.cs | 47 - .../Contracts/ReadWrite.cs | 85 - .../Contracts/Session.cs | 31 - .../Contracts/Subscriptions.cs | 61 - .../FrameReader.cs | 67 - .../FrameWriter.cs | 56 - ....MOM.WW.OtOpcUa.Driver.FOCAS.Shared.csproj | 23 - .../FocasDriver.cs | 576 +++- .../FocasDriverFactoryExtensions.cs | 69 +- .../FocasDriverOptions.cs | 71 + .../FwlibFocasClient.cs | 328 -- .../FwlibNative.cs | 190 -- .../IFocasClient.cs | 245 +- .../Ipc/FocasIpcClient.cs | 120 - .../Ipc/IpcFocasClient.cs | 199 -- .../Supervisor/Backoff.cs | 30 - .../Supervisor/CircuitBreaker.cs | 69 - .../Supervisor/FocasHostSupervisor.cs | 159 - .../Supervisor/HeartbeatMonitor.cs | 29 - .../Supervisor/IHostProcessLauncher.cs | 32 - .../Supervisor/PostMortemReader.cs | 57 - .../Supervisor/ProcessHostLauncher.cs | 113 - .../Wire/FocasConstants.cs | 120 + .../Wire/FocasWireClient.cs | 883 ++++++ .../Wire/FocasWireException.cs | 51 + .../Wire/FocasWireModels.cs | 131 + .../Wire/FocasWireProtocol.cs | 250 ++ .../Wire/WireFocasClient.cs | 333 ++ .../ZB.MOM.WW.OtOpcUa.Driver.FOCAS.csproj | 13 +- .../FwlibFrameHandlerTests.cs | 200 -- .../IpcHandshakeIntegrationTests.cs | 157 - .../PostMortemMmfTests.cs | 86 - ....WW.OtOpcUa.Driver.FOCAS.Host.Tests.csproj | 33 - .../Docker/README.md | 94 + .../Docker/docker-compose.yml | 34 + .../Docker/focas-mock/Dockerfile | 13 + .../Docker/focas-mock/LICENSE | 21 + .../Docker/focas-mock/README.md | 191 ++ .../Docker/focas-mock/VENDORED.md | 45 + .../Docker/focas-mock/pyproject.toml | 24 + .../src/focas_mock.egg-info/PKG-INFO | 185 ++ .../src/focas_mock.egg-info/SOURCES.txt | 25 + .../focas_mock.egg-info/dependency_links.txt | 1 + .../src/focas_mock.egg-info/entry_points.txt | 2 + .../src/focas_mock.egg-info/requires.txt | 1 + .../src/focas_mock.egg-info/top_level.txt | 1 + .../focas-mock/src/focas_mock/__init__.py | 5 + .../focas_mock/builtin_profiles/FWLIB64.json | 2677 +++++++++++++++++ .../builtin_profiles/fwlib0DN64.json | 1936 ++++++++++++ .../builtin_profiles/fwlib0iD64.json | 1936 ++++++++++++ .../builtin_profiles/fwlib30i64.json | 2407 +++++++++++++++ .../builtin_profiles/fwlibNCG64.json | 2407 +++++++++++++++ .../focas_mock/builtin_profiles/fwlibe64.json | 1669 ++++++++++ .../Docker/focas-mock/src/focas_mock/cli.py | 80 + .../focas-mock/src/focas_mock/constants.py | 120 + .../focas-mock/src/focas_mock/data_store.py | 59 + .../focas-mock/src/focas_mock/defaults.py | 142 + .../src/focas_mock/export_introspection.py | 82 + .../focas-mock/src/focas_mock/profiles.py | 41 + .../focas-mock/src/focas_mock/server.py | 1018 +++++++ .../FocasSimFixture.cs | 192 ++ .../Series/WireBackendCoverageTests.cs | 263 ++ .../Series/WireBackendTests.cs | 280 ++ ...pcUa.Driver.FOCAS.IntegrationTests.csproj} | 10 +- .../ContractRoundTripTests.cs | 280 -- .../FramingTests.cs | 107 - .../FakeFocasClient.cs | 46 + .../FocasDriverFactoryExtensionsTests.cs | 162 - .../FocasScaffoldingTests.cs | 6 +- .../FwlibNativeHelperTests.cs | 106 - .../IpcFocasClientTests.cs | 265 -- .../IpcLoopback.cs | 72 - .../PostMortemReaderCompatibilityTests.cs | 84 - .../SupervisorTests.cs | 249 -- 105 files changed, 19530 insertions(+), 4873 deletions(-) create mode 100644 docs/drivers/FOCAS.md delete mode 100644 scripts/install/Install-FocasHost.ps1 create mode 100644 scripts/integration/README.md create mode 100644 scripts/integration/run-focas.ps1 delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Backend/FakeFocasBackend.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Backend/IFocasBackend.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Backend/UnconfiguredFocasBackend.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/FwlibFrameHandler.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/IFrameHandler.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/PipeAcl.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/PipeServer.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/StubFrameHandler.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Program.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Stability/PostMortemMmf.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.csproj delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Addresses.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Framing.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Hello.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Probe.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/ReadWrite.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Session.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Subscriptions.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/FrameReader.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/FrameWriter.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.csproj delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FwlibFocasClient.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FwlibNative.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Ipc/FocasIpcClient.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Ipc/IpcFocasClient.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/Backoff.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/CircuitBreaker.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/FocasHostSupervisor.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/HeartbeatMonitor.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/IHostProcessLauncher.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/PostMortemReader.cs delete mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/ProcessHostLauncher.cs create mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasConstants.cs create mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs create mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireException.cs create mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireModels.cs create mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireProtocol.cs create mode 100644 src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/WireFocasClient.cs delete mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/FwlibFrameHandlerTests.cs delete mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/IpcHandshakeIntegrationTests.cs delete mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/PostMortemMmfTests.cs delete mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests.csproj create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/README.md create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/docker-compose.yml create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/Dockerfile create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/LICENSE create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/README.md create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/VENDORED.md create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/pyproject.toml create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/PKG-INFO create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/SOURCES.txt create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/dependency_links.txt create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/entry_points.txt create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/requires.txt create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/top_level.txt create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/__init__.py create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/FWLIB64.json create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlib0DN64.json create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlib0iD64.json create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlib30i64.json create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlibNCG64.json create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlibe64.json create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/cli.py create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/constants.py create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/data_store.py create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/defaults.py create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/export_introspection.py create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/profiles.py create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/server.py create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/FocasSimFixture.cs create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendCoverageTests.cs create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendTests.cs rename tests/{ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests.csproj => ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests.csproj} (66%) delete mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests/ContractRoundTripTests.cs delete mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests/FramingTests.cs delete mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasDriverFactoryExtensionsTests.cs delete mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FwlibNativeHelperTests.cs delete mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/IpcFocasClientTests.cs delete mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/IpcLoopback.cs delete mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/PostMortemReaderCompatibilityTests.cs delete mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/SupervisorTests.cs diff --git a/ZB.MOM.WW.OtOpcUa.slnx b/ZB.MOM.WW.OtOpcUa.slnx index 61939a2..02b892c 100644 --- a/ZB.MOM.WW.OtOpcUa.slnx +++ b/ZB.MOM.WW.OtOpcUa.slnx @@ -18,8 +18,6 @@ - - @@ -65,8 +63,7 @@ - - + diff --git a/docs/Driver.FOCAS.Cli.md b/docs/Driver.FOCAS.Cli.md index e558a53..d5a258f 100644 --- a/docs/Driver.FOCAS.Cli.md +++ b/docs/Driver.FOCAS.Cli.md @@ -5,27 +5,22 @@ protocol. Uses the **same** `FocasDriver` the OtOpcUa server does — PMC R/G/F file registers, axis bits, parameters, and macro variables — all through `FocasAddressParser` syntax. -Sixth of the driver test-client CLIs, added alongside the Tier-C isolation -work tracked in task #220. +Sixth of the driver test-client CLIs. ## Architecture note -FOCAS is a Tier-C driver: `Fwlib32.dll` is a proprietary 32-bit Fanuc library -with a documented habit of crashing its hosting process on network errors. -The target runtime deployment splits the driver into an in-process -`FocasProxyDriver` (.NET 10 x64) and an out-of-process `Driver.FOCAS.Host` -(.NET 4.8 x86 Windows service) that owns the DLL — see -[v2/implementation/focas-isolation-plan.md](v2/implementation/focas-isolation-plan.md) -and -[v2/implementation/phase-6-1-resilience-and-observability.md](v2/implementation/phase-6-1-resilience-and-observability.md) -for topology + supervisor / respawn / back-pressure design. +FOCAS is an in-process driver. The pure-managed `WireFocasClient` +speaks the FOCAS2 binary protocol directly over TCP:8193, removing the +Tier-C process-isolation split that the historical P/Invoke + out-of- +process Host arrangement required. The CLI loads `FocasDriver` with +`WireFocasClientFactory` and talks to the CNC without any native +components. -The CLI skips the proxy and loads `FocasDriver` directly (via -`FwlibFocasClientFactory`, which P/Invokes `Fwlib32.dll` in the CLI's own -process). There is **no public simulator** for FOCAS; a meaningful probe -requires a real CNC + a licensed `Fwlib32.dll` on `PATH` (or next to the -executable). On a dev box without the DLL, every wire call surfaces as -`BadCommunicationError` — still useful as a "CLI wire-up is correct" signal. +A dev-friendly mock is available — start +`tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/docker-compose.yml` +and point `--cnc-host` at `localhost` for end-to-end CLI exercises +without a real CNC. See +[drivers/FOCAS-Test-Fixture.md](drivers/FOCAS-Test-Fixture.md). ## Build + run @@ -152,7 +147,8 @@ fails. **"Why did this macro flip?"** → `subscribe` to the macro, let the operator reproduce the cycle, watch the HH:mm:ss.fff timeline. -**"Is the Fwlib32 DLL wired up?"** → `probe` against any host. A -`DllNotFoundException` surfacing as `BadCommunicationError` with a -matching `Last error` line means the driver is loading but the DLL is -missing; anything else means a transport-layer problem. +**"Can I reach the CNC on TCP:8193?"** → `probe` against any host. A +`BadCommunicationError` means the wire client couldn't open a socket +(firewall / wrong host / FOCAS Ethernet option unlicensed on the CNC). +`BadDeviceFailure` after a successful connect means the CNC is rejecting +the session setup — check the CNC's FOCAS option and password settings. diff --git a/docs/drivers/FOCAS-Test-Fixture.md b/docs/drivers/FOCAS-Test-Fixture.md index 3ecf97d..c99919f 100644 --- a/docs/drivers/FOCAS-Test-Fixture.md +++ b/docs/drivers/FOCAS-Test-Fixture.md @@ -2,132 +2,149 @@ Coverage map + gap inventory for the FANUC FOCAS2 CNC driver. -**TL;DR: there is no integration fixture.** Every test uses a -`FakeFocasClient` injected via `IFocasClientFactory`. Fanuc's FOCAS library -(`Fwlib32.dll`) is closed-source proprietary with no public simulator; -CNC-side behavior is trusted from field deployments. +**Status:** as of 2026-04-24, OtOpcUa speaks FOCAS2 directly over TCP +via the pure-managed [`Focas.Wire`](https://github.com/Ladder99/focas-mock/tree/main/dotnet/Focas.Wire) +client. Integration tests run the managed driver end-to-end against the +vendored `focas-mock` Python server (at +[`tests/.../Docker/focas-mock/`](../../tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/VENDORED.md)) +whose native FOCAS Ethernet responder is verified PDU-by-PDU against the +real `fwlibe64.dll`. -## What the fixture is +No shim DLL, no P/Invoke, no licensed binary — any dev box or CI runner +with Docker can run the full fixture end-to-end. -Nothing at the integration layer. -`tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/` is unit-only. The driver ships -as Tier C (process-isolated) per `docs/v2/driver-stability.md` because the -FANUC DLL has known crash modes; tests can't replicate those in-process. +Hardware validation against a real CNC is still useful to catch +series-specific firmware quirks (see [§ Hardware-only gaps](#hardware-only-gaps)) +but the mock's wire responder covers every FOCAS call OtOpcUa issues. -## What it actually covers (unit only) +## What the fixture covers -- `FocasCapabilityTests` — data-type mapping (PMC bit / word / float, - macro variable types, parameter types) -- `FocasCapabilityMatrixTests` — per-CNC-series range validation (macro - / parameter / PMC letter + number) across 16i / 0i-D / 0i-F / - 30i / PowerMotion. See [`docs/v2/focas-version-matrix.md`](../v2/focas-version-matrix.md) - for the authoritative matrix. 46 theory cases lock every documented - range boundary — widening a range without updating the doc fails a - test. -- `FocasReadWriteTests` — read + write against the fake, FOCAS native status - → OPC UA StatusCode mapping +### Unit layer (no container required) + +`tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/` uses `FakeFocasClient` +injected via `IFocasClientFactory`: + +- `FocasCapabilityTests` — data-type mapping (PMC bit / byte / word / + long / float / double, macro variable types, parameter types) +- `FocasCapabilityMatrixTests` — per-CNC-series range validation across + 16i / 0i-D / 0i-F / 30i / Power Motion, 46 theory cases locking every + documented range boundary. See + [`docs/v2/focas-version-matrix.md`](../v2/focas-version-matrix.md). +- `FocasReadWriteTests` — read / write contract against the fake, FOCAS + native status → OPC UA `StatusCode` mapping - `FocasScaffoldingTests` — `IDriver` lifecycle + multi-device routing -- `FocasPmcBitRmwTests` — PMC bit read-modify-write synchronization (per-byte - `SemaphoreSlim`, mirrors the AB / Modbus pattern from #181) -- `FwlibNativeHelperTests` — `Focas32.dll` → `Fwlib32.dll` bridge validation - + P/Invoke signature validation +- `FocasPmcBitRmwTests` — PMC bit read-modify-write synchronisation +- `FocasAlarmProjectionTests` — raise / clear diffing, severity mapping +- `FocasHandleRecycleTests` — proactive session recycle cadence Capability surfaces whose contract is verified: `IDriver`, `IReadable`, -`IWritable`, `ITagDiscovery`, `ISubscribable`, `IHostConnectivityProbe`, -`IPerCallHostResolver`. +`ITagDiscovery`, `ISubscribable`, `IHostConnectivityProbe`, +`IPerCallHostResolver`, `IAlarmSource`. `IWritable` intentionally +returns `BadNotWritable` — OtOpcUa is read-only against FOCAS. Pre-flight validation runs in `FocasDriver.InitializeAsync` — configs referencing out-of-range addresses fail at load time with a diagnostic -message naming the CNC series + documented limit. This closes the -cheap half of the hardware-free stability gap; Tier-C process -isolation (task #220) closes the expensive half — see -[`docs/v2/implementation/focas-isolation-plan.md`](../v2/implementation/focas-isolation-plan.md). +message naming the CNC series + documented limit. -## What it does NOT cover +### Integration layer (mock only, no CNC, no shim) -### 1. FOCAS wire traffic +`tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/` drives the +managed `FocasDriver` end-to-end. A single gate: -No FOCAS TCP frame is sent. `Fwlib32.dll`'s TCP-to-FANUC-gateway exchange is -closed-source; the driver trusts the P/Invoke layer per #193. Real CNC -correctness is trusted from field deployments. +**Docker compose up** — tests skip when the TCP probe to +`localhost:8193` fails with a pointer to the compose command. -### 2. Alarm / parameter-change callbacks +When the mock is up, `WireFocasClient` dials it over TCP exactly like a +real CNC, and the mock's native FOCAS Ethernet responder replies with +binary PDUs against the documented command IDs. Covered assertions: -FOCAS has no push model — the driver polls via the shared `PollGroupEngine`. -There are no CNC-initiated callbacks to test; the absence is by design. +- Session open / close (`cnc_allclibhndl3` + `cnc_freelibhndl`) +- Parameter read-back after `mock_patch` seed → `cnc_rdparam` +- Macro read-back after seed → `cnc_rdmacro` (scaled-decimal + translation verified) +- PMC range read after seed → `pmc_rdpmcrng` +- `IAlarmSource` raise + clear transitions after `mock_patch` + alarm-list changes → `cnc_rdalmmsg2` +- Fixed-tree bootstrap: identity / axes / spindle / program / timers / + servo meters populate via `cnc_sysinfo`, `cnc_rdaxisname`, + `cnc_rdspdlname`, `cnc_rddynamic2`, `cnc_exeprgname2`, + `cnc_rdblkcount`, `cnc_rdopmode`, `cnc_rdsvmeter`, `cnc_rdspload`, + `cnc_rdspmaxrpm`, `cnc_rdtimer` +- Per-series profile selection via `mock_load_profile` — tests can + pin one profile and assert series-gated capability suppression -### 3. Macro / ladder variable types +### E2E script (CLI) -FANUC has CNC-specific extensions (macro variables `#100-#999`, system -variables `#1000-#5000`, PMC timers / counters / keep-relays) whose -per-address semantics differ across 0i-F / 30i / 31i / 32i Series. Driver -covers the common address shapes; per-model quirks are not stressed. +`scripts/e2e/test-focas.ps1` drives the Client.CLI against a running +OtOpcUa server. Accepts: -### 4. Model-specific behavior +- `-CncHost` / `-CncPort` for real hardware +- `-ProfileName ` for the Docker mock +- `-Series ` for per-series matrix mode +- `-HandleLeakCycles ` for handle-leak stress -- Alarm retention across power cycles (model-specific CNC behavior) -- Parameter range enforcement (CNC rejects out-of-range writes) -- MTB (machine tool builder) custom screens that expose non-standard data +## Hardware-only gaps -### 5. Tier-C process isolation — architecture shipped, Fwlib32 integration hardware-gated +The mock has parity with the real `fwlibe64.dll` for the calls OtOpcUa +issues, but a real CNC can still surface things a reference +implementation can't: -The Tier-C architecture is now in place as of PRs #169–#173 (FOCAS -PR A–E, task #220): +1. **Series-specific firmware quirks** — alarm retention across power + cycles, parameter range enforcement by the CNC (not the driver), + MTB custom screens, series-specific option bits. Each series has + documented behaviours that only a bench CNC exercises. +2. **Wire-level stress** — burst reads, concurrent device writes, + network-partition recovery under load. The mock handles these + correctly but production behaviour is the source of truth. +3. **Transient operational states** — alarm floods, emergency-stop + transitions, power-on resync. These are easy to stub but hard to + cover comprehensively in the mock. -- `Driver.FOCAS.Shared` carries MessagePack IPC contracts -- `Driver.FOCAS.Host` (.NET 4.8 x86 Windows service via NSSM) accepts - a connection on a strictly-ACL'd named pipe + dispatches frames to - an `IFocasBackend` -- `Driver.FOCAS.Ipc.IpcFocasClient` implements the `IFocasClient` DI - seam by forwarding over IPC — swap the DI registration and the - driver runs Tier-C with zero other changes -- `Driver.FOCAS.Supervisor.FocasHostSupervisor` owns the spawn + - heartbeat + respawn + 3-in-5min crash-loop breaker + sticky alert -- `Driver.FOCAS.Host.Stability.PostMortemMmf` ↔ - `Driver.FOCAS.Supervisor.PostMortemReader` — ring-buffer of the - last ~1000 IPC operations survives a Host crash +Track the close-out under task #54 (live-CNC smoke). When the rig +lands, the hardware path runs alongside the mock path; the mock +stays as the CI quality gate. -The one remaining gap is the production `FwlibHostedBackend`: an -`IFocasBackend` implementation that wraps the licensed -`Fwlib32.dll` P/Invoke. That's hardware-gated on task #222 — we -need a CNC on the bench (or the licensed FANUC developer kit DLL -with a test harness) to validate it. Until then, the Host ships -`FakeFocasBackend` + `UnconfiguredFocasBackend`. Setting -`OTOPCUA_FOCAS_BACKEND=fake` lets operators smoke-test the whole -Tier-C pipeline end-to-end without any CNC. +## When to trust each layer -## When to trust FOCAS tests, when to reach for a rig +| Question | Unit | Integration (mock) | Real CNC | +| --- | :---: | :---: | :---: | +| "Does PMC address `R100.3` route to the right bit?" | ✅ | ✅ | ✅ | +| "Does the Fanuc status → OPC UA StatusCode map cover every documented code?" | ✅ (contract) | ✅ | ✅ | +| "Does `FocasDriver.ReadAsync` correctly decode a seeded parameter?" | no | ✅ | ✅ | +| "Does `IAlarmSource` fire raise + clear events?" | ✅ (Fake) | ✅ (wire) | ✅ | +| "Does a real read against a 30i Series return correct bytes?" | no | ✅ (via profile) | ✅ (required) | +| "Do series-specific firmware quirks behave as documented?" | no | no | ✅ (required) | +| "Does the driver survive real network partitions?" | no | partial (socket kill) | ✅ (required) | -| Question | Unit tests | Real CNC | -| --- | --- | --- | -| "Does PMC address `R100.3` route to the right bit?" | yes | yes | -| "Does the FANUC status → OPC UA StatusCode map cover every documented code?" | yes (contract) | yes | -| "Does a real read against a 30i Series return correct bytes?" | no | yes (required) | -| "Does `Fwlib32.dll` crash on concurrent reads?" | no | yes (stress) | -| "Do macro variables round-trip across power cycles?" | no | yes (required) | +## Running the integration fixture -## Follow-up candidates +```powershell +# 1) Start the mock on a chosen profile. +docker compose -f tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/docker-compose.yml up -d -1. **Nothing public** — Fanuc's FOCAS Developer Kit ships an emulator DLL - but it's under NDA + tied to licensed dev-kit installations; can't - redistribute for CI. -2. **Lab rig** — used FANUC 0i-F simulator controller (or a retired machine - tool) on a dedicated network; only path that covers real CNC behavior. -3. **Process isolation first** — before trusting FOCAS in production at - scale, shipping the Tier-C out-of-process Host architecture (similar to - Galaxy) is higher value than a CI simulator. +# 2) Run the tests. No shim build, no DLL copy — the driver dials the mock directly. +dotnet test tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/ +``` + +Or use `scripts/integration/run-focas.ps1` which wraps compose up / test +/ compose down and accepts `-Profile ` to pin a per-series run. ## Key fixture / config files +- `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/` + — vendored `focas-mock` Python source + Dockerfile +- `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/docker-compose.yml` + — per-series compose profiles +- `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/FocasSimFixture.cs` + — collection fixture + mock admin API client +- `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/FixedTreePopulatesTests.cs` + — fixed-tree end-to-end tests +- `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendTests.cs` + — pure-wire-backend end-to-end tests - `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FakeFocasClient.cs` — - in-process fake implementing `IFocasClient` -- `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasCapabilityMatrixTests.cs` - — parameterized theories locking the per-series matrix -- `src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs` — ctor takes - `IFocasClientFactory` + in-process unit fake +- `src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/WireFocasClient.cs` — the + managed wire client backing production deployments - `src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasCapabilityMatrix.cs` — - per-CNC-series range validator (the matrix the doc describes) + per-series range validator - `docs/v2/focas-version-matrix.md` — authoritative range reference -- `docs/v2/implementation/focas-isolation-plan.md` — Tier-C isolation - plan (task #220) -- `docs/v2/driver-stability.md` — Tier C scope + process-isolation rationale diff --git a/docs/drivers/FOCAS.md b/docs/drivers/FOCAS.md new file mode 100644 index 0000000..1fa1dbf --- /dev/null +++ b/docs/drivers/FOCAS.md @@ -0,0 +1,238 @@ +# FOCAS Driver + +Getting-started guide for the FANUC FOCAS2 driver. This is the short path — for +the exhaustive per-node mapping read [`docs/v2/driver-specs.md §7`](../v2/driver-specs.md), +for deployment details read [`docs/v2/focas-deployment.md`](../v2/focas-deployment.md), +for the test-harness map read [FOCAS-Test-Fixture.md](FOCAS-Test-Fixture.md). + +## What it talks to + +FANUC CNCs (0i-D / 0i-F / 0i-MF / 0i-TF / 16i / 30i / 31i / 32i / Power Motion i) +over the proprietary FOCAS2 protocol on TCP port 8193. The wire is spoken +directly by the pure-managed [`Focas.Wire`](https://github.com/Ladder99/focas-mock) +client — no Fwlib64.dll, no P/Invoke, no out-of-process isolation needed. + +OtOpcUa is **read-only** against FOCAS; all reads go over the native wire +protocol using the documented command IDs. Writes return +`BadNotWritable` by design. + +## Project split + +| Project | Target | Role | +|---------|--------|------| +| `src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/` | net10.0 | In-process driver — hosts `WireFocasClient` which speaks FOCAS2 over TCP directly | + +Previous `Driver.FOCAS.Host` / `Driver.FOCAS.Shared` Tier-C split has been +retired — the managed wire client removes the native-crash blast radius +that justified the out-of-process service. + +## Minimum deployment + +Register the driver instance in the main server's `appsettings.json`. No +separate service, no DLL deployment, no shared-secret handshake: + +```jsonc +"Drivers": { + "focas-cnc-1": { + "Type": "FOCAS", + "Config": { + "Backend": "wire", + "Devices": [ + { "HostAddress": "focas://10.20.30.40:8193", "Series": "ThirtyOne_i" } + ], + "Tags": [ + { "Name": "Mode", "DeviceHostAddress": "focas://10.20.30.40:8193", + "Address": "PARAM:3402", "DataType": "Int32", "Writable": false }, + { "Name": "SpndLoad", "DeviceHostAddress": "focas://10.20.30.40:8193", + "Address": "MACRO:500", "DataType": "Float64", "Writable": false } + ] + } + } +} +``` + +The main server opens two TCP sockets per configured device and speaks the +FOCAS2 binary protocol directly. No local privileged components, no +platform bitness constraint — the driver runs on every host OtOpcUa runs +on. + +## Address forms + +| Form | Example | Meaning | +|------|---------|---------| +| `X0.0` / `R100` / `R100.3` | PMC bit or byte | Letter + number; optional `.bit` for bit access | +| `PARAM:1815` / `PARAM:1815/0` | CNC parameter | Number + optional axis index | +| `MACRO:500` | Custom macro variable | System / user macro variable number | + +Addresses are validated against the per-device `Series` at `InitializeAsync` — +a config referencing a number outside the documented range for that series +fails at load time with an error message naming the limit. See +[`docs/v2/focas-version-matrix.md`](../v2/focas-version-matrix.md) for the +authoritative range table. + +## Backend selection + +The driver picks its client from `Config.Backend`: + +| Value | Client | Use it for | +|-------|--------|------------| +| `wire` (default) | `WireFocasClient` | Production — pure-managed FOCAS2 over TCP | +| `unimplemented` / `none` / `stub` | `UnimplementedFocasClientFactory` | Scaffolding a DriverInstance row before the CNC endpoint is reachable | + +Previous backends (`fwlib`, `fwlib32`, `ipc`) have been retired along +with `Driver.FOCAS.Host` and the Fwlib P/Invoke path. Configs that still +reference them will throw at startup with a message pointing here. + +## Capability surface + +| Capability | Wire path | Notes | +|------------|-----------|-------| +| `IReadable` | `ReadAsync` → `cnc_rdpmcrng` / `cnc_rdparam` / `cnc_rdmacro` | One TCP request/response per read; `Focas.Wire` serializes requests on socket 2 internally | +| `IWritable` | returns `BadNotWritable` | OtOpcUa is read-only against FOCAS by design — no `cnc_wrparam` / `pmc_wrpmcrng` / `cnc_wrmacro` path is implemented | +| `ITagDiscovery` | `DiscoverAsync` | Emits `FOCAS/{device}/{tag}` folders per configured device | +| `ISubscribable` | polled via shared `PollGroupEngine` | FOCAS has no push model — subscriptions turn into per-tag polling groups | +| `IHostConnectivityProbe` | periodic `cnc_rdcncstat` | Probe cadence is `Probe.Interval`; transitions fire `OnHostStatusChanged` | +| `IPerCallHostResolver` | lookup in `_tagsByName` | Each call routes to the device of the referenced tag | +| `IAlarmSource` | polled `cnc_rdalmmsg2` via `FocasAlarmProjection` | Opt-in — set `AlarmProjection.Enabled=true`; diffs `(AlarmNumber, Type)` between ticks | + +Ack is a no-op — FANUC clears alarms on its own once the underlying condition +resolves, so `AcknowledgeAsync` swallows the batch rather than surfacing +`BadNotSupported`. + +## Fixed node tree + +Enable a pre-defined hierarchy of CNC nodes populated automatically from +`cnc_sysinfo` + `cnc_rdaxisname` + `cnc_rddynamic2` + related FWLIB calls, +so operators get an out-of-the-box view of identity / axes / program / +timers without declaring per-address tags. + +```jsonc +"Config": { + "Devices": [ ... ], + "Tags": [ ... ], + "FixedTree": { + "Enabled": true, + "PollInterval": "00:00:00.250", // fast — per-axis dynamic reads + "ProgramPollInterval": "00:00:01", // medium — program + mode changes + "TimerPollInterval": "00:00:30" // slow — cumulative counters + } +} +``` + +What gets populated (all under `FOCAS/{deviceHostAddress}/`): + +| Subtree | Nodes | Source call | +|---------|-------|-------------| +| `Identity/` | `SeriesNumber`, `Version`, `MaxAxes`, `CncType`, `MtType`, `AxisCount` | `cnc_sysinfo` once at bootstrap | +| `Axes/{name}/` | `AbsolutePosition`, `MachinePosition`, `RelativePosition`, `DistanceToGo`, `ServoLoad` — one folder per discovered axis | `cnc_rdaxisname` once + `cnc_rddynamic2` + `cnc_rdsvmeter` per tick | +| `Axes/FeedRate/Actual`, `Axes/SpindleSpeed/Actual` | Current feed + spindle RPM | `cnc_rddynamic2` | +| `Spindle/{name}/` | `Load` (percentage), `MaxRpm` — one folder per discovered spindle | `cnc_rdspdlname` once + `cnc_rdspload` + `cnc_rdspmaxrpm` | +| `Program/` | `Name` (filename), `ONumber`, `Number`, `MainNumber`, `Sequence`, `BlockCount` | `cnc_exeprgname2` + `cnc_rdblkcount` + cached `cnc_rddynamic2` | +| `OperationMode/` | `Mode` (int), `ModeText` ("AUTO", "MDI", "EDIT", …) | `cnc_rdopmode` | +| `Timers/` | `PowerOnSeconds`, `OperatingSeconds`, `CuttingSeconds`, `CycleSeconds` | `cnc_rdtimer` × 4 | + +### Per-series node suppression + +The driver probes each optional call once at bootstrap. If the target CNC +returns `EW_FUNC` / `EW_NOOPT` / `EW_VERSION` on the wire, the +corresponding subtree is **not emitted** — the operator doesn't see nodes +that will only ever return `BadDeviceFailure`. Capability suppression +covers `Spindle/`, `Program/` + `OperationMode/`, `Timers/`, and +per-axis `ServoLoad` independently. Identity + `Axes/*` position reads +(which every Fanuc CNC supports) are always emitted. + +Position values are scaled integers (matching FOCAS's convention). The +managed side exposes them as `Float64` OPC UA nodes; a future +`cnc_getfigure` integration will add per-axis decimal scaling. Until +then, treat the raw integer as the value the CNC reports and scale on +the client side if decimal precision matters. + +**Still user-authored**: `PARAM:6711`, `MACRO:500`, `R100` etc. — specific +numbers whose meaning is MTB-specific. Those go under the device folder +alongside the fixed subtree. + +## Alarm projection + +Alarm surfacing is **disabled by default** because the polling cost is wasted +on sites that don't consume CNC alarms. Opt in per driver instance: + +```jsonc +"Config": { + "Devices": [ ... ], + "Tags": [ ... ], + "AlarmProjection": { + "Enabled": true, + "PollInterval": "00:00:02" + } +} +``` + +Every alarm transition fires `OnAlarmEvent` with: + +- `SourceNodeId` = the device host address (FOCAS has no per-node alarm model; + the CNC exposes a single flat active-alarm list per session) +- `ConditionId` = `"{host}#{Type}:{AlarmNumber}"` +- `AlarmType` = projected from FANUC's `ALM_TYPE_*` (e.g. `Overtravel`, `Servo`, + `Parameter`, `MacroAlarm`) +- `Severity` = Overtravel / Servo / PulseCode → `Critical`; Parameter / Macro + → `Medium`; everything else → `High` + +Cleared alarms fire a second event with `" (cleared)"` appended to the message +so downstream consumers can ignore the clear if they only care about raises. + +## Handle recycling + +FANUC CNCs have a finite FWLIB handle pool (~5–10 concurrent connections) and +certain series have documented handle-leak bugs that manifest after long uptime. +The driver can proactively close + reopen each device's session on a cadence to +release its slot back to the pool: + +```jsonc +"Config": { + "Devices": [ ... ], + "HandleRecycle": { + "Enabled": true, + "Interval": "01:00:00" + } +} +``` + +Disabled by default — a healthy CNC + driver doesn't need it. Enable when field +experience shows handle exhaustion. Typical tuning: 30 min for sites running +multiple OtOpcUa instances against the same CNC (they share the pool); 6 h for a +single-client deployment. Reads / writes during recycle simply wait for the +reconnect rather than failing — worst case, an operator sees a brief read +latency spike once per cadence. + +## Testing + +- **Unit tests** — `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/` cover the + driver surface via `FakeFocasClient`. Includes the alarm-projection raise / + clear diffing tests. +- **Integration tests** — `tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/` + hold the Docker simulator scaffold (Stream B / C of the simulator plan — + `docs/v2/implementation/focas-simulator-plan.md`). +- **E2E script** — `scripts/e2e/test-focas.ps1` stages Host + Proxy + a real + CNC (or the simulator) and exercises connect → read → write → subscribe + round-trips. See [`docs/drivers/FOCAS-Test-Fixture.md`](FOCAS-Test-Fixture.md) + for the coverage map. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +|---------|--------------|-----| +| `BadCommunicationError` on every read | CNC unreachable on TCP:8193 | Check firewall / LAN reachability; FOCAS Ethernet option must be licensed on the CNC side | +| Every read returns `BadNotWritable` on writes | Expected — OtOpcUa is read-only against FOCAS | If you actually need writes, open a feature request — the driver's managed wire client doesn't expose the write commands | +| `BadOutOfRange` on reads for a macro/parameter | Config address outside the declared `Series` range | Check `docs/v2/focas-version-matrix.md` — either fix the address or widen the `Series` | +| Alarm events never fire | `AlarmProjection.Enabled` left at default (false) | Set it to `true` in the driver config | + +## Further reading + +- [`docs/v2/driver-specs.md §7`](../v2/driver-specs.md) — full OPC UA node + mapping, pre-defined tag set, per-API notes +- [`docs/v2/focas-version-matrix.md`](../v2/focas-version-matrix.md) — + per-series macro / parameter / PMC range table +- [`docs/v2/implementation/focas-wire-protocol.md`](../v2/implementation/focas-wire-protocol.md) + — captured FOCAS2 wire semantics (magic prefix, handshake, command-id table) +- [upstream `Focas.Wire`](https://github.com/Ladder99/focas-mock/tree/main/dotnet/Focas.Wire) + — the managed client implementation OtOpcUa consumes as a NuGet dependency diff --git a/docs/drivers/README.md b/docs/drivers/README.md index 74edd2b..3c12e93 100644 --- a/docs/drivers/README.md +++ b/docs/drivers/README.md @@ -26,7 +26,7 @@ Driver type metadata is registered at startup in `DriverTypeRegistry` (`src/ZB.M | AB CIP | `Driver.AbCip` | A | libplctag CIP | IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IHostConnectivityProbe, IPerCallHostResolver, IAlarmSource | ControlLogix / CompactLogix. Tag discovery uses the `@tags` walker to enumerate controller-scoped + program-scoped symbols; UDT member resolution via the UDT template reader | | AB Legacy | `Driver.AbLegacy` | A | libplctag PCCC | IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IHostConnectivityProbe, IPerCallHostResolver | SLC 500 / MicroLogix. File-based addressing (`N7:0`, `F8:0`) — no symbol table, tag list is user-authored in the config DB | | TwinCAT | `Driver.TwinCAT` | B | Beckhoff `TwinCAT.Ads` (`TcAdsClient`) | IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IHostConnectivityProbe, IPerCallHostResolver | The only native-notification driver outside Galaxy — ADS delivers `ValueChangedCallback` events the driver forwards straight to `ISubscribable.OnDataChange` without polling. Symbol tree uploaded via `SymbolLoaderFactory` | -| FOCAS | `Driver.FOCAS` | C | FANUC FOCAS2 (`Fwlib32.dll` P/Invoke) | IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IHostConnectivityProbe, IPerCallHostResolver | Tier C — FOCAS DLL has crash modes that warrant process isolation. CNC-shaped data model (axes, spindle, PMC, macros, alarms) not a flat tag map | +| [FOCAS](FOCAS.md) | `Driver.FOCAS` | A | Pure-managed `FocasWireClient` — FOCAS/2 Ethernet binary protocol on TCP:8193, inlined into the driver assembly | IDriver, ITagDiscovery, IReadable, ISubscribable, IHostConnectivityProbe, IPerCallHostResolver, IAlarmSource | Read-only by design (WriteAsync returns `BadNotWritable`). CNC-shaped data model (axes, spindle, PMC, macros, alarms) not a flat tag map. Previously Tier-C (Host + P/Invoke + shim DLL); retired in the 2026-04-24 migration when the managed wire client landed | | OPC UA Client | `Driver.OpcUaClient` | B | OPCFoundation `Opc.Ua.Client` | IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IAlarmSource, IHistoryProvider, IHostConnectivityProbe | Gateway/aggregation driver. Opens a single `Session` against a remote OPC UA server and re-exposes its address space. Owns its own `ApplicationConfiguration` (distinct from `Client.Shared`) because it's always-on with keep-alive + `TransferSubscriptions` across SDK reconnect, not an interactive CLI | ## Per-driver documentation @@ -35,6 +35,9 @@ Driver type metadata is registered at startup in `DriverTypeRegistry` (`src/ZB.M - [Galaxy.md](Galaxy.md) — COM bridge, STA pump, IPC, runtime probes - [Galaxy-Repository.md](Galaxy-Repository.md) — ZB SQL reader, `LocalPlatform` scope filter, change detection +- **FOCAS** has a short getting-started doc because the Tier-C two-project deployment + backend-selection env var + alarm projection opt-in all need explaining up front: + - [FOCAS.md](FOCAS.md) — deployment, config, capability surface, alarm projection, troubleshooting + - **All other drivers** share a single per-driver specification in [docs/v2/driver-specs.md](../v2/driver-specs.md) — addressing, data-type maps, connection settings, and quirks live there. That file is the authoritative per-driver reference; this index points at it rather than duplicating. ## Test-fixture coverage maps diff --git a/docs/v2/focas-version-matrix.md b/docs/v2/focas-version-matrix.md index ee21a8e..9f76b40 100644 --- a/docs/v2/focas-version-matrix.md +++ b/docs/v2/focas-version-matrix.md @@ -6,7 +6,7 @@ enforces at driver init time. Every row cites the Fanuc FOCAS Developer Kit function whose documented input range determines the ceiling. **Why this exists** — we have no FOCAS hardware on the bench and no -working simulator. Fwlib32 returns `EW_NUMBER` / `EW_PARAM` when you +working simulator. FWLIB (Fwlib64, or Fwlib32 on legacy deployments) returns `EW_NUMBER` / `EW_PARAM` when you hand it an address outside the controller's supported range; the driver would map that to a per-read `BadOutOfRange` at steady state. Catching at `InitializeAsync` with this matrix surfaces operator @@ -140,6 +140,6 @@ matrix: Macro variable #50000 is outside the documented range This validation closes the cheap half of the FOCAS hardware-free stability gap — config errors now fail at load instead of per-read. The expensive half is Tier-C process isolation so that a crashing -`Fwlib32.dll` doesn't take the main OPC UA server down with it. See +`Fwlib64.dll` doesn't take the main OPC UA server down with it. See [`docs/v2/implementation/focas-isolation-plan.md`](implementation/focas-isolation-plan.md) for that plan (task #220). diff --git a/docs/v2/implementation/focas-isolation-plan.md b/docs/v2/implementation/focas-isolation-plan.md index ecb6eb2..362e753 100644 --- a/docs/v2/implementation/focas-isolation-plan.md +++ b/docs/v2/implementation/focas-isolation-plan.md @@ -1,10 +1,21 @@ # FOCAS Tier-C isolation — plan for task #220 -> **Status**: PRs A–E shipped. Architecture is in place; the only -> remaining FOCAS work is the hardware-dependent production -> integration of `Fwlib32.dll` into a real `IFocasBackend` -> (`FwlibHostedBackend`), which needs an actual CNC on the bench -> and is tracked as a follow-up on #220. +> **Status**: **FULLY SHIPPED** (code). PRs A–E shipped the architecture; the +> 2026-04-23 follow-up shipped the production `Fwlib64FocasBackend` wrapping +> the licensed `Fwlib64.dll`. Only the wire-level live-boot against real +> hardware remains (task #222 / requires a bench CNC). +> +> **Major update 2026-04-23 — Host retargeted to .NET 10 x64 + Fwlib64**: +> Both `Fwlib32.dll` and `Fwlib64.dll` are licensed for this project. The +> original plan put the Host on .NET 4.8 x86 because Fwlib32 was assumed. +> With Fwlib64 available, the Host moves to `net10.0-windows` x64 — same +> runtime as the rest of the fleet. **Tier-C isolation stays anyway** — the +> blast-radius argument against a closed-source vendor P/Invoke is independent +> of bitness. Galaxy (forced x86 by MXAccess COM) is a pure bitness forcing; +> FOCAS is a pure blast-radius choice. Body of this document still reflects +> the original x86 assumptions in a few places — read them as historical +> design context; the current shape is in `docs/drivers/FOCAS-Test-Fixture.md` +> and `exit-gate-phase-3.md`. > > **Pre-reqs shipped**: version matrix + pre-flight validation > (PR #168 — the cheap half of the hardware-free stability gap). diff --git a/docs/v2/v2-release-readiness.md b/docs/v2/v2-release-readiness.md index 08e2e57..dc7bf6e 100644 --- a/docs/v2/v2-release-readiness.md +++ b/docs/v2/v2-release-readiness.md @@ -1,7 +1,7 @@ # v2 Release Readiness -> **Last updated**: 2026-04-19 (all three release blockers CLOSED — Phase 6.3 Streams A/C core shipped) -> **Status**: **RELEASE-READY (code-path)** for v2 GA — all three code-path release blockers are closed. Remaining work is manual (client interop matrix, deployment checklist signoff, OPC UA CTT pass) + hardening follow-ups; see exit-criteria checklist below. +> **Last updated**: 2026-04-24 (Phase 5 driver complement closed — AB CIP, AB Legacy, TwinCAT, FOCAS all shipped; FOCAS Tier-C retired for a pure-managed in-process client) +> **Status**: **RELEASE-READY (code-path)** for v2 GA. All three original code-path release blockers remain closed. Phase 5 is now complete. Remaining work is manual (live-hardware validations, client interop matrix, deployment checklist signoff, OPC UA CTT pass) + hardening follow-ups; see exit-criteria checklist below. This doc is the single view of where v2 stands against its release criteria. Update it whenever a deferred follow-up closes or a new release blocker is discovered. @@ -14,23 +14,25 @@ This doc is the single view of where v2 stands against its release criteria. Upd | Phase 2 — Galaxy driver split (Proxy/Host/Shared) | ✓ | Shipped | | Phase 3 — OPC UA server + LDAP + security profiles | ✓ | Shipped | | Phase 4 — Redundancy scaffold (entities + endpoints) | ✓ | Shipped (runtime closes in 6.3) | -| Phase 5 — Drivers | ⚠ partial | Galaxy / Modbus / S7 / OpcUaClient shipped; AB CIP / AB Legacy / TwinCAT / FOCAS deferred (task #120) | -| Phase 6.1 — Resilience & Observability | ✓ | **SHIPPED** (PRs #78–83) | -| Phase 6.2 — Authorization runtime | ◐ core | **SHIPPED (core)** (PRs #84–88); dispatch wiring + Admin UI deferred | -| Phase 6.3 — Redundancy runtime | ◐ core | **SHIPPED (core)** (PRs #89–90); coordinator + UA-node wiring + Admin UI + interop deferred | -| Phase 6.4 — Admin UI completion | ◐ data layer | **SHIPPED (data layer)** (PRs #91–92); Blazor UI + OPC 40010 address-space wiring deferred | +| Phase 5 — Drivers | ✓ | **Shipped** — Galaxy, Modbus (+ DL205/S7/MELSEC profiles), S7 native, OPC UA Client, AB CIP, AB Legacy, TwinCAT ADS, FOCAS (managed wire client) | +| Phase 6.1 — Resilience & Observability | ✓ | Shipped (PRs #78–83) | +| Phase 6.2 — Authorization runtime | ◐ core | Core shipped (PRs #84–88, #94 dispatch wiring); finer-grained Browse/Subscribe/Alarm/Call gating + 3-user interop matrix deferred | +| Phase 6.3 — Redundancy runtime | ◐ core | Core shipped (PRs #89–90, #98–99); peer-probe HostedServices, OPC UA variable-node binding, `sp_PublishGeneration` lease wrap, client interop matrix deferred | +| Phase 6.4 — Admin UI completion | ◐ data layer + Identification | Data layer + OPC 40010 Identification folder shipped (PRs #91–92, Identification audit close-out 2026-04-23); Blazor UI pieces deferred | -**Aggregate test counts:** 906 baseline (pre-Phase-6) → **1159 passing** across Phase 6. One pre-existing Client.CLI `SubscribeCommandTests.Execute_PrintsSubscriptionMessage` flake tracked separately. +**Driver integration-test counts** (end-to-end against live or simulated targets): Modbus 26, FOCAS 9, AbCip 7, OpcUaClient 3, S7 3, AbLegacy 2, TwinCAT 2. Plus Galaxy's separate cross-FX parity/stability suite. + +**Aggregate test counts** (2026-04-19 baseline): 1159 passing across the solution. One pre-existing Client.CLI `SubscribeCommandTests.Execute_PrintsSubscriptionMessage` flake tracked separately. Rerun `dotnet test ZB.MOM.WW.OtOpcUa.slnx` after the FOCAS migration commits land to refresh the number. ## Release blockers (must close before v2 GA) -Ordered by severity + impact on production fitness. +All code-path release blockers are closed. The remaining items are live-hardware / manual validations listed under exit criteria. ### ~~Security — Phase 6.2 dispatch wiring~~ (task #143 — **CLOSED** 2026-04-19, PR #94) -**Closed**. `AuthorizationGate` + `NodeScopeResolver` now thread through `OpcUaApplicationHost → OtOpcUaServer → DriverNodeManager`. `OnReadValue` + `OnWriteValue` + all four HistoryRead paths call `gate.IsAllowed(identity, operation, scope)` before the invoker. Production deployments activate enforcement by constructing `OpcUaApplicationHost` with an `AuthorizationGate(StrictMode: true)` + populating the `NodeAcl` table. +**Closed**. `AuthorizationGate` + `NodeScopeResolver` thread through `OpcUaApplicationHost → OtOpcUaServer → DriverNodeManager`. `OnReadValue` + `OnWriteValue` + all four HistoryRead paths call `gate.IsAllowed(identity, operation, scope)` before the invoker. Production deployments activate enforcement by constructing `OpcUaApplicationHost` with an `AuthorizationGate(StrictMode: true)` + populating the `NodeAcl` table. -Additional Stream C surfaces (not release-blocking, hardening only): +Remaining Stream C surfaces (hardening, not release-blocking): - Browse + TranslateBrowsePathsToNodeIds gating with ancestor-visibility logic per `acl-design.md` §Browse. - CreateMonitoredItems + TransferSubscriptions gating with per-item `(AuthGenerationId, MembershipVersion)` stamp so revoked grants surface `BadUserAccessDenied` within one publish cycle (decision #153). @@ -39,42 +41,51 @@ Additional Stream C surfaces (not release-blocking, hardening only): - Finer-grained scope resolution — current `NodeScopeResolver` returns a flat cluster-level scope. Joining against the live Configuration DB to populate UnsArea / UnsLine / Equipment path is tracked as Stream C.12. - 3-user integration matrix covering every operation × allow/deny. -These are additional hardening — the three highest-value surfaces (Read / Write / HistoryRead) are now gated, which covers the base-security gap for v2 GA. - ### ~~Config fallback — Phase 6.1 Stream D wiring~~ (task #136 — **CLOSED** 2026-04-19, PR #96) -**Closed**. `SealedBootstrap` consumes `ResilientConfigReader` + `GenerationSealedCache` + `StaleConfigFlag` end-to-end: bootstrap calls go through the timeout → retry → fallback-to-sealed pipeline; every central-DB success writes a fresh sealed snapshot so the next cache-miss has a known-good fallback; `StaleConfigFlag.IsStale` is now consumed by `HealthEndpointsHost.usingStaleConfig` so `/healthz` body reports reality. +**Closed**. `SealedBootstrap` consumes `ResilientConfigReader` + `GenerationSealedCache` + `StaleConfigFlag` end-to-end; `/healthz` surfaces the stale flag. -Production activation: Program.cs switches `NodeBootstrap → SealedBootstrap` + constructs `OpcUaApplicationHost` with the `StaleConfigFlag` as an optional ctor parameter. - -Remaining follow-ups (hardening, not release-blocking): +Remaining follow-ups (hardening): - A `HostedService` that polls `sp_GetCurrentGenerationForCluster` periodically so peer-published generations land in this node's cache without a restart. -- Richer snapshot payload via `sp_GetGenerationContent` so fallback can serve the full generation content (DriverInstance enumeration, ACL rows, etc.) from the sealed cache alone. +- Richer snapshot payload via `sp_GetGenerationContent` so fallback can serve full generation content (DriverInstance enumeration, ACL rows, etc.) from the sealed cache alone. ### ~~Redundancy — Phase 6.3 Streams A/C core~~ (tasks #145 + #147 — **CLOSED** 2026-04-19, PRs #98–99) -**Closed**. The runtime orchestration layer now exists end-to-end: - -- `RedundancyCoordinator` reads `ClusterNode` + peer list at startup (Stream A shipped in PR #98). Invariants enforced: 1-2 nodes (decision #83), unique ApplicationUri (#86), ≤1 Primary in Warm/Hot (#84). Startup fails fast on violation; runtime refresh logs + flips `IsTopologyValid=false` so the calculator falls to band 2 without tearing down. -- `RedundancyStatePublisher` orchestrates topology + apply lease + recovery state + peer reachability through `ServiceLevelCalculator` + emits `OnStateChanged` / `OnServerUriArrayChanged` edge-triggered events (Stream C core shipped in PR #99). The OPC UA `ServiceLevel` Byte variable + `ServerUriArray` String[] variable subscribe to these events. +**Closed**. `RedundancyCoordinator` + `RedundancyStatePublisher` + `PeerReachabilityTracker` orchestrate topology + apply lease + recovery state + peer reachability through `ServiceLevelCalculator` + emit `OnStateChanged` / `OnServerUriArrayChanged` edge-triggered events. Remaining Phase 6.3 surfaces (hardening, not release-blocking): -- `PeerHttpProbeLoop` + `PeerUaProbeLoop` HostedServices that poll the peer + write to `PeerReachabilityTracker` on each tick. Without these the publisher sees `PeerReachability.Unknown` for every peer → Isolated-Primary band (230) even when the peer is up. Safe default (retains authority) but not the full non-transparent-redundancy UX. -- OPC UA variable-node wiring layer: bind the `ServiceLevel` Byte node + `ServerUriArray` String[] node to the publisher's events via `BaseDataVariable.OnReadValue` / direct value push. Scoped follow-up on the Opc.Ua.Server stack integration. +- `PeerHttpProbeLoop` + `PeerUaProbeLoop` HostedServices populating `PeerReachabilityTracker` on each tick. Without these the publisher sees `PeerReachability.Unknown` → Isolated-Primary band (230). Safe default but not the full non-transparent-redundancy UX. +- OPC UA variable-node wiring: bind `ServiceLevel` Byte + `ServerUriArray` String[] to the publisher's events via `BaseDataVariable.OnReadValue` / direct value push. - `sp_PublishGeneration` wraps its apply in `await using var lease = coordinator.BeginApplyLease(...)` so the `PrimaryMidApply` band (200) fires during actual publishes (task #148 part 2). -- Client interop matrix validation — Ignition / Kepware / Aveva OI Gateway (Stream F, task #150). Manual + doc-only work; doesn't block code ship. +- Client interop matrix — Ignition / Kepware / Aveva OI Gateway (Stream F, task #150). Manual + doc-only. -### Remaining drivers (task #120) +### ~~Phase 5 driver complement~~ (task #120 — **CLOSED** 2026-04-24) -AB CIP, AB Legacy, TwinCAT ADS, FOCAS drivers are planned but unshipped. Decision pending on whether these are release-blocking for v2 GA or can slip to a v2.1 follow-up. +**Closed**. All four deferred drivers shipped: + +- **AB CIP** (PRs #202–222) — `Driver.AbCip`, `Driver.AbCip.IntegrationTests` (7 tests), AB CIP Cli. Live-boot verified against a ControlLogix rig. +- **AB Legacy** (PRs #202, #223) — `Driver.AbLegacy`, `Driver.AbLegacy.IntegrationTests` (2 tests), AB Legacy Cli. PCCC cip-path workaround for SLC/MicroLogix. +- **TwinCAT ADS** (PRs #205, this branch `task-galaxy-e2e`) — `Driver.TwinCAT`, `Driver.TwinCAT.IntegrationTests` (2 tests), TwinCAT Cli. TCBSD/ESXi fixture for e2e since local Hyper-V / TwinCAT RTIME are mutually exclusive on the dev box. +- **FOCAS** (PRs #173, #199 + this session's migration) — `Driver.FOCAS` with an **in-process managed `FocasWireClient`** that speaks FOCAS/2 over TCP directly. Tier-C isolation retired — `Driver.FOCAS.Host` + `Driver.FOCAS.Shared` + `FwlibNative` P/Invoke + shim DLL + NSSM service all deleted. `Driver.FOCAS.IntegrationTests` covers 9 scenarios (fixed tree identity/axes/program/timers/spindle + user-authored PARAM/MACRO/PMC reads, Browse, Subscribe, IAlarmSource raise/clear, Probe transitions). + +Decision recorded: FOCAS is **read-only** against the CNC by design — writes return `BadNotWritable`. See `docs/drivers/FOCAS.md` + `docs/drivers/FOCAS-Test-Fixture.md` for the deployment + coverage map. ## Nice-to-haves (not release-blocking) - **Admin UI** — Phase 6.1 Stream E.2/E.3 (`/hosts` column refresh), Phase 6.2 Stream D (`RoleGrantsTab` + `AclsTab` Probe), Phase 6.3 Stream E (`RedundancyTab`), Phase 6.4 Streams A/B UI pieces, Stream C DiffViewer, Stream D `IdentificationFields.razor`. Tasks #134, #144, #149, #153, #155, #156, #157. - **Background services** — Phase 6.1 Stream B.4 `ScheduledRecycleScheduler` HostedService (task #137), Phase 6.1 Stream A analyzer (task #135 — Roslyn analyzer asserting every capability surface routes through `CapabilityInvoker`). -- **Multi-host dispatch** — Phase 6.1 Stream A follow-up (task #135). Currently every driver gets a single pipeline keyed on `driver.DriverInstanceId`; multi-host drivers (Modbus with N PLCs) need per-PLC host resolution so failing PLCs trip per-PLC breakers without poisoning siblings. Decision #144 requires this but we haven't wired it yet. +- **Multi-host dispatch** — Phase 6.1 Stream A follow-up (task #135). Every driver currently gets a single pipeline keyed on `driver.DriverInstanceId`; multi-host drivers (Modbus with N PLCs) need per-PLC host resolution so failing PLCs trip per-PLC breakers without poisoning siblings. Decision #144 requires this but not wired. +- **Phase 7** — scripting + alarming + historian sink (plan drafted 2026-04-20 in `docs/v2/implementation/phase-7-*.md`). Out of scope for v2 GA. + +## Live-hardware validations (task #54 + task family) + +The code ships; these tasks remain open as lab/field verification: + +- **#54** — FOCAS live-CNC wire-level smoke against a real FANUC control. The mock's wire responder is PDU-verified against `fwlibe64.dll` upstream but OtOpcUa's managed client has not been pointed at a production CNC. +- **AB CIP live-boot** — already passed on a ControlLogix rig (PR #222). Continue to run ahead of each release. +- **TwinCAT wire-live** — TCBSD/ESXi fixture covers the common path; production PLC verification remains lab-gated. ## Running the release-readiness check @@ -82,7 +93,12 @@ AB CIP, AB Legacy, TwinCAT ADS, FOCAS drivers are planned but unshipped. Decisio pwsh ./scripts/compliance/phase-6-all.ps1 ``` -This meta-runner invokes each `phase-6-N-compliance.ps1` script in sequence and reports an aggregate PASS/FAIL. It is the single-command verification that what we claim is shipped still compiles + tests pass + the plan-level invariants are still satisfied. +This meta-runner invokes each `phase-6-N-compliance.ps1` script in sequence and reports an aggregate PASS/FAIL: + +- `phase-6-1-compliance.ps1` — Resilience & Observability +- `phase-6-2-compliance.ps1` — Authorization runtime +- `phase-6-3-compliance.ps1` — Redundancy runtime +- `phase-6-4-compliance.ps1` — Admin UI completion Exit 0 = every phase passes its compliance checks + no test-count regression. @@ -92,18 +108,23 @@ v2 GA requires all of the following: - [ ] All four Phase 6.N compliance scripts exit 0. - [ ] `dotnet test ZB.MOM.WW.OtOpcUa.slnx` passes with ≤ 1 known-flake failure. -- [ ] Release blockers listed above all closed (or consciously deferred to v2.1 with a written decision). +- [x] Release blockers listed above all closed. +- [x] Phase 5 driver complement shipped (Galaxy, Modbus, S7, OpcUaClient, AbCip, AbLegacy, TwinCAT, FOCAS). - [ ] Production deployment checklist (separate doc) signed off by Fleet Admin. - [ ] At least one end-to-end integration run against the live Galaxy on the dev box succeeds. +- [ ] FOCAS live-CNC wire-level smoke (#54) runs clean against a real FANUC control. - [ ] OPC UA conformance test (CTT or UA Compliance Test Tool) passes against the live endpoint. - [ ] Non-transparent redundancy cutover validated with at least one production client (Ignition 8.3 recommended — see decision #85). ## Change log -- **2026-04-19** — Release blocker #3 **closed** (PRs #98–99). Phase 6.3 Streams A + C core shipped: `ClusterTopologyLoader` + `RedundancyCoordinator` + `RedundancyStatePublisher` + `PeerReachabilityTracker`. Code-path release blockers all closed; remaining Phase 6.3 surfaces (peer-probe HostedServices, OPC UA variable-node binding, sp_PublishGeneration lease wrap, client interop matrix) are hardening follow-ups. -- **2026-04-19** — Release blocker #2 **closed** (PR #96). `SealedBootstrap` consumes `ResilientConfigReader` + `GenerationSealedCache` + `StaleConfigFlag`; `/healthz` now surfaces the stale flag. Remaining follow-ups (periodic poller + richer snapshot payload) downgraded to hardening. -- **2026-04-19** — Release blocker #1 **closed** (PR #94). `AuthorizationGate` wired into `DriverNodeManager` Read / Write / HistoryRead dispatch. Remaining Stream C surfaces (Browse / Subscribe / Alarm / Call + finer-grained scope resolution) downgraded to hardening follow-ups — no longer release-blocking. -- **2026-04-19** — Phase 6.4 data layer merged (PRs #91–92). Phase 6 core complete. Capstone doc created. +- **2026-04-24** — Phase 5 driver complement closed (task #120 CLOSED). AB CIP, AB Legacy, TwinCAT, FOCAS all shipped. FOCAS migration: retired the Tier-C split (`Driver.FOCAS.Host` + `Driver.FOCAS.Shared` + `FwlibNative` + shim DLL deleted) in favour of a pure-managed in-process `FocasWireClient` inlined into `Driver.FOCAS`; driver is now read-only against the CNC by design. Integration test matrix grew to cover Browse / Subscribe / IAlarmSource / Probe end-to-end. +- **2026-04-23** — Phase 6.4 audit close-out. IdentificationFolderBuilder + OPC 40010 Identification folder verified against the shipped code. +- **2026-04-20** — Phase 7 plan drafted (`phase-7-scripting-and-alarming.md`, `phase-7-e2e-smoke.md`). Out of scope for v2 GA. +- **2026-04-19** — Release blocker #3 closed (PRs #98–99). Phase 6.3 Streams A + C core shipped: `ClusterTopologyLoader` + `RedundancyCoordinator` + `RedundancyStatePublisher` + `PeerReachabilityTracker`. Code-path release blockers all closed; remaining Phase 6.3 surfaces (peer-probe HostedServices, OPC UA variable-node binding, `sp_PublishGeneration` lease wrap, client interop matrix) are hardening follow-ups. +- **2026-04-19** — Release blocker #2 closed (PR #96). `SealedBootstrap` consumes `ResilientConfigReader` + `GenerationSealedCache` + `StaleConfigFlag`; `/healthz` surfaces the stale flag. Remaining follow-ups (periodic poller + richer snapshot payload) downgraded to hardening. +- **2026-04-19** — Release blocker #1 closed (PR #94). `AuthorizationGate` wired into `DriverNodeManager` Read / Write / HistoryRead dispatch. Remaining Stream C surfaces (Browse / Subscribe / Alarm / Call + finer-grained scope resolution) downgraded to hardening follow-ups — no longer release-blocking. +- **2026-04-19** — Phase 6.4 data layer merged (PRs #91–92). Phase 6 core complete. - **2026-04-19** — Phase 6.3 core merged (PRs #89–90). `ServiceLevelCalculator` + `RecoveryStateManager` + `ApplyLeaseRegistry` land as pure logic; coordinator / UA-node wiring / Admin UI / interop deferred. - **2026-04-19** — Phase 6.2 core merged (PRs #84–88). `AuthorizationGate` + `TriePermissionEvaluator` + `LdapGroupRoleMapping` land; dispatch wiring + Admin UI deferred. - **2026-04-19** — Phase 6.1 shipped (PRs #78–83). Polly resilience + Tier A/B/C stability + health endpoints + LiteDB generation-sealed cache + Admin `/hosts` data layer all live. diff --git a/scripts/e2e/e2e-config.sample.json b/scripts/e2e/e2e-config.sample.json index a3ce156..f735efe 100644 --- a/scripts/e2e/e2e-config.sample.json +++ b/scripts/e2e/e2e-config.sample.json @@ -34,7 +34,7 @@ }, "focas": { - "$comment": "Gated behind FOCAS_TRUST_WIRE=1 — no public simulator. Point at a real CNC + ensure Fwlib32.dll is on PATH.", + "$comment": "Gated behind FOCAS_TRUST_WIRE=1 for real-CNC runs, or pass -ProfileName to run against the focas-mock Docker fixture. Managed wire client — no native dependencies.", "host": "192.168.1.20", "port": 8193, "address": "R100", @@ -60,6 +60,21 @@ "historyLookbackSec": 3600 }, + "opcuaclient": { + "$comment": "OPC UA Client (gateway) driver. Default opc-plc Docker fixture exposes ns=3;s=FastUInt1 as a ticker. The `bridgeNodeId` is the local mirror of remoteNodeId after the OpcUaClient driver's DiscoverAsync runs — dev-specific. Stages 5/7/8 are opt-in: supply writable* NodeIds to enable reverse-bridge, alarmNodeId to enable alarm, historyNodeId to enable history (opc-plc does not historize by default — a Prosys / UA Expert sample server is needed for stage 8).", + "remoteUrl": "opc.tcp://localhost:50000", + "remoteNodeId": "ns=3;s=FastUInt1", + "bridgeNodeId": "ns=2;s=OpcUaClient/FastUInt1", + "bridgeRootNodeId": "ns=2;s=OpcUaClient", + "browseDepth": 3, + "browseMinNodes": 5, + "changeWaitSec": 8, + "writableRemoteNodeId": "", + "writableBridgeNodeId": "", + "alarmNodeId": "", + "historyNodeId": "" + }, + "phase7": { "$comment": "Virtual tags + scripted alarms. The VirtualNodeId must resolve to a server-side virtual tag whose script reads the modbus InputNodeId and writes VT = input * 2. The AlarmNodeId is the ConditionId of a scripted alarm that fires when VT > 100.", "modbusEndpoint": "127.0.0.1:5502", diff --git a/scripts/e2e/test-focas.ps1 b/scripts/e2e/test-focas.ps1 index ef6e9a5..2e49b2a 100644 --- a/scripts/e2e/test-focas.ps1 +++ b/scripts/e2e/test-focas.ps1 @@ -1,16 +1,35 @@ -#Requires -Version 7.0 +#Requires -Version 7.0 <# .SYNOPSIS End-to-end CLI test for the FOCAS (Fanuc CNC) driver. .DESCRIPTION - **Hardware-gated.** There is no public FOCAS simulator; the driver's - FwlibFocasClient P/Invokes Fanuc's licensed Fwlib32.dll. Against a dev - box without the DLL on PATH the test will skip with a clear message. - Against a real CNC with the DLL present it runs probe / driver-loopback / - server-bridge the same way the other scripts do. + Runs the CLI against either the managed wire client (default — Driver.FOCAS.Cli + dials the CNC on TCP:8193 directly, no native dependencies) or the focas-mock + Docker fixture. Hardware-gated by default because the default CncHost is + 127.0.0.1; set FOCAS_TRUST_WIRE=1 once -CncHost points at a real CNC, or pass + -ProfileName to run against the Docker sim. - Set FOCAS_TRUST_WIRE=1 when -CncHost points at a real CNC to un-gate. + The script also supports three nice-to-have modes shipped 2026-04-24: + + -Series — per-series matrix mode. Accepts a comma-separated list; the + core stages are run once per series, swapping the -Address to + the supplied per-series probe. Fails fast if any series's + configured address is outside the documented range (the driver + itself enforces that at InitializeAsync). + + -ProfileName — for use with the Python Docker simulator (see + docs/v2/implementation/focas-simulator-plan.md). Selects a + docker-compose profile + matching -Series. When set, the + FOCAS_TRUST_WIRE gate is considered satisfied because the sim + is a legitimate non-hardware target. + + -HandleLeakCycles — stress stage that opens + closes sessions + via the CLI's `probe` command with a short sleep between + cycles. Exercises the Tier-C supervisor's handle-recycle path + without touching user data. Typical values: 100–1000. A CNC's + FWLIB handle pool is finite (~5–10), so this shakes out + handle-leak bugs if either side forgets to free. .PARAMETER CncHost IP or hostname of the CNC. Default 127.0.0.1 — override for real runs. @@ -19,7 +38,22 @@ FOCAS TCP port. Default 8193. .PARAMETER Address - FOCAS address to exercise. Default R100 (PMC R-file register). + FOCAS address to exercise. Default R100 (PMC R-file register). Ignored + when -Series is set and the series profile supplies its own probe. + +.PARAMETER Series + Comma-separated list of CNC series to run the matrix against. Known: + ZeroI_D, ZeroI_F, ZeroI_MF, ZeroI_TF, Sixteen_i, Thirty_i, ThirtyOne_i, + ThirtyTwo_i, PowerMotion_i. When empty the script runs a single pass + without a series constraint. + +.PARAMETER ProfileName + docker-compose profile name from tests/.../Docker/profiles/. When set, + the script assumes the Python simulator is the target + un-gates + FOCAS_TRUST_WIRE. + +.PARAMETER HandleLeakCycles + Run a handle-leak stress stage with open/close cycles. 0 = skip. .PARAMETER OpcUaUrl OtOpcUa server endpoint. @@ -32,6 +66,9 @@ param( [string]$CncHost = "127.0.0.1", [int]$CncPort = 8193, [string]$Address = "R100", + [string]$Series = "", + [string]$ProfileName = "", + [int]$HandleLeakCycles = 0, [string]$OpcUaUrl = "opc.tcp://localhost:4840", [Parameter(Mandatory)] [string]$BridgeNodeId ) @@ -39,11 +76,41 @@ param( $ErrorActionPreference = "Stop" . "$PSScriptRoot/_common.ps1" -if (-not ($env:FOCAS_TRUST_WIRE -eq "1" -or $env:FOCAS_TRUST_WIRE -eq "true")) { - Write-Skip "FOCAS_TRUST_WIRE not set — no public simulator exists (task #222 tracks the lab rig). Set =1 when -CncHost points at a real CNC with Fwlib32.dll on PATH." +$simGated = -not [string]::IsNullOrWhiteSpace($ProfileName) +if (-not $simGated -and -not ($env:FOCAS_TRUST_WIRE -eq "1" -or $env:FOCAS_TRUST_WIRE -eq "true")) { + Write-Skip "FOCAS_TRUST_WIRE not set. Pass -ProfileName to run against the Docker mock in tests/.../Driver.FOCAS.IntegrationTests/Docker/, or set FOCAS_TRUST_WIRE=1 when -CncHost points at a real CNC." exit 0 } +if ($simGated) { + Write-Info "Sim mode — profile '$ProfileName'. FOCAS_TRUST_WIRE gate bypassed." +} + +# Per-series probe addresses — each one is inside the authoritative range for +# that series (docs/v2/focas-version-matrix.md). Picking one representative per +# kind (PMC / parameter / macro) is enough to exercise the driver's validator. +$seriesProbes = @{ + "ZeroI_D" = "R100" + "ZeroI_F" = "R100" + "ZeroI_MF" = "R100" + "ZeroI_TF" = "R100" + "Sixteen_i" = "R100" + "Thirty_i" = "R100" + "ThirtyOne_i" = "R100" + "ThirtyTwo_i" = "R100" + "PowerMotion_i"= "R100" +} + +$seriesList = @() +if (-not [string]::IsNullOrWhiteSpace($Series)) { + $seriesList = @($Series.Split(',') | ForEach-Object { $_.Trim() } | Where-Object { $_ }) + $unknown = @($seriesList | Where-Object { -not $seriesProbes.ContainsKey($_) }) + if ($unknown.Count -gt 0) { + Write-Fail "Unknown -Series entries: $($unknown -join ', '). Known: $($seriesProbes.Keys -join ', ')." + exit 2 + } +} + $focasCli = Get-CliInvocation ` -ProjectFolder "src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli" ` -ExeName "otopcua-focas-cli" @@ -51,46 +118,103 @@ $opcUaCli = Get-CliInvocation ` -ProjectFolder "src/ZB.MOM.WW.OtOpcUa.Client.CLI" ` -ExeName "otopcua-cli" -$commonFocas = @("-h", $CncHost, "-p", $CncPort) -$results = @() +$allResults = @() -$results += Test-Probe ` - -Cli $focasCli ` - -ProbeArgs (@("probe") + $commonFocas + @("-a", $Address, "--type", "Int16")) +function Invoke-FocasCore { + param( + [string]$Label, + [string]$ProbeAddress + ) -$writeValue = Get-Random -Minimum 1 -Maximum 9999 -$results += Test-DriverLoopback ` - -Cli $focasCli ` - -WriteArgs (@("write") + $commonFocas + @("-a", $Address, "-t", "Int16", "-v", $writeValue)) ` - -ReadArgs (@("read") + $commonFocas + @("-a", $Address, "-t", "Int16")) ` - -ExpectedValue "$writeValue" + Write-Header "FOCAS stages — $Label" + $commonFocas = @("-h", $CncHost, "-p", $CncPort) + $results = @() -$bridgeValue = Get-Random -Minimum 10000 -Maximum 19999 -$results += Test-ServerBridge ` - -DriverCli $focasCli ` - -DriverWriteArgs (@("write") + $commonFocas + @("-a", $Address, "-t", "Int16", "-v", $bridgeValue)) ` - -OpcUaCli $opcUaCli ` - -OpcUaUrl $OpcUaUrl ` - -OpcUaNodeId $BridgeNodeId ` - -ExpectedValue "$bridgeValue" + $results += Test-Probe ` + -Cli $focasCli ` + -ProbeArgs (@("probe") + $commonFocas + @("-a", $ProbeAddress, "--type", "Int16")) -$reverseValue = Get-Random -Minimum 20000 -Maximum 29999 -$results += Test-OpcUaWriteBridge ` - -OpcUaCli $opcUaCli ` - -OpcUaUrl $OpcUaUrl ` - -OpcUaNodeId $BridgeNodeId ` - -DriverCli $focasCli ` - -DriverReadArgs (@("read") + $commonFocas + @("-a", $Address, "-t", "Int16")) ` - -ExpectedValue "$reverseValue" + $writeValue = Get-Random -Minimum 1 -Maximum 9999 + $results += Test-DriverLoopback ` + -Cli $focasCli ` + -WriteArgs (@("write") + $commonFocas + @("-a", $ProbeAddress, "-t", "Int16", "-v", $writeValue)) ` + -ReadArgs (@("read") + $commonFocas + @("-a", $ProbeAddress, "-t", "Int16")) ` + -ExpectedValue "$writeValue" -$subValue = Get-Random -Minimum 30000 -Maximum 32766 -$results += Test-SubscribeSeesChange ` - -OpcUaCli $opcUaCli ` - -OpcUaUrl $OpcUaUrl ` - -OpcUaNodeId $BridgeNodeId ` - -DriverCli $focasCli ` - -DriverWriteArgs (@("write") + $commonFocas + @("-a", $Address, "-t", "Int16", "-v", $subValue)) ` - -ExpectedValue "$subValue" + $bridgeValue = Get-Random -Minimum 10000 -Maximum 19999 + $results += Test-ServerBridge ` + -DriverCli $focasCli ` + -DriverWriteArgs (@("write") + $commonFocas + @("-a", $ProbeAddress, "-t", "Int16", "-v", $bridgeValue)) ` + -OpcUaCli $opcUaCli ` + -OpcUaUrl $OpcUaUrl ` + -OpcUaNodeId $BridgeNodeId ` + -ExpectedValue "$bridgeValue" -Write-Summary -Title "FOCAS e2e" -Results $results -if ($results | Where-Object { -not $_.Passed }) { exit 1 } + $reverseValue = Get-Random -Minimum 20000 -Maximum 29999 + $results += Test-OpcUaWriteBridge ` + -OpcUaCli $opcUaCli ` + -OpcUaUrl $OpcUaUrl ` + -OpcUaNodeId $BridgeNodeId ` + -DriverCli $focasCli ` + -DriverReadArgs (@("read") + $commonFocas + @("-a", $ProbeAddress, "-t", "Int16")) ` + -ExpectedValue "$reverseValue" + + $subValue = Get-Random -Minimum 30000 -Maximum 32766 + $results += Test-SubscribeSeesChange ` + -OpcUaCli $opcUaCli ` + -OpcUaUrl $OpcUaUrl ` + -OpcUaNodeId $BridgeNodeId ` + -DriverCli $focasCli ` + -DriverWriteArgs (@("write") + $commonFocas + @("-a", $ProbeAddress, "-t", "Int16", "-v", $subValue)) ` + -ExpectedValue "$subValue" + + return $results +} + +function Invoke-HandleLeakStage { + param([int]$Cycles) + + Write-Header "FOCAS handle-leak stress — $Cycles cycles" + $commonFocas = @("-h", $CncHost, "-p", $CncPort) + $failed = 0 + for ($i = 1; $i -le $Cycles; $i++) { + $probe = Test-Probe ` + -Cli $focasCli ` + -ProbeArgs (@("probe") + $commonFocas + @("-a", $Address, "--type", "Int16")) + if (-not $probe.Passed) { + $failed++ + # First 3 failures are informative; the rest just tally. + if ($failed -le 3) { + Write-Fail "cycle $i failed: $($probe.Reason)" + } + } + # Tiny delay so a broken loop can't DDoS the CNC; FWLIB handles take a + # few tens of ms to recycle in practice. + Start-Sleep -Milliseconds 50 + } + $passed = $Cycles - $failed + if ($failed -eq 0) { + Write-Pass "handle-leak stress: $passed/$Cycles cycles succeeded" + return @{ Passed = $true; Reason = "$passed/$Cycles" } + } else { + Write-Fail "handle-leak stress: $failed/$Cycles cycles failed" + return @{ Passed = $false; Reason = "$failed/$Cycles failed" } + } +} + +if ($seriesList.Count -eq 0) { + $allResults += Invoke-FocasCore -Label "single" -ProbeAddress $Address +} else { + foreach ($series in $seriesList) { + $probeAddr = $seriesProbes[$series] + Write-Info "Running matrix pass for series '$series' with address $probeAddr" + $allResults += Invoke-FocasCore -Label $series -ProbeAddress $probeAddr + } +} + +if ($HandleLeakCycles -gt 0) { + $allResults += Invoke-HandleLeakStage -Cycles $HandleLeakCycles +} + +Write-Summary -Title "FOCAS e2e" -Results $allResults +if ($allResults | Where-Object { -not $_.Passed }) { exit 1 } diff --git a/scripts/install/Install-FocasHost.ps1 b/scripts/install/Install-FocasHost.ps1 deleted file mode 100644 index b81e376..0000000 --- a/scripts/install/Install-FocasHost.ps1 +++ /dev/null @@ -1,108 +0,0 @@ -<# -.SYNOPSIS - Registers the OtOpcUaFocasHost Windows service. Optional companion to - Install-Services.ps1 — only run this on nodes where FOCAS driver instances will run - with Tier-C process isolation enabled. - -.DESCRIPTION - FOCAS PR #220 / Tier-C isolation plan. Wraps OtOpcUa.Driver.FOCAS.Host.exe (net48 x86) - as a Windows service using NSSM, running under the same service account as the main - OtOpcUa service so the named-pipe ACL works. Passes the per-process shared secret via - environment variable at service-start time so it never hits disk. - -.PARAMETER InstallRoot - Where the FOCAS Host binaries live (typically - C:\Program Files\OtOpcUa\Driver.FOCAS.Host). - -.PARAMETER ServiceAccount - Service account SID or DOMAIN\name. Must match the main OtOpcUa server account so the - PipeAcl match succeeds. - -.PARAMETER FocasSharedSecret - Per-process secret passed via env var. Generated freshly per install if not supplied. - -.PARAMETER FocasBackend - Backend selector for the Host process. One of: - fwlib32 (default — real Fanuc Fwlib32.dll integration; requires licensed DLL on PATH) - fake (in-memory; smoke-test mode) - unconfigured (safe default returning structured errors; use until hardware is wired) - -.PARAMETER FocasPipeName - Pipe name the Host listens on. Default: OtOpcUaFocas. - -.EXAMPLE - .\Install-FocasHost.ps1 -InstallRoot 'C:\Program Files\OtOpcUa\Driver.FOCAS.Host' ` - -ServiceAccount 'OTOPCUA\svc-otopcua' -FocasBackend fwlib32 -#> -[CmdletBinding()] -param( - [Parameter(Mandatory)] [string]$InstallRoot, - [Parameter(Mandatory)] [string]$ServiceAccount, - [string]$FocasSharedSecret, - [ValidateSet('fwlib32','fake','unconfigured')] [string]$FocasBackend = 'unconfigured', - [string]$FocasPipeName = 'OtOpcUaFocas', - [string]$ServiceName = 'OtOpcUaFocasHost', - [string]$NssmPath = 'C:\Program Files\nssm\nssm.exe' -) - -$ErrorActionPreference = 'Stop' - -function Resolve-Sid { - param([string]$Account) - if ($Account -match '^S-\d-\d+') { return $Account } - try { - $nt = New-Object System.Security.Principal.NTAccount($Account) - return $nt.Translate([System.Security.Principal.SecurityIdentifier]).Value - } catch { - throw "Could not resolve '$Account' to a SID. Pass an explicit SID or check the account name." - } -} - -if (-not (Test-Path $NssmPath)) { - throw "nssm.exe not found at '$NssmPath'. Install NSSM or pass -NssmPath." -} - -$hostExe = Join-Path $InstallRoot 'OtOpcUa.Driver.FOCAS.Host.exe' -if (-not (Test-Path $hostExe)) { - throw "FOCAS Host binary not found at '$hostExe'. Publish the Driver.FOCAS.Host project first." -} - -if (-not $FocasSharedSecret) { - $FocasSharedSecret = [System.Guid]::NewGuid().ToString('N') - Write-Host "Generated FocasSharedSecret — store it alongside the OtOpcUa service config." -} - -$allowedSid = Resolve-Sid $ServiceAccount - -# Idempotent install — remove + re-create if present. -$existing = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue -if ($existing) { - Write-Host "Removing existing '$ServiceName' service..." - & $NssmPath stop $ServiceName confirm | Out-Null - & $NssmPath remove $ServiceName confirm | Out-Null -} - -& $NssmPath install $ServiceName $hostExe | Out-Null -& $NssmPath set $ServiceName DisplayName 'OT-OPC-UA FOCAS Host (Tier-C isolated Fwlib32)' | Out-Null -& $NssmPath set $ServiceName Description 'Out-of-process Fwlib32.dll host for OtOpcUa FOCAS driver. Crash-isolated from the main OPC UA server.' | Out-Null -& $NssmPath set $ServiceName ObjectName $ServiceAccount | Out-Null -& $NssmPath set $ServiceName Start SERVICE_AUTO_START | Out-Null -& $NssmPath set $ServiceName AppStdout (Join-Path $env:ProgramData 'OtOpcUa\focas-host-stdout.log') | Out-Null -& $NssmPath set $ServiceName AppStderr (Join-Path $env:ProgramData 'OtOpcUa\focas-host-stderr.log') | Out-Null -& $NssmPath set $ServiceName AppRotateFiles 1 | Out-Null -& $NssmPath set $ServiceName AppRotateBytes 10485760 | Out-Null - -& $NssmPath set $ServiceName AppEnvironmentExtra ` - "OTOPCUA_FOCAS_PIPE=$FocasPipeName" ` - "OTOPCUA_ALLOWED_SID=$allowedSid" ` - "OTOPCUA_FOCAS_SECRET=$FocasSharedSecret" ` - "OTOPCUA_FOCAS_BACKEND=$FocasBackend" | Out-Null - -& $NssmPath set $ServiceName DependOnService OtOpcUa | Out-Null - -Write-Host "Installed '$ServiceName' under '$ServiceAccount' (SID=$allowedSid)." -Write-Host "Pipe: \\.\pipe\$FocasPipeName Backend: $FocasBackend" -Write-Host "Start the service with: Start-Service $ServiceName" -Write-Host "" -Write-Host "NOTE: the Fwlib32 backend requires the licensed Fwlib32.dll on PATH" -Write-Host "alongside the Host exe. See docs/v2/focas-deployment.md." diff --git a/scripts/integration/README.md b/scripts/integration/README.md new file mode 100644 index 0000000..d8d2bee --- /dev/null +++ b/scripts/integration/README.md @@ -0,0 +1,87 @@ +# Integration runners + +Scripts that orchestrate multi-component integration-test loops — +each one wires up docker fixtures, support binaries, and `dotnet test` +in sequence so a developer (or a CI agent) can get from "freshly +cloned repo" to "green integration suite" with one command. + +Unlike `scripts/e2e/test-*.ps1` (which drive the built server through +the CLI for black-box coverage), scripts in this folder operate +**below** the server layer — they bring up the raw fixtures the +driver-level `IntegrationTests` projects need. + +## Scripts + +| Script | Purpose | +|--------|---------| +| [`run-focas.ps1`](run-focas.ps1) | FOCAS driver: builds shim DLLs + starts focas-mock docker + copies shim into test bin + runs `WireCompatGatedTests` + `FocasSimSmokeTests` + tears down docker | + +## run-focas.ps1 + +### Prerequisites + +- **Windows + PowerShell 7+** +- **.NET 10 SDK** — `dotnet --version` prints 10.x +- **Native C compiler** — one of: + - Visual Studio Build Tools with the C++ workload (then run from an + "x64 Native Tools Command Prompt for VS" shell), or + - Zig (`zig.exe` on PATH) as a drop-in alternative +- **Docker Desktop** running, OR pass `-SkipDocker` and run the mock + externally + +### One-shot run + +```powershell +cd C:\Users\dohertj2\Desktop\lmxopcua +pwsh .\scripts\integration\run-focas.ps1 +``` + +That's the default invocation: thirtyone profile, debug build, +docker cleans up on exit. + +### Development iteration + +Re-run the tests without rebuilding the shim or restarting docker: + +```powershell +# First run bootstraps everything + keeps the mock up. +pwsh .\scripts\integration\run-focas.ps1 -KeepDocker + +# Iterate on test bodies without re-doing the slow steps. +pwsh .\scripts\integration\run-focas.ps1 -SkipShimBuild -SkipDocker +``` + +### Per-series runs + +```powershell +pwsh .\scripts\integration\run-focas.ps1 -Profile thirty # 30i series +pwsh .\scripts\integration\run-focas.ps1 -Profile zerod # 0i-D +pwsh .\scripts\integration\run-focas.ps1 -Profile powermotion +``` + +Full profile list is in +`tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/docker-compose.yml`. + +### Exit codes + +| Code | Meaning | +|------|---------| +| 0 | All tests passed or cleanly skipped | +| 1 | `dotnet test` reported failures | +| 2 | The runner itself crashed (missing file, unexpected exception) | +| 3 | No C compiler detected for shim build | +| 4 | Docker CLI not on PATH | + +### CI integration + +Wire this into the project's CI runner (Gitea Actions, Jenkins, +whatever's hosting this repo) by calling: + +```yaml +- name: FOCAS integration + shell: pwsh + run: ./scripts/integration/run-focas.ps1 +``` + +The script is idempotent; a previous run's `docker compose down` +failure won't block the next one. diff --git a/scripts/integration/run-focas.ps1 b/scripts/integration/run-focas.ps1 new file mode 100644 index 0000000..716502a --- /dev/null +++ b/scripts/integration/run-focas.ps1 @@ -0,0 +1,123 @@ +#Requires -Version 7.0 +<# +.SYNOPSIS + Orchestrates the FOCAS driver integration-test loop: bring up the + focas-mock Docker container, run the managed wire-client integration + tests, tear down Docker. + +.DESCRIPTION + The FOCAS integration fixture now needs just two things running + together: + 1. A single focas-mock container listening on :8193 (one service, + no per-series compose profile ceremony — the mock's native + FOCAS Ethernet responder handles every call the managed driver + issues). + 2. The integration-test assembly built. The managed + `WireFocasClient` dials the mock directly; there is no shim + DLL, no P/Invoke, no test-bin DLL copy step. + This script handles both and cleans up on exit. + + Designed to run unattended on a build agent or on a developer box. + Exit code matches the test suite (0 = all pass or skip-clean, + non-zero when any integration test failed). + +.PARAMETER Profile + focas-mock profile name to seed at startup (e.g. `ThirtyOne_i`, + `Sixteen_i`, `fwlib30i64`). Defaults to `ThirtyOne_i`. The fixture + resolves aliases via `FocasCncSeries`, and tests that need per-series + state can call `fixture.LoadProfileAsync` directly at test start to + override the default. + +.PARAMETER SkipDocker + Skip docker up/down. Use when the mock is already running from + another shell. + +.PARAMETER Configuration + Build configuration — Debug or Release. Default: Debug. + +.PARAMETER KeepDocker + Don't tear down the docker stack on exit. Useful for iterating on + tests. +#> + +param( + [string]$Profile = "ThirtyOne_i", + [switch]$SkipDocker, + [ValidateSet("Debug", "Release")] + [string]$Configuration = "Debug", + [switch]$KeepDocker +) + +Set-StrictMode -Version 3.0 +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path +$integTests = Join-Path $repoRoot "tests\ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests" +$dockerYml = Join-Path $integTests "Docker\docker-compose.yml" + +function Write-Step { param([string]$Msg) Write-Host ""; Write-Host "=== $Msg ===" -ForegroundColor Cyan } +function Write-Info { param([string]$Msg) Write-Host "[INFO] $Msg" -ForegroundColor Gray } +function Write-Fail { param([string]$Msg) Write-Host "[FAIL] $Msg" -ForegroundColor Red } + +$cleanupScripts = @() +trap { + Write-Host "" + Write-Fail "run-focas.ps1 crashed: $_" + foreach ($c in $cleanupScripts) { try { & $c } catch { Write-Host "cleanup failed: $_" -ForegroundColor DarkYellow } } + exit 2 +} + +Write-Step "Build FOCAS IntegrationTests ($Configuration)" +dotnet build (Join-Path $integTests "ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests.csproj") ` + --configuration $Configuration --nologo --verbosity minimal +if ($LASTEXITCODE -ne 0) { throw "dotnet build failed (exit $LASTEXITCODE)" } + +if (-not $SkipDocker) { + Write-Step "docker compose up" + if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { + Write-Fail "docker CLI not on PATH. Install Docker Desktop or pass -SkipDocker + run the mock externally." + exit 4 + } + + docker compose -f $dockerYml up -d --build --wait 2>&1 | Write-Host + if ($LASTEXITCODE -ne 0) { throw "docker compose up failed (exit $LASTEXITCODE)" } + + if (-not $KeepDocker) { + $cleanupScripts += { + Write-Step "docker compose down" + docker compose -f $dockerYml down --remove-orphans 2>&1 | Write-Host + } + } + + Write-Info "probing localhost:8193..." + $tcp = [System.Net.Sockets.TcpClient]::new() + try { + $ok = $tcp.ConnectAsync("127.0.0.1", 8193).Wait([TimeSpan]::FromSeconds(5)) + if (-not $ok -or -not $tcp.Connected) { + throw "TCP probe to localhost:8193 failed after docker compose --wait succeeded" + } + } + finally { $tcp.Dispose() } + Write-Info "mock is accepting connections" +} +else { + Write-Step "Docker (skipped)" +} + +Write-Step "dotnet test (wire-backend integration)" +$env:OTOPCUA_FOCAS_SIM_PROFILE = $Profile + +dotnet test (Join-Path $integTests "ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests.csproj") ` + --configuration $Configuration --no-build --nologo --verbosity minimal +$testExit = $LASTEXITCODE + +foreach ($c in $cleanupScripts) { & $c } + +if ($testExit -ne 0) { + Write-Fail "integration tests failed with exit $testExit" + exit $testExit +} + +Write-Host "" +Write-Host "run-focas.ps1 completed successfully." -ForegroundColor Green +exit 0 diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/Commands/ProbeCommand.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/Commands/ProbeCommand.cs index f0e6ef9..e76c799 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/Commands/ProbeCommand.cs +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/Commands/ProbeCommand.cs @@ -5,11 +5,11 @@ using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common; namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli.Commands; /// -/// Probes a Fanuc CNC: opens a FOCAS session + reads one PMC address. No public -/// simulator exists — this command only produces meaningful results against a real -/// CNC with Fwlib32.dll present. Against a dev box it surfaces -/// BadCommunicationError (DLL missing) which is still a useful signal that -/// the CLI wire-up is correct. +/// Probes a Fanuc CNC: opens a FOCAS session + reads one PMC address. Uses the managed +/// WireFocasClient on TCP:8193. Against an unreachable endpoint it surfaces +/// BadCommunicationError which is still a useful signal that the CLI wire-up is +/// correct. Also runs cleanly against the focas-mock Docker fixture in +/// tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/. /// [Command("probe", Description = "Verify the CNC is reachable + a sample FOCAS read succeeds.")] public sealed class ProbeCommand : FocasCommandBase diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/FocasCommandBase.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/FocasCommandBase.cs index a8413b4..89ea96f 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/FocasCommandBase.cs +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/FocasCommandBase.cs @@ -38,10 +38,9 @@ public abstract class FocasCommandBase : DriverCommandBase /// /// Build a with the CNC target this base collected - /// + the tag list a subclass supplies. Probe disabled; the default - /// attempts Fwlib32.dll P/Invoke, which - /// throws at first call when the DLL is absent — - /// surfaced through the driver as BadCommunicationError. + /// + the tag list a subclass supplies. Probe disabled; the driver's default managed + /// wire client opens a TCP:8193 session to the CNC and surfaces unreachable endpoints + /// as BadCommunicationError. /// protected FocasDriverOptions BuildOptions(IReadOnlyList tags) => new() { diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/Program.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/Program.cs index b94257d..9ebe0d4 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/Program.cs +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/Program.cs @@ -4,9 +4,9 @@ return await new CliApplicationBuilder() .AddCommandsFromThisAssembly() .SetExecutableName("otopcua-focas-cli") .SetDescription( - "OtOpcUa FOCAS test-client — ad-hoc probe + PMC/param/macro reads/writes + polled " + - "subscriptions against Fanuc CNCs via the FOCAS/2 protocol. Requires a real CNC + a " + - "licensed Fwlib32.dll on PATH (or next to the executable) — no public simulator " + - "exists. Addresses use FocasAddressParser syntax: R100, X0.0, PARAM:1815/0, MACRO:500.") + "OtOpcUa FOCAS test-client — ad-hoc probe + PMC/param/macro reads + polled " + + "subscriptions against Fanuc CNCs via the FOCAS/2 protocol. Uses the managed " + + "WireFocasClient on TCP:8193 directly; no native dependencies. Addresses use " + + "FocasAddressParser syntax: R100, X0.0, PARAM:1815/0, MACRO:500.") .Build() .RunAsync(args); diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli.csproj b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli.csproj index 76520db..23d28bf 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli.csproj +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli.csproj @@ -22,4 +22,7 @@ + + diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Backend/FakeFocasBackend.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Backend/FakeFocasBackend.cs deleted file mode 100644 index 3f4bedf..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Backend/FakeFocasBackend.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using MessagePack; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Backend; - -/// -/// In-memory for tests + an operational stub mode when -/// OTOPCUA_FOCAS_BACKEND=fake. Keeps per-address values keyed by a canonical -/// string; RMW semantics honor PMC bit-writes against the containing byte so the -/// PmcBitWriteRequest path can be exercised end-to-end without hardware. -/// -public sealed class FakeFocasBackend : IFocasBackend -{ - private readonly object _gate = new(); - private long _nextSessionId; - private readonly HashSet _openSessions = []; - private readonly Dictionary _pmcValues = []; - private readonly Dictionary _paramValues = []; - private readonly Dictionary _macroValues = []; - - public Task OpenSessionAsync(OpenSessionRequest request, CancellationToken ct) - { - lock (_gate) - { - var id = ++_nextSessionId; - _openSessions.Add(id); - return Task.FromResult(new OpenSessionResponse { Success = true, SessionId = id }); - } - } - - public Task CloseSessionAsync(CloseSessionRequest request, CancellationToken ct) - { - lock (_gate) { _openSessions.Remove(request.SessionId); } - return Task.CompletedTask; - } - - public Task ReadAsync(ReadRequest request, CancellationToken ct) - { - lock (_gate) - { - if (!_openSessions.Contains(request.SessionId)) - return Task.FromResult(new ReadResponse { Success = false, StatusCode = 0x80020000u, Error = "session-not-open" }); - - var store = StoreFor(request.Address.Kind); - var key = CanonicalKey(request.Address); - store.TryGetValue(key, out var value); - return Task.FromResult(new ReadResponse - { - Success = true, - StatusCode = 0, - ValueBytes = value ?? MessagePackSerializer.Serialize((int)0), - ValueTypeCode = request.DataType, - SourceTimestampUtcUnixMs = System.DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), - }); - } - } - - public Task WriteAsync(WriteRequest request, CancellationToken ct) - { - lock (_gate) - { - if (!_openSessions.Contains(request.SessionId)) - return Task.FromResult(new WriteResponse { Success = false, StatusCode = 0x80020000u, Error = "session-not-open" }); - - var store = StoreFor(request.Address.Kind); - store[CanonicalKey(request.Address)] = request.ValueBytes ?? []; - return Task.FromResult(new WriteResponse { Success = true, StatusCode = 0 }); - } - } - - public Task PmcBitWriteAsync(PmcBitWriteRequest request, CancellationToken ct) - { - lock (_gate) - { - if (!_openSessions.Contains(request.SessionId)) - return Task.FromResult(new PmcBitWriteResponse { Success = false, StatusCode = 0x80020000u, Error = "session-not-open" }); - if (request.BitIndex is < 0 or > 7) - return Task.FromResult(new PmcBitWriteResponse { Success = false, StatusCode = 0x803C0000u, Error = "bit-out-of-range" }); - - var key = CanonicalKey(request.Address); - _pmcValues.TryGetValue(key, out var current); - current ??= MessagePackSerializer.Serialize((byte)0); - var b = MessagePackSerializer.Deserialize(current); - var mask = (byte)(1 << request.BitIndex); - b = request.Value ? (byte)(b | mask) : (byte)(b & ~mask); - _pmcValues[key] = MessagePackSerializer.Serialize(b); - return Task.FromResult(new PmcBitWriteResponse { Success = true, StatusCode = 0 }); - } - } - - public Task ProbeAsync(ProbeRequest request, CancellationToken ct) - { - lock (_gate) - { - return Task.FromResult(new ProbeResponse - { - Healthy = _openSessions.Contains(request.SessionId), - ObservedAtUtcUnixMs = System.DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), - }); - } - } - - private Dictionary StoreFor(int kind) => kind switch - { - 0 => _pmcValues, - 1 => _paramValues, - 2 => _macroValues, - _ => _pmcValues, - }; - - private static string CanonicalKey(FocasAddressDto addr) => - addr.Kind switch - { - 0 => $"{addr.PmcLetter}{addr.Number}", - 1 => $"P{addr.Number}", - 2 => $"M{addr.Number}", - _ => $"?{addr.Number}", - }; -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Backend/IFocasBackend.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Backend/IFocasBackend.cs deleted file mode 100644 index 4176f08..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Backend/IFocasBackend.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Backend; - -/// -/// The Host's view of a FOCAS session. One implementation wraps the real -/// Fwlib32.dll via P/Invoke (lands with the real Fwlib32 integration follow-up, -/// since no hardware is available today); a second implementation — -/// — is used by tests. -/// Both live on .NET 4.8 x86 so the Host can be deployed in either mode without -/// changing the pipe server. -/// Invoked via FwlibFrameHandler in the Ipc namespace. -/// -public interface IFocasBackend -{ - Task OpenSessionAsync(OpenSessionRequest request, CancellationToken ct); - Task CloseSessionAsync(CloseSessionRequest request, CancellationToken ct); - Task ReadAsync(ReadRequest request, CancellationToken ct); - Task WriteAsync(WriteRequest request, CancellationToken ct); - Task PmcBitWriteAsync(PmcBitWriteRequest request, CancellationToken ct); - Task ProbeAsync(ProbeRequest request, CancellationToken ct); -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Backend/UnconfiguredFocasBackend.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Backend/UnconfiguredFocasBackend.cs deleted file mode 100644 index 4889739..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Backend/UnconfiguredFocasBackend.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Backend; - -/// -/// Safe default when the deployment hasn't configured a real Fwlib32 backend. -/// Returns structured failure responses instead of throwing so the Proxy can map the -/// error to BadDeviceFailure and surface a clear operator message pointing at -/// docs/v2/focas-deployment.md. Used when OTOPCUA_FOCAS_BACKEND is unset -/// or set to unconfigured. -/// -public sealed class UnconfiguredFocasBackend : IFocasBackend -{ - private const uint BadDeviceFailure = 0x80550000u; - private const string Reason = - "FOCAS Host is running without a real Fwlib32 backend. Set OTOPCUA_FOCAS_BACKEND=fwlib32 " + - "and ensure Fwlib32.dll is on PATH — see docs/v2/focas-deployment.md."; - - public Task OpenSessionAsync(OpenSessionRequest request, CancellationToken ct) => - Task.FromResult(new OpenSessionResponse { Success = false, Error = Reason, ErrorCode = "NoFwlibBackend" }); - - public Task CloseSessionAsync(CloseSessionRequest request, CancellationToken ct) => Task.CompletedTask; - - public Task ReadAsync(ReadRequest request, CancellationToken ct) => - Task.FromResult(new ReadResponse { Success = false, StatusCode = BadDeviceFailure, Error = Reason }); - - public Task WriteAsync(WriteRequest request, CancellationToken ct) => - Task.FromResult(new WriteResponse { Success = false, StatusCode = BadDeviceFailure, Error = Reason }); - - public Task PmcBitWriteAsync(PmcBitWriteRequest request, CancellationToken ct) => - Task.FromResult(new PmcBitWriteResponse { Success = false, StatusCode = BadDeviceFailure, Error = Reason }); - - public Task ProbeAsync(ProbeRequest request, CancellationToken ct) => - Task.FromResult(new ProbeResponse { Healthy = false, Error = Reason, ObservedAtUtcUnixMs = System.DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() }); -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/FwlibFrameHandler.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/FwlibFrameHandler.cs deleted file mode 100644 index d6e8ecf..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/FwlibFrameHandler.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using MessagePack; -using Serilog; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Backend; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Ipc; - -/// -/// Real FOCAS frame handler. Deserializes each request DTO, delegates to -/// , re-serializes the response. The backend owns the -/// Fwlib32 handle + STA thread — the handler is pure dispatch. -/// -public sealed class FwlibFrameHandler : IFrameHandler -{ - private readonly IFocasBackend _backend; - private readonly ILogger _logger; - - public FwlibFrameHandler(IFocasBackend backend, ILogger logger) - { - _backend = backend ?? throw new ArgumentNullException(nameof(backend)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task HandleAsync(FocasMessageKind kind, byte[] body, FrameWriter writer, CancellationToken ct) - { - try - { - switch (kind) - { - case FocasMessageKind.Heartbeat: - { - var hb = MessagePackSerializer.Deserialize(body); - await writer.WriteAsync(FocasMessageKind.HeartbeatAck, - new HeartbeatAck - { - MonotonicTicks = hb.MonotonicTicks, - HostUtcUnixMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), - }, ct).ConfigureAwait(false); - return; - } - - case FocasMessageKind.OpenSessionRequest: - { - var req = MessagePackSerializer.Deserialize(body); - var resp = await _backend.OpenSessionAsync(req, ct).ConfigureAwait(false); - await writer.WriteAsync(FocasMessageKind.OpenSessionResponse, resp, ct).ConfigureAwait(false); - return; - } - - case FocasMessageKind.CloseSessionRequest: - { - var req = MessagePackSerializer.Deserialize(body); - await _backend.CloseSessionAsync(req, ct).ConfigureAwait(false); - return; - } - - case FocasMessageKind.ReadRequest: - { - var req = MessagePackSerializer.Deserialize(body); - var resp = await _backend.ReadAsync(req, ct).ConfigureAwait(false); - await writer.WriteAsync(FocasMessageKind.ReadResponse, resp, ct).ConfigureAwait(false); - return; - } - - case FocasMessageKind.WriteRequest: - { - var req = MessagePackSerializer.Deserialize(body); - var resp = await _backend.WriteAsync(req, ct).ConfigureAwait(false); - await writer.WriteAsync(FocasMessageKind.WriteResponse, resp, ct).ConfigureAwait(false); - return; - } - - case FocasMessageKind.PmcBitWriteRequest: - { - var req = MessagePackSerializer.Deserialize(body); - var resp = await _backend.PmcBitWriteAsync(req, ct).ConfigureAwait(false); - await writer.WriteAsync(FocasMessageKind.PmcBitWriteResponse, resp, ct).ConfigureAwait(false); - return; - } - - case FocasMessageKind.ProbeRequest: - { - var req = MessagePackSerializer.Deserialize(body); - var resp = await _backend.ProbeAsync(req, ct).ConfigureAwait(false); - await writer.WriteAsync(FocasMessageKind.ProbeResponse, resp, ct).ConfigureAwait(false); - return; - } - - default: - await writer.WriteAsync(FocasMessageKind.ErrorResponse, - new ErrorResponse { Code = "unknown-kind", Message = $"Kind {kind} is not handled by the Host" }, - ct).ConfigureAwait(false); - return; - } - } - catch (OperationCanceledException) { throw; } - catch (Exception ex) - { - _logger.Error(ex, "FwlibFrameHandler error processing {Kind}", kind); - await writer.WriteAsync(FocasMessageKind.ErrorResponse, - new ErrorResponse { Code = "backend-exception", Message = ex.Message }, - ct).ConfigureAwait(false); - } - } - - public IDisposable AttachConnection(FrameWriter writer) => IFrameHandler.NoopAttachment.Instance; -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/IFrameHandler.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/IFrameHandler.cs deleted file mode 100644 index 75cef97..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/IFrameHandler.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Ipc; - -/// -/// Dispatches a single IPC frame to the backend. Implementations own the FOCAS session -/// state and translate request DTOs into Fwlib32 calls. -/// -public interface IFrameHandler -{ - Task HandleAsync(FocasMessageKind kind, byte[] body, FrameWriter writer, CancellationToken ct); - - /// - /// Called once per accepted connection after the Hello handshake. Lets the handler - /// attach server-pushed event sinks (data-change notifications, runtime-status - /// changes) to the connection's . Returns an - /// the pipe server disposes when the connection closes — - /// backends use it to unsubscribe from their push sources. - /// - IDisposable AttachConnection(FrameWriter writer); - - public sealed class NoopAttachment : IDisposable - { - public static readonly NoopAttachment Instance = new(); - public void Dispose() { } - } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/PipeAcl.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/PipeAcl.cs deleted file mode 100644 index aac29a1..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/PipeAcl.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.IO.Pipes; -using System.Security.AccessControl; -using System.Security.Principal; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Ipc; - -/// -/// Builds the for the FOCAS Host pipe. Same pattern as -/// Galaxy.Host: only the configured OtOpcUa server principal SID gets -/// ReadWrite | Synchronize; LocalSystem + Administrators are explicitly denied -/// so a compromised service account on the same host can't escalate via the pipe. -/// -public static class PipeAcl -{ - public static PipeSecurity Create(SecurityIdentifier allowedSid) - { - if (allowedSid is null) throw new ArgumentNullException(nameof(allowedSid)); - - var security = new PipeSecurity(); - - security.AddAccessRule(new PipeAccessRule( - allowedSid, - PipeAccessRights.ReadWrite | PipeAccessRights.Synchronize, - AccessControlType.Allow)); - - var localSystem = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null); - var admins = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null); - - if (allowedSid != localSystem) - security.AddAccessRule(new PipeAccessRule(localSystem, PipeAccessRights.FullControl, AccessControlType.Deny)); - if (allowedSid != admins) - security.AddAccessRule(new PipeAccessRule(admins, PipeAccessRights.FullControl, AccessControlType.Deny)); - - security.SetOwner(allowedSid); - - return security; - } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/PipeServer.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/PipeServer.cs deleted file mode 100644 index 582870e..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/PipeServer.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System; -using System.IO.Pipes; -using System.Security.Principal; -using System.Threading; -using System.Threading.Tasks; -using MessagePack; -using Serilog; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Ipc; - -/// -/// Accepts one client connection at a time on the FOCAS Host's named pipe with the -/// strict ACL from . Verifies the peer SID + per-process shared -/// secret before any RPC frame is accepted. Mirrors the Galaxy.Host pipe server byte for -/// byte — different MessageKind enum, same negotiation semantics. -/// -public sealed class PipeServer : IDisposable -{ - private readonly string _pipeName; - private readonly SecurityIdentifier _allowedSid; - private readonly string _sharedSecret; - private readonly ILogger _logger; - private readonly CancellationTokenSource _cts = new(); - private NamedPipeServerStream? _current; - - public PipeServer(string pipeName, SecurityIdentifier allowedSid, string sharedSecret, ILogger logger) - { - _pipeName = pipeName ?? throw new ArgumentNullException(nameof(pipeName)); - _allowedSid = allowedSid ?? throw new ArgumentNullException(nameof(allowedSid)); - _sharedSecret = sharedSecret ?? throw new ArgumentNullException(nameof(sharedSecret)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task RunOneConnectionAsync(IFrameHandler handler, CancellationToken ct) - { - using var linked = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, ct); - var acl = PipeAcl.Create(_allowedSid); - - _current = new NamedPipeServerStream( - _pipeName, - PipeDirection.InOut, - maxNumberOfServerInstances: 1, - PipeTransmissionMode.Byte, - PipeOptions.Asynchronous, - inBufferSize: 64 * 1024, - outBufferSize: 64 * 1024, - pipeSecurity: acl); - - try - { - await _current.WaitForConnectionAsync(linked.Token).ConfigureAwait(false); - - if (!VerifyCaller(_current, out var reason)) - { - _logger.Warning("FOCAS IPC caller rejected: {Reason}", reason); - _current.Disconnect(); - return; - } - - using var reader = new FrameReader(_current, leaveOpen: true); - using var writer = new FrameWriter(_current, leaveOpen: true); - - var first = await reader.ReadFrameAsync(linked.Token).ConfigureAwait(false); - if (first is null || first.Value.Kind != FocasMessageKind.Hello) - { - _logger.Warning("FOCAS IPC first frame was not Hello; dropping"); - return; - } - - var hello = MessagePackSerializer.Deserialize(first.Value.Body); - if (!string.Equals(hello.SharedSecret, _sharedSecret, StringComparison.Ordinal)) - { - await writer.WriteAsync(FocasMessageKind.HelloAck, - new HelloAck { Accepted = false, RejectReason = "shared-secret-mismatch" }, - linked.Token).ConfigureAwait(false); - _logger.Warning("FOCAS IPC Hello rejected: shared-secret-mismatch"); - return; - } - - if (hello.ProtocolMajor != Hello.CurrentMajor) - { - await writer.WriteAsync(FocasMessageKind.HelloAck, - new HelloAck - { - Accepted = false, - RejectReason = $"major-version-mismatch-peer={hello.ProtocolMajor}-server={Hello.CurrentMajor}", - }, - linked.Token).ConfigureAwait(false); - _logger.Warning("FOCAS IPC Hello rejected: major mismatch peer={Peer} server={Server}", - hello.ProtocolMajor, Hello.CurrentMajor); - return; - } - - await writer.WriteAsync(FocasMessageKind.HelloAck, - new HelloAck { Accepted = true, HostName = Environment.MachineName }, - linked.Token).ConfigureAwait(false); - - using var attachment = handler.AttachConnection(writer); - - while (!linked.Token.IsCancellationRequested) - { - var frame = await reader.ReadFrameAsync(linked.Token).ConfigureAwait(false); - if (frame is null) break; - - await handler.HandleAsync(frame.Value.Kind, frame.Value.Body, writer, linked.Token).ConfigureAwait(false); - } - } - finally - { - _current.Dispose(); - _current = null; - } - } - - public async Task RunAsync(IFrameHandler handler, CancellationToken ct) - { - while (!ct.IsCancellationRequested) - { - try { await RunOneConnectionAsync(handler, ct).ConfigureAwait(false); } - catch (OperationCanceledException) { break; } - catch (Exception ex) { _logger.Error(ex, "FOCAS IPC connection loop error — accepting next"); } - } - } - - private bool VerifyCaller(NamedPipeServerStream pipe, out string reason) - { - try - { - pipe.RunAsClient(() => - { - using var wi = WindowsIdentity.GetCurrent(); - if (wi.User is null) - throw new InvalidOperationException("GetCurrent().User is null — cannot verify caller"); - if (wi.User != _allowedSid) - throw new UnauthorizedAccessException( - $"caller SID {wi.User.Value} does not match allowed {_allowedSid.Value}"); - }); - reason = string.Empty; - return true; - } - catch (Exception ex) { reason = ex.Message; return false; } - } - - public void Dispose() - { - _cts.Cancel(); - _current?.Dispose(); - _cts.Dispose(); - } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/StubFrameHandler.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/StubFrameHandler.cs deleted file mode 100644 index 2c28dac..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Ipc/StubFrameHandler.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using MessagePack; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Ipc; - -/// -/// Placeholder handler that returns ErrorResponse{Code=not-implemented} for every -/// FOCAS data-plane request. Exists so PR B can ship the pipe server + ACL + handshake -/// plumbing before PR C moves the Fwlib32 calls. Heartbeats are handled fully so the -/// supervisor's liveness detector stays happy. -/// -public sealed class StubFrameHandler : IFrameHandler -{ - public Task HandleAsync(FocasMessageKind kind, byte[] body, FrameWriter writer, CancellationToken ct) - { - if (kind == FocasMessageKind.Heartbeat) - { - var hb = MessagePackSerializer.Deserialize(body); - return writer.WriteAsync(FocasMessageKind.HeartbeatAck, - new HeartbeatAck - { - MonotonicTicks = hb.MonotonicTicks, - HostUtcUnixMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), - }, ct); - } - - return writer.WriteAsync(FocasMessageKind.ErrorResponse, - new ErrorResponse - { - Code = "not-implemented", - Message = $"Kind {kind} is stubbed — Fwlib32 lift lands in PR C", - }, - ct); - } - - public IDisposable AttachConnection(FrameWriter writer) => IFrameHandler.NoopAttachment.Instance; -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Program.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Program.cs deleted file mode 100644 index 05ab13e..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Program.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Security.Principal; -using System.Threading; -using Serilog; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Backend; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Ipc; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host; - -/// -/// Entry point for the OtOpcUaFocasHost Windows service / console host. The -/// supervisor (Proxy-side) spawns this process per FOCAS driver instance and passes the -/// pipe name, allowed-SID, and per-process shared secret as environment variables. In -/// PR B the backend is — PR C swaps in the real -/// Fwlib32-backed handler once the session state + STA thread move out of the .NET 10 -/// driver. -/// -public static class Program -{ - public static int Main(string[] args) - { - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Information() - .WriteTo.File( - @"%ProgramData%\OtOpcUa\focas-host-.log".Replace("%ProgramData%", Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)), - rollingInterval: RollingInterval.Day) - .CreateLogger(); - - try - { - var pipeName = Environment.GetEnvironmentVariable("OTOPCUA_FOCAS_PIPE") ?? "OtOpcUaFocas"; - var allowedSidValue = Environment.GetEnvironmentVariable("OTOPCUA_ALLOWED_SID") - ?? throw new InvalidOperationException( - "OTOPCUA_ALLOWED_SID not set — the FOCAS Proxy supervisor must pass the server principal SID"); - var sharedSecret = Environment.GetEnvironmentVariable("OTOPCUA_FOCAS_SECRET") - ?? throw new InvalidOperationException( - "OTOPCUA_FOCAS_SECRET not set — the FOCAS Proxy supervisor must pass the per-process secret at spawn time"); - - var allowedSid = new SecurityIdentifier(allowedSidValue); - - using var server = new PipeServer(pipeName, allowedSid, sharedSecret, Log.Logger); - using var cts = new CancellationTokenSource(); - Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); }; - - Log.Information("OtOpcUaFocasHost starting — pipe={Pipe} allowedSid={Sid}", - pipeName, allowedSidValue); - - var backendKind = (Environment.GetEnvironmentVariable("OTOPCUA_FOCAS_BACKEND") ?? "unconfigured") - .ToLowerInvariant(); - IFocasBackend backend = backendKind switch - { - "fake" => new FakeFocasBackend(), - "unconfigured" => new UnconfiguredFocasBackend(), - "fwlib32" => new UnconfiguredFocasBackend(), // real Fwlib32 backend lands with hardware integration follow-up - _ => new UnconfiguredFocasBackend(), - }; - Log.Information("OtOpcUaFocasHost backend={Backend}", backendKind); - - var handler = new FwlibFrameHandler(backend, Log.Logger); - server.RunAsync(handler, cts.Token).GetAwaiter().GetResult(); - - Log.Information("OtOpcUaFocasHost stopped cleanly"); - return 0; - } - catch (Exception ex) - { - Log.Fatal(ex, "OtOpcUaFocasHost fatal"); - return 2; - } - finally { Log.CloseAndFlush(); } - } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Stability/PostMortemMmf.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Stability/PostMortemMmf.cs deleted file mode 100644 index 90e7e9b..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/Stability/PostMortemMmf.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.IO; -using System.IO.MemoryMappedFiles; -using System.Text; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Stability; - -/// -/// Ring-buffer of the last N IPC operations, written into a memory-mapped file. On a -/// hard crash the Proxy-side supervisor reads the MMF after the corpse is gone to see -/// what was in flight at the moment the Host died. Single-writer (the Host), multi-reader -/// (the supervisor) — the file format is identical to the Galaxy Tier-C -/// PostMortemMmf so a single reader tool can work both. -/// -/// -/// File layout: -/// -/// [16-byte header: magic(4) | version(4) | capacity(4) | writeIndex(4)] -/// [capacity × 256-byte entries: each is [8-byte utcUnixMs | 8-byte opKind | 240-byte UTF-8 message]] -/// -/// Magic is 'OFPC' (0x4F46_5043) to distinguish a FOCAS file from the Galaxy MMF. -/// -public sealed class PostMortemMmf : IDisposable -{ - private const int Magic = 0x4F465043; // 'OFPC' - private const int Version = 1; - private const int HeaderBytes = 16; - public const int EntryBytes = 256; - private const int MessageOffset = 16; - private const int MessageCapacity = EntryBytes - MessageOffset; - - public int Capacity { get; } - public string Path { get; } - - private readonly MemoryMappedFile _mmf; - private readonly MemoryMappedViewAccessor _accessor; - private readonly object _writeGate = new(); - - public PostMortemMmf(string path, int capacity = 1000) - { - if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity)); - Capacity = capacity; - Path = path; - - var fileBytes = HeaderBytes + capacity * EntryBytes; - Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)!); - - var fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read); - fs.SetLength(fileBytes); - _mmf = MemoryMappedFile.CreateFromFile(fs, null, fileBytes, - MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, leaveOpen: false); - _accessor = _mmf.CreateViewAccessor(0, fileBytes, MemoryMappedFileAccess.ReadWrite); - - if (_accessor.ReadInt32(0) != Magic) - { - _accessor.Write(0, Magic); - _accessor.Write(4, Version); - _accessor.Write(8, capacity); - _accessor.Write(12, 0); - } - } - - public void Write(long opKind, string message) - { - lock (_writeGate) - { - var idx = _accessor.ReadInt32(12); - var offset = HeaderBytes + idx * EntryBytes; - - _accessor.Write(offset + 0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()); - _accessor.Write(offset + 8, opKind); - - var msgBytes = Encoding.UTF8.GetBytes(message ?? string.Empty); - var copy = Math.Min(msgBytes.Length, MessageCapacity - 1); - _accessor.WriteArray(offset + MessageOffset, msgBytes, 0, copy); - _accessor.Write(offset + MessageOffset + copy, (byte)0); - - var next = (idx + 1) % Capacity; - _accessor.Write(12, next); - } - } - - public PostMortemEntry[] ReadAll() - { - var magic = _accessor.ReadInt32(0); - if (magic != Magic) return new PostMortemEntry[0]; - - var capacity = _accessor.ReadInt32(8); - var writeIndex = _accessor.ReadInt32(12); - - var entries = new PostMortemEntry[capacity]; - var count = 0; - for (var i = 0; i < capacity; i++) - { - var slot = (writeIndex + i) % capacity; - var offset = HeaderBytes + slot * EntryBytes; - - var ts = _accessor.ReadInt64(offset + 0); - if (ts == 0) continue; - - var op = _accessor.ReadInt64(offset + 8); - var msgBuf = new byte[MessageCapacity]; - _accessor.ReadArray(offset + MessageOffset, msgBuf, 0, MessageCapacity); - var nulTerm = Array.IndexOf(msgBuf, 0); - var msg = Encoding.UTF8.GetString(msgBuf, 0, nulTerm < 0 ? MessageCapacity : nulTerm); - - entries[count++] = new PostMortemEntry(ts, op, msg); - } - - Array.Resize(ref entries, count); - return entries; - } - - public void Dispose() - { - _accessor.Dispose(); - _mmf.Dispose(); - } -} - -public readonly struct PostMortemEntry -{ - public long UtcUnixMs { get; } - public long OpKind { get; } - public string Message { get; } - - public PostMortemEntry(long utcUnixMs, long opKind, string message) - { - UtcUnixMs = utcUnixMs; - OpKind = opKind; - Message = message; - } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.csproj b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.csproj deleted file mode 100644 index b9682f6..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - Exe - net48 - - x86 - true - enable - latest - true - true - $(NoWarn);CS1591 - ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host - OtOpcUa.Driver.FOCAS.Host - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Addresses.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Addresses.cs deleted file mode 100644 index ca443a1..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Addresses.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MessagePack; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -/// -/// Wire shape for a parsed FOCAS address. Mirrors FocasAddress in the driver -/// package but lives in Shared so the Host (.NET 4.8) can decode without taking a -/// reference to the .NET 10 driver assembly. The Proxy serializes from its own -/// FocasAddress; the Host maps back to its local equivalent. -/// -[MessagePackObject] -public sealed class FocasAddressDto -{ - /// 0 = Pmc, 1 = Parameter, 2 = Macro. Matches FocasAreaKind enum order. - [Key(0)] public int Kind { get; set; } - - /// PMC letter — null for Parameter / Macro. - [Key(1)] public string? PmcLetter { get; set; } - - [Key(2)] public int Number { get; set; } - - /// Optional bit index (0-7 for PMC, 0-31 for Parameter). - [Key(3)] public int? BitIndex { get; set; } -} - -/// -/// 0 = Bit, 1 = Byte, 2 = Int16, 3 = Int32, 4 = Float32, 5 = Float64, 6 = String. -/// Matches FocasDataType enum order so both sides can cast (int). -/// -public static class FocasDataTypeCode -{ - public const int Bit = 0; - public const int Byte = 1; - public const int Int16 = 2; - public const int Int32 = 3; - public const int Float32 = 4; - public const int Float64 = 5; - public const int String = 6; -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Framing.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Framing.cs deleted file mode 100644 index fe2b979..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Framing.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -/// -/// Length-prefixed framing. Each IPC frame is: -/// [4-byte big-endian length][1-byte message kind][MessagePack body]. -/// Length is the body size only; the kind byte is not part of the prefixed length. -/// Mirrors the Galaxy Tier-C framing so operators see one wire format across hosts. -/// -public static class Framing -{ - public const int LengthPrefixSize = 4; - public const int KindByteSize = 1; - - /// - /// Maximum permitted body length (16 MiB). Protects the receiver from a hostile or - /// misbehaving peer sending an oversized length prefix. - /// - public const int MaxFrameBodyBytes = 16 * 1024 * 1024; -} - -/// -/// Wire identifier for each contract. Values are stable — new contracts append, never -/// reuse. Ranges kept aligned with Galaxy so an operator reading a hex dump doesn't have -/// to context-switch between drivers. -/// -public enum FocasMessageKind : byte -{ - Hello = 0x01, - HelloAck = 0x02, - Heartbeat = 0x03, - HeartbeatAck = 0x04, - - OpenSessionRequest = 0x10, - OpenSessionResponse = 0x11, - CloseSessionRequest = 0x12, - - ReadRequest = 0x30, - ReadResponse = 0x31, - WriteRequest = 0x32, - WriteResponse = 0x33, - PmcBitWriteRequest = 0x34, - PmcBitWriteResponse = 0x35, - - SubscribeRequest = 0x40, - SubscribeResponse = 0x41, - UnsubscribeRequest = 0x42, - OnDataChangeNotification = 0x43, - - ProbeRequest = 0x70, - ProbeResponse = 0x71, - RuntimeStatusChange = 0x72, - - RecycleHostRequest = 0xF0, - RecycleStatusResponse = 0xF1, - - ErrorResponse = 0xFE, -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Hello.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Hello.cs deleted file mode 100644 index 716bb1a..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Hello.cs +++ /dev/null @@ -1,63 +0,0 @@ -using MessagePack; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -/// -/// First frame of every FOCAS Proxy -> Host connection. Advertises protocol major/minor -/// and the per-process shared secret the Proxy passed to the Host at spawn time. Major -/// mismatch is fatal; minor is advisory. -/// -[MessagePackObject] -public sealed class Hello -{ - public const int CurrentMajor = 1; - public const int CurrentMinor = 0; - - [Key(0)] public int ProtocolMajor { get; set; } = CurrentMajor; - [Key(1)] public int ProtocolMinor { get; set; } = CurrentMinor; - [Key(2)] public string PeerName { get; set; } = string.Empty; - - /// - /// Per-process shared secret verified on the Host side against the value passed by the - /// supervisor at spawn time. Protects against a local attacker connecting to the pipe - /// after authenticating via the pipe ACL. - /// - [Key(3)] public string SharedSecret { get; set; } = string.Empty; - - [Key(4)] public string[] Features { get; set; } = System.Array.Empty(); -} - -[MessagePackObject] -public sealed class HelloAck -{ - [Key(0)] public int ProtocolMajor { get; set; } = Hello.CurrentMajor; - [Key(1)] public int ProtocolMinor { get; set; } = Hello.CurrentMinor; - - /// True if the Host accepted the hello; false + filled if not. - [Key(2)] public bool Accepted { get; set; } - [Key(3)] public string? RejectReason { get; set; } - - [Key(4)] public string HostName { get; set; } = string.Empty; -} - -[MessagePackObject] -public sealed class Heartbeat -{ - [Key(0)] public long MonotonicTicks { get; set; } -} - -[MessagePackObject] -public sealed class HeartbeatAck -{ - [Key(0)] public long MonotonicTicks { get; set; } - [Key(1)] public long HostUtcUnixMs { get; set; } -} - -[MessagePackObject] -public sealed class ErrorResponse -{ - /// Stable symbolic code — e.g. InvalidAddress, SessionNotFound, Fwlib32Crashed. - [Key(0)] public string Code { get; set; } = string.Empty; - - [Key(1)] public string Message { get; set; } = string.Empty; -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Probe.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Probe.cs deleted file mode 100644 index 151f9d7..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Probe.cs +++ /dev/null @@ -1,47 +0,0 @@ -using MessagePack; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -/// Lightweight connectivity probe — maps to cnc_rdcncstat on the Host. -[MessagePackObject] -public sealed class ProbeRequest -{ - [Key(0)] public long SessionId { get; set; } - [Key(1)] public int TimeoutMs { get; set; } = 2000; -} - -[MessagePackObject] -public sealed class ProbeResponse -{ - [Key(0)] public bool Healthy { get; set; } - [Key(1)] public string? Error { get; set; } - [Key(2)] public long ObservedAtUtcUnixMs { get; set; } -} - -/// Per-host runtime status — fan-out target when the Host observes the CNC going unreachable without the Proxy asking. -[MessagePackObject] -public sealed class RuntimeStatusChangeNotification -{ - [Key(0)] public long SessionId { get; set; } - - /// Running | Stopped | Unknown. - [Key(1)] public string RuntimeStatus { get; set; } = string.Empty; - - [Key(2)] public long ObservedAtUtcUnixMs { get; set; } -} - -[MessagePackObject] -public sealed class RecycleHostRequest -{ - /// Soft | Hard. Soft drains subscriptions first; Hard kills immediately. - [Key(0)] public string Kind { get; set; } = "Soft"; - [Key(1)] public string Reason { get; set; } = string.Empty; -} - -[MessagePackObject] -public sealed class RecycleStatusResponse -{ - [Key(0)] public bool Accepted { get; set; } - [Key(1)] public int GraceSeconds { get; set; } = 15; - [Key(2)] public string? Error { get; set; } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/ReadWrite.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/ReadWrite.cs deleted file mode 100644 index 3f3e7ae..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/ReadWrite.cs +++ /dev/null @@ -1,85 +0,0 @@ -using MessagePack; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -/// -/// Read one FOCAS address. Multi-read is the Proxy's responsibility — it batches -/// per-tag reads into parallel frames the Host services on its -/// STA thread. Keeping the IPC read single-address keeps the Host side trivial; FOCAS -/// itself has no multi-read primitive that spans area kinds. -/// -[MessagePackObject] -public sealed class ReadRequest -{ - [Key(0)] public long SessionId { get; set; } - [Key(1)] public FocasAddressDto Address { get; set; } = new(); - [Key(2)] public int DataType { get; set; } - [Key(3)] public int TimeoutMs { get; set; } = 2000; -} - -[MessagePackObject] -public sealed class ReadResponse -{ - [Key(0)] public bool Success { get; set; } - [Key(1)] public string? Error { get; set; } - - /// OPC UA status code mapped by the Host via FocasStatusMapper — 0 = Good. - [Key(2)] public uint StatusCode { get; set; } - - /// MessagePack-serialized boxed value. null when is false. - [Key(3)] public byte[]? ValueBytes { get; set; } - - /// Matches so the Proxy knows how to deserialize. - [Key(4)] public int ValueTypeCode { get; set; } - - [Key(5)] public long SourceTimestampUtcUnixMs { get; set; } -} - -[MessagePackObject] -public sealed class WriteRequest -{ - [Key(0)] public long SessionId { get; set; } - [Key(1)] public FocasAddressDto Address { get; set; } = new(); - [Key(2)] public int DataType { get; set; } - [Key(3)] public byte[]? ValueBytes { get; set; } - [Key(4)] public int ValueTypeCode { get; set; } - [Key(5)] public int TimeoutMs { get; set; } = 2000; -} - -[MessagePackObject] -public sealed class WriteResponse -{ - [Key(0)] public bool Success { get; set; } - [Key(1)] public string? Error { get; set; } - - /// OPC UA status code — 0 = Good. - [Key(2)] public uint StatusCode { get; set; } -} - -/// -/// PMC bit read-modify-write. Handled as a first-class operation (not two separate -/// read+write round-trips) so the critical section stays on the Host — serializing -/// concurrent bit writers to the same parent byte is Host-side via -/// SemaphoreSlim keyed on (PmcLetter, Number). Mirrors the in-process -/// pattern from FocasPmcBitRmw. -/// -[MessagePackObject] -public sealed class PmcBitWriteRequest -{ - [Key(0)] public long SessionId { get; set; } - [Key(1)] public FocasAddressDto Address { get; set; } = new(); - - /// The bit index to set/clear. 0-7. - [Key(2)] public int BitIndex { get; set; } - - [Key(3)] public bool Value { get; set; } - [Key(4)] public int TimeoutMs { get; set; } = 2000; -} - -[MessagePackObject] -public sealed class PmcBitWriteResponse -{ - [Key(0)] public bool Success { get; set; } - [Key(1)] public string? Error { get; set; } - [Key(2)] public uint StatusCode { get; set; } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Session.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Session.cs deleted file mode 100644 index fad9126..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Session.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MessagePack; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -/// -/// Open a FOCAS session against the CNC at . One session per -/// configured device. The Host owns the Fwlib32 handle; the Proxy tracks only the -/// opaque returned on success. -/// -[MessagePackObject] -public sealed class OpenSessionRequest -{ - [Key(0)] public string HostAddress { get; set; } = string.Empty; - [Key(1)] public int TimeoutMs { get; set; } = 2000; - [Key(2)] public int CncSeries { get; set; } -} - -[MessagePackObject] -public sealed class OpenSessionResponse -{ - [Key(0)] public bool Success { get; set; } - [Key(1)] public long SessionId { get; set; } - [Key(2)] public string? Error { get; set; } - [Key(3)] public string? ErrorCode { get; set; } -} - -[MessagePackObject] -public sealed class CloseSessionRequest -{ - [Key(0)] public long SessionId { get; set; } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Subscriptions.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Subscriptions.cs deleted file mode 100644 index 9417d2b..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/Contracts/Subscriptions.cs +++ /dev/null @@ -1,61 +0,0 @@ -using MessagePack; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -/// -/// Subscribe the Host to polling a set of tags on behalf of the Proxy. FOCAS is -/// poll-only — there are no CNC-initiated callbacks — so the Host runs the poll loop and -/// pushes frames whenever a value differs from -/// the last observation. Delta-only + per-group interval keeps the wire quiet. -/// -[MessagePackObject] -public sealed class SubscribeRequest -{ - [Key(0)] public long SessionId { get; set; } - [Key(1)] public long SubscriptionId { get; set; } - [Key(2)] public int IntervalMs { get; set; } = 1000; - [Key(3)] public SubscribeItem[] Items { get; set; } = System.Array.Empty(); -} - -[MessagePackObject] -public sealed class SubscribeItem -{ - /// Opaque correlation id the Proxy uses to route notifications back to the right OPC UA MonitoredItem. - [Key(0)] public long MonitoredItemId { get; set; } - - [Key(1)] public FocasAddressDto Address { get; set; } = new(); - [Key(2)] public int DataType { get; set; } -} - -[MessagePackObject] -public sealed class SubscribeResponse -{ - [Key(0)] public bool Success { get; set; } - [Key(1)] public string? Error { get; set; } - - /// Items the Host refused (address mismatch, unsupported type). Empty on full success. - [Key(2)] public long[] RejectedMonitoredItemIds { get; set; } = System.Array.Empty(); -} - -[MessagePackObject] -public sealed class UnsubscribeRequest -{ - [Key(0)] public long SubscriptionId { get; set; } -} - -[MessagePackObject] -public sealed class OnDataChangeNotification -{ - [Key(0)] public long SubscriptionId { get; set; } - [Key(1)] public DataChange[] Changes { get; set; } = System.Array.Empty(); -} - -[MessagePackObject] -public sealed class DataChange -{ - [Key(0)] public long MonitoredItemId { get; set; } - [Key(1)] public uint StatusCode { get; set; } - [Key(2)] public byte[]? ValueBytes { get; set; } - [Key(3)] public int ValueTypeCode { get; set; } - [Key(4)] public long SourceTimestampUtcUnixMs { get; set; } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/FrameReader.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/FrameReader.cs deleted file mode 100644 index 7cb142d..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/FrameReader.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MessagePack; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared; - -/// -/// Reads length-prefixed, kind-tagged frames from a stream. Single-consumer — do not call -/// from multiple threads against the same instance. -/// -public sealed class FrameReader : IDisposable -{ - private readonly Stream _stream; - private readonly bool _leaveOpen; - - public FrameReader(Stream stream, bool leaveOpen = false) - { - _stream = stream ?? throw new ArgumentNullException(nameof(stream)); - _leaveOpen = leaveOpen; - } - - public async Task<(FocasMessageKind Kind, byte[] Body)?> ReadFrameAsync(CancellationToken ct) - { - var lengthPrefix = new byte[Framing.LengthPrefixSize]; - if (!await ReadExactAsync(lengthPrefix, ct).ConfigureAwait(false)) - return null; - - var length = (lengthPrefix[0] << 24) | (lengthPrefix[1] << 16) | (lengthPrefix[2] << 8) | lengthPrefix[3]; - if (length < 0 || length > Framing.MaxFrameBodyBytes) - throw new InvalidDataException($"IPC frame length {length} out of range."); - - var kindByte = _stream.ReadByte(); - if (kindByte < 0) throw new EndOfStreamException("EOF after length prefix, before kind byte."); - - var body = new byte[length]; - if (!await ReadExactAsync(body, ct).ConfigureAwait(false)) - throw new EndOfStreamException("EOF mid-frame."); - - return ((FocasMessageKind)(byte)kindByte, body); - } - - public static T Deserialize(byte[] body) => MessagePackSerializer.Deserialize(body); - - private async Task ReadExactAsync(byte[] buffer, CancellationToken ct) - { - var offset = 0; - while (offset < buffer.Length) - { - var read = await _stream.ReadAsync(buffer, offset, buffer.Length - offset, ct).ConfigureAwait(false); - if (read == 0) - { - if (offset == 0) return false; - throw new EndOfStreamException($"Stream ended after reading {offset} of {buffer.Length} bytes."); - } - offset += read; - } - return true; - } - - public void Dispose() - { - if (!_leaveOpen) _stream.Dispose(); - } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/FrameWriter.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/FrameWriter.cs deleted file mode 100644 index 76b0b19..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/FrameWriter.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MessagePack; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared; - -/// -/// Writes length-prefixed, kind-tagged MessagePack frames to a stream. Thread-safe via -/// — multiple producers (e.g. heartbeat + data-plane sharing a -/// stream) get serialized writes. -/// -public sealed class FrameWriter : IDisposable -{ - private readonly Stream _stream; - private readonly SemaphoreSlim _gate = new(1, 1); - private readonly bool _leaveOpen; - - public FrameWriter(Stream stream, bool leaveOpen = false) - { - _stream = stream ?? throw new ArgumentNullException(nameof(stream)); - _leaveOpen = leaveOpen; - } - - public async Task WriteAsync(FocasMessageKind kind, T message, CancellationToken ct) - { - var body = MessagePackSerializer.Serialize(message, cancellationToken: ct); - if (body.Length > Framing.MaxFrameBodyBytes) - throw new InvalidOperationException( - $"IPC frame body {body.Length} exceeds {Framing.MaxFrameBodyBytes} byte cap."); - - var lengthPrefix = new byte[Framing.LengthPrefixSize]; - lengthPrefix[0] = (byte)((body.Length >> 24) & 0xFF); - lengthPrefix[1] = (byte)((body.Length >> 16) & 0xFF); - lengthPrefix[2] = (byte)((body.Length >> 8) & 0xFF); - lengthPrefix[3] = (byte)( body.Length & 0xFF); - - await _gate.WaitAsync(ct).ConfigureAwait(false); - try - { - await _stream.WriteAsync(lengthPrefix, 0, lengthPrefix.Length, ct).ConfigureAwait(false); - _stream.WriteByte((byte)kind); - await _stream.WriteAsync(body, 0, body.Length, ct).ConfigureAwait(false); - await _stream.FlushAsync(ct).ConfigureAwait(false); - } - finally { _gate.Release(); } - } - - public void Dispose() - { - _gate.Dispose(); - if (!_leaveOpen) _stream.Dispose(); - } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.csproj b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.csproj deleted file mode 100644 index a154f19..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - netstandard2.0 - enable - latest - true - true - $(NoWarn);CS1591 - ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared - - - - - - - - - - - - - diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs index b04c422..3b0100f 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs @@ -16,7 +16,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS; /// fail fast. /// public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery, ISubscribable, - IHostConnectivityProbe, IPerCallHostResolver, IDisposable, IAsyncDisposable + IHostConnectivityProbe, IPerCallHostResolver, IAlarmSource, IDisposable, IAsyncDisposable { private readonly FocasDriverOptions _options; private readonly string _driverInstanceId; @@ -24,10 +24,12 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery, private readonly PollGroupEngine _poll; private readonly Dictionary _devices = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _tagsByName = new(StringComparer.OrdinalIgnoreCase); + private FocasAlarmProjection? _alarmProjection; private DriverHealth _health = new(DriverState.Unknown, null, null); public event EventHandler? OnDataChange; public event EventHandler? OnHostStatusChanged; + public event EventHandler? OnAlarmEvent; public FocasDriver(FocasDriverOptions options, string driverInstanceId, IFocasClientFactory? clientFactory = null) @@ -35,7 +37,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery, ArgumentNullException.ThrowIfNull(options); _options = options; _driverInstanceId = driverInstanceId; - _clientFactory = clientFactory ?? new FwlibFocasClientFactory(); + _clientFactory = clientFactory ?? new Wire.WireFocasClientFactory(); _poll = new PollGroupEngine( reader: ReadAsync, onChange: (handle, tagRef, snapshot) => @@ -85,6 +87,30 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery, _ = Task.Run(() => ProbeLoopAsync(state, ct), ct); } } + + if (_options.HandleRecycle.Enabled) + { + foreach (var state in _devices.Values) + { + state.RecycleCts = new CancellationTokenSource(); + var ct = state.RecycleCts.Token; + _ = Task.Run(() => RecycleLoopAsync(state, ct), ct); + } + } + + if (_options.AlarmProjection.Enabled) + _alarmProjection = new FocasAlarmProjection(this, _options.AlarmProjection.PollInterval); + + if (_options.FixedTree.Enabled) + { + foreach (var state in _devices.Values) + { + state.FixedTreeCts = new CancellationTokenSource(); + var ct = state.FixedTreeCts.Token; + _ = Task.Run(() => FixedTreeLoopAsync(state, ct), ct); + } + } + _health = new DriverHealth(DriverState.Healthy, DateTime.UtcNow, null); } catch (Exception ex) @@ -104,11 +130,22 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery, public async Task ShutdownAsync(CancellationToken cancellationToken) { await _poll.DisposeAsync().ConfigureAwait(false); + if (_alarmProjection is { } proj) + { + await proj.DisposeAsync().ConfigureAwait(false); + _alarmProjection = null; + } foreach (var state in _devices.Values) { try { state.ProbeCts?.Cancel(); } catch { } state.ProbeCts?.Dispose(); state.ProbeCts = null; + try { state.RecycleCts?.Cancel(); } catch { } + state.RecycleCts?.Dispose(); + state.RecycleCts = null; + try { state.FixedTreeCts?.Cancel(); } catch { } + state.FixedTreeCts?.Dispose(); + state.FixedTreeCts = null; state.DisposeClient(); } _devices.Clear(); @@ -136,6 +173,16 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery, for (var i = 0; i < fullReferences.Count; i++) { var reference = fullReferences[i]; + + // Fixed-tree T1 — fixed-tree references are synthesized from the cached + // dynamic snapshot + sysinfo; no P/Invoke per Read since the poll loop + // already fires them on cadence. + if (_options.FixedTree.Enabled && TryReadFixedTree(reference, now) is { } fx) + { + results[i] = fx; + continue; + } + if (!_tagsByName.TryGetValue(reference, out var def)) { results[i] = new DataValueSnapshot(null, FocasStatusMapper.BadNodeIdUnknown, null, now); @@ -241,6 +288,80 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery, { var label = device.DeviceName ?? device.HostAddress; var deviceFolder = root.Folder(device.HostAddress, label); + + // Fixed-tree T1 — Identity + Axes subtrees, populated once per session + // from cnc_sysinfo + cnc_rdaxisname at init time and kept in DeviceState. + if (_options.FixedTree.Enabled + && _devices.TryGetValue(device.HostAddress, out var state) + && state.FixedTreeCache is { } cache) + { + var identity = deviceFolder.Folder("Identity", "Identity"); + EmitIdentityVariable(identity, device.HostAddress, "SeriesNumber", FocasDriverDataType.String); + EmitIdentityVariable(identity, device.HostAddress, "Version", FocasDriverDataType.String); + EmitIdentityVariable(identity, device.HostAddress, "MaxAxes", FocasDriverDataType.Int32); + EmitIdentityVariable(identity, device.HostAddress, "CncType", FocasDriverDataType.String); + EmitIdentityVariable(identity, device.HostAddress, "MtType", FocasDriverDataType.String); + EmitIdentityVariable(identity, device.HostAddress, "AxisCount", FocasDriverDataType.Int32); + + var axesFolder = deviceFolder.Folder("Axes", "Axes"); + foreach (var axis in cache.Axes) + { + var axisFolder = axesFolder.Folder(axis.Display, axis.Display); + EmitAxisVariable(axisFolder, device.HostAddress, axis.Display, "AbsolutePosition"); + EmitAxisVariable(axisFolder, device.HostAddress, axis.Display, "MachinePosition"); + EmitAxisVariable(axisFolder, device.HostAddress, axis.Display, "RelativePosition"); + EmitAxisVariable(axisFolder, device.HostAddress, axis.Display, "DistanceToGo"); + if (cache.Capabilities.ServoLoad) + EmitAxisVariable(axisFolder, device.HostAddress, axis.Display, "ServoLoad"); + } + EmitAxisVariable(axesFolder, device.HostAddress, "FeedRate", "Actual"); + EmitAxisVariable(axesFolder, device.HostAddress, "SpindleSpeed", "Actual"); + + // Spindle subtree — one folder per discovered spindle, suppressed + // entirely on series that don't export cnc_rdspdlname. Per-spindle + // Load + MaxRpm each gated on their own capability probe. + if (cache.Capabilities.Spindles) + { + var spindleRoot = deviceFolder.Folder("Spindle", "Spindle"); + for (var i = 0; i < cache.Spindles.Count; i++) + { + var s = cache.Spindles[i]; + var name = string.IsNullOrEmpty(s.Display) ? $"S{i + 1}" : s.Display; + var spindleFolder = spindleRoot.Folder(name, name); + if (cache.Capabilities.SpindleLoad) + EmitFixedVariable(spindleFolder, device.HostAddress, $"Spindle/{name}", "Load", DriverDataType.Int32); + if (cache.Capabilities.SpindleMaxRpm && i < cache.SpindleMaxRpms.Count) + EmitFixedVariable(spindleFolder, device.HostAddress, $"Spindle/{name}", "MaxRpm", DriverDataType.Int32); + } + } + + // Fixed-tree T2 — Program + OperationMode subtrees (gated on capability). + if (cache.Capabilities.ProgramInfo) + { + var program = deviceFolder.Folder("Program", "Program"); + EmitFixedVariable(program, device.HostAddress, "Program", "Name", DriverDataType.String); + EmitFixedVariable(program, device.HostAddress, "Program", "ONumber", DriverDataType.Int32); + EmitFixedVariable(program, device.HostAddress, "Program", "Number", DriverDataType.Int32); + EmitFixedVariable(program, device.HostAddress, "Program", "MainNumber", DriverDataType.Int32); + EmitFixedVariable(program, device.HostAddress, "Program", "Sequence", DriverDataType.Int32); + EmitFixedVariable(program, device.HostAddress, "Program", "BlockCount", DriverDataType.Int32); + + var opMode = deviceFolder.Folder("OperationMode", "OperationMode"); + EmitFixedVariable(opMode, device.HostAddress, "OperationMode", "Mode", DriverDataType.Int32); + EmitFixedVariable(opMode, device.HostAddress, "OperationMode", "ModeText", DriverDataType.String); + } + + // Fixed-tree T3 — Timers subtree (power-on / operating / cutting / cycle). + if (cache.Capabilities.Timers) + { + var timers = deviceFolder.Folder("Timers", "Timers"); + EmitFixedVariable(timers, device.HostAddress, "Timers", "PowerOnSeconds", DriverDataType.Float64); + EmitFixedVariable(timers, device.HostAddress, "Timers", "OperatingSeconds", DriverDataType.Float64); + EmitFixedVariable(timers, device.HostAddress, "Timers", "CuttingSeconds", DriverDataType.Float64); + EmitFixedVariable(timers, device.HostAddress, "Timers", "CycleSeconds", DriverDataType.Float64); + } + } + var tagsForDevice = _options.Tags.Where(t => string.Equals(t.DeviceHostAddress, device.HostAddress, StringComparison.OrdinalIgnoreCase)); foreach (var tag in tagsForDevice) @@ -261,6 +382,72 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery, return Task.CompletedTask; } + private enum FocasDriverDataType { String, Int32, Float64 } + + private static void EmitIdentityVariable( + IAddressSpaceBuilder folder, string deviceHost, string field, FocasDriverDataType type) + { + var fullName = FixedTreeReference(deviceHost, $"Identity/{field}"); + folder.Variable(field, field, new DriverAttributeInfo( + FullName: fullName, + DriverDataType: type switch + { + FocasDriverDataType.Int32 => DriverDataType.Int32, + FocasDriverDataType.Float64 => DriverDataType.Float64, + _ => DriverDataType.String, + }, + IsArray: false, + ArrayDim: null, + SecurityClass: SecurityClassification.ViewOnly, + IsHistorized: false, + IsAlarm: false, + WriteIdempotent: false)); + } + + private static void EmitAxisVariable( + IAddressSpaceBuilder folder, string deviceHost, string axisName, string field) + { + var fullName = FixedTreeReference(deviceHost, $"Axes/{axisName}/{field}"); + folder.Variable(field, field, new DriverAttributeInfo( + FullName: fullName, + DriverDataType: DriverDataType.Float64, + IsArray: false, + ArrayDim: null, + SecurityClass: SecurityClassification.ViewOnly, + IsHistorized: false, + IsAlarm: false, + WriteIdempotent: false)); + } + + /// + /// Emit a variable under a named fixed-tree folder (Program, OperationMode, + /// …). Full-reference shape is {deviceHost}/{folderPath}/{field}. + /// + private static void EmitFixedVariable( + IAddressSpaceBuilder folder, string deviceHost, string folderPath, + string field, DriverDataType type) + { + var fullName = FixedTreeReference(deviceHost, $"{folderPath}/{field}"); + folder.Variable(field, field, new DriverAttributeInfo( + FullName: fullName, + DriverDataType: type, + IsArray: false, + ArrayDim: null, + SecurityClass: SecurityClassification.ViewOnly, + IsHistorized: false, + IsAlarm: false, + WriteIdempotent: false)); + } + + /// + /// Canonical full-reference shape for a fixed-tree node. Keeps the device + /// host as a prefix so multi-device configs don't collide, and the rest is + /// the path inside the tree. Matches what poll-loop snapshots publish + + /// what looks up. + /// + internal static string FixedTreeReference(string deviceHost, string path) => + $"{deviceHost}/{path}"; + // ---- ISubscribable (polling overlay via shared engine) ---- public Task SubscribeAsync( @@ -298,6 +485,310 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery, } } + /// + /// Per-device fixed-tree poll loop. First tick resolves sysinfo + axis names + /// (once) so can render the subtree on its next + /// invocation; every tick thereafter fires a cnc_rddynamic2 per axis + /// and publishes OnDataChange for the axis positions + feed rate + spindle + /// speed. + /// + private async Task FixedTreeLoopAsync(DeviceState state, CancellationToken ct) + { + // Bootstrap: identity + axis names + per-optional-API capability probe. + // Each optional call is attempted once; failures (EW_FUNC / EW_NOOPT / EW_VERSION) + // record the capability as unsupported and suppress the corresponding nodes + // in DiscoverAsync + the poll loop. + while (!ct.IsCancellationRequested && state.FixedTreeCache is null) + { + try + { + var client = await EnsureConnectedAsync(state, ct).ConfigureAwait(false); + var sys = await client.GetSysInfoAsync(ct).ConfigureAwait(false); + var axes = await client.GetAxisNamesAsync(ct).ConfigureAwait(false); + + // Optional-API probes — each returns empty / throws when unsupported. + var spindles = await SafeProbe(() => client.GetSpindleNamesAsync(ct), []); + var spindleMaxRpms = await SafeProbe(() => client.GetSpindleMaxRpmsAsync(ct), []); + var servoLoads = await SafeProbe(() => client.GetServoLoadsAsync(ct), []); + var programInfo = await SafeTryProbe(() => client.GetProgramInfoAsync(ct)); + var timer = await SafeTryProbe(() => client.GetTimerAsync(FocasTimerKind.PowerOn, ct)); + var spindleLoad = await SafeProbe(() => client.GetSpindleLoadsAsync(ct), []); + + var caps = new FocasFixedTreeCapabilities( + Spindles: spindles.Count > 0, + SpindleLoad: spindleLoad.Count > 0, + SpindleMaxRpm: spindleMaxRpms.Count > 0, + ServoLoad: servoLoads.Count > 0, + ProgramInfo: programInfo is not null, + Timers: timer is not null); + + state.FixedTreeCache = new FocasFixedTreeCache( + sys, [.. axes], [.. spindles], [.. spindleMaxRpms], caps); + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) { return; } + catch + { + try { await Task.Delay(TimeSpan.FromSeconds(2), ct).ConfigureAwait(false); } + catch (OperationCanceledException) { return; } + } + } + + // Prime the spindle-loads cache from bootstrap if supported — avoids a + // "tree is there but reads say BadNodeIdUnknown" window on startup. + if (state.FixedTreeCache?.Capabilities is { SpindleLoad: true }) + { + try + { + var client2 = await EnsureConnectedAsync(state, ct).ConfigureAwait(false); + var loads = await client2.GetSpindleLoadsAsync(ct).ConfigureAwait(false); + for (var i = 0; i < loads.Count; i++) state.LastSpindleLoads[i] = loads[i]; + } + catch { /* first-tick poll will retry */ } + } + + var programPollDue = DateTime.MinValue; + var timerPollDue = DateTime.MinValue; + while (!ct.IsCancellationRequested) + { + try + { + var client = await EnsureConnectedAsync(state, ct).ConfigureAwait(false); + var cache = state.FixedTreeCache; + if (cache is null) break; + FocasDynamicSnapshot? firstAxisSnap = null; + for (var i = 0; i < cache.Axes.Count; i++) + { + var axisIndex = i + 1; // FOCAS uses 1-based axis indexing + var axis = cache.Axes[i]; + var snap = await client.ReadDynamicAsync(axisIndex, ct).ConfigureAwait(false); + PublishAxisSnapshot(state, axis, snap); + if (i == 0) { firstAxisSnap = snap; PublishRateSnapshot(state, snap); } + } + + // Servo loads + spindle loads — both return bulk arrays, so folding + // into the axis cadence is cheap. Each is gated by the bootstrap + // capability probe — unsupported on this series = silent skip. + if (cache.Capabilities.ServoLoad) + { + try + { + var loads = await client.GetServoLoadsAsync(ct).ConfigureAwait(false); + PublishServoLoads(state, loads); + } + catch { /* transient — next tick retries */ } + } + if (cache.Capabilities.SpindleLoad) + { + try + { + var loads = await client.GetSpindleLoadsAsync(ct).ConfigureAwait(false); + for (var i = 0; i < loads.Count; i++) state.LastSpindleLoads[i] = loads[i]; + } + catch { /* transient */ } + } + + // Program-info poll runs on its own cadence — much slower than the axis + // poll because program / mode transitions are operator-driven. + var programInterval = _options.FixedTree.ProgramPollInterval; + if (cache.Capabilities.ProgramInfo + && programInterval > TimeSpan.Zero && DateTime.UtcNow >= programPollDue) + { + try + { + var program = await client.GetProgramInfoAsync(ct).ConfigureAwait(false); + state.LastProgramInfo = program; + if (firstAxisSnap is { } s) state.LastProgramAxisRef = s; + } + catch { /* transient — next tick retries */ } + programPollDue = DateTime.UtcNow + programInterval; + } + + // Timers — slowest cadence. Fires 4 FWLIB calls per tick (one per kind). + var timerInterval = _options.FixedTree.TimerPollInterval; + if (cache.Capabilities.Timers + && timerInterval > TimeSpan.Zero && DateTime.UtcNow >= timerPollDue) + { + foreach (FocasTimerKind kind in Enum.GetValues()) + { + try + { + var t = await client.GetTimerAsync(kind, ct).ConfigureAwait(false); + state.LastTimers[kind] = t; + } + catch { /* per-kind failures are non-fatal */ } + } + timerPollDue = DateTime.UtcNow + timerInterval; + } + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) { break; } + catch { /* next tick retries — transient blips are expected */ } + + try { await Task.Delay(_options.FixedTree.PollInterval, ct).ConfigureAwait(false); } + catch (OperationCanceledException) { break; } + } + } + + /// + /// Cache a fresh axis snapshot. The poll loop doesn't fire OnDataChange + /// directly — subscribers go through the normal SubscribeAsync → + /// path, which hits + /// and returns these cached values. + /// + private static void PublishAxisSnapshot(DeviceState state, FocasAxisName axis, FocasDynamicSnapshot snap) + { + var host = state.Options.HostAddress; + state.LastFixedSnapshots[FixedTreeReference(host, $"Axes/{axis.Display}/AbsolutePosition")] = snap.AbsolutePosition; + state.LastFixedSnapshots[FixedTreeReference(host, $"Axes/{axis.Display}/MachinePosition")] = snap.MachinePosition; + state.LastFixedSnapshots[FixedTreeReference(host, $"Axes/{axis.Display}/RelativePosition")] = snap.RelativePosition; + state.LastFixedSnapshots[FixedTreeReference(host, $"Axes/{axis.Display}/DistanceToGo")] = snap.DistanceToGo; + } + + private static void PublishRateSnapshot(DeviceState state, FocasDynamicSnapshot snap) + { + var host = state.Options.HostAddress; + state.LastFixedSnapshots[FixedTreeReference(host, "Axes/FeedRate/Actual")] = snap.ActualFeedRate; + state.LastFixedSnapshots[FixedTreeReference(host, "Axes/SpindleSpeed/Actual")] = snap.ActualSpindleSpeed; + } + + /// + /// Cache servo-load percentages keyed by axis name. Stored separately from + /// LastFixedSnapshots (which is int-typed) so the double-valued load + /// values don't need casting on every read. + /// + private static void PublishServoLoads(DeviceState state, IReadOnlyList loads) + { + foreach (var load in loads) + state.LastServoLoads[load.AxisName] = load.LoadPercent; + } + + private static object? TimerValue(DeviceState state, FocasTimerKind kind) => + state.LastTimers.TryGetValue(kind, out var t) ? (object)t.TotalSeconds : null; + + /// + /// Call an optional probe that returns a collection; swallow any exception + /// and return . Used by bootstrap to capture + /// per-series capability without letting one failed probe take down the + /// entire bootstrap sequence. + /// + private static async Task> SafeProbe( + Func>> probe, IReadOnlyList fallback) + { + try { return await probe().ConfigureAwait(false); } + catch { return fallback; } + } + + /// + /// Nullable variant — probe returns a single object or null on failure. + /// + private static async Task SafeTryProbe(Func> probe) where T : class + { + try { return await probe().ConfigureAwait(false); } + catch { return null; } + } + + /// + /// Read cached last-fixed-tree snapshots. Returns the projected value when + /// the reference looks like a fixed-tree FullName; null when it doesn't + /// (callers fall through to the user-authored tag path). + /// + private DataValueSnapshot? TryReadFixedTree(string reference, DateTime now) + { + foreach (var state in _devices.Values) + { + if (!reference.StartsWith(state.Options.HostAddress + "/", StringComparison.OrdinalIgnoreCase)) continue; + if (state.LastFixedSnapshots.TryGetValue(reference, out var raw)) + return new DataValueSnapshot((double)raw, FocasStatusMapper.Good, now, now); + + // Servo-load match: reference shape is "{host}/Axes/{name}/ServoLoad" + var suffixFull = reference[(state.Options.HostAddress.Length + 1)..]; + if (suffixFull.StartsWith("Axes/", StringComparison.Ordinal) && suffixFull.EndsWith("/ServoLoad", StringComparison.Ordinal)) + { + var axisName = suffixFull["Axes/".Length..^"/ServoLoad".Length]; + if (state.LastServoLoads.TryGetValue(axisName, out var load)) + return new DataValueSnapshot(load, FocasStatusMapper.Good, now, now); + } + + // Spindle matches: "{host}/Spindle/{name}/Load" + "{host}/Spindle/{name}/MaxRpm" + if (suffixFull.StartsWith("Spindle/", StringComparison.Ordinal) + && state.FixedTreeCache is { } spindleCache) + { + var tail = suffixFull["Spindle/".Length..]; + var slash = tail.IndexOf('/'); + if (slash > 0) + { + var spindleName = tail[..slash]; + var field = tail[(slash + 1)..]; + var idx = -1; + for (var i = 0; i < spindleCache.Spindles.Count; i++) + { + var s = spindleCache.Spindles[i]; + var display = string.IsNullOrEmpty(s.Display) ? $"S{i + 1}" : s.Display; + if (string.Equals(display, spindleName, StringComparison.OrdinalIgnoreCase)) { idx = i; break; } + } + if (idx >= 0) + { + object? value = field switch + { + "Load" => state.LastSpindleLoads.TryGetValue(idx, out var l) ? (object)l : null, + "MaxRpm" => idx < spindleCache.SpindleMaxRpms.Count ? (object)spindleCache.SpindleMaxRpms[idx] : null, + _ => null, + }; + if (value is not null) + return new DataValueSnapshot(value, FocasStatusMapper.Good, now, now); + } + } + } + // Identity strings + program / op-mode fields aren't cached as doubles — + // re-derive from the struct caches. + if (state.FixedTreeCache is { } cache) + { + var suffix = reference[(state.Options.HostAddress.Length + 1)..]; + var value = suffix switch + { + "Identity/SeriesNumber" => (object)cache.SysInfo.Series, + "Identity/Version" => cache.SysInfo.Version, + "Identity/MaxAxes" => cache.SysInfo.MaxAxis, + "Identity/CncType" => cache.SysInfo.CncType, + "Identity/MtType" => cache.SysInfo.MtType, + "Identity/AxisCount" => cache.SysInfo.AxesCount, + "Program/Name" => (object?)state.LastProgramInfo?.Name, + "Program/ONumber" => state.LastProgramInfo?.ONumber, + "Program/BlockCount" => state.LastProgramInfo?.BlockCount, + "Program/Number" => state.LastProgramAxisRef?.ProgramNumber, + "Program/MainNumber" => state.LastProgramAxisRef?.MainProgramNumber, + "Program/Sequence" => state.LastProgramAxisRef?.SequenceNumber, + "OperationMode/Mode" => state.LastProgramInfo?.Mode, + "OperationMode/ModeText" => state.LastProgramInfo is { } pi + ? FocasOpMode.ToText(pi.Mode) : null, + "Timers/PowerOnSeconds" => TimerValue(state, FocasTimerKind.PowerOn), + "Timers/OperatingSeconds" => TimerValue(state, FocasTimerKind.Operating), + "Timers/CuttingSeconds" => TimerValue(state, FocasTimerKind.Cutting), + "Timers/CycleSeconds" => TimerValue(state, FocasTimerKind.Cycle), + _ => null, + }; + if (value is not null) + return new DataValueSnapshot(value, FocasStatusMapper.Good, now, now); + } + } + return null; + } + + private async Task RecycleLoopAsync(DeviceState state, CancellationToken ct) + { + while (!ct.IsCancellationRequested) + { + try { await Task.Delay(_options.HandleRecycle.Interval, ct).ConfigureAwait(false); } + catch (OperationCanceledException) { break; } + + // Close the current handle — the next Read / Write / Probe call triggers + // EnsureConnectedAsync, which reopens a fresh one. We don't block here on + // reconnect because the goal is just to release the FWLIB handle slot; a + // readable tick one probe cycle later is an acceptable cost. + try { state.DisposeClient(); } + catch { /* already disposed or race — next EnsureConnected recovers */ } + } + } + private void TransitionDeviceState(DeviceState state, HostState newState) { HostState old; @@ -312,6 +803,50 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery, new HostStatusChangedEventArgs(state.Options.HostAddress, old, newState)); } + // ---- IAlarmSource ---- + + public Task SubscribeAlarmsAsync( + IReadOnlyList sourceNodeIds, CancellationToken cancellationToken) + { + if (_alarmProjection is null) + throw new NotSupportedException( + "FOCAS alarm projection is disabled — set FocasDriverOptions.AlarmProjection.Enabled=true to opt in."); + return _alarmProjection.SubscribeAsync(sourceNodeIds, cancellationToken); + } + + public Task UnsubscribeAlarmsAsync(IAlarmSubscriptionHandle handle, CancellationToken cancellationToken) => + _alarmProjection is { } p ? p.UnsubscribeAsync(handle, cancellationToken) : Task.CompletedTask; + + public Task AcknowledgeAsync( + IReadOnlyList acknowledgements, CancellationToken cancellationToken) => + _alarmProjection is { } p ? p.AcknowledgeAsync(acknowledgements, cancellationToken) : Task.CompletedTask; + + internal void InvokeAlarmEvent(AlarmEventArgs args) => OnAlarmEvent?.Invoke(this, args); + + /// + /// Poll every configured device's active-alarm list in one pass. Used by the alarm + /// projection — kept internal rather than public because callers that + /// want alarm events should subscribe through IAlarmSource instead. + /// + internal async Task Alarms)>> + ReadActiveAlarmsAcrossDevicesAsync(HashSet? deviceFilter, CancellationToken ct) + { + var result = new List<(string, IReadOnlyList)>(); + foreach (var state in _devices.Values) + { + if (deviceFilter is not null && !deviceFilter.Contains(state.Options.HostAddress)) continue; + try + { + var client = await EnsureConnectedAsync(state, ct).ConfigureAwait(false); + var alarms = await client.ReadAlarmsAsync(ct).ConfigureAwait(false); + result.Add((state.Options.HostAddress, alarms)); + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) { break; } + catch { /* surface a device-local fault on the next tick */ } + } + return result; + } + // ---- IPerCallHostResolver ---- public string ResolveHost(string fullReference) @@ -341,6 +876,33 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery, public void Dispose() => DisposeAsync().AsTask().GetAwaiter().GetResult(); public async ValueTask DisposeAsync() => await ShutdownAsync(CancellationToken.None).ConfigureAwait(false); + /// + /// Per-device fixed-tree cache populated once at first successful connect and + /// read-only thereafter. Used by to render the + /// tree + by for synchronous Identity/* reads. + /// + internal sealed record FocasFixedTreeCache( + FocasSysInfo SysInfo, + IReadOnlyList Axes, + IReadOnlyList Spindles, + IReadOnlyList SpindleMaxRpms, + FocasFixedTreeCapabilities Capabilities); + + /// + /// Per-device optional-API capability flags — which of the "this may or may not + /// exist on this CNC series" calls succeeded at bootstrap. Drives per-series + /// node suppression so a 16i that doesn't export cnc_rdspmaxrpm simply + /// doesn't get a Spindle/{name}/MaxRpm node (instead of surfacing + /// BadDeviceFailure on every read). + /// + internal sealed record FocasFixedTreeCapabilities( + bool Spindles, // cnc_rdspdlname returned 1+ spindle names + bool SpindleLoad, // cnc_rdspload bootstrap probe succeeded + bool SpindleMaxRpm, // cnc_rdspmaxrpm bootstrap probe succeeded + bool ServoLoad, // cnc_rdsvmeter bootstrap probe returned data + bool ProgramInfo, // cnc_exeprgname2 + cnc_rdblkcount + cnc_rdopmode work + bool Timers); // cnc_rdtimer works for at least PowerOn + internal sealed class DeviceState(FocasHostAddress parsedAddress, FocasDeviceOptions options) { public FocasHostAddress ParsedAddress { get; } = parsedAddress; @@ -351,6 +913,16 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery, public HostState HostState { get; set; } = HostState.Unknown; public DateTime HostStateChangedUtc { get; set; } = DateTime.UtcNow; public CancellationTokenSource? ProbeCts { get; set; } + public CancellationTokenSource? RecycleCts { get; set; } + public CancellationTokenSource? FixedTreeCts { get; set; } + public FocasFixedTreeCache? FixedTreeCache { get; set; } + public Dictionary LastFixedSnapshots { get; } = new(StringComparer.OrdinalIgnoreCase); + public FocasProgramInfo? LastProgramInfo { get; set; } + /// Cached first-axis dynamic snapshot — feeds Program/Number, /MainNumber, /Sequence. + public FocasDynamicSnapshot? LastProgramAxisRef { get; set; } + public Dictionary LastTimers { get; } = []; + public Dictionary LastServoLoads { get; } = new(StringComparer.OrdinalIgnoreCase); + public Dictionary LastSpindleLoads { get; } = []; public void DisposeClient() { diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriverFactoryExtensions.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriverFactoryExtensions.cs index 7d4bdef..cd18733 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriverFactoryExtensions.cs +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriverFactoryExtensions.cs @@ -1,32 +1,28 @@ using System.Text.Json; using System.Text.Json.Serialization; using ZB.MOM.WW.OtOpcUa.Core.Hosting; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Ipc; +using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire; namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS; /// -/// Static factory registration helper for . Server's Program.cs -/// calls once at startup; the bootstrapper (task #248) then -/// materialises FOCAS DriverInstance rows from the central config DB into live driver -/// instances. Mirrors GalaxyProxyDriverFactoryExtensions; no dependency on -/// Microsoft.Extensions.DependencyInjection so the driver project stays DI-free. +/// Static factory registration helper for . Server's +/// Program.cs calls once at startup; the bootstrapper +/// then materialises FOCAS DriverInstance rows from the central config DB +/// into live driver instances. /// /// /// The DriverConfig JSON selects the backend: /// -/// "Backend": "ipc" (default) — wires -/// against a named-pipe talking to a separate -/// Driver.FOCAS.Host process (Tier-C isolation). Requires PipeName + -/// SharedSecret. -/// "Backend": "fwlib" — direct in-process Fwlib32.dll P/Invoke via -/// . Use only when the main server is licensed -/// for FOCAS and you accept the native-crash blast-radius trade-off. -/// "Backend": "unimplemented" — returns the no-op factory; useful for -/// scaffolding DriverInstance rows before the Host is deployed so the server boots. +/// "Backend": "wire" (default) — pure-managed FOCAS2 wire +/// client () speaking directly to +/// the CNC on TCP:8193. +/// "Backend": "unimplemented" / "none" / "stub" +/// — returns the no-op factory; useful for scaffolding DriverInstance +/// rows before the CNC endpoint is reachable. /// -/// Devices / Tags / Probe / Timeout / Series come from the same JSON and feed directly -/// into . +/// Devices / Tags / Probe / Timeout / Series come from the same JSON and +/// feed directly into . /// public static class FocasDriverFactoryExtensions { @@ -92,45 +88,19 @@ public static class FocasDriverFactoryExtensions internal static IFocasClientFactory BuildClientFactory( FocasDriverConfigDto dto, string driverInstanceId) { - var backend = (dto.Backend ?? "ipc").Trim().ToLowerInvariant(); + var backend = (dto.Backend ?? "wire").Trim().ToLowerInvariant(); return backend switch { - "ipc" => BuildIpcFactory(dto, driverInstanceId), - "fwlib" or "fwlib32" => new FwlibFocasClientFactory(), + "wire" => new WireFocasClientFactory(), "unimplemented" or "none" or "stub" => new UnimplementedFocasClientFactory(), _ => throw new InvalidOperationException( $"FOCAS driver config for '{driverInstanceId}' has unknown Backend '{dto.Backend}'. " + - "Expected one of: ipc, fwlib, unimplemented."), + "Expected one of: wire, unimplemented. " + + "(The legacy 'ipc' / 'fwlib' backends were retired in the Wire migration — " + + "see docs/drivers/FOCAS.md.)"), }; } - private static IpcFocasClientFactory BuildIpcFactory( - FocasDriverConfigDto dto, string driverInstanceId) - { - var pipeName = dto.PipeName - ?? throw new InvalidOperationException( - $"FOCAS driver config for '{driverInstanceId}' missing required PipeName (Tier-C ipc backend)"); - var sharedSecret = dto.SharedSecret - ?? throw new InvalidOperationException( - $"FOCAS driver config for '{driverInstanceId}' missing required SharedSecret (Tier-C ipc backend)"); - var connectTimeout = TimeSpan.FromMilliseconds(dto.ConnectTimeoutMs ?? 10_000); - var series = ParseSeries(dto.Series); - - // Each IFocasClientFactory.Create() call opens a fresh pipe to the Host — matches the - // driver's one-client-per-device invariant. FocasIpcClient.ConnectAsync is awaited - // synchronously via GetAwaiter().GetResult() because IFocasClientFactory.Create is a - // sync contract; the blocking call lands inside FocasDriver.EnsureConnectedAsync, - // which immediately awaits IFocasClient.ConnectAsync afterwards so the perceived - // latency is identical to a fully-async factory. - return new IpcFocasClientFactory( - ipcClientFactory: () => FocasIpcClient.ConnectAsync( - pipeName: pipeName, - sharedSecret: sharedSecret, - connectTimeout: connectTimeout, - ct: CancellationToken.None).GetAwaiter().GetResult(), - series: series); - } - private static FocasCncSeries ParseSeries(string? raw) { if (string.IsNullOrWhiteSpace(raw)) return FocasCncSeries.Unknown; @@ -162,9 +132,6 @@ public static class FocasDriverFactoryExtensions internal sealed class FocasDriverConfigDto { public string? Backend { get; init; } - public string? PipeName { get; init; } - public string? SharedSecret { get; init; } - public int? ConnectTimeoutMs { get; init; } public string? Series { get; init; } public int? TimeoutMs { get; init; } public List? Devices { get; init; } diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriverOptions.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriverOptions.cs index ce8a042..09b240f 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriverOptions.cs +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriverOptions.cs @@ -11,6 +11,77 @@ public sealed class FocasDriverOptions public IReadOnlyList Tags { get; init; } = []; public FocasProbeOptions Probe { get; init; } = new(); public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2); + public FocasAlarmProjectionOptions AlarmProjection { get; init; } = new(); + public FocasHandleRecycleOptions HandleRecycle { get; init; } = new(); + public FocasFixedTreeOptions FixedTree { get; init; } = new(); +} + +/// +/// Fixed-node tree exposed by FOCAS per docs/v2/driver-specs.md §7 — +/// Identity/, Axes/{name}/, etc. populated from +/// cnc_sysinfo / cnc_rdaxisname / cnc_rddynamic2. Disabled by +/// default so existing configs that only use user-authored tags don't grow new +/// nodes on upgrade. +/// +public sealed class FocasFixedTreeOptions +{ + /// Enable the fixed-node tree for every configured device. + public bool Enabled { get; init; } = false; + + /// + /// Poll cadence for cnc_rddynamic2. Each tick calls the API once per + /// configured axis + publishes OnDataChange for the axis subtree. Real CNCs + /// serve ~100ms loops comfortably; the default is conservative. + /// + public TimeSpan PollInterval { get; init; } = TimeSpan.FromMilliseconds(250); + + /// + /// Poll cadence for program + operation-mode info. Slower than the axis + /// poll because program / mode transitions happen on operator timescales. + /// Zero / negative disables the program poll entirely. + /// + public TimeSpan ProgramPollInterval { get; init; } = TimeSpan.FromSeconds(1); + + /// + /// Poll cadence for timers (power-on / operating / cutting / cycle). + /// These change at human timescales — default is 30s. Zero / negative + /// disables the timer poll entirely. + /// + public TimeSpan TimerPollInterval { get; init; } = TimeSpan.FromSeconds(30); +} + +/// +/// Proactive session-recycle cadence. Fanuc CNCs have a finite FWLIB handle pool +/// (~5–10 concurrent connections) and certain series have documented handle-leak bugs +/// that manifest after long uptime. When is true the +/// driver closes + reopens each device's session on the cadence, +/// forcing FWLIB to release its handle slot back to the pool. Reads / writes during +/// recycle wait for the reconnect rather than failing — worst case an operator sees a +/// brief read latency spike once per cadence. +/// +/// +/// Disabled by default because a healthy CNC + driver doesn't need it. Enable when +/// field experience shows handle exhaustion against a specific series / firmware. +/// Typical tuning: 30 min for sites running multiple OtOpcUa instances against the +/// same CNC (they share the pool); 6 h for a single-client deployment. +/// +public sealed class FocasHandleRecycleOptions +{ + public bool Enabled { get; init; } = false; + public TimeSpan Interval { get; init; } = TimeSpan.FromHours(1); +} + +/// +/// Controls the CNC active-alarm polling projection that surfaces FOCAS alarms via +/// IAlarmSource. Disabled by default — operators opt in by setting +/// in appsettings.json. +/// +public sealed class FocasAlarmProjectionOptions +{ + public bool Enabled { get; init; } = false; + + /// Poll cadence. One cnc_rdalmmsg2 call per device per tick. + public TimeSpan PollInterval { get; init; } = TimeSpan.FromSeconds(2); } /// diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FwlibFocasClient.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FwlibFocasClient.cs deleted file mode 100644 index 63a55fd..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FwlibFocasClient.cs +++ /dev/null @@ -1,328 +0,0 @@ -using System.Buffers.Binary; -using System.Collections.Concurrent; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS; - -/// -/// implementation backed by Fanuc's licensed -/// Fwlib32.dll via P/Invoke. The DLL is NOT shipped with -/// OtOpcUa; the deployment places it next to the server executable or on PATH -/// (per Fanuc licensing — see docs/v2/focas-deployment.md). -/// -/// -/// Construction is licence-safe — .NET P/Invoke is lazy, so instantiating this class -/// does NOT load Fwlib32.dll. The DLL only loads on the first wire call (Connect / -/// Read / Write / Probe). When missing, those calls throw -/// which the driver surfaces as BadCommunicationError through the normal exception -/// mapping. -/// -/// Session-scoped handle — cnc_allclibhndl3 opens one FWLIB handle per CNC; -/// all PMC / parameter / macro reads on that device go through the same handle. Dispose -/// calls cnc_freelibhndl. -/// -internal sealed class FwlibFocasClient : IFocasClient -{ - private ushort _handle; - private bool _connected; - - // Per-PMC-byte RMW lock registry. Bit writes to the same byte get serialised so two - // concurrent bit updates don't lose one another's modification. Key = "{addrType}:{byteAddr}". - private readonly ConcurrentDictionary _rmwLocks = new(); - - private SemaphoreSlim GetRmwLock(short addrType, int byteAddr) => - _rmwLocks.GetOrAdd($"{addrType}:{byteAddr}", _ => new SemaphoreSlim(1, 1)); - - public bool IsConnected => _connected; - - public Task ConnectAsync(FocasHostAddress address, TimeSpan timeout, CancellationToken cancellationToken) - { - if (_connected) return Task.CompletedTask; - - var timeoutMs = (int)Math.Max(1, timeout.TotalMilliseconds); - var ret = FwlibNative.AllcLibHndl3(address.Host, (ushort)address.Port, timeoutMs, out var handle); - if (ret != 0) - throw new InvalidOperationException( - $"FWLIB cnc_allclibhndl3 failed with EW_{ret} connecting to {address}."); - _handle = handle; - _connected = true; - return Task.CompletedTask; - } - - public Task<(object? value, uint status)> ReadAsync( - FocasAddress address, FocasDataType type, CancellationToken cancellationToken) - { - if (!_connected) return Task.FromResult<(object?, uint)>((null, FocasStatusMapper.BadCommunicationError)); - cancellationToken.ThrowIfCancellationRequested(); - - return address.Kind switch - { - FocasAreaKind.Pmc => Task.FromResult(ReadPmc(address, type)), - FocasAreaKind.Parameter => Task.FromResult(ReadParameter(address, type)), - FocasAreaKind.Macro => Task.FromResult(ReadMacro(address)), - _ => Task.FromResult<(object?, uint)>((null, FocasStatusMapper.BadNotSupported)), - }; - } - - public async Task WriteAsync( - FocasAddress address, FocasDataType type, object? value, CancellationToken cancellationToken) - { - if (!_connected) return FocasStatusMapper.BadCommunicationError; - cancellationToken.ThrowIfCancellationRequested(); - - return address.Kind switch - { - FocasAreaKind.Pmc when type == FocasDataType.Bit && address.BitIndex is int => - await WritePmcBitAsync(address, Convert.ToBoolean(value), cancellationToken).ConfigureAwait(false), - FocasAreaKind.Pmc => WritePmc(address, type, value), - FocasAreaKind.Parameter => WriteParameter(address, type, value), - FocasAreaKind.Macro => WriteMacro(address, value), - _ => FocasStatusMapper.BadNotSupported, - }; - } - - /// - /// Read-modify-write one bit within a PMC byte. Acquires a per-byte semaphore so - /// concurrent bit writes against the same byte serialise and neither loses its update. - /// - private async Task WritePmcBitAsync( - FocasAddress address, bool newValue, CancellationToken cancellationToken) - { - var addrType = FocasPmcAddrType.FromLetter(address.PmcLetter ?? "") ?? (short)0; - var bit = address.BitIndex ?? 0; - if (bit is < 0 or > 7) - throw new InvalidOperationException( - $"PMC bit index {bit} out of range (0-7) for {address.Canonical}."); - - var rmwLock = GetRmwLock(addrType, address.Number); - await rmwLock.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - // Read the parent byte. - var readBuf = new FwlibNative.IODBPMC { Data = new byte[40] }; - var readRet = FwlibNative.PmcRdPmcRng( - _handle, addrType, FocasPmcDataType.Byte, - (ushort)address.Number, (ushort)address.Number, 8 + 1, ref readBuf); - if (readRet != 0) return FocasStatusMapper.MapFocasReturn(readRet); - - var current = readBuf.Data[0]; - var updated = newValue - ? (byte)(current | (1 << bit)) - : (byte)(current & ~(1 << bit)); - - // Write the updated byte. - var writeBuf = new FwlibNative.IODBPMC - { - TypeA = addrType, - TypeD = FocasPmcDataType.Byte, - DatanoS = (ushort)address.Number, - DatanoE = (ushort)address.Number, - Data = new byte[40], - }; - writeBuf.Data[0] = updated; - - var writeRet = FwlibNative.PmcWrPmcRng(_handle, 8 + 1, ref writeBuf); - return writeRet == 0 ? FocasStatusMapper.Good : FocasStatusMapper.MapFocasReturn(writeRet); - } - finally - { - rmwLock.Release(); - } - } - - public Task ProbeAsync(CancellationToken cancellationToken) - { - if (!_connected) return Task.FromResult(false); - var buf = new FwlibNative.ODBST(); - var ret = FwlibNative.StatInfo(_handle, ref buf); - return Task.FromResult(ret == 0); - } - - // ---- PMC ---- - - private (object? value, uint status) ReadPmc(FocasAddress address, FocasDataType type) - { - var addrType = FocasPmcAddrType.FromLetter(address.PmcLetter ?? "") - ?? throw new InvalidOperationException($"Unknown PMC letter '{address.PmcLetter}'."); - var dataType = FocasPmcDataType.FromFocasDataType(type); - var length = PmcReadLength(type); - - var buf = new FwlibNative.IODBPMC { Data = new byte[40] }; - var ret = FwlibNative.PmcRdPmcRng( - _handle, addrType, dataType, - (ushort)address.Number, (ushort)address.Number, (ushort)length, ref buf); - if (ret != 0) return (null, FocasStatusMapper.MapFocasReturn(ret)); - - var value = type switch - { - FocasDataType.Bit => ExtractBit(buf.Data[0], address.BitIndex ?? 0), - FocasDataType.Byte => (object)(sbyte)buf.Data[0], - FocasDataType.Int16 => (object)BinaryPrimitives.ReadInt16LittleEndian(buf.Data), - FocasDataType.Int32 => (object)BinaryPrimitives.ReadInt32LittleEndian(buf.Data), - FocasDataType.Float32 => (object)BinaryPrimitives.ReadSingleLittleEndian(buf.Data), - FocasDataType.Float64 => (object)BinaryPrimitives.ReadDoubleLittleEndian(buf.Data), - _ => (object)buf.Data[0], - }; - return (value, FocasStatusMapper.Good); - } - - private uint WritePmc(FocasAddress address, FocasDataType type, object? value) - { - var addrType = FocasPmcAddrType.FromLetter(address.PmcLetter ?? "") ?? (short)0; - var dataType = FocasPmcDataType.FromFocasDataType(type); - var length = PmcWriteLength(type); - - var buf = new FwlibNative.IODBPMC - { - TypeA = addrType, - TypeD = dataType, - DatanoS = (ushort)address.Number, - DatanoE = (ushort)address.Number, - Data = new byte[40], - }; - EncodePmcValue(buf.Data, type, value, address.BitIndex); - - var ret = FwlibNative.PmcWrPmcRng(_handle, (ushort)length, ref buf); - return ret == 0 ? FocasStatusMapper.Good : FocasStatusMapper.MapFocasReturn(ret); - } - - private (object? value, uint status) ReadParameter(FocasAddress address, FocasDataType type) - { - var buf = new FwlibNative.IODBPSD { Data = new byte[32] }; - var length = ParamReadLength(type); - var ret = FwlibNative.RdParam(_handle, (ushort)address.Number, axis: 0, (short)length, ref buf); - if (ret != 0) return (null, FocasStatusMapper.MapFocasReturn(ret)); - - var value = type switch - { - FocasDataType.Bit when address.BitIndex is int bit => ExtractBit(buf.Data[0], bit), - FocasDataType.Byte => (object)(sbyte)buf.Data[0], - FocasDataType.Int16 => (object)BinaryPrimitives.ReadInt16LittleEndian(buf.Data), - FocasDataType.Int32 => (object)BinaryPrimitives.ReadInt32LittleEndian(buf.Data), - _ => (object)BinaryPrimitives.ReadInt32LittleEndian(buf.Data), - }; - return (value, FocasStatusMapper.Good); - } - - private uint WriteParameter(FocasAddress address, FocasDataType type, object? value) - { - var buf = new FwlibNative.IODBPSD - { - Datano = (short)address.Number, - Type = 0, - Data = new byte[32], - }; - var length = ParamReadLength(type); - EncodeParamValue(buf.Data, type, value); - var ret = FwlibNative.WrParam(_handle, (short)length, ref buf); - return ret == 0 ? FocasStatusMapper.Good : FocasStatusMapper.MapFocasReturn(ret); - } - - private (object? value, uint status) ReadMacro(FocasAddress address) - { - var buf = new FwlibNative.ODBM(); - var ret = FwlibNative.RdMacro(_handle, (short)address.Number, length: 8, ref buf); - if (ret != 0) return (null, FocasStatusMapper.MapFocasReturn(ret)); - - // Macro value = mcr_val / 10^dec_val. Convert to double so callers get the correct - // scaled value regardless of the decimal-point count the CNC reports. - var scaled = buf.McrVal / Math.Pow(10.0, buf.DecVal); - return (scaled, FocasStatusMapper.Good); - } - - private uint WriteMacro(FocasAddress address, object? value) - { - // Write as integer + 0 decimal places — callers that need decimal precision can extend - // this via a future WriteMacroScaled overload. Consistent with what most HMIs do today. - var intValue = Convert.ToInt32(value); - var ret = FwlibNative.WrMacro(_handle, (short)address.Number, length: 8, intValue, decimalPointCount: 0); - return ret == 0 ? FocasStatusMapper.Good : FocasStatusMapper.MapFocasReturn(ret); - } - - public void Dispose() - { - if (_connected) - { - try { FwlibNative.FreeLibHndl(_handle); } catch { } - _connected = false; - } - } - - // ---- helpers ---- - - private static int PmcReadLength(FocasDataType type) => type switch - { - FocasDataType.Bit or FocasDataType.Byte => 8 + 1, // 8-byte header + 1 byte payload - FocasDataType.Int16 => 8 + 2, - FocasDataType.Int32 => 8 + 4, - FocasDataType.Float32 => 8 + 4, - FocasDataType.Float64 => 8 + 8, - _ => 8 + 1, - }; - - private static int PmcWriteLength(FocasDataType type) => PmcReadLength(type); - private static int ParamReadLength(FocasDataType type) => type switch - { - FocasDataType.Bit or FocasDataType.Byte => 4 + 1, - FocasDataType.Int16 => 4 + 2, - FocasDataType.Int32 => 4 + 4, - _ => 4 + 4, - }; - - private static bool ExtractBit(byte word, int bit) => (word & (1 << bit)) != 0; - - internal static void EncodePmcValue(byte[] data, FocasDataType type, object? value, int? bitIndex) - { - switch (type) - { - case FocasDataType.Bit: - // PMC Bit writes with a non-null bitIndex go through WritePmcBitAsync's RMW path - // upstream. This branch only fires when a caller passes Bit with no bitIndex — - // treat the value as a whole-byte boolean (non-zero / zero). - data[0] = Convert.ToBoolean(value) ? (byte)1 : (byte)0; - break; - case FocasDataType.Byte: - data[0] = (byte)(sbyte)Convert.ToSByte(value); - break; - case FocasDataType.Int16: - BinaryPrimitives.WriteInt16LittleEndian(data, Convert.ToInt16(value)); - break; - case FocasDataType.Int32: - BinaryPrimitives.WriteInt32LittleEndian(data, Convert.ToInt32(value)); - break; - case FocasDataType.Float32: - BinaryPrimitives.WriteSingleLittleEndian(data, Convert.ToSingle(value)); - break; - case FocasDataType.Float64: - BinaryPrimitives.WriteDoubleLittleEndian(data, Convert.ToDouble(value)); - break; - default: - throw new NotSupportedException($"FocasDataType {type} not writable via PMC."); - } - _ = bitIndex; // bit-in-byte handled above - } - - internal static void EncodeParamValue(byte[] data, FocasDataType type, object? value) - { - switch (type) - { - case FocasDataType.Byte: - data[0] = (byte)(sbyte)Convert.ToSByte(value); - break; - case FocasDataType.Int16: - BinaryPrimitives.WriteInt16LittleEndian(data, Convert.ToInt16(value)); - break; - case FocasDataType.Int32: - BinaryPrimitives.WriteInt32LittleEndian(data, Convert.ToInt32(value)); - break; - default: - BinaryPrimitives.WriteInt32LittleEndian(data, Convert.ToInt32(value)); - break; - } - } -} - -/// Default — produces a fresh per device. -public sealed class FwlibFocasClientFactory : IFocasClientFactory -{ - public IFocasClient Create() => new FwlibFocasClient(); -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FwlibNative.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FwlibNative.cs deleted file mode 100644 index 08c2761..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FwlibNative.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System.Runtime.InteropServices; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS; - -/// -/// P/Invoke surface for Fanuc FWLIB (Fwlib32.dll). Declarations extracted from -/// fwlib32.h in the strangesast/fwlib repo; the licensed DLL itself is NOT shipped -/// with OtOpcUa — the deployment places Fwlib32.dll next to the server executable -/// or on PATH. -/// -/// -/// Deliberately narrow — only the calls actually makes. -/// FOCAS has 800+ functions in fwlib32.h; pulling in every one would bloat the -/// P/Invoke surface + signal more coverage than this driver provides. Expand as capabilities -/// are added. -/// -internal static class FwlibNative -{ - private const string Library = "Fwlib32.dll"; - - // ---- Handle lifetime ---- - - /// Open an Ethernet FWLIB handle. Returns EW_OK (0) on success; handle written out. - [DllImport(Library, EntryPoint = "cnc_allclibhndl3", CharSet = CharSet.Ansi, ExactSpelling = true)] - public static extern short AllcLibHndl3( - [MarshalAs(UnmanagedType.LPStr)] string ipaddr, - ushort port, - int timeout, - out ushort handle); - - [DllImport(Library, EntryPoint = "cnc_freelibhndl", ExactSpelling = true)] - public static extern short FreeLibHndl(ushort handle); - - // ---- PMC ---- - - /// PMC range read. is the ADR_* enum; is 0 byte / 1 word / 2 long. - [DllImport(Library, EntryPoint = "pmc_rdpmcrng", ExactSpelling = true)] - public static extern short PmcRdPmcRng( - ushort handle, - short addrType, - short dataType, - ushort startNumber, - ushort endNumber, - ushort length, - ref IODBPMC buffer); - - [DllImport(Library, EntryPoint = "pmc_wrpmcrng", ExactSpelling = true)] - public static extern short PmcWrPmcRng( - ushort handle, - ushort length, - ref IODBPMC buffer); - - // ---- Parameters ---- - - [DllImport(Library, EntryPoint = "cnc_rdparam", ExactSpelling = true)] - public static extern short RdParam( - ushort handle, - ushort number, - short axis, - short length, - ref IODBPSD buffer); - - [DllImport(Library, EntryPoint = "cnc_wrparam", ExactSpelling = true)] - public static extern short WrParam( - ushort handle, - short length, - ref IODBPSD buffer); - - // ---- Macro variables ---- - - [DllImport(Library, EntryPoint = "cnc_rdmacro", ExactSpelling = true)] - public static extern short RdMacro( - ushort handle, - short number, - short length, - ref ODBM buffer); - - [DllImport(Library, EntryPoint = "cnc_wrmacro", ExactSpelling = true)] - public static extern short WrMacro( - ushort handle, - short number, - short length, - int macroValue, - short decimalPointCount); - - // ---- Status ---- - - [DllImport(Library, EntryPoint = "cnc_statinfo", ExactSpelling = true)] - public static extern short StatInfo(ushort handle, ref ODBST buffer); - - // ---- Structs ---- - - /// - /// IODBPMC — PMC range I/O buffer. 8-byte header + 40-byte union. We marshal the union - /// as a fixed byte buffer + interpret per on the managed side. - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct IODBPMC - { - public short TypeA; - public short TypeD; - public ushort DatanoS; - public ushort DatanoE; - // 40-byte union: cdata[5] / idata[5] / ldata[5] / fdata[5] / dbdata[5] — dbdata is the widest. - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)] - public byte[] Data; - } - - /// - /// IODBPSD — CNC parameter I/O buffer. Axis-aware; for non-axis parameters pass axis=0. - /// Union payload is bytes / shorts / longs — we marshal 32 bytes as the widest slot. - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct IODBPSD - { - public short Datano; - public short Type; // axis index (0 for non-axis) - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public byte[] Data; - } - - /// ODBM — macro variable read buffer. Value = McrVal / 10^DecVal. - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct ODBM - { - public short Datano; - public short Dummy; - public int McrVal; // long in C; 32-bit signed - public short DecVal; // decimal-point count - } - - /// ODBST — CNC status info. Machine state, alarm flags, automatic / edit mode. - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct ODBST - { - public short Dummy; - public short TmMode; - public short Aut; - public short Run; - public short Motion; - public short Mstb; - public short Emergency; - public short Alarm; - public short Edit; - } -} - -/// -/// PMC address-letter → FOCAS ADR_* numeric code. Per Fanuc FOCAS/2 spec the codes -/// are: G=0, F=1, Y=2, X=3, A=4, R=5, T=6, K=7, C=8, D=9, E=10. Exposed internally + -/// tested so the FwlibFocasClient translation is verifiable without the DLL loaded. -/// -internal static class FocasPmcAddrType -{ - public static short? FromLetter(string letter) => letter.ToUpperInvariant() switch - { - "G" => 0, - "F" => 1, - "Y" => 2, - "X" => 3, - "A" => 4, - "R" => 5, - "T" => 6, - "K" => 7, - "C" => 8, - "D" => 9, - "E" => 10, - _ => null, - }; -} - -/// PMC data-type numeric codes per FOCAS/2: 0 = byte, 1 = word, 2 = long, 4 = float, 5 = double. -internal static class FocasPmcDataType -{ - public const short Byte = 0; - public const short Word = 1; - public const short Long = 2; - public const short Float = 4; - public const short Double = 5; - - public static short FromFocasDataType(FocasDataType t) => t switch - { - FocasDataType.Bit or FocasDataType.Byte => Byte, - FocasDataType.Int16 => Word, - FocasDataType.Int32 => Long, - FocasDataType.Float32 => Float, - FocasDataType.Float64 => Double, - _ => Byte, - }; -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs index 4c7733a..7e4d7fc 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/IFocasClient.cs @@ -5,15 +5,14 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS; /// configured device; lifetime matches the device. /// /// -/// No default wire implementation ships with this assembly. FWLIB -/// (Fwlib32.dll) is Fanuc-proprietary and requires a valid customer license — it -/// cannot legally be redistributed. The deployment team supplies an -/// that wraps the licensed Fwlib32.dll via -/// P/Invoke and registers it at server startup. +/// The default implementation is — a pure-managed +/// FOCAS/2 Ethernet client that speaks the wire protocol directly on TCP:8193. No +/// P/Invoke, no native DLLs, no out-of-process isolation. /// -/// The default throws with a pointer at -/// the deployment docs so misconfigured servers fail fast with a clear error rather than -/// mysteriously hanging. +/// is a scaffolding backend that +/// throws on — selected by +/// "Backend": "unimplemented" so a DriverInstance row can be seeded before the CNC +/// endpoint is reachable without silently reading stale data. /// public interface IFocasClient : IDisposable { @@ -48,8 +47,208 @@ public interface IFocasClient : IDisposable /// responds with any valid status. /// Task ProbeAsync(CancellationToken cancellationToken); + + /// + /// Read active alarm messages from the CNC via cnc_rdalmmsg2. Returns + /// zero-or-more active alarms. Null / empty list means "no alarms currently + /// active". IAlarmSource projection polls this at a configurable interval + + /// emits transitions (raise / clear) on the driver's OnAlarmEvent. + /// + Task> ReadAlarmsAsync(CancellationToken cancellationToken); + + // ---- Fixed-tree T1 (identity + axis discovery + fast-poll dynamic bundle) ---- + + /// + /// Read CNC identity via cnc_sysinfo. Populates the Identity/* + /// subtree of the fixed-node surface. Callable once at session open; the + /// values don't change across the session. + /// + Task GetSysInfoAsync(CancellationToken cancellationToken); + + /// + /// Read the CNC's configured axis names via cnc_rdaxisname. The driver + /// uses these to build the Axes/{name}/ subtree and to index + /// calls. + /// + Task> GetAxisNamesAsync(CancellationToken cancellationToken); + + /// + /// Read the CNC's configured spindle names via cnc_rdspdlname. Drives + /// the Spindle/{name}/ subtree. + /// + Task> GetSpindleNamesAsync(CancellationToken cancellationToken); + + /// + /// Read the fast-poll dynamic bundle for one axis via cnc_rddynamic2. + /// Returns the current position quadruple (absolute / machine / relative / + /// distance-to-go) plus actual feed rate + actual spindle speed + alarm + /// flags + program / sequence numbers — one network round-trip per call. + /// + Task ReadDynamicAsync(int axisIndex, CancellationToken cancellationToken); + + // ---- Fixed-tree T2 (program + operation mode) ---- + + /// + /// Aggregate program + operation-mode snapshot. One wire round-trip per + /// underlying FWLIB call — cnc_rdblkcount, cnc_exeprgname2, + /// cnc_rdopmode. The driver polls this on a slower cadence than + /// since program / mode transitions happen + /// on human-operator timescales. + /// + Task GetProgramInfoAsync(CancellationToken cancellationToken); + + // ---- Fixed-tree T3 (timers) ---- + + /// + /// Read one CNC cumulative timer. Kind selects PowerOn / Operating / Cutting / + /// Cycle. Values are seconds — the managed side already converted the native + /// minute+msec representation so downstream nodes display uniform units. + /// + Task GetTimerAsync(FocasTimerKind kind, CancellationToken cancellationToken); + + // ---- Fixed-tree T3.5 (servo meters) ---- + + /// + /// Read the servo-load meter percentages across all configured axes. + /// Values are percentages (scaled by 10^Dec). Empty list on a + /// disconnected session or unsupported CNC. + /// + Task> GetServoLoadsAsync(CancellationToken cancellationToken); + + // ---- Fixed-tree T3.6 (spindle meters) ---- + + /// + /// Read per-spindle load percentages. Result list index corresponds to + /// spindle index from . Empty list on a + /// disconnected session or when the CNC doesn't support the call (older + /// series like 16i may return EW_FUNC). + /// + Task> GetSpindleLoadsAsync(CancellationToken cancellationToken); + + /// + /// Read per-spindle maximum RPM values. Static configuration, fetched once at + /// bootstrap. Index alignment as per . + /// + Task> GetSpindleMaxRpmsAsync(CancellationToken cancellationToken); } +/// One servo-meter entry — one axis's current load percentage. +public sealed record FocasServoLoad(string AxisName, double LoadPercent); + +/// Which cumulative counter reads. +public enum FocasTimerKind +{ + /// Machine power-on hours — resets never. + PowerOn = 0, + /// Cycle operating time — resets when the operator clears the counter. + Operating = 1, + /// Cutting time — only counts while in cutting feed. + Cutting = 2, + /// Cycle time since the last program start. + Cycle = 3, +} + +/// One cumulative timer reading. is the canonical unit. +public sealed record FocasTimer(FocasTimerKind Kind, int Minutes, int Milliseconds) +{ + /// Cumulative time in seconds — Minutes * 60 + Milliseconds / 1000. + public double TotalSeconds => Minutes * 60.0 + Milliseconds / 1000.0; +} + +/// +/// CNC identity snapshot from cnc_sysinfo. Strings are trimmed ASCII. +/// +public sealed record FocasSysInfo( + int AddInfo, + int MaxAxis, + string CncType, // "M" (mill) / "T" (lathe) + string MtType, + string Series, // e.g. "30i" + string Version, // e.g. "A1.0" + int AxesCount); + +/// One configured axis name (e.g. "X", "X1"). +public sealed record FocasAxisName(string Name, string Suffix) +{ + /// + /// Display name — name + suffix concatenated, trimmed. Empty suffix yields + /// just the name (the common case on single-channel CNCs). + /// + public string Display => string.IsNullOrEmpty(Suffix) ? Name : $"{Name}{Suffix}"; +} + +/// One configured spindle name (e.g. "S1"). +public sealed record FocasSpindleName(string Name, string Suffix1, string Suffix2, string Suffix3) +{ + public string Display + { + get + { + var s = Name + Suffix1 + Suffix2 + Suffix3; + return s.TrimEnd('\0', ' '); + } + } +} + +/// +/// Fast-poll bundle for one axis. Position values are scaled integers; the caller +/// divides by 10^DecimalPlaces to get the decimal value. DecimalPlaces is +/// currently left to the caller to supply (via device config or a future +/// cnc_getfigure path once that export lands). +/// +/// +/// Program + operation-mode snapshot. Name is the currently-executing +/// program filename (e.g. "O0001.NC"); ONumber is its Fanuc O-number (1-9999). +/// Mode is the numeric code from cnc_rdopmode — see . +/// +public sealed record FocasProgramInfo( + string Name, + int ONumber, + int BlockCount, + int Mode); + +/// Human-readable text for the integer. +public static class FocasOpMode +{ + public static string ToText(int mode) => mode switch + { + 0 => "MDI", + 1 => "AUTO", + 2 => "TJOG", + 3 => "EDIT", + 4 => "HANDLE", + 5 => "JOG", + 6 => "TEACH_IN_HANDLE", + 7 => "REFERENCE", + 8 => "REMOTE", + 9 => "TEST", + _ => $"Mode{mode}", + }; +} + +public sealed record FocasDynamicSnapshot( + int AxisIndex, + int AlarmFlags, + int ProgramNumber, + int MainProgramNumber, + int SequenceNumber, + int ActualFeedRate, + int ActualSpindleSpeed, + int AbsolutePosition, + int MachinePosition, + int RelativePosition, + int DistanceToGo); + +/// +/// One active alarm surfaced by . Shape +/// mirrors ODBALMMSG2 but normalises the message bytes to a .NET string. +/// +public sealed record FocasActiveAlarm( + int AlarmNumber, + short Type, + short Axis, + string Message); + /// Factory for s. One client per configured device. public interface IFocasClientFactory { @@ -57,14 +256,32 @@ public interface IFocasClientFactory } /// -/// Default factory that throws at construction time — the deployment must register a real -/// factory. Keeps the driver assembly licence-clean while still allowing the skeleton to -/// compile + the abstraction tests to run. +/// Scaffolding factory — throws on so a DriverInstance row can be +/// seeded ahead of the CNC endpoint being reachable without silently reading stale data. +/// Select via "Backend": "unimplemented" in driver config. Flip to +/// "Backend": "wire" once the CNC is provisioned. /// public sealed class UnimplementedFocasClientFactory : IFocasClientFactory { public IFocasClient Create() => throw new NotSupportedException( - "FOCAS driver has no wire client configured. Register a real IFocasClientFactory at " + - "server startup wrapping the licensed Fwlib32.dll — see docs/v2/focas-deployment.md. " + - "Fanuc licensing forbids shipping Fwlib32.dll in the OtOpcUa package."); + "FOCAS driver backend is 'unimplemented'. Switch to 'Backend: \"wire\"' in driver config " + + "once the CNC is provisioned — see docs/drivers/FOCAS.md."); +} + +/// +/// Well-known FOCAS alarm types from fwlib32.h ALM_TYPE_*. Narrow subset — +/// the full list is ~15 types per model; these cover the universally-present categories. +/// +public static class FocasAlarmType +{ + /// Pass to -equivalent to mean "any type". + public const int All = -1; + public const int Parameter = 0; // ALM_P + public const int PulseCode = 1; // ALM_Y (servo) + public const int Overtravel = 2; // ALM_O + public const int Overheat = 3; // ALM_H + public const int Servo = 4; // ALM_S + public const int DataIo = 5; // ALM_T + public const int MemoryCheck = 6; // ALM_M + public const int MacroAlarm = 13; // ALM_MC — used by #3006 etc. } diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Ipc/FocasIpcClient.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Ipc/FocasIpcClient.cs deleted file mode 100644 index 51ede9b..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Ipc/FocasIpcClient.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.IO; -using System.IO.Pipes; -using MessagePack; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Ipc; - -/// -/// Proxy-side IPC channel to a running Driver.FOCAS.Host. Owns the pipe connection -/// and serializes request/response round-trips through a single call gate so -/// concurrent callers don't interleave frames. One instance per FOCAS Host session. -/// -public sealed class FocasIpcClient : IAsyncDisposable -{ - private readonly Stream _stream; - private readonly FrameReader _reader; - private readonly FrameWriter _writer; - private readonly SemaphoreSlim _callGate = new(1, 1); - - private FocasIpcClient(Stream stream) - { - _stream = stream; - _reader = new FrameReader(stream, leaveOpen: true); - _writer = new FrameWriter(stream, leaveOpen: true); - } - - /// Named-pipe factory: connects, sends Hello, awaits HelloAck. - public static async Task ConnectAsync( - string pipeName, string sharedSecret, TimeSpan connectTimeout, CancellationToken ct) - { - var stream = new NamedPipeClientStream( - serverName: ".", - pipeName: pipeName, - direction: PipeDirection.InOut, - options: PipeOptions.Asynchronous); - - await stream.ConnectAsync((int)connectTimeout.TotalMilliseconds, ct); - return await HandshakeAsync(stream, sharedSecret, ct).ConfigureAwait(false); - } - - /// - /// Stream factory — used by tests that wire the Proxy against an in-memory stream - /// pair instead of a real pipe. is owned by the caller - /// until . - /// - public static Task ConnectAsync(Stream stream, string sharedSecret, CancellationToken ct) - => HandshakeAsync(stream, sharedSecret, ct); - - private static async Task HandshakeAsync(Stream stream, string sharedSecret, CancellationToken ct) - { - var client = new FocasIpcClient(stream); - try - { - await client._writer.WriteAsync(FocasMessageKind.Hello, - new Hello { PeerName = "FOCAS.Proxy", SharedSecret = sharedSecret }, ct).ConfigureAwait(false); - - var ack = await client._reader.ReadFrameAsync(ct).ConfigureAwait(false); - if (ack is null || ack.Value.Kind != FocasMessageKind.HelloAck) - throw new InvalidOperationException("Did not receive HelloAck from FOCAS.Host"); - - var ackMsg = FrameReader.Deserialize(ack.Value.Body); - if (!ackMsg.Accepted) - throw new UnauthorizedAccessException($"FOCAS.Host rejected Hello: {ackMsg.RejectReason}"); - - return client; - } - catch - { - await client.DisposeAsync().ConfigureAwait(false); - throw; - } - } - - public async Task CallAsync( - FocasMessageKind requestKind, TReq request, FocasMessageKind expectedResponseKind, CancellationToken ct) - { - await _callGate.WaitAsync(ct).ConfigureAwait(false); - try - { - await _writer.WriteAsync(requestKind, request, ct).ConfigureAwait(false); - - var frame = await _reader.ReadFrameAsync(ct).ConfigureAwait(false); - if (frame is null) throw new EndOfStreamException("FOCAS IPC peer closed before response"); - - if (frame.Value.Kind == FocasMessageKind.ErrorResponse) - { - var err = MessagePackSerializer.Deserialize(frame.Value.Body); - throw new FocasIpcException(err.Code, err.Message); - } - - if (frame.Value.Kind != expectedResponseKind) - throw new InvalidOperationException( - $"Expected {expectedResponseKind}, got {frame.Value.Kind}"); - - return MessagePackSerializer.Deserialize(frame.Value.Body); - } - finally { _callGate.Release(); } - } - - public async Task SendOneWayAsync(FocasMessageKind requestKind, TReq request, CancellationToken ct) - { - await _callGate.WaitAsync(ct).ConfigureAwait(false); - try { await _writer.WriteAsync(requestKind, request, ct).ConfigureAwait(false); } - finally { _callGate.Release(); } - } - - public async ValueTask DisposeAsync() - { - _callGate.Dispose(); - _reader.Dispose(); - _writer.Dispose(); - await _stream.DisposeAsync().ConfigureAwait(false); - } -} - -public sealed class FocasIpcException(string code, string message) : Exception($"[{code}] {message}") -{ - public string Code { get; } = code; -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Ipc/IpcFocasClient.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Ipc/IpcFocasClient.cs deleted file mode 100644 index 01c227c..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Ipc/IpcFocasClient.cs +++ /dev/null @@ -1,199 +0,0 @@ -using MessagePack; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Ipc; - -/// -/// implementation that forwards every operation over a -/// to a Driver.FOCAS.Host process. Keeps the -/// Fwlib32.dll P/Invoke out of the main server process so a native crash -/// blast-radius stops at the Host boundary. -/// -/// -/// Session lifecycle: sends OpenSessionRequest and -/// caches the returned SessionId. Subsequent / -/// / calls thread that session id -/// onto each request DTO. sends CloseSessionRequest + -/// disposes the underlying pipe. -/// -public sealed class IpcFocasClient : IFocasClient -{ - private readonly FocasIpcClient _ipc; - private readonly FocasCncSeries _series; - private long _sessionId; - private bool _connected; - - public IpcFocasClient(FocasIpcClient ipc, FocasCncSeries series = FocasCncSeries.Unknown) - { - _ipc = ipc ?? throw new ArgumentNullException(nameof(ipc)); - _series = series; - } - - public bool IsConnected => _connected; - - public async Task ConnectAsync(FocasHostAddress address, TimeSpan timeout, CancellationToken cancellationToken) - { - if (_connected) return; - - var resp = await _ipc.CallAsync( - FocasMessageKind.OpenSessionRequest, - new OpenSessionRequest - { - HostAddress = $"{address.Host}:{address.Port}", - TimeoutMs = (int)Math.Max(1, timeout.TotalMilliseconds), - CncSeries = (int)_series, - }, - FocasMessageKind.OpenSessionResponse, - cancellationToken).ConfigureAwait(false); - - if (!resp.Success) - throw new InvalidOperationException( - $"FOCAS Host rejected OpenSession for {address}: {resp.ErrorCode ?? "?"} — {resp.Error}"); - - _sessionId = resp.SessionId; - _connected = true; - } - - public async Task<(object? value, uint status)> ReadAsync( - FocasAddress address, FocasDataType type, CancellationToken cancellationToken) - { - if (!_connected) return (null, FocasStatusMapper.BadCommunicationError); - - var resp = await _ipc.CallAsync( - FocasMessageKind.ReadRequest, - new ReadRequest - { - SessionId = _sessionId, - Address = ToDto(address), - DataType = (int)type, - }, - FocasMessageKind.ReadResponse, - cancellationToken).ConfigureAwait(false); - - if (!resp.Success) return (null, resp.StatusCode); - - var value = DecodeValue(resp.ValueBytes, resp.ValueTypeCode); - return (value, resp.StatusCode); - } - - public async Task WriteAsync( - FocasAddress address, FocasDataType type, object? value, CancellationToken cancellationToken) - { - if (!_connected) return FocasStatusMapper.BadCommunicationError; - - // PMC bit writes get the first-class RMW frame so the critical section stays on the Host. - if (address.Kind == FocasAreaKind.Pmc && type == FocasDataType.Bit && address.BitIndex is int bit) - { - var bitResp = await _ipc.CallAsync( - FocasMessageKind.PmcBitWriteRequest, - new PmcBitWriteRequest - { - SessionId = _sessionId, - Address = ToDto(address), - BitIndex = bit, - Value = Convert.ToBoolean(value), - }, - FocasMessageKind.PmcBitWriteResponse, - cancellationToken).ConfigureAwait(false); - return bitResp.StatusCode; - } - - var resp = await _ipc.CallAsync( - FocasMessageKind.WriteRequest, - new WriteRequest - { - SessionId = _sessionId, - Address = ToDto(address), - DataType = (int)type, - ValueTypeCode = (int)type, - ValueBytes = EncodeValue(value, type), - }, - FocasMessageKind.WriteResponse, - cancellationToken).ConfigureAwait(false); - - return resp.StatusCode; - } - - public async Task ProbeAsync(CancellationToken cancellationToken) - { - if (!_connected) return false; - try - { - var resp = await _ipc.CallAsync( - FocasMessageKind.ProbeRequest, - new ProbeRequest { SessionId = _sessionId }, - FocasMessageKind.ProbeResponse, - cancellationToken).ConfigureAwait(false); - return resp.Healthy; - } - catch { return false; } - } - - public void Dispose() - { - if (_connected) - { - try - { - _ipc.SendOneWayAsync(FocasMessageKind.CloseSessionRequest, - new CloseSessionRequest { SessionId = _sessionId }, CancellationToken.None) - .GetAwaiter().GetResult(); - } - catch { /* best effort */ } - _connected = false; - } - _ipc.DisposeAsync().AsTask().GetAwaiter().GetResult(); - } - - private static FocasAddressDto ToDto(FocasAddress addr) => new() - { - Kind = (int)addr.Kind, - PmcLetter = addr.PmcLetter, - Number = addr.Number, - BitIndex = addr.BitIndex, - }; - - private static byte[]? EncodeValue(object? value, FocasDataType type) - { - if (value is null) return null; - return type switch - { - FocasDataType.Bit => MessagePackSerializer.Serialize(Convert.ToBoolean(value)), - FocasDataType.Byte => MessagePackSerializer.Serialize(Convert.ToByte(value)), - FocasDataType.Int16 => MessagePackSerializer.Serialize(Convert.ToInt16(value)), - FocasDataType.Int32 => MessagePackSerializer.Serialize(Convert.ToInt32(value)), - FocasDataType.Float32 => MessagePackSerializer.Serialize(Convert.ToSingle(value)), - FocasDataType.Float64 => MessagePackSerializer.Serialize(Convert.ToDouble(value)), - FocasDataType.String => MessagePackSerializer.Serialize(Convert.ToString(value) ?? string.Empty), - _ => MessagePackSerializer.Serialize(Convert.ToInt32(value)), - }; - } - - private static object? DecodeValue(byte[]? bytes, int typeCode) - { - if (bytes is null) return null; - return typeCode switch - { - FocasDataTypeCode.Bit => MessagePackSerializer.Deserialize(bytes), - FocasDataTypeCode.Byte => MessagePackSerializer.Deserialize(bytes), - FocasDataTypeCode.Int16 => MessagePackSerializer.Deserialize(bytes), - FocasDataTypeCode.Int32 => MessagePackSerializer.Deserialize(bytes), - FocasDataTypeCode.Float32 => MessagePackSerializer.Deserialize(bytes), - FocasDataTypeCode.Float64 => MessagePackSerializer.Deserialize(bytes), - FocasDataTypeCode.String => MessagePackSerializer.Deserialize(bytes), - _ => MessagePackSerializer.Deserialize(bytes), - }; - } -} - -/// -/// Factory producing s. One pipe connection per -/// IFocasClient — matches the driver's one-client-per-device invariant. The -/// deployment wires this into the DI container in place of -/// . -/// -public sealed class IpcFocasClientFactory(Func ipcClientFactory, FocasCncSeries series = FocasCncSeries.Unknown) - : IFocasClientFactory -{ - public IFocasClient Create() => new IpcFocasClient(ipcClientFactory(), series); -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/Backoff.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/Backoff.cs deleted file mode 100644 index 8ffa193..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/Backoff.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Supervisor; - -/// -/// Respawn-with-backoff schedule for the FOCAS Host process. Matches Galaxy Tier-C: -/// 5s → 15s → 60s cap. A sustained stable run (default 2 min) resets the index so a -/// one-off crash after hours of steady-state doesn't start from the top of the ladder. -/// -public sealed class Backoff -{ - public static TimeSpan[] DefaultSequence { get; } = - [TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(60)]; - - public TimeSpan StableRunThreshold { get; init; } = TimeSpan.FromMinutes(2); - - private readonly TimeSpan[] _sequence; - private int _index; - - public Backoff(TimeSpan[]? sequence = null) => _sequence = sequence ?? DefaultSequence; - - public TimeSpan Next() - { - var delay = _sequence[Math.Min(_index, _sequence.Length - 1)]; - _index++; - return delay; - } - - public void RecordStableRun() => _index = 0; - - public int AttemptIndex => _index; -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/CircuitBreaker.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/CircuitBreaker.cs deleted file mode 100644 index eeb1a7b..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/CircuitBreaker.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Supervisor; - -/// -/// Crash-loop circuit breaker for the FOCAS Host. Matches Galaxy Tier-C defaults: -/// 3 crashes within 5 minutes opens the breaker; cooldown escalates 1h → 4h → manual -/// reset. A sticky alert stays live until the operator explicitly clears it so -/// recurring crashes can't silently burn through the cooldown ladder overnight. -/// -public sealed class CircuitBreaker -{ - public int CrashesAllowedPerWindow { get; init; } = 3; - public TimeSpan Window { get; init; } = TimeSpan.FromMinutes(5); - - public TimeSpan[] CooldownEscalation { get; init; } = - [TimeSpan.FromHours(1), TimeSpan.FromHours(4), TimeSpan.MaxValue]; - - private readonly List _crashesUtc = []; - private DateTime? _openSinceUtc; - private int _escalationLevel; - public bool StickyAlertActive { get; private set; } - - /// - /// Records a crash + returns true if the supervisor may respawn. On - /// false, is how long to wait before - /// trying again (TimeSpan.MaxValue means manual reset required). - /// - public bool TryRecordCrash(DateTime utcNow, out TimeSpan cooldownRemaining) - { - if (_openSinceUtc is { } openedAt) - { - var cooldown = CooldownEscalation[Math.Min(_escalationLevel, CooldownEscalation.Length - 1)]; - if (cooldown == TimeSpan.MaxValue) - { - cooldownRemaining = TimeSpan.MaxValue; - return false; - } - if (utcNow - openedAt < cooldown) - { - cooldownRemaining = cooldown - (utcNow - openedAt); - return false; - } - - _openSinceUtc = null; - _escalationLevel++; - } - - _crashesUtc.RemoveAll(t => utcNow - t > Window); - _crashesUtc.Add(utcNow); - - if (_crashesUtc.Count > CrashesAllowedPerWindow) - { - _openSinceUtc = utcNow; - StickyAlertActive = true; - cooldownRemaining = CooldownEscalation[Math.Min(_escalationLevel, CooldownEscalation.Length - 1)]; - return false; - } - - cooldownRemaining = TimeSpan.Zero; - return true; - } - - public void ManualReset() - { - _crashesUtc.Clear(); - _openSinceUtc = null; - _escalationLevel = 0; - StickyAlertActive = false; - } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/FocasHostSupervisor.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/FocasHostSupervisor.cs deleted file mode 100644 index fc58d38..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/FocasHostSupervisor.cs +++ /dev/null @@ -1,159 +0,0 @@ -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Supervisor; - -/// -/// Ties + + -/// + into one object the -/// driver asks for IFocasClients. On a detected crash (process exit or -/// heartbeat loss) the supervisor fans out BadCommunicationError to all -/// subscribers via the callback, then respawns with -/// backoff unless the breaker is open. -/// -/// -/// The supervisor itself is I/O-free — it doesn't know how to spawn processes, probe -/// pipes, or send heartbeats. Production wires the concrete -/// over FocasIpcClient + Process; -/// tests drive the same state machine with a deterministic launcher stub. -/// -public sealed class FocasHostSupervisor : IDisposable -{ - private readonly IHostProcessLauncher _launcher; - private readonly Backoff _backoff; - private readonly CircuitBreaker _breaker; - private readonly Func _clock; - private IFocasClient? _current; - private DateTime _currentStartedUtc; - private bool _disposed; - - public FocasHostSupervisor( - IHostProcessLauncher launcher, - Backoff? backoff = null, - CircuitBreaker? breaker = null, - Func? clock = null) - { - _launcher = launcher ?? throw new ArgumentNullException(nameof(launcher)); - _backoff = backoff ?? new Backoff(); - _breaker = breaker ?? new CircuitBreaker(); - _clock = clock ?? (() => DateTime.UtcNow); - } - - /// Raised with a short reason string whenever the Host goes unavailable (crash / heartbeat loss / breaker-open). - public event Action? OnUnavailable; - - /// Crash count observed in the current process lifetime. Exposed for /hosts Admin telemetry. - public int ObservedCrashes { get; private set; } - - /// true if the crash-loop breaker has latched a sticky alert that needs operator reset. - public bool StickyAlertActive => _breaker.StickyAlertActive; - - public int BackoffAttempt => _backoff.AttemptIndex; - - /// - /// Returns the current live client. If none, tries to launch — applying the - /// backoff schedule between attempts and stopping once the breaker opens. - /// - public async Task GetOrLaunchAsync(CancellationToken ct) - { - ThrowIfDisposed(); - if (_current is not null && _launcher.IsProcessAlive) return _current; - - return await LaunchWithBackoffAsync(ct).ConfigureAwait(false); - } - - /// - /// Called by the heartbeat task each time a miss threshold is crossed. - /// Treated as a crash: fan out Bad status + attempt respawn. - /// - public async Task NotifyHostDeadAsync(string reason, CancellationToken ct) - { - ThrowIfDisposed(); - OnUnavailable?.Invoke(reason); - ObservedCrashes++; - try { await _launcher.TerminateAsync(ct).ConfigureAwait(false); } - catch { /* best effort */ } - _current?.Dispose(); - _current = null; - - if (!_breaker.TryRecordCrash(_clock(), out var cooldown)) - { - OnUnavailable?.Invoke(cooldown == TimeSpan.MaxValue - ? "circuit-breaker-open-manual-reset-required" - : $"circuit-breaker-open-cooldown-{cooldown:g}"); - return; - } - // Successful crash recording — do not respawn synchronously; GetOrLaunchAsync will - // pick up the attempt on the next call. Keeps the fan-out fast. - } - - /// Operator action — clear the sticky alert + reset the breaker. - public void AcknowledgeAndReset() - { - _breaker.ManualReset(); - _backoff.RecordStableRun(); - } - - private async Task LaunchWithBackoffAsync(CancellationToken ct) - { - while (true) - { - if (_breaker.StickyAlertActive) - { - if (!_breaker.TryRecordCrash(_clock(), out var cooldown) && cooldown == TimeSpan.MaxValue) - throw new InvalidOperationException( - "FOCAS Host circuit breaker is open and awaiting manual reset. " + - "See Admin /hosts; call AcknowledgeAndReset after investigating the Host log."); - } - - try - { - _current = await _launcher.LaunchAsync(ct).ConfigureAwait(false); - _currentStartedUtc = _clock(); - - // If the launch sequence itself takes long enough to count as a stable run, - // reset the backoff ladder immediately. - if (_clock() - _currentStartedUtc >= _backoff.StableRunThreshold) - _backoff.RecordStableRun(); - - return _current; - } - catch (Exception ex) when (ex is not OperationCanceledException) - { - OnUnavailable?.Invoke($"launch-failed: {ex.Message}"); - ObservedCrashes++; - if (!_breaker.TryRecordCrash(_clock(), out var cooldown)) - { - var hint = cooldown == TimeSpan.MaxValue - ? "manual reset required" - : $"cooldown {cooldown:g}"; - throw new InvalidOperationException( - $"FOCAS Host circuit breaker opened after {ObservedCrashes} crashes — {hint}.", ex); - } - - var delay = _backoff.Next(); - await Task.Delay(delay, ct).ConfigureAwait(false); - } - } - } - - /// Called from the heartbeat loop after a successful ack run — relaxes the backoff ladder. - public void NotifyStableRun() - { - if (_current is null) return; - if (_clock() - _currentStartedUtc >= _backoff.StableRunThreshold) - _backoff.RecordStableRun(); - } - - public void Dispose() - { - if (_disposed) return; - _disposed = true; - try { _launcher.TerminateAsync(CancellationToken.None).GetAwaiter().GetResult(); } - catch { /* best effort */ } - _current?.Dispose(); - _current = null; - } - - private void ThrowIfDisposed() - { - if (_disposed) throw new ObjectDisposedException(nameof(FocasHostSupervisor)); - } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/HeartbeatMonitor.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/HeartbeatMonitor.cs deleted file mode 100644 index 5b43309..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/HeartbeatMonitor.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Supervisor; - -/// -/// Tracks missed heartbeats from the FOCAS Host. 2s cadence + 3 consecutive misses = -/// host declared dead (~6s detection). Same defaults as Galaxy Tier-C so operators -/// see the same cadence across hosts on the /hosts Admin page. -/// -public sealed class HeartbeatMonitor -{ - public int MissesUntilDead { get; init; } = 3; - - public TimeSpan Cadence { get; init; } = TimeSpan.FromSeconds(2); - - public int ConsecutiveMisses { get; private set; } - public DateTime? LastAckUtc { get; private set; } - - public void RecordAck(DateTime utcNow) - { - ConsecutiveMisses = 0; - LastAckUtc = utcNow; - } - - /// Records a missed heartbeat; returns true when the death threshold is crossed. - public bool RecordMiss() - { - ConsecutiveMisses++; - return ConsecutiveMisses >= MissesUntilDead; - } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/IHostProcessLauncher.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/IHostProcessLauncher.cs deleted file mode 100644 index 50c4024..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/IHostProcessLauncher.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Supervisor; - -/// -/// Abstraction over the act of spawning a FOCAS Host process and obtaining an -/// connected to it. Production wires this to a real -/// Process.Start + FocasIpcClient.ConnectAsync; tests use a fake that -/// exposes deterministic failure modes so the supervisor logic can be stressed -/// without spawning actual exes. -/// -public interface IHostProcessLauncher -{ - /// - /// Spawn a new Host process (if one isn't already running) and return a live - /// client session. Throws on unrecoverable errors; transient errors (e.g. Host - /// not ready yet) should throw so the supervisor - /// applies the backoff ladder. - /// - Task LaunchAsync(CancellationToken ct); - - /// - /// Terminate the Host process if one is running. Called on Dispose and after a - /// heartbeat loss is detected. - /// - Task TerminateAsync(CancellationToken ct); - - /// - /// true when the most recently spawned Host process is still alive. - /// Supervisor polls this at heartbeat cadence; going false without a - /// clean shutdown counts as a crash. - /// - bool IsProcessAlive { get; } -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/PostMortemReader.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/PostMortemReader.cs deleted file mode 100644 index f5236dc..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/PostMortemReader.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.IO.MemoryMappedFiles; -using System.Text; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Supervisor; - -/// -/// Proxy-side reader for the Host's post-mortem MMF. After a Host crash the supervisor -/// opens the file (which persists beyond the process lifetime) and enumerates the last -/// few thousand IPC operations that were in flight. Format matches -/// Driver.FOCAS.Host.Stability.PostMortemMmf — magic 'OFPC' / 256-byte entries. -/// -public sealed class PostMortemReader -{ - private const int Magic = 0x4F465043; // 'OFPC' - private const int HeaderBytes = 16; - private const int EntryBytes = 256; - private const int MessageOffset = 16; - private const int MessageCapacity = EntryBytes - MessageOffset; - - public string Path { get; } - - public PostMortemReader(string path) => Path = path; - - public PostMortemEntry[] ReadAll() - { - if (!File.Exists(Path)) return []; - - using var mmf = MemoryMappedFile.CreateFromFile(Path, FileMode.Open, null, 0, MemoryMappedFileAccess.Read); - using var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); - - if (accessor.ReadInt32(0) != Magic) return []; - - var capacity = accessor.ReadInt32(8); - var writeIndex = accessor.ReadInt32(12); - var entries = new PostMortemEntry[capacity]; - var count = 0; - - for (var i = 0; i < capacity; i++) - { - var slot = (writeIndex + i) % capacity; - var offset = HeaderBytes + slot * EntryBytes; - var ts = accessor.ReadInt64(offset + 0); - if (ts == 0) continue; - var op = accessor.ReadInt64(offset + 8); - var msgBuf = new byte[MessageCapacity]; - accessor.ReadArray(offset + MessageOffset, msgBuf, 0, MessageCapacity); - var nulTerm = Array.IndexOf(msgBuf, 0); - var msg = Encoding.UTF8.GetString(msgBuf, 0, nulTerm < 0 ? MessageCapacity : nulTerm); - entries[count++] = new PostMortemEntry(ts, op, msg); - } - - Array.Resize(ref entries, count); - return entries; - } -} - -public readonly record struct PostMortemEntry(long UtcUnixMs, long OpKind, string Message); diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/ProcessHostLauncher.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/ProcessHostLauncher.cs deleted file mode 100644 index f31145c..0000000 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Supervisor/ProcessHostLauncher.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Diagnostics; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Ipc; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Supervisor; - -/// -/// Production . Spawns OtOpcUa.Driver.FOCAS.Host.exe -/// with the pipe name / allowed-SID / per-spawn shared secret in the environment, waits for -/// the pipe to come up, then connects a and wraps it in an -/// . On best-effort kills the -/// process and closes the IPC stream. -/// -public sealed class ProcessHostLauncher : IHostProcessLauncher -{ - private readonly ProcessHostLauncherOptions _options; - private Process? _process; - private FocasIpcClient? _ipc; - - public ProcessHostLauncher(ProcessHostLauncherOptions options) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - } - - public bool IsProcessAlive => _process is { HasExited: false }; - - public async Task LaunchAsync(CancellationToken ct) - { - await TerminateAsync(ct).ConfigureAwait(false); - - var secret = _options.SharedSecret ?? Guid.NewGuid().ToString("N"); - - var psi = new ProcessStartInfo - { - FileName = _options.HostExePath, - Arguments = _options.Arguments ?? string.Empty, - UseShellExecute = false, - CreateNoWindow = true, - }; - psi.Environment["OTOPCUA_FOCAS_PIPE"] = _options.PipeName; - psi.Environment["OTOPCUA_ALLOWED_SID"] = _options.AllowedSid; - psi.Environment["OTOPCUA_FOCAS_SECRET"] = secret; - psi.Environment["OTOPCUA_FOCAS_BACKEND"] = _options.Backend; - - _process = Process.Start(psi) - ?? throw new InvalidOperationException($"Failed to start {_options.HostExePath}"); - - // Poll for pipe readiness up to the configured connect timeout. - var deadline = DateTime.UtcNow + _options.ConnectTimeout; - while (true) - { - ct.ThrowIfCancellationRequested(); - if (_process.HasExited) - throw new InvalidOperationException( - $"FOCAS Host exited before pipe was ready (ExitCode={_process.ExitCode})."); - - try - { - _ipc = await FocasIpcClient.ConnectAsync( - _options.PipeName, secret, TimeSpan.FromSeconds(1), ct).ConfigureAwait(false); - break; - } - catch (TimeoutException) - { - if (DateTime.UtcNow >= deadline) - throw new TimeoutException( - $"FOCAS Host pipe {_options.PipeName} did not come up within {_options.ConnectTimeout:g}."); - await Task.Delay(TimeSpan.FromMilliseconds(250), ct).ConfigureAwait(false); - } - } - - return new IpcFocasClient(_ipc, _options.Series); - } - - public async Task TerminateAsync(CancellationToken ct) - { - if (_ipc is not null) - { - try { await _ipc.DisposeAsync().ConfigureAwait(false); } - catch { /* best effort */ } - _ipc = null; - } - - if (_process is not null) - { - try - { - if (!_process.HasExited) - { - _process.Kill(entireProcessTree: true); - await _process.WaitForExitAsync(ct).ConfigureAwait(false); - } - } - catch { /* best effort */ } - finally - { - _process.Dispose(); - _process = null; - } - } - } -} - -public sealed record ProcessHostLauncherOptions( - string HostExePath, - string PipeName, - string AllowedSid) -{ - public string? SharedSecret { get; init; } - public string? Arguments { get; init; } - public string Backend { get; init; } = "fwlib32"; - public TimeSpan ConnectTimeout { get; init; } = TimeSpan.FromSeconds(15); - public FocasCncSeries Series { get; init; } = FocasCncSeries.Unknown; -} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasConstants.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasConstants.cs new file mode 100644 index 0000000..b82824d --- /dev/null +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasConstants.cs @@ -0,0 +1,120 @@ +namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire; + +/// +/// PMC address-letter → FOCAS ADR_* numeric code. Values are the FOCAS/2 wire +/// constants passed as the area argument on pmc_rdpmcrng +/// (G=0, F=1, Y=2, X=3, A=4, R=5, T=6, K=7, C=8, D=9, E=10). +/// +public enum FocasPmcArea : short +{ + G = 0, + F = 1, + Y = 2, + X = 3, + A = 4, + R = 5, + T = 6, + K = 7, + C = 8, + D = 9, + E = 10, +} + +/// +/// PMC data-type numeric codes per FOCAS/2: Byte=0, Word=1, Long=2, +/// Real=4, Double=5. Passed as the data_type argument on +/// pmc_rdpmcrng. +/// +public enum FocasPmcDataType : short +{ + Byte = 0, + Word = 1, + Long = 2, + Real = 4, + Double = 5, +} + +/// +/// CNC operation mode as reported by cnc_rdopmode. Values are the FOCAS-defined +/// mode codes; see for the canonical +/// operator-facing labels. +/// +public enum FocasOperationMode : short +{ + Mdi = 0, + Auto = 1, + TJog = 2, + Edit = 3, + Handle = 4, + Jog = 5, + TeachInHandle = 6, + Reference = 7, + Remote = 8, + Test = 9, +} + +/// Extension helpers over . +public static class FocasOperationModeExtensions +{ + /// + /// Canonical operator-facing label for an operation mode (e.g. "AUTO", + /// "EDIT"). Unknown codes fall back to the raw numeric value as a string + /// so the UI still shows something interpretable. + /// + public static string ToText(this FocasOperationMode mode) => mode switch + { + FocasOperationMode.Mdi => "MDI", + FocasOperationMode.Auto => "AUTO", + FocasOperationMode.TJog => "T-JOG", + FocasOperationMode.Edit => "EDIT", + FocasOperationMode.Handle => "HANDLE", + FocasOperationMode.Jog => "JOG", + FocasOperationMode.TeachInHandle => "TEACH-IN-HANDLE", + FocasOperationMode.Reference => "REFERENCE", + FocasOperationMode.Remote => "REMOTE", + FocasOperationMode.Test => "TEST", + _ => ((short)mode).ToString(), + }; +} + +/// +/// Letter → lookup. Used by to +/// translate a parsed into the wire code expected by +/// pmc_rdpmcrng. +/// +internal static class FocasPmcAreaLookup +{ + public static FocasPmcArea? FromLetter(string letter) => letter.ToUpperInvariant() switch + { + "G" => FocasPmcArea.G, + "F" => FocasPmcArea.F, + "Y" => FocasPmcArea.Y, + "X" => FocasPmcArea.X, + "A" => FocasPmcArea.A, + "R" => FocasPmcArea.R, + "T" => FocasPmcArea.T, + "K" => FocasPmcArea.K, + "C" => FocasPmcArea.C, + "D" => FocasPmcArea.D, + "E" => FocasPmcArea.E, + _ => null, + }; +} + +/// +/// mapping for wire PMC +/// reads. Bit reads collapse to byte — the caller extracts the bit from the returned +/// value. +/// +internal static class FocasPmcDataTypeLookup +{ + public static FocasPmcDataType FromFocasDataType(FocasDataType t) => t switch + { + FocasDataType.Bit or FocasDataType.Byte => FocasPmcDataType.Byte, + FocasDataType.Int16 => FocasPmcDataType.Word, + FocasDataType.Int32 => FocasPmcDataType.Long, + FocasDataType.Float32 => FocasPmcDataType.Real, + FocasDataType.Float64 => FocasPmcDataType.Double, + _ => FocasPmcDataType.Byte, + }; +} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs new file mode 100644 index 0000000..1d636d1 --- /dev/null +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireClient.cs @@ -0,0 +1,883 @@ +using System.Buffers.Binary; +using System.Net.Sockets; +using Microsoft.Extensions.Logging; + +namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire; + +/// +/// Pure-managed read-only FOCAS/2 Ethernet wire client. Speaks the proprietary Fanuc +/// binary protocol on TCP:8193 directly — no P/Invoke, no Fwlib64.dll, no +/// out-of-process Host. One instance owns two TCP sockets for the duration of a CNC +/// session; runs the +/// two-socket initiate handshake and a setup request, subsequent reads reuse +/// socket 2 serialised through an internal semaphore. +/// +/// +/// Read surface. Covers every FOCAS call OtOpcUa's managed driver issues: +/// sysinfo, status, axis + spindle names, the cnc_rddynamic2 fast-poll bundle, +/// parameters (typed + raw-bytes overloads), macros, PMC ranges, alarms, operation mode, +/// executing program, block count, timers, and servo / spindle meters. Writes are +/// intentionally out of scope. +/// Concurrency. Callers may issue reads concurrently from multiple threads +/// — socket 2 is guarded by a so at most one +/// request/response pair is in flight at a time. +/// and share a second semaphore to stop the two racing. +/// Transient failures. When cancellation or a socket-level error happens +/// mid-request the client closes both sockets and throws +/// with +/// set — the caller must reconnect before issuing the next request. The transport is +/// left deliberately torn down rather than half-open so a truncated response never +/// desynchronises the next caller's read. +/// +public sealed class FocasWireClient : IAsyncDisposable, IDisposable +{ + private readonly ILogger? _logger; + private readonly SemaphoreSlim _requestGate = new(1, 1); + private readonly SemaphoreSlim _lifetimeGate = new(1, 1); + private TcpClient? _socket1; + private TcpClient? _socket2; + private NetworkStream? _stream1; + private NetworkStream? _stream2; + private bool _connected; + private bool _disposed; + private FocasResult? _sysInfo; + + /// + /// Construct a disconnected client. Optional receives + /// Debug-level entries per response block (command ID, RC, payload length). + /// + public FocasWireClient(ILogger? logger = null) + { + _logger = logger; + } + + /// + /// Default PathId applied when no per-call override is supplied. Relevant for + /// multi-path CNCs; single-path controllers leave this at the default of 1. + /// + public ushort PathId { get; set; } = 1; + + /// True when the two-socket handshake has completed and the transport is live. + public bool IsConnected => _connected; + + /// + /// Open the FOCAS session using an integer-seconds timeout. Idempotent — a second + /// call while already connected is a no-op. Sub-second timeouts require the + /// overload. + /// + public Task ConnectAsync( + string host, + int port, + int timeoutSeconds = 10, + CancellationToken cancellationToken = default) + => ConnectCoreAsync( + host, + port, + timeoutSeconds > 0 ? TimeSpan.FromSeconds(timeoutSeconds) : null, + cancellationToken); + + /// + /// Open the FOCAS session with a timeout. Pass + /// to disable the timeout entirely (rely on the caller's + /// instead). Idempotent. + /// + public Task ConnectAsync( + string host, + int port, + TimeSpan timeout, + CancellationToken cancellationToken = default) + => ConnectCoreAsync(host, port, timeout == TimeSpan.Zero ? null : timeout, cancellationToken); + + private async Task ConnectCoreAsync( + string host, + int port, + TimeSpan? timeoutValue, + CancellationToken cancellationToken) + { + await _lifetimeGate.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + ThrowIfDisposed(); + if (_connected) return; + + using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + if (timeoutValue is { } value) timeout.CancelAfter(value); + + try + { + _socket1 = await ConnectSocketAsync(host, port, timeout.Token).ConfigureAwait(false); + _stream1 = _socket1.GetStream(); + await SendPduAsync(_stream1, FocasWireProtocol.TypeInitiate, FocasWireProtocol.BuildInitiateBody(1), timeout.Token).ConfigureAwait(false); + await ReadExpectedPduAsync(_stream1, FocasWireProtocol.TypeInitiate, timeout.Token).ConfigureAwait(false); + + _socket2 = await ConnectSocketAsync(host, port, timeout.Token).ConfigureAwait(false); + _stream2 = _socket2.GetStream(); + await SendPduAsync(_stream2, FocasWireProtocol.TypeInitiate, FocasWireProtocol.BuildInitiateBody(2), timeout.Token).ConfigureAwait(false); + await ReadExpectedPduAsync(_stream2, FocasWireProtocol.TypeInitiate, timeout.Token).ConfigureAwait(false); + + _connected = true; + // Cache the sysinfo payload from the setup exchange so later + // ReadSysInfoAsync calls are a lookup rather than a wire hit. + var sysInfoBlock = await SendSingleRequestAsync(timeout.Token, new RequestBlock(0x0018, PathId: PathId)).ConfigureAwait(false); + _sysInfo = ToResult(sysInfoBlock, ParseSysInfo); + // Kick the cached path/session metadata request the DLL sends + // right after initiate. The result is ignored; the CNC uses it to + // populate internal state the subsequent reads depend on. + await SendRequestAsync(timeout.Token, new RequestBlock(0x000e, 0x26f0, 0x26f0, PathId: PathId)).ConfigureAwait(false); + } + catch (Exception ex) when (IsTransientException(ex)) + { + CloseTransport(); + throw new FocasWireException("FOCAS wire connect failed.", ex, isTransient: true); + } + } + finally + { + _lifetimeGate.Release(); + } + } + + /// + /// Synchronous dispose — sends the close PDU when connected and tears down both + /// sockets. Idempotent. Callers on an async context should prefer + /// . + /// + public void Dispose() + { + _lifetimeGate.Wait(); + try + { + if (_disposed) return; + + _disposed = true; + if (_stream2 is not null && _connected) + { + try + { + SendPdu(_stream2, FocasWireProtocol.TypeClose, ReadOnlySpan.Empty); + _ = FocasWireProtocol.ReadPdu(_stream2); + } + catch + { + // Close best-effort — don't let teardown failure hide a caller's real error. + } + } + CloseTransport(); + } + finally + { + _lifetimeGate.Release(); + } + } + + /// + /// Async dispose — sends the close PDU when connected and tears down both sockets. + /// Idempotent. + /// + public async ValueTask DisposeAsync() + { + await _lifetimeGate.WaitAsync(CancellationToken.None).ConfigureAwait(false); + try + { + if (_disposed) return; + + _disposed = true; + if (_stream2 is not null && _connected) + { + try + { + await SendPduAsync(_stream2, FocasWireProtocol.TypeClose, ReadOnlyMemory.Empty, CancellationToken.None).ConfigureAwait(false); + await FocasWireProtocol.ReadPduAsync(_stream2, CancellationToken.None).ConfigureAwait(false); + } + catch + { + // Close best-effort — don't let teardown failure hide a caller's real error. + } + } + CloseTransport(); + } + finally + { + _lifetimeGate.Release(); + } + } + + /// + /// Read CNC identity via cnc_sysinfo. Cached from the connect-time exchange + /// unless a per-call override is supplied. + /// + public async Task> ReadSysInfoAsync( + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + { + if (pathId is null && _sysInfo is { } cached) return cached; + + using var callTimeout = CreateCallTimeout(cancellationToken, timeout); + return await ReadSingleAsync(0x0018, ParseSysInfo, EffectivePathId(pathId), cancellationToken: callTimeout.Token).ConfigureAwait(false); + } + + /// Read CNC status bits via cnc_statinfo (3 command blocks aggregated into one ). + public async Task> ReadStatusAsync( + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + { + using var callTimeout = CreateCallTimeout(cancellationToken, timeout); + var requestPathId = EffectivePathId(pathId); + var blocks = await SendRequestAsync( + callTimeout.Token, + new RequestBlock(0x0019, PathId: requestPathId), + new RequestBlock(0x00e1, PathId: requestPathId), + new RequestBlock(0x0098, PathId: requestPathId)).ConfigureAwait(false); + + var rc = AggregateRc(blocks); + if (rc != 0) return new FocasResult(rc, null); + + var primary = FindPayload(blocks, 0x0019); + RequireLength(primary, 14, "cnc_statinfo"); + var tmModePayload = FindPayload(blocks, 0x0098); + var tmMode = tmModePayload.Length >= 2 ? ReadInt16(tmModePayload, 0) : (short)0; + + return new FocasResult( + rc, + new WireStatus( + Auto: ReadInt16(primary, 0), + Run: ReadInt16(primary, 2), + Motion: ReadInt16(primary, 4), + Mstb: ReadInt16(primary, 6), + Emergency: ReadInt16(primary, 8), + Alarm: ReadInt16(primary, 10), + Edit: ReadInt16(primary, 12), + TmMode: tmMode)); + } + + /// Read configured axis names via cnc_rdaxisname (command 0x0089). + public async Task>> ReadAxisNamesAsync( + short maxCount = 32, + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + { + using var callTimeout = CreateCallTimeout(cancellationToken, timeout); + var block = await SendSingleRequestAsync(callTimeout.Token, new RequestBlock(0x0089, PathId: EffectivePathId(pathId))).ConfigureAwait(false); + return ToResult(block, payload => ReadNameRecords(payload, maxCount, (index, name) => new WireAxisRecord(index, name))); + } + + /// Read configured spindle names via cnc_rdspdlname (command 0x008a). + public async Task>> ReadSpindleNamesAsync( + short maxCount = 8, + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + { + using var callTimeout = CreateCallTimeout(cancellationToken, timeout); + var block = await SendSingleRequestAsync(callTimeout.Token, new RequestBlock(0x008a, PathId: EffectivePathId(pathId))).ConfigureAwait(false); + return ToResult(block, payload => ReadNameRecords(payload, maxCount, (index, name) => new WireSpindleRecord(index, name))); + } + + /// + /// Fast-poll bundle for one axis via cnc_rddynamic2. Sends 9 request blocks in + /// one PDU and aggregates the replies — alarm flags, program/sequence numbers, feed + /// and spindle actuals, plus the four-slot position quadruple. + /// + public async Task> ReadDynamic2Async( + short axis = 1, + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + { + using var callTimeout = CreateCallTimeout(cancellationToken, timeout); + var requestPathId = EffectivePathId(pathId); + var blocks = await SendRequestAsync( + callTimeout.Token, + new RequestBlock(0x001a, PathId: requestPathId), + new RequestBlock(0x001c, PathId: requestPathId), + new RequestBlock(0x001d, PathId: requestPathId), + new RequestBlock(0x0024, PathId: requestPathId), + new RequestBlock(0x0025, PathId: requestPathId), + new RequestBlock(0x0026, 4, axis, PathId: requestPathId), + new RequestBlock(0x0026, 1, axis, PathId: requestPathId), + new RequestBlock(0x0026, 6, axis, PathId: requestPathId), + new RequestBlock(0x0026, 7, axis, PathId: requestPathId)).ConfigureAwait(false); + + var rc = AggregateRc(blocks); + if (rc != 0) return new FocasResult(rc, null); + + var programPayload = FindPayload(blocks, 0x001c); + return new FocasResult( + rc, + new WireDynamic( + ReadFirstInt32(blocks, 0x001a), + programPayload.Length >= 4 ? ReadInt32(programPayload, 0) : 0, + programPayload.Length >= 8 ? ReadInt32(programPayload, 4) : 0, + ReadFirstInt32(blocks, 0x001d), + ReadFirstInt32(blocks, 0x0024), + ReadFirstInt32(blocks, 0x0025), + new WireAxisPosition( + ReadSelectorPosition(blocks, 0x0026, 0), + ReadSelectorPosition(blocks, 0x0026, 1), + ReadSelectorPosition(blocks, 0x0026, 2), + ReadSelectorPosition(blocks, 0x0026, 3)))); + } + + /// Read servo-meter load percentages via cnc_rdsvmeter (command 0x0056). + public async Task>> ReadServoMeterAsync( + short maxCount = 32, + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + { + using var callTimeout = CreateCallTimeout(cancellationToken, timeout); + var requestPathId = EffectivePathId(pathId); + var blocks = await SendRequestAsync( + callTimeout.Token, + new RequestBlock(0x0056, 1, PathId: requestPathId), + new RequestBlock(0x0089, PathId: requestPathId)).ConfigureAwait(false); + + var rc = AggregateRc(blocks); + if (rc != 0) return new FocasResult>(rc, null); + + var payload = FindPayload(blocks, 0x0056); + var result = new List(); + for (var offset = 0; offset + 12 <= payload.Length && result.Count < maxCount; offset += 12) + { + var name = FocasWireProtocol.ReadNameRecord(payload.AsSpan(offset + 8, 4)); + result.Add(new WireServoMeter( + (short)(result.Count + 1), + name, + ReadInt32(payload, offset), + ReadInt16(payload, offset + 4), + ReadInt16(payload, offset + 6))); + } + + return new FocasResult>(rc, result); + } + + /// Read per-spindle load percentages via cnc_rdspload (command 0x0040 with arg1=0). + public Task>> ReadSpindleLoadAsync( + short spindleSelector = -1, + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + => ReadSpindleMetricAsync(0, spindleSelector, cancellationToken, timeout, pathId); + + /// Read per-spindle maximum RPMs via cnc_rdspmaxrpm (command 0x0040 with arg1=1). + public Task>> ReadSpindleMaxRpmAsync( + short spindleSelector = -1, + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + => ReadSpindleMetricAsync(1, spindleSelector, cancellationToken, timeout, pathId); + + /// + /// Raw-bytes parameter read via cnc_rdparam. Caller marshals the returned + /// payload to the type declared in the per-series parameter catalog. + /// selects an axis-scoped parameter; 0 means global. + /// + public async Task> ReadParameterBytesAsync( + short dataNumber, + short axis = 0, + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + { + using var callTimeout = CreateCallTimeout(cancellationToken, timeout); + var secondArg = axis == 0 ? dataNumber : axis; + var block = await SendSingleRequestAsync(callTimeout.Token, new RequestBlock(0x000e, dataNumber, secondArg, PathId: EffectivePathId(pathId))).ConfigureAwait(false); + return ToResult(block, payload => payload); + } + + /// Typed Int32 parameter read — convenience over . + public async Task> ReadParameterAsync( + short dataNumber, + short type = 0, + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + { + var result = await ReadParameterBytesAsync(dataNumber, cancellationToken: cancellationToken, timeout: timeout, pathId: pathId).ConfigureAwait(false); + if (!result.IsOk || result.Value is null) return new FocasResult(result.Rc, null); + return new FocasResult( + result.Rc, + new WireParameter(dataNumber, type, result.Value.Length >= 4 ? ReadInt32(result.Value, 0) : 0)); + } + + /// Typed 8-bit parameter read. + public async Task> ReadParameterByteAsync(short dataNumber, short axis = 0, CancellationToken cancellationToken = default, TimeSpan? timeout = null, ushort? pathId = null) + { + var result = await ReadParameterBytesAsync(dataNumber, axis, cancellationToken, timeout, pathId).ConfigureAwait(false); + return !result.IsOk || result.Value is null + ? new FocasResult(result.Rc, default) + : new FocasResult(result.Rc, result.Value.Length >= 1 ? result.Value[0] : default); + } + + /// Typed 16-bit parameter read. + public async Task> ReadParameterInt16Async(short dataNumber, short axis = 0, CancellationToken cancellationToken = default, TimeSpan? timeout = null, ushort? pathId = null) + { + var result = await ReadParameterBytesAsync(dataNumber, axis, cancellationToken, timeout, pathId).ConfigureAwait(false); + return !result.IsOk || result.Value is null + ? new FocasResult(result.Rc, default) + : new FocasResult(result.Rc, result.Value.Length >= 2 ? ReadInt16(result.Value, 0) : default); + } + + /// Typed 32-bit parameter read. + public async Task> ReadParameterInt32Async(short dataNumber, short axis = 0, CancellationToken cancellationToken = default, TimeSpan? timeout = null, ushort? pathId = null) + { + var result = await ReadParameterBytesAsync(dataNumber, axis, cancellationToken, timeout, pathId).ConfigureAwait(false); + return !result.IsOk || result.Value is null + ? new FocasResult(result.Rc, default) + : new FocasResult(result.Rc, result.Value.Length >= 4 ? ReadInt32(result.Value, 0) : default); + } + + /// Typed IEEE-754 single-precision parameter read. + public async Task> ReadParameterFloat32Async(short dataNumber, short axis = 0, CancellationToken cancellationToken = default, TimeSpan? timeout = null, ushort? pathId = null) + { + var result = await ReadParameterBytesAsync(dataNumber, axis, cancellationToken, timeout, pathId).ConfigureAwait(false); + return !result.IsOk || result.Value is null || result.Value.Length < 4 + ? new FocasResult(result.Rc, default) + : new FocasResult(result.Rc, BitConverter.Int32BitsToSingle(ReadInt32(result.Value, 0))); + } + + /// Typed IEEE-754 double-precision parameter read. + public async Task> ReadParameterFloat64Async(short dataNumber, short axis = 0, CancellationToken cancellationToken = default, TimeSpan? timeout = null, ushort? pathId = null) + { + var result = await ReadParameterBytesAsync(dataNumber, axis, cancellationToken, timeout, pathId).ConfigureAwait(false); + return !result.IsOk || result.Value is null || result.Value.Length < 8 + ? new FocasResult(result.Rc, default) + : new FocasResult(result.Rc, BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64BigEndian(result.Value.AsSpan(0, 8)))); + } + + /// Read a single macro variable via cnc_rdmacro (command 0x0015). + public Task> ReadMacroAsync( + short number, + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + => ReadSingleWithTimeoutAsync( + 0x0015, + payload => new WireMacro(number, payload.Length >= 4 ? ReadInt32(payload, 0) : 0, payload.Length >= 6 ? ReadInt16(payload, 4) : (short)0), + cancellationToken, timeout, EffectivePathId(pathId), number, number); + + /// + /// Read a PMC range via pmc_rdpmcrng. is the numeric + /// address-letter code (see ); + /// is the width code (see ). Payload is decoded into + /// — one entry per slot of the requested width. + /// + public async Task> ReadPmcRangeAsync( + short area, + short dataType, + ushort start, + ushort end, + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + { + if (end < start) + throw new ArgumentOutOfRangeException(nameof(end), "PMC end address must be greater than or equal to start."); + + using var callTimeout = CreateCallTimeout(cancellationToken, timeout); + var block = await SendSingleRequestAsync( + callTimeout.Token, + new RequestBlock(0x8001, start, end, area, dataType, RequestClass: 2, PathId: EffectivePathId(pathId))).ConfigureAwait(false); + + return ToResult(block, payload => + { + var width = dataType switch + { + 1 => 2, + 2 or 4 => 4, + 5 => 8, + _ => 1, + }; + + var values = new List(); + for (var offset = 0; offset + width <= payload.Length; offset += width) + { + values.Add(width switch + { + 1 => payload[offset], + 2 => ReadInt16(payload, offset), + 4 => ReadInt32(payload, offset), + 8 => BinaryPrimitives.ReadInt64BigEndian(payload.AsSpan(offset, 8)), + _ => 0, + }); + } + + return new WirePmcRange(area, dataType, start, end, values); + }); + } + + /// Typed overload for . + public Task> ReadPmcRangeAsync( + FocasPmcArea area, + FocasPmcDataType dataType, + ushort start, + ushort end, + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + => ReadPmcRangeAsync((short)area, (short)dataType, start, end, cancellationToken, timeout, pathId); + + /// + /// Read active alarms via cnc_rdalmmsg2 (command 0x0023). Parses both + /// the 76-byte vendor ODBALMMSG2_data layout and the 80-byte legacy wire + /// shape so the same managed surface works across firmware revisions. + /// + public async Task>> ReadAlarmsAsync( + short type = -1, + short count = 32, + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + { + using var callTimeout = CreateCallTimeout(cancellationToken, timeout); + var block = await SendSingleRequestAsync( + callTimeout.Token, + new RequestBlock(0x0023, type, count, 2, 0x40, PathId: EffectivePathId(pathId))).ConfigureAwait(false); + + return ToResult(block, payload => ParseAlarms(payload, count)); + } + + /// Read operation mode via cnc_rdopmode, returned as the typed . + public Task> ReadOperationModeAsync( + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + => ReadSingleWithTimeoutAsync( + 0x0057, + payload => (FocasOperationMode)(payload.Length >= 2 ? ReadInt16(payload, 0) : (short)0), + cancellationToken, timeout, EffectivePathId(pathId)); + + /// + /// Raw-code variant of — returns the underlying + /// FOCAS short so callers storing the raw mode code (e.g. OtOpcUa's + /// FocasProgramInfo.Mode int field) don't have to cast the enum. + /// + public Task> ReadOperationModeCodeAsync( + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + => ReadSingleWithTimeoutAsync( + 0x0057, + payload => payload.Length >= 2 ? ReadInt16(payload, 0) : (short)0, + cancellationToken, timeout, EffectivePathId(pathId)); + + /// Read the currently-executing program name + O-number via cnc_exeprgname2 (command 0x00fc). + public Task> ReadExecutingProgramNameAsync( + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + => ReadSingleWithTimeoutAsync(0x00fc, ParseProgramName, cancellationToken, timeout, EffectivePathId(pathId)); + + /// Read the executed block count via cnc_rdblkcount. + public Task> ReadBlockCountAsync( + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + => ReadSingleWithTimeoutAsync( + 0x0035, + payload => payload.Length >= 4 ? ReadInt32(payload, 0) : 0, + cancellationToken, timeout, EffectivePathId(pathId)); + + /// + /// Read one cumulative timer via cnc_rdtimer. selects + /// PowerOn / Operating / Cutting / Cycle per the FOCAS spec (0..3). + /// + public Task> ReadTimerAsync( + short type, + CancellationToken cancellationToken = default, + TimeSpan? timeout = null, + ushort? pathId = null) + => ReadSingleWithTimeoutAsync( + 0x0120, + payload => new WireTimer(type, payload.Length >= 4 ? ReadInt32(payload, 0) : 0, payload.Length >= 8 ? ReadInt32(payload, 4) : 0), + cancellationToken, timeout, EffectivePathId(pathId), type); + + // ---- internal plumbing ------------------------------------------------------------ + + private async Task>> ReadSpindleMetricAsync( + int metric, short spindleSelector, CancellationToken cancellationToken, TimeSpan? timeout, ushort? pathId) + { + using var callTimeout = CreateCallTimeout(cancellationToken, timeout); + var block = await SendSingleRequestAsync( + callTimeout.Token, + new RequestBlock(0x0040, metric, spindleSelector, PathId: EffectivePathId(pathId))).ConfigureAwait(false); + + return ToResult>(block, payload => + { + var values = new List(); + for (var offset = 0; offset + 8 <= payload.Length; offset += 8) + values.Add(new WireSpindleMetric((short)(values.Count + 1), ReadInt32(payload, offset))); + return values; + }); + } + + private async Task> ReadSingleAsync( + ushort command, + Func parser, + ushort? pathId = null, + int arg1 = 0, + int arg2 = 0, + int arg3 = 0, + int arg4 = 0, + CancellationToken cancellationToken = default) + { + var block = await SendSingleRequestAsync(cancellationToken, new RequestBlock(command, arg1, arg2, arg3, arg4, PathId: EffectivePathId(pathId))).ConfigureAwait(false); + return ToResult(block, parser); + } + + private async Task> ReadSingleWithTimeoutAsync( + ushort command, + Func parser, + CancellationToken cancellationToken, + TimeSpan? timeout, + ushort pathId, + int arg1 = 0, + int arg2 = 0, + int arg3 = 0, + int arg4 = 0) + { + using var callTimeout = CreateCallTimeout(cancellationToken, timeout); + return await ReadSingleAsync(command, parser, pathId, arg1, arg2, arg3, arg4, callTimeout.Token).ConfigureAwait(false); + } + + private async Task SendSingleRequestAsync(CancellationToken cancellationToken, RequestBlock block) + { + var blocks = await SendRequestAsync(cancellationToken, block).ConfigureAwait(false); + return blocks.Count == 0 ? new ResponseBlock(block.Command, 0, Array.Empty()) : blocks[0]; + } + + private async Task> SendRequestAsync(CancellationToken cancellationToken, params RequestBlock[] blocks) + { + EnsureConnected(); + await _requestGate.WaitAsync(cancellationToken).ConfigureAwait(false); + var requestStarted = false; + try + { + var body = FocasWireProtocol.BuildRequestBody(blocks); + requestStarted = true; + await SendPduAsync(_stream2!, FocasWireProtocol.TypeData, body, cancellationToken).ConfigureAwait(false); + var response = await ReadExpectedPduAsync(_stream2!, FocasWireProtocol.TypeData, cancellationToken).ConfigureAwait(false); + var responseBlocks = FocasWireProtocol.ParseResponseBlocks(response.Body); + foreach (var block in responseBlocks) + _logger?.LogDebug("FOCAS response command=0x{Command:x4} rc={Rc} payloadLength={PayloadLength}", block.Command, block.Rc, block.Payload.Length); + return responseBlocks; + } + catch (Exception ex) when (requestStarted && IsTransientException(ex)) + { + // A cancelled or failed mid-request write leaves the wire in an undefined state — + // tear the connection down so the next caller reconnects cleanly instead of + // consuming a stale response. + CloseTransport(); + throw new FocasWireException("FOCAS wire request failed; connection was closed to avoid response desynchronization.", ex, isTransient: true); + } + finally + { + _requestGate.Release(); + } + } + + private static async Task ConnectSocketAsync(string host, int port, CancellationToken cancellationToken) + { + var socket = new TcpClient { NoDelay = true }; + try + { + await WithCancellation(socket.ConnectAsync(host, port), cancellationToken).ConfigureAwait(false); + return socket; + } + catch + { + socket.Dispose(); + throw; + } + } + + private static async Task SendPduAsync(NetworkStream stream, byte type, ReadOnlyMemory body, CancellationToken cancellationToken) + { + var pdu = FocasWireProtocol.BuildPdu(type, FocasWireProtocol.DirectionRequest, body.Span); + await stream.WriteAsync(pdu, 0, pdu.Length, cancellationToken).ConfigureAwait(false); + await stream.FlushAsync(cancellationToken).ConfigureAwait(false); + } + + private static void SendPdu(NetworkStream stream, byte type, ReadOnlySpan body) + { + var pdu = FocasWireProtocol.BuildPdu(type, FocasWireProtocol.DirectionRequest, body); + stream.Write(pdu, 0, pdu.Length); + stream.Flush(); + } + + private void ThrowIfDisposed() + { + if (_disposed) throw new ObjectDisposedException(nameof(FocasWireClient)); + } + + private static async Task WithCancellation(Task task, CancellationToken cancellationToken) + { + if (!cancellationToken.CanBeCanceled) + { + await task.ConfigureAwait(false); + return; + } + + var cancellation = new TaskCompletionSource(); + using var registration = cancellationToken.Register(static state => ((TaskCompletionSource)state!).TrySetResult(true), cancellation); + if (task != await Task.WhenAny(task, cancellation.Task).ConfigureAwait(false)) + throw new OperationCanceledException(cancellationToken); + await task.ConfigureAwait(false); + } + + private static CancellationTokenSource CreateCallTimeout(CancellationToken cancellationToken, TimeSpan? timeout) + { + var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + if (timeout is { } value) source.CancelAfter(value); + return source; + } + + private static async Task ReadExpectedPduAsync(NetworkStream stream, byte expectedType, CancellationToken cancellationToken) + { + var pdu = await FocasWireProtocol.ReadPduAsync(stream, cancellationToken).ConfigureAwait(false); + if (pdu.Type != expectedType || pdu.Direction != FocasWireProtocol.DirectionResponse) + throw new FocasWireException($"Unexpected FOCAS PDU type 0x{pdu.Type:x2}, direction 0x{pdu.Direction:x2}.", rc: null); + return pdu; + } + + private void EnsureConnected() + { + ThrowIfDisposed(); + if (!_connected || _stream2 is null) + throw new FocasWireException("FOCAS wire client is not connected.", rc: null, isTransient: true); + } + + private void CloseTransport() + { + _connected = false; + _sysInfo = null; + _stream1?.Dispose(); + _stream2?.Dispose(); + _socket1?.Dispose(); + _socket2?.Dispose(); + _stream1 = null; + _stream2 = null; + _socket1 = null; + _socket2 = null; + } + + private ushort EffectivePathId(ushort? pathId) => pathId ?? PathId; + + private static FocasResult ToResult(ResponseBlock block, Func parser) + => block.Rc != 0 + ? new FocasResult(block.Rc, default) + : new FocasResult(block.Rc, parser(block.Payload)); + + private static short AggregateRc(IReadOnlyList blocks) + => blocks.FirstOrDefault(block => block.Rc != 0)?.Rc ?? 0; + + private static byte[] FindPayload(IReadOnlyList blocks, ushort command) + => blocks.FirstOrDefault(block => block.Command == command)?.Payload ?? Array.Empty(); + + private static int ReadFirstInt32(IReadOnlyList blocks, ushort command) + { + var payload = FindPayload(blocks, command); + return payload.Length >= 4 ? ReadInt32(payload, 0) : 0; + } + + private static int ReadSelectorPosition(IReadOnlyList blocks, ushort command, int selectorIndex) + { + var seen = 0; + foreach (var block in blocks) + { + if (block.Command != command) continue; + if (seen == selectorIndex) + return block.Payload.Length >= 4 ? ReadInt32(block.Payload, 0) : 0; + seen++; + } + return 0; + } + + private static WireSysInfo ParseSysInfo(byte[] payload) + { + RequireLength(payload, 16, "cnc_sysinfo"); + return new WireSysInfo( + ReadInt16(payload, 0), + ReadInt16(payload, 2), + FocasWireProtocol.ReadAscii(payload.AsSpan(4, 2)), + FocasWireProtocol.ReadAscii(payload.AsSpan(6, 2)), + FocasWireProtocol.ReadAscii(payload.AsSpan(8, 4)), + FocasWireProtocol.ReadAscii(payload.AsSpan(12, 4)), + payload.Length >= 18 ? FocasWireProtocol.ReadAscii(payload.AsSpan(16, 2)) : string.Empty); + } + + private static WireProgramName ParseProgramName(byte[] payload) + { + var nameLength = payload.Length >= 40 ? 36 : payload.Length; + var name = FocasWireProtocol.ReadAscii(payload.AsSpan(0, nameLength)); + var number = payload.Length >= 40 ? ReadInt32(payload, 36) : (int?)null; + return new WireProgramName(name, number); + } + + private static IReadOnlyList ParseAlarms(byte[] payload, short count) + => payload.Length % 76 == 0 + ? ParseVendorAlarms(payload, count) + : ParseLegacyWireAlarms(payload, count); + + private static IReadOnlyList ParseVendorAlarms(byte[] payload, short count) + { + var alarms = new List(); + for (var offset = 0; offset + 76 <= payload.Length && alarms.Count < count; offset += 76) + { + var messageLength = ReadInt16(payload, offset + 10); + alarms.Add(new WireAlarm( + ReadInt32(payload, offset), + ReadInt16(payload, offset + 4), + ReadInt16(payload, offset + 6), + messageLength, + FocasWireProtocol.ReadAscii(payload.AsSpan(offset + 12, 64)))); + } + return alarms; + } + + private static IReadOnlyList ParseLegacyWireAlarms(byte[] payload, short count) + { + var alarms = new List(); + for (var offset = 0; offset + 80 <= payload.Length && alarms.Count < count; offset += 80) + { + alarms.Add(new WireAlarm( + ReadInt32(payload, offset), + (short)ReadInt32(payload, offset + 4), + (short)ReadInt32(payload, offset + 8), + (short)ReadInt32(payload, offset + 12), + FocasWireProtocol.ReadAscii(payload.AsSpan(offset + 16, 64)))); + } + return alarms; + } + + private static IReadOnlyList ReadNameRecords(byte[] payload, short maxCount, Func factory) + { + var names = new List(); + for (var offset = 0; offset + 4 <= payload.Length && offset / 4 < maxCount; offset += 4) + { + var name = FocasWireProtocol.ReadNameRecord(payload.AsSpan(offset, 4)); + if (!string.IsNullOrWhiteSpace(name)) + names.Add(factory((short)((offset / 4) + 1), name)); + } + return names; + } + + private static void RequireLength(byte[] payload, int length, string call) + { + if (payload.Length < length) + throw new FocasWireException($"{call} returned {payload.Length} bytes; expected at least {length}.", rc: null); + } + + private static bool IsTransientException(Exception exception) + => exception is IOException or SocketException or TimeoutException or OperationCanceledException + || exception.InnerException is IOException or SocketException or TimeoutException or OperationCanceledException; + + private static short ReadInt16(byte[] bytes, int offset) + => BinaryPrimitives.ReadInt16BigEndian(bytes.AsSpan(offset, 2)); + + private static int ReadInt32(byte[] bytes, int offset) + => BinaryPrimitives.ReadInt32BigEndian(bytes.AsSpan(offset, 4)); +} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireException.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireException.cs new file mode 100644 index 0000000..9b18ca7 --- /dev/null +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireException.cs @@ -0,0 +1,51 @@ +namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire; + +/// +/// Thrown by the wire client when a FOCAS request fails — either at the protocol layer +/// (invalid PDU magic, desynchronised response framing, connection dropped mid-request) +/// or when the CNC returns a non-zero EW_* return code. +/// +/// +/// Callers distinguish the two classes via : true +/// when the transport is gone (socket closed, timeout, cancellation mid-write) and the +/// wire client has already torn the sockets down, so a reconnect is required before any +/// further call. false for protocol-level errors where the connection is still +/// usable. +/// carries the wire-level FOCAS return code when the exception +/// came from a parsed response block. Null when the failure happened before a response +/// was received (e.g. connect-time handshake errors). +/// +public class FocasWireException : Exception +{ + /// FOCAS EW_* return code from the response block, when available. + public short? Rc { get; } + + /// + /// True when the transport was closed as a side effect of this failure — the caller + /// must reconnect before issuing the next request. + /// + public bool IsTransient { get; } + + public FocasWireException(string message) + : base(message) + { + } + + public FocasWireException(string message, short? rc, bool isTransient = false) + : base(message) + { + Rc = rc; + IsTransient = isTransient; + } + + public FocasWireException(string message, Exception innerException) + : base(message, innerException) + { + } + + public FocasWireException(string message, Exception innerException, bool isTransient) + : base(message, innerException) + { + IsTransient = isTransient; + } +} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireModels.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireModels.cs new file mode 100644 index 0000000..abdc1d7 --- /dev/null +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireModels.cs @@ -0,0 +1,131 @@ +namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire; + +/// +/// Return envelope over a parsed wire response. carries the FOCAS +/// EW_* code from the response block — 0 / means the +/// call succeeded and is populated; non-zero means the CNC rejected +/// the call and is default. Callers use the RC to distinguish +/// "feature missing on this series" (EW_FUNC / EW_NOOPT) from genuine +/// failures. +/// +public readonly record struct FocasResult(short Rc, T? Value) +{ + /// True when is zero (EW_OK). + public bool IsOk => Rc == 0; +} + +/// CNC identity payload returned by cnc_sysinfo. +public sealed record WireSysInfo( + short AddInfo, + short MaxAxis, + string CncType, + string MachineType, + string Series, + string Version, + string Axes); + +/// Coarse CNC state bits returned by cnc_statinfo — the seven-word status block plus TM mode. +public sealed record WireStatus( + short Auto, + short Run, + short Motion, + short Mstb, + short Emergency, + short Alarm, + short Edit, + short TmMode); + +/// Four-slot position quadruple for one axis: absolute / machine / relative / distance-to-go. +public sealed record WireAxisPosition( + int Absolute, + int Machine, + int Relative, + int Distance); + +/// +/// Fast-poll bundle for one axis from cnc_rddynamic2 — alarm flags, active program +/// numbers, sequence number, actual feed rate, actual spindle speed, and the position +/// quadruple. +/// +public sealed record WireDynamic( + int Alarm, + int ProgramNumber, + int MainProgramNumber, + int SequenceNumber, + int FeedRate, + int SpindleSpeed, + WireAxisPosition Axis); + +/// One servo-meter entry from cnc_rdsvmeter — per-axis load percentage (scale by 10^). +public sealed record WireServoMeter( + short Index, + string Name, + int Value, + short Decimal, + short Unit); + +/// One spindle metric slot from cnc_rdspload / cnc_rdspmaxrpm. +public sealed record WireSpindleMetric( + short Index, + int Value); + +/// +/// One axis-name slot from cnc_rdaxisname. is the 1-based +/// axis index (preserved even when the name is empty so callers can pass it to +/// cnc_rddynamic2). +/// +public readonly record struct WireAxisRecord(short Index, string Name); + +/// One spindle-name slot from cnc_rdspdlname. +public readonly record struct WireSpindleRecord(short Index, string Name); + +/// Parameter value returned by cnc_rdparam, interpreted as a scalar Int32. +public sealed record WireParameter( + short DataNumber, + short Type, + int Value); + +/// +/// Macro variable from cnc_rdmacro. Scaled decimal: the callable value is +/// Value / 10^Decimal. +/// +public sealed record WireMacro( + short Number, + int Value, + short Decimal); + +/// PMC range read-back from pmc_rdpmcrng: one or more values of the requested width. +public sealed record WirePmcRange( + short Area, + short DataType, + ushort Start, + ushort End, + IReadOnlyList Values); + +/// +/// One active alarm from cnc_rdalmmsg2. Mirrors the vendor ODBALMMSG2 +/// layout; is populated when the wire responder carries it +/// (currently null for both the 76-byte vendor shape and the 80-byte legacy +/// shape). +/// +public sealed record WireAlarm( + int AlarmNumber, + short Type, + short Axis, + short MessageLength, + string Message, + int? AlarmGroup = null); + +/// +/// Executing-program identity from cnc_exeprgname2: the NUL-terminated name and +/// the trailing 32-bit O-number (null when the wire responder omits the trailing int). +/// +public sealed record WireProgramName( + string Name, + int? ONumber); + +/// One cumulative timer reading from cnc_rdtimer (minutes + fractional milliseconds). +public sealed record WireTimer( + short Type, + int Minutes, + int Milliseconds); diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireProtocol.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireProtocol.cs new file mode 100644 index 0000000..c24b154 --- /dev/null +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/FocasWireProtocol.cs @@ -0,0 +1,250 @@ +using System.Buffers.Binary; +using System.Net.Sockets; +using System.Text; + +namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire; + +/// +/// Framing primitives for the FOCAS/2 Ethernet wire protocol — magic-prefixed PDU +/// header + request/response block envelopes. Read-only subset: every call OtOpcUa +/// issues maps to one of the command IDs documented in +/// docs/v2/implementation/focas-wire-protocol.md. +/// +/// +/// All multi-byte integer fields are big-endian on the wire. The 10-byte header is +/// a0 a0 a0 a0 magic + 2-byte version + type byte + direction byte + 2-byte body +/// length. Version 1 is the only version this implementation supports. +/// Type 0x01 is the initiate handshake, 0x02 is the session close, +/// 0x21 is a request/response data PDU carrying one or more request blocks. +/// +internal static class FocasWireProtocol +{ + public const ushort Version = 1; + public const byte DirectionRequest = 0x01; + public const byte DirectionResponse = 0x02; + public const byte TypeInitiate = 0x01; + public const byte TypeClose = 0x02; + public const byte TypeData = 0x21; + + private static readonly byte[] Magic = [0xa0, 0xa0, 0xa0, 0xa0]; + + /// Assemble a full PDU (10-byte header + body) for transmission. + public static byte[] BuildPdu(byte type, byte direction, ReadOnlySpan body) + { + if (body.Length > ushort.MaxValue) + throw new ArgumentOutOfRangeException(nameof(body), "FOCAS PDU body is limited to 65535 bytes."); + + var bytes = new byte[10 + body.Length]; + Magic.CopyTo(bytes, 0); + BinaryPrimitives.WriteUInt16BigEndian(bytes.AsSpan(4, 2), Version); + bytes[6] = type; + bytes[7] = direction; + BinaryPrimitives.WriteUInt16BigEndian(bytes.AsSpan(8, 2), (ushort)body.Length); + body.CopyTo(bytes.AsSpan(10)); + return bytes; + } + + /// + /// Initiate-body shape — just the 2-byte socket index (1 or 2). cnc_allclibhndl3 + /// opens two TCP sockets in sequence and each sends its own initiate PDU carrying its + /// index. + /// + public static byte[] BuildInitiateBody(ushort socketIndex) + { + var body = new byte[2]; + BinaryPrimitives.WriteUInt16BigEndian(body, socketIndex); + return body; + } + + /// Assemble a type-0x21 body carrying one or more request blocks. + public static byte[] BuildRequestBody(IReadOnlyList blocks) + { + if (blocks.Count > ushort.MaxValue) + throw new ArgumentOutOfRangeException(nameof(blocks), "Too many request blocks."); + + var blockBytes = blocks.Select(BuildRequestBlock).ToArray(); + var bodyLength = 2 + blockBytes.Sum(block => block.Length); + if (bodyLength > ushort.MaxValue) + throw new ArgumentOutOfRangeException(nameof(blocks), "FOCAS request body is too large."); + + var body = new byte[bodyLength]; + BinaryPrimitives.WriteUInt16BigEndian(body.AsSpan(0, 2), (ushort)blocks.Count); + var offset = 2; + foreach (var block in blockBytes) + { + block.CopyTo(body.AsSpan(offset)); + offset += block.Length; + } + return body; + } + + /// Async read of one full PDU off a stream. Throws on invalid magic / version / truncation. + public static async Task ReadPduAsync(NetworkStream stream, CancellationToken cancellationToken) + { + var header = new byte[10]; + await ReadExactlyAsync(stream, header, cancellationToken).ConfigureAwait(false); + + if (!header.AsSpan(0, 4).SequenceEqual(Magic)) + throw new FocasWireException("Invalid FOCAS PDU magic."); + + var version = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(4, 2)); + if (version != Version) + throw new FocasWireException($"Unsupported FOCAS PDU version {version}."); + + var bodyLength = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(8, 2)); + var body = new byte[bodyLength]; + if (bodyLength > 0) + await ReadExactlyAsync(stream, body, cancellationToken).ConfigureAwait(false); + + return new Pdu(header[6], header[7], body); + } + + /// Synchronous counterpart to — used by 's sync dispose. + public static Pdu ReadPdu(NetworkStream stream) + { + var header = new byte[10]; + ReadExactly(stream, header); + + if (!header.AsSpan(0, 4).SequenceEqual(Magic)) + throw new FocasWireException("Invalid FOCAS PDU magic."); + + var version = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(4, 2)); + if (version != Version) + throw new FocasWireException($"Unsupported FOCAS PDU version {version}."); + + var bodyLength = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(8, 2)); + var body = new byte[bodyLength]; + if (bodyLength > 0) + ReadExactly(stream, body); + + return new Pdu(header[6], header[7], body); + } + + private static async Task ReadExactlyAsync(NetworkStream stream, byte[] buffer, CancellationToken cancellationToken) + { + var offset = 0; + while (offset < buffer.Length) + { + var read = await stream.ReadAsync(buffer, offset, buffer.Length - offset, cancellationToken).ConfigureAwait(false); + if (read == 0) + throw new EndOfStreamException("FOCAS socket closed before the expected number of bytes were read."); + offset += read; + } + } + + private static void ReadExactly(NetworkStream stream, byte[] buffer) + { + var offset = 0; + while (offset < buffer.Length) + { + var read = stream.Read(buffer, offset, buffer.Length - offset); + if (read == 0) + throw new EndOfStreamException("FOCAS socket closed before the expected number of bytes were read."); + offset += read; + } + } + + /// + /// Unpack a type-0x21 response body into its constituent response blocks. Each + /// block carries the command ID, the FOCAS EW_* return code, and the payload + /// bytes. + /// + public static IReadOnlyList ParseResponseBlocks(ReadOnlySpan body) + { + if (body.Length < 2) + return Array.Empty(); + + var count = BinaryPrimitives.ReadUInt16BigEndian(body.Slice(0, 2)); + var blocks = new List(count); + var offset = 2; + for (var index = 0; index < count; index++) + { + if (offset + 2 > body.Length) + throw new FocasWireException("Truncated FOCAS response block length."); + + var blockLength = BinaryPrimitives.ReadUInt16BigEndian(body.Slice(offset, 2)); + if (blockLength < 0x10 || offset + blockLength > body.Length) + throw new FocasWireException($"Invalid FOCAS response block length {blockLength}."); + + var block = body.Slice(offset, blockLength); + var command = BinaryPrimitives.ReadUInt16BigEndian(block.Slice(6, 2)); + var payloadLength = BinaryPrimitives.ReadUInt16BigEndian(block.Slice(14, 2)); + if (0x10 + payloadLength > blockLength) + throw new FocasWireException("Invalid FOCAS response payload length."); + + var rc = BinaryPrimitives.ReadInt16BigEndian(block.Slice(8, 2)); + blocks.Add(new ResponseBlock(command, rc, block.Slice(16, payloadLength).ToArray())); + offset += blockLength; + } + + return blocks; + } + + /// Read an ASCII string out of a payload span, stopping at the first NUL and trimming trailing spaces. + public static string ReadAscii(ReadOnlySpan bytes) + { + var end = bytes.IndexOf((byte)0); + if (end >= 0) bytes = bytes.Slice(0, end); + return Encoding.ASCII.GetString(bytes.ToArray()).TrimEnd(' ', '\0'); + } + + /// + /// Read an axis/spindle name record — the first 2 bytes of a 2-byte (axis) or 4-byte + /// (spindle) slot. Trailing spaces and NULs are stripped so "X " becomes + /// "X". + /// + public static string ReadNameRecord(ReadOnlySpan bytes) + { + if (bytes.Length < 2) return string.Empty; + var buffer = bytes.Slice(0, Math.Min(2, bytes.Length)).ToArray(); + return Encoding.ASCII.GetString(buffer).TrimEnd(' ', '\0'); + } + + private static byte[] BuildRequestBlock(RequestBlock request) + { + var extra = request.ExtraPayload ?? Array.Empty(); + if (extra.Length > ushort.MaxValue) + throw new ArgumentOutOfRangeException(nameof(request), "FOCAS request extra payload is too large."); + + var blockLength = 0x1c + extra.Length; + if (blockLength > ushort.MaxValue) + throw new ArgumentOutOfRangeException(nameof(request), "FOCAS request block is too large."); + + var block = new byte[blockLength]; + BinaryPrimitives.WriteUInt16BigEndian(block.AsSpan(0, 2), (ushort)blockLength); + BinaryPrimitives.WriteUInt16BigEndian(block.AsSpan(2, 2), request.RequestClass); + BinaryPrimitives.WriteUInt16BigEndian(block.AsSpan(4, 2), request.PathId); + BinaryPrimitives.WriteUInt16BigEndian(block.AsSpan(6, 2), request.Command); + BinaryPrimitives.WriteInt32BigEndian(block.AsSpan(8, 4), request.Arg1); + BinaryPrimitives.WriteInt32BigEndian(block.AsSpan(12, 4), request.Arg2); + BinaryPrimitives.WriteInt32BigEndian(block.AsSpan(16, 4), request.Arg3); + BinaryPrimitives.WriteInt32BigEndian(block.AsSpan(20, 4), request.Arg4); + BinaryPrimitives.WriteUInt16BigEndian(block.AsSpan(24, 2), request.Arg5); + BinaryPrimitives.WriteUInt16BigEndian(block.AsSpan(26, 2), (ushort)extra.Length); + extra.CopyTo(block.AsSpan(28)); + return block; + } +} + +/// One raw PDU off the wire — header bytes plus the body. +internal sealed record Pdu(byte Type, byte Direction, byte[] Body); + +/// +/// One request block within a type-0x21 PDU body. is the +/// FOCAS command ID (e.g. 0x0018 for sysinfo); .. +/// are the command-specific scalar arguments; carries the +/// optional extra bytes for writes. +/// +internal sealed record RequestBlock( + ushort Command, + int Arg1 = 0, + int Arg2 = 0, + int Arg3 = 0, + int Arg4 = 0, + ushort Arg5 = 0, + ushort RequestClass = 1, + ushort PathId = 1, + byte[]? ExtraPayload = null); + +/// One response block — command ID + FOCAS return code + payload bytes. +internal sealed record ResponseBlock(ushort Command, short Rc, byte[] Payload); diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/WireFocasClient.cs b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/WireFocasClient.cs new file mode 100644 index 0000000..c7273ee --- /dev/null +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/Wire/WireFocasClient.cs @@ -0,0 +1,333 @@ +namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire; + +/// +/// implementation backed by the in-tree managed +/// . No P/Invoke, no Fwlib64.dll, no out-of-process +/// Host — the wire client dials the CNC on TCP:8193 directly and speaks the FOCAS/2 +/// Ethernet binary protocol. +/// +/// +/// OtOpcUa is read-only against FOCAS. returns +/// for every address — the managed wire +/// client intentionally does not expose cnc_wrparam / pmc_wrpmcrng / +/// cnc_wrmacro. +/// +public sealed class WireFocasClient : IFocasClient +{ + private readonly FocasWireClient _wire = new(); + private FocasHostAddress? _address; + + public bool IsConnected => _wire.IsConnected; + + public async Task ConnectAsync(FocasHostAddress address, TimeSpan timeout, CancellationToken cancellationToken) + { + if (_wire.IsConnected) return; + _address = address; + // FocasWireClient.ConnectAsync interprets TimeSpan.Zero as "no timeout" — clamp the + // driver's default TimeSpan to at least 1s so a caller passing TimeSpan.Zero gets a + // sane fail-fast instead of hanging indefinitely. + var effective = timeout <= TimeSpan.Zero ? TimeSpan.FromSeconds(1) : timeout; + await _wire.ConnectAsync(address.Host, address.Port, effective, cancellationToken).ConfigureAwait(false); + } + + public async Task<(object? value, uint status)> ReadAsync( + FocasAddress address, FocasDataType type, CancellationToken cancellationToken) + { + if (!_wire.IsConnected) return (null, FocasStatusMapper.BadCommunicationError); + cancellationToken.ThrowIfCancellationRequested(); + + return address.Kind switch + { + FocasAreaKind.Pmc => await ReadPmcAsync(address, type, cancellationToken).ConfigureAwait(false), + FocasAreaKind.Parameter => await ReadParameterAsync(address, type, cancellationToken).ConfigureAwait(false), + FocasAreaKind.Macro => await ReadMacroAsync(address, cancellationToken).ConfigureAwait(false), + _ => (null, FocasStatusMapper.BadNotSupported), + }; + } + + public Task WriteAsync( + FocasAddress address, FocasDataType type, object? value, CancellationToken cancellationToken) + => Task.FromResult(FocasStatusMapper.BadNotWritable); + + public async Task ProbeAsync(CancellationToken cancellationToken) + { + if (!_wire.IsConnected) return false; + try + { + var result = await _wire.ReadStatusAsync(cancellationToken).ConfigureAwait(false); + return result.IsOk; + } + catch (FocasWireException) + { + return false; + } + } + + public async Task> ReadAlarmsAsync(CancellationToken cancellationToken) + { + if (!_wire.IsConnected) return []; + try + { + var result = await _wire.ReadAlarmsAsync(FocasAlarmType.All, 32, cancellationToken).ConfigureAwait(false); + if (!result.IsOk || result.Value is null) return []; + return result.Value.Select(Map).ToList(); + } + catch (FocasWireException) + { + return []; + } + + static FocasActiveAlarm Map(WireAlarm a) => new( + AlarmNumber: a.AlarmNumber, + Type: a.Type, + Axis: a.Axis, + Message: a.Message ?? string.Empty); + } + + public async Task GetSysInfoAsync(CancellationToken cancellationToken) + { + RequireConnected(); + var result = await _wire.ReadSysInfoAsync(cancellationToken).ConfigureAwait(false); + ThrowIfRcNonZero(result.Rc, "cnc_sysinfo", result.IsOk); + var info = result.Value!; + // Fanuc right-pads the ASCII axis count with spaces; fall back to MaxAxis if the + // text field isn't interpretable as an integer. + var axesCount = int.TryParse(info.Axes?.Trim(), out var parsed) ? parsed : info.MaxAxis; + return new FocasSysInfo( + AddInfo: info.AddInfo, + MaxAxis: info.MaxAxis, + CncType: info.CncType ?? string.Empty, + MtType: info.MachineType ?? string.Empty, + Series: info.Series ?? string.Empty, + Version: info.Version ?? string.Empty, + AxesCount: axesCount); + } + + public async Task> GetAxisNamesAsync(CancellationToken cancellationToken) + { + if (!_wire.IsConnected) return []; + var result = await _wire.ReadAxisNamesAsync(32, cancellationToken).ConfigureAwait(false); + if (!result.IsOk || result.Value is null) return []; + return result.Value.Select(SplitAxis).Where(n => n.Name.Length > 0).ToList(); + + // FocasWireClient returns axis records as a single Name string (e.g. "X" or "X1"). + // IFocasClient wants Name + Suffix split — the first char is the axis letter, the + // rest is the multi-channel suffix. + static FocasAxisName SplitAxis(WireAxisRecord r) + { + var n = r.Name ?? string.Empty; + return n.Length == 0 + ? new FocasAxisName(string.Empty, string.Empty) + : new FocasAxisName(n[..1], n.Length > 1 ? n[1..] : string.Empty); + } + } + + public async Task> GetSpindleNamesAsync(CancellationToken cancellationToken) + { + if (!_wire.IsConnected) return []; + var result = await _wire.ReadSpindleNamesAsync(8, cancellationToken).ConfigureAwait(false); + if (!result.IsOk || result.Value is null) return []; + return result.Value.Select(SplitSpindle).Where(n => n.Name.Length > 0).ToList(); + + static FocasSpindleName SplitSpindle(WireSpindleRecord r) + { + var n = r.Name ?? string.Empty; + return new FocasSpindleName( + Name: n.Length > 0 ? n[..1] : string.Empty, + Suffix1: n.Length > 1 ? n[1..2] : string.Empty, + Suffix2: n.Length > 2 ? n[2..3] : string.Empty, + Suffix3: n.Length > 3 ? n[3..4] : string.Empty); + } + } + + public async Task ReadDynamicAsync(int axisIndex, CancellationToken cancellationToken) + { + RequireConnected(); + var result = await _wire.ReadDynamic2Async((short)axisIndex, cancellationToken).ConfigureAwait(false); + ThrowIfRcNonZero(result.Rc, "cnc_rddynamic2", result.IsOk); + var d = result.Value!; + var pos = d.Axis ?? new WireAxisPosition(0, 0, 0, 0); + return new FocasDynamicSnapshot( + AxisIndex: axisIndex, + AlarmFlags: d.Alarm, + ProgramNumber: d.ProgramNumber, + MainProgramNumber: d.MainProgramNumber, + SequenceNumber: d.SequenceNumber, + ActualFeedRate: d.FeedRate, + ActualSpindleSpeed: d.SpindleSpeed, + AbsolutePosition: pos.Absolute, + MachinePosition: pos.Machine, + RelativePosition: pos.Relative, + DistanceToGo: pos.Distance); + } + + public async Task GetProgramInfoAsync(CancellationToken cancellationToken) + { + RequireConnected(); + var nameResult = await _wire.ReadExecutingProgramNameAsync(cancellationToken).ConfigureAwait(false); + var blkResult = await _wire.ReadBlockCountAsync(cancellationToken).ConfigureAwait(false); + // Use the raw short variant — FocasProgramInfo.Mode stores the integer code so the + // managed ToText path in FocasOpMode can map it for display. + var modeResult = await _wire.ReadOperationModeCodeAsync(cancellationToken).ConfigureAwait(false); + + var wireName = nameResult.Value; + return new FocasProgramInfo( + Name: wireName?.Name ?? string.Empty, + ONumber: wireName?.ONumber ?? 0, + BlockCount: blkResult.IsOk ? blkResult.Value : 0, + Mode: modeResult.IsOk ? modeResult.Value : 0); + } + + public async Task GetTimerAsync(FocasTimerKind kind, CancellationToken cancellationToken) + { + RequireConnected(); + var result = await _wire.ReadTimerAsync((short)kind, cancellationToken).ConfigureAwait(false); + ThrowIfRcNonZero(result.Rc, $"cnc_rdtimer kind={kind}", result.IsOk); + var t = result.Value!; + return new FocasTimer(kind, t.Minutes, t.Milliseconds); + } + + public async Task> GetServoLoadsAsync(CancellationToken cancellationToken) + { + if (!_wire.IsConnected) return []; + var result = await _wire.ReadServoMeterAsync(32, cancellationToken).ConfigureAwait(false); + if (!result.IsOk || result.Value is null) return []; + return result.Value + .Select(m => new FocasServoLoad(m.Name ?? string.Empty, m.Value / Math.Pow(10.0, m.Decimal))) + .Where(s => s.AxisName.Length > 0) + .ToList(); + } + + public Task> GetSpindleLoadsAsync(CancellationToken cancellationToken) => + ReadSpindleMetricAsync((sel, ct) => _wire.ReadSpindleLoadAsync(sel, ct), cancellationToken); + + public Task> GetSpindleMaxRpmsAsync(CancellationToken cancellationToken) => + ReadSpindleMetricAsync((sel, ct) => _wire.ReadSpindleMaxRpmAsync(sel, ct), cancellationToken); + + private static async Task> ReadSpindleMetricAsync( + Func>>> call, + CancellationToken cancellationToken) + { + var result = await call(-1, cancellationToken).ConfigureAwait(false); + if (!result.IsOk || result.Value is null) return []; + var list = new List(); + foreach (var m in result.Value) + { + // Fanuc pads unused spindle slots with 0 — stop at the first trailing zero so the + // list length matches the configured spindle count. + if (m.Value == 0 && list.Count > 0) break; + list.Add(m.Value); + } + return list; + } + + public void Dispose() => _wire.Dispose(); + + // ---- PMC / Parameter / Macro read paths ------------------------------------------ + + private async Task<(object? value, uint status)> ReadPmcAsync( + FocasAddress address, FocasDataType type, CancellationToken cancellationToken) + { + var area = FocasPmcAreaLookup.FromLetter(address.PmcLetter ?? string.Empty); + if (area is null) return (null, FocasStatusMapper.BadNodeIdUnknown); + var dataType = FocasPmcDataTypeLookup.FromFocasDataType(type); + var start = (ushort)address.Number; + var end = start; + + try + { + var result = await _wire.ReadPmcRangeAsync(area.Value, dataType, start, end, cancellationToken) + .ConfigureAwait(false); + if (!result.IsOk || result.Value is null) + return (null, FocasStatusMapper.MapFocasReturn(result.Rc)); + var values = result.Value.Values; + if (values.Count == 0) return (null, FocasStatusMapper.BadOutOfRange); + var raw = values[0]; + var mapped = type switch + { + FocasDataType.Bit => (object)(((long)raw >> (address.BitIndex ?? 0) & 1L) != 0), + FocasDataType.Byte => (object)(sbyte)(raw & 0xFFL), + FocasDataType.Int16 => (object)(short)raw, + FocasDataType.Int32 => (object)(int)raw, + FocasDataType.Float32 => (object)BitConverter.Int32BitsToSingle((int)raw), + FocasDataType.Float64 => (object)BitConverter.Int64BitsToDouble(raw), + _ => (object)raw, + }; + return (mapped, FocasStatusMapper.Good); + } + catch (FocasWireException ex) + { + return (null, ex.Rc is short rc ? FocasStatusMapper.MapFocasReturn(rc) : FocasStatusMapper.BadCommunicationError); + } + } + + private async Task<(object? value, uint status)> ReadParameterAsync( + FocasAddress address, FocasDataType type, CancellationToken cancellationToken) + { + try + { + switch (type) + { + case FocasDataType.Byte: + var b = await _wire.ReadParameterByteAsync((short)address.Number, 0, cancellationToken).ConfigureAwait(false); + return b.IsOk ? ((object)(sbyte)b.Value, FocasStatusMapper.Good) : (null, FocasStatusMapper.MapFocasReturn(b.Rc)); + case FocasDataType.Int16: + var s = await _wire.ReadParameterInt16Async((short)address.Number, 0, cancellationToken).ConfigureAwait(false); + return s.IsOk ? ((object)s.Value, FocasStatusMapper.Good) : (null, FocasStatusMapper.MapFocasReturn(s.Rc)); + case FocasDataType.Float32: + var f = await _wire.ReadParameterFloat32Async((short)address.Number, 0, cancellationToken).ConfigureAwait(false); + return f.IsOk ? ((object)f.Value, FocasStatusMapper.Good) : (null, FocasStatusMapper.MapFocasReturn(f.Rc)); + case FocasDataType.Float64: + var d = await _wire.ReadParameterFloat64Async((short)address.Number, 0, cancellationToken).ConfigureAwait(false); + return d.IsOk ? ((object)d.Value, FocasStatusMapper.Good) : (null, FocasStatusMapper.MapFocasReturn(d.Rc)); + case FocasDataType.Bit when address.BitIndex is int bit: + var bi = await _wire.ReadParameterInt32Async((short)address.Number, 0, cancellationToken).ConfigureAwait(false); + if (!bi.IsOk) return (null, FocasStatusMapper.MapFocasReturn(bi.Rc)); + return ((object)((bi.Value >> bit & 1) != 0), FocasStatusMapper.Good); + default: + var i = await _wire.ReadParameterInt32Async((short)address.Number, 0, cancellationToken).ConfigureAwait(false); + return i.IsOk ? ((object)i.Value, FocasStatusMapper.Good) : (null, FocasStatusMapper.MapFocasReturn(i.Rc)); + } + } + catch (FocasWireException ex) + { + return (null, ex.Rc is short rc ? FocasStatusMapper.MapFocasReturn(rc) : FocasStatusMapper.BadCommunicationError); + } + } + + private async Task<(object? value, uint status)> ReadMacroAsync( + FocasAddress address, CancellationToken cancellationToken) + { + try + { + var result = await _wire.ReadMacroAsync((short)address.Number, cancellationToken).ConfigureAwait(false); + if (!result.IsOk || result.Value is null) + return (null, FocasStatusMapper.MapFocasReturn(result.Rc)); + var m = result.Value; + // Macro value is scaled-decimal: the real value is Value / 10^Decimal. + var scaled = m.Value / Math.Pow(10.0, m.Decimal); + return ((object)scaled, FocasStatusMapper.Good); + } + catch (FocasWireException ex) + { + return (null, ex.Rc is short rc ? FocasStatusMapper.MapFocasReturn(rc) : FocasStatusMapper.BadCommunicationError); + } + } + + private void RequireConnected() + { + if (!_wire.IsConnected) + throw new InvalidOperationException("FOCAS wire session not connected."); + } + + private static void ThrowIfRcNonZero(short rc, string call, bool isOk) + { + if (!isOk) throw new InvalidOperationException($"{call} failed EW_{rc}."); + } +} + +/// Factory producing instances — one per configured device. +public sealed class WireFocasClientFactory : IFocasClientFactory +{ + public IFocasClient Create() => new WireFocasClient(); +} diff --git a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.csproj b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.csproj index b63b6c0..ce08b38 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.csproj +++ b/src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.csproj @@ -15,20 +15,15 @@ - - + + + + diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/FwlibFrameHandlerTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/FwlibFrameHandlerTests.cs deleted file mode 100644 index a47a9f5..0000000 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/FwlibFrameHandlerTests.cs +++ /dev/null @@ -1,200 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MessagePack; -using Serilog; -using Shouldly; -using Xunit; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Backend; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Ipc; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests -{ - /// - /// Validates that correctly dispatches each - /// to the corresponding - /// method and serializes the response into the expected response kind. Uses - /// so no hardware is needed. - /// - [Trait("Category", "Unit")] - public sealed class FwlibFrameHandlerTests - { - private static async Task RoundTripAsync( - IFrameHandler handler, FocasMessageKind reqKind, TReq req, FocasMessageKind expectedRespKind, - Action assertResponse) - { - using var buffer = new MemoryStream(); - using var writer = new FrameWriter(buffer, leaveOpen: true); - await handler.HandleAsync(reqKind, MessagePackSerializer.Serialize(req), writer, CancellationToken.None); - - buffer.Position = 0; - using var reader = new FrameReader(buffer, leaveOpen: true); - var frame = await reader.ReadFrameAsync(CancellationToken.None); - frame.HasValue.ShouldBeTrue(); - frame!.Value.Kind.ShouldBe(expectedRespKind); - assertResponse(MessagePackSerializer.Deserialize(frame.Value.Body)); - } - - private static FwlibFrameHandler BuildHandler() => - new(new FakeFocasBackend(), new LoggerConfiguration().CreateLogger()); - - [Fact] - public async Task OpenSession_returns_a_new_session_id() - { - long sessionId = 0; - await RoundTripAsync( - BuildHandler(), - FocasMessageKind.OpenSessionRequest, - new OpenSessionRequest { HostAddress = "h:8193" }, - FocasMessageKind.OpenSessionResponse, - resp => { resp.Success.ShouldBeTrue(); resp.SessionId.ShouldBeGreaterThan(0L); sessionId = resp.SessionId; }); - sessionId.ShouldBeGreaterThan(0L); - } - - [Fact] - public async Task Read_without_open_session_returns_internal_error() - { - await RoundTripAsync( - BuildHandler(), - FocasMessageKind.ReadRequest, - new ReadRequest - { - SessionId = 999, - Address = new FocasAddressDto { Kind = 0, PmcLetter = "R", Number = 100 }, - DataType = FocasDataTypeCode.Int32, - }, - FocasMessageKind.ReadResponse, - resp => { resp.Success.ShouldBeFalse(); resp.Error.ShouldContain("session-not-open"); }); - } - - [Fact] - public async Task Full_open_write_read_round_trip_preserves_value() - { - var handler = BuildHandler(); - - // Open. - using var buffer = new MemoryStream(); - using var writer = new FrameWriter(buffer, leaveOpen: true); - await handler.HandleAsync(FocasMessageKind.OpenSessionRequest, - MessagePackSerializer.Serialize(new OpenSessionRequest { HostAddress = "h:8193" }), writer, CancellationToken.None); - - buffer.Position = 0; - using var reader = new FrameReader(buffer, leaveOpen: true); - var openFrame = await reader.ReadFrameAsync(CancellationToken.None); - var openResp = MessagePackSerializer.Deserialize(openFrame!.Value.Body); - var sessionId = openResp.SessionId; - - // Write 42 at MACRO:500 as Int32. - buffer.Position = 0; - buffer.SetLength(0); - await handler.HandleAsync(FocasMessageKind.WriteRequest, - MessagePackSerializer.Serialize(new WriteRequest - { - SessionId = sessionId, - Address = new FocasAddressDto { Kind = 2, Number = 500 }, - DataType = FocasDataTypeCode.Int32, - ValueTypeCode = FocasDataTypeCode.Int32, - ValueBytes = MessagePackSerializer.Serialize((int)42), - }), writer, CancellationToken.None); - - // Read back. - buffer.Position = 0; - buffer.SetLength(0); - await handler.HandleAsync(FocasMessageKind.ReadRequest, - MessagePackSerializer.Serialize(new ReadRequest - { - SessionId = sessionId, - Address = new FocasAddressDto { Kind = 2, Number = 500 }, - DataType = FocasDataTypeCode.Int32, - }), writer, CancellationToken.None); - - buffer.Position = 0; - var readFrame = await reader.ReadFrameAsync(CancellationToken.None); - readFrame.HasValue.ShouldBeTrue(); - readFrame!.Value.Kind.ShouldBe(FocasMessageKind.ReadResponse); - // With buffer reuse there may be multiple queued frames; we want the last one. - var lastResp = MessagePackSerializer.Deserialize(readFrame.Value.Body); - // If the Write frame is first, drain it. - if (lastResp.ValueBytes is null) - { - var next = await reader.ReadFrameAsync(CancellationToken.None); - lastResp = MessagePackSerializer.Deserialize(next!.Value.Body); - } - lastResp.Success.ShouldBeTrue(); - MessagePackSerializer.Deserialize(lastResp.ValueBytes!).ShouldBe(42); - } - - [Fact] - public async Task PmcBitWrite_sets_specified_bit() - { - var handler = BuildHandler(); - using var buffer = new MemoryStream(); - using var writer = new FrameWriter(buffer, leaveOpen: true); - - await handler.HandleAsync(FocasMessageKind.OpenSessionRequest, - MessagePackSerializer.Serialize(new OpenSessionRequest { HostAddress = "h:8193" }), writer, CancellationToken.None); - buffer.Position = 0; - using var reader = new FrameReader(buffer, leaveOpen: true); - var openFrame = await reader.ReadFrameAsync(CancellationToken.None); - var sessionId = MessagePackSerializer.Deserialize(openFrame!.Value.Body).SessionId; - - buffer.Position = 0; buffer.SetLength(0); - await handler.HandleAsync(FocasMessageKind.PmcBitWriteRequest, - MessagePackSerializer.Serialize(new PmcBitWriteRequest - { - SessionId = sessionId, - Address = new FocasAddressDto { Kind = 0, PmcLetter = "R", Number = 100 }, - BitIndex = 3, - Value = true, - }), writer, CancellationToken.None); - - buffer.Position = 0; - var resp = MessagePackSerializer.Deserialize( - (await reader.ReadFrameAsync(CancellationToken.None))!.Value.Body); - resp.Success.ShouldBeTrue(); - resp.StatusCode.ShouldBe(0u); - } - - [Fact] - public async Task Probe_reports_healthy_when_session_open() - { - var handler = BuildHandler(); - using var buffer = new MemoryStream(); - using var writer = new FrameWriter(buffer, leaveOpen: true); - await handler.HandleAsync(FocasMessageKind.OpenSessionRequest, - MessagePackSerializer.Serialize(new OpenSessionRequest { HostAddress = "h:8193" }), writer, CancellationToken.None); - buffer.Position = 0; - using var reader = new FrameReader(buffer, leaveOpen: true); - var sessionId = MessagePackSerializer.Deserialize( - (await reader.ReadFrameAsync(CancellationToken.None))!.Value.Body).SessionId; - - buffer.Position = 0; buffer.SetLength(0); - await handler.HandleAsync(FocasMessageKind.ProbeRequest, - MessagePackSerializer.Serialize(new ProbeRequest { SessionId = sessionId }), writer, CancellationToken.None); - buffer.Position = 0; - var resp = MessagePackSerializer.Deserialize( - (await reader.ReadFrameAsync(CancellationToken.None))!.Value.Body); - resp.Healthy.ShouldBeTrue(); - } - - [Fact] - public async Task Unconfigured_backend_returns_pointed_error_message() - { - var handler = new FwlibFrameHandler(new UnconfiguredFocasBackend(), new LoggerConfiguration().CreateLogger()); - await RoundTripAsync( - handler, - FocasMessageKind.OpenSessionRequest, - new OpenSessionRequest { HostAddress = "h:8193" }, - FocasMessageKind.OpenSessionResponse, - resp => - { - resp.Success.ShouldBeFalse(); - resp.Error.ShouldContain("Fwlib32"); - resp.ErrorCode.ShouldBe("NoFwlibBackend"); - }); - } - } -} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/IpcHandshakeIntegrationTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/IpcHandshakeIntegrationTests.cs deleted file mode 100644 index b520c2a..0000000 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/IpcHandshakeIntegrationTests.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using System.IO; -using System.IO.Pipes; -using System.Security.Principal; -using System.Threading; -using System.Threading.Tasks; -using MessagePack; -using Serilog; -using Serilog.Core; -using Shouldly; -using Xunit; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Ipc; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests -{ - /// - /// Direct FOCAS Host IPC handshake test. Drives through a - /// hand-rolled pipe client built on / - /// from FOCAS.Shared. Skipped on Administrator shells because PipeAcl denies - /// the BuiltinAdministrators group. - /// - [Trait("Category", "Integration")] - public sealed class IpcHandshakeIntegrationTests - { - private static bool IsAdministrator() - { - using var identity = WindowsIdentity.GetCurrent(); - return new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator); - } - - private static async Task<(NamedPipeClientStream Stream, FrameReader Reader, FrameWriter Writer)> - ConnectAndHelloAsync(string pipeName, string secret, CancellationToken ct) - { - var stream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); - await stream.ConnectAsync(5_000, ct); - - var reader = new FrameReader(stream, leaveOpen: true); - var writer = new FrameWriter(stream, leaveOpen: true); - await writer.WriteAsync(FocasMessageKind.Hello, - new Hello { PeerName = "test-client", SharedSecret = secret }, ct); - - var ack = await reader.ReadFrameAsync(ct); - if (ack is null) throw new EndOfStreamException("no HelloAck"); - if (ack.Value.Kind != FocasMessageKind.HelloAck) - throw new InvalidOperationException("unexpected first frame kind " + ack.Value.Kind); - var ackMsg = MessagePackSerializer.Deserialize(ack.Value.Body); - if (!ackMsg.Accepted) throw new UnauthorizedAccessException(ackMsg.RejectReason); - - return (stream, reader, writer); - } - - [Fact] - public async Task Handshake_with_correct_secret_succeeds_and_heartbeat_round_trips() - { - if (IsAdministrator()) return; - - using var identity = WindowsIdentity.GetCurrent(); - var sid = identity.User!; - var pipe = $"OtOpcUaFocasTest-{Guid.NewGuid():N}"; - const string secret = "test-secret-2026"; - Logger log = new LoggerConfiguration().CreateLogger(); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); - - var server = new PipeServer(pipe, sid, secret, log); - var serverTask = Task.Run(() => server.RunOneConnectionAsync(new StubFrameHandler(), cts.Token)); - - var (stream, reader, writer) = await ConnectAndHelloAsync(pipe, secret, cts.Token); - using (stream) - using (reader) - using (writer) - { - await writer.WriteAsync(FocasMessageKind.Heartbeat, - new Heartbeat { MonotonicTicks = 42 }, cts.Token); - - var hbAck = await reader.ReadFrameAsync(cts.Token); - hbAck.HasValue.ShouldBeTrue(); - hbAck!.Value.Kind.ShouldBe(FocasMessageKind.HeartbeatAck); - MessagePackSerializer.Deserialize(hbAck.Value.Body).MonotonicTicks.ShouldBe(42L); - } - - cts.Cancel(); - try { await serverTask; } catch { } - server.Dispose(); - } - - [Fact] - public async Task Handshake_with_wrong_secret_is_rejected() - { - if (IsAdministrator()) return; - - using var identity = WindowsIdentity.GetCurrent(); - var sid = identity.User!; - var pipe = $"OtOpcUaFocasTest-{Guid.NewGuid():N}"; - Logger log = new LoggerConfiguration().CreateLogger(); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); - - var server = new PipeServer(pipe, sid, "real-secret", log); - var serverTask = Task.Run(() => server.RunOneConnectionAsync(new StubFrameHandler(), cts.Token)); - - await Should.ThrowAsync(async () => - { - var (s, r, w) = await ConnectAndHelloAsync(pipe, "wrong-secret", cts.Token); - s.Dispose(); - r.Dispose(); - w.Dispose(); - }); - - cts.Cancel(); - try { await serverTask; } catch { } - server.Dispose(); - } - - [Fact] - public async Task Stub_handler_returns_not_implemented_for_data_plane_request() - { - if (IsAdministrator()) return; - - using var identity = WindowsIdentity.GetCurrent(); - var sid = identity.User!; - var pipe = $"OtOpcUaFocasTest-{Guid.NewGuid():N}"; - const string secret = "stub-test"; - Logger log = new LoggerConfiguration().CreateLogger(); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); - - var server = new PipeServer(pipe, sid, secret, log); - var serverTask = Task.Run(() => server.RunOneConnectionAsync(new StubFrameHandler(), cts.Token)); - - var (stream, reader, writer) = await ConnectAndHelloAsync(pipe, secret, cts.Token); - using (stream) - using (reader) - using (writer) - { - await writer.WriteAsync(FocasMessageKind.ReadRequest, - new ReadRequest - { - SessionId = 1, - Address = new FocasAddressDto { Kind = 0, PmcLetter = "R", Number = 100 }, - DataType = FocasDataTypeCode.Int32, - }, - cts.Token); - - var resp = await reader.ReadFrameAsync(cts.Token); - resp.HasValue.ShouldBeTrue(); - resp!.Value.Kind.ShouldBe(FocasMessageKind.ErrorResponse); - var err = MessagePackSerializer.Deserialize(resp.Value.Body); - err.Code.ShouldBe("not-implemented"); - err.Message.ShouldContain("PR C"); - } - - cts.Cancel(); - try { await serverTask; } catch { } - server.Dispose(); - } - } -} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/PostMortemMmfTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/PostMortemMmfTests.cs deleted file mode 100644 index 45908b2..0000000 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/PostMortemMmfTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.IO; -using Shouldly; -using Xunit; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Stability; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests -{ - [Trait("Category", "Unit")] - public sealed class PostMortemMmfTests : IDisposable - { - private readonly string _tempPath; - - public PostMortemMmfTests() - { - _tempPath = Path.Combine(Path.GetTempPath(), $"focas-mmf-{Guid.NewGuid():N}.bin"); - } - - public void Dispose() - { - if (File.Exists(_tempPath)) File.Delete(_tempPath); - } - - [Fact] - public void Write_and_read_preserve_order_and_content() - { - using (var mmf = new PostMortemMmf(_tempPath, capacity: 10)) - { - mmf.Write(opKind: 1, "read R100"); - mmf.Write(opKind: 2, "write MACRO:500 = 3.14"); - mmf.Write(opKind: 3, "probe ok"); - } - - // Reopen (simulating a reader after the writer crashed). - using var reader = new PostMortemMmf(_tempPath, capacity: 10); - var entries = reader.ReadAll(); - entries.Length.ShouldBe(3); - entries[0].OpKind.ShouldBe(1L); - entries[0].Message.ShouldBe("read R100"); - entries[1].OpKind.ShouldBe(2L); - entries[2].Message.ShouldBe("probe ok"); - } - - [Fact] - public void Ring_buffer_wraps_at_capacity() - { - using var mmf = new PostMortemMmf(_tempPath, capacity: 3); - for (var i = 0; i < 10; i++) mmf.Write(i, $"op-{i}"); - - var entries = mmf.ReadAll(); - entries.Length.ShouldBe(3); - // Oldest surviving entry is op-7 (entries 7,8,9 survive in FIFO order). - entries[0].Message.ShouldBe("op-7"); - entries[1].Message.ShouldBe("op-8"); - entries[2].Message.ShouldBe("op-9"); - } - - [Fact] - public void Truncated_message_is_null_terminated_and_does_not_overflow() - { - using var mmf = new PostMortemMmf(_tempPath, capacity: 4); - var big = new string('x', 500); // longer than the 240-byte message capacity - mmf.Write(42, big); - - var entries = mmf.ReadAll(); - entries.Length.ShouldBe(1); - entries[0].Message.Length.ShouldBeLessThanOrEqualTo(240); - entries[0].OpKind.ShouldBe(42L); - } - - [Fact] - public void Reopening_with_existing_data_preserves_entries() - { - using (var first = new PostMortemMmf(_tempPath, capacity: 5)) - { - first.Write(1, "first-run-1"); - first.Write(2, "first-run-2"); - } - - using var second = new PostMortemMmf(_tempPath, capacity: 5); - var entries = second.ReadAll(); - entries.Length.ShouldBe(2); - entries[0].Message.ShouldBe("first-run-1"); - } - } -} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests.csproj b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests.csproj deleted file mode 100644 index 74b5ccf..0000000 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - net48 - x86 - true - enable - latest - false - true - ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/README.md b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/README.md new file mode 100644 index 0000000..af585f9 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/README.md @@ -0,0 +1,94 @@ +# FOCAS Docker simulator — focas-mock + shim DLL + +Hardware-free FOCAS fixture for OtOpcUa's integration test matrix. Runs +the vendored [`focas-mock`](focas-mock/VENDORED.md) Python server under +Docker and pairs it with the [shim DLL](../Shim/VENDORED.md) that +masquerades as `Fwlib64.dll` inside the .NET test process. + +## Architecture + +``` +┌────────────────────────────┐ cnc_allclibhndl3 / cnc_rdparam / ... +│ xunit test process │ (P/Invoke, __stdcall) +│ ├── Driver.FOCAS │ +│ │ └── FwlibNative.cs ─┼─┐ +│ └── FocasSimFixture │ │ resolves to... +└────────────────────────────┘ │ + ▼ + ┌────────────────────────────┐ + │ Fwlib64.dll (shim) │ JSON over TCP + │ tests/.../Shim/focas_ │──────────────────────┐ + │ shim.c compiled here │ │ + └────────────────────────────┘ │ + ▼ + ┌─────────────────────────────┐ + │ focas-mock (Docker) │ + │ python:3.11-slim │ + │ profile-aware responses │ + │ mock_load_profile / │ + │ mock_patch admin methods │ + └─────────────────────────────┘ +``` + +The shim bridges the binary ABI (C `__stdcall` exports with FOCAS struct +shapes) to the mock's newline-delimited JSON protocol. OtOpcUa's +`FocasSimFixture` seeds per-test state by sending `mock_load_profile` + +`mock_patch` admin calls on the same socket. Tests assert the managed +driver sees the seeded values through its normal P/Invoke path. + +## Running + +Pick one compose profile (they all publish 8193 — only one at a time): + +```powershell +docker compose -f Docker/docker-compose.yml --profile thirtyone up -d +dotnet test tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests +docker compose -f Docker/docker-compose.yml --profile thirtyone down +``` + +Available profiles + their focas-mock target: + +| compose --profile | focas-mock profile | Covers | +|---|---|---| +| `thirtyone` / `thirty` / `thirtytwo` | `fwlib30i64` | 30i / 31i / 32i series | +| `sixteen` | `FWLIB64` | 16i / 18i / 21i legacy family | +| `zerod` / `zerof` / `zeromf` / `zerotf` | `fwlib0iD64` | 0i-D / 0i-F / 0i-MF / 0i-TF | +| `powermotion` | `fwlib0DN64` | Power Motion i | +| `ethernet` | `fwlibe64` | Ethernet-variant DLL | +| `ncguide` | `fwlibNCG64` | NC Guide PC simulator | + +## What this covers — and what it doesn't + +**Covered:** + +- All 10 FOCAS functions `FwlibNative.cs` P/Invokes +- Read-after-write round-trip for parameters, macros, PMC ranges +- PMC bit read-modify-write path (via the `pmc_wrpmcrng` seam) +- `IAlarmSource` raise + clear transitions (via `mock_schedule_alarms`) +- Per-series profile selection — tests can pin one and assert series-gated + behaviour + +**Not covered** (still hardware-gated): + +- Real FOCAS2 TCP wire protocol (this is a JSON mock; the shim hides + the real protocol entirely) +- CNC-specific firmware quirks (position scaling across power cycles, + edit-mode session locks, MTB custom screens) +- Concurrent-read behaviour on the real `Fwlib64.dll` — the shim is + single-threaded per connection + +See [`docs/drivers/FOCAS-Test-Fixture.md`](../../../docs/drivers/FOCAS-Test-Fixture.md) +for the full coverage map. + +## Skip behaviour + +`FocasSimFixture` probes the mock at collection init time: + +- Mock unreachable → tests skip with the compose-up command to run +- Mock reachable but shim DLL not loaded → tests skip with a pointer + at `Shim/build.ps1` +- Both available → tests run + +This lets the same test assembly be green on a fresh CI box without +docker, green on a dev box with just the docker compose up, and +exercise the full wire path when the shim is built. diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/docker-compose.yml b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/docker-compose.yml new file mode 100644 index 0000000..77595dc --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/docker-compose.yml @@ -0,0 +1,34 @@ +# FOCAS simulator — focas-mock JSON/TCP + native FOCAS2 Ethernet server. +# +# The image is built from the vendored focas-mock snapshot at ./focas-mock/ +# (see focas-mock/VENDORED.md for refresh procedure). +# +# Usage: +# docker compose -f Docker/docker-compose.yml up -d --wait +# docker compose -f Docker/docker-compose.yml down +# +# One service, one container — the mock's native FOCAS Ethernet responder +# auto-detects the binary PDU prefix (`a0 a0 a0 a0`) on the same TCP port +# that serves JSON admin commands. Tests that need per-series behaviour +# call `mock_load_profile` via the fixture's admin API at test start. +# The pre-wire-client era had one compose profile per CNC series; that +# ceremony is gone because the managed wire client doesn't depend on a +# per-series shim DLL. + +services: + focas-sim: + image: otopcua-focas-sim:latest + build: + context: ./focas-mock + dockerfile: Dockerfile + container_name: otopcua-focas-sim + ports: + - "8193:8193" + restart: "no" + command: ["--profile", "FWLIB64"] + healthcheck: + test: ["CMD-SHELL", "python -c \"import socket; s=socket.create_connection(('127.0.0.1',8193),timeout=2); s.close()\" || exit 1"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 5s diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/Dockerfile b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/Dockerfile new file mode 100644 index 0000000..b69f704 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY pyproject.toml README.md LICENSE ./ +COPY src ./src + +RUN pip install --no-cache-dir . + +EXPOSE 8193 + +ENTRYPOINT ["focas-mock", "serve", "--host", "0.0.0.0", "--port", "8193"] +CMD ["--profile", "FWLIB64"] diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/LICENSE b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/LICENSE new file mode 100644 index 0000000..14fac91 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/README.md b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/README.md new file mode 100644 index 0000000..a5fbb78 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/README.md @@ -0,0 +1,191 @@ +# focas-mock + +`focas-mock` is a Python TCP mock server for testing higher-level FOCAS clients without a real FANUC control. + +The project is built from two inputs: + +- The 64-bit FANUC-related DLLs downloaded from [Ladder99/fanuc-cnc-api](https://github.com/Ladder99/fanuc-cnc-api) +- The vendor `fwlib.cs` interop file, used as the callable surface reference + +The DLLs are not reimplemented at the binary ABI level. Instead, this project extracts their export tables, builds per-version capability profiles, exposes a JSON-over-TCP mock API, and implements the targeted native FOCAS Ethernet wire protocol used by OtOpcUa fixed-tree tests. + +## What is included + +- Vendored 64-bit DLLs under `vendor/fanuc-cnc-api/64bit/` +- A profile extractor that inspects PE exports with `pefile` +- A Windows P/Invoke shim source under `shim/` for clients that load `FWLIB64.dll` directly +- Built-in profiles for: + - `FWLIB64` + - `fwlib0DN64` + - `fwlib0iD64` + - `fwlib30i64` + - `fwlibe64` + - `fwlibNCG64` +- A stateful mock server with: + - version/profile switching + - forced error injection + - runtime state patching + - built-in default mock data + - auto-detected native FOCAS Ethernet PDU handling for the targeted API subset + +## Quick start + +Install in editable mode: + +```powershell +python -m pip install -e . +``` + +List the generated profiles: + +```powershell +focas-mock list-profiles +``` + +Start the mock server with the 30i profile: + +```powershell +focas-mock serve --profile fwlib30i64 --host 127.0.0.1 --port 8193 +``` + +Start with a JSON patch file that overrides the default data: + +```powershell +focas-mock serve --profile fwlib30i64 --data examples/mock-30i.json +``` + +## Protocol + +The server accepts two protocols on the same port: + +- newline-delimited JSON for fixture control and shim tests +- native FOCAS Ethernet binary PDUs from the real `fwlibe64.dll` + +JSON requests are one object per line: + +```json +{"id":1,"method":"cnc_allclibhndl3","params":{"ipaddr":"127.0.0.1","port":8193,"timeout":10}} +``` + +Example response: + +```json +{"id":1,"method":"cnc_allclibhndl3","rc":0,"message":"EW_OK","result":{"FlibHndl":1,"profile":"fwlib30i64"}} +``` + +Supported admin methods: + +- `mock_get_state` +- `mock_patch` +- `mock_reset` +- `mock_load_profile` +- `mock_list_methods` +- `mock_schedule_alarms` + +Example patch request: + +```json +{"id":2,"method":"mock_patch","params":{"state":{"parameters":{"6711":{"type":"long","value":1234,"decimal":0}}}}} +``` + +Native FOCAS Ethernet clients do not use the JSON request format. Seed profile +and fixture state with JSON first, then point `cnc_allclibhndl3` at the same +host and port. Wire-level details are documented in +`docs/FOCAS_WIRE_PROTOCOL.md`. + +For clients that should avoid FANUC DLL loading entirely, `dotnet/Focas.Wire` +contains a native C# read-only TCP client for the verified wire subset. It does +not expose write APIs; use the JSON control channel to preset fixture state. + +Example test setup over TCP: + +```json +{"id":1,"method":"mock_load_profile","params":{"profile":"FWLIB64"}} +{"id":2,"method":"mock_patch","params":{"state":{"pmc":{"R":{"100":{"type":"byte","value":1}}},"parameters":{"6711":{"type":"long","value":1234,"decimal":0}},"macros":{"500":{"value":42000,"decimal":3}},"statinfo":{"run":3,"aut":1,"emergency":0},"alarms":[{"alm_no":100,"type":1,"axis":0,"msg":"TEST ALARM"}]}}} +{"id":3,"method":"cnc_allclibhndl3","params":{"ipaddr":"127.0.0.1","port":8193,"timeout":10}} +{"id":4,"method":"pmc_rdpmcrng","params":{"FlibHndl":1,"area":"R","data_type":"byte","start":100,"end":100}} +``` + +## Regenerating profiles + +The built-in JSON profiles are generated from the vendored binaries: + +```powershell +python -m focas_mock.cli extract-profiles +``` + +By default this reads: + +- `vendor/fanuc-cnc-api/64bit/*.dll` +- `upstream/fwlib.cs` + +and writes: + +- `src/focas_mock/builtin_profiles/*.json` + +## Testing Direct P/Invoke Clients + +If a client directly P/Invokes FANUC's 64-bit DLLs, point it at the shim DLLs built from `shim/` instead of the real vendor DLLs. The shim exports the small FOCAS surface used by the client and forwards calls to this Python server over JSON/TCP. + +```powershell +focas-mock serve --profile FWLIB64 --host 127.0.0.1 --port 8193 +.\shim\build.ps1 +$env:FOCAS_MOCK_HOST = "127.0.0.1" +$env:FOCAS_MOCK_PORT = "8193" +``` + +Before running the client, seed profile/state with `mock_load_profile` and `mock_patch` as shown above. + +Detailed documentation for the supported FOCAS subset is in `docs/USED_FOCAS_API.md`. +Native Ethernet wire notes are in `docs/FOCAS_WIRE_PROTOCOL.md`. +OtOpcUa-specific setup notes are in `docs/OTOPCUA_DOTNET_INTEGRATION.md`. + +## Implemented mock calls + +The server currently implements a practical subset of the surface observed in the exported DLLs and the C# wrapper: + +- `cnc_allclibhndl` +- `cnc_allclibhndl2` +- `cnc_allclibhndl3` +- `cnc_freelibhndl` +- `cnc_sysinfo` +- `cnc_statinfo` +- `cnc_rddynamic2` +- `cnc_actf` +- `cnc_acts` +- `cnc_acts2` +- `cnc_getpath` +- `cnc_setpath` +- `cnc_rdaxisname` +- `cnc_rdspdlname` +- `cnc_rdparam` +- `cnc_wrparam` +- `cnc_rdmacro` +- `cnc_wrmacro` +- `cnc_rdalmmsg2` +- `pmc_rdpmcrng` +- `pmc_wrpmcrng` +- `cnc_rdopmsg` +- `cnc_rdopmode` +- `cnc_rdprgnum` +- `cnc_exeprgname2` +- `cnc_rdexecprog` +- `cnc_rdseqnum` +- `cnc_rdblkcount` +- `cnc_rdproginfo` +- `cnc_rdprogdir3` +- `cnc_rdtimer` +- `cnc_rdspmeter` +- `cnc_rdsvmeter` +- `cnc_rdspload` +- `cnc_rdspgear` +- `cnc_rdspmaxrpm` +- `cnc_rddiagnum` +- `cnc_rddiaginfo` +- `cnc_diagnoss` + +## Limitations + +- This is not a binary-compatible replacement for FANUC's DLLs. +- Native FOCAS Ethernet support is intentionally scoped to the targeted API subset documented in `docs/FOCAS_WIRE_PROTOCOL.md`. +- The per-version profiles are grounded in exported symbol tables plus the published interop wrapper, while some defaults such as axis-count hints are inferred from filename families and documented as heuristics. diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/VENDORED.md b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/VENDORED.md new file mode 100644 index 0000000..701c4cc --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/VENDORED.md @@ -0,0 +1,45 @@ +# focas-mock — vendored snapshot + +Source: `C:\Users\dohertj2\Desktop\focas` (sibling project in this dev environment). + +**Snapshot date:** 2026-04-24 (second refresh — pulled the native FOCAS2 Ethernet responder work in). + +## Why vendored + +OtOpcUa's FOCAS integration fixture runs against the Python mock server. +The upstream lives in its own repo; this directory is a verbatim +snapshot so CI can build the Docker image without network access to the +source repo and so OtOpcUa's test matrix pins a known-good revision. + +The managed `WireFocasClient` speaks the mock's native FOCAS2 Ethernet +binary protocol directly — there's no longer a companion shim DLL. + +## What's here + +| Path | Purpose | +|------|---------| +| `src/focas_mock/` | Python package — TCP JSON/line-delimited mock server with 6 Fanuc CNC profiles | +| `pyproject.toml` | Package metadata; installs `focas-mock` CLI | +| `Dockerfile` | `python:3.11-slim` image built by the parent `docker-compose.yml` | +| `README.md` | Upstream README | +| `LICENSE` | MIT — permissive, vendoring allowed | + + +## Refreshing the snapshot + +When upstream ships changes worth pulling: + +```powershell +$src = "C:\Users\dohertj2\Desktop\focas" +$dest = "$PWD" +Remove-Item -Recurse -Force "$dest\src" 2>$null +Copy-Item -Recurse "$src\src" "$dest\src" +Copy-Item "$src\pyproject.toml" "$dest\" +Copy-Item "$src\README.md" "$dest\" +Copy-Item "$src\LICENSE" "$dest\" +Copy-Item "$src\Dockerfile" "$dest\" +``` + +Update the snapshot date at the top of this file afterward. No other +files belong here — the Docker build context is just the Python package +and its metadata. diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/pyproject.toml b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/pyproject.toml new file mode 100644 index 0000000..d0b28a5 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["setuptools>=69", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "focas-mock" +version = "0.1.0" +description = "Mock FOCAS server with version-aware profiles derived from FANUC 64-bit DLL exports." +readme = "README.md" +requires-python = ">=3.11" +license = "MIT" +dependencies = ["pefile>=2024.8.26"] + +[project.scripts] +focas-mock = "focas_mock.cli:main" + +[tool.setuptools] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +focas_mock = ["builtin_profiles/*.json"] diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/PKG-INFO b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/PKG-INFO new file mode 100644 index 0000000..323cae7 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/PKG-INFO @@ -0,0 +1,185 @@ +Metadata-Version: 2.4 +Name: focas-mock +Version: 0.1.0 +Summary: Mock FOCAS server with version-aware profiles derived from FANUC 64-bit DLL exports. +License-Expression: MIT +Requires-Python: >=3.11 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: pefile>=2024.8.26 +Dynamic: license-file + +# focas-mock + +`focas-mock` is a Python TCP mock server for testing higher-level FOCAS clients without a real FANUC control. + +The project is built from two inputs: + +- The 64-bit FANUC-related DLLs downloaded from [Ladder99/fanuc-cnc-api](https://github.com/Ladder99/fanuc-cnc-api) +- The vendor `fwlib.cs` interop file, used as the callable surface reference + +The DLLs are not reimplemented at the binary ABI level. Instead, this project extracts their export tables, builds per-version capability profiles, and exposes a JSON-over-TCP mock API whose method names match common FOCAS entry points such as `cnc_allclibhndl3`, `cnc_sysinfo`, `cnc_statinfo`, and `cnc_rddynamic2`. + +## What is included + +- Vendored 64-bit DLLs under `vendor/fanuc-cnc-api/64bit/` +- A profile extractor that inspects PE exports with `pefile` +- A Windows P/Invoke shim source under `shim/` for clients that load `FWLIB64.dll` directly +- Built-in profiles for: + - `FWLIB64` + - `fwlib0DN64` + - `fwlib0iD64` + - `fwlib30i64` + - `fwlibe64` + - `fwlibNCG64` +- A stateful mock server with: + - version/profile switching + - forced error injection + - runtime state patching + - built-in default mock data + +## Quick start + +Install in editable mode: + +```powershell +python -m pip install -e . +``` + +List the generated profiles: + +```powershell +focas-mock list-profiles +``` + +Start the mock server with the 30i profile: + +```powershell +focas-mock serve --profile fwlib30i64 --host 127.0.0.1 --port 8193 +``` + +Start with a JSON patch file that overrides the default data: + +```powershell +focas-mock serve --profile fwlib30i64 --data examples/mock-30i.json +``` + +## Protocol + +The server speaks newline-delimited JSON. Each request is one JSON object per line: + +```json +{"id":1,"method":"cnc_allclibhndl3","params":{"ipaddr":"127.0.0.1","port":8193,"timeout":10}} +``` + +Example response: + +```json +{"id":1,"method":"cnc_allclibhndl3","rc":0,"message":"EW_OK","result":{"FlibHndl":1,"profile":"fwlib30i64"}} +``` + +Supported admin methods: + +- `mock_get_state` +- `mock_patch` +- `mock_reset` +- `mock_load_profile` +- `mock_list_methods` + +Example patch request: + +```json +{"id":2,"method":"mock_patch","params":{"state":{"parameters":{"6711":{"type":"long","value":1234,"decimal":0}}}}} +``` + +Example test setup over TCP: + +```json +{"id":1,"method":"mock_load_profile","params":{"profile":"FWLIB64"}} +{"id":2,"method":"mock_patch","params":{"state":{"pmc":{"R":{"100":{"type":"byte","value":1}}},"parameters":{"6711":{"type":"long","value":1234,"decimal":0}},"macros":{"500":{"value":42000,"decimal":3}},"statinfo":{"run":3,"aut":1,"emergency":0},"alarms":[{"alm_no":100,"type":1,"axis":0,"msg":"TEST ALARM"}]}}} +{"id":3,"method":"cnc_allclibhndl3","params":{"ipaddr":"127.0.0.1","port":8193,"timeout":10}} +{"id":4,"method":"pmc_rdpmcrng","params":{"FlibHndl":1,"area":"R","data_type":"byte","start":100,"end":100}} +``` + +## Regenerating profiles + +The built-in JSON profiles are generated from the vendored binaries: + +```powershell +python -m focas_mock.cli extract-profiles +``` + +By default this reads: + +- `vendor/fanuc-cnc-api/64bit/*.dll` +- `upstream/fwlib.cs` + +and writes: + +- `src/focas_mock/builtin_profiles/*.json` + +## Testing Direct P/Invoke Clients + +If a client directly P/Invokes FANUC's 64-bit DLLs, point it at the shim DLLs built from `shim/` instead of the real vendor DLLs. The shim exports the small FOCAS surface used by the client and forwards calls to this Python server over JSON/TCP. + +```powershell +focas-mock serve --profile FWLIB64 --host 127.0.0.1 --port 8193 +.\shim\build.ps1 +$env:FOCAS_MOCK_HOST = "127.0.0.1" +$env:FOCAS_MOCK_PORT = "8193" +``` + +Before running the client, seed profile/state with `mock_load_profile` and `mock_patch` as shown above. + +Detailed documentation for the supported FOCAS subset is in `docs/USED_FOCAS_API.md`. +OtOpcUa-specific setup notes are in `docs/OTOPCUA_DOTNET_INTEGRATION.md`. + +## Implemented mock calls + +The server currently implements a practical subset of the surface observed in the exported DLLs and the C# wrapper: + +- `cnc_allclibhndl` +- `cnc_allclibhndl2` +- `cnc_allclibhndl3` +- `cnc_freelibhndl` +- `cnc_sysinfo` +- `cnc_statinfo` +- `cnc_rddynamic2` +- `cnc_actf` +- `cnc_acts` +- `cnc_acts2` +- `cnc_getpath` +- `cnc_setpath` +- `cnc_rdaxisname` +- `cnc_rdspdlname` +- `cnc_rdparam` +- `cnc_wrparam` +- `cnc_rdmacro` +- `cnc_wrmacro` +- `cnc_rdalmmsg2` +- `pmc_rdpmcrng` +- `pmc_wrpmcrng` +- `cnc_rdopmsg` +- `cnc_rdopmode` +- `cnc_rdprgnum` +- `cnc_exeprgname2` +- `cnc_rdexecprog` +- `cnc_rdseqnum` +- `cnc_rdblkcount` +- `cnc_rdproginfo` +- `cnc_rdprogdir3` +- `cnc_rdtimer` +- `cnc_rdspmeter` +- `cnc_rdsvmeter` +- `cnc_rdspload` +- `cnc_rdspgear` +- `cnc_rdspmaxrpm` +- `cnc_rddiagnum` +- `cnc_rddiaginfo` +- `cnc_diagnoss` + +## Limitations + +- This is not a binary-compatible replacement for FANUC's DLLs. +- This is not a reverse-engineered implementation of FANUC's wire protocol. +- The per-version profiles are grounded in exported symbol tables plus the published interop wrapper, while some defaults such as axis-count hints are inferred from filename families and documented as heuristics. diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/SOURCES.txt b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/SOURCES.txt new file mode 100644 index 0000000..ceb5c8f --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/SOURCES.txt @@ -0,0 +1,25 @@ +LICENSE +README.md +pyproject.toml +src/focas_mock/__init__.py +src/focas_mock/cli.py +src/focas_mock/constants.py +src/focas_mock/data_store.py +src/focas_mock/defaults.py +src/focas_mock/export_introspection.py +src/focas_mock/profiles.py +src/focas_mock/server.py +src/focas_mock.egg-info/PKG-INFO +src/focas_mock.egg-info/SOURCES.txt +src/focas_mock.egg-info/dependency_links.txt +src/focas_mock.egg-info/entry_points.txt +src/focas_mock.egg-info/requires.txt +src/focas_mock.egg-info/top_level.txt +src/focas_mock/builtin_profiles/FWLIB64.json +src/focas_mock/builtin_profiles/fwlib0DN64.json +src/focas_mock/builtin_profiles/fwlib0iD64.json +src/focas_mock/builtin_profiles/fwlib30i64.json +src/focas_mock/builtin_profiles/fwlibNCG64.json +src/focas_mock/builtin_profiles/fwlibe64.json +tests/test_profiles.py +tests/test_server.py \ No newline at end of file diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/dependency_links.txt b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/entry_points.txt b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/entry_points.txt new file mode 100644 index 0000000..79a03ec --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +focas-mock = focas_mock.cli:main diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/requires.txt b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/requires.txt new file mode 100644 index 0000000..90db2f4 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/requires.txt @@ -0,0 +1 @@ +pefile>=2024.8.26 diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/top_level.txt b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/top_level.txt new file mode 100644 index 0000000..805fc02 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock.egg-info/top_level.txt @@ -0,0 +1 @@ +focas_mock diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/__init__.py b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/__init__.py new file mode 100644 index 0000000..4cfb876 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/__init__.py @@ -0,0 +1,5 @@ +from .profiles import list_profiles, load_profile +from .server import FocasMockServer + +__all__ = ["FocasMockServer", "list_profiles", "load_profile"] + diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/FWLIB64.json b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/FWLIB64.json new file mode 100644 index 0000000..3c1fd1b --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/FWLIB64.json @@ -0,0 +1,2677 @@ +{ + "profile_name": "FWLIB64", + "dll_name": "FWLIB64.dll", + "series_hint": "generic-ethernet", + "max_axis_hint": 8, + "max_path_hint": 1, + "export_count": 1883, + "connection_methods": [ + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_allclibhndl3", + "cnc_allclibhndl4", + "cnc_allclibhndls" + ], + "mock_methods": [ + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_allclibhndl3", + "cnc_diagnoss", + "cnc_exeprgname2", + "cnc_freelibhndl", + "cnc_getpath", + "cnc_rdalmmsg2", + "cnc_rdaxisname", + "cnc_rdblkcount", + "cnc_rddiaginfo", + "cnc_rddiagnum", + "cnc_rddynamic2", + "cnc_rdexecprog", + "cnc_rdmacro", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdparam", + "cnc_rdprgnum", + "cnc_rdprogdir3", + "cnc_rdproginfo", + "cnc_rdseqnum", + "cnc_rdspdlname", + "cnc_rdspgear", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsvmeter", + "cnc_rdtimer", + "cnc_setpath", + "cnc_statinfo", + "cnc_sysinfo", + "cnc_wrmacro", + "cnc_wrparam", + "pmc_rdpmcrng", + "pmc_wrpmcrng" + ], + "wrapper_supported_count": 728, + "wrapper_supported_methods": [ + "cnc_absolute", + "cnc_absolute2", + "cnc_accdecdly", + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_adcnv", + "cnc_alarm", + "cnc_alarm2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_allclibhndl3", + "cnc_allclibhndl4", + "cnc_allowance", + "cnc_allowcnd", + "cnc_async_busy_state", + "cnc_buff", + "cnc_canmovrlap", + "cnc_cdnc", + "cnc_cdownload", + "cnc_cexedirectory", + "cnc_cexesraminfo", + "cnc_cexesramsize", + "cnc_chgprotbit", + "cnc_chkrmtdgn", + "cnc_clearomhis", + "cnc_clearophis", + "cnc_closecexefile", + "cnc_clr3dplsmov", + "cnc_clr5axpls", + "cnc_clralm", + "cnc_clrcntinfo", + "cnc_clrdgdat", + "cnc_clrgrphcmd", + "cnc_clrmsgbuff", + "cnc_condense", + "cnc_copyprog", + "cnc_cupload", + "cnc_cverify", + "cnc_delall", + "cnc_delete", + "cnc_delmagazine", + "cnc_delprogline", + "cnc_delrange", + "cnc_deltlifedt", + "cnc_deltlifegrp", + "cnc_deltool", + "cnc_diagnosr", + "cnc_diagnoss", + "cnc_dispoptmsg", + "cnc_distance", + "cnc_dnc", + "cnc_dnc2", + "cnc_dncend", + "cnc_dncend2", + "cnc_dncstart", + "cnc_dncstart2", + "cnc_download", + "cnc_download3", + "cnc_download4", + "cnc_dtsvchkdsk", + "cnc_dtsvcnclupdn", + "cnc_dtsvdelete", + "cnc_dtsvdownload", + "cnc_dtsvftpget", + "cnc_dtsvftpput", + "cnc_dtsvftpstat", + "cnc_dtsvgetdncpg", + "cnc_dtsvgetmode", + "cnc_dtsvhdformat", + "cnc_dtsvmntinfo", + "cnc_dtsvrdcram", + "cnc_dtsvrderrmsg", + "cnc_dtsvrdfile", + "cnc_dtsvrdpgdir", + "cnc_dtsvrdset", + "cnc_dtsvsavecram", + "cnc_dtsvsetdncpg", + "cnc_dtsvsetmode", + "cnc_dtsvupdnstat", + "cnc_dtsvupload", + "cnc_dtsvwrfile", + "cnc_dtsvwrset", + "cnc_dwnend", + "cnc_dwnend2", + "cnc_dwnend3", + "cnc_dwnend4", + "cnc_dwnstart", + "cnc_dwnstart3", + "cnc_dwnstart3_f", + "cnc_dwnstart4", + "cnc_end_async_wrparam", + "cnc_exaxisname", + "cnc_exeprgname", + "cnc_exeprgname2", + "cnc_freelibhndl", + "cnc_fromdelete", + "cnc_fromget", + "cnc_fromgetend", + "cnc_fromgetstart", + "cnc_fromldend", + "cnc_fromldstart", + "cnc_fromload", + "cnc_fromput", + "cnc_fromputend", + "cnc_fromputstart", + "cnc_fromremove", + "cnc_fromsave", + "cnc_fromsvend", + "cnc_fromsvstart", + "cnc_ftosjis", + "cnc_getcncmodel", + "cnc_getcrntscrn", + "cnc_getdspmode", + "cnc_getdtailerr", + "cnc_getfigure", + "cnc_getfocas1opt", + "cnc_getfrominfo", + "cnc_getlibopt", + "cnc_getlockstat", + "cnc_getmactype", + "cnc_getpath", + "cnc_getpmactype", + "cnc_getsraminfo", + "cnc_gettimer", + "cnc_hpccactfine", + "cnc_hpccatset", + "cnc_hpccattune", + "cnc_hpccselfine", + "cnc_instlifedt", + "cnc_machine", + "cnc_mergeprog", + "cnc_modal", + "cnc_newprog", + "cnc_opencexefile", + "cnc_optmsgans", + "cnc_pdf_add", + "cnc_pdf_cond", + "cnc_pdf_copy", + "cnc_pdf_del", + "cnc_pdf_delline", + "cnc_pdf_move", + "cnc_pdf_rdactpt", + "cnc_pdf_rdmain", + "cnc_pdf_rename", + "cnc_pdf_searchresult", + "cnc_pdf_searchword", + "cnc_pdf_slctmain", + "cnc_pdf_wractpt", + "cnc_progdigit", + "cnc_prstwkcd", + "cnc_rd1length", + "cnc_rd1radius", + "cnc_rd1tlifedat2", + "cnc_rd1tlifedata", + "cnc_rd2length", + "cnc_rd2radius", + "cnc_rd2tlifedata", + "cnc_rd3dcdcnv", + "cnc_rd3dmovrlap", + "cnc_rd3dofschg", + "cnc_rd3dpulse", + "cnc_rd3dtofs", + "cnc_rd3dtooltip", + "cnc_rd5axmandt", + "cnc_rd5axovrlap", + "cnc_rdactfixofs", + "cnc_rdactpt", + "cnc_rdacttlzone", + "cnc_rdalmhisno", + "cnc_rdalmhistry", + "cnc_rdalmhistry2", + "cnc_rdalmhistry3", + "cnc_rdalmhistry5", + "cnc_rdalmhistry_w", + "cnc_rdalminfo", + "cnc_rdalmmsg", + "cnc_rdalmmsg2", + "cnc_rdaxisdata", + "cnc_rdaxisname", + "cnc_rdbaxis", + "cnc_rdblkcount", + "cnc_rdbrstrinfo", + "cnc_rdbtofsinfo", + "cnc_rdbtofsr", + "cnc_rdcdrotate", + "cnc_rdcexefile", + "cnc_rdcexesram", + "cnc_rdcncid", + "cnc_rdcomlogmsg", + "cnc_rdcommand", + "cnc_rdcomopemsg", + "cnc_rdcomparam", + "cnc_rdcoordnum", + "cnc_rdcount", + "cnc_rdctrldi", + "cnc_rdctrldo", + "cnc_rdcurrent", + "cnc_rddgdat", + "cnc_rddiag_ext", + "cnc_rddiaginfo", + "cnc_rddiagnum", + "cnc_rddischarge", + "cnc_rddischrgalm", + "cnc_rddncdgndt", + "cnc_rddncfname", + "cnc_rddynamic", + "cnc_rddynamic2", + "cnc_rdetherinfo", + "cnc_rdexchgtgrp", + "cnc_rdexecmcode", + "cnc_rdexecprog", + "cnc_rdexecpt", + "cnc_rdfbusmem", + "cnc_rdfixcycle", + "cnc_rdfixofs", + "cnc_rdfrominfo", + "cnc_rdgcode", + "cnc_rdgrphcanflg", + "cnc_rdgrphcmd", + "cnc_rdgrpid", + "cnc_rdgrpid2", + "cnc_rdgrpinfo", + "cnc_rdgrpinfo2", + "cnc_rdgrpinfo3", + "cnc_rdgrpinfo4", + "cnc_rdhissgnl", + "cnc_rdhissgnl2", + "cnc_rdhissgnl3", + "cnc_rdhndintrpt", + "cnc_rdhpccset", + "cnc_rdhpcctuac", + "cnc_rdhpcctupr", + "cnc_rdhsparam", + "cnc_rdhsprminfo", + "cnc_rdintchk", + "cnc_rdintinfo", + "cnc_rdlactnum", + "cnc_rdlagslt", + "cnc_rdlagst", + "cnc_rdlcmddat", + "cnc_rdlcmmt", + "cnc_rdldsplc", + "cnc_rdledgprc", + "cnc_rdlenofs", + "cnc_rdlerrz", + "cnc_rdlife", + "cnc_rdloopgain", + "cnc_rdlprcprc", + "cnc_rdlpwrcpst", + "cnc_rdlpwrctrl", + "cnc_rdlpwrdat", + "cnc_rdlpwrdty", + "cnc_rdlpwrslt", + "cnc_rdmacro", + "cnc_rdmacroinfo", + "cnc_rdmacror", + "cnc_rdmacror2", + "cnc_rdmagazine", + "cnc_rdmaxgrp", + "cnc_rdmaxtool", + "cnc_rdmdipntr", + "cnc_rdmdiprgstat", + "cnc_rdmdiprog", + "cnc_rdmdlconfig", + "cnc_rdmdlconfig2", + "cnc_rdmenuswitch", + "cnc_rdmgrpdata", + "cnc_rdmirimage", + "cnc_rdmngtime", + "cnc_rdmovrlap", + "cnc_rdmsptype", + "cnc_rdmtapdata", + "cnc_rdmultipieceno", + "cnc_rdmultitldt", + "cnc_rdngrp", + "cnc_rdnodeinfo", + "cnc_rdnodenum", + "cnc_rdnspdl", + "cnc_rdntool", + "cnc_rdomhisinfo", + "cnc_rdomhisno", + "cnc_rdomhistry", + "cnc_rdomhistry2", + "cnc_rdophisno", + "cnc_rdophistry", + "cnc_rdophistry2", + "cnc_rdophistry4", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdopmsg2", + "cnc_rdopmsg3", + "cnc_rdopnlgnrl", + "cnc_rdopnlgsname", + "cnc_rdopnlsgnl", + "cnc_rdparainfo", + "cnc_rdparam", + "cnc_rdparam3", + "cnc_rdparam_ext", + "cnc_rdparanum", + "cnc_rdparar", + "cnc_rdpdf_alldir", + "cnc_rdpdf_curdir", + "cnc_rdpdf_drive", + "cnc_rdpdf_inf", + "cnc_rdpdf_line", + "cnc_rdpdf_subdir", + "cnc_rdpdf_subdirn", + "cnc_rdpglockstat", + "cnc_rdpitchinfo", + "cnc_rdpitchr", + "cnc_rdpm_cncitem", + "cnc_rdpm_item", + "cnc_rdpm_mcnitem", + "cnc_rdpmacro", + "cnc_rdpmacroinfo", + "cnc_rdpmacror", + "cnc_rdpmacror2", + "cnc_rdposerrs", + "cnc_rdposerrs2", + "cnc_rdposerrz", + "cnc_rdposition", + "cnc_rdposofs", + "cnc_rdprgdirtime", + "cnc_rdprgnum", + "cnc_rdproctime", + "cnc_rdprogdir", + "cnc_rdprogdir2", + "cnc_rdprogdir3", + "cnc_rdprogdir4", + "cnc_rdproginfo", + "cnc_rdprogline", + "cnc_rdprogline2", + "cnc_rdprstrinfo", + "cnc_rdpscdedge", + "cnc_rdpscdpirc", + "cnc_rdpscdproc", + "cnc_rdpscdslop", + "cnc_rdpwofsthis", + "cnc_rdradofs", + "cnc_rdrcvmsg", + "cnc_rdrcvstat", + "cnc_rdrepeatval", + "cnc_rdrmtdgn", + "cnc_rdrmtwavedt", + "cnc_rdrmtwaveprm", + "cnc_rdrstrmcode", + "cnc_rdsafetyzone", + "cnc_rdsavsigadr", + "cnc_rdsavsigdata", + "cnc_rdscaling", + "cnc_rdseqnum", + "cnc_rdset", + "cnc_rdsetinfo", + "cnc_rdsetnum", + "cnc_rdsetr", + "cnc_rdsetzone", + "cnc_rdsndmsg", + "cnc_rdspcss", + "cnc_rdspdlalm", + "cnc_rdspdlname", + "cnc_rdspeed", + "cnc_rdspgear", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsramaddr", + "cnc_rdsraminfo", + "cnc_rdsramnum", + "cnc_rdsrvspeed", + "cnc_rdsvmeter", + "cnc_rdsynerrrg", + "cnc_rdsynerrsy", + "cnc_rdsyshard", + "cnc_rdsyssoft", + "cnc_rdsyssoft2", + "cnc_rdsyssoft3", + "cnc_rdtimer", + "cnc_rdtlctldata", + "cnc_rdtlgrp", + "cnc_rdtlinfo", + "cnc_rdtltool", + "cnc_rdtlusegrp", + "cnc_rdtofs", + "cnc_rdtofsinfo", + "cnc_rdtofsinfo2", + "cnc_rdtofsr", + "cnc_rdtool", + "cnc_rdtool_f2", + "cnc_rdtooldata", + "cnc_rdtoolgrp", + "cnc_rdtoolinfo", + "cnc_rdtoolrng", + "cnc_rdtoolzone", + "cnc_rdunsolicmsg", + "cnc_rdunsolicprm", + "cnc_rdusegrpid", + "cnc_rdusetlno", + "cnc_rduvactpt", + "cnc_rdwavedata", + "cnc_rdwaveprm", + "cnc_rdwaveprm2", + "cnc_rdwkcdsfms", + "cnc_rdwkcdshft", + "cnc_rdzofs", + "cnc_rdzofsinfo", + "cnc_rdzofsr", + "cnc_regmagazine", + "cnc_regtool", + "cnc_regtool_f2", + "cnc_relative", + "cnc_relative2", + "cnc_renameprog", + "cnc_reset", + "cnc_reset2", + "cnc_resetconnect", + "cnc_resetpglock", + "cnc_rewind", + "cnc_rmtwavestart", + "cnc_rmtwavestat", + "cnc_rmtwavestop", + "cnc_rstrseqsrch", + "cnc_rstrseqsrch2", + "cnc_sdcancelsmpl", + "cnc_sdclrchnl", + "cnc_sdendsmpl", + "cnc_sdread1shot", + "cnc_sdreadsmpl", + "cnc_sdsetchnl", + "cnc_sdstartsmpl", + "cnc_search", + "cnc_search2", + "cnc_searchresult", + "cnc_searchword", + "cnc_sendmessage", + "cnc_seqsrch", + "cnc_seqsrch2", + "cnc_setdefnode", + "cnc_setlibopt", + "cnc_setmactype", + "cnc_setpath", + "cnc_setpglock", + "cnc_setpmactype", + "cnc_setthrdngpos", + "cnc_settimeout", + "cnc_settimer", + "cnc_setvrtclpos", + "cnc_sfbcancelsmpl", + "cnc_sfbclrchnl", + "cnc_sfbendsmpl", + "cnc_sfbreadsmpl", + "cnc_sfbsetchnl", + "cnc_sfbstartsmpl", + "cnc_skip", + "cnc_slctscrn", + "cnc_slide", + "cnc_srambackup", + "cnc_srambkend", + "cnc_srambkstart", + "cnc_sramget", + "cnc_sramget2", + "cnc_sramgetend", + "cnc_sramgetend2", + "cnc_sramgetstart", + "cnc_sramgetstart2", + "cnc_srcsfreechnl", + "cnc_srcsrddrvcp", + "cnc_srcsrdexstat", + "cnc_srcsrdidinfo", + "cnc_srcsrdlayout", + "cnc_srcsrdopdata", + "cnc_srcsrsvchnl", + "cnc_srcsstartrd", + "cnc_srcsstartwrt", + "cnc_srcsstopexec", + "cnc_srcswridinfo", + "cnc_srcswropdata", + "cnc_srvdelay", + "cnc_start", + "cnc_start_async_wrparam", + "cnc_startdrawpos", + "cnc_startdyngrph", + "cnc_startgetdgdat", + "cnc_startnccmd", + "cnc_startnccmd2", + "cnc_startomhis", + "cnc_startophis", + "cnc_startrmtdgn", + "cnc_statinfo", + "cnc_stopdrawpos", + "cnc_stopdyngrph", + "cnc_stopgetdgdat", + "cnc_stopnccmd", + "cnc_stopomhis", + "cnc_stopophis", + "cnc_stoprmtdgn", + "cnc_svdtendrd", + "cnc_svdtendwr", + "cnc_svdtrddata", + "cnc_svdtstartrd", + "cnc_svdtstartwr", + "cnc_svdtstopexec", + "cnc_svdtwrdata", + "cnc_sysconfig", + "cnc_sysinfo", + "cnc_sysinfo_ex", + "cnc_t1info", + "cnc_t2info", + "cnc_tofs_rnge", + "cnc_toolnum", + "cnc_unsolicstart", + "cnc_unsolicstop", + "cnc_upend", + "cnc_upend3", + "cnc_upend4", + "cnc_upload", + "cnc_upload3", + "cnc_upload4", + "cnc_upstart", + "cnc_upstart3", + "cnc_upstart3_f", + "cnc_upstart4", + "cnc_verify", + "cnc_verify4", + "cnc_vrfend", + "cnc_vrfend4", + "cnc_vrfstart", + "cnc_vrfstart4", + "cnc_wavestart", + "cnc_wavestat", + "cnc_wavestop", + "cnc_wksft_rnge", + "cnc_workzero", + "cnc_wr1tlifedat2", + "cnc_wr1tlifedata", + "cnc_wr2tlifedata", + "cnc_wractpt", + "cnc_wrbtofsr", + "cnc_wrcexefile", + "cnc_wrcexesram", + "cnc_wrcommstatus", + "cnc_wrcomparam", + "cnc_wrcountr", + "cnc_wrdgdatptr", + "cnc_wrdncfname", + "cnc_wrfbusmem", + "cnc_wrfixofs", + "cnc_wrgrphcmdptr", + "cnc_wrgrpinfo", + "cnc_wrgrpinfo2", + "cnc_wrgrpinfo3", + "cnc_wrhissgnl", + "cnc_wrhissgnl2", + "cnc_wrhissgnl3", + "cnc_wrhpccset", + "cnc_wrhpcctuac", + "cnc_wrhpcctupr", + "cnc_wrintchk", + "cnc_wrkeyhistry", + "cnc_wrlagslt", + "cnc_wrlagst", + "cnc_wrldsplc", + "cnc_wrledgprc", + "cnc_wrlprcprc", + "cnc_wrlpwrcpst", + "cnc_wrlpwrctrl", + "cnc_wrlpwrdty", + "cnc_wrlpwrslt", + "cnc_wrmacro", + "cnc_wrmacror", + "cnc_wrmacror2", + "cnc_wrmagazine", + "cnc_wrmdipntr", + "cnc_wrmdiprog", + "cnc_wrmenuswitch", + "cnc_wrmgrpdata", + "cnc_wrmngtime", + "cnc_wrmsptype", + "cnc_wrmtapdata", + "cnc_wrmultitldt", + "cnc_wropnlgnrl", + "cnc_wropnlgsname", + "cnc_wropnlsgnl", + "cnc_wrparam", + "cnc_wrparas", + "cnc_wrpdf_attr", + "cnc_wrpdf_curdir", + "cnc_wrpdf_line", + "cnc_wrpitchr", + "cnc_wrpm_item", + "cnc_wrpm_mcnitem", + "cnc_wrpmacro", + "cnc_wrpmacror", + "cnc_wrpmacror2", + "cnc_wrprogline", + "cnc_wrpscdedge", + "cnc_wrpscdpirc", + "cnc_wrpscdproc", + "cnc_wrpscdslop", + "cnc_wrrelpos", + "cnc_wrrmtdgn", + "cnc_wrrmtwaveprm", + "cnc_wrsafetyzone", + "cnc_wrsavsigadr", + "cnc_wrset", + "cnc_wrsets", + "cnc_wrsetzone", + "cnc_wrtimer", + "cnc_wrtlctldata", + "cnc_wrtofs", + "cnc_wrtofsr", + "cnc_wrtool", + "cnc_wrtool2", + "cnc_wrtool_f2", + "cnc_wrtooldata", + "cnc_wrtoolzone", + "cnc_wrunsolicprm", + "cnc_wrwaveprm", + "cnc_wrwaveprm2", + "cnc_wrwkcdsfms", + "cnc_wrwkcdshft", + "cnc_wrzofs", + "cnc_wrzofsr", + "cnc_zofs_rnge", + "ds_cancel", + "ds_checkhdd", + "ds_chghdddir", + "ds_copyhddfile", + "ds_delhdddir", + "ds_delhddfile", + "ds_delhostfile", + "ds_formathdd", + "ds_gethostfile", + "ds_ldelhddfile", + "ds_lgethostfile", + "ds_lputhddfile", + "ds_makehdddir", + "ds_mgethostfile", + "ds_mputhddfile", + "ds_puthddfile", + "ds_rddnchddfile", + "ds_rddnchostfile", + "ds_rdfile", + "ds_rdhdddir", + "ds_rdhddinfo", + "ds_rdhostdir", + "ds_rdhostdir2", + "ds_rdhostinfo", + "ds_rdhostno", + "ds_rdm198hdddir", + "ds_rdm198host", + "ds_rdmntinfo", + "ds_rdmode", + "ds_rdncfile", + "ds_rdncfile2", + "ds_rdresult", + "ds_renhddfile", + "ds_searchresult", + "ds_searchword", + "ds_wrdnchddfile", + "ds_wrdnchostfile", + "ds_wrfile", + "ds_wrhostno", + "ds_wrm198hdddir", + "ds_wrm198host", + "ds_wrmode", + "ds_wrncfile", + "etb_rderrmsg", + "etb_rdparam", + "etb_wrparam", + "pmc_crdmsg", + "pmc_cwrmsg", + "pmc_get_current_pmc_unit", + "pmc_get_number_of_pmc", + "pmc_get_pmc_unit_types", + "pmc_get_timer_type", + "pmc_getdtailerr", + "pmc_kpmsiz", + "pmc_prfrdallcadr", + "pmc_prfrdbusprm", + "pmc_prfrdconfig", + "pmc_prfrddido", + "pmc_prfrdindiadr", + "pmc_prfrdopmode", + "pmc_prfrdslvaddr", + "pmc_prfrdslvid", + "pmc_prfrdslvprm", + "pmc_prfrdslvprm2", + "pmc_prfrdslvstat", + "pmc_prfwrallcadr", + "pmc_prfwrbusprm", + "pmc_prfwrdido", + "pmc_prfwrindiadr", + "pmc_prfwropmode", + "pmc_prfwrslvaddr", + "pmc_prfwrslvid", + "pmc_prfwrslvprm", + "pmc_prfwrslvprm2", + "pmc_rdalmmsg", + "pmc_rdcntldata", + "pmc_rdcntlgrp", + "pmc_rdkpm", + "pmc_rdkpm2", + "pmc_rdmsg", + "pmc_rdpmcaddr", + "pmc_rdpmcinfo", + "pmc_rdpmcmem", + "pmc_rdpmcparam", + "pmc_rdpmcrng", + "pmc_rdpmcrng_ext", + "pmc_rdpmcsemem", + "pmc_rdpmctitle", + "pmc_rdprmend", + "pmc_rdprmstart", + "pmc_select_pmc_unit", + "pmc_set_timer_type", + "pmc_wrcntldata", + "pmc_wrcntlgrp", + "pmc_wriolinkdat", + "pmc_wrkpm", + "pmc_wrkpm2", + "pmc_wrmsg", + "pmc_wrpmcmem", + "pmc_wrpmcparam", + "pmc_wrpmcrng", + "pmc_wrpmcsemem", + "pmc_wrprmend", + "pmc_wrprmstart" + ], + "exports": [ + "Get_HSSB_Port_no", + "Get_HSSB_Stat", + "Init_HSSB", + "Set_HSSB_Port_no", + "anm_rdsimuelm", + "anm_rdsimuelm2", + "anm_simuclose", + "anm_simuopen", + "anm_simuproc", + "anm_simurwd", + "anm_simusngl", + "anm_simustart", + "anm_simustop", + "cb_download", + "cb_dwnend", + "cb_dwnstart", + "cb_transinfo", + "cb_upend", + "cb_upload", + "cb_upstart", + "cnc_3dchk_end", + "cnc_3dchk_getprginfo", + "cnc_3dchk_mchn_stop", + "cnc_3dchk_rddata", + "cnc_3dchk_rddata2", + "cnc_3dchk_rddata3", + "cnc_3dchk_rddata4", + "cnc_3dchk_start", + "cnc_3dchk_start2", + "cnc_absolute", + "cnc_absolute2", + "cnc_absolute2_exdgt", + "cnc_absolute_bg", + "cnc_absolute_mgi", + "cnc_abspoint", + "cnc_accdecdly", + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_adcnv", + "cnc_addexauxr", + "cnc_alarm", + "cnc_alarm2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_allclibhndl3", + "cnc_allclibhndl4", + "cnc_allclibhndls", + "cnc_allowance", + "cnc_allowcnd", + "cnc_async_busy_state", + "cnc_atbk_rdtime", + "cnc_aux_statinfo", + "cnc_axisnum", + "cnc_axisnum2", + "cnc_block_status", + "cnc_btlfpotsrh", + "cnc_buff", + "cnc_canaux", + "cnc_cancel_prm", + "cnc_canmcdfinfo", + "cnc_canmovrlap", + "cnc_cannedcycle", + "cnc_cdautoset", + "cnc_cdnc", + "cnc_cdownload", + "cnc_cexedirectory", + "cnc_cexesraminfo", + "cnc_cexesramsize", + "cnc_checkpitch", + "cnc_chglang", + "cnc_chgoverstore", + "cnc_chgprotbit", + "cnc_chkmcdfile", + "cnc_chkrmtdgn", + "cnc_chktdistatus", + "cnc_chkversion", + "cnc_clear_maint", + "cnc_clearomhis", + "cnc_clearophis", + "cnc_closecexefile", + "cnc_clr3dplsmov", + "cnc_clr5axpls", + "cnc_clr5dplsmov", + "cnc_clralm", + "cnc_clrcntinfo", + "cnc_clrdgdat", + "cnc_clrfromsvspid", + "cnc_clrgrphcmd", + "cnc_clrmsgbuff", + "cnc_clroverstore", + "cnc_clrptdata", + "cnc_clrrstraxis", + "cnc_clrtofs", + "cnc_condense", + "cnc_confirm_restart", + "cnc_coordre", + "cnc_copyprog", + "cnc_cupload", + "cnc_cverify", + "cnc_data_copy", + "cnc_delall", + "cnc_delallauxdata", + "cnc_delete", + "cnc_deleteo8", + "cnc_delexauxr", + "cnc_delmag_property", + "cnc_delmagazine", + "cnc_delmcdfile", + "cnc_delmcdfilebynum", + "cnc_delpot_property", + "cnc_delprogline", + "cnc_delrange", + "cnc_delsysalm", + "cnc_deltlifedt", + "cnc_deltlifegrp", + "cnc_deltool", + "cnc_diagnosr", + "cnc_diagnosr64", + "cnc_diagnoss", + "cnc_diagnoss64", + "cnc_dispoptmsg", + "cnc_distance", + "cnc_distance2", + "cnc_distance_exdgt", + "cnc_distancem", + "cnc_dnc", + "cnc_dnc2", + "cnc_dncend", + "cnc_dncend2", + "cnc_dncprgname", + "cnc_dncstart", + "cnc_dncstart2", + "cnc_download", + "cnc_download3", + "cnc_download3m", + "cnc_download4", + "cnc_drctver", + "cnc_dschdir", + "cnc_dscopyfile", + "cnc_dsfile_req", + "cnc_dsftpcancel", + "cnc_dsftpstat", + "cnc_dsget_req", + "cnc_dslistdel_req", + "cnc_dslistget_req", + "cnc_dslistput_req", + "cnc_dsmget_req", + "cnc_dsmkdir", + "cnc_dsmput_req", + "cnc_dsput_req", + "cnc_dsrdclose", + "cnc_dsrdopen", + "cnc_dsread", + "cnc_dsremove", + "cnc_dsrename", + "cnc_dsrmdir", + "cnc_dssearch", + "cnc_dsstat_rdfile", + "cnc_dswrclose", + "cnc_dswrite", + "cnc_dswropen", + "cnc_dtsvchkdsk", + "cnc_dtsvcnclupdn", + "cnc_dtsvdelete", + "cnc_dtsvdownload", + "cnc_dtsvftpget", + "cnc_dtsvftpput", + "cnc_dtsvftpstat", + "cnc_dtsvgetdncpg", + "cnc_dtsvgetmode", + "cnc_dtsvhdformat", + "cnc_dtsvmntinfo", + "cnc_dtsvrdcram", + "cnc_dtsvrderrmsg", + "cnc_dtsvrdfile", + "cnc_dtsvrdpgdir", + "cnc_dtsvrdset", + "cnc_dtsvsavecram", + "cnc_dtsvsetdncpg", + "cnc_dtsvsetmode", + "cnc_dtsvupdnstat", + "cnc_dtsvupload", + "cnc_dtsvwrfile", + "cnc_dtsvwrset", + "cnc_dvpunchvolc", + "cnc_dvreadvolc", + "cnc_dwell", + "cnc_dwnend", + "cnc_dwnend2", + "cnc_dwnend3", + "cnc_dwnend3m", + "cnc_dwnend4", + "cnc_dwnstart", + "cnc_dwnstart3", + "cnc_dwnstart3_f", + "cnc_dwnstart3m", + "cnc_dwnstart4", + "cnc_edplutosmpl", + "cnc_end_async_data_punch", + "cnc_end_async_data_read", + "cnc_end_async_pdf_punch", + "cnc_end_async_pdf_read", + "cnc_end_async_punch_prog3", + "cnc_end_async_punch_prog3_bg", + "cnc_end_async_read_prog3", + "cnc_end_async_read_prog3_bg", + "cnc_end_async_wrparam", + "cnc_endpossmpl", + "cnc_exaxisname", + "cnc_exaxisname2", + "cnc_exebufstat", + "cnc_exeprgname", + "cnc_exeprgname2", + "cnc_file_cpmv_end", + "cnc_file_cpmv_poll", + "cnc_file_cpmv_restart", + "cnc_file_cpmv_start", + "cnc_fileread", + "cnc_fileread_end", + "cnc_fileread_start", + "cnc_filewrite", + "cnc_filewrite_end", + "cnc_filewrite_start", + "cnc_finstate", + "cnc_freelibhndl", + "cnc_fromdelete", + "cnc_fromget", + "cnc_fromget2", + "cnc_fromgetend", + "cnc_fromgetend2", + "cnc_fromgetstart", + "cnc_fromgetstart2", + "cnc_fromldend", + "cnc_fromldstart", + "cnc_fromload", + "cnc_fromput", + "cnc_fromput2", + "cnc_fromputend", + "cnc_fromputend2", + "cnc_fromputstart", + "cnc_fromputstart2", + "cnc_fromremove", + "cnc_fromsave", + "cnc_fromsvend", + "cnc_fromsvstart", + "cnc_fssb_autoset", + "cnc_fssb_reset", + "cnc_ftosjis", + "cnc_get_crosschk_alarm", + "cnc_get_flowmonitor", + "cnc_get_mccteststs", + "cnc_get_safetysts", + "cnc_get_safetysts2", + "cnc_getauxdatar", + "cnc_getauxoverdatar", + "cnc_getauxregnum", + "cnc_getcncmodel", + "cnc_getcrntscrn", + "cnc_getdefnode", + "cnc_getdllversion", + "cnc_getdspmode", + "cnc_getdtailerr", + "cnc_getdtailerr2", + "cnc_getexauxr", + "cnc_getexauxregnum", + "cnc_getexemacstat", + "cnc_getfigure", + "cnc_getfocas1opt", + "cnc_getfrominfo", + "cnc_getinfoaux", + "cnc_getlanguage", + "cnc_getlibopt", + "cnc_getlockstat", + "cnc_getmacaddress", + "cnc_getmactype", + "cnc_getpath", + "cnc_getpaxispath", + "cnc_getpmactype", + "cnc_getprntname", + "cnc_getregprgnum", + "cnc_getrtmrvar", + "cnc_getrtmrvars", + "cnc_getsraminfo", + "cnc_getstsaux", + "cnc_getsysfolder_num", + "cnc_gettimer", + "cnc_gettolnum_qset", + "cnc_getupdcount", + "cnc_getvcprg", + "cnc_getzofsnum_qset", + "cnc_glvndiagnoss", + "cnc_hdck_nochange_info", + "cnc_hpccactfine", + "cnc_hpccatset", + "cnc_hpccattune", + "cnc_hpccselfine", + "cnc_ifsb_autoset", + "cnc_ifsb_reset", + "cnc_incpoint", + "cnc_instlifedt", + "cnc_lctcdcstm", + "cnc_loadtorq", + "cnc_machine", + "cnc_machine2", + "cnc_machine3", + "cnc_machine3_ex", + "cnc_machine_exdgt", + "cnc_magazinesrch", + "cnc_mcdp_create", + "cnc_mcdp_mount", + "cnc_mcdp_mountchk", + "cnc_mcdp_unmount", + "cnc_mcdp_update_entry", + "cnc_mcdp_wractpt", + "cnc_mdg_moniclear", + "cnc_mdg_monistat", + "cnc_mdg_msgsrch", + "cnc_mdg_rdalminfo", + "cnc_mdg_rdalminfoview", + "cnc_mdg_rdalminfoview2", + "cnc_mdg_rdalmnum", + "cnc_mdg_rdcontinfo", + "cnc_mdg_rddtmsg", + "cnc_mdg_rdflow", + "cnc_mdg_rdheatsimlt", + "cnc_mdg_rdlatchedalm", + "cnc_mdg_rdloadlvl", + "cnc_mdg_rdmsg", + "cnc_mdg_rdmsgnum", + "cnc_mdg_rdmsgordr", + "cnc_mdg_rdorderalmno", + "cnc_mdg_rdwvdata", + "cnc_mergeprog", + "cnc_modal", + "cnc_modal_bg", + "cnc_msim_end", + "cnc_msim_rdprgname", + "cnc_msim_rewind", + "cnc_msim_start", + "cnc_msr_delhis_all", + "cnc_msr_rdhis_allnum", + "cnc_msr_rdhis_inf", + "cnc_msr_rdhis_msudat", + "cnc_msr_rdhis_ncdat", + "cnc_msr_rdhis_ohisnum", + "cnc_msr_rdhis_ohisrec", + "cnc_msr_rdhis_pmc", + "cnc_msr_rdmon_msudat", + "cnc_msr_rdmon_msunum", + "cnc_msr_rdmon_pmcinf", + "cnc_msr_start_sample", + "cnc_msr_stop_sample", + "cnc_ncg_protcancel", + "cnc_newprog", + "cnc_nextdistance", + "cnc_nextdistance_bg", + "cnc_opdi", + "cnc_opencexefile", + "cnc_opentdicubeinfo", + "cnc_optmsgans", + "cnc_pdf_add", + "cnc_pdf_add_bgedt", + "cnc_pdf_cond", + "cnc_pdf_copy", + "cnc_pdf_cpmv_end", + "cnc_pdf_cpmv_poll", + "cnc_pdf_cpmv_restart", + "cnc_pdf_cpmv_start", + "cnc_pdf_del", + "cnc_pdf_delall", + "cnc_pdf_delchar", + "cnc_pdf_delline", + "cnc_pdf_dncread", + "cnc_pdf_dncset", + "cnc_pdf_dncset2", + "cnc_pdf_dssearch", + "cnc_pdf_mergeprog", + "cnc_pdf_move", + "cnc_pdf_rdactpt", + "cnc_pdf_rdactpt_bg", + "cnc_pdf_rdactpt_bgedt", + "cnc_pdf_rdcallstack", + "cnc_pdf_rdmain", + "cnc_pdf_rdmainpt", + "cnc_pdf_rdprgname", + "cnc_pdf_relsmain", + "cnc_pdf_rename", + "cnc_pdf_replacechar", + "cnc_pdf_replaceword_all", + "cnc_pdf_searchresult", + "cnc_pdf_searchresult2", + "cnc_pdf_searchresult_bgedt", + "cnc_pdf_searchword", + "cnc_pdf_searchword2", + "cnc_pdf_searchword_bgedt", + "cnc_pdf_slctmain", + "cnc_pdf_wractpt", + "cnc_pdf_wractpt_bg", + "cnc_pdf_wractpt_bgedt", + "cnc_pmclad_screen", + "cnc_pmmchkalm", + "cnc_pmmget", + "cnc_pmmgetend", + "cnc_pmmgetstart", + "cnc_pmminit", + "cnc_pmmprmpage", + "cnc_pmmsysdt", + "cnc_preset_prm", + "cnc_progdigit", + "cnc_prot_pswcan", + "cnc_prot_pswchg", + "cnc_prot_pswinit", + "cnc_prot_pswinp", + "cnc_prstwkcd", + "cnc_prstwkcd64", + "cnc_ptdownload", + "cnc_ptdwnend", + "cnc_ptdwnstart", + "cnc_ptlink", + "cnc_ptlink2", + "cnc_punch_data", + "cnc_punch_data_end", + "cnc_punch_data_start", + "cnc_punch_prog", + "cnc_punch_prog2", + "cnc_punch_prog3", + "cnc_punch_prog_bg", + "cnc_pwcm_clear_consump", + "cnc_pwoff_alarm", + "cnc_rd1length", + "cnc_rd1punchtl_ex", + "cnc_rd1radius", + "cnc_rd1tlifedat2", + "cnc_rd1tlifedata", + "cnc_rd2length", + "cnc_rd2punchtl_ex", + "cnc_rd2radius", + "cnc_rd2tlifedata", + "cnc_rd3dcdcnv", + "cnc_rd3dmovrlap", + "cnc_rd3dofschg", + "cnc_rd3dpulse", + "cnc_rd3dtofs", + "cnc_rd3dtooltip", + "cnc_rd5axmandt", + "cnc_rd5axovrlap", + "cnc_rd5dmacmov", + "cnc_rd5dpulse", + "cnc_rd5dtooltip", + "cnc_rd_grpaxisinfo", + "cnc_rd_grppos", + "cnc_rdabsaxis", + "cnc_rdactdofs", + "cnc_rdactfixofs", + "cnc_rdactpos_w", + "cnc_rdactpt", + "cnc_rdactpt_w", + "cnc_rdacttlzone", + "cnc_rdalmhisno", + "cnc_rdalmhisno3", + "cnc_rdalmhistry", + "cnc_rdalmhistry2", + "cnc_rdalmhistry3", + "cnc_rdalmhistry4", + "cnc_rdalmhistry5", + "cnc_rdalmhistry_w", + "cnc_rdalminfo", + "cnc_rdalminfo2", + "cnc_rdalminfo_bg", + "cnc_rdalmmsg", + "cnc_rdalmmsg2", + "cnc_rdalmmsg3", + "cnc_rdaxisdata", + "cnc_rdaxisdata64", + "cnc_rdaxisname", + "cnc_rdaxisstatus_bg", + "cnc_rdbaxis", + "cnc_rdblkcount", + "cnc_rdblkdist", + "cnc_rdbrstrinfo", + "cnc_rdbtofsinfo", + "cnc_rdbtofsr", + "cnc_rdcbmem", + "cnc_rdcbmem2", + "cnc_rdcbprm", + "cnc_rdcdrotate", + "cnc_rdcdslctprm", + "cnc_rdcdslctprmm", + "cnc_rdcenblinfo", + "cnc_rdcenter", + "cnc_rdcenter64", + "cnc_rdcexefile", + "cnc_rdcexesram", + "cnc_rdchopping", + "cnc_rdcncid", + "cnc_rdcncmem", + "cnc_rdcntover", + "cnc_rdcnttype", + "cnc_rdcomlogmsg", + "cnc_rdcommand", + "cnc_rdcommand64", + "cnc_rdcomopemsg", + "cnc_rdcomparam", + "cnc_rdcoordnum", + "cnc_rdcount", + "cnc_rdcstm_decfig", + "cnc_rdcstmdecfig", + "cnc_rdctname", + "cnc_rdctrldi", + "cnc_rdctrldo", + "cnc_rdcurrent", + "cnc_rddgdat", + "cnc_rddiag_ext", + "cnc_rddiaginfo", + "cnc_rddiaginfo2", + "cnc_rddiagnum", + "cnc_rddischarge", + "cnc_rddischrgalm", + "cnc_rddncdgndt", + "cnc_rddncdgndt2", + "cnc_rddncfname", + "cnc_rddofs", + "cnc_rddsdevinfo", + "cnc_rddsdir", + "cnc_rddsdncfile", + "cnc_rddsfile", + "cnc_rddynamic", + "cnc_rddynamic2", + "cnc_rddynamic3", + "cnc_rddynamic3m", + "cnc_rddynamico8", + "cnc_rdecamdatar", + "cnc_rdedgeactive", + "cnc_rdedgedata", + "cnc_rdedmcram", + "cnc_rdenblinfo", + "cnc_rdetherinfo", + "cnc_rdexchgtgrp", + "cnc_rdexecmcode", + "cnc_rdexecprgnum", + "cnc_rdexecprog", + "cnc_rdexecprog2", + "cnc_rdexecprog3", + "cnc_rdexecpt", + "cnc_rdexecptm", + "cnc_rdfbusmem", + "cnc_rdfctfssinfo", + "cnc_rdfctsitem", + "cnc_rdfctsmsgid", + "cnc_rdfctsstat", + "cnc_rdfile_sram", + "cnc_rdfixcycle", + "cnc_rdfixoffs", + "cnc_rdfixofs", + "cnc_rdflnetsram", + "cnc_rdfrominfo", + "cnc_rdfromservoid", + "cnc_rdfromservoid2", + "cnc_rdfromspindleid", + "cnc_rdfromspindleid2", + "cnc_rdfsraminfo", + "cnc_rdfssb_amp", + "cnc_rdfssb_axis", + "cnc_rdfssb_info", + "cnc_rdfssb_mainte", + "cnc_rdfssb_plsmod", + "cnc_rdgcode", + "cnc_rdgcodem", + "cnc_rdglvndiaginfo", + "cnc_rdglvnparainfo", + "cnc_rdglvnparam", + "cnc_rdgrphcanflg", + "cnc_rdgrphcmd", + "cnc_rdgrpid", + "cnc_rdgrpid2", + "cnc_rdgrpinfo", + "cnc_rdgrpinfo2", + "cnc_rdgrpinfo3", + "cnc_rdgrpinfo4", + "cnc_rdhdnxt", + "cnc_rdhipitchinfo", + "cnc_rdhipitchr", + "cnc_rdhissgnl", + "cnc_rdhissgnl2", + "cnc_rdhissgnl3", + "cnc_rdhndintrpt", + "cnc_rdholmes", + "cnc_rdholmes64", + "cnc_rdhpccset", + "cnc_rdhpcctuac", + "cnc_rdhpcctupr", + "cnc_rdhsparam", + "cnc_rdhsparamm", + "cnc_rdhsprminfo", + "cnc_rdifsb_almstate", + "cnc_rdifsb_as_amp_sp", + "cnc_rdifsb_as_amp_sv", + "cnc_rdifsb_as_hrv", + "cnc_rdifsb_as_plsmod", + "cnc_rdifsb_as_sv_axis", + "cnc_rdifsb_comstatdtl", + "cnc_rdifsb_fssbunt", + "cnc_rdifsb_info", + "cnc_rdifsb_mainte_sp", + "cnc_rdifsb_mainte_sv", + "cnc_rdifsb_slu_pm", + "cnc_rdifsb_slu_sp", + "cnc_rdifsb_slu_sv", + "cnc_rdifsb_slvunt", + "cnc_rdifsb_sysalm", + "cnc_rdifsb_warnhst_cnt", + "cnc_rdifsb_warnhst_msg", + "cnc_rdifsb_warning_cnt", + "cnc_rdifsb_warning_msg", + "cnc_rdintchk", + "cnc_rdinterference", + "cnc_rdintinfo", + "cnc_rdioassigned", + "cnc_rdipltp", + "cnc_rdjogdrun", + "cnc_rdjogmdi", + "cnc_rdlactnum", + "cnc_rdlagingmode", + "cnc_rdlagingtime", + "cnc_rdlagslt", + "cnc_rdlagst", + "cnc_rdlalmhistry", + "cnc_rdlcmddat", + "cnc_rdlcmmt", + "cnc_rdlcstmname", + "cnc_rdldsplc", + "cnc_rdldsplc2", + "cnc_rdledgprc", + "cnc_rdlenofs", + "cnc_rdlerrz", + "cnc_rdlfiberdata", + "cnc_rdlhsstate", + "cnc_rdlife", + "cnc_rdlnopmsg", + "cnc_rdlnzlmcn", + "cnc_rdloopgain", + "cnc_rdlpoweroffset", + "cnc_rdlppfbdt", + "cnc_rdlprcprc", + "cnc_rdlpscdpwrctl", + "cnc_rdlpwrcpst", + "cnc_rdlpwrctrl", + "cnc_rdlpwrdat", + "cnc_rdlpwrdty", + "cnc_rdlpwrslt", + "cnc_rdmacro", + "cnc_rdmacro2", + "cnc_rdmacro3", + "cnc_rdmacroinfo", + "cnc_rdmacronum", + "cnc_rdmacror", + "cnc_rdmacror2", + "cnc_rdmacror2_name", + "cnc_rdmacror3", + "cnc_rdmag_property", + "cnc_rdmagazine", + "cnc_rdmaxgrp", + "cnc_rdmaxtool", + "cnc_rdmcdfinfo", + "cnc_rdmcdprgcmnt", + "cnc_rdmdipntr", + "cnc_rdmdipntro8", + "cnc_rdmdiprgstat", + "cnc_rdmdiprog", + "cnc_rdmdlconfig", + "cnc_rdmdlconfig2", + "cnc_rdmenuswitch", + "cnc_rdmgrpdata", + "cnc_rdmirimage", + "cnc_rdmngtime", + "cnc_rdmovrlap", + "cnc_rdmovrlapm", + "cnc_rdmsptype", + "cnc_rdmtapdata", + "cnc_rdmultipieceno", + "cnc_rdmultitldt", + "cnc_rdngrp", + "cnc_rdnodeinfo", + "cnc_rdnodenum", + "cnc_rdnspdl", + "cnc_rdntool", + "cnc_rdnutatortofs_vect", + "cnc_rdofslength", + "cnc_rdomhisinfo", + "cnc_rdomhisno", + "cnc_rdomhistry", + "cnc_rdomhistry2", + "cnc_rdope_lvl", + "cnc_rdophisno", + "cnc_rdophisno3", + "cnc_rdophisno4", + "cnc_rdophistry", + "cnc_rdophistry2", + "cnc_rdophistry3", + "cnc_rdophistry4", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdopmsg2", + "cnc_rdopmsg3", + "cnc_rdopmsg3m", + "cnc_rdopmsgmps", + "cnc_rdopnlgnrl", + "cnc_rdopnlgnrl2", + "cnc_rdopnlgsname", + "cnc_rdopnlgsname2", + "cnc_rdopnlsgnl", + "cnc_rdoptfuncinfo", + "cnc_rdoverstore", + "cnc_rdoverstoremode", + "cnc_rdpalaxis", + "cnc_rdparainfo", + "cnc_rdparainfo2", + "cnc_rdparainfo3", + "cnc_rdparam", + "cnc_rdparam3", + "cnc_rdparam64", + "cnc_rdparam_ext", + "cnc_rdparanum", + "cnc_rdparar", + "cnc_rdparar3", + "cnc_rdpdf_alldir", + "cnc_rdpdf_curdir", + "cnc_rdpdf_drive", + "cnc_rdpdf_execline", + "cnc_rdpdf_inf", + "cnc_rdpdf_line", + "cnc_rdpdf_line2", + "cnc_rdpdf_line_bgedt", + "cnc_rdpdf_pglockstat", + "cnc_rdpdf_prginf", + "cnc_rdpdf_subdir", + "cnc_rdpdf_subdirn", + "cnc_rdpglockstat", + "cnc_rdpitchblkinfo", + "cnc_rdpitchinfo", + "cnc_rdpitchr", + "cnc_rdpitchr2", + "cnc_rdplutosmpl", + "cnc_rdpm_cncitem", + "cnc_rdpm_item", + "cnc_rdpm_mcnitem", + "cnc_rdpmacro", + "cnc_rdpmacroinfo", + "cnc_rdpmacroinfo2", + "cnc_rdpmacroinfo3", + "cnc_rdpmacror", + "cnc_rdpmacror2", + "cnc_rdpmacror_bg", + "cnc_rdpmcaxisinfo", + "cnc_rdpmmprmtp", + "cnc_rdposerrs", + "cnc_rdposerrs2", + "cnc_rdposerrz", + "cnc_rdposfig", + "cnc_rdposition", + "cnc_rdposofs", + "cnc_rdpossmpl", + "cnc_rdpot_property", + "cnc_rdprgdirtime", + "cnc_rdprgnum", + "cnc_rdprgnumo8", + "cnc_rdprgrmupdtcnt", + "cnc_rdproctime", + "cnc_rdproctime2", + "cnc_rdproctime3", + "cnc_rdprogdata", + "cnc_rdprogdir", + "cnc_rdprogdir2", + "cnc_rdprogdir2o8", + "cnc_rdprogdir3", + "cnc_rdprogdir4", + "cnc_rdprogdir4_w", + "cnc_rdprogdiro8", + "cnc_rdproginfo", + "cnc_rdprogline", + "cnc_rdprogline2", + "cnc_rdprotect", + "cnc_rdprotect2", + "cnc_rdprstrinfo", + "cnc_rdprstrinfom", + "cnc_rdprt_data", + "cnc_rdprt_lvl", + "cnc_rdpscdedge", + "cnc_rdpscdedge2", + "cnc_rdpscdpirc", + "cnc_rdpscdproc", + "cnc_rdpscdproc2", + "cnc_rdpscdslop", + "cnc_rdptaxfunctablestatus", + "cnc_rdptaxitablestatus", + "cnc_rdptcmdsize", + "cnc_rdptcnvalm", + "cnc_rdptcnvinfo", + "cnc_rdptcnvinfo2", + "cnc_rdptcomment", + "cnc_rdptexedistalm", + "cnc_rdptsptablestatus", + "cnc_rdptstoptime", + "cnc_rdpwofsthis", + "cnc_rdradofs", + "cnc_rdrcvmsg", + "cnc_rdrcvstat", + "cnc_rdrelaxis", + "cnc_rdrepeatval", + "cnc_rdrepeatval_ext", + "cnc_rdrmtdgn", + "cnc_rdrmtwavedt", + "cnc_rdrmtwaveprm", + "cnc_rdrotvolc", + "cnc_rdrstrmcode", + "cnc_rdrtmrvar", + "cnc_rdrtmrvars", + "cnc_rdsafetyzone", + "cnc_rdsavsigadr", + "cnc_rdsavsigdata", + "cnc_rdscaling", + "cnc_rdseqnum", + "cnc_rdseqnum_bg", + "cnc_rdservoid", + "cnc_rdservoid2", + "cnc_rdset", + "cnc_rdsetinfo", + "cnc_rdsetinfo2", + "cnc_rdsetnum", + "cnc_rdsetr", + "cnc_rdsetzone", + "cnc_rdsndmsg", + "cnc_rdsoc_curdat", + "cnc_rdsoc_tlatrr", + "cnc_rdsoc_tldat", + "cnc_rdsoc_wave", + "cnc_rdsoc_wave_end", + "cnc_rdsoc_wave_start", + "cnc_rdspcss", + "cnc_rdspdlalm", + "cnc_rdspdlname", + "cnc_rdspdlnamem", + "cnc_rdspdlspeed", + "cnc_rdspdlwaitname", + "cnc_rdspeed", + "cnc_rdspgear", + "cnc_rdspindleid", + "cnc_rdspindleid2", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsramaddr", + "cnc_rdsraminfo", + "cnc_rdsramnum", + "cnc_rdsrvspeed", + "cnc_rdsvfeedback", + "cnc_rdsvmeter", + "cnc_rdsynerrrg", + "cnc_rdsynerrsy", + "cnc_rdsysalm", + "cnc_rdsyshard", + "cnc_rdsyshard_str", + "cnc_rdsyssoft", + "cnc_rdsyssoft2", + "cnc_rdsyssoft3", + "cnc_rdsyssoft3_str", + "cnc_rdtdiblankfigno", + "cnc_rdtdicrntshapeinf", + "cnc_rdtdicubedata", + "cnc_rdtdicubeinfo", + "cnc_rdtdicurrentshape", + "cnc_rdtdicylinderdata", + "cnc_rdtdidispsetting", + "cnc_rdtdieffectshape", + "cnc_rdtdifignum", + "cnc_rdtdifiguredata", + "cnc_rdtdiinfo", + "cnc_rdtdiinitview", + "cnc_rdtdimoveaxis", + "cnc_rdtdinamesetting", + "cnc_rdtdiplanedata", + "cnc_rdtdiseltool", + "cnc_rdtdishapedata", + "cnc_rdtimer", + "cnc_rdtlctldata", + "cnc_rdtldata", + "cnc_rdtldspcstms", + "cnc_rdtlgeomsize", + "cnc_rdtlgeomsize_ext", + "cnc_rdtlgeomsize_ext2", + "cnc_rdtlgrp", + "cnc_rdtlinfo", + "cnc_rdtlmgr_check", + "cnc_rdtlmgr_name", + "cnc_rdtlmsinfo", + "cnc_rdtlname", + "cnc_rdtlnewstatus", + "cnc_rdtltool", + "cnc_rdtlusegrp", + "cnc_rdtofs", + "cnc_rdtofsenbl", + "cnc_rdtofsinfo", + "cnc_rdtofsinfo2", + "cnc_rdtofsr", + "cnc_rdtool", + "cnc_rdtool2", + "cnc_rdtool_cause", + "cnc_rdtool_f2", + "cnc_rdtool_inhis", + "cnc_rdtool_outhis", + "cnc_rdtoolchggrp", + "cnc_rdtooldata", + "cnc_rdtoolgeom_tlm", + "cnc_rdtoolgrp", + "cnc_rdtoolinfo", + "cnc_rdtoollife_count", + "cnc_rdtoollife_data", + "cnc_rdtoollife_tcodedata", + "cnc_rdtoollifed_count", + "cnc_rdtoollifed_data", + "cnc_rdtoolrng", + "cnc_rdtoolzone", + "cnc_rdtrqmonitor", + "cnc_rdunsolicmsg", + "cnc_rdunsolicmsg2", + "cnc_rdunsolicprm", + "cnc_rdunsolicprm2", + "cnc_rdusegrpid", + "cnc_rdusetlno", + "cnc_rduvactpt", + "cnc_rduvactpt2", + "cnc_rdvolc", + "cnc_rdvolccomp", + "cnc_rdwavedata", + "cnc_rdwavedata3", + "cnc_rdwaveprm", + "cnc_rdwaveprm2", + "cnc_rdwaveprm3", + "cnc_rdwkcdsfms", + "cnc_rdwkcdsfms2", + "cnc_rdwkcdsfms64", + "cnc_rdwkcdshft", + "cnc_rdwkcdshft2", + "cnc_rdwkcdshft64", + "cnc_rdwseterror", + "cnc_rdzofs", + "cnc_rdzofsinfo", + "cnc_rdzofsmes", + "cnc_rdzofsmes64", + "cnc_rdzofsr", + "cnc_rdzofsr64", + "cnc_read_data", + "cnc_read_prog", + "cnc_read_prog2", + "cnc_read_prog2_bg", + "cnc_read_prog3", + "cnc_refpoint", + "cnc_reg_toolstrage", + "cnc_regmagazine", + "cnc_regtool", + "cnc_regtool_f2", + "cnc_relative", + "cnc_relative2", + "cnc_relative2_exdgt", + "cnc_relative_bg", + "cnc_renameprog", + "cnc_req_alarm", + "cnc_reset", + "cnc_reset2", + "cnc_reset_prps", + "cnc_resetcntr", + "cnc_resetconnect", + "cnc_resetpdf_pglock", + "cnc_resetpglock", + "cnc_rewind", + "cnc_rmtwavestart", + "cnc_rmtwavestat", + "cnc_rmtwavestop", + "cnc_robo_clrsignals", + "cnc_robo_rdalmmsg", + "cnc_robo_rdcomsetting", + "cnc_robo_rdgrouplist", + "cnc_robo_rdponprop", + "cnc_robo_rdselectedsignals", + "cnc_robo_rdsignals", + "cnc_robo_rdsignals2", + "cnc_robo_selectgroup", + "cnc_robo_wralmmsg", + "cnc_robo_wrcomsetting", + "cnc_robo_wrgroup", + "cnc_robo_wrselectedsignals", + "cnc_robo_wrsignalname", + "cnc_robo_wrsignals2", + "cnc_rstrdstatus", + "cnc_rstrseqsrch", + "cnc_rstrseqsrch2", + "cnc_rstrt_createpnt", + "cnc_rstrt_getdncprg", + "cnc_rstrt_getpntcnt", + "cnc_rstrt_lpslctblk", + "cnc_rstrt_rdaddinfo", + "cnc_rstrt_rdlpmppnt", + "cnc_rstrt_rdmodal", + "cnc_rstrt_rdpnt", + "cnc_rstrt_rdpnt2", + "cnc_rstrt_rdpntlist", + "cnc_rstrt_rdpntlist2", + "cnc_rstrt_search", + "cnc_rstrt_selectpnt", + "cnc_rstrt_setsuppress", + "cnc_rstrt_wrpnt", + "cnc_rstrt_wrpnt2", + "cnc_save_maint", + "cnc_saveprog_end", + "cnc_saveprog_start", + "cnc_sdbetainfo", + "cnc_sdcancelsmpl", + "cnc_sdclrchnl", + "cnc_sdclrocp", + "cnc_sdendocp", + "cnc_sdendsmpl", + "cnc_sdendsmpl2", + "cnc_sdfmnghwnd", + "cnc_sdfstatchg", + "cnc_sdread1shot", + "cnc_sdreadsmpl", + "cnc_sdreadsmpl2", + "cnc_sdsetchnl", + "cnc_sdsetchnl2", + "cnc_sdstartocp", + "cnc_sdstartsmpl", + "cnc_sdstartsmpl2", + "cnc_sdstartsmplb", + "cnc_sdtcancelsmpl", + "cnc_sdtclrchnl", + "cnc_sdtclrocp", + "cnc_sdtendocp", + "cnc_sdtendsmpl", + "cnc_sdtendsmpl2", + "cnc_sdtread1shot", + "cnc_sdtreadsmpl", + "cnc_sdtreadsmpl2", + "cnc_sdtsetchnl", + "cnc_sdtsetchnl2", + "cnc_sdtstartocp", + "cnc_sdtstartsmpl", + "cnc_sdtstartsmpl2", + "cnc_search", + "cnc_search2", + "cnc_searcho8", + "cnc_searchresult", + "cnc_searchresult2", + "cnc_searchword", + "cnc_searchword2", + "cnc_sendkey", + "cnc_sendmessage", + "cnc_seqrdtdicubeinfo", + "cnc_seqsrch", + "cnc_seqsrch2", + "cnc_set_cutcnd_exval", + "cnc_set_prps", + "cnc_set_smth_exval", + "cnc_setactptopt", + "cnc_setallexauxr", + "cnc_setcurscrn", + "cnc_setdefnode", + "cnc_setfin", + "cnc_setfrp", + "cnc_setlibopt", + "cnc_setmactype", + "cnc_setpath", + "cnc_setpdf_pglock", + "cnc_setpdf_pglockexec", + "cnc_setpglock", + "cnc_setpmactype", + "cnc_setrstraxis", + "cnc_setsumdt", + "cnc_settdiobjectshape", + "cnc_settditoolshape", + "cnc_setthrdngpos", + "cnc_settimeout", + "cnc_settimer", + "cnc_settolnum_qset", + "cnc_settpnlcalib", + "cnc_setvrtclpos", + "cnc_setzofsnum_qset", + "cnc_sfbcancelsmpl", + "cnc_sfbclrchnl", + "cnc_sfbendsmpl", + "cnc_sfbreadsmpl", + "cnc_sfbsetchnl", + "cnc_sfbstartsmpl", + "cnc_simulation", + "cnc_skip", + "cnc_slctptdata", + "cnc_slctpttype", + "cnc_slctscrn", + "cnc_slide", + "cnc_soc_wave_setchnl", + "cnc_srambackup", + "cnc_srambkend", + "cnc_srambkstart", + "cnc_sramget", + "cnc_sramget2", + "cnc_sramgetend", + "cnc_sramgetend2", + "cnc_sramgetstart", + "cnc_sramgetstart2", + "cnc_sramput", + "cnc_sramput2", + "cnc_sramputend", + "cnc_sramputend2", + "cnc_sramputstart", + "cnc_sramputstart2", + "cnc_sramstat", + "cnc_sramstatus", + "cnc_srcsfreechnl", + "cnc_srcsrddrvcp", + "cnc_srcsrdexstat", + "cnc_srcsrdidinfo", + "cnc_srcsrdlayout", + "cnc_srcsrdopdata", + "cnc_srcsrsvchnl", + "cnc_srcsstartrd", + "cnc_srcsstartwrt", + "cnc_srcsstopexec", + "cnc_srcswridinfo", + "cnc_srcswropdata", + "cnc_srttl_getdata", + "cnc_srttl_getnum", + "cnc_srvdelay", + "cnc_start", + "cnc_start_async_data_punch", + "cnc_start_async_data_read", + "cnc_start_async_pdf_punch", + "cnc_start_async_pdf_read", + "cnc_start_async_punch_prog3", + "cnc_start_async_punch_prog3_bg", + "cnc_start_async_read_prog3", + "cnc_start_async_read_prog3_bg", + "cnc_start_async_wrparam", + "cnc_start_grppos", + "cnc_startaux", + "cnc_startdrawpos", + "cnc_startdyngrph", + "cnc_startgetdgdat", + "cnc_startnccmd", + "cnc_startnccmd2", + "cnc_startomhis", + "cnc_startophis", + "cnc_startptcnv", + "cnc_startrmtdgn", + "cnc_statinfo", + "cnc_statinfo2", + "cnc_statinfo_bg", + "cnc_status_prps", + "cnc_stop_async_read_punch", + "cnc_stop_grppos", + "cnc_stopdrawpos", + "cnc_stopdyngrph", + "cnc_stopgetdgdat", + "cnc_stopnccmd", + "cnc_stopomhis", + "cnc_stopophis", + "cnc_stoprmtdgn", + "cnc_stplutosmpl", + "cnc_stpossmpl", + "cnc_svdtendrd", + "cnc_svdtendrd2", + "cnc_svdtendwr", + "cnc_svdtendwr2", + "cnc_svdtrddata", + "cnc_svdtrddata2", + "cnc_svdtstartrd", + "cnc_svdtstartrd2", + "cnc_svdtstartwr", + "cnc_svdtstartwr2", + "cnc_svdtstopexec", + "cnc_svdtwrdata", + "cnc_svdtwrdata2", + "cnc_sysconfig", + "cnc_sysinfo", + "cnc_sysinfo_ex", + "cnc_t1info", + "cnc_t2info", + "cnc_tofs_rnge", + "cnc_tool_in", + "cnc_tool_in2", + "cnc_tool_in3", + "cnc_tool_move", + "cnc_tool_out", + "cnc_tool_srh_free_min_num", + "cnc_tool_temp_in", + "cnc_tool_temp_out", + "cnc_toolnum", + "cnc_toolsrch", + "cnc_twp_rdfcoord", + "cnc_twp_rdfmt_mtrx", + "cnc_unsolicstart", + "cnc_unsolicstop", + "cnc_updexauxr", + "cnc_upend", + "cnc_upend3", + "cnc_upend4", + "cnc_upload", + "cnc_upload3", + "cnc_upload4", + "cnc_upstart", + "cnc_upstart3", + "cnc_upstart3_f", + "cnc_upstart4", + "cnc_upstarto8", + "cnc_validate_opt", + "cnc_validate_prm", + "cnc_verify", + "cnc_verify4", + "cnc_verify_prog", + "cnc_verify_prog_bg", + "cnc_vrfend", + "cnc_vrfend4", + "cnc_vrfstart", + "cnc_vrfstart4", + "cnc_wavestart", + "cnc_wavestat", + "cnc_wavestop", + "cnc_wksft_rnge", + "cnc_wksft_rnge64", + "cnc_workzero", + "cnc_wr1tlifedat2", + "cnc_wr1tlifedata", + "cnc_wr2tlifedata", + "cnc_wractpt", + "cnc_wractpt_w", + "cnc_wrbtofsr", + "cnc_wrcbmem", + "cnc_wrcbmem2", + "cnc_wrcbprm", + "cnc_wrcexefile", + "cnc_wrcexesram", + "cnc_wrcncmem", + "cnc_wrcommstatus", + "cnc_wrcomparam", + "cnc_wrcountr", + "cnc_wrdgdatptr", + "cnc_wrdncfname", + "cnc_wrdofs", + "cnc_wrdsdncfile", + "cnc_wrecamdatar", + "cnc_wredgedata", + "cnc_wredgedata2", + "cnc_wredmcram", + "cnc_wrfbusmem", + "cnc_wrfctfssadjust", + "cnc_wrfctfssinfo", + "cnc_wrfctsitem", + "cnc_wrfile_sram", + "cnc_wrfixoffs", + "cnc_wrfixofs", + "cnc_wrflnetsram", + "cnc_wrfromservoid", + "cnc_wrfromservoid2", + "cnc_wrfromspindleid", + "cnc_wrfromspindleid2", + "cnc_wrfssb_axis", + "cnc_wrfssb_axis_num", + "cnc_wrglvnparam", + "cnc_wrgrphcmdptr", + "cnc_wrgrpinfo", + "cnc_wrgrpinfo2", + "cnc_wrgrpinfo3", + "cnc_wrhipitchr", + "cnc_wrhissgnl", + "cnc_wrhissgnl2", + "cnc_wrhissgnl3", + "cnc_wrhpccset", + "cnc_wrhpcctuac", + "cnc_wrhpcctupr", + "cnc_wrifsb_as_axis_num", + "cnc_wrifsb_as_hrv", + "cnc_wrifsb_as_spdl_num", + "cnc_wrifsb_as_sv_axis", + "cnc_wrintchk", + "cnc_wrjogmdi", + "cnc_wrjogmdiclr", + "cnc_wrkeyhistry", + "cnc_wrlagingmode", + "cnc_wrlagslt", + "cnc_wrlagst", + "cnc_wrldsplc", + "cnc_wrldsplc2", + "cnc_wrledgprc", + "cnc_wrlppfbdt", + "cnc_wrlprcprc", + "cnc_wrlpscdpwrctl", + "cnc_wrlpwrcpst", + "cnc_wrlpwrctrl", + "cnc_wrlpwrdty", + "cnc_wrlpwrslt", + "cnc_wrlswork", + "cnc_wrmacro", + "cnc_wrmacro3", + "cnc_wrmacror", + "cnc_wrmacror2", + "cnc_wrmag_property", + "cnc_wrmagazine", + "cnc_wrmdipntr", + "cnc_wrmdiprog", + "cnc_wrmenuswitch", + "cnc_wrmgrpdata", + "cnc_wrmngtime", + "cnc_wrmsptype", + "cnc_wrmtapdata", + "cnc_wrmultitldt", + "cnc_wropnlgnrl", + "cnc_wropnlgnrl2", + "cnc_wropnlgsname", + "cnc_wropnlgsname2", + "cnc_wropnlsgnl", + "cnc_wroverstore", + "cnc_wrparam", + "cnc_wrparam3", + "cnc_wrparam64", + "cnc_wrparas", + "cnc_wrpdf_attr", + "cnc_wrpdf_char", + "cnc_wrpdf_curdir", + "cnc_wrpdf_line", + "cnc_wrpitchr", + "cnc_wrpitchr2", + "cnc_wrpm_item", + "cnc_wrpm_mcnitem", + "cnc_wrpmacro", + "cnc_wrpmacror", + "cnc_wrpmacror2", + "cnc_wrpmacror_bg", + "cnc_wrpmmprm", + "cnc_wrpmmprmtp", + "cnc_wrpot_property", + "cnc_wrprogline", + "cnc_wrprt_lvl", + "cnc_wrpscdedge", + "cnc_wrpscdedge2", + "cnc_wrpscdpirc", + "cnc_wrpscdproc", + "cnc_wrpscdproc2", + "cnc_wrpscdslop", + "cnc_wrptstoptime", + "cnc_wrpunchtl_ex", + "cnc_wrrelpos", + "cnc_wrrelpos64", + "cnc_wrrmtdgn", + "cnc_wrrmtwaveprm", + "cnc_wrrotvolc", + "cnc_wrrotvolc2", + "cnc_wrrtmiowrenbl", + "cnc_wrrtmiowrenblrng", + "cnc_wrrtmrvar", + "cnc_wrrtmrvars", + "cnc_wrsafetyzone", + "cnc_wrsavsigadr", + "cnc_wrset", + "cnc_wrsets", + "cnc_wrsetzone", + "cnc_wrsignal_f", + "cnc_wrsoc_tldat", + "cnc_wrtdicubedata", + "cnc_wrtdicylinderdata", + "cnc_wrtdidispsetting", + "cnc_wrtdieffectshape", + "cnc_wrtdifignum", + "cnc_wrtdifiguredata", + "cnc_wrtdiinitview", + "cnc_wrtdimoveaxis", + "cnc_wrtdinamesetting", + "cnc_wrtdiplanedata", + "cnc_wrtdishapedata", + "cnc_wrtimer", + "cnc_wrtlctldata", + "cnc_wrtldata", + "cnc_wrtlgeomsize", + "cnc_wrtlgeomsize_ext", + "cnc_wrtlgeomsize_ext2", + "cnc_wrtofs", + "cnc_wrtofsdrctinp", + "cnc_wrtofsdrctinp64", + "cnc_wrtofsms", + "cnc_wrtofsr", + "cnc_wrtool", + "cnc_wrtool2", + "cnc_wrtool_f2", + "cnc_wrtooldata", + "cnc_wrtoolgeom_tlm", + "cnc_wrtoolzone", + "cnc_wrtrqlimit", + "cnc_wrunsolicprm", + "cnc_wrunsolicprm2", + "cnc_wrvolc", + "cnc_wrwaveprm", + "cnc_wrwaveprm2", + "cnc_wrwaveprm3", + "cnc_wrwkcdsfms", + "cnc_wrwkcdsfms2", + "cnc_wrwkcdsfms3", + "cnc_wrwkcdsfms64", + "cnc_wrwkcdshft", + "cnc_wrwkcdshft2", + "cnc_wrwkcdshft64", + "cnc_wrwseterror", + "cnc_wrzofs", + "cnc_wrzofs64", + "cnc_wrzofsr", + "cnc_zofs_rnge", + "cnc_zofs_rnge64", + "ds_cancel", + "ds_checkhdd", + "ds_chghdddir", + "ds_copyhddfile", + "ds_delhdddir", + "ds_delhddfile", + "ds_delhostfile", + "ds_download", + "ds_dwnend", + "ds_dwnstart", + "ds_formathdd", + "ds_gethostfile", + "ds_ldelhddfile", + "ds_lgethostfile", + "ds_lputhddfile", + "ds_makehdddir", + "ds_mgethostfile", + "ds_mputhddfile", + "ds_puthddfile", + "ds_rddnchddfile", + "ds_rddnchostfile", + "ds_rdfile", + "ds_rdhdddir", + "ds_rdhddinfo", + "ds_rdhostdir", + "ds_rdhostdir2", + "ds_rdhostinfo", + "ds_rdhostno", + "ds_rdm198hdddir", + "ds_rdm198host", + "ds_rdmntinfo", + "ds_rdmode", + "ds_rdncfile", + "ds_rdncfile2", + "ds_rdresult", + "ds_renhddfile", + "ds_searchresult", + "ds_searchword", + "ds_wrdnchddfile", + "ds_wrdnchostfile", + "ds_wrfile", + "ds_wrhostno", + "ds_wrm198hdddir", + "ds_wrm198host", + "ds_wrmode", + "ds_wrncfile", + "dsa_rdbyte", + "dsa_rddword", + "dsa_rdword", + "dsa_wrbyte", + "dsa_wrdword", + "dsa_wrword", + "embetb_rdparam_w", + "embetb_wrparam_w", + "etb_rderrmsg", + "etb_rdparam", + "etb_wrparam", + "eth_abortdnc", + "eth_abortdownload", + "eth_abortupload", + "eth_changelockstat1", + "eth_changelockstat2", + "eth_clrlog", + "eth_clrlsistate", + "eth_dncsegment", + "eth_downloadsegment", + "eth_dschkdsk", + "eth_dsformat", + "eth_embrestart", + "eth_exit", + "eth_fs_abortdirectory", + "eth_fs_abortdownload", + "eth_fs_abortupload", + "eth_fs_directorysegment", + "eth_fs_downloadsegment", + "eth_fs_exit", + "eth_fs_getrequest", + "eth_fs_init", + "eth_fs_putresponse", + "eth_fs_terminatedirectory", + "eth_fs_terminatedownload", + "eth_fs_uploadsegment", + "eth_getlockstat1", + "eth_getlockstat2", + "eth_init", + "eth_init2", + "eth_initiatednc", + "eth_initiatedownload", + "eth_initiateupload", + "eth_ping", + "eth_ping_cancel", + "eth_ping_result", + "eth_rddsformat", + "eth_rddsm198dir", + "eth_rddsm198host", + "eth_rddsmode", + "eth_rddsstate", + "eth_rdembdev", + "eth_rdembm198host", + "eth_rdhost", + "eth_rdlog", + "eth_rdlsistate", + "eth_rdparam", + "eth_rdrmdinquiry", + "eth_rdtaskstate", + "eth_rdtype", + "eth_remotecontrol", + "eth_terminatednc", + "eth_terminatedownload", + "eth_uploadsegment", + "eth_vga_datetime", + "eth_vga_exit", + "eth_vga_getkillstate", + "eth_vga_getselectsignal", + "eth_vga_getsignal", + "eth_vga_idle", + "eth_vga_init", + "eth_vga_read", + "eth_vga_sendkey", + "eth_vga_sendmouse", + "eth_vga_setkillstate", + "eth_vga_start", + "eth_vga_start2", + "eth_vga_stopcommand", + "eth_vga_stopreq", + "eth_vga_stoprsp", + "eth_vga_sysinfo", + "eth_vga_updatecounter", + "eth_vga_write", + "eth_workinit", + "eth_wrdsm198dir", + "eth_wrdsm198host", + "eth_wrdsmode", + "eth_wrembdev", + "eth_wrembm198host", + "eth_wrhost", + "eth_wrparam", + "eth_wrrmdinquiry", + "flnt_clrlog", + "flnt_clrlog2", + "flnt_clrmsg", + "flnt_clrmsg2", + "flnt_clrnetwork", + "flnt_clrnetwork2", + "flnt_rddeviceinfo", + "flnt_rddeviceinfo2", + "flnt_rdentry", + "flnt_rdentry2", + "flnt_rdlog", + "flnt_rdlog2", + "flnt_rdmsg", + "flnt_rdmsg2", + "flnt_rdnetwork", + "flnt_rdnetwork2", + "flnt_rdnodeinfo", + "flnt_rdnodeinfo2", + "flnt_rdparam", + "flnt_rdparam2", + "flnt_rdsferrnode", + "flnt_rdsfstatus", + "flnt_wrparam", + "flnt_wrparam2", + "pbm_chg_mode", + "pbm_exe_subfunc", + "pbm_ini_prm", + "pbm_rd_allslvtbl", + "pbm_rd_cominfo", + "pbm_rd_errcode", + "pbm_rd_nodeinfo", + "pbm_rd_nodetable", + "pbm_rd_param", + "pbm_rd_slot", + "pbm_rd_slotinfo", + "pbm_rd_subprm", + "pbm_wr_param", + "pbs_ini_prm", + "pbs_rd_cominfo", + "pbs_rd_cominfo2", + "pbs_rd_param", + "pbs_rd_param2", + "pbs_wr_param", + "pbs_wr_param2", + "pmc_abort_all_transfer", + "pmc_abort_end_process", + "pmc_abort_trace_function", + "pmc_abort_transfer", + "pmc_can_apply_override", + "pmc_change_operation", + "pmc_change_operation_with_passwd", + "pmc_check_io_assignment_empty", + "pmc_check_permission", + "pmc_check_permission_with_passwd", + "pmc_control_snp_driver", + "pmc_convert_address_pmc_to_vio", + "pmc_convert_from_string_to_address", + "pmc_convert_pmc_address_from_sync", + "pmc_convert_pmc_address_to_string", + "pmc_convert_pmc_address_to_sync", + "pmc_convert_vio_address_from_sync", + "pmc_convert_vio_address_to_string", + "pmc_convert_vio_address_to_sync", + "pmc_convret_address_vio_to_pmc", + "pmc_crdmsg", + "pmc_cwrmsg", + "pmc_delete_all_io_assignment_data", + "pmc_delete_all_message_data", + "pmc_delete_data_table_group_data", + "pmc_delete_io_assignment_data", + "pmc_delete_message_entry", + "pmc_delete_seq_program_object", + "pmc_delete_symcmt_entry", + "pmc_exchange_permission", + "pmc_fdcas_compare_file_by_name", + "pmc_fdcas_compare_file_by_number", + "pmc_fdcas_delete_all_files", + "pmc_fdcas_delete_file_by_name", + "pmc_fdcas_delete_file_by_number", + "pmc_fdcas_get_comm_parameter", + "pmc_fdcas_init_comm_parameter", + "pmc_fdcas_read_directory", + "pmc_fdcas_read_file_by_name", + "pmc_fdcas_read_file_by_number", + "pmc_fdcas_read_file_count", + "pmc_fdcas_read_file_status", + "pmc_fdcas_set_comm_parameter", + "pmc_fdcas_write_pmc_parameter", + "pmc_fdcas_write_seq_program", + "pmc_flashrom_compare_seq_program", + "pmc_flashrom_read_seq_program", + "pmc_flashrom_write_seq_program", + "pmc_force_release_object", + "pmc_force_status_bit", + "pmc_force_status_byte", + "pmc_get_address_entry", + "pmc_get_alarm_status", + "pmc_get_all_associated_symbols", + "pmc_get_available_io_connection", + "pmc_get_comment_set_information", + "pmc_get_current_comment", + "pmc_get_current_divided_ladder", + "pmc_get_current_pmc", + "pmc_get_current_pmc_unit", + "pmc_get_data_table_group_count", + "pmc_get_data_table_max_group_count", + "pmc_get_default_filename", + "pmc_get_divided_ladders", + "pmc_get_io_assignment_address", + "pmc_get_io_device_id", + "pmc_get_language", + "pmc_get_maximum_trace_frames", + "pmc_get_message_data_size", + "pmc_get_message_entry_address", + "pmc_get_message_entry_count", + "pmc_get_message_maximum_size", + "pmc_get_message_remain_memory", + "pmc_get_nonvolatile_memory_status", + "pmc_get_number_of_address_entries", + "pmc_get_number_of_comment_set", + "pmc_get_number_of_comments", + "pmc_get_number_of_ladder", + "pmc_get_number_of_pmc", + "pmc_get_object_version", + "pmc_get_override_status", + "pmc_get_permission_status", + "pmc_get_pmc_unit_types", + "pmc_get_programable_io_channels", + "pmc_get_result_trace_parameter", + "pmc_get_snp_communication_status", + "pmc_get_snp_driver_status", + "pmc_get_splist_status", + "pmc_get_subprogram_list", + "pmc_get_symcmt_bit", + "pmc_get_symcmt_byte", + "pmc_get_symcmt_data_size", + "pmc_get_symcmt_entry_count", + "pmc_get_symcmt_maximum_size", + "pmc_get_symcmt_remain_memory", + "pmc_get_system_language", + "pmc_get_timer_type", + "pmc_get_trace_result", + "pmc_get_trace_specification", + "pmc_get_trace_status", + "pmc_get_transfer_status", + "pmc_get_unit_type", + "pmc_get_xref_address_result", + "pmc_get_xref_instruction_result", + "pmc_get_xref_status", + "pmc_getdtailerr", + "pmc_initialize_data_table_group", + "pmc_initialize_nonvolatile_memory", + "pmc_insert_seq_program_space", + "pmc_is_bit_accessible_pmc_address", + "pmc_is_effective_pmc_address", + "pmc_is_seq_program_available", + "pmc_is_trace_function_active", + "pmc_is_visible_pmc_address", + "pmc_is_writable_pmc_address", + "pmc_kpmsiz", + "pmc_logical_and_pmc_address_uint8", + "pmc_logical_and_vio_address_uint8", + "pmc_logical_or_pmc_address_uint8", + "pmc_logical_or_vio_address_uint8", + "pmc_logical_xor_pmc_address_uint8", + "pmc_logical_xor_vio_address_uint8", + "pmc_mcard_compare_file", + "pmc_mcard_delete_file", + "pmc_mcard_format", + "pmc_mcard_read_directory", + "pmc_mcard_read_file", + "pmc_mcard_read_file_count", + "pmc_mcard_read_file_status", + "pmc_mcard_write_pmc_parameter", + "pmc_mcard_write_seq_program", + "pmc_mcard_write_trace_result", + "pmc_parse_string_into_pmc_address", + "pmc_prfrdallcadr", + "pmc_prfrdbusprm", + "pmc_prfrdconfig", + "pmc_prfrddido", + "pmc_prfrdindiadr", + "pmc_prfrdinfo", + "pmc_prfrdopmode", + "pmc_prfrdslvaddr", + "pmc_prfrdslvid", + "pmc_prfrdslvprm", + "pmc_prfrdslvprm2", + "pmc_prfrdslvstat", + "pmc_prfwrallcadr", + "pmc_prfwrbusprm", + "pmc_prfwrdido", + "pmc_prfwrindiadr", + "pmc_prfwropmode", + "pmc_prfwrslvaddr", + "pmc_prfwrslvid", + "pmc_prfwrslvprm", + "pmc_prfwrslvprm2", + "pmc_rdalmmsg", + "pmc_rdcntl_exrelay_grp", + "pmc_rdcntldata", + "pmc_rdcntlexrelay", + "pmc_rdcntlgrp", + "pmc_rdioconfigtitle", + "pmc_rdkpm", + "pmc_rdkpm2", + "pmc_rdmessagetitle", + "pmc_rdmsg", + "pmc_rdpmcaddr", + "pmc_rdpmcinfo", + "pmc_rdpmcmem", + "pmc_rdpmcparam", + "pmc_rdpmcrng", + "pmc_rdpmcrng_ext", + "pmc_rdpmcsemem", + "pmc_rdpmctitle", + "pmc_rdpmctitle2", + "pmc_rdprmend", + "pmc_rdprmstart", + "pmc_rdwrpmcrng", + "pmc_rdwrpmcrng2", + "pmc_read_cnc_dido", + "pmc_read_data_table_group_data", + "pmc_read_end_process_status", + "pmc_read_instruction_monitor_status", + "pmc_read_io_assignment_data", + "pmc_read_machine_signal", + "pmc_read_message_entry_data", + "pmc_read_nonvolatile_memory", + "pmc_read_performance_information", + "pmc_read_pmc_address_bit", + "pmc_read_pmc_address_uint16", + "pmc_read_pmc_address_uint32", + "pmc_read_pmc_address_uint8", + "pmc_read_pmc_software_version", + "pmc_read_raw_memory", + "pmc_read_scan_time", + "pmc_read_seq_object_size", + "pmc_read_seq_program_and_memory_type", + "pmc_read_seq_program_config", + "pmc_read_seq_program_object", + "pmc_read_seq_program_size", + "pmc_read_seq_program_status", + "pmc_read_seq_program_type", + "pmc_read_setting", + "pmc_read_snp_parameter", + "pmc_read_symcmt_entry", + "pmc_read_system_parameter", + "pmc_read_title_data", + "pmc_read_trace_parameter", + "pmc_read_vio_address_bit", + "pmc_read_vio_address_uint16", + "pmc_read_vio_address_uint32", + "pmc_read_vio_address_uint8", + "pmc_request_permission", + "pmc_request_permission_with_passwd", + "pmc_reserve_nonvolatile_memory", + "pmc_reset_all_override", + "pmc_reset_override", + "pmc_restore_seq_program_edit", + "pmc_resume_end_process", + "pmc_resume_transfer", + "pmc_retrieve_permission", + "pmc_return_permission", + "pmc_rs232c_compare", + "pmc_rs232c_get_comm_parameter", + "pmc_rs232c_init_comm_parameter", + "pmc_rs232c_read", + "pmc_rs232c_set_comm_parameter", + "pmc_rs232c_write_pmc_parameter", + "pmc_rs232c_write_seq_program", + "pmc_run_seq_program", + "pmc_search_ladder_address", + "pmc_search_ladder_function", + "pmc_search_message_entry_position", + "pmc_search_message_string", + "pmc_search_symcmt_entry_by_symbol", + "pmc_search_symcmt_entry_position", + "pmc_search_symcmt_string", + "pmc_search_unit_type", + "pmc_select_divided_ladder", + "pmc_select_pmc", + "pmc_select_pmc_unit", + "pmc_set_current_comment", + "pmc_set_data_table_group_count", + "pmc_set_language", + "pmc_set_override", + "pmc_set_timer_type", + "pmc_start_end_process", + "pmc_start_list_subprogram", + "pmc_start_trace_function", + "pmc_start_xref_address", + "pmc_start_xref_instruction", + "pmc_stop_seq_program", + "pmc_wrcntl_exrelay_grp", + "pmc_wrcntldata", + "pmc_wrcntlexrelay", + "pmc_wrcntlgrp", + "pmc_wriolinkdat", + "pmc_write_cnc_dido", + "pmc_write_data_table_group_data", + "pmc_write_io_assignment_data", + "pmc_write_message_entry", + "pmc_write_nonvolatile_memory", + "pmc_write_pmc_address_bit", + "pmc_write_pmc_address_uint16", + "pmc_write_pmc_address_uint32", + "pmc_write_pmc_address_uint8", + "pmc_write_raw_io_assignment_data", + "pmc_write_raw_memory", + "pmc_write_seq_program_object", + "pmc_write_setting", + "pmc_write_snp_parameter", + "pmc_write_symcmt_entry", + "pmc_write_system_parameter", + "pmc_write_title_data", + "pmc_write_trace_parameter", + "pmc_write_vio_address_bit", + "pmc_write_vio_address_uint16", + "pmc_write_vio_address_uint32", + "pmc_write_vio_address_uint8", + "pmc_wrkpm", + "pmc_wrkpm2", + "pmc_wrmsg", + "pmc_wrpmcmem", + "pmc_wrpmcparam", + "pmc_wrpmcrng", + "pmc_wrpmcrng2", + "pmc_wrpmcsemem", + "pmc_wrprmend", + "pmc_wrprmstart" + ], + "notes": [ + "Exports extracted directly from the 64-bit DLL PE export directory.", + "Wrapper-supported methods are intersected with upstream fwlib.cs extern declarations.", + "Axis and path hints are filename-family heuristics, not protocol-level proofs." + ] +} \ No newline at end of file diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlib0DN64.json b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlib0DN64.json new file mode 100644 index 0000000..a40f825 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlib0DN64.json @@ -0,0 +1,1936 @@ +{ + "profile_name": "fwlib0DN64", + "dll_name": "fwlib0DN64.dll", + "series_hint": "0-dn-family", + "max_axis_hint": 24, + "max_path_hint": 1, + "export_count": 1422, + "connection_methods": [ + "cnc_allclibhndl", + "cnc_allclibhndl2" + ], + "mock_methods": [ + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_diagnoss", + "cnc_exeprgname2", + "cnc_freelibhndl", + "cnc_getpath", + "cnc_rdalmmsg2", + "cnc_rdaxisname", + "cnc_rdblkcount", + "cnc_rddiaginfo", + "cnc_rddiagnum", + "cnc_rddynamic2", + "cnc_rdexecprog", + "cnc_rdmacro", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdparam", + "cnc_rdprgnum", + "cnc_rdprogdir3", + "cnc_rdproginfo", + "cnc_rdseqnum", + "cnc_rdspdlname", + "cnc_rdspgear", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsvmeter", + "cnc_rdtimer", + "cnc_setpath", + "cnc_statinfo", + "cnc_sysinfo", + "cnc_wrmacro", + "cnc_wrparam", + "pmc_rdpmcrng", + "pmc_wrpmcrng" + ], + "wrapper_supported_count": 452, + "wrapper_supported_methods": [ + "cnc_absolute", + "cnc_absolute2", + "cnc_accdecdly", + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_adcnv", + "cnc_alarm", + "cnc_alarm2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_async_busy_state", + "cnc_buff", + "cnc_canmovrlap", + "cnc_cdownload", + "cnc_cexesramsize", + "cnc_clearomhis", + "cnc_clearophis", + "cnc_clr3dplsmov", + "cnc_clr5axpls", + "cnc_clralm", + "cnc_clrcntinfo", + "cnc_condense", + "cnc_copyprog", + "cnc_cupload", + "cnc_cverify", + "cnc_delall", + "cnc_delete", + "cnc_delmagazine", + "cnc_delprogline", + "cnc_deltlifedt", + "cnc_deltlifegrp", + "cnc_deltool", + "cnc_diagnosr", + "cnc_diagnoss", + "cnc_distance", + "cnc_dnc2", + "cnc_dncend2", + "cnc_dncstart2", + "cnc_download", + "cnc_download3", + "cnc_download4", + "cnc_dwnend", + "cnc_dwnend3", + "cnc_dwnend4", + "cnc_dwnstart", + "cnc_dwnstart3", + "cnc_dwnstart4", + "cnc_end_async_wrparam", + "cnc_exaxisname", + "cnc_exeprgname", + "cnc_exeprgname2", + "cnc_freelibhndl", + "cnc_fromget", + "cnc_fromgetend", + "cnc_fromgetstart", + "cnc_fromput", + "cnc_fromputend", + "cnc_fromputstart", + "cnc_fromremove", + "cnc_getdspmode", + "cnc_getdtailerr", + "cnc_getfigure", + "cnc_getfrominfo", + "cnc_getlibopt", + "cnc_getmactype", + "cnc_getpath", + "cnc_getpmactype", + "cnc_getsraminfo", + "cnc_gettimer", + "cnc_hpccactfine", + "cnc_hpccatset", + "cnc_hpccattune", + "cnc_hpccselfine", + "cnc_instlifedt", + "cnc_machine", + "cnc_modal", + "cnc_newprog", + "cnc_pdf_add", + "cnc_pdf_cond", + "cnc_pdf_copy", + "cnc_pdf_del", + "cnc_pdf_delline", + "cnc_pdf_move", + "cnc_pdf_rdactpt", + "cnc_pdf_rdmain", + "cnc_pdf_rename", + "cnc_pdf_searchresult", + "cnc_pdf_searchword", + "cnc_pdf_slctmain", + "cnc_pdf_wractpt", + "cnc_progdigit", + "cnc_prstwkcd", + "cnc_rd1length", + "cnc_rd1radius", + "cnc_rd1tlifedat2", + "cnc_rd1tlifedata", + "cnc_rd2length", + "cnc_rd2radius", + "cnc_rd2tlifedata", + "cnc_rd3dcdcnv", + "cnc_rd3dmovrlap", + "cnc_rd3dofschg", + "cnc_rd3dpulse", + "cnc_rd3dtofs", + "cnc_rd3dtooltip", + "cnc_rd5axmandt", + "cnc_rd5axovrlap", + "cnc_rdactfixofs", + "cnc_rdactpt", + "cnc_rdacttlzone", + "cnc_rdalmhisno", + "cnc_rdalmhistry", + "cnc_rdalmhistry2", + "cnc_rdalmhistry3", + "cnc_rdalmhistry5", + "cnc_rdalminfo", + "cnc_rdalmmsg", + "cnc_rdalmmsg2", + "cnc_rdaxisdata", + "cnc_rdaxisname", + "cnc_rdblkcount", + "cnc_rdbrstrinfo", + "cnc_rdcdrotate", + "cnc_rdcexesram", + "cnc_rdcncid", + "cnc_rdcommand", + "cnc_rdcoordnum", + "cnc_rdcount", + "cnc_rdctrldi", + "cnc_rdctrldo", + "cnc_rdcurrent", + "cnc_rddiag_ext", + "cnc_rddiaginfo", + "cnc_rddiagnum", + "cnc_rddncdgndt", + "cnc_rddynamic", + "cnc_rddynamic2", + "cnc_rdexchgtgrp", + "cnc_rdexecmcode", + "cnc_rdexecprog", + "cnc_rdexecpt", + "cnc_rdfbusmem", + "cnc_rdfixcycle", + "cnc_rdfixofs", + "cnc_rdgcode", + "cnc_rdgrphcmd", + "cnc_rdgrpid", + "cnc_rdgrpid2", + "cnc_rdgrpinfo", + "cnc_rdgrpinfo2", + "cnc_rdgrpinfo3", + "cnc_rdgrpinfo4", + "cnc_rdhissgnl", + "cnc_rdhissgnl3", + "cnc_rdhndintrpt", + "cnc_rdhsparam", + "cnc_rdhsprminfo", + "cnc_rdintchk", + "cnc_rdintinfo", + "cnc_rdlenofs", + "cnc_rdlife", + "cnc_rdloopgain", + "cnc_rdmacro", + "cnc_rdmacroinfo", + "cnc_rdmacror", + "cnc_rdmacror2", + "cnc_rdmagazine", + "cnc_rdmaxgrp", + "cnc_rdmaxtool", + "cnc_rdmdipntr", + "cnc_rdmdiprgstat", + "cnc_rdmdiprog", + "cnc_rdmdlconfig2", + "cnc_rdmenuswitch", + "cnc_rdmgrpdata", + "cnc_rdmirimage", + "cnc_rdmovrlap", + "cnc_rdmultitldt", + "cnc_rdngrp", + "cnc_rdnspdl", + "cnc_rdntool", + "cnc_rdomhisinfo", + "cnc_rdomhisno", + "cnc_rdomhistry", + "cnc_rdomhistry2", + "cnc_rdophisno", + "cnc_rdophistry", + "cnc_rdophistry2", + "cnc_rdophistry4", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdopmsg2", + "cnc_rdopmsg3", + "cnc_rdopnlgnrl", + "cnc_rdopnlgsname", + "cnc_rdopnlsgnl", + "cnc_rdparainfo", + "cnc_rdparam", + "cnc_rdparam3", + "cnc_rdparam_ext", + "cnc_rdparanum", + "cnc_rdparar", + "cnc_rdpdf_alldir", + "cnc_rdpdf_curdir", + "cnc_rdpdf_drive", + "cnc_rdpdf_inf", + "cnc_rdpdf_line", + "cnc_rdpdf_subdir", + "cnc_rdpdf_subdirn", + "cnc_rdpglockstat", + "cnc_rdpitchinfo", + "cnc_rdpitchr", + "cnc_rdpm_item", + "cnc_rdpm_mcnitem", + "cnc_rdpmacro", + "cnc_rdpmacroinfo", + "cnc_rdpmacror", + "cnc_rdpmacror2", + "cnc_rdposerrs", + "cnc_rdposerrs2", + "cnc_rdposerrz", + "cnc_rdposition", + "cnc_rdprgdirtime", + "cnc_rdprgnum", + "cnc_rdproctime", + "cnc_rdprogdir", + "cnc_rdprogdir2", + "cnc_rdprogdir3", + "cnc_rdprogdir4", + "cnc_rdproginfo", + "cnc_rdprogline", + "cnc_rdprogline2", + "cnc_rdprstrinfo", + "cnc_rdradofs", + "cnc_rdrepeatval", + "cnc_rdrstrmcode", + "cnc_rdsafetyzone", + "cnc_rdscaling", + "cnc_rdseqnum", + "cnc_rdset", + "cnc_rdsetinfo", + "cnc_rdsetnum", + "cnc_rdsetr", + "cnc_rdsetzone", + "cnc_rdspcss", + "cnc_rdspdlalm", + "cnc_rdspdlname", + "cnc_rdspeed", + "cnc_rdspgear", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsrvspeed", + "cnc_rdsvmeter", + "cnc_rdsynerrrg", + "cnc_rdsynerrsy", + "cnc_rdsyshard", + "cnc_rdsyssoft", + "cnc_rdsyssoft2", + "cnc_rdsyssoft3", + "cnc_rdtimer", + "cnc_rdtlctldata", + "cnc_rdtlgrp", + "cnc_rdtlinfo", + "cnc_rdtltool", + "cnc_rdtlusegrp", + "cnc_rdtofs", + "cnc_rdtofsinfo", + "cnc_rdtofsinfo2", + "cnc_rdtofsr", + "cnc_rdtool", + "cnc_rdtool_f2", + "cnc_rdtooldata", + "cnc_rdtoolgrp", + "cnc_rdtoolinfo", + "cnc_rdtoolrng", + "cnc_rdtoolzone", + "cnc_rdusegrpid", + "cnc_rdusetlno", + "cnc_rdwavedata", + "cnc_rdwaveprm", + "cnc_rdwaveprm2", + "cnc_rdwkcdsfms", + "cnc_rdwkcdshft", + "cnc_rdzofs", + "cnc_rdzofsinfo", + "cnc_rdzofsr", + "cnc_regmagazine", + "cnc_regtool", + "cnc_regtool_f2", + "cnc_relative", + "cnc_relative2", + "cnc_renameprog", + "cnc_reset", + "cnc_reset2", + "cnc_resetpglock", + "cnc_rewind", + "cnc_rstrseqsrch", + "cnc_rstrseqsrch2", + "cnc_sdcancelsmpl", + "cnc_sdclrchnl", + "cnc_sdendsmpl", + "cnc_sdread1shot", + "cnc_sdreadsmpl", + "cnc_sdsetchnl", + "cnc_sdstartsmpl", + "cnc_search", + "cnc_search2", + "cnc_searchresult", + "cnc_searchword", + "cnc_seqsrch", + "cnc_seqsrch2", + "cnc_setlibopt", + "cnc_setmactype", + "cnc_setpath", + "cnc_setpglock", + "cnc_setpmactype", + "cnc_settimer", + "cnc_skip", + "cnc_sramget", + "cnc_sramgetend", + "cnc_sramgetstart", + "cnc_srvdelay", + "cnc_start", + "cnc_start_async_wrparam", + "cnc_startdyngrph", + "cnc_startnccmd", + "cnc_startomhis", + "cnc_startophis", + "cnc_statinfo", + "cnc_stopdyngrph", + "cnc_stopnccmd", + "cnc_stopomhis", + "cnc_stopophis", + "cnc_svdtendrd", + "cnc_svdtendwr", + "cnc_svdtrddata", + "cnc_svdtstartrd", + "cnc_svdtstartwr", + "cnc_svdtstopexec", + "cnc_svdtwrdata", + "cnc_sysinfo", + "cnc_sysinfo_ex", + "cnc_t1info", + "cnc_t2info", + "cnc_tofs_rnge", + "cnc_toolnum", + "cnc_upend", + "cnc_upend3", + "cnc_upend4", + "cnc_upload", + "cnc_upload3", + "cnc_upload4", + "cnc_upstart", + "cnc_upstart3", + "cnc_upstart4", + "cnc_verify", + "cnc_verify4", + "cnc_vrfend", + "cnc_vrfend4", + "cnc_vrfstart", + "cnc_vrfstart4", + "cnc_wavestart", + "cnc_wavestat", + "cnc_wavestop", + "cnc_wksft_rnge", + "cnc_wr1tlifedat2", + "cnc_wr1tlifedata", + "cnc_wr2tlifedata", + "cnc_wractpt", + "cnc_wrcexesram", + "cnc_wrcountr", + "cnc_wrfbusmem", + "cnc_wrfixofs", + "cnc_wrgrpinfo", + "cnc_wrgrpinfo2", + "cnc_wrgrpinfo3", + "cnc_wrhissgnl", + "cnc_wrhissgnl3", + "cnc_wrintchk", + "cnc_wrkeyhistry", + "cnc_wrmacro", + "cnc_wrmacror", + "cnc_wrmacror2", + "cnc_wrmagazine", + "cnc_wrmdipntr", + "cnc_wrmdiprog", + "cnc_wrmenuswitch", + "cnc_wrmgrpdata", + "cnc_wrmultitldt", + "cnc_wropnlgnrl", + "cnc_wropnlgsname", + "cnc_wropnlsgnl", + "cnc_wrparam", + "cnc_wrparas", + "cnc_wrpdf_attr", + "cnc_wrpdf_curdir", + "cnc_wrpdf_line", + "cnc_wrpitchr", + "cnc_wrpm_item", + "cnc_wrpm_mcnitem", + "cnc_wrpmacro", + "cnc_wrpmacror", + "cnc_wrpmacror2", + "cnc_wrprogline", + "cnc_wrrelpos", + "cnc_wrsafetyzone", + "cnc_wrset", + "cnc_wrsets", + "cnc_wrsetzone", + "cnc_wrtimer", + "cnc_wrtlctldata", + "cnc_wrtofs", + "cnc_wrtofsr", + "cnc_wrtool", + "cnc_wrtool2", + "cnc_wrtool_f2", + "cnc_wrtooldata", + "cnc_wrtoolzone", + "cnc_wrwaveprm", + "cnc_wrwaveprm2", + "cnc_wrwkcdsfms", + "cnc_wrwkcdshft", + "cnc_wrzofs", + "cnc_wrzofsr", + "cnc_zofs_rnge", + "pmc_get_current_pmc_unit", + "pmc_get_number_of_pmc", + "pmc_get_pmc_unit_types", + "pmc_get_timer_type", + "pmc_getdtailerr", + "pmc_kpmsiz", + "pmc_rdalmmsg", + "pmc_rdcntldata", + "pmc_rdcntlgrp", + "pmc_rdkpm", + "pmc_rdpmcinfo", + "pmc_rdpmcparam", + "pmc_rdpmcrng", + "pmc_rdpmctitle", + "pmc_rdprmend", + "pmc_rdprmstart", + "pmc_select_pmc_unit", + "pmc_set_timer_type", + "pmc_wrcntldata", + "pmc_wrcntlgrp", + "pmc_wrkpm", + "pmc_wrpmcparam", + "pmc_wrpmcrng", + "pmc_wrprmend", + "pmc_wrprmstart" + ], + "exports": [ + "PmcProcessCB", + "cnc_absolute", + "cnc_absolute2", + "cnc_absolute_bg", + "cnc_absolute_mgi", + "cnc_accdecdly", + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_adcnv", + "cnc_alarm", + "cnc_alarm2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_async_busy_state", + "cnc_aux_statinfo", + "cnc_axisnum2", + "cnc_backuplrn", + "cnc_block_status", + "cnc_btlfpotsrh", + "cnc_buff", + "cnc_cancel_prm", + "cnc_canmcdfinfo", + "cnc_canmovrlap", + "cnc_cdautoset", + "cnc_cdownload", + "cnc_cexesramsize", + "cnc_chglang", + "cnc_chgoverstore", + "cnc_chkmcdfile", + "cnc_chkusbfile", + "cnc_chkversion", + "cnc_clear_maint", + "cnc_clearalm", + "cnc_clearomhis", + "cnc_clearophis", + "cnc_clr3dplsmov", + "cnc_clr5axpls", + "cnc_clr5dplsmov", + "cnc_clralm", + "cnc_clrcntinfo", + "cnc_clrfromsvspid", + "cnc_clrlrncrnt", + "cnc_clrmofs", + "cnc_clroverstore", + "cnc_clrptdata", + "cnc_clrrstraxis", + "cnc_clrtofs", + "cnc_condense", + "cnc_confirm_restart", + "cnc_copyprog", + "cnc_cupload", + "cnc_cverify", + "cnc_data_copy", + "cnc_delall", + "cnc_delete", + "cnc_deleteo8", + "cnc_delmag_property", + "cnc_delmagazine", + "cnc_delmcdfile", + "cnc_delmcdfilebynum", + "cnc_delpot_property", + "cnc_delprogline", + "cnc_delsysalm", + "cnc_deltlifedt", + "cnc_deltlifegrp", + "cnc_deltool", + "cnc_diagnosr", + "cnc_diagnoss", + "cnc_dismountmcd", + "cnc_distance", + "cnc_distancem", + "cnc_dnc2", + "cnc_dncend2", + "cnc_dncprgname", + "cnc_dncstart2", + "cnc_download", + "cnc_download3", + "cnc_download3m", + "cnc_download4", + "cnc_dschdir", + "cnc_dscopyfile", + "cnc_dsftpcancel", + "cnc_dsftpstat", + "cnc_dsget_req", + "cnc_dslistdel_req", + "cnc_dslistget_req", + "cnc_dslistput_req", + "cnc_dsmget_req", + "cnc_dsmkdir", + "cnc_dsmput_req", + "cnc_dsput_req", + "cnc_dsremove", + "cnc_dsrename", + "cnc_dsrmdir", + "cnc_dtsvinfo", + "cnc_dvpunchvolc", + "cnc_dvreadvolc", + "cnc_dwnend", + "cnc_dwnend3", + "cnc_dwnend3m", + "cnc_dwnend4", + "cnc_dwnstart", + "cnc_dwnstart3", + "cnc_dwnstart3m", + "cnc_dwnstart4", + "cnc_edplutosmpl", + "cnc_end_async_data_punch", + "cnc_end_async_data_read", + "cnc_end_async_pdf_punch", + "cnc_end_async_pdf_read", + "cnc_end_async_punch_prog3", + "cnc_end_async_punch_prog3_bg", + "cnc_end_async_read_prog3", + "cnc_end_async_read_prog3_bg", + "cnc_end_async_wrparam", + "cnc_exaxisname", + "cnc_exaxisname2", + "cnc_exeprgname", + "cnc_exeprgname2", + "cnc_exeprgname_bg", + "cnc_freelibhndl", + "cnc_fromget", + "cnc_fromgetend", + "cnc_fromgetstart", + "cnc_fromput", + "cnc_fromputend", + "cnc_fromputstart", + "cnc_fromremove", + "cnc_fssb_autoset", + "cnc_fssb_reset", + "cnc_ftrq_data_copy", + "cnc_ftrq_from_load", + "cnc_ftrq_from_save", + "cnc_get_crosschk_alarm", + "cnc_get_flowmonitor", + "cnc_get_mccteststs", + "cnc_get_safetysts", + "cnc_get_safetysts2", + "cnc_getdspmode", + "cnc_getdtailerr", + "cnc_getdtailerr2", + "cnc_getfigure", + "cnc_getfrominfo", + "cnc_getlibopt", + "cnc_getmactype", + "cnc_getpath", + "cnc_getpmactype", + "cnc_getprntname", + "cnc_getrtmioinfo", + "cnc_getrtmiorngnum", + "cnc_getrtmrvar", + "cnc_getrtmrvars", + "cnc_getsraminfo", + "cnc_getsysfolder_num", + "cnc_gettimer", + "cnc_gettimer_sec", + "cnc_gettolnum_qset", + "cnc_getzofsnum_qset", + "cnc_hdck_nochange_info", + "cnc_hpccactfine", + "cnc_hpccatset", + "cnc_hpccattune", + "cnc_hpccselfine", + "cnc_instlifedt", + "cnc_loadtorq", + "cnc_machine", + "cnc_machine3", + "cnc_machine_bg", + "cnc_magazinesrch", + "cnc_mcdp_mount", + "cnc_mcdp_mountchk", + "cnc_mcdp_unmount", + "cnc_mdd_getexceptparam", + "cnc_mdd_getswitch", + "cnc_mdd_lock", + "cnc_mdd_rdinfo", + "cnc_mdd_register", + "cnc_mdd_setexceptparam", + "cnc_mdd_setpassword", + "cnc_mdd_setswitch", + "cnc_mdd_unlock", + "cnc_mdd_unregister", + "cnc_mdd_update", + "cnc_mdg_moniclear", + "cnc_mdg_monistat", + "cnc_mdg_msgsrch", + "cnc_mdg_rdalminfo", + "cnc_mdg_rdalminfoview", + "cnc_mdg_rdalmnum", + "cnc_mdg_rdcontinfo", + "cnc_mdg_rddtmsg", + "cnc_mdg_rdflow", + "cnc_mdg_rdheatsimlt", + "cnc_mdg_rdlatchedalm", + "cnc_mdg_rdloadlvl", + "cnc_mdg_rdmsg", + "cnc_mdg_rdmsgnum", + "cnc_mdg_rdmsgordr", + "cnc_mdg_rdorderalmno", + "cnc_mdg_rdwvdata", + "cnc_modal", + "cnc_modal_bg", + "cnc_mountmcd", + "cnc_msr_delhis_all", + "cnc_msr_rdhis_allnum", + "cnc_msr_rdhis_inf", + "cnc_msr_rdhis_msudat", + "cnc_msr_rdhis_ncdat", + "cnc_msr_rdhis_ohisnum", + "cnc_msr_rdhis_ohisrec", + "cnc_msr_rdhis_pmc", + "cnc_msr_rdmon_msudat", + "cnc_msr_rdmon_msunum", + "cnc_msr_rdmon_pmcinf", + "cnc_msr_start_sample", + "cnc_msr_stop_sample", + "cnc_newprog", + "cnc_nextdistance", + "cnc_nextdistance_bg", + "cnc_opentdicubeinfo", + "cnc_pdf_add", + "cnc_pdf_add_bgedt", + "cnc_pdf_chginquiry", + "cnc_pdf_cond", + "cnc_pdf_copy", + "cnc_pdf_del", + "cnc_pdf_delall", + "cnc_pdf_delchar", + "cnc_pdf_delline", + "cnc_pdf_dncread", + "cnc_pdf_dncset", + "cnc_pdf_dncset2", + "cnc_pdf_dssearch", + "cnc_pdf_mergeprog", + "cnc_pdf_move", + "cnc_pdf_punch", + "cnc_pdf_punch_bg", + "cnc_pdf_rdactpt", + "cnc_pdf_rdactpt_bg", + "cnc_pdf_rdactpt_bgedt", + "cnc_pdf_rdactpt_mem", + "cnc_pdf_rdmain", + "cnc_pdf_rdprgname", + "cnc_pdf_rdrmtdgn", + "cnc_pdf_read", + "cnc_pdf_read_bg", + "cnc_pdf_rename", + "cnc_pdf_replacechar", + "cnc_pdf_replaceword_all", + "cnc_pdf_searchresult", + "cnc_pdf_searchresult2", + "cnc_pdf_searchresult_bgedt", + "cnc_pdf_searchword", + "cnc_pdf_searchword2", + "cnc_pdf_searchword_bgedt", + "cnc_pdf_slctmain", + "cnc_pdf_startrmtdgn", + "cnc_pdf_stoprmtdgn", + "cnc_pdf_wractpt", + "cnc_pdf_wractpt_bg", + "cnc_pdf_wractpt_bgedt", + "cnc_pmclad_screen", + "cnc_pmmchkalm", + "cnc_pmmget", + "cnc_pmmgetend", + "cnc_pmmgetstart", + "cnc_pmminit", + "cnc_pmmiochanl", + "cnc_pmmprmpage", + "cnc_pmmsysdt", + "cnc_powc_clear_inte", + "cnc_powc_del_cycle_data", + "cnc_powc_rd_clear_time", + "cnc_powc_rd_cycle_data", + "cnc_powc_rd_history", + "cnc_powc_rd_outer_set", + "cnc_powc_wr_outer_set", + "cnc_preset_prm", + "cnc_progdigit", + "cnc_prot_pswcan", + "cnc_prot_pswchg", + "cnc_prot_pswinit", + "cnc_prot_pswinp", + "cnc_prstwkcd", + "cnc_ptdownload", + "cnc_ptdwnend", + "cnc_ptdwnstart", + "cnc_ptlink", + "cnc_punch_data", + "cnc_punch_data_end", + "cnc_punch_data_start", + "cnc_punch_prog", + "cnc_punch_prog2", + "cnc_punch_prog2_bg", + "cnc_punch_prog3", + "cnc_punch_prog3_bg", + "cnc_punch_prog_bg", + "cnc_punchlrncrnt", + "cnc_pwcm_clear_consump", + "cnc_pwoff_alarm", + "cnc_rd1length", + "cnc_rd1punchtl_ex", + "cnc_rd1radius", + "cnc_rd1tlifedat2", + "cnc_rd1tlifedata", + "cnc_rd2length", + "cnc_rd2punchtl_ex", + "cnc_rd2radius", + "cnc_rd2tlifedata", + "cnc_rd3dcdcnv", + "cnc_rd3dmovrlap", + "cnc_rd3dofschg", + "cnc_rd3dpulse", + "cnc_rd3dtofs", + "cnc_rd3dtooltip", + "cnc_rd5axmandt", + "cnc_rd5axovrlap", + "cnc_rd5dmacmov", + "cnc_rd5dpulse", + "cnc_rd5dtooltip", + "cnc_rd_grpaxisinfo", + "cnc_rd_grppos", + "cnc_rd_grppos2", + "cnc_rdactfixofs", + "cnc_rdactpos_w", + "cnc_rdactpt", + "cnc_rdactspdl", + "cnc_rdacttlzone", + "cnc_rdalmhisno", + "cnc_rdalmhisno3", + "cnc_rdalmhistry", + "cnc_rdalmhistry2", + "cnc_rdalmhistry3", + "cnc_rdalmhistry4", + "cnc_rdalmhistry5", + "cnc_rdalminfo", + "cnc_rdalminfo2_bg", + "cnc_rdalminfo_bg", + "cnc_rdalmmsg", + "cnc_rdalmmsg2", + "cnc_rdaxisdata", + "cnc_rdaxisname", + "cnc_rdaxisstatus_bg", + "cnc_rdblkcount", + "cnc_rdblockcount", + "cnc_rdbrstrinfo", + "cnc_rdcbmem", + "cnc_rdcbprm", + "cnc_rdcdrotate", + "cnc_rdcdslctprmm", + "cnc_rdcenblinfo", + "cnc_rdcenter", + "cnc_rdcexesram", + "cnc_rdcncid", + "cnc_rdcncmem", + "cnc_rdcncmem2", + "cnc_rdcntover", + "cnc_rdcnttype", + "cnc_rdcommand", + "cnc_rdcoordnum", + "cnc_rdcount", + "cnc_rdcstm_decfig", + "cnc_rdcstmdecfig", + "cnc_rdctname", + "cnc_rdctrldi", + "cnc_rdctrldo", + "cnc_rdcurrent", + "cnc_rddiag_ext", + "cnc_rddiaginfo", + "cnc_rddiaginfo2", + "cnc_rddiagnum", + "cnc_rddncdgndt", + "cnc_rddsdevinfo", + "cnc_rddsdir", + "cnc_rddsdncfile", + "cnc_rddsfile", + "cnc_rddynamic", + "cnc_rddynamic2", + "cnc_rddynamic3", + "cnc_rddynamic3m", + "cnc_rddynamico8", + "cnc_rdembedf_inf", + "cnc_rdenblinfo", + "cnc_rdexchgtgrp", + "cnc_rdexecmcode", + "cnc_rdexecprgnum", + "cnc_rdexecprog", + "cnc_rdexecprog2", + "cnc_rdexecprog_bg", + "cnc_rdexecpt", + "cnc_rdexecptm", + "cnc_rdfbusmem", + "cnc_rdfile_sram", + "cnc_rdfixcycle", + "cnc_rdfixofs", + "cnc_rdfromservoid", + "cnc_rdfromspindleid", + "cnc_rdfsraminfo", + "cnc_rdfsraminfo2", + "cnc_rdfssb_amp", + "cnc_rdfssb_axis", + "cnc_rdfssb_info", + "cnc_rdfssb_mainte", + "cnc_rdfssb_plsmod", + "cnc_rdftrq_data", + "cnc_rdftrq_info", + "cnc_rdftrq_storecount", + "cnc_rdgcode", + "cnc_rdgcodem", + "cnc_rdgrphcmd", + "cnc_rdgrpid", + "cnc_rdgrpid2", + "cnc_rdgrpinfo", + "cnc_rdgrpinfo2", + "cnc_rdgrpinfo3", + "cnc_rdgrpinfo4", + "cnc_rdhdnxt", + "cnc_rdhipitchinfo", + "cnc_rdhipitchr", + "cnc_rdhissgnl", + "cnc_rdhissgnl3", + "cnc_rdhndintrpt", + "cnc_rdholmes", + "cnc_rdhsparam", + "cnc_rdhsparamm", + "cnc_rdhsprminfo", + "cnc_rdintchk", + "cnc_rdinterference", + "cnc_rdintinfo", + "cnc_rdipltp", + "cnc_rdipltp_bg", + "cnc_rdjogdrun", + "cnc_rdjogmdi", + "cnc_rdlenofs", + "cnc_rdlife", + "cnc_rdloopgain", + "cnc_rdlrninfo", + "cnc_rdlrninfol", + "cnc_rdlrnprfcmnt", + "cnc_rdlrntrnsdata", + "cnc_rdmacro", + "cnc_rdmacro_bg", + "cnc_rdmacroinfo", + "cnc_rdmacrolclevel", + "cnc_rdmacrolcval", + "cnc_rdmacror", + "cnc_rdmacror2", + "cnc_rdmacror2_name", + "cnc_rdmacror3", + "cnc_rdmag_property", + "cnc_rdmagazine", + "cnc_rdmaxgrp", + "cnc_rdmaxtool", + "cnc_rdmcdfinfo", + "cnc_rdmcdprgcmnt", + "cnc_rdmcdprgcmnt2", + "cnc_rdmdipntr", + "cnc_rdmdipntro8", + "cnc_rdmdiprgstat", + "cnc_rdmdiprog", + "cnc_rdmdlconfig2", + "cnc_rdmenuswitch", + "cnc_rdmgrpdata", + "cnc_rdmirimage", + "cnc_rdmofs", + "cnc_rdmovrlap", + "cnc_rdmovrlapm", + "cnc_rdmultitldt", + "cnc_rdngrp", + "cnc_rdnspdl", + "cnc_rdntool", + "cnc_rdofslength", + "cnc_rdomhisinfo", + "cnc_rdomhisno", + "cnc_rdomhistry", + "cnc_rdomhistry2", + "cnc_rdope_lvl", + "cnc_rdophisno", + "cnc_rdophisno3", + "cnc_rdophistry", + "cnc_rdophistry2", + "cnc_rdophistry3", + "cnc_rdophistry4", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdopmsg2", + "cnc_rdopmsg3", + "cnc_rdopmsg3m", + "cnc_rdopnlgnrl", + "cnc_rdopnlgnrl2", + "cnc_rdopnlgsname", + "cnc_rdopnlgsname2", + "cnc_rdopnlsgnl", + "cnc_rdoverstore", + "cnc_rdoverstoremode", + "cnc_rdpalaxis", + "cnc_rdparainfo", + "cnc_rdparainfo2", + "cnc_rdparam", + "cnc_rdparam3", + "cnc_rdparam_ext", + "cnc_rdparanum", + "cnc_rdparar", + "cnc_rdparar3", + "cnc_rdpdf_alldir", + "cnc_rdpdf_curdir", + "cnc_rdpdf_drive", + "cnc_rdpdf_execline", + "cnc_rdpdf_inf", + "cnc_rdpdf_line", + "cnc_rdpdf_line2", + "cnc_rdpdf_line_bgedt", + "cnc_rdpdf_pglockstat", + "cnc_rdpdf_prginf", + "cnc_rdpdf_subdir", + "cnc_rdpdf_subdirn", + "cnc_rdpglockstat", + "cnc_rdpitchblkinfo", + "cnc_rdpitchinfo", + "cnc_rdpitchr", + "cnc_rdplutosmpl", + "cnc_rdpm_item", + "cnc_rdpm_mcnitem", + "cnc_rdpmacro", + "cnc_rdpmacroinfo", + "cnc_rdpmacroinfo2", + "cnc_rdpmacroinfo3", + "cnc_rdpmacror", + "cnc_rdpmacror2", + "cnc_rdpmacror_bg", + "cnc_rdpmacvalflag", + "cnc_rdpmmprmtp", + "cnc_rdposerrs", + "cnc_rdposerrs2", + "cnc_rdposerrz", + "cnc_rdposition", + "cnc_rdpot_property", + "cnc_rdprgdirtime", + "cnc_rdprgnum", + "cnc_rdprgnumo8", + "cnc_rdprgnumo8_bg", + "cnc_rdprgrmupdtcnt", + "cnc_rdproctime", + "cnc_rdproctime2", + "cnc_rdproctime3", + "cnc_rdprogdata", + "cnc_rdprogdir", + "cnc_rdprogdir2", + "cnc_rdprogdir2o8", + "cnc_rdprogdir3", + "cnc_rdprogdir4", + "cnc_rdprogdiro8", + "cnc_rdproginfo", + "cnc_rdprogline", + "cnc_rdprogline2", + "cnc_rdprotect", + "cnc_rdprstrinfo", + "cnc_rdprstrinfom", + "cnc_rdprt_data", + "cnc_rdprt_lvl", + "cnc_rdptaxfunctablestatus", + "cnc_rdptaxitablestatus", + "cnc_rdptcnvalm", + "cnc_rdptcnvinfo", + "cnc_rdptexedistalm", + "cnc_rdptsptablestatus", + "cnc_rdradofs", + "cnc_rdrepeatval", + "cnc_rdrepeatval_ext", + "cnc_rdrstrmcode", + "cnc_rdrtmiowrenbl", + "cnc_rdrtmiowrenblbit", + "cnc_rdrtmiowrenblrng", + "cnc_rdrtmrvar", + "cnc_rdrtmrvars", + "cnc_rdsafetyzone", + "cnc_rdscaling", + "cnc_rdscdldat", + "cnc_rdscdlinfo", + "cnc_rdseqnum", + "cnc_rdseqnum_bg", + "cnc_rdservoid", + "cnc_rdset", + "cnc_rdsetinfo", + "cnc_rdsetinfo2", + "cnc_rdsetnum", + "cnc_rdsetr", + "cnc_rdsetzone", + "cnc_rdspcss", + "cnc_rdspdlalm", + "cnc_rdspdlfeed", + "cnc_rdspdlname", + "cnc_rdspdlnamem", + "cnc_rdspdlspeed", + "cnc_rdspdlwaitname", + "cnc_rdspeed", + "cnc_rdspgear", + "cnc_rdspindleid", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsrvspeed", + "cnc_rdsvgtungstat", + "cnc_rdsvmeter", + "cnc_rdsynerrrg", + "cnc_rdsynerrsy", + "cnc_rdsysalm", + "cnc_rdsyshard", + "cnc_rdsyssoft", + "cnc_rdsyssoft2", + "cnc_rdsyssoft3", + "cnc_rdtdicrntshapeinf", + "cnc_rdtdicubedata", + "cnc_rdtdicubeinfo", + "cnc_rdtdicurrentshape", + "cnc_rdtdicylinderdata", + "cnc_rdtdidispsetting", + "cnc_rdtdieffectshape", + "cnc_rdtdifignum", + "cnc_rdtdifiguredata", + "cnc_rdtdiinfo", + "cnc_rdtdiinitview", + "cnc_rdtdimoveaxis", + "cnc_rdtdinamesetting", + "cnc_rdtdiplanedata", + "cnc_rdtdiseltool", + "cnc_rdtdishapedata", + "cnc_rdtimer", + "cnc_rdtlctldata", + "cnc_rdtldata", + "cnc_rdtldspcstms", + "cnc_rdtlgeomsize", + "cnc_rdtlgeomsize_ext", + "cnc_rdtlgrp", + "cnc_rdtlinfo", + "cnc_rdtlmgr_check", + "cnc_rdtlmgr_name", + "cnc_rdtlmsinfo", + "cnc_rdtlname", + "cnc_rdtlnewstatus", + "cnc_rdtltool", + "cnc_rdtlusegrp", + "cnc_rdtofs", + "cnc_rdtofs_bg", + "cnc_rdtofsenbl", + "cnc_rdtofsinfo", + "cnc_rdtofsinfo2", + "cnc_rdtofsr", + "cnc_rdtofsr_bg", + "cnc_rdtool", + "cnc_rdtool_cause", + "cnc_rdtool_f2", + "cnc_rdtool_inhis", + "cnc_rdtool_outhis", + "cnc_rdtoolchggrp", + "cnc_rdtooldata", + "cnc_rdtoolgeom_tlm", + "cnc_rdtoolgrp", + "cnc_rdtoolinfo", + "cnc_rdtoollife_count", + "cnc_rdtoollife_data", + "cnc_rdtoollife_tcodedata", + "cnc_rdtoollifed_count", + "cnc_rdtoollifed_data", + "cnc_rdtoolrng", + "cnc_rdtoolzone", + "cnc_rdusbdevinfo", + "cnc_rdusbfilelist", + "cnc_rdusegrpid", + "cnc_rdusetlno", + "cnc_rduvactpt2", + "cnc_rdvolc", + "cnc_rdvolccomp", + "cnc_rdwavecount", + "cnc_rdwavedata", + "cnc_rdwavedata2", + "cnc_rdwavedata3", + "cnc_rdwaveprm", + "cnc_rdwaveprm2", + "cnc_rdwaveprm3", + "cnc_rdwkcdsfms", + "cnc_rdwkcdsfms2", + "cnc_rdwkcdsfms3", + "cnc_rdwkcdshft", + "cnc_rdwkcdshft2", + "cnc_rdwkcdshft3", + "cnc_rdwkcdshft_bg", + "cnc_rdwseterror", + "cnc_rdzofs", + "cnc_rdzofs_bg", + "cnc_rdzofsinfo", + "cnc_rdzofsmes", + "cnc_rdzofsr", + "cnc_rdzofsr_bg", + "cnc_read_cexeinfo", + "cnc_read_data", + "cnc_read_prog", + "cnc_read_prog2", + "cnc_read_prog2_bg", + "cnc_read_prog3", + "cnc_read_prog3_bg", + "cnc_read_prog_bg", + "cnc_readlrncrnt", + "cnc_reg_toolstrage", + "cnc_regmagazine", + "cnc_regtool", + "cnc_regtool_f2", + "cnc_relative", + "cnc_relative2", + "cnc_relative_bg", + "cnc_renameprog", + "cnc_reqsvgtung", + "cnc_reset", + "cnc_reset2", + "cnc_resetpdf_pglock", + "cnc_resetpglock", + "cnc_restorlrn", + "cnc_rewind", + "cnc_robo_rdalmmsg", + "cnc_robo_rdcomsetting", + "cnc_robo_rdgrouplist", + "cnc_robo_rdselectedsignals", + "cnc_robo_rdsignals", + "cnc_robo_selectgroup", + "cnc_robo_wralmmsg", + "cnc_robo_wrcomsetting", + "cnc_robo_wrgroup", + "cnc_robo_wrselectedsignals", + "cnc_robo_wrsignalname", + "cnc_rstrseqsrch", + "cnc_rstrseqsrch2", + "cnc_save_maint", + "cnc_saveprog_end", + "cnc_saveprog_start", + "cnc_sdbetainfo", + "cnc_sdcancelsmpl", + "cnc_sdclrchnl", + "cnc_sdendsmpl", + "cnc_sdendsmpl2", + "cnc_sdfmnghwnd", + "cnc_sdfstatchg", + "cnc_sdread1shot", + "cnc_sdreadsmpl", + "cnc_sdreadsmpl2", + "cnc_sdsetchnl", + "cnc_sdsetchnl2", + "cnc_sdstartsmpl", + "cnc_sdstartsmpl2", + "cnc_sdstartsmplb", + "cnc_sdtcancelsmpl", + "cnc_sdtclrchnl", + "cnc_sdtendsmpl", + "cnc_sdtread1shot", + "cnc_sdtreadsmpl", + "cnc_sdtsetchnl", + "cnc_sdtstartsmpl", + "cnc_search", + "cnc_search2", + "cnc_searcho8", + "cnc_searchresult", + "cnc_searchresult2", + "cnc_searchusbfile", + "cnc_searchword", + "cnc_searchword2", + "cnc_sendkey", + "cnc_seqrdtdicubeinfo", + "cnc_seqsrch", + "cnc_seqsrch2", + "cnc_set_cutcnd_exval", + "cnc_set_smth_exval", + "cnc_setactptopt", + "cnc_setcurscrn", + "cnc_setdtailerr", + "cnc_setfrp", + "cnc_setlibopt", + "cnc_setmactype", + "cnc_setpath", + "cnc_setpdf_pglock", + "cnc_setpdf_pglockexec", + "cnc_setpglock", + "cnc_setpmactype", + "cnc_setrstraxis", + "cnc_setsumdt", + "cnc_settdiobjectshape", + "cnc_settditoolshape", + "cnc_settimer", + "cnc_settolnum_qset", + "cnc_settpnlcalib", + "cnc_setzofsnum_qset", + "cnc_skip", + "cnc_sramget", + "cnc_sramgetend", + "cnc_sramgetstart", + "cnc_sramstat", + "cnc_srttl_getdata", + "cnc_srttl_getnum", + "cnc_srvdelay", + "cnc_start", + "cnc_start_async_data_punch", + "cnc_start_async_data_read", + "cnc_start_async_pdf_punch", + "cnc_start_async_pdf_read", + "cnc_start_async_punch_prog3", + "cnc_start_async_punch_prog3_bg", + "cnc_start_async_read_prog3", + "cnc_start_async_read_prog3_bg", + "cnc_start_async_wrparam", + "cnc_start_grppos", + "cnc_start_grppos2", + "cnc_startdyngrph", + "cnc_startnccmd", + "cnc_startomhis", + "cnc_startophis", + "cnc_startptcnv", + "cnc_statinfo", + "cnc_statinfo2", + "cnc_statinfo_bg", + "cnc_statinfo_dmg", + "cnc_statlrntrns", + "cnc_stop_async_read_punch", + "cnc_stop_grppos", + "cnc_stopdyngrph", + "cnc_stoplrntrns", + "cnc_stopnccmd", + "cnc_stopomhis", + "cnc_stopophis", + "cnc_stopsvgtung", + "cnc_stplutosmpl", + "cnc_svdtendrd", + "cnc_svdtendwr", + "cnc_svdtrddata", + "cnc_svdtstartrd", + "cnc_svdtstartwr", + "cnc_svdtstopexec", + "cnc_svdtwrdata", + "cnc_sysinfo", + "cnc_sysinfo_ex", + "cnc_t1info", + "cnc_t2info", + "cnc_tofs_rnge", + "cnc_tool_in", + "cnc_tool_in2", + "cnc_tool_move", + "cnc_tool_out", + "cnc_tool_srh_free_min_num", + "cnc_tool_temp_in", + "cnc_tool_temp_out", + "cnc_toolnum", + "cnc_toolsrch", + "cnc_upend", + "cnc_upend3", + "cnc_upend4", + "cnc_upload", + "cnc_upload3", + "cnc_upload4", + "cnc_upstart", + "cnc_upstart3", + "cnc_upstart4", + "cnc_upstarto8", + "cnc_usbmkdir", + "cnc_usbremove", + "cnc_usbrename", + "cnc_usbrmdir", + "cnc_validate_opt", + "cnc_validate_prm", + "cnc_verify", + "cnc_verify4", + "cnc_verify_prog", + "cnc_verify_prog_bg", + "cnc_vrfend", + "cnc_vrfend4", + "cnc_vrfstart", + "cnc_vrfstart4", + "cnc_wavestart", + "cnc_wavestat", + "cnc_wavestop", + "cnc_wksft_rnge", + "cnc_wr1length", + "cnc_wr1radius", + "cnc_wr1tlifedat2", + "cnc_wr1tlifedata", + "cnc_wr2length", + "cnc_wr2radius", + "cnc_wr2tlifedata", + "cnc_wractpt", + "cnc_wrcbmem", + "cnc_wrcbprm", + "cnc_wrcexesram", + "cnc_wrcncmem", + "cnc_wrcncmem2", + "cnc_wrcnttype", + "cnc_wrcount", + "cnc_wrcountr", + "cnc_wrdsdncfile", + "cnc_wrdsdncfile2", + "cnc_wrfbusmem", + "cnc_wrfile_sram", + "cnc_wrfixofs", + "cnc_wrfromservoid", + "cnc_wrfromspindleid", + "cnc_wrfssb_axis", + "cnc_wrfssb_axis_num", + "cnc_wrftl", + "cnc_wrgrpinfo", + "cnc_wrgrpinfo2", + "cnc_wrgrpinfo3", + "cnc_wrhipitchr", + "cnc_wrhissgnl", + "cnc_wrhissgnl3", + "cnc_wrintchk", + "cnc_wrjogmdi", + "cnc_wrjogmdiclr", + "cnc_wrkeyhistry", + "cnc_wrlife", + "cnc_wrlrninfo", + "cnc_wrlrnprf", + "cnc_wrmacro", + "cnc_wrmacro_bg", + "cnc_wrmacror", + "cnc_wrmacror2", + "cnc_wrmag_property", + "cnc_wrmagazine", + "cnc_wrmdipntr", + "cnc_wrmdiprog", + "cnc_wrmenuswitch", + "cnc_wrmgrpdata", + "cnc_wrmofs", + "cnc_wrmultitldt", + "cnc_wropnlgnrl", + "cnc_wropnlgnrl2", + "cnc_wropnlgsname", + "cnc_wropnlgsname2", + "cnc_wropnlsgnl", + "cnc_wroverstore", + "cnc_wrparam", + "cnc_wrparam3", + "cnc_wrparas", + "cnc_wrparas3", + "cnc_wrpdf_attr", + "cnc_wrpdf_char", + "cnc_wrpdf_curdir", + "cnc_wrpdf_line", + "cnc_wrpitchr", + "cnc_wrpm_item", + "cnc_wrpm_mcnitem", + "cnc_wrpmacro", + "cnc_wrpmacror", + "cnc_wrpmacror2", + "cnc_wrpmacror_bg", + "cnc_wrpmmprm", + "cnc_wrpmmprmtp", + "cnc_wrpot_property", + "cnc_wrprogline", + "cnc_wrprt_lvl", + "cnc_wrpunchtl_ex", + "cnc_wrrelpos", + "cnc_wrrtmiowrenbl", + "cnc_wrrtmiowrenblbit", + "cnc_wrrtmiowrenblrng", + "cnc_wrrtmrvar", + "cnc_wrrtmrvars", + "cnc_wrsafetyzone", + "cnc_wrscdldat", + "cnc_wrset", + "cnc_wrsets", + "cnc_wrsetzone", + "cnc_wrsignal_f", + "cnc_wrt1info", + "cnc_wrt2info", + "cnc_wrtdicubedata", + "cnc_wrtdicylinderdata", + "cnc_wrtdidispsetting", + "cnc_wrtdieffectshape", + "cnc_wrtdifignum", + "cnc_wrtdifiguredata", + "cnc_wrtdiinitview", + "cnc_wrtdimoveaxis", + "cnc_wrtdinamesetting", + "cnc_wrtdiplanedata", + "cnc_wrtdishapedata", + "cnc_wrtimer", + "cnc_wrtlctldata", + "cnc_wrtldata", + "cnc_wrtlgeomsize", + "cnc_wrtlgeomsize_ext", + "cnc_wrtofs", + "cnc_wrtofsdrctinp", + "cnc_wrtofsms", + "cnc_wrtofsr", + "cnc_wrtool", + "cnc_wrtool2", + "cnc_wrtool_f2", + "cnc_wrtooldata", + "cnc_wrtoolgeom_tlm", + "cnc_wrtoolgrp", + "cnc_wrtoolnum", + "cnc_wrtoolzone", + "cnc_wrtrqlimit", + "cnc_wrvolc", + "cnc_wrwaveprm", + "cnc_wrwaveprm2", + "cnc_wrwaveprm3", + "cnc_wrwkcdsfms", + "cnc_wrwkcdsfms2", + "cnc_wrwkcdsfms3", + "cnc_wrwkcdshft", + "cnc_wrwkcdshft2", + "cnc_wrwkcdshft3", + "cnc_wrwseterror", + "cnc_wrzofs", + "cnc_wrzofsr", + "cnc_zofs_rnge", + "dnm_clrerrorrecord", + "dnm_rderrorrecord", + "dnm_rdfirminfo", + "dnm_rdnodeinfo", + "dnm_rdnodetable", + "dnm_rdparam", + "dnm_wrparam", + "dns_rdinfo", + "dns_rdparam", + "dns_restart", + "dns_wrparam", + "eth_abortdnc", + "eth_abortdownload", + "eth_abortupload", + "eth_applyunsolicprm", + "eth_changelockstat1", + "eth_changelockstat2", + "eth_clrlog", + "eth_clrlsistate", + "eth_disconfsclnt", + "eth_disconfsclntall", + "eth_dncsegment", + "eth_downloadsegment", + "eth_dschkdsk", + "eth_dsformat", + "eth_embrestart", + "eth_exit", + "eth_getlockstat1", + "eth_getlockstat2", + "eth_init", + "eth_init2", + "eth_initiatednc", + "eth_initiatedownload", + "eth_initiateupload", + "eth_ping", + "eth_ping_cancel", + "eth_ping_result", + "eth_rddsformat", + "eth_rddsm198dir", + "eth_rddsm198host", + "eth_rddsmode", + "eth_rddsstate", + "eth_rdembdev", + "eth_rdembm198host", + "eth_rdfsclntinfo", + "eth_rdhost", + "eth_rdlog", + "eth_rdlsistate", + "eth_rdmbsclntinfo", + "eth_rdmbsclntinfo2", + "eth_rdparam", + "eth_rdrmdinquiry", + "eth_rdtaskstate", + "eth_rdtype", + "eth_rdtype2", + "eth_rdunsolicmode", + "eth_rdunsolicstate", + "eth_remotecontrol", + "eth_terminatednc", + "eth_terminatedownload", + "eth_uploadsegment", + "eth_workinit", + "eth_wrdsm198dir", + "eth_wrdsm198host", + "eth_wrdsmode", + "eth_wrembdev", + "eth_wrembm198host", + "eth_wrhost", + "eth_wrparam", + "eth_wrrmdinquiry", + "eth_wrunsolicmode", + "flnt_clrlog", + "flnt_clrmsg", + "flnt_rdentry", + "flnt_rdlog", + "flnt_rdmsg", + "flnt_rdnetwork", + "flnt_rdnodeinfo", + "flnt_rdparam", + "flnt_wrparam", + "net_backup_param", + "net_restore_param", + "pbm_chg_mode", + "pbm_exe_subfunc", + "pbm_ini_prm", + "pbm_rd_allslvtbl", + "pbm_rd_cominfo", + "pbm_rd_errcode", + "pbm_rd_nodeinfo", + "pbm_rd_nodetable", + "pbm_rd_param", + "pbm_rd_slot", + "pbm_rd_slotinfo", + "pbm_rd_subprm", + "pbm_wr_param", + "pbs_ini_prm", + "pbs_rd_cominfo", + "pbs_rd_cominfo2", + "pbs_rd_param", + "pbs_rd_param2", + "pbs_wr_param", + "pbs_wr_param2", + "pmc_abort_all_transfer", + "pmc_abort_end_process", + "pmc_abort_multiple_write_check", + "pmc_abort_trace_function", + "pmc_abort_transfer", + "pmc_allocate_work_memory", + "pmc_can_apply_override", + "pmc_change_operation", + "pmc_change_operation_with_passwd", + "pmc_change_scoped_symcmt", + "pmc_check_io_assignment_empty", + "pmc_check_permission", + "pmc_check_permission_with_passwd", + "pmc_control_snp_driver", + "pmc_convert_address_pmc_to_vio", + "pmc_convert_pmc_address_from_sync", + "pmc_convert_pmc_address_to_string", + "pmc_convert_pmc_address_to_sync", + "pmc_convert_vio_address_from_sync", + "pmc_convert_vio_address_to_string", + "pmc_convert_vio_address_to_sync", + "pmc_convret_address_vio_to_pmc", + "pmc_delete_all_io_assignment_data", + "pmc_delete_all_message_data", + "pmc_delete_all_symcmt_data", + "pmc_delete_data_table_group_data", + "pmc_delete_extra_relay_group_data", + "pmc_delete_io_assignment_data", + "pmc_delete_message_entry", + "pmc_delete_node_mutex", + "pmc_delete_scoped_symcmt", + "pmc_delete_seq_program_object", + "pmc_delete_sfc_monitor_time", + "pmc_delete_symcmt_entry", + "pmc_end_search_symbol", + "pmc_exchange_permission", + "pmc_fdcas_compare_file_by_name", + "pmc_fdcas_compare_file_by_number", + "pmc_fdcas_delete_all_files", + "pmc_fdcas_delete_file_by_name", + "pmc_fdcas_delete_file_by_number", + "pmc_fdcas_get_comm_parameter", + "pmc_fdcas_init_comm_parameter", + "pmc_fdcas_read_directory", + "pmc_fdcas_read_file_by_name", + "pmc_fdcas_read_file_by_number", + "pmc_fdcas_read_file_count", + "pmc_fdcas_read_file_status", + "pmc_fdcas_set_comm_parameter", + "pmc_fdcas_write_pmc_parameter", + "pmc_fdcas_write_seq_program", + "pmc_flashrom_compare_message", + "pmc_flashrom_compare_seq_program", + "pmc_flashrom_read_message", + "pmc_flashrom_read_seq_program", + "pmc_flashrom_write_message", + "pmc_flashrom_write_seq_program", + "pmc_force_release_object", + "pmc_force_status_bit", + "pmc_force_status_byte", + "pmc_get_address_entry", + "pmc_get_alarm_status", + "pmc_get_all_associated_symbols", + "pmc_get_all_associated_symcmts", + "pmc_get_available_comment_set", + "pmc_get_available_io_connection", + "pmc_get_common_memory_group", + "pmc_get_current_comment", + "pmc_get_current_pmc", + "pmc_get_current_pmc_unit", + "pmc_get_data_table_group_count", + "pmc_get_data_table_max_group_count", + "pmc_get_default_filename", + "pmc_get_extra_relay_group_count", + "pmc_get_extra_relay_max_group_count", + "pmc_get_font_information", + "pmc_get_instruction_set_version", + "pmc_get_io_assignment_address", + "pmc_get_io_device_id", + "pmc_get_max_sfc_monitor_time_count", + "pmc_get_maximum_trace_frames", + "pmc_get_message_data_size", + "pmc_get_message_entry_address", + "pmc_get_message_entry_count", + "pmc_get_message_maximum_size", + "pmc_get_message_remain_memory", + "pmc_get_multiple_coil_result", + "pmc_get_multiple_instruction_result", + "pmc_get_multiple_symcmts", + "pmc_get_nonvolatile_memory_status", + "pmc_get_number_of_address_entries", + "pmc_get_number_of_pmc", + "pmc_get_number_of_pmc_path", + "pmc_get_object_update_counter", + "pmc_get_object_version", + "pmc_get_override_status", + "pmc_get_permission_status", + "pmc_get_pmc_address_info", + "pmc_get_pmc_address_map", + "pmc_get_pmc_path_types", + "pmc_get_pmc_unit_types", + "pmc_get_programable_io_channels", + "pmc_get_result_trace_parameter", + "pmc_get_scoped_symcmt", + "pmc_get_search_symbol_result", + "pmc_get_search_symbol_status", + "pmc_get_sfc_diagram_memory", + "pmc_get_sfc_monitor_status", + "pmc_get_sfc_monitor_time_information", + "pmc_get_snp_communication_status", + "pmc_get_snp_driver_status", + "pmc_get_splist_status", + "pmc_get_subprogram_list", + "pmc_get_symbol_object_version", + "pmc_get_symcmt_bit", + "pmc_get_symcmt_byte", + "pmc_get_symcmt_data_size", + "pmc_get_symcmt_entry_count", + "pmc_get_symcmt_maximum_size", + "pmc_get_symcmt_remain_memory", + "pmc_get_timer_type", + "pmc_get_trace_result", + "pmc_get_trace_specification", + "pmc_get_trace_status", + "pmc_get_transfer_status", + "pmc_get_unit_type", + "pmc_get_xref_address_result", + "pmc_get_xref_instruction_result", + "pmc_get_xref_status", + "pmc_getdtailerr", + "pmc_getpmcptr", + "pmc_giveout_node_mutex", + "pmc_handle_work_area_size", + "pmc_initialize_data_table_group", + "pmc_initialize_extra_relay_group", + "pmc_initialize_handle_work_area", + "pmc_initialize_node_mutex", + "pmc_initialize_node_work_area", + "pmc_initialize_nonvolatile_memory", + "pmc_insert_seq_program_space", + "pmc_is_bit_accessible_pmc_address", + "pmc_is_effective_pmc_address", + "pmc_is_seq_program_available", + "pmc_is_trace_function_active", + "pmc_is_visible_pmc_address", + "pmc_is_writable_pmc_address", + "pmc_kpmsiz", + "pmc_logical_and_pmc_address_uint8", + "pmc_logical_and_vio_address_uint8", + "pmc_logical_or_pmc_address_uint8", + "pmc_logical_or_vio_address_uint8", + "pmc_logical_xor_pmc_address_uint8", + "pmc_logical_xor_vio_address_uint8", + "pmc_mcard_compare_file", + "pmc_mcard_delete_file", + "pmc_mcard_format", + "pmc_mcard_read_directory", + "pmc_mcard_read_file", + "pmc_mcard_read_file_count", + "pmc_mcard_read_file_status", + "pmc_mcard_write_pmc_message", + "pmc_mcard_write_pmc_parameter", + "pmc_mcard_write_seq_program", + "pmc_mcard_write_trace_result", + "pmc_node_work_area_size", + "pmc_notify_handle_close", + "pmc_parse_string_into_pmc_address", + "pmc_parse_string_into_scoped_pmc_address", + "pmc_rdalmmsg", + "pmc_rdcntl_exrelay_grp", + "pmc_rdcntldata", + "pmc_rdcntlexrelay", + "pmc_rdcntlgrp", + "pmc_rdkpm", + "pmc_rdpmcinfo", + "pmc_rdpmcparam", + "pmc_rdpmcrng", + "pmc_rdpmctitle", + "pmc_rdpmctitle2", + "pmc_rdprmend", + "pmc_rdprmstart", + "pmc_read_actual_io_assignment_data", + "pmc_read_actual_io_assignment_data_by_address", + "pmc_read_cnc_dido", + "pmc_read_data_table_group_data", + "pmc_read_end_process_status", + "pmc_read_extra_relay_group_data", + "pmc_read_instruction_monitor_status", + "pmc_read_io_assignment_data", + "pmc_read_machine_signal", + "pmc_read_message_entry_data", + "pmc_read_message_title", + "pmc_read_nonvolatile_memory", + "pmc_read_performance_information", + "pmc_read_pmc_address_bit", + "pmc_read_pmc_address_uint16", + "pmc_read_pmc_address_uint32", + "pmc_read_pmc_address_uint8", + "pmc_read_pmc_software_version", + "pmc_read_raw_memory", + "pmc_read_scan_time", + "pmc_read_scoped_symcmt", + "pmc_read_seq_object_size", + "pmc_read_seq_program_and_memory_type", + "pmc_read_seq_program_config", + "pmc_read_seq_program_object", + "pmc_read_seq_program_size", + "pmc_read_seq_program_status", + "pmc_read_seq_program_type", + "pmc_read_setting", + "pmc_read_snp_parameter", + "pmc_read_symcmt_entry", + "pmc_read_system_parameter", + "pmc_read_title_data", + "pmc_read_trace_parameter", + "pmc_read_vio_address_bit", + "pmc_read_vio_address_uint16", + "pmc_read_vio_address_uint32", + "pmc_read_vio_address_uint8", + "pmc_release_node_work_area", + "pmc_request_permission", + "pmc_request_permission_with_passwd", + "pmc_reserve_nonvolatile_memory", + "pmc_reset_all_override", + "pmc_reset_override", + "pmc_reset_sfc_monitor_time", + "pmc_restore_seq_program_edit", + "pmc_resume_end_process", + "pmc_resume_transfer", + "pmc_retrieve_permission", + "pmc_return_permission", + "pmc_rs232c_compare", + "pmc_rs232c_get_comm_parameter", + "pmc_rs232c_init_comm_parameter", + "pmc_rs232c_read", + "pmc_rs232c_set_comm_parameter", + "pmc_rs232c_write_pmc_parameter", + "pmc_rs232c_write_seq_program", + "pmc_run_seq_program", + "pmc_search_comment_set", + "pmc_search_ladder_address", + "pmc_search_ladder_function", + "pmc_search_message_entry_position", + "pmc_search_message_string", + "pmc_search_scoped_symcmt_by_address", + "pmc_search_scoped_symcmt_by_symbol", + "pmc_search_scoped_symcmt_string", + "pmc_search_symbol_string", + "pmc_search_symcmt_entry_by_symbol", + "pmc_search_symcmt_entry_position", + "pmc_search_symcmt_string", + "pmc_search_unit_type", + "pmc_select_pmc", + "pmc_select_pmc_unit", + "pmc_set_data_table_group_count", + "pmc_set_extra_relay_group_count", + "pmc_set_language", + "pmc_set_override", + "pmc_set_sfc_monitor_time", + "pmc_set_timer_type", + "pmc_start_end_process", + "pmc_start_list_subprogram", + "pmc_start_multiple_coil_check", + "pmc_start_multiple_instruction_check", + "pmc_start_search_symbol", + "pmc_start_trace_function", + "pmc_start_xref_address", + "pmc_start_xref_instruction", + "pmc_stop_seq_program", + "pmc_wrcntl_exrelay_grp", + "pmc_wrcntldata", + "pmc_wrcntlexrelay", + "pmc_wrcntlgrp", + "pmc_write_cnc_dido", + "pmc_write_data_table_group_data", + "pmc_write_extra_relay_group_data", + "pmc_write_io_assignment_data", + "pmc_write_message_entry", + "pmc_write_nonvolatile_memory", + "pmc_write_pmc_address_bit", + "pmc_write_pmc_address_uint16", + "pmc_write_pmc_address_uint32", + "pmc_write_pmc_address_uint8", + "pmc_write_raw_io_assignment_data", + "pmc_write_raw_memory", + "pmc_write_scoped_symcmt", + "pmc_write_seq_program_object", + "pmc_write_setting", + "pmc_write_snp_parameter", + "pmc_write_symcmt_entry", + "pmc_write_system_parameter", + "pmc_write_title_data", + "pmc_write_trace_parameter", + "pmc_write_vio_address_bit", + "pmc_write_vio_address_uint16", + "pmc_write_vio_address_uint32", + "pmc_write_vio_address_uint8", + "pmc_wrkpm", + "pmc_wrpmcparam", + "pmc_wrpmcrng", + "pmc_wrprmend", + "pmc_wrprmstart", + "usb_clrlog", + "usb_format_result", + "usb_format_start", + "usb_rdinfo", + "usb_rdlog" + ], + "notes": [ + "Exports extracted directly from the 64-bit DLL PE export directory.", + "Wrapper-supported methods are intersected with upstream fwlib.cs extern declarations.", + "Axis and path hints are filename-family heuristics, not protocol-level proofs." + ] +} \ No newline at end of file diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlib0iD64.json b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlib0iD64.json new file mode 100644 index 0000000..ea5292b --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlib0iD64.json @@ -0,0 +1,1936 @@ +{ + "profile_name": "fwlib0iD64", + "dll_name": "fwlib0iD64.dll", + "series_hint": "0i-d-family", + "max_axis_hint": 24, + "max_path_hint": 1, + "export_count": 1422, + "connection_methods": [ + "cnc_allclibhndl", + "cnc_allclibhndl2" + ], + "mock_methods": [ + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_diagnoss", + "cnc_exeprgname2", + "cnc_freelibhndl", + "cnc_getpath", + "cnc_rdalmmsg2", + "cnc_rdaxisname", + "cnc_rdblkcount", + "cnc_rddiaginfo", + "cnc_rddiagnum", + "cnc_rddynamic2", + "cnc_rdexecprog", + "cnc_rdmacro", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdparam", + "cnc_rdprgnum", + "cnc_rdprogdir3", + "cnc_rdproginfo", + "cnc_rdseqnum", + "cnc_rdspdlname", + "cnc_rdspgear", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsvmeter", + "cnc_rdtimer", + "cnc_setpath", + "cnc_statinfo", + "cnc_sysinfo", + "cnc_wrmacro", + "cnc_wrparam", + "pmc_rdpmcrng", + "pmc_wrpmcrng" + ], + "wrapper_supported_count": 452, + "wrapper_supported_methods": [ + "cnc_absolute", + "cnc_absolute2", + "cnc_accdecdly", + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_adcnv", + "cnc_alarm", + "cnc_alarm2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_async_busy_state", + "cnc_buff", + "cnc_canmovrlap", + "cnc_cdownload", + "cnc_cexesramsize", + "cnc_clearomhis", + "cnc_clearophis", + "cnc_clr3dplsmov", + "cnc_clr5axpls", + "cnc_clralm", + "cnc_clrcntinfo", + "cnc_condense", + "cnc_copyprog", + "cnc_cupload", + "cnc_cverify", + "cnc_delall", + "cnc_delete", + "cnc_delmagazine", + "cnc_delprogline", + "cnc_deltlifedt", + "cnc_deltlifegrp", + "cnc_deltool", + "cnc_diagnosr", + "cnc_diagnoss", + "cnc_distance", + "cnc_dnc2", + "cnc_dncend2", + "cnc_dncstart2", + "cnc_download", + "cnc_download3", + "cnc_download4", + "cnc_dwnend", + "cnc_dwnend3", + "cnc_dwnend4", + "cnc_dwnstart", + "cnc_dwnstart3", + "cnc_dwnstart4", + "cnc_end_async_wrparam", + "cnc_exaxisname", + "cnc_exeprgname", + "cnc_exeprgname2", + "cnc_freelibhndl", + "cnc_fromget", + "cnc_fromgetend", + "cnc_fromgetstart", + "cnc_fromput", + "cnc_fromputend", + "cnc_fromputstart", + "cnc_fromremove", + "cnc_getdspmode", + "cnc_getdtailerr", + "cnc_getfigure", + "cnc_getfrominfo", + "cnc_getlibopt", + "cnc_getmactype", + "cnc_getpath", + "cnc_getpmactype", + "cnc_getsraminfo", + "cnc_gettimer", + "cnc_hpccactfine", + "cnc_hpccatset", + "cnc_hpccattune", + "cnc_hpccselfine", + "cnc_instlifedt", + "cnc_machine", + "cnc_modal", + "cnc_newprog", + "cnc_pdf_add", + "cnc_pdf_cond", + "cnc_pdf_copy", + "cnc_pdf_del", + "cnc_pdf_delline", + "cnc_pdf_move", + "cnc_pdf_rdactpt", + "cnc_pdf_rdmain", + "cnc_pdf_rename", + "cnc_pdf_searchresult", + "cnc_pdf_searchword", + "cnc_pdf_slctmain", + "cnc_pdf_wractpt", + "cnc_progdigit", + "cnc_prstwkcd", + "cnc_rd1length", + "cnc_rd1radius", + "cnc_rd1tlifedat2", + "cnc_rd1tlifedata", + "cnc_rd2length", + "cnc_rd2radius", + "cnc_rd2tlifedata", + "cnc_rd3dcdcnv", + "cnc_rd3dmovrlap", + "cnc_rd3dofschg", + "cnc_rd3dpulse", + "cnc_rd3dtofs", + "cnc_rd3dtooltip", + "cnc_rd5axmandt", + "cnc_rd5axovrlap", + "cnc_rdactfixofs", + "cnc_rdactpt", + "cnc_rdacttlzone", + "cnc_rdalmhisno", + "cnc_rdalmhistry", + "cnc_rdalmhistry2", + "cnc_rdalmhistry3", + "cnc_rdalmhistry5", + "cnc_rdalminfo", + "cnc_rdalmmsg", + "cnc_rdalmmsg2", + "cnc_rdaxisdata", + "cnc_rdaxisname", + "cnc_rdblkcount", + "cnc_rdbrstrinfo", + "cnc_rdcdrotate", + "cnc_rdcexesram", + "cnc_rdcncid", + "cnc_rdcommand", + "cnc_rdcoordnum", + "cnc_rdcount", + "cnc_rdctrldi", + "cnc_rdctrldo", + "cnc_rdcurrent", + "cnc_rddiag_ext", + "cnc_rddiaginfo", + "cnc_rddiagnum", + "cnc_rddncdgndt", + "cnc_rddynamic", + "cnc_rddynamic2", + "cnc_rdexchgtgrp", + "cnc_rdexecmcode", + "cnc_rdexecprog", + "cnc_rdexecpt", + "cnc_rdfbusmem", + "cnc_rdfixcycle", + "cnc_rdfixofs", + "cnc_rdgcode", + "cnc_rdgrphcmd", + "cnc_rdgrpid", + "cnc_rdgrpid2", + "cnc_rdgrpinfo", + "cnc_rdgrpinfo2", + "cnc_rdgrpinfo3", + "cnc_rdgrpinfo4", + "cnc_rdhissgnl", + "cnc_rdhissgnl3", + "cnc_rdhndintrpt", + "cnc_rdhsparam", + "cnc_rdhsprminfo", + "cnc_rdintchk", + "cnc_rdintinfo", + "cnc_rdlenofs", + "cnc_rdlife", + "cnc_rdloopgain", + "cnc_rdmacro", + "cnc_rdmacroinfo", + "cnc_rdmacror", + "cnc_rdmacror2", + "cnc_rdmagazine", + "cnc_rdmaxgrp", + "cnc_rdmaxtool", + "cnc_rdmdipntr", + "cnc_rdmdiprgstat", + "cnc_rdmdiprog", + "cnc_rdmdlconfig2", + "cnc_rdmenuswitch", + "cnc_rdmgrpdata", + "cnc_rdmirimage", + "cnc_rdmovrlap", + "cnc_rdmultitldt", + "cnc_rdngrp", + "cnc_rdnspdl", + "cnc_rdntool", + "cnc_rdomhisinfo", + "cnc_rdomhisno", + "cnc_rdomhistry", + "cnc_rdomhistry2", + "cnc_rdophisno", + "cnc_rdophistry", + "cnc_rdophistry2", + "cnc_rdophistry4", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdopmsg2", + "cnc_rdopmsg3", + "cnc_rdopnlgnrl", + "cnc_rdopnlgsname", + "cnc_rdopnlsgnl", + "cnc_rdparainfo", + "cnc_rdparam", + "cnc_rdparam3", + "cnc_rdparam_ext", + "cnc_rdparanum", + "cnc_rdparar", + "cnc_rdpdf_alldir", + "cnc_rdpdf_curdir", + "cnc_rdpdf_drive", + "cnc_rdpdf_inf", + "cnc_rdpdf_line", + "cnc_rdpdf_subdir", + "cnc_rdpdf_subdirn", + "cnc_rdpglockstat", + "cnc_rdpitchinfo", + "cnc_rdpitchr", + "cnc_rdpm_item", + "cnc_rdpm_mcnitem", + "cnc_rdpmacro", + "cnc_rdpmacroinfo", + "cnc_rdpmacror", + "cnc_rdpmacror2", + "cnc_rdposerrs", + "cnc_rdposerrs2", + "cnc_rdposerrz", + "cnc_rdposition", + "cnc_rdprgdirtime", + "cnc_rdprgnum", + "cnc_rdproctime", + "cnc_rdprogdir", + "cnc_rdprogdir2", + "cnc_rdprogdir3", + "cnc_rdprogdir4", + "cnc_rdproginfo", + "cnc_rdprogline", + "cnc_rdprogline2", + "cnc_rdprstrinfo", + "cnc_rdradofs", + "cnc_rdrepeatval", + "cnc_rdrstrmcode", + "cnc_rdsafetyzone", + "cnc_rdscaling", + "cnc_rdseqnum", + "cnc_rdset", + "cnc_rdsetinfo", + "cnc_rdsetnum", + "cnc_rdsetr", + "cnc_rdsetzone", + "cnc_rdspcss", + "cnc_rdspdlalm", + "cnc_rdspdlname", + "cnc_rdspeed", + "cnc_rdspgear", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsrvspeed", + "cnc_rdsvmeter", + "cnc_rdsynerrrg", + "cnc_rdsynerrsy", + "cnc_rdsyshard", + "cnc_rdsyssoft", + "cnc_rdsyssoft2", + "cnc_rdsyssoft3", + "cnc_rdtimer", + "cnc_rdtlctldata", + "cnc_rdtlgrp", + "cnc_rdtlinfo", + "cnc_rdtltool", + "cnc_rdtlusegrp", + "cnc_rdtofs", + "cnc_rdtofsinfo", + "cnc_rdtofsinfo2", + "cnc_rdtofsr", + "cnc_rdtool", + "cnc_rdtool_f2", + "cnc_rdtooldata", + "cnc_rdtoolgrp", + "cnc_rdtoolinfo", + "cnc_rdtoolrng", + "cnc_rdtoolzone", + "cnc_rdusegrpid", + "cnc_rdusetlno", + "cnc_rdwavedata", + "cnc_rdwaveprm", + "cnc_rdwaveprm2", + "cnc_rdwkcdsfms", + "cnc_rdwkcdshft", + "cnc_rdzofs", + "cnc_rdzofsinfo", + "cnc_rdzofsr", + "cnc_regmagazine", + "cnc_regtool", + "cnc_regtool_f2", + "cnc_relative", + "cnc_relative2", + "cnc_renameprog", + "cnc_reset", + "cnc_reset2", + "cnc_resetpglock", + "cnc_rewind", + "cnc_rstrseqsrch", + "cnc_rstrseqsrch2", + "cnc_sdcancelsmpl", + "cnc_sdclrchnl", + "cnc_sdendsmpl", + "cnc_sdread1shot", + "cnc_sdreadsmpl", + "cnc_sdsetchnl", + "cnc_sdstartsmpl", + "cnc_search", + "cnc_search2", + "cnc_searchresult", + "cnc_searchword", + "cnc_seqsrch", + "cnc_seqsrch2", + "cnc_setlibopt", + "cnc_setmactype", + "cnc_setpath", + "cnc_setpglock", + "cnc_setpmactype", + "cnc_settimer", + "cnc_skip", + "cnc_sramget", + "cnc_sramgetend", + "cnc_sramgetstart", + "cnc_srvdelay", + "cnc_start", + "cnc_start_async_wrparam", + "cnc_startdyngrph", + "cnc_startnccmd", + "cnc_startomhis", + "cnc_startophis", + "cnc_statinfo", + "cnc_stopdyngrph", + "cnc_stopnccmd", + "cnc_stopomhis", + "cnc_stopophis", + "cnc_svdtendrd", + "cnc_svdtendwr", + "cnc_svdtrddata", + "cnc_svdtstartrd", + "cnc_svdtstartwr", + "cnc_svdtstopexec", + "cnc_svdtwrdata", + "cnc_sysinfo", + "cnc_sysinfo_ex", + "cnc_t1info", + "cnc_t2info", + "cnc_tofs_rnge", + "cnc_toolnum", + "cnc_upend", + "cnc_upend3", + "cnc_upend4", + "cnc_upload", + "cnc_upload3", + "cnc_upload4", + "cnc_upstart", + "cnc_upstart3", + "cnc_upstart4", + "cnc_verify", + "cnc_verify4", + "cnc_vrfend", + "cnc_vrfend4", + "cnc_vrfstart", + "cnc_vrfstart4", + "cnc_wavestart", + "cnc_wavestat", + "cnc_wavestop", + "cnc_wksft_rnge", + "cnc_wr1tlifedat2", + "cnc_wr1tlifedata", + "cnc_wr2tlifedata", + "cnc_wractpt", + "cnc_wrcexesram", + "cnc_wrcountr", + "cnc_wrfbusmem", + "cnc_wrfixofs", + "cnc_wrgrpinfo", + "cnc_wrgrpinfo2", + "cnc_wrgrpinfo3", + "cnc_wrhissgnl", + "cnc_wrhissgnl3", + "cnc_wrintchk", + "cnc_wrkeyhistry", + "cnc_wrmacro", + "cnc_wrmacror", + "cnc_wrmacror2", + "cnc_wrmagazine", + "cnc_wrmdipntr", + "cnc_wrmdiprog", + "cnc_wrmenuswitch", + "cnc_wrmgrpdata", + "cnc_wrmultitldt", + "cnc_wropnlgnrl", + "cnc_wropnlgsname", + "cnc_wropnlsgnl", + "cnc_wrparam", + "cnc_wrparas", + "cnc_wrpdf_attr", + "cnc_wrpdf_curdir", + "cnc_wrpdf_line", + "cnc_wrpitchr", + "cnc_wrpm_item", + "cnc_wrpm_mcnitem", + "cnc_wrpmacro", + "cnc_wrpmacror", + "cnc_wrpmacror2", + "cnc_wrprogline", + "cnc_wrrelpos", + "cnc_wrsafetyzone", + "cnc_wrset", + "cnc_wrsets", + "cnc_wrsetzone", + "cnc_wrtimer", + "cnc_wrtlctldata", + "cnc_wrtofs", + "cnc_wrtofsr", + "cnc_wrtool", + "cnc_wrtool2", + "cnc_wrtool_f2", + "cnc_wrtooldata", + "cnc_wrtoolzone", + "cnc_wrwaveprm", + "cnc_wrwaveprm2", + "cnc_wrwkcdsfms", + "cnc_wrwkcdshft", + "cnc_wrzofs", + "cnc_wrzofsr", + "cnc_zofs_rnge", + "pmc_get_current_pmc_unit", + "pmc_get_number_of_pmc", + "pmc_get_pmc_unit_types", + "pmc_get_timer_type", + "pmc_getdtailerr", + "pmc_kpmsiz", + "pmc_rdalmmsg", + "pmc_rdcntldata", + "pmc_rdcntlgrp", + "pmc_rdkpm", + "pmc_rdpmcinfo", + "pmc_rdpmcparam", + "pmc_rdpmcrng", + "pmc_rdpmctitle", + "pmc_rdprmend", + "pmc_rdprmstart", + "pmc_select_pmc_unit", + "pmc_set_timer_type", + "pmc_wrcntldata", + "pmc_wrcntlgrp", + "pmc_wrkpm", + "pmc_wrpmcparam", + "pmc_wrpmcrng", + "pmc_wrprmend", + "pmc_wrprmstart" + ], + "exports": [ + "PmcProcessCB", + "cnc_absolute", + "cnc_absolute2", + "cnc_absolute_bg", + "cnc_absolute_mgi", + "cnc_accdecdly", + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_adcnv", + "cnc_alarm", + "cnc_alarm2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_async_busy_state", + "cnc_aux_statinfo", + "cnc_axisnum2", + "cnc_backuplrn", + "cnc_block_status", + "cnc_btlfpotsrh", + "cnc_buff", + "cnc_cancel_prm", + "cnc_canmcdfinfo", + "cnc_canmovrlap", + "cnc_cdautoset", + "cnc_cdownload", + "cnc_cexesramsize", + "cnc_chglang", + "cnc_chgoverstore", + "cnc_chkmcdfile", + "cnc_chkusbfile", + "cnc_chkversion", + "cnc_clear_maint", + "cnc_clearalm", + "cnc_clearomhis", + "cnc_clearophis", + "cnc_clr3dplsmov", + "cnc_clr5axpls", + "cnc_clr5dplsmov", + "cnc_clralm", + "cnc_clrcntinfo", + "cnc_clrfromsvspid", + "cnc_clrlrncrnt", + "cnc_clrmofs", + "cnc_clroverstore", + "cnc_clrptdata", + "cnc_clrrstraxis", + "cnc_clrtofs", + "cnc_condense", + "cnc_confirm_restart", + "cnc_copyprog", + "cnc_cupload", + "cnc_cverify", + "cnc_data_copy", + "cnc_delall", + "cnc_delete", + "cnc_deleteo8", + "cnc_delmag_property", + "cnc_delmagazine", + "cnc_delmcdfile", + "cnc_delmcdfilebynum", + "cnc_delpot_property", + "cnc_delprogline", + "cnc_delsysalm", + "cnc_deltlifedt", + "cnc_deltlifegrp", + "cnc_deltool", + "cnc_diagnosr", + "cnc_diagnoss", + "cnc_dismountmcd", + "cnc_distance", + "cnc_distancem", + "cnc_dnc2", + "cnc_dncend2", + "cnc_dncprgname", + "cnc_dncstart2", + "cnc_download", + "cnc_download3", + "cnc_download3m", + "cnc_download4", + "cnc_dschdir", + "cnc_dscopyfile", + "cnc_dsftpcancel", + "cnc_dsftpstat", + "cnc_dsget_req", + "cnc_dslistdel_req", + "cnc_dslistget_req", + "cnc_dslistput_req", + "cnc_dsmget_req", + "cnc_dsmkdir", + "cnc_dsmput_req", + "cnc_dsput_req", + "cnc_dsremove", + "cnc_dsrename", + "cnc_dsrmdir", + "cnc_dtsvinfo", + "cnc_dvpunchvolc", + "cnc_dvreadvolc", + "cnc_dwnend", + "cnc_dwnend3", + "cnc_dwnend3m", + "cnc_dwnend4", + "cnc_dwnstart", + "cnc_dwnstart3", + "cnc_dwnstart3m", + "cnc_dwnstart4", + "cnc_edplutosmpl", + "cnc_end_async_data_punch", + "cnc_end_async_data_read", + "cnc_end_async_pdf_punch", + "cnc_end_async_pdf_read", + "cnc_end_async_punch_prog3", + "cnc_end_async_punch_prog3_bg", + "cnc_end_async_read_prog3", + "cnc_end_async_read_prog3_bg", + "cnc_end_async_wrparam", + "cnc_exaxisname", + "cnc_exaxisname2", + "cnc_exeprgname", + "cnc_exeprgname2", + "cnc_exeprgname_bg", + "cnc_freelibhndl", + "cnc_fromget", + "cnc_fromgetend", + "cnc_fromgetstart", + "cnc_fromput", + "cnc_fromputend", + "cnc_fromputstart", + "cnc_fromremove", + "cnc_fssb_autoset", + "cnc_fssb_reset", + "cnc_ftrq_data_copy", + "cnc_ftrq_from_load", + "cnc_ftrq_from_save", + "cnc_get_crosschk_alarm", + "cnc_get_flowmonitor", + "cnc_get_mccteststs", + "cnc_get_safetysts", + "cnc_get_safetysts2", + "cnc_getdspmode", + "cnc_getdtailerr", + "cnc_getdtailerr2", + "cnc_getfigure", + "cnc_getfrominfo", + "cnc_getlibopt", + "cnc_getmactype", + "cnc_getpath", + "cnc_getpmactype", + "cnc_getprntname", + "cnc_getrtmioinfo", + "cnc_getrtmiorngnum", + "cnc_getrtmrvar", + "cnc_getrtmrvars", + "cnc_getsraminfo", + "cnc_getsysfolder_num", + "cnc_gettimer", + "cnc_gettimer_sec", + "cnc_gettolnum_qset", + "cnc_getzofsnum_qset", + "cnc_hdck_nochange_info", + "cnc_hpccactfine", + "cnc_hpccatset", + "cnc_hpccattune", + "cnc_hpccselfine", + "cnc_instlifedt", + "cnc_loadtorq", + "cnc_machine", + "cnc_machine3", + "cnc_machine_bg", + "cnc_magazinesrch", + "cnc_mcdp_mount", + "cnc_mcdp_mountchk", + "cnc_mcdp_unmount", + "cnc_mdd_getexceptparam", + "cnc_mdd_getswitch", + "cnc_mdd_lock", + "cnc_mdd_rdinfo", + "cnc_mdd_register", + "cnc_mdd_setexceptparam", + "cnc_mdd_setpassword", + "cnc_mdd_setswitch", + "cnc_mdd_unlock", + "cnc_mdd_unregister", + "cnc_mdd_update", + "cnc_mdg_moniclear", + "cnc_mdg_monistat", + "cnc_mdg_msgsrch", + "cnc_mdg_rdalminfo", + "cnc_mdg_rdalminfoview", + "cnc_mdg_rdalmnum", + "cnc_mdg_rdcontinfo", + "cnc_mdg_rddtmsg", + "cnc_mdg_rdflow", + "cnc_mdg_rdheatsimlt", + "cnc_mdg_rdlatchedalm", + "cnc_mdg_rdloadlvl", + "cnc_mdg_rdmsg", + "cnc_mdg_rdmsgnum", + "cnc_mdg_rdmsgordr", + "cnc_mdg_rdorderalmno", + "cnc_mdg_rdwvdata", + "cnc_modal", + "cnc_modal_bg", + "cnc_mountmcd", + "cnc_msr_delhis_all", + "cnc_msr_rdhis_allnum", + "cnc_msr_rdhis_inf", + "cnc_msr_rdhis_msudat", + "cnc_msr_rdhis_ncdat", + "cnc_msr_rdhis_ohisnum", + "cnc_msr_rdhis_ohisrec", + "cnc_msr_rdhis_pmc", + "cnc_msr_rdmon_msudat", + "cnc_msr_rdmon_msunum", + "cnc_msr_rdmon_pmcinf", + "cnc_msr_start_sample", + "cnc_msr_stop_sample", + "cnc_newprog", + "cnc_nextdistance", + "cnc_nextdistance_bg", + "cnc_opentdicubeinfo", + "cnc_pdf_add", + "cnc_pdf_add_bgedt", + "cnc_pdf_chginquiry", + "cnc_pdf_cond", + "cnc_pdf_copy", + "cnc_pdf_del", + "cnc_pdf_delall", + "cnc_pdf_delchar", + "cnc_pdf_delline", + "cnc_pdf_dncread", + "cnc_pdf_dncset", + "cnc_pdf_dncset2", + "cnc_pdf_dssearch", + "cnc_pdf_mergeprog", + "cnc_pdf_move", + "cnc_pdf_punch", + "cnc_pdf_punch_bg", + "cnc_pdf_rdactpt", + "cnc_pdf_rdactpt_bg", + "cnc_pdf_rdactpt_bgedt", + "cnc_pdf_rdactpt_mem", + "cnc_pdf_rdmain", + "cnc_pdf_rdprgname", + "cnc_pdf_rdrmtdgn", + "cnc_pdf_read", + "cnc_pdf_read_bg", + "cnc_pdf_rename", + "cnc_pdf_replacechar", + "cnc_pdf_replaceword_all", + "cnc_pdf_searchresult", + "cnc_pdf_searchresult2", + "cnc_pdf_searchresult_bgedt", + "cnc_pdf_searchword", + "cnc_pdf_searchword2", + "cnc_pdf_searchword_bgedt", + "cnc_pdf_slctmain", + "cnc_pdf_startrmtdgn", + "cnc_pdf_stoprmtdgn", + "cnc_pdf_wractpt", + "cnc_pdf_wractpt_bg", + "cnc_pdf_wractpt_bgedt", + "cnc_pmclad_screen", + "cnc_pmmchkalm", + "cnc_pmmget", + "cnc_pmmgetend", + "cnc_pmmgetstart", + "cnc_pmminit", + "cnc_pmmiochanl", + "cnc_pmmprmpage", + "cnc_pmmsysdt", + "cnc_powc_clear_inte", + "cnc_powc_del_cycle_data", + "cnc_powc_rd_clear_time", + "cnc_powc_rd_cycle_data", + "cnc_powc_rd_history", + "cnc_powc_rd_outer_set", + "cnc_powc_wr_outer_set", + "cnc_preset_prm", + "cnc_progdigit", + "cnc_prot_pswcan", + "cnc_prot_pswchg", + "cnc_prot_pswinit", + "cnc_prot_pswinp", + "cnc_prstwkcd", + "cnc_ptdownload", + "cnc_ptdwnend", + "cnc_ptdwnstart", + "cnc_ptlink", + "cnc_punch_data", + "cnc_punch_data_end", + "cnc_punch_data_start", + "cnc_punch_prog", + "cnc_punch_prog2", + "cnc_punch_prog2_bg", + "cnc_punch_prog3", + "cnc_punch_prog3_bg", + "cnc_punch_prog_bg", + "cnc_punchlrncrnt", + "cnc_pwcm_clear_consump", + "cnc_pwoff_alarm", + "cnc_rd1length", + "cnc_rd1punchtl_ex", + "cnc_rd1radius", + "cnc_rd1tlifedat2", + "cnc_rd1tlifedata", + "cnc_rd2length", + "cnc_rd2punchtl_ex", + "cnc_rd2radius", + "cnc_rd2tlifedata", + "cnc_rd3dcdcnv", + "cnc_rd3dmovrlap", + "cnc_rd3dofschg", + "cnc_rd3dpulse", + "cnc_rd3dtofs", + "cnc_rd3dtooltip", + "cnc_rd5axmandt", + "cnc_rd5axovrlap", + "cnc_rd5dmacmov", + "cnc_rd5dpulse", + "cnc_rd5dtooltip", + "cnc_rd_grpaxisinfo", + "cnc_rd_grppos", + "cnc_rd_grppos2", + "cnc_rdactfixofs", + "cnc_rdactpos_w", + "cnc_rdactpt", + "cnc_rdactspdl", + "cnc_rdacttlzone", + "cnc_rdalmhisno", + "cnc_rdalmhisno3", + "cnc_rdalmhistry", + "cnc_rdalmhistry2", + "cnc_rdalmhistry3", + "cnc_rdalmhistry4", + "cnc_rdalmhistry5", + "cnc_rdalminfo", + "cnc_rdalminfo2_bg", + "cnc_rdalminfo_bg", + "cnc_rdalmmsg", + "cnc_rdalmmsg2", + "cnc_rdaxisdata", + "cnc_rdaxisname", + "cnc_rdaxisstatus_bg", + "cnc_rdblkcount", + "cnc_rdblockcount", + "cnc_rdbrstrinfo", + "cnc_rdcbmem", + "cnc_rdcbprm", + "cnc_rdcdrotate", + "cnc_rdcdslctprmm", + "cnc_rdcenblinfo", + "cnc_rdcenter", + "cnc_rdcexesram", + "cnc_rdcncid", + "cnc_rdcncmem", + "cnc_rdcncmem2", + "cnc_rdcntover", + "cnc_rdcnttype", + "cnc_rdcommand", + "cnc_rdcoordnum", + "cnc_rdcount", + "cnc_rdcstm_decfig", + "cnc_rdcstmdecfig", + "cnc_rdctname", + "cnc_rdctrldi", + "cnc_rdctrldo", + "cnc_rdcurrent", + "cnc_rddiag_ext", + "cnc_rddiaginfo", + "cnc_rddiaginfo2", + "cnc_rddiagnum", + "cnc_rddncdgndt", + "cnc_rddsdevinfo", + "cnc_rddsdir", + "cnc_rddsdncfile", + "cnc_rddsfile", + "cnc_rddynamic", + "cnc_rddynamic2", + "cnc_rddynamic3", + "cnc_rddynamic3m", + "cnc_rddynamico8", + "cnc_rdembedf_inf", + "cnc_rdenblinfo", + "cnc_rdexchgtgrp", + "cnc_rdexecmcode", + "cnc_rdexecprgnum", + "cnc_rdexecprog", + "cnc_rdexecprog2", + "cnc_rdexecprog_bg", + "cnc_rdexecpt", + "cnc_rdexecptm", + "cnc_rdfbusmem", + "cnc_rdfile_sram", + "cnc_rdfixcycle", + "cnc_rdfixofs", + "cnc_rdfromservoid", + "cnc_rdfromspindleid", + "cnc_rdfsraminfo", + "cnc_rdfsraminfo2", + "cnc_rdfssb_amp", + "cnc_rdfssb_axis", + "cnc_rdfssb_info", + "cnc_rdfssb_mainte", + "cnc_rdfssb_plsmod", + "cnc_rdftrq_data", + "cnc_rdftrq_info", + "cnc_rdftrq_storecount", + "cnc_rdgcode", + "cnc_rdgcodem", + "cnc_rdgrphcmd", + "cnc_rdgrpid", + "cnc_rdgrpid2", + "cnc_rdgrpinfo", + "cnc_rdgrpinfo2", + "cnc_rdgrpinfo3", + "cnc_rdgrpinfo4", + "cnc_rdhdnxt", + "cnc_rdhipitchinfo", + "cnc_rdhipitchr", + "cnc_rdhissgnl", + "cnc_rdhissgnl3", + "cnc_rdhndintrpt", + "cnc_rdholmes", + "cnc_rdhsparam", + "cnc_rdhsparamm", + "cnc_rdhsprminfo", + "cnc_rdintchk", + "cnc_rdinterference", + "cnc_rdintinfo", + "cnc_rdipltp", + "cnc_rdipltp_bg", + "cnc_rdjogdrun", + "cnc_rdjogmdi", + "cnc_rdlenofs", + "cnc_rdlife", + "cnc_rdloopgain", + "cnc_rdlrninfo", + "cnc_rdlrninfol", + "cnc_rdlrnprfcmnt", + "cnc_rdlrntrnsdata", + "cnc_rdmacro", + "cnc_rdmacro_bg", + "cnc_rdmacroinfo", + "cnc_rdmacrolclevel", + "cnc_rdmacrolcval", + "cnc_rdmacror", + "cnc_rdmacror2", + "cnc_rdmacror2_name", + "cnc_rdmacror3", + "cnc_rdmag_property", + "cnc_rdmagazine", + "cnc_rdmaxgrp", + "cnc_rdmaxtool", + "cnc_rdmcdfinfo", + "cnc_rdmcdprgcmnt", + "cnc_rdmcdprgcmnt2", + "cnc_rdmdipntr", + "cnc_rdmdipntro8", + "cnc_rdmdiprgstat", + "cnc_rdmdiprog", + "cnc_rdmdlconfig2", + "cnc_rdmenuswitch", + "cnc_rdmgrpdata", + "cnc_rdmirimage", + "cnc_rdmofs", + "cnc_rdmovrlap", + "cnc_rdmovrlapm", + "cnc_rdmultitldt", + "cnc_rdngrp", + "cnc_rdnspdl", + "cnc_rdntool", + "cnc_rdofslength", + "cnc_rdomhisinfo", + "cnc_rdomhisno", + "cnc_rdomhistry", + "cnc_rdomhistry2", + "cnc_rdope_lvl", + "cnc_rdophisno", + "cnc_rdophisno3", + "cnc_rdophistry", + "cnc_rdophistry2", + "cnc_rdophistry3", + "cnc_rdophistry4", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdopmsg2", + "cnc_rdopmsg3", + "cnc_rdopmsg3m", + "cnc_rdopnlgnrl", + "cnc_rdopnlgnrl2", + "cnc_rdopnlgsname", + "cnc_rdopnlgsname2", + "cnc_rdopnlsgnl", + "cnc_rdoverstore", + "cnc_rdoverstoremode", + "cnc_rdpalaxis", + "cnc_rdparainfo", + "cnc_rdparainfo2", + "cnc_rdparam", + "cnc_rdparam3", + "cnc_rdparam_ext", + "cnc_rdparanum", + "cnc_rdparar", + "cnc_rdparar3", + "cnc_rdpdf_alldir", + "cnc_rdpdf_curdir", + "cnc_rdpdf_drive", + "cnc_rdpdf_execline", + "cnc_rdpdf_inf", + "cnc_rdpdf_line", + "cnc_rdpdf_line2", + "cnc_rdpdf_line_bgedt", + "cnc_rdpdf_pglockstat", + "cnc_rdpdf_prginf", + "cnc_rdpdf_subdir", + "cnc_rdpdf_subdirn", + "cnc_rdpglockstat", + "cnc_rdpitchblkinfo", + "cnc_rdpitchinfo", + "cnc_rdpitchr", + "cnc_rdplutosmpl", + "cnc_rdpm_item", + "cnc_rdpm_mcnitem", + "cnc_rdpmacro", + "cnc_rdpmacroinfo", + "cnc_rdpmacroinfo2", + "cnc_rdpmacroinfo3", + "cnc_rdpmacror", + "cnc_rdpmacror2", + "cnc_rdpmacror_bg", + "cnc_rdpmacvalflag", + "cnc_rdpmmprmtp", + "cnc_rdposerrs", + "cnc_rdposerrs2", + "cnc_rdposerrz", + "cnc_rdposition", + "cnc_rdpot_property", + "cnc_rdprgdirtime", + "cnc_rdprgnum", + "cnc_rdprgnumo8", + "cnc_rdprgnumo8_bg", + "cnc_rdprgrmupdtcnt", + "cnc_rdproctime", + "cnc_rdproctime2", + "cnc_rdproctime3", + "cnc_rdprogdata", + "cnc_rdprogdir", + "cnc_rdprogdir2", + "cnc_rdprogdir2o8", + "cnc_rdprogdir3", + "cnc_rdprogdir4", + "cnc_rdprogdiro8", + "cnc_rdproginfo", + "cnc_rdprogline", + "cnc_rdprogline2", + "cnc_rdprotect", + "cnc_rdprstrinfo", + "cnc_rdprstrinfom", + "cnc_rdprt_data", + "cnc_rdprt_lvl", + "cnc_rdptaxfunctablestatus", + "cnc_rdptaxitablestatus", + "cnc_rdptcnvalm", + "cnc_rdptcnvinfo", + "cnc_rdptexedistalm", + "cnc_rdptsptablestatus", + "cnc_rdradofs", + "cnc_rdrepeatval", + "cnc_rdrepeatval_ext", + "cnc_rdrstrmcode", + "cnc_rdrtmiowrenbl", + "cnc_rdrtmiowrenblbit", + "cnc_rdrtmiowrenblrng", + "cnc_rdrtmrvar", + "cnc_rdrtmrvars", + "cnc_rdsafetyzone", + "cnc_rdscaling", + "cnc_rdscdldat", + "cnc_rdscdlinfo", + "cnc_rdseqnum", + "cnc_rdseqnum_bg", + "cnc_rdservoid", + "cnc_rdset", + "cnc_rdsetinfo", + "cnc_rdsetinfo2", + "cnc_rdsetnum", + "cnc_rdsetr", + "cnc_rdsetzone", + "cnc_rdspcss", + "cnc_rdspdlalm", + "cnc_rdspdlfeed", + "cnc_rdspdlname", + "cnc_rdspdlnamem", + "cnc_rdspdlspeed", + "cnc_rdspdlwaitname", + "cnc_rdspeed", + "cnc_rdspgear", + "cnc_rdspindleid", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsrvspeed", + "cnc_rdsvgtungstat", + "cnc_rdsvmeter", + "cnc_rdsynerrrg", + "cnc_rdsynerrsy", + "cnc_rdsysalm", + "cnc_rdsyshard", + "cnc_rdsyssoft", + "cnc_rdsyssoft2", + "cnc_rdsyssoft3", + "cnc_rdtdicrntshapeinf", + "cnc_rdtdicubedata", + "cnc_rdtdicubeinfo", + "cnc_rdtdicurrentshape", + "cnc_rdtdicylinderdata", + "cnc_rdtdidispsetting", + "cnc_rdtdieffectshape", + "cnc_rdtdifignum", + "cnc_rdtdifiguredata", + "cnc_rdtdiinfo", + "cnc_rdtdiinitview", + "cnc_rdtdimoveaxis", + "cnc_rdtdinamesetting", + "cnc_rdtdiplanedata", + "cnc_rdtdiseltool", + "cnc_rdtdishapedata", + "cnc_rdtimer", + "cnc_rdtlctldata", + "cnc_rdtldata", + "cnc_rdtldspcstms", + "cnc_rdtlgeomsize", + "cnc_rdtlgeomsize_ext", + "cnc_rdtlgrp", + "cnc_rdtlinfo", + "cnc_rdtlmgr_check", + "cnc_rdtlmgr_name", + "cnc_rdtlmsinfo", + "cnc_rdtlname", + "cnc_rdtlnewstatus", + "cnc_rdtltool", + "cnc_rdtlusegrp", + "cnc_rdtofs", + "cnc_rdtofs_bg", + "cnc_rdtofsenbl", + "cnc_rdtofsinfo", + "cnc_rdtofsinfo2", + "cnc_rdtofsr", + "cnc_rdtofsr_bg", + "cnc_rdtool", + "cnc_rdtool_cause", + "cnc_rdtool_f2", + "cnc_rdtool_inhis", + "cnc_rdtool_outhis", + "cnc_rdtoolchggrp", + "cnc_rdtooldata", + "cnc_rdtoolgeom_tlm", + "cnc_rdtoolgrp", + "cnc_rdtoolinfo", + "cnc_rdtoollife_count", + "cnc_rdtoollife_data", + "cnc_rdtoollife_tcodedata", + "cnc_rdtoollifed_count", + "cnc_rdtoollifed_data", + "cnc_rdtoolrng", + "cnc_rdtoolzone", + "cnc_rdusbdevinfo", + "cnc_rdusbfilelist", + "cnc_rdusegrpid", + "cnc_rdusetlno", + "cnc_rduvactpt2", + "cnc_rdvolc", + "cnc_rdvolccomp", + "cnc_rdwavecount", + "cnc_rdwavedata", + "cnc_rdwavedata2", + "cnc_rdwavedata3", + "cnc_rdwaveprm", + "cnc_rdwaveprm2", + "cnc_rdwaveprm3", + "cnc_rdwkcdsfms", + "cnc_rdwkcdsfms2", + "cnc_rdwkcdsfms3", + "cnc_rdwkcdshft", + "cnc_rdwkcdshft2", + "cnc_rdwkcdshft3", + "cnc_rdwkcdshft_bg", + "cnc_rdwseterror", + "cnc_rdzofs", + "cnc_rdzofs_bg", + "cnc_rdzofsinfo", + "cnc_rdzofsmes", + "cnc_rdzofsr", + "cnc_rdzofsr_bg", + "cnc_read_cexeinfo", + "cnc_read_data", + "cnc_read_prog", + "cnc_read_prog2", + "cnc_read_prog2_bg", + "cnc_read_prog3", + "cnc_read_prog3_bg", + "cnc_read_prog_bg", + "cnc_readlrncrnt", + "cnc_reg_toolstrage", + "cnc_regmagazine", + "cnc_regtool", + "cnc_regtool_f2", + "cnc_relative", + "cnc_relative2", + "cnc_relative_bg", + "cnc_renameprog", + "cnc_reqsvgtung", + "cnc_reset", + "cnc_reset2", + "cnc_resetpdf_pglock", + "cnc_resetpglock", + "cnc_restorlrn", + "cnc_rewind", + "cnc_robo_rdalmmsg", + "cnc_robo_rdcomsetting", + "cnc_robo_rdgrouplist", + "cnc_robo_rdselectedsignals", + "cnc_robo_rdsignals", + "cnc_robo_selectgroup", + "cnc_robo_wralmmsg", + "cnc_robo_wrcomsetting", + "cnc_robo_wrgroup", + "cnc_robo_wrselectedsignals", + "cnc_robo_wrsignalname", + "cnc_rstrseqsrch", + "cnc_rstrseqsrch2", + "cnc_save_maint", + "cnc_saveprog_end", + "cnc_saveprog_start", + "cnc_sdbetainfo", + "cnc_sdcancelsmpl", + "cnc_sdclrchnl", + "cnc_sdendsmpl", + "cnc_sdendsmpl2", + "cnc_sdfmnghwnd", + "cnc_sdfstatchg", + "cnc_sdread1shot", + "cnc_sdreadsmpl", + "cnc_sdreadsmpl2", + "cnc_sdsetchnl", + "cnc_sdsetchnl2", + "cnc_sdstartsmpl", + "cnc_sdstartsmpl2", + "cnc_sdstartsmplb", + "cnc_sdtcancelsmpl", + "cnc_sdtclrchnl", + "cnc_sdtendsmpl", + "cnc_sdtread1shot", + "cnc_sdtreadsmpl", + "cnc_sdtsetchnl", + "cnc_sdtstartsmpl", + "cnc_search", + "cnc_search2", + "cnc_searcho8", + "cnc_searchresult", + "cnc_searchresult2", + "cnc_searchusbfile", + "cnc_searchword", + "cnc_searchword2", + "cnc_sendkey", + "cnc_seqrdtdicubeinfo", + "cnc_seqsrch", + "cnc_seqsrch2", + "cnc_set_cutcnd_exval", + "cnc_set_smth_exval", + "cnc_setactptopt", + "cnc_setcurscrn", + "cnc_setdtailerr", + "cnc_setfrp", + "cnc_setlibopt", + "cnc_setmactype", + "cnc_setpath", + "cnc_setpdf_pglock", + "cnc_setpdf_pglockexec", + "cnc_setpglock", + "cnc_setpmactype", + "cnc_setrstraxis", + "cnc_setsumdt", + "cnc_settdiobjectshape", + "cnc_settditoolshape", + "cnc_settimer", + "cnc_settolnum_qset", + "cnc_settpnlcalib", + "cnc_setzofsnum_qset", + "cnc_skip", + "cnc_sramget", + "cnc_sramgetend", + "cnc_sramgetstart", + "cnc_sramstat", + "cnc_srttl_getdata", + "cnc_srttl_getnum", + "cnc_srvdelay", + "cnc_start", + "cnc_start_async_data_punch", + "cnc_start_async_data_read", + "cnc_start_async_pdf_punch", + "cnc_start_async_pdf_read", + "cnc_start_async_punch_prog3", + "cnc_start_async_punch_prog3_bg", + "cnc_start_async_read_prog3", + "cnc_start_async_read_prog3_bg", + "cnc_start_async_wrparam", + "cnc_start_grppos", + "cnc_start_grppos2", + "cnc_startdyngrph", + "cnc_startnccmd", + "cnc_startomhis", + "cnc_startophis", + "cnc_startptcnv", + "cnc_statinfo", + "cnc_statinfo2", + "cnc_statinfo_bg", + "cnc_statinfo_dmg", + "cnc_statlrntrns", + "cnc_stop_async_read_punch", + "cnc_stop_grppos", + "cnc_stopdyngrph", + "cnc_stoplrntrns", + "cnc_stopnccmd", + "cnc_stopomhis", + "cnc_stopophis", + "cnc_stopsvgtung", + "cnc_stplutosmpl", + "cnc_svdtendrd", + "cnc_svdtendwr", + "cnc_svdtrddata", + "cnc_svdtstartrd", + "cnc_svdtstartwr", + "cnc_svdtstopexec", + "cnc_svdtwrdata", + "cnc_sysinfo", + "cnc_sysinfo_ex", + "cnc_t1info", + "cnc_t2info", + "cnc_tofs_rnge", + "cnc_tool_in", + "cnc_tool_in2", + "cnc_tool_move", + "cnc_tool_out", + "cnc_tool_srh_free_min_num", + "cnc_tool_temp_in", + "cnc_tool_temp_out", + "cnc_toolnum", + "cnc_toolsrch", + "cnc_upend", + "cnc_upend3", + "cnc_upend4", + "cnc_upload", + "cnc_upload3", + "cnc_upload4", + "cnc_upstart", + "cnc_upstart3", + "cnc_upstart4", + "cnc_upstarto8", + "cnc_usbmkdir", + "cnc_usbremove", + "cnc_usbrename", + "cnc_usbrmdir", + "cnc_validate_opt", + "cnc_validate_prm", + "cnc_verify", + "cnc_verify4", + "cnc_verify_prog", + "cnc_verify_prog_bg", + "cnc_vrfend", + "cnc_vrfend4", + "cnc_vrfstart", + "cnc_vrfstart4", + "cnc_wavestart", + "cnc_wavestat", + "cnc_wavestop", + "cnc_wksft_rnge", + "cnc_wr1length", + "cnc_wr1radius", + "cnc_wr1tlifedat2", + "cnc_wr1tlifedata", + "cnc_wr2length", + "cnc_wr2radius", + "cnc_wr2tlifedata", + "cnc_wractpt", + "cnc_wrcbmem", + "cnc_wrcbprm", + "cnc_wrcexesram", + "cnc_wrcncmem", + "cnc_wrcncmem2", + "cnc_wrcnttype", + "cnc_wrcount", + "cnc_wrcountr", + "cnc_wrdsdncfile", + "cnc_wrdsdncfile2", + "cnc_wrfbusmem", + "cnc_wrfile_sram", + "cnc_wrfixofs", + "cnc_wrfromservoid", + "cnc_wrfromspindleid", + "cnc_wrfssb_axis", + "cnc_wrfssb_axis_num", + "cnc_wrftl", + "cnc_wrgrpinfo", + "cnc_wrgrpinfo2", + "cnc_wrgrpinfo3", + "cnc_wrhipitchr", + "cnc_wrhissgnl", + "cnc_wrhissgnl3", + "cnc_wrintchk", + "cnc_wrjogmdi", + "cnc_wrjogmdiclr", + "cnc_wrkeyhistry", + "cnc_wrlife", + "cnc_wrlrninfo", + "cnc_wrlrnprf", + "cnc_wrmacro", + "cnc_wrmacro_bg", + "cnc_wrmacror", + "cnc_wrmacror2", + "cnc_wrmag_property", + "cnc_wrmagazine", + "cnc_wrmdipntr", + "cnc_wrmdiprog", + "cnc_wrmenuswitch", + "cnc_wrmgrpdata", + "cnc_wrmofs", + "cnc_wrmultitldt", + "cnc_wropnlgnrl", + "cnc_wropnlgnrl2", + "cnc_wropnlgsname", + "cnc_wropnlgsname2", + "cnc_wropnlsgnl", + "cnc_wroverstore", + "cnc_wrparam", + "cnc_wrparam3", + "cnc_wrparas", + "cnc_wrparas3", + "cnc_wrpdf_attr", + "cnc_wrpdf_char", + "cnc_wrpdf_curdir", + "cnc_wrpdf_line", + "cnc_wrpitchr", + "cnc_wrpm_item", + "cnc_wrpm_mcnitem", + "cnc_wrpmacro", + "cnc_wrpmacror", + "cnc_wrpmacror2", + "cnc_wrpmacror_bg", + "cnc_wrpmmprm", + "cnc_wrpmmprmtp", + "cnc_wrpot_property", + "cnc_wrprogline", + "cnc_wrprt_lvl", + "cnc_wrpunchtl_ex", + "cnc_wrrelpos", + "cnc_wrrtmiowrenbl", + "cnc_wrrtmiowrenblbit", + "cnc_wrrtmiowrenblrng", + "cnc_wrrtmrvar", + "cnc_wrrtmrvars", + "cnc_wrsafetyzone", + "cnc_wrscdldat", + "cnc_wrset", + "cnc_wrsets", + "cnc_wrsetzone", + "cnc_wrsignal_f", + "cnc_wrt1info", + "cnc_wrt2info", + "cnc_wrtdicubedata", + "cnc_wrtdicylinderdata", + "cnc_wrtdidispsetting", + "cnc_wrtdieffectshape", + "cnc_wrtdifignum", + "cnc_wrtdifiguredata", + "cnc_wrtdiinitview", + "cnc_wrtdimoveaxis", + "cnc_wrtdinamesetting", + "cnc_wrtdiplanedata", + "cnc_wrtdishapedata", + "cnc_wrtimer", + "cnc_wrtlctldata", + "cnc_wrtldata", + "cnc_wrtlgeomsize", + "cnc_wrtlgeomsize_ext", + "cnc_wrtofs", + "cnc_wrtofsdrctinp", + "cnc_wrtofsms", + "cnc_wrtofsr", + "cnc_wrtool", + "cnc_wrtool2", + "cnc_wrtool_f2", + "cnc_wrtooldata", + "cnc_wrtoolgeom_tlm", + "cnc_wrtoolgrp", + "cnc_wrtoolnum", + "cnc_wrtoolzone", + "cnc_wrtrqlimit", + "cnc_wrvolc", + "cnc_wrwaveprm", + "cnc_wrwaveprm2", + "cnc_wrwaveprm3", + "cnc_wrwkcdsfms", + "cnc_wrwkcdsfms2", + "cnc_wrwkcdsfms3", + "cnc_wrwkcdshft", + "cnc_wrwkcdshft2", + "cnc_wrwkcdshft3", + "cnc_wrwseterror", + "cnc_wrzofs", + "cnc_wrzofsr", + "cnc_zofs_rnge", + "dnm_clrerrorrecord", + "dnm_rderrorrecord", + "dnm_rdfirminfo", + "dnm_rdnodeinfo", + "dnm_rdnodetable", + "dnm_rdparam", + "dnm_wrparam", + "dns_rdinfo", + "dns_rdparam", + "dns_restart", + "dns_wrparam", + "eth_abortdnc", + "eth_abortdownload", + "eth_abortupload", + "eth_applyunsolicprm", + "eth_changelockstat1", + "eth_changelockstat2", + "eth_clrlog", + "eth_clrlsistate", + "eth_disconfsclnt", + "eth_disconfsclntall", + "eth_dncsegment", + "eth_downloadsegment", + "eth_dschkdsk", + "eth_dsformat", + "eth_embrestart", + "eth_exit", + "eth_getlockstat1", + "eth_getlockstat2", + "eth_init", + "eth_init2", + "eth_initiatednc", + "eth_initiatedownload", + "eth_initiateupload", + "eth_ping", + "eth_ping_cancel", + "eth_ping_result", + "eth_rddsformat", + "eth_rddsm198dir", + "eth_rddsm198host", + "eth_rddsmode", + "eth_rddsstate", + "eth_rdembdev", + "eth_rdembm198host", + "eth_rdfsclntinfo", + "eth_rdhost", + "eth_rdlog", + "eth_rdlsistate", + "eth_rdmbsclntinfo", + "eth_rdmbsclntinfo2", + "eth_rdparam", + "eth_rdrmdinquiry", + "eth_rdtaskstate", + "eth_rdtype", + "eth_rdtype2", + "eth_rdunsolicmode", + "eth_rdunsolicstate", + "eth_remotecontrol", + "eth_terminatednc", + "eth_terminatedownload", + "eth_uploadsegment", + "eth_workinit", + "eth_wrdsm198dir", + "eth_wrdsm198host", + "eth_wrdsmode", + "eth_wrembdev", + "eth_wrembm198host", + "eth_wrhost", + "eth_wrparam", + "eth_wrrmdinquiry", + "eth_wrunsolicmode", + "flnt_clrlog", + "flnt_clrmsg", + "flnt_rdentry", + "flnt_rdlog", + "flnt_rdmsg", + "flnt_rdnetwork", + "flnt_rdnodeinfo", + "flnt_rdparam", + "flnt_wrparam", + "net_backup_param", + "net_restore_param", + "pbm_chg_mode", + "pbm_exe_subfunc", + "pbm_ini_prm", + "pbm_rd_allslvtbl", + "pbm_rd_cominfo", + "pbm_rd_errcode", + "pbm_rd_nodeinfo", + "pbm_rd_nodetable", + "pbm_rd_param", + "pbm_rd_slot", + "pbm_rd_slotinfo", + "pbm_rd_subprm", + "pbm_wr_param", + "pbs_ini_prm", + "pbs_rd_cominfo", + "pbs_rd_cominfo2", + "pbs_rd_param", + "pbs_rd_param2", + "pbs_wr_param", + "pbs_wr_param2", + "pmc_abort_all_transfer", + "pmc_abort_end_process", + "pmc_abort_multiple_write_check", + "pmc_abort_trace_function", + "pmc_abort_transfer", + "pmc_allocate_work_memory", + "pmc_can_apply_override", + "pmc_change_operation", + "pmc_change_operation_with_passwd", + "pmc_change_scoped_symcmt", + "pmc_check_io_assignment_empty", + "pmc_check_permission", + "pmc_check_permission_with_passwd", + "pmc_control_snp_driver", + "pmc_convert_address_pmc_to_vio", + "pmc_convert_pmc_address_from_sync", + "pmc_convert_pmc_address_to_string", + "pmc_convert_pmc_address_to_sync", + "pmc_convert_vio_address_from_sync", + "pmc_convert_vio_address_to_string", + "pmc_convert_vio_address_to_sync", + "pmc_convret_address_vio_to_pmc", + "pmc_delete_all_io_assignment_data", + "pmc_delete_all_message_data", + "pmc_delete_all_symcmt_data", + "pmc_delete_data_table_group_data", + "pmc_delete_extra_relay_group_data", + "pmc_delete_io_assignment_data", + "pmc_delete_message_entry", + "pmc_delete_node_mutex", + "pmc_delete_scoped_symcmt", + "pmc_delete_seq_program_object", + "pmc_delete_sfc_monitor_time", + "pmc_delete_symcmt_entry", + "pmc_end_search_symbol", + "pmc_exchange_permission", + "pmc_fdcas_compare_file_by_name", + "pmc_fdcas_compare_file_by_number", + "pmc_fdcas_delete_all_files", + "pmc_fdcas_delete_file_by_name", + "pmc_fdcas_delete_file_by_number", + "pmc_fdcas_get_comm_parameter", + "pmc_fdcas_init_comm_parameter", + "pmc_fdcas_read_directory", + "pmc_fdcas_read_file_by_name", + "pmc_fdcas_read_file_by_number", + "pmc_fdcas_read_file_count", + "pmc_fdcas_read_file_status", + "pmc_fdcas_set_comm_parameter", + "pmc_fdcas_write_pmc_parameter", + "pmc_fdcas_write_seq_program", + "pmc_flashrom_compare_message", + "pmc_flashrom_compare_seq_program", + "pmc_flashrom_read_message", + "pmc_flashrom_read_seq_program", + "pmc_flashrom_write_message", + "pmc_flashrom_write_seq_program", + "pmc_force_release_object", + "pmc_force_status_bit", + "pmc_force_status_byte", + "pmc_get_address_entry", + "pmc_get_alarm_status", + "pmc_get_all_associated_symbols", + "pmc_get_all_associated_symcmts", + "pmc_get_available_comment_set", + "pmc_get_available_io_connection", + "pmc_get_common_memory_group", + "pmc_get_current_comment", + "pmc_get_current_pmc", + "pmc_get_current_pmc_unit", + "pmc_get_data_table_group_count", + "pmc_get_data_table_max_group_count", + "pmc_get_default_filename", + "pmc_get_extra_relay_group_count", + "pmc_get_extra_relay_max_group_count", + "pmc_get_font_information", + "pmc_get_instruction_set_version", + "pmc_get_io_assignment_address", + "pmc_get_io_device_id", + "pmc_get_max_sfc_monitor_time_count", + "pmc_get_maximum_trace_frames", + "pmc_get_message_data_size", + "pmc_get_message_entry_address", + "pmc_get_message_entry_count", + "pmc_get_message_maximum_size", + "pmc_get_message_remain_memory", + "pmc_get_multiple_coil_result", + "pmc_get_multiple_instruction_result", + "pmc_get_multiple_symcmts", + "pmc_get_nonvolatile_memory_status", + "pmc_get_number_of_address_entries", + "pmc_get_number_of_pmc", + "pmc_get_number_of_pmc_path", + "pmc_get_object_update_counter", + "pmc_get_object_version", + "pmc_get_override_status", + "pmc_get_permission_status", + "pmc_get_pmc_address_info", + "pmc_get_pmc_address_map", + "pmc_get_pmc_path_types", + "pmc_get_pmc_unit_types", + "pmc_get_programable_io_channels", + "pmc_get_result_trace_parameter", + "pmc_get_scoped_symcmt", + "pmc_get_search_symbol_result", + "pmc_get_search_symbol_status", + "pmc_get_sfc_diagram_memory", + "pmc_get_sfc_monitor_status", + "pmc_get_sfc_monitor_time_information", + "pmc_get_snp_communication_status", + "pmc_get_snp_driver_status", + "pmc_get_splist_status", + "pmc_get_subprogram_list", + "pmc_get_symbol_object_version", + "pmc_get_symcmt_bit", + "pmc_get_symcmt_byte", + "pmc_get_symcmt_data_size", + "pmc_get_symcmt_entry_count", + "pmc_get_symcmt_maximum_size", + "pmc_get_symcmt_remain_memory", + "pmc_get_timer_type", + "pmc_get_trace_result", + "pmc_get_trace_specification", + "pmc_get_trace_status", + "pmc_get_transfer_status", + "pmc_get_unit_type", + "pmc_get_xref_address_result", + "pmc_get_xref_instruction_result", + "pmc_get_xref_status", + "pmc_getdtailerr", + "pmc_getpmcptr", + "pmc_giveout_node_mutex", + "pmc_handle_work_area_size", + "pmc_initialize_data_table_group", + "pmc_initialize_extra_relay_group", + "pmc_initialize_handle_work_area", + "pmc_initialize_node_mutex", + "pmc_initialize_node_work_area", + "pmc_initialize_nonvolatile_memory", + "pmc_insert_seq_program_space", + "pmc_is_bit_accessible_pmc_address", + "pmc_is_effective_pmc_address", + "pmc_is_seq_program_available", + "pmc_is_trace_function_active", + "pmc_is_visible_pmc_address", + "pmc_is_writable_pmc_address", + "pmc_kpmsiz", + "pmc_logical_and_pmc_address_uint8", + "pmc_logical_and_vio_address_uint8", + "pmc_logical_or_pmc_address_uint8", + "pmc_logical_or_vio_address_uint8", + "pmc_logical_xor_pmc_address_uint8", + "pmc_logical_xor_vio_address_uint8", + "pmc_mcard_compare_file", + "pmc_mcard_delete_file", + "pmc_mcard_format", + "pmc_mcard_read_directory", + "pmc_mcard_read_file", + "pmc_mcard_read_file_count", + "pmc_mcard_read_file_status", + "pmc_mcard_write_pmc_message", + "pmc_mcard_write_pmc_parameter", + "pmc_mcard_write_seq_program", + "pmc_mcard_write_trace_result", + "pmc_node_work_area_size", + "pmc_notify_handle_close", + "pmc_parse_string_into_pmc_address", + "pmc_parse_string_into_scoped_pmc_address", + "pmc_rdalmmsg", + "pmc_rdcntl_exrelay_grp", + "pmc_rdcntldata", + "pmc_rdcntlexrelay", + "pmc_rdcntlgrp", + "pmc_rdkpm", + "pmc_rdpmcinfo", + "pmc_rdpmcparam", + "pmc_rdpmcrng", + "pmc_rdpmctitle", + "pmc_rdpmctitle2", + "pmc_rdprmend", + "pmc_rdprmstart", + "pmc_read_actual_io_assignment_data", + "pmc_read_actual_io_assignment_data_by_address", + "pmc_read_cnc_dido", + "pmc_read_data_table_group_data", + "pmc_read_end_process_status", + "pmc_read_extra_relay_group_data", + "pmc_read_instruction_monitor_status", + "pmc_read_io_assignment_data", + "pmc_read_machine_signal", + "pmc_read_message_entry_data", + "pmc_read_message_title", + "pmc_read_nonvolatile_memory", + "pmc_read_performance_information", + "pmc_read_pmc_address_bit", + "pmc_read_pmc_address_uint16", + "pmc_read_pmc_address_uint32", + "pmc_read_pmc_address_uint8", + "pmc_read_pmc_software_version", + "pmc_read_raw_memory", + "pmc_read_scan_time", + "pmc_read_scoped_symcmt", + "pmc_read_seq_object_size", + "pmc_read_seq_program_and_memory_type", + "pmc_read_seq_program_config", + "pmc_read_seq_program_object", + "pmc_read_seq_program_size", + "pmc_read_seq_program_status", + "pmc_read_seq_program_type", + "pmc_read_setting", + "pmc_read_snp_parameter", + "pmc_read_symcmt_entry", + "pmc_read_system_parameter", + "pmc_read_title_data", + "pmc_read_trace_parameter", + "pmc_read_vio_address_bit", + "pmc_read_vio_address_uint16", + "pmc_read_vio_address_uint32", + "pmc_read_vio_address_uint8", + "pmc_release_node_work_area", + "pmc_request_permission", + "pmc_request_permission_with_passwd", + "pmc_reserve_nonvolatile_memory", + "pmc_reset_all_override", + "pmc_reset_override", + "pmc_reset_sfc_monitor_time", + "pmc_restore_seq_program_edit", + "pmc_resume_end_process", + "pmc_resume_transfer", + "pmc_retrieve_permission", + "pmc_return_permission", + "pmc_rs232c_compare", + "pmc_rs232c_get_comm_parameter", + "pmc_rs232c_init_comm_parameter", + "pmc_rs232c_read", + "pmc_rs232c_set_comm_parameter", + "pmc_rs232c_write_pmc_parameter", + "pmc_rs232c_write_seq_program", + "pmc_run_seq_program", + "pmc_search_comment_set", + "pmc_search_ladder_address", + "pmc_search_ladder_function", + "pmc_search_message_entry_position", + "pmc_search_message_string", + "pmc_search_scoped_symcmt_by_address", + "pmc_search_scoped_symcmt_by_symbol", + "pmc_search_scoped_symcmt_string", + "pmc_search_symbol_string", + "pmc_search_symcmt_entry_by_symbol", + "pmc_search_symcmt_entry_position", + "pmc_search_symcmt_string", + "pmc_search_unit_type", + "pmc_select_pmc", + "pmc_select_pmc_unit", + "pmc_set_data_table_group_count", + "pmc_set_extra_relay_group_count", + "pmc_set_language", + "pmc_set_override", + "pmc_set_sfc_monitor_time", + "pmc_set_timer_type", + "pmc_start_end_process", + "pmc_start_list_subprogram", + "pmc_start_multiple_coil_check", + "pmc_start_multiple_instruction_check", + "pmc_start_search_symbol", + "pmc_start_trace_function", + "pmc_start_xref_address", + "pmc_start_xref_instruction", + "pmc_stop_seq_program", + "pmc_wrcntl_exrelay_grp", + "pmc_wrcntldata", + "pmc_wrcntlexrelay", + "pmc_wrcntlgrp", + "pmc_write_cnc_dido", + "pmc_write_data_table_group_data", + "pmc_write_extra_relay_group_data", + "pmc_write_io_assignment_data", + "pmc_write_message_entry", + "pmc_write_nonvolatile_memory", + "pmc_write_pmc_address_bit", + "pmc_write_pmc_address_uint16", + "pmc_write_pmc_address_uint32", + "pmc_write_pmc_address_uint8", + "pmc_write_raw_io_assignment_data", + "pmc_write_raw_memory", + "pmc_write_scoped_symcmt", + "pmc_write_seq_program_object", + "pmc_write_setting", + "pmc_write_snp_parameter", + "pmc_write_symcmt_entry", + "pmc_write_system_parameter", + "pmc_write_title_data", + "pmc_write_trace_parameter", + "pmc_write_vio_address_bit", + "pmc_write_vio_address_uint16", + "pmc_write_vio_address_uint32", + "pmc_write_vio_address_uint8", + "pmc_wrkpm", + "pmc_wrpmcparam", + "pmc_wrpmcrng", + "pmc_wrprmend", + "pmc_wrprmstart", + "usb_clrlog", + "usb_format_result", + "usb_format_start", + "usb_rdinfo", + "usb_rdlog" + ], + "notes": [ + "Exports extracted directly from the 64-bit DLL PE export directory.", + "Wrapper-supported methods are intersected with upstream fwlib.cs extern declarations.", + "Axis and path hints are filename-family heuristics, not protocol-level proofs." + ] +} \ No newline at end of file diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlib30i64.json b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlib30i64.json new file mode 100644 index 0000000..4a8358d --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlib30i64.json @@ -0,0 +1,2407 @@ +{ + "profile_name": "fwlib30i64", + "dll_name": "fwlib30i64.dll", + "series_hint": "30i/31i/32i", + "max_axis_hint": 32, + "max_path_hint": 2, + "export_count": 1850, + "connection_methods": [ + "cnc_allclibhndl", + "cnc_allclibhndl2" + ], + "mock_methods": [ + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_diagnoss", + "cnc_exeprgname2", + "cnc_freelibhndl", + "cnc_getpath", + "cnc_rdalmmsg2", + "cnc_rdaxisname", + "cnc_rdblkcount", + "cnc_rddiaginfo", + "cnc_rddiagnum", + "cnc_rddynamic2", + "cnc_rdexecprog", + "cnc_rdmacro", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdparam", + "cnc_rdprgnum", + "cnc_rdprogdir3", + "cnc_rdproginfo", + "cnc_rdseqnum", + "cnc_rdspdlname", + "cnc_rdspgear", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsvmeter", + "cnc_rdtimer", + "cnc_setpath", + "cnc_statinfo", + "cnc_sysinfo", + "cnc_wrmacro", + "cnc_wrparam", + "pmc_rdpmcrng", + "pmc_wrpmcrng" + ], + "wrapper_supported_count": 495, + "wrapper_supported_methods": [ + "cnc_absolute", + "cnc_absolute2", + "cnc_accdecdly", + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_adcnv", + "cnc_alarm", + "cnc_alarm2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_allowance", + "cnc_allowcnd", + "cnc_async_busy_state", + "cnc_buff", + "cnc_canmovrlap", + "cnc_cdownload", + "cnc_cexesramsize", + "cnc_clearomhis", + "cnc_clearophis", + "cnc_clr3dplsmov", + "cnc_clr5axpls", + "cnc_clralm", + "cnc_clrcntinfo", + "cnc_condense", + "cnc_copyprog", + "cnc_cupload", + "cnc_cverify", + "cnc_delall", + "cnc_delete", + "cnc_delmagazine", + "cnc_delprogline", + "cnc_deltlifedt", + "cnc_deltlifegrp", + "cnc_deltool", + "cnc_diagnosr", + "cnc_diagnoss", + "cnc_distance", + "cnc_dnc2", + "cnc_dncend2", + "cnc_dncstart2", + "cnc_download", + "cnc_download3", + "cnc_download4", + "cnc_dwnend", + "cnc_dwnend3", + "cnc_dwnend4", + "cnc_dwnstart", + "cnc_dwnstart3", + "cnc_dwnstart4", + "cnc_end_async_wrparam", + "cnc_exaxisname", + "cnc_exeprgname", + "cnc_exeprgname2", + "cnc_freelibhndl", + "cnc_fromget", + "cnc_fromgetend", + "cnc_fromgetstart", + "cnc_fromput", + "cnc_fromputend", + "cnc_fromputstart", + "cnc_fromremove", + "cnc_getdspmode", + "cnc_getdtailerr", + "cnc_getfigure", + "cnc_getfrominfo", + "cnc_getlibopt", + "cnc_getmactype", + "cnc_getpath", + "cnc_getpmactype", + "cnc_getsraminfo", + "cnc_gettimer", + "cnc_hpccactfine", + "cnc_hpccatset", + "cnc_hpccattune", + "cnc_hpccselfine", + "cnc_instlifedt", + "cnc_machine", + "cnc_modal", + "cnc_newprog", + "cnc_pdf_add", + "cnc_pdf_cond", + "cnc_pdf_copy", + "cnc_pdf_del", + "cnc_pdf_delline", + "cnc_pdf_move", + "cnc_pdf_rdactpt", + "cnc_pdf_rdmain", + "cnc_pdf_rename", + "cnc_pdf_searchresult", + "cnc_pdf_searchword", + "cnc_pdf_slctmain", + "cnc_pdf_wractpt", + "cnc_progdigit", + "cnc_prstwkcd", + "cnc_rd1length", + "cnc_rd1radius", + "cnc_rd1tlifedat2", + "cnc_rd1tlifedata", + "cnc_rd2length", + "cnc_rd2radius", + "cnc_rd2tlifedata", + "cnc_rd3dcdcnv", + "cnc_rd3dmovrlap", + "cnc_rd3dofschg", + "cnc_rd3dpulse", + "cnc_rd3dtofs", + "cnc_rd3dtooltip", + "cnc_rd5axmandt", + "cnc_rd5axovrlap", + "cnc_rdactfixofs", + "cnc_rdactpt", + "cnc_rdacttlzone", + "cnc_rdalmhisno", + "cnc_rdalmhistry", + "cnc_rdalmhistry2", + "cnc_rdalmhistry3", + "cnc_rdalmhistry5", + "cnc_rdalminfo", + "cnc_rdalmmsg", + "cnc_rdalmmsg2", + "cnc_rdaxisdata", + "cnc_rdaxisname", + "cnc_rdblkcount", + "cnc_rdbrstrinfo", + "cnc_rdcdrotate", + "cnc_rdcexesram", + "cnc_rdcncid", + "cnc_rdcommand", + "cnc_rdcoordnum", + "cnc_rdcount", + "cnc_rdctrldi", + "cnc_rdctrldo", + "cnc_rdcurrent", + "cnc_rddiag_ext", + "cnc_rddiaginfo", + "cnc_rddiagnum", + "cnc_rddischarge", + "cnc_rddischrgalm", + "cnc_rddncdgndt", + "cnc_rddynamic", + "cnc_rddynamic2", + "cnc_rdexchgtgrp", + "cnc_rdexecmcode", + "cnc_rdexecprog", + "cnc_rdexecpt", + "cnc_rdfbusmem", + "cnc_rdfixcycle", + "cnc_rdfixofs", + "cnc_rdgcode", + "cnc_rdgrphcmd", + "cnc_rdgrpid", + "cnc_rdgrpid2", + "cnc_rdgrpinfo", + "cnc_rdgrpinfo2", + "cnc_rdgrpinfo3", + "cnc_rdgrpinfo4", + "cnc_rdhissgnl", + "cnc_rdhissgnl3", + "cnc_rdhndintrpt", + "cnc_rdhsparam", + "cnc_rdhsprminfo", + "cnc_rdintchk", + "cnc_rdintinfo", + "cnc_rdlactnum", + "cnc_rdlagslt", + "cnc_rdlcmddat", + "cnc_rdlcmmt", + "cnc_rdldsplc", + "cnc_rdledgprc", + "cnc_rdlenofs", + "cnc_rdlerrz", + "cnc_rdlife", + "cnc_rdloopgain", + "cnc_rdlprcprc", + "cnc_rdlpwrctrl", + "cnc_rdlpwrdat", + "cnc_rdlpwrdty", + "cnc_rdlpwrslt", + "cnc_rdmacro", + "cnc_rdmacroinfo", + "cnc_rdmacror", + "cnc_rdmacror2", + "cnc_rdmagazine", + "cnc_rdmaxgrp", + "cnc_rdmaxtool", + "cnc_rdmdipntr", + "cnc_rdmdiprgstat", + "cnc_rdmdiprog", + "cnc_rdmdlconfig2", + "cnc_rdmenuswitch", + "cnc_rdmgrpdata", + "cnc_rdmirimage", + "cnc_rdmngtime", + "cnc_rdmovrlap", + "cnc_rdmultitldt", + "cnc_rdngrp", + "cnc_rdnspdl", + "cnc_rdntool", + "cnc_rdomhisinfo", + "cnc_rdomhisno", + "cnc_rdomhistry", + "cnc_rdomhistry2", + "cnc_rdophisno", + "cnc_rdophistry", + "cnc_rdophistry2", + "cnc_rdophistry4", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdopmsg2", + "cnc_rdopmsg3", + "cnc_rdopnlgnrl", + "cnc_rdopnlgsname", + "cnc_rdopnlsgnl", + "cnc_rdparainfo", + "cnc_rdparam", + "cnc_rdparam3", + "cnc_rdparam_ext", + "cnc_rdparanum", + "cnc_rdparar", + "cnc_rdpdf_alldir", + "cnc_rdpdf_curdir", + "cnc_rdpdf_drive", + "cnc_rdpdf_inf", + "cnc_rdpdf_line", + "cnc_rdpdf_subdir", + "cnc_rdpdf_subdirn", + "cnc_rdpglockstat", + "cnc_rdpitchinfo", + "cnc_rdpitchr", + "cnc_rdpm_item", + "cnc_rdpm_mcnitem", + "cnc_rdpmacro", + "cnc_rdpmacroinfo", + "cnc_rdpmacror", + "cnc_rdpmacror2", + "cnc_rdposerrs", + "cnc_rdposerrs2", + "cnc_rdposerrz", + "cnc_rdposition", + "cnc_rdprgdirtime", + "cnc_rdprgnum", + "cnc_rdproctime", + "cnc_rdprogdir", + "cnc_rdprogdir2", + "cnc_rdprogdir3", + "cnc_rdprogdir4", + "cnc_rdproginfo", + "cnc_rdprogline", + "cnc_rdprogline2", + "cnc_rdprstrinfo", + "cnc_rdpscdedge", + "cnc_rdpscdpirc", + "cnc_rdpscdproc", + "cnc_rdpscdslop", + "cnc_rdpwofsthis", + "cnc_rdradofs", + "cnc_rdrepeatval", + "cnc_rdrstrmcode", + "cnc_rdsafetyzone", + "cnc_rdscaling", + "cnc_rdseqnum", + "cnc_rdset", + "cnc_rdsetinfo", + "cnc_rdsetnum", + "cnc_rdsetr", + "cnc_rdsetzone", + "cnc_rdspcss", + "cnc_rdspdlalm", + "cnc_rdspdlname", + "cnc_rdspeed", + "cnc_rdspgear", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsrvspeed", + "cnc_rdsvmeter", + "cnc_rdsynerrrg", + "cnc_rdsynerrsy", + "cnc_rdsyshard", + "cnc_rdsyssoft", + "cnc_rdsyssoft2", + "cnc_rdsyssoft3", + "cnc_rdtimer", + "cnc_rdtlctldata", + "cnc_rdtlgrp", + "cnc_rdtlinfo", + "cnc_rdtltool", + "cnc_rdtlusegrp", + "cnc_rdtofs", + "cnc_rdtofsinfo", + "cnc_rdtofsinfo2", + "cnc_rdtofsr", + "cnc_rdtool", + "cnc_rdtool_f2", + "cnc_rdtooldata", + "cnc_rdtoolgrp", + "cnc_rdtoolinfo", + "cnc_rdtoolrng", + "cnc_rdtoolzone", + "cnc_rdusegrpid", + "cnc_rdusetlno", + "cnc_rdwavedata", + "cnc_rdwaveprm", + "cnc_rdwaveprm2", + "cnc_rdwkcdsfms", + "cnc_rdwkcdshft", + "cnc_rdzofs", + "cnc_rdzofsinfo", + "cnc_rdzofsr", + "cnc_regmagazine", + "cnc_regtool", + "cnc_regtool_f2", + "cnc_relative", + "cnc_relative2", + "cnc_renameprog", + "cnc_reset", + "cnc_reset2", + "cnc_resetpglock", + "cnc_rewind", + "cnc_rstrseqsrch", + "cnc_rstrseqsrch2", + "cnc_sdcancelsmpl", + "cnc_sdclrchnl", + "cnc_sdendsmpl", + "cnc_sdread1shot", + "cnc_sdreadsmpl", + "cnc_sdsetchnl", + "cnc_sdstartsmpl", + "cnc_search", + "cnc_search2", + "cnc_searchresult", + "cnc_searchword", + "cnc_seqsrch", + "cnc_seqsrch2", + "cnc_setlibopt", + "cnc_setmactype", + "cnc_setpath", + "cnc_setpglock", + "cnc_setpmactype", + "cnc_settimer", + "cnc_skip", + "cnc_slide", + "cnc_sramget", + "cnc_sramget2", + "cnc_sramgetend", + "cnc_sramgetend2", + "cnc_sramgetstart", + "cnc_sramgetstart2", + "cnc_srvdelay", + "cnc_start", + "cnc_start_async_wrparam", + "cnc_startdyngrph", + "cnc_startnccmd", + "cnc_startomhis", + "cnc_startophis", + "cnc_statinfo", + "cnc_stopdyngrph", + "cnc_stopnccmd", + "cnc_stopomhis", + "cnc_stopophis", + "cnc_svdtendrd", + "cnc_svdtendwr", + "cnc_svdtrddata", + "cnc_svdtstartrd", + "cnc_svdtstartwr", + "cnc_svdtstopexec", + "cnc_svdtwrdata", + "cnc_sysinfo", + "cnc_sysinfo_ex", + "cnc_t1info", + "cnc_t2info", + "cnc_tofs_rnge", + "cnc_toolnum", + "cnc_upend", + "cnc_upend3", + "cnc_upend4", + "cnc_upload", + "cnc_upload3", + "cnc_upload4", + "cnc_upstart", + "cnc_upstart3", + "cnc_upstart4", + "cnc_verify", + "cnc_verify4", + "cnc_vrfend", + "cnc_vrfend4", + "cnc_vrfstart", + "cnc_vrfstart4", + "cnc_wavestart", + "cnc_wavestat", + "cnc_wavestop", + "cnc_wksft_rnge", + "cnc_workzero", + "cnc_wr1tlifedat2", + "cnc_wr1tlifedata", + "cnc_wr2tlifedata", + "cnc_wractpt", + "cnc_wrcexesram", + "cnc_wrcountr", + "cnc_wrfbusmem", + "cnc_wrfixofs", + "cnc_wrgrpinfo", + "cnc_wrgrpinfo2", + "cnc_wrgrpinfo3", + "cnc_wrhissgnl", + "cnc_wrhissgnl3", + "cnc_wrintchk", + "cnc_wrkeyhistry", + "cnc_wrlagslt", + "cnc_wrldsplc", + "cnc_wrledgprc", + "cnc_wrlprcprc", + "cnc_wrlpwrctrl", + "cnc_wrlpwrdty", + "cnc_wrlpwrslt", + "cnc_wrmacro", + "cnc_wrmacror", + "cnc_wrmacror2", + "cnc_wrmagazine", + "cnc_wrmdipntr", + "cnc_wrmdiprog", + "cnc_wrmenuswitch", + "cnc_wrmgrpdata", + "cnc_wrmngtime", + "cnc_wrmultitldt", + "cnc_wropnlgnrl", + "cnc_wropnlgsname", + "cnc_wropnlsgnl", + "cnc_wrparam", + "cnc_wrparas", + "cnc_wrpdf_attr", + "cnc_wrpdf_curdir", + "cnc_wrpdf_line", + "cnc_wrpitchr", + "cnc_wrpm_item", + "cnc_wrpm_mcnitem", + "cnc_wrpmacro", + "cnc_wrpmacror", + "cnc_wrpmacror2", + "cnc_wrprogline", + "cnc_wrpscdedge", + "cnc_wrpscdpirc", + "cnc_wrpscdproc", + "cnc_wrpscdslop", + "cnc_wrrelpos", + "cnc_wrsafetyzone", + "cnc_wrset", + "cnc_wrsets", + "cnc_wrsetzone", + "cnc_wrtimer", + "cnc_wrtlctldata", + "cnc_wrtofs", + "cnc_wrtofsr", + "cnc_wrtool", + "cnc_wrtool2", + "cnc_wrtool_f2", + "cnc_wrtooldata", + "cnc_wrtoolzone", + "cnc_wrwaveprm", + "cnc_wrwaveprm2", + "cnc_wrwkcdsfms", + "cnc_wrwkcdshft", + "cnc_wrzofs", + "cnc_wrzofsr", + "cnc_zofs_rnge", + "pmc_get_current_pmc_unit", + "pmc_get_number_of_pmc", + "pmc_get_pmc_unit_types", + "pmc_get_timer_type", + "pmc_getdtailerr", + "pmc_kpmsiz", + "pmc_rdalmmsg", + "pmc_rdcntldata", + "pmc_rdcntlgrp", + "pmc_rdkpm", + "pmc_rdkpm2", + "pmc_rdpmcaddr", + "pmc_rdpmcinfo", + "pmc_rdpmcparam", + "pmc_rdpmcrng", + "pmc_rdpmctitle", + "pmc_rdprmend", + "pmc_rdprmstart", + "pmc_select_pmc_unit", + "pmc_set_timer_type", + "pmc_wrcntldata", + "pmc_wrcntlgrp", + "pmc_wriolinkdat", + "pmc_wrkpm", + "pmc_wrkpm2", + "pmc_wrpmcparam", + "pmc_wrpmcrng", + "pmc_wrprmend", + "pmc_wrprmstart" + ], + "exports": [ + "PmcProcessCB", + "anm_rdsimuelm", + "anm_rdsimuelm2", + "anm_simuclose", + "anm_simuopen", + "anm_simuproc", + "anm_simurwd", + "anm_simusngl", + "anm_simustart", + "anm_simustop", + "cclr_rdinfo", + "cclr_rdparam", + "cclr_wrparam", + "cnc_3dchk_end", + "cnc_3dchk_getprginfo", + "cnc_3dchk_mchn_stop", + "cnc_3dchk_rddata", + "cnc_3dchk_rddata2", + "cnc_3dchk_rddata3", + "cnc_3dchk_rddata4", + "cnc_3dchk_start", + "cnc_3dchk_start2", + "cnc_absolute", + "cnc_absolute2", + "cnc_absolute2_exdgt", + "cnc_absolute_bg", + "cnc_absolute_mgi", + "cnc_accdecdly", + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_adcnv", + "cnc_addexauxr", + "cnc_alarm", + "cnc_alarm2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_allowance", + "cnc_allowcnd", + "cnc_async_busy_state", + "cnc_atbk_rdtime", + "cnc_aux_statinfo", + "cnc_axisnum", + "cnc_axisnum2", + "cnc_backuplrn", + "cnc_backupophis", + "cnc_block_status", + "cnc_btlfpotsrh", + "cnc_buff", + "cnc_canaux", + "cnc_cancel_prm", + "cnc_canmcdfinfo", + "cnc_canmovrlap", + "cnc_cannedcycle", + "cnc_cdautoset", + "cnc_cdownload", + "cnc_cexesramsize", + "cnc_chglang", + "cnc_chgoverstore", + "cnc_chkmcdfile", + "cnc_chktdistatus", + "cnc_chkversion", + "cnc_clear_maint", + "cnc_clearalm", + "cnc_clearomhis", + "cnc_clearophis", + "cnc_clr3dplsmov", + "cnc_clr5axpls", + "cnc_clr5dplsmov", + "cnc_clralm", + "cnc_clrcntinfo", + "cnc_clrfromsvspid", + "cnc_clrlrncrnt", + "cnc_clrmofs", + "cnc_clroverstore", + "cnc_clrptdata", + "cnc_clrrstraxis", + "cnc_clrtofs", + "cnc_condense", + "cnc_confirm_restart", + "cnc_copyprog", + "cnc_cupload", + "cnc_cverify", + "cnc_data_copy", + "cnc_del_scrlwaitmcode", + "cnc_delall", + "cnc_delallauxdata", + "cnc_delete", + "cnc_deleteo8", + "cnc_delexauxr", + "cnc_delmag_property", + "cnc_delmagazine", + "cnc_delmcdfile", + "cnc_delmcdfilebynum", + "cnc_delpot_property", + "cnc_delprogline", + "cnc_delsysalm", + "cnc_deltlifedt", + "cnc_deltlifegrp", + "cnc_deltool", + "cnc_diagnosr", + "cnc_diagnosr64", + "cnc_diagnoss", + "cnc_diagnoss64", + "cnc_distance", + "cnc_distance_exdgt", + "cnc_distancem", + "cnc_dnc2", + "cnc_dncend2", + "cnc_dncprgname", + "cnc_dncstart2", + "cnc_download", + "cnc_download3", + "cnc_download3m", + "cnc_download4", + "cnc_dschdir", + "cnc_dscopyfile", + "cnc_dsfile_req", + "cnc_dsftpcancel", + "cnc_dsftpstat", + "cnc_dsget_req", + "cnc_dslistdel_req", + "cnc_dslistget_req", + "cnc_dslistput_req", + "cnc_dsmget_req", + "cnc_dsmkdir", + "cnc_dsmput_req", + "cnc_dsput_req", + "cnc_dsrdclose", + "cnc_dsrdopen", + "cnc_dsread", + "cnc_dsremove", + "cnc_dsrename", + "cnc_dsrmdir", + "cnc_dssearch", + "cnc_dsstat_rdfile", + "cnc_dswrclose", + "cnc_dswrite", + "cnc_dswropen", + "cnc_dtsvinfo", + "cnc_dvpunchvolc", + "cnc_dvreadvolc", + "cnc_dwnend", + "cnc_dwnend3", + "cnc_dwnend3m", + "cnc_dwnend4", + "cnc_dwnstart", + "cnc_dwnstart3", + "cnc_dwnstart3m", + "cnc_dwnstart4", + "cnc_edplutosmpl", + "cnc_end_async_data_punch", + "cnc_end_async_data_read", + "cnc_end_async_pdf_punch", + "cnc_end_async_pdf_read", + "cnc_end_async_punch_prog3", + "cnc_end_async_punch_prog3_bg", + "cnc_end_async_read_prog3", + "cnc_end_async_read_prog3_bg", + "cnc_end_async_wrparam", + "cnc_endpossmpl", + "cnc_exaxisname", + "cnc_exaxisname2", + "cnc_exeprgname", + "cnc_exeprgname2", + "cnc_exeprgname_bg", + "cnc_file_cpmv_end", + "cnc_file_cpmv_poll", + "cnc_file_cpmv_restart", + "cnc_file_cpmv_start", + "cnc_freelibhndl", + "cnc_fromget", + "cnc_fromget2", + "cnc_fromgetend", + "cnc_fromgetend2", + "cnc_fromgetstart", + "cnc_fromgetstart2", + "cnc_fromput", + "cnc_fromput2", + "cnc_fromputend", + "cnc_fromputend2", + "cnc_fromputstart", + "cnc_fromputstart2", + "cnc_fromremove", + "cnc_fssb_autoset", + "cnc_fssb_reset", + "cnc_ftrq_data_copy", + "cnc_ftrq_from_load", + "cnc_ftrq_from_save", + "cnc_get_crosschk_alarm", + "cnc_get_flowmonitor", + "cnc_get_mccteststs", + "cnc_get_safetysts", + "cnc_get_safetysts2", + "cnc_getauxdatar", + "cnc_getauxoverdatar", + "cnc_getauxregnum", + "cnc_getdspmode", + "cnc_getdtailerr", + "cnc_getdtailerr2", + "cnc_getexauxr", + "cnc_getexauxregnum", + "cnc_getfigure", + "cnc_getfrominfo", + "cnc_getinfoaux", + "cnc_getlibopt", + "cnc_getmactype", + "cnc_getpath", + "cnc_getpaxispath", + "cnc_getpmactype", + "cnc_getprntname", + "cnc_getrtmioinfo", + "cnc_getrtmiorngnum", + "cnc_getrtmrvar", + "cnc_getrtmrvars", + "cnc_getsraminfo", + "cnc_getstsaux", + "cnc_getsysfolder_num", + "cnc_gettimer", + "cnc_gettolnum_qset", + "cnc_getupdcount", + "cnc_getvcprg", + "cnc_getzofsnum_qset", + "cnc_glvndiagnoss", + "cnc_hdck_nochange_info", + "cnc_hpccactfine", + "cnc_hpccatset", + "cnc_hpccattune", + "cnc_hpccselfine", + "cnc_ifsb_autoset", + "cnc_ifsb_reset", + "cnc_instlifedt", + "cnc_lctcdcstm", + "cnc_loadtorq", + "cnc_machine", + "cnc_machine3", + "cnc_machine3_ex", + "cnc_machine_bg", + "cnc_machine_exdgt", + "cnc_magazinesrch", + "cnc_mcdp_create", + "cnc_mcdp_mount", + "cnc_mcdp_mountchk", + "cnc_mcdp_unmount", + "cnc_mcdp_update_entry", + "cnc_mcdp_wractpt", + "cnc_mcs_rdactset", + "cnc_mcs_rdcompparam", + "cnc_mcs_rdheader", + "cnc_mcs_rdparainfo2", + "cnc_mcs_rdparam", + "cnc_mcs_rdparanum", + "cnc_mcs_wractset", + "cnc_mcs_wrheader", + "cnc_mcs_wrparam", + "cnc_mdd_getswitch", + "cnc_mdd_lock", + "cnc_mdd_rdinfo", + "cnc_mdd_register", + "cnc_mdd_setpassword", + "cnc_mdd_setswitch", + "cnc_mdd_unlock", + "cnc_mdg_moniclear", + "cnc_mdg_monistat", + "cnc_mdg_msgsrch", + "cnc_mdg_rdalminfo", + "cnc_mdg_rdalminfoview2", + "cnc_mdg_rdalmnum", + "cnc_mdg_rdcontinfo", + "cnc_mdg_rddtmsg", + "cnc_mdg_rdflow", + "cnc_mdg_rdheatsimlt", + "cnc_mdg_rdlatchedalm", + "cnc_mdg_rdloadlvl", + "cnc_mdg_rdmsg", + "cnc_mdg_rdmsgnum", + "cnc_mdg_rdmsgordr", + "cnc_mdg_rdorderalmno", + "cnc_mdg_rdsysinfo", + "cnc_mdg_rdwvdata", + "cnc_modal", + "cnc_modal_bg", + "cnc_msim_end", + "cnc_msim_rdprgname", + "cnc_msim_rewind", + "cnc_msim_start", + "cnc_msr_delhis_all", + "cnc_msr_rdhis_allnum", + "cnc_msr_rdhis_inf", + "cnc_msr_rdhis_msudat", + "cnc_msr_rdhis_ncdat", + "cnc_msr_rdhis_ohisnum", + "cnc_msr_rdhis_ohisrec", + "cnc_msr_rdhis_pmc", + "cnc_msr_rdhis_pmc_ex", + "cnc_msr_rdmon_msudat", + "cnc_msr_rdmon_msunum", + "cnc_msr_rdmon_pmcinf", + "cnc_msr_rdmon_pmcinf_ex", + "cnc_msr_start_sample", + "cnc_msr_stop_sample", + "cnc_mtdtnstart", + "cnc_ncg_protcancel", + "cnc_newprog", + "cnc_nextdistance", + "cnc_nextdistance_bg", + "cnc_opentdicubeinfo", + "cnc_pdf_add", + "cnc_pdf_add_bgedt", + "cnc_pdf_chginquiry", + "cnc_pdf_cond", + "cnc_pdf_copy", + "cnc_pdf_cpmv_end", + "cnc_pdf_cpmv_poll", + "cnc_pdf_cpmv_restart", + "cnc_pdf_cpmv_start", + "cnc_pdf_del", + "cnc_pdf_delall", + "cnc_pdf_delchar", + "cnc_pdf_delline", + "cnc_pdf_dncread", + "cnc_pdf_dncset", + "cnc_pdf_dncset2", + "cnc_pdf_dssearch", + "cnc_pdf_mergeprog", + "cnc_pdf_move", + "cnc_pdf_punch", + "cnc_pdf_punch_bg", + "cnc_pdf_rdactpt", + "cnc_pdf_rdactpt_bg", + "cnc_pdf_rdactpt_bgedt", + "cnc_pdf_rdmain", + "cnc_pdf_rdmainpt", + "cnc_pdf_rdprgname", + "cnc_pdf_rdrmtdgn", + "cnc_pdf_read", + "cnc_pdf_read_bg", + "cnc_pdf_relsmain", + "cnc_pdf_rename", + "cnc_pdf_replacechar", + "cnc_pdf_replaceword_all", + "cnc_pdf_searchresult", + "cnc_pdf_searchresult2", + "cnc_pdf_searchresult_bgedt", + "cnc_pdf_searchword", + "cnc_pdf_searchword2", + "cnc_pdf_searchword_bgedt", + "cnc_pdf_slctmain", + "cnc_pdf_startrmtdgn", + "cnc_pdf_stoprmtdgn", + "cnc_pdf_wractpt", + "cnc_pdf_wractpt_bg", + "cnc_pdf_wractpt_bgedt", + "cnc_pmclad_screen", + "cnc_pmmchkalm", + "cnc_pmmget", + "cnc_pmmgetend", + "cnc_pmmgetstart", + "cnc_pmminit", + "cnc_pmmiochanl", + "cnc_pmmprmpage", + "cnc_pmmsysdt", + "cnc_powc_clear_inte", + "cnc_powc_del_cycle_data", + "cnc_powc_rd_clear_time", + "cnc_powc_rd_cycle_data", + "cnc_powc_rd_history", + "cnc_powc_rd_outer_set", + "cnc_powc_wr_outer_set", + "cnc_preset_prm", + "cnc_progdigit", + "cnc_prot_pswcan", + "cnc_prot_pswchg", + "cnc_prot_pswinit", + "cnc_prot_pswinp", + "cnc_prstwkcd", + "cnc_prstwkcd64", + "cnc_ptdownload", + "cnc_ptdwnend", + "cnc_ptdwnstart", + "cnc_ptlink", + "cnc_ptlink2", + "cnc_punch_data", + "cnc_punch_data_end", + "cnc_punch_data_start", + "cnc_punch_prog", + "cnc_punch_prog2", + "cnc_punch_prog2_bg", + "cnc_punch_prog3", + "cnc_punch_prog3_bg", + "cnc_punch_prog_bg", + "cnc_punchlrncrnt", + "cnc_pwcm_clear_consump", + "cnc_pwoff_alarm", + "cnc_rd1length", + "cnc_rd1punchtl_ex", + "cnc_rd1radius", + "cnc_rd1tlifedat2", + "cnc_rd1tlifedata", + "cnc_rd2length", + "cnc_rd2punchtl_ex", + "cnc_rd2radius", + "cnc_rd2tlifedata", + "cnc_rd3dcdcnv", + "cnc_rd3dmovrlap", + "cnc_rd3dofschg", + "cnc_rd3dpulse", + "cnc_rd3dtofs", + "cnc_rd3dtooltip", + "cnc_rd5axmandt", + "cnc_rd5axovrlap", + "cnc_rd5dmacmov", + "cnc_rd5dpulse", + "cnc_rd5dtooltip", + "cnc_rd_grpaxisinfo", + "cnc_rd_grppos", + "cnc_rd_grppos2", + "cnc_rd_grppos3", + "cnc_rd_scrlwaitmcode", + "cnc_rd_sfsg_disp_stat", + "cnc_rd_sfsg_loginf", + "cnc_rd_sfsg_search", + "cnc_rd_sfsg_sighis", + "cnc_rd_sfsg_siginf", + "cnc_rd_sfsg_signal_num", + "cnc_rd_sfsg_update_count", + "cnc_rdabsaxis", + "cnc_rdactdofs", + "cnc_rdactfixofs", + "cnc_rdactpos_w", + "cnc_rdactpt", + "cnc_rdactspdl", + "cnc_rdacttlzone", + "cnc_rdalarmchar", + "cnc_rdalmblkinfo", + "cnc_rdalmhisno", + "cnc_rdalmhisno3", + "cnc_rdalmhistry", + "cnc_rdalmhistry2", + "cnc_rdalmhistry3", + "cnc_rdalmhistry4", + "cnc_rdalmhistry5", + "cnc_rdalminfo", + "cnc_rdalminfo_bg", + "cnc_rdalmmsg", + "cnc_rdalmmsg2", + "cnc_rdalmmsg3", + "cnc_rdaxisdata", + "cnc_rdaxisdata64", + "cnc_rdaxisname", + "cnc_rdaxisstatus_bg", + "cnc_rdblkcount", + "cnc_rdblkdist", + "cnc_rdblockcount", + "cnc_rdbrstrinfo", + "cnc_rdcbmem", + "cnc_rdcbmem2", + "cnc_rdcbprm", + "cnc_rdcdrotate", + "cnc_rdcdslctprmm", + "cnc_rdcenblinfo", + "cnc_rdcenter", + "cnc_rdcexesram", + "cnc_rdcmstatdata", + "cnc_rdcncid", + "cnc_rdcncmem", + "cnc_rdcncmem2", + "cnc_rdcntover", + "cnc_rdcnttype", + "cnc_rdcommand", + "cnc_rdcommand64", + "cnc_rdcoordnum", + "cnc_rdcount", + "cnc_rdcstm_decfig", + "cnc_rdcstmdecfig", + "cnc_rdctname", + "cnc_rdctrldi", + "cnc_rdctrldo", + "cnc_rdcurrent", + "cnc_rddiag_ext", + "cnc_rddiaginfo", + "cnc_rddiaginfo2", + "cnc_rddiagnum", + "cnc_rddischarge", + "cnc_rddischrgalm", + "cnc_rddncdgndt", + "cnc_rddncdgndt2", + "cnc_rddofs", + "cnc_rddsdevinfo", + "cnc_rddsdir", + "cnc_rddsdncfile", + "cnc_rddsfile", + "cnc_rddynamic", + "cnc_rddynamic2", + "cnc_rddynamic3", + "cnc_rddynamic3m", + "cnc_rddynamico8", + "cnc_rdedgeactive", + "cnc_rdedgedata", + "cnc_rdedmcram", + "cnc_rdembedf_inf", + "cnc_rdenblinfo", + "cnc_rdexchgtgrp", + "cnc_rdexecmcode", + "cnc_rdexecprgnum", + "cnc_rdexecprog", + "cnc_rdexecprog2", + "cnc_rdexecprog3", + "cnc_rdexecprog_bg", + "cnc_rdexecpt", + "cnc_rdexecptm", + "cnc_rdfbusmem", + "cnc_rdfctfssinfo", + "cnc_rdfctsitem", + "cnc_rdfctsmsgid", + "cnc_rdfctsstat", + "cnc_rdfeedmode", + "cnc_rdfile_sram", + "cnc_rdfixcycle", + "cnc_rdfixofs", + "cnc_rdfromservoid", + "cnc_rdfromservoid2", + "cnc_rdfromspindleid", + "cnc_rdfromspindleid2", + "cnc_rdfsraminfo", + "cnc_rdfsraminfo2", + "cnc_rdfssb_amp", + "cnc_rdfssb_axis", + "cnc_rdfssb_info", + "cnc_rdfssb_mainte", + "cnc_rdfssb_plsmod", + "cnc_rdftrq_data", + "cnc_rdftrq_info", + "cnc_rdftrq_storecount", + "cnc_rdgcode", + "cnc_rdgcodem", + "cnc_rdglvndiaginfo", + "cnc_rdglvnparainfo", + "cnc_rdglvnparam", + "cnc_rdgrphcmd", + "cnc_rdgrpid", + "cnc_rdgrpid2", + "cnc_rdgrpinfo", + "cnc_rdgrpinfo2", + "cnc_rdgrpinfo3", + "cnc_rdgrpinfo4", + "cnc_rdhdnxt", + "cnc_rdhipitchinfo", + "cnc_rdhipitchr", + "cnc_rdhissgnl", + "cnc_rdhissgnl3", + "cnc_rdhndintrpt", + "cnc_rdholmes", + "cnc_rdhsparam", + "cnc_rdhsparamm", + "cnc_rdhsprminfo", + "cnc_rdifsb_almstate", + "cnc_rdifsb_as_amp_sp", + "cnc_rdifsb_as_amp_sv", + "cnc_rdifsb_as_hrv", + "cnc_rdifsb_as_plsmod", + "cnc_rdifsb_as_sv_axis", + "cnc_rdifsb_comstatdtl", + "cnc_rdifsb_fssbunt", + "cnc_rdifsb_info", + "cnc_rdifsb_mainte_sp", + "cnc_rdifsb_mainte_sv", + "cnc_rdifsb_slu_pm", + "cnc_rdifsb_slu_sp", + "cnc_rdifsb_slu_sv", + "cnc_rdifsb_slvunt", + "cnc_rdifsb_sysalm", + "cnc_rdifsb_warnhst_cnt", + "cnc_rdifsb_warnhst_msg", + "cnc_rdifsb_warning_cnt", + "cnc_rdifsb_warning_msg", + "cnc_rdindexdata", + "cnc_rdindexinfo", + "cnc_rdindexofs", + "cnc_rdindexposdata", + "cnc_rdindexprm", + "cnc_rdintchk", + "cnc_rdinterference", + "cnc_rdintinfo", + "cnc_rdioassigned", + "cnc_rdipltp", + "cnc_rdipltp_bg", + "cnc_rdjogdrun", + "cnc_rdjogmdi", + "cnc_rdlactnum", + "cnc_rdlagingmode", + "cnc_rdlagingtime", + "cnc_rdlagslt", + "cnc_rdlalmhistry", + "cnc_rdlcmddat", + "cnc_rdlcmmt", + "cnc_rdlcstmname", + "cnc_rdldsplc", + "cnc_rdldsplc2", + "cnc_rdledgprc", + "cnc_rdlenofs", + "cnc_rdlerrz", + "cnc_rdlfiberdata", + "cnc_rdlhsstate", + "cnc_rdlife", + "cnc_rdlnzlmcn", + "cnc_rdloopgain", + "cnc_rdlpoweroffset", + "cnc_rdlppfbdt", + "cnc_rdlprcprc", + "cnc_rdlpscdpwrctl", + "cnc_rdlpwrctrl", + "cnc_rdlpwrdat", + "cnc_rdlpwrdty", + "cnc_rdlpwrslt", + "cnc_rdlrninfo", + "cnc_rdlrninfo2", + "cnc_rdlrninfol", + "cnc_rdlrnprfcmnt", + "cnc_rdlrntrnsdata", + "cnc_rdmacro", + "cnc_rdmacro_bg", + "cnc_rdmacroinfo", + "cnc_rdmacrolclevel", + "cnc_rdmacrolcval", + "cnc_rdmacronum", + "cnc_rdmacror", + "cnc_rdmacror2", + "cnc_rdmacror2_name", + "cnc_rdmacror3", + "cnc_rdmacror4", + "cnc_rdmag_property", + "cnc_rdmagazine", + "cnc_rdmaxgrp", + "cnc_rdmaxtool", + "cnc_rdmcdfinfo", + "cnc_rdmcdprgcmnt", + "cnc_rdmdipntr", + "cnc_rdmdipntro8", + "cnc_rdmdiprgstat", + "cnc_rdmdiprog", + "cnc_rdmdlconfig2", + "cnc_rdmenuswitch", + "cnc_rdmgrpdata", + "cnc_rdmirimage", + "cnc_rdmngtime", + "cnc_rdmodalval", + "cnc_rdmofs", + "cnc_rdmovestate", + "cnc_rdmovrlap", + "cnc_rdmovrlapm", + "cnc_rdmtdtnid", + "cnc_rdmultitldt", + "cnc_rdngrp", + "cnc_rdnspdl", + "cnc_rdntool", + "cnc_rdnutatortofs_vect", + "cnc_rdofslength", + "cnc_rdomhisinfo", + "cnc_rdomhisno", + "cnc_rdomhistry", + "cnc_rdomhistry2", + "cnc_rdope_lvl", + "cnc_rdophisno", + "cnc_rdophisno3", + "cnc_rdophistry", + "cnc_rdophistry2", + "cnc_rdophistry3", + "cnc_rdophistry4", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdopmsg2", + "cnc_rdopmsg3", + "cnc_rdopmsg3m", + "cnc_rdopnlgnrl", + "cnc_rdopnlgnrl2", + "cnc_rdopnlgsname", + "cnc_rdopnlgsname2", + "cnc_rdopnlsgnl", + "cnc_rdoptfuncinfo", + "cnc_rdoverstore", + "cnc_rdoverstoremode", + "cnc_rdpalaxis", + "cnc_rdparainfo", + "cnc_rdparainfo2", + "cnc_rdparainfo3", + "cnc_rdparam", + "cnc_rdparam3", + "cnc_rdparam64", + "cnc_rdparam_ext", + "cnc_rdparanum", + "cnc_rdparar", + "cnc_rdparar3", + "cnc_rdpdf_alldir", + "cnc_rdpdf_curdir", + "cnc_rdpdf_drive", + "cnc_rdpdf_execline", + "cnc_rdpdf_inf", + "cnc_rdpdf_line", + "cnc_rdpdf_line2", + "cnc_rdpdf_line_bgedt", + "cnc_rdpdf_pglockstat", + "cnc_rdpdf_prginf", + "cnc_rdpdf_subdir", + "cnc_rdpdf_subdirn", + "cnc_rdpglockstat", + "cnc_rdpitchblkinfo", + "cnc_rdpitchinfo", + "cnc_rdpitchr", + "cnc_rdpitchr2", + "cnc_rdplutosmpl", + "cnc_rdpm_item", + "cnc_rdpm_mcnitem", + "cnc_rdpmacro", + "cnc_rdpmacroinfo", + "cnc_rdpmacroinfo2", + "cnc_rdpmacroinfo3", + "cnc_rdpmacror", + "cnc_rdpmacror2", + "cnc_rdpmacror_bg", + "cnc_rdpmacvalflag", + "cnc_rdpmmprmtp", + "cnc_rdposerrs", + "cnc_rdposerrs2", + "cnc_rdposerrz", + "cnc_rdposfig", + "cnc_rdposition", + "cnc_rdpossmpl", + "cnc_rdpot_property", + "cnc_rdpressure", + "cnc_rdprgdirtime", + "cnc_rdprgnum", + "cnc_rdprgnumo8", + "cnc_rdprgnumo8_bg", + "cnc_rdprgrmupdtcnt", + "cnc_rdproctime", + "cnc_rdproctime2", + "cnc_rdproctime3", + "cnc_rdprogdata", + "cnc_rdprogdir", + "cnc_rdprogdir2", + "cnc_rdprogdir2o8", + "cnc_rdprogdir3", + "cnc_rdprogdir4", + "cnc_rdprogdiro8", + "cnc_rdproginfo", + "cnc_rdprogline", + "cnc_rdprogline2", + "cnc_rdprotect", + "cnc_rdprotect2", + "cnc_rdprstrinfo", + "cnc_rdprstrinfom", + "cnc_rdprt_data", + "cnc_rdprt_lvl", + "cnc_rdpscdedge", + "cnc_rdpscdedge2", + "cnc_rdpscdpirc", + "cnc_rdpscdproc", + "cnc_rdpscdproc2", + "cnc_rdpscdslop", + "cnc_rdptaxfunctablestatus", + "cnc_rdptaxitablestatus", + "cnc_rdptcmdsize", + "cnc_rdptcnvalm", + "cnc_rdptcnvinfo", + "cnc_rdptcnvinfo2", + "cnc_rdptcomment", + "cnc_rdptexedistalm", + "cnc_rdpthis_aux", + "cnc_rdpthis_ax", + "cnc_rdpthis_gb", + "cnc_rdpthis_log", + "cnc_rdpthis_num", + "cnc_rdpthis_pt", + "cnc_rdpthis_sp", + "cnc_rdptsptablestatus", + "cnc_rdptstoptime", + "cnc_rdpwofsthis", + "cnc_rdradofs", + "cnc_rdrelaxis", + "cnc_rdrepeatval", + "cnc_rdrepeatval_ext", + "cnc_rdrotvolc", + "cnc_rdrstrmcode", + "cnc_rdrtmiowrenbl", + "cnc_rdrtmiowrenblbit", + "cnc_rdrtmiowrenblrng", + "cnc_rdrtmrvar", + "cnc_rdrtmrvars", + "cnc_rdsafetyzone", + "cnc_rdscaling", + "cnc_rdscdldat", + "cnc_rdscdlinfo", + "cnc_rdseqnum", + "cnc_rdseqnum_bg", + "cnc_rdservoid", + "cnc_rdservoid2", + "cnc_rdset", + "cnc_rdsetinfo", + "cnc_rdsetinfo2", + "cnc_rdsetnum", + "cnc_rdsetr", + "cnc_rdsetzone", + "cnc_rdsoc_curdat", + "cnc_rdsoc_tlatrr", + "cnc_rdsoc_tldat", + "cnc_rdsoc_wave", + "cnc_rdsoc_wave_end", + "cnc_rdsoc_wave_start", + "cnc_rdspcss", + "cnc_rdspdlalm", + "cnc_rdspdlname", + "cnc_rdspdlnamem", + "cnc_rdspdlspeed", + "cnc_rdspdlwaitname", + "cnc_rdspeed", + "cnc_rdspgear", + "cnc_rdspindleid", + "cnc_rdspindleid2", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdspmonitor", + "cnc_rdsrvspeed", + "cnc_rdsrvtcmd", + "cnc_rdsrvtsa", + "cnc_rdsuo_prm_name", + "cnc_rdsuofs_vect", + "cnc_rdsvmeter", + "cnc_rdsvmonitor", + "cnc_rdsynerrrg", + "cnc_rdsynerrsy", + "cnc_rdsysalm", + "cnc_rdsyshard", + "cnc_rdsyshard_str", + "cnc_rdsyssoft", + "cnc_rdsyssoft2", + "cnc_rdsyssoft3", + "cnc_rdsyssoft3_str", + "cnc_rdtcodemsg", + "cnc_rdtdiblankfigno", + "cnc_rdtdicomment", + "cnc_rdtdicrntshapeinf", + "cnc_rdtdicubedata", + "cnc_rdtdicubeinfo", + "cnc_rdtdicurrentshape", + "cnc_rdtdicylinderdata", + "cnc_rdtdidispsetting", + "cnc_rdtdieffectshape", + "cnc_rdtdifignum", + "cnc_rdtdifiguredata", + "cnc_rdtdiinfo", + "cnc_rdtdiinitview", + "cnc_rdtdimoveaxis", + "cnc_rdtdinamesetting", + "cnc_rdtdiplanedata", + "cnc_rdtdiseltool", + "cnc_rdtdishapedata", + "cnc_rdtimer", + "cnc_rdtlctldata", + "cnc_rdtldata", + "cnc_rdtldspcstms", + "cnc_rdtlgeomsize", + "cnc_rdtlgeomsize_ext", + "cnc_rdtlgeomsize_ext2", + "cnc_rdtlgrp", + "cnc_rdtlinfo", + "cnc_rdtlmgr_check", + "cnc_rdtlmgr_name", + "cnc_rdtlmsinfo", + "cnc_rdtlname", + "cnc_rdtlnewstatus", + "cnc_rdtltool", + "cnc_rdtlusegrp", + "cnc_rdtofs", + "cnc_rdtofs_bg", + "cnc_rdtofsenbl", + "cnc_rdtofsinfo", + "cnc_rdtofsinfo2", + "cnc_rdtofsr", + "cnc_rdtofsr_bg", + "cnc_rdtool", + "cnc_rdtool2", + "cnc_rdtool_cause", + "cnc_rdtool_f2", + "cnc_rdtool_inhis", + "cnc_rdtool_outhis", + "cnc_rdtoolchggrp", + "cnc_rdtooldata", + "cnc_rdtoolgeom_tlm", + "cnc_rdtoolgrp", + "cnc_rdtoolinfo", + "cnc_rdtoollife_count", + "cnc_rdtoollife_data", + "cnc_rdtoollife_tcodedata", + "cnc_rdtoollifed_count", + "cnc_rdtoollifed_data", + "cnc_rdtoolrng", + "cnc_rdtoolzone", + "cnc_rdusegrpid", + "cnc_rdusetlno", + "cnc_rduvactpt2", + "cnc_rdvolc", + "cnc_rdvolccomp", + "cnc_rdwavecount", + "cnc_rdwavedata", + "cnc_rdwavedata2", + "cnc_rdwavedata3", + "cnc_rdwaveprm", + "cnc_rdwaveprm2", + "cnc_rdwaveprm3", + "cnc_rdwkcdsfms", + "cnc_rdwkcdsfms2", + "cnc_rdwkcdsfms3", + "cnc_rdwkcdsfms64", + "cnc_rdwkcdshft", + "cnc_rdwkcdshft2", + "cnc_rdwkcdshft3", + "cnc_rdwkcdshft64", + "cnc_rdwkcdshft_bg", + "cnc_rdwseterror", + "cnc_rdzofs", + "cnc_rdzofs_bg", + "cnc_rdzofsinfo", + "cnc_rdzofsmes", + "cnc_rdzofsr", + "cnc_rdzofsr64", + "cnc_rdzofsr_bg", + "cnc_read_cexeinfo", + "cnc_read_data", + "cnc_read_prog", + "cnc_read_prog2", + "cnc_read_prog2_bg", + "cnc_read_prog3", + "cnc_read_prog3_bg", + "cnc_read_prog_bg", + "cnc_readlrncrnt", + "cnc_reg_toolstrage", + "cnc_regmagazine", + "cnc_regtool", + "cnc_regtool_f2", + "cnc_relative", + "cnc_relative2", + "cnc_relative2_exdgt", + "cnc_relative_bg", + "cnc_renameprog", + "cnc_req_alarm", + "cnc_reset", + "cnc_reset2", + "cnc_reset_prps", + "cnc_resetcntr", + "cnc_resetpdf_pglock", + "cnc_resetpglock", + "cnc_restorlrn", + "cnc_rewind", + "cnc_robo_clrsignals", + "cnc_robo_rdalmmsg", + "cnc_robo_rdcomsetting", + "cnc_robo_rdgrouplist", + "cnc_robo_rdponprop", + "cnc_robo_rdselectedsignals", + "cnc_robo_rdsignals", + "cnc_robo_rdsignals2", + "cnc_robo_selectgroup", + "cnc_robo_wralmmsg", + "cnc_robo_wrcomsetting", + "cnc_robo_wrgroup", + "cnc_robo_wrselectedsignals", + "cnc_robo_wrsignalname", + "cnc_robo_wrsignals2", + "cnc_rstrdstatus", + "cnc_rstrseqsrch", + "cnc_rstrseqsrch2", + "cnc_rstrt_createpnt", + "cnc_rstrt_getdncprg", + "cnc_rstrt_getpntcnt", + "cnc_rstrt_lpslctblk", + "cnc_rstrt_rdaddinfo", + "cnc_rstrt_rdlpmppnt", + "cnc_rstrt_rdmodal", + "cnc_rstrt_rdpnt", + "cnc_rstrt_rdpnt2", + "cnc_rstrt_rdpntlist", + "cnc_rstrt_rdpntlist2", + "cnc_rstrt_search", + "cnc_rstrt_selectpnt", + "cnc_rstrt_setsuppress", + "cnc_rstrt_wrpnt", + "cnc_rstrt_wrpnt2", + "cnc_s5s_rdactset", + "cnc_s5s_rdname", + "cnc_s5s_rdparainfo2", + "cnc_s5s_rdparam", + "cnc_s5s_rdparanum", + "cnc_s5s_wractset", + "cnc_s5s_wrname", + "cnc_s5s_wrparam", + "cnc_save_maint", + "cnc_saveprog_end", + "cnc_saveprog_start", + "cnc_sdbetainfo", + "cnc_sdcancelsmpl", + "cnc_sdclrchnl", + "cnc_sdclrocp", + "cnc_sdendocp", + "cnc_sdendsmpl", + "cnc_sdendsmpl2", + "cnc_sdfmnghwnd", + "cnc_sdfstatchg", + "cnc_sdread1shot", + "cnc_sdreadsmpl", + "cnc_sdreadsmpl2", + "cnc_sdsetchnl", + "cnc_sdsetchnl2", + "cnc_sdstartocp", + "cnc_sdstartsmpl", + "cnc_sdstartsmpl2", + "cnc_sdstartsmplb", + "cnc_sdtcancelsmpl", + "cnc_sdtclrchnl", + "cnc_sdtclrocp", + "cnc_sdtendocp", + "cnc_sdtendsmpl", + "cnc_sdtendsmpl2", + "cnc_sdtread1shot", + "cnc_sdtreadsmpl", + "cnc_sdtreadsmpl2", + "cnc_sdtsetchnl", + "cnc_sdtsetchnl2", + "cnc_sdtstartocp", + "cnc_sdtstartsmpl", + "cnc_sdtstartsmpl2", + "cnc_search", + "cnc_search2", + "cnc_searcho8", + "cnc_searchresult", + "cnc_searchresult2", + "cnc_searchword", + "cnc_searchword2", + "cnc_sendkey", + "cnc_seqrdtdicubeinfo", + "cnc_seqsrch", + "cnc_seqsrch2", + "cnc_set_cutcnd_exval", + "cnc_set_prps", + "cnc_set_smth_exval", + "cnc_setactptopt", + "cnc_setallexauxr", + "cnc_setcurscrn", + "cnc_setdtailerr", + "cnc_setfrp", + "cnc_setlibopt", + "cnc_setmactype", + "cnc_setpath", + "cnc_setpdf_pglock", + "cnc_setpdf_pglockexec", + "cnc_setpglock", + "cnc_setpmactype", + "cnc_setrstraxis", + "cnc_setsumdt", + "cnc_settdiobjectshape", + "cnc_settditoolshape", + "cnc_settimer", + "cnc_settolnum_qset", + "cnc_settpnlcalib", + "cnc_setzofsnum_qset", + "cnc_skip", + "cnc_slctptdata", + "cnc_slctpttype", + "cnc_slide", + "cnc_soc_wave_setchnl", + "cnc_sramget", + "cnc_sramget2", + "cnc_sramgetend", + "cnc_sramgetend2", + "cnc_sramgetstart", + "cnc_sramgetstart2", + "cnc_sramstat", + "cnc_srttl_getdata", + "cnc_srttl_getnum", + "cnc_srvdelay", + "cnc_start", + "cnc_start_async_data_punch", + "cnc_start_async_data_read", + "cnc_start_async_pdf_punch", + "cnc_start_async_pdf_read", + "cnc_start_async_punch_prog3", + "cnc_start_async_punch_prog3_bg", + "cnc_start_async_read_prog3", + "cnc_start_async_read_prog3_bg", + "cnc_start_async_wrparam", + "cnc_start_grppos", + "cnc_start_grppos2", + "cnc_start_grppos3", + "cnc_startaux", + "cnc_startdyngrph", + "cnc_startnccmd", + "cnc_startomhis", + "cnc_startophis", + "cnc_startptcnv", + "cnc_statinfo", + "cnc_statinfo2", + "cnc_statinfo_bg", + "cnc_statinfo_dmg", + "cnc_statlrntrns", + "cnc_status_prps", + "cnc_stop_async_read_punch", + "cnc_stop_grppos", + "cnc_stop_grppos2", + "cnc_stop_grppos3", + "cnc_stopdyngrph", + "cnc_stoplrntrns", + "cnc_stopnccmd", + "cnc_stopomhis", + "cnc_stopophis", + "cnc_stplutosmpl", + "cnc_stpossmpl", + "cnc_svdtendrd", + "cnc_svdtendwr", + "cnc_svdtrddata", + "cnc_svdtstartrd", + "cnc_svdtstartwr", + "cnc_svdtstopexec", + "cnc_svdtwrdata", + "cnc_sysinfo", + "cnc_sysinfo_ex", + "cnc_t1info", + "cnc_t2info", + "cnc_tofs_rnge", + "cnc_tool_in", + "cnc_tool_in2", + "cnc_tool_in3", + "cnc_tool_move", + "cnc_tool_out", + "cnc_tool_srh_free_min_num", + "cnc_tool_temp_in", + "cnc_tool_temp_out", + "cnc_toolnum", + "cnc_toolsrch", + "cnc_tpl_read", + "cnc_twp_rdfcoord", + "cnc_twp_rdfmt_mtrx", + "cnc_updexauxr", + "cnc_upend", + "cnc_upend3", + "cnc_upend4", + "cnc_upload", + "cnc_upload3", + "cnc_upload4", + "cnc_upstart", + "cnc_upstart3", + "cnc_upstart4", + "cnc_upstarto8", + "cnc_validate_opt", + "cnc_validate_prm", + "cnc_verify", + "cnc_verify4", + "cnc_verify_prog", + "cnc_verify_prog_bg", + "cnc_vrfend", + "cnc_vrfend4", + "cnc_vrfstart", + "cnc_vrfstart4", + "cnc_wavestart", + "cnc_wavestat", + "cnc_wavestop", + "cnc_wksft_rnge", + "cnc_wksft_rnge64", + "cnc_workzero", + "cnc_wr1length", + "cnc_wr1radius", + "cnc_wr1tlifedat2", + "cnc_wr1tlifedata", + "cnc_wr2length", + "cnc_wr2radius", + "cnc_wr2tlifedata", + "cnc_wr_scrlwaitmcode", + "cnc_wr_sfsg_disp_stat", + "cnc_wr_sfsg_extractslct", + "cnc_wractpt", + "cnc_wrcbmem", + "cnc_wrcbmem2", + "cnc_wrcbprm", + "cnc_wrcexesram", + "cnc_wrcncmem", + "cnc_wrcncmem2", + "cnc_wrcnttype", + "cnc_wrcount", + "cnc_wrcountr", + "cnc_wrdofs", + "cnc_wrdsdncfile", + "cnc_wrdsdncfile2", + "cnc_wredgedata", + "cnc_wredgedata2", + "cnc_wredmcram", + "cnc_wrfbusmem", + "cnc_wrfctfssadjust", + "cnc_wrfctfssinfo", + "cnc_wrfctsitem", + "cnc_wrfile_sram", + "cnc_wrfixofs", + "cnc_wrfromservoid", + "cnc_wrfromservoid2", + "cnc_wrfromspindleid", + "cnc_wrfromspindleid2", + "cnc_wrfssb_axis", + "cnc_wrfssb_axis_num", + "cnc_wrftl", + "cnc_wrglvnparam", + "cnc_wrgrpinfo", + "cnc_wrgrpinfo2", + "cnc_wrgrpinfo3", + "cnc_wrhipitchr", + "cnc_wrhissgnl", + "cnc_wrhissgnl3", + "cnc_wrifsb_as_axis_num", + "cnc_wrifsb_as_hrv", + "cnc_wrifsb_as_spdl_num", + "cnc_wrifsb_as_sv_axis", + "cnc_wrindexdata", + "cnc_wrindexofs", + "cnc_wrindexposdata", + "cnc_wrindexprm", + "cnc_wrintchk", + "cnc_wrjogmdi", + "cnc_wrjogmdiclr", + "cnc_wrkeyhistry", + "cnc_wrlagingmode", + "cnc_wrlagslt", + "cnc_wrldsplc", + "cnc_wrldsplc2", + "cnc_wrledgprc", + "cnc_wrlife", + "cnc_wrlppfbdt", + "cnc_wrlprcprc", + "cnc_wrlpscdpwrctl", + "cnc_wrlpwrctrl", + "cnc_wrlpwrdty", + "cnc_wrlpwrslt", + "cnc_wrlrninfo", + "cnc_wrlrnprf", + "cnc_wrlswork", + "cnc_wrmacro", + "cnc_wrmacro_bg", + "cnc_wrmacror", + "cnc_wrmacror2", + "cnc_wrmag_property", + "cnc_wrmagazine", + "cnc_wrmdipntr", + "cnc_wrmdiprog", + "cnc_wrmenuswitch", + "cnc_wrmgrpdata", + "cnc_wrmngtime", + "cnc_wrmofs", + "cnc_wrmultitldt", + "cnc_wropnlgnrl", + "cnc_wropnlgnrl2", + "cnc_wropnlgsname", + "cnc_wropnlgsname2", + "cnc_wropnlsgnl", + "cnc_wroverstore", + "cnc_wrparam", + "cnc_wrparam3", + "cnc_wrparam64", + "cnc_wrparas", + "cnc_wrparas3", + "cnc_wrpdf_attr", + "cnc_wrpdf_char", + "cnc_wrpdf_curdir", + "cnc_wrpdf_line", + "cnc_wrpitchr", + "cnc_wrpitchr2", + "cnc_wrpm_item", + "cnc_wrpm_mcnitem", + "cnc_wrpmacro", + "cnc_wrpmacror", + "cnc_wrpmacror2", + "cnc_wrpmacror_bg", + "cnc_wrpmmprm", + "cnc_wrpmmprmtp", + "cnc_wrpot_property", + "cnc_wrprogline", + "cnc_wrprt_lvl", + "cnc_wrpscdedge", + "cnc_wrpscdedge2", + "cnc_wrpscdpirc", + "cnc_wrpscdproc", + "cnc_wrpscdproc2", + "cnc_wrpscdslop", + "cnc_wrptstoptime", + "cnc_wrpunchtl_ex", + "cnc_wrrelpos", + "cnc_wrrelpos64", + "cnc_wrrotvolc", + "cnc_wrrotvolc2", + "cnc_wrrtmiowrenbl", + "cnc_wrrtmiowrenblbit", + "cnc_wrrtmiowrenblrng", + "cnc_wrrtmrvar", + "cnc_wrrtmrvars", + "cnc_wrsafetyzone", + "cnc_wrscdldat", + "cnc_wrset", + "cnc_wrsets", + "cnc_wrsetzone", + "cnc_wrsignal_f", + "cnc_wrsoc_tldat", + "cnc_wrt1info", + "cnc_wrt2info", + "cnc_wrtdicomment", + "cnc_wrtdicubedata", + "cnc_wrtdicylinderdata", + "cnc_wrtdidispsetting", + "cnc_wrtdieffectshape", + "cnc_wrtdifignum", + "cnc_wrtdifiguredata", + "cnc_wrtdiinitview", + "cnc_wrtdimoveaxis", + "cnc_wrtdinamesetting", + "cnc_wrtdiplanedata", + "cnc_wrtdishapedata", + "cnc_wrtimer", + "cnc_wrtlctldata", + "cnc_wrtldata", + "cnc_wrtlgeomsize", + "cnc_wrtlgeomsize_ext", + "cnc_wrtlgeomsize_ext2", + "cnc_wrtofs", + "cnc_wrtofsdrctinp", + "cnc_wrtofsdrctinp64", + "cnc_wrtofsms", + "cnc_wrtofsr", + "cnc_wrtool", + "cnc_wrtool2", + "cnc_wrtool_f2", + "cnc_wrtooldata", + "cnc_wrtoolgeom_tlm", + "cnc_wrtoolgrp", + "cnc_wrtoolnum", + "cnc_wrtoolzone", + "cnc_wrtrqlimit", + "cnc_wrvolc", + "cnc_wrwaveprm", + "cnc_wrwaveprm2", + "cnc_wrwaveprm3", + "cnc_wrwkcdsfms", + "cnc_wrwkcdsfms2", + "cnc_wrwkcdsfms3", + "cnc_wrwkcdsfms64", + "cnc_wrwkcdshft", + "cnc_wrwkcdshft2", + "cnc_wrwkcdshft3", + "cnc_wrwkcdshft64", + "cnc_wrwseterror", + "cnc_wrzofs", + "cnc_wrzofs64", + "cnc_wrzofsr", + "cnc_zofs_rnge", + "cnc_zofs_rnge64", + "dnm_clrerrorrecord", + "dnm_rderrorrecord", + "dnm_rdfirminfo", + "dnm_rdnodeinfo", + "dnm_rdnodetable", + "dnm_rdparam", + "dnm_wrparam", + "dns_rdinfo", + "dns_rdparam", + "dns_restart", + "dns_wrparam", + "eth_abortdnc", + "eth_abortdownload", + "eth_abortupload", + "eth_applyunsolicprm", + "eth_changelockstat1", + "eth_changelockstat2", + "eth_clrlog", + "eth_clrlsistate", + "eth_disconfsclnt", + "eth_disconfsclntall", + "eth_dncsegment", + "eth_downloadsegment", + "eth_dschkdsk", + "eth_dsformat", + "eth_embrestart", + "eth_exit", + "eth_getlockstat1", + "eth_getlockstat2", + "eth_init", + "eth_initiatednc", + "eth_initiatedownload", + "eth_initiateupload", + "eth_ping", + "eth_ping_cancel", + "eth_ping_result", + "eth_rddsformat", + "eth_rddsm198dir", + "eth_rddsm198host", + "eth_rddsmode", + "eth_rddsstate", + "eth_rdembdev", + "eth_rdembm198host", + "eth_rdfsclntinfo", + "eth_rdhost", + "eth_rdlog", + "eth_rdlsistate", + "eth_rdparam", + "eth_rdrmdinquiry", + "eth_rdtaskstate", + "eth_rdtype", + "eth_rdtype2", + "eth_rdunsolicmode", + "eth_rdunsolicstate", + "eth_remotecontrol", + "eth_terminatednc", + "eth_terminatedownload", + "eth_uploadsegment", + "eth_workinit", + "eth_wrdsm198dir", + "eth_wrdsm198host", + "eth_wrdsmode", + "eth_wrembdev", + "eth_wrembm198host", + "eth_wrhost", + "eth_wrparam", + "eth_wrrmdinquiry", + "eth_wrunsolicmode", + "flnt_clrlog", + "flnt_clrlog2", + "flnt_clrmsg", + "flnt_clrmsg2", + "flnt_clrnetwork2", + "flnt_rddeviceinfo", + "flnt_rdentry", + "flnt_rdentry2", + "flnt_rdlog", + "flnt_rdlog2", + "flnt_rdmsg", + "flnt_rdmsg2", + "flnt_rdnetwork", + "flnt_rdnetwork2", + "flnt_rdnodeinfo", + "flnt_rdnodeinfo2", + "flnt_rdparam", + "flnt_rdparam2", + "flnt_rdsferrnode", + "flnt_rdsfstatus", + "flnt_wrparam", + "flnt_wrparam2", + "net_backup_param", + "net_restore_param", + "pbm_chg_mode", + "pbm_exe_subfunc", + "pbm_ini_prm", + "pbm_rd_allslvtbl", + "pbm_rd_cominfo", + "pbm_rd_errcode", + "pbm_rd_nodeinfo", + "pbm_rd_nodetable", + "pbm_rd_param", + "pbm_rd_slot", + "pbm_rd_slotinfo", + "pbm_rd_subprm", + "pbm_wr_param", + "pbs_ini_prm", + "pbs_rd_cominfo", + "pbs_rd_cominfo2", + "pbs_rd_param", + "pbs_rd_param2", + "pbs_wr_param", + "pbs_wr_param2", + "pmc_abort_all_transfer", + "pmc_abort_auto_trace_output", + "pmc_abort_end_process", + "pmc_abort_multiple_write_check", + "pmc_abort_trace_function", + "pmc_abort_transfer", + "pmc_allocate_work_memory", + "pmc_can_apply_override", + "pmc_change_operation", + "pmc_change_operation_with_passwd", + "pmc_change_scoped_symcmt", + "pmc_check_io_assignment_empty", + "pmc_check_iolinki_assign", + "pmc_check_permission", + "pmc_check_permission_with_passwd", + "pmc_clear_io_configuration", + "pmc_control_snp_driver", + "pmc_convert_address_pmc_to_vio", + "pmc_convert_from_string_to_address", + "pmc_convert_pmc_address_from_sync", + "pmc_convert_pmc_address_to_string", + "pmc_convert_pmc_address_to_sync", + "pmc_convert_vio_address_from_sync", + "pmc_convert_vio_address_to_string", + "pmc_convert_vio_address_to_sync", + "pmc_convret_address_vio_to_pmc", + "pmc_delete_all_io_assignment_data", + "pmc_delete_all_message_data", + "pmc_delete_all_symcmt_data", + "pmc_delete_data_table_group_data", + "pmc_delete_extra_relay_group_data", + "pmc_delete_io_assignment_data", + "pmc_delete_iolinki_assign_group", + "pmc_delete_message_entry", + "pmc_delete_node_mutex", + "pmc_delete_scoped_symcmt", + "pmc_delete_seq_program_object", + "pmc_delete_sfc_monitor_time", + "pmc_delete_symcmt_entry", + "pmc_end_search_symbol", + "pmc_exchange_permission", + "pmc_fdcas_compare_file_by_name", + "pmc_fdcas_compare_file_by_number", + "pmc_fdcas_delete_all_files", + "pmc_fdcas_delete_file_by_name", + "pmc_fdcas_delete_file_by_number", + "pmc_fdcas_get_comm_parameter", + "pmc_fdcas_init_comm_parameter", + "pmc_fdcas_read_directory", + "pmc_fdcas_read_file_by_name", + "pmc_fdcas_read_file_by_number", + "pmc_fdcas_read_file_count", + "pmc_fdcas_read_file_status", + "pmc_fdcas_set_comm_parameter", + "pmc_fdcas_write_pmc_parameter", + "pmc_fdcas_write_seq_program", + "pmc_flashrom_compare_message", + "pmc_flashrom_compare_seq_program", + "pmc_flashrom_read_message", + "pmc_flashrom_read_seq_program", + "pmc_flashrom_write_message", + "pmc_flashrom_write_seq_program", + "pmc_force_release_object", + "pmc_force_status_bit", + "pmc_force_status_byte", + "pmc_get_address_entry", + "pmc_get_alarm_status", + "pmc_get_all_associated_symbols", + "pmc_get_all_associated_symcmts", + "pmc_get_auto_trace_output_status", + "pmc_get_available_comment_set", + "pmc_get_available_io_connection", + "pmc_get_common_memory_group", + "pmc_get_current_comment", + "pmc_get_current_divided_ladder", + "pmc_get_current_pmc", + "pmc_get_current_pmc_unit", + "pmc_get_current_program_number", + "pmc_get_data_table_group_count", + "pmc_get_data_table_max_group_count", + "pmc_get_default_filename", + "pmc_get_divided_ladder", + "pmc_get_divided_ladders", + "pmc_get_extra_relay_group_count", + "pmc_get_extra_relay_max_group_count", + "pmc_get_file_info_count", + "pmc_get_font_information", + "pmc_get_instruction_set_version", + "pmc_get_io_assignment_address", + "pmc_get_io_configuration", + "pmc_get_io_device_diagnosis", + "pmc_get_io_device_id", + "pmc_get_io_device_list", + "pmc_get_max_sfc_monitor_time_count", + "pmc_get_maximum_trace_frames", + "pmc_get_message_data_size", + "pmc_get_message_entry_address", + "pmc_get_message_entry_count", + "pmc_get_message_maximum_size", + "pmc_get_message_remain_memory", + "pmc_get_multiple_coil_result", + "pmc_get_multiple_instruction_result", + "pmc_get_multiple_symcmts", + "pmc_get_nonvolatile_memory_status", + "pmc_get_number_of_address_entries", + "pmc_get_number_of_iolinki_assign_channels", + "pmc_get_number_of_ladder", + "pmc_get_number_of_pmc", + "pmc_get_number_of_pmc_path", + "pmc_get_object_update_counter", + "pmc_get_object_version", + "pmc_get_override_status", + "pmc_get_permission_status", + "pmc_get_pmc_address_attribute", + "pmc_get_pmc_address_info", + "pmc_get_pmc_address_map", + "pmc_get_pmc_path_types", + "pmc_get_pmc_unit_types", + "pmc_get_programable_io_channels", + "pmc_get_real_io_assignment_info", + "pmc_get_real_selectable_io_assignment_data", + "pmc_get_result_trace_parameter", + "pmc_get_scoped_symcmt", + "pmc_get_search_symbol_result", + "pmc_get_search_symbol_status", + "pmc_get_sfc_active_step_trace", + "pmc_get_sfc_diagram_memory", + "pmc_get_sfc_monitor_status", + "pmc_get_sfc_monitor_time_information", + "pmc_get_snp_communication_status", + "pmc_get_snp_driver_status", + "pmc_get_splist_status", + "pmc_get_subprogram_list", + "pmc_get_symbol_comment", + "pmc_get_symbol_comment2", + "pmc_get_symbol_object_version", + "pmc_get_symcmt_bit", + "pmc_get_symcmt_byte", + "pmc_get_symcmt_data_size", + "pmc_get_symcmt_entry_count", + "pmc_get_symcmt_maximum_size", + "pmc_get_symcmt_remain_memory", + "pmc_get_timer_type", + "pmc_get_tmr_instruction_accuracy", + "pmc_get_trace_result", + "pmc_get_trace_specification", + "pmc_get_trace_status", + "pmc_get_transfer_status", + "pmc_get_unit_type", + "pmc_get_xref_address_result", + "pmc_get_xref_address_result2", + "pmc_get_xref_instruction_result", + "pmc_get_xref_status", + "pmc_getdtailerr", + "pmc_getpmcptr", + "pmc_giveout_node_mutex", + "pmc_global_program_number_to_local", + "pmc_handle_work_area_size", + "pmc_initialize_data_table_group", + "pmc_initialize_extra_relay_group", + "pmc_initialize_handle_work_area", + "pmc_initialize_iolinki_assign_channel", + "pmc_initialize_node_mutex", + "pmc_initialize_node_work_area", + "pmc_initialize_nonvolatile_memory", + "pmc_insert_iolinki_assign_group", + "pmc_insert_seq_program_space", + "pmc_is_bit_accessible_pmc_address", + "pmc_is_effective_pmc_address", + "pmc_is_fb_instance_id", + "pmc_is_seq_program_available", + "pmc_is_trace_function_active", + "pmc_is_visible_pmc_address", + "pmc_is_writable_pmc_address", + "pmc_kpmsiz", + "pmc_local_program_number_to_global", + "pmc_logical_and_pmc_address_uint8", + "pmc_logical_and_vio_address_uint8", + "pmc_logical_or_pmc_address_uint8", + "pmc_logical_or_vio_address_uint8", + "pmc_logical_xor_pmc_address_uint8", + "pmc_logical_xor_vio_address_uint8", + "pmc_mcard_compare_file", + "pmc_mcard_delete_file", + "pmc_mcard_format", + "pmc_mcard_read_directory", + "pmc_mcard_read_file", + "pmc_mcard_read_file_count", + "pmc_mcard_read_file_status", + "pmc_mcard_write_pmc_message", + "pmc_mcard_write_pmc_parameter", + "pmc_mcard_write_seq_program", + "pmc_mcard_write_trace_result", + "pmc_move_iolinki_assign_group", + "pmc_node_work_area_size", + "pmc_notify_handle_close", + "pmc_parse_string_into_pmc_address", + "pmc_parse_string_into_scoped_pmc_address", + "pmc_rdalmmsg", + "pmc_rdcntl_exrelay_grp", + "pmc_rdcntldata", + "pmc_rdcntlexrelay", + "pmc_rdcntlgrp", + "pmc_rdioconfigtitle", + "pmc_rdkpm", + "pmc_rdkpm2", + "pmc_rdmessagetitle", + "pmc_rdpmcaddr", + "pmc_rdpmcinfo", + "pmc_rdpmcparam", + "pmc_rdpmcrng", + "pmc_rdpmctitle", + "pmc_rdpmctitle2", + "pmc_rdprmend", + "pmc_rdprmstart", + "pmc_rdwrpmcrng", + "pmc_read_actual_io_assignment_data", + "pmc_read_actual_io_assignment_data_by_address", + "pmc_read_alarm_number", + "pmc_read_cnc_dido", + "pmc_read_data_table_group_data", + "pmc_read_detail_scan_time", + "pmc_read_end_process_status", + "pmc_read_extra_relay_group_data", + "pmc_read_file_info_list", + "pmc_read_instruction_monitor_status", + "pmc_read_io_assignment_data", + "pmc_read_iolink_alarm_info", + "pmc_read_iolinki_assign_channel_data", + "pmc_read_iolinki_assign_group_list", + "pmc_read_iolinki_assign_group_slot", + "pmc_read_iolinki_assign_info", + "pmc_read_iolinki_assign_slot_list", + "pmc_read_iolinki_assign_title", + "pmc_read_iolinki_assign_title2", + "pmc_read_machine_signal", + "pmc_read_message_entry_data", + "pmc_read_message_info", + "pmc_read_message_title", + "pmc_read_message_title2", + "pmc_read_multi_lang_message_data", + "pmc_read_nonvolatile_memory", + "pmc_read_performance_information", + "pmc_read_pmc_address_bit", + "pmc_read_pmc_address_real32", + "pmc_read_pmc_address_real64", + "pmc_read_pmc_address_uint16", + "pmc_read_pmc_address_uint32", + "pmc_read_pmc_address_uint8", + "pmc_read_pmc_software_version", + "pmc_read_raw_memory", + "pmc_read_scan_time", + "pmc_read_scoped_symcmt", + "pmc_read_scoped_symcmt_ext", + "pmc_read_seq_object_size", + "pmc_read_seq_program_and_memory_type", + "pmc_read_seq_program_config", + "pmc_read_seq_program_object", + "pmc_read_seq_program_size", + "pmc_read_seq_program_status", + "pmc_read_seq_program_type", + "pmc_read_setting", + "pmc_read_snp_parameter", + "pmc_read_symcmt_entry", + "pmc_read_system_parameter", + "pmc_read_title_data", + "pmc_read_title_data2", + "pmc_read_trace_parameter", + "pmc_read_vio_address_bit", + "pmc_read_vio_address_uint16", + "pmc_read_vio_address_uint32", + "pmc_read_vio_address_uint8", + "pmc_register_io_configuration", + "pmc_release_node_work_area", + "pmc_request_permission", + "pmc_request_permission_with_passwd", + "pmc_reserve_nonvolatile_memory", + "pmc_reset_all_override", + "pmc_reset_override", + "pmc_reset_scan_time", + "pmc_reset_sfc_monitor_time", + "pmc_restore_seq_program_edit", + "pmc_resume_end_process", + "pmc_resume_transfer", + "pmc_retrieve_permission", + "pmc_return_permission", + "pmc_rs232c_compare", + "pmc_rs232c_get_comm_parameter", + "pmc_rs232c_init_comm_parameter", + "pmc_rs232c_read", + "pmc_rs232c_set_comm_parameter", + "pmc_rs232c_write_pmc_parameter", + "pmc_rs232c_write_seq_program", + "pmc_run_seq_program", + "pmc_search_comment_set", + "pmc_search_divided_ladder", + "pmc_search_iolinki_assign_address", + "pmc_search_iolinki_assign_free_address", + "pmc_search_ladder_address", + "pmc_search_ladder_function", + "pmc_search_message_entry_position", + "pmc_search_message_string", + "pmc_search_multi_ladder", + "pmc_search_scoped_symcmt_by_address", + "pmc_search_scoped_symcmt_by_symbol", + "pmc_search_scoped_symcmt_string", + "pmc_search_symbol_string", + "pmc_search_symcmt_entry_by_symbol", + "pmc_search_symcmt_entry_position", + "pmc_search_symcmt_string", + "pmc_search_unit_type", + "pmc_select_divided_ladder", + "pmc_select_pmc", + "pmc_select_pmc_unit", + "pmc_select_program_number", + "pmc_set_data_table_group_count", + "pmc_set_extra_relay_group_count", + "pmc_set_language", + "pmc_set_override", + "pmc_set_sfc_monitor_time", + "pmc_set_timer_type", + "pmc_start_end_process", + "pmc_start_end_process2", + "pmc_start_list_subprogram", + "pmc_start_multiple_coil_check", + "pmc_start_multiple_coil_check2", + "pmc_start_multiple_instruction_check", + "pmc_start_multiple_instruction_check2", + "pmc_start_search_symbol", + "pmc_start_trace_function", + "pmc_start_transfer", + "pmc_start_xref_address", + "pmc_start_xref_address2", + "pmc_start_xref_instruction", + "pmc_start_xref_instruction2", + "pmc_stop_seq_program", + "pmc_wrcntl_exrelay_grp", + "pmc_wrcntldata", + "pmc_wrcntlexrelay", + "pmc_wrcntlgrp", + "pmc_wriolinkdat", + "pmc_write_cnc_dido", + "pmc_write_data_table_group_data", + "pmc_write_extra_relay_group_data", + "pmc_write_io_assignment_data", + "pmc_write_iolinki_assign_channel_data", + "pmc_write_iolinki_assign_group_data", + "pmc_write_iolinki_assign_group_slot", + "pmc_write_iolinki_assign_slot_data", + "pmc_write_message_entry", + "pmc_write_nonvolatile_memory", + "pmc_write_pmc_address_bit", + "pmc_write_pmc_address_real32", + "pmc_write_pmc_address_real64", + "pmc_write_pmc_address_uint16", + "pmc_write_pmc_address_uint32", + "pmc_write_pmc_address_uint8", + "pmc_write_raw_io_assignment_data", + "pmc_write_raw_memory", + "pmc_write_scoped_symcmt", + "pmc_write_seq_program_object", + "pmc_write_setting", + "pmc_write_snp_parameter", + "pmc_write_symcmt_entry", + "pmc_write_system_parameter", + "pmc_write_title_data", + "pmc_write_title_data2", + "pmc_write_trace_parameter", + "pmc_write_vio_address_bit", + "pmc_write_vio_address_uint16", + "pmc_write_vio_address_uint32", + "pmc_write_vio_address_uint8", + "pmc_wrkpm", + "pmc_wrkpm2", + "pmc_wrpmcparam", + "pmc_wrpmcrng", + "pmc_wrpmcrng2", + "pmc_wrprmend", + "pmc_wrprmstart" + ], + "notes": [ + "Exports extracted directly from the 64-bit DLL PE export directory.", + "Wrapper-supported methods are intersected with upstream fwlib.cs extern declarations.", + "Axis and path hints are filename-family heuristics, not protocol-level proofs." + ] +} \ No newline at end of file diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlibNCG64.json b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlibNCG64.json new file mode 100644 index 0000000..a96d0b6 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlibNCG64.json @@ -0,0 +1,2407 @@ +{ + "profile_name": "fwlibNCG64", + "dll_name": "fwlibNCG64.dll", + "series_hint": "ncguide-family", + "max_axis_hint": 32, + "max_path_hint": 2, + "export_count": 1850, + "connection_methods": [ + "cnc_allclibhndl", + "cnc_allclibhndl2" + ], + "mock_methods": [ + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_diagnoss", + "cnc_exeprgname2", + "cnc_freelibhndl", + "cnc_getpath", + "cnc_rdalmmsg2", + "cnc_rdaxisname", + "cnc_rdblkcount", + "cnc_rddiaginfo", + "cnc_rddiagnum", + "cnc_rddynamic2", + "cnc_rdexecprog", + "cnc_rdmacro", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdparam", + "cnc_rdprgnum", + "cnc_rdprogdir3", + "cnc_rdproginfo", + "cnc_rdseqnum", + "cnc_rdspdlname", + "cnc_rdspgear", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsvmeter", + "cnc_rdtimer", + "cnc_setpath", + "cnc_statinfo", + "cnc_sysinfo", + "cnc_wrmacro", + "cnc_wrparam", + "pmc_rdpmcrng", + "pmc_wrpmcrng" + ], + "wrapper_supported_count": 495, + "wrapper_supported_methods": [ + "cnc_absolute", + "cnc_absolute2", + "cnc_accdecdly", + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_adcnv", + "cnc_alarm", + "cnc_alarm2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_allowance", + "cnc_allowcnd", + "cnc_async_busy_state", + "cnc_buff", + "cnc_canmovrlap", + "cnc_cdownload", + "cnc_cexesramsize", + "cnc_clearomhis", + "cnc_clearophis", + "cnc_clr3dplsmov", + "cnc_clr5axpls", + "cnc_clralm", + "cnc_clrcntinfo", + "cnc_condense", + "cnc_copyprog", + "cnc_cupload", + "cnc_cverify", + "cnc_delall", + "cnc_delete", + "cnc_delmagazine", + "cnc_delprogline", + "cnc_deltlifedt", + "cnc_deltlifegrp", + "cnc_deltool", + "cnc_diagnosr", + "cnc_diagnoss", + "cnc_distance", + "cnc_dnc2", + "cnc_dncend2", + "cnc_dncstart2", + "cnc_download", + "cnc_download3", + "cnc_download4", + "cnc_dwnend", + "cnc_dwnend3", + "cnc_dwnend4", + "cnc_dwnstart", + "cnc_dwnstart3", + "cnc_dwnstart4", + "cnc_end_async_wrparam", + "cnc_exaxisname", + "cnc_exeprgname", + "cnc_exeprgname2", + "cnc_freelibhndl", + "cnc_fromget", + "cnc_fromgetend", + "cnc_fromgetstart", + "cnc_fromput", + "cnc_fromputend", + "cnc_fromputstart", + "cnc_fromremove", + "cnc_getdspmode", + "cnc_getdtailerr", + "cnc_getfigure", + "cnc_getfrominfo", + "cnc_getlibopt", + "cnc_getmactype", + "cnc_getpath", + "cnc_getpmactype", + "cnc_getsraminfo", + "cnc_gettimer", + "cnc_hpccactfine", + "cnc_hpccatset", + "cnc_hpccattune", + "cnc_hpccselfine", + "cnc_instlifedt", + "cnc_machine", + "cnc_modal", + "cnc_newprog", + "cnc_pdf_add", + "cnc_pdf_cond", + "cnc_pdf_copy", + "cnc_pdf_del", + "cnc_pdf_delline", + "cnc_pdf_move", + "cnc_pdf_rdactpt", + "cnc_pdf_rdmain", + "cnc_pdf_rename", + "cnc_pdf_searchresult", + "cnc_pdf_searchword", + "cnc_pdf_slctmain", + "cnc_pdf_wractpt", + "cnc_progdigit", + "cnc_prstwkcd", + "cnc_rd1length", + "cnc_rd1radius", + "cnc_rd1tlifedat2", + "cnc_rd1tlifedata", + "cnc_rd2length", + "cnc_rd2radius", + "cnc_rd2tlifedata", + "cnc_rd3dcdcnv", + "cnc_rd3dmovrlap", + "cnc_rd3dofschg", + "cnc_rd3dpulse", + "cnc_rd3dtofs", + "cnc_rd3dtooltip", + "cnc_rd5axmandt", + "cnc_rd5axovrlap", + "cnc_rdactfixofs", + "cnc_rdactpt", + "cnc_rdacttlzone", + "cnc_rdalmhisno", + "cnc_rdalmhistry", + "cnc_rdalmhistry2", + "cnc_rdalmhistry3", + "cnc_rdalmhistry5", + "cnc_rdalminfo", + "cnc_rdalmmsg", + "cnc_rdalmmsg2", + "cnc_rdaxisdata", + "cnc_rdaxisname", + "cnc_rdblkcount", + "cnc_rdbrstrinfo", + "cnc_rdcdrotate", + "cnc_rdcexesram", + "cnc_rdcncid", + "cnc_rdcommand", + "cnc_rdcoordnum", + "cnc_rdcount", + "cnc_rdctrldi", + "cnc_rdctrldo", + "cnc_rdcurrent", + "cnc_rddiag_ext", + "cnc_rddiaginfo", + "cnc_rddiagnum", + "cnc_rddischarge", + "cnc_rddischrgalm", + "cnc_rddncdgndt", + "cnc_rddynamic", + "cnc_rddynamic2", + "cnc_rdexchgtgrp", + "cnc_rdexecmcode", + "cnc_rdexecprog", + "cnc_rdexecpt", + "cnc_rdfbusmem", + "cnc_rdfixcycle", + "cnc_rdfixofs", + "cnc_rdgcode", + "cnc_rdgrphcmd", + "cnc_rdgrpid", + "cnc_rdgrpid2", + "cnc_rdgrpinfo", + "cnc_rdgrpinfo2", + "cnc_rdgrpinfo3", + "cnc_rdgrpinfo4", + "cnc_rdhissgnl", + "cnc_rdhissgnl3", + "cnc_rdhndintrpt", + "cnc_rdhsparam", + "cnc_rdhsprminfo", + "cnc_rdintchk", + "cnc_rdintinfo", + "cnc_rdlactnum", + "cnc_rdlagslt", + "cnc_rdlcmddat", + "cnc_rdlcmmt", + "cnc_rdldsplc", + "cnc_rdledgprc", + "cnc_rdlenofs", + "cnc_rdlerrz", + "cnc_rdlife", + "cnc_rdloopgain", + "cnc_rdlprcprc", + "cnc_rdlpwrctrl", + "cnc_rdlpwrdat", + "cnc_rdlpwrdty", + "cnc_rdlpwrslt", + "cnc_rdmacro", + "cnc_rdmacroinfo", + "cnc_rdmacror", + "cnc_rdmacror2", + "cnc_rdmagazine", + "cnc_rdmaxgrp", + "cnc_rdmaxtool", + "cnc_rdmdipntr", + "cnc_rdmdiprgstat", + "cnc_rdmdiprog", + "cnc_rdmdlconfig2", + "cnc_rdmenuswitch", + "cnc_rdmgrpdata", + "cnc_rdmirimage", + "cnc_rdmngtime", + "cnc_rdmovrlap", + "cnc_rdmultitldt", + "cnc_rdngrp", + "cnc_rdnspdl", + "cnc_rdntool", + "cnc_rdomhisinfo", + "cnc_rdomhisno", + "cnc_rdomhistry", + "cnc_rdomhistry2", + "cnc_rdophisno", + "cnc_rdophistry", + "cnc_rdophistry2", + "cnc_rdophistry4", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdopmsg2", + "cnc_rdopmsg3", + "cnc_rdopnlgnrl", + "cnc_rdopnlgsname", + "cnc_rdopnlsgnl", + "cnc_rdparainfo", + "cnc_rdparam", + "cnc_rdparam3", + "cnc_rdparam_ext", + "cnc_rdparanum", + "cnc_rdparar", + "cnc_rdpdf_alldir", + "cnc_rdpdf_curdir", + "cnc_rdpdf_drive", + "cnc_rdpdf_inf", + "cnc_rdpdf_line", + "cnc_rdpdf_subdir", + "cnc_rdpdf_subdirn", + "cnc_rdpglockstat", + "cnc_rdpitchinfo", + "cnc_rdpitchr", + "cnc_rdpm_item", + "cnc_rdpm_mcnitem", + "cnc_rdpmacro", + "cnc_rdpmacroinfo", + "cnc_rdpmacror", + "cnc_rdpmacror2", + "cnc_rdposerrs", + "cnc_rdposerrs2", + "cnc_rdposerrz", + "cnc_rdposition", + "cnc_rdprgdirtime", + "cnc_rdprgnum", + "cnc_rdproctime", + "cnc_rdprogdir", + "cnc_rdprogdir2", + "cnc_rdprogdir3", + "cnc_rdprogdir4", + "cnc_rdproginfo", + "cnc_rdprogline", + "cnc_rdprogline2", + "cnc_rdprstrinfo", + "cnc_rdpscdedge", + "cnc_rdpscdpirc", + "cnc_rdpscdproc", + "cnc_rdpscdslop", + "cnc_rdpwofsthis", + "cnc_rdradofs", + "cnc_rdrepeatval", + "cnc_rdrstrmcode", + "cnc_rdsafetyzone", + "cnc_rdscaling", + "cnc_rdseqnum", + "cnc_rdset", + "cnc_rdsetinfo", + "cnc_rdsetnum", + "cnc_rdsetr", + "cnc_rdsetzone", + "cnc_rdspcss", + "cnc_rdspdlalm", + "cnc_rdspdlname", + "cnc_rdspeed", + "cnc_rdspgear", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsrvspeed", + "cnc_rdsvmeter", + "cnc_rdsynerrrg", + "cnc_rdsynerrsy", + "cnc_rdsyshard", + "cnc_rdsyssoft", + "cnc_rdsyssoft2", + "cnc_rdsyssoft3", + "cnc_rdtimer", + "cnc_rdtlctldata", + "cnc_rdtlgrp", + "cnc_rdtlinfo", + "cnc_rdtltool", + "cnc_rdtlusegrp", + "cnc_rdtofs", + "cnc_rdtofsinfo", + "cnc_rdtofsinfo2", + "cnc_rdtofsr", + "cnc_rdtool", + "cnc_rdtool_f2", + "cnc_rdtooldata", + "cnc_rdtoolgrp", + "cnc_rdtoolinfo", + "cnc_rdtoolrng", + "cnc_rdtoolzone", + "cnc_rdusegrpid", + "cnc_rdusetlno", + "cnc_rdwavedata", + "cnc_rdwaveprm", + "cnc_rdwaveprm2", + "cnc_rdwkcdsfms", + "cnc_rdwkcdshft", + "cnc_rdzofs", + "cnc_rdzofsinfo", + "cnc_rdzofsr", + "cnc_regmagazine", + "cnc_regtool", + "cnc_regtool_f2", + "cnc_relative", + "cnc_relative2", + "cnc_renameprog", + "cnc_reset", + "cnc_reset2", + "cnc_resetpglock", + "cnc_rewind", + "cnc_rstrseqsrch", + "cnc_rstrseqsrch2", + "cnc_sdcancelsmpl", + "cnc_sdclrchnl", + "cnc_sdendsmpl", + "cnc_sdread1shot", + "cnc_sdreadsmpl", + "cnc_sdsetchnl", + "cnc_sdstartsmpl", + "cnc_search", + "cnc_search2", + "cnc_searchresult", + "cnc_searchword", + "cnc_seqsrch", + "cnc_seqsrch2", + "cnc_setlibopt", + "cnc_setmactype", + "cnc_setpath", + "cnc_setpglock", + "cnc_setpmactype", + "cnc_settimer", + "cnc_skip", + "cnc_slide", + "cnc_sramget", + "cnc_sramget2", + "cnc_sramgetend", + "cnc_sramgetend2", + "cnc_sramgetstart", + "cnc_sramgetstart2", + "cnc_srvdelay", + "cnc_start", + "cnc_start_async_wrparam", + "cnc_startdyngrph", + "cnc_startnccmd", + "cnc_startomhis", + "cnc_startophis", + "cnc_statinfo", + "cnc_stopdyngrph", + "cnc_stopnccmd", + "cnc_stopomhis", + "cnc_stopophis", + "cnc_svdtendrd", + "cnc_svdtendwr", + "cnc_svdtrddata", + "cnc_svdtstartrd", + "cnc_svdtstartwr", + "cnc_svdtstopexec", + "cnc_svdtwrdata", + "cnc_sysinfo", + "cnc_sysinfo_ex", + "cnc_t1info", + "cnc_t2info", + "cnc_tofs_rnge", + "cnc_toolnum", + "cnc_upend", + "cnc_upend3", + "cnc_upend4", + "cnc_upload", + "cnc_upload3", + "cnc_upload4", + "cnc_upstart", + "cnc_upstart3", + "cnc_upstart4", + "cnc_verify", + "cnc_verify4", + "cnc_vrfend", + "cnc_vrfend4", + "cnc_vrfstart", + "cnc_vrfstart4", + "cnc_wavestart", + "cnc_wavestat", + "cnc_wavestop", + "cnc_wksft_rnge", + "cnc_workzero", + "cnc_wr1tlifedat2", + "cnc_wr1tlifedata", + "cnc_wr2tlifedata", + "cnc_wractpt", + "cnc_wrcexesram", + "cnc_wrcountr", + "cnc_wrfbusmem", + "cnc_wrfixofs", + "cnc_wrgrpinfo", + "cnc_wrgrpinfo2", + "cnc_wrgrpinfo3", + "cnc_wrhissgnl", + "cnc_wrhissgnl3", + "cnc_wrintchk", + "cnc_wrkeyhistry", + "cnc_wrlagslt", + "cnc_wrldsplc", + "cnc_wrledgprc", + "cnc_wrlprcprc", + "cnc_wrlpwrctrl", + "cnc_wrlpwrdty", + "cnc_wrlpwrslt", + "cnc_wrmacro", + "cnc_wrmacror", + "cnc_wrmacror2", + "cnc_wrmagazine", + "cnc_wrmdipntr", + "cnc_wrmdiprog", + "cnc_wrmenuswitch", + "cnc_wrmgrpdata", + "cnc_wrmngtime", + "cnc_wrmultitldt", + "cnc_wropnlgnrl", + "cnc_wropnlgsname", + "cnc_wropnlsgnl", + "cnc_wrparam", + "cnc_wrparas", + "cnc_wrpdf_attr", + "cnc_wrpdf_curdir", + "cnc_wrpdf_line", + "cnc_wrpitchr", + "cnc_wrpm_item", + "cnc_wrpm_mcnitem", + "cnc_wrpmacro", + "cnc_wrpmacror", + "cnc_wrpmacror2", + "cnc_wrprogline", + "cnc_wrpscdedge", + "cnc_wrpscdpirc", + "cnc_wrpscdproc", + "cnc_wrpscdslop", + "cnc_wrrelpos", + "cnc_wrsafetyzone", + "cnc_wrset", + "cnc_wrsets", + "cnc_wrsetzone", + "cnc_wrtimer", + "cnc_wrtlctldata", + "cnc_wrtofs", + "cnc_wrtofsr", + "cnc_wrtool", + "cnc_wrtool2", + "cnc_wrtool_f2", + "cnc_wrtooldata", + "cnc_wrtoolzone", + "cnc_wrwaveprm", + "cnc_wrwaveprm2", + "cnc_wrwkcdsfms", + "cnc_wrwkcdshft", + "cnc_wrzofs", + "cnc_wrzofsr", + "cnc_zofs_rnge", + "pmc_get_current_pmc_unit", + "pmc_get_number_of_pmc", + "pmc_get_pmc_unit_types", + "pmc_get_timer_type", + "pmc_getdtailerr", + "pmc_kpmsiz", + "pmc_rdalmmsg", + "pmc_rdcntldata", + "pmc_rdcntlgrp", + "pmc_rdkpm", + "pmc_rdkpm2", + "pmc_rdpmcaddr", + "pmc_rdpmcinfo", + "pmc_rdpmcparam", + "pmc_rdpmcrng", + "pmc_rdpmctitle", + "pmc_rdprmend", + "pmc_rdprmstart", + "pmc_select_pmc_unit", + "pmc_set_timer_type", + "pmc_wrcntldata", + "pmc_wrcntlgrp", + "pmc_wriolinkdat", + "pmc_wrkpm", + "pmc_wrkpm2", + "pmc_wrpmcparam", + "pmc_wrpmcrng", + "pmc_wrprmend", + "pmc_wrprmstart" + ], + "exports": [ + "PmcProcessCB", + "anm_rdsimuelm", + "anm_rdsimuelm2", + "anm_simuclose", + "anm_simuopen", + "anm_simuproc", + "anm_simurwd", + "anm_simusngl", + "anm_simustart", + "anm_simustop", + "cclr_rdinfo", + "cclr_rdparam", + "cclr_wrparam", + "cnc_3dchk_end", + "cnc_3dchk_getprginfo", + "cnc_3dchk_mchn_stop", + "cnc_3dchk_rddata", + "cnc_3dchk_rddata2", + "cnc_3dchk_rddata3", + "cnc_3dchk_rddata4", + "cnc_3dchk_start", + "cnc_3dchk_start2", + "cnc_absolute", + "cnc_absolute2", + "cnc_absolute2_exdgt", + "cnc_absolute_bg", + "cnc_absolute_mgi", + "cnc_accdecdly", + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_adcnv", + "cnc_addexauxr", + "cnc_alarm", + "cnc_alarm2", + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_allowance", + "cnc_allowcnd", + "cnc_async_busy_state", + "cnc_atbk_rdtime", + "cnc_aux_statinfo", + "cnc_axisnum", + "cnc_axisnum2", + "cnc_backuplrn", + "cnc_backupophis", + "cnc_block_status", + "cnc_btlfpotsrh", + "cnc_buff", + "cnc_canaux", + "cnc_cancel_prm", + "cnc_canmcdfinfo", + "cnc_canmovrlap", + "cnc_cannedcycle", + "cnc_cdautoset", + "cnc_cdownload", + "cnc_cexesramsize", + "cnc_chglang", + "cnc_chgoverstore", + "cnc_chkmcdfile", + "cnc_chktdistatus", + "cnc_chkversion", + "cnc_clear_maint", + "cnc_clearalm", + "cnc_clearomhis", + "cnc_clearophis", + "cnc_clr3dplsmov", + "cnc_clr5axpls", + "cnc_clr5dplsmov", + "cnc_clralm", + "cnc_clrcntinfo", + "cnc_clrfromsvspid", + "cnc_clrlrncrnt", + "cnc_clrmofs", + "cnc_clroverstore", + "cnc_clrptdata", + "cnc_clrrstraxis", + "cnc_clrtofs", + "cnc_condense", + "cnc_confirm_restart", + "cnc_copyprog", + "cnc_cupload", + "cnc_cverify", + "cnc_data_copy", + "cnc_del_scrlwaitmcode", + "cnc_delall", + "cnc_delallauxdata", + "cnc_delete", + "cnc_deleteo8", + "cnc_delexauxr", + "cnc_delmag_property", + "cnc_delmagazine", + "cnc_delmcdfile", + "cnc_delmcdfilebynum", + "cnc_delpot_property", + "cnc_delprogline", + "cnc_delsysalm", + "cnc_deltlifedt", + "cnc_deltlifegrp", + "cnc_deltool", + "cnc_diagnosr", + "cnc_diagnosr64", + "cnc_diagnoss", + "cnc_diagnoss64", + "cnc_distance", + "cnc_distance_exdgt", + "cnc_distancem", + "cnc_dnc2", + "cnc_dncend2", + "cnc_dncprgname", + "cnc_dncstart2", + "cnc_download", + "cnc_download3", + "cnc_download3m", + "cnc_download4", + "cnc_dschdir", + "cnc_dscopyfile", + "cnc_dsfile_req", + "cnc_dsftpcancel", + "cnc_dsftpstat", + "cnc_dsget_req", + "cnc_dslistdel_req", + "cnc_dslistget_req", + "cnc_dslistput_req", + "cnc_dsmget_req", + "cnc_dsmkdir", + "cnc_dsmput_req", + "cnc_dsput_req", + "cnc_dsrdclose", + "cnc_dsrdopen", + "cnc_dsread", + "cnc_dsremove", + "cnc_dsrename", + "cnc_dsrmdir", + "cnc_dssearch", + "cnc_dsstat_rdfile", + "cnc_dswrclose", + "cnc_dswrite", + "cnc_dswropen", + "cnc_dtsvinfo", + "cnc_dvpunchvolc", + "cnc_dvreadvolc", + "cnc_dwnend", + "cnc_dwnend3", + "cnc_dwnend3m", + "cnc_dwnend4", + "cnc_dwnstart", + "cnc_dwnstart3", + "cnc_dwnstart3m", + "cnc_dwnstart4", + "cnc_edplutosmpl", + "cnc_end_async_data_punch", + "cnc_end_async_data_read", + "cnc_end_async_pdf_punch", + "cnc_end_async_pdf_read", + "cnc_end_async_punch_prog3", + "cnc_end_async_punch_prog3_bg", + "cnc_end_async_read_prog3", + "cnc_end_async_read_prog3_bg", + "cnc_end_async_wrparam", + "cnc_endpossmpl", + "cnc_exaxisname", + "cnc_exaxisname2", + "cnc_exeprgname", + "cnc_exeprgname2", + "cnc_exeprgname_bg", + "cnc_file_cpmv_end", + "cnc_file_cpmv_poll", + "cnc_file_cpmv_restart", + "cnc_file_cpmv_start", + "cnc_freelibhndl", + "cnc_fromget", + "cnc_fromget2", + "cnc_fromgetend", + "cnc_fromgetend2", + "cnc_fromgetstart", + "cnc_fromgetstart2", + "cnc_fromput", + "cnc_fromput2", + "cnc_fromputend", + "cnc_fromputend2", + "cnc_fromputstart", + "cnc_fromputstart2", + "cnc_fromremove", + "cnc_fssb_autoset", + "cnc_fssb_reset", + "cnc_ftrq_data_copy", + "cnc_ftrq_from_load", + "cnc_ftrq_from_save", + "cnc_get_crosschk_alarm", + "cnc_get_flowmonitor", + "cnc_get_mccteststs", + "cnc_get_safetysts", + "cnc_get_safetysts2", + "cnc_getauxdatar", + "cnc_getauxoverdatar", + "cnc_getauxregnum", + "cnc_getdspmode", + "cnc_getdtailerr", + "cnc_getdtailerr2", + "cnc_getexauxr", + "cnc_getexauxregnum", + "cnc_getfigure", + "cnc_getfrominfo", + "cnc_getinfoaux", + "cnc_getlibopt", + "cnc_getmactype", + "cnc_getpath", + "cnc_getpaxispath", + "cnc_getpmactype", + "cnc_getprntname", + "cnc_getrtmioinfo", + "cnc_getrtmiorngnum", + "cnc_getrtmrvar", + "cnc_getrtmrvars", + "cnc_getsraminfo", + "cnc_getstsaux", + "cnc_getsysfolder_num", + "cnc_gettimer", + "cnc_gettolnum_qset", + "cnc_getupdcount", + "cnc_getvcprg", + "cnc_getzofsnum_qset", + "cnc_glvndiagnoss", + "cnc_hdck_nochange_info", + "cnc_hpccactfine", + "cnc_hpccatset", + "cnc_hpccattune", + "cnc_hpccselfine", + "cnc_ifsb_autoset", + "cnc_ifsb_reset", + "cnc_instlifedt", + "cnc_lctcdcstm", + "cnc_loadtorq", + "cnc_machine", + "cnc_machine3", + "cnc_machine3_ex", + "cnc_machine_bg", + "cnc_machine_exdgt", + "cnc_magazinesrch", + "cnc_mcdp_create", + "cnc_mcdp_mount", + "cnc_mcdp_mountchk", + "cnc_mcdp_unmount", + "cnc_mcdp_update_entry", + "cnc_mcdp_wractpt", + "cnc_mcs_rdactset", + "cnc_mcs_rdcompparam", + "cnc_mcs_rdheader", + "cnc_mcs_rdparainfo2", + "cnc_mcs_rdparam", + "cnc_mcs_rdparanum", + "cnc_mcs_wractset", + "cnc_mcs_wrheader", + "cnc_mcs_wrparam", + "cnc_mdd_getswitch", + "cnc_mdd_lock", + "cnc_mdd_rdinfo", + "cnc_mdd_register", + "cnc_mdd_setpassword", + "cnc_mdd_setswitch", + "cnc_mdd_unlock", + "cnc_mdg_moniclear", + "cnc_mdg_monistat", + "cnc_mdg_msgsrch", + "cnc_mdg_rdalminfo", + "cnc_mdg_rdalminfoview2", + "cnc_mdg_rdalmnum", + "cnc_mdg_rdcontinfo", + "cnc_mdg_rddtmsg", + "cnc_mdg_rdflow", + "cnc_mdg_rdheatsimlt", + "cnc_mdg_rdlatchedalm", + "cnc_mdg_rdloadlvl", + "cnc_mdg_rdmsg", + "cnc_mdg_rdmsgnum", + "cnc_mdg_rdmsgordr", + "cnc_mdg_rdorderalmno", + "cnc_mdg_rdsysinfo", + "cnc_mdg_rdwvdata", + "cnc_modal", + "cnc_modal_bg", + "cnc_msim_end", + "cnc_msim_rdprgname", + "cnc_msim_rewind", + "cnc_msim_start", + "cnc_msr_delhis_all", + "cnc_msr_rdhis_allnum", + "cnc_msr_rdhis_inf", + "cnc_msr_rdhis_msudat", + "cnc_msr_rdhis_ncdat", + "cnc_msr_rdhis_ohisnum", + "cnc_msr_rdhis_ohisrec", + "cnc_msr_rdhis_pmc", + "cnc_msr_rdhis_pmc_ex", + "cnc_msr_rdmon_msudat", + "cnc_msr_rdmon_msunum", + "cnc_msr_rdmon_pmcinf", + "cnc_msr_rdmon_pmcinf_ex", + "cnc_msr_start_sample", + "cnc_msr_stop_sample", + "cnc_mtdtnstart", + "cnc_ncg_protcancel", + "cnc_newprog", + "cnc_nextdistance", + "cnc_nextdistance_bg", + "cnc_opentdicubeinfo", + "cnc_pdf_add", + "cnc_pdf_add_bgedt", + "cnc_pdf_chginquiry", + "cnc_pdf_cond", + "cnc_pdf_copy", + "cnc_pdf_cpmv_end", + "cnc_pdf_cpmv_poll", + "cnc_pdf_cpmv_restart", + "cnc_pdf_cpmv_start", + "cnc_pdf_del", + "cnc_pdf_delall", + "cnc_pdf_delchar", + "cnc_pdf_delline", + "cnc_pdf_dncread", + "cnc_pdf_dncset", + "cnc_pdf_dncset2", + "cnc_pdf_dssearch", + "cnc_pdf_mergeprog", + "cnc_pdf_move", + "cnc_pdf_punch", + "cnc_pdf_punch_bg", + "cnc_pdf_rdactpt", + "cnc_pdf_rdactpt_bg", + "cnc_pdf_rdactpt_bgedt", + "cnc_pdf_rdmain", + "cnc_pdf_rdmainpt", + "cnc_pdf_rdprgname", + "cnc_pdf_rdrmtdgn", + "cnc_pdf_read", + "cnc_pdf_read_bg", + "cnc_pdf_relsmain", + "cnc_pdf_rename", + "cnc_pdf_replacechar", + "cnc_pdf_replaceword_all", + "cnc_pdf_searchresult", + "cnc_pdf_searchresult2", + "cnc_pdf_searchresult_bgedt", + "cnc_pdf_searchword", + "cnc_pdf_searchword2", + "cnc_pdf_searchword_bgedt", + "cnc_pdf_slctmain", + "cnc_pdf_startrmtdgn", + "cnc_pdf_stoprmtdgn", + "cnc_pdf_wractpt", + "cnc_pdf_wractpt_bg", + "cnc_pdf_wractpt_bgedt", + "cnc_pmclad_screen", + "cnc_pmmchkalm", + "cnc_pmmget", + "cnc_pmmgetend", + "cnc_pmmgetstart", + "cnc_pmminit", + "cnc_pmmiochanl", + "cnc_pmmprmpage", + "cnc_pmmsysdt", + "cnc_powc_clear_inte", + "cnc_powc_del_cycle_data", + "cnc_powc_rd_clear_time", + "cnc_powc_rd_cycle_data", + "cnc_powc_rd_history", + "cnc_powc_rd_outer_set", + "cnc_powc_wr_outer_set", + "cnc_preset_prm", + "cnc_progdigit", + "cnc_prot_pswcan", + "cnc_prot_pswchg", + "cnc_prot_pswinit", + "cnc_prot_pswinp", + "cnc_prstwkcd", + "cnc_prstwkcd64", + "cnc_ptdownload", + "cnc_ptdwnend", + "cnc_ptdwnstart", + "cnc_ptlink", + "cnc_ptlink2", + "cnc_punch_data", + "cnc_punch_data_end", + "cnc_punch_data_start", + "cnc_punch_prog", + "cnc_punch_prog2", + "cnc_punch_prog2_bg", + "cnc_punch_prog3", + "cnc_punch_prog3_bg", + "cnc_punch_prog_bg", + "cnc_punchlrncrnt", + "cnc_pwcm_clear_consump", + "cnc_pwoff_alarm", + "cnc_rd1length", + "cnc_rd1punchtl_ex", + "cnc_rd1radius", + "cnc_rd1tlifedat2", + "cnc_rd1tlifedata", + "cnc_rd2length", + "cnc_rd2punchtl_ex", + "cnc_rd2radius", + "cnc_rd2tlifedata", + "cnc_rd3dcdcnv", + "cnc_rd3dmovrlap", + "cnc_rd3dofschg", + "cnc_rd3dpulse", + "cnc_rd3dtofs", + "cnc_rd3dtooltip", + "cnc_rd5axmandt", + "cnc_rd5axovrlap", + "cnc_rd5dmacmov", + "cnc_rd5dpulse", + "cnc_rd5dtooltip", + "cnc_rd_grpaxisinfo", + "cnc_rd_grppos", + "cnc_rd_grppos2", + "cnc_rd_grppos3", + "cnc_rd_scrlwaitmcode", + "cnc_rd_sfsg_disp_stat", + "cnc_rd_sfsg_loginf", + "cnc_rd_sfsg_search", + "cnc_rd_sfsg_sighis", + "cnc_rd_sfsg_siginf", + "cnc_rd_sfsg_signal_num", + "cnc_rd_sfsg_update_count", + "cnc_rdabsaxis", + "cnc_rdactdofs", + "cnc_rdactfixofs", + "cnc_rdactpos_w", + "cnc_rdactpt", + "cnc_rdactspdl", + "cnc_rdacttlzone", + "cnc_rdalarmchar", + "cnc_rdalmblkinfo", + "cnc_rdalmhisno", + "cnc_rdalmhisno3", + "cnc_rdalmhistry", + "cnc_rdalmhistry2", + "cnc_rdalmhistry3", + "cnc_rdalmhistry4", + "cnc_rdalmhistry5", + "cnc_rdalminfo", + "cnc_rdalminfo_bg", + "cnc_rdalmmsg", + "cnc_rdalmmsg2", + "cnc_rdalmmsg3", + "cnc_rdaxisdata", + "cnc_rdaxisdata64", + "cnc_rdaxisname", + "cnc_rdaxisstatus_bg", + "cnc_rdblkcount", + "cnc_rdblkdist", + "cnc_rdblockcount", + "cnc_rdbrstrinfo", + "cnc_rdcbmem", + "cnc_rdcbmem2", + "cnc_rdcbprm", + "cnc_rdcdrotate", + "cnc_rdcdslctprmm", + "cnc_rdcenblinfo", + "cnc_rdcenter", + "cnc_rdcexesram", + "cnc_rdcmstatdata", + "cnc_rdcncid", + "cnc_rdcncmem", + "cnc_rdcncmem2", + "cnc_rdcntover", + "cnc_rdcnttype", + "cnc_rdcommand", + "cnc_rdcommand64", + "cnc_rdcoordnum", + "cnc_rdcount", + "cnc_rdcstm_decfig", + "cnc_rdcstmdecfig", + "cnc_rdctname", + "cnc_rdctrldi", + "cnc_rdctrldo", + "cnc_rdcurrent", + "cnc_rddiag_ext", + "cnc_rddiaginfo", + "cnc_rddiaginfo2", + "cnc_rddiagnum", + "cnc_rddischarge", + "cnc_rddischrgalm", + "cnc_rddncdgndt", + "cnc_rddncdgndt2", + "cnc_rddofs", + "cnc_rddsdevinfo", + "cnc_rddsdir", + "cnc_rddsdncfile", + "cnc_rddsfile", + "cnc_rddynamic", + "cnc_rddynamic2", + "cnc_rddynamic3", + "cnc_rddynamic3m", + "cnc_rddynamico8", + "cnc_rdedgeactive", + "cnc_rdedgedata", + "cnc_rdedmcram", + "cnc_rdembedf_inf", + "cnc_rdenblinfo", + "cnc_rdexchgtgrp", + "cnc_rdexecmcode", + "cnc_rdexecprgnum", + "cnc_rdexecprog", + "cnc_rdexecprog2", + "cnc_rdexecprog3", + "cnc_rdexecprog_bg", + "cnc_rdexecpt", + "cnc_rdexecptm", + "cnc_rdfbusmem", + "cnc_rdfctfssinfo", + "cnc_rdfctsitem", + "cnc_rdfctsmsgid", + "cnc_rdfctsstat", + "cnc_rdfeedmode", + "cnc_rdfile_sram", + "cnc_rdfixcycle", + "cnc_rdfixofs", + "cnc_rdfromservoid", + "cnc_rdfromservoid2", + "cnc_rdfromspindleid", + "cnc_rdfromspindleid2", + "cnc_rdfsraminfo", + "cnc_rdfsraminfo2", + "cnc_rdfssb_amp", + "cnc_rdfssb_axis", + "cnc_rdfssb_info", + "cnc_rdfssb_mainte", + "cnc_rdfssb_plsmod", + "cnc_rdftrq_data", + "cnc_rdftrq_info", + "cnc_rdftrq_storecount", + "cnc_rdgcode", + "cnc_rdgcodem", + "cnc_rdglvndiaginfo", + "cnc_rdglvnparainfo", + "cnc_rdglvnparam", + "cnc_rdgrphcmd", + "cnc_rdgrpid", + "cnc_rdgrpid2", + "cnc_rdgrpinfo", + "cnc_rdgrpinfo2", + "cnc_rdgrpinfo3", + "cnc_rdgrpinfo4", + "cnc_rdhdnxt", + "cnc_rdhipitchinfo", + "cnc_rdhipitchr", + "cnc_rdhissgnl", + "cnc_rdhissgnl3", + "cnc_rdhndintrpt", + "cnc_rdholmes", + "cnc_rdhsparam", + "cnc_rdhsparamm", + "cnc_rdhsprminfo", + "cnc_rdifsb_almstate", + "cnc_rdifsb_as_amp_sp", + "cnc_rdifsb_as_amp_sv", + "cnc_rdifsb_as_hrv", + "cnc_rdifsb_as_plsmod", + "cnc_rdifsb_as_sv_axis", + "cnc_rdifsb_comstatdtl", + "cnc_rdifsb_fssbunt", + "cnc_rdifsb_info", + "cnc_rdifsb_mainte_sp", + "cnc_rdifsb_mainte_sv", + "cnc_rdifsb_slu_pm", + "cnc_rdifsb_slu_sp", + "cnc_rdifsb_slu_sv", + "cnc_rdifsb_slvunt", + "cnc_rdifsb_sysalm", + "cnc_rdifsb_warnhst_cnt", + "cnc_rdifsb_warnhst_msg", + "cnc_rdifsb_warning_cnt", + "cnc_rdifsb_warning_msg", + "cnc_rdindexdata", + "cnc_rdindexinfo", + "cnc_rdindexofs", + "cnc_rdindexposdata", + "cnc_rdindexprm", + "cnc_rdintchk", + "cnc_rdinterference", + "cnc_rdintinfo", + "cnc_rdioassigned", + "cnc_rdipltp", + "cnc_rdipltp_bg", + "cnc_rdjogdrun", + "cnc_rdjogmdi", + "cnc_rdlactnum", + "cnc_rdlagingmode", + "cnc_rdlagingtime", + "cnc_rdlagslt", + "cnc_rdlalmhistry", + "cnc_rdlcmddat", + "cnc_rdlcmmt", + "cnc_rdlcstmname", + "cnc_rdldsplc", + "cnc_rdldsplc2", + "cnc_rdledgprc", + "cnc_rdlenofs", + "cnc_rdlerrz", + "cnc_rdlfiberdata", + "cnc_rdlhsstate", + "cnc_rdlife", + "cnc_rdlnzlmcn", + "cnc_rdloopgain", + "cnc_rdlpoweroffset", + "cnc_rdlppfbdt", + "cnc_rdlprcprc", + "cnc_rdlpscdpwrctl", + "cnc_rdlpwrctrl", + "cnc_rdlpwrdat", + "cnc_rdlpwrdty", + "cnc_rdlpwrslt", + "cnc_rdlrninfo", + "cnc_rdlrninfo2", + "cnc_rdlrninfol", + "cnc_rdlrnprfcmnt", + "cnc_rdlrntrnsdata", + "cnc_rdmacro", + "cnc_rdmacro_bg", + "cnc_rdmacroinfo", + "cnc_rdmacrolclevel", + "cnc_rdmacrolcval", + "cnc_rdmacronum", + "cnc_rdmacror", + "cnc_rdmacror2", + "cnc_rdmacror2_name", + "cnc_rdmacror3", + "cnc_rdmacror4", + "cnc_rdmag_property", + "cnc_rdmagazine", + "cnc_rdmaxgrp", + "cnc_rdmaxtool", + "cnc_rdmcdfinfo", + "cnc_rdmcdprgcmnt", + "cnc_rdmdipntr", + "cnc_rdmdipntro8", + "cnc_rdmdiprgstat", + "cnc_rdmdiprog", + "cnc_rdmdlconfig2", + "cnc_rdmenuswitch", + "cnc_rdmgrpdata", + "cnc_rdmirimage", + "cnc_rdmngtime", + "cnc_rdmodalval", + "cnc_rdmofs", + "cnc_rdmovestate", + "cnc_rdmovrlap", + "cnc_rdmovrlapm", + "cnc_rdmtdtnid", + "cnc_rdmultitldt", + "cnc_rdngrp", + "cnc_rdnspdl", + "cnc_rdntool", + "cnc_rdnutatortofs_vect", + "cnc_rdofslength", + "cnc_rdomhisinfo", + "cnc_rdomhisno", + "cnc_rdomhistry", + "cnc_rdomhistry2", + "cnc_rdope_lvl", + "cnc_rdophisno", + "cnc_rdophisno3", + "cnc_rdophistry", + "cnc_rdophistry2", + "cnc_rdophistry3", + "cnc_rdophistry4", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdopmsg2", + "cnc_rdopmsg3", + "cnc_rdopmsg3m", + "cnc_rdopnlgnrl", + "cnc_rdopnlgnrl2", + "cnc_rdopnlgsname", + "cnc_rdopnlgsname2", + "cnc_rdopnlsgnl", + "cnc_rdoptfuncinfo", + "cnc_rdoverstore", + "cnc_rdoverstoremode", + "cnc_rdpalaxis", + "cnc_rdparainfo", + "cnc_rdparainfo2", + "cnc_rdparainfo3", + "cnc_rdparam", + "cnc_rdparam3", + "cnc_rdparam64", + "cnc_rdparam_ext", + "cnc_rdparanum", + "cnc_rdparar", + "cnc_rdparar3", + "cnc_rdpdf_alldir", + "cnc_rdpdf_curdir", + "cnc_rdpdf_drive", + "cnc_rdpdf_execline", + "cnc_rdpdf_inf", + "cnc_rdpdf_line", + "cnc_rdpdf_line2", + "cnc_rdpdf_line_bgedt", + "cnc_rdpdf_pglockstat", + "cnc_rdpdf_prginf", + "cnc_rdpdf_subdir", + "cnc_rdpdf_subdirn", + "cnc_rdpglockstat", + "cnc_rdpitchblkinfo", + "cnc_rdpitchinfo", + "cnc_rdpitchr", + "cnc_rdpitchr2", + "cnc_rdplutosmpl", + "cnc_rdpm_item", + "cnc_rdpm_mcnitem", + "cnc_rdpmacro", + "cnc_rdpmacroinfo", + "cnc_rdpmacroinfo2", + "cnc_rdpmacroinfo3", + "cnc_rdpmacror", + "cnc_rdpmacror2", + "cnc_rdpmacror_bg", + "cnc_rdpmacvalflag", + "cnc_rdpmmprmtp", + "cnc_rdposerrs", + "cnc_rdposerrs2", + "cnc_rdposerrz", + "cnc_rdposfig", + "cnc_rdposition", + "cnc_rdpossmpl", + "cnc_rdpot_property", + "cnc_rdpressure", + "cnc_rdprgdirtime", + "cnc_rdprgnum", + "cnc_rdprgnumo8", + "cnc_rdprgnumo8_bg", + "cnc_rdprgrmupdtcnt", + "cnc_rdproctime", + "cnc_rdproctime2", + "cnc_rdproctime3", + "cnc_rdprogdata", + "cnc_rdprogdir", + "cnc_rdprogdir2", + "cnc_rdprogdir2o8", + "cnc_rdprogdir3", + "cnc_rdprogdir4", + "cnc_rdprogdiro8", + "cnc_rdproginfo", + "cnc_rdprogline", + "cnc_rdprogline2", + "cnc_rdprotect", + "cnc_rdprotect2", + "cnc_rdprstrinfo", + "cnc_rdprstrinfom", + "cnc_rdprt_data", + "cnc_rdprt_lvl", + "cnc_rdpscdedge", + "cnc_rdpscdedge2", + "cnc_rdpscdpirc", + "cnc_rdpscdproc", + "cnc_rdpscdproc2", + "cnc_rdpscdslop", + "cnc_rdptaxfunctablestatus", + "cnc_rdptaxitablestatus", + "cnc_rdptcmdsize", + "cnc_rdptcnvalm", + "cnc_rdptcnvinfo", + "cnc_rdptcnvinfo2", + "cnc_rdptcomment", + "cnc_rdptexedistalm", + "cnc_rdpthis_aux", + "cnc_rdpthis_ax", + "cnc_rdpthis_gb", + "cnc_rdpthis_log", + "cnc_rdpthis_num", + "cnc_rdpthis_pt", + "cnc_rdpthis_sp", + "cnc_rdptsptablestatus", + "cnc_rdptstoptime", + "cnc_rdpwofsthis", + "cnc_rdradofs", + "cnc_rdrelaxis", + "cnc_rdrepeatval", + "cnc_rdrepeatval_ext", + "cnc_rdrotvolc", + "cnc_rdrstrmcode", + "cnc_rdrtmiowrenbl", + "cnc_rdrtmiowrenblbit", + "cnc_rdrtmiowrenblrng", + "cnc_rdrtmrvar", + "cnc_rdrtmrvars", + "cnc_rdsafetyzone", + "cnc_rdscaling", + "cnc_rdscdldat", + "cnc_rdscdlinfo", + "cnc_rdseqnum", + "cnc_rdseqnum_bg", + "cnc_rdservoid", + "cnc_rdservoid2", + "cnc_rdset", + "cnc_rdsetinfo", + "cnc_rdsetinfo2", + "cnc_rdsetnum", + "cnc_rdsetr", + "cnc_rdsetzone", + "cnc_rdsoc_curdat", + "cnc_rdsoc_tlatrr", + "cnc_rdsoc_tldat", + "cnc_rdsoc_wave", + "cnc_rdsoc_wave_end", + "cnc_rdsoc_wave_start", + "cnc_rdspcss", + "cnc_rdspdlalm", + "cnc_rdspdlname", + "cnc_rdspdlnamem", + "cnc_rdspdlspeed", + "cnc_rdspdlwaitname", + "cnc_rdspeed", + "cnc_rdspgear", + "cnc_rdspindleid", + "cnc_rdspindleid2", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdspmonitor", + "cnc_rdsrvspeed", + "cnc_rdsrvtcmd", + "cnc_rdsrvtsa", + "cnc_rdsuo_prm_name", + "cnc_rdsuofs_vect", + "cnc_rdsvmeter", + "cnc_rdsvmonitor", + "cnc_rdsynerrrg", + "cnc_rdsynerrsy", + "cnc_rdsysalm", + "cnc_rdsyshard", + "cnc_rdsyshard_str", + "cnc_rdsyssoft", + "cnc_rdsyssoft2", + "cnc_rdsyssoft3", + "cnc_rdsyssoft3_str", + "cnc_rdtcodemsg", + "cnc_rdtdiblankfigno", + "cnc_rdtdicomment", + "cnc_rdtdicrntshapeinf", + "cnc_rdtdicubedata", + "cnc_rdtdicubeinfo", + "cnc_rdtdicurrentshape", + "cnc_rdtdicylinderdata", + "cnc_rdtdidispsetting", + "cnc_rdtdieffectshape", + "cnc_rdtdifignum", + "cnc_rdtdifiguredata", + "cnc_rdtdiinfo", + "cnc_rdtdiinitview", + "cnc_rdtdimoveaxis", + "cnc_rdtdinamesetting", + "cnc_rdtdiplanedata", + "cnc_rdtdiseltool", + "cnc_rdtdishapedata", + "cnc_rdtimer", + "cnc_rdtlctldata", + "cnc_rdtldata", + "cnc_rdtldspcstms", + "cnc_rdtlgeomsize", + "cnc_rdtlgeomsize_ext", + "cnc_rdtlgeomsize_ext2", + "cnc_rdtlgrp", + "cnc_rdtlinfo", + "cnc_rdtlmgr_check", + "cnc_rdtlmgr_name", + "cnc_rdtlmsinfo", + "cnc_rdtlname", + "cnc_rdtlnewstatus", + "cnc_rdtltool", + "cnc_rdtlusegrp", + "cnc_rdtofs", + "cnc_rdtofs_bg", + "cnc_rdtofsenbl", + "cnc_rdtofsinfo", + "cnc_rdtofsinfo2", + "cnc_rdtofsr", + "cnc_rdtofsr_bg", + "cnc_rdtool", + "cnc_rdtool2", + "cnc_rdtool_cause", + "cnc_rdtool_f2", + "cnc_rdtool_inhis", + "cnc_rdtool_outhis", + "cnc_rdtoolchggrp", + "cnc_rdtooldata", + "cnc_rdtoolgeom_tlm", + "cnc_rdtoolgrp", + "cnc_rdtoolinfo", + "cnc_rdtoollife_count", + "cnc_rdtoollife_data", + "cnc_rdtoollife_tcodedata", + "cnc_rdtoollifed_count", + "cnc_rdtoollifed_data", + "cnc_rdtoolrng", + "cnc_rdtoolzone", + "cnc_rdusegrpid", + "cnc_rdusetlno", + "cnc_rduvactpt2", + "cnc_rdvolc", + "cnc_rdvolccomp", + "cnc_rdwavecount", + "cnc_rdwavedata", + "cnc_rdwavedata2", + "cnc_rdwavedata3", + "cnc_rdwaveprm", + "cnc_rdwaveprm2", + "cnc_rdwaveprm3", + "cnc_rdwkcdsfms", + "cnc_rdwkcdsfms2", + "cnc_rdwkcdsfms3", + "cnc_rdwkcdsfms64", + "cnc_rdwkcdshft", + "cnc_rdwkcdshft2", + "cnc_rdwkcdshft3", + "cnc_rdwkcdshft64", + "cnc_rdwkcdshft_bg", + "cnc_rdwseterror", + "cnc_rdzofs", + "cnc_rdzofs_bg", + "cnc_rdzofsinfo", + "cnc_rdzofsmes", + "cnc_rdzofsr", + "cnc_rdzofsr64", + "cnc_rdzofsr_bg", + "cnc_read_cexeinfo", + "cnc_read_data", + "cnc_read_prog", + "cnc_read_prog2", + "cnc_read_prog2_bg", + "cnc_read_prog3", + "cnc_read_prog3_bg", + "cnc_read_prog_bg", + "cnc_readlrncrnt", + "cnc_reg_toolstrage", + "cnc_regmagazine", + "cnc_regtool", + "cnc_regtool_f2", + "cnc_relative", + "cnc_relative2", + "cnc_relative2_exdgt", + "cnc_relative_bg", + "cnc_renameprog", + "cnc_req_alarm", + "cnc_reset", + "cnc_reset2", + "cnc_reset_prps", + "cnc_resetcntr", + "cnc_resetpdf_pglock", + "cnc_resetpglock", + "cnc_restorlrn", + "cnc_rewind", + "cnc_robo_clrsignals", + "cnc_robo_rdalmmsg", + "cnc_robo_rdcomsetting", + "cnc_robo_rdgrouplist", + "cnc_robo_rdponprop", + "cnc_robo_rdselectedsignals", + "cnc_robo_rdsignals", + "cnc_robo_rdsignals2", + "cnc_robo_selectgroup", + "cnc_robo_wralmmsg", + "cnc_robo_wrcomsetting", + "cnc_robo_wrgroup", + "cnc_robo_wrselectedsignals", + "cnc_robo_wrsignalname", + "cnc_robo_wrsignals2", + "cnc_rstrdstatus", + "cnc_rstrseqsrch", + "cnc_rstrseqsrch2", + "cnc_rstrt_createpnt", + "cnc_rstrt_getdncprg", + "cnc_rstrt_getpntcnt", + "cnc_rstrt_lpslctblk", + "cnc_rstrt_rdaddinfo", + "cnc_rstrt_rdlpmppnt", + "cnc_rstrt_rdmodal", + "cnc_rstrt_rdpnt", + "cnc_rstrt_rdpnt2", + "cnc_rstrt_rdpntlist", + "cnc_rstrt_rdpntlist2", + "cnc_rstrt_search", + "cnc_rstrt_selectpnt", + "cnc_rstrt_setsuppress", + "cnc_rstrt_wrpnt", + "cnc_rstrt_wrpnt2", + "cnc_s5s_rdactset", + "cnc_s5s_rdname", + "cnc_s5s_rdparainfo2", + "cnc_s5s_rdparam", + "cnc_s5s_rdparanum", + "cnc_s5s_wractset", + "cnc_s5s_wrname", + "cnc_s5s_wrparam", + "cnc_save_maint", + "cnc_saveprog_end", + "cnc_saveprog_start", + "cnc_sdbetainfo", + "cnc_sdcancelsmpl", + "cnc_sdclrchnl", + "cnc_sdclrocp", + "cnc_sdendocp", + "cnc_sdendsmpl", + "cnc_sdendsmpl2", + "cnc_sdfmnghwnd", + "cnc_sdfstatchg", + "cnc_sdread1shot", + "cnc_sdreadsmpl", + "cnc_sdreadsmpl2", + "cnc_sdsetchnl", + "cnc_sdsetchnl2", + "cnc_sdstartocp", + "cnc_sdstartsmpl", + "cnc_sdstartsmpl2", + "cnc_sdstartsmplb", + "cnc_sdtcancelsmpl", + "cnc_sdtclrchnl", + "cnc_sdtclrocp", + "cnc_sdtendocp", + "cnc_sdtendsmpl", + "cnc_sdtendsmpl2", + "cnc_sdtread1shot", + "cnc_sdtreadsmpl", + "cnc_sdtreadsmpl2", + "cnc_sdtsetchnl", + "cnc_sdtsetchnl2", + "cnc_sdtstartocp", + "cnc_sdtstartsmpl", + "cnc_sdtstartsmpl2", + "cnc_search", + "cnc_search2", + "cnc_searcho8", + "cnc_searchresult", + "cnc_searchresult2", + "cnc_searchword", + "cnc_searchword2", + "cnc_sendkey", + "cnc_seqrdtdicubeinfo", + "cnc_seqsrch", + "cnc_seqsrch2", + "cnc_set_cutcnd_exval", + "cnc_set_prps", + "cnc_set_smth_exval", + "cnc_setactptopt", + "cnc_setallexauxr", + "cnc_setcurscrn", + "cnc_setdtailerr", + "cnc_setfrp", + "cnc_setlibopt", + "cnc_setmactype", + "cnc_setpath", + "cnc_setpdf_pglock", + "cnc_setpdf_pglockexec", + "cnc_setpglock", + "cnc_setpmactype", + "cnc_setrstraxis", + "cnc_setsumdt", + "cnc_settdiobjectshape", + "cnc_settditoolshape", + "cnc_settimer", + "cnc_settolnum_qset", + "cnc_settpnlcalib", + "cnc_setzofsnum_qset", + "cnc_skip", + "cnc_slctptdata", + "cnc_slctpttype", + "cnc_slide", + "cnc_soc_wave_setchnl", + "cnc_sramget", + "cnc_sramget2", + "cnc_sramgetend", + "cnc_sramgetend2", + "cnc_sramgetstart", + "cnc_sramgetstart2", + "cnc_sramstat", + "cnc_srttl_getdata", + "cnc_srttl_getnum", + "cnc_srvdelay", + "cnc_start", + "cnc_start_async_data_punch", + "cnc_start_async_data_read", + "cnc_start_async_pdf_punch", + "cnc_start_async_pdf_read", + "cnc_start_async_punch_prog3", + "cnc_start_async_punch_prog3_bg", + "cnc_start_async_read_prog3", + "cnc_start_async_read_prog3_bg", + "cnc_start_async_wrparam", + "cnc_start_grppos", + "cnc_start_grppos2", + "cnc_start_grppos3", + "cnc_startaux", + "cnc_startdyngrph", + "cnc_startnccmd", + "cnc_startomhis", + "cnc_startophis", + "cnc_startptcnv", + "cnc_statinfo", + "cnc_statinfo2", + "cnc_statinfo_bg", + "cnc_statinfo_dmg", + "cnc_statlrntrns", + "cnc_status_prps", + "cnc_stop_async_read_punch", + "cnc_stop_grppos", + "cnc_stop_grppos2", + "cnc_stop_grppos3", + "cnc_stopdyngrph", + "cnc_stoplrntrns", + "cnc_stopnccmd", + "cnc_stopomhis", + "cnc_stopophis", + "cnc_stplutosmpl", + "cnc_stpossmpl", + "cnc_svdtendrd", + "cnc_svdtendwr", + "cnc_svdtrddata", + "cnc_svdtstartrd", + "cnc_svdtstartwr", + "cnc_svdtstopexec", + "cnc_svdtwrdata", + "cnc_sysinfo", + "cnc_sysinfo_ex", + "cnc_t1info", + "cnc_t2info", + "cnc_tofs_rnge", + "cnc_tool_in", + "cnc_tool_in2", + "cnc_tool_in3", + "cnc_tool_move", + "cnc_tool_out", + "cnc_tool_srh_free_min_num", + "cnc_tool_temp_in", + "cnc_tool_temp_out", + "cnc_toolnum", + "cnc_toolsrch", + "cnc_tpl_read", + "cnc_twp_rdfcoord", + "cnc_twp_rdfmt_mtrx", + "cnc_updexauxr", + "cnc_upend", + "cnc_upend3", + "cnc_upend4", + "cnc_upload", + "cnc_upload3", + "cnc_upload4", + "cnc_upstart", + "cnc_upstart3", + "cnc_upstart4", + "cnc_upstarto8", + "cnc_validate_opt", + "cnc_validate_prm", + "cnc_verify", + "cnc_verify4", + "cnc_verify_prog", + "cnc_verify_prog_bg", + "cnc_vrfend", + "cnc_vrfend4", + "cnc_vrfstart", + "cnc_vrfstart4", + "cnc_wavestart", + "cnc_wavestat", + "cnc_wavestop", + "cnc_wksft_rnge", + "cnc_wksft_rnge64", + "cnc_workzero", + "cnc_wr1length", + "cnc_wr1radius", + "cnc_wr1tlifedat2", + "cnc_wr1tlifedata", + "cnc_wr2length", + "cnc_wr2radius", + "cnc_wr2tlifedata", + "cnc_wr_scrlwaitmcode", + "cnc_wr_sfsg_disp_stat", + "cnc_wr_sfsg_extractslct", + "cnc_wractpt", + "cnc_wrcbmem", + "cnc_wrcbmem2", + "cnc_wrcbprm", + "cnc_wrcexesram", + "cnc_wrcncmem", + "cnc_wrcncmem2", + "cnc_wrcnttype", + "cnc_wrcount", + "cnc_wrcountr", + "cnc_wrdofs", + "cnc_wrdsdncfile", + "cnc_wrdsdncfile2", + "cnc_wredgedata", + "cnc_wredgedata2", + "cnc_wredmcram", + "cnc_wrfbusmem", + "cnc_wrfctfssadjust", + "cnc_wrfctfssinfo", + "cnc_wrfctsitem", + "cnc_wrfile_sram", + "cnc_wrfixofs", + "cnc_wrfromservoid", + "cnc_wrfromservoid2", + "cnc_wrfromspindleid", + "cnc_wrfromspindleid2", + "cnc_wrfssb_axis", + "cnc_wrfssb_axis_num", + "cnc_wrftl", + "cnc_wrglvnparam", + "cnc_wrgrpinfo", + "cnc_wrgrpinfo2", + "cnc_wrgrpinfo3", + "cnc_wrhipitchr", + "cnc_wrhissgnl", + "cnc_wrhissgnl3", + "cnc_wrifsb_as_axis_num", + "cnc_wrifsb_as_hrv", + "cnc_wrifsb_as_spdl_num", + "cnc_wrifsb_as_sv_axis", + "cnc_wrindexdata", + "cnc_wrindexofs", + "cnc_wrindexposdata", + "cnc_wrindexprm", + "cnc_wrintchk", + "cnc_wrjogmdi", + "cnc_wrjogmdiclr", + "cnc_wrkeyhistry", + "cnc_wrlagingmode", + "cnc_wrlagslt", + "cnc_wrldsplc", + "cnc_wrldsplc2", + "cnc_wrledgprc", + "cnc_wrlife", + "cnc_wrlppfbdt", + "cnc_wrlprcprc", + "cnc_wrlpscdpwrctl", + "cnc_wrlpwrctrl", + "cnc_wrlpwrdty", + "cnc_wrlpwrslt", + "cnc_wrlrninfo", + "cnc_wrlrnprf", + "cnc_wrlswork", + "cnc_wrmacro", + "cnc_wrmacro_bg", + "cnc_wrmacror", + "cnc_wrmacror2", + "cnc_wrmag_property", + "cnc_wrmagazine", + "cnc_wrmdipntr", + "cnc_wrmdiprog", + "cnc_wrmenuswitch", + "cnc_wrmgrpdata", + "cnc_wrmngtime", + "cnc_wrmofs", + "cnc_wrmultitldt", + "cnc_wropnlgnrl", + "cnc_wropnlgnrl2", + "cnc_wropnlgsname", + "cnc_wropnlgsname2", + "cnc_wropnlsgnl", + "cnc_wroverstore", + "cnc_wrparam", + "cnc_wrparam3", + "cnc_wrparam64", + "cnc_wrparas", + "cnc_wrparas3", + "cnc_wrpdf_attr", + "cnc_wrpdf_char", + "cnc_wrpdf_curdir", + "cnc_wrpdf_line", + "cnc_wrpitchr", + "cnc_wrpitchr2", + "cnc_wrpm_item", + "cnc_wrpm_mcnitem", + "cnc_wrpmacro", + "cnc_wrpmacror", + "cnc_wrpmacror2", + "cnc_wrpmacror_bg", + "cnc_wrpmmprm", + "cnc_wrpmmprmtp", + "cnc_wrpot_property", + "cnc_wrprogline", + "cnc_wrprt_lvl", + "cnc_wrpscdedge", + "cnc_wrpscdedge2", + "cnc_wrpscdpirc", + "cnc_wrpscdproc", + "cnc_wrpscdproc2", + "cnc_wrpscdslop", + "cnc_wrptstoptime", + "cnc_wrpunchtl_ex", + "cnc_wrrelpos", + "cnc_wrrelpos64", + "cnc_wrrotvolc", + "cnc_wrrotvolc2", + "cnc_wrrtmiowrenbl", + "cnc_wrrtmiowrenblbit", + "cnc_wrrtmiowrenblrng", + "cnc_wrrtmrvar", + "cnc_wrrtmrvars", + "cnc_wrsafetyzone", + "cnc_wrscdldat", + "cnc_wrset", + "cnc_wrsets", + "cnc_wrsetzone", + "cnc_wrsignal_f", + "cnc_wrsoc_tldat", + "cnc_wrt1info", + "cnc_wrt2info", + "cnc_wrtdicomment", + "cnc_wrtdicubedata", + "cnc_wrtdicylinderdata", + "cnc_wrtdidispsetting", + "cnc_wrtdieffectshape", + "cnc_wrtdifignum", + "cnc_wrtdifiguredata", + "cnc_wrtdiinitview", + "cnc_wrtdimoveaxis", + "cnc_wrtdinamesetting", + "cnc_wrtdiplanedata", + "cnc_wrtdishapedata", + "cnc_wrtimer", + "cnc_wrtlctldata", + "cnc_wrtldata", + "cnc_wrtlgeomsize", + "cnc_wrtlgeomsize_ext", + "cnc_wrtlgeomsize_ext2", + "cnc_wrtofs", + "cnc_wrtofsdrctinp", + "cnc_wrtofsdrctinp64", + "cnc_wrtofsms", + "cnc_wrtofsr", + "cnc_wrtool", + "cnc_wrtool2", + "cnc_wrtool_f2", + "cnc_wrtooldata", + "cnc_wrtoolgeom_tlm", + "cnc_wrtoolgrp", + "cnc_wrtoolnum", + "cnc_wrtoolzone", + "cnc_wrtrqlimit", + "cnc_wrvolc", + "cnc_wrwaveprm", + "cnc_wrwaveprm2", + "cnc_wrwaveprm3", + "cnc_wrwkcdsfms", + "cnc_wrwkcdsfms2", + "cnc_wrwkcdsfms3", + "cnc_wrwkcdsfms64", + "cnc_wrwkcdshft", + "cnc_wrwkcdshft2", + "cnc_wrwkcdshft3", + "cnc_wrwkcdshft64", + "cnc_wrwseterror", + "cnc_wrzofs", + "cnc_wrzofs64", + "cnc_wrzofsr", + "cnc_zofs_rnge", + "cnc_zofs_rnge64", + "dnm_clrerrorrecord", + "dnm_rderrorrecord", + "dnm_rdfirminfo", + "dnm_rdnodeinfo", + "dnm_rdnodetable", + "dnm_rdparam", + "dnm_wrparam", + "dns_rdinfo", + "dns_rdparam", + "dns_restart", + "dns_wrparam", + "eth_abortdnc", + "eth_abortdownload", + "eth_abortupload", + "eth_applyunsolicprm", + "eth_changelockstat1", + "eth_changelockstat2", + "eth_clrlog", + "eth_clrlsistate", + "eth_disconfsclnt", + "eth_disconfsclntall", + "eth_dncsegment", + "eth_downloadsegment", + "eth_dschkdsk", + "eth_dsformat", + "eth_embrestart", + "eth_exit", + "eth_getlockstat1", + "eth_getlockstat2", + "eth_init", + "eth_initiatednc", + "eth_initiatedownload", + "eth_initiateupload", + "eth_ping", + "eth_ping_cancel", + "eth_ping_result", + "eth_rddsformat", + "eth_rddsm198dir", + "eth_rddsm198host", + "eth_rddsmode", + "eth_rddsstate", + "eth_rdembdev", + "eth_rdembm198host", + "eth_rdfsclntinfo", + "eth_rdhost", + "eth_rdlog", + "eth_rdlsistate", + "eth_rdparam", + "eth_rdrmdinquiry", + "eth_rdtaskstate", + "eth_rdtype", + "eth_rdtype2", + "eth_rdunsolicmode", + "eth_rdunsolicstate", + "eth_remotecontrol", + "eth_terminatednc", + "eth_terminatedownload", + "eth_uploadsegment", + "eth_workinit", + "eth_wrdsm198dir", + "eth_wrdsm198host", + "eth_wrdsmode", + "eth_wrembdev", + "eth_wrembm198host", + "eth_wrhost", + "eth_wrparam", + "eth_wrrmdinquiry", + "eth_wrunsolicmode", + "flnt_clrlog", + "flnt_clrlog2", + "flnt_clrmsg", + "flnt_clrmsg2", + "flnt_clrnetwork2", + "flnt_rddeviceinfo", + "flnt_rdentry", + "flnt_rdentry2", + "flnt_rdlog", + "flnt_rdlog2", + "flnt_rdmsg", + "flnt_rdmsg2", + "flnt_rdnetwork", + "flnt_rdnetwork2", + "flnt_rdnodeinfo", + "flnt_rdnodeinfo2", + "flnt_rdparam", + "flnt_rdparam2", + "flnt_rdsferrnode", + "flnt_rdsfstatus", + "flnt_wrparam", + "flnt_wrparam2", + "net_backup_param", + "net_restore_param", + "pbm_chg_mode", + "pbm_exe_subfunc", + "pbm_ini_prm", + "pbm_rd_allslvtbl", + "pbm_rd_cominfo", + "pbm_rd_errcode", + "pbm_rd_nodeinfo", + "pbm_rd_nodetable", + "pbm_rd_param", + "pbm_rd_slot", + "pbm_rd_slotinfo", + "pbm_rd_subprm", + "pbm_wr_param", + "pbs_ini_prm", + "pbs_rd_cominfo", + "pbs_rd_cominfo2", + "pbs_rd_param", + "pbs_rd_param2", + "pbs_wr_param", + "pbs_wr_param2", + "pmc_abort_all_transfer", + "pmc_abort_auto_trace_output", + "pmc_abort_end_process", + "pmc_abort_multiple_write_check", + "pmc_abort_trace_function", + "pmc_abort_transfer", + "pmc_allocate_work_memory", + "pmc_can_apply_override", + "pmc_change_operation", + "pmc_change_operation_with_passwd", + "pmc_change_scoped_symcmt", + "pmc_check_io_assignment_empty", + "pmc_check_iolinki_assign", + "pmc_check_permission", + "pmc_check_permission_with_passwd", + "pmc_clear_io_configuration", + "pmc_control_snp_driver", + "pmc_convert_address_pmc_to_vio", + "pmc_convert_from_string_to_address", + "pmc_convert_pmc_address_from_sync", + "pmc_convert_pmc_address_to_string", + "pmc_convert_pmc_address_to_sync", + "pmc_convert_vio_address_from_sync", + "pmc_convert_vio_address_to_string", + "pmc_convert_vio_address_to_sync", + "pmc_convret_address_vio_to_pmc", + "pmc_delete_all_io_assignment_data", + "pmc_delete_all_message_data", + "pmc_delete_all_symcmt_data", + "pmc_delete_data_table_group_data", + "pmc_delete_extra_relay_group_data", + "pmc_delete_io_assignment_data", + "pmc_delete_iolinki_assign_group", + "pmc_delete_message_entry", + "pmc_delete_node_mutex", + "pmc_delete_scoped_symcmt", + "pmc_delete_seq_program_object", + "pmc_delete_sfc_monitor_time", + "pmc_delete_symcmt_entry", + "pmc_end_search_symbol", + "pmc_exchange_permission", + "pmc_fdcas_compare_file_by_name", + "pmc_fdcas_compare_file_by_number", + "pmc_fdcas_delete_all_files", + "pmc_fdcas_delete_file_by_name", + "pmc_fdcas_delete_file_by_number", + "pmc_fdcas_get_comm_parameter", + "pmc_fdcas_init_comm_parameter", + "pmc_fdcas_read_directory", + "pmc_fdcas_read_file_by_name", + "pmc_fdcas_read_file_by_number", + "pmc_fdcas_read_file_count", + "pmc_fdcas_read_file_status", + "pmc_fdcas_set_comm_parameter", + "pmc_fdcas_write_pmc_parameter", + "pmc_fdcas_write_seq_program", + "pmc_flashrom_compare_message", + "pmc_flashrom_compare_seq_program", + "pmc_flashrom_read_message", + "pmc_flashrom_read_seq_program", + "pmc_flashrom_write_message", + "pmc_flashrom_write_seq_program", + "pmc_force_release_object", + "pmc_force_status_bit", + "pmc_force_status_byte", + "pmc_get_address_entry", + "pmc_get_alarm_status", + "pmc_get_all_associated_symbols", + "pmc_get_all_associated_symcmts", + "pmc_get_auto_trace_output_status", + "pmc_get_available_comment_set", + "pmc_get_available_io_connection", + "pmc_get_common_memory_group", + "pmc_get_current_comment", + "pmc_get_current_divided_ladder", + "pmc_get_current_pmc", + "pmc_get_current_pmc_unit", + "pmc_get_current_program_number", + "pmc_get_data_table_group_count", + "pmc_get_data_table_max_group_count", + "pmc_get_default_filename", + "pmc_get_divided_ladder", + "pmc_get_divided_ladders", + "pmc_get_extra_relay_group_count", + "pmc_get_extra_relay_max_group_count", + "pmc_get_file_info_count", + "pmc_get_font_information", + "pmc_get_instruction_set_version", + "pmc_get_io_assignment_address", + "pmc_get_io_configuration", + "pmc_get_io_device_diagnosis", + "pmc_get_io_device_id", + "pmc_get_io_device_list", + "pmc_get_max_sfc_monitor_time_count", + "pmc_get_maximum_trace_frames", + "pmc_get_message_data_size", + "pmc_get_message_entry_address", + "pmc_get_message_entry_count", + "pmc_get_message_maximum_size", + "pmc_get_message_remain_memory", + "pmc_get_multiple_coil_result", + "pmc_get_multiple_instruction_result", + "pmc_get_multiple_symcmts", + "pmc_get_nonvolatile_memory_status", + "pmc_get_number_of_address_entries", + "pmc_get_number_of_iolinki_assign_channels", + "pmc_get_number_of_ladder", + "pmc_get_number_of_pmc", + "pmc_get_number_of_pmc_path", + "pmc_get_object_update_counter", + "pmc_get_object_version", + "pmc_get_override_status", + "pmc_get_permission_status", + "pmc_get_pmc_address_attribute", + "pmc_get_pmc_address_info", + "pmc_get_pmc_address_map", + "pmc_get_pmc_path_types", + "pmc_get_pmc_unit_types", + "pmc_get_programable_io_channels", + "pmc_get_real_io_assignment_info", + "pmc_get_real_selectable_io_assignment_data", + "pmc_get_result_trace_parameter", + "pmc_get_scoped_symcmt", + "pmc_get_search_symbol_result", + "pmc_get_search_symbol_status", + "pmc_get_sfc_active_step_trace", + "pmc_get_sfc_diagram_memory", + "pmc_get_sfc_monitor_status", + "pmc_get_sfc_monitor_time_information", + "pmc_get_snp_communication_status", + "pmc_get_snp_driver_status", + "pmc_get_splist_status", + "pmc_get_subprogram_list", + "pmc_get_symbol_comment", + "pmc_get_symbol_comment2", + "pmc_get_symbol_object_version", + "pmc_get_symcmt_bit", + "pmc_get_symcmt_byte", + "pmc_get_symcmt_data_size", + "pmc_get_symcmt_entry_count", + "pmc_get_symcmt_maximum_size", + "pmc_get_symcmt_remain_memory", + "pmc_get_timer_type", + "pmc_get_tmr_instruction_accuracy", + "pmc_get_trace_result", + "pmc_get_trace_specification", + "pmc_get_trace_status", + "pmc_get_transfer_status", + "pmc_get_unit_type", + "pmc_get_xref_address_result", + "pmc_get_xref_address_result2", + "pmc_get_xref_instruction_result", + "pmc_get_xref_status", + "pmc_getdtailerr", + "pmc_getpmcptr", + "pmc_giveout_node_mutex", + "pmc_global_program_number_to_local", + "pmc_handle_work_area_size", + "pmc_initialize_data_table_group", + "pmc_initialize_extra_relay_group", + "pmc_initialize_handle_work_area", + "pmc_initialize_iolinki_assign_channel", + "pmc_initialize_node_mutex", + "pmc_initialize_node_work_area", + "pmc_initialize_nonvolatile_memory", + "pmc_insert_iolinki_assign_group", + "pmc_insert_seq_program_space", + "pmc_is_bit_accessible_pmc_address", + "pmc_is_effective_pmc_address", + "pmc_is_fb_instance_id", + "pmc_is_seq_program_available", + "pmc_is_trace_function_active", + "pmc_is_visible_pmc_address", + "pmc_is_writable_pmc_address", + "pmc_kpmsiz", + "pmc_local_program_number_to_global", + "pmc_logical_and_pmc_address_uint8", + "pmc_logical_and_vio_address_uint8", + "pmc_logical_or_pmc_address_uint8", + "pmc_logical_or_vio_address_uint8", + "pmc_logical_xor_pmc_address_uint8", + "pmc_logical_xor_vio_address_uint8", + "pmc_mcard_compare_file", + "pmc_mcard_delete_file", + "pmc_mcard_format", + "pmc_mcard_read_directory", + "pmc_mcard_read_file", + "pmc_mcard_read_file_count", + "pmc_mcard_read_file_status", + "pmc_mcard_write_pmc_message", + "pmc_mcard_write_pmc_parameter", + "pmc_mcard_write_seq_program", + "pmc_mcard_write_trace_result", + "pmc_move_iolinki_assign_group", + "pmc_node_work_area_size", + "pmc_notify_handle_close", + "pmc_parse_string_into_pmc_address", + "pmc_parse_string_into_scoped_pmc_address", + "pmc_rdalmmsg", + "pmc_rdcntl_exrelay_grp", + "pmc_rdcntldata", + "pmc_rdcntlexrelay", + "pmc_rdcntlgrp", + "pmc_rdioconfigtitle", + "pmc_rdkpm", + "pmc_rdkpm2", + "pmc_rdmessagetitle", + "pmc_rdpmcaddr", + "pmc_rdpmcinfo", + "pmc_rdpmcparam", + "pmc_rdpmcrng", + "pmc_rdpmctitle", + "pmc_rdpmctitle2", + "pmc_rdprmend", + "pmc_rdprmstart", + "pmc_rdwrpmcrng", + "pmc_read_actual_io_assignment_data", + "pmc_read_actual_io_assignment_data_by_address", + "pmc_read_alarm_number", + "pmc_read_cnc_dido", + "pmc_read_data_table_group_data", + "pmc_read_detail_scan_time", + "pmc_read_end_process_status", + "pmc_read_extra_relay_group_data", + "pmc_read_file_info_list", + "pmc_read_instruction_monitor_status", + "pmc_read_io_assignment_data", + "pmc_read_iolink_alarm_info", + "pmc_read_iolinki_assign_channel_data", + "pmc_read_iolinki_assign_group_list", + "pmc_read_iolinki_assign_group_slot", + "pmc_read_iolinki_assign_info", + "pmc_read_iolinki_assign_slot_list", + "pmc_read_iolinki_assign_title", + "pmc_read_iolinki_assign_title2", + "pmc_read_machine_signal", + "pmc_read_message_entry_data", + "pmc_read_message_info", + "pmc_read_message_title", + "pmc_read_message_title2", + "pmc_read_multi_lang_message_data", + "pmc_read_nonvolatile_memory", + "pmc_read_performance_information", + "pmc_read_pmc_address_bit", + "pmc_read_pmc_address_real32", + "pmc_read_pmc_address_real64", + "pmc_read_pmc_address_uint16", + "pmc_read_pmc_address_uint32", + "pmc_read_pmc_address_uint8", + "pmc_read_pmc_software_version", + "pmc_read_raw_memory", + "pmc_read_scan_time", + "pmc_read_scoped_symcmt", + "pmc_read_scoped_symcmt_ext", + "pmc_read_seq_object_size", + "pmc_read_seq_program_and_memory_type", + "pmc_read_seq_program_config", + "pmc_read_seq_program_object", + "pmc_read_seq_program_size", + "pmc_read_seq_program_status", + "pmc_read_seq_program_type", + "pmc_read_setting", + "pmc_read_snp_parameter", + "pmc_read_symcmt_entry", + "pmc_read_system_parameter", + "pmc_read_title_data", + "pmc_read_title_data2", + "pmc_read_trace_parameter", + "pmc_read_vio_address_bit", + "pmc_read_vio_address_uint16", + "pmc_read_vio_address_uint32", + "pmc_read_vio_address_uint8", + "pmc_register_io_configuration", + "pmc_release_node_work_area", + "pmc_request_permission", + "pmc_request_permission_with_passwd", + "pmc_reserve_nonvolatile_memory", + "pmc_reset_all_override", + "pmc_reset_override", + "pmc_reset_scan_time", + "pmc_reset_sfc_monitor_time", + "pmc_restore_seq_program_edit", + "pmc_resume_end_process", + "pmc_resume_transfer", + "pmc_retrieve_permission", + "pmc_return_permission", + "pmc_rs232c_compare", + "pmc_rs232c_get_comm_parameter", + "pmc_rs232c_init_comm_parameter", + "pmc_rs232c_read", + "pmc_rs232c_set_comm_parameter", + "pmc_rs232c_write_pmc_parameter", + "pmc_rs232c_write_seq_program", + "pmc_run_seq_program", + "pmc_search_comment_set", + "pmc_search_divided_ladder", + "pmc_search_iolinki_assign_address", + "pmc_search_iolinki_assign_free_address", + "pmc_search_ladder_address", + "pmc_search_ladder_function", + "pmc_search_message_entry_position", + "pmc_search_message_string", + "pmc_search_multi_ladder", + "pmc_search_scoped_symcmt_by_address", + "pmc_search_scoped_symcmt_by_symbol", + "pmc_search_scoped_symcmt_string", + "pmc_search_symbol_string", + "pmc_search_symcmt_entry_by_symbol", + "pmc_search_symcmt_entry_position", + "pmc_search_symcmt_string", + "pmc_search_unit_type", + "pmc_select_divided_ladder", + "pmc_select_pmc", + "pmc_select_pmc_unit", + "pmc_select_program_number", + "pmc_set_data_table_group_count", + "pmc_set_extra_relay_group_count", + "pmc_set_language", + "pmc_set_override", + "pmc_set_sfc_monitor_time", + "pmc_set_timer_type", + "pmc_start_end_process", + "pmc_start_end_process2", + "pmc_start_list_subprogram", + "pmc_start_multiple_coil_check", + "pmc_start_multiple_coil_check2", + "pmc_start_multiple_instruction_check", + "pmc_start_multiple_instruction_check2", + "pmc_start_search_symbol", + "pmc_start_trace_function", + "pmc_start_transfer", + "pmc_start_xref_address", + "pmc_start_xref_address2", + "pmc_start_xref_instruction", + "pmc_start_xref_instruction2", + "pmc_stop_seq_program", + "pmc_wrcntl_exrelay_grp", + "pmc_wrcntldata", + "pmc_wrcntlexrelay", + "pmc_wrcntlgrp", + "pmc_wriolinkdat", + "pmc_write_cnc_dido", + "pmc_write_data_table_group_data", + "pmc_write_extra_relay_group_data", + "pmc_write_io_assignment_data", + "pmc_write_iolinki_assign_channel_data", + "pmc_write_iolinki_assign_group_data", + "pmc_write_iolinki_assign_group_slot", + "pmc_write_iolinki_assign_slot_data", + "pmc_write_message_entry", + "pmc_write_nonvolatile_memory", + "pmc_write_pmc_address_bit", + "pmc_write_pmc_address_real32", + "pmc_write_pmc_address_real64", + "pmc_write_pmc_address_uint16", + "pmc_write_pmc_address_uint32", + "pmc_write_pmc_address_uint8", + "pmc_write_raw_io_assignment_data", + "pmc_write_raw_memory", + "pmc_write_scoped_symcmt", + "pmc_write_seq_program_object", + "pmc_write_setting", + "pmc_write_snp_parameter", + "pmc_write_symcmt_entry", + "pmc_write_system_parameter", + "pmc_write_title_data", + "pmc_write_title_data2", + "pmc_write_trace_parameter", + "pmc_write_vio_address_bit", + "pmc_write_vio_address_uint16", + "pmc_write_vio_address_uint32", + "pmc_write_vio_address_uint8", + "pmc_wrkpm", + "pmc_wrkpm2", + "pmc_wrpmcparam", + "pmc_wrpmcrng", + "pmc_wrpmcrng2", + "pmc_wrprmend", + "pmc_wrprmstart" + ], + "notes": [ + "Exports extracted directly from the 64-bit DLL PE export directory.", + "Wrapper-supported methods are intersected with upstream fwlib.cs extern declarations.", + "Axis and path hints are filename-family heuristics, not protocol-level proofs." + ] +} \ No newline at end of file diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlibe64.json b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlibe64.json new file mode 100644 index 0000000..30357dc --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/builtin_profiles/fwlibe64.json @@ -0,0 +1,1669 @@ +{ + "profile_name": "fwlibe64", + "dll_name": "fwlibe64.dll", + "series_hint": "embedded-ethernet", + "max_axis_hint": 8, + "max_path_hint": 1, + "export_count": 1032, + "connection_methods": [ + "cnc_allclibhndl3", + "cnc_allclibhndl4", + "cnc_allclibhndls" + ], + "mock_methods": [ + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_allclibhndl3", + "cnc_diagnoss", + "cnc_exeprgname2", + "cnc_freelibhndl", + "cnc_getpath", + "cnc_rdalmmsg2", + "cnc_rdaxisname", + "cnc_rdblkcount", + "cnc_rddiaginfo", + "cnc_rddiagnum", + "cnc_rddynamic2", + "cnc_rdexecprog", + "cnc_rdmacro", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdparam", + "cnc_rdprgnum", + "cnc_rdprogdir3", + "cnc_rdproginfo", + "cnc_rdseqnum", + "cnc_rdspdlname", + "cnc_rdspgear", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsvmeter", + "cnc_rdtimer", + "cnc_setpath", + "cnc_statinfo", + "cnc_sysinfo", + "cnc_wrmacro", + "cnc_wrparam", + "pmc_rdpmcrng", + "pmc_wrpmcrng" + ], + "wrapper_supported_count": 575, + "wrapper_supported_methods": [ + "cnc_absolute", + "cnc_absolute2", + "cnc_accdecdly", + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_adcnv", + "cnc_alarm", + "cnc_alarm2", + "cnc_allclibhndl3", + "cnc_allclibhndl4", + "cnc_allowance", + "cnc_allowcnd", + "cnc_buff", + "cnc_canmovrlap", + "cnc_cdnc", + "cnc_cdownload", + "cnc_cexedirectory", + "cnc_cexesraminfo", + "cnc_cexesramsize", + "cnc_chgprotbit", + "cnc_clearomhis", + "cnc_clearophis", + "cnc_closecexefile", + "cnc_clr3dplsmov", + "cnc_clr5axpls", + "cnc_clralm", + "cnc_clrcntinfo", + "cnc_clrgrphcmd", + "cnc_condense", + "cnc_copyprog", + "cnc_cupload", + "cnc_cverify", + "cnc_delall", + "cnc_delete", + "cnc_delmagazine", + "cnc_deltlifedt", + "cnc_deltlifegrp", + "cnc_deltool", + "cnc_diagnosr", + "cnc_diagnoss", + "cnc_dispoptmsg", + "cnc_distance", + "cnc_dnc", + "cnc_dnc2", + "cnc_dncend", + "cnc_dncend2", + "cnc_dncstart", + "cnc_dncstart2", + "cnc_download", + "cnc_download3", + "cnc_download4", + "cnc_dwnend", + "cnc_dwnend3", + "cnc_dwnend4", + "cnc_dwnstart", + "cnc_dwnstart3", + "cnc_dwnstart3_f", + "cnc_dwnstart4", + "cnc_exaxisname", + "cnc_exeprgname", + "cnc_exeprgname2", + "cnc_freelibhndl", + "cnc_fromdelete", + "cnc_fromget", + "cnc_fromgetend", + "cnc_fromgetstart", + "cnc_fromldend", + "cnc_fromldstart", + "cnc_fromload", + "cnc_fromput", + "cnc_fromputend", + "cnc_fromputstart", + "cnc_fromremove", + "cnc_fromsave", + "cnc_fromsvend", + "cnc_fromsvstart", + "cnc_ftosjis", + "cnc_getcncmodel", + "cnc_getdtailerr", + "cnc_getfigure", + "cnc_getfocas1opt", + "cnc_getfrominfo", + "cnc_getlibopt", + "cnc_getlockstat", + "cnc_getmactype", + "cnc_getpath", + "cnc_getpmactype", + "cnc_getsraminfo", + "cnc_gettimer", + "cnc_hpccactfine", + "cnc_hpccatset", + "cnc_hpccattune", + "cnc_hpccselfine", + "cnc_instlifedt", + "cnc_machine", + "cnc_modal", + "cnc_opencexefile", + "cnc_optmsgans", + "cnc_pdf_add", + "cnc_pdf_cond", + "cnc_pdf_copy", + "cnc_pdf_del", + "cnc_pdf_delline", + "cnc_pdf_move", + "cnc_pdf_rdactpt", + "cnc_pdf_rdmain", + "cnc_pdf_rename", + "cnc_pdf_searchresult", + "cnc_pdf_searchword", + "cnc_pdf_slctmain", + "cnc_pdf_wractpt", + "cnc_prstwkcd", + "cnc_rd1length", + "cnc_rd1radius", + "cnc_rd1tlifedat2", + "cnc_rd1tlifedata", + "cnc_rd2length", + "cnc_rd2radius", + "cnc_rd2tlifedata", + "cnc_rd3dcdcnv", + "cnc_rd3dmovrlap", + "cnc_rd3dofschg", + "cnc_rd3dpulse", + "cnc_rd3dtofs", + "cnc_rd3dtooltip", + "cnc_rd5axmandt", + "cnc_rd5axovrlap", + "cnc_rdactfixofs", + "cnc_rdactpt", + "cnc_rdacttlzone", + "cnc_rdalmhisno", + "cnc_rdalmhistry", + "cnc_rdalmhistry2", + "cnc_rdalmhistry5", + "cnc_rdalminfo", + "cnc_rdalmmsg", + "cnc_rdalmmsg2", + "cnc_rdaxisdata", + "cnc_rdaxisname", + "cnc_rdbaxis", + "cnc_rdblkcount", + "cnc_rdbrstrinfo", + "cnc_rdbtofsinfo", + "cnc_rdbtofsr", + "cnc_rdcdrotate", + "cnc_rdcexefile", + "cnc_rdcexesram", + "cnc_rdcncid", + "cnc_rdcommand", + "cnc_rdcoordnum", + "cnc_rdcount", + "cnc_rdctrldi", + "cnc_rdctrldo", + "cnc_rdcurrent", + "cnc_rddiag_ext", + "cnc_rddiaginfo", + "cnc_rddiagnum", + "cnc_rddischarge", + "cnc_rddischrgalm", + "cnc_rddncdgndt", + "cnc_rddynamic", + "cnc_rddynamic2", + "cnc_rdetherinfo", + "cnc_rdexchgtgrp", + "cnc_rdexecmcode", + "cnc_rdexecprog", + "cnc_rdfbusmem", + "cnc_rdfixcycle", + "cnc_rdfixofs", + "cnc_rdfrominfo", + "cnc_rdgcode", + "cnc_rdgrphcanflg", + "cnc_rdgrphcmd", + "cnc_rdgrpid", + "cnc_rdgrpid2", + "cnc_rdgrpinfo", + "cnc_rdgrpinfo2", + "cnc_rdgrpinfo3", + "cnc_rdgrpinfo4", + "cnc_rdhissgnl", + "cnc_rdhissgnl3", + "cnc_rdhndintrpt", + "cnc_rdintchk", + "cnc_rdintinfo", + "cnc_rdlactnum", + "cnc_rdlagslt", + "cnc_rdlagst", + "cnc_rdlcmddat", + "cnc_rdlcmmt", + "cnc_rdldsplc", + "cnc_rdledgprc", + "cnc_rdlenofs", + "cnc_rdlerrz", + "cnc_rdlife", + "cnc_rdloopgain", + "cnc_rdlprcprc", + "cnc_rdlpwrcpst", + "cnc_rdlpwrctrl", + "cnc_rdlpwrdat", + "cnc_rdlpwrdty", + "cnc_rdlpwrslt", + "cnc_rdmacro", + "cnc_rdmacroinfo", + "cnc_rdmacror", + "cnc_rdmacror2", + "cnc_rdmagazine", + "cnc_rdmaxgrp", + "cnc_rdmaxtool", + "cnc_rdmdipntr", + "cnc_rdmdiprgstat", + "cnc_rdmdiprog", + "cnc_rdmdlconfig2", + "cnc_rdmenuswitch", + "cnc_rdmgrpdata", + "cnc_rdmirimage", + "cnc_rdmngtime", + "cnc_rdmovrlap", + "cnc_rdmsptype", + "cnc_rdmtapdata", + "cnc_rdmultipieceno", + "cnc_rdmultitldt", + "cnc_rdngrp", + "cnc_rdnspdl", + "cnc_rdntool", + "cnc_rdomhisinfo", + "cnc_rdomhisno", + "cnc_rdomhistry", + "cnc_rdomhistry2", + "cnc_rdophisno", + "cnc_rdophistry", + "cnc_rdophistry2", + "cnc_rdophistry4", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdopmsg2", + "cnc_rdopmsg3", + "cnc_rdopnlgnrl", + "cnc_rdopnlgsname", + "cnc_rdopnlsgnl", + "cnc_rdparainfo", + "cnc_rdparam", + "cnc_rdparam3", + "cnc_rdparam_ext", + "cnc_rdparanum", + "cnc_rdparar", + "cnc_rdpdf_alldir", + "cnc_rdpdf_curdir", + "cnc_rdpdf_drive", + "cnc_rdpdf_inf", + "cnc_rdpdf_line", + "cnc_rdpdf_subdir", + "cnc_rdpdf_subdirn", + "cnc_rdpitchinfo", + "cnc_rdpitchr", + "cnc_rdpm_cncitem", + "cnc_rdpm_item", + "cnc_rdpm_mcnitem", + "cnc_rdpmacro", + "cnc_rdpmacroinfo", + "cnc_rdpmacror", + "cnc_rdpmacror2", + "cnc_rdposerrs", + "cnc_rdposerrs2", + "cnc_rdposerrz", + "cnc_rdposition", + "cnc_rdprgdirtime", + "cnc_rdprgnum", + "cnc_rdproctime", + "cnc_rdprogdir", + "cnc_rdprogdir2", + "cnc_rdprogdir3", + "cnc_rdproginfo", + "cnc_rdprstrinfo", + "cnc_rdpscdedge", + "cnc_rdpscdpirc", + "cnc_rdpscdproc", + "cnc_rdpscdslop", + "cnc_rdpwofsthis", + "cnc_rdradofs", + "cnc_rdrepeatval", + "cnc_rdrmtwavedt", + "cnc_rdrmtwaveprm", + "cnc_rdrstrmcode", + "cnc_rdsafetyzone", + "cnc_rdsavsigadr", + "cnc_rdsavsigdata", + "cnc_rdscaling", + "cnc_rdseqnum", + "cnc_rdset", + "cnc_rdsetinfo", + "cnc_rdsetnum", + "cnc_rdsetr", + "cnc_rdsetzone", + "cnc_rdspcss", + "cnc_rdspdlalm", + "cnc_rdspdlname", + "cnc_rdspeed", + "cnc_rdspgear", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsramaddr", + "cnc_rdsraminfo", + "cnc_rdsramnum", + "cnc_rdsrvspeed", + "cnc_rdsvmeter", + "cnc_rdsynerrrg", + "cnc_rdsynerrsy", + "cnc_rdsyshard", + "cnc_rdsyssoft", + "cnc_rdsyssoft2", + "cnc_rdsyssoft3", + "cnc_rdtimer", + "cnc_rdtlctldata", + "cnc_rdtlgrp", + "cnc_rdtlinfo", + "cnc_rdtltool", + "cnc_rdtlusegrp", + "cnc_rdtofs", + "cnc_rdtofsinfo", + "cnc_rdtofsinfo2", + "cnc_rdtofsr", + "cnc_rdtool", + "cnc_rdtool_f2", + "cnc_rdtooldata", + "cnc_rdtoolgrp", + "cnc_rdtoolinfo", + "cnc_rdtoolrng", + "cnc_rdtoolzone", + "cnc_rdunsolicmsg", + "cnc_rdunsolicprm", + "cnc_rdusegrpid", + "cnc_rdusetlno", + "cnc_rduvactpt", + "cnc_rdwavedata", + "cnc_rdwaveprm", + "cnc_rdwaveprm2", + "cnc_rdwkcdsfms", + "cnc_rdwkcdshft", + "cnc_rdzofs", + "cnc_rdzofsinfo", + "cnc_rdzofsr", + "cnc_regmagazine", + "cnc_regtool", + "cnc_regtool_f2", + "cnc_relative", + "cnc_relative2", + "cnc_renameprog", + "cnc_reset", + "cnc_reset2", + "cnc_resetconnect", + "cnc_rewind", + "cnc_rmtwavestart", + "cnc_rmtwavestat", + "cnc_rmtwavestop", + "cnc_rstrseqsrch", + "cnc_rstrseqsrch2", + "cnc_sdcancelsmpl", + "cnc_sdclrchnl", + "cnc_sdendsmpl", + "cnc_sdread1shot", + "cnc_sdreadsmpl", + "cnc_sdsetchnl", + "cnc_sdstartsmpl", + "cnc_search", + "cnc_searchresult", + "cnc_searchword", + "cnc_seqsrch", + "cnc_setlibopt", + "cnc_setmactype", + "cnc_setpath", + "cnc_setpmactype", + "cnc_setthrdngpos", + "cnc_settimeout", + "cnc_settimer", + "cnc_setvrtclpos", + "cnc_sfbcancelsmpl", + "cnc_sfbclrchnl", + "cnc_sfbendsmpl", + "cnc_sfbreadsmpl", + "cnc_sfbsetchnl", + "cnc_sfbstartsmpl", + "cnc_skip", + "cnc_slide", + "cnc_srambackup", + "cnc_srambkend", + "cnc_srambkstart", + "cnc_sramget", + "cnc_sramget2", + "cnc_sramgetend", + "cnc_sramgetend2", + "cnc_sramgetstart", + "cnc_sramgetstart2", + "cnc_srvdelay", + "cnc_start", + "cnc_startdrawpos", + "cnc_startdyngrph", + "cnc_startomhis", + "cnc_startophis", + "cnc_statinfo", + "cnc_stopdrawpos", + "cnc_stopdyngrph", + "cnc_stopomhis", + "cnc_stopophis", + "cnc_sysinfo", + "cnc_sysinfo_ex", + "cnc_t1info", + "cnc_t2info", + "cnc_tofs_rnge", + "cnc_toolnum", + "cnc_unsolicstart", + "cnc_unsolicstop", + "cnc_upend", + "cnc_upend3", + "cnc_upend4", + "cnc_upload", + "cnc_upload3", + "cnc_upload4", + "cnc_upstart", + "cnc_upstart3", + "cnc_upstart3_f", + "cnc_upstart4", + "cnc_verify", + "cnc_verify4", + "cnc_vrfend", + "cnc_vrfend4", + "cnc_vrfstart", + "cnc_vrfstart4", + "cnc_wavestart", + "cnc_wavestat", + "cnc_wavestop", + "cnc_wksft_rnge", + "cnc_workzero", + "cnc_wr1tlifedat2", + "cnc_wr1tlifedata", + "cnc_wr2tlifedata", + "cnc_wractpt", + "cnc_wrbtofsr", + "cnc_wrcexefile", + "cnc_wrcexesram", + "cnc_wrcountr", + "cnc_wrfbusmem", + "cnc_wrfixofs", + "cnc_wrgrphcmdptr", + "cnc_wrgrpinfo", + "cnc_wrgrpinfo2", + "cnc_wrgrpinfo3", + "cnc_wrhissgnl", + "cnc_wrhissgnl3", + "cnc_wrintchk", + "cnc_wrkeyhistry", + "cnc_wrlagslt", + "cnc_wrlagst", + "cnc_wrldsplc", + "cnc_wrledgprc", + "cnc_wrlprcprc", + "cnc_wrlpwrcpst", + "cnc_wrlpwrctrl", + "cnc_wrlpwrdty", + "cnc_wrlpwrslt", + "cnc_wrmacro", + "cnc_wrmacror", + "cnc_wrmacror2", + "cnc_wrmagazine", + "cnc_wrmdipntr", + "cnc_wrmdiprog", + "cnc_wrmenuswitch", + "cnc_wrmgrpdata", + "cnc_wrmngtime", + "cnc_wrmsptype", + "cnc_wrmtapdata", + "cnc_wrmultitldt", + "cnc_wropnlgnrl", + "cnc_wropnlgsname", + "cnc_wropnlsgnl", + "cnc_wrparam", + "cnc_wrparas", + "cnc_wrpdf_attr", + "cnc_wrpdf_curdir", + "cnc_wrpdf_line", + "cnc_wrpitchr", + "cnc_wrpm_item", + "cnc_wrpm_mcnitem", + "cnc_wrpmacro", + "cnc_wrpmacror", + "cnc_wrpmacror2", + "cnc_wrpscdedge", + "cnc_wrpscdpirc", + "cnc_wrpscdproc", + "cnc_wrpscdslop", + "cnc_wrrelpos", + "cnc_wrrmtwaveprm", + "cnc_wrsafetyzone", + "cnc_wrsavsigadr", + "cnc_wrset", + "cnc_wrsets", + "cnc_wrsetzone", + "cnc_wrtimer", + "cnc_wrtlctldata", + "cnc_wrtofs", + "cnc_wrtofsr", + "cnc_wrtool", + "cnc_wrtool2", + "cnc_wrtool_f2", + "cnc_wrtooldata", + "cnc_wrtoolzone", + "cnc_wrunsolicprm", + "cnc_wrwaveprm", + "cnc_wrwaveprm2", + "cnc_wrwkcdsfms", + "cnc_wrwkcdshft", + "cnc_wrzofs", + "cnc_wrzofsr", + "cnc_zofs_rnge", + "ds_cancel", + "ds_chghdddir", + "ds_delhddfile", + "ds_rdhdddir", + "ds_rdresult", + "pmc_crdmsg", + "pmc_cwrmsg", + "pmc_get_current_pmc_unit", + "pmc_get_number_of_pmc", + "pmc_get_pmc_unit_types", + "pmc_get_timer_type", + "pmc_getdtailerr", + "pmc_kpmsiz", + "pmc_prfrdallcadr", + "pmc_prfrdbusprm", + "pmc_prfrdconfig", + "pmc_prfrddido", + "pmc_prfrdindiadr", + "pmc_prfrdopmode", + "pmc_prfrdslvaddr", + "pmc_prfrdslvid", + "pmc_prfrdslvprm", + "pmc_prfrdslvprm2", + "pmc_prfrdslvstat", + "pmc_prfwrallcadr", + "pmc_prfwrbusprm", + "pmc_prfwrdido", + "pmc_prfwrindiadr", + "pmc_prfwropmode", + "pmc_prfwrslvaddr", + "pmc_prfwrslvid", + "pmc_prfwrslvprm", + "pmc_prfwrslvprm2", + "pmc_rdalmmsg", + "pmc_rdcntldata", + "pmc_rdcntlgrp", + "pmc_rdkpm", + "pmc_rdkpm2", + "pmc_rdmsg", + "pmc_rdpmcinfo", + "pmc_rdpmcmem", + "pmc_rdpmcparam", + "pmc_rdpmcrng", + "pmc_rdpmcsemem", + "pmc_rdpmctitle", + "pmc_rdprmend", + "pmc_rdprmstart", + "pmc_select_pmc_unit", + "pmc_set_timer_type", + "pmc_wrcntldata", + "pmc_wrcntlgrp", + "pmc_wrkpm", + "pmc_wrkpm2", + "pmc_wrmsg", + "pmc_wrpmcmem", + "pmc_wrpmcparam", + "pmc_wrpmcrng", + "pmc_wrpmcsemem", + "pmc_wrprmend", + "pmc_wrprmstart" + ], + "exports": [ + "?cnc_sramrestore@@YAFGPEAFPEAXPEAJ@Z", + "?cnc_sramrestoreend@@YAFG@Z", + "cnc_3dchk_end", + "cnc_3dchk_getprginfo", + "cnc_3dchk_mchn_stop", + "cnc_3dchk_rddata", + "cnc_3dchk_rddata2", + "cnc_3dchk_rddata3", + "cnc_3dchk_rddata4", + "cnc_3dchk_start", + "cnc_3dchk_start2", + "cnc_absolute", + "cnc_absolute2", + "cnc_absolute2_exdgt", + "cnc_accdecdly", + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_adcnv", + "cnc_alarm", + "cnc_alarm2", + "cnc_allclibhndl3", + "cnc_allclibhndl4", + "cnc_allclibhndls", + "cnc_allowance", + "cnc_allowcnd", + "cnc_atbk_rdtime", + "cnc_axisnum", + "cnc_btlfpotsrh", + "cnc_buff", + "cnc_canmcdfinfo", + "cnc_canmovrlap", + "cnc_cdnc", + "cnc_cdownload", + "cnc_cexedirectory", + "cnc_cexesraminfo", + "cnc_cexesramsize", + "cnc_checkpitch", + "cnc_chglang", + "cnc_chgprotbit", + "cnc_chkmcdfile", + "cnc_chkversion", + "cnc_clearomhis", + "cnc_clearophis", + "cnc_closecexefile", + "cnc_clr3dplsmov", + "cnc_clr5axpls", + "cnc_clralm", + "cnc_clrcntinfo", + "cnc_clrgrphcmd", + "cnc_clrptdata", + "cnc_clrtofs", + "cnc_condense", + "cnc_copyprog", + "cnc_cupload", + "cnc_cverify", + "cnc_delall", + "cnc_delete", + "cnc_deleteo8", + "cnc_delmag_property", + "cnc_delmagazine", + "cnc_delmcdfile", + "cnc_delmcdfilebynum", + "cnc_delpot_property", + "cnc_deltlifedt", + "cnc_deltlifegrp", + "cnc_deltool", + "cnc_diagnosr", + "cnc_diagnoss", + "cnc_dispoptmsg", + "cnc_distance", + "cnc_distance_exdgt", + "cnc_dnc", + "cnc_dnc2", + "cnc_dncend", + "cnc_dncend2", + "cnc_dncstart", + "cnc_dncstart2", + "cnc_download", + "cnc_download3", + "cnc_download4", + "cnc_dschdir", + "cnc_dsftpcancel", + "cnc_dsftpstat", + "cnc_dsget_req", + "cnc_dslistdel_req", + "cnc_dslistget_req", + "cnc_dslistput_req", + "cnc_dsmget_req", + "cnc_dsmkdir", + "cnc_dsmput_req", + "cnc_dsput_req", + "cnc_dsremove", + "cnc_dsrename", + "cnc_dsrmdir", + "cnc_dwnend", + "cnc_dwnend3", + "cnc_dwnend4", + "cnc_dwnstart", + "cnc_dwnstart3", + "cnc_dwnstart3_f", + "cnc_dwnstart4", + "cnc_end_async_data_punch", + "cnc_end_async_data_read", + "cnc_end_async_pdf_punch", + "cnc_end_async_pdf_read", + "cnc_end_async_punch_prog3", + "cnc_end_async_punch_prog3_bg", + "cnc_end_async_read_prog3", + "cnc_end_async_read_prog3_bg", + "cnc_endpossmpl", + "cnc_exaxisname", + "cnc_exaxisname2", + "cnc_exeprgname", + "cnc_exeprgname2", + "cnc_fileread", + "cnc_fileread_end", + "cnc_fileread_start", + "cnc_filewrite", + "cnc_filewrite_end", + "cnc_filewrite_start", + "cnc_freelibhndl", + "cnc_fromdelete", + "cnc_fromget", + "cnc_fromget2", + "cnc_fromgetend", + "cnc_fromgetend2", + "cnc_fromgetstart", + "cnc_fromgetstart2", + "cnc_fromldend", + "cnc_fromldstart", + "cnc_fromload", + "cnc_fromput", + "cnc_fromput2", + "cnc_fromputend", + "cnc_fromputend2", + "cnc_fromputstart", + "cnc_fromputstart2", + "cnc_fromremove", + "cnc_fromsave", + "cnc_fromsvend", + "cnc_fromsvstart", + "cnc_fssb_autoset", + "cnc_fssb_reset", + "cnc_ftosjis", + "cnc_get_crosschk_alarm", + "cnc_get_flowmonitor", + "cnc_get_mccteststs", + "cnc_get_safetysts", + "cnc_get_safetysts2", + "cnc_getcncmodel", + "cnc_getdtailerr", + "cnc_getfigure", + "cnc_getfocas1opt", + "cnc_getfrominfo", + "cnc_getlanguage", + "cnc_getlibopt", + "cnc_getlockstat", + "cnc_getmacaddress", + "cnc_getmactype", + "cnc_getpath", + "cnc_getpmactype", + "cnc_getrtmrvar", + "cnc_getrtmrvars", + "cnc_getsraminfo", + "cnc_getsysfolder_num", + "cnc_gettimer", + "cnc_hpccactfine", + "cnc_hpccatset", + "cnc_hpccattune", + "cnc_hpccselfine", + "cnc_instlifedt", + "cnc_loadtorq", + "cnc_machine", + "cnc_machine2", + "cnc_machine3", + "cnc_machine_exdgt", + "cnc_modal", + "cnc_msr_delhis_all", + "cnc_msr_rdhis_allnum", + "cnc_msr_rdhis_inf", + "cnc_msr_rdhis_msudat", + "cnc_msr_rdhis_ncdat", + "cnc_msr_rdhis_ohisnum", + "cnc_msr_rdhis_ohisrec", + "cnc_msr_rdhis_pmc", + "cnc_msr_rdmon_msudat", + "cnc_msr_rdmon_msunum", + "cnc_msr_rdmon_pmcinf", + "cnc_msr_start_sample", + "cnc_msr_stop_sample", + "cnc_opencexefile", + "cnc_opentdicubeinfo", + "cnc_optmsgans", + "cnc_pdf_add", + "cnc_pdf_cond", + "cnc_pdf_copy", + "cnc_pdf_del", + "cnc_pdf_delall", + "cnc_pdf_delline", + "cnc_pdf_move", + "cnc_pdf_rdactpt", + "cnc_pdf_rdmain", + "cnc_pdf_rename", + "cnc_pdf_searchresult", + "cnc_pdf_searchword", + "cnc_pdf_slctmain", + "cnc_pdf_wractpt", + "cnc_pmclad_screen", + "cnc_prot_pswcan", + "cnc_prot_pswchg", + "cnc_prot_pswinit", + "cnc_prot_pswinp", + "cnc_prstwkcd", + "cnc_ptdownload", + "cnc_ptdwnend", + "cnc_ptdwnstart", + "cnc_ptlink", + "cnc_ptlink2", + "cnc_punch_data", + "cnc_punch_prog", + "cnc_punch_prog2", + "cnc_pwoff_alarm", + "cnc_rd1length", + "cnc_rd1punchtl_ex", + "cnc_rd1radius", + "cnc_rd1tlifedat2", + "cnc_rd1tlifedata", + "cnc_rd2length", + "cnc_rd2punchtl_ex", + "cnc_rd2radius", + "cnc_rd2tlifedata", + "cnc_rd3dcdcnv", + "cnc_rd3dmovrlap", + "cnc_rd3dofschg", + "cnc_rd3dpulse", + "cnc_rd3dtofs", + "cnc_rd3dtooltip", + "cnc_rd5axmandt", + "cnc_rd5axovrlap", + "cnc_rdabsaxis", + "cnc_rdactfixofs", + "cnc_rdactpt", + "cnc_rdacttlzone", + "cnc_rdalmhisno", + "cnc_rdalmhisno3", + "cnc_rdalmhistry", + "cnc_rdalmhistry2", + "cnc_rdalmhistry4", + "cnc_rdalmhistry5", + "cnc_rdalminfo", + "cnc_rdalmmsg", + "cnc_rdalmmsg2", + "cnc_rdaxisdata", + "cnc_rdaxisname", + "cnc_rdbaxis", + "cnc_rdblkcount", + "cnc_rdbrstrinfo", + "cnc_rdbtofsinfo", + "cnc_rdbtofsr", + "cnc_rdcbmem", + "cnc_rdcbmem2", + "cnc_rdcbprm", + "cnc_rdcdrotate", + "cnc_rdcenblinfo", + "cnc_rdcenter", + "cnc_rdcexefile", + "cnc_rdcexesram", + "cnc_rdcncid", + "cnc_rdcncmem", + "cnc_rdcommand", + "cnc_rdcoordnum", + "cnc_rdcount", + "cnc_rdctname", + "cnc_rdctrldi", + "cnc_rdctrldo", + "cnc_rdcurrent", + "cnc_rddiag_ext", + "cnc_rddiaginfo", + "cnc_rddiaginfo2", + "cnc_rddiagnum", + "cnc_rddischarge", + "cnc_rddischrgalm", + "cnc_rddncdgndt", + "cnc_rddncdgndt2", + "cnc_rddsdevinfo", + "cnc_rddsdir", + "cnc_rddsdncfile", + "cnc_rddsfile", + "cnc_rddynamic", + "cnc_rddynamic2", + "cnc_rddynamico8", + "cnc_rdecamdatar", + "cnc_rdedgeactive", + "cnc_rdedgedata", + "cnc_rdedmcram", + "cnc_rdenblinfo", + "cnc_rdetherinfo", + "cnc_rdexchgtgrp", + "cnc_rdexecmcode", + "cnc_rdexecprog", + "cnc_rdexecprog2", + "cnc_rdfbusmem", + "cnc_rdfile_sram", + "cnc_rdfixcycle", + "cnc_rdfixofs", + "cnc_rdflnetsram", + "cnc_rdfrominfo", + "cnc_rdfromservoid", + "cnc_rdfromservoid2", + "cnc_rdfromspindleid", + "cnc_rdfromspindleid2", + "cnc_rdfsraminfo", + "cnc_rdfssb_amp", + "cnc_rdfssb_axis", + "cnc_rdfssb_info", + "cnc_rdfssb_mainte", + "cnc_rdfssb_plsmod", + "cnc_rdgcode", + "cnc_rdgrphcanflg", + "cnc_rdgrphcmd", + "cnc_rdgrpid", + "cnc_rdgrpid2", + "cnc_rdgrpinfo", + "cnc_rdgrpinfo2", + "cnc_rdgrpinfo3", + "cnc_rdgrpinfo4", + "cnc_rdhdnxt", + "cnc_rdhissgnl", + "cnc_rdhissgnl3", + "cnc_rdhndintrpt", + "cnc_rdholmes", + "cnc_rdintchk", + "cnc_rdinterference", + "cnc_rdintinfo", + "cnc_rdjogdrun", + "cnc_rdjogmdi", + "cnc_rdlactnum", + "cnc_rdlagingmode", + "cnc_rdlagingtime", + "cnc_rdlagslt", + "cnc_rdlagst", + "cnc_rdlcmddat", + "cnc_rdlcmmt", + "cnc_rdldsplc", + "cnc_rdldsplc2", + "cnc_rdledgprc", + "cnc_rdlenofs", + "cnc_rdlerrz", + "cnc_rdlfiberdata", + "cnc_rdlhsstate", + "cnc_rdlife", + "cnc_rdlnopmsg", + "cnc_rdlnzlmcn", + "cnc_rdloopgain", + "cnc_rdlpoweroffset", + "cnc_rdlppfbdt", + "cnc_rdlprcprc", + "cnc_rdlpscdpwrctl", + "cnc_rdlpwrcpst", + "cnc_rdlpwrctrl", + "cnc_rdlpwrdat", + "cnc_rdlpwrdty", + "cnc_rdlpwrslt", + "cnc_rdmacro", + "cnc_rdmacroinfo", + "cnc_rdmacronum", + "cnc_rdmacror", + "cnc_rdmacror2", + "cnc_rdmacror3", + "cnc_rdmag_property", + "cnc_rdmagazine", + "cnc_rdmaxgrp", + "cnc_rdmaxtool", + "cnc_rdmcdfinfo", + "cnc_rdmcdprgcmnt", + "cnc_rdmdipntr", + "cnc_rdmdipntro8", + "cnc_rdmdiprgstat", + "cnc_rdmdiprog", + "cnc_rdmdlconfig2", + "cnc_rdmenuswitch", + "cnc_rdmgrpdata", + "cnc_rdmirimage", + "cnc_rdmngtime", + "cnc_rdmovrlap", + "cnc_rdmsptype", + "cnc_rdmtapdata", + "cnc_rdmultipieceno", + "cnc_rdmultitldt", + "cnc_rdngrp", + "cnc_rdnspdl", + "cnc_rdntool", + "cnc_rdomhisinfo", + "cnc_rdomhisno", + "cnc_rdomhistry", + "cnc_rdomhistry2", + "cnc_rdope_lvl", + "cnc_rdophisno", + "cnc_rdophisno3", + "cnc_rdophistry", + "cnc_rdophistry2", + "cnc_rdophistry4", + "cnc_rdopmode", + "cnc_rdopmsg", + "cnc_rdopmsg2", + "cnc_rdopmsg3", + "cnc_rdopnlgnrl", + "cnc_rdopnlgnrl2", + "cnc_rdopnlgsname", + "cnc_rdopnlgsname2", + "cnc_rdopnlsgnl", + "cnc_rdparainfo", + "cnc_rdparainfo2", + "cnc_rdparainfo3", + "cnc_rdparam", + "cnc_rdparam3", + "cnc_rdparam_ext", + "cnc_rdparanum", + "cnc_rdparar", + "cnc_rdpdf_alldir", + "cnc_rdpdf_curdir", + "cnc_rdpdf_drive", + "cnc_rdpdf_inf", + "cnc_rdpdf_line", + "cnc_rdpdf_line2", + "cnc_rdpdf_pglockstat", + "cnc_rdpdf_prginf", + "cnc_rdpdf_subdir", + "cnc_rdpdf_subdirn", + "cnc_rdpitchinfo", + "cnc_rdpitchr", + "cnc_rdpitchr2", + "cnc_rdpm_cncitem", + "cnc_rdpm_item", + "cnc_rdpm_mcnitem", + "cnc_rdpmacro", + "cnc_rdpmacroinfo", + "cnc_rdpmacroinfo2", + "cnc_rdpmacror", + "cnc_rdpmacror2", + "cnc_rdposerrs", + "cnc_rdposerrs2", + "cnc_rdposerrz", + "cnc_rdposition", + "cnc_rdpossmpl", + "cnc_rdpot_property", + "cnc_rdprgdirtime", + "cnc_rdprgnum", + "cnc_rdprgnumo8", + "cnc_rdproctime", + "cnc_rdproctime2", + "cnc_rdprogdir", + "cnc_rdprogdir2", + "cnc_rdprogdir2o8", + "cnc_rdprogdir3", + "cnc_rdprogdiro8", + "cnc_rdproginfo", + "cnc_rdprotect", + "cnc_rdprstrinfo", + "cnc_rdprt_data", + "cnc_rdprt_lvl", + "cnc_rdpscdedge", + "cnc_rdpscdedge2", + "cnc_rdpscdpirc", + "cnc_rdpscdproc", + "cnc_rdpscdproc2", + "cnc_rdpscdslop", + "cnc_rdptaxfunctablestatus", + "cnc_rdptaxitablestatus", + "cnc_rdptcnvalm", + "cnc_rdptcnvinfo", + "cnc_rdptcnvinfo2", + "cnc_rdptcomment", + "cnc_rdptexedistalm", + "cnc_rdptsptablestatus", + "cnc_rdptstoptime", + "cnc_rdpwofsthis", + "cnc_rdradofs", + "cnc_rdrelaxis", + "cnc_rdrepeatval", + "cnc_rdrmtwavedt", + "cnc_rdrmtwaveprm", + "cnc_rdrotvolc", + "cnc_rdrstrmcode", + "cnc_rdrtmrvar", + "cnc_rdrtmrvars", + "cnc_rdsafetyzone", + "cnc_rdsavsigadr", + "cnc_rdsavsigdata", + "cnc_rdscaling", + "cnc_rdseqnum", + "cnc_rdservoid", + "cnc_rdservoid2", + "cnc_rdset", + "cnc_rdsetinfo", + "cnc_rdsetinfo2", + "cnc_rdsetnum", + "cnc_rdsetr", + "cnc_rdsetzone", + "cnc_rdspcss", + "cnc_rdspdlalm", + "cnc_rdspdlname", + "cnc_rdspeed", + "cnc_rdspgear", + "cnc_rdspindleid", + "cnc_rdspindleid2", + "cnc_rdspload", + "cnc_rdspmaxrpm", + "cnc_rdspmeter", + "cnc_rdsramaddr", + "cnc_rdsraminfo", + "cnc_rdsramnum", + "cnc_rdsrvspeed", + "cnc_rdsvmeter", + "cnc_rdsynerrrg", + "cnc_rdsynerrsy", + "cnc_rdsyshard", + "cnc_rdsyshard_str", + "cnc_rdsyssoft", + "cnc_rdsyssoft2", + "cnc_rdsyssoft3", + "cnc_rdsyssoft3_str", + "cnc_rdtdicrntshapeinf", + "cnc_rdtdicurrentshape", + "cnc_rdtdidispsetting", + "cnc_rdtdieffectshape", + "cnc_rdtdifignum", + "cnc_rdtdifiguredata", + "cnc_rdtdiinfo", + "cnc_rdtdiinitview", + "cnc_rdtdimoveaxis", + "cnc_rdtdinamesetting", + "cnc_rdtdiseltool", + "cnc_rdtdishapedata", + "cnc_rdtimer", + "cnc_rdtlctldata", + "cnc_rdtldata", + "cnc_rdtlgeomsize_ext", + "cnc_rdtlgrp", + "cnc_rdtlinfo", + "cnc_rdtlmsinfo", + "cnc_rdtlname", + "cnc_rdtltool", + "cnc_rdtlusegrp", + "cnc_rdtofs", + "cnc_rdtofsenbl", + "cnc_rdtofsinfo", + "cnc_rdtofsinfo2", + "cnc_rdtofsr", + "cnc_rdtool", + "cnc_rdtool_f2", + "cnc_rdtooldata", + "cnc_rdtoolgeom_tlm", + "cnc_rdtoolgrp", + "cnc_rdtoolinfo", + "cnc_rdtoolrng", + "cnc_rdtoolzone", + "cnc_rdtrqmonitor", + "cnc_rdunsolicmsg", + "cnc_rdunsolicmsg2", + "cnc_rdunsolicprm", + "cnc_rdunsolicprm2", + "cnc_rdusegrpid", + "cnc_rdusetlno", + "cnc_rduvactpt", + "cnc_rduvactpt2", + "cnc_rdvolc", + "cnc_rdvolccomp", + "cnc_rdwavedata", + "cnc_rdwavedata3", + "cnc_rdwaveprm", + "cnc_rdwaveprm2", + "cnc_rdwaveprm3", + "cnc_rdwkcdsfms", + "cnc_rdwkcdshft", + "cnc_rdwseterror", + "cnc_rdzofs", + "cnc_rdzofsinfo", + "cnc_rdzofsmes", + "cnc_rdzofsr", + "cnc_read_data", + "cnc_read_prog", + "cnc_read_prog2", + "cnc_reg_toolstrage", + "cnc_regmagazine", + "cnc_regtool", + "cnc_regtool_f2", + "cnc_relative", + "cnc_relative2", + "cnc_relative2_exdgt", + "cnc_renameprog", + "cnc_reset", + "cnc_reset2", + "cnc_reset_prps", + "cnc_resetconnect", + "cnc_resetpdf_pglock", + "cnc_rewind", + "cnc_rmtwavestart", + "cnc_rmtwavestat", + "cnc_rmtwavestop", + "cnc_robo_clrsignals", + "cnc_robo_wralmmsg", + "cnc_robo_wrcomsetting", + "cnc_robo_wrsignalname", + "cnc_robo_wrsignals2", + "cnc_rstrseqsrch", + "cnc_rstrseqsrch2", + "cnc_saveprog_end", + "cnc_saveprog_start", + "cnc_sdbetainfo", + "cnc_sdcancelsmpl", + "cnc_sdclrchnl", + "cnc_sdclrocp", + "cnc_sdendocp", + "cnc_sdendsmpl", + "cnc_sdendsmpl2", + "cnc_sdfmnghwnd", + "cnc_sdfstatchg", + "cnc_sdread1shot", + "cnc_sdreadsmpl", + "cnc_sdreadsmpl2", + "cnc_sdsetchnl", + "cnc_sdsetchnl2", + "cnc_sdstartocp", + "cnc_sdstartsmpl", + "cnc_sdstartsmpl2", + "cnc_sdstartsmplb", + "cnc_sdtcancelsmpl", + "cnc_sdtclrchnl", + "cnc_sdtclrocp", + "cnc_sdtendocp", + "cnc_sdtendsmpl", + "cnc_sdtendsmpl2", + "cnc_sdtread1shot", + "cnc_sdtreadsmpl", + "cnc_sdtreadsmpl2", + "cnc_sdtsetchnl", + "cnc_sdtsetchnl2", + "cnc_sdtstartocp", + "cnc_sdtstartsmpl", + "cnc_sdtstartsmpl2", + "cnc_search", + "cnc_searcho8", + "cnc_searchresult", + "cnc_searchword", + "cnc_seqrdtdicubeinfo", + "cnc_seqsrch", + "cnc_set_prps", + "cnc_setcurscrn", + "cnc_setfrp", + "cnc_setlibopt", + "cnc_setmactype", + "cnc_setpath", + "cnc_setpdf_pglock", + "cnc_setpmactype", + "cnc_settdiobjectshape", + "cnc_settditoolshape", + "cnc_setthrdngpos", + "cnc_settimeout", + "cnc_settimer", + "cnc_setvrtclpos", + "cnc_sfbcancelsmpl", + "cnc_sfbclrchnl", + "cnc_sfbendsmpl", + "cnc_sfbreadsmpl", + "cnc_sfbsetchnl", + "cnc_sfbstartsmpl", + "cnc_simulation", + "cnc_skip", + "cnc_slctptdata", + "cnc_slctpttype", + "cnc_slide", + "cnc_srambackup", + "cnc_srambkend", + "cnc_srambkstart", + "cnc_sramget", + "cnc_sramget2", + "cnc_sramgetend", + "cnc_sramgetend2", + "cnc_sramgetstart", + "cnc_sramgetstart2", + "cnc_sramput", + "cnc_sramput2", + "cnc_sramputend", + "cnc_sramputend2", + "cnc_sramputstart", + "cnc_sramputstart2", + "cnc_sramstat", + "cnc_sramstatus", + "cnc_srvdelay", + "cnc_start", + "cnc_start_async_data_punch", + "cnc_start_async_data_read", + "cnc_start_async_pdf_punch", + "cnc_start_async_pdf_read", + "cnc_start_async_punch_prog3", + "cnc_start_async_punch_prog3_bg", + "cnc_start_async_read_prog3", + "cnc_start_async_read_prog3_bg", + "cnc_startdrawpos", + "cnc_startdyngrph", + "cnc_startomhis", + "cnc_startophis", + "cnc_startptcnv", + "cnc_statinfo", + "cnc_statinfo2", + "cnc_status_prps", + "cnc_stopdrawpos", + "cnc_stopdyngrph", + "cnc_stopomhis", + "cnc_stopophis", + "cnc_stpossmpl", + "cnc_sysinfo", + "cnc_sysinfo_ex", + "cnc_t1info", + "cnc_t2info", + "cnc_tofs_rnge", + "cnc_tool_move", + "cnc_toolnum", + "cnc_twp_rdfcoord", + "cnc_unsolicstart", + "cnc_unsolicstop", + "cnc_upend", + "cnc_upend3", + "cnc_upend4", + "cnc_upload", + "cnc_upload3", + "cnc_upload4", + "cnc_upstart", + "cnc_upstart3", + "cnc_upstart3_f", + "cnc_upstart4", + "cnc_upstarto8", + "cnc_validate_opt", + "cnc_verify", + "cnc_verify4", + "cnc_vrfend", + "cnc_vrfend4", + "cnc_vrfstart", + "cnc_vrfstart4", + "cnc_wavestart", + "cnc_wavestat", + "cnc_wavestop", + "cnc_wksft_rnge", + "cnc_workzero", + "cnc_wr1tlifedat2", + "cnc_wr1tlifedata", + "cnc_wr2tlifedata", + "cnc_wrabspos", + "cnc_wractpt", + "cnc_wrbtofsr", + "cnc_wrcbmem", + "cnc_wrcbmem2", + "cnc_wrcbprm", + "cnc_wrcexefile", + "cnc_wrcexesram", + "cnc_wrcncmem", + "cnc_wrcountr", + "cnc_wrdsdncfile", + "cnc_wrecamdatar", + "cnc_wredgedata2", + "cnc_wredmcram", + "cnc_wrfbusmem", + "cnc_wrfile_sram", + "cnc_wrfixofs", + "cnc_wrflnetsram", + "cnc_wrfssb_axis", + "cnc_wrfssb_axis_num", + "cnc_wrgrphcmdptr", + "cnc_wrgrpinfo", + "cnc_wrgrpinfo2", + "cnc_wrgrpinfo3", + "cnc_wrhissgnl", + "cnc_wrhissgnl3", + "cnc_wrintchk", + "cnc_wrjogmdi", + "cnc_wrjogmdiclr", + "cnc_wrkeyhistry", + "cnc_wrlagingmode", + "cnc_wrlagslt", + "cnc_wrlagst", + "cnc_wrldsplc", + "cnc_wrldsplc2", + "cnc_wrledgprc", + "cnc_wrlppfbdt", + "cnc_wrlprcprc", + "cnc_wrlpscdpwrctl", + "cnc_wrlpwrcpst", + "cnc_wrlpwrctrl", + "cnc_wrlpwrdty", + "cnc_wrlpwrslt", + "cnc_wrmacro", + "cnc_wrmacror", + "cnc_wrmacror2", + "cnc_wrmag_property", + "cnc_wrmagazine", + "cnc_wrmdipntr", + "cnc_wrmdiprog", + "cnc_wrmenuswitch", + "cnc_wrmgrpdata", + "cnc_wrmngtime", + "cnc_wrmsptype", + "cnc_wrmtapdata", + "cnc_wrmultitldt", + "cnc_wropnlgnrl", + "cnc_wropnlgnrl2", + "cnc_wropnlgsname", + "cnc_wropnlgsname2", + "cnc_wropnlsgnl", + "cnc_wrparam", + "cnc_wrparam3", + "cnc_wrparas", + "cnc_wrpdf_attr", + "cnc_wrpdf_curdir", + "cnc_wrpdf_line", + "cnc_wrpitchr", + "cnc_wrpitchr2", + "cnc_wrpm_item", + "cnc_wrpm_mcnitem", + "cnc_wrpmacro", + "cnc_wrpmacror", + "cnc_wrpmacror2", + "cnc_wrpot_property", + "cnc_wrprt_lvl", + "cnc_wrpscdedge", + "cnc_wrpscdedge2", + "cnc_wrpscdpirc", + "cnc_wrpscdproc", + "cnc_wrpscdproc2", + "cnc_wrpscdslop", + "cnc_wrptstoptime", + "cnc_wrpunchtl_ex", + "cnc_wrrelpos", + "cnc_wrrmtwaveprm", + "cnc_wrrotvolc", + "cnc_wrrtmrvar", + "cnc_wrrtmrvars", + "cnc_wrsafetyzone", + "cnc_wrsavsigadr", + "cnc_wrset", + "cnc_wrsets", + "cnc_wrsetzone", + "cnc_wrtdidispsetting", + "cnc_wrtdieffectshape", + "cnc_wrtdifignum", + "cnc_wrtdifiguredata", + "cnc_wrtdiinitview", + "cnc_wrtdimoveaxis", + "cnc_wrtdinamesetting", + "cnc_wrtdishapedata", + "cnc_wrtimer", + "cnc_wrtlctldata", + "cnc_wrtldata", + "cnc_wrtlgeomsize_ext", + "cnc_wrtofs", + "cnc_wrtofsdrctinp", + "cnc_wrtofsms", + "cnc_wrtofsr", + "cnc_wrtool", + "cnc_wrtool2", + "cnc_wrtool_f2", + "cnc_wrtooldata", + "cnc_wrtoolgeom_tlm", + "cnc_wrtoolzone", + "cnc_wrunsolicprm", + "cnc_wrunsolicprm2", + "cnc_wrvolc", + "cnc_wrwaveprm", + "cnc_wrwaveprm2", + "cnc_wrwaveprm3", + "cnc_wrwkcdsfms", + "cnc_wrwkcdshft", + "cnc_wrwseterror", + "cnc_wrzofs", + "cnc_wrzofsr", + "cnc_zofs_rnge", + "ds_cancel", + "ds_chghdddir", + "ds_delhddfile", + "ds_download", + "ds_dwnend", + "ds_dwnstart", + "ds_rdhdddir", + "ds_rdresult", + "eth_rddsm198dir", + "eth_rddsm198host", + "eth_rddsmode", + "eth_rdhost", + "eth_rdlog", + "eth_rdparam", + "eth_wrdsm198dir", + "eth_wrdsm198host", + "eth_wrhost", + "eth_wrparam", + "flnt_clrlog", + "flnt_clrmsg", + "flnt_clrnetwork", + "flnt_rdentry", + "flnt_rdlog", + "flnt_rdlog2", + "flnt_rdmsg", + "flnt_rdnetwork", + "flnt_rdnetwork2", + "flnt_rdnodeinfo", + "flnt_rdnodeinfo2", + "flnt_rdparam", + "flnt_rdparam2", + "flnt_wrparam", + "pbm_chg_mode", + "pbm_rd_param", + "pbm_wr_param", + "pmc_check_permission", + "pmc_convert_address_pmc_to_vio", + "pmc_convert_from_string_to_address", + "pmc_convert_pmc_address_from_sync", + "pmc_convert_pmc_address_to_string", + "pmc_convert_pmc_address_to_sync", + "pmc_convert_vio_address_from_sync", + "pmc_convert_vio_address_to_sync", + "pmc_convret_address_vio_to_pmc", + "pmc_crdmsg", + "pmc_cwrmsg", + "pmc_get_address_entry", + "pmc_get_alarm_status", + "pmc_get_current_divided_ladder", + "pmc_get_current_pmc", + "pmc_get_current_pmc_unit", + "pmc_get_divided_ladders", + "pmc_get_language", + "pmc_get_number_of_address_entries", + "pmc_get_number_of_ladder", + "pmc_get_number_of_pmc", + "pmc_get_pmc_unit_types", + "pmc_get_system_language", + "pmc_get_timer_type", + "pmc_get_unit_type", + "pmc_getdtailerr", + "pmc_is_bit_accessible_pmc_address", + "pmc_is_effective_pmc_address", + "pmc_is_visible_pmc_address", + "pmc_is_writable_pmc_address", + "pmc_kpmsiz", + "pmc_logical_and_pmc_address_uint8", + "pmc_logical_and_vio_address_uint8", + "pmc_logical_or_pmc_address_uint8", + "pmc_logical_or_vio_address_uint8", + "pmc_logical_xor_pmc_address_uint8", + "pmc_logical_xor_vio_address_uint8", + "pmc_parse_string_into_pmc_address", + "pmc_prfrdallcadr", + "pmc_prfrdbusprm", + "pmc_prfrdconfig", + "pmc_prfrddido", + "pmc_prfrdindiadr", + "pmc_prfrdinfo", + "pmc_prfrdopmode", + "pmc_prfrdslvaddr", + "pmc_prfrdslvid", + "pmc_prfrdslvprm", + "pmc_prfrdslvprm2", + "pmc_prfrdslvstat", + "pmc_prfwrallcadr", + "pmc_prfwrbusprm", + "pmc_prfwrdido", + "pmc_prfwrindiadr", + "pmc_prfwropmode", + "pmc_prfwrslvaddr", + "pmc_prfwrslvid", + "pmc_prfwrslvprm", + "pmc_prfwrslvprm2", + "pmc_rdalmmsg", + "pmc_rdcntl_exrelay_grp", + "pmc_rdcntldata", + "pmc_rdcntlexrelay", + "pmc_rdcntlgrp", + "pmc_rdioconfigtitle", + "pmc_rdkpm", + "pmc_rdkpm2", + "pmc_rdmessagetitle", + "pmc_rdmsg", + "pmc_rdpmcinfo", + "pmc_rdpmcmem", + "pmc_rdpmcparam", + "pmc_rdpmcrng", + "pmc_rdpmcsemem", + "pmc_rdpmctitle", + "pmc_rdpmctitle2", + "pmc_rdprmend", + "pmc_rdprmstart", + "pmc_rdwrpmcrng", + "pmc_read_cnc_dido", + "pmc_read_pmc_address_bit", + "pmc_read_pmc_address_uint16", + "pmc_read_pmc_address_uint32", + "pmc_read_pmc_address_uint8", + "pmc_read_pmc_software_version", + "pmc_read_seq_program_and_memory_type", + "pmc_read_seq_program_status", + "pmc_read_vio_address_bit", + "pmc_read_vio_address_uint16", + "pmc_read_vio_address_uint32", + "pmc_read_vio_address_uint8", + "pmc_search_unit_type", + "pmc_select_divided_ladder", + "pmc_select_pmc", + "pmc_select_pmc_unit", + "pmc_set_language", + "pmc_set_timer_type", + "pmc_wrcntl_exrelay_grp", + "pmc_wrcntldata", + "pmc_wrcntlexrelay", + "pmc_wrcntlgrp", + "pmc_write_cnc_dido", + "pmc_write_pmc_address_bit", + "pmc_write_pmc_address_uint16", + "pmc_write_pmc_address_uint32", + "pmc_write_pmc_address_uint8", + "pmc_write_vio_address_bit", + "pmc_write_vio_address_uint16", + "pmc_write_vio_address_uint32", + "pmc_write_vio_address_uint8", + "pmc_wrkpm", + "pmc_wrkpm2", + "pmc_wrmsg", + "pmc_wrpmcmem", + "pmc_wrpmcparam", + "pmc_wrpmcrng", + "pmc_wrpmcrng2", + "pmc_wrpmcsemem", + "pmc_wrprmend", + "pmc_wrprmstart" + ], + "notes": [ + "Exports extracted directly from the 64-bit DLL PE export directory.", + "Wrapper-supported methods are intersected with upstream fwlib.cs extern declarations.", + "Axis and path hints are filename-family heuristics, not protocol-level proofs." + ] +} \ No newline at end of file diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/cli.py b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/cli.py new file mode 100644 index 0000000..930685e --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/cli.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +import argparse +import asyncio +import json +from pathlib import Path + +from .data_store import MockDataStore +from .export_introspection import write_profiles +from .profiles import list_profiles, load_profile +from .server import FocasMockServer + + +def _default_root() -> Path: + return Path(__file__).resolve().parents[2] + + +def build_parser() -> argparse.ArgumentParser: + root = _default_root() + parser = argparse.ArgumentParser(prog="focas-mock") + sub = parser.add_subparsers(dest="command", required=True) + + serve = sub.add_parser("serve", help="Start the mock server.") + serve.add_argument("--host", default="127.0.0.1") + serve.add_argument("--port", type=int, default=8193) + serve.add_argument("--profile", default="fwlib30i64") + serve.add_argument("--data", help="Optional JSON patch file.") + + sub.add_parser("list-profiles", help="List built-in profiles.") + + dump_profile = sub.add_parser("dump-profile", help="Print one built-in profile as JSON.") + dump_profile.add_argument("profile") + + extract = sub.add_parser("extract-profiles", help="Regenerate JSON profiles from vendored DLLs.") + extract.add_argument("--dll-dir", default=str(root / "vendor" / "fanuc-cnc-api" / "64bit")) + extract.add_argument("--fwlib", default=str(root / "upstream" / "fwlib.cs")) + extract.add_argument("--out-dir", default=str(root / "src" / "focas_mock" / "builtin_profiles")) + return parser + + +async def _run_server(args: argparse.Namespace) -> None: + profile = load_profile(args.profile) + store = MockDataStore(profile) + if args.data: + store.load_patch_file(args.data) + server = FocasMockServer(args.host, args.port, profile, store) + await server.start() + print(f"focas-mock listening on {server.host}:{server.port} with profile {profile['profile_name']}") + try: + await server.serve_forever() + finally: + await server.close() + + +def main(argv: list[str] | None = None) -> None: + parser = build_parser() + args = parser.parse_args(argv) + + if args.command == "list-profiles": + for profile in list_profiles(): + print(profile) + return + + if args.command == "dump-profile": + print(json.dumps(load_profile(args.profile), indent=2)) + return + + if args.command == "extract-profiles": + written = write_profiles(args.dll_dir, args.fwlib, args.out_dir) + for path in written: + print(path) + return + + if args.command == "serve": + asyncio.run(_run_server(args)) + return + + +if __name__ == "__main__": + main() diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/constants.py b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/constants.py new file mode 100644 index 0000000..e7785bf --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/constants.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +EW_PROTOCOL = -17 +EW_SOCKET = -16 +EW_NODLL = -15 +EW_BUS = -11 +EW_SYSTEM2 = -10 +EW_HSSB = -9 +EW_HANDLE = -8 +EW_VERSION = -7 +EW_UNEXP = -6 +EW_SYSTEM = -5 +EW_PARITY = -4 +EW_MMCSYS = -3 +EW_RESET = -2 +EW_BUSY = -1 +EW_OK = 0 +EW_FUNC = 1 +EW_LENGTH = 2 +EW_NUMBER = 3 +EW_ATTRIB = 4 +EW_DATA = 5 +EW_NOOPT = 6 +EW_PROT = 7 +EW_OVRFLOW = 8 +EW_PARAM = 9 +EW_BUFFER = 10 +EW_PATH = 11 +EW_MODE = 12 +EW_REJECT = 13 +EW_DTSRVR = 14 +EW_ALARM = 15 +EW_STOP = 16 +EW_PASSWD = 17 + +RC_LABELS = { + EW_PROTOCOL: "EW_PROTOCOL", + EW_SOCKET: "EW_SOCKET", + EW_NODLL: "EW_NODLL", + EW_BUS: "EW_BUS", + EW_SYSTEM2: "EW_SYSTEM2", + EW_HSSB: "EW_HSSB", + EW_HANDLE: "EW_HANDLE", + EW_VERSION: "EW_VERSION", + EW_UNEXP: "EW_UNEXP", + EW_SYSTEM: "EW_SYSTEM", + EW_PARITY: "EW_PARITY", + EW_MMCSYS: "EW_MMCSYS", + EW_RESET: "EW_RESET", + EW_BUSY: "EW_BUSY", + EW_OK: "EW_OK", + EW_FUNC: "EW_FUNC", + EW_LENGTH: "EW_LENGTH", + EW_NUMBER: "EW_NUMBER", + EW_ATTRIB: "EW_ATTRIB", + EW_DATA: "EW_DATA", + EW_NOOPT: "EW_NOOPT", + EW_PROT: "EW_PROT", + EW_OVRFLOW: "EW_OVRFLOW", + EW_PARAM: "EW_PARAM", + EW_BUFFER: "EW_BUFFER", + EW_PATH: "EW_PATH", + EW_MODE: "EW_MODE", + EW_REJECT: "EW_REJECT", + EW_DTSRVR: "EW_DTSRVR", + EW_ALARM: "EW_ALARM", + EW_STOP: "EW_STOP", + EW_PASSWD: "EW_PASSWD", +} + +IMPLEMENTED_FOCAS_METHODS = [ + "cnc_allclibhndl", + "cnc_allclibhndl2", + "cnc_allclibhndl3", + "cnc_freelibhndl", + "cnc_sysinfo", + "cnc_statinfo", + "cnc_rddynamic2", + "cnc_actf", + "cnc_acts", + "cnc_acts2", + "cnc_getpath", + "cnc_setpath", + "cnc_rdaxisname", + "cnc_rdspdlname", + "cnc_rdparam", + "cnc_wrparam", + "cnc_rdmacro", + "cnc_wrmacro", + "cnc_rdalmmsg2", + "pmc_rdpmcrng", + "pmc_wrpmcrng", + "cnc_rdopmsg", + "cnc_rdopmode", + "cnc_rdprgnum", + "cnc_exeprgname2", + "cnc_rdexecprog", + "cnc_rdseqnum", + "cnc_rdblkcount", + "cnc_rdproginfo", + "cnc_rdprogdir3", + "cnc_rdtimer", + "cnc_rdspmeter", + "cnc_rdsvmeter", + "cnc_rdspload", + "cnc_rdspgear", + "cnc_rdspmaxrpm", + "cnc_rddiagnum", + "cnc_rddiaginfo", + "cnc_diagnoss", +] + +ADMIN_METHODS = [ + "mock_get_state", + "mock_patch", + "mock_reset", + "mock_load_profile", + "mock_list_methods", + "mock_schedule_alarms", +] diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/data_store.py b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/data_store.py new file mode 100644 index 0000000..7be3c8c --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/data_store.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +import json +from copy import deepcopy +from pathlib import Path +from typing import Any, Mapping + +from .defaults import make_default_state + + +def _deep_merge(target: dict[str, Any], patch: Mapping[str, Any]) -> dict[str, Any]: + for key, value in patch.items(): + if isinstance(value, Mapping) and isinstance(target.get(key), dict): + _deep_merge(target[key], value) + else: + target[key] = deepcopy(value) + return target + + +class MockDataStore: + def __init__(self, profile: Mapping[str, Any]) -> None: + self.profile = dict(profile) + self._defaults = make_default_state(profile) + self._state = deepcopy(self._defaults) + + @property + def state(self) -> dict[str, Any]: + return self._state + + def snapshot(self) -> dict[str, Any]: + return deepcopy(self._state) + + def reset(self) -> dict[str, Any]: + self._state = deepcopy(self._defaults) + return self.snapshot() + + def merge_patch(self, patch: Mapping[str, Any]) -> dict[str, Any]: + _deep_merge(self._state, patch) + return self.snapshot() + + def load_patch_file(self, path: str | Path) -> dict[str, Any]: + patch = json.loads(Path(path).read_text(encoding="utf-8")) + return self.merge_patch(patch) + + def consume_forced_error(self, method: str) -> tuple[int, str] | None: + entry = self._state.get("forced_errors", {}).get(method) + if not entry: + return None + if isinstance(entry, int): + return entry, f"forced error for {method}" + rc = int(entry.get("rc", 0)) + count = int(entry.get("count", 1)) + message = str(entry.get("message", f"forced error for {method}")) + if count <= 1: + self._state["forced_errors"].pop(method, None) + else: + entry["count"] = count - 1 + return rc, message + diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/defaults.py b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/defaults.py new file mode 100644 index 0000000..d19b564 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/defaults.py @@ -0,0 +1,142 @@ +from __future__ import annotations + +from typing import Any, Mapping + + +def _family_config(profile_name: str) -> tuple[str, int, int, int]: + lowered = profile_name.lower() + if "30i" in lowered or "ncg" in lowered: + return ("30i", 32, 2, 4) + if "0id" in lowered or "0dn" in lowered: + return ("0i-D", 24, 1, 2) + if "fwlibe64" in lowered: + return ("e64", 8, 1, 2) + return ("generic", 8, 1, 2) + + +def make_default_state(profile: Mapping[str, Any]) -> dict[str, Any]: + profile_name = str(profile.get("profile_name", "FWLIB64")) + family, default_axes, max_path, max_spindles = _family_config(profile_name) + max_axis = int(profile.get("max_axis_hint") or default_axes) + axis_names = [ + "X", + "Y", + "Z", + "A", + "B", + "C", + "U", + "V", + "W", + "P", + "Q", + "R", + ] + while len(axis_names) < max_axis: + axis_names.append(f"A{len(axis_names) + 1}") + + spindle_names = [f"S{i}" for i in range(1, max_spindles + 1)] + programs = [ + {"number": 1, "comment": "MAIN", "length": 128}, + {"number": 100, "comment": "TOOLCHANGE", "length": 84}, + ] + + return { + "sysinfo": { + "addinfo": 0, + "max_axis": max_axis, + "cnc_type": family[:2].ljust(2), + "mt_type": "M ", + "series": family[:4].ljust(4), + "version": "A1.0", + "axes": str(max_axis).rjust(2, "0"), + }, + "statinfo": { + "aut": 1, + "run": 3, + "motion": 1, + "mstb": 0, + "emergency": 0, + "alarm": 0, + "edit": 0, + }, + "paths": {"current": 1, "max": max_path}, + "dynamic": { + "alarm": 0, + "prgnum": 1, + "prgmnum": 1, + "seqnum": 120, + "actf": 1500, + "acts": 3200, + "axes": { + axis_names[0]: {"absolute": 123456, "machine": 123450, "relative": 6, "distance": 0}, + axis_names[1]: {"absolute": -22000, "machine": -22010, "relative": 10, "distance": 0}, + axis_names[2]: {"absolute": 8000, "machine": 7990, "relative": 10, "distance": 0}, + }, + }, + "actf": 1500, + "acts": 3200, + "acts2": [3200] + [0] * (max_spindles - 1), + "axis_names": axis_names[:max_axis], + "spindle_names": spindle_names, + "parameters": { + "6711": {"type": "long", "value": 1, "decimal": 0, "description": "example parameter"}, + "6712": {"type": "long", "value": 500, "decimal": 0, "description": "example parameter"}, + }, + "pmc": { + "R": { + "100": {"type": "byte", "value": 0}, + "101": {"type": "byte", "value": 1}, + "102": {"type": "byte", "value": 0}, + }, + }, + "macros": { + "100": {"value": 12345, "decimal": 3}, + "101": {"value": 98765, "decimal": 3}, + }, + "alarms": [ + {"alm_no": 0, "type": 0, "axis": 0, "msg": ""}, + ], + "operator_messages": [ + {"number": 200, "type": 0, "char_num": 12, "data": "READY"}, + ], + "program": { + "current": 1, + "main": 1, + "sequence": 120, + "block_count": 42, + "executing": "%\nO0001\nG90 G54 G00 X0 Y0\nM30\n%", + "executing_path": "//CNC_MEM/USER/PATH1/O0001", + "directory": programs, + }, + "spindle": { + "meter": [ + {"name": spindle_names[0], "value": 56, "unit": "%"}, + {"name": spindle_names[1], "value": 0, "unit": "%"}, + ], + "servo_meter": [ + {"name": axis_names[0], "value": 14, "unit": "%"}, + {"name": axis_names[1], "value": 8, "unit": "%"}, + {"name": axis_names[2], "value": 5, "unit": "%"}, + ], + "load": [ + {"name": spindle_names[0], "load": 56, "speed": 3200}, + {"name": spindle_names[1], "load": 0, "speed": 0}, + ], + "gear": [1] * max_spindles, + "max_rpm": [6000] * max_spindles, + }, + "timers": { + "power_on": 86400, + "operating": 7200, + "cutting": 3600, + "cycle": 95, + }, + "operation_mode": {"mode": 1, "name": "MEM"}, + "diagnostics": { + "300": {"type": "long", "value": 14, "description": "servo load X"}, + "301": {"type": "long", "value": 8, "description": "servo load Y"}, + "302": {"type": "long", "value": 5, "description": "servo load Z"}, + }, + "forced_errors": {}, + } diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/export_introspection.py b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/export_introspection.py new file mode 100644 index 0000000..d5275a8 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/export_introspection.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +import json +import re +from pathlib import Path +from typing import Any + +from .constants import IMPLEMENTED_FOCAS_METHODS + + +def parse_fwlib_imports(fwlib_cs_path: str | Path) -> list[str]: + text = Path(fwlib_cs_path).read_text(encoding="utf-8", errors="ignore") + imports = re.findall(r"extern short\s+([A-Za-z0-9_]+)\s*\(", text) + return sorted(set(imports)) + + +def extract_dll_exports(dll_path: str | Path) -> list[str]: + import pefile + + pe = pefile.PE(str(dll_path)) + exports = [entry.name.decode("ascii", "ignore") for entry in pe.DIRECTORY_ENTRY_EXPORT.symbols if entry.name] + return sorted(set(exports)) + + +def _infer_metadata(dll_name: str) -> tuple[str, int, int]: + lowered = dll_name.lower() + if lowered == "fwlib64.dll": + return ("generic-ethernet", 8, 1) + if lowered == "fwlibe64.dll": + return ("embedded-ethernet", 8, 1) + if "30i" in lowered: + return ("30i/31i/32i", 32, 2) + if "ncg" in lowered: + return ("ncguide-family", 32, 2) + if "0id" in lowered: + return ("0i-d-family", 24, 1) + if "0dn" in lowered: + return ("0-dn-family", 24, 1) + return ("unknown", 8, 1) + + +def build_profile(dll_path: str | Path, fwlib_cs_path: str | Path) -> dict[str, Any]: + dll_path = Path(dll_path) + exports = extract_dll_exports(dll_path) + wrapper_imports = set(parse_fwlib_imports(fwlib_cs_path)) + series_hint, max_axis_hint, max_path_hint = _infer_metadata(dll_path.name) + export_set = set(exports) + connection_methods = sorted(symbol for symbol in exports if symbol.startswith("cnc_allclibhndl")) + mock_methods = sorted(set(IMPLEMENTED_FOCAS_METHODS) & export_set) + wrapper_supported = sorted(wrapper_imports & export_set) + return { + "profile_name": dll_path.stem, + "dll_name": dll_path.name, + "series_hint": series_hint, + "max_axis_hint": max_axis_hint, + "max_path_hint": max_path_hint, + "export_count": len(exports), + "connection_methods": connection_methods, + "mock_methods": mock_methods, + "wrapper_supported_count": len(wrapper_supported), + "wrapper_supported_methods": wrapper_supported, + "exports": exports, + "notes": [ + "Exports extracted directly from the 64-bit DLL PE export directory.", + "Wrapper-supported methods are intersected with upstream fwlib.cs extern declarations.", + "Axis and path hints are filename-family heuristics, not protocol-level proofs.", + ], + } + + +def write_profiles(dll_dir: str | Path, fwlib_cs_path: str | Path, out_dir: str | Path) -> list[Path]: + dll_dir = Path(dll_dir) + out_dir = Path(out_dir) + out_dir.mkdir(parents=True, exist_ok=True) + written: list[Path] = [] + for dll_path in sorted(dll_dir.glob("*.dll")): + profile = build_profile(dll_path, fwlib_cs_path) + out_path = out_dir / f"{dll_path.stem}.json" + out_path.write_text(json.dumps(profile, indent=2), encoding="utf-8") + written.append(out_path) + return written + diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/profiles.py b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/profiles.py new file mode 100644 index 0000000..4a681db --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/profiles.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + + +PROFILE_DIR = Path(__file__).resolve().parent / "builtin_profiles" + +PROFILE_ALIASES = { + "ZeroI_D": "fwlib0iD64", + "ZeroI_F": "fwlib0iD64", + "ZeroI_MF": "fwlib0iD64", + "ZeroI_TF": "fwlib0iD64", + "Sixteen_i": "FWLIB64", + "Thirty_i": "fwlib30i64", + "ThirtyOne_i": "fwlib30i64", + "ThirtyTwo_i": "fwlib30i64", + "PowerMotion_i": "fwlib0DN64", +} + + +def list_profiles() -> list[str]: + return sorted(path.stem for path in PROFILE_DIR.glob("*.json")) + + +def resolve_profile_name(profile_name: str) -> str: + return PROFILE_ALIASES.get(profile_name, profile_name) + + +def load_profile(profile_name: str) -> dict[str, Any]: + profile_name = resolve_profile_name(profile_name) + candidates = [ + PROFILE_DIR / f"{profile_name}.json", + PROFILE_DIR / f"{Path(profile_name).stem}.json", + ] + for candidate in candidates: + if candidate.exists(): + return json.loads(candidate.read_text(encoding="utf-8")) + available = ", ".join(list_profiles()) + raise FileNotFoundError(f"Unknown profile '{profile_name}'. Available profiles: {available}") diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/server.py b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/server.py new file mode 100644 index 0000000..babb3d5 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Docker/focas-mock/src/focas_mock/server.py @@ -0,0 +1,1018 @@ +from __future__ import annotations + +import asyncio +import json +from itertools import count +from typing import Any, Awaitable, Callable + +from .constants import ( + ADMIN_METHODS, + EW_FUNC, + EW_HANDLE, + EW_NUMBER, + EW_OK, + EW_PARAM, + EW_PATH, + EW_PROTOCOL, + EW_VERSION, + RC_LABELS, +) +from .data_store import MockDataStore +from .profiles import load_profile + + +Handler = Callable[[dict[str, Any]], Awaitable[tuple[int, dict[str, Any] | None, str | None]]] + + +class FocasMockServer: + def __init__( + self, + host: str, + port: int, + profile: dict[str, Any], + store: MockDataStore | None = None, + ) -> None: + self.host = host + self.port = port + self.profile = profile + self.store = store or MockDataStore(profile) + self._next_handle = count(1) + self._handles: dict[int, dict[str, Any]] = {} + self._server: asyncio.AbstractServer | None = None + self._alarm_schedule_task: asyncio.Task[None] | None = None + self._wire_connection_count = count(1) + self._handlers: dict[str, Handler] = { + "cnc_allclibhndl": self._connect, + "cnc_allclibhndl2": self._connect, + "cnc_allclibhndl3": self._connect, + "cnc_freelibhndl": self._disconnect, + "cnc_sysinfo": self._sysinfo, + "cnc_statinfo": self._statinfo, + "cnc_rddynamic2": self._rddynamic2, + "cnc_actf": self._actf, + "cnc_acts": self._acts, + "cnc_acts2": self._acts2, + "cnc_getpath": self._getpath, + "cnc_setpath": self._setpath, + "cnc_rdaxisname": self._rdaxisname, + "cnc_rdspdlname": self._rdspdlname, + "cnc_rdparam": self._rdparam, + "cnc_wrparam": self._wrparam, + "cnc_rdmacro": self._rdmacro, + "cnc_wrmacro": self._wrmacro, + "cnc_rdalmmsg2": self._rdalmmsg2, + "pmc_rdpmcrng": self._pmc_rdpmcrng, + "pmc_wrpmcrng": self._pmc_wrpmcrng, + "cnc_rdopmsg": self._rdopmsg, + "cnc_rdopmode": self._rdopmode, + "cnc_rdprgnum": self._rdprgnum, + "cnc_exeprgname2": self._exeprgname2, + "cnc_rdexecprog": self._rdexecprog, + "cnc_rdseqnum": self._rdseqnum, + "cnc_rdblkcount": self._rdblkcount, + "cnc_rdproginfo": self._rdproginfo, + "cnc_rdprogdir3": self._rdprogdir3, + "cnc_rdtimer": self._rdtimer, + "cnc_rdspmeter": self._rdspmeter, + "cnc_rdsvmeter": self._rdsvmeter, + "cnc_rdspload": self._rdspload, + "cnc_rdspgear": self._rdspgear, + "cnc_rdspmaxrpm": self._rdspmaxrpm, + "cnc_rddiagnum": self._rddiagnum, + "cnc_rddiaginfo": self._rddiaginfo, + "cnc_diagnoss": self._diagnoss, + "mock_get_state": self._mock_get_state, + "mock_patch": self._mock_patch, + "mock_reset": self._mock_reset, + "mock_load_profile": self._mock_load_profile, + "mock_list_methods": self._mock_list_methods, + "mock_schedule_alarms": self._mock_schedule_alarms, + } + + async def start(self) -> "FocasMockServer": + self._server = await asyncio.start_server(self._handle_client, self.host, self.port) + socket = self._server.sockets[0] + self.port = int(socket.getsockname()[1]) + return self + + async def close(self) -> None: + self._cancel_alarm_schedule() + if self._server is None: + return + self._server.close() + await self._server.wait_closed() + self._server = None + + async def serve_forever(self) -> None: + if self._server is None: + await self.start() + assert self._server is not None + async with self._server: + await self._server.serve_forever() + + async def _handle_client(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: + try: + while not reader.at_eof(): + first = await reader.read(1) + if not first: + break + if first == b"\xa0": + header = first + await reader.readexactly(9) + await self._handle_wire_client(reader, writer, header) + break + raw = first + await reader.readline() + raw = raw.strip() + if not raw: + continue + response = await self._dispatch_raw(raw) + writer.write(json.dumps(response).encode("utf-8") + b"\n") + await writer.drain() + finally: + writer.close() + await writer.wait_closed() + + async def _handle_wire_client( + self, + reader: asyncio.StreamReader, + writer: asyncio.StreamWriter, + first_header: bytes, + ) -> None: + connection_index = next(self._wire_connection_count) + header = first_header + while True: + if len(header) != 10 or header[:4] != b"\xa0\xa0\xa0\xa0": + break + body_len = int.from_bytes(header[8:10], "big") + body = await reader.readexactly(body_len) if body_len else b"" + response = self._dispatch_wire_pdu(connection_index, header, body) + if response: + writer.write(response) + await writer.drain() + try: + header = await reader.readexactly(10) + except asyncio.IncompleteReadError: + break + + def _dispatch_wire_pdu(self, connection_index: int, header: bytes, body: bytes) -> bytes: + pdu_type = header[6] + if pdu_type == 0x01: + return self._wire_initiate_response(connection_index) + if pdu_type == 0x02: + return self._wire_pdu(0x02, 0x02, b"") + if pdu_type == 0x21: + return self._wire_request_response(body) + return b"" + + def _wire_pdu(self, pdu_type: int, direction: int, body: bytes, version: int = 1) -> bytes: + return ( + b"\xa0\xa0\xa0\xa0" + + version.to_bytes(2, "big") + + bytes([pdu_type & 0xFF, direction & 0xFF]) + + len(body).to_bytes(2, "big") + + body + ) + + def _wire_initiate_response(self, connection_index: int) -> bytes: + del connection_index + max_axis = int(self.store.snapshot()["sysinfo"].get("max_axis", 1)) + body = bytearray(24) + body[8:10] = (1).to_bytes(2, "big") + body[16:18] = b"M " + body[20:22] = max_axis.to_bytes(2, "big") + body[22:24] = (len(self.store.snapshot()["spindle_names"])).to_bytes(2, "big") + return self._wire_pdu(0x01, 0x02, bytes(body)) + + def _wire_request_response(self, request_body: bytes) -> bytes: + if len(request_body) < 2: + return self._wire_pdu(0x21, 0x02, self._wire_response_body([self._make_wire_response_block(0, b"\x00\x00\x00\x00")])) + + count_value = int.from_bytes(request_body[:2], "big") + blocks: list[bytes] = [] + offset = 2 + for _index in range(count_value): + if offset + 2 > len(request_body): + break + block_len = int.from_bytes(request_body[offset : offset + 2], "big") + block = request_body[offset : offset + block_len] + if len(block) < block_len: + break + blocks.append(self._wire_response_block(block)) + offset += block_len + + if not blocks: + blocks.append(self._make_wire_response_block(0, b"\x00\x00\x00\x00")) + return self._wire_pdu(0x21, 0x02, self._wire_response_body(blocks)) + + def _wire_response_body(self, blocks: list[bytes]) -> bytes: + return len(blocks).to_bytes(2, "big") + b"".join(blocks) + + def _wire_response_block(self, request_block: bytes) -> bytes: + command = int.from_bytes(request_block[6:8], "big") if len(request_block) >= 8 else 0 + payload = self._wire_payload(command, request_block) + return self._make_wire_response_block(command, payload, request_block) + + def _make_wire_response_block(self, command: int, payload: bytes, request_block: bytes | None = None) -> bytes: + block_len = 0x10 + len(payload) + block = bytearray(block_len) + block[0:2] = block_len.to_bytes(2, "big") + if request_block and len(request_block) >= 8: + block[2:8] = request_block[2:8] + else: + block[6:8] = command.to_bytes(2, "big") + block[14:16] = len(payload).to_bytes(2, "big") + block[16:] = payload + return bytes(block) + + def _wire_payload(self, command: int, request_block: bytes) -> bytes: + if command == 0x0E and self._block_u32(request_block, 8) == 0x26F0: + return bytes(8) + self._u32(0xFF) + if command == 0x18: + return self._wire_sysinfo() + if command == 0x19: + return self._wire_statinfo_primary() + if command == 0xE1: + return self._u16(0) + if command == 0x98: + statinfo = self.store.snapshot()["statinfo"] + return self._u16(int(statinfo.get("tmmode", statinfo.get("manual", 0)))) + if command == 0x0E: + return self._wire_rdparam(request_block) + if command == 0x1A: + return self._wire_dynamic_scalar("alarm") + if command == 0x1C: + dynamic = self.store.snapshot()["dynamic"] + return self._u32(int(dynamic.get("prgnum", 0))) + self._u32(int(dynamic.get("prgmnum", 0))) + if command == 0x1D: + return self._wire_dynamic_scalar("seqnum") + if command == 0x24: + return self._wire_dynamic_scalar("actf") + if command == 0x25: + return self._wire_dynamic_scalar("acts") + if command == 0x26: + return self._wire_dynamic_axis(request_block) + if command == 0x35: + return self._u32(int(self.store.snapshot()["program"].get("block_count", 0))) + if command == 0x40: + return self._wire_spindle_metric(request_block) + if command == 0x56: + return self._wire_servo_meter() + if command == 0x57: + mode = self.store.snapshot()["operation_mode"] + return self._u16(int(mode.get("mode", 0) if isinstance(mode, dict) else mode)) + if command == 0x89: + return self._wire_axis_names() + if command == 0x8A: + return self._wire_spindle_names() + if command == 0xFC: + return self._wire_program_name() + if command == 0x120: + return self._wire_timer(request_block) + if command == 0x10: + self._wire_wrparam(request_block) + return b"" + if command == 0x15: + return self._wire_rdmacro(request_block) + if command == 0x16: + self._wire_wrmacro(request_block) + return b"" + if command == 0x23: + return self._wire_alarms(request_block) + if command == 0x8001: + return self._wire_pmc_read(request_block) + if command == 0x8002: + self._wire_pmc_write(request_block) + return b"" + return b"\x00\x00\x00\x00" + + def _wire_sysinfo(self) -> bytes: + info = self.store.snapshot()["sysinfo"] + payload = bytearray() + payload += self._u16(int(info.get("addinfo", 0))) + payload += self._u16(int(info.get("max_axis", 0))) + payload += self._ascii_fixed(str(info.get("cnc_type", "")), 2) + payload += self._ascii_fixed(str(info.get("mt_type", "")), 2) + payload += self._ascii_fixed(str(info.get("series", "")), 4) + payload += self._ascii_fixed(str(info.get("version", "")), 4) + payload += self._ascii_fixed(str(info.get("axes", "")), 2) + return bytes(payload) + + def _wire_statinfo_primary(self) -> bytes: + statinfo = self.store.snapshot()["statinfo"] + values = ( + int(statinfo.get("aut", 0)), + int(statinfo.get("run", 0)), + int(statinfo.get("motion", 0)), + int(statinfo.get("mstb", 0)), + int(statinfo.get("emergency", 0)), + int(statinfo.get("alarm", 0)), + int(statinfo.get("edit", 0)), + ) + return b"".join(self._u16(value) for value in values) + + def _wire_rdparam(self, request_block: bytes) -> bytes: + datano = self._block_u32(request_block, 8) + parameter = self.store.snapshot()["parameters"].get(str(datano), {}) + value = int(parameter.get("value", 0)) if isinstance(parameter, dict) else 0 + return self._u32(value) + + def _wire_dynamic_scalar(self, key: str) -> bytes: + value = int(self.store.snapshot()["dynamic"].get(key, 0)) + return self._u32(value) + + def _wire_dynamic_axis(self, request_block: bytes) -> bytes: + selector = self._block_u32(request_block, 8) + axis = self._block_u32(request_block, 12) + field = {4: "absolute", 1: "machine", 6: "relative", 7: "distance"}.get(selector, "absolute") + state = self.store.snapshot() + names = list(state["axis_names"]) + dynamic_axes = state["dynamic"]["axes"] + payload = bytearray() + for name in names: + position = dynamic_axes.get(name, {}) + payload += self._u32(int(position.get(field, 0))) + payload += self._u32(0) + if axis > 0 and axis <= len(names): + name = names[axis - 1] + position = dynamic_axes.get(name, {}) + return self._u32(int(position.get(field, 0))) + self._u32(0) + return bytes(payload) + + def _wire_wrparam(self, request_block: bytes) -> None: + datano = self._block_u32(request_block, 8) + extra = request_block[28:] + value = int.from_bytes(extra[:4], "big", signed=True) if len(extra) >= 4 else self._block_u32(request_block, 12) + self.store.merge_patch({"parameters": {str(datano): {"type": "long", "value": value, "decimal": 0}}}) + + def _wire_rdmacro(self, request_block: bytes) -> bytes: + number = self._block_u32(request_block, 8) + macro = self.store.snapshot()["macros"].get(str(number), {}) + value = int(macro.get("value", 0)) if isinstance(macro, dict) else 0 + decimal = int(macro.get("decimal", 0)) if isinstance(macro, dict) else 0 + return self._u32(value) + self._u16(decimal) + self._u16(0) + + def _wire_wrmacro(self, request_block: bytes) -> None: + number = self._block_u32(request_block, 8) + extra = request_block[28:] + value = int.from_bytes(extra[:4], "big", signed=True) if len(extra) >= 4 else 0 + decimal = int.from_bytes(extra[4:6], "big", signed=True) if len(extra) >= 6 else 0 + self.store.merge_patch({"macros": {str(number): {"value": value, "decimal": decimal}}}) + + def _wire_axis_names(self) -> bytes: + payload = bytearray() + for name in self.store.snapshot()["axis_names"]: + payload += self._name_record(name, 4) + return bytes(payload) + + def _wire_spindle_names(self) -> bytes: + payload = bytearray() + for name in self.store.snapshot()["spindle_names"]: + payload += self._name_record(name, 4) + return bytes(payload) + + def _wire_servo_meter(self) -> bytes: + state = self.store.snapshot() + configured = {str(entry.get("name", "")): entry for entry in state["spindle"]["servo_meter"]} + payload = bytearray() + for name in state["axis_names"]: + entry = configured.get(name, {"name": name, "value": 0}) + payload += self._u32(int(entry.get("value", 0))) + payload += self._u16(0) + payload += self._u16(1) + payload += self._name_record(name, 4) + return bytes(payload) + + def _wire_spindle_metric(self, request_block: bytes) -> bytes: + metric = self._block_u32(request_block, 8) + spindle = self.store.snapshot()["spindle"] + if metric == 1: + values = [int(value) for value in spindle.get("max_rpm", [])] + else: + values = [int(entry.get("load", 0)) for entry in spindle.get("load", [])] + payload = bytearray() + for value in values[:4]: + payload += self._u32(value) + payload += self._u32(0) + return bytes(payload) + + def _wire_program_name(self) -> bytes: + program = self.store.snapshot()["program"] + name = str(program.get("executing_path", "")) + leaf = name.replace("\\", "/").rstrip("/").split("/")[-1] or name + number = int(program.get("current", program.get("main", 0))) + return self._ascii_fixed(leaf, 36) + self._u32(number) + + def _wire_timer(self, request_block: bytes) -> bytes: + timer_type = self._block_u32(request_block, 8) + key_map = {0: "power_on", 1: "operating", 2: "cutting", 3: "cycle"} + seconds = int(self.store.snapshot()["timers"].get(key_map.get(timer_type, "power_on"), 0)) + return self._u32(seconds // 60) + self._u32((seconds % 60) * 1000) + + def _wire_alarms(self, request_block: bytes) -> bytes: + requested = max(self._block_u32(request_block, 12), 1) + records = bytearray() + for alarm in self.store.snapshot()["alarms"][:requested]: + alm_no = int(alarm.get("alm_no", alarm.get("number", 0))) + alarm_type = int(alarm.get("type", 0)) + axis = int(alarm.get("axis", 0)) + message = str(alarm.get("msg", alarm.get("message", ""))).encode("ascii", errors="replace")[:64] + records += self._u32(alm_no) + records += self._u32(alarm_type) + records += self._u32(axis) + records += self._u32(len(message)) + records += message.ljust(64, b"\x00") + return bytes(records) + + def _wire_pmc_read(self, request_block: bytes) -> bytes: + start = self._block_u32(request_block, 8) + end = self._block_u32(request_block, 12) + area = self._pmc_area_name(self._block_u32(request_block, 16)) + data_type_code = self._block_u32(request_block, 20) + data_type = self._pmc_data_type(data_type_code) + area_state = self.store.snapshot().get("pmc", {}).get(area, {}) + payload = bytearray() + for address in range(start, end + 1): + entry = area_state.get(str(address), {"value": 0}) + value = int(entry.get("value", 0)) if isinstance(entry, dict) else int(entry) + if data_type_code == 1: + payload += self._u16(value) + elif data_type_code in (2, 4): + payload += self._u32(value) + elif data_type_code == 5: + payload += int(value).to_bytes(8, "big", signed=True) + else: + payload += bytes([value & 0xFF]) + return bytes(payload) + + def _wire_pmc_write(self, request_block: bytes) -> None: + start = self._block_u32(request_block, 8) + end = self._block_u32(request_block, 12) + area = self._pmc_area_name(self._block_u32(request_block, 16)) + data_type_code = self._block_u32(request_block, 20) + data_type = self._pmc_data_type(data_type_code) + extra = request_block[28:] + width = {1: 2, 2: 4, 4: 4, 5: 8}.get(data_type_code, 1) + patch = {"pmc": {area: {}}} + for index, address in enumerate(range(start, end + 1)): + raw = extra[index * width : (index + 1) * width] + if len(raw) < width: + break + value = int.from_bytes(raw, "big", signed=data_type_code != 0) + patch["pmc"][area][str(address)] = {"type": data_type, "value": value} + self.store.merge_patch(patch) + + def _block_u32(self, block: bytes, offset: int) -> int: + if offset + 4 > len(block): + return 0 + return int.from_bytes(block[offset : offset + 4], "big") + + def _u16(self, value: int) -> bytes: + return int(value).to_bytes(2, "big", signed=True) + + def _u32(self, value: int) -> bytes: + return int(value).to_bytes(4, "big", signed=True) + + def _ascii_fixed(self, value: str, length: int) -> bytes: + return value.encode("ascii", errors="replace")[:length].ljust(length, b" ") + + def _name_record(self, value: str, length: int) -> bytes: + encoded = value.encode("ascii", errors="replace") + record = bytearray(length) + if encoded: + record[0] = encoded[0] + if len(encoded) > 1: + record[1 : min(len(encoded), length)] = encoded[1:length] + return bytes(record) + + async def _dispatch_raw(self, raw: bytes) -> dict[str, Any]: + try: + request = json.loads(raw.decode("utf-8")) + except json.JSONDecodeError as exc: + return {"id": None, "method": None, "rc": EW_PROTOCOL, "message": f"invalid JSON: {exc.msg}", "result": None} + + request_id = request.get("id") + method = request.get("method") + params = request.get("params", {}) + if not isinstance(method, str): + return {"id": request_id, "method": method, "rc": EW_PROTOCOL, "message": "method must be a string", "result": None} + if not isinstance(params, dict): + return {"id": request_id, "method": method, "rc": EW_PROTOCOL, "message": "params must be an object", "result": None} + + rc, result, message = await self.dispatch(method, params) + return { + "id": request_id, + "method": method, + "rc": rc, + "message": message or RC_LABELS.get(rc, f"RC_{rc}"), + "result": result, + } + + async def dispatch(self, method: str, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + if method not in ADMIN_METHODS: + forced = self.store.consume_forced_error(method) + if forced: + rc, message = forced + return rc, None, message + + if method not in ADMIN_METHODS and method not in set(self.profile.get("exports", [])): + return EW_VERSION, None, f"{method} is not exported by profile {self.profile['profile_name']}" + + handler = self._handlers.get(method) + if handler is None: + return EW_FUNC, None, f"{method} is exported but not implemented by the mock server" + return await handler(params) + + def _require_handle(self, params: dict[str, Any]) -> tuple[int | None, dict[str, Any] | None]: + handle_value = params.get("FlibHndl", params.get("handle")) + if handle_value is None: + return None, None + try: + handle = int(handle_value) + except (TypeError, ValueError): + return None, None + return handle, self._handles.get(handle) + + async def _connect(self, params: dict[str, Any]) -> tuple[int, dict[str, Any], str | None]: + handle = next(self._next_handle) + self._handles[handle] = { + "ipaddr": params.get("ipaddr", "127.0.0.1"), + "port": int(params.get("port", self.port)), + "timeout": int(params.get("timeout", 10)), + "path": 1, + } + return EW_OK, {"FlibHndl": handle, "profile": self.profile["profile_name"]}, None + + async def _disconnect(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + handle, _ = self._require_handle(params) + if handle is None or handle not in self._handles: + return EW_HANDLE, None, "invalid library handle" + self._handles.pop(handle, None) + return EW_OK, {"FlibHndl": handle}, None + + async def _sysinfo(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + return EW_OK, {"sysinfo": self.store.snapshot()["sysinfo"]}, None + + async def _statinfo(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + return EW_OK, {"statinfo": self.store.snapshot()["statinfo"]}, None + + async def _rddynamic2(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + state = self.store.snapshot() + axis_selector = params.get("axis", 1) + dynamic = state["dynamic"] + axis_names = state["axis_names"] + axis_name = axis_names[0] + if isinstance(axis_selector, int) and axis_selector > 0 and axis_selector <= len(axis_names): + axis_name = axis_names[axis_selector - 1] + pos = dynamic["axes"].get(axis_name, next(iter(dynamic["axes"].values()))) + payload = { + "rddynamic2": { + "axis": axis_selector, + "alarm": dynamic["alarm"], + "prgnum": dynamic["prgnum"], + "prgmnum": dynamic["prgmnum"], + "seqnum": dynamic["seqnum"], + "actf": dynamic["actf"], + "acts": dynamic["acts"], + "pos": pos, + } + } + return EW_OK, payload, None + + async def _actf(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + return await self._simple_handle_value(params, "actf", "actf") + + async def _acts(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + return await self._simple_handle_value(params, "acts", "acts") + + async def _acts2(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + acts2 = self.store.snapshot()["acts2"] + return EW_OK, {"acts2": acts2}, None + + async def _getpath(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + handle, session = self._require_handle(params) + if handle is None or session is None: + return EW_HANDLE, None, "invalid library handle" + max_path = int(self.store.snapshot()["paths"]["max"]) + return EW_OK, {"path_no": session["path"], "max_path": max_path}, None + + async def _setpath(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + handle, session = self._require_handle(params) + if handle is None or session is None: + return EW_HANDLE, None, "invalid library handle" + path_no = int(params.get("path", params.get("path_no", 1))) + max_path = int(self.store.snapshot()["paths"]["max"]) + if path_no < 1 or path_no > max_path: + return EW_PATH, None, f"path {path_no} is outside 1..{max_path}" + session["path"] = path_no + return EW_OK, {"path_no": path_no}, None + + async def _rdaxisname(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + return EW_OK, {"axis_names": self.store.snapshot()["axis_names"]}, None + + async def _rdspdlname(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + return EW_OK, {"spindle_names": self.store.snapshot()["spindle_names"]}, None + + async def _rdparam(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + key = str(params.get("datano")) + parameter = self.store.snapshot()["parameters"].get(key) + if parameter is None: + return EW_NUMBER, None, f"parameter {key} is not mocked" + return EW_OK, {"param": {"datano": int(key), **parameter}}, None + + async def _wrparam(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + incoming = params.get("param", params) + if not isinstance(incoming, dict): + return EW_PARAM, None, "cnc_wrparam requires parameter fields" + datano = incoming.get("datano", params.get("datano")) + if datano is None: + return EW_PARAM, None, "cnc_wrparam requires datano" + key = str(datano) + current = self.store.snapshot()["parameters"].get(key, {}) + parameter = dict(current) + value = incoming.get("value", incoming.get("ldata", incoming.get("idata", incoming.get("cdata")))) + if value is not None: + parameter["value"] = value + parameter["type"] = incoming.get("type", parameter.get("type", "long")) + parameter["decimal"] = incoming.get("decimal", incoming.get("dec_val", parameter.get("decimal", 0))) + if "description" in incoming: + parameter["description"] = incoming["description"] + self.store.merge_patch({"parameters": {key: parameter}}) + return EW_OK, {"param": {"datano": int(key), **parameter}}, None + + async def _rdmacro(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + key = str(params.get("number", params.get("datano"))) + macro = self.store.snapshot()["macros"].get(key) + if macro is None: + return EW_NUMBER, None, f"macro {key} is not mocked" + return EW_OK, {"macro": {"number": int(key), **macro}}, None + + async def _wrmacro(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + incoming = params.get("macro", params) + if not isinstance(incoming, dict): + return EW_PARAM, None, "cnc_wrmacro requires macro fields" + number = incoming.get("number", incoming.get("datano", params.get("number", params.get("datano")))) + if number is None: + return EW_PARAM, None, "cnc_wrmacro requires number" + key = str(number) + current = self.store.snapshot()["macros"].get(key, {}) + macro = dict(current) + value = incoming.get("value", incoming.get("mcr_val")) + if value is not None: + macro["value"] = value + macro["decimal"] = incoming.get("decimal", incoming.get("dec_val", macro.get("decimal", 0))) + self.store.merge_patch({"macros": {key: macro}}) + return EW_OK, {"macro": {"number": int(key), **macro}}, None + + async def _rdalmmsg2(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + return EW_OK, {"alarms": self.store.snapshot()["alarms"]}, None + + async def _pmc_rdpmcrng(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + area, data_type, start, end = self._pmc_range(params) + if start is None: + return EW_PARAM, None, "pmc_rdpmcrng requires start/datano_s" + pmc_state = self.store.snapshot().get("pmc", {}) + area_state = pmc_state.get(area, {}) + addresses = [] + values = [] + for address in range(start, end + 1): + entry = area_state.get(str(address), {"type": data_type, "value": 0}) + if not isinstance(entry, dict): + entry = {"type": data_type, "value": entry} + value = entry.get("value", 0) + values.append(value) + addresses.append({"address": address, **entry}) + return EW_OK, { + "pmc": { + "area": area, + "data_type": data_type, + "start": start, + "end": end, + "values": values, + "addresses": addresses, + } + }, None + + async def _pmc_wrpmcrng(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + area, data_type, start, end = self._pmc_range(params) + if start is None: + return EW_PARAM, None, "pmc_wrpmcrng requires start/datano_s" + values = params.get("values", params.get("data", params.get("cdata", params.get("idata", params.get("ldata"))))) + if values is None: + value = params.get("value") + values = [value] if value is not None else [] + if not isinstance(values, list): + values = [values] + if not values: + return EW_PARAM, None, "pmc_wrpmcrng requires values" + if not any(key in params for key in ("end", "datano_e", "d", "length", "count", "e")): + end = start + len(values) - 1 + + patch = {"pmc": {area: {}}} + for offset, value in enumerate(values): + address = start + offset + if address > end: + break + patch["pmc"][area][str(address)] = {"type": data_type, "value": value} + state = self.store.merge_patch(patch) + area_state = state["pmc"].get(area, {}) + addresses = [{"address": int(address), **entry} for address, entry in sorted(area_state.items(), key=lambda item: int(item[0])) if start <= int(address) <= end] + return EW_OK, {"pmc": {"area": area, "data_type": data_type, "start": start, "end": end, "addresses": addresses}}, None + + async def _rdopmsg(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + return EW_OK, {"operator_messages": self.store.snapshot()["operator_messages"]}, None + + async def _rdopmode(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + return EW_OK, {"operation_mode": self.store.snapshot()["operation_mode"]}, None + + async def _rdprgnum(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + program = self.store.snapshot()["program"] + return EW_OK, {"program_numbers": {"current": program["current"], "main": program["main"]}}, None + + async def _exeprgname2(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + program = self.store.snapshot()["program"] + return EW_OK, {"program_name": program["executing_path"]}, None + + async def _rdexecprog(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + program = self.store.snapshot()["program"] + return EW_OK, {"program": program["executing"]}, None + + async def _rdseqnum(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + return await self._program_value(params, "sequence", "sequence") + + async def _rdblkcount(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + return await self._program_value(params, "block_count", "block_count") + + async def _rdproginfo(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + program = self.store.snapshot()["program"] + current = next((item for item in program["directory"] if item["number"] == program["current"]), None) + return EW_OK, {"program_info": current or {}}, None + + async def _rdprogdir3(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + return EW_OK, {"directory": self.store.snapshot()["program"]["directory"]}, None + + async def _rdtimer(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + timers = self.store.snapshot()["timers"] + timer_kind = params.get("type") + if timer_kind is None: + return EW_OK, {"timers": timers}, None + key_map = {0: "power_on", 1: "operating", 2: "cutting", 3: "cycle"} + key = key_map.get(int(timer_kind)) + if key is None: + return EW_PARAM, None, f"timer type {timer_kind} is not mapped" + return EW_OK, {"timer": {"type": int(timer_kind), "name": key, "value": timers[key]}}, None + + async def _rdspmeter(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + return await self._spindle_group(params, "meter", "spindle_meter") + + async def _rdsvmeter(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + return await self._spindle_group(params, "servo_meter", "servo_meter") + + async def _rdspload(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + return await self._spindle_group(params, "load", "spindle_load") + + async def _rdspgear(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + return await self._spindle_group(params, "gear", "spindle_gear") + + async def _rdspmaxrpm(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + return await self._spindle_group(params, "max_rpm", "spindle_max_rpm") + + async def _rddiagnum(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + numbers = sorted(int(key) for key in self.store.snapshot()["diagnostics"].keys()) + return EW_OK, {"diagnostic_numbers": {"count": len(numbers), "min": numbers[0], "max": numbers[-1]}}, None + + async def _rddiaginfo(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + diagnostics = self.store.snapshot()["diagnostics"] + return EW_OK, {"diagnostic_info": [{"number": int(key), **value} for key, value in diagnostics.items()]}, None + + async def _diagnoss(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + key = str(params.get("datano")) + diag = self.store.snapshot()["diagnostics"].get(key) + if diag is None: + return EW_NUMBER, None, f"diagnostic {key} is not mocked" + return EW_OK, {"diagnostic": {"datano": int(key), **diag}}, None + + async def _mock_get_state(self, params: dict[str, Any]) -> tuple[int, dict[str, Any], str | None]: + return EW_OK, {"profile": self.profile["profile_name"], "state": self.store.snapshot()}, None + + async def _mock_patch(self, params: dict[str, Any]) -> tuple[int, dict[str, Any], str | None]: + state_patch = params.get("state", params) + if not isinstance(state_patch, dict): + return EW_PARAM, None, "mock_patch requires an object under 'state' or as params" + state = self.store.merge_patch(state_patch) + return EW_OK, {"profile": self.profile["profile_name"], "state": state}, None + + async def _mock_reset(self, params: dict[str, Any]) -> tuple[int, dict[str, Any], str | None]: + self._cancel_alarm_schedule() + return EW_OK, {"profile": self.profile["profile_name"], "state": self.store.reset()}, None + + async def _mock_load_profile(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + profile_name = params.get("profile") + if not isinstance(profile_name, str): + return EW_PARAM, None, "mock_load_profile requires a string 'profile'" + self._cancel_alarm_schedule() + self.profile = load_profile(profile_name) + self.store = MockDataStore(self.profile) + self._handles.clear() + self._next_handle = count(1) + return EW_OK, {"profile": self.profile["profile_name"]}, None + + async def _mock_list_methods(self, params: dict[str, Any]) -> tuple[int, dict[str, Any], str | None]: + return EW_OK, { + "profile": self.profile["profile_name"], + "connection_methods": self.profile.get("connection_methods", []), + "mock_methods": self.profile.get("mock_methods", []), + }, None + + async def _mock_schedule_alarms(self, params: dict[str, Any]) -> tuple[int, dict[str, Any] | None, str | None]: + sequence = params.get("sequence") + if not isinstance(sequence, list): + return EW_PARAM, None, "mock_schedule_alarms requires a sequence list" + normalized = [] + for item in sequence: + if not isinstance(item, dict): + return EW_PARAM, None, "each alarm schedule item must be an object" + at_ms = int(item.get("at_ms", 0)) + alarms = item.get("alarms", []) + if not isinstance(alarms, list): + return EW_PARAM, None, "alarm schedule item alarms must be a list" + normalized.append({"at_ms": max(at_ms, 0), "alarms": alarms}) + normalized.sort(key=lambda item: item["at_ms"]) + self._cancel_alarm_schedule() + self._alarm_schedule_task = asyncio.create_task(self._run_alarm_schedule(normalized)) + return EW_OK, {"scheduled": len(normalized)}, None + + async def _run_alarm_schedule(self, sequence: list[dict[str, Any]]) -> None: + previous_ms = 0 + try: + for item in sequence: + at_ms = int(item["at_ms"]) + await asyncio.sleep(max(at_ms - previous_ms, 0) / 1000) + self.store.merge_patch({"alarms": item["alarms"]}) + previous_ms = at_ms + except asyncio.CancelledError: + return + + def _cancel_alarm_schedule(self) -> None: + if self._alarm_schedule_task is not None: + self._alarm_schedule_task.cancel() + self._alarm_schedule_task = None + + def _validate_handle(self, params: dict[str, Any]) -> int: + handle, session = self._require_handle(params) + if handle is None or session is None: + return EW_HANDLE + return EW_OK + + def _pmc_range(self, params: dict[str, Any]) -> tuple[str, str, int | None, int]: + area = params.get("area", params.get("adr_type", params.get("type_a", params.get("a", "R")))) + data_type = params.get("data_type", params.get("type_d", params.get("b", "byte"))) + area = self._pmc_area_name(area) + data_type = self._pmc_data_type(data_type) + start_value = params.get("start", params.get("datano_s", params.get("c"))) + if start_value is None: + return str(area), str(data_type), None, 0 + start = int(start_value) + if "end" in params: + end = int(params["end"]) + elif "datano_e" in params: + end = int(params["datano_e"]) + elif "d" in params: + end = int(params["d"]) + else: + length = int(params.get("length", params.get("count", params.get("e", 1)))) + end = start + max(length, 1) - 1 + return str(area), str(data_type), start, end + + def _pmc_area_name(self, area: Any) -> str: + if isinstance(area, int): + return { + 0: "G", + 1: "F", + 2: "Y", + 3: "X", + 4: "A", + 5: "R", + 6: "T", + 7: "K", + 8: "C", + 9: "D", + 10: "E", + }.get(area, "R") + text = str(area).upper() + return text if text else "R" + + def _pmc_data_type(self, data_type: Any) -> str: + if isinstance(data_type, int): + return { + 0: "byte", + 1: "word", + 2: "long", + 4: "real", + 5: "double", + }.get(data_type, "byte") + text = str(data_type).lower() + return text if text else "byte" + + async def _simple_handle_value( + self, + params: dict[str, Any], + state_key: str, + result_key: str, + ) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + return EW_OK, {result_key: self.store.snapshot()[state_key]}, None + + async def _program_value( + self, + params: dict[str, Any], + state_key: str, + result_key: str, + ) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + return EW_OK, {result_key: self.store.snapshot()["program"][state_key]}, None + + async def _spindle_group( + self, + params: dict[str, Any], + state_key: str, + result_key: str, + ) -> tuple[int, dict[str, Any] | None, str | None]: + rc = self._validate_handle(params) + if rc: + return rc, None, "invalid library handle" + return EW_OK, {result_key: self.store.snapshot()["spindle"][state_key]}, None diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/FocasSimFixture.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/FocasSimFixture.cs new file mode 100644 index 0000000..25305f7 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/FocasSimFixture.cs @@ -0,0 +1,192 @@ +using System.Net.Sockets; +using System.Text; +using System.Text.Json; + +namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests; + +/// +/// Fixture for the focas-mock simulator. Probes the Docker mock at +/// collection init; if reachable, exposes helpers that drive the mock's +/// admin surface (mock_load_profile, mock_patch, +/// mock_reset, mock_schedule_alarms) so tests can seed +/// deterministic state before exercising the managed driver. +/// +/// +/// Single skip gate: is non-null when the +/// localhost:8193 TCP probe fails. Tests call +/// Assert.Skip. +/// +public sealed class FocasSimFixture : IAsyncDisposable +{ + private const string EndpointEnvVar = "OTOPCUA_FOCAS_SIM_ENDPOINT"; + private const string ProfileEnvVar = "OTOPCUA_FOCAS_SIM_PROFILE"; + private const string DefaultHost = "localhost"; + private const int DefaultPort = 8193; + + public string Host { get; } + public int Port { get; } + + /// focas-mock profile stem the fixture should load (e.g. fwlib30i64, + /// ThirtyOne_i — both resolve via the mock's alias table). Null when unset. + public string? ExpectedProfile { get; } + + /// When the maps to a concrete + /// , this is it. Null otherwise. + public FocasCncSeries? ExpectedSeries { get; } + + /// Non-null when the mock probe failed — tests skip with this reason. + public string? SkipReason { get; } + + public FocasSimFixture() + { + var endpoint = Environment.GetEnvironmentVariable(EndpointEnvVar) ?? $"{DefaultHost}:{DefaultPort}"; + (Host, Port) = ParseEndpoint(endpoint); + + ExpectedProfile = Environment.GetEnvironmentVariable(ProfileEnvVar); + ExpectedSeries = ParseSeries(ExpectedProfile); + + try + { + using var client = new TcpClient(AddressFamily.InterNetwork); + var addresses = System.Net.Dns.GetHostAddresses(Host); + var ip = addresses.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork) + ?? System.Net.IPAddress.Loopback; + var task = client.ConnectAsync(ip, Port); + if (!task.Wait(TimeSpan.FromSeconds(2)) || !client.Connected) + { + SkipReason = $"focas-mock at {Host}:{Port} did not accept a TCP connection within 2s. " + + $"Start it (`docker compose -f Docker/docker-compose.yml up -d`) " + + $"or override {EndpointEnvVar}."; + } + } + catch (Exception ex) + { + SkipReason = $"focas-mock at {Host}:{Port} unreachable: {ex.GetType().Name}: {ex.Message}. " + + $"Start it or override {EndpointEnvVar}."; + } + } + + public ValueTask DisposeAsync() => ValueTask.CompletedTask; + + // ---- Admin API helpers ---- + + /// + /// Load a focas-mock profile. Accepts either the raw DLL-stem name + /// (fwlib30i64) or the OtOpcUa-style alias (ThirtyOne_i); + /// focas-mock's PROFILE_ALIASES resolves both. + /// + public Task LoadProfileAsync(string profileName, CancellationToken ct = default) => + SendAdminAsync("mock_load_profile", new { profile = profileName }, ct); + + /// Deep-merge into the mock's current state. + public Task PatchStateAsync(object state, CancellationToken ct = default) => + SendAdminAsync("mock_patch", new { state }, ct); + + /// Reset the mock to the selected profile's default state. + public Task ResetAsync(CancellationToken ct = default) => + SendAdminAsync("mock_reset", new { }, ct); + + /// Install a time-scheduled alarm raise / clear sequence. + public Task ScheduleAlarmsAsync(IEnumerable sequence, CancellationToken ct = default) => + SendAdminAsync("mock_schedule_alarms", new { sequence }, ct); + + /// Low-level JSON round-trip. One TCP connection per call — matches + /// how the shim talks to the mock; simpler than pooling. + public async Task SendAdminAsync(string method, object @params, CancellationToken ct = default) + { + using var client = new TcpClient(); + await client.ConnectAsync(Host, Port, ct).ConfigureAwait(false); + using var stream = client.GetStream(); + + var request = JsonSerializer.SerializeToUtf8Bytes(new + { + id = Interlocked.Increment(ref _nextId), + method, + @params, + }); + await stream.WriteAsync(request, ct).ConfigureAwait(false); + await stream.WriteAsync(new byte[] { (byte)'\n' }, ct).ConfigureAwait(false); + + var buffer = new byte[65536]; + var len = 0; + while (len < buffer.Length) + { + var read = await stream.ReadAsync(buffer.AsMemory(len), ct).ConfigureAwait(false); + if (read == 0) break; + len += read; + // focas-mock replies with a single newline-terminated JSON object. + if (Array.IndexOf(buffer, (byte)'\n', 0, len) >= 0) break; + } + var newline = Array.IndexOf(buffer, (byte)'\n', 0, len); + var jsonLen = newline >= 0 ? newline : len; + var text = Encoding.UTF8.GetString(buffer, 0, jsonLen); + + using var doc = JsonDocument.Parse(text); + var rc = doc.RootElement.GetProperty("rc").GetInt32(); + if (rc != 0) + { + var message = doc.RootElement.TryGetProperty("message", out var m) ? m.GetString() : "?"; + throw new InvalidOperationException($"focas-mock {method} returned rc={rc} ({message})."); + } + // Return the "result" subtree cloned — document is disposed on exit. + return doc.RootElement.GetProperty("result").Clone(); + } + + private static int _nextId; + + // ---- Parsing ---- + + private static (string Host, int Port) ParseEndpoint(string endpoint) + { + const string focasScheme = "focas://"; + var body = endpoint.StartsWith(focasScheme, StringComparison.OrdinalIgnoreCase) + ? endpoint[focasScheme.Length..] + : endpoint; + var slash = body.IndexOf('/'); + if (slash >= 0) body = body[..slash]; + var colon = body.LastIndexOf(':'); + if (colon < 0) return (body, DefaultPort); + var host = body[..colon]; + return int.TryParse(body[(colon + 1)..], out var p) ? (host, p) : (host, DefaultPort); + } + + /// + /// Map either a focas-mock DLL-stem profile (fwlib30i64) or a + /// OtOpcUa-style alias (ThirtyOne_i) to the matching + /// . Keeps tests able to assert + /// series-gated behaviour regardless of how the profile was pinned. + /// + private static FocasCncSeries? ParseSeries(string? profile) + { + if (string.IsNullOrWhiteSpace(profile)) return null; + var trimmed = profile.Trim(); + + // Try the OtOpcUa alias set first — it's a superset of human-readable names. + // The docker-compose profile names (thirtyone / zerod / ...) are accepted too so + // run-focas.ps1's -Profile argument threads straight through. + var aliasMapped = trimmed switch + { + "ThirtyOne_i" or "Thirty_i" or "ThirtyTwo_i" + or "thirtyone_i" or "thirty_i" or "thirtytwo_i" + or "thirtyone" or "thirty" or "thirtytwo" + or "fwlib30i64" => "ThirtyOne_i", + "Sixteen_i" or "sixteen_i" or "sixteen" or "FWLIB64" => "Sixteen_i", + "Zero_i_D" or "Zero_i_F" or "Zero_i_MF" or "Zero_i_TF" + or "zero_i_d" or "zero_i_f" or "zero_i_mf" or "zero_i_tf" + or "zerod" or "zerof" or "zeromf" or "zerotf" + or "fwlib0iD64" => "Zero_i_D", + "PowerMotion_i" or "powermotion_i" or "powermotion" + or "fwlib0DN64" => "PowerMotion_i", + _ => null, + }; + + return aliasMapped is not null && Enum.TryParse(aliasMapped, out var parsed) + ? parsed : null; + } +} + +[Xunit.CollectionDefinition(Name)] +public sealed class FocasSimCollection : Xunit.ICollectionFixture +{ + public const string Name = "FocasSim"; +} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendCoverageTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendCoverageTests.cs new file mode 100644 index 0000000..5d282c3 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendCoverageTests.cs @@ -0,0 +1,263 @@ +using System.Collections.Concurrent; +using Shouldly; +using Xunit; +using ZB.MOM.WW.OtOpcUa.Core.Abstractions; +using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire; + +namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests.Series; + +/// +/// End-to-end coverage for the driver capabilities that aren't part of +/// the fixed-tree path: user-authored PARAM: / MACRO: / PMC +/// reads, DiscoverAsync emission, SubscribeAsync + +/// OnDataChange, IAlarmSource raise/clear, and +/// IHostConnectivityProbe transitions. All via the managed +/// against the running focas-mock. +/// +[Collection(FocasSimCollection.Name)] +public sealed class WireBackendCoverageTests +{ + private readonly FocasSimFixture _fx; + + public WireBackendCoverageTests(FocasSimFixture fx) => _fx = fx; + + private const string DeviceHost = "focas://127.0.0.1:8193"; + + [Fact] + public async Task User_tag_reads_route_via_wire_backend() + { + if (_fx.SkipReason is not null) Assert.Skip(_fx.SkipReason); + var ct = TestContext.Current.CancellationToken; + + await _fx.LoadProfileAsync("FWLIB64", ct); + await _fx.PatchStateAsync(new + { + parameters = new Dictionary + { + ["6711"] = new { type = "long", value = 1234, @decimal = 0 }, + }, + macros = new Dictionary + { + ["500"] = new { value = 42000, @decimal = 3 }, + }, + pmc = new { R = new Dictionary + { + ["100"] = new { type = "byte", value = 7 }, + }}, + }, ct); + + var drv = new FocasDriver(new FocasDriverOptions + { + Devices = [new FocasDeviceOptions(DeviceHost)], + Tags = + [ + new FocasTagDefinition("Param6711", DeviceHost, "PARAM:6711", FocasDataType.Int32, Writable: false), + new FocasTagDefinition("Macro500", DeviceHost, "MACRO:500", FocasDataType.Float64, Writable: false), + new FocasTagDefinition("R100", DeviceHost, "R100", FocasDataType.Byte, Writable: false), + ], + Probe = new FocasProbeOptions { Enabled = false }, + }, driverInstanceId: "wire-usertags", clientFactory: new WireFocasClientFactory()); + + await using (drv) + { + await drv.InitializeAsync("{}", ct); + var snaps = await drv.ReadAsync(["Param6711", "Macro500", "R100"], ct); + snaps.ShouldAllBe(s => s.StatusCode == FocasStatusMapper.Good); + Convert.ToInt32(snaps[0].Value).ShouldBe(1234); + Convert.ToDouble(snaps[1].Value).ShouldBe(42.0, tolerance: 0.001); + Convert.ToInt32(snaps[2].Value).ShouldBe(7); + } + } + + [Fact] + public async Task Discover_emits_device_folder_and_tag_variables() + { + if (_fx.SkipReason is not null) Assert.Skip(_fx.SkipReason); + var ct = TestContext.Current.CancellationToken; + + await _fx.LoadProfileAsync("FWLIB64", ct); + + var drv = new FocasDriver(new FocasDriverOptions + { + Devices = [new FocasDeviceOptions(DeviceHost, DeviceName: "Lathe-1")], + Tags = + [ + new FocasTagDefinition("Run", DeviceHost, "R100", FocasDataType.Byte, Writable: false), + new FocasTagDefinition("Speed", DeviceHost, "MACRO:500", FocasDataType.Float64, Writable: false), + ], + Probe = new FocasProbeOptions { Enabled = false }, + }, driverInstanceId: "wire-discover", clientFactory: new WireFocasClientFactory()); + + await using (drv) + { + await drv.InitializeAsync("{}", ct); + + var builder = new RecordingBuilder(); + await drv.DiscoverAsync(builder, ct); + + builder.Folders.ShouldContain(f => f.BrowseName == "FOCAS"); + builder.Folders.ShouldContain(f => f.BrowseName == DeviceHost && f.DisplayName == "Lathe-1"); + builder.Variables.ShouldContain(v => v.BrowseName == "Run"); + builder.Variables.ShouldContain(v => v.BrowseName == "Speed"); + } + } + + [Fact] + public async Task Subscribe_fires_OnDataChange_via_wire_backend() + { + if (_fx.SkipReason is not null) Assert.Skip(_fx.SkipReason); + var ct = TestContext.Current.CancellationToken; + + await _fx.LoadProfileAsync("FWLIB64", ct); + await _fx.PatchStateAsync(new + { + pmc = new { R = new Dictionary + { + ["100"] = new { type = "byte", value = 1 }, + }}, + }, ct); + + var drv = new FocasDriver(new FocasDriverOptions + { + Devices = [new FocasDeviceOptions(DeviceHost)], + Tags = [new FocasTagDefinition("Run", DeviceHost, "R100", FocasDataType.Byte, Writable: false)], + Probe = new FocasProbeOptions { Enabled = false }, + }, driverInstanceId: "wire-subscribe", clientFactory: new WireFocasClientFactory()); + + await using (drv) + { + await drv.InitializeAsync("{}", ct); + var events = new ConcurrentQueue(); + drv.OnDataChange += (_, e) => events.Enqueue(e); + + var handle = await drv.SubscribeAsync(["Run"], TimeSpan.FromMilliseconds(150), ct); + await WaitFor(() => events.Count >= 1, TimeSpan.FromSeconds(3)); + Convert.ToInt32(events.First().Snapshot.Value).ShouldBe(1); + + // Flip the PMC byte — next poll tick should emit a fresh OnDataChange. + var before = events.Count; + await _fx.PatchStateAsync(new + { + pmc = new { R = new Dictionary + { + ["100"] = new { type = "byte", value = 99 }, + }}, + }, ct); + await WaitFor(() => events.Any(e => Convert.ToInt32(e.Snapshot.Value) == 99), + TimeSpan.FromSeconds(3)); + + await drv.UnsubscribeAsync(handle, ct); + events.Count.ShouldBeGreaterThan(before); + } + } + + [Fact] + public async Task Alarm_raise_then_clear_emits_both_events_via_wire_backend() + { + if (_fx.SkipReason is not null) Assert.Skip(_fx.SkipReason); + var ct = TestContext.Current.CancellationToken; + + await _fx.LoadProfileAsync("FWLIB64", ct); + // Start with no active alarms. + await _fx.PatchStateAsync(new { alarms = Array.Empty() }, ct); + + var drv = new FocasDriver(new FocasDriverOptions + { + Devices = [new FocasDeviceOptions(DeviceHost)], + Tags = [], + Probe = new FocasProbeOptions { Enabled = false }, + AlarmProjection = new FocasAlarmProjectionOptions + { + Enabled = true, + PollInterval = TimeSpan.FromMilliseconds(200), + }, + }, driverInstanceId: "wire-alarms", clientFactory: new WireFocasClientFactory()); + + await using (drv) + { + await drv.InitializeAsync("{}", ct); + var events = new List(); + drv.OnAlarmEvent += (_, e) => { lock (events) events.Add(e); }; + + var sub = await drv.SubscribeAlarmsAsync([], ct); + + // Raise one alarm. + await _fx.PatchStateAsync(new + { + alarms = new[] + { + new { alm_no = 500, type = 2, axis = 1, msg = "TEST OVERTRAVEL" }, + }, + }, ct); + await WaitFor(() => events.Any(e => e.Message.Contains("OVERTRAVEL")), TimeSpan.FromSeconds(5)); + + // Clear. + await _fx.PatchStateAsync(new { alarms = Array.Empty() }, ct); + await WaitFor(() => events.Any(e => e.Message.Contains("cleared")), TimeSpan.FromSeconds(5)); + + await drv.UnsubscribeAlarmsAsync(sub, ct); + + events.ShouldContain(e => e.AlarmType == "Overtravel" && e.Severity == AlarmSeverity.Critical); + events.ShouldContain(e => e.Message.Contains("cleared")); + events[0].SourceNodeId.ShouldBe(DeviceHost); + } + } + + [Fact] + public async Task Probe_transitions_to_Running_against_live_mock() + { + if (_fx.SkipReason is not null) Assert.Skip(_fx.SkipReason); + var ct = TestContext.Current.CancellationToken; + + var transitions = new ConcurrentQueue(); + var drv = new FocasDriver(new FocasDriverOptions + { + Devices = [new FocasDeviceOptions(DeviceHost)], + Probe = new FocasProbeOptions + { + Enabled = true, + Interval = TimeSpan.FromMilliseconds(150), + Timeout = TimeSpan.FromSeconds(1), + }, + }, driverInstanceId: "wire-probe", clientFactory: new WireFocasClientFactory()); + drv.OnHostStatusChanged += (_, e) => transitions.Enqueue(e); + + await using (drv) + { + await drv.InitializeAsync("{}", ct); + await WaitFor(() => transitions.Any(t => t.NewState == HostState.Running), TimeSpan.FromSeconds(5)); + drv.GetHostStatuses().Single().State.ShouldBe(HostState.Running); + } + } + + private static async Task WaitFor(Func pred, TimeSpan timeout) + { + var deadline = DateTime.UtcNow + timeout; + while (DateTime.UtcNow < deadline) + { + if (pred()) return; + await Task.Delay(50); + } + } + + private sealed class RecordingBuilder : IAddressSpaceBuilder + { + public List<(string BrowseName, string DisplayName)> Folders { get; } = new(); + public List<(string BrowseName, DriverAttributeInfo Info)> Variables { get; } = new(); + + public IAddressSpaceBuilder Folder(string browseName, string displayName) + { Folders.Add((browseName, displayName)); return this; } + + public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo info) + { Variables.Add((browseName, info)); return new Handle(info.FullName); } + + public void AddProperty(string _, DriverDataType __, object? ___) { } + + private sealed class Handle(string fullRef) : IVariableHandle + { + public string FullReference => fullRef; + public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) => new NullSink(); + } + private sealed class NullSink : IAlarmConditionSink { public void OnTransition(AlarmEventArgs args) { } } + } +} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendTests.cs new file mode 100644 index 0000000..ccbae23 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/Series/WireBackendTests.cs @@ -0,0 +1,280 @@ +using Shouldly; +using Xunit; +using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire; + +namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests.Series; + +/// +/// Dual-run companion to — exercises the same +/// fixed-tree scenarios through the pure-managed +/// instead of the shim/P-Invoke path. Proves both backends observe identical +/// state against the same focas-mock instance. +/// +/// +/// Scheduled for removal in Wire migration phase 3 (task #104) once the shim is +/// deleted — at that point only this class survives and becomes the canonical +/// fixed-tree integration test. +/// +[Collection(FocasSimCollection.Name)] +public sealed class WireBackendTests +{ + private readonly FocasSimFixture _fx; + + public WireBackendTests(FocasSimFixture fx) => _fx = fx; + + private const string DeviceHost = "focas://127.0.0.1:8193"; + + [Fact] + public async Task Identity_axes_and_dynamic_populate_via_wire_backend() + { + if (_fx.SkipReason is not null) Assert.Skip(_fx.SkipReason); + var ct = TestContext.Current.CancellationToken; + + await _fx.LoadProfileAsync("FWLIB64", ct); + await _fx.PatchStateAsync(new + { + sysinfo = new + { + addinfo = 0, max_axis = 8, cnc_type = "M ", mt_type = "M ", + series = "30i ", version = "A1.0", axes = "3 ", + }, + axis_names = new[] { "X", "Y", "Z" }, + rddynamic2 = new + { + axis = 1, alarm = 0, prgnum = 1, prgmnum = 1, seqnum = 42, + actf = 1500, acts = 3200, + pos = new { absolute = 123456, machine = 123450, relative = 6, distance = 0 }, + }, + }, ct); + + var driver = new FocasDriver(new FocasDriverOptions + { + Devices = [new FocasDeviceOptions(DeviceHost)], + Tags = [], + Probe = new FocasProbeOptions { Enabled = false }, + FixedTree = new FocasFixedTreeOptions + { + Enabled = true, + PollInterval = TimeSpan.FromMilliseconds(100), + }, + }, driverInstanceId: "focas-wire-identity", clientFactory: new WireFocasClientFactory()); + + await using (driver) + { + await driver.InitializeAsync("{}", ct); + + await WaitFor(() => + driver.GetDeviceState(DeviceHost) is { FixedTreeCache: not null }, TimeSpan.FromSeconds(5)); + + var state = driver.GetDeviceState(DeviceHost); + state.ShouldNotBeNull(); + state.FixedTreeCache.ShouldNotBeNull(); + state.FixedTreeCache.SysInfo.Series.ShouldStartWith("30i"); + state.FixedTreeCache.Axes.Count.ShouldBe(3); + state.FixedTreeCache.Axes[0].Display.ShouldBe("X"); + + await WaitFor(() => + state.LastFixedSnapshots.ContainsKey($"{DeviceHost}/Axes/X/AbsolutePosition"), + TimeSpan.FromSeconds(3)); + state.LastFixedSnapshots[$"{DeviceHost}/Axes/X/AbsolutePosition"].ShouldBe(123456); + } + } + + [Fact] + public async Task Program_and_operation_mode_populate_via_wire_backend() + { + if (_fx.SkipReason is not null) Assert.Skip(_fx.SkipReason); + var ct = TestContext.Current.CancellationToken; + + await _fx.LoadProfileAsync("FWLIB64", ct); + await _fx.PatchStateAsync(new + { + sysinfo = new + { + addinfo = 0, max_axis = 8, cnc_type = "M ", mt_type = "M ", + series = "30i ", version = "A1.0", axes = "1 ", + }, + axis_names = new[] { "X" }, + rddynamic2 = new + { + axis = 1, alarm = 0, prgnum = 42, prgmnum = 42, seqnum = 100, + actf = 0, acts = 0, + pos = new { absolute = 0, machine = 0, relative = 0, distance = 0 }, + }, + program = new + { + current = 42, main = 42, sequence = 100, block_count = 17, + executing_path = "O0042.NC", + }, + operation_mode = new { mode = 3 }, + }, ct); + + var driver = new FocasDriver(new FocasDriverOptions + { + Devices = [new FocasDeviceOptions(DeviceHost)], + Tags = [], + Probe = new FocasProbeOptions { Enabled = false }, + FixedTree = new FocasFixedTreeOptions + { + Enabled = true, + PollInterval = TimeSpan.FromMilliseconds(100), + ProgramPollInterval = TimeSpan.FromMilliseconds(200), + }, + }, driverInstanceId: "focas-wire-program", clientFactory: new WireFocasClientFactory()); + + await using (driver) + { + await driver.InitializeAsync("{}", ct); + + await WaitFor(() => + driver.GetDeviceState(DeviceHost) is { LastProgramInfo: not null }, + TimeSpan.FromSeconds(5)); + + var snapshots = await driver.ReadAsync( + [$"{DeviceHost}/Program/Name", + $"{DeviceHost}/Program/ONumber", + $"{DeviceHost}/Program/BlockCount", + $"{DeviceHost}/OperationMode/Mode"], ct); + + snapshots.ShouldAllBe(s => s.StatusCode == FocasStatusMapper.Good); + snapshots[0].Value!.ToString().ShouldStartWith("O0042"); + Convert.ToInt32(snapshots[1].Value).ShouldBe(42); + Convert.ToInt32(snapshots[2].Value).ShouldBe(17); + Convert.ToInt32(snapshots[3].Value).ShouldBe(3); + } + } + + [Fact] + public async Task Timers_populate_via_wire_backend() + { + if (_fx.SkipReason is not null) Assert.Skip(_fx.SkipReason); + var ct = TestContext.Current.CancellationToken; + + await _fx.LoadProfileAsync("FWLIB64", ct); + await _fx.PatchStateAsync(new + { + axis_names = new[] { "X" }, + timers = new + { + power_on = 3600, + operating = 7200, + cutting = 1800, + cycle = 120, + }, + }, ct); + + var driver = new FocasDriver(new FocasDriverOptions + { + Devices = [new FocasDeviceOptions(DeviceHost)], + Tags = [], + Probe = new FocasProbeOptions { Enabled = false }, + FixedTree = new FocasFixedTreeOptions + { + Enabled = true, + PollInterval = TimeSpan.FromMilliseconds(100), + TimerPollInterval = TimeSpan.FromMilliseconds(200), + ProgramPollInterval = TimeSpan.Zero, + }, + }, driverInstanceId: "focas-wire-timers", clientFactory: new WireFocasClientFactory()); + + await using (driver) + { + await driver.InitializeAsync("{}", ct); + + await WaitFor(() => + { + var state = driver.GetDeviceState(DeviceHost); + return state is not null && state.LastTimers.Count == 4; + }, TimeSpan.FromSeconds(5)); + + var snapshots = await driver.ReadAsync( + [$"{DeviceHost}/Timers/PowerOnSeconds", + $"{DeviceHost}/Timers/OperatingSeconds", + $"{DeviceHost}/Timers/CuttingSeconds", + $"{DeviceHost}/Timers/CycleSeconds"], ct); + + snapshots.ShouldAllBe(s => s.StatusCode == FocasStatusMapper.Good); + Convert.ToDouble(snapshots[0].Value).ShouldBe(3600.0, tolerance: 1); + Convert.ToDouble(snapshots[1].Value).ShouldBe(7200.0, tolerance: 1); + Convert.ToDouble(snapshots[2].Value).ShouldBe(1800.0, tolerance: 1); + Convert.ToDouble(snapshots[3].Value).ShouldBe(120.0, tolerance: 1); + } + } + + [Fact] + public async Task Spindle_load_and_max_rpm_populate_via_wire_backend() + { + if (_fx.SkipReason is not null) Assert.Skip(_fx.SkipReason); + var ct = TestContext.Current.CancellationToken; + + await _fx.LoadProfileAsync("FWLIB64", ct); + await _fx.PatchStateAsync(new + { + axis_names = new[] { "X" }, + spindle_names = new[] { "S1", "S2" }, + spindle = new + { + load = new object[] + { + new { name = "S1", load = 56, speed = 3200 }, + new { name = "S2", load = 12, speed = 1800 }, + }, + max_rpm = new[] { 6000, 4500 }, + }, + rddynamic2 = new + { + axis = 1, alarm = 0, prgnum = 1, prgmnum = 1, seqnum = 1, + actf = 0, acts = 0, + pos = new { absolute = 0, machine = 0, relative = 0, distance = 0 }, + }, + }, ct); + + var driver = new FocasDriver(new FocasDriverOptions + { + Devices = [new FocasDeviceOptions(DeviceHost)], + Tags = [], + Probe = new FocasProbeOptions { Enabled = false }, + FixedTree = new FocasFixedTreeOptions + { + Enabled = true, + PollInterval = TimeSpan.FromMilliseconds(100), + ProgramPollInterval = TimeSpan.Zero, + TimerPollInterval = TimeSpan.Zero, + }, + }, driverInstanceId: "focas-wire-spindle", clientFactory: new WireFocasClientFactory()); + + await using (driver) + { + await driver.InitializeAsync("{}", ct); + + await WaitFor(() => + { + var state = driver.GetDeviceState(DeviceHost); + return state?.FixedTreeCache is { Capabilities.SpindleLoad: true, Capabilities.SpindleMaxRpm: true } + && state.LastSpindleLoads.Count >= 2; + }, TimeSpan.FromSeconds(5)); + + var snapshots = await driver.ReadAsync( + [$"{DeviceHost}/Spindle/S1/Load", + $"{DeviceHost}/Spindle/S1/MaxRpm", + $"{DeviceHost}/Spindle/S2/Load", + $"{DeviceHost}/Spindle/S2/MaxRpm"], ct); + + snapshots.ShouldAllBe(s => s.StatusCode == FocasStatusMapper.Good); + Convert.ToInt32(snapshots[0].Value).ShouldBe(56); + Convert.ToInt32(snapshots[1].Value).ShouldBe(6000); + Convert.ToInt32(snapshots[2].Value).ShouldBe(12); + Convert.ToInt32(snapshots[3].Value).ShouldBe(4500); + } + } + + private static async Task WaitFor(Func pred, TimeSpan timeout) + { + var deadline = DateTime.UtcNow + timeout; + while (DateTime.UtcNow < deadline) + { + if (pred()) return; + await Task.Delay(50); + } + } +} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests.csproj b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests.csproj similarity index 66% rename from tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests.csproj rename to tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests.csproj index 88c368e..c1eaa10 100644 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests.csproj +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests.csproj @@ -6,7 +6,7 @@ enable false true - ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests + ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests @@ -20,12 +20,14 @@ - + - - + + diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests/ContractRoundTripTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests/ContractRoundTripTests.cs deleted file mode 100644 index bbe85d6..0000000 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests/ContractRoundTripTests.cs +++ /dev/null @@ -1,280 +0,0 @@ -using MessagePack; -using Shouldly; -using Xunit; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests; - -/// -/// MessagePack round-trip coverage for every FOCAS IPC contract. Ensures -/// [Key]-tagged fields survive serialize -> deserialize without loss so the -/// wire format stays stable across Proxy (.NET 10) and Host (.NET 4.8) processes. -/// -[Trait("Category", "Unit")] -public sealed class ContractRoundTripTests -{ - private static T RoundTrip(T value) - { - var bytes = MessagePackSerializer.Serialize(value); - return MessagePackSerializer.Deserialize(bytes); - } - - [Fact] - public void Hello_round_trips() - { - var original = new Hello - { - ProtocolMajor = 1, - ProtocolMinor = 2, - PeerName = "OtOpcUa.Server", - SharedSecret = "abc-123", - Features = ["bulk-read", "pmc-rmw"], - }; - var decoded = RoundTrip(original); - decoded.ProtocolMajor.ShouldBe(1); - decoded.ProtocolMinor.ShouldBe(2); - decoded.PeerName.ShouldBe("OtOpcUa.Server"); - decoded.SharedSecret.ShouldBe("abc-123"); - decoded.Features.ShouldBe(["bulk-read", "pmc-rmw"]); - } - - [Fact] - public void HelloAck_rejected_carries_reason() - { - var original = new HelloAck { Accepted = false, RejectReason = "bad secret" }; - var decoded = RoundTrip(original); - decoded.Accepted.ShouldBeFalse(); - decoded.RejectReason.ShouldBe("bad secret"); - } - - [Fact] - public void Heartbeat_and_ack_preserve_ticks() - { - var hb = RoundTrip(new Heartbeat { MonotonicTicks = 987654321 }); - hb.MonotonicTicks.ShouldBe(987654321); - - var ack = RoundTrip(new HeartbeatAck { MonotonicTicks = 987654321, HostUtcUnixMs = 1_700_000_000_000 }); - ack.MonotonicTicks.ShouldBe(987654321); - ack.HostUtcUnixMs.ShouldBe(1_700_000_000_000); - } - - [Fact] - public void ErrorResponse_preserves_code_and_message() - { - var decoded = RoundTrip(new ErrorResponse { Code = "Fwlib32Crashed", Message = "EW_UNEXPECTED" }); - decoded.Code.ShouldBe("Fwlib32Crashed"); - decoded.Message.ShouldBe("EW_UNEXPECTED"); - } - - [Fact] - public void OpenSessionRequest_preserves_series_and_timeout() - { - var decoded = RoundTrip(new OpenSessionRequest - { - HostAddress = "192.168.1.50:8193", - TimeoutMs = 3500, - CncSeries = 5, - }); - decoded.HostAddress.ShouldBe("192.168.1.50:8193"); - decoded.TimeoutMs.ShouldBe(3500); - decoded.CncSeries.ShouldBe(5); - } - - [Fact] - public void OpenSessionResponse_failure_carries_error_code() - { - var decoded = RoundTrip(new OpenSessionResponse - { - Success = false, - SessionId = 0, - Error = "unreachable", - ErrorCode = "EW_SOCKET", - }); - decoded.Success.ShouldBeFalse(); - decoded.Error.ShouldBe("unreachable"); - decoded.ErrorCode.ShouldBe("EW_SOCKET"); - } - - [Fact] - public void FocasAddressDto_carries_pmc_with_bit_index() - { - var decoded = RoundTrip(new FocasAddressDto - { - Kind = 0, - PmcLetter = "R", - Number = 100, - BitIndex = 3, - }); - decoded.Kind.ShouldBe(0); - decoded.PmcLetter.ShouldBe("R"); - decoded.Number.ShouldBe(100); - decoded.BitIndex.ShouldBe(3); - } - - [Fact] - public void FocasAddressDto_macro_omits_letter_and_bit() - { - var decoded = RoundTrip(new FocasAddressDto { Kind = 2, Number = 500 }); - decoded.Kind.ShouldBe(2); - decoded.PmcLetter.ShouldBeNull(); - decoded.Number.ShouldBe(500); - decoded.BitIndex.ShouldBeNull(); - } - - [Fact] - public void ReadRequest_and_response_round_trip() - { - var req = RoundTrip(new ReadRequest - { - SessionId = 42, - Address = new FocasAddressDto { Kind = 1, Number = 1815 }, - DataType = FocasDataTypeCode.Int32, - TimeoutMs = 1500, - }); - req.SessionId.ShouldBe(42); - req.Address.Number.ShouldBe(1815); - req.DataType.ShouldBe(FocasDataTypeCode.Int32); - - var resp = RoundTrip(new ReadResponse - { - Success = true, - StatusCode = 0, - ValueBytes = MessagePackSerializer.Serialize((int)12345), - ValueTypeCode = FocasDataTypeCode.Int32, - SourceTimestampUtcUnixMs = 1_700_000_000_000, - }); - resp.Success.ShouldBeTrue(); - resp.StatusCode.ShouldBe(0u); - MessagePackSerializer.Deserialize(resp.ValueBytes!).ShouldBe(12345); - resp.ValueTypeCode.ShouldBe(FocasDataTypeCode.Int32); - } - - [Fact] - public void WriteRequest_and_response_round_trip() - { - var req = RoundTrip(new WriteRequest - { - SessionId = 1, - Address = new FocasAddressDto { Kind = 2, Number = 500 }, - DataType = FocasDataTypeCode.Float64, - ValueBytes = MessagePackSerializer.Serialize(3.14159), - ValueTypeCode = FocasDataTypeCode.Float64, - }); - MessagePackSerializer.Deserialize(req.ValueBytes!).ShouldBe(3.14159); - - var resp = RoundTrip(new WriteResponse { Success = true, StatusCode = 0 }); - resp.Success.ShouldBeTrue(); - resp.StatusCode.ShouldBe(0u); - } - - [Fact] - public void PmcBitWriteRequest_preserves_bit_and_value() - { - var req = RoundTrip(new PmcBitWriteRequest - { - SessionId = 7, - Address = new FocasAddressDto { Kind = 0, PmcLetter = "Y", Number = 12 }, - BitIndex = 5, - Value = true, - }); - req.BitIndex.ShouldBe(5); - req.Value.ShouldBeTrue(); - } - - [Fact] - public void SubscribeRequest_round_trips_multiple_items() - { - var original = new SubscribeRequest - { - SessionId = 1, - SubscriptionId = 100, - IntervalMs = 250, - Items = - [ - new() { MonitoredItemId = 1, Address = new() { Kind = 0, PmcLetter = "R", Number = 100 }, DataType = FocasDataTypeCode.Bit }, - new() { MonitoredItemId = 2, Address = new() { Kind = 2, Number = 500 }, DataType = FocasDataTypeCode.Float64 }, - ], - }; - var decoded = RoundTrip(original); - decoded.Items.Length.ShouldBe(2); - decoded.Items[0].MonitoredItemId.ShouldBe(1); - decoded.Items[0].Address.PmcLetter.ShouldBe("R"); - decoded.Items[1].DataType.ShouldBe(FocasDataTypeCode.Float64); - } - - [Fact] - public void SubscribeResponse_rejected_items_survive() - { - var decoded = RoundTrip(new SubscribeResponse - { - Success = true, - RejectedMonitoredItemIds = [2, 7], - }); - decoded.RejectedMonitoredItemIds.ShouldBe([2, 7]); - } - - [Fact] - public void UnsubscribeRequest_round_trips() - { - var decoded = RoundTrip(new UnsubscribeRequest { SubscriptionId = 42 }); - decoded.SubscriptionId.ShouldBe(42); - } - - [Fact] - public void OnDataChangeNotification_round_trips() - { - var original = new OnDataChangeNotification - { - SubscriptionId = 100, - Changes = - [ - new() - { - MonitoredItemId = 1, - StatusCode = 0, - ValueBytes = MessagePackSerializer.Serialize(true), - ValueTypeCode = FocasDataTypeCode.Bit, - SourceTimestampUtcUnixMs = 1_700_000_000_000, - }, - ], - }; - var decoded = RoundTrip(original); - decoded.Changes.Length.ShouldBe(1); - MessagePackSerializer.Deserialize(decoded.Changes[0].ValueBytes!).ShouldBeTrue(); - } - - [Fact] - public void ProbeRequest_and_response_round_trip() - { - var req = RoundTrip(new ProbeRequest { SessionId = 1, TimeoutMs = 500 }); - req.TimeoutMs.ShouldBe(500); - - var resp = RoundTrip(new ProbeResponse { Healthy = true, ObservedAtUtcUnixMs = 1_700_000_000_000 }); - resp.Healthy.ShouldBeTrue(); - resp.ObservedAtUtcUnixMs.ShouldBe(1_700_000_000_000); - } - - [Fact] - public void RuntimeStatusChangeNotification_round_trips() - { - var decoded = RoundTrip(new RuntimeStatusChangeNotification - { - SessionId = 5, - RuntimeStatus = "Stopped", - ObservedAtUtcUnixMs = 1_700_000_000_000, - }); - decoded.RuntimeStatus.ShouldBe("Stopped"); - } - - [Fact] - public void RecycleHostRequest_and_response_round_trip() - { - var req = RoundTrip(new RecycleHostRequest { Kind = "Hard", Reason = "wedge-detected" }); - req.Kind.ShouldBe("Hard"); - req.Reason.ShouldBe("wedge-detected"); - - var resp = RoundTrip(new RecycleStatusResponse { Accepted = true, GraceSeconds = 20 }); - resp.Accepted.ShouldBeTrue(); - resp.GraceSeconds.ShouldBe(20); - } -} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests/FramingTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests/FramingTests.cs deleted file mode 100644 index 66778da..0000000 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests/FramingTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.IO; -using Shouldly; -using Xunit; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Tests; - -[Trait("Category", "Unit")] -public sealed class FramingTests -{ - [Fact] - public async Task FrameWriter_round_trips_single_frame_through_FrameReader() - { - var buffer = new MemoryStream(); - using (var writer = new FrameWriter(buffer, leaveOpen: true)) - { - await writer.WriteAsync(FocasMessageKind.Hello, - new Hello { PeerName = "proxy", SharedSecret = "s3cr3t" }, TestContext.Current.CancellationToken); - } - - buffer.Position = 0; - using var reader = new FrameReader(buffer, leaveOpen: true); - var frame = await reader.ReadFrameAsync(TestContext.Current.CancellationToken); - frame.ShouldNotBeNull(); - frame!.Value.Kind.ShouldBe(FocasMessageKind.Hello); - var hello = FrameReader.Deserialize(frame.Value.Body); - hello.PeerName.ShouldBe("proxy"); - hello.SharedSecret.ShouldBe("s3cr3t"); - } - - [Fact] - public async Task FrameReader_returns_null_on_clean_EOF_at_frame_boundary() - { - using var empty = new MemoryStream(); - using var reader = new FrameReader(empty, leaveOpen: true); - var frame = await reader.ReadFrameAsync(TestContext.Current.CancellationToken); - frame.ShouldBeNull(); - } - - [Fact] - public async Task FrameReader_throws_on_oversized_length_prefix() - { - var hostile = new byte[] { 0x7F, 0xFF, 0xFF, 0xFF, 0x01 }; // length > 16 MiB - using var stream = new MemoryStream(hostile); - using var reader = new FrameReader(stream, leaveOpen: true); - await Should.ThrowAsync(async () => - await reader.ReadFrameAsync(TestContext.Current.CancellationToken)); - } - - [Fact] - public async Task FrameReader_throws_on_mid_frame_eof() - { - var buffer = new MemoryStream(); - using (var writer = new FrameWriter(buffer, leaveOpen: true)) - { - await writer.WriteAsync(FocasMessageKind.Hello, new Hello { PeerName = "x" }, - TestContext.Current.CancellationToken); - } - // Truncate so body is incomplete. - var truncated = buffer.ToArray()[..(buffer.ToArray().Length - 2)]; - using var partial = new MemoryStream(truncated); - using var reader = new FrameReader(partial, leaveOpen: true); - await Should.ThrowAsync(async () => - await reader.ReadFrameAsync(TestContext.Current.CancellationToken)); - } - - [Fact] - public async Task FrameWriter_serializes_concurrent_writes() - { - var buffer = new MemoryStream(); - using var writer = new FrameWriter(buffer, leaveOpen: true); - - var tasks = Enumerable.Range(0, 20).Select(i => writer.WriteAsync( - FocasMessageKind.Heartbeat, - new Heartbeat { MonotonicTicks = i }, - TestContext.Current.CancellationToken)).ToArray(); - await Task.WhenAll(tasks); - - buffer.Position = 0; - using var reader = new FrameReader(buffer, leaveOpen: true); - var seen = new List(); - while (await reader.ReadFrameAsync(TestContext.Current.CancellationToken) is { } frame) - { - frame.Kind.ShouldBe(FocasMessageKind.Heartbeat); - seen.Add(FrameReader.Deserialize(frame.Body).MonotonicTicks); - } - seen.Count.ShouldBe(20); - seen.OrderBy(x => x).ShouldBe(Enumerable.Range(0, 20).Select(x => (long)x)); - } - - [Fact] - public void MessageKind_values_are_stable() - { - // Guardrail — if someone reorders/renumbers, the wire format breaks for deployed peers. - ((byte)FocasMessageKind.Hello).ShouldBe((byte)0x01); - ((byte)FocasMessageKind.Heartbeat).ShouldBe((byte)0x03); - ((byte)FocasMessageKind.OpenSessionRequest).ShouldBe((byte)0x10); - ((byte)FocasMessageKind.ReadRequest).ShouldBe((byte)0x30); - ((byte)FocasMessageKind.WriteRequest).ShouldBe((byte)0x32); - ((byte)FocasMessageKind.PmcBitWriteRequest).ShouldBe((byte)0x34); - ((byte)FocasMessageKind.SubscribeRequest).ShouldBe((byte)0x40); - ((byte)FocasMessageKind.OnDataChangeNotification).ShouldBe((byte)0x43); - ((byte)FocasMessageKind.ProbeRequest).ShouldBe((byte)0x70); - ((byte)FocasMessageKind.ErrorResponse).ShouldBe((byte)0xFE); - } -} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FakeFocasClient.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FakeFocasClient.cs index c15dbf3..5b40761 100644 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FakeFocasClient.cs +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FakeFocasClient.cs @@ -48,6 +48,52 @@ internal class FakeFocasClient : IFocasClient public virtual Task ProbeAsync(CancellationToken ct) => Task.FromResult(ProbeResult); + public List Alarms { get; } = []; + + public virtual Task> ReadAlarmsAsync(CancellationToken ct) => + Task.FromResult>([.. Alarms]); + + // ---- Fixed-tree T1 ---- + public FocasSysInfo SysInfo { get; set; } = new(0, 3, "M", "M", "30i", "A1.0", 3); + public List AxisNames { get; } = [new("X", ""), new("Y", ""), new("Z", "")]; + public List SpindleNames { get; } = [new("S", "1", "", "")]; + public Dictionary DynamicByAxis { get; } = []; + + public virtual Task GetSysInfoAsync(CancellationToken ct) => Task.FromResult(SysInfo); + public virtual Task> GetAxisNamesAsync(CancellationToken ct) => + Task.FromResult>([.. AxisNames]); + public virtual Task> GetSpindleNamesAsync(CancellationToken ct) => + Task.FromResult>([.. SpindleNames]); + public virtual Task ReadDynamicAsync(int axisIndex, CancellationToken ct) + { + if (!DynamicByAxis.TryGetValue(axisIndex, out var snap)) + snap = new FocasDynamicSnapshot(axisIndex, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + return Task.FromResult(snap); + } + + public FocasProgramInfo ProgramInfo { get; set; } = new("O0001", 1, 0, 1); + public virtual Task GetProgramInfoAsync(CancellationToken ct) => + Task.FromResult(ProgramInfo); + + public Dictionary Timers { get; } = []; + public virtual Task GetTimerAsync(FocasTimerKind kind, CancellationToken ct) + { + if (!Timers.TryGetValue(kind, out var t)) + t = new FocasTimer(kind, 0, 0); + return Task.FromResult(t); + } + + public List ServoLoads { get; } = []; + public virtual Task> GetServoLoadsAsync(CancellationToken ct) => + Task.FromResult>([.. ServoLoads]); + + public List SpindleLoads { get; } = []; + public List SpindleMaxRpms { get; } = []; + public virtual Task> GetSpindleLoadsAsync(CancellationToken ct) => + Task.FromResult>([.. SpindleLoads]); + public virtual Task> GetSpindleMaxRpmsAsync(CancellationToken ct) => + Task.FromResult>([.. SpindleMaxRpms]); + public virtual void Dispose() { DisposeCount++; diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasDriverFactoryExtensionsTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasDriverFactoryExtensionsTests.cs deleted file mode 100644 index 2a20e9c..0000000 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasDriverFactoryExtensionsTests.cs +++ /dev/null @@ -1,162 +0,0 @@ -using Shouldly; -using Xunit; -using ZB.MOM.WW.OtOpcUa.Core.Hosting; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests; - -/// -/// Task #220 — covers the DriverConfig JSON contract that -/// parses when the bootstrap -/// pipeline (task #248) materialises FOCAS DriverInstance rows. Pure unit tests, no pipe -/// or CNC required. -/// -[Trait("Category", "Unit")] -public sealed class FocasDriverFactoryExtensionsTests -{ - [Fact] - public void Register_adds_FOCAS_entry_to_registry() - { - var registry = new DriverFactoryRegistry(); - FocasDriverFactoryExtensions.Register(registry); - registry.TryGet("FOCAS").ShouldNotBeNull(); - } - - [Fact] - public void Register_is_case_insensitive_via_registry() - { - var registry = new DriverFactoryRegistry(); - FocasDriverFactoryExtensions.Register(registry); - registry.TryGet("focas").ShouldNotBeNull(); - registry.TryGet("Focas").ShouldNotBeNull(); - } - - [Fact] - public void CreateInstance_with_ipc_backend_and_valid_config_returns_FocasDriver() - { - const string json = """ - { - "Backend": "ipc", - "PipeName": "OtOpcUaFocasHost", - "SharedSecret": "secret-for-test", - "ConnectTimeoutMs": 5000, - "Series": "Thirty_i", - "TimeoutMs": 3000, - "Devices": [ - { "HostAddress": "focas://10.0.0.5:8193", "DeviceName": "Lathe1" } - ], - "Tags": [ - { "Name": "Override", "DeviceHostAddress": "focas://10.0.0.5:8193", - "Address": "R100", "DataType": "Int32", "Writable": true } - ] - } - """; - - var driver = FocasDriverFactoryExtensions.CreateInstance("focas-0", json); - - driver.ShouldNotBeNull(); - driver.DriverInstanceId.ShouldBe("focas-0"); - driver.DriverType.ShouldBe("FOCAS"); - } - - [Fact] - public void CreateInstance_defaults_Backend_to_ipc_when_unspecified() - { - // No "Backend" key → defaults to ipc → requires PipeName + SharedSecret. - const string json = """ - { "PipeName": "p", "SharedSecret": "s" } - """; - var driver = FocasDriverFactoryExtensions.CreateInstance("focas-default", json); - driver.DriverType.ShouldBe("FOCAS"); - } - - [Fact] - public void CreateInstance_ipc_backend_missing_PipeName_throws() - { - const string json = """{ "Backend": "ipc", "SharedSecret": "s" }"""; - Should.Throw( - () => FocasDriverFactoryExtensions.CreateInstance("focas-missing-pipe", json)) - .Message.ShouldContain("PipeName"); - } - - [Fact] - public void CreateInstance_ipc_backend_missing_SharedSecret_throws() - { - const string json = """{ "Backend": "ipc", "PipeName": "p" }"""; - Should.Throw( - () => FocasDriverFactoryExtensions.CreateInstance("focas-missing-secret", json)) - .Message.ShouldContain("SharedSecret"); - } - - [Fact] - public void CreateInstance_fwlib_backend_does_not_require_pipe_fields() - { - // Direct in-process Fwlib32 path. No pipe config needed; driver connects the DLL - // natively on first use. - const string json = """{ "Backend": "fwlib" }"""; - var driver = FocasDriverFactoryExtensions.CreateInstance("focas-fwlib", json); - driver.DriverInstanceId.ShouldBe("focas-fwlib"); - } - - [Fact] - public void CreateInstance_unimplemented_backend_yields_driver_that_fails_fast_on_use() - { - // Useful for staging DriverInstance rows in the config DB before the Host is - // actually deployed — the server boots but reads/writes surface clear errors. - const string json = """{ "Backend": "unimplemented" }"""; - var driver = FocasDriverFactoryExtensions.CreateInstance("focas-unimpl", json); - driver.DriverInstanceId.ShouldBe("focas-unimpl"); - } - - [Fact] - public void CreateInstance_unknown_backend_throws_with_expected_list() - { - const string json = """{ "Backend": "gibberish", "PipeName": "p", "SharedSecret": "s" }"""; - Should.Throw( - () => FocasDriverFactoryExtensions.CreateInstance("focas-bad-backend", json)) - .Message.ShouldContain("gibberish"); - } - - [Fact] - public void CreateInstance_rejects_unknown_Series() - { - const string json = """ - { "Backend": "fwlib", "Series": "NotARealSeries" } - """; - Should.Throw( - () => FocasDriverFactoryExtensions.CreateInstance("focas-bad-series", json)) - .Message.ShouldContain("NotARealSeries"); - } - - [Fact] - public void CreateInstance_rejects_tag_with_missing_DataType() - { - const string json = """ - { - "Backend": "fwlib", - "Devices": [{ "HostAddress": "focas://1.1.1.1:8193" }], - "Tags": [{ "Name": "Broken", "DeviceHostAddress": "focas://1.1.1.1:8193", "Address": "R1" }] - } - """; - Should.Throw( - () => FocasDriverFactoryExtensions.CreateInstance("focas-bad-tag", json)) - .Message.ShouldContain("DataType"); - } - - [Fact] - public void CreateInstance_null_or_whitespace_args_rejected() - { - Should.Throw( - () => FocasDriverFactoryExtensions.CreateInstance("", "{}")); - Should.Throw( - () => FocasDriverFactoryExtensions.CreateInstance("id", "")); - } - - [Fact] - public void Register_twice_throws() - { - var registry = new DriverFactoryRegistry(); - FocasDriverFactoryExtensions.Register(registry); - Should.Throw( - () => FocasDriverFactoryExtensions.Register(registry)); - } -} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasScaffoldingTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasScaffoldingTests.cs index 58044eb..fb1a94c 100644 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasScaffoldingTests.cs +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasScaffoldingTests.cs @@ -219,11 +219,11 @@ public sealed class FocasScaffoldingTests // ---- UnimplementedFocasClientFactory ---- [Fact] - public void Default_factory_throws_on_Create_with_deployment_pointer() + public void Unimplemented_factory_throws_on_Create_with_config_pointer() { var factory = new UnimplementedFocasClientFactory(); var ex = Should.Throw(() => factory.Create()); - ex.Message.ShouldContain("Fwlib32.dll"); - ex.Message.ShouldContain("licensed"); + ex.Message.ShouldContain("wire"); + ex.Message.ShouldContain("docs/drivers/FOCAS.md"); } } diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FwlibNativeHelperTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FwlibNativeHelperTests.cs deleted file mode 100644 index 31136b5..0000000 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FwlibNativeHelperTests.cs +++ /dev/null @@ -1,106 +0,0 @@ -using Shouldly; -using Xunit; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests; - -/// -/// Tests for the managed helpers inside FwlibNative + FwlibFocasClient that don't require the -/// licensed Fwlib32.dll — letter→ADR_* mapping, FocasDataType→data-type mapping, byte encoding. -/// The actual P/Invoke calls can only run where the DLL is present; field testing covers those. -/// -[Trait("Category", "Unit")] -public sealed class FwlibNativeHelperTests -{ - [Theory] - [InlineData("G", 0)] - [InlineData("F", 1)] - [InlineData("Y", 2)] - [InlineData("X", 3)] - [InlineData("A", 4)] - [InlineData("R", 5)] - [InlineData("T", 6)] - [InlineData("K", 7)] - [InlineData("C", 8)] - [InlineData("D", 9)] - [InlineData("E", 10)] - [InlineData("g", 0)] // case-insensitive - public void PmcAddrType_maps_every_valid_letter(string letter, short expected) - { - FocasPmcAddrType.FromLetter(letter).ShouldBe(expected); - } - - [Theory] - [InlineData("Z")] - [InlineData("")] - [InlineData("XX")] - public void PmcAddrType_rejects_unknown_letters(string letter) - { - FocasPmcAddrType.FromLetter(letter).ShouldBeNull(); - } - - [Theory] - [InlineData(FocasDataType.Bit, 0)] // byte - [InlineData(FocasDataType.Byte, 0)] - [InlineData(FocasDataType.Int16, 1)] // word - [InlineData(FocasDataType.Int32, 2)] // long - [InlineData(FocasDataType.Float32, 4)] - [InlineData(FocasDataType.Float64, 5)] - public void PmcDataType_maps_FocasDataType_to_FOCAS_code(FocasDataType input, short expected) - { - FocasPmcDataType.FromFocasDataType(input).ShouldBe(expected); - } - - [Fact] - public void EncodePmcValue_Byte_writes_signed_byte_at_offset_0() - { - var buf = new byte[40]; - FwlibFocasClient.EncodePmcValue(buf, FocasDataType.Byte, (sbyte)-5, bitIndex: null); - ((sbyte)buf[0]).ShouldBe((sbyte)-5); - } - - [Fact] - public void EncodePmcValue_Int16_writes_little_endian() - { - var buf = new byte[40]; - FwlibFocasClient.EncodePmcValue(buf, FocasDataType.Int16, (short)0x1234, bitIndex: null); - buf[0].ShouldBe((byte)0x34); - buf[1].ShouldBe((byte)0x12); - } - - [Fact] - public void EncodePmcValue_Int32_writes_little_endian() - { - var buf = new byte[40]; - FwlibFocasClient.EncodePmcValue(buf, FocasDataType.Int32, 0x12345678, bitIndex: null); - buf[0].ShouldBe((byte)0x78); - buf[1].ShouldBe((byte)0x56); - buf[2].ShouldBe((byte)0x34); - buf[3].ShouldBe((byte)0x12); - } - - [Fact] - public void EncodePmcValue_Bit_without_bit_index_writes_byte_boolean() - { - // Task #181 closed the Bit-write gap — PMC Bit with a bitIndex now routes through - // WritePmcBitAsync's RMW path upstream, and raw EncodePmcValue only gets the - // no-bit-index case (treated as a whole-byte boolean). - var buf = new byte[40]; - FwlibFocasClient.EncodePmcValue(buf, FocasDataType.Bit, true, bitIndex: null); - buf[0].ShouldBe((byte)1); - - FwlibFocasClient.EncodePmcValue(buf, FocasDataType.Bit, false, bitIndex: null); - buf[0].ShouldBe((byte)0); - } - - [Fact] - public void EncodeParamValue_Int32_writes_little_endian() - { - var buf = new byte[32]; - FwlibFocasClient.EncodeParamValue(buf, FocasDataType.Int32, 0x0A0B0C0D); - buf[0].ShouldBe((byte)0x0D); - buf[1].ShouldBe((byte)0x0C); - buf[2].ShouldBe((byte)0x0B); - buf[3].ShouldBe((byte)0x0A); - } -} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/IpcFocasClientTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/IpcFocasClientTests.cs deleted file mode 100644 index cab5f9c..0000000 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/IpcFocasClientTests.cs +++ /dev/null @@ -1,265 +0,0 @@ -using MessagePack; -using Shouldly; -using Xunit; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Ipc; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests; - -/// -/// End-to-end IPC round-trips over an in-memory loopback: IpcFocasClient talks -/// to a test fake that plays the Host's role by reading frames, dispatching on kind, -/// and responding with canned DTOs. Validates that every -/// method translates to the right wire frame + decodes the response correctly. -/// -[Trait("Category", "Unit")] -public sealed class IpcFocasClientTests -{ - private const string Secret = "test-secret"; - - private static async Task ServerLoopAsync(Stream serverSide, Func dispatch, CancellationToken ct) - { - using var reader = new FrameReader(serverSide, leaveOpen: true); - using var writer = new FrameWriter(serverSide, leaveOpen: true); - - // Hello handshake. - var first = await reader.ReadFrameAsync(ct); - if (first is null) return; - var hello = MessagePackSerializer.Deserialize(first.Value.Body); - var accepted = hello.SharedSecret == Secret; - await writer.WriteAsync(FocasMessageKind.HelloAck, - new HelloAck { Accepted = accepted, RejectReason = accepted ? null : "wrong-secret" }, ct); - if (!accepted) return; - - while (!ct.IsCancellationRequested) - { - var frame = await reader.ReadFrameAsync(ct); - if (frame is null) return; - await dispatch(frame.Value.Kind, frame.Value.Body, writer); - } - } - - [Fact] - public async Task Connect_sends_OpenSessionRequest_and_caches_session_id() - { - await using var loop = new IpcLoopback(); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - - OpenSessionRequest? received = null; - var server = Task.Run(() => ServerLoopAsync(loop.ServerSide, async (kind, body, writer) => - { - if (kind == FocasMessageKind.OpenSessionRequest) - { - received = MessagePackSerializer.Deserialize(body); - await writer.WriteAsync(FocasMessageKind.OpenSessionResponse, - new OpenSessionResponse { Success = true, SessionId = 42 }, cts.Token); - } - }, cts.Token)); - - var ipc = await FocasIpcClient.ConnectAsync(loop.ClientSide, Secret, cts.Token); - var client = new IpcFocasClient(ipc, FocasCncSeries.Thirty_i); - await client.ConnectAsync(new FocasHostAddress("192.168.1.50", 8193), TimeSpan.FromSeconds(2), cts.Token); - - client.IsConnected.ShouldBeTrue(); - received.ShouldNotBeNull(); - received!.HostAddress.ShouldBe("192.168.1.50:8193"); - received.CncSeries.ShouldBe((int)FocasCncSeries.Thirty_i); - - cts.Cancel(); - try { await server; } catch { } - } - - [Fact] - public async Task Connect_throws_when_host_rejects() - { - await using var loop = new IpcLoopback(); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - - var server = Task.Run(() => ServerLoopAsync(loop.ServerSide, async (kind, body, writer) => - { - if (kind == FocasMessageKind.OpenSessionRequest) - { - await writer.WriteAsync(FocasMessageKind.OpenSessionResponse, - new OpenSessionResponse { Success = false, Error = "unreachable", ErrorCode = "EW_SOCKET" }, cts.Token); - } - }, cts.Token)); - - var ipc = await FocasIpcClient.ConnectAsync(loop.ClientSide, Secret, cts.Token); - var client = new IpcFocasClient(ipc); - await Should.ThrowAsync(async () => - await client.ConnectAsync(new FocasHostAddress("10.0.0.1", 8193), TimeSpan.FromSeconds(1), cts.Token)); - - cts.Cancel(); - try { await server; } catch { } - } - - [Fact] - public async Task Read_sends_ReadRequest_and_decodes_response() - { - await using var loop = new IpcLoopback(); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - - ReadRequest? received = null; - var server = Task.Run(() => ServerLoopAsync(loop.ServerSide, async (kind, body, writer) => - { - switch (kind) - { - case FocasMessageKind.OpenSessionRequest: - await writer.WriteAsync(FocasMessageKind.OpenSessionResponse, - new OpenSessionResponse { Success = true, SessionId = 1 }, cts.Token); - break; - case FocasMessageKind.ReadRequest: - received = MessagePackSerializer.Deserialize(body); - await writer.WriteAsync(FocasMessageKind.ReadResponse, - new ReadResponse - { - Success = true, - StatusCode = 0, - ValueBytes = MessagePackSerializer.Serialize((int)12345), - ValueTypeCode = FocasDataTypeCode.Int32, - }, cts.Token); - break; - } - }, cts.Token)); - - var ipc = await FocasIpcClient.ConnectAsync(loop.ClientSide, Secret, cts.Token); - var client = new IpcFocasClient(ipc); - await client.ConnectAsync(new FocasHostAddress("h", 8193), TimeSpan.FromSeconds(1), cts.Token); - - var addr = new FocasAddress(FocasAreaKind.Parameter, null, 1815, null); - var (value, status) = await client.ReadAsync(addr, FocasDataType.Int32, cts.Token); - status.ShouldBe(0u); - value.ShouldBe(12345); - received!.Address.Number.ShouldBe(1815); - - cts.Cancel(); - try { await server; } catch { } - } - - [Fact] - public async Task Write_sends_WriteRequest_and_returns_status() - { - await using var loop = new IpcLoopback(); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - - var server = Task.Run(() => ServerLoopAsync(loop.ServerSide, async (kind, body, writer) => - { - switch (kind) - { - case FocasMessageKind.OpenSessionRequest: - await writer.WriteAsync(FocasMessageKind.OpenSessionResponse, - new OpenSessionResponse { Success = true, SessionId = 1 }, cts.Token); - break; - case FocasMessageKind.WriteRequest: - var req = MessagePackSerializer.Deserialize(body); - MessagePackSerializer.Deserialize(req.ValueBytes!).ShouldBe(3.14); - await writer.WriteAsync(FocasMessageKind.WriteResponse, - new WriteResponse { Success = true, StatusCode = 0 }, cts.Token); - break; - } - }, cts.Token)); - - var ipc = await FocasIpcClient.ConnectAsync(loop.ClientSide, Secret, cts.Token); - var client = new IpcFocasClient(ipc); - await client.ConnectAsync(new FocasHostAddress("h", 8193), TimeSpan.FromSeconds(1), cts.Token); - - var status = await client.WriteAsync(new FocasAddress(FocasAreaKind.Macro, null, 500, null), - FocasDataType.Float64, 3.14, cts.Token); - status.ShouldBe(0u); - - cts.Cancel(); - try { await server; } catch { } - } - - [Fact] - public async Task Write_pmc_bit_sends_first_class_RMW_frame() - { - await using var loop = new IpcLoopback(); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - - PmcBitWriteRequest? received = null; - var server = Task.Run(() => ServerLoopAsync(loop.ServerSide, async (kind, body, writer) => - { - switch (kind) - { - case FocasMessageKind.OpenSessionRequest: - await writer.WriteAsync(FocasMessageKind.OpenSessionResponse, - new OpenSessionResponse { Success = true, SessionId = 1 }, cts.Token); - break; - case FocasMessageKind.PmcBitWriteRequest: - received = MessagePackSerializer.Deserialize(body); - await writer.WriteAsync(FocasMessageKind.PmcBitWriteResponse, - new PmcBitWriteResponse { Success = true, StatusCode = 0 }, cts.Token); - break; - } - }, cts.Token)); - - var ipc = await FocasIpcClient.ConnectAsync(loop.ClientSide, Secret, cts.Token); - var client = new IpcFocasClient(ipc); - await client.ConnectAsync(new FocasHostAddress("h", 8193), TimeSpan.FromSeconds(1), cts.Token); - - var addr = new FocasAddress(FocasAreaKind.Pmc, "R", 100, BitIndex: 5); - var status = await client.WriteAsync(addr, FocasDataType.Bit, true, cts.Token); - status.ShouldBe(0u); - received.ShouldNotBeNull(); - received!.BitIndex.ShouldBe(5); - received.Value.ShouldBeTrue(); - received.Address.PmcLetter.ShouldBe("R"); - - cts.Cancel(); - try { await server; } catch { } - } - - [Fact] - public async Task Probe_round_trips_health_from_host() - { - await using var loop = new IpcLoopback(); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - - var server = Task.Run(() => ServerLoopAsync(loop.ServerSide, async (kind, body, writer) => - { - switch (kind) - { - case FocasMessageKind.OpenSessionRequest: - await writer.WriteAsync(FocasMessageKind.OpenSessionResponse, - new OpenSessionResponse { Success = true, SessionId = 1 }, cts.Token); - break; - case FocasMessageKind.ProbeRequest: - await writer.WriteAsync(FocasMessageKind.ProbeResponse, - new ProbeResponse { Healthy = true, ObservedAtUtcUnixMs = 1_700_000_000_000 }, cts.Token); - break; - } - }, cts.Token)); - - var ipc = await FocasIpcClient.ConnectAsync(loop.ClientSide, Secret, cts.Token); - var client = new IpcFocasClient(ipc); - await client.ConnectAsync(new FocasHostAddress("h", 8193), TimeSpan.FromSeconds(1), cts.Token); - (await client.ProbeAsync(cts.Token)).ShouldBeTrue(); - - cts.Cancel(); - try { await server; } catch { } - } - - [Fact] - public async Task Error_response_from_host_surfaces_as_FocasIpcException() - { - await using var loop = new IpcLoopback(); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - - var server = Task.Run(() => ServerLoopAsync(loop.ServerSide, async (kind, body, writer) => - { - await writer.WriteAsync(FocasMessageKind.ErrorResponse, - new ErrorResponse { Code = "backend-exception", Message = "simulated" }, cts.Token); - }, cts.Token)); - - var ipc = await FocasIpcClient.ConnectAsync(loop.ClientSide, Secret, cts.Token); - var client = new IpcFocasClient(ipc); - var ex = await Should.ThrowAsync(async () => - await client.ConnectAsync(new FocasHostAddress("h", 8193), TimeSpan.FromSeconds(1), cts.Token)); - ex.Code.ShouldBe("backend-exception"); - - cts.Cancel(); - try { await server; } catch { } - } -} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/IpcLoopback.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/IpcLoopback.cs deleted file mode 100644 index 9936cdd..0000000 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/IpcLoopback.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.IO.Pipelines; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests; - -/// -/// Bidirectional in-memory stream pair for IPC tests. Two System.IO.Pipelines.Pipe -/// instances — one per direction — exposed as endpoints -/// via PipeReader.AsStream / PipeWriter.AsStream. Lets the test set up a -/// FocasIpcClient on one end and a minimal fake server loop on the other without -/// standing up a real named pipe. -/// -internal sealed class IpcLoopback : IAsyncDisposable -{ - public Stream ClientSide { get; } - public Stream ServerSide { get; } - - public IpcLoopback() - { - var clientToServer = new Pipe(); - var serverToClient = new Pipe(); - - ClientSide = new DuplexPipeStream(serverToClient.Reader.AsStream(), clientToServer.Writer.AsStream()); - ServerSide = new DuplexPipeStream(clientToServer.Reader.AsStream(), serverToClient.Writer.AsStream()); - } - - public async ValueTask DisposeAsync() - { - await ClientSide.DisposeAsync(); - await ServerSide.DisposeAsync(); - } - - private sealed class DuplexPipeStream(Stream read, Stream write) : Stream - { - public override bool CanRead => true; - public override bool CanWrite => true; - public override bool CanSeek => false; - public override long Length => throw new NotSupportedException(); - public override long Position - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } - - public override int Read(byte[] buffer, int offset, int count) => read.Read(buffer, offset, count); - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct) => - read.ReadAsync(buffer, offset, count, ct); - public override ValueTask ReadAsync(Memory buffer, CancellationToken ct = default) => - read.ReadAsync(buffer, ct); - - public override void Write(byte[] buffer, int offset, int count) => write.Write(buffer, offset, count); - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken ct) => - write.WriteAsync(buffer, offset, count, ct); - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken ct = default) => - write.WriteAsync(buffer, ct); - - public override void Flush() => write.Flush(); - public override Task FlushAsync(CancellationToken ct) => write.FlushAsync(ct); - - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - public override void SetLength(long value) => throw new NotSupportedException(); - - protected override void Dispose(bool disposing) - { - if (disposing) - { - read.Dispose(); - write.Dispose(); - } - base.Dispose(disposing); - } - } -} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/PostMortemReaderCompatibilityTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/PostMortemReaderCompatibilityTests.cs deleted file mode 100644 index 622ef96..0000000 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/PostMortemReaderCompatibilityTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.IO.MemoryMappedFiles; -using System.Text; -using Shouldly; -using Xunit; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Supervisor; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests; - -/// -/// The Proxy-side must read the Host's MMF format -/// (magic 'OFPC', 256-byte entries). This test writes a hand-crafted file that mimics -/// the Host's layout exactly + asserts the reader decodes it correctly. Keeps the two -/// codebases in lockstep on the wire format without needing to reference the net48 -/// Host assembly from the net10 test project. -/// -[Trait("Category", "Unit")] -public sealed class PostMortemReaderCompatibilityTests : IDisposable -{ - private readonly string _tempPath = Path.Combine(Path.GetTempPath(), $"focas-mmf-compat-{Guid.NewGuid():N}.bin"); - - public void Dispose() - { - if (File.Exists(_tempPath)) File.Delete(_tempPath); - } - - [Fact] - public void Reader_parses_host_format_and_returns_entries_in_oldest_first_order() - { - const int magic = 0x4F465043; - const int capacity = 5; - const int headerBytes = 16; - const int entryBytes = 256; - const int messageOffset = 16; - var fileBytes = headerBytes + capacity * entryBytes; - - using (var fs = new FileStream(_tempPath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read)) - { - fs.SetLength(fileBytes); - using var mmf = MemoryMappedFile.CreateFromFile(fs, null, fileBytes, - MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, leaveOpen: false); - using var acc = mmf.CreateViewAccessor(0, fileBytes, MemoryMappedFileAccess.ReadWrite); - acc.Write(0, magic); - acc.Write(4, 1); - acc.Write(8, capacity); - acc.Write(12, 2); // writeIndex — next write would land at slot 2 - - void WriteEntry(int slot, long ts, long op, string msg) - { - var offset = headerBytes + slot * entryBytes; - acc.Write(offset + 0, ts); - acc.Write(offset + 8, op); - var bytes = Encoding.UTF8.GetBytes(msg); - acc.WriteArray(offset + messageOffset, bytes, 0, bytes.Length); - acc.Write(offset + messageOffset + bytes.Length, (byte)0); - } - - WriteEntry(0, 100, 1, "op-a"); - WriteEntry(1, 200, 2, "op-b"); - // Slots 2,3 unwritten (ts=0) — reader must skip. - WriteEntry(4, 50, 9, "old-wrapped"); - } - - var entries = new PostMortemReader(_tempPath).ReadAll(); - entries.Length.ShouldBe(3); - // writeIndex=2 means the ring walk starts at slot 2, so iteration order is 2→3→4→0→1. - // Slots 2 and 3 are empty; 4 yields "old-wrapped"; then 0="op-a", 1="op-b". - entries[0].Message.ShouldBe("old-wrapped"); - entries[1].Message.ShouldBe("op-a"); - entries[2].Message.ShouldBe("op-b"); - } - - [Fact] - public void Reader_returns_empty_when_file_missing() - { - new PostMortemReader(_tempPath + "-does-not-exist").ReadAll().ShouldBeEmpty(); - } - - [Fact] - public void Reader_returns_empty_when_magic_mismatches() - { - File.WriteAllBytes(_tempPath, new byte[1024]); - new PostMortemReader(_tempPath).ReadAll().ShouldBeEmpty(); - } -} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/SupervisorTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/SupervisorTests.cs deleted file mode 100644 index 9694e7f..0000000 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/SupervisorTests.cs +++ /dev/null @@ -1,249 +0,0 @@ -using Shouldly; -using Xunit; -using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Supervisor; - -namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests; - -[Trait("Category", "Unit")] -public sealed class BackoffTests -{ - [Fact] - public void Default_sequence_is_5s_15s_60s_then_clamped() - { - var b = new Backoff(); - b.Next().ShouldBe(TimeSpan.FromSeconds(5)); - b.Next().ShouldBe(TimeSpan.FromSeconds(15)); - b.Next().ShouldBe(TimeSpan.FromSeconds(60)); - b.Next().ShouldBe(TimeSpan.FromSeconds(60)); - b.Next().ShouldBe(TimeSpan.FromSeconds(60)); - } - - [Fact] - public void RecordStableRun_resets_the_ladder_to_the_start() - { - var b = new Backoff(); - b.Next(); b.Next(); - b.AttemptIndex.ShouldBe(2); - b.RecordStableRun(); - b.AttemptIndex.ShouldBe(0); - b.Next().ShouldBe(TimeSpan.FromSeconds(5)); - } -} - -[Trait("Category", "Unit")] -public sealed class CircuitBreakerTests -{ - [Fact] - public void Allows_crashes_below_threshold() - { - var b = new CircuitBreaker(); - var now = DateTime.UtcNow; - b.TryRecordCrash(now, out _).ShouldBeTrue(); - b.TryRecordCrash(now.AddSeconds(1), out _).ShouldBeTrue(); - b.TryRecordCrash(now.AddSeconds(2), out _).ShouldBeTrue(); - b.StickyAlertActive.ShouldBeFalse(); - } - - [Fact] - public void Opens_when_exceeding_threshold_in_window() - { - var b = new CircuitBreaker(); - var now = DateTime.UtcNow; - b.TryRecordCrash(now, out _); - b.TryRecordCrash(now.AddSeconds(1), out _); - b.TryRecordCrash(now.AddSeconds(2), out _); - b.TryRecordCrash(now.AddSeconds(3), out var cooldown).ShouldBeFalse(); - cooldown.ShouldBe(TimeSpan.FromHours(1)); - b.StickyAlertActive.ShouldBeTrue(); - } - - [Fact] - public void Escalates_cooldown_after_second_open() - { - var b = new CircuitBreaker(); - var t0 = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc); - // First burst — 4 crashes opens breaker with 1h cooldown. - for (var i = 0; i < 4; i++) b.TryRecordCrash(t0.AddSeconds(i), out _); - b.StickyAlertActive.ShouldBeTrue(); - - // Wait past cooldown. The first crash after cooldown-elapsed resets _openSinceUtc and - // bumps escalation level; the next 3 crashes then re-open with the escalated 4h cooldown. - b.TryRecordCrash(t0.AddHours(1).AddMinutes(1), out _); - var t1 = t0.AddHours(1).AddMinutes(1).AddSeconds(1); - b.TryRecordCrash(t1, out _); - b.TryRecordCrash(t1.AddSeconds(1), out _); - b.TryRecordCrash(t1.AddSeconds(2), out var cooldown).ShouldBeFalse(); - cooldown.ShouldBe(TimeSpan.FromHours(4)); - } - - [Fact] - public void ManualReset_clears_everything() - { - var b = new CircuitBreaker(); - var now = DateTime.UtcNow; - for (var i = 0; i < 5; i++) b.TryRecordCrash(now.AddSeconds(i), out _); - b.StickyAlertActive.ShouldBeTrue(); - b.ManualReset(); - b.StickyAlertActive.ShouldBeFalse(); - b.TryRecordCrash(now.AddSeconds(10), out _).ShouldBeTrue(); - } -} - -[Trait("Category", "Unit")] -public sealed class HeartbeatMonitorTests -{ - [Fact] - public void Three_consecutive_misses_declares_dead() - { - var m = new HeartbeatMonitor(); - m.RecordMiss().ShouldBeFalse(); - m.RecordMiss().ShouldBeFalse(); - m.RecordMiss().ShouldBeTrue(); - } - - [Fact] - public void Ack_resets_the_miss_counter() - { - var m = new HeartbeatMonitor(); - m.RecordMiss(); m.RecordMiss(); - m.ConsecutiveMisses.ShouldBe(2); - m.RecordAck(DateTime.UtcNow); - m.ConsecutiveMisses.ShouldBe(0); - } -} - -[Trait("Category", "Unit")] -public sealed class FocasHostSupervisorTests -{ - private sealed class FakeLauncher : IHostProcessLauncher - { - public int LaunchAttempts { get; private set; } - public int Terminations { get; private set; } - public Queue> Plan { get; } = new(); - public bool IsProcessAlive { get; set; } - - public Task LaunchAsync(CancellationToken ct) - { - LaunchAttempts++; - if (Plan.Count == 0) throw new InvalidOperationException("FakeLauncher plan exhausted"); - var next = Plan.Dequeue()(); - IsProcessAlive = true; - return Task.FromResult(next); - } - - public Task TerminateAsync(CancellationToken ct) - { - Terminations++; - IsProcessAlive = false; - return Task.CompletedTask; - } - } - - private sealed class StubFocasClient : IFocasClient - { - public bool IsConnected => true; - public Task ConnectAsync(FocasHostAddress address, TimeSpan timeout, CancellationToken ct) => Task.CompletedTask; - public Task<(object? value, uint status)> ReadAsync(FocasAddress a, FocasDataType t, CancellationToken ct) => - Task.FromResult<(object?, uint)>((0, 0)); - public Task WriteAsync(FocasAddress a, FocasDataType t, object? v, CancellationToken ct) => Task.FromResult(0u); - public Task ProbeAsync(CancellationToken ct) => Task.FromResult(true); - public void Dispose() { } - } - - [Fact] - public async Task GetOrLaunch_returns_client_on_first_success() - { - var launcher = new FakeLauncher(); - launcher.Plan.Enqueue(() => new StubFocasClient()); - var supervisor = new FocasHostSupervisor(launcher); - var client = await supervisor.GetOrLaunchAsync(TestContext.Current.CancellationToken); - client.ShouldNotBeNull(); - launcher.LaunchAttempts.ShouldBe(1); - } - - [Fact] - public async Task GetOrLaunch_retries_after_transient_failure_with_backoff() - { - var launcher = new FakeLauncher(); - launcher.Plan.Enqueue(() => throw new TimeoutException("pipe not ready")); - launcher.Plan.Enqueue(() => new StubFocasClient()); - - var backoff = new Backoff([TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(20)]); - var supervisor = new FocasHostSupervisor(launcher, backoff); - - var unavailableMessages = new List(); - supervisor.OnUnavailable += m => unavailableMessages.Add(m); - - var client = await supervisor.GetOrLaunchAsync(TestContext.Current.CancellationToken); - client.ShouldNotBeNull(); - launcher.LaunchAttempts.ShouldBe(2); - unavailableMessages.Count.ShouldBe(1); - unavailableMessages[0].ShouldContain("launch-failed"); - } - - [Fact] - public async Task Repeated_launch_failures_open_breaker_and_surface_InvalidOperation() - { - var launcher = new FakeLauncher(); - for (var i = 0; i < 10; i++) - launcher.Plan.Enqueue(() => throw new InvalidOperationException("simulated host refused")); - - var supervisor = new FocasHostSupervisor( - launcher, - backoff: new Backoff([TimeSpan.FromMilliseconds(1)]), - breaker: new CircuitBreaker { CrashesAllowedPerWindow = 2, Window = TimeSpan.FromMinutes(5) }); - - var ex = await Should.ThrowAsync(async () => - await supervisor.GetOrLaunchAsync(TestContext.Current.CancellationToken)); - ex.Message.ShouldContain("circuit breaker"); - supervisor.StickyAlertActive.ShouldBeTrue(); - } - - [Fact] - public async Task NotifyHostDeadAsync_terminates_current_and_fans_out_unavailable() - { - var launcher = new FakeLauncher(); - launcher.Plan.Enqueue(() => new StubFocasClient()); - var supervisor = new FocasHostSupervisor(launcher); - - var messages = new List(); - supervisor.OnUnavailable += m => messages.Add(m); - await supervisor.GetOrLaunchAsync(TestContext.Current.CancellationToken); - - await supervisor.NotifyHostDeadAsync("heartbeat-loss", TestContext.Current.CancellationToken); - - launcher.Terminations.ShouldBe(1); - messages.ShouldContain("heartbeat-loss"); - supervisor.ObservedCrashes.ShouldBe(1); - } - - [Fact] - public async Task AcknowledgeAndReset_clears_sticky_alert() - { - var launcher = new FakeLauncher(); - for (var i = 0; i < 10; i++) - launcher.Plan.Enqueue(() => throw new InvalidOperationException("refused")); - var supervisor = new FocasHostSupervisor( - launcher, - backoff: new Backoff([TimeSpan.FromMilliseconds(1)]), - breaker: new CircuitBreaker { CrashesAllowedPerWindow = 1 }); - - try { await supervisor.GetOrLaunchAsync(TestContext.Current.CancellationToken); } catch { } - supervisor.StickyAlertActive.ShouldBeTrue(); - - supervisor.AcknowledgeAndReset(); - supervisor.StickyAlertActive.ShouldBeFalse(); - } - - [Fact] - public async Task Dispose_terminates_host_process() - { - var launcher = new FakeLauncher(); - launcher.Plan.Enqueue(() => new StubFocasClient()); - var supervisor = new FocasHostSupervisor(launcher); - await supervisor.GetOrLaunchAsync(TestContext.Current.CancellationToken); - - supervisor.Dispose(); - launcher.Terminations.ShouldBe(1); - } -}