Files
mxaccessgw/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/IMxAccessAlarmConsumer.cs
T
Joseph Doherty 0d4b2aea2d refactor(worker): alarm consumers resolve the AckMsg item instead of acking
IMxAccessAlarmConsumer.ResolveAckTarget/ResolveAckTargetByName replace AcknowledgeByGuid/AcknowledgeByName. wnwrap maps Galaxy-provider alarms to <tag>.AckMsg and drops its inert parallel ack consumer; subtag resolves to the target's AckCommentSubtag and no longer advises or writes it; ISubtagAlarmSource.Write is removed. Does not build alone: AlarmDispatcher moves to the new API in the next commit.

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:35:49 -04:00

117 lines
6.3 KiB
C#

using System;
using System.Collections.Generic;
namespace ZB.MOM.WW.MxGateway.Worker.MxAccess;
/// <summary>
/// Abstraction over an AVEVA alarm-consumer COM library. The production
/// implementation (<see cref="WnWrapAlarmConsumer"/>) wraps
/// <c>WNWRAPCONSUMERLib.wwAlarmConsumerClass</c> from
/// <c>C:\Program Files (x86)\Common Files\ArchestrA\wnwrapConsumer.dll</c>;
/// tests substitute a fake to drive transition events without a live
/// Galaxy.
/// </summary>
/// <remarks>
/// <para>
/// The receive surface is poll-based: the production consumer
/// periodically calls <c>GetXmlCurrentAlarms2</c>, parses the
/// returned XML payload, diffs against the previous snapshot keyed
/// by alarm GUID, and raises <see cref="AlarmTransitionEmitted"/>
/// once per state change. This bypasses the FILETIME marshaling
/// crash in <c>aaAlarmManagedClient.AlarmClient.GetHighPriAlarm</c>
/// (see <c>docs/AlarmClientDiscovery.md</c>) — XML strings carry
/// timestamps as ASCII fields, no DateTime auto-conversion happens
/// on the .NET interop boundary.
/// </para>
/// </remarks>
public interface IMxAccessAlarmConsumer : IDisposable
{
/// <summary>
/// Fires once per detected alarm-state transition (raise, acknowledge,
/// clear, or new-alarm-already-acked-on-arrival). Subscribers are
/// expected to translate the record into the proto family
/// <c>OnAlarmTransition</c> and enqueue it. Fired on the consumer's
/// polling thread (the worker's STA in production); subscribers that
/// need a different thread must marshal back themselves.
/// </summary>
event EventHandler<MxAlarmTransitionEvent>? AlarmTransitionEmitted;
/// <summary>
/// Initializes the AVEVA alarm-client connection, registers as a
/// consumer, and subscribes to the supplied alarm-provider expression.
/// Subscription string follows AVEVA's canonical format:
/// <c>\\&lt;node&gt;\Galaxy!&lt;area&gt;</c>. The literal "Galaxy" is
/// the provider name (regardless of the configured Galaxy database
/// name). Subscribe does not start any polling of its own; the caller
/// drives polls explicitly via <see cref="PollOnce"/>.
/// </summary>
/// <param name="subscription">The subscription expression (e.g., <c>\\HOST\Galaxy!Area</c>).</param>
void Subscribe(string subscription);
/// <summary>
/// Resolves the MXAccess item whose write acknowledges the alarm with the
/// given GUID (e.g. <c>Obj.Alarm.AckMsg</c>). Does not perform the ack.
/// </summary>
/// <param name="alarmGuid">The alarm GUID as reported by this consumer.</param>
/// <returns>The resolved target or the reason none exists.</returns>
AlarmAckTarget ResolveAckTarget(Guid alarmGuid);
/// <summary>Resolves the ack item for an alarm identified by name, provider and group.</summary>
/// <param name="alarmName">The tag/alarm name, e.g. <c>TestMachine_001.TestAlarm001</c>.</param>
/// <param name="providerName">The provider (literal <c>Galaxy</c> for ArchestrA).</param>
/// <param name="groupName">The area/group.</param>
/// <returns>The resolved target or the reason none exists.</returns>
AlarmAckTarget ResolveAckTargetByName(string alarmName, string providerName, string groupName);
/// <summary>
/// Returns the consumer's most recently parsed snapshot of currently
/// active alarms, together with whether the fetch that produced it hit
/// the per-fetch cap. Used by the gateway's QueryActiveAlarms (PR A.7)
/// ConditionRefresh path — operator clients call this after reconnect
/// to seed local Part 9 state.
/// </summary>
/// <remarks>
/// <para>
/// The verdict is an <c>out</c> parameter rather than a separate
/// property on purpose, and the reason is a correctness one.
/// Implementations must produce both values from a single acquisition
/// of whatever lock guards the retained snapshot, mirroring the write
/// side (<c>WnWrapAlarmConsumer.FoldFetch</c> updates snapshot and
/// verdict together). Two separate reads could straddle a poll that
/// flips not-truncated → truncated and pair a stale
/// <see langword="false"/> with a capped snapshot — a snapshot that
/// reads as complete while missing actives, which is exactly the
/// false all-clear this signal exists to prevent. Making the pair
/// inseparable in the signature removes the possibility rather than
/// relying on callers, or on the STA serializing them.
/// </para>
/// <para>
/// While <paramref name="truncated"/> is <see langword="true"/> the
/// returned snapshot is authoritative about presence only: the
/// provider may hold actives it had no room to report, and the
/// consumer has suspended the absence-implies-Clear inference. Not
/// latched — the first sub-cap fetch clears it, because that fetch is
/// complete and its snapshot is again authoritative about absence.
/// Consumers with no per-fetch cap (the subtag fallback, which is
/// advise-driven) always report <see langword="false"/>.
/// </para>
/// </remarks>
/// <param name="truncated">
/// Receives whether the fetch behind the returned snapshot hit the
/// per-fetch cap.
/// </param>
/// <returns>The most recently parsed snapshot of currently active alarms.</returns>
IReadOnlyList<MxAlarmSnapshotRecord> SnapshotActiveAlarms(out bool truncated);
/// <summary>
/// Drives a single synchronous poll of the underlying alarm source.
/// The production consumer owns no internal timer; the worker's STA
/// drives polls via <c>StaRuntime.InvokeAsync</c>, satisfying the
/// <c>ThreadingModel=Apartment</c> requirement of
/// <c>wwAlarmConsumerClass</c>. Fake implementations should no-op.
/// This method must be invoked on the thread that created the consumer
/// (the worker's STA in production).
/// </summary>
void PollOnce();
}