ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
nmea0183.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file nmea0183.h
6 * @brief NMEA 0183 sentence codec (PROTOCORE_ENABLE_NMEA0183) - the marine / GPS ASCII protocol.
7 *
8 * NMEA 0183 sentences look like `$GPGGA,123519,4807.038,N,...*47<CR><LF>`: a `$` (or `!` for
9 * AIS-encapsulated), a comma-separated field list whose first field is the 5-char address
10 * (2-char talker id + 3-char sentence type), a `*`, and a two-hex-digit XOR checksum. This
11 * codec builds a sentence (adding the `$`, checksum, and CR/LF) and parses one (validating the
12 * checksum and splitting the fields), with `cellul.to_float` / `cellul.to_long` field-value helpers.
13 *
14 * GPS / marine receivers are cheap UART breakouts, so on an ESP32 this is a plain
15 * `HardwareSerial` link (commonly 4800 or 9600 baud); the UART is the application's. Pure
16 * codec, host-tested. Bridge position / wind / depth data onto Wi-Fi.
17 *
18 * @author Douglas Quigg (dstroy0)
19 * @date 2026
20 */
21
22#ifndef PROTOCORE_NMEA0183_H
23#define PROTOCORE_NMEA0183_H
24
25#include "protocore_config.h"
26
27#if PROTOCORE_ENABLE_NMEA0183
28
30
31/** @brief A parsed NMEA 0183 sentence. Field pointers reference the caller's buffer. */
32typedef struct Nmea0183
33{
34 char talker[3]; ///< 2-char talker id (e.g. "GP") + NUL
35 char type[4]; ///< 3-char sentence type (e.g. "GGA") + NUL
36 uint8_t field_count; ///< number of fields, including field 0 (the address)
37 const char *fields[PROTOCORE_NMEA0183_MAX_FIELDS]; ///< field 0 is the address; data is 1..n
38 uint8_t field_len[PROTOCORE_NMEA0183_MAX_FIELDS]; ///< each field's length (0 for an empty field)
39} Nmea0183;
40
41/** @brief XOR checksum over @p len octets (the sentence body between `$` and `*`). */
42uint8_t protocore_nmea0183_checksum(const char *s, size_t len);
43
44/**
45 * @brief Build a sentence from @p body (e.g. "GPGGA,123519,..."): writes `$<body>*HH\r\n` and
46 * NUL-terminates. Returns the length (excluding the NUL) or 0 on overflow.
47 */
48size_t protocore_nmea0183_build(char *buf, size_t cap, const char *body);
49
50/**
51 * @brief Parse a sentence: requires a leading `$`/`!`, validates the `*HH` XOR checksum, and
52 * splits the comma-separated fields. Fills @p out (field 0 is the address; talker / type are
53 * derived from it). Returns false on a bad frame or checksum.
54 */
55proto_bool protocore_nmea0183_parse(const char *s, size_t len, Nmea0183 *out);
56
57/** @brief Decode field @p idx as a float (false if absent / empty / non-numeric). */
58proto_bool protocore_nmea0183_field_float(const Nmea0183 *m, uint8_t idx, float *out);
59
60/** @brief Decode field @p idx as a long integer (false if absent / empty / non-numeric). */
61proto_bool protocore_nmea0183_field_int(const Nmea0183 *m, uint8_t idx, long *out);
62
63// -- typed decoders for the two common GPS position sentences --
64//
65// These lift a parsed sentence (protocore_nmea0183_parse) into a position struct: the ddmm.mmmm coordinates
66// become signed decimal degrees (hemisphere-adjusted), and the hhmmss.ss / ddmmyy fields split into their
67// components. Coordinates are read through the float field helper, so their precision is float's (~1 m).
68
69/** @brief Decoded GGA (fix data). */
70typedef struct
71{
72 uint8_t hour, minute; ///< UTC time of the fix
73 float second;
74 double lat_deg; ///< latitude in signed decimal degrees (+ N, - S); 0 if absent
75 double lon_deg; ///< longitude in signed decimal degrees (+ E, - W); 0 if absent
76 uint8_t fix_quality; ///< 0 = no fix, 1 = GPS, 2 = DGPS, ... (field 6)
77 uint8_t num_sats; ///< satellites used in the fix
78 float hdop; ///< horizontal dilution of precision
79 float alt_m; ///< altitude above mean sea level (metres)
80} protocore_nmea_gga;
81
82/** @brief Decoded RMC (recommended minimum: position + velocity + date). */
83typedef struct
84{
85 proto_bool valid; ///< true when the status field is 'A' (data valid), false for 'V' (warning)
86 uint8_t hour, minute; ///< UTC time
87 float second;
88 uint8_t day, month, year; ///< UTC date (year is the 2-digit field value, 0..99)
89 double lat_deg; ///< latitude in signed decimal degrees
90 double lon_deg; ///< longitude in signed decimal degrees
91 float speed_knots; ///< speed over ground (knots)
92 float course_deg; ///< course over ground (degrees true)
93} protocore_nmea_rmc;
94
95/**
96 * @brief Decode a parsed GGA sentence into @p out. @return true iff @p m is a GGA sentence with enough
97 * fields; false (and @p out untouched) otherwise. Empty optional fields read back as 0.
98 */
99proto_bool protocore_nmea0183_parse_gga(const Nmea0183 *m, protocore_nmea_gga *out);
100
101/**
102 * @brief Decode a parsed RMC sentence into @p out. @return true iff @p m is an RMC sentence with enough
103 * fields; false otherwise. @c valid reflects the A/V status field (a 'V' sentence still decodes).
104 */
105proto_bool protocore_nmea0183_parse_rmc(const Nmea0183 *m, protocore_nmea_rmc *out);
106
107/** @brief One satellite record from a GSV sentence. */
108typedef struct
109{
110 uint8_t prn; ///< satellite PRN / id
111 int16_t elev_deg; ///< elevation (degrees, 0..90)
112 int16_t azim_deg; ///< azimuth (degrees true, 0..359)
113 uint8_t snr_db; ///< signal-to-noise ratio (dB-Hz), valid only when @ref snr_valid
114 proto_bool snr_valid; ///< false when the SNR field is blank (the satellite is not being tracked)
115} protocore_nmea_gsv_sat;
116
117/** @brief Decoded GSV (satellites in view). One sentence carries up to four satellite records; a full sky
118 * view spans @ref total_msgs sentences. */
119typedef struct
120{
121 uint8_t total_msgs; ///< total GSV sentences in this cycle
122 uint8_t msg_num; ///< this sentence's number (1-based)
123 uint8_t sats_in_view; ///< total satellites in view across the cycle
124 uint8_t sat_count; ///< satellite records present in THIS sentence (0..4)
125 protocore_nmea_gsv_sat sats[4];
126} protocore_nmea_gsv;
127
128/**
129 * @brief Decode a parsed GSV sentence into @p out. @return true iff @p m is a GSV sentence with at least
130 * the 3-field header; the per-satellite records present in this sentence are filled (0..4).
131 */
132proto_bool protocore_nmea0183_parse_gsv(const Nmea0183 *m, protocore_nmea_gsv *out);
133
134/** @brief Decoded ZDA (UTC time + calendar date + local zone offset). Unlike RMC this carries the full
135 * 4-digit year, so it is the sentence to read for wall-clock time sync. */
136typedef struct
137{
138 uint8_t hour, minute; ///< UTC time
139 float second;
140 uint8_t day, month; ///< UTC date
141 uint16_t year; ///< UTC year (4-digit)
142 int8_t zone_hours; ///< local zone offset hours (-13..+13); 0 if the field is absent
143 uint8_t zone_minutes; ///< local zone offset minutes (0..59); 0 if the field is absent
144} protocore_nmea_zda;
145
146/**
147 * @brief Decode a parsed ZDA sentence into @p out. @return true iff @p m is a ZDA sentence with at least
148 * the time / day / month / year fields; false otherwise. The zone offset reads back 0 when absent.
149 */
150proto_bool protocore_nmea0183_parse_zda(const Nmea0183 *m, protocore_nmea_zda *out);
151
152/** @brief Decoded VTG (course over ground + ground speed). The course-over-ground vector complements the
153 * RMC/GGA position - it is the sentence to read for heading and speed. */
154typedef struct
155{
156 float course_true_deg; ///< course over ground, degrees true (0 if the field is absent)
157 float course_mag_deg; ///< course over ground, degrees magnetic (0 if absent)
158 float speed_knots; ///< speed over ground in knots (0 if absent)
159 float speed_kmh; ///< speed over ground in km/h (0 if absent)
160 char mode; ///< NMEA 2.3+ mode indicator ('A'/'D'/'E'/'N'), or '\0' when the field is absent
161} protocore_nmea_vtg;
162
163/**
164 * @brief Decode a parsed VTG sentence into @p out. @return true iff @p m is a VTG sentence with at least the
165 * course / speed fields (through the km/h unit); false otherwise. The mode reads back '\0' when absent.
166 */
167proto_bool protocore_nmea0183_parse_vtg(const Nmea0183 *m, protocore_nmea_vtg *out);
168
169/** @brief Decoded GSA (GPS DOP + the satellites active in the fix). Where GSV lists satellites in view and
170 * GGA gives the fix quality, GSA gives the 2D/3D fix mode, which satellites were used, and all three DOPs. */
171typedef struct
172{
173 char mode; ///< selection mode: 'M' manual, 'A' automatic 2D/3D
174 uint8_t fix_type; ///< 1 = no fix, 2 = 2D, 3 = 3D
175 uint8_t sat_count; ///< number of satellite PRNs present (0..12)
176 uint8_t sats[12]; ///< PRNs of the satellites used in the fix
177 float pdop; ///< position (3D) dilution of precision
178 float hdop; ///< horizontal dilution of precision
179 float vdop; ///< vertical dilution of precision
180} protocore_nmea_gsa;
181
182/**
183 * @brief Decode a parsed GSA sentence into @p out. @return true iff @p m is a GSA sentence with the full
184 * field set (through VDOP); false otherwise. Empty PRN slots are skipped (not counted).
185 */
186proto_bool protocore_nmea0183_parse_gsa(const Nmea0183 *m, protocore_nmea_gsa *out);
187
188/** @brief Decoded MWV (wind speed + angle): the standard wind-instrument sentence. */
189typedef struct
190{
191 float wind_angle_deg; ///< wind angle (0..359 degrees)
192 char reference; ///< 'R' relative (apparent) or 'T' true, or '\0' if the field is absent
193 float wind_speed; ///< wind speed, in the units given by @ref speed_units
194 char speed_units; ///< 'K' km/h, 'M' m/s, 'N' knots, or '\0' if absent
195 proto_bool valid; ///< status field: true for 'A' (valid), false for 'V'
196} protocore_nmea_mwv;
197
198/**
199 * @brief Decode a parsed MWV sentence into @p out. @return true iff @p m is an MWV sentence with the angle /
200 * reference / speed / units / status fields; false otherwise.
201 */
202proto_bool protocore_nmea0183_parse_mwv(const Nmea0183 *m, protocore_nmea_mwv *out);
203
204/** @brief Decoded DPT (depth of water): depth relative to the transducer plus the transducer offset. */
205typedef struct
206{
207 float depth_m; ///< water depth relative to the transducer (meters)
208 float offset_m; ///< transducer offset (meters): + = transducer to waterline, - = transducer to keel
209 proto_bool has_range; ///< true when the optional maximum-range-scale field is present
210 float range_m; ///< maximum range scale in use (meters), valid only when @ref has_range
211} protocore_nmea_dpt;
212
213/**
214 * @brief Decode a parsed DPT sentence into @p out. @return true iff @p m is a DPT sentence with the depth
215 * and offset fields; false otherwise. The optional range scale sets @ref protocore_nmea_dpt::has_range.
216 */
217proto_bool protocore_nmea0183_parse_dpt(const Nmea0183 *m, protocore_nmea_dpt *out);
218
219/** @brief Decoded HDG (magnetic heading + deviation + variation): the compass sentence. The deviation and
220 * variation are signed with East positive / West negative, so true heading = heading + deviation + variation. */
221typedef struct
222{
223 float heading_deg; ///< magnetic sensor heading (degrees)
224 float deviation_deg; ///< magnetic deviation (degrees), + East / - West; 0 if absent
225 float variation_deg; ///< magnetic variation (degrees), + East / - West; 0 if absent
226} protocore_nmea_hdg;
227
228/**
229 * @brief Decode a parsed HDG sentence into @p out. @return true iff @p m is an HDG sentence with the heading
230 * / deviation / variation fields; false otherwise. The E/W direction fields are folded into the sign.
231 */
232proto_bool protocore_nmea0183_parse_hdg(const Nmea0183 *m, protocore_nmea_hdg *out);
233
234/** @brief Decoded GLL (geographic position - latitude / longitude): position + UTC time + validity. Where
235 * GGA carries the full fix quality and RMC adds velocity, GLL is the minimal position report. */
236typedef struct
237{
238 double lat_deg; ///< latitude in signed decimal degrees (+ N, - S); 0 if absent
239 double lon_deg; ///< longitude in signed decimal degrees (+ E, - W); 0 if absent
240 uint8_t hour, minute; ///< UTC time of the position
241 float second;
242 proto_bool valid; ///< true when the status field is 'A' (data valid), false for 'V' (warning)
243 char mode; ///< FAA mode indicator (NMEA 2.3+), or '\0' if the field is absent
244} protocore_nmea_gll;
245
246/**
247 * @brief Decode a parsed GLL sentence into @p out. @return true iff @p m is a GLL sentence through the
248 * status field; false otherwise. @c valid reflects the A/V status (a 'V' sentence still decodes);
249 * the FAA mode indicator is set only when the (optional, NMEA 2.3+) field is present.
250 */
251proto_bool protocore_nmea0183_parse_gll(const Nmea0183 *m, protocore_nmea_gll *out);
252
253/** @brief Decoded VHW (water speed + heading): the vessel's heading and its speed through the water. Where VTG
254 * reports GPS course + speed over ground, VHW reports the heading the vessel points and its speed relative to
255 * the water (from a paddlewheel / pitot log). */
256typedef struct
257{
258 float heading_true_deg; ///< vessel heading, degrees true (0 if the field is absent)
259 float heading_mag_deg; ///< vessel heading, degrees magnetic (0 if absent)
260 float speed_knots; ///< speed through the water in knots (0 if absent)
261 float speed_kmh; ///< speed through the water in km/h (0 if absent)
262} protocore_nmea_vhw;
263
264/**
265 * @brief Decode a parsed VHW sentence into @p out. @return true iff @p m is a VHW sentence with at least the
266 * heading / speed fields (through the km/h value); false otherwise. Empty optional fields read back 0.
267 */
268proto_bool protocore_nmea0183_parse_vhw(const Nmea0183 *m, protocore_nmea_vhw *out);
269
270/** @brief Decoded VLW (distance traveled through the water): the cumulative and trip water-distance log from a
271 * paddlewheel / pitot log - the marine odometer, the through-water companion to VHW's speed. */
272typedef struct
273{
274 float total_water_nm; ///< total cumulative water distance (nautical miles; 0 if absent)
275 float trip_water_nm; ///< water distance since the last reset (nautical miles; 0 if absent)
276} protocore_nmea_vlw;
277
278/**
279 * @brief Decode a parsed VLW sentence into @p out. @return true iff @p m is a VLW sentence with at least the
280 * total + trip water-distance fields; false otherwise. Empty optional fields read back 0.
281 */
282proto_bool protocore_nmea0183_parse_vlw(const Nmea0183 *m, protocore_nmea_vlw *out);
283
285
286#endif // PROTOCORE_ENABLE_NMEA0183
287
288#endif // PROTOCORE_NMEA0183_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97