#Requires -Version 7.0 <# .SYNOPSIS End-to-end CLI test for the AB Legacy (PCCC) driver. .DESCRIPTION Runs against libplctag's ab_server PCCC Docker fixture (one of the slc500 / micrologix / plc5 compose profiles) or real SLC / MicroLogix / PLC-5 hardware. Five assertions: probe / driver-loopback / forward- bridge / reverse-bridge / subscribe-sees-change. ab_server enforces a non-empty CIP routing path (`/1,0`) before the PCCC dispatcher runs; real hardware accepts an empty path. The default $Gateway uses `/1,0` for the Docker fixture — pass `-Gateway "ab://host:44818/"` when pointing at a real SLC 5/05 / MicroLogix / PLC-5. .PARAMETER Gateway ab://host[:port]/cip-path. Default ab://127.0.0.1/1,0 (Docker fixture). .PARAMETER PlcType Slc500 / MicroLogix / Plc5 / LogixPccc (default Slc500). .PARAMETER Address PCCC address to exercise. Default N7:5. .PARAMETER OpcUaUrl OtOpcUa server endpoint. .PARAMETER BridgeNodeId NodeId at which the server publishes the Address. #> param( [string]$Gateway = "ab://127.0.0.1/1,0", [string]$PlcType = "Slc500", [string]$Address = "N7:5", [string]$OpcUaUrl = "opc.tcp://localhost:4840", [Parameter(Mandatory)] [string]$BridgeNodeId ) $ErrorActionPreference = "Stop" . "$PSScriptRoot/_common.ps1" # ab_server PCCC works; the earlier "upstream-broken" gate is gone. The only # caveat: libplctag's ab_server rejects empty CIP paths, so $Gateway must # carry a non-empty path segment (default /1,0). Real SLC/PLC-5 hardware # accepts an empty path — use `ab://host:44818/` when pointing at real PLCs. $abLegacyCli = Get-CliInvocation ` -ProjectFolder "src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Cli" ` -ExeName "otopcua-ablegacy-cli" $opcUaCli = Get-CliInvocation ` -ProjectFolder "src/ZB.MOM.WW.OtOpcUa.Client.CLI" ` -ExeName "otopcua-cli" $commonAbLegacy = @("-g", $Gateway, "-P", $PlcType) $results = @() $results += Test-Probe ` -Cli $abLegacyCli ` -ProbeArgs (@("probe") + $commonAbLegacy + @("-a", "N7:0")) $writeValue = Get-Random -Minimum 1 -Maximum 9999 $results += Test-DriverLoopback ` -Cli $abLegacyCli ` -WriteArgs (@("write") + $commonAbLegacy + @("-a", $Address, "-t", "Int", "-v", $writeValue)) ` -ReadArgs (@("read") + $commonAbLegacy + @("-a", $Address, "-t", "Int")) ` -ExpectedValue "$writeValue" $bridgeValue = Get-Random -Minimum 10000 -Maximum 19999 $results += Test-ServerBridge ` -DriverCli $abLegacyCli ` -DriverWriteArgs (@("write") + $commonAbLegacy + @("-a", $Address, "-t", "Int", "-v", $bridgeValue)) ` -OpcUaCli $opcUaCli ` -OpcUaUrl $OpcUaUrl ` -OpcUaNodeId $BridgeNodeId ` -ExpectedValue "$bridgeValue" $reverseValue = Get-Random -Minimum 20000 -Maximum 29999 $results += Test-OpcUaWriteBridge ` -OpcUaCli $opcUaCli ` -OpcUaUrl $OpcUaUrl ` -OpcUaNodeId $BridgeNodeId ` -DriverCli $abLegacyCli ` -DriverReadArgs (@("read") + $commonAbLegacy + @("-a", $Address, "-t", "Int")) ` -ExpectedValue "$reverseValue" $subValue = Get-Random -Minimum 30000 -Maximum 32766 $results += Test-SubscribeSeesChange ` -OpcUaCli $opcUaCli ` -OpcUaUrl $OpcUaUrl ` -OpcUaNodeId $BridgeNodeId ` -DriverCli $abLegacyCli ` -DriverWriteArgs (@("write") + $commonAbLegacy + @("-a", $Address, "-t", "Int", "-v", $subValue)) ` -ExpectedValue "$subValue" # PR 7 — contiguous array read smoke. The default `--tag=N7[120]` in the Docker # fixture's docker-compose.yml has plenty of room for `,10`; against real hardware # the seeded N7 file just needs at least 10 words. Asserts the CLI exits 0 (the # driver issued one PCCC frame for the whole block) — the per-element values are # whatever the device currently holds. Write-Header "Array contiguous read" $arrayResult = Invoke-Cli -Cli $abLegacyCli ` -Args (@("read") + $commonAbLegacy + @("-a", "N7:0,10", "-t", "Int")) if ($arrayResult.ExitCode -eq 0) { Write-Pass "array read N7:0,10 succeeded" $results += @{ Passed = $true } } else { Write-Fail "array read N7:0,10 exit=$($arrayResult.ExitCode)" Write-Host $arrayResult.Output $results += @{ Passed = $false; Reason = "array read exit $($arrayResult.ExitCode)" } } # PR 8 — deadband subscribe assertion. Subscribe with --deadband-absolute 5, # write three small deltas (each within the 5-unit deadband), assert exactly # one notification fires (the first-seen sample). The fourth write breaks # above the threshold and the subscription should fire again. Write-Header "Deadband subscribe (--deadband-absolute 5)" $baseValue = Get-Random -Minimum 100 -Maximum 200 & $abLegacyCli.File @($abLegacyCli.PrefixArgs) ` @("write") + $commonAbLegacy + @("-a", $Address, "-t", "Int", "-v", $baseValue) | Out-Null $subscribeProc = Start-Process -FilePath $abLegacyCli.File ` -ArgumentList ($abLegacyCli.PrefixArgs + @("subscribe") + $commonAbLegacy ` + @("-a", $Address, "-t", "Int", "-i", "200", "--deadband-absolute", "5")) ` -PassThru -RedirectStandardOutput "$env:TEMP/ablegacy-deadband.out" ` -RedirectStandardError "$env:TEMP/ablegacy-deadband.err" Start-Sleep -Seconds 2 # Three small deltas within deadband. & $abLegacyCli.File @($abLegacyCli.PrefixArgs) ` @("write") + $commonAbLegacy + @("-a", $Address, "-t", "Int", "-v", ($baseValue + 1)) | Out-Null Start-Sleep -Milliseconds 500 & $abLegacyCli.File @($abLegacyCli.PrefixArgs) ` @("write") + $commonAbLegacy + @("-a", $Address, "-t", "Int", "-v", ($baseValue + 2)) | Out-Null Start-Sleep -Milliseconds 500 & $abLegacyCli.File @($abLegacyCli.PrefixArgs) ` @("write") + $commonAbLegacy + @("-a", $Address, "-t", "Int", "-v", ($baseValue + 3)) | Out-Null Start-Sleep -Milliseconds 500 Stop-Process -Id $subscribeProc.Id -Force -ErrorAction SilentlyContinue $subscribeOutput = Get-Content "$env:TEMP/ablegacy-deadband.out" -ErrorAction SilentlyContinue # Count `=` lines (the SubscribeCommand format prints one per OnDataChange). Expect exactly 1 # (the first-seen sample at $baseValue) — none of the +1/+2/+3 deltas crosses the 5 absolute. $notifyLines = @($subscribeOutput | Where-Object { $_ -match " = " }) if ($notifyLines.Count -eq 1) { Write-Pass "deadband subscribe emitted 1 notification (initial only); 3 sub-threshold writes suppressed" $results += @{ Passed = $true } } else { Write-Fail "deadband subscribe expected 1 notification; got $($notifyLines.Count)" Write-Host ($subscribeOutput -join "`n") $results += @{ Passed = $false; Reason = "deadband notify count $($notifyLines.Count)" } } Write-Summary -Title "AB Legacy e2e" -Results $results if ($results | Where-Object { -not $_.Passed }) { exit 1 }