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.", }, }); } } }