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