ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
focas.cpp
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.cpp
6 * @brief FANUC FOCAS Ethernet builder + parser (pure, host-tested). All fields big-endian.
7 */
8
10
11#if PC_ENABLE_FOCAS
12
13#include <string.h>
14
15// C++14 out-of-line definitions for the FocasCommand selector table. The in-class initializers in
16// focas.h declare them; a `static constexpr` member of CLASS type still needs a definition here
17// once it is odr-used (passing one to pc_focas_build() binds it to a const reference, which
18// counts). Without these the link fails with "undefined reference to FocasCommand::read_macro" -
19// it only stayed hidden where the optimizer happened to fold every use. C++17 would make these
20// implicitly inline and let the whole block go away; the library targets C++14.
21constexpr FocasCmd FocasCommand::read_cnc_param;
22constexpr FocasCmd FocasCommand::read_macro;
23constexpr FocasCmd FocasCommand::set_macro;
24constexpr FocasCmd FocasCommand::sys_info;
25constexpr FocasCmd FocasCommand::read_alarm;
26constexpr FocasCmd FocasCommand::read_prg_num;
27constexpr FocasCmd FocasCommand::read_seq_num;
28constexpr FocasCmd FocasCommand::read_alarm_info;
29constexpr FocasCmd FocasCommand::read_feedrate;
30constexpr FocasCmd FocasCommand::read_spindle;
31constexpr FocasCmd FocasCommand::read_position;
32constexpr FocasCmd FocasCommand::read_diag;
33constexpr FocasCmd FocasCommand::read_spindle2;
34constexpr FocasCmd FocasCommand::read_datetime;
35constexpr FocasCmd FocasCommand::read_servo_load;
36constexpr FocasCmd FocasCommand::read_axis_names;
37constexpr FocasCmd FocasCommand::read_spindle_names;
38constexpr FocasCmd FocasCommand::read_cnc_param3;
39constexpr FocasCmd FocasCommand::read_macro_dbl;
40constexpr FocasCmd FocasCommand::read_pmc;
41
42// FOCAS is big-endian throughout.
43static size_t put16be(uint8_t *p, uint16_t v)
44{
45 p[0] = (uint8_t)(v >> 8);
46 p[1] = (uint8_t)(v & 0xFF);
47 return 2;
48}
49
50static size_t put32be(uint8_t *p, uint32_t v)
51{
52 p[0] = (uint8_t)((v >> 24) & 0xFF);
53 p[1] = (uint8_t)((v >> 16) & 0xFF);
54 p[2] = (uint8_t)((v >> 8) & 0xFF);
55 p[3] = (uint8_t)(v & 0xFF);
56 return 4;
57}
58
59static uint16_t get16be(const uint8_t *p)
60{
61 return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
62}
63
64static uint32_t get32be(const uint8_t *p)
65{
66 return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];
67}
68
69// Write the 10-octet envelope (magic + version + type + payload length). Returns FOCAS_FRAME_HDR_LEN,
70// or 0 if the buffer cannot hold the header plus its declared payload.
71static size_t write_envelope(uint8_t *buf, size_t cap, FocasFrameType type, uint16_t payload_len)
72{
73 if (!buf || cap < (size_t)FOCAS_FRAME_HDR_LEN + payload_len)
74 {
75 return 0;
76 }
77 buf[0] = 0xA0;
78 buf[1] = 0xA0;
79 buf[2] = 0xA0;
80 buf[3] = 0xA0;
81 size_t p = 4;
82 p += put16be(buf + p, FOCAS_PROTO_VERSION);
83 p += put16be(buf + p, (uint16_t)type); // wire byte in
84 p += put16be(buf + p, payload_len);
85 return p; // == FOCAS_FRAME_HDR_LEN
86}
87
88size_t pc_focas_build_open(uint8_t *buf, size_t cap)
89{
90 size_t p = write_envelope(buf, cap, FocasFrameType::open_req, 2);
91 if (!p)
92 {
93 return 0;
94 }
95 p += put16be(buf + p, FOCAS_FRAME_DST);
96 return p;
97}
98
99size_t pc_focas_build_close(uint8_t *buf, size_t cap)
100{
101 return write_envelope(buf, cap, FocasFrameType::close_req, 0);
102}
103
104size_t pc_focas_build_request(uint8_t *buf, size_t cap, FocasCmd cmd, int32_t v1, int32_t v2, int32_t v3, int32_t v4,
105 int32_t v5, const uint8_t *extra, size_t extra_len)
106{
107 if (extra_len && !extra)
108 {
109 return 0;
110 }
111 if (extra_len > 0xFFFF - (size_t)FOCAS_REQ_BODY_LEN)
112 {
113 return 0;
114 }
115 uint16_t payload_len = (uint16_t)(FOCAS_REQ_BODY_LEN + extra_len);
116 size_t p = write_envelope(buf, cap, FocasFrameType::command_req, payload_len);
117 if (!p)
118 {
119 return 0;
120 }
121 p += put16be(buf + p, cmd.c1);
122 p += put16be(buf + p, cmd.c2);
123 p += put16be(buf + p, cmd.c3);
124 p += put32be(buf + p, (uint32_t)v1);
125 p += put32be(buf + p, (uint32_t)v2);
126 p += put32be(buf + p, (uint32_t)v3);
127 p += put32be(buf + p, (uint32_t)v4);
128 p += put32be(buf + p, (uint32_t)v5);
129 if (extra_len)
130 {
131 memcpy(buf + p, extra, extra_len);
132 p += extra_len;
133 }
134 return p;
135}
136
137size_t pc_focas_build_sysinfo(uint8_t *buf, size_t cap)
138{
139 return pc_focas_build_request(buf, cap, FocasCommand::sys_info, 0, 0, 0, 0, 0, nullptr, 0);
140}
141
142size_t pc_focas_build_read_alarm(uint8_t *buf, size_t cap)
143{
144 return pc_focas_build_request(buf, cap, FocasCommand::read_alarm, 0, 0, 0, 0, 0, nullptr, 0);
145}
146
147size_t pc_focas_build_read_param(uint8_t *buf, size_t cap, int32_t first, int32_t last, int32_t axis)
148{
149 return pc_focas_build_request(buf, cap, FocasCommand::read_cnc_param, first, last, axis, 0, 0, nullptr, 0);
150}
151
152size_t pc_focas_build_read_macro(uint8_t *buf, size_t cap, int32_t first, int32_t last)
153{
154 return pc_focas_build_request(buf, cap, FocasCommand::read_macro, first, last, 0, 0, 0, nullptr, 0);
155}
156
157size_t pc_focas_build_read_position(uint8_t *buf, size_t cap, int32_t kind, int32_t axis)
158{
159 return pc_focas_build_request(buf, cap, FocasCommand::read_position, kind, axis, 0, 0, 0, nullptr, 0);
160}
161
162size_t pc_focas_build_read_feedrate(uint8_t *buf, size_t cap)
163{
164 return pc_focas_build_request(buf, cap, FocasCommand::read_feedrate, 0, 0, 0, 0, 0, nullptr, 0);
165}
166
167size_t pc_focas_build_read_spindle(uint8_t *buf, size_t cap)
168{
169 return pc_focas_build_request(buf, cap, FocasCommand::read_spindle, 0, 0, 0, 0, 0, nullptr, 0);
170}
171
172bool pc_focas_parse_frame(const uint8_t *buf, size_t len, FocasFrame *out)
173{
174 if (!buf || !out || len < (size_t)FOCAS_FRAME_HDR_LEN)
175 {
176 return false;
177 }
178 if (buf[0] != 0xA0 || buf[1] != 0xA0 || buf[2] != 0xA0 || buf[3] != 0xA0)
179 {
180 return false;
181 }
182 out->version = get16be(buf + 4);
183 out->type = (FocasFrameType)get16be(buf + 6); // wire byte out
184 out->payload_len = get16be(buf + 8);
185 if ((size_t)FOCAS_FRAME_HDR_LEN + out->payload_len > len)
186 {
187 return false;
188 }
189 out->payload = buf + FOCAS_FRAME_HDR_LEN;
190 return true;
191}
192
193bool pc_focas_parse_response(const uint8_t *payload, size_t payload_len, FocasResponse *out)
194{
195 if (!payload || !out || payload_len < (size_t)FOCAS_RESP_HDR_LEN)
196 {
197 return false;
198 }
199 out->c1 = get16be(payload);
200 out->c2 = get16be(payload + 2);
201 out->c3 = get16be(payload + 4);
202 out->status = (int16_t)get16be(payload + 6); // signed FOCAS return code
203 out->data_len = get16be(payload + 12);
204 if ((size_t)FOCAS_RESP_HDR_LEN + out->data_len > payload_len)
205 {
206 return false;
207 }
208 out->data = payload + FOCAS_RESP_HDR_LEN;
209 return true;
210}
211
212bool pc_focas_parse_command_frame(const uint8_t *buf, size_t len, FocasResponse *out)
213{
214 FocasFrame f;
215 if (!pc_focas_parse_frame(buf, len, &f))
216 {
217 return false;
218 }
219 if (f.type != FocasFrameType::command_resp)
220 {
221 return false;
222 }
223 return pc_focas_parse_response(f.payload, f.payload_len, out);
224}
225
226bool pc_focas_parse_sysinfo(const uint8_t *data, size_t data_len, FocasSysInfo *out)
227{
228 if (!data || !out || data_len < (size_t)FOCAS_SYSINFO_LEN)
229 {
230 return false;
231 }
232 out->add_info = get16be(data);
233 out->max_axis = get16be(data + 2);
234 memcpy(out->cnc_type, data + 4, 2);
235 out->cnc_type[2] = '\0';
236 memcpy(out->mt_type, data + 6, 2);
237 out->mt_type[2] = '\0';
238 memcpy(out->series, data + 8, 4);
239 out->series[4] = '\0';
240 memcpy(out->version, data + 12, 4);
241 out->version[4] = '\0';
242 memcpy(out->axes, data + 16, 2);
243 out->axes[2] = '\0';
244 return true;
245}
246
247bool pc_focas_parse_alarm(const uint8_t *data, size_t data_len, uint32_t *alarm_status)
248{
249 if (!data || !alarm_status || data_len < 4)
250 {
251 return false;
252 }
253 *alarm_status = get32be(data);
254 return true;
255}
256
257bool pc_focas_decode8(const uint8_t *chunk, size_t len, FocasValue *out)
258{
259 if (!chunk || !out || len < (size_t)FOCAS_VALUE_LEN)
260 {
261 return false;
262 }
263 out->data = (int32_t)get32be(chunk);
264 out->base = chunk[5];
265 out->exp = chunk[7];
266 // The 0xFFFF sentinel (octets 6-7) marks "no value"; only base 2 and 10 are decimal-scaled.
267 out->valid = !(chunk[6] == 0xFF && chunk[7] == 0xFF) && (out->base == 2 || out->base == 10);
268 return out->valid;
269}
270
271float pc_focas_value_f(const FocasValue *v)
272{
273 if (!v || !v->valid)
274 {
275 return 0.0f;
276 }
277 float div = 1.0f;
278 for (uint8_t i = 0; i < v->exp; i++)
279 {
280 div *= (float)v->base;
281 }
282 return (float)v->data / div;
283}
284
285#endif // PC_ENABLE_FOCAS
FANUC FOCAS Ethernet protocol codec (PC_ENABLE_FOCAS) - zero-heap request builders + response parsers...