Auto: focas-f3a — cnc_rdalmhistry alarm-history extension

Adds FocasAlarmProjection with two modes (ActiveOnly default, ActivePlusHistory)
that polls cnc_rdalmhistry on connect + on a configurable cadence (5 min default,
HistoryDepth=100 capped at 250). Emits historic events via IAlarmSource with
SourceTimestampUtc set from the CNC's reported timestamp; dedup keyed on
(OccurrenceTime, AlarmNumber, AlarmType). Ships the ODBALMHIS packed-buffer
decoder + encoder in Wire/FocasAlarmHistoryDecoder.cs and threads
ReadAlarmHistoryAsync through IFocasClient (default no-op so existing transport
variants stay back-compat). FocasDriver now implements IAlarmSource.

13 new unit tests cover: mode switch, dedup, distinct-timestamp emission,
type-as-key behaviour, OccurrenceTime passthrough (not Now), HistoryDepth
clamp/fallback, and decoder round-trip. All 341 FOCAS unit tests still pass.

Docs: docs/drivers/FOCAS.md (new), docs/v2/focas-deployment.md (new),
docs/v2/implementation/focas-wire-protocol.md (new),
docs/v2/implementation/focas-simulator-plan.md (new),
docs/drivers/FOCAS-Test-Fixture.md (alarm-history bullet appended).

Closes #267
This commit is contained in:
Joseph Doherty
2026-04-26 00:07:59 -04:00
parent 1922b93bd5
commit 7f9d6a778e
12 changed files with 1248 additions and 1 deletions

View File

@@ -185,6 +185,20 @@ public interface IFocasClient : IDisposable
Task SetPathAsync(int pathId, CancellationToken cancellationToken)
=> Task.CompletedTask;
/// <summary>
/// Read up to <paramref name="depth"/> most-recent entries from the CNC's alarm-history
/// ring buffer via <c>cnc_rdalmhistry</c>. Used by <see cref="FocasAlarmProjection"/>
/// when <see cref="FocasAlarmProjectionOptions.Mode"/> is
/// <see cref="FocasAlarmProjectionMode.ActivePlusHistory"/> (issue #267, plan PR F3-a).
/// Default returns an empty list so transport variants that have not yet implemented
/// the call keep working — the projection's history poll becomes a no-op rather than
/// faulting. Wire decode of the FWLIB <c>ODBALMHIS</c> struct lives in
/// <see cref="Wire.FocasAlarmHistoryDecoder"/>.
/// </summary>
Task<IReadOnlyList<FocasAlarmHistoryEntry>> ReadAlarmHistoryAsync(
int depth, CancellationToken cancellationToken)
=> Task.FromResult<IReadOnlyList<FocasAlarmHistoryEntry>>(Array.Empty<FocasAlarmHistoryEntry>());
/// <summary>
/// Read a contiguous range of PMC bytes in a single wire call (FOCAS
/// <c>pmc_rdpmcrng</c> with byte data type) for the given <paramref name="letter"/>
@@ -353,6 +367,30 @@ public sealed record FocasOperatorMessagesInfo(IReadOnlyList<FocasOperatorMessag
/// </summary>
public sealed record FocasCurrentBlockInfo(string Text);
/// <summary>
/// One entry returned by <c>cnc_rdalmhistry</c> — a single historical alarm
/// occurrence the CNC retained in its ring buffer (issue #267, plan PR F3-a).
/// The projection emits these as historic <see cref="Core.Abstractions.AlarmEventArgs"/>
/// with <c>SourceTimestampUtc</c> set from <see cref="OccurrenceTime"/> so OPC UA clients
/// see the real CNC timestamp rather than the moment the projection polled.
/// </summary>
/// <remarks>
/// <para>The dedup key for the projection is
/// <c>(<see cref="OccurrenceTime"/>, <see cref="AlarmNumber"/>, <see cref="AlarmType"/>)</c>.
/// Same triple across two polls only emits once — see
/// <see cref="FocasAlarmProjection"/>.</para>
///
/// <para>FANUC ring buffers are typically capped at ~100 entries; the host parameter that
/// governs the cap varies by series + MTB so the driver clamps user-requested depth to a
/// conservative <c>250</c> ceiling (see <see cref="FocasAlarmProjectionOptions.HistoryDepth"/>).</para>
/// </remarks>
public sealed record FocasAlarmHistoryEntry(
DateTimeOffset OccurrenceTime,
int AxisNo,
int AlarmType,
int AlarmNumber,
string Message);
/// <summary>Factory for <see cref="IFocasClient"/>s. One client per configured device.</summary>
public interface IFocasClientFactory
{