Auto: abcip-4.1 — per-tag scan rate / scan group bucketing

Closes #238
This commit is contained in:
Joseph Doherty
2026-04-26 02:15:50 -04:00
parent e5c38a5a0e
commit b45713622f
8 changed files with 761 additions and 7 deletions

View File

@@ -30,6 +30,15 @@
.PARAMETER BridgeNodeId
NodeId at which the server publishes the TagPath.
.PARAMETER FastBridgeNodeId
Optional NodeId for a Tag declared with ScanRateMs <= 100. When supplied
alongside SlowBridgeNodeId the script runs the per-tag scan-rate assertion
(PR abcip-4.1).
.PARAMETER SlowBridgeNodeId
Optional NodeId for a Tag declared with ScanRateMs >= 1000. Pair with
FastBridgeNodeId to enable the scan-rate assertion.
#>
param(
@@ -37,7 +46,9 @@ param(
[string]$Family = "ControlLogix",
[string]$TagPath = "TestDINT",
[string]$OpcUaUrl = "opc.tcp://localhost:4840",
[Parameter(Mandatory)] [string]$BridgeNodeId
[Parameter(Mandatory)] [string]$BridgeNodeId,
[string]$FastBridgeNodeId,
[string]$SlowBridgeNodeId
)
$ErrorActionPreference = "Stop"
@@ -119,5 +130,57 @@ if ($Family -ne "Micro800") {
}
}
# PR abcip-4.1 — per-tag scan-rate divergence assertion. Runs only when both fast + slow
# NodeIds are wired; otherwise this knob is skipped on the existing single-NodeId fixture.
# The assertion is "fast bucket sees > 5x as many notifications as slow bucket" — the
# unit + integration tests cover the bucketing math, this just proves the multi-rate split
# survives end-to-end through the OPC UA server's Subscription / MonitoredItem path.
if ($FastBridgeNodeId -and $SlowBridgeNodeId) {
Write-Header "Per-tag scan rate (FastBridge=$FastBridgeNodeId, SlowBridge=$SlowBridgeNodeId)"
$duration = 8
$fastOut = New-TemporaryFile
$slowOut = New-TemporaryFile
$fastErr = New-TemporaryFile
$slowErr = New-TemporaryFile
$fastArgs = @($opcUaCli.PrefixArgs) + @("subscribe", "-u", $OpcUaUrl, "-n", $FastBridgeNodeId, "-i", "100", "--duration", "$duration")
$slowArgs = @($opcUaCli.PrefixArgs) + @("subscribe", "-u", $OpcUaUrl, "-n", $SlowBridgeNodeId, "-i", "1000", "--duration", "$duration")
$fastProc = Start-Process -FilePath $opcUaCli.File -ArgumentList $fastArgs `
-NoNewWindow -PassThru `
-RedirectStandardOutput $fastOut.FullName `
-RedirectStandardError $fastErr.FullName
$slowProc = Start-Process -FilePath $opcUaCli.File -ArgumentList $slowArgs `
-NoNewWindow -PassThru `
-RedirectStandardOutput $slowOut.FullName `
-RedirectStandardError $slowErr.FullName
Start-Sleep -Seconds 2
# Drive a single PLC change so even stable tags get *one* notification during the window
# (initial-data push + 1 change). The cadence assertion below relies on the fast tag
# accumulating sampling-interval-driven events even between explicit changes.
$tickValue = Get-Random -Minimum 50000 -Maximum 59999
$writeArgs = @("write") + $commonAbCip + @("-t", $TagPath, "--type", "DInt", "-v", $tickValue)
& $abcipCli.Exe @($abcipCli.Args + $writeArgs) | Out-Null
$fastProc.WaitForExit(($duration + 5) * 1000) | Out-Null
$slowProc.WaitForExit(($duration + 5) * 1000) | Out-Null
if (-not $fastProc.HasExited) { Stop-Process -Id $fastProc.Id -Force }
if (-not $slowProc.HasExited) { Stop-Process -Id $slowProc.Id -Force }
$fastText = (Get-Content $fastOut.FullName -Raw) + (Get-Content $fastErr.FullName -Raw)
$slowText = (Get-Content $slowOut.FullName -Raw) + (Get-Content $slowErr.FullName -Raw)
Remove-Item $fastOut.FullName, $slowOut.FullName, $fastErr.FullName, $slowErr.FullName -ErrorAction SilentlyContinue
# Each data-change line matches `=\s*<value>\s*(<status>)` per Test-SubscribeSeesChange.
$fastMatches = ([regex]::Matches($fastText, "=\s*\S+\s*\(")).Count
$slowMatches = ([regex]::Matches($slowText, "=\s*\S+\s*\(")).Count
$passed = ($fastMatches -ge 5) -and ($fastMatches -gt ($slowMatches * 5))
$detail = if ($passed) {
"fast=$fastMatches notifications vs slow=$slowMatches (>5x ratio achieved)"
} else {
"fast=$fastMatches slow=$slowMatches — expected fast > slow*5"
}
$results += [PSCustomObject]@{ Name = "PerTagScanRate"; Passed = $passed; Detail = $detail }
}
Write-Summary -Title "AB CIP e2e" -Results $results
if ($results | Where-Object { -not $_.Passed }) { exit 1 }