Files
lmxopcua/CLAUDE.md
Joseph Doherty d5a87c7467 PR 7.3 — Doc updates for v2 Galaxy backend (partial)
Forward-looking doc surface for the new in-process GalaxyDriver:

- CLAUDE.md gains a "v2 Galaxy backend" preamble at the top pointing
  readers at lmx_mxgw.md and docs/v2/Galaxy.Performance.md, and
  framing the rest of the doc as the still-accurate v1 Galaxy.Host
  description.
- New auto-memory entry project_galaxy_via_mxgateway.md captures the
  default-since-PR-7.1 status, perf surface entry points, and the
  soak validation knobs.

Intentionally deferred until PR 7.2 (parity-rig-validated):

- Removing the v1 description and rewriting the architecture section
  outright.
- Deleting mxaccess_documentation.md (still consumed by Galaxy.Host).
- Retiring memory entries for project_galaxy_host_service.md /
  project_galaxy_host_installed.md / project_aveva_platform_installed.md
  — those describe a stack that's still installed and in active use.
- Scrubbing Galaxy.Host references from docs/v2/dev-environment.md,
  docs/ServiceHosting.md, docs/Redundancy.md, docs/security.md.

All those changes presuppose the legacy stack is gone, which it isn't
yet. Re-open this PR's tail once the parity matrix in
docs/v2/Galaxy.ParityMatrix.md is fully green on a live rig.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 17:07:23 -04:00

11 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

v2 Galaxy backend (default since PR 7.1)

The OPC UA server is now a .NET 10 host. Galaxy access flows through the in-process GalaxyDriver (src/ZB.MOM.WW.OtOpcUa.Driver.Galaxy/) talking gRPC to a separate mxaccessgw gateway process — see lmx_mxgw.md for the design and docs/v2/Galaxy.Performance.md for the perf surface (tracing, metrics, soak). The legacy out-of-process Galaxy.Host (.NET 4.8 x86 + MXAccess COM, fronted by GalaxyProxyDriver) still ships and registers under DriverType "Galaxy" for parity testing — it retires in PR 7.2 once the parity matrix in docs/v2/Galaxy.ParityMatrix.md is fully green on the parity rig.

The v1 description below covers the Galaxy.Host backend that is now one of two backends; everything from "Project Goal" through the "Library Preferences" section is still accurate for that path.

Project Goal

Build an OPC UA server on .NET Framework 4.8 (32-bit) that exposes AVEVA System Platform (Wonderware) Galaxy tags via the MXAccess toolkit. The server mirrors the Galaxy object hierarchy as an OPC UA address space, translating between contained-name browse paths and tag-name runtime references.

Architecture Overview

Data Flow

  1. Galaxy Repository DB (ZB) — SQL Server database holding the deployed object hierarchy and attribute definitions. Queried at startup and on change detection to build/rebuild the OPC UA address space.
  2. MXAccess COM API — Runtime data access layer. Subscribes to Galaxy tag attributes for live read/write. Requires a dedicated STA thread with a Win32 message pump for COM callbacks.
  3. OPC UA Server — Exposes the hierarchy as browse nodes and attributes as variable nodes. Clients browse via contained names but reads/writes are translated to tag_name.AttributeName format for MXAccess.

Key Concept: Contained Name vs Tag Name

Galaxy objects have two names:

  • contained_name — human-readable name scoped to parent (used for OPC UA browse tree)
  • tag_name — globally unique system name (used for MXAccess read/write)

Example: browsing TestMachine_001/DelmiaReceiver/DownloadPath translates to MXAccess reference DelmiaReceiver_001.DownloadPath.

See gr/layout.md for the full mapping and target OPC UA structure.

Data Type Mapping

Galaxy mx_data_type values map to OPC UA types (Boolean, Int32, Float, Double, String, DateTime, etc.). Array attributes use ValueRank=1 with ArrayDimensions from the Galaxy attribute definition. Full mapping in gr/data_type_mapping.md.

Change Detection

Poll galaxy.time_of_last_deploy in the ZB database to detect redeployments, then rebuild the address space. See gr/build_layout_plan.md for the step-by-step plan.

Reference Implementation

An existing MXAccess client implementation is at: C:\Users\dohertj2\Desktop\scadalink-design\lmxproxy\src\ZB.MOM.WW.LmxProxy.Host

