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