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