worker: document MXAccess Toolkit alarm-API gap (A.2 follow-up) #114
@@ -4,33 +4,80 @@ using MxGateway.Contracts.Proto;
|
||||
namespace MxGateway.Worker.MxAccess;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="OnAlarmTransitionEvent"/>. Sister to
|
||||
/// <see cref="MxAccessBaseEventSink"/>, but for the alarm event family
|
||||
/// instead of data-change.
|
||||
/// PR A.2 sink intended to register against an MXAccess alarm event source
|
||||
/// and forward each alarm transition into the worker's event queue as an
|
||||
/// <see cref="OnAlarmTransitionEvent"/>. The mapper bridge is fully
|
||||
/// implemented + unit-tested via
|
||||
/// <see cref="MxAccessEventMapper.CreateOnAlarmTransition"/>; the
|
||||
/// <see cref="Attach"/> path is intentionally a no-op pending the
|
||||
/// architectural decision documented below.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The MXAccess Toolkit's alarm subscription API differs across major
|
||||
/// AVEVA versions. The exact COM interface (today expected to be one of
|
||||
/// <c>IAlarmEventSink</c>, <c>IAlarmEventSubscription</c>, or a method
|
||||
/// on the existing <c>LMXProxyServerClass</c> like
|
||||
/// <c>OnAlarmEvent</c>) is pinned during dev-rig validation against the
|
||||
/// worker host's installed Toolkit version. Until that pin lands, the
|
||||
/// <see cref="Attach"/> 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
|
||||
/// <see cref="MxEventFamily.OnAlarmTransition"/> path simply receives no
|
||||
/// events.
|
||||
/// <strong>2026-04-30 dev-rig finding:</strong> the MXAccess COM
|
||||
/// Toolkit installed at <c>C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll</c>
|
||||
/// does <strong>not</strong> expose any alarm event family. Reflection
|
||||
/// enumeration of the assembly (which exports a single COM interop
|
||||
/// module containing <c>ILMXProxyServerEvents</c> and
|
||||
/// <c>ILMXProxyServerEvents2</c>) confirms the only available events
|
||||
/// are <c>OnDataChange</c>, <c>OnWriteComplete</c>,
|
||||
/// <c>OperationComplete</c>, and <c>OnBufferedDataChange</c>. There is
|
||||
/// no <c>OnAlarmTransition</c>, no <c>IAlarmEventSink</c>, and no
|
||||
/// <c>Alarms</c> collection on the COM server.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="MxAccessEventMapper.CreateOnAlarmTransition"/> 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
|
||||
/// <see cref="EnqueueTransition"/>.
|
||||
/// AVEVA's separate alarm-subscription managed assemblies
|
||||
/// (<c>aaAlarmManagedClient.dll</c> under
|
||||
/// <c>InTouch\ViewAppFramework\Content\MA\</c>,
|
||||
/// <c>ArchestrAAlarmsAndEvents.SDK.Common.dll</c> under
|
||||
/// <c>Wonderware\Historian\x64\</c>) are present on this box but are
|
||||
/// <strong>x64-only</strong>; they cannot load into the worker process,
|
||||
/// which is x86 because of the MXAccess COM bitness constraint that
|
||||
/// the <c>mxaccessgw</c> architecture exists to isolate. Loading them
|
||||
/// in a separate x64 helper process would add meaningful operational
|
||||
/// complexity (a third process tier alongside worker + gateway) and is
|
||||
/// not in the current architecture.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <strong>Two paths forward — operator decision needed before the
|
||||
/// sink can be wired:</strong>
|
||||
/// </para>
|
||||
/// <list type="number">
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <strong>Stay on the value-driven sub-attribute path</strong>
|
||||
/// (current production behaviour). The lmxopcua server's
|
||||
/// <c>AlarmConditionService</c> already synthesizes Part 9
|
||||
/// transitions from the four MXAccess sub-attributes
|
||||
/// (<c>InAlarm</c>, <c>Acked</c>, <c>Priority</c>,
|
||||
/// <c>Description</c>) via the data-change subscription.
|
||||
/// Operator-comment fidelity is the only regression vs. v1; if
|
||||
/// acceptable, this row stays the production path and the
|
||||
/// <see cref="MxEventFamily.OnAlarmTransition"/> family stays
|
||||
/// reserved-but-dormant on the wire.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <strong>Add an x64 alarm-helper sub-process</strong> alongside
|
||||
/// the worker that loads <c>aaAlarmManagedClient</c>,
|
||||
/// subscribes to alarms, and forwards transitions to the worker
|
||||
/// over a small named-pipe IPC. Then this sink's
|
||||
/// <see cref="Attach"/> connects to that helper instead of to
|
||||
/// the COM server, and routes each transition through
|
||||
/// <see cref="EnqueueTransition"/>. Adds operational complexity
|
||||
/// but recovers full v1 fidelity (operator user, comment,
|
||||
/// original raise time, category).
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// <para>
|
||||
/// Until that decision is made, this sink's <see cref="Attach"/> is a
|
||||
/// no-op. The worker continues to function for data subscriptions, and
|
||||
/// the gateway's <see cref="MxEventFamily.OnAlarmTransition"/> family
|
||||
/// is reserved on the wire but never emitted. lmxopcua-side
|
||||
/// <c>AlarmConditionService</c> keeps the sub-attribute synthesis
|
||||
/// active and continues to surface alarms to OPC UA Part 9 clients.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class MxAccessAlarmEventSink : IMxAccessEventSink
|
||||
|
||||
Reference in New Issue
Block a user