Files
lmxopcua/docs/v2/implementation/focas-simulator-plan.md
Joseph Doherty 7f9d6a778e 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
2026-04-26 00:07:59 -04:00

3.4 KiB

FOCAS simulator (focas-mock) plan

Notes on the focas-mock simulator that the FOCAS driver's integration tests will eventually talk to. Today there is no FOCAS integration-test project; this doc is the contract the future fixture will be built against. Keeping the contract tracked in repo means the wire-protocol command ids (and their request/response payloads) don't drift between the .NET wire client and a future Python implementation.

Ground rules

  • Append-only command ids. Mirror focas-wire-protocol.md verbatim.
  • Per-profile state. The simulator hosts N CNC profiles concurrently (Series0i, Series30i, PowerMotion, ...). Each profile has its own alarm-history ring buffer + its own override map.
  • Admin endpoints under POST /admin/... mutate state without going through the wire protocol; integration tests use these to seed canned inputs.

Protocol surface (current scope)

Cmd API State impact
0x0001 cnc_rdcncstat reads cached ODBST per profile
0x0002 cnc_rdparam reads parameter map per profile
0x0003 cnc_rdmacro reads macro variables per profile
0x0004 cnc_rddiag reads diagnostic map per profile
0x0010 pmc_rdpmcrng reads PMC byte ranges
0x0020 cnc_modal reads cached modal MSTB per profile
... ... ...
0x0F1A cnc_rdalmhistry dumps the per-profile alarm-history ring buffer (issue #267, plan PR F3-a)

cnc_rdalmhistry mock behaviour

The simulator keeps a per-profile ring buffer of alarm-history entries. Default fixture seeds 5 profiles with 10 canned entries each (per the F3-a plan).

Request decode

[int16 LE depth]

Response encode

Use FocasAlarmHistoryDecoder.Encode semantics in reverse: emit the count followed by ALMHIS_data blocks padded to 4-byte boundaries. The .NET-side decoder consumes the same format verbatim, so a Python encoder written against the table in focas-wire-protocol.md interoperates without extra glue.

Admin endpoint — POST /admin/mock_patch_alarmhistory

Replaces the alarm-history ring buffer for a profile.

POST /admin/mock_patch_alarmhistory
{
  "profile": "Series30i",
  "entries": [
    {
      "occurrenceTime": "2025-04-01T09:30:00Z",
      "axisNo": 1,
      "alarmType": 2,
      "alarmNumber": 100,
      "message": "Spindle overload"
    },
    ...
  ]
}

entries order is interpreted as ring-buffer order (most-recent first to match FANUC's natural surface).

FocasSimFixture.SeedAlarmHistoryAsync

The future test-support helper wraps the admin endpoint:

await fixture.SeedAlarmHistoryAsync(
    profile: "Series30i",
    entries: new []
    {
        new FocasAlarmHistoryEntry(
            new DateTimeOffset(2025, 4, 1, 9, 30, 0, TimeSpan.Zero),
            AxisNo: 1, AlarmType: 2, AlarmNumber: 100, Message: "Spindle overload"),
    });

Integration test Series/AlarmHistoryProjectionTests.cs will assert:

  • historic events fire once with the seeded timestamps
  • second poll yields zero new events (dedup honoured end-to-end)
  • active-alarm raise/clear still works alongside the history poll

These tests are blocked on the focas-mock + integration-test project landing; the unit-test coverage in FocasAlarmProjectionTests already exercises every same-process invariant.