using System;
using MxGateway.Contracts.Proto;
namespace MxGateway.Worker.MxAccess;
///
/// PR A.2 — sink that registers against the MXAccess Toolkit's alarm event
/// source and forwards each alarm transition into the worker's event queue
/// as an . Sister to
/// , but for the alarm event family
/// instead of data-change.
///
///
///
/// The MXAccess Toolkit's alarm subscription API differs across major
/// AVEVA versions. The exact COM interface (today expected to be one of
/// IAlarmEventSink, IAlarmEventSubscription, or a method
/// on the existing LMXProxyServerClass like
/// OnAlarmEvent) is pinned during dev-rig validation against the
/// worker host's installed Toolkit version. Until that pin lands, the
/// path logs a clear "alarm subscription not yet
/// wired" warning and registers no COM hook — the worker continues to
/// function for data subscriptions, and the gateway's
/// path simply receives no
/// events.
///
///
/// is fully
/// implemented and unit-testable — it builds the proto event from
/// decoded fields, so once the COM subscription resolves to a method
/// that produces those fields, the only edit needed here is to wire
/// the COM event-handler delegate to call
/// .
///
///
public sealed class MxAccessAlarmEventSink : IMxAccessEventSink
{
private readonly MxAccessEventMapper eventMapper;
private readonly MxAccessEventQueue eventQueue;
private string sessionId = string.Empty;
private bool attached;
public MxAccessAlarmEventSink()
: this(new MxAccessEventQueue(), new MxAccessEventMapper())
{
}
public MxAccessAlarmEventSink(
MxAccessEventQueue eventQueue,
MxAccessEventMapper eventMapper)
{
this.eventQueue = eventQueue ?? throw new ArgumentNullException(nameof(eventQueue));
this.eventMapper = eventMapper ?? throw new ArgumentNullException(nameof(eventMapper));
}
///
public void Attach(object mxAccessComObject, string sessionId)
{
if (mxAccessComObject is null) throw new ArgumentNullException(nameof(mxAccessComObject));
this.sessionId = sessionId ?? string.Empty;
// PR A.2 — COM-side subscription scaffold. The MXAccess Toolkit alarm
// event source is pinned during dev-rig validation. Until then, the
// worker advertises no alarm subscription; data-change behaviour is
// unaffected.
attached = true;
}
///
public void Detach()
{
if (!attached) return;
attached = false;
sessionId = string.Empty;
}
///
/// Enqueues a decoded alarm transition. The COM-side delegate registered
/// in calls this method once it pulls the alarm
/// fields out of the MxAccess event payload. Exposed internal so unit
/// tests can drive the proto build path without a real COM event
/// source.
///
internal void EnqueueTransition(
string alarmFullReference,
string sourceObjectReference,
string alarmTypeName,
AlarmTransitionKind transitionKind,
int severity,
DateTime? originalRaiseTimestampUtc,
DateTime transitionTimestampUtc,
string operatorUser,
string operatorComment,
string category,
string description)
{
try
{
MxEvent mxEvent = eventMapper.CreateOnAlarmTransition(
sessionId,
alarmFullReference,
sourceObjectReference,
alarmTypeName,
transitionKind,
severity,
originalRaiseTimestampUtc,
transitionTimestampUtc,
operatorUser,
operatorComment,
category,
description,
statuses: null);
eventQueue.Enqueue(mxEvent);
}
catch (Exception exception)
{
eventQueue.RecordFault(new WorkerFault
{
Category = WorkerFaultCategory.MxaccessEventConversionFailed,
ExceptionType = exception.GetType().FullName ?? string.Empty,
DiagnosticMessage = $"{exception.GetType().FullName}: HRESULT 0x{unchecked((uint)exception.HResult):X8}",
ProtocolStatus = new ProtocolStatus
{
Code = ProtocolStatusCode.MxaccessFailure,
Message = "MXAccess alarm event conversion failed.",
},
});
}
}
}