Key patterns from that codebase:

  • StaComThread — Dedicated STA thread with Win32 message pump (GetMessage/DispatchMessage loop). All MXAccess COM objects must be created and called on this thread. Uses PostThreadMessage(WM_APP) to marshal work items.
  • LMXProxyServer COM objectRegister(clientName) returns a connection handle. AddItem(handle, address) + AdviseSupervisory(handle, itemHandle) for subscriptions. OnDataChange/OnWriteComplete events for callbacks.
  • Reconnect — Stored subscriptions are replayed after reconnect. A probe tag subscription monitors connection health.
  • COM cleanupMarshal.ReleaseComObject() on disconnect. Event handlers must be unwired before unregister.

MXAccess Documentation

mxaccess_documentation.md in the project root contains the full ArchestrA MXAccess Toolkit User's Guide. Key API: ArchestrA.MxAccess namespace, LMXProxyServer class. The toolkit DLLs are in Program Files (x86)\ArchestrA\Framework\bin.

Galaxy Repository Database

Connection: sqlcmd -S localhost -d ZB -E (Windows Auth). See gr/connectioninfo.md.

The gr/ folder contains:

  • queries/ — SQL for hierarchy extraction, attribute lookup, and change detection
  • ddl/tables/ and ddl/views/ — Schema definitions
  • schema.md — Full table/view reference
  • build_layout_plan.md — Step-by-step plan for building the OPC UA address space from DB queries
  • gr/CLAUDE.md — Detailed guidance for working within the gr/ subfolder

Key tables: gobject (hierarchy/deployment), template_definition (object categories), dynamic_attribute (user-defined attributes), primitive_instance (primitive-to-attribute links), galaxy (change detection).

Build Commands

dotnet restore ZB.MOM.WW.OtOpcUa.slnx
dotnet build ZB.MOM.WW.OtOpcUa.slnx
dotnet test ZB.MOM.WW.OtOpcUa.slnx                          # all tests
dotnet test tests/ZB.MOM.WW.OtOpcUa.Tests                    # unit tests only
dotnet test tests/ZB.MOM.WW.OtOpcUa.IntegrationTests         # integration tests only
dotnet test --filter "FullyQualifiedName~MyTestClass.MyMethod"  # single test

Docker Workflow (driver fixtures + central SQL Server)

Migrated 2026-04-28: Docker config + host moved off this dev VM (DESKTOP-6JL3KKO) onto the shared Linux Docker host (DOCKER, 10.100.0.35) so the dev VM could shed WSL2/Hyper-V and have its GPU re-attached via ESXi passthrough. Docker Desktop is no longer installed here. All checked-in appsettings.json defaults, fixture-class default endpoints, and e2e-config.sample.json were rewritten to target 10.100.0.35. The driver fixture compose files under tests/.../Docker/docker-compose.yml now carry a project: lmxopcua label on every service. See docs/v2/dev-environment.md for the full rewrite (header dated 2026-04-28).

Docker workloads run on a shared Linux host at 10.100.0.35 — not on this VM. Stacks live at /opt/otopcua-<driver>/ on the host and carry the project=lmxopcua label so they're discoverable via docker ps --filter label=project=lmxopcua.

docker -H ssh://... does NOT work from this VM. Windows OpenSSH ↔ docker.exe stdio bridging hangs (docker system dial-stdio runs server-side but no API data flows). Use the helper below — it SSHes into the docker host and runs docker compose server-side.

Use lmxopcua-fix.ps1 (in ~/bin) to control fixtures from this VM:

lmxopcua-fix ls                            # list all lmxopcua-tagged containers on the host
lmxopcua-fix up   modbus      standard     # bring a profile up
lmxopcua-fix up   abcip       controllogix
lmxopcua-fix up   s7          s7_1500
lmxopcua-fix up   opcuaclient                # single-service stack, no profile arg
lmxopcua-fix down modbus                   # tear stack down
lmxopcua-fix logs modbus
lmxopcua-fix sync modbus                   # rsync this repo's tests/.../Docker/ → /opt/otopcua-modbus/

sync is the deployment step. When you edit a fixture's compose file or Dockerfile under tests/.../Docker/, run lmxopcua-fix sync <driver> to push the changes to the docker host before bringing the stack up. The repo files are the source of truth; /opt/otopcua-<driver>/ is a mirrored deployment.

