Auto: twincat-5.1 — IAlarmSource via TC3 EventLogger (gated, scaffold)

Closes #316
This commit is contained in:
Joseph Doherty
2026-04-26 11:13:24 -04:00
parent 3babfb8a99
commit c88e0b6bed
13 changed files with 1238 additions and 7 deletions
@@ -0,0 +1,94 @@
using CliFx.Attributes;
using CliFx.Infrastructure;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Cli.Commands;
/// <summary>
/// PR 5.1 / #316 — stream TC3 EventLogger alarms to the terminal until Ctrl+C.
/// Mirrors the OPC UA Client CLI <c>alarms</c> verb shape: subscribe + print every
/// incoming <see cref="AlarmEventArgs"/> with timestamp, source, severity, and
/// message text. Requires <c>EnableAlarms=true</c> on the driver — set via the
/// base options builder when the verb is selected.
/// </summary>
[Command("alarms", Description =
"Subscribe to TC3 EventLogger alarms via the driver's IAlarmSource bridge and " +
"stream events to stdout until Ctrl+C.")]
public sealed class AlarmsCommand : TwinCATCommandBase
{
[CommandOption("source", Description =
"Optional alarm source filter (matched case-insensitively against the event's " +
"Source field). Repeat the flag to match multiple sources; omit to subscribe to " +
"every event the EventLogger surfaces.")]
public IReadOnlyList<string> Sources { get; init; } = Array.Empty<string>();
public override async ValueTask ExecuteAsync(IConsole console)
{
ConfigureLogging();
var ct = console.RegisterCancellationHandler();
// Empty Tags + EnableAlarms=true builds a driver that opens only the alarm path.
// TwinCATDriverOptions is a regular class with init-only properties — rebuild
// the instance instead of using a record-style `with` clone.
var baseOptions = BuildOptions([]);
var options = new TwinCATDriverOptions
{
Devices = baseOptions.Devices,
Tags = baseOptions.Tags,
Probe = baseOptions.Probe,
Timeout = baseOptions.Timeout,
UseNativeNotifications = baseOptions.UseNativeNotifications,
EnableControllerBrowse = baseOptions.EnableControllerBrowse,
MaxArrayExpansion = baseOptions.MaxArrayExpansion,
EnableAlarms = true,
};
await using var driver = new TwinCATDriver(options, DriverInstanceId);
IAlarmSubscriptionHandle? handle = null;
try
{
await driver.InitializeAsync("{}", ct);
driver.OnAlarmEvent += (_, e) =>
{
var line =
$"[{e.SourceTimestampUtc:HH:mm:ss.fff}] " +
$"{e.SourceNodeId} " +
$"sev={e.Severity} " +
$"type={e.AlarmType} " +
$"cond={e.ConditionId} " +
$"\"{e.Message}\"";
console.Output.WriteLine(line);
};
handle = await driver.SubscribeAlarmsAsync(Sources, ct);
var filterDesc = Sources.Count == 0
? "all sources"
: $"sources [{string.Join(", ", Sources)}]";
await console.Output.WriteLineAsync(
$"Subscribed to TC3 EventLogger alarms on {AmsNetId}:{AmsPort} ({filterDesc}). Ctrl+C to stop.");
await console.Output.WriteLineAsync(
"Note: Beckhoff doesn't ship a managed TcEventLogger wrapper; the driver " +
"uses a best-effort decode of AMS port 110 notifications. Some fields may " +
"surface as 'Unknown' until a binary-protocol decoder lands.");
try
{
await Task.Delay(System.Threading.Timeout.InfiniteTimeSpan, ct);
}
catch (OperationCanceledException)
{
// Expected on Ctrl+C.
}
}
finally
{
if (handle is not null)
{
try { await driver.UnsubscribeAlarmsAsync(handle, CancellationToken.None); }
catch { /* teardown best-effort */ }
}
await driver.ShutdownAsync(CancellationToken.None);
}
}
}