ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
haas_mdc.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 haas_mdc.cpp
6 * @brief Haas Machine Data Collection (MDC) Q-command codec (pure, host-tested).
7 */
8
10
11#if PC_ENABLE_HAAS_MDC
12
13#include "shared_primitives/strbuf.h" // pc_sb frame builder
14
15// Trim leading and trailing spaces from [s, s+len); updates s and len in place.
16static void trim(const char **s, size_t *len)
17{
18 const char *p = *s;
19 size_t n = *len;
20 while (n && p[n - 1] == ' ')
21 {
22 n--;
23 }
24 while (n && *p == ' ')
25 {
26 p++;
27 n--;
28 }
29 *s = p;
30 *len = n;
31}
32
33// Compare a parsed field to a NUL-terminated literal without <string.h> / strlen.
34static bool field_is(const HaasMdcResp *r, size_t idx, const char *lit)
35{
36 if (idx >= r->n_fields)
37 {
38 return false;
39 }
40 const char *f = r->field[idx];
41 size_t fl = r->field_len[idx];
42 size_t i = 0;
43 for (; i < fl; i++)
44 {
45 if (lit[i] == '\0' || f[i] != lit[i])
46 {
47 return false;
48 }
49 }
50 return lit[i] == '\0'; // both ended together
51}
52
53// Hand-rolled unsigned decimal parse of a (space-trimmed) field; false unless all digits.
54static bool parse_u32(const char *s, size_t len, uint32_t *out)
55{
56 trim(&s, &len);
57 if (len == 0)
58 {
59 return false;
60 }
61 uint32_t v = 0;
62 for (size_t i = 0; i < len; i++)
63 {
64 if (s[i] < '0' || s[i] > '9')
65 {
66 return false;
67 }
68 v = v * 10 + (uint32_t)(s[i] - '0');
69 }
70 // The false half is unreachable: parse_u32() is static and both call sites (pc_haas_mdc_parse_status,
71 // pc_haas_mdc_parse_macro) always pass the address of a local, never NULL.
72 if (out) // GCOVR_EXCL_BR_LINE out == nullptr half unreachable, see above
73 {
74 *out = v;
75 }
76 return true;
77}
78
79size_t pc_haas_mdc_build_q(char *buf, size_t cap, uint16_t qnum)
80{
81 if (!buf || cap == 0)
82 {
83 return 0;
84 }
85 pc_sb sb_q = {buf, cap, 0, true};
86 pc_sb_lit(&sb_q, "?Q");
87 pc_sb_u32(&sb_q, qnum);
88 pc_sb_ch(&sb_q, '\r');
89 return pc_sb_finish(&sb_q);
90}
91
92size_t pc_haas_mdc_build_var(char *buf, size_t cap, uint32_t var)
93{
94 if (!buf || cap == 0)
95 {
96 return 0;
97 }
98 pc_sb sb_var = {buf, cap, 0, true};
99 pc_sb_lit(&sb_var, "?Q600 ");
100 pc_sb_u32(&sb_var, var);
101 pc_sb_ch(&sb_var, '\r');
102 return pc_sb_finish(&sb_var);
103}
104
105bool pc_haas_mdc_parse(const char *buf, size_t len, HaasMdcResp *out)
106{
107 if (!buf || !out)
108 {
109 return false;
110 }
111 out->n_fields = 0;
112
113 // Locate the payload strictly between STX and the first following ETB (scan, never by offset).
114 size_t stx = 0;
115 bool have_stx = false;
116 for (size_t i = 0; i < len; i++)
117 {
118 if (buf[i] == PC_HAAS_MDC_STX)
119 {
120 stx = i;
121 have_stx = true;
122 break;
123 }
124 }
125 if (!have_stx)
126 {
127 return false;
128 }
129 size_t etb = 0;
130 bool have_etb = false;
131 for (size_t i = stx + 1; i < len; i++)
132 {
133 if (buf[i] == PC_HAAS_MDC_ETB)
134 {
135 etb = i;
136 have_etb = true;
137 break;
138 }
139 }
140 if (!have_etb)
141 {
142 return false;
143 }
144
145 const char *p = buf + stx + 1;
146 size_t plen = etb - stx - 1;
147
148 // Split the CSV payload; each field trimmed of surrounding spaces. Extra fields past the cap drop.
149 size_t start = 0;
150 for (size_t i = 0; i <= plen; i++)
151 {
152 if (i == plen || p[i] == ',')
153 {
154 const char *f = p + start;
155 size_t fl = i - start;
156 trim(&f, &fl);
157 if (out->n_fields < PC_HAAS_MDC_MAX_FIELDS)
158 {
159 out->field[out->n_fields] = f;
160 out->field_len[out->n_fields] = fl;
161 out->n_fields++;
162 }
163 start = i + 1;
164 }
165 }
166 return out->n_fields > 0;
167}
168
169bool pc_haas_mdc_field(const HaasMdcResp *r, size_t idx, const char **p, size_t *l)
170{
171 if (!r || idx >= r->n_fields)
172 {
173 return false;
174 }
175 if (p)
176 {
177 *p = r->field[idx];
178 }
179 if (l)
180 {
181 *l = r->field_len[idx];
182 }
183 return true;
184}
185
186bool pc_haas_mdc_value(const HaasMdcResp *r, const char **p, size_t *l)
187{
188 return pc_haas_mdc_field(r, 1, p, l);
189}
190
191bool pc_haas_mdc_is_error(const HaasMdcResp *r)
192{
193 return r && field_is(r, 0, "UNKNOWN");
194}
195
196bool pc_haas_mdc_parse_status(const HaasMdcResp *r, HaasMdcStatus *out)
197{
198 if (!r || !out)
199 {
200 return false;
201 }
202 out->busy = false;
203 out->program = nullptr;
204 out->program_len = 0;
205 out->status = nullptr;
206 out->status_len = 0;
207 out->parts = 0;
208 out->parts_valid = false;
209
210 if (field_is(r, 0, "STATUS"))
211 {
212 // Busy collapse: `STATUS, BUSY`.
213 out->busy = true;
214 if (r->n_fields >= 2)
215 {
216 out->status = r->field[1];
217 out->status_len = r->field_len[1];
218 }
219 return true;
220 }
221 if (field_is(r, 0, "PROGRAM") && r->n_fields >= 3)
222 {
223 // `PROGRAM, Oxxxxx, <status>, PARTS, n`.
224 out->program = r->field[1];
225 out->program_len = r->field_len[1];
226 out->status = r->field[2];
227 out->status_len = r->field_len[2];
228 if (r->n_fields >= 5)
229 {
230 uint32_t n = 0;
231 if (parse_u32(r->field[4], r->field_len[4], &n))
232 {
233 out->parts = n;
234 out->parts_valid = true;
235 }
236 }
237 return true;
238 }
239 return false;
240}
241
242bool pc_haas_mdc_parse_macro(const HaasMdcResp *r, uint32_t *var, const char **value, size_t *value_len)
243{
244 if (!r || r->n_fields < 3 || !field_is(r, 0, "MACRO"))
245 {
246 return false;
247 }
248 uint32_t v = 0;
249 if (!parse_u32(r->field[1], r->field_len[1], &v))
250 {
251 return false;
252 }
253 if (var)
254 {
255 *var = v;
256 }
257 if (value)
258 {
259 *value = r->field[2];
260 }
261 if (value_len)
262 {
263 *value_len = r->field_len[2];
264 }
265 return true;
266}
267
268bool pc_haas_mdc_dprnt_line(const char *buf, size_t len, const char **text, size_t *text_len)
269{
270 if (!buf || len == 0)
271 {
272 return false;
273 }
274 // A framed Q response carries an STX - not a DPRNT push.
275 for (size_t i = 0; i < len; i++)
276 {
277 if (buf[i] == PC_HAAS_MDC_STX)
278 {
279 return false;
280 }
281 }
282
283 const char *p = buf;
284 size_t n = len;
285 // Strip a leading prompt / newline / POPEN (DC2).
286 while (n && (*p == (char)PC_HAAS_MDC_PROMPT || *p == '\r' || *p == '\n' || *p == 0x12))
287 {
288 p++;
289 n--;
290 }
291 // Strip trailing CR / LF / PCLOS (DC4). Interior spaces are preserved (a DPRNT `*` is a space).
292 while (n && (p[n - 1] == '\r' || p[n - 1] == '\n' || p[n - 1] == 0x14))
293 {
294 n--;
295 }
296 if (n == 0)
297 {
298 return false;
299 }
300 if (text)
301 {
302 *text = p;
303 }
304 if (text_len)
305 {
306 *text_len = n;
307 }
308 return true;
309}
310
311#endif // PC_ENABLE_HAAS_MDC
Haas Machine Data Collection (MDC) Q-command codec (PC_ENABLE_HAAS_MDC) - a zero-heap codec for the d...
Bounded no-heap string builder that fails closed on overflow (one shared copy).
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
Definition strbuf.h:648
void pc_sb_ch(pc_sb *b, char c)
Append a single character.
Definition strbuf.h:186
void pc_sb_lit(pc_sb *b, const char(&s)[N])
Append a string literal. The array parameter deduces N, so the length is a constant.
Definition strbuf.h:76
void pc_sb_u32(pc_sb *b, uint32_t v)
Append v as decimal (no leading zeros; "0" for zero).
Definition strbuf.h:303
Bump-append target; ok latches false once an append would overflow cap.
Definition strbuf.h:30