DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
nmea0183.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 nmea0183.cpp
6 * @brief NMEA 0183 sentence codec (pure, host-tested).
7 */
8
10
11#if DETWS_ENABLE_NMEA0183
12
14#include <string.h>
15
16uint8_t nmea0183_checksum(const char *s, size_t len)
17{
18 uint8_t cs = 0;
19 for (size_t i = 0; i < len; i++)
20 cs ^= (uint8_t)s[i];
21 return cs;
22}
23
24static char hex_digit(uint8_t v)
25{
26 v &= 0x0Fu;
27 return (char)(v < 10 ? ('0' + v) : ('A' + v - 10));
28}
29
30static int hex_val(char c)
31{
32 if (c >= '0' && c <= '9')
33 return c - '0';
34 if (c >= 'A' && c <= 'F')
35 return c - 'A' + 10;
36 if (c >= 'a' && c <= 'f')
37 return c - 'a' + 10;
38 return -1;
39}
40
41size_t nmea0183_build(char *buf, size_t cap, const char *body)
42{
43 if (!buf || !body)
44 return 0;
45 size_t blen = strnlen(body, cap);
46 size_t total = 1 + blen + 1 + 2 + 2; // '$' + body + '*' + HH + CRLF
47 if (cap < total + 1) // + NUL
48 return 0;
49 uint8_t cs = nmea0183_checksum(body, blen);
50 size_t p = 0;
51 buf[p++] = '$';
52 memcpy(buf + p, body, blen);
53 p += blen;
54 buf[p++] = '*';
55 buf[p++] = hex_digit((uint8_t)(cs >> 4));
56 buf[p++] = hex_digit(cs);
57 buf[p++] = '\r';
58 buf[p++] = '\n';
59 buf[p] = '\0';
60 return p;
61}
62
63bool nmea0183_parse(const char *s, size_t len, Nmea0183 *out)
64{
65 if (!s || !out || len < 4 || (s[0] != '$' && s[0] != '!'))
66 return false;
67
68 // Find the '*' that introduces the checksum, stopping at any CR/LF.
69 size_t star = 0;
70 bool found = false;
71 for (size_t i = 1; i < len; i++)
72 {
73 if (s[i] == '*')
74 {
75 star = i;
76 found = true;
77 break;
78 }
79 if (s[i] == '\r' || s[i] == '\n')
80 break;
81 }
82 if (!found || star + 2 >= len) // need two checksum hex digits after '*'
83 return false;
84
85 int hi = hex_val(s[star + 1]);
86 int lo = hex_val(s[star + 2]);
87 if (hi < 0 || lo < 0)
88 return false;
89 uint8_t expect = (uint8_t)((hi << 4) | lo);
90 if (nmea0183_checksum(s + 1, star - 1) != expect)
91 return false;
92
93 // Split the payload s[1..star-1] on commas (field 0 is the address).
94 uint8_t fc = 0;
95 size_t fstart = 1;
96 for (size_t i = 1; i <= star; i++)
97 {
98 if (i == star || s[i] == ',')
99 {
100 if (fc < DETWS_NMEA0183_MAX_FIELDS)
101 {
102 out->fields[fc] = s + fstart;
103 out->field_len[fc] = (uint8_t)(i - fstart);
104 fc++;
105 }
106 fstart = i + 1;
107 }
108 }
109 out->field_count = fc;
110
111 // Derive talker / type from the address field (field 0).
112 memset(out->talker, 0, sizeof(out->talker));
113 memset(out->type, 0, sizeof(out->type));
114 if (fc > 0)
115 {
116 uint8_t al = out->field_len[0];
117 const char *a = out->fields[0];
118 for (uint8_t i = 0; i < 2 && i < al; i++)
119 out->talker[i] = a[i];
120 for (uint8_t i = 0; i < 3 && (uint8_t)(2 + i) < al; i++)
121 out->type[i] = a[2 + i];
122 }
123 return true;
124}
125
126bool nmea0183_field_float(const Nmea0183 *m, uint8_t idx, float *out)
127{
128 if (!m || !out || idx >= m->field_count || m->field_len[idx] == 0)
129 return false;
130 const char *end = m->fields[idx];
131 // The field is delimited by a ',' or '*' in the source, so det_strtof stops at the field end.
132 float v = det_strtof(m->fields[idx], &end);
133 if (end == m->fields[idx])
134 return false;
135 *out = v;
136 return true;
137}
138
139bool nmea0183_field_int(const Nmea0183 *m, uint8_t idx, long *out)
140{
141 if (!m || !out || idx >= m->field_count || m->field_len[idx] == 0)
142 return false;
143 const char *end = m->fields[idx];
144 long v = det_strtol(m->fields[idx], &end);
145 if (end == m->fields[idx])
146 return false;
147 *out = v;
148 return true;
149}
150
151#endif // DETWS_ENABLE_NMEA0183
NMEA 0183 sentence codec (DETWS_ENABLE_NMEA0183) - the marine / GPS ASCII protocol.
Tiny no-stdlib base-10 number parsers (strtol/strtoul/strtof replacements).
float det_strtof(const char *s, const char **end)
Parse a float (integer[.frac][e[+/-]exp]); sets end (or to s if none).
Definition numparse.h:124
long det_strtol(const char *s, const char **end)
Parse a base-10 long; sets end past the digits (or to s if none).
Definition numparse.h:32