feat(focas): CNC alarms reach Part 9 conditions via ALARM: tags (#647)
An ALARM: address (*, <Type>, <Type>/<n>, <Type>/<lo>-<hi>, T<code>, OTHER) on a raw tag that
also carries the cross-driver alarm object is ONE condition with ConditionId == RawPath and a
Kind (Raise / Retrigger / Clear), so DriverHostActor.ForwardNativeAlarm routes it and
NativeAlarmProjector activates it. Fixes all seven triage defects:
1. SubscribeAlarmsAsync treated the host's RawPaths as device hosts -> nothing polled.
2. Decorated "{host}#{Type}:{Number}" ConditionId -> every event dropped.
3. No Kind -> projector kept prior state.
4. WireFocasClient.ReadAlarmsAsync answered [] on failure -> mass clear + un-acked re-raise.
It now throws; a failed device read keeps every condition's state.
5. Acks lost on reconnect -> told-record dedup (MTConnect #626 pattern) held for the driver's
lifetime; same-set re-subscribe / re-init re-raise nothing; changed set prunes leavers only.
6. Subscribe threw while AlarmProjection.Enabled=false -> the tag is the opt-in; flag is a no-op.
7. Alarm tags could not be authored -> ALARM: grammar (FocasAlarmSelector, Contracts) backs the
editor, the address picker, the deploy-time inspector and driver init.
Severity: authored alarm.severity -> exact OpcUaSeverity; else the 16i-era type bucket. The type
table stays the only one (moved to Contracts, documented 16i-era; 30i codes not invented; raw
T<code> selector + raw code in every message). Read-only: ack is server-side. Also clears the
value table on a re-initialise (#630 gap that let a removed/moved tag keep reading).
E2E: Runtime.Tests FocasNativeAlarmEndToEndTests drives the real driver through the real
DriverHostActor and asserts the condition goes Active then inactive.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -65,12 +65,18 @@ public interface IFocasClient : IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Read active alarm messages from the CNC via <c>cnc_rdalmmsg2</c>. Returns
|
||||
/// zero-or-more active alarms. Null / empty list means "no alarms currently
|
||||
/// active". IAlarmSource projection polls this at a configurable interval +
|
||||
/// emits transitions (raise / clear) on the driver's <c>OnAlarmEvent</c>.
|
||||
/// zero-or-more active alarms; an empty list means "the CNC answered, and no alarm is
|
||||
/// active". The alarm projection polls this per device and diffs it into Part 9
|
||||
/// condition transitions on the driver's <c>OnAlarmEvent</c>.
|
||||
/// <para>
|
||||
/// <b>A failed read MUST throw</b>, never answer empty (Gitea #647): the projection treats an
|
||||
/// empty answer as "every alarm cleared", so a failure reported as <c>[]</c> becomes a mass
|
||||
/// clear followed by an un-acknowledged re-raise on recovery. A throw keeps the last state.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The list of currently active alarms; empty when none are active.</returns>
|
||||
/// <exception cref="Exception">Any failure to obtain the list — the session is down, the CNC refused, the wire faulted.</exception>
|
||||
Task<IReadOnlyList<FocasActiveAlarm>> ReadAlarmsAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -446,27 +452,3 @@ public sealed class UnimplementedFocasClientFactory : IFocasClientFactory
|
||||
/// <inheritdoc />
|
||||
public IFocasClient Create() => throw new NotSupportedException(Message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Well-known FOCAS alarm types from <c>fwlib32.h</c> <c>ALM_TYPE_*</c>. Narrow subset —
|
||||
/// the full list is ~15 types per model; these cover the universally-present categories.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Constants are typed <see cref="short"/> so they match the wire field width on
|
||||
/// <c>cnc_rdalmmsg2</c> (and so <see cref="FocasAlarmProjection"/>'s <c>switch (short)</c>
|
||||
/// statements compile against a matching type rather than relying on implicit int→short
|
||||
/// narrowing on the constants).
|
||||
/// </remarks>
|
||||
public static class FocasAlarmType
|
||||
{
|
||||
/// <summary>Pass to <see cref="IFocasClient.ReadAlarmsAsync"/>-equivalent to mean "any type".</summary>
|
||||
public const short All = -1;
|
||||
public const short Parameter = 0; // ALM_P
|
||||
public const short PulseCode = 1; // ALM_Y (servo)
|
||||
public const short Overtravel = 2; // ALM_O
|
||||
public const short Overheat = 3; // ALM_H
|
||||
public const short Servo = 4; // ALM_S
|
||||
public const short DataIo = 5; // ALM_T
|
||||
public const short MemoryCheck = 6; // ALM_M
|
||||
public const short MacroAlarm = 13; // ALM_MC
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user