fix(abcip): ALMA review round 1 + ALMD polls InAlarm (not InFaulted)

M1 drop-out is a no-row Unspecified+IsRestatement update (projector case); M2 ack judged over every declared
limit; L1 ActiveAcknowledged for an already-acked new limit; L2 Bad Acked/severity carried/skipped; L3 host
end-to-end test; L4 BOOL member-by-member tests + mixed ALMD/ALMA; L5 detection requires no plain Acked.
ALMD active bit is InAlarm per Rockwell's ALMD reference; InFaulted is input health.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-09 04:22:45 -04:00
parent b3838fcce9
commit 83d8de303c
13 changed files with 641 additions and 161 deletions
@@ -8,16 +8,16 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests.Emulate;
/// <summary>
/// Golden-box-tier ALMD alarm projection tests against Logix Emulate.
/// Promotes the feature-flagged ALMD projection (task #177) from unit-only coverage
/// (<c>AbCipAlarmProjectionTests</c> with faked InFaulted sequences) to end-to-end
/// (<c>AbCipAlarmProjectionTests</c> with faked InAlarm sequences) to end-to-end
/// wire-level coverage — Emulate runs the real ALMD instruction, with real
/// rising-edge semantics on <c>InFaulted</c> + <c>Ack</c>, so the driver's poll-based
/// rising-edge semantics on <c>InAlarm</c> + <c>Ack</c>, so the driver's poll-based
/// projection gets validated against the actual behaviour shops running FT View see.
/// </summary>
/// <remarks>
/// <para><b>Required Emulate project state</b> (see <c>LogixProject/README.md</c>):</para>
/// <list type="bullet">
/// <item>Controller-scope ALMD tag <c>HighTempAlarm</c> — a standard ALMD instruction
/// with default member set (<c>In</c>, <c>InFaulted</c>, <c>Acked</c>,
/// with default member set (<c>In</c>, <c>InAlarm</c>, <c>Acked</c>,
/// <c>Severity</c>, <c>Cfg_ProgTime</c>, …).</item>
/// <item>A periodic task that drives <c>HighTempAlarm.In</c> false→true→false at a
/// cadence the operator can script via a one-shot routine (e.g. a
@@ -64,7 +64,7 @@ public sealed class AbCipEmulateAlmdTests
TagPath: "HighTempAlarm",
DataType: AbCipDataType.Structure,
Members: [
new AbCipStructureMember("InFaulted", AbCipDataType.DInt),
new AbCipStructureMember("InAlarm", AbCipDataType.DInt),
new AbCipStructureMember("Acked", AbCipDataType.DInt),
new AbCipStructureMember("Severity", AbCipDataType.DInt),
new AbCipStructureMember("In", AbCipDataType.DInt),
@@ -92,7 +92,7 @@ public sealed class AbCipEmulateAlmdTests
["HighTempAlarm"], TestContext.Current.CancellationToken);
// Pulse the input bit the ladder watches, then wait for the driver's poll loop
// to see InFaulted rise + fire the raise event.
// to see InAlarm rise + fire the raise event.
_ = await drv.WriteAsync(
[new WriteRequest("SimulateAlarm", true)],
TestContext.Current.CancellationToken);