Merge branch 'master' of http://olsr.org/git/olsrd
[olsrd.git] / lib / pud / nmealib / src / parse.c
1 /*
2  * This file is part of nmealib.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #include <nmea/parse.h>
19
20 #include <nmea/gmath.h>
21 #include <nmea/tok.h>
22 #include <nmea/context.h>
23
24 #include <string.h>
25 #include <assert.h>
26 #include <math.h>
27 #include <ctype.h>
28 #include <stdio.h>
29
30 /** the size of the buffer to put time string (that is to be parsed) into */
31 #define NMEA_TIMEPARSE_BUF  256
32
33 /**
34  * Parse nmeaTIME (time only, no date) from a string.
35  * The format that is used (hhmmss, hhmmss.s, hhmmss.ss or hhmmss.sss) is
36  * determined by the length of the string.
37  *
38  * @param s the string
39  * @param len the length of the string
40  * @param t a pointer to the nmeaTIME structure in which to store the parsed time
41  * @return true on success, false otherwise
42  */
43 static bool _nmea_parse_time(const char *s, const int len, nmeaTIME *t) {
44   assert(s);
45   assert(t);
46
47   if (len == (sizeof("hhmmss") - 1)) {
48     t->hsec = 0;
49     return (3 == nmea_scanf(s, len, "%2d%2d%2d", &t->hour, &t->min, &t->sec));
50   }
51
52   if (len == (sizeof("hhmmss.s") - 1)) {
53     if (4 == nmea_scanf(s, len, "%2d%2d%2d.%d", &t->hour, &t->min, &t->sec, &t->hsec)) {
54       t->hsec *= 10;
55       return true;
56     }
57     return false;
58   }
59
60   if (len == (sizeof("hhmmss.ss") - 1)) {
61     return (4 == nmea_scanf(s, len, "%2d%2d%2d.%d", &t->hour, &t->min, &t->sec, &t->hsec));
62   }
63
64   if (len == (sizeof("hhmmss.sss") - 1)) {
65     if ((4 == nmea_scanf(s, len, "%2d%2d%2d.%d", &t->hour, &t->min, &t->sec, &t->hsec))) {
66       t->hsec = (t->hsec + 9) / 10;
67       return true;
68     }
69     return false;
70   }
71
72   nmea_error("Parse error: invalid time format in %s", s);
73   return false;
74 }
75
76 /**
77  * Parse nmeaTIME (date only, no time) from a string.
78  * The month is adjusted -1 to comply with the nmeaTIME month range of [0, 11].
79  * The year is adjusted +100 for years before 90 to comply with the nmeaTIME
80  * year range of [90, 189].
81  *
82  * @param date the date
83  * @param t a pointer to the nmeaTIME structure in which to store the parsed date
84  * @return true on success, false otherwise
85  */
86 static bool _nmea_parse_date(const int date, nmeaTIME *t) {
87   assert(t);
88
89   if ((date < 0) || (date > 999999)) {
90     nmea_error("Parse error: invalid time format in %d", date);
91     return false;
92   }
93
94   t->day = date / 10000;
95   t->mon = (date / 100) % 100;
96   t->mon--;
97   t->year = date % 100;
98   if (t->year < 90) {
99     t->year += 100;
100   }
101
102   return true;
103 }
104
105 /**
106  * Validate the time fields in an nmeaTIME structure.
107  * Expects:
108  * <pre>
109  *   0 <= hour <   24
110  *   0 <= min  <   60
111  *   0 <= sec  <=  60
112  *   0 <= hsec <  100
113  * </pre>
114  *
115  * @param t a pointer to the structure
116  * @return true when valid, false otherwise
117  */
118 static bool validateTime(const nmeaTIME * t) {
119   if (!t) {
120     return false;
121   }
122
123   if (!((t->hour >= 0) && (t->hour < 24) && (t->min >= 0) && (t->min < 60) && (t->sec >= 0) && (t->sec <= 60)
124       && (t->hsec >= 0) && (t->hsec < 100))) {
125     nmea_error("Parse error: invalid time (%d:%d:%d.%d)", t->hour, t->min, t->sec, t->hsec);
126     return false;
127   }
128
129   return true;
130 }
131
132 /**
133  * Validate the date fields in an nmeaTIME structure.
134  * Expects:
135  * <pre>
136  *   year  [90, 189]
137  *   month [ 0,  11]
138  *   day   [ 1,  31]
139  * </pre>
140  *
141  * @param t a pointer to the structure
142  * @return true when valid, false otherwise
143  */
144 static bool validateDate(const nmeaTIME * t) {
145   if (!t) {
146     return false;
147   }
148
149   if (!((t->year >= 90) && (t->year <= 189) && (t->mon >= 0) && (t->mon <= 11) && (t->day >= 1) && (t->day <= 31))) {
150     nmea_error("Parse error: invalid date (%d-%d-%d - D-M-Y)", t->day, t->mon, t->year);
151     return false;
152   }
153
154   return true;
155 }
156
157 /**
158  * Validate north/south or east/west and uppercase it.
159  * Expects:
160  * <pre>
161  *   c in { n, N, s, S } (for north/south)
162  *   c in { e, E, w, W } (for east/west)
163  * </pre>
164  *
165  * @param c a pointer to the character. The character will be converted to uppercase.
166  * @param ns true: evaluate north/south, false: evaluate east/west
167  * @return true when valid, false otherwise
168  */
169 static bool validateNSEW(char * c, const bool ns) {
170   if (!c) {
171     return false;
172   }
173
174   *c = toupper(*c);
175
176   if (ns) {
177     if (!((*c == 'N') || (*c == 'S'))) {
178       nmea_error("Parse error: invalid north/south (%c)", *c);
179       return false;
180     }
181   } else {
182     if (!((*c == 'E') || (*c == 'W'))) {
183       nmea_error("Parse error: invalid east/west (%c)", *c);
184       return false;
185     }
186   }
187
188   return true;
189 }
190
191 /**
192  * Uppercase mode and validate it.
193  * Expects:
194  * <pre>
195  *   c in { A, D, E, F, M, N, P, R, S }
196  *
197  *   A = Autonomous. Satellite system used in non-differential mode in position fix
198  *   D = Differential. Satellite system used in differential mode in position fix
199  *   E = Estimated (dead reckoning) mode
200  *   F = Float RTK. Satellite system used in real time kinematic mode with floating integers
201  *   M = Manual input mode
202  *   N = No fix. Satellite system not used in position fix, or fix not valid
203  *   P = Precise. Satellite system used in precision mode. Precision mode is defined
204  *       as no deliberate degradation (such as Selective Availability) and higher
205  *       resolution code (P-code) is used to compute position fix.
206  *   R = Real Time Kinematic. Satellite system used in RTK mode with fixed integers
207  *   S = Simulator mode
208  * </pre>
209  *
210  * @param c a pointer to the character. The character will be converted to uppercase.
211  * @return true when valid, false otherwise
212  */
213 static bool validateMode(char * c) {
214   if (!c) {
215     return false;
216   }
217
218   *c = toupper(*c);
219
220   if (!((*c == 'A') || (*c == 'D') || (*c == 'E') || (*c == 'F') || (*c == 'M') || (*c == 'N') || (*c == 'P')
221       || (*c == 'R') || (*c == 'S'))) {
222     nmea_error("Parse error: invalid mode (%c)", *c);
223     return false;
224   }
225
226   return true;
227 }
228
229 /**
230  * Determine whether the given character is not allowed in an NMEA string.
231  *
232  * @param c
233  * The character to check
234  *
235  * @return
236  * - a pointer to the invalid character name/description when the string has invalid characters
237  * - NULL otherwise
238  */
239 const char * isInvalidNMEACharacter(const char * c) {
240   static const char * invalidNonAsciiCharsName = "non-ascii character";
241   static const char invalidChars[] = {
242       '$',
243       '*',
244       '!',
245       '\\',
246       '^',
247       '~'
248   };
249   static const char * invalidCharsNames[] = {
250       "sentence delimiter ($)",
251       "checksum field delimiter (*)",
252       "exclamation mark (!)",
253       "backslash (\\)",
254       "power (^)",
255       "tilde (~)"
256   };
257
258   size_t charIndex;
259
260   if (!((*c >= 32) && (*c <= 126))) {
261     return invalidNonAsciiCharsName;
262   }
263
264   for (charIndex = 0; charIndex < sizeof(invalidChars); charIndex++) {
265     if (*c == invalidChars[charIndex]) {
266       return invalidCharsNames[charIndex];
267     }
268   }
269
270   return NULL;
271 }
272
273 /**
274  * Determine whether the given string contains characters that are not allowed
275  * in an NMEA string.
276  *
277  * @param s
278  * The string to check
279  * @param len
280  * The length of the string to check
281  *
282  * @return
283  * - a pointer to the invalid character name/description when the string has invalid characters
284  * - NULL otherwise
285  */
286 const char * nmea_parse_sentence_has_invalid_chars(const char * s, const size_t len) {
287   size_t i;
288
289   if (!s || !len) {
290     return NULL;
291   }
292
293   for (i = 0; i < len; i++) {
294     const char * invalidCharName = isInvalidNMEACharacter(&s[i]);
295     if (invalidCharName) {
296       return invalidCharName;
297     }
298   }
299
300   return NULL;
301 }
302
303 /**
304  * Determine sentence type (see nmeaPACKTYPE) by the header of a string.
305  * The header is the start of an NMEA sentence, right after the $.
306  *
307  * @param s the string. must be the NMEA string, right after the initial $
308  * @param len the length of the string
309  * @return The packet type (or GPNON when it could not be determined)
310  */
311 enum nmeaPACKTYPE nmea_parse_get_sentence_type(const char *s, const int len) {
312   static const char *pheads[] = { "GPGGA", "GPGSA", "GPGSV", "GPRMC", "GPVTG" };
313   static const enum nmeaPACKTYPE types[] = { GPGGA, GPGSA, GPGSV, GPRMC, GPVTG };
314   unsigned int i;
315
316   assert(s);
317
318   if (len < 5) {
319     return GPNON;
320   }
321
322   for (i = 0; i < (sizeof(types) / sizeof(types[0])); i++) {
323     if (!memcmp(s, pheads[i], 5)) {
324       return types[i];
325     }
326   }
327
328   return GPNON;
329 }
330
331 /**
332  * Parse a GPGGA sentence from a string
333  *
334  * @param s the string
335  * @param len the length of the string
336  * @param has_checksum true when the string contains a checksum
337  * @param pack a pointer to the result structure
338  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
339  */
340 int nmea_parse_GPGGA(const char *s, const int len, bool has_checksum, nmeaGPGGA *pack) {
341   int token_count;
342   char time_buff[NMEA_TIMEPARSE_BUF];
343   size_t time_buff_len = 0;
344
345   if (!has_checksum) {
346     return 0;
347   }
348
349   assert(s);
350   assert(pack);
351
352   nmea_trace_buff(s, len);
353
354   /*
355    * Clear before parsing, to be able to detect absent fields
356    */
357   time_buff[0] = '\0';
358   pack->present = 0;
359   pack->utc.hour = -1;
360   pack->utc.min = -1;
361   pack->utc.sec = -1;
362   pack->utc.hsec = -1;
363   pack->lat = NAN;
364   pack->ns = 0;
365   pack->lon = NAN;
366   pack->ew = 0;
367   pack->sig = -1;
368   pack->satinuse = -1;
369   pack->HDOP = NAN;
370   pack->elv = NAN;
371   pack->elv_units = 0;
372   pack->diff = 0;     /* ignored */
373   pack->diff_units = 0; /* ignored */
374   pack->dgps_age = 0;   /* ignored */
375   pack->dgps_sid = 0;   /* ignored */
376
377   /* parse */
378   token_count = nmea_scanf(s, len, "$GPGGA,%s,%f,%c,%f,%c,%d,%d,%f,%f,%c,%f,%c,%f,%d*", &time_buff[0], &pack->lat,
379       &pack->ns, &pack->lon, &pack->ew, &pack->sig, &pack->satinuse, &pack->HDOP, &pack->elv, &pack->elv_units,
380       &pack->diff, &pack->diff_units, &pack->dgps_age, &pack->dgps_sid);
381
382   /* see that we have enough tokens */
383   if (token_count != 14) {
384     nmea_error("GPGGA parse error: need 14 tokens, got %d in %s", token_count, s);
385     return 0;
386   }
387
388   /* determine which fields are present and validate them */
389
390   time_buff_len = strlen(&time_buff[0]);
391   if (time_buff_len > (NMEA_TIMEPARSE_BUF - 1))
392     time_buff_len = NMEA_TIMEPARSE_BUF - 1;
393   if (time_buff_len) {
394     if (!_nmea_parse_time(&time_buff[0], time_buff_len, &pack->utc)) {
395       return 0;
396     }
397
398     if (!validateTime(&pack->utc)) {
399       return 0;
400     }
401
402     nmea_INFO_set_present(&pack->present, UTCTIME);
403   }
404   if (!isnan(pack->lat) && (pack->ns)) {
405     if (!validateNSEW(&pack->ns, true)) {
406       return 0;
407     }
408
409     nmea_INFO_set_present(&pack->present, LAT);
410   }
411   if (!isnan(pack->lon) && (pack->ew)) {
412     if (!validateNSEW(&pack->ew, false)) {
413       return 0;
414     }
415
416     nmea_INFO_set_present(&pack->present, LON);
417   }
418   if (pack->sig != -1) {
419     if (!((pack->sig >= NMEA_SIG_FIRST) && (pack->sig <= NMEA_SIG_LAST))) {
420       nmea_error("GPGGA parse error: invalid signal %d, expected [%d, %d]", pack->sig, NMEA_SIG_FIRST, NMEA_SIG_LAST);
421       return 0;
422     }
423
424     nmea_INFO_set_present(&pack->present, SIG);
425   }
426   if (pack->satinuse != -1) {
427     nmea_INFO_set_present(&pack->present, SATINUSECOUNT);
428   }
429   if (!isnan(pack->HDOP)) {
430     nmea_INFO_set_present(&pack->present, HDOP);
431   }
432   if (!isnan(pack->elv) && (pack->elv_units)) {
433     if (pack->elv_units != 'M') {
434       nmea_error("GPGGA parse error: invalid elevation unit (%c)", pack->elv_units);
435       return 0;
436     }
437
438     nmea_INFO_set_present(&pack->present, ELV);
439   }
440   /* ignore diff and diff_units */
441   /* ignore dgps_age and dgps_sid */
442
443   return 1;
444 }
445
446 /**
447  * Parse a GPGSA sentence from a string
448  *
449  * @param s the string
450  * @param len the length of the string
451  * @param has_checksum true when the string contains a checksum
452  * @param pack a pointer to the result structure
453  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
454  */
455 int nmea_parse_GPGSA(const char *s, const int len, bool has_checksum, nmeaGPGSA *pack) {
456   int token_count;
457   int i;
458
459   if (!has_checksum) {
460     return 0;
461   }
462
463   assert(s);
464   assert(pack);
465
466   nmea_trace_buff(s, len);
467
468   /*
469    * Clear before parsing, to be able to detect absent fields
470    */
471   pack->present = 0;
472   pack->fix_mode = 0;
473   pack->fix_type = -1;
474   for (i = 0; i < NMEA_MAXSAT; i++) {
475     pack->sat_prn[i] = 0;
476   }
477   pack->PDOP = NAN;
478   pack->HDOP = NAN;
479   pack->VDOP = NAN;
480
481   /* parse */
482   token_count = nmea_scanf(s, len, "$GPGSA,%c,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%f,%f,%f*", &pack->fix_mode,
483       &pack->fix_type, &pack->sat_prn[0], &pack->sat_prn[1], &pack->sat_prn[2], &pack->sat_prn[3],
484       &pack->sat_prn[4], &pack->sat_prn[5], &pack->sat_prn[6], &pack->sat_prn[7], &pack->sat_prn[8],
485       &pack->sat_prn[9], &pack->sat_prn[10], &pack->sat_prn[11], &pack->PDOP, &pack->HDOP, &pack->VDOP);
486
487   /* see that we have enough tokens */
488   if (token_count != 17) {
489     nmea_error("GPGSA parse error: need 17 tokens, got %d in %s", token_count, s);
490     return 0;
491   }
492
493   /* determine which fields are present and validate them */
494
495   pack->fix_mode = toupper(pack->fix_mode);
496   if (!((pack->fix_mode == 'A') || (pack->fix_mode == 'M'))) {
497     nmea_error("GPGSA parse error: invalid fix mode (%c)", pack->fix_mode);
498     return 0;
499   }
500   if (pack->fix_type != -1) {
501     if (!((pack->fix_type >= NMEA_FIX_FIRST) && (pack->fix_type <= NMEA_FIX_LAST))) {
502       nmea_error("GPGSA parse error: invalid fix type %d, expected [%d, %d]", pack->fix_type, NMEA_FIX_FIRST, NMEA_FIX_LAST);
503       return 0;
504     }
505
506     nmea_INFO_set_present(&pack->present, FIX);
507   }
508   for (i = 0; i < NMEA_MAXSAT; i++) {
509     if (pack->sat_prn[i] != 0) {
510       nmea_INFO_set_present(&pack->present, SATINUSE);
511       break;
512     }
513   }
514   if (!isnan(pack->PDOP)) {
515     nmea_INFO_set_present(&pack->present, PDOP);
516   }
517   if (!isnan(pack->HDOP)) {
518     nmea_INFO_set_present(&pack->present, HDOP);
519   }
520   if (!isnan(pack->VDOP)) {
521     nmea_INFO_set_present(&pack->present, VDOP);
522   }
523
524   return 1;
525 }
526
527 /**
528  * Parse a GPGSV sentence from a string
529  *
530  * @param s the string
531  * @param len the length of the string
532  * @param has_checksum true when the string contains a checksum
533  * @param pack a pointer to the result structure
534  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
535  */
536 int nmea_parse_GPGSV(const char *s, const int len, bool has_checksum, nmeaGPGSV *pack) {
537   int token_count;
538   int token_count_expected;
539   int sat_count;
540   int sat_counted = 0;
541
542   if (!has_checksum) {
543     return 0;
544   }
545
546   assert(s);
547   assert(pack);
548
549   nmea_trace_buff(s, len);
550
551   /*
552    * Clear before parsing, to be able to detect absent fields
553    */
554   memset(pack, 0, sizeof(nmeaGPGSV));
555
556   /* parse */
557   token_count = nmea_scanf(s, len, "$GPGSV,%d,%d,%d,"
558       "%d,%d,%d,%d,"
559       "%d,%d,%d,%d,"
560       "%d,%d,%d,%d,"
561       "%d,%d,%d,%d*", &pack->pack_count, &pack->pack_index, &pack->sat_count, &pack->sat_data[0].id,
562       &pack->sat_data[0].elv, &pack->sat_data[0].azimuth, &pack->sat_data[0].sig, &pack->sat_data[1].id,
563       &pack->sat_data[1].elv, &pack->sat_data[1].azimuth, &pack->sat_data[1].sig, &pack->sat_data[2].id,
564       &pack->sat_data[2].elv, &pack->sat_data[2].azimuth, &pack->sat_data[2].sig, &pack->sat_data[3].id,
565       &pack->sat_data[3].elv, &pack->sat_data[3].azimuth, &pack->sat_data[3].sig);
566
567   /* return if we have no sentences or sats */
568   if ((pack->pack_count < 1) || (pack->pack_count > NMEA_NSATPACKS) || (pack->pack_index < 1)
569       || (pack->pack_index > pack->pack_count) || (pack->sat_count < 0) || (pack->sat_count > NMEA_MAXSAT)) {
570     nmea_error("GPGSV parse error: inconsistent pack (count/index/satcount = %d/%d/%d)", pack->pack_count,
571         pack->pack_index, pack->sat_count);
572     return 0;
573   }
574
575   /* validate all sat settings and count the number of sats in the sentence */
576   for (sat_count = 0; sat_count < NMEA_SATINPACK; sat_count++) {
577     if (pack->sat_data[sat_count].id != 0) {
578       if ((pack->sat_data[sat_count].id < 0)) {
579         nmea_error("GPGSV parse error: invalid sat %d id (%d)", sat_count + 1, pack->sat_data[sat_count].id);
580         return 0;
581       }
582       if ((pack->sat_data[sat_count].elv < -90) || (pack->sat_data[sat_count].elv > 90)) {
583         nmea_error("GPGSV parse error: invalid sat %d elevation (%d)", sat_count + 1, pack->sat_data[sat_count].elv);
584         return 0;
585       }
586       if ((pack->sat_data[sat_count].azimuth < 0) || (pack->sat_data[sat_count].azimuth >= 360)) {
587         nmea_error("GPGSV parse error: invalid sat %d azimuth (%d)", sat_count + 1, pack->sat_data[sat_count].azimuth);
588         return 0;
589       }
590       if ((pack->sat_data[sat_count].sig < 0) || (pack->sat_data[sat_count].sig > 99)) {
591         nmea_error("GPGSV parse error: invalid sat %d signal (%d)", sat_count + 1, pack->sat_data[sat_count].sig);
592         return 0;
593       }
594       sat_counted++;
595     }
596   }
597
598   /* see that we have enough tokens */
599   token_count_expected = (sat_counted * 4) + 3;
600   if ((token_count < token_count_expected) || (token_count > (NMEA_SATINPACK * 4 + 3))) {
601     nmea_error("GPGSV parse error: need %d tokens, got %d", token_count_expected, token_count);
602     return 0;
603   }
604
605   /* determine which fields are present and validate them */
606
607   if (pack->sat_count > 0) {
608     nmea_INFO_set_present(&pack->present, SATINVIEW);
609   }
610
611   return 1;
612 }
613
614 /**
615  * Parse a GPRMC sentence from a string
616  *
617  * @param s the string
618  * @param len the length of the string
619  * @param has_checksum true when the string contains a checksum
620  * @param pack a pointer to the result structure
621  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
622  */
623 int nmea_parse_GPRMC(const char *s, const int len, bool has_checksum, nmeaGPRMC *pack) {
624   int token_count;
625   char time_buff[NMEA_TIMEPARSE_BUF];
626   int date;
627   size_t time_buff_len = 0;
628
629   if (!has_checksum) {
630     return 0;
631   }
632
633   assert(s);
634   assert(pack);
635
636   nmea_trace_buff(s, len);
637
638   /*
639    * Clear before parsing, to be able to detect absent fields
640    */
641   time_buff[0] = '\0';
642   date = -1;
643   pack->present = 0;
644   pack->utc.year = -1;
645   pack->utc.mon = -1;
646   pack->utc.day = -1;
647   pack->utc.hour = -1;
648   pack->utc.min = -1;
649   pack->utc.sec = -1;
650   pack->utc.hsec = -1;
651   pack->status = 0;
652   pack->lat = NAN;
653   pack->ns = 0;
654   pack->lon = NAN;
655   pack->ew = 0;
656   pack->speed = NAN;
657   pack->track = NAN;
658   pack->magvar = NAN;
659   pack->magvar_ew = 0;
660   pack->mode = 0;
661
662   /* parse */
663   token_count = nmea_scanf(s, len, "$GPRMC,%s,%c,%f,%c,%f,%c,%f,%f,%d,%f,%c,%c*", &time_buff[0], &pack->status,
664       &pack->lat, &pack->ns, &pack->lon, &pack->ew, &pack->speed, &pack->track, &date,
665       &pack->magvar, &pack->magvar_ew, &pack->mode);
666
667   /* see that we have enough tokens */
668   if ((token_count != 11) && (token_count != 12)) {
669     nmea_error("GPRMC parse error: need 11 or 12 tokens, got %d in %s", token_count, s);
670     return 0;
671   }
672
673   /* determine which fields are present and validate them */
674
675   time_buff_len = strlen(&time_buff[0]);
676   if (time_buff_len) {
677     if (!_nmea_parse_time(&time_buff[0], time_buff_len, &pack->utc)) {
678       return 0;
679     }
680
681     if (!validateTime(&pack->utc)) {
682       return 0;
683     }
684
685     nmea_INFO_set_present(&pack->present, UTCTIME);
686   }
687
688   if (!pack->status) {
689     pack->status = 'V';
690   } else {
691     pack->status = toupper(pack->status);
692     if (!((pack->status == 'A') || (pack->status == 'V'))) {
693       nmea_error("GPRMC parse error: invalid status (%c)", pack->status);
694       return 0;
695     }
696   }
697   if (!isnan(pack->lat) && (pack->ns)) {
698     if (!validateNSEW(&pack->ns, true)) {
699       return 0;
700     }
701
702     nmea_INFO_set_present(&pack->present, LAT);
703   }
704   if (!isnan(pack->lon) && (pack->ew)) {
705     if (!validateNSEW(&pack->ew, false)) {
706       return 0;
707     }
708
709     nmea_INFO_set_present(&pack->present, LON);
710   }
711   if (!isnan(pack->speed)) {
712     nmea_INFO_set_present(&pack->present, SPEED);
713   }
714   if (!isnan(pack->track)) {
715     nmea_INFO_set_present(&pack->present, TRACK);
716   }
717
718   if (date != -1) {
719     if (!_nmea_parse_date(date, &pack->utc)) {
720       return 0;
721     }
722
723     if (!validateDate(&pack->utc)) {
724       return 0;
725     }
726
727     nmea_INFO_set_present(&pack->present, UTCDATE);
728   }
729
730   if (!isnan(pack->magvar) && (pack->magvar_ew)) {
731     if (!validateNSEW(&pack->magvar_ew, false)) {
732       return 0;
733     }
734
735     nmea_INFO_set_present(&pack->present, MAGVAR);
736   }
737   if (token_count == 11) {
738     pack->mode = 'A';
739   } else {
740     if (!pack->mode) {
741       pack->mode = 'N';
742     } else {
743       if (!validateMode(&pack->mode)) {
744         return 0;
745       }
746     }
747   }
748
749   return 1;
750 }
751
752 /**
753  * Parse a GPVTG sentence from a string
754  *
755  * @param s the string
756  * @param len the length of the string
757  * @param has_checksum true when the string contains a checksum
758  * @param pack a pointer to the result structure
759  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
760  */
761 int nmea_parse_GPVTG(const char *s, const int len, bool has_checksum, nmeaGPVTG *pack) {
762   int token_count;
763
764   if (!has_checksum) {
765     return 0;
766   }
767
768   assert(s);
769   assert(pack);
770
771   nmea_trace_buff(s, len);
772
773   /*
774    * Clear before parsing, to be able to detect absent fields
775    */
776   pack->present = 0;
777   pack->track = NAN;
778   pack->track_t = 0;
779   pack->mtrack = NAN;
780   pack->mtrack_m = 0;
781   pack->spn = NAN;
782   pack->spn_n = 0;
783   pack->spk = NAN;
784   pack->spk_k = 0;
785
786   /* parse */
787   token_count = nmea_scanf(s, len, "$GPVTG,%f,%c,%f,%c,%f,%c,%f,%c*", &pack->track, &pack->track_t, &pack->mtrack,
788       &pack->mtrack_m, &pack->spn, &pack->spn_n, &pack->spk, &pack->spk_k);
789
790   /* see that we have enough tokens */
791   if (token_count != 8) {
792     nmea_error("GPVTG parse error: need 8 tokens, got %d in %s", token_count, s);
793     return 0;
794   }
795
796   /* determine which fields are present and validate them */
797
798   if (!isnan(pack->track) && (pack->track_t)) {
799     pack->track_t = toupper(pack->track_t);
800     if (pack->track_t != 'T') {
801       nmea_error("GPVTG parse error: invalid track unit, got %c, expected T", pack->track_t);
802       return 0;
803     }
804
805     nmea_INFO_set_present(&pack->present, TRACK);
806   }
807   if (!isnan(pack->mtrack) && (pack->mtrack_m)) {
808     pack->mtrack_m = toupper(pack->mtrack_m);
809     if (pack->mtrack_m != 'M') {
810       nmea_error("GPVTG parse error: invalid mtrack unit, got %c, expected M", pack->mtrack_m);
811       return 0;
812     }
813
814     nmea_INFO_set_present(&pack->present, MTRACK);
815   }
816   if (!isnan(pack->spn) && (pack->spn_n)) {
817     pack->spn_n = toupper(pack->spn_n);
818     if (pack->spn_n != 'N') {
819       nmea_error("GPVTG parse error: invalid knots speed unit, got %c, expected N", pack->spn_n);
820       return 0;
821     }
822
823     nmea_INFO_set_present(&pack->present, SPEED);
824
825     if (isnan(pack->spk)) {
826       pack->spk = pack->spn * NMEA_TUD_KNOTS;
827       pack->spk_k = 'K';
828     }
829   }
830   if (!isnan(pack->spk) && (pack->spk_k)) {
831     pack->spk_k = toupper(pack->spk_k);
832     if (pack->spk_k != 'K') {
833       nmea_error("GPVTG parse error: invalid kph speed unit, got %c, expected K", pack->spk_k);
834       return 0;
835     }
836
837     nmea_INFO_set_present(&pack->present, SPEED);
838
839     if (isnan(pack->spn)) {
840       pack->spn = pack->spk / NMEA_TUD_KNOTS;
841       pack->spn_n = 'N';
842     }
843   }
844
845   return 1;
846 }