pud: update nmealib to 2.0.0
[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 invalidChars[] = { '$', '*', '!', '\\', '^', '~' };
241         static const char * invalidNonAsciiCharsName = "non-ascii character";
242         static const char * invalidCharsNames[] = { "sentence delimiter ($)", "checksum field delimiter (*)", "comma (,)",
243                         "exclamation mark (!)", "backslash (\\)", "power (^)", "tilde (~)" };
244
245         size_t charIndex;
246
247   if (!((*c >= 32) && (*c <= 126))) {
248     return invalidNonAsciiCharsName;
249   }
250
251   for (charIndex = 0; charIndex < sizeof(invalidChars); charIndex++) {
252     if (*c == invalidChars[charIndex]) {
253       return invalidCharsNames[charIndex];
254     }
255   }
256
257   return NULL;
258 }
259
260 /**
261  * Determine whether the given string contains characters that are not allowed
262  * in an NMEA string.
263  *
264  * @param s
265  * The string to check
266  * @param len
267  * The length of the string to check
268  *
269  * @return
270  * - a pointer to the invalid character name/description when the string has invalid characters
271  * - NULL otherwise
272  */
273 const char * nmea_parse_sentence_has_invalid_chars(const char * s, const size_t len) {
274         size_t i;
275
276         if (!s || !len) {
277                 return NULL;
278         }
279
280         for (i = 0; i < len; i++) {
281                 const char * invalidCharName = isInvalidNMEACharacter(&s[i]);
282                 if (invalidCharName) {
283                   return invalidCharName;
284                 }
285         }
286
287         return NULL;
288 }
289
290 /**
291  * Determine sentence type (see nmeaPACKTYPE) by the header of a string.
292  * The header is the start of an NMEA sentence, right after the $.
293  *
294  * @param s the string. must be the NMEA string, right after the initial $
295  * @param len the length of the string
296  * @return The packet type (or GPNON when it could not be determined)
297  */
298 enum nmeaPACKTYPE nmea_parse_get_sentence_type(const char *s, const int len) {
299         static const char *pheads[] = { "GPGGA", "GPGSA", "GPGSV", "GPRMC", "GPVTG" };
300         static const enum nmeaPACKTYPE types[] = { GPGGA, GPGSA, GPGSV, GPRMC, GPVTG };
301         unsigned int i;
302
303         assert(s);
304
305         if (len < 5) {
306                 return GPNON;
307         }
308
309         for (i = 0; i < (sizeof(types) / sizeof(types[0])); i++) {
310                 if (!memcmp(s, pheads[i], 5)) {
311                         return types[i];
312                 }
313         }
314
315         return GPNON;
316 }
317
318 /**
319  * Parse a GPGGA sentence from a string
320  *
321  * @param s the string
322  * @param len the length of the string
323  * @param pack a pointer to the result structure
324  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
325  */
326 int nmea_parse_GPGGA(const char *s, const int len, nmeaGPGGA *pack) {
327         int token_count;
328         char time_buff[NMEA_TIMEPARSE_BUF];
329         size_t time_buff_len = 0;
330
331         assert(s);
332         assert(pack);
333
334         nmea_trace_buff(s, len);
335
336         /*
337          * Clear before parsing, to be able to detect absent fields
338          */
339         time_buff[0] = '\0';
340         pack->present = 0;
341         pack->utc.hour = -1;
342         pack->utc.min = -1;
343         pack->utc.sec = -1;
344         pack->utc.hsec = -1;
345         pack->lat = NAN;
346         pack->ns = 0;
347         pack->lon = NAN;
348         pack->ew = 0;
349         pack->sig = -1;
350         pack->satinuse = -1;
351         pack->HDOP = NAN;
352         pack->elv = NAN;
353         pack->elv_units = 0;
354         pack->diff = 0;                 /* ignored */
355         pack->diff_units = 0;   /* ignored */
356         pack->dgps_age = 0;             /* ignored */
357         pack->dgps_sid = 0;             /* ignored */
358
359         /* parse */
360         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,
361                         &pack->ns, &pack->lon, &pack->ew, &pack->sig, &pack->satinuse, &pack->HDOP, &pack->elv, &pack->elv_units,
362                         &pack->diff, &pack->diff_units, &pack->dgps_age, &pack->dgps_sid);
363
364         /* see that we have enough tokens */
365         if (token_count != 14) {
366                 nmea_error("GPGGA parse error: need 14 tokens, got %d in %s", token_count, s);
367                 return 0;
368         }
369
370         /* determine which fields are present and validate them */
371
372         time_buff_len = strlen(&time_buff[0]);
373         if (time_buff_len > (NMEA_TIMEPARSE_BUF - 1))
374                 time_buff_len = NMEA_TIMEPARSE_BUF - 1;
375         if (time_buff_len) {
376                 if (!_nmea_parse_time(&time_buff[0], time_buff_len, &pack->utc)) {
377                         return 0;
378                 }
379
380                 if (!validateTime(&pack->utc)) {
381                         return 0;
382                 }
383
384                 nmea_INFO_set_present(&pack->present, UTCTIME);
385         }
386         if (!isnan(pack->lat) && (pack->ns)) {
387                 if (!validateNSEW(&pack->ns, true)) {
388                         return 0;
389                 }
390
391                 nmea_INFO_set_present(&pack->present, LAT);
392         }
393         if (!isnan(pack->lon) && (pack->ew)) {
394                 if (!validateNSEW(&pack->ew, false)) {
395                         return 0;
396                 }
397
398                 nmea_INFO_set_present(&pack->present, LON);
399         }
400         if (pack->sig != -1) {
401                 if (!((pack->sig >= NMEA_SIG_FIRST) && (pack->sig <= NMEA_SIG_LAST))) {
402                         nmea_error("GPGGA parse error: invalid signal %d, expected [%d, %d]", pack->sig, NMEA_SIG_FIRST, NMEA_SIG_LAST);
403                         return 0;
404                 }
405
406                 nmea_INFO_set_present(&pack->present, SIG);
407         }
408         if (pack->satinuse != -1) {
409                 nmea_INFO_set_present(&pack->present, SATINUSECOUNT);
410         }
411         if (!isnan(pack->HDOP)) {
412                 nmea_INFO_set_present(&pack->present, HDOP);
413         }
414         if (!isnan(pack->elv) && (pack->elv_units)) {
415                 if (pack->elv_units != 'M') {
416                         nmea_error("GPGGA parse error: invalid elevation unit (%c)", pack->elv_units);
417                         return 0;
418                 }
419
420                 nmea_INFO_set_present(&pack->present, ELV);
421         }
422         /* ignore diff and diff_units */
423         /* ignore dgps_age and dgps_sid */
424
425         return 1;
426 }
427
428 /**
429  * Parse a GPGSA sentence from a string
430  *
431  * @param s the string
432  * @param len the length of the string
433  * @param pack a pointer to the result structure
434  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
435  */
436 int nmea_parse_GPGSA(const char *s, const int len, nmeaGPGSA *pack) {
437         int token_count;
438         int i;
439
440         assert(s);
441         assert(pack);
442
443         nmea_trace_buff(s, len);
444
445         /*
446          * Clear before parsing, to be able to detect absent fields
447          */
448         pack->present = 0;
449         pack->fix_mode = 0;
450         pack->fix_type = -1;
451         for (i = 0; i < NMEA_MAXSAT; i++) {
452                 pack->sat_prn[i] = 0;
453         }
454         pack->PDOP = NAN;
455         pack->HDOP = NAN;
456         pack->VDOP = NAN;
457
458         /* parse */
459         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,
460                         &pack->fix_type, &pack->sat_prn[0], &pack->sat_prn[1], &pack->sat_prn[2], &pack->sat_prn[3],
461                         &pack->sat_prn[4], &pack->sat_prn[5], &pack->sat_prn[6], &pack->sat_prn[7], &pack->sat_prn[8],
462                         &pack->sat_prn[9], &pack->sat_prn[10], &pack->sat_prn[11], &pack->PDOP, &pack->HDOP, &pack->VDOP);
463
464         /* see that we have enough tokens */
465         if (token_count != 17) {
466                 nmea_error("GPGSA parse error: need 17 tokens, got %d in %s", token_count, s);
467                 return 0;
468         }
469
470         /* determine which fields are present and validate them */
471
472         pack->fix_mode = toupper(pack->fix_mode);
473         if (!((pack->fix_mode == 'A') || (pack->fix_mode == 'M'))) {
474                 nmea_error("GPGSA parse error: invalid fix mode (%c)", pack->fix_mode);
475                 return 0;
476         }
477         if (pack->fix_type != -1) {
478                 if (!((pack->fix_type >= NMEA_FIX_FIRST) && (pack->fix_type <= NMEA_FIX_LAST))) {
479                         nmea_error("GPGSA parse error: invalid fix type %d, expected [%d, %d]", pack->fix_type, NMEA_FIX_FIRST, NMEA_FIX_LAST);
480                         return 0;
481                 }
482
483                 nmea_INFO_set_present(&pack->present, FIX);
484         }
485         for (i = 0; i < NMEA_MAXSAT; i++) {
486                 if (pack->sat_prn[i] != 0) {
487                         nmea_INFO_set_present(&pack->present, SATINUSE);
488                         break;
489                 }
490         }
491         if (!isnan(pack->PDOP)) {
492                 nmea_INFO_set_present(&pack->present, PDOP);
493         }
494         if (!isnan(pack->HDOP)) {
495                 nmea_INFO_set_present(&pack->present, HDOP);
496         }
497         if (!isnan(pack->VDOP)) {
498                 nmea_INFO_set_present(&pack->present, VDOP);
499         }
500
501         return 1;
502 }
503
504 /**
505  * Parse a GPGSV sentence from a string
506  *
507  * @param s the string
508  * @param len the length of the string
509  * @param pack a pointer to the result structure
510  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
511  */
512 int nmea_parse_GPGSV(const char *s, const int len, nmeaGPGSV *pack) {
513         int token_count;
514         int token_count_expected;
515         int sat_count;
516         int sat_counted = 0;
517
518         assert(s);
519         assert(pack);
520
521         nmea_trace_buff(s, len);
522
523         /*
524          * Clear before parsing, to be able to detect absent fields
525          */
526         memset(pack, 0, sizeof(nmeaGPGSV));
527
528         /* parse */
529         token_count = nmea_scanf(s, len, "$GPGSV,%d,%d,%d,"
530                         "%d,%d,%d,%d,"
531                         "%d,%d,%d,%d,"
532                         "%d,%d,%d,%d,"
533                         "%d,%d,%d,%d*", &pack->pack_count, &pack->pack_index, &pack->sat_count, &pack->sat_data[0].id,
534                         &pack->sat_data[0].elv, &pack->sat_data[0].azimuth, &pack->sat_data[0].sig, &pack->sat_data[1].id,
535                         &pack->sat_data[1].elv, &pack->sat_data[1].azimuth, &pack->sat_data[1].sig, &pack->sat_data[2].id,
536                         &pack->sat_data[2].elv, &pack->sat_data[2].azimuth, &pack->sat_data[2].sig, &pack->sat_data[3].id,
537                         &pack->sat_data[3].elv, &pack->sat_data[3].azimuth, &pack->sat_data[3].sig);
538
539         /* return if we have no sentences or sats */
540         if ((pack->pack_count < 1) || (pack->pack_count > NMEA_NSATPACKS) || (pack->pack_index < 1)
541                         || (pack->pack_index > pack->pack_count) || (pack->sat_count < 0) || (pack->sat_count > NMEA_MAXSAT)) {
542                 nmea_error("GPGSV parse error: inconsistent pack (count/index/satcount = %d/%d/%d)", pack->pack_count,
543                                 pack->pack_index, pack->sat_count);
544                 return 0;
545         }
546
547         /* validate all sat settings and count the number of sats in the sentence */
548         for (sat_count = 0; sat_count < NMEA_SATINPACK; sat_count++) {
549                 if (pack->sat_data[sat_count].id != 0) {
550                         if ((pack->sat_data[sat_count].id < 0)) {
551                                 nmea_error("GPGSV parse error: invalid sat %d id (%d)", sat_count + 1, pack->sat_data[sat_count].id);
552                                 return 0;
553                         }
554                         if ((pack->sat_data[sat_count].elv < -90) || (pack->sat_data[sat_count].elv > 90)) {
555                                 nmea_error("GPGSV parse error: invalid sat %d elevation (%d)", sat_count + 1, pack->sat_data[sat_count].elv);
556                                 return 0;
557                         }
558                         if ((pack->sat_data[sat_count].azimuth < 0) || (pack->sat_data[sat_count].azimuth >= 360)) {
559                                 nmea_error("GPGSV parse error: invalid sat %d azimuth (%d)", sat_count + 1, pack->sat_data[sat_count].azimuth);
560                                 return 0;
561                         }
562                         if ((pack->sat_data[sat_count].sig < 0) || (pack->sat_data[sat_count].sig > 99)) {
563                                 nmea_error("GPGSV parse error: invalid sat %d signal (%d)", sat_count + 1, pack->sat_data[sat_count].sig);
564                                 return 0;
565                         }
566                         sat_counted++;
567                 }
568         }
569
570         /* see that we have enough tokens */
571         token_count_expected = (sat_counted * 4) + 3;
572         if ((token_count < token_count_expected) || (token_count > (NMEA_SATINPACK * 4 + 3))) {
573                 nmea_error("GPGSV parse error: need %d tokens, got %d", token_count_expected, token_count);
574                 return 0;
575         }
576
577         /* determine which fields are present and validate them */
578
579         if (pack->sat_count > 0) {
580                 nmea_INFO_set_present(&pack->present, SATINVIEW);
581         }
582
583         return 1;
584 }
585
586 /**
587  * Parse a GPRMC sentence from a string
588  *
589  * @param s the string
590  * @param len the length of the string
591  * @param pack a pointer to the result structure
592  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
593  */
594 int nmea_parse_GPRMC(const char *s, const int len, nmeaGPRMC *pack) {
595         int token_count;
596         char time_buff[NMEA_TIMEPARSE_BUF];
597         int date;
598         size_t time_buff_len = 0;
599
600         assert(s);
601         assert(pack);
602
603         nmea_trace_buff(s, len);
604
605         /*
606          * Clear before parsing, to be able to detect absent fields
607          */
608         time_buff[0] = '\0';
609         date = -1;
610         pack->present = 0;
611         pack->utc.year = -1;
612         pack->utc.mon = -1;
613         pack->utc.day = -1;
614         pack->utc.hour = -1;
615         pack->utc.min = -1;
616         pack->utc.sec = -1;
617         pack->utc.hsec = -1;
618         pack->status = 0;
619         pack->lat = NAN;
620         pack->ns = 0;
621         pack->lon = NAN;
622         pack->ew = 0;
623         pack->speed = NAN;
624         pack->track = NAN;
625         pack->magvar = NAN;
626         pack->magvar_ew = 0;
627         pack->mode = 0;
628
629         /* parse */
630         token_count = nmea_scanf(s, len, "$GPRMC,%s,%c,%f,%c,%f,%c,%f,%f,%d,%f,%c,%c*", &time_buff[0], &pack->status,
631                         &pack->lat, &pack->ns, &pack->lon, &pack->ew, &pack->speed, &pack->track, &date,
632                         &pack->magvar, &pack->magvar_ew, &pack->mode);
633
634         /* see that we have enough tokens */
635         if ((token_count != 11) && (token_count != 12)) {
636                 nmea_error("GPRMC parse error: need 11 or 12 tokens, got %d in %s", token_count, s);
637                 return 0;
638         }
639
640         /* determine which fields are present and validate them */
641
642         time_buff_len = strlen(&time_buff[0]);
643         if (time_buff_len) {
644                 if (!_nmea_parse_time(&time_buff[0], time_buff_len, &pack->utc)) {
645                         return 0;
646                 }
647
648                 if (!validateTime(&pack->utc)) {
649                         return 0;
650                 }
651
652                 nmea_INFO_set_present(&pack->present, UTCTIME);
653         }
654
655         if (!pack->status) {
656                 pack->status = 'V';
657         } else {
658                 pack->status = toupper(pack->status);
659                 if (!((pack->status == 'A') || (pack->status == 'V'))) {
660                         nmea_error("GPRMC parse error: invalid status (%c)", pack->status);
661                         return 0;
662                 }
663         }
664         if (!isnan(pack->lat) && (pack->ns)) {
665                 if (!validateNSEW(&pack->ns, true)) {
666                         return 0;
667                 }
668
669                 nmea_INFO_set_present(&pack->present, LAT);
670         }
671         if (!isnan(pack->lon) && (pack->ew)) {
672                 if (!validateNSEW(&pack->ew, false)) {
673                         return 0;
674                 }
675
676                 nmea_INFO_set_present(&pack->present, LON);
677         }
678         if (!isnan(pack->speed)) {
679                 nmea_INFO_set_present(&pack->present, SPEED);
680         }
681         if (!isnan(pack->track)) {
682                 nmea_INFO_set_present(&pack->present, TRACK);
683         }
684
685         if (date != -1) {
686                 if (!_nmea_parse_date(date, &pack->utc)) {
687                         return 0;
688                 }
689
690                 if (!validateDate(&pack->utc)) {
691                         return 0;
692                 }
693
694                 nmea_INFO_set_present(&pack->present, UTCDATE);
695         }
696
697         if (!isnan(pack->magvar) && (pack->magvar_ew)) {
698                 if (!validateNSEW(&pack->magvar_ew, false)) {
699                         return 0;
700                 }
701
702                 nmea_INFO_set_present(&pack->present, MAGVAR);
703         }
704         if (token_count == 11) {
705                 pack->mode = 'A';
706         } else {
707                 if (!pack->mode) {
708                         pack->mode = 'N';
709                 } else {
710                         if (!validateMode(&pack->mode)) {
711                                 return 0;
712                         }
713                 }
714         }
715
716         return 1;
717 }
718
719 /**
720  * Parse a GPVTG sentence from a string
721  *
722  * @param s the string
723  * @param len the length of the string
724  * @param pack a pointer to the result structure
725  * @return 1 (true) - if parsed successfully or 0 (false) otherwise.
726  */
727 int nmea_parse_GPVTG(const char *s, const int len, nmeaGPVTG *pack) {
728         int token_count;
729
730         assert(s);
731         assert(pack);
732
733         nmea_trace_buff(s, len);
734
735         /*
736          * Clear before parsing, to be able to detect absent fields
737          */
738         pack->present = 0;
739         pack->track = NAN;
740         pack->track_t = 0;
741         pack->mtrack = NAN;
742         pack->mtrack_m = 0;
743         pack->spn = NAN;
744         pack->spn_n = 0;
745         pack->spk = NAN;
746         pack->spk_k = 0;
747
748         /* parse */
749         token_count = nmea_scanf(s, len, "$GPVTG,%f,%c,%f,%c,%f,%c,%f,%c*", &pack->track, &pack->track_t, &pack->mtrack,
750                         &pack->mtrack_m, &pack->spn, &pack->spn_n, &pack->spk, &pack->spk_k);
751
752         /* see that we have enough tokens */
753         if (token_count != 8) {
754                 nmea_error("GPVTG parse error: need 8 tokens, got %d in %s", token_count, s);
755                 return 0;
756         }
757
758         /* determine which fields are present and validate them */
759
760         if (!isnan(pack->track) && (pack->track_t)) {
761                 pack->track_t = toupper(pack->track_t);
762                 if (pack->track_t != 'T') {
763                         nmea_error("GPVTG parse error: invalid track unit, got %c, expected T", pack->track_t);
764                         return 0;
765                 }
766
767                 nmea_INFO_set_present(&pack->present, TRACK);
768         }
769         if (!isnan(pack->mtrack) && (pack->mtrack_m)) {
770                 pack->mtrack_m = toupper(pack->mtrack_m);
771                 if (pack->mtrack_m != 'M') {
772                         nmea_error("GPVTG parse error: invalid mtrack unit, got %c, expected M", pack->mtrack_m);
773                         return 0;
774                 }
775
776                 nmea_INFO_set_present(&pack->present, MTRACK);
777         }
778         if (!isnan(pack->spn) && (pack->spn_n)) {
779                 pack->spn_n = toupper(pack->spn_n);
780                 if (pack->spn_n != 'N') {
781                         nmea_error("GPVTG parse error: invalid knots speed unit, got %c, expected N", pack->spn_n);
782                         return 0;
783                 }
784
785                 nmea_INFO_set_present(&pack->present, SPEED);
786
787                 if (isnan(pack->spk)) {
788                         pack->spk = pack->spn * NMEA_TUD_KNOTS;
789                         pack->spk_k = 'K';
790                 }
791         }
792         if (!isnan(pack->spk) && (pack->spk_k)) {
793                 pack->spk_k = toupper(pack->spk_k);
794                 if (pack->spk_k != 'K') {
795                         nmea_error("GPVTG parse error: invalid kph speed unit, got %c, expected K", pack->spk_k);
796                         return 0;
797                 }
798
799                 nmea_INFO_set_present(&pack->present, SPEED);
800
801                 if (isnan(pack->spn)) {
802                         pack->spn = pack->spk / NMEA_TUD_KNOTS;
803                         pack->spn_n = 'N';
804                 }
805         }
806
807         return 1;
808 }