16uint8_t pc_nmea0183_checksum(
const char *s,
size_t len)
19 for (
size_t i = 0; i < len; i++)
26static char hex_digit(uint8_t v)
29 return (
char)(v < 10 ? (
'0' + v) : (
'A' + v - 10));
32static int hex_val(
char c)
34 if (c >=
'0' && c <=
'9')
38 if (c >=
'A' && c <=
'F')
42 if (c >=
'a' && c <=
'f')
49size_t pc_nmea0183_build(
char *buf,
size_t cap,
const char *body)
55 size_t blen = strnlen(body, cap);
56 size_t total = 1 + blen + 1 + 2 + 2;
61 uint8_t cs = pc_nmea0183_checksum(body, blen);
64 memcpy(buf + p, body, blen);
67 buf[p++] = hex_digit((uint8_t)(cs >> 4));
68 buf[p++] = hex_digit(cs);
75bool pc_nmea0183_parse(
const char *s,
size_t len, Nmea0183 *out)
77 if (!s || !out || len < 4 || (s[0] !=
'$' && s[0] !=
'!'))
85 for (
size_t i = 1; i < len; i++)
93 if (s[i] ==
'\r' || s[i] ==
'\n')
98 if (!found || star + 2 >= len)
103 int hi = hex_val(s[star + 1]);
104 int lo = hex_val(s[star + 2]);
105 if (hi < 0 || lo < 0)
109 uint8_t expect = (uint8_t)((hi << 4) | lo);
110 if (pc_nmea0183_checksum(s + 1, star - 1) != expect)
118 for (
size_t i = 1; i <= star; i++)
120 if (i == star || s[i] ==
',')
122 if (fc < PC_NMEA0183_MAX_FIELDS)
124 out->fields[fc] = s + fstart;
125 out->field_len[fc] = (uint8_t)(i - fstart);
131 out->field_count = fc;
134 memset(out->talker, 0,
sizeof(out->talker));
135 memset(out->type, 0,
sizeof(out->type));
146 uint8_t al = out->field_len[0];
147 const char *a = out->fields[0];
148 for (uint8_t i = 0; i < 2 && i < al; i++)
150 out->talker[i] = a[i];
152 for (uint8_t i = 0; i < 3 && (uint8_t)(2 + i) < al; i++)
154 out->type[i] = a[2 + i];
160bool pc_nmea0183_field_float(
const Nmea0183 *m, uint8_t idx,
float *out)
162 if (!m || !out || idx >= m->field_count || m->field_len[idx] == 0)
166 const char *end = m->fields[idx];
168 float v =
pc_strtof(m->fields[idx], &end);
169 if (end == m->fields[idx])
177bool pc_nmea0183_field_int(
const Nmea0183 *m, uint8_t idx,
long *out)
179 if (!m || !out || idx >= m->field_count || m->field_len[idx] == 0)
183 const char *end = m->fields[idx];
184 long v =
pc_strtol(m->fields[idx], &end);
185 if (end == m->fields[idx])
194static double nmea_coord(
float ddmm,
char hemi)
196 int deg = (int)(ddmm / 100.0f);
197 double dec = (double)deg + ((
double)ddmm - (double)deg * 100.0) / 60.0;
198 if (hemi ==
'S' || hemi ==
's' || hemi ==
'W' || hemi ==
'w')
206static void nmea_time(
const Nmea0183 *m, uint8_t idx, uint8_t *h, uint8_t *mi,
float *s)
211 if (idx >= m->field_count || m->field_len[idx] < 6)
215 const char *f = m->fields[idx];
216 *h = (uint8_t)((f[0] -
'0') * 10 + (f[1] -
'0'));
217 *mi = (uint8_t)((f[2] -
'0') * 10 + (f[3] -
'0'));
218 const char *end = f + 4;
223static void nmea_date(
const Nmea0183 *m, uint8_t idx, uint8_t *d, uint8_t *mo, uint8_t *y)
228 if (idx >= m->field_count || m->field_len[idx] < 6)
232 const char *f = m->fields[idx];
233 *d = (uint8_t)((f[0] -
'0') * 10 + (f[1] -
'0'));
234 *mo = (uint8_t)((f[2] -
'0') * 10 + (f[3] -
'0'));
235 *y = (uint8_t)((f[4] -
'0') * 10 + (f[5] -
'0'));
238bool pc_nmea0183_parse_gga(
const Nmea0183 *m, pc_nmea_gga *out)
240 if (!m || !out || strcmp(m->type,
"GGA") != 0 || m->field_count < 10)
244 memset(out, 0,
sizeof(*out));
245 nmea_time(m, 1, &out->hour, &out->minute, &out->second);
246 float lat = 0.0f, lon = 0.0f;
247 if (pc_nmea0183_field_float(m, 2, &lat) && m->field_len[3] >= 1)
249 out->lat_deg = nmea_coord(lat, m->fields[3][0]);
251 if (pc_nmea0183_field_float(m, 4, &lon) && m->field_len[5] >= 1)
253 out->lon_deg = nmea_coord(lon, m->fields[5][0]);
256 if (pc_nmea0183_field_int(m, 6, &q))
258 out->fix_quality = (uint8_t)q;
261 if (pc_nmea0183_field_int(m, 7, &ns))
263 out->num_sats = (uint8_t)ns;
265 pc_nmea0183_field_float(m, 8, &out->hdop);
266 pc_nmea0183_field_float(m, 9, &out->alt_m);
270bool pc_nmea0183_parse_rmc(
const Nmea0183 *m, pc_nmea_rmc *out)
272 if (!m || !out || strcmp(m->type,
"RMC") != 0 || m->field_count < 10)
276 memset(out, 0,
sizeof(*out));
277 nmea_time(m, 1, &out->hour, &out->minute, &out->second);
278 out->valid = (m->field_len[2] >= 1 && (m->fields[2][0] ==
'A' || m->fields[2][0] ==
'a'));
279 float lat = 0.0f, lon = 0.0f;
280 if (pc_nmea0183_field_float(m, 3, &lat) && m->field_len[4] >= 1)
282 out->lat_deg = nmea_coord(lat, m->fields[4][0]);
284 if (pc_nmea0183_field_float(m, 5, &lon) && m->field_len[6] >= 1)
286 out->lon_deg = nmea_coord(lon, m->fields[6][0]);
288 pc_nmea0183_field_float(m, 7, &out->speed_knots);
289 pc_nmea0183_field_float(m, 8, &out->course_deg);
290 nmea_date(m, 9, &out->day, &out->month, &out->year);
294bool pc_nmea0183_parse_gsv(
const Nmea0183 *m, pc_nmea_gsv *out)
296 if (!m || !out || strcmp(m->type,
"GSV") != 0 || m->field_count < 4)
300 memset(out, 0,
sizeof(*out));
302 if (pc_nmea0183_field_int(m, 1, &v))
304 out->total_msgs = (uint8_t)v;
306 if (pc_nmea0183_field_int(m, 2, &v))
308 out->msg_num = (uint8_t)v;
310 if (pc_nmea0183_field_int(m, 3, &v))
312 out->sats_in_view = (uint8_t)v;
316 uint8_t records = (uint8_t)((m->field_count - 4) / 4);
321 out->sat_count = records;
322 for (uint8_t i = 0; i < records; i++)
324 uint8_t base = (uint8_t)(4 + i * 4);
325 pc_nmea_gsv_sat *s = &out->sats[i];
326 if (pc_nmea0183_field_int(m, base, &v))
330 if (pc_nmea0183_field_int(m, (uint8_t)(base + 1), &v))
332 s->elev_deg = (int16_t)v;
334 if (pc_nmea0183_field_int(m, (uint8_t)(base + 2), &v))
336 s->azim_deg = (int16_t)v;
339 s->snr_valid = pc_nmea0183_field_int(m, (uint8_t)(base + 3), &v);
342 s->snr_db = (uint8_t)v;
348bool pc_nmea0183_parse_zda(
const Nmea0183 *m, pc_nmea_zda *out)
350 if (!m || !out || strcmp(m->type,
"ZDA") != 0 || m->field_count < 5)
354 memset(out, 0,
sizeof(*out));
355 nmea_time(m, 1, &out->hour, &out->minute, &out->second);
357 if (pc_nmea0183_field_int(m, 2, &v))
359 out->day = (uint8_t)v;
361 if (pc_nmea0183_field_int(m, 3, &v))
363 out->month = (uint8_t)v;
365 if (pc_nmea0183_field_int(m, 4, &v))
367 out->year = (uint16_t)v;
370 if (pc_nmea0183_field_int(m, 5, &v))
372 out->zone_hours = (int8_t)v;
374 if (pc_nmea0183_field_int(m, 6, &v))
376 out->zone_minutes = (uint8_t)v;
381bool pc_nmea0183_parse_vtg(
const Nmea0183 *m, pc_nmea_vtg *out)
383 if (!m || !out || strcmp(m->type,
"VTG") != 0 || m->field_count < 9)
387 memset(out, 0,
sizeof(*out));
388 pc_nmea0183_field_float(m, 1, &out->course_true_deg);
389 pc_nmea0183_field_float(m, 3, &out->course_mag_deg);
390 pc_nmea0183_field_float(m, 5, &out->speed_knots);
391 pc_nmea0183_field_float(m, 7, &out->speed_kmh);
393 if (m->field_count > 9 && m->field_len[9] >= 1)
395 out->mode = m->fields[9][0];
400bool pc_nmea0183_parse_gsa(
const Nmea0183 *m, pc_nmea_gsa *out)
402 if (!m || !out || strcmp(m->type,
"GSA") != 0 || m->field_count < 18)
406 memset(out, 0,
sizeof(*out));
407 if (m->field_len[1] >= 1)
409 out->mode = m->fields[1][0];
412 if (pc_nmea0183_field_int(m, 2, &v))
414 out->fix_type = (uint8_t)v;
418 for (uint8_t f = 3; f <= 14; f++)
420 if (pc_nmea0183_field_int(m, f, &v))
422 out->sats[cnt++] = (uint8_t)v;
425 out->sat_count = cnt;
426 pc_nmea0183_field_float(m, 15, &out->pdop);
427 pc_nmea0183_field_float(m, 16, &out->hdop);
428 pc_nmea0183_field_float(m, 17, &out->vdop);
432bool pc_nmea0183_parse_mwv(
const Nmea0183 *m, pc_nmea_mwv *out)
434 if (!m || !out || strcmp(m->type,
"MWV") != 0 || m->field_count < 6)
438 memset(out, 0,
sizeof(*out));
439 pc_nmea0183_field_float(m, 1, &out->wind_angle_deg);
440 if (m->field_len[2] >= 1)
442 out->reference = m->fields[2][0];
444 pc_nmea0183_field_float(m, 3, &out->wind_speed);
445 if (m->field_len[4] >= 1)
447 out->speed_units = m->fields[4][0];
449 out->valid = (m->field_len[5] >= 1 && (m->fields[5][0] ==
'A' || m->fields[5][0] ==
'a'));
453bool pc_nmea0183_parse_dpt(
const Nmea0183 *m, pc_nmea_dpt *out)
455 if (!m || !out || strcmp(m->type,
"DPT") != 0 || m->field_count < 3)
459 memset(out, 0,
sizeof(*out));
460 pc_nmea0183_field_float(m, 1, &out->depth_m);
461 pc_nmea0183_field_float(m, 2, &out->offset_m);
462 out->has_range = pc_nmea0183_field_float(m, 3, &out->range_m);
466bool pc_nmea0183_parse_hdg(
const Nmea0183 *m, pc_nmea_hdg *out)
468 if (!m || !out || strcmp(m->type,
"HDG") != 0 || m->field_count < 6)
472 memset(out, 0,
sizeof(*out));
473 pc_nmea0183_field_float(m, 1, &out->heading_deg);
475 if (pc_nmea0183_field_float(m, 2, &dev))
477 out->deviation_deg = (m->field_len[3] >= 1 && (m->fields[3][0] ==
'W' || m->fields[3][0] ==
'w')) ? -dev : dev;
480 if (pc_nmea0183_field_float(m, 4, &var))
482 out->variation_deg = (m->field_len[5] >= 1 && (m->fields[5][0] ==
'W' || m->fields[5][0] ==
'w')) ? -var : var;
487bool pc_nmea0183_parse_gll(
const Nmea0183 *m, pc_nmea_gll *out)
489 if (!m || !out || strcmp(m->type,
"GLL") != 0 || m->field_count < 7)
493 memset(out, 0,
sizeof(*out));
494 float lat = 0.0f, lon = 0.0f;
495 if (pc_nmea0183_field_float(m, 1, &lat) && m->field_len[2] >= 1)
497 out->lat_deg = nmea_coord(lat, m->fields[2][0]);
499 if (pc_nmea0183_field_float(m, 3, &lon) && m->field_len[4] >= 1)
501 out->lon_deg = nmea_coord(lon, m->fields[4][0]);
503 nmea_time(m, 5, &out->hour, &out->minute, &out->second);
504 out->valid = (m->field_len[6] >= 1 && (m->fields[6][0] ==
'A' || m->fields[6][0] ==
'a'));
506 if (m->field_count > 7 && m->field_len[7] >= 1)
508 out->mode = m->fields[7][0];
513bool pc_nmea0183_parse_vhw(
const Nmea0183 *m, pc_nmea_vhw *out)
515 if (!m || !out || strcmp(m->type,
"VHW") != 0 || m->field_count < 8)
519 memset(out, 0,
sizeof(*out));
520 pc_nmea0183_field_float(m, 1, &out->heading_true_deg);
521 pc_nmea0183_field_float(m, 3, &out->heading_mag_deg);
522 pc_nmea0183_field_float(m, 5, &out->speed_knots);
523 pc_nmea0183_field_float(m, 7, &out->speed_kmh);
527bool pc_nmea0183_parse_vlw(
const Nmea0183 *m, pc_nmea_vlw *out)
529 if (!m || !out || strcmp(m->type,
"VLW") != 0 || m->field_count < 4)
533 memset(out, 0,
sizeof(*out));
534 pc_nmea0183_field_float(m, 1, &out->total_water_nm);
535 pc_nmea0183_field_float(m, 3, &out->trip_water_nm);
NMEA 0183 sentence codec (PC_ENABLE_NMEA0183) - the marine / GPS ASCII protocol.
Tiny no-stdlib base-10 number parsers (strtol/strtoul/strtof replacements).
long pc_strtol(const char *s, const char **end)
Parse a base-10 long; sets end past the digits (or to s if none).
float pc_strtof(const char *s, const char **end)
Parse a float (integer[.frac][e[+/-]exp]); sets end (or to s if none).