DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
focas.h
Go to the documentation of this file.
1// 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 (DETWS_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 * (`det_client_*`) and drives the open -> command -> close sequence.
38 *
39 * @author Douglas Quigg (dstroy0)
40 * @date 2026
41 */
42
43#ifndef DETERMINISTICESPASYNCWEBSERVER_FOCAS_H
44#define DETERMINISTICESPASYNCWEBSERVER_FOCAS_H
45
46#include "ServerConfig.h"
47
48#if DETWS_ENABLE_FOCAS
49
50#include <stddef.h>
51#include <stdint.h>
52
53#define FOCAS_TCP_PORT 8193 ///< FOCAS Ethernet listening port
54#define FOCAS_FRAME_HDR_LEN 10 ///< magic(4) + version(2) + type(2) + length(2)
55#define FOCAS_PROTO_VERSION 1 ///< envelope version field
56#define FOCAS_FRAME_DST 0x0002 ///< open-request payload (FRAME_DST)
57#define FOCAS_CMD_SEL_LEN 6 ///< the c1/c2/c3 selector (three u16)
58#define FOCAS_CMD_ARGS_LEN 20 ///< v1..v5 (five i32)
59#define FOCAS_REQ_BODY_LEN 26 ///< FOCAS_CMD_SEL_LEN + FOCAS_CMD_ARGS_LEN
60#define FOCAS_RESP_HDR_LEN 14 ///< echoed selector(6) + status(6) + data length(2)
61#define FOCAS_VALUE_LEN 8 ///< one FOCAS 8-octet numeric value
62#define FOCAS_SYSINFO_LEN 18 ///< ODBSYS: addinfo+maxaxis+cnctype+mttype+series+version+axes
63
64/// Frame types (envelope octets 6-7). Cast to/from the wire only at the byte boundary.
65enum class FocasFrameType : uint16_t
66{
67 invalid = 0x0000,
68 open_req = 0x0101,
69 open_resp = 0x0102,
70 close_req = 0x0201,
71 close_resp = 0x0202,
72 command_req = 0x2101, ///< FTYPE_VAR_REQU
73 command_resp = 0x2102 ///< FTYPE_VAR_RESP
74};
75
76/// A FOCAS function selector: three big-endian u16 (c1, c2, c3).
77struct FocasCmd
78{
79 uint16_t c1;
80 uint16_t c2;
81 uint16_t c3;
82};
83
84/// The documented FOCAS function selectors (verbatim from the pyfanuc protocol notes). Grouped as
85/// constants (not an enum) because a selector is a struct, not a single scalar.
86struct FocasCommand
87{
88 static constexpr FocasCmd read_cnc_param = {1, 1, 0x0e}; ///< cnc_rdparam
89 static constexpr FocasCmd read_macro = {1, 1, 0x15}; ///< cnc_rdmacro
90 static constexpr FocasCmd set_macro = {1, 1, 0x16}; ///< cnc_wrmacro
91 static constexpr FocasCmd sys_info = {1, 1, 0x18}; ///< cnc_sysinfo (ODBSYS)
92 static constexpr FocasCmd read_alarm = {1, 1, 0x1a}; ///< cnc_alarm (u32 status word)
93 static constexpr FocasCmd read_prg_num = {1, 1, 0x1c}; ///< cnc_rdprgnum (main/running)
94 static constexpr FocasCmd read_seq_num = {1, 1, 0x1d}; ///< cnc_rdseqnum
95 static constexpr FocasCmd read_alarm_info = {1, 1, 0x23}; ///< cnc_rdalminfo
96 static constexpr FocasCmd read_feedrate = {1, 1, 0x24}; ///< cnc_actf (actual feed)
97 static constexpr FocasCmd read_spindle = {1, 1, 0x25}; ///< cnc_acts (actual spindle speed)
98 static constexpr FocasCmd read_position = {1, 1, 0x26}; ///< cnc_rdposition / axis read
99 static constexpr FocasCmd read_diag = {1, 1, 0x30}; ///< cnc_diagnoss
100 static constexpr FocasCmd read_spindle2 = {1, 1, 0x40}; ///< cnc_acts2 (speed + load)
101 static constexpr FocasCmd read_datetime = {1, 1, 0x45}; ///< cnc_rdtimer (v1=0 date, 1 time)
102 static constexpr FocasCmd read_servo_load = {1, 1, 0x56}; ///< servo load, MAX_AXIS
103 static constexpr FocasCmd read_axis_names = {1, 1, 0x89}; ///< controlled-axis names
104 static constexpr FocasCmd read_spindle_names = {1, 1, 0x8a};
105 static constexpr FocasCmd read_cnc_param3 = {1, 1, 0x8d}; ///< cnc_rdparam3
106 static constexpr FocasCmd read_macro_dbl = {1, 1, 0xa7}; ///< cnc_rdmacror (double)
107 static constexpr FocasCmd read_pmc = {2, 1, 0x8001}; ///< pmc_rdpmcrng
108};
109
110/// Position/axis read kinds (SysInfo 0x26 `v1`); the axis argument is `v2` (0 = all axes).
111struct FocasPosKind
112{
113 static constexpr int32_t machine = 1; ///< machine (reference) coordinate
114 static constexpr int32_t absolute = 4; ///< absolute (program) coordinate
115 static constexpr int32_t relative = 6; ///< relative coordinate
116 static constexpr int32_t distance = 7; ///< distance to go
117 static constexpr int32_t skip = 8; ///< skip position
118};
119
120/// A parsed frame envelope; `payload`/`payload_len` point into the caller's buffer (no copy).
121struct FocasFrame
122{
123 FocasFrameType type;
124 uint16_t version;
125 const uint8_t *payload;
126 uint16_t payload_len;
127};
128
129/// A parsed command response; `data`/`data_len` point into the caller's buffer (no copy).
130struct FocasResponse
131{
132 uint16_t c1; ///< echoed selector
133 uint16_t c2;
134 uint16_t c3;
135 int16_t status; ///< FOCAS return code (0 = EW_OK; negative = error)
136 const uint8_t *data;
137 uint16_t data_len;
138};
139
140/// Parsed SysInfo (ODBSYS). The char fields are NUL-terminated copies of fixed-width ASCII fields.
141struct FocasSysInfo
142{
143 uint16_t add_info;
144 uint16_t max_axis;
145 char cnc_type[3]; ///< e.g. "30", "16", "0i"
146 char mt_type[3]; ///< " M" milling / " T" turning
147 char series[5]; ///< software series
148 char version[5]; ///< software version
149 char axes[3]; ///< controlled-axis count as ASCII
150};
151
152/// One decoded FOCAS 8-octet numeric value. The scaled value is `data / base^exp`; `valid` is
153/// false for the 0xFFFF sentinel or an unrecognized base (only 2 and 10 are decimal-scaled).
154struct FocasValue
155{
156 int32_t data;
157 uint8_t base; ///< 2 or 10
158 uint8_t exp; ///< decimal places
159 bool valid;
160};
161
162// ---------------------------------------------------------------------------------------------
163// Request builders. Each writes a complete on-wire frame into `buf` and returns the total octet
164// count, or 0 if `buf` is null or `cap` is too small.
165// ---------------------------------------------------------------------------------------------
166
167/// Open the session (payload = FRAME_DST). Send first; expect a 0x0102 response frame.
168size_t focas_build_open(uint8_t *buf, size_t cap);
169
170/// Close the session (empty payload). Send last; expect a 0x0202 response frame.
171size_t focas_build_close(uint8_t *buf, size_t cap);
172
173/// Generic command: selector + five i32 arguments + optional trailing `extra` bytes.
174size_t focas_build_request(uint8_t *buf, size_t cap, FocasCmd cmd, int32_t v1, int32_t v2, int32_t v3, int32_t v4,
175 int32_t v5, const uint8_t *extra, size_t extra_len);
176
177/// SysInfo (1/1/0x18): no arguments. Response = FocasSysInfo.
178size_t focas_build_sysinfo(uint8_t *buf, size_t cap);
179
180/// Alarm status (1/1/0x1a): no arguments. Response = a big-endian u32 alarm bitmask.
181size_t focas_build_read_alarm(uint8_t *buf, size_t cap);
182
183/// Read CNC parameter(s) (1/1/0x0e): parameter range [first, last], axis (0 = not axis-specific).
184size_t focas_build_read_param(uint8_t *buf, size_t cap, int32_t first, int32_t last, int32_t axis);
185
186/// Read macro variables (1/1/0x15): variable range [first, last]. Response values are 8-octet.
187size_t focas_build_read_macro(uint8_t *buf, size_t cap, int32_t first, int32_t last);
188
189/// Read position/axis data (1/1/0x26): `kind` (FocasPosKind), `axis` (0 = all). 8-octet values.
190size_t focas_build_read_position(uint8_t *buf, size_t cap, int32_t kind, int32_t axis);
191
192/// Read actual feedrate (1/1/0x24): no arguments. Response = one 8-octet value.
193size_t focas_build_read_feedrate(uint8_t *buf, size_t cap);
194
195/// Read actual spindle speed (1/1/0x25): no arguments. Response = one 8-octet value.
196size_t focas_build_read_spindle(uint8_t *buf, size_t cap);
197
198// ---------------------------------------------------------------------------------------------
199// Response parsers. Each returns false on a short/garbled buffer.
200// ---------------------------------------------------------------------------------------------
201
202/// Validate the 10-octet envelope (magic + version) and expose the payload (into `buf`).
203bool focas_parse_frame(const uint8_t *buf, size_t len, FocasFrame *out);
204
205/// Decode a command-response payload (echoed selector + status + length + data) into `out`.
206bool focas_parse_response(const uint8_t *payload, size_t payload_len, FocasResponse *out);
207
208/// Convenience: validate a whole command-response frame (type 0x2102) straight into `out`.
209bool focas_parse_command_frame(const uint8_t *buf, size_t len, FocasResponse *out);
210
211/// SysInfo response data: ODBSYS (addinfo + maxaxis + cnctype + mttype + series + version + axes).
212bool focas_parse_sysinfo(const uint8_t *data, size_t data_len, FocasSysInfo *out);
213
214/// Alarm response data: a single big-endian u32 alarm bitmask.
215bool focas_parse_alarm(const uint8_t *data, size_t data_len, uint32_t *alarm_status);
216
217/// Decode one FOCAS 8-octet value at `chunk`. Returns true only for a usable value (`out->valid`
218/// is set the same way); false if fewer than 8 octets are available.
219bool focas_decode8(const uint8_t *chunk, size_t len, FocasValue *out);
220
221/// The scaled value `data / base^exp` as a float (0 for an invalid value).
222float focas_value_f(const FocasValue *v);
223
224#endif // DETWS_ENABLE_FOCAS
225
226#endif // DETERMINISTICESPASYNCWEBSERVER_FOCAS_H
User-facing configuration for DeterministicESPAsyncWebServer.