Endpoints (defaults already point at the docker host):

  • SQL Server (always-on): 10.100.0.35,14330 — used by appsettings.json for ConfigDb.
  • Modbus: 10.100.0.35:5020 (MODBUS_SIM_ENDPOINT)
  • AB CIP: 10.100.0.35:44818 (AB_SERVER_ENDPOINT)
  • S7: 10.100.0.35:1102 (S7_SIM_ENDPOINT)
  • OPC UA reference (opc-plc): opc.tcp://10.100.0.35:50000 (OPCUA_SIM_ENDPOINT)

Override any endpoint via the env var to point at a real PLC. The local OtOpcUa server runs on this VM at opc.tcp://localhost:4840that's not on the docker host.

See docs/v2/dev-environment.md for the full inventory and rationale.

Build & Runtime Constraints

  • Language: C#, .NET Framework 4.8, x86 (32-bit) platform target — required for MXAccess COM interop
  • MXAccess requires a deployed ArchestrA Platform on the machine running the server
  • COM apartment: MXAccess objects must live on an STA thread with a message pump

Transport Security

The server supports configurable OPC UA transport security via the Security section in appsettings.json. Phase 1 profiles: None (default), Basic256Sha256-Sign, Basic256Sha256-SignAndEncrypt. Security profiles are resolved by SecurityProfileResolver at startup. The server certificate is always created even for None-only deployments because UserName token encryption depends on it. See docs/security.md for the full guide.

Redundancy

The server supports non-transparent warm/hot redundancy via the Redundancy section in appsettings.json. Two instances share the same Galaxy DB and MXAccess runtime but have unique ApplicationUri values. Each exposes RedundancySupport, ServerUriArray, and a dynamic ServiceLevel based on role and runtime health. The primary advertises a higher ServiceLevel than the secondary. See docs/Redundancy.md for the full guide.

LDAP Authentication

The server uses LDAP-based user authentication via the Authentication.Ldap section in appsettings.json. When enabled, credentials are validated by LDAP bind against a GLAuth server (installed at C:\publish\glauth\), and LDAP group membership maps to OPC UA permissions: ReadOnly (browse/read), WriteOperate (write FreeAccess/Operate attributes), WriteTune (write Tune attributes), WriteConfigure (write Configure attributes), AlarmAck (alarm acknowledgment). LdapUserAuthenticator (src/ZB.MOM.WW.OtOpcUa.Server/Security/LdapUserAuthenticator.cs) implements IUserAuthenticator. See docs/Security.md for the full guide and C:\publish\glauth\auth.md for LDAP user/group reference.

Library Preferences

  • Logging: Serilog with rolling daily file sink
  • Unit tests: xUnit + Shouldly for assertions
  • Service hosting (Server, Admin): .NET generic host with AddWindowsService (decision #30 — replaced TopShelf in v2; see src/ZB.MOM.WW.OtOpcUa.Server/OpcUaServerService.cs)
  • Service hosting (Galaxy.Host): plain console app wrapped by NSSM (.NET Framework 4.8 x86 — required by MXAccess COM bitness)
  • OPC UA: OPC Foundation UA .NET Standard stack (https://github.com/opcfoundation/ua-.netstandard) — NuGet: OPCFoundation.NetStandard.Opc.Ua.Server

OPC UA .NET Standard Documentation

Use the DeepWiki MCP (mcp__deepwiki) to query documentation for the OPC UA .NET Standard stack: https://deepwiki.com/OPCFoundation/UA-.NETStandard. Tools: read_wiki_structure, read_wiki_contents, and ask_question with repo OPCFoundation/UA-.NETStandard.

Testing

Use the Client CLI at src/ZB.MOM.WW.OtOpcUa.Client.CLI/ for manual testing against the running OPC UA server. Supports connect, read, write, browse, subscribe, historyread, alarms, and redundancy commands. See docs/Client.CLI.md for full documentation.

dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- connect -u opc.tcp://localhost:4840
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- browse -u opc.tcp://localhost:4840 -r -d 3
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- read -u opc.tcp://localhost:4840 -n "ns=2;s=SomeNode"
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- subscribe -u opc.tcp://localhost:4840 -n "ns=2;s=SomeNode" -i 500