Snapshot of Milestone 1b completion (equipment-tag live values across OpcUaClient / protocol drivers / Galaxy, read + write) and the open follow-ups, for cross-session continuity.
7.9 KiB
Current status — Galaxy standard-driver effort
Updated 2026-06-13.
Where we are
Phase A (Galaxy = standard Equipment-kind driver) is DONE, merged to master (c3c56172), pushed, and live-verified.
GalaxyMxGatewayis now an ordinary Equipment-kind driver. Retired: theSystemPlatformNamespaceKind+ its mirror (Phase7Applier.MaterialiseGalaxyTags, the composer/artifactGalaxyTagscontract +GalaxyTagPlan), the byte-parity|| DriverType=="GalaxyMxGateway"exception clauses, the authzNodeHierarchyKind.SystemPlatform+PermissionTrie.WalkSystemPlatform, and the entire alias-tag/relay AdminUI machinery.- A Galaxy point is now a plain equipment
Tag{EquipmentId set, DriverInstanceId=GalaxyMxGateway, TagConfig={"FullName":"tag.attr"}}, authored through the standardTagModal+ the "Browse Galaxy" picker. - Forward-only data-only migration
CleanupSystemPlatformNamespacesdeletes orphaned SystemPlatform config +NodeAclgrants and releasesExternalIdReservations. KeptUX_Namespace_Cluster_Kind(Simulated kind still reserved). - Gate: 1009 unit tests green + full docker-dev live
/run(migration applied clean, server boots on migrated DB, alias UI gone, namespace picker drops SystemPlatform, Galaxy driver created under Equipment, Galaxy tag authored via the new picker).
Design: docs/plans/2026-06-12-galaxy-standard-driver-design.md (3-phase). Phase A plan: docs/plans/2026-06-12-galaxy-standard-driver-phase-a.md (+ .tasks.json).
The rest of the plan (all NOT started)
✅ Milestone 1 — Equipment-tag live-value DELIVERY (the FullName→NodeId router) — DONE, merged c4435e4f, pushed
Design docs/plans/2026-06-13-equipment-tag-live-values-design.md; plan …-plan.md (+ .tasks.json). Mirrored the proven VirtualTagHostActor._nodeIdByVtag pattern for DRIVER values:
- NEW
ZB.MOM.WW.OtOpcUa.Commons.OpcUa.EquipmentNodeIds= single source of truth for the folder-scoped NodeId formula (Variable(eq,fp,name)); repointedPhase7Applier+VirtualTagHostActorto it (byte-identical, kills the drift risk). DriverInstanceIdadded toAttributeValuePublished(the router key — FullName is unique only WITHIN a driver instance).DriverHostActorbuilds a(DriverInstanceId, FullName) → NodeId[]map each apply (inPushDesiredSubscriptions, viaEquipmentNodeIds.Variable) and resolves it inForwardToMux, emitting the existingAttributeValueUpdateper resolved NodeId (1→many handled; no-match drops+debug-logs). NOOpcUaPublishActorchange.- Gate: build 0 errors; 322 tests (incl. 3 Akka.TestKit tests driving the full router through a real deploy/apply); final integration review = READY TO MERGE (end-to-end NodeId coherence confirmed). Live
/run: the deploy applied + the variable materialised at the exact router NodeId + SubscribeBulk pushed refs — the materialisation + router wiring is confirmed live.
🔴 Milestone 1b — Per-driver "actually PUBLISH a value" gaps (NOT started — the router has nothing to route until these land; live value could NOT be shown in dev for this reason)
The router is correct, but no dev driver currently publishes an equipment-tag value, so a variable still won't show data until the owning driver's publish path is wired. Three orthogonal, independent gaps (each its own follow-up):
- OpcUaClient — MISSING FACTORY (real bug).
src/Server/.../Host/Drivers/DriverFactoryBootstrap.cs:98-107registers AbCip/AbLegacy/FOCAS/Galaxy/Modbus/S7/TwinCAT but OMITS OpcUaClient — and theOpcUaClientdriver project has no…FactoryExtensionsclass at all (onlyOpcUaClientDriver.cs+OpcUaClientDriverProbe.cs). So OpcUaClient is always stubbed ("no factory for driver type OpcUaClient … falling back to stub") — completely non-functional. Fix: add anOpcUaClientDriverFactoryExtensions(CreateInstance parsingdriverConfigJson→OpcUaClientDriverOptions+new OpcUaClientDriver(options, id, …), mirroringModbusDriverFactoryExtensions) + register it in the bootstrap. This is the cheapest path to a live value (OpcUaClient is direct-ref:TagConfig.FullName="ns=3;s=FastUInt1"→ subscribes to opc-plc → publishes → router delivers). opc-plc sim is up atopc.tcp://10.100.0.35:50000(ns=3;s=FastUInt1). - Protocol drivers (Modbus/S7/AbCip/…) — equipment-tag↔driver-subscription linkage. The Modbus driver subscribes by tag NAME from
DriverConfig.Tags(_tagsByName, keyed byt.Name), but an equipment tag'sTagConfig(region/address, viaModbusTagConfigModel) carries no top-levelFullName, soExtractTagFullNamefalls back to the raw JSON blob → never matches aDriverConfig.Tagsname →SubscribeAsyncsilently skips it (if (!_tagsByName.TryGetValue(...)) continue;). The equipment tag's register config is never connected to the driver. This is the bigger "equipment tags work for protocol drivers" gap (needs the deploy to build the driver's tag table from equipment tags, or the equipment-tag FullName to address a register directly). - Galaxy — needs a reachable mxaccessgw. Direct-ref (FullName = MxAccess ref), so the router would deliver — but the gateway must be up +
ApiKeySecretRefresolvable (devMX_API_KEYunset).
Recommended next: build the OpcUaClient factory (small, fixes a real bug, gives the first live value). Then the protocol-driver linkage (larger).
🟡 Phase B — Native alarms on the equipment-tag path (NOT started)
Galaxy implements IAlarmSource (native MXAccess alarms), but that wiring lived only on the retired SystemPlatform-mirror path (GenericDriverNodeManager registers an alarm sink per MarkAsAlarmCondition variable keyed by FullName, routes OnAlarmEvent.SourceNodeId to it). After Phase A, native Galaxy alarms have no materialization path until this is ported.
Work: teach MaterialiseEquipmentTags/OtOpcUaNodeManager to register an alarm-condition sink for IsAlarm equipment tags (keyed by FullName) and subscribe the owning driver's IAlarmSource, routing OnAlarmEvent.SourceNodeId == FullName → sink. Reuse the existing Part 9 AlarmConditionState materialization + alarm-commands/ack plumbing (scripted alarms already use it). Extract GenericDriverNodeManager's forwarder so both paths share it. Mirror its unsubscribe-before-rewalk for IRediscoverable redeploys.
🟢 Phase C — Server-side historian / HistoryRead (NOT started)
Driver-agnostic server-side history backend over the existing Wonderware Historian reader (Historian.Wonderware.Client already implements IHistoryProvider). No mxaccessgw history RPC needed.
Work: add an OPC UA HistoryRead service override in OtOpcUaNodeManager (none exists today) → a router that, for an IsHistorized node, resolves a historian tagname (default = the tag's driver FullName, optional override) and calls the registered IHistorianDataSource. Apply DriverAttributeInfo.IsHistorized → UA Historizing + AccessLevel.HistoryRead at materialization (currently hardcoded false). Config: a server Historian section (Null default; Wonderware when configured), mirroring the existing AlarmHistorian pattern. Works for any IsHistorized tag, not just Galaxy.
Dev rig state
docker-dev runs locally (Docker 29.4.0 on this Mac); login disabled in dev (Security__Auth__DisableLogin: "true"); central UI at http://localhost:9200, SQL at localhost:14330 (sa/OtOpcUa!Dev123). Currently on the Phase-A build with the clean-break migration applied. Verification artifacts left in place: MAIN-galaxy-eq Galaxy driver + GalaxyTestTag under the nw-uns Equipment namespace. Prior 1,391-tag Galaxy config is restorable from OtOpcUa-prePhaseA-20260612-224908.bak (in the SQL volume /var/opt/mssql/backup/). Rebuild central from a branch: docker compose -f docker-dev/docker-compose.yml up -d --build migrator central-1 central-2.