103 lines
5.1 KiB
C#
103 lines
5.1 KiB
C#
using System.Collections.Concurrent;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests;
|
|
|
|
/// <summary>
|
|
/// PR 5.1 / #316 — end-to-end alarm-integration scaffold against a live TwinCAT 3 XAR
|
|
/// runtime. Skipped via <see cref="TwinCATFactAttribute"/> when the VM isn't reachable.
|
|
/// Proves the driver's <see cref="IAlarmSource"/> bridge surfaces TC3 EventLogger events
|
|
/// when the PLC's <c>FB_AlarmHarness</c> calls <c>FB_TcLogEvent</c>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para><b>Required VM project state</b> (see <c>TwinCatProject/README.md</c>
|
|
/// §"Alarm scenarios"):</para>
|
|
/// <list type="bullet">
|
|
/// <item>GVL <c>GVL_Alarms</c> with <c>bTriggerEvent : BOOL</c> + <c>bAcked : BOOL</c>.
|
|
/// A test harness flips <c>bTriggerEvent</c> from <c>FALSE</c> to <c>TRUE</c> via the
|
|
/// driver's <c>WriteAsync</c> path; <c>FB_AlarmHarness</c> sees the rising edge +
|
|
/// calls <c>FB_TcLogEvent</c> on the PLC side.</item>
|
|
/// <item>FB <c>FB_AlarmHarness</c> wired into <c>MAIN</c> that calls <c>FB_TcLogEvent</c>
|
|
/// with a configured event class GUID + severity + source string when
|
|
/// <c>GVL_Alarms.bTriggerEvent</c> rises.</item>
|
|
/// </list>
|
|
/// <para><b>Decode caveat</b> — Beckhoff doesn't ship a managed wrapper for
|
|
/// <c>TcEventLogger</c> in <c>Beckhoff.TwinCAT.Ads</c> v6, so the production
|
|
/// gate is best-effort and several event fields may surface as <c>"Unknown"</c>.
|
|
/// This test asserts the bridge fires + the event has non-empty content; field-level
|
|
/// decode tightening lands on a follow-up PR (see
|
|
/// <c>docs/v3/twincat-eventlogger-spike.md</c>).</para>
|
|
/// </remarks>
|
|
[Collection("TwinCATXar")]
|
|
[Trait("Category", "Integration")]
|
|
[Trait("Simulator", "TwinCAT-XAR")]
|
|
public sealed class TwinCATAlarmIntegrationTests(TwinCATXarFixture sim)
|
|
{
|
|
[TwinCATFact]
|
|
public async Task Driver_raises_alarm_event_when_PLC_logs_event()
|
|
{
|
|
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
|
|
|
|
// Fixture-side state is documented in TwinCatProject/README.md §"Alarm scenarios".
|
|
// The harness is currently a build-only placeholder — once the GVL + FB_AlarmHarness
|
|
// ship, replace the Skip below with the live-trigger flow:
|
|
// 1. Init driver with EnableAlarms=true + GVL_Alarms.bTriggerEvent declared as a
|
|
// writable BOOL tag.
|
|
// 2. SubscribeAlarmsAsync([], ct).
|
|
// 3. WriteAsync to flip bTriggerEvent from FALSE to TRUE — the PLC's
|
|
// FB_AlarmHarness sees the rising edge + calls FB_TcLogEvent.
|
|
// 4. Assert OnAlarmEvent fires within ~5s with a non-empty Source + Message.
|
|
Assert.Skip(
|
|
"PR 5.1 / #316 — alarm-integration build-only scaffold. The GVL_Alarms + " +
|
|
"FB_AlarmHarness fixture hasn't been authored on the XAR project yet (build-time " +
|
|
"stubs ship under TwinCatProject/PLC; live trigger lands once the XAR project " +
|
|
"imports them). Until then this test self-skips even when the runtime is up.");
|
|
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Build the AMS options with <see cref="TwinCATDriverOptions.EnableAlarms"/> on so
|
|
/// the driver instantiates the alarm source. Mirrors the smoke-test option builder
|
|
/// but flips the alarm gate on; once the live fixture lands, the test body above
|
|
/// calls this helper directly.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
|
"Style", "IDE0051", Justification = "Used by the live test body once the fixture ships.")]
|
|
private static TwinCATDriverOptions BuildAlarmOptions(TwinCATXarFixture sim) => new()
|
|
{
|
|
Devices = [new TwinCATDeviceOptions(
|
|
HostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
|
|
DeviceName: $"xar-{sim.TargetNetId}:{sim.AmsPort}")],
|
|
Tags =
|
|
[
|
|
// bTriggerEvent rises FALSE→TRUE to fire the PLC-side LogEvent call.
|
|
new TwinCATTagDefinition(
|
|
Name: "AlarmTrigger",
|
|
DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
|
|
SymbolPath: "GVL_Alarms.bTriggerEvent",
|
|
DataType: TwinCATDataType.Bool,
|
|
Writable: true),
|
|
],
|
|
Probe = new TwinCATProbeOptions { Enabled = false },
|
|
EnableAlarms = true,
|
|
};
|
|
|
|
/// <summary>
|
|
/// Helper kept for parity with the live test body once the fixture ships — collects
|
|
/// <see cref="IAlarmSource.OnAlarmEvent"/> off the driver into a queue caller can
|
|
/// drain after the trigger flip.
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
|
"Style", "IDE0051", Justification = "Used by the live test body once the fixture ships.")]
|
|
private static ConcurrentQueue<AlarmEventArgs> WireAlarmCollector(IAlarmSource src)
|
|
{
|
|
var q = new ConcurrentQueue<AlarmEventArgs>();
|
|
src.OnAlarmEvent += (_, e) => q.Enqueue(e);
|
|
return q;
|
|
}
|
|
}
|