Files
lmxopcua/scripts/e2e/test-opcuaclient.ps1
2026-04-26 00:24:24 -04:00

147 lines
6.0 KiB
PowerShell

#Requires -Version 7.0
<#
.SYNOPSIS
End-to-end CLI test for the OPC UA Client (gateway) driver bridged through
the OtOpcUa server. Stages: probe, read, subscribe, topology-change.
.DESCRIPTION
The OPC UA Client driver reads from an upstream OPC UA server (default:
Microsoft's opc-plc simulator on opc.tcp://localhost:50000) and re-exposes
its address space through the local OtOpcUa server. This script drives
the bridged path end-to-end via `otopcua-cli`.
Four stages:
1. Probe — otopcua-cli connect succeeds against the OtOpcUa
server; confirms the gateway is up.
2. Bridged read — otopcua-cli read on the bridged NodeId returns a
Good value with a non-null payload; proves the
IReadable.ReadAsync path round-trips through the
driver to the upstream simulator.
3. Subscribe — otopcua-cli subscribe observes a data change within
N seconds (opc-plc's StepUp ticks once per second by
default, so this should always see a change).
4. Topology change — assert the auto-reimport-on-ModelChangeEvent path
is wired up. We can't easily fire a real upstream
model change without elevated opc-plc access, so
this stage prints the option settings + asserts the
driver's diagnostic surface reflects WatchModelChanges
is enabled (or skips with INFO when the upstream
doesn't expose ModelChangeEventType).
Requires:
- a running OtOpcUa server whose config DB has an OpcUaClient
DriverInstance bound to opc-plc (or another upstream server)
- the upstream OPC UA simulator reachable at $UpstreamUrl
- a Tag bridged from upstream NodeId $UpstreamNodeId to local
$BridgedNodeId
.PARAMETER OpcUaUrl
Endpoint URL of the OtOpcUa server. Default opc.tcp://localhost:4840.
.PARAMETER UpstreamUrl
Endpoint URL of the upstream OPC UA server (for documentation; the bridge
itself is wired in the OtOpcUa server config). Default opc.tcp://localhost:50000.
.PARAMETER BridgedNodeId
Local NodeId the OtOpcUa server exposes for the upstream tag. Required —
set per your server config (e.g. 'ns=2;s=/warsaw/opc-plc/StepUp').
.PARAMETER UpstreamNodeId
The upstream NodeId being bridged (informational only; default
'ns=3;s=StepUp' which is opc-plc's monotonically-increasing UInt32).
.PARAMETER ChangeWaitSec
How long the subscribe stage waits for a data-change. Default 10s.
.EXAMPLE
.\test-opcuaclient.ps1 -BridgedNodeId "ns=2;s=/warsaw/opc-plc/StepUp"
#>
param(
[string]$OpcUaUrl = "opc.tcp://localhost:4840",
[string]$UpstreamUrl = "opc.tcp://localhost:50000",
[Parameter(Mandatory)] [string]$BridgedNodeId,
[string]$UpstreamNodeId = "ns=3;s=StepUp",
[int]$ChangeWaitSec = 10
)
$ErrorActionPreference = "Stop"
. "$PSScriptRoot/_common.ps1"
$opcUaCli = Get-CliInvocation `
-ProjectFolder "src/ZB.MOM.WW.OtOpcUa.Client.CLI" `
-ExeName "otopcua-cli"
$results = @()
# Stage 1: probe
$results += Test-Probe `
-Name "OpcUaClient probe" `
-Cmd $opcUaCli `
-Args @("connect", "-u", $OpcUaUrl)
# Stage 2: bridged read
$results += Test-Probe `
-Name "OpcUaClient bridged read" `
-Cmd $opcUaCli `
-Args @("read", "-u", $OpcUaUrl, "-n", $BridgedNodeId)
# Stage 3: subscribe-sees-change
Write-Host "[INFO] Subscribing to $BridgedNodeId for ${ChangeWaitSec}s..."
$subResults = & $opcUaCli.Cmd @($opcUaCli.Args + @(
"subscribe", "-u", $OpcUaUrl, "-n", $BridgedNodeId,
"-i", "500", "--duration", "$ChangeWaitSec"))
if ($LASTEXITCODE -eq 0 -and $subResults -match "DataChange|StepUp|value=") {
$results += [pscustomobject]@{ Stage = "Subscribe-sees-change"; Status = "PASS" }
} else {
$results += [pscustomobject]@{ Stage = "Subscribe-sees-change"; Status = "FAIL" }
}
# Stage 4: topology change (auto-reimport on ModelChangeEvent)
#
# The OPC UA Client driver subscribes to BaseModelChangeEventType on the
# upstream Server node (i=2253) at the end of InitializeAsync, then debounces
# events over OpcUaClientDriverOptions.ModelChangeDebounce (default 5s) and
# triggers ReinitializeAsync.
#
# Driving a real upstream ModelChangeEvent from outside the simulator is
# upstream-specific:
# - opc-plc: invoke OpcPlc.AddSlowNode via OPC UA Call (requires a session
# directly to opc-plc, not via the gateway, since the gateway exposes
# mirrored read/write paths only for variables — methods are mirrored
# under PR-9 but call permissions on the simulator's namespace may
# not allow downstream invocation).
# - production server: deploy a topology-change to the upstream server +
# observe the local re-import.
#
# This stage is therefore documentation-only by default. Set
# $env:OPCUACLIENT_TOPOLOGY_TRIGGER_CMD to a command that drives a real
# topology change on the upstream and we'll execute it + wait for the
# debounced re-import.
$triggerCmd = $env:OPCUACLIENT_TOPOLOGY_TRIGGER_CMD
if ($triggerCmd) {
Write-Host "[INFO] Driving topology change via: $triggerCmd"
& cmd.exe /c $triggerCmd
Start-Sleep -Seconds 8 # debounce window + re-import duration
# After re-import the bridged node should still be readable (or, if
# the upstream removed the node, the read should return BadNodeIdUnknown).
# Either way the gateway must remain healthy.
$results += Test-Probe `
-Name "Topology-change re-read" `
-Cmd $opcUaCli `
-Args @("read", "-u", $OpcUaUrl, "-n", $BridgedNodeId)
} else {
Write-Host "[INFO] Topology-change stage skipped (set OPCUACLIENT_TOPOLOGY_TRIGGER_CMD to drive a real upstream model change)."
$results += [pscustomobject]@{ Stage = "Topology-change"; Status = "SKIP" }
}
Write-Host ""
Write-Host "=== test-opcuaclient.ps1 results ==="
$results | Format-Table -AutoSize
$failed = $results | Where-Object { $_.Status -eq "FAIL" }
if ($failed) {
exit 1
}
exit 0