Files
lmxopcua/docs/v2/implementation/focas-wire-protocol.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.0 KiB

FOCAS wire protocol — packed-buffer surface

Notes on the language-neutral packed-buffer encoding the FOCAS driver + focas-mock simulator share. This format is not the FWLIB native struct layout — Tier-C Fwlib32 backends marshal directly from the FANUC C struct. The packed surface exists so the simulator (Python / FastAPI) and the .NET wire client can speak a common format over IPC without piping a Win32 DLL through both ends.

Command id table

Each FOCAS-equivalent call gets a stable wire-protocol command id. Ids are append-only — never renumber, never reuse.

Id FOCAS API Surface
0x0001 cnc_rdcncstat ODBST 9-field status struct
0x0002 cnc_rdparam parameter value (one number)
0x0003 cnc_rdmacro macro variable value
0x0004 cnc_rddiag diagnostic value
... ... ...
0x0F1A cnc_rdalmhistry ODBALMHIS alarm-history ring-buffer dump (issue #267, plan PR F3-a)

ODBALMHIS — alarm history (cnc_rdalmhistry, command 0x0F1A)

Issued by FocasAlarmProjection when FocasDriverOptions.AlarmProjection.Mode == ActivePlusHistory. Returns up to depth most-recent ring-buffer entries.

Request

Offset Width Field Notes
0 int16 LE depth clamped client-side to [1..250] (FocasAlarmProjectionOptions.MaxHistoryDepth)

Response (packed buffer, little-endian)

Offset Width Field
0 int16 LE num_alm — number of entries that follow. < 0 indicates CNC error.
2 repeated ALMHIS_data alm[num_alm] (see below)

Each entry block:

Offset (rel.) Width Field
0 int16 LE year
2 int16 LE month
4 int16 LE day
6 int16 LE hour
8 int16 LE minute
10 int16 LE second
12 int16 LE axis_no (1-based; 0 = whole-CNC)
14 int16 LE alm_type (P/S/OT/SV/SR/MC/SP/PW/IO encoded numerically)
16 int16 LE alm_no
18 int16 LE msg_len (0..32 typical)
20 msg_len ASCII message (no null terminator)
20 + msg_len 0..3 pad to 4-byte boundary so per-entry blocks stay self-delimiting

The CNC stamps year..second in its own local time. The deployment guide instructs operators to keep CNC clocks on UTC so the projection's dedup key (OccurrenceTime, AlarmNumber, AlarmType) stays stable across DST transitions. The .NET decoder (Wire/FocasAlarmHistoryDecoder.Decode) constructs each DateTimeOffset with TimeSpan.Zero (UTC) on that assumption.

Error handling

  • A negative num_alm short-circuits decode to an empty list — the projection treats it as "no history this tick" and the next poll retries.
  • Malformed timestamps (e.g. month=0) are skipped per-entry instead of faulting the whole decode; the dedup key for malformed entries would be unstable anyway.
  • msg_len overrunning the payload truncates the entry list at the malformed entry rather than throwing.