@@ -0,0 +1,87 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// PR abcip-4.2 — end-to-end coverage for write-deadband / write-on-change suppression
|
||||
/// against a running <c>ab_server</c>. Drives a 5-write jittery sequence with
|
||||
/// <c>WriteDeadband=1.0</c> and asserts the driver's <c>AbCip.WritesSuppressed</c>
|
||||
/// diagnostics counter reflects the expected number of suppressions. Wire-level write
|
||||
/// count isn't directly observable in <c>ab_server</c> (no admin shim for "tell me how
|
||||
/// many CIP writes you got"), so the suppression evidence is the driver's own counter
|
||||
/// plus the final read confirming the last passed-through value reached the PLC.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Unit coverage in <see cref="ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests.AbCipWriteDeadbandTests"/>
|
||||
/// proves the suppression math against an in-process fake. This test exercises the full
|
||||
/// libplctag stack so a regression in how the driver wires its coalescer to the real wire
|
||||
/// path shows up here.
|
||||
/// </remarks>
|
||||
[Trait("Category", "Integration")]
|
||||
[Trait("Requires", "AbServer")]
|
||||
public sealed class AbCipWriteDeadbandTests
|
||||
{
|
||||
[AbServerFact]
|
||||
public async Task Jittery_setpoints_within_deadband_dont_reach_the_wire()
|
||||
{
|
||||
var profile = KnownProfiles.ControlLogix;
|
||||
var fixture = new AbServerFixture(profile);
|
||||
await fixture.InitializeAsync();
|
||||
try
|
||||
{
|
||||
var deviceUri = $"ab://127.0.0.1:{fixture.Port}/1,0";
|
||||
var drv = new AbCipDriver(new AbCipDriverOptions
|
||||
{
|
||||
Devices = [new AbCipDeviceOptions(deviceUri, profile.Family)],
|
||||
// ab_server seeds TestDINT — drive integer setpoints with a 1.0 deadband so
|
||||
// values that differ by 0 are suppressed. Real-world deadbanding usually
|
||||
// targets REAL setpoints; integer here is fine because the suppression rule
|
||||
// looks at the boxed numeric value, not the on-wire encoding.
|
||||
Tags = [new AbCipTagDefinition("Setpoint", deviceUri, "TestDINT",
|
||||
AbCipDataType.DInt, WriteDeadband: 1.0)],
|
||||
Timeout = TimeSpan.FromSeconds(5),
|
||||
}, "drv-write-deadband-smoke");
|
||||
|
||||
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
||||
|
||||
// Five-write jittery sequence: 100, 100, 100, 102, 102.
|
||||
// - 100 (first): passes (no prior).
|
||||
// - 100, 100: suppressed (|0| < 1.0).
|
||||
// - 102: passes (|2| ≥ 1.0).
|
||||
// - 102: suppressed (|0| < 1.0).
|
||||
// Expected: 2 wire writes, 3 suppressions.
|
||||
var inputs = new[] { 100, 100, 100, 102, 102 };
|
||||
foreach (var v in inputs)
|
||||
{
|
||||
var results = await drv.WriteAsync(
|
||||
[new WriteRequest("Setpoint", v)],
|
||||
TestContext.Current.CancellationToken);
|
||||
results.Single().StatusCode.ShouldBe(AbCipStatusMapper.Good,
|
||||
"every write — suppressed or not — must surface as Good to the OPC UA client");
|
||||
}
|
||||
|
||||
drv.WriteCoalescer.TotalWritesSuppressed.ShouldBe(3);
|
||||
drv.WriteCoalescer.TotalWritesPassedThrough.ShouldBe(2);
|
||||
|
||||
// Final readback proves the last passed-through value (102) made it to the PLC.
|
||||
var readback = await drv.ReadAsync(["Setpoint"], TestContext.Current.CancellationToken);
|
||||
readback.Single().StatusCode.ShouldBe(AbCipStatusMapper.Good);
|
||||
Convert.ToInt32(readback.Single().Value).ShouldBe(102);
|
||||
|
||||
// Diagnostics counters are also reflected through GetHealth — the path the
|
||||
// driver-diagnostics RPC + Admin UI consume.
|
||||
var diag = drv.GetHealth().DiagnosticsOrEmpty;
|
||||
diag["AbCip.WritesSuppressed"].ShouldBe(3);
|
||||
diag["AbCip.WritesPassedThrough"].ShouldBe(2);
|
||||
|
||||
await drv.ShutdownAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await fixture.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user