ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
fanuc_j519.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 fanuc_j519.h
6 * @brief FANUC Stream Motion (option J519) UDP codec (PROTOCORE_ENABLE_FANUC_J519) - the robot counterpart
7 * to the shipped FOCAS CNC codec (`services/focas`).
8 *
9 * Stream Motion is FANUC's real-time external motion interface on R-30iB / R-30iA robot controllers:
10 * an external controller streams joint (or Cartesian) setpoints to the robot over UDP at the
11 * controller's interpolation rate (typically 125 Hz or 250 Hz) and the robot answers every command
12 * with its measured state. This is a pure, zero-heap codec for that wire protocol - the caller owns
13 * the UDP socket and the real-time cadence.
14 *
15 * Wire format (UDP port @ref PROTOCORE_J519_UDP_PORT, default 60015). Every packet opens with an 8-octet
16 * header, and **every multi-octet field is BIG-endian**, network order (floats are IEEE-754
17 * binary32). The dissector cited below reads every one of them with Wireshark's big-endian calls,
18 * and the two Python clients pack them `>I` / `>H` / `>f`:
19 * @code
20 * header (8)
21 * packet type (4) u32 be - see J519Type
22 * version no (4) u32 be
23 * @endcode
24 *
25 * The packet type does NOT identify a packet on its own: the numeric space is reused per direction
26 * (type 0 is *Start* from the PC but *Robot Status* from the robot; type 3 is *Request* from the PC
27 * but *Ack* from the robot). A decoder must therefore know which way the datagram travelled - hence
28 * the direction is in the function name, not a runtime flag. Sizes disambiguate in practice
29 * (Start 8 vs Status 132, Request 16 vs Ack 184) and every parser here checks the exact length.
30 *
31 * Packets, PC -> robot:
32 * @code
33 * Start (type 0) 8 octets header only
34 * Motion (type 1) 64 octets seq, last_data, read-IO selector, data style, write-IO, 9 x f32 setpoints
35 * Stop (type 2) 8 octets header only
36 * Request (type 3) 16 octets axis no + threshold type (asks for the motion-limit tables)
37 * @endcode
38 * Packets, robot -> PC:
39 * @code
40 * Status (type 0) 132 octets seq, status bits, read-IO value, timestamp,
41 * 9 x f32 Cartesian pose + 9 x f32 joint pose + 9 x f32 motor current
42 * Ack (type 3) 184 octets axis no, threshold type, max Cartesian speed,
43 * 20 x f32 thresholds at NO load + 20 x f32 at MAX load
44 * @endcode
45 *
46 * The codec is symmetric (like `services/scpi`): the PC-side builders pair with robot-side parsers and
47 * vice versa, so a build -> parse round trip is exact and the device can act as either end (streaming
48 * controller, or a robot simulator for bench work).
49 *
50 * Field layout, packet sizes, the type codes, the I/O-type and threshold-type enumerations, and the
51 * status bit assignments were taken from the public Wireshark dissector
52 * `fanuc-stream-motion/packet-fanuc-stream-motion-j519` (the same class of public reference the FOCAS
53 * codec was cross-checked against). No FANUC source or header is used or required. Pure codec,
54 * host-tested; no heap, no stdlib.
55 *
56 * @author Douglas Quigg (dstroy0)
57 * @date 2026
58 */
59
60#ifndef PROTOCORE_FANUC_J519_H
61#define PROTOCORE_FANUC_J519_H
62
63#include "protocore_config.h" // the entry point: protocore_types.h for the widths
64
65#if PROTOCORE_ENABLE_FANUC_J519
66
68
69/** @brief Default Stream Motion UDP port on the robot controller. */
70#define PROTOCORE_J519_UDP_PORT 60015
71
72/** @brief Axis slots carried by every pose / joint / current block (the protocol always sends 9). */
73#define PROTOCORE_J519_AXES 9
74
75/** @brief Entries in each of the Ack's two motion-limit threshold tables. */
76#define PROTOCORE_J519_THRESHOLDS 20
77
78/** @brief Exact octet length of each packet (the parsers require these). */
79enum : size_t // NOSONAR(cpp:S3642): anonymous table of exact wire lengths compared as bare size_t in the parsers; enum
80 // class would force a cast at every bound check
81{
82 PROTOCORE_J519_LEN_START = 8, ///< PC -> robot Start.
83 PROTOCORE_J519_LEN_MOTION = 64, ///< PC -> robot Motion Command.
84 PROTOCORE_J519_LEN_STOP = 8, ///< PC -> robot Stop.
85 PROTOCORE_J519_LEN_REQUEST = 16, ///< PC -> robot Request.
86 PROTOCORE_J519_LEN_STATUS = 132, ///< robot -> PC Robot Status.
87 PROTOCORE_J519_LEN_ACK = 184, ///< robot -> PC Ack.
88};
89
90/**
91 * @brief The packet-type word (header octets 0..3). The numeric space is shared between directions -
92 * 0 and 3 each mean one thing from the PC and another from the robot.
93 */
94typedef enum PROTO_ENUM_PACKED
95{
96 J519_START_OR_STATUS = 0, ///< PC -> robot Start; robot -> PC Robot Status.
97 J519_MOTION = 1, ///< PC -> robot Motion Command.
98 J519_STOP = 2, ///< PC -> robot Stop.
99 J519_REQUEST_OR_ACK = 3, ///< PC -> robot Request; robot -> PC Ack.
100} J519Type;
101
102/** @brief Motion Command `data_style` - how the 9 setpoints are interpreted. */
103typedef enum PROTO_ENUM_PACKED
104{
105 J519_STYLE_CARTESIAN = 0, ///< setpoints are a Cartesian pose.
106 J519_STYLE_JOINT = 1, ///< setpoints are joint angles.
107} J519DataStyle;
108
109/** @brief FANUC I/O port class, for the read-/write-IO selectors carried alongside a Motion Command. */
110typedef enum PROTO_ENUM_PACKED
111{
112 J519_IO_NONE = 0, ///< no I/O access requested.
113 J519_IO_DI = 1, ///< digital in.
114 J519_IO_DO = 2, ///< digital out.
115 J519_IO_RI = 8, ///< robot in.
116 J519_IO_RO = 9, ///< robot out.
117 J519_IO_SI = 11, ///< operator-panel in.
118 J519_IO_SO = 12, ///< operator-panel out.
119 J519_IO_WI = 16, ///< weld in.
120 J519_IO_WO = 17, ///< weld out.
121 J519_IO_UI = 20, ///< peripheral (UOP) in.
122 J519_IO_UO = 21, ///< peripheral (UOP) out.
123 J519_IO_WSI = 26, ///< weld stick in.
124 J519_IO_WSO = 27, ///< weld stick out.
125 J519_IO_F = 35, ///< flag.
126 J519_IO_M = 36, ///< marker.
127} J519IoType;
128
129/** @brief Request / Ack `threshold_type` - which motion-limit table is being asked for. */
130typedef enum PROTO_ENUM_PACKED
131{
132 J519_THR_VELOCITY = 0, ///< deg/s.
133 J519_THR_ACCELERATION = 1, ///< deg/s^2.
134 J519_THR_JERK = 2, ///< deg/s^3.
135} J519ThresholdType;
136
137/** @brief Robot Status `status` bit masks. */
138enum : uint8_t // NOSONAR(cpp:S3642): anonymous bitmask constants OR'd/AND'd against a status octet; enum class forbids
139 // the bitwise use
140{
141 J519_STATUS_READY = 0x01, ///< ready to accept motion commands.
142 J519_STATUS_CMD_RECEIVED = 0x02, ///< a command was received.
143 J519_STATUS_SYSRDY = 0x04, ///< SYSRDY (system ready).
144 J519_STATUS_IN_MOTION = 0x08, ///< the robot is moving.
145};
146
147/** @brief PC -> robot Motion Command (@ref PROTOCORE_J519_LEN_MOTION octets on the wire). */
148typedef struct
149{
150 uint32_t version_no; ///< header version word.
151 uint32_t sequence_no; ///< command sequence number (the robot echoes it in Status).
152 uint8_t last_data; ///< non-zero marks the final command of the stream.
153 uint8_t read_io_type; ///< @ref J519IoType of the port to read back.
154 uint16_t read_io_index; ///< index of the port to read back.
155 uint16_t read_io_mask; ///< bit mask applied to the read-back port.
156 uint8_t data_style; ///< @ref J519DataStyle of @ref joint_data.
157 uint8_t write_io_type; ///< @ref J519IoType of the port to write.
158 uint16_t write_io_index; ///< index of the port to write.
159 uint16_t write_io_mask; ///< bit mask applied to the written port.
160 uint16_t write_io_value; ///< value written to the port.
161 float joint_data[PROTOCORE_J519_AXES]; ///< the 9 setpoints (joint angles or a Cartesian pose).
162} J519MotionCommand;
163
164/** @brief robot -> PC Robot Status (@ref PROTOCORE_J519_LEN_STATUS octets on the wire). */
165typedef struct
166{
167 uint32_t version_no; ///< header version word.
168 uint32_t sequence_no; ///< echoed command sequence number.
169 uint8_t status; ///< J519_STATUS_* bits.
170 uint8_t read_io_type; ///< echoed @ref J519IoType that was read.
171 uint16_t read_io_index; ///< echoed port index.
172 uint16_t read_io_mask; ///< echoed mask.
173 uint16_t read_io_value; ///< the port value read back.
174 uint32_t time_stamp; ///< controller timestamp.
175 float cartesian_pose[PROTOCORE_J519_AXES]; ///< measured Cartesian pose (world -> tool0).
176 float joint_pose[PROTOCORE_J519_AXES]; ///< measured joint angles.
177 float motor_current[PROTOCORE_J519_AXES]; ///< per-axis motor current.
178} J519RobotStatus;
179
180/** @brief PC -> robot Request for a motion-limit table (@ref PROTOCORE_J519_LEN_REQUEST octets). */
181typedef struct
182{
183 uint32_t version_no; ///< header version word.
184 uint32_t axis_no; ///< axis the thresholds are requested for.
185 uint32_t threshold_type; ///< @ref J519ThresholdType.
186} J519Request;
187
188/** @brief robot -> PC Ack carrying the motion-limit tables (@ref PROTOCORE_J519_LEN_ACK octets). */
189typedef struct
190{
191 uint32_t version_no; ///< header version word.
192 uint32_t axis_no; ///< echoed axis number.
193 uint32_t threshold_type; ///< echoed @ref J519ThresholdType.
194 uint32_t max_cart_speed; ///< maximum Cartesian speed.
195 uint32_t unknown0; ///< reserved word (undocumented; preserved verbatim).
196 float threshold_no_load[PROTOCORE_J519_THRESHOLDS]; ///< limits with no payload.
197 float threshold_max_load[PROTOCORE_J519_THRESHOLDS]; ///< limits at maximum payload.
198} J519Ack;
199
200// --- header ---------------------------------------------------------------------------------------
201
202/**
203 * @brief Read the 8-octet header without decoding the body.
204 *
205 * Because the type space is shared between directions, @p type alone does not identify the packet -
206 * pair it with the direction the datagram arrived from (and @p len) to choose a parser.
207 *
208 * @return false if @p len is under 8 octets.
209 */
210proto_bool protocore_j519_peek(const uint8_t *buf, size_t len, uint32_t *type, uint32_t *version_no);
211
212// --- PC -> robot: build ---------------------------------------------------------------------------
213
214/** @brief Build a Start packet. @return octets written (@ref PROTOCORE_J519_LEN_START), or 0 if @p cap is short. */
215size_t protocore_j519_build_start(uint8_t *buf, size_t cap, uint32_t version_no);
216
217/** @brief Build a Stop packet. @return octets written (@ref PROTOCORE_J519_LEN_STOP), or 0 if @p cap is short. */
218size_t protocore_j519_build_stop(uint8_t *buf, size_t cap, uint32_t version_no);
219
220/** @brief Build a Motion Command. @return octets written (@ref PROTOCORE_J519_LEN_MOTION), or 0 if @p cap is short. */
221size_t protocore_j519_build_motion(uint8_t *buf, size_t cap, const J519MotionCommand *cmd);
222
223/** @brief Build a Request. @return octets written (@ref PROTOCORE_J519_LEN_REQUEST), or 0 if @p cap is short. */
224size_t protocore_j519_build_request(uint8_t *buf, size_t cap, const J519Request *req);
225
226// --- PC -> robot: parse (the robot side of the link, and the round-trip check) ---------------------
227
228/** @brief Parse a Motion Command. @return false unless @p len is exactly @ref PROTOCORE_J519_LEN_MOTION and the type
229 * is 1. */
230proto_bool protocore_j519_parse_motion(const uint8_t *buf, size_t len, J519MotionCommand *out);
231
232/** @brief Parse a Request. @return false unless @p len is exactly @ref PROTOCORE_J519_LEN_REQUEST and the type is 3. */
233proto_bool protocore_j519_parse_request(const uint8_t *buf, size_t len, J519Request *out);
234
235// --- robot -> PC: build (robot simulator) ---------------------------------------------------------
236
237/** @brief Build a Robot Status. @return octets written (@ref PROTOCORE_J519_LEN_STATUS), or 0 if @p cap is short. */
238size_t protocore_j519_build_status(uint8_t *buf, size_t cap, const J519RobotStatus *st);
239
240/** @brief Build an Ack. @return octets written (@ref PROTOCORE_J519_LEN_ACK), or 0 if @p cap is short. */
241size_t protocore_j519_build_ack(uint8_t *buf, size_t cap, const J519Ack *ack);
242
243// --- robot -> PC: parse (the streaming controller) ------------------------------------------------
244
245/** @brief Parse a Robot Status. @return false unless @p len is exactly @ref PROTOCORE_J519_LEN_STATUS and the type is
246 * 0. */
247proto_bool protocore_j519_parse_status(const uint8_t *buf, size_t len, J519RobotStatus *out);
248
249/** @brief Parse an Ack. @return false unless @p len is exactly @ref PROTOCORE_J519_LEN_ACK and the type is 3. */
250proto_bool protocore_j519_parse_ack(const uint8_t *buf, size_t len, J519Ack *out);
251
253
254#endif // PROTOCORE_ENABLE_FANUC_J519
255
256#endif // PROTOCORE_FANUC_J519_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