ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
focas.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file focas.h
6 * @brief FANUC FOCAS Ethernet protocol codec (PROTOCORE_ENABLE_FOCAS) - zero-heap request builders +
7 * response parsers for FANUC CNCs over TCP 8193 (the machine-tool data protocol).
8 *
9 * FOCAS ("FANUC Open CNC API Specification") is normally a proprietary PC library (fwlib32); this
10 * is a pure codec for its Ethernet wire protocol. Every multi-octet field is BIG-endian. A frame
11 * is a 10-octet envelope + a payload:
12 * @code
13 * envelope (10)
14 * A0 A0 A0 A0 frame magic
15 * version (2) = 1
16 * frame type (2) 0x0101 open req / 0x0102 open resp
17 * 0x2101 command (VAR) req / 0x2102 command resp
18 * 0x0201 close req / 0x0202 close resp
19 * length (2) octets of payload that follow
20 * payload (length) type-specific (see below)
21 * @endcode
22 *
23 * The session is: open (payload = FRAME_DST 0x0002) -> one or more commands -> close. A command
24 * request payload is a 6-octet selector + five signed 32-bit arguments + optional extra data:
25 * @code
26 * c1 c2 c3 three u16: the FOCAS function selector (e.g. 1/1/0x18 = SysInfo)
27 * v1 v2 v3 v4 v5 five i32: the function's integer arguments
28 * extra... function-specific trailing bytes (none for the reads below)
29 * @endcode
30 * A command response payload echoes the 6-octet selector, then a 6-octet status (a signed-short
31 * FOCAS return code in its first two octets, 0 = EW_OK), then a u16 data length, then the data.
32 *
33 * Frame layout, selector encoding, the open/close handshake, the SysInfo (`ODBSYS`) and alarm
34 * (`>L`) response layouts, and the 8-octet value encoding (`data / base^exp`) were reverse-
35 * engineered by and cross-checked against diohpix/pyfanuc. Function selectors are the verbatim
36 * set documented there. Pure codec, host-tested - the caller owns the TCP connection
37 * (`protocore_client_*`) and drives the open -> command -> close sequence.
38 *
39 * @author Douglas Quigg (dstroy0)
40 * @date 2026
41 */
42
43#ifndef PROTOCORE_FOCAS_H
44#define PROTOCORE_FOCAS_H
45
46#include "protocore_config.h" // the entry point: protocore_types.h for the widths
47
48#if PROTOCORE_ENABLE_FOCAS
49
51
52#define FOCAS_TCP_PORT 8193 ///< FOCAS Ethernet listening port
53#define FOCAS_FRAME_HDR_LEN 10 ///< magic(4) + version(2) + type(2) + length(2)
54#define FOCAS_PROTO_VERSION 1 ///< envelope version field
55#define FOCAS_FRAME_DST 0x0002 ///< open-request payload (FRAME_DST)
56#define FOCAS_CMD_SEL_LEN 6 ///< the c1/c2/c3 selector (three u16)
57#define FOCAS_CMD_ARGS_LEN 20 ///< v1..v5 (five i32)
58#define FOCAS_REQ_BODY_LEN 26 ///< FOCAS_CMD_SEL_LEN + FOCAS_CMD_ARGS_LEN
59#define FOCAS_RESP_HDR_LEN 14 ///< echoed selector(6) + status(6) + data length(2)
60#define FOCAS_VALUE_LEN 8 ///< one FOCAS 8-octet numeric value
61#define FOCAS_SYSINFO_LEN 18 ///< ODBSYS: addinfo+maxaxis+cnctype+mttype+series+version+axes
62
63/// Frame types (envelope octets 6-7). Cast to/from the wire only at the byte boundary.
64typedef enum PROTO_ENUM_PACKED
65{
66 FOCAS_FRAME_TYPE_INVALID = 0x0000,
67 FOCAS_FRAME_TYPE_OPEN_REQ = 0x0101,
68 FOCAS_FRAME_TYPE_OPEN_RESP = 0x0102,
69 FOCAS_FRAME_TYPE_CLOSE_REQ = 0x0201,
70 FOCAS_FRAME_TYPE_CLOSE_RESP = 0x0202,
71 FOCAS_FRAME_TYPE_COMMAND_REQ = 0x2101, ///< FTYPE_VAR_REQU
72 FOCAS_FRAME_TYPE_COMMAND_RESP = 0x2102 ///< FTYPE_VAR_RESP
73} FocasFrameType;
74
75/// A FOCAS function selector: three big-endian u16 (c1, c2, c3).
76typedef struct
77{
78 uint16_t c1;
79 uint16_t c2;
80 uint16_t c3;
81} FocasCmd;
82
83/// The documented FOCAS function selectors (verbatim from the pyfanuc protocol notes). Each is a
84/// compound literal, so it passes by value wherever a FocasCmd argument is taken.
85#define FOCAS_CMD_READ_CNC_PARAM ((FocasCmd){1, 1, 0x0e}) ///< cnc_rdparam
86#define FOCAS_CMD_READ_MACRO ((FocasCmd){1, 1, 0x15}) ///< cnc_rdmacro
87#define FOCAS_CMD_SET_MACRO ((FocasCmd){1, 1, 0x16}) ///< cnc_wrmacro
88#define FOCAS_CMD_SYS_INFO ((FocasCmd){1, 1, 0x18}) ///< cnc_sysinfo (ODBSYS)
89#define FOCAS_CMD_READ_ALARM ((FocasCmd){1, 1, 0x1a}) ///< cnc_alarm (u32 status word)
90#define FOCAS_CMD_READ_PRG_NUM ((FocasCmd){1, 1, 0x1c}) ///< cnc_rdprgnum (main/running)
91#define FOCAS_CMD_READ_SEQ_NUM ((FocasCmd){1, 1, 0x1d}) ///< cnc_rdseqnum
92#define FOCAS_CMD_READ_ALARM_INFO ((FocasCmd){1, 1, 0x23}) ///< cnc_rdalminfo
93#define FOCAS_CMD_READ_FEEDRATE ((FocasCmd){1, 1, 0x24}) ///< cnc_actf (actual feed)
94#define FOCAS_CMD_READ_SPINDLE ((FocasCmd){1, 1, 0x25}) ///< cnc_acts (actual spindle speed)
95#define FOCAS_CMD_READ_POSITION ((FocasCmd){1, 1, 0x26}) ///< cnc_rdposition / axis read
96#define FOCAS_CMD_READ_DIAG ((FocasCmd){1, 1, 0x30}) ///< cnc_diagnoss
97#define FOCAS_CMD_READ_SPINDLE2 ((FocasCmd){1, 1, 0x40}) ///< cnc_acts2 (speed + load)
98#define FOCAS_CMD_READ_DATETIME ((FocasCmd){1, 1, 0x45}) ///< cnc_rdtimer (v1=0 date, 1 time)
99#define FOCAS_CMD_READ_SERVO_LOAD ((FocasCmd){1, 1, 0x56}) ///< servo load, MAX_AXIS
100#define FOCAS_CMD_READ_AXIS_NAMES ((FocasCmd){1, 1, 0x89}) ///< controlled-axis names
101#define FOCAS_CMD_READ_SPINDLE_NAMES ((FocasCmd){1, 1, 0x8a})
102#define FOCAS_CMD_READ_CNC_PARAM3 ((FocasCmd){1, 1, 0x8d}) ///< cnc_rdparam3
103#define FOCAS_CMD_READ_MACRO_DBL ((FocasCmd){1, 1, 0xa7}) ///< cnc_rdmacror (double)
104#define FOCAS_CMD_READ_PMC ((FocasCmd){2, 1, 0x8001}) ///< pmc_rdpmcrng
105
106/// Position/axis read kinds (SysInfo 0x26 `v1`); the axis argument is `v2` (0 = all axes).
107#define FOCAS_POS_MACHINE 1 ///< machine (reference) coordinate
108#define FOCAS_POS_ABSOLUTE 4 ///< absolute (program) coordinate
109#define FOCAS_POS_RELATIVE 6 ///< relative coordinate
110#define FOCAS_POS_DISTANCE 7 ///< distance to go
111#define FOCAS_POS_SKIP 8 ///< skip position
112
113/// A parsed frame envelope; `payload`/`payload_len` point into the caller's buffer (no copy).
114typedef struct
115{
116 FocasFrameType type;
117 uint16_t version;
118 const uint8_t *payload;
119 uint16_t payload_len;
120} FocasFrame;
121
122/// A parsed command response; `data`/`data_len` point into the caller's buffer (no copy).
123typedef struct
124{
125 uint16_t c1; ///< echoed selector
126 uint16_t c2;
127 uint16_t c3;
128 int16_t status; ///< FOCAS return code (0 = EW_OK; negative = error)
129 const uint8_t *data;
130 uint16_t data_len;
131} FocasResponse;
132
133/// Parsed SysInfo (ODBSYS). The char fields are NUL-terminated copies of fixed-width ASCII fields.
134typedef struct
135{
136 uint16_t add_info;
137 uint16_t max_axis;
138 char cnc_type[3]; ///< e.g. "30", "16", "0i"
139 char mt_type[3]; ///< " M" milling / " T" turning
140 char series[5]; ///< software series
141 char version[5]; ///< software version
142 char axes[3]; ///< controlled-axis count as ASCII
143} FocasSysInfo;
144
145/// One decoded FOCAS 8-octet numeric value. The scaled value is `data / base^exp`; `valid` is
146/// false for the 0xFFFF sentinel or an unrecognized base (only 2 and 10 are decimal-scaled).
147typedef struct
148{
149 int32_t data;
150 uint8_t base; ///< 2 or 10
151 uint8_t exp; ///< decimal places
152 proto_bool valid;
153} FocasValue;
154
155// ---------------------------------------------------------------------------------------------
156// Request builders. Each writes a complete on-wire frame into `buf` and returns the total octet
157// count, or 0 if `buf` is null or `cap` is too small.
158// ---------------------------------------------------------------------------------------------
159
160/// Open the session (payload = FRAME_DST). Send first; expect a 0x0102 response frame.
161size_t protocore_focas_build_open(uint8_t *buf, size_t cap);
162
163/// Close the session (empty payload). Send last; expect a 0x0202 response frame.
164size_t protocore_focas_build_close(uint8_t *buf, size_t cap);
165
166/// Generic command: selector + five i32 arguments + optional trailing `extra` bytes.
167size_t protocore_focas_build_request(uint8_t *buf, size_t cap, FocasCmd cmd, int32_t v1, int32_t v2, int32_t v3,
168 int32_t v4, int32_t v5, const uint8_t *extra, size_t extra_len);
169
170/// SysInfo (1/1/0x18): no arguments. Response = FocasSysInfo.
171size_t protocore_focas_build_sysinfo(uint8_t *buf, size_t cap);
172
173/// Alarm status (1/1/0x1a): no arguments. Response = a big-endian u32 alarm bitmask.
174size_t protocore_focas_build_read_alarm(uint8_t *buf, size_t cap);
175
176/// Read CNC parameter(s) (1/1/0x0e): parameter range [first, last], axis (0 = not axis-specific).
177size_t protocore_focas_build_read_param(uint8_t *buf, size_t cap, int32_t first, int32_t last, int32_t axis);
178
179/// Read macro variables (1/1/0x15): variable range [first, last]. Response values are 8-octet.
180size_t protocore_focas_build_read_macro(uint8_t *buf, size_t cap, int32_t first, int32_t last);
181
182/// Read position/axis data (1/1/0x26): `kind` (FocasPosKind), `axis` (0 = all). 8-octet values.
183size_t protocore_focas_build_read_position(uint8_t *buf, size_t cap, int32_t kind, int32_t axis);
184
185/// Read actual feedrate (1/1/0x24): no arguments. Response = one 8-octet value.
186size_t protocore_focas_build_read_feedrate(uint8_t *buf, size_t cap);
187
188/// Read actual spindle speed (1/1/0x25): no arguments. Response = one 8-octet value.
189size_t protocore_focas_build_read_spindle(uint8_t *buf, size_t cap);
190
191// ---------------------------------------------------------------------------------------------
192// Response parsers. Each returns false on a short/garbled buffer.
193// ---------------------------------------------------------------------------------------------
194
195/// Validate the 10-octet envelope (magic + version) and expose the payload (into `buf`).
196proto_bool protocore_focas_parse_frame(const uint8_t *buf, size_t len, FocasFrame *out);
197
198/// Decode a command-response payload (echoed selector + status + length + data) into `out`.
199proto_bool protocore_focas_parse_response(const uint8_t *payload, size_t payload_len, FocasResponse *out);
200
201/// Convenience: validate a whole command-response frame (type 0x2102) straight into `out`.
202proto_bool protocore_focas_parse_command_frame(const uint8_t *buf, size_t len, FocasResponse *out);
203
204/// SysInfo response data: ODBSYS (addinfo + maxaxis + cnctype + mttype + series + version + axes).
205proto_bool protocore_focas_parse_sysinfo(const uint8_t *data, size_t data_len, FocasSysInfo *out);
206
207/// Alarm response data: a single big-endian u32 alarm bitmask.
208proto_bool protocore_focas_parse_alarm(const uint8_t *data, size_t data_len, uint32_t *alarm_status);
209
210/// Decode one FOCAS 8-octet value at `chunk`. Returns true only for a usable value (`out->valid`
211/// is set the same way); false if fewer than 8 octets are available.
212proto_bool protocore_focas_decode8(const uint8_t *chunk, size_t len, FocasValue *out);
213
214/// The scaled value `data / base^exp` as a float (0 for an invalid value).
215float protocore_focas_value_f(const FocasValue *v);
216
218
219#endif // PROTOCORE_ENABLE_FOCAS
220
221#endif // PROTOCORE_FOCAS_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97