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