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