0d4b2aea2d
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
117 lines
6.3 KiB
C#
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>\\<node>\Galaxy!<area></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();
|
|
}
|