ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ubx.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 ubx.cpp
6 * @brief u-blox UBX binary protocol codec (pure, host-tested).
7 */
8
10
11#if PC_ENABLE_UBX
12
13#include <string.h>
14
15void pc_ubx_checksum(const uint8_t *body, size_t len, uint8_t *ck_a, uint8_t *ck_b)
16{
17 uint8_t a = 0, b = 0;
18 for (size_t i = 0; i < len; i++)
19 {
20 a = (uint8_t)(a + body[i]);
21 b = (uint8_t)(b + a);
22 }
23 if (ck_a)
24 {
25 *ck_a = a;
26 }
27 if (ck_b)
28 {
29 *ck_b = b;
30 }
31}
32
33size_t pc_ubx_build(uint8_t *buf, size_t cap, uint8_t cls, uint8_t id, const uint8_t *payload, uint16_t len)
34{
35 if (!buf || (len && !payload))
36 {
37 return 0;
38 }
39 size_t total = 8u + (size_t)len; // 2 sync + cls + id + 2 len + payload + 2 checksum
40 if (cap < total)
41 {
42 return 0;
43 }
44 size_t p = 0;
45 buf[p++] = PC_UBX_SYNC1;
46 buf[p++] = PC_UBX_SYNC2;
47 buf[p++] = cls;
48 buf[p++] = id;
49 buf[p++] = (uint8_t)(len & 0xFFu);
50 buf[p++] = (uint8_t)(len >> 8);
51 for (uint16_t i = 0; i < len; i++)
52 {
53 buf[p++] = payload[i];
54 }
55 uint8_t a = 0, b = 0;
56 pc_ubx_checksum(buf + 2, (size_t)len + 4u, &a, &b); // class..payload end
57 buf[p++] = a;
58 buf[p++] = b;
59 return p;
60}
61
62size_t pc_ubx_build_poll(uint8_t *buf, size_t cap, uint8_t cls, uint8_t id)
63{
64 return pc_ubx_build(buf, cap, cls, id, nullptr, 0);
65}
66
67bool pc_ubx_parse(const uint8_t *s, size_t len, pc_ubx *out)
68{
69 if (!s || !out || len < 8)
70 {
71 return false;
72 }
73 if (s[0] != PC_UBX_SYNC1 || s[1] != PC_UBX_SYNC2)
74 {
75 return false;
76 }
77 uint16_t plen = (uint16_t)(s[4] | ((uint16_t)s[5] << 8));
78 if (len < 8u + (size_t)plen)
79 {
80 return false;
81 }
82 uint8_t a = 0, b = 0;
83 pc_ubx_checksum(s + 2, (size_t)plen + 4u, &a, &b);
84 if (a != s[6 + plen] || b != s[7 + plen])
85 {
86 return false;
87 }
88 out->cls = s[2];
89 out->id = s[3];
90 out->len = plen;
91 out->payload = s + 6;
92 return true;
93}
94
95int pc_ubx_ack(const pc_ubx *m, uint8_t *acked_cls, uint8_t *acked_id)
96{
97 if (!m || m->cls != 0x05u || m->len < 2 || !m->payload)
98 {
99 return -1;
100 }
101 if (m->id != 0x00u && m->id != 0x01u)
102 {
103 return -1;
104 }
105 if (acked_cls)
106 {
107 *acked_cls = m->payload[0];
108 }
109 if (acked_id)
110 {
111 *acked_id = m->payload[1];
112 }
113 return m->id == 0x01u ? 1 : 0; // ACK-ACK id 0x01, ACK-NAK id 0x00
114}
115
116uint16_t pc_ubx_u16(const uint8_t *p, size_t off)
117{
118 return (uint16_t)(p[off] | ((uint16_t)p[off + 1] << 8));
119}
120
121uint32_t pc_ubx_u32(const uint8_t *p, size_t off)
122{
123 return (uint32_t)p[off] | ((uint32_t)p[off + 1] << 8) | ((uint32_t)p[off + 2] << 16) | ((uint32_t)p[off + 3] << 24);
124}
125
126int16_t pc_ubx_i16(const uint8_t *p, size_t off)
127{
128 return (int16_t)pc_ubx_u16(p, off);
129}
130
131int32_t pc_ubx_i32(const uint8_t *p, size_t off)
132{
133 return (int32_t)pc_ubx_u32(p, off);
134}
135
136bool pc_ubx_parse_nav_pvt(const pc_ubx *m, pc_ubx_nav_pvt *out)
137{
138 if (!m || !out || !m->payload)
139 {
140 return false;
141 }
142 if (m->cls != PC_UBX_CLASS_NAV || m->id != PC_UBX_NAV_PVT || m->len < PC_UBX_NAV_PVT_LEN)
143 {
144 return false;
145 }
146 const uint8_t *p = m->payload;
147 out->itow_ms = pc_ubx_u32(p, 0);
148 out->year = pc_ubx_u16(p, 4);
149 out->month = p[6];
150 out->day = p[7];
151 out->hour = p[8];
152 out->minute = p[9];
153 out->second = p[10];
154 out->valid = p[11];
155 out->time_acc_ns = pc_ubx_u32(p, 12);
156 out->nano = pc_ubx_i32(p, 16);
157 out->fix_type = p[20];
158 out->flags = p[21];
159 out->num_sv = p[23];
160 out->lon_1e7 = pc_ubx_i32(p, 24);
161 out->lat_1e7 = pc_ubx_i32(p, 28);
162 out->height_mm = pc_ubx_i32(p, 32);
163 out->hmsl_mm = pc_ubx_i32(p, 36);
164 out->h_acc_mm = pc_ubx_u32(p, 40);
165 out->v_acc_mm = pc_ubx_u32(p, 44);
166 out->vel_n_mm_s = pc_ubx_i32(p, 48);
167 out->vel_e_mm_s = pc_ubx_i32(p, 52);
168 out->vel_d_mm_s = pc_ubx_i32(p, 56);
169 out->gspeed_mm_s = pc_ubx_i32(p, 60);
170 out->head_mot_1e5 = pc_ubx_i32(p, 64);
171 out->s_acc_mm_s = pc_ubx_u32(p, 68);
172 out->head_acc_1e5 = pc_ubx_u32(p, 72);
173 out->pdop_1e2 = pc_ubx_u16(p, 76);
174 return true;
175}
176
177// True iff m is a NAV-SAT frame whose declared length holds the fixed header + numSvs blocks.
178static bool ubx_nav_sat_ok(const pc_ubx *m, uint8_t *num_svs_out)
179{
180 if (!m || !m->payload || m->cls != PC_UBX_CLASS_NAV || m->id != PC_UBX_NAV_SAT)
181 {
182 return false;
183 }
184 if (m->len < PC_UBX_NAV_SAT_HDR_LEN)
185 {
186 return false;
187 }
188 uint8_t num = m->payload[5]; // numSvs
189 if ((size_t)m->len < (size_t)PC_UBX_NAV_SAT_HDR_LEN + (size_t)num * PC_UBX_NAV_SAT_ENTRY_LEN)
190 {
191 return false;
192 }
193 if (num_svs_out)
194 {
195 *num_svs_out = num;
196 }
197 return true;
198}
199
200bool pc_ubx_parse_nav_sat(const pc_ubx *m, pc_ubx_nav_sat_hdr *out)
201{
202 uint8_t num = 0;
203 if (!out || !ubx_nav_sat_ok(m, &num))
204 {
205 return false;
206 }
207 out->itow_ms = pc_ubx_u32(m->payload, 0);
208 out->version = m->payload[4];
209 out->num_svs = num;
210 return true;
211}
212
213bool pc_ubx_nav_sat_get(const pc_ubx *m, uint8_t index, pc_ubx_sat *out)
214{
215 uint8_t num = 0;
216 if (!out || !ubx_nav_sat_ok(m, &num) || index >= num)
217 {
218 return false;
219 }
220 const uint8_t *p = m->payload + PC_UBX_NAV_SAT_HDR_LEN + (size_t)index * PC_UBX_NAV_SAT_ENTRY_LEN;
221 out->gnss_id = p[0];
222 out->sv_id = p[1];
223 out->cno_dbhz = p[2];
224 out->elev_deg = (int8_t)p[3];
225 out->azim_deg = pc_ubx_i16(p, 4);
226 out->pr_res_01m = pc_ubx_i16(p, 6);
227 out->flags = pc_ubx_u32(p, 8);
228 return true;
229}
230
231bool pc_ubx_parse_nav_timeutc(const pc_ubx *m, pc_ubx_nav_time_utc *out)
232{
233 if (!m || !out || !m->payload)
234 {
235 return false;
236 }
237 if (m->cls != PC_UBX_CLASS_NAV || m->id != PC_UBX_NAV_TIMEUTC || m->len < PC_UBX_NAV_TIMEUTC_LEN)
238 {
239 return false;
240 }
241 const uint8_t *p = m->payload;
242 out->itow_ms = pc_ubx_u32(p, 0);
243 out->time_acc_ns = pc_ubx_u32(p, 4);
244 out->nano = pc_ubx_i32(p, 8);
245 out->year = pc_ubx_u16(p, 12);
246 out->month = p[14];
247 out->day = p[15];
248 out->hour = p[16];
249 out->minute = p[17];
250 out->second = p[18];
251 out->valid = p[19];
252 out->utc_valid = (p[19] & PC_UBX_TIMEUTC_VALID_UTC) != 0;
253 return true;
254}
255
256size_t pc_ubx_build_cfg_msg(uint8_t *buf, size_t cap, uint8_t cls, uint8_t id, uint8_t rate)
257{
258 const uint8_t pl[3] = {cls, id, rate}; // msgClass, msgID, rate (on the current port)
259 return pc_ubx_build(buf, cap, PC_UBX_CLASS_CFG, PC_UBX_CFG_MSG, pl, sizeof(pl));
260}
261
262size_t pc_ubx_build_cfg_rate(uint8_t *buf, size_t cap, uint16_t meas_rate_ms, uint16_t nav_rate, uint16_t time_ref)
263{
264 // measRate(U2) | navRate(U2) | timeRef(U2), all little-endian.
265 const uint8_t pl[6] = {(uint8_t)meas_rate_ms, (uint8_t)(meas_rate_ms >> 8),
266 (uint8_t)nav_rate, (uint8_t)(nav_rate >> 8),
267 (uint8_t)time_ref, (uint8_t)(time_ref >> 8)};
268 return pc_ubx_build(buf, cap, PC_UBX_CLASS_CFG, PC_UBX_CFG_RATE, pl, sizeof(pl));
269}
270
271// -- streaming demultiplexer --
272
273enum
274{
275 S_SYNC1 = 0,
276 S_SYNC2,
277 S_CLASS,
278 S_ID,
279 S_LEN_LO,
280 S_LEN_HI,
281 S_PAYLOAD,
282 S_CK_A,
283 S_CK_B,
284 S_SKIP
285};
286
287void pc_ubx_stream_init(pc_ubx_stream *st)
288{
289 if (st)
290 {
291 memset(st, 0, sizeof(*st)); // state = S_SYNC1
292 }
293}
294
295static void ck_add(pc_ubx_stream *st, uint8_t b)
296{
297 st->ck_a = (uint8_t)(st->ck_a + b);
298 st->ck_b = (uint8_t)(st->ck_b + st->ck_a);
299}
300
301// The sync-hunt phase (S_SYNC1 / S_SYNC2): find the 0xB5 0x62 frame start, passing non-frame bytes
302// through untouched. Returns the stream-feed result and advances st->state on a match.
303static int ubx_feed_sync(pc_ubx_stream *st, uint8_t b, uint8_t *passthrough)
304{
305 if (st->state == S_SYNC1)
306 {
307 if (b == PC_UBX_SYNC1)
308 {
309 st->state = S_SYNC2;
310 return PC_UBX_NONE;
311 }
312 if (passthrough)
313 {
314 *passthrough = b;
315 }
316 return PC_UBX_PASSTHROUGH;
317 }
318 // S_SYNC2
319 if (b == PC_UBX_SYNC2)
320 {
321 st->state = S_CLASS;
322 return PC_UBX_NONE;
323 }
324 st->state = S_SYNC1;
325 if (b == PC_UBX_SYNC1) // a fresh sync1; the previous one was a false start
326 {
327 st->state = S_SYNC2;
328 return PC_UBX_NONE;
329 }
330 if (passthrough)
331 {
332 *passthrough = b;
333 }
334 return PC_UBX_PASSTHROUGH;
335}
336
337int pc_ubx_stream_feed(pc_ubx_stream *st, uint8_t b, pc_ubx *out, uint8_t *passthrough)
338{
339 if (!st)
340 {
341 return PC_UBX_NONE;
342 }
343 if (st->state == S_SYNC1 || st->state == S_SYNC2)
344 {
345 return ubx_feed_sync(st, b, passthrough);
346 }
347 switch (st->state)
348 {
349 case S_CLASS:
350 st->cls = b;
351 st->ck_a = b; // Fletcher seed: first byte of the checksummed span
352 st->ck_b = b;
353 st->state = S_ID;
354 return PC_UBX_NONE;
355 case S_ID:
356 st->id = b;
357 ck_add(st, b);
358 st->state = S_LEN_LO;
359 return PC_UBX_NONE;
360 case S_LEN_LO:
361 st->len = b;
362 ck_add(st, b);
363 st->state = S_LEN_HI;
364 return PC_UBX_NONE;
365 case S_LEN_HI:
366 st->len = (uint16_t)(st->len | ((uint16_t)b << 8));
367 ck_add(st, b);
368 st->idx = 0;
369 if (st->len > PC_UBX_MAX_PAYLOAD)
370 {
371 st->skip = (uint32_t)st->len + 2u; // payload + 2 checksum octets to discard
372 st->state = S_SKIP;
373 return PC_UBX_NONE;
374 }
375 st->state = (st->len == 0) ? S_CK_A : S_PAYLOAD;
376 return PC_UBX_NONE;
377 case S_PAYLOAD:
378 st->buf[st->idx++] = b;
379 ck_add(st, b);
380 if (st->idx >= st->len)
381 {
382 st->state = S_CK_A;
383 }
384 return PC_UBX_NONE;
385 case S_CK_A:
386 st->rx_ck_a = b;
387 st->state = S_CK_B;
388 return PC_UBX_NONE;
389 case S_CK_B:
390 st->state = S_SYNC1;
391 if (st->rx_ck_a == st->ck_a && b == st->ck_b)
392 {
393 if (out)
394 {
395 out->cls = st->cls;
396 out->id = st->id;
397 out->len = st->len;
398 out->payload = st->buf;
399 }
400 return PC_UBX_FRAME;
401 }
402 return PC_UBX_NONE; // bad checksum: discard, resume hunting for sync
403 case S_SKIP:
404 st->skip--;
405 if (st->skip == 0)
406 {
407 st->state = S_SYNC1;
408 return PC_UBX_OVERFLOW;
409 }
410 return PC_UBX_NONE;
411 default: // GCOVR_EXCL_START state is always one of the enum above
412 st->state = S_SYNC1; //
413 return PC_UBX_NONE; //
414 // GCOVR_EXCL_STOP
415 }
416}
417
418#endif // PC_ENABLE_UBX
u-blox UBX binary protocol codec (PC_ENABLE_UBX) - the GNSS receiver control/nav protocol.