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
103 lines
3.4 KiB
Markdown
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.
|