DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
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 DETWS_ENABLE_FOCAS
12
13#include <string.h>
14
15// FOCAS is big-endian throughout.
16static size_t put16be(uint8_t *p, uint16_t v)
17{
18 p[0] = (uint8_t)(v >> 8);
19 p[1] = (uint8_t)(v & 0xFF);
20 return 2;
21}
22
23static size_t put32be(uint8_t *p, uint32_t v)
24{
25 p[0] = (uint8_t)((v >> 24) & 0xFF);
26 p[1] = (uint8_t)((v >> 16) & 0xFF);
27 p[2] = (uint8_t)((v >> 8) & 0xFF);
28 p[3] = (uint8_t)(v & 0xFF);
29 return 4;
30}
31
32static uint16_t get16be(const uint8_t *p)
33{
34 return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
35}
36
37static uint32_t get32be(const uint8_t *p)
38{
39 return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];
40}
41
42// Write the 10-octet envelope (magic + version + type + payload length). Returns FOCAS_FRAME_HDR_LEN,
43// or 0 if the buffer cannot hold the header plus its declared payload.
44static size_t write_envelope(uint8_t *buf, size_t cap, FocasFrameType type, uint16_t payload_len)
45{
46 if (!buf || cap < (size_t)FOCAS_FRAME_HDR_LEN + payload_len)
47 return 0;
48 buf[0] = 0xA0;
49 buf[1] = 0xA0;
50 buf[2] = 0xA0;
51 buf[3] = 0xA0;
52 size_t p = 4;
53 p += put16be(buf + p, FOCAS_PROTO_VERSION);
54 p += put16be(buf + p, (uint16_t)type); // wire byte in
55 p += put16be(buf + p, payload_len);
56 return p; // == FOCAS_FRAME_HDR_LEN
57}
58
59size_t focas_build_open(uint8_t *buf, size_t cap)
60{
61 size_t p = write_envelope(buf, cap, FocasFrameType::open_req, 2);
62 if (!p)
63 return 0;
64 p += put16be(buf + p, FOCAS_FRAME_DST);
65 return p;
66}
67
68size_t focas_build_close(uint8_t *buf, size_t cap)
69{
70 return write_envelope(buf, cap, FocasFrameType::close_req, 0);
71}
72
73size_t focas_build_request(uint8_t *buf, size_t cap, FocasCmd cmd, int32_t v1, int32_t v2, int32_t v3, int32_t v4,
74 int32_t v5, const uint8_t *extra, size_t extra_len)
75{
76 if (extra_len && !extra)
77 return 0;
78 if (extra_len > 0xFFFF - (size_t)FOCAS_REQ_BODY_LEN)
79 return 0;
80 uint16_t payload_len = (uint16_t)(FOCAS_REQ_BODY_LEN + extra_len);
81 size_t p = write_envelope(buf, cap, FocasFrameType::command_req, payload_len);
82 if (!p)
83 return 0;
84 p += put16be(buf + p, cmd.c1);
85 p += put16be(buf + p, cmd.c2);
86 p += put16be(buf + p, cmd.c3);
87 p += put32be(buf + p, (uint32_t)v1);
88 p += put32be(buf + p, (uint32_t)v2);
89 p += put32be(buf + p, (uint32_t)v3);
90 p += put32be(buf + p, (uint32_t)v4);
91 p += put32be(buf + p, (uint32_t)v5);
92 if (extra_len)
93 {
94 memcpy(buf + p, extra, extra_len);
95 p += extra_len;
96 }
97 return p;
98}
99
100size_t focas_build_sysinfo(uint8_t *buf, size_t cap)
101{
102 return focas_build_request(buf, cap, FocasCommand::sys_info, 0, 0, 0, 0, 0, nullptr, 0);
103}
104
105size_t focas_build_read_alarm(uint8_t *buf, size_t cap)
106{
107 return focas_build_request(buf, cap, FocasCommand::read_alarm, 0, 0, 0, 0, 0, nullptr, 0);
108}
109
110size_t focas_build_read_param(uint8_t *buf, size_t cap, int32_t first, int32_t last, int32_t axis)
111{
112 return focas_build_request(buf, cap, FocasCommand::read_cnc_param, first, last, axis, 0, 0, nullptr, 0);
113}
114
115size_t focas_build_read_macro(uint8_t *buf, size_t cap, int32_t first, int32_t last)
116{
117 return focas_build_request(buf, cap, FocasCommand::read_macro, first, last, 0, 0, 0, nullptr, 0);
118}
119
120size_t focas_build_read_position(uint8_t *buf, size_t cap, int32_t kind, int32_t axis)
121{
122 return focas_build_request(buf, cap, FocasCommand::read_position, kind, axis, 0, 0, 0, nullptr, 0);
123}
124
125size_t focas_build_read_feedrate(uint8_t *buf, size_t cap)
126{
127 return focas_build_request(buf, cap, FocasCommand::read_feedrate, 0, 0, 0, 0, 0, nullptr, 0);
128}
129
130size_t focas_build_read_spindle(uint8_t *buf, size_t cap)
131{
132 return focas_build_request(buf, cap, FocasCommand::read_spindle, 0, 0, 0, 0, 0, nullptr, 0);
133}
134
135bool focas_parse_frame(const uint8_t *buf, size_t len, FocasFrame *out)
136{
137 if (!buf || !out || len < (size_t)FOCAS_FRAME_HDR_LEN)
138 return false;
139 if (buf[0] != 0xA0 || buf[1] != 0xA0 || buf[2] != 0xA0 || buf[3] != 0xA0)
140 return false;
141 out->version = get16be(buf + 4);
142 out->type = (FocasFrameType)get16be(buf + 6); // wire byte out
143 out->payload_len = get16be(buf + 8);
144 if ((size_t)FOCAS_FRAME_HDR_LEN + out->payload_len > len)
145 return false;
146 out->payload = buf + FOCAS_FRAME_HDR_LEN;
147 return true;
148}
149
150bool focas_parse_response(const uint8_t *payload, size_t payload_len, FocasResponse *out)
151{
152 if (!payload || !out || payload_len < (size_t)FOCAS_RESP_HDR_LEN)
153 return false;
154 out->c1 = get16be(payload);
155 out->c2 = get16be(payload + 2);
156 out->c3 = get16be(payload + 4);
157 out->status = (int16_t)get16be(payload + 6); // signed FOCAS return code
158 out->data_len = get16be(payload + 12);
159 if ((size_t)FOCAS_RESP_HDR_LEN + out->data_len > payload_len)
160 return false;
161 out->data = payload + FOCAS_RESP_HDR_LEN;
162 return true;
163}
164
165bool focas_parse_command_frame(const uint8_t *buf, size_t len, FocasResponse *out)
166{
167 FocasFrame f;
168 if (!focas_parse_frame(buf, len, &f))
169 return false;
170 if (f.type != FocasFrameType::command_resp)
171 return false;
172 return focas_parse_response(f.payload, f.payload_len, out);
173}
174
175bool focas_parse_sysinfo(const uint8_t *data, size_t data_len, FocasSysInfo *out)
176{
177 if (!data || !out || data_len < (size_t)FOCAS_SYSINFO_LEN)
178 return false;
179 out->add_info = get16be(data);
180 out->max_axis = get16be(data + 2);
181 memcpy(out->cnc_type, data + 4, 2);
182 out->cnc_type[2] = '\0';
183 memcpy(out->mt_type, data + 6, 2);
184 out->mt_type[2] = '\0';
185 memcpy(out->series, data + 8, 4);
186 out->series[4] = '\0';
187 memcpy(out->version, data + 12, 4);
188 out->version[4] = '\0';
189 memcpy(out->axes, data + 16, 2);
190 out->axes[2] = '\0';
191 return true;
192}
193
194bool focas_parse_alarm(const uint8_t *data, size_t data_len, uint32_t *alarm_status)
195{
196 if (!data || !alarm_status || data_len < 4)
197 return false;
198 *alarm_status = get32be(data);
199 return true;
200}
201
202bool focas_decode8(const uint8_t *chunk, size_t len, FocasValue *out)
203{
204 if (!chunk || !out || len < (size_t)FOCAS_VALUE_LEN)
205 return false;
206 out->data = (int32_t)get32be(chunk);
207 out->base = chunk[5];
208 out->exp = chunk[7];
209 // The 0xFFFF sentinel (octets 6-7) marks "no value"; only base 2 and 10 are decimal-scaled.
210 out->valid = !(chunk[6] == 0xFF && chunk[7] == 0xFF) && (out->base == 2 || out->base == 10);
211 return out->valid;
212}
213
214float focas_value_f(const FocasValue *v)
215{
216 if (!v || !v->valid)
217 return 0.0f;
218 float div = 1.0f;
219 for (uint8_t i = 0; i < v->exp; i++)
220 div *= (float)v->base;
221 return (float)v->data / div;
222}
223
224#endif // DETWS_ENABLE_FOCAS
FANUC FOCAS Ethernet protocol codec (DETWS_ENABLE_FOCAS) - zero-heap request builders + response pars...