using System;
using MxGateway.Contracts.Proto;
namespace MxGateway.Worker.MxAccess;
///
/// Sink for native MxAccess alarm transitions. Bridges
/// to the worker's event queue,
/// producing messages via
/// .
///
///
///
/// owns the wire-up: it constructs the
/// consumer/sink pair, calls to propagate the
/// session id, and subscribes the consumer's
/// event
/// so each decoded transition reaches .
/// The method here carries only the session id —
/// the alarm path needs no COM-event subscription of its own because
/// the consumer already polls and raises transition events. The
/// captured payload schema is described in
/// docs/AlarmClientDiscovery.md "Option A — captured".
///
///
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;
// The alarm path needs no COM-event subscription here: the wnwrap
// consumer is polled by the worker's STA and raises transition events
// that AlarmDispatcher routes into EnqueueTransition. Attach only
// records the session id stamped onto every emitted MxEvent.
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.",
},
});
}
}
}