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

103 lines
3.4 KiB
Markdown

# 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`](./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`](./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:
```csharp
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.