Files
scadaproj/otopcua-uns-loader
Joseph Doherty fd34e25cb1 feat(uns-loader): verify-equipment — recursive Equipment UNS tree browse + leaf count
browse_summary assumes the flat 2-level Galaxy hierarchy; the Equipment tree is deep
(Area/Line/Equipment/[FolderPath]/Signal). Add browse_tree (recursive leaf descent) + a
verify-equipment subcommand that reports/asserts the leaf signal count (--expect N), for
verifying OtOpcUa equipment-namespace structure materialisation. Smoke-tested against a live
:4840 (40 folders / 396 leaf signals).
2026-06-06 15:25:17 -04:00
..

otopcua-uns-loader

A reloadable populate-and-verify tool for the OtOpcUa galaxy Unified Namespace. Recreates a UNS load grounded in the real AVEVA Galaxy DEV hierarchy (the 40 TestMachine instances) and verifies it streams live values on OPC UA — so you can rebuild the OtOpcUa docker-dev instance and get the namespace back with one populate + one deploy click.

What it loads

One SystemPlatform Tag per (machine, signal) bound to the existing GalaxyMxGateway driver (MAIN-galaxy-mxgw). Each tag's FolderPath is the Galaxy object and its Name the attribute, so the materialised OPC UA variable OtOpcUa/<machine>/<signal> has a NodeId equal to the MXAccess reference the driver subscribes to — which is what makes the value go live.

Signals mirrored per machine (only those the instance actually has): the $TestMachine process UDAs — TestChangingInt, TestHistoryValue, TestDouble/Float/Duration/DateTime, ProtectedValue(1), TestAlarm001..003, InAlarm396 tags across 40 machines.

Every row carries the nw-mirror- TagId prefix, so clean removes exactly what the tool created (adopting any pre-existing seed row for the same ref).

Why a deploy click is in the middle

OtOpcUa applies config only from sealed Deployment snapshots, and the only way to seal one is the AdminUI "Deploy current configuration" button (http://localhost:9200/deployments) — there is no SQL/REST/CLI trigger (it's an in-cluster Akka operation). So the flow is:

populate ──SQL──▶ live config tables
                    │  (you click Deploy at :9200, sign in multi-role/password)
                    ▼
   driver applies ▶ materialises variables ▶ SubscribeBulk ▶ live values
                    │
verify ──OPC UA──▶ browse + read Good values on :4840

populate and clean print the reminder; verify --wait polls until the deploy lands.

Live values depend on the driver SubscribeBulk pass (OtOpcUa master ≥ commit c1ce583). On older builds variables materialise but stay BadWaitingForInitialData.

Setup

cd otopcua-uns-loader
python3 -m venv .venv
./.venv/bin/pip install -r requirements.txt

Use

./.venv/bin/python otopcua_uns.py generate   # build load-plan.json from galaxy-hierarchy.json
./.venv/bin/python otopcua_uns.py populate    # upsert the 396 mirror Tag rows (idempotent)
#   → open http://localhost:9200/deployments, sign in, click "Deploy current configuration"
./.venv/bin/python otopcua_uns.py verify --wait   # poll until live values are Good on :4840
./.venv/bin/python otopcua_uns.py status      # config-DB + address-space snapshot
./.venv/bin/python otopcua_uns.py clean       # remove all nw-mirror-* tags (then Deploy again)

Rebuild recovery (the point of the tool)

After the docker-dev instance is rebuilt (DB wiped):

  1. Ensure the schema + clusters + the MAIN-galaxy-mxgw driver exist (dotnet ef database update + the docker-dev cluster-seed; see the OtOpcUa docker-dev/README.md).
  2. populateDeploy at the AdminUI → verify --wait.

Troubleshooting

verify stays INCOMPLETE / deployment "Sealed" but drivers never applied it. If you recreate the admin/coordinator node (admin-a) around the same time you click Deploy, the dispatch broadcast can be lost — the deployment seals but NodeDeploymentState shows no row for the driver nodes, and the address space keeps the old content. Recover by: restart the driver nodes (docker restart otopcua-dev-driver-a-1 otopcua-dev-driver-b-1) so they cleanly re-subscribe, then Deploy again. If a no-op "NoChanges" blocks the re-deploy, delete the orphan sealed Deployment row that no node applied (it has no NodeDeploymentState children) so Deploy sees drift again.

A rebuilt/restarted node serves an empty address space until the next Deploy. On bootstrap a node recovers to its last-applied revision and does not re-materialise until a new deployment is dispatched — so after any node restart, click Deploy once (a config change bumps the revision) to repopulate + re-subscribe.

Configuration

Defaults target docker-dev; override via flags or env:

Flag Env Default
--sql-host/-port/-user/-password/-db OTOPCUA_SQL_* localhost:14330 sa / OtOpcUa!Dev123 / OtOpcUa
--opcua-endpoint OTOPCUA_OPCUA_ENDPOINT opc.tcp://localhost:4840
--driver OTOPCUA_GALAXY_DRIVER MAIN-galaxy-mxgw
--galaxy-json OTOPCUA_GALAXY_JSON ../galaxy-hierarchy.json
--deploy-url http://localhost:9200/deployments

Files

  • otopcua_uns.py — the CLI (generate / populate / verify / status / clean)
  • load-plan.json — generated load plan (machine → signal → MXAccess ref)
  • ../galaxy-hierarchy.json — the source of truth, pulled live from the gateway
  • requirements.txt, .venv/

Scope note — company-UNS shape

This tool loads the galaxy in its native hierarchy (OtOpcUa/TestMachine_NNN/<signal>), which is the only shape that can carry live Galaxy values: OtOpcUa forbids the GalaxyMxGateway driver in an Equipment namespace, so a custom Enterprise/Site/Area/Line/Equipment UNS (e.g. the Northwind model in ../company-uns.json) must be a separate Equipment namespace fed by an OpcUaClient driver + UnsMappingTable that remaps this mirror. That overlay is the designed next layer; ../company-uns.json already carries the area/line/equipment → galaxy-ref mapping it needs.