6.3 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 |
| ... | ... | ... |
0x0102 |
cnc_wrparam |
IODBPSD parameter-write packet (issue #269, plan PR F4-b) |
0x0103 |
cnc_wrmacro |
ODBM macro-write packet (issue #269, plan PR F4-b) |
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_almshort-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_lenoverrunning the payload truncates the entry list at the malformed entry rather than throwing.
IODBPSD — parameter write (cnc_wrparam, command 0x0102)
Issue #269, plan PR F4-b. The write-side payload is the byte-symmetric
inverse of the cnc_rdparam read — the same IODBPSD struct shape, and
the .NET wire client uses the read-side decoder reversed (EncodeParamValue
in FwlibFocasClient.cs) so the encoder/decoder are guaranteed to stay in
lock-step.
Request
| Offset | Width | Field |
|---|---|---|
| 0 | int16 LE |
datano — parameter number (e.g. 1815) |
| 2 | int16 LE |
type — axis index (1-based; 0 = whole-CNC parameter) |
| 4 | length |
data payload — width depends on parameter type |
length (request frame trailer, drives data width):
| FocasDataType | length |
Payload encoding |
|---|---|---|
Byte |
4 + 1 |
one signed byte at offset 4 |
Int16 |
4 + 2 |
int16 LE at offset 4 |
Int32 |
4 + 4 |
int32 LE at offset 4 |
Bit-addressed parameters (PARAM:1815/0 form) are not supported by F4-b
and surface as BadNotSupported; F4-c will land the read-modify-write
helper alongside the PMC bit RMW path.
Response
Single int16 LE return code per the standard FWLIB convention:
0→Good11(EW_PASSWD) →BadUserAccessDenied(wasBadNotWritablepre-F4-b — seeFocasStatusMapper). Means the parameter-write switch is off or the CNC isn't in MDI mode; the F4-d unlock workflow will close the loop on this from the OPC UA side.- Other
EW_*codes map perFocasStatusMapper.MapFocasReturn.
ODBM — macro write (cnc_wrmacro, command 0x0103)
Issue #269, plan PR F4-b. The write-side payload mirrors the
cnc_rdmacro read shape: the same (mcr_val, dec_val) (integer +
decimal-point count) split, but emitted from the .NET side rather than
decoded.
Request
| Offset | Width | Field |
|---|---|---|
| 0 | int16 LE |
number — macro variable number (e.g. 500) |
| 2 | int16 LE |
length — fixed at 8 for ODBM |
| 4 | int32 LE |
mcr_val — scaled integer value |
| 8 | int16 LE |
dec_val — decimal-point count |
F4-b ships integer-only writes (dec_val = 0) to match the most
common HMI pattern; a future WriteMacroScaled overload will land if the
field calls for fractional macro setpoints. Read-side decoders apply
mcr_val / 10^dec_val, so a dec_val = 0 write surfaces back as the
integer it was emitted as.
Response
Same single-int16 envelope as cnc_wrparam. EW_PASSWD is rare on macro
writes (the gate-switch protection is parameter-specific) but the mapper
treats both kinds identically.
Symmetry note
The plan carries a "byte layout symmetry" requirement — the encoder for
each kind is the read-side decoder reversed. Adding a new parameter type
(e.g. Int64 parameters, when they ship) means extending both sides in
the same PR; the unit test
FocasWriteParameterTests.ParameterWrite_round_trip_stores_value_visible_to_subsequent_read
exercises encode → store → decode with the fake wire client and is the
canary for symmetry regressions.