Coverage Report

Created: 2024-05-03 06:03

/src/gpsd/gpsd-3.25.1~dev/drivers/driver_nmea0183.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Driver for NMEA 0183 protocol, aka IEC 61162-1
3
 * There are many versions of NMEA 0183.
4
 *
5
 * IEC 61162-1:1995
6
 * IEC 61162-1:2000
7
 * IEC 61162-1:2007
8
 * NMEA 4.00 aligns with IEC 61162-1:2010
9
 * NMEA 4.10 aligns with IEC 61162-1:2016
10
 *
11
 * Sadly, the protocol is proprietary and not documented publicly.
12
 * So every firmware seems to have a different opinion on how
13
 * to implement the messages.
14
 *
15
 * This file is Copyright 2010 by the GPSD project
16
 * SPDX-License-Identifier: BSD-2-clause
17
 */
18
19
#include "../include/gpsd_config.h"  // must be before all includes
20
21
#include <ctype.h>       // for isdigit()
22
#include <float.h>       // for FLT_EVAL_METHOD
23
#include <stdio.h>
24
#include <stdlib.h>
25
#include <stdbool.h>
26
#include <math.h>
27
#include <string.h>
28
#include <stdarg.h>
29
#include <time.h>
30
31
#include "../include/gpsd.h"
32
#include "../include/strfuncs.h"
33
34
#include "../include/timespec.h"
35
/**************************************************************************
36
 *
37
 * Parser helpers begin here
38
 *
39
 **************************************************************************/
40
41
/* Allow avoiding long double intermediate values.
42
 *
43
 * On platforms with 0 != FLT_EVAL_METHOD intermediate values may be kept
44
 * as long doubles.  Some 32-bit OpenBSD and 32-bit Debian have
45
 * FLT_EVAL_METHOD == 2.  FreeBSD 13,0 has FLT_EVAL_METHOD == -1.  Various
46
 * cc options (-mfpmath=387, -mno-sse, etc.) can also change FLT_EVAL_METHOD
47
 * from 0.
48
 *
49
 * Although (long double) may in principle more accurate then (double), it
50
 * can cause slight differences that lead to regression failures.  In
51
 * other cases (long double) and (double) are the same, thus no effect.
52
 * Storing values in volatile variables forces the exact size requested.
53
 * Where the volatile declaration is unnessary (and absent), such extra
54
 * intermediate variables are normally optimized out.
55
 */
56
57
#if !defined(FLT_EVAL_METHOD) || 0 != FLT_EVAL_METHOD
58
#define FLT_VOLATILE volatile
59
#else
60
#define FLT_VOLATILE
61
#endif   // FLT_EVAL_METHOD
62
63
/* Common lat/lon decoding for do_lat_lon
64
 *
65
 * This version avoids the use of modf(), which can be slow and also suffers
66
 * from exactness problems.  The integer minutes are first extracted and
67
 * corrected for the improper degree scaling, using integer arithmetic.
68
 * Then the fractional minutes are added as a double, and the result is scaled
69
 * to degrees, using multiply which is faster than divide.
70
 *
71
 * Forcing the intermediate minutes value to a double is sufficient to
72
 * avoid regression problems with FLT_EVAL_METHOD>=2.
73
 */
74
static inline double decode_lat_or_lon(const char *field)
75
0
{
76
0
    long degrees, minutes;
77
0
    FLT_VOLATILE double full_minutes;
78
0
    char *cp;
79
80
    // Get integer "minutes"
81
0
    minutes = strtol(field, &cp, 10);
82
    // Must have decimal point
83
0
    if ('.' != *cp) {
84
0
        return NAN;
85
0
    }
86
    // Extract degrees (scaled by 100)
87
0
    degrees = minutes / 100;
88
    // Rescale degrees to normal factor of 60
89
0
    minutes -= degrees * (100 - 60);
90
    // Add fractional minutes
91
0
    full_minutes = minutes + safe_atof(cp);
92
    // Scale to degrees & return
93
0
    return full_minutes * (1.0 / 60.0);
94
0
}
95
96
/* process a pair of latitude/longitude fields starting at field index BEGIN
97
 * The input fields look like this:
98
 *     field[0]: 4404.1237962
99
 *     field[1]: N
100
 *     field[2]: 12118.8472460
101
 *     field[3]: W
102
 * input format of lat/lon is NMEA style  DDDMM.mmmmmmm
103
 * yes, 7 digits of precision past the decimal point from survey grade GPS
104
 *
105
 * Ignoring the complications ellipsoids add:
106
 *   1 minute latitude = 1853 m
107
 *   0.001 minute latitude = 1.853 m
108
 *   0.000001 minute latitude = 0.001853 m = 1.853 mm
109
 *   0.0000001 minute latitude = 0.0001853 m = 0.1853 mm
110
 *
111
 * return: 0 == OK, non zero is failure.
112
 */
113
static int do_lat_lon(char *field[], struct gps_fix_t *out)
114
0
{
115
0
    double lon;
116
0
    double lat;
117
118
0
    if ('\0' == field[0][0] ||
119
0
        '\0' == field[1][0] ||
120
0
        '\0' == field[2][0] ||
121
0
        '\0' == field[3][0]) {
122
0
        return 1;
123
0
    }
124
125
0
    lat = decode_lat_or_lon(field[0]);
126
0
    if ('S' == field[1][0])
127
0
        lat = -lat;
128
129
0
    lon = decode_lat_or_lon(field[2]);
130
0
    if ('W' == field[3][0])
131
0
        lon = -lon;
132
133
0
    if (0 == isfinite(lat) ||
134
0
        0 == isfinite(lon)) {
135
0
        return 2;
136
0
    }
137
138
0
    out->latitude = lat;
139
0
    out->longitude = lon;
140
0
    return 0;
141
0
}
142
143
/* process an FAA mode character
144
 * As used in $GPRMC (field 13) and similar.
145
 * return status as in session->newdata.status
146
 */
147
static int faa_mode(char mode)
148
0
{
149
0
    int newstatus = STATUS_GPS;
150
151
0
    switch (mode) {
152
0
    case '\0':  // missing
153
0
        newstatus = STATUS_UNK;
154
0
        break;
155
0
    case 'A':   // Autonomous
156
0
    default:
157
0
        newstatus = STATUS_GPS;
158
0
        break;
159
0
    case 'D':   // Differential
160
0
        newstatus = STATUS_DGPS;
161
0
        break;
162
0
    case 'E':   // Estimated dead reckoning
163
0
        newstatus = STATUS_DR;
164
0
        break;
165
0
    case 'F':   // Float RTK
166
0
        newstatus = STATUS_RTK_FLT;
167
0
        break;
168
0
    case 'M':   // manual input.  Interpret as surveyed to better match GGA
169
0
        newstatus = STATUS_TIME;
170
0
        break;
171
0
    case 'N':   // Data Not Valid
172
        // already handled, for paranoia sake also here
173
0
        newstatus = STATUS_UNK;
174
0
        break;
175
0
    case 'P':   // Precise (NMEA 4+)
176
0
        newstatus = STATUS_DGPS;    // sort of DGPS
177
0
        break;
178
0
    case 'R':   // fixed RTK
179
0
        newstatus = STATUS_RTK_FIX;
180
0
        break;
181
0
    case 'S':   // simulator
182
0
        newstatus = STATUS_SIM;
183
0
        break;
184
0
    }
185
0
    return newstatus;
186
0
}
187
188
/**************************************************************************
189
 *
190
 * Scary timestamp fudging begins here
191
 *
192
 * Four sentences, GGA and GLL and RMC and ZDA, contain timestamps.
193
 * GGA/GLL/RMC timestamps look like hhmmss.ss, with the trailing .ss,
194
 * or .sss, part optional.
195
 * RMC has a date field, in the format ddmmyy.  ZDA has separate fields
196
 * for day/month/year, with a 4-digit year.  This means that for RMC we
197
 * must supply a century and for GGA and GLL we must supply a century,
198
 * year, and day.  We get the missing data from a previous RMC or ZDA;
199
 * century in RMC is supplied from the daemon's context (initialized at
200
 * startup time) if there has been no previous ZDA.
201
 *
202
 **************************************************************************/
203
204
0
#define DD(s)   ((int)((s)[0]-'0')*10+(int)((s)[1]-'0'))
205
206
/* sentence supplied ddmmyy, but no century part
207
 *
208
 * return: 0 == OK,  greater than zero on failure
209
 */
210
static int merge_ddmmyy(char *ddmmyy, struct gps_device_t *session)
211
0
{
212
0
    int yy;
213
0
    int mon;
214
0
    int mday;
215
0
    int year;
216
0
    unsigned i;    // NetBSD complains about signed array index
217
218
0
    if (NULL == ddmmyy) {
219
0
        return 1;
220
0
    }
221
0
    for (i = 0; i < 6; i++) {
222
        // NetBSD 6 wants the cast
223
0
        if (0 == isdigit((int)ddmmyy[i])) {
224
            // catches NUL and non-digits
225
            // Telit HE910 can set year to "-1" (1999 - 2000)
226
0
            GPSD_LOG(LOG_WARN, &session->context->errout,
227
0
                     "NMEA0183: merge_ddmmyy(%s), malformed date\n",  ddmmyy);
228
0
            return 2;
229
0
        }
230
0
    }
231
    // check for termination
232
0
    if ('\0' != ddmmyy[6]) {
233
        // missing NUL
234
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
235
0
                 "NMEA0183: merge_ddmmyy(%s), malformed date\n",  ddmmyy);
236
0
        return 3;
237
0
    }
238
239
    // should be no defects left to segfault DD()
240
0
    yy = DD(ddmmyy + 4);
241
0
    mon = DD(ddmmyy + 2);
242
0
    mday = DD(ddmmyy);
243
244
    // check for century wrap
245
0
    if (session->nmea.date.tm_year % 100 == 99 && yy == 0)
246
0
        gpsd_century_update(session, session->context->century + 100);
247
0
    year = (session->context->century + yy);
248
249
    /* 32 bit systems will break in 2038.
250
     * Telix fails on GPS rollover to 2099, which 32 bit system
251
     * can not handle.  So wrap at 2080.  That way 64 bit systems
252
     * work until 2080, and 2099 gets reported as 1999.
253
     * since GPS epoch started in 1980, allows for old NMEA to work.
254
     */
255
0
    if (2080 <= year) {
256
0
        year -= 100;
257
0
    }
258
259
0
    if ((1 > mon) ||
260
0
        (12 < mon)) {
261
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
262
0
                 "NMEA0183: merge_ddmmyy(%s), malformed month\n",  ddmmyy);
263
0
        return 4;
264
0
    } else if (1 > mday ||
265
0
               31 < mday) {
266
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
267
0
                 "NMEA0183: merge_ddmmyy(%s), malformed day\n",  ddmmyy);
268
0
        return 5;
269
0
    } else {
270
0
        GPSD_LOG(LOG_DATA, &session->context->errout,
271
0
                 "NMEA0183: merge_ddmmyy(%s) sets year %d\n",
272
0
                 ddmmyy, year);
273
0
        session->nmea.date.tm_year = year - 1900;
274
0
        session->nmea.date.tm_mon = mon - 1;
275
0
        session->nmea.date.tm_mday = mday;
276
0
    }
277
0
    GPSD_LOG(LOG_RAW, &session->context->errout,
278
0
             "NMEA0183: merge_ddmmyy(%s) %d %d %d\n",
279
0
             ddmmyy,
280
0
             session->nmea.date.tm_mon,
281
0
             session->nmea.date.tm_mday,
282
0
             session->nmea.date.tm_year);
283
0
    return 0;
284
0
}
285
286
/* decode an hhmmss.ss string into struct tm data and nsecs
287
 *
288
 * return: 0 == OK,  otherwise failure
289
 */
290
static int decode_hhmmss(struct tm *date, long *nsec, char *hhmmss,
291
                         struct gps_device_t *session)
292
0
{
293
0
    int old_hour = date->tm_hour;
294
0
    int i;
295
296
0
    if (NULL == hhmmss) {
297
0
        return 1;
298
0
    }
299
0
    for (i = 0; i < 6; i++) {
300
        // NetBSD 6 wants the cast
301
0
        if (0 == isdigit((int)hhmmss[i])) {
302
            // catches NUL and non-digits
303
0
            GPSD_LOG(LOG_WARN, &session->context->errout,
304
0
                     "NMEA0183: decode_hhmmss(%s), malformed time\n",  hhmmss);
305
0
            return 2;
306
0
        }
307
0
    }
308
    // don't check for termination, might have fractional seconds
309
310
0
    date->tm_hour = DD(hhmmss);
311
0
    if (date->tm_hour < old_hour) {  // midnight wrap
312
0
        date->tm_mday++;
313
0
    }
314
0
    date->tm_min = DD(hhmmss + 2);
315
0
    date->tm_sec = DD(hhmmss + 4);
316
317
0
    if ('.' == hhmmss[6] &&
318
        // NetBSD 6 wants the cast
319
0
        0 != isdigit((int)hhmmss[7])) {
320
        // codacy hates strlen()
321
0
        int sublen = strnlen(hhmmss + 7, 20);
322
0
        i = atoi(hhmmss + 7);
323
0
        *nsec = (long)i * (long)pow(10.0, 9 - sublen);
324
0
    } else {
325
0
        *nsec = 0;
326
0
    }
327
328
0
    return 0;
329
0
}
330
331
/* update from a UTC time
332
 *
333
 * return: 0 == OK,  greater than zero on failure
334
 */
335
static int merge_hhmmss(char *hhmmss, struct gps_device_t *session)
336
0
{
337
0
    int old_hour = session->nmea.date.tm_hour;
338
0
    int i;
339
340
0
    if (NULL == hhmmss) {
341
0
        return 1;
342
0
    }
343
0
    for (i = 0; i < 6; i++) {
344
        // NetBSD 6 wants the cast
345
0
        if (0 == isdigit((int)hhmmss[i])) {
346
            // catches NUL and non-digits
347
0
            GPSD_LOG(LOG_WARN, &session->context->errout,
348
0
                     "NMEA0183: merge_hhmmss(%s), malformed time\n",  hhmmss);
349
0
            return 2;
350
0
        }
351
0
    }
352
    // don't check for termination, might have fractional seconds
353
354
0
    session->nmea.date.tm_hour = DD(hhmmss);
355
0
    if (session->nmea.date.tm_hour < old_hour) {  // midnight wrap
356
0
        session->nmea.date.tm_mday++;
357
0
    }
358
0
    session->nmea.date.tm_min = DD(hhmmss + 2);
359
0
    session->nmea.date.tm_sec = DD(hhmmss + 4);
360
361
0
    session->nmea.subseconds.tv_sec = 0;
362
0
    if ('.' == hhmmss[6] &&
363
        // NetBSD 6 wants the cast
364
0
        0 != isdigit((int)hhmmss[7])) {
365
        // codacy hates strlen()
366
0
        int sublen = strnlen(hhmmss + 7, 20);
367
368
0
        i = atoi(hhmmss + 7);
369
0
        session->nmea.subseconds.tv_nsec = (long)i *
370
0
                                           (long)pow(10.0, 9 - sublen);
371
0
    } else {
372
0
        session->nmea.subseconds.tv_nsec = 0;
373
0
    }
374
375
0
    return 0;
376
0
}
377
378
static void register_fractional_time(const char *tag, const char *fld,
379
                                     struct gps_device_t *session)
380
0
{
381
382
0
    if ('\0' != fld[0]) {
383
0
        char ts_buf[TIMESPEC_LEN];
384
385
0
        session->nmea.last_frac_time = session->nmea.this_frac_time;
386
0
        DTOTS(&session->nmea.this_frac_time, safe_atof(fld));
387
0
        session->nmea.latch_frac_time = true;
388
0
        GPSD_LOG(LOG_DATA, &session->context->errout,
389
0
                 "NMEA0183: %s: registers fractional time %s\n",
390
0
                 tag,
391
0
                 timespec_str(&session->nmea.this_frac_time, ts_buf,
392
0
                              sizeof(ts_buf)));
393
0
    }
394
0
}
395
396
// convert NMEA sigid to ublox sigid
397
static unsigned char nmea_sigid_to_ubx(struct gps_device_t *session,
398
                                       unsigned char nmea_sigid)
399
0
{
400
0
    unsigned char ubx_sigid = 0;
401
402
    // FIXME: need to know gnssid to guess sigid
403
0
    switch (nmea_sigid) {
404
0
    default:
405
0
        GPSD_LOG(LOG_PROG, &session->context->errout,
406
0
                 "NMEA0183: Unknown nmea_sigid %u\n", nmea_sigid);
407
0
        FALLTHROUGH
408
0
    case 0:
409
        // missing, assume GPS L1
410
        // ALLYSTAR, "all signal"
411
0
        ubx_sigid = 0;
412
0
        break;
413
0
    case 1:
414
        // L1
415
        // ALLYSTAR, GLO G1CA
416
        // ALLYSTAR, GAL E5A
417
        // ALLYSTAR, BDS B1I
418
0
        ubx_sigid = 0;
419
0
        break;
420
0
    case 2:
421
        // E5, could be 5 or 6.
422
        // ALLYSTAR, GAL E5B
423
        // ALLYSTAR, BDS B2I
424
0
        ubx_sigid = 5;
425
0
        break;
426
0
    case 3:
427
        // B2 or L2, could be 2 or 3.
428
        // ALLYSTAR, GLO G2CA
429
        // ALLYSTAR, BDS B3I
430
0
        ubx_sigid = 2;
431
0
        break;
432
0
    case 4:
433
        // ALLYSTAR, BDS B2A
434
0
        ubx_sigid = 7;     // This is for NMEA digid 5, but we have 4!  Close.
435
0
        break;
436
0
    case 5:
437
        // L2
438
        // ALLYSTAR, BDS B2A
439
0
        ubx_sigid = 4;
440
0
        break;
441
0
    case 6:
442
        // L2CL
443
        // ALLYSTAR, GAL L1A
444
0
        ubx_sigid = 3;
445
0
        break;
446
0
    case 7:
447
        // E1, could be 0 or 1.
448
        // ALLYSTAR, GAL LBC
449
0
        ubx_sigid = 0;
450
0
        break;
451
0
    case 8:
452
        // ALLYSTAR, GPS L5Q
453
0
        ubx_sigid = 7;
454
0
        break;
455
0
    case 9:
456
        // ALLYSTAR, Quectel, GPS L1C
457
        // ALLYSTAR, BDS B1C
458
0
        ubx_sigid = 0;
459
0
        break;
460
0
    case 11:
461
        // ALLYSTAR, GPS L6
462
0
        ubx_sigid = 0;
463
0
        break;
464
0
    }
465
466
0
    return ubx_sigid;
467
0
}
468
469
/* Deal with range-mapping attempts to use IDs 1-32 by Beidou, etc.
470
 *
471
 * See struct satellite_t in gps.h for ubx and nmea gnssid and svid mappings
472
 *
473
 * char *talker              -- NMEA talker string
474
 * int nmea_satnum           -- NMEA (All ver) satellite number (kinda the PRN)
475
 * int nmea_gnssid           -- NMEA 4.10 gnssid, if known, otherwise zero
476
 * unsigned char *ubx_gnssid -- returned u-blox gnssid
477
 * unsigned char *ubx_svid   -- returned u-blox gnssid
478
 *
479
 * Return the NMEA 2.x to 4.0 extended PRN
480
 */
481
static int nmeaid_to_prn(char *talker, int nmea_satnum,
482
                         int nmea_gnssid,
483
                         unsigned char *ubx_gnssid,
484
                         unsigned char *ubx_svid)
485
0
{
486
    /*
487
     * According to https://github.com/mvglasow/satstat/wiki/NMEA-IDs
488
     * and u-blox documentation.
489
     * NMEA IDs can be roughly divided into the following ranges:
490
     *
491
     *   1..32:  GPS
492
     *   33..64: Various SBAS systems (EGNOS, WAAS, SDCM, GAGAN, MSAS)
493
     *   65..96: GLONASS
494
     *   101..136: Quectel Querk, (not NMEA), seems to be Galileo
495
     *   152..158: Various SBAS systems (EGNOS, WAAS, SDCM, GAGAN, MSAS)
496
     *   173..182: IMES
497
     *   193..202: QZSS   (u-blox extended 4.10)
498
     *   201..264: BeiDou (not NMEA, not u-blox?) Quectel Querk.
499
     *   301..336: Galileo
500
     *   401..437: BeiDou
501
     *   null: GLONASS unused
502
     *   500-509: NavIC (IRNSS)  NOT STANDARD!
503
     *   901..918: NavIC (IRNSS), ALLYSTAR
504
     *
505
     * The issue is what to do when GPSes from these different systems
506
     * fight for IDs in the  1-32 range, as in this pair of Beidou sentences
507
     *
508
     * $BDGSV,2,1,07,01,00,000,45,02,13,089,35,03,00,000,37,04,00,000,42*6E
509
     * $BDGSV,2,2,07,05,27,090,,13,19,016,,11,07,147,*5E
510
     *
511
     * Because the PRNs are only used for generating a satellite
512
     * chart, mistakes here aren't dangerous.  The code will record
513
     * and use multiple sats with the same ID in one skyview; in
514
     * effect, they're recorded by the order in which they occur
515
     * rather than by PRN.
516
     */
517
0
    int nmea2_prn = nmea_satnum;
518
519
0
    *ubx_gnssid = 0;   // default to ubx_gnssid is GPS
520
0
    *ubx_svid = 0;     // default to unnknown ubx_svid
521
522
0
    if (1 > nmea_satnum) {
523
        // uh, oh...
524
0
        nmea2_prn = 0;
525
0
    } else if (0 < nmea_gnssid) {
526
        // this switch handles case where nmea_gnssid is known
527
0
        switch (nmea_gnssid) {
528
0
        case 1:
529
0
            if (33 > nmea_satnum) {
530
                // 1 = GPS       1-32
531
0
                *ubx_gnssid = 0;
532
0
                *ubx_svid = nmea_satnum;
533
0
            } else if (65 > nmea_satnum) {
534
                // 1 = SBAS      33-64
535
0
                *ubx_gnssid = 1;
536
0
                *ubx_svid = nmea_satnum + 87;
537
0
            } else if (137 > nmea_satnum) {
538
                // 3 = Galileo, 101-136, NOT NMEA.  Quectel Querk
539
0
                *ubx_gnssid = 3;
540
0
                *ubx_svid = nmea_satnum - 100;
541
0
            } else if (152 > nmea_satnum) {
542
                // Huh?
543
0
                *ubx_gnssid = 0;
544
0
                *ubx_svid = 0;
545
0
                nmea2_prn = 0;
546
0
            } else if (158 > nmea_satnum) {
547
                // 1 = SBAS      152-158
548
0
                *ubx_gnssid = 1;
549
0
                *ubx_svid = nmea_satnum;
550
0
            } else if (193 > nmea_satnum) {
551
                // Huh?
552
0
                *ubx_gnssid = 0;
553
0
                *ubx_svid = 0;
554
0
                nmea2_prn = 0;
555
0
            } else if (200 > nmea_satnum) {
556
                // 1 = QZSS      193-197
557
                // undocumented u-blox goes to 199
558
0
                *ubx_gnssid = 3;
559
0
                *ubx_svid = nmea_satnum - 192;
560
0
            } else if (265 > nmea_satnum) {
561
                // 3 = BeiDor, 201-264, NOT NMEA.  Quectel Querk
562
0
                *ubx_gnssid = 3;
563
0
                *ubx_svid = nmea_satnum - 200;
564
0
            } else {
565
                // Huh?
566
0
                *ubx_gnssid = 0;
567
0
                *ubx_svid = 0;
568
0
                nmea2_prn = 0;
569
0
            }
570
0
            break;
571
0
        case 2:
572
            //  2 = GLONASS   65-96, nul
573
0
            *ubx_gnssid = 6;
574
0
            if (64 > nmea_satnum) {
575
                // NMEA svid 1 - 64
576
0
                *ubx_svid = nmea_satnum;
577
0
            } else {
578
                /* Jackson Labs Micro JLT, Quectel Querk, SiRF, Skytrak,
579
                 * u-blox quirk: GLONASS are  65 to 96 */
580
0
                *ubx_svid = nmea_satnum - 64;
581
0
            }
582
0
            nmea2_prn = 64 + *ubx_svid;
583
0
            break;
584
0
        case 3:
585
            //  3 = Galileo   1-36
586
0
            *ubx_gnssid = 2;
587
0
            if (100 > nmea_satnum) {
588
                // NMEA
589
0
                *ubx_svid = nmea_satnum;
590
0
            } else if (100 < nmea_satnum &&
591
0
                       200 > nmea_satnum) {
592
                // Quectel Querk, NOT NMEA, 101 - 199
593
0
                *ubx_svid = nmea_satnum - 100;
594
0
            } else if (300 < nmea_satnum &&
595
0
                       400 > nmea_satnum) {
596
                // Jackson Labs quirk, NOT NMEA, 301 - 399
597
0
                *ubx_svid = nmea_satnum - 300;
598
0
            }
599
0
            nmea2_prn = 300 + *ubx_svid;    // 301 - 399
600
0
            break;
601
0
        case 4:
602
            //  4 - BeiDou    1-37
603
0
            *ubx_gnssid = 3;
604
0
            if (100 > nmea_satnum) {
605
                // NMEA 1 - 99
606
0
                *ubx_svid = nmea_satnum;
607
0
            } else if (200 < nmea_satnum &&
608
0
                       300 > nmea_satnum) {
609
                // Quectel Querk, NOT NMEA, 201 - 299
610
0
                *ubx_svid = nmea_satnum - 200;
611
0
            } else if (400 < nmea_satnum &&
612
0
                       500 > nmea_satnum) {
613
                // Jackson Labs quirk, NOT NMEA, 401 - 499
614
0
                *ubx_svid = nmea_satnum - 400;
615
0
            }
616
            // put it at 400+ where NMEA 4.11 wants it
617
0
            nmea2_prn = 400 + *ubx_svid;
618
0
            break;
619
0
        case 5:
620
            //  5 - QZSS, 1 - 10, NMEA 4.11
621
0
            *ubx_gnssid = 5;
622
0
            if (100 > nmea_satnum) {
623
                // NMEA 1 - 99
624
0
                *ubx_svid = nmea_satnum;
625
0
            } else {
626
                // Telit quirk, not NMEA 193 - 199
627
0
                *ubx_svid = nmea_satnum - 192;
628
0
            }
629
630
            // put it at 193 to 199 where NMEA 4.11 wants it
631
            // huh?  space for only 7?
632
0
            nmea2_prn = 192 + *ubx_svid;
633
0
            break;
634
0
        case 6:
635
            //  6 - NavIC (IRNSS)    1-15
636
0
            *ubx_gnssid = 7;
637
0
            *ubx_svid = nmea_satnum;
638
0
            nmea2_prn = nmea_satnum + 500;  // This is wrong...
639
0
            break;
640
0
        default:
641
            // unknown
642
            // x = IMES                Not defined by NMEA 4.10
643
0
            nmea2_prn = 0;
644
0
            break;
645
0
        }
646
647
    /* left with NMEA 2.x to NMEA 4.0 satnums
648
     * use talker ID to disambiguate */
649
0
    } else if (32 >= nmea_satnum) {
650
0
        *ubx_svid = nmea_satnum;
651
0
        switch (talker[0]) {
652
0
        case 'G':
653
0
            switch (talker[1]) {
654
0
            case 'A':
655
                // Galileo
656
0
                nmea2_prn = 300 + nmea_satnum;
657
0
                *ubx_gnssid = 2;
658
0
                break;
659
0
            case 'B':
660
                // map Beidou IDs 1..37 to 401..437
661
0
                *ubx_gnssid = 3;
662
0
                nmea2_prn = 400 + nmea_satnum;
663
0
                break;
664
0
            case 'I':
665
                // map NavIC (IRNSS) IDs 1..10 to 500 - 509, not NMEA
666
0
                *ubx_gnssid = 7;
667
0
                nmea2_prn = 500 + nmea_satnum;
668
0
                break;
669
0
            case 'L':
670
                // GLONASS GL doesn't seem to do this, better safe than sorry
671
0
                nmea2_prn = 64 + nmea_satnum;
672
0
                *ubx_gnssid = 6;
673
0
                break;
674
0
            case 'Q':
675
                // GQ, QZSS, 1 - 10
676
0
                nmea2_prn = 192 + nmea_satnum;
677
0
                *ubx_gnssid = 5;
678
0
                break;
679
0
            case 'N':
680
                // all of them, but only GPS is 0 < PRN < 33
681
0
                FALLTHROUGH
682
0
            case 'P':
683
                // GPS,SBAS,QZSS, but only GPS is 0 < PRN < 33
684
0
                FALLTHROUGH
685
0
            default:
686
                // WTF?
687
0
                break;
688
0
            }  // else ??
689
0
            break;
690
0
        case 'B':
691
0
            if ('D' == talker[1]) {
692
                // map Beidou IDs
693
0
                nmea2_prn = 400 + nmea_satnum;
694
0
                *ubx_gnssid = 3;
695
0
            }  // else ??
696
0
            break;
697
0
        case 'P':
698
            // Quectel EC25 & EC21 use PQxxx for BeiDou
699
0
            if ('Q' == talker[1]) {
700
                // map Beidou IDs
701
0
                nmea2_prn = 400 + nmea_satnum;
702
0
                *ubx_gnssid = 3;
703
0
            }  // else ??
704
0
            break;
705
0
        case 'Q':
706
0
            if ('Z' == talker[1]) {
707
                // QZSS
708
0
                nmea2_prn = 192 + nmea_satnum;
709
0
                *ubx_gnssid = 5;
710
0
            }  // else ?
711
0
            break;
712
0
        default:
713
            // huh?
714
0
            break;
715
0
        }
716
0
    } else if (64 >= nmea_satnum) {
717
        // NMEA-ID (33..64) to SBAS PRN 120-151.
718
        // SBAS
719
0
        *ubx_gnssid = 1;
720
0
        *ubx_svid = 87 + nmea_satnum;
721
0
    } else if (96 >= nmea_satnum) {
722
        // GLONASS 65..96
723
0
        *ubx_gnssid = 6;
724
0
        *ubx_svid = nmea_satnum - 64;
725
0
    } else if (120 > nmea_satnum) {
726
        // Huh?
727
0
        *ubx_gnssid = 0;
728
0
        *ubx_svid = 0;
729
0
        nmea2_prn = 0;
730
0
    } else if (158 >= nmea_satnum) {
731
        // SBAS 120..158
732
0
        *ubx_gnssid = 1;
733
0
        *ubx_svid = nmea_satnum;
734
0
    } else if (173 > nmea_satnum) {
735
        // Huh?
736
0
        *ubx_gnssid = 0;
737
0
        *ubx_svid = 0;
738
0
        nmea2_prn = 0;
739
0
    } else if (182 >= nmea_satnum) {
740
        // IMES 173..182
741
0
        *ubx_gnssid = 4;
742
0
        *ubx_svid = nmea_satnum - 172;
743
0
    } else if (193 > nmea_satnum) {
744
        // Huh?
745
0
        *ubx_gnssid = 0;
746
0
        *ubx_svid = 0;
747
0
        nmea2_prn = 0;
748
0
    } else if (197 >= nmea_satnum) {
749
        // QZSS 193..197
750
        // undocumented u-blox goes to 199
751
0
        *ubx_gnssid = 5;
752
0
        *ubx_svid = nmea_satnum - 192;
753
0
    } else if (201 > nmea_satnum) {
754
        // Huh?
755
0
        *ubx_gnssid = 0;
756
0
        *ubx_svid = 0;
757
0
        nmea2_prn = 0;
758
0
    } else if (237 >= nmea_satnum) {
759
        // BeiDou, non-standard, some SiRF put BeiDou 201-237
760
        // $GBGSV,2,2,05,209,07,033,*62
761
0
        *ubx_gnssid = 3;
762
0
        *ubx_svid = nmea_satnum - 200;
763
0
        nmea2_prn += 200;           // move up to 400 where NMEA 2.x wants it.
764
0
    } else if (301 > nmea_satnum) {
765
        // Huh?
766
0
        *ubx_gnssid = 0;
767
0
        *ubx_svid = 0;
768
0
        nmea2_prn = 0;
769
0
    } else if (356 >= nmea_satnum) {
770
        // Galileo 301..356
771
0
        *ubx_gnssid = 2;
772
0
        *ubx_svid = nmea_satnum - 300;
773
0
    } else if (401 > nmea_satnum) {
774
        // Huh?
775
0
        *ubx_gnssid = 0;
776
0
        *ubx_svid = 0;
777
0
        nmea2_prn = 0;
778
0
    } else if (437 >= nmea_satnum) {
779
        // BeiDou
780
0
        *ubx_gnssid = 3;
781
0
        *ubx_svid = nmea_satnum - 400;
782
0
    } else if (499 >= nmea_satnum) {
783
        // 438 to 500??
784
0
        *ubx_gnssid = 0;
785
0
        *ubx_svid = 0;
786
0
        nmea2_prn = 0;
787
0
    } else if (518 >= nmea_satnum) {
788
        // NavIC (IRNSS) IDs 1..18 to 510 - 509, not NMEA
789
0
        *ubx_gnssid = 7;
790
0
        *ubx_svid = nmea_satnum - 500;
791
0
    } else if (900 >= nmea_satnum) {
792
        // 438 to 900??
793
0
        *ubx_gnssid = 0;
794
0
        *ubx_svid = 0;
795
0
        nmea2_prn = 0;
796
0
    } else if (918 >= nmea_satnum) {
797
        // 900 to 918 NavIC (IRNSS), per ALLYSTAR (NMEA?)
798
0
        *ubx_gnssid = 7;
799
0
        *ubx_svid = nmea_satnum - 900;
800
0
    } else {
801
        // greater than 437 Huh?
802
0
        *ubx_gnssid = 0;
803
0
        *ubx_svid = 0;
804
0
        nmea2_prn = 0;
805
0
    }
806
807
0
    return nmea2_prn;
808
0
}
809
810
/**************************************************************************
811
 *
812
 * NMEA sentence handling begins here
813
 *
814
 **************************************************************************/
815
816
static gps_mask_t processACCURACY(int c UNUSED, char *field[],
817
                                  struct gps_device_t *session)
818
0
{
819
    /*
820
     * $GPACCURACY,961.2*04
821
     *
822
     * ACCURACY,x.x*hh<cr><lf>
823
     *
824
     * The only data field is "accuracy".
825
     * The MT3333 manual just says "The smaller the number is, the be better"
826
     */
827
0
    gps_mask_t mask = ONLINE_SET;
828
829
0
    if ('\0' == field[1][0]) {
830
        // no data
831
0
        return mask;
832
0
    }
833
834
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
835
0
             "NMEA0183: $GPACCURACY: %10s.\n", field[1]);
836
0
    return mask;
837
0
}
838
839
// BWC - Bearing and Distance to Waypoint - Great Circle
840
static gps_mask_t processBWC(int count, char *field[],
841
                             struct gps_device_t *session)
842
0
{
843
    /*
844
     * GPBWC,220516,5130.02,N,00046.34,W,213.8,T,218.0,M,0004.6,N,EGLM*11
845
     *
846
     * 1. UTC Time, hh is hours, mm is minutes, ss.ss is seconds
847
     * 2. Waypoint Latitude
848
     * 3. N = North, S = South
849
     * 4. Waypoint Longitude
850
     * 5. E = East, W = West
851
     * 6. Bearing, degrees True
852
     * 7. T = True
853
     * 8. Bearing, degrees Magnetic
854
     * 9. M = Magnetic
855
     * 10. Distance, Nautical Miles
856
     * 11. N = Nautical Miles
857
     * 12. Waypoint ID
858
     * 13. FAA mode indicator (NMEA 2.3 and later, optional)
859
     * 14. Checksum
860
     *
861
     * Parse this just to get the time, to help the cycle ender
862
     */
863
0
    gps_mask_t mask = ONLINE_SET;
864
865
0
    if ('\0' != field[1][0]) {
866
0
        if (0 == merge_hhmmss(field[1], session)) {
867
0
            if (0 == session->nmea.date.tm_year) {
868
0
                GPSD_LOG(LOG_WARN, &session->context->errout,
869
0
                         "NMEA0183: can't use BWC time until after ZDA or RMC"
870
0
                         " has supplied a year.\n");
871
0
            } else {
872
0
                mask = TIME_SET;
873
0
            }
874
0
        }
875
0
    }
876
0
    if (12 <= count) {
877
        // NMEA 2.3 and later
878
0
        session->newdata.status = faa_mode(field[12][0]);
879
0
    }
880
881
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
882
0
             "NMEA0183: BWC: hhmmss=%s faa mode %d\n",
883
0
             field[1], session->newdata.status);
884
0
    return mask;
885
0
}
886
887
static gps_mask_t processDBT(int c UNUSED, char *field[],
888
                             struct gps_device_t *session)
889
0
{
890
    /*
891
     * $SDDBT,7.7,f,2.3,M,1.3,F*05
892
     * 1) Depth below sounder in feet
893
     * 2) Fixed value 'f' indicating feet
894
     * 3) Depth below sounder in meters
895
     * 4) Fixed value 'M' indicating meters
896
     * 5) Depth below sounder in fathoms
897
     * 6) Fixed value 'F' indicating fathoms
898
     * 7) Checksum.
899
     *
900
     * In real-world sensors, sometimes not all three conversions are reported.
901
     */
902
0
    gps_mask_t mask = ONLINE_SET;
903
904
0
    if ('\0' != field[3][0]) {
905
0
        session->newdata.depth = safe_atof(field[3]);
906
0
        mask |= (ALTITUDE_SET);
907
0
    } else if ('\0' != field[1][0]) {
908
0
        session->newdata.depth = safe_atof(field[1]) * FEET_TO_METERS;
909
0
        mask |= (ALTITUDE_SET);
910
0
    } else if ('\0' != field[5][0]) {
911
0
        session->newdata.depth = safe_atof(field[5]) * FATHOMS_TO_METERS;
912
0
        mask |= (ALTITUDE_SET);
913
0
    }
914
915
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
916
0
             "NMEA0183: %s mode %d, depth %lf.\n",
917
0
             field[0],
918
0
             session->newdata.mode,
919
0
             session->newdata.depth);
920
0
    return mask;
921
0
}
922
923
static gps_mask_t processDPT(int c UNUSED, char *field[],
924
                             struct gps_device_t *session)
925
0
{
926
    /*
927
     * $--DPT,x.x,x.x,x.x*hh<CR><LF>
928
     * 1) Depth below sounder in meters
929
     * 2) (+) Offset between sounder and waterline in meters
930
     *    (-) Offset between sounder and keel in meters
931
     * 3) Maximum range scale
932
     * 4) Checksum.
933
     *
934
     * $SDDBT and $SDDPT should agree, but often don't.
935
     *
936
     */
937
0
    double offset;
938
0
    gps_mask_t mask = ONLINE_SET;
939
940
0
    if ('\0' == field[1][0]) {
941
        // no depth
942
0
        return mask;
943
0
    }
944
0
    session->newdata.depth = safe_atof(field[1]);
945
0
    offset = safe_atof(field[2]);
946
0
    if (0.0 > offset) {
947
        // adjust to get depth from keel
948
0
        session->newdata.depth -= offset;
949
0
    }
950
0
    mask |= ALTITUDE_SET;
951
952
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
953
0
             "NMEA0183: %s depth %.1f offset %s max %s\n",
954
0
             field[0],
955
0
             session->newdata.depth, field[2], field[3]);
956
0
    return mask;
957
0
}
958
959
/* NMEA Map Datum
960
 *
961
 * FIXME: seems to happen after cycle ender, so nothing happens...
962
 */
963
static gps_mask_t processDTM(int c UNUSED, char *field[],
964
                               struct gps_device_t *session)
965
0
{
966
    /*
967
     * $GPDTM,W84,C*52
968
     * $GPDTM,xxx,x,xx.xxxx,x,xx.xxxx,x,,xxx*hh<CR><LF>
969
     * 1    = Local datum code (xxx):
970
     *          W84 – WGS84
971
     *          W72 – WGS72
972
     *          S85 – SGS85
973
     *          P90 – PE90
974
     *          999 – User defined
975
     *          IHO datum code
976
     * 2     = Local datum sub code (x)
977
     * 3     = Latitude offset in minutes (xx.xxxx)
978
     * 4     = Latitude offset mark (N: +, S: -) (x)
979
     * 5     = Longitude offset in minutes (xx.xxxx)
980
     * 6     = Longitude offset mark (E: +, W: -) (x)
981
     * 7     = Altitude offset in meters. Always null
982
     * 8     = Datum (xxx):
983
     *          W84 – WGS84
984
     *          W72 – WGS72
985
     *          S85 – SGS85
986
     *          P90 – PE90
987
     *          999 – User defined
988
     *          IHO datum code
989
     * 9    = checksum
990
     */
991
0
    int i;
992
0
    static struct
993
0
    {
994
0
        char *code;
995
0
        char *name;
996
0
    } codes[] = {
997
0
        {"W84", "WGS84"},
998
0
        {"W72", "WGS72"},
999
0
        {"S85", "SGS85"},
1000
0
        {"P90", "PE90"},
1001
0
        {"999", "User Defined"},
1002
0
        {"", ""},
1003
0
    };
1004
1005
0
    gps_mask_t mask = ONLINE_SET;
1006
1007
0
    if ('\0' == field[1][0]) {
1008
0
        return mask;
1009
0
    }
1010
1011
0
    for (i = 0; ; i++) {
1012
0
        if ('\0' == codes[i].code[0]) {
1013
            // not found
1014
0
            strlcpy(session->newdata.datum, field[1],
1015
0
                    sizeof(session->newdata.datum));
1016
0
            break;
1017
0
        }
1018
0
        if (0 ==strcmp(codes[i].code, field[1])) {
1019
0
            strlcpy(session->newdata.datum, codes[i].name,
1020
0
                    sizeof(session->newdata.datum));
1021
0
            break;
1022
0
        }
1023
0
    }
1024
1025
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
1026
0
             "NMEA0183: xxDTM: datum=%.40s\n",
1027
0
             session->newdata.datum);
1028
0
    return mask;
1029
0
}
1030
1031
// NMEA 3.0 Estimated Position Error
1032
static gps_mask_t processGBS(int c UNUSED, char *field[],
1033
                               struct gps_device_t *session)
1034
0
{
1035
    /*
1036
     * $GPGBS,082941.00,2.4,1.5,3.9,25,,-43.7,27.5*65
1037
     *  1) UTC time of the fix associated with this sentence (hhmmss.ss)
1038
     *  2) Expected error in latitude (meters)
1039
     *  3) Expected error in longitude (meters)
1040
     *  4) Expected error in altitude (meters)
1041
     *  5) PRN of most likely failed satellite
1042
     *  6) Probability of missed detection for most likely failed satellite
1043
     *  7) Estimate of bias in meters on most likely failed satellite
1044
     *  8) Standard deviation of bias estimate
1045
     *  9) NMEA 4.1 GNSS ID
1046
     * 10) NMEA 4.1 Signal ID
1047
     *     Checksum
1048
     *
1049
     * Fields 2, 3 and 4 are one standard deviation.
1050
     */
1051
0
    gps_mask_t mask = ONLINE_SET;
1052
1053
    // register fractional time for end-of-cycle detection
1054
0
    register_fractional_time(field[0], field[1], session);
1055
1056
    // check that we're associated with the current fix
1057
0
    if (session->nmea.date.tm_hour == DD(field[1]) &&
1058
0
        session->nmea.date.tm_min == DD(field[1] + 2) &&
1059
0
        session->nmea.date.tm_sec == DD(field[1] + 4)) {
1060
0
        session->newdata.epy = safe_atof(field[2]);
1061
0
        session->newdata.epx = safe_atof(field[3]);
1062
0
        session->newdata.epv = safe_atof(field[4]);
1063
0
        GPSD_LOG(LOG_DATA, &session->context->errout,
1064
0
                 "NMEA0183: GBS: epx=%.2f epy=%.2f epv=%.2f\n",
1065
0
                 session->newdata.epx,
1066
0
                 session->newdata.epy,
1067
0
                 session->newdata.epv);
1068
0
        mask = HERR_SET | VERR_SET;
1069
0
    } else {
1070
0
        GPSD_LOG(LOG_PROG, &session->context->errout,
1071
0
                 "NMEA0183: second in $GPGBS error estimates doesn't match.\n");
1072
0
    }
1073
0
    return mask;
1074
0
}
1075
1076
// Global Positioning System Fix Data
1077
static gps_mask_t processGGA(int c UNUSED, char *field[],
1078
                               struct gps_device_t *session)
1079
0
{
1080
    /*
1081
     * GGA,123519,4807.038,N,01131.324,E,1,08,0.9,545.4,M,46.9,M, , *42
1082
     * 1     123519       Fix taken at 12:35:19 UTC
1083
     * 2,3   4807.038,N   Latitude 48 deg 07.038' N
1084
     * 4,5   01131.324,E  Longitude 11 deg 31.324' E
1085
     * 6     1            Fix quality:
1086
     *                     0 = invalid,
1087
     *                     1 = GPS,
1088
     *                         u-blox may use 1 for Estimated
1089
     *                     2 = DGPS,
1090
     *                     3 = PPS (Precise Position Service),
1091
     *                     4 = RTK (Real Time Kinematic) with fixed integers,
1092
     *                     5 = Float RTK,
1093
     *                     6 = Estimated,
1094
     *                     7 = Manual,
1095
     *                     8 = Simulator
1096
     * 7     08           Number of satellites in use
1097
     * 8     0.9          Horizontal dilution of position
1098
     * 9,10  545.4,M      Altitude, Meters MSL
1099
     * 11,12 46.9,M       Height of geoid (mean sea level) above WGS84
1100
     *                    ellipsoid, in Meters
1101
     * 13    33           time in seconds since last DGPS update
1102
     *                    usually empty
1103
     * 14    1023         DGPS station ID number (0000-1023)
1104
     *                    usually empty
1105
     *
1106
     * Some GPS, like the SiRFstarV in NMEA mode, send both GPGSA and
1107
     * GLGPSA with identical data.
1108
     */
1109
0
    gps_mask_t mask = ONLINE_SET;
1110
0
    int newstatus;
1111
0
    char last_last_gga_talker = session->nmea.last_gga_talker;
1112
0
    int fix;              // a.k.a Quality flag
1113
0
    session->nmea.last_gga_talker = field[0][1];
1114
1115
0
    if ('\0' == field[6][0]) {
1116
        /* no data is no data, assume no fix
1117
         * the test/daemon/myguide-3100.log shows lat/lon/alt but
1118
         * no status, and related RMC shows no fix. */
1119
0
        fix = -1;
1120
0
    } else {
1121
0
        fix = atoi(field[6]);
1122
0
    }
1123
    // Jackson Labs Micro JLT uses nonstadard fix flag, not handled
1124
0
    switch (fix) {
1125
0
    case 0:     // no fix
1126
0
        newstatus = STATUS_UNK;
1127
0
        if ('\0' == field[1][0]) {
1128
            /* No time available. That breaks cycle end detector
1129
             * Force report to bypass cycle detector and get report out.
1130
             * To handle Querks (Quectel) like this:
1131
             *  $GPGGA,,,,,,0,,,,,,,,*66
1132
             */
1133
0
            memset(&session->nmea.date, 0, sizeof(session->nmea.date));
1134
0
            session->cycle_end_reliable = false;
1135
0
            mask |= REPORT_IS | TIME_SET;
1136
0
        }
1137
0
        break;
1138
0
    case 1:
1139
        // could be 2D, 3D, GNSSDR
1140
0
        newstatus = STATUS_GPS;
1141
0
        break;
1142
0
    case 2:     // differential
1143
0
        newstatus = STATUS_DGPS;
1144
0
        break;
1145
0
    case 3:
1146
        // GPS PPS, fix valid, could be 2D, 3D, GNSSDR
1147
0
        newstatus = STATUS_PPS_FIX;
1148
0
        break;
1149
0
    case 4:     // RTK integer
1150
0
        newstatus = STATUS_RTK_FIX;
1151
0
        break;
1152
0
    case 5:     // RTK float
1153
0
        newstatus = STATUS_RTK_FLT;
1154
0
        break;
1155
0
    case 6:
1156
        // dead reckoning, could be valid or invalid
1157
0
        newstatus = STATUS_DR;
1158
0
        break;
1159
0
    case 7:
1160
        // manual input, surveyed
1161
0
        newstatus = STATUS_TIME;
1162
0
        break;
1163
0
    case 8:
1164
        /* simulated mode
1165
         * Garmin GPSMAP and Gecko sends an 8, but undocumented why */
1166
0
        newstatus = STATUS_SIM;
1167
0
        break;
1168
0
    case -1:
1169
0
        FALLTHROUGH
1170
0
    default:
1171
0
        newstatus = -1;
1172
0
        break;
1173
0
    }
1174
0
    if (0 <= newstatus) {
1175
0
        session->newdata.status = newstatus;
1176
0
        mask = STATUS_SET;
1177
0
    }
1178
    /*
1179
     * There are some receivers (the Trimble Placer 450 is an example) that
1180
     * don't ship a GSA with mode 1 when they lose satellite lock. Instead
1181
     * they just keep reporting GGA and GSA on subsequent cycles with the
1182
     * timestamp not advancing and a bogus mode.
1183
     *
1184
     * On the assumption that GGA is only issued once per cycle we can
1185
     * detect this here (it would be nicer to do it on GSA but GSA has
1186
     * no timestamp).
1187
     *
1188
     * SiRFstarV breaks this assumption, sending GGA with different
1189
     * talker IDs.
1190
     */
1191
0
    if ('\0' != last_last_gga_talker &&
1192
0
        last_last_gga_talker != session->nmea.last_gga_talker) {
1193
        // skip the time check
1194
0
        session->nmea.latch_mode = 0;
1195
0
    } else {
1196
0
        session->nmea.latch_mode = strncmp(field[1],
1197
0
                          session->nmea.last_gga_timestamp,
1198
0
                          sizeof(session->nmea.last_gga_timestamp))==0;
1199
0
    }
1200
1201
0
    if (session->nmea.latch_mode) {
1202
0
        session->newdata.status = STATUS_UNK;
1203
0
        session->newdata.mode = MODE_NO_FIX;
1204
0
        mask |= MODE_SET | STATUS_SET;
1205
0
        GPSD_LOG(LOG_PROG, &session->context->errout,
1206
0
                 "NMEA0183: xxGGA: latch mode\n");
1207
0
    } else {
1208
0
        (void)strlcpy(session->nmea.last_gga_timestamp, field[1],
1209
0
                      sizeof(session->nmea.last_gga_timestamp));
1210
0
    }
1211
1212
    /* satellites_visible is used as an accumulator in xxGSV
1213
     * so if we set it here we break xxGSV
1214
     * Some GPS, like SiRFstarV NMEA, report per GNSS used
1215
     * counts in GPGGA and GLGGA.
1216
     */
1217
0
    session->nmea.gga_sats_used = atoi(field[7]);
1218
1219
0
    if ('\0' == field[1][0]) {
1220
0
        GPSD_LOG(LOG_DATA, &session->context->errout,
1221
0
                 "NMEA0183: GGA time missing.\n");
1222
0
    } else if (0 == merge_hhmmss(field[1], session)) {
1223
0
        register_fractional_time(field[0], field[1], session);
1224
0
        if (0 == session->nmea.date.tm_year) {
1225
0
            GPSD_LOG(LOG_WARN, &session->context->errout,
1226
0
                     "NMEA0183: can't use GGA time until after ZDA or RMC"
1227
0
                     " has supplied a year.\n");
1228
0
        } else {
1229
0
            mask |= TIME_SET;
1230
0
        }
1231
0
    }
1232
1233
0
    if (0 == do_lat_lon(&field[2], &session->newdata)) {
1234
0
        session->newdata.mode = MODE_2D;
1235
0
        mask |= LATLON_SET;
1236
0
        if ('\0' != field[11][0]) {
1237
0
            session->newdata.geoid_sep = safe_atof(field[11]);
1238
0
        } else {
1239
0
            session->newdata.geoid_sep = wgs84_separation(
1240
0
                session->newdata.latitude, session->newdata.longitude);
1241
0
        }
1242
        /*
1243
         * SiRF chipsets up to version 2.2 report a null altitude field.
1244
         * See <http://www.sirf.com/Downloads/Technical/apnt0033.pdf>.
1245
         * If we see this, force mode to 2D at most.
1246
         */
1247
0
        if ('\0' != field[9][0]) {
1248
            // altitude is MSL
1249
0
            session->newdata.altMSL = safe_atof(field[9]);
1250
            // Let gpsd_error_model() deal with altHAE
1251
0
            mask |= ALTITUDE_SET;
1252
            /*
1253
             * This is a bit dodgy.  Technically we shouldn't set the mode
1254
             * bit until we see GSA.  But it may be later in the cycle,
1255
             * some devices like the FV-18 don't send it by default, and
1256
             * elsewhere in the code we want to be able to test for the
1257
             * presence of a valid fix with mode > MODE_NO_FIX.
1258
             *
1259
             * Use gga_sats_used; as double check on MODE_3D
1260
             */
1261
0
            if (4 <= session->nmea.gga_sats_used) {
1262
0
                session->newdata.mode = MODE_3D;
1263
0
            }
1264
0
        }
1265
0
        if (3 > session->nmea.gga_sats_used) {
1266
0
            session->newdata.mode = MODE_NO_FIX;
1267
0
        }
1268
0
    } else {
1269
0
        session->newdata.mode = MODE_NO_FIX;
1270
0
    }
1271
0
    mask |= MODE_SET;
1272
1273
    // BT-451, and others, send 99.99 for invalid DOPs
1274
0
    if ('\0' != field[8][0]) {
1275
0
        double hdop;
1276
0
        hdop = safe_atof(field[8]);
1277
0
        if (99.0 > hdop) {
1278
            // why not to newdata?
1279
0
            session->gpsdata.dop.hdop = hdop;
1280
0
            mask |= DOP_SET;
1281
0
        }
1282
0
    }
1283
1284
    // get DGPS stuff
1285
0
    if ('\0' != field[13][0] &&
1286
0
        '\0' != field[14][0]) {
1287
        // both, or neither
1288
0
        double age;
1289
0
        int station;
1290
1291
0
        age = safe_atof(field[13]);
1292
0
        station = atoi(field[14]);
1293
0
        if (0.09 < age ||
1294
0
            0 < station) {
1295
            // ignore both zeros
1296
0
            session->newdata.dgps_age = age;
1297
0
            session->newdata.dgps_station = station;
1298
0
        }
1299
0
    }
1300
1301
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
1302
0
             "NMEA0183: GGA: hhmmss=%s lat=%.2f lon=%.2f altMSL=%.2f "
1303
0
             "mode=%d status=%d\n",
1304
0
             field[1],
1305
0
             session->newdata.latitude,
1306
0
             session->newdata.longitude,
1307
0
             session->newdata.altMSL,
1308
0
             session->newdata.mode,
1309
0
             session->newdata.status);
1310
0
    return mask;
1311
0
}
1312
1313
// Geographic position - Latitude, Longitude
1314
static gps_mask_t processGLL(int count, char *field[],
1315
                             struct gps_device_t *session)
1316
0
{
1317
    /* Introduced in NMEA 3.0.
1318
     *
1319
     * $GPGLL,4916.45,N,12311.12,W,225444,A,A*5C
1320
     *
1321
     * 1,2: 4916.46,N    Latitude 49 deg. 16.45 min. North
1322
     * 3,4: 12311.12,W   Longitude 123 deg. 11.12 min. West
1323
     * 5:   225444       Fix taken at 22:54:44 UTC
1324
     * 6:   A            Data valid
1325
     * 7:   A            Autonomous mode
1326
     * 8:   *5C          Mandatory NMEA checksum
1327
     *
1328
     * 1,2 Latitude, N (North) or S (South)
1329
     * 3,4 Longitude, E (East) or W (West)
1330
     * 5 UTC of position
1331
     * 6 A = Active, V = Invalid data
1332
     * 7 Mode Indicator
1333
     *    See faa_mode() for possible mode values.
1334
     *
1335
     * I found a note at <http://www.secoh.ru/windows/gps/nmfqexep.txt>
1336
     * indicating that the Garmin 65 does not return time and status.
1337
     * SiRF chipsets don't return the Mode Indicator.
1338
     * This code copes gracefully with both quirks.
1339
     *
1340
     * Unless you care about the FAA indicator, this sentence supplies nothing
1341
     * that GPRMC doesn't already.  But at least two (Garmin GPS 48 and
1342
     * Magellan Triton 400) actually ship updates in GLL that aren't redundant.
1343
     *
1344
     */
1345
0
    char *status = field[7];
1346
0
    gps_mask_t mask = ONLINE_SET;
1347
1348
0
    if (field[5][0] != '\0') {
1349
0
        if (0 == merge_hhmmss(field[5], session)) {
1350
0
            register_fractional_time(field[0], field[5], session);
1351
0
            if (0 == session->nmea.date.tm_year) {
1352
0
                GPSD_LOG(LOG_WARN, &session->context->errout,
1353
0
                         "NMEA0183: can't use GLL time until after ZDA or RMC"
1354
0
                         " has supplied a year.\n");
1355
0
            } else {
1356
0
                mask = TIME_SET;
1357
0
            }
1358
0
        }
1359
0
    }
1360
0
    if ('\0' == field[6][0] ||
1361
0
        'V' == field[6][0]) {
1362
        // Invalid
1363
0
        session->newdata.status = STATUS_UNK;
1364
0
        session->newdata.mode = MODE_NO_FIX;
1365
0
    } else if ('A' == field[6][0] &&
1366
0
        (count < 8 || *status != 'N') &&
1367
0
        0 == do_lat_lon(&field[1], &session->newdata)) {
1368
0
        int newstatus;
1369
1370
0
        mask |= LATLON_SET;
1371
1372
0
        newstatus = STATUS_GPS;
1373
0
        if (8 <= count) {
1374
0
            newstatus = faa_mode(*status);
1375
0
        }
1376
        /*
1377
         * This is a bit dodgy.  Technically we shouldn't set the mode
1378
         * bit until we see GSA, or similar.  But it may be later in the
1379
         * cycle, some devices like the FV-18 don't send it by default,
1380
         * and elsewhere in the code we want to be able to test for the
1381
         * presence of a valid fix with mode > MODE_NO_FIX.
1382
         */
1383
0
        if (0 != isfinite(session->gpsdata.fix.altHAE) ||
1384
0
            0 != isfinite(session->gpsdata.fix.altMSL)) {
1385
0
            session->newdata.mode = MODE_3D;
1386
0
        } else if (3 < session->gpsdata.satellites_used) {
1387
            // 4 sats used means 3D
1388
0
            session->newdata.mode = MODE_3D;
1389
0
        } else if (MODE_2D > session->gpsdata.fix.mode ||
1390
0
                   (0 == isfinite(session->oldfix.altHAE) &&
1391
0
                    0 == isfinite(session->oldfix.altMSL))) {
1392
0
            session->newdata.mode = MODE_2D;
1393
0
        }
1394
0
        session->newdata.status = newstatus;
1395
0
    } else {
1396
0
        session->newdata.status = STATUS_UNK;
1397
0
        session->newdata.mode = MODE_NO_FIX;
1398
0
    }
1399
0
    mask |= STATUS_SET | MODE_SET;
1400
1401
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
1402
0
             "NMEA0183: GLL: hhmmss=%s lat=%.2f lon=%.2f mode=%d status=%d\n",
1403
0
             field[5],
1404
0
             session->newdata.latitude,
1405
0
             session->newdata.longitude,
1406
0
             session->newdata.mode,
1407
0
             session->newdata.status);
1408
0
    return mask;
1409
0
}
1410
1411
// Geographic position - Latitude, Longitude, and more
1412
static gps_mask_t processGNS(int count UNUSED, char *field[],
1413
                             struct gps_device_t *session)
1414
0
{
1415
    /* Introduced in NMEA 4.0?
1416
     *
1417
     * This mostly duplicates RMC, except for the multi GNSS mode
1418
     * indicator.
1419
     *
1420
     * Example.  Ignore the line break.
1421
     * $GPGNS,224749.00,3333.4268304,N,11153.3538273,W,D,19,0.6,406.110,
1422
     *        -26.294,6.0,0138,S,*6A
1423
     *
1424
     * 1:  224749.00     UTC HHMMSS.SS.  22:47:49.00
1425
     * 2:  3333.4268304  Latitude DDMM.MMMMM. 33 deg. 33.4268304 min
1426
     * 3:  N             Latitude North
1427
     * 4:  12311.12      Longitude 111 deg. 53.3538273 min
1428
     * 5:  W             Longitude West
1429
     * 6:  D             FAA mode indicator
1430
     *                     see faa_mode() for possible mode values
1431
     *                     May be one to six characters.
1432
     *                       Char 1 = GPS
1433
     *                       Char 2 = GLONASS
1434
     *                       Char 3 = Galileo
1435
     *                       Char 4 = BDS
1436
     *                       Char 5 = QZSS
1437
     *                       Char 6 = NavIC (IRNSS)
1438
     * 7:  19           Number of Satellites used in solution
1439
     * 8:  0.6          HDOP
1440
     * 9:  406110       MSL Altitude in meters
1441
     * 10: -26.294      Geoid separation in meters
1442
     * 11: 6.0          Age of differential corrections, in seconds
1443
     * 12: 0138         Differential reference station ID
1444
     * 13: S            NMEA 4.1+ Navigation status
1445
     *                   S = Safe
1446
     *                   C = Caution
1447
     *                   U = Unsafe
1448
     *                   V = Not valid for navigation
1449
     * 8:   *6A          Mandatory NMEA checksum
1450
     *
1451
     */
1452
0
    int newstatus;
1453
0
    gps_mask_t mask = ONLINE_SET;
1454
1455
0
    if ('\0' != field[1][0]) {
1456
0
        if (0 == merge_hhmmss(field[1], session)) {
1457
0
            register_fractional_time(field[0], field[1], session);
1458
0
            if (0 == session->nmea.date.tm_year) {
1459
0
                GPSD_LOG(LOG_WARN, &session->context->errout,
1460
0
                         "NMEA0183: can't use GNS time until after ZDA or RMC"
1461
0
                         " has supplied a year.\n");
1462
0
            } else {
1463
0
                mask = TIME_SET;
1464
0
            }
1465
0
        }
1466
0
    }
1467
1468
    /* FAA mode: not valid, ignore
1469
     * Yes, in 2019 a GLONASS only fix may be valid, but not worth
1470
     * the confusion */
1471
0
    if ('\0' == field[6][0] ||      // FAA mode: missing
1472
0
        'N' == field[6][0]) {       // FAA mode: not valid
1473
0
        session->newdata.mode = MODE_NO_FIX;
1474
0
        mask |= MODE_SET;
1475
0
        return mask;
1476
0
    }
1477
    /* navigation status, assume S=safe and C=caution are OK
1478
     * can be missing on valid fix */
1479
0
    if ('U' == field[13][0] ||      // Unsafe
1480
0
        'V' == field[13][0]) {      // not valid
1481
0
        return mask;
1482
0
    }
1483
1484
0
    session->nmea.gga_sats_used = atoi(field[7]);
1485
1486
0
    if (0 == do_lat_lon(&field[2], &session->newdata)) {
1487
0
        mask |= LATLON_SET;
1488
0
        session->newdata.mode = MODE_2D;
1489
1490
0
        if ('\0' != field[9][0]) {
1491
            // altitude is MSL
1492
0
            session->newdata.altMSL = safe_atof(field[9]);
1493
0
            if (0 != isfinite(session->newdata.altMSL)) {
1494
0
                mask |= ALTITUDE_SET;
1495
0
                if (3 < session->nmea.gga_sats_used) {
1496
                    // more than 3 sats used means 3D
1497
0
                    session->newdata.mode = MODE_3D;
1498
0
                }
1499
0
            }
1500
            // only need geoid_sep if in 3D mode
1501
0
            if ('\0' != field[10][0]) {
1502
0
                session->newdata.geoid_sep = safe_atof(field[10]);
1503
0
            }
1504
            // Let gpsd_error_model() deal with geoid_sep and altHAE
1505
0
        }
1506
0
    } else {
1507
0
        session->newdata.mode = MODE_NO_FIX;
1508
0
        mask |= MODE_SET;
1509
0
    }
1510
1511
0
    if ('\0' != field[8][0]) {
1512
0
        session->gpsdata.dop.hdop = safe_atof(field[8]);
1513
0
        mask |= DOP_SET;
1514
0
    }
1515
1516
    // we ignore all but the leading mode indicator.
1517
0
    newstatus = faa_mode(field[6][0]);
1518
1519
0
    session->newdata.status = newstatus;
1520
0
    mask |= MODE_SET | STATUS_SET;
1521
1522
    // get DGPS stuff
1523
0
    if ('\0' != field[11][0] &&
1524
0
        '\0' != field[12][0]) {
1525
        // both, or neither
1526
0
        session->newdata.dgps_age = safe_atof(field[11]);
1527
0
        session->newdata.dgps_station = atoi(field[12]);
1528
0
    }
1529
1530
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
1531
0
             "NMEA0183: GNS: hhmmss=%s lat=%.2f lon=%.2f mode=%d status=%d\n",
1532
0
             field[1],
1533
0
             session->newdata.latitude,
1534
0
             session->newdata.longitude,
1535
0
             session->newdata.mode,
1536
0
             session->newdata.status);
1537
0
    return mask;
1538
0
}
1539
1540
// GNSS Range residuals
1541
static gps_mask_t processGRS(int count UNUSED, char *field[],
1542
                             struct gps_device_t *session)
1543
0
{
1544
    /* In NMEA 3.01
1545
     *
1546
     * Example:
1547
     * $GPGRS,150119.000,1,-0.33,-2.59,3.03,-0.09,-2.98,7.12,-15.6,17.0,,,,*5A
1548
     *
1549
     * 1:  150119.000    UTC HHMMSS.SS
1550
     * 2:  1             Mode: 0 == original, 1 == recomputed
1551
     * 3:  -0.33         range residual in meters sat 1
1552
     * 4:  -2.59         range residual sat 2
1553
     * [...]
1554
     * n:   *5A          Mandatory NMEA checksum
1555
     *
1556
     */
1557
0
    int mode;
1558
0
    gps_mask_t mask = ONLINE_SET;
1559
1560
0
    if ('\0' == field[1][0] ||
1561
0
        0 != merge_hhmmss(field[1], session)) {
1562
        // bad time
1563
0
        return mask;
1564
0
    }
1565
1566
0
    mode = atoi(field[2]);
1567
0
    if (1 != mode &&
1568
0
        2 != mode) {
1569
        // bad mode
1570
0
        return mask;
1571
0
    }
1572
1573
    // FIXME: partial decode.  How to match sat numbers up with GSA?
1574
1575
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
1576
0
             "NMEA0183: %s: mode %d count %d\n",
1577
0
             field[0], mode, count);
1578
0
    return mask;
1579
0
}
1580
1581
// GPS DOP and Active Satellites
1582
static gps_mask_t processGSA(int count, char *field[],
1583
                             struct gps_device_t *session)
1584
0
{
1585
0
#define GSA_TALKER      field[0][1]
1586
    /*
1587
     * eg1. $GPGSA,A,3,,,,,,16,18,,22,24,,,3.6,2.1,2.2*3C
1588
     * eg2. $GPGSA,A,3,19,28,14,18,27,22,31,39,,,,,1.7,1.0,1.3*35
1589
     * NMEA 4.10: $GNGSA,A,3,13,12,22,19,08,21,,,,,,,1.05,0.64,0.83,4*0B
1590
     * 1    = Mode:
1591
     *         M=Manual, forced to operate in 2D or 3D
1592
     *         A=Automatic, 3D/2D
1593
     * 2    = Mode:
1594
     *         1=Fix not available,
1595
     *         2=2D,
1596
     *         3=3D
1597
     *         E=Dead Reckonig (Antaris)
1598
     * 3-14 (or 24!) = satellite PRNs used in position fix (null unused)
1599
     * 15   = PDOP
1600
     * 16   = HDOP
1601
     * 17   = VDOP
1602
     *  -- -- --
1603
     * 18   - NMEA 4.10+ GNSS System ID, u-blox extended, Quectel $PQ
1604
     *             0 = QZSS (Trimble only)
1605
     *             1 = GPS
1606
     *             2 = GLONASS
1607
     *             3 = Galileo
1608
     *             4 = BeiDou
1609
     *             5 = QZSS
1610
     *             6 - NavIC (IRNSS)
1611
     *  -- OR --
1612
     * 18     SiRF TriG puts a floating point number here.
1613
     *  -- -- --
1614
     *
1615
     * Not all documentation specifies the number of PRN fields, it
1616
     * may be variable.  Most doc that specifies says 12 PRNs.
1617
     *
1618
     * The Navior-24 CH-4701 outputs 30 fields, 24 PRNs!
1619
     * GPGSA,A,3,27,23,13,07,25,,,,,,,,,,,,,,,,,,,,07.9,06.0,05.2
1620
     *
1621
     * The Skytraq S2525F8-BD-RTK output both GPGSA and BDGSA in the
1622
     * same cycle:
1623
     * $GPGSA,A,3,23,31,22,16,03,07,,,,,,,1.8,1.1,1.4*3E
1624
     * $BDGSA,A,3,214,,,,,,,,,,,,1.8,1.1,1.4*18
1625
     * These need to be combined like GPGSV and BDGSV
1626
     *
1627
     * The SiRF-TriG, found in their Atlas VI SoC,  uses field 18 for
1628
     * something other than the NMEA gnss ID:
1629
     * $GPGSA,A,3,25,32,12,14,,,,,,,,,2.1,1.1,1.8,1.2*39
1630
     * $BDGSA,A,3,02,03,04,,,,,,,,,,2.1,1.1,1.8,1.2*2D
1631
     *
1632
     * Some GPS emit GNGSA.  So far we have not seen a GPS emit GNGSA
1633
     * and then another flavor of xxGSA
1634
     *
1635
     * Some Skytraq will emit all GPS in one GNGSA, Then follow with
1636
     * another GNGSA with the BeiDou birds.
1637
     *
1638
     * SEANEXX, SiRFstarIV, and others also do it twice in one cycle:
1639
     * $GNGSA,A,3,31,26,21,,,,,,,,,,3.77,2.55,2.77*1A
1640
     * $GNGSA,A,3,75,86,87,,,,,,,,,,3.77,2.55,2.77*1C
1641
     * seems like the first is GNSS and the second GLONASS
1642
     *
1643
     * u-blox 9 outputs one per GNSS on each cycle.  Note the
1644
     * extra last parameter which is NMEA gnssid:
1645
     * $GNGSA,A,3,13,16,21,15,10,29,27,20,,,,,1.05,0.64,0.83,1*03
1646
     * $GNGSA,A,3,82,66,81,,,,,,,,,,1.05,0.64,0.83,2*0C
1647
     * $GNGSA,A,3,07,12,33,,,,,,,,,,1.05,0.64,0.83,3*0A
1648
     * $GNGSA,A,3,13,12,22,19,08,21,,,,,,,1.05,0.64,0.83,4*0B
1649
     * Also note the NMEA 4.0 GLONASS PRN (82) in an NMEA 4.1
1650
     * sentence.
1651
     *
1652
     * Another Quectel Querk.  Note the extra field on the end.
1653
     *   System ID, 4 = BeiDou, 5 = QZSS
1654
     *
1655
     * $PQGSA,A,3,12,,,,,,,,,,,,1.2,0.9,0.9,4*3C
1656
     * $PQGSA,A,3,,,,,,,,,,,,,1.2,0.9,0.9,5*3E
1657
     * NMEA 4.11 says they should use $BDGSA and $GQGSA
1658
     */
1659
0
    gps_mask_t mask = ONLINE_SET;
1660
0
    char last_last_gsa_talker = session->nmea.last_gsa_talker;
1661
0
    int nmea_gnssid = 0;
1662
1663
    /*
1664
     * One chipset called the i.Trek M3 issues GPGSA lines that look like
1665
     * this: "$GPGSA,A,1,,,,*32" when it has no fix.  This is broken
1666
     * in at least two ways: it's got the wrong number of fields, and
1667
     * it claims to be a valid sentence (A flag) when it isn't.
1668
     * Alarmingly, it's possible this error may be generic to SiRFstarIII.
1669
     */
1670
0
    if (session->nmea.latch_mode) {
1671
        // last GGA had a non-advancing timestamp; don't trust this GSA
1672
0
        GPSD_LOG(LOG_PROG, &session->context->errout,
1673
0
                 "NMEA0183: %s: non-advancing timestamp\n", field[0]);
1674
        // FIXME: return here?
1675
0
    } else {
1676
0
        int i;
1677
1678
0
        i = atoi(field[2]);
1679
        /*
1680
         * The first arm of this conditional ignores dead-reckoning
1681
         * fixes from an Antaris chipset. which returns E in field 2
1682
         * for a dead-reckoning estimate.  Fix by Andreas Stricker.
1683
         */
1684
0
        if (1 <= i &&
1685
0
            3 >= i) {
1686
0
            session->newdata.mode = i;
1687
0
            mask = MODE_SET;
1688
1689
0
            GPSD_LOG(LOG_PROG, &session->context->errout,
1690
0
                     "NMEA0183: %s sets mode %d\n",
1691
0
                     field[0], session->newdata.mode);
1692
0
        }
1693
1694
#if 0   // debug
1695
        GPSD_LOG(LOG_SHOUT, &session->context->errout,
1696
                 "NMEA0183: %s: count %d \n", field[0], count);
1697
#endif  // debug
1698
0
        if (19 < count) {
1699
0
            GPSD_LOG(LOG_WARN, &session->context->errout,
1700
0
                     "NMEA0183: %s: count %d too long!\n", field[0], count);
1701
0
        } else {
1702
0
            double dop;
1703
1704
            // Just ignore the last fields of the Navior CH-4701
1705
0
            if ('\0' != field[15][0]) {
1706
0
                dop = safe_atof(field[15]);
1707
0
                if (99.0 > dop) {
1708
0
                    session->gpsdata.dop.pdop = dop;
1709
0
                    mask |= DOP_SET;
1710
0
                }
1711
0
            }
1712
            // BT-451, and others, send 99.99 for invalid DOPs
1713
0
            if ('\0' != field[16][0]) {
1714
0
                dop = safe_atof(field[16]);
1715
0
                if (99.0 > dop) {
1716
0
                    session->gpsdata.dop.hdop = dop;
1717
0
                    mask |= DOP_SET;
1718
0
                }
1719
0
            }
1720
0
            if ('\0' != field[17][0]) {
1721
0
                dop = safe_atof(field[17]);
1722
0
                if (99.0 > dop) {
1723
0
                    session->gpsdata.dop.vdop = dop;
1724
0
                    mask |= DOP_SET;
1725
0
                }
1726
0
            }
1727
0
            if (19 == count &&
1728
0
                '\0' != field[18][0]) {
1729
0
                if (NULL != strchr(field[18], '.')) {
1730
                    // SiRF TriG puts a floating point in field 18
1731
0
                    GPSD_LOG(LOG_WARN, &session->context->errout,
1732
0
                             "NMEA0183: %s: illegal field 18 (%s)!\n",
1733
0
                             field[0], field[18]);
1734
0
                } else {
1735
                    // get the NMEA 4.10, or $PQGSA, system ID
1736
0
                    nmea_gnssid = atoi(field[18]);
1737
0
                }
1738
0
            }
1739
0
        }
1740
        /*
1741
         * might have gone from GPGSA to GLGSA/BDGSA
1742
         * or GNGSA to GNGSA
1743
         * or GNGSA to PQGSA
1744
         * in which case accumulate
1745
         */
1746
        // FIXME: maybe on clear on first GPGSA?
1747
0
        if ('\0' == session->nmea.last_gsa_talker ||
1748
0
            (GSA_TALKER == session->nmea.last_gsa_talker &&
1749
0
             'N' != GSA_TALKER &&
1750
0
             'Q' != GSA_TALKER) ) {
1751
0
            session->gpsdata.satellites_used = 0;
1752
0
            memset(session->nmea.sats_used, 0, sizeof(session->nmea.sats_used));
1753
0
            GPSD_LOG(LOG_PROG, &session->context->errout,
1754
0
                     "NMEA0183: %s: clear sats_used\n", field[0]);
1755
0
        }
1756
0
        session->nmea.last_gsa_talker = GSA_TALKER;
1757
1758
        /* figure out which constellation(s) this GSA is for by looking
1759
         * at the talker ID. */
1760
0
        switch (session->nmea.last_gsa_talker) {
1761
0
        case 'A':
1762
            // GA Galileo
1763
0
            nmea_gnssid = 3;
1764
0
            session->nmea.seen_gagsa = true;
1765
0
            break;
1766
0
        case 'B':
1767
            // GB BeiDou
1768
0
            FALLTHROUGH
1769
0
        case 'D':
1770
            // BD BeiDou
1771
0
            nmea_gnssid = 4;
1772
0
            session->nmea.seen_bdgsa = true;
1773
0
            break;
1774
0
        case 'I':
1775
            // GI IRNSS
1776
0
            nmea_gnssid = 6;
1777
0
            session->nmea.seen_gigsa = true;
1778
0
            break;
1779
0
        case 'L':
1780
            // GL GLONASS
1781
0
            nmea_gnssid = 2;
1782
0
            session->nmea.seen_glgsa = true;
1783
0
            break;
1784
0
        case 'N':
1785
            // GN GNSS
1786
0
            session->nmea.seen_gngsa = true;
1787
            // field 18 is the NMEA gnssid in 4.10 and up.
1788
            // nmea_gnssid set above
1789
0
            break;
1790
0
        case 'P':
1791
            // GP GPS
1792
0
            session->nmea.seen_gpgsa = true;
1793
0
            nmea_gnssid = 1;
1794
0
            break;
1795
0
        case 'Q':
1796
            // Quectel EC25 & EC21 use PQGSA for QZSS and GLONASS
1797
0
            if ('P' == field[0][0] &&
1798
0
                0 != nmea_gnssid) {
1799
                /* Quectel EC25 & EC21 use PQGSV for BeiDou or QZSS
1800
                 * nmea_gnssid set above.  What about seen?
1801
                 */
1802
0
                break;
1803
0
            }
1804
0
            FALLTHROUGH
1805
0
        case 'Z':        // QZ QZSS
1806
            // NMEA 4.11 GQGSA for QZSS
1807
0
            nmea_gnssid = 5;
1808
0
            session->nmea.seen_qzgsa = true;
1809
0
            break;
1810
0
        }
1811
1812
        /* The magic 6 here is the tag, two mode fields, and three DOP fields.
1813
         * Maybe 7, NMEA 4.10+, also has gnssid field. */
1814
0
        for (i = 0; i < count - 6; i++) {
1815
0
            int prn;
1816
0
            int n;
1817
0
            int nmea_satnum;            // almost svid...
1818
0
            unsigned char ubx_gnssid;   // UNUSED
1819
0
            unsigned char ubx_svid;     // UNUSED
1820
1821
            // skip empty fields, otherwise empty becomes prn=200
1822
0
            if ('\0' == field[i + 3][0]) {
1823
0
                continue;
1824
0
            }
1825
0
            if (NULL != strchr(field[i + 3], '.')) {
1826
                // found a float, must be PDOP, done.
1827
0
                break;
1828
0
            }
1829
0
            nmea_satnum = atoi(field[i + 3]);
1830
0
            if (1 > nmea_satnum ||
1831
0
                600 < nmea_satnum) {
1832
0
                continue;
1833
0
            }
1834
0
            prn = nmeaid_to_prn(field[0], nmea_satnum, nmea_gnssid,
1835
0
                                &ubx_gnssid, &ubx_svid);
1836
1837
#if 0       // debug
1838
            GPSD_LOG(LOG_SHOUT, &session->context->errout,
1839
                     "NMEA0183: %s PRN %d nmea_gnssid %d "
1840
                     "nmea_satnum %d ubx_gnssid %d ubx_svid %d count %d \n",
1841
                     field[0], prn, nmea_gnssid, nmea_satnum, ubx_gnssid,
1842
                     ubx_svid, count);
1843
#endif      //  debug
1844
1845
0
            if (0 >= prn) {
1846
                // huh?
1847
0
                continue;
1848
0
            }
1849
            // check first BEFORE over-writing memory
1850
0
            if (MAXCHANNELS <= session->gpsdata.satellites_used) {
1851
                /* This should never happen as xxGSA is limited to 12,
1852
                 * except for the Navior-24 CH-4701.
1853
                 * But it could happen with multiple GSA per cycle */
1854
0
                GPSD_LOG(LOG_ERROR, &session->context->errout,
1855
0
                         "NMEA0183: %s used >= MAXCHANNELS!\n", field[0]);
1856
0
                break;
1857
0
            }
1858
            /* check for duplicate.
1859
             * Often GPS in both $GPGSA and $GNGSA, for example Quectel. */
1860
0
            for (n = 0; n < MAXCHANNELS; n++) {
1861
0
                if ( 0 == session->nmea.sats_used[n]) {
1862
                    // unused slot, use it.
1863
0
                    session->nmea.sats_used[n] = (unsigned short)prn;
1864
0
                    session->gpsdata.satellites_used = n + 1;
1865
0
                    break;
1866
0
                }
1867
0
                if (session->nmea.sats_used[n] == (unsigned short)prn) {
1868
                    // Duplicate!
1869
0
                    break;
1870
0
                }
1871
0
            }
1872
0
        }
1873
0
        mask |= USED_IS;
1874
0
        GPSD_LOG(LOG_PROG, &session->context->errout,
1875
0
                 "NMEA0183: %s: mode=%d used=%d pdop=%.2f hdop=%.2f "
1876
0
                 "vdop=%.2f nmea_gnssid %d\n",
1877
0
                 field[0], session->newdata.mode,
1878
0
                 session->gpsdata.satellites_used,
1879
0
                 session->gpsdata.dop.pdop,
1880
0
                 session->gpsdata.dop.hdop,
1881
0
                 session->gpsdata.dop.vdop, nmea_gnssid);
1882
0
    }
1883
    // assumes GLGSA or BDGSA, if present, is emitted directly after the GPGSA
1884
0
    if ((session->nmea.seen_bdgsa ||
1885
0
         session->nmea.seen_gagsa ||
1886
0
         session->nmea.seen_gigsa ||
1887
0
         session->nmea.seen_glgsa ||
1888
0
         session->nmea.seen_gngsa ||
1889
0
         session->nmea.seen_qzgsa) &&
1890
0
         GSA_TALKER == 'P') {
1891
0
        mask = ONLINE_SET;
1892
0
    } else if ('N' != last_last_gsa_talker &&
1893
0
               'N' == GSA_TALKER) {
1894
        /* first of two GNGSA
1895
         * if mode == 1 some GPS only output 1 GNGSA, so ship mode always */
1896
0
        mask =  ONLINE_SET | MODE_SET;
1897
0
    }
1898
1899
    // cast for 32/64 compatibility
1900
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
1901
0
             "NMEA0183: %s: count %d visible %d used %d mask %#llx\n",
1902
0
             field[0], count, session->gpsdata.satellites_used,
1903
0
             session->gpsdata.satellites_used,
1904
0
             (long long unsigned)mask);
1905
0
    return mask;
1906
0
#undef GSA_TALKER
1907
0
}
1908
1909
// GST - GPS Pseudorange Noise Statistics
1910
static gps_mask_t processGST(int count, char *field[],
1911
                             struct gps_device_t *session)
1912
0
{
1913
    /*
1914
     * GST,hhmmss.ss,x,x,x,x,x,x,x,*hh
1915
     * 1 UTC time of associated GGA fix
1916
     * 2 Total RMS standard deviation of ranges inputs to the nav solution
1917
     * 3 Standard deviation (meters) of semi-major axis of error ellipse
1918
     * 4 Standard deviation (meters) of semi-minor axis of error ellipse
1919
     * 5 Orientation of semi-major axis of error ellipse (true north degrees)
1920
     * 6 Standard deviation (meters) of latitude error
1921
     * 7 Standard deviation (meters) of longitude error
1922
     * 8 Standard deviation (meters) of altitude error
1923
     * 9 Checksum
1924
     */
1925
0
    struct tm date;
1926
0
    timespec_t ts;
1927
0
    int ret;
1928
0
    char ts_buf[TIMESPEC_LEN];
1929
0
    gps_mask_t mask = ONLINE_SET;
1930
1931
0
    if (0 > count) {
1932
0
      return mask;
1933
0
    }
1934
1935
    // since it is NOT current time, do not register_fractional_time()
1936
    // compute start of today
1937
0
    if (0 < session->nmea.date.tm_year) {
1938
        // Do not bother if no current year
1939
0
        memset(&date, 0, sizeof(date));
1940
0
        date.tm_year = session->nmea.date.tm_year;
1941
0
        date.tm_mon = session->nmea.date.tm_mon;
1942
0
        date.tm_mday = session->nmea.date.tm_mday;
1943
1944
        /* note this is not full UTC, just HHMMSS.ss
1945
         * this is not the current time,
1946
         * it references another GPA of the same stamp. So do not set
1947
         * any time stamps with it */
1948
0
        ret = decode_hhmmss(&date, &ts.tv_nsec, field[1], session);
1949
0
    } else {
1950
0
        ret = 1;
1951
0
    }
1952
0
    if (0 == ret) {
1953
        // convert to timespec_t , tv_nsec already set
1954
0
        session->gpsdata.gst.utctime.tv_sec = mkgmtime(&date);
1955
0
        session->gpsdata.gst.utctime.tv_nsec = ts.tv_nsec;
1956
0
    } else {
1957
        // no idea of UTC time now
1958
0
        session->gpsdata.gst.utctime.tv_sec = 0;
1959
0
        session->gpsdata.gst.utctime.tv_nsec = 0;
1960
0
    }
1961
0
    session->gpsdata.gst.rms_deviation       = safe_atof(field[2]);
1962
0
    session->gpsdata.gst.smajor_deviation    = safe_atof(field[3]);
1963
0
    session->gpsdata.gst.sminor_deviation    = safe_atof(field[4]);
1964
0
    session->gpsdata.gst.smajor_orientation  = safe_atof(field[5]);
1965
0
    session->gpsdata.gst.lat_err_deviation   = safe_atof(field[6]);
1966
0
    session->gpsdata.gst.lon_err_deviation   = safe_atof(field[7]);
1967
0
    session->gpsdata.gst.alt_err_deviation   = safe_atof(field[8]);
1968
1969
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
1970
0
             "NMEA0183: GST: utc = %s, rms = %.2f, maj = %.2f, min = %.2f,"
1971
0
             " ori = %.2f, lat = %.2f, lon = %.2f, alt = %.2f\n",
1972
0
             timespec_str(&session->gpsdata.gst.utctime, ts_buf,
1973
0
                          sizeof(ts_buf)),
1974
0
             session->gpsdata.gst.rms_deviation,
1975
0
             session->gpsdata.gst.smajor_deviation,
1976
0
             session->gpsdata.gst.sminor_deviation,
1977
0
             session->gpsdata.gst.smajor_orientation,
1978
0
             session->gpsdata.gst.lat_err_deviation,
1979
0
             session->gpsdata.gst.lon_err_deviation,
1980
0
             session->gpsdata.gst.alt_err_deviation);
1981
1982
0
    mask = GST_SET | ONLINE_SET;
1983
0
    return mask;
1984
0
}
1985
1986
// xxGSV -  GPS Satellites in View
1987
static gps_mask_t processGSV(int count, char *field[],
1988
                             struct gps_device_t *session)
1989
0
{
1990
0
#define GSV_TALKER      field[0][1]
1991
    /*
1992
     * GSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75
1993
     *  1) 2           Number of sentences for full data
1994
     *  2) 1           Sentence 1 of 2
1995
     *  3) 08          Total number of satellites in view
1996
     *  4) 01          Satellite PRN number
1997
     *  5) 40          Elevation, degrees
1998
     *  6) 083         Azimuth, degrees
1999
     *  7) 46          Signal-to-noise ratio in decibels
2000
     * <repeat for up to 4 satellites per sentence>
2001
     *   m - 1)        NMEA 4.10 Signal Id (optional)
2002
     *                 Quecktel Querk: 0 for "All Signa;s".
2003
     *   m=n-1)        Quectel Querk: System ID (optional)
2004
     *                     4 = BeiDou, 5 = QZSS
2005
     *   n)            checksum
2006
     *
2007
     * NMEA 4.1+:
2008
     * $GAGSV,3,1,09,02,00,179,,04,09,321,,07,11,134,11,11,10,227,,7*7F
2009
     * after the satellite block, before the checksum, new field:
2010
     *             NMEA Signal ID, depends on constellation.
2011
     &             see include/gps.h
2012
     *
2013
     * Quectel Querk:
2014
     * $PQGSV,4,2,15,09,16,120,,10,26,049,,16,07,123,,19,34,212,,0,4*62
2015
     * after the Signal ID, before the checksum, new field:
2016
     *             System ID
2017
     *             4 = BeiDou
2018
     *             5 = QZSS
2019
     *
2020
     * Can occur with talker IDs:
2021
     *   BD (Beidou),
2022
     *   GA (Galileo),
2023
     *   GB (Beidou),
2024
     *   GI (IRNSS),
2025
     *   GL (GLONASS),
2026
     *   GN (GLONASS, any combination GNSS),
2027
     *   GP (GPS, SBAS, QZSS),
2028
     *   GQ (QZSS).
2029
     *   PQ (QZSS). Quectel Querk. BeiDou or QZSS
2030
     *   QZ (QZSS).
2031
     *
2032
     * As of April 2019:
2033
     *    no gpsd regressions have GNGSV
2034
     *    every xxGSV cycle starts with GPGSV
2035
     *    xxGSV cycles may be spread over several xxRMC cycles
2036
     *
2037
     * GL may be (incorrectly) used when GSVs are mixed containing
2038
     * GLONASS, GN may be (incorrectly) used when GSVs contain GLONASS
2039
     * only.  Usage is inconsistent.
2040
     *
2041
     * In the GLONASS version sat IDs run from 65-96 (NMEA0183
2042
     * standardizes this). At least two GPSes, the BU-353 GLONASS and
2043
     * the u-blox NEO-M8N, emit a GPGSV set followed by a GLGSV set.
2044
     * We have also seen two GPSes, the Skytraq S2525F8-BD-RTK and a
2045
     * SiRF-IV variant, that emit GPGSV followed by BDGSV. We need to
2046
     * combine these.
2047
     *
2048
     * The following shows how the Skytraq S2525F8-BD-RTK output both
2049
     * GPGSV and BDGSV in the same cycle:
2050
     * $GPGSV,4,1,13,23,66,310,29,03,65,186,33,26,43,081,27,16,41,124,38*78
2051
     * $GPGSV,4,2,13,51,37,160,38,04,37,066,25,09,34,291,07,22,26,156,37*77
2052
     * $GPGSV,4,3,13,06,19,301,,31,17,052,20,193,11,307,,07,11,232,27*4F
2053
     * $GPGSV,4,4,13,01,03,202,30*4A
2054
     * $BDGSV,1,1,02,214,55,153,40,208,01,299,*67
2055
     *
2056
     * The driver automatically adapts to either case, but it takes until the
2057
     * second cycle (usually 10 seconds from device connect) for it to
2058
     * learn to expect BDGSV or GLGSV.
2059
     *
2060
     * Some GPS (Garmin 17N) spread the xxGSV over several cycles.  So
2061
     * cycles, or cycle time, can not be used to determine start of
2062
     * xxGSV cycle.
2063
     *
2064
     * NMEA 4.1 adds a signal-ID field just before the checksum. First
2065
     * seen in May 2015 on a u-blox M8.  It can output 2 sets of GPGSV
2066
     * in one cycle, one for L1C and the other for L2C.
2067
     *
2068
     * Once again, Quectel is Querky.  They added the $PQGSV sentence
2069
     * to handle what NMEA 4.11 says should be in the $BDGSV and $GQGSV
2070
     * sentences.  $PQGSV adds a new field just before the checksum for the
2071
     * System ID. This field is set to 4 for BeiDou, or 5 for QZSS.  The EG25
2072
     * output can look like this:
2073
     *
2074
     * $GLGSV,2,1,08,78,37,039,22,79,53,317,21,69,56,275,20,88,23,077,18,1*7A
2075
     * $GLGSV,2,2,08,87,11,030,17,68,37,195,21,70,11,331,,81,13,129,,1*79
2076
     * $PQGSV,4,1,15,02,20,116,,03,,,,05,34,137,,07,05,046,,0,4*58
2077
     * $PQGSV,4,2,15,09,16,120,,10,26,049,,16,07,123,,19,34,212,,0,4*62
2078
     * $PQGSV,4,3,15,20,03,174,,21,05,324,,22,37,281,,27,28,085,,0,4*65
2079
     * $PQGSV,4,4,15,28,07,039,,29,,,,30,23,143,,0,4*67
2080
     * $GAGSV,1,1,02,04,53,296,,09,05,322,,7*71
2081
     * $GPGSV,3,1,10,13,80,247,17,14,47,043,19,15,48,295,20,17,48,108,17,1*62
2082
     * $GPGSV,3,2,10,19,36,151,18,30,32,087,18,05,14,230,,07,03,098,,1*65
2083
     * $GPGSV,3,3,10,12,00,234,,24,13,295,,1*69
2084
     *
2085
     * Skytraq PX1172RH_DS can output GPGSV, GLGSV, GAGSV and GBGSV all in
2086
     * same epoch.  And each of those repeated for different signals
2087
     * (L1C/L2C/etc.)
2088
     */
2089
2090
0
    int n, fldnum;
2091
0
    unsigned char  nmea_sigid = 0;
2092
0
    int nmea_gnssid = 0;
2093
0
    unsigned char  ubx_sigid = 0;
2094
2095
0
    if (3 >= count) {
2096
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
2097
0
                 "NMEA0183: %s, malformed - fieldcount %d <= 3\n",
2098
0
                 field[0], count);
2099
0
        gpsd_zero_satellites(&session->gpsdata);
2100
0
        return ONLINE_SET;
2101
0
    }
2102
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
2103
0
             "NMEA0183: %s: part %s of %s, last_gsv_talker '%#x' "
2104
0
             " last_gsv_sigid %u\n",
2105
0
             field[0], field[2], field[1],
2106
0
             session->nmea.last_gsv_talker,
2107
0
             session->nmea.last_gsv_sigid);
2108
2109
    /*
2110
     * This check used to be !=0, but we have loosen it a little to let by
2111
     * NMEA 4.1 GSVs with an extra signal-ID field at the end.  Then loosen
2112
     * some more for Quectel  Querky $PQGSV.
2113
     */
2114
0
    switch (count % 4) {
2115
0
    case 0:
2116
        // normal, pre-NMEA 4.10
2117
0
        break;
2118
0
    case 1:
2119
        // NMEA 4.10, get the signal ID
2120
0
        nmea_sigid = atoi(field[count - 1]);
2121
0
        ubx_sigid = nmea_sigid_to_ubx(session, nmea_sigid);
2122
0
        break;
2123
0
    case 2:
2124
        // Quectel Querk. $PQGSV, get the signal ID, and system ID
2125
0
        nmea_sigid = atoi(field[count - 2]);
2126
0
        ubx_sigid = nmea_sigid_to_ubx(session, nmea_sigid);
2127
0
        nmea_gnssid = atoi(field[count - 1]);
2128
0
        if (4 > nmea_gnssid ||
2129
0
            5 < nmea_gnssid) {
2130
            // Quectel says only 4 or 5
2131
0
            GPSD_LOG(LOG_WARN, &session->context->errout,
2132
0
                     "NMEA0183: %sm invalid nmea_gnssid %d\n",
2133
0
                     field[0], nmea_gnssid);
2134
0
            return ONLINE_SET;
2135
0
        }
2136
0
        break;
2137
0
    default:
2138
        // bad count
2139
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
2140
0
                 "NMEA0183: malformed %s - fieldcount(%d)\n",
2141
0
                 field[0], count);
2142
0
        gpsd_zero_satellites(&session->gpsdata);
2143
0
        return ONLINE_SET;
2144
0
    }
2145
2146
0
    session->nmea.await = atoi(field[1]);
2147
0
    session->nmea.part = atoi(field[2]);
2148
0
    if (1 > session->nmea.part) {
2149
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
2150
0
                 "NMEA0183: %s: malformed - bad part %d\n",
2151
0
                 field[0], session->nmea.part);
2152
0
        gpsd_zero_satellites(&session->gpsdata);
2153
0
        return ONLINE_SET;
2154
0
    }
2155
2156
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
2157
0
             "NMEA0183: %s: part %d of %d nmea_gnssid %d nmea_sigid %d "
2158
0
             "ubx_sigid %d\n",
2159
0
             field[0], session->nmea.part, session->nmea.await,
2160
0
             nmea_gnssid, nmea_sigid, ubx_sigid);
2161
2162
0
    if (1 == session->nmea.part) {
2163
        /*
2164
         * might have gone from GPGSV to GLGSV/BDGSV/QZGSV,
2165
         * in which case accumulate
2166
         *
2167
         * NMEA 4.1 might have gone from GPGVS,sigid=x to GPGSV,sigid=y
2168
         *
2169
         * Quectel EG25 can go GLGSV, PQGSV, GAGSV, GPGSV, in one cycle.
2170
         *
2171
         * session->nmea.last_gsv_talker is zero at cycle start
2172
         */
2173
0
        if ('\0' == session->nmea.last_gsv_talker) {
2174
            // Assume all xxGSV in same epoch.  Clear at 1st in eopoch.
2175
0
            GPSD_LOG(LOG_PROG, &session->context->errout,
2176
0
                     "NMEA0183: %s: new part %d, last_gsv_talker '%#x', "
2177
0
                     "zeroing\n",
2178
0
                     field[0],
2179
0
                     session->nmea.part,
2180
0
                     session->nmea.last_gsv_talker);
2181
0
            gpsd_zero_satellites(&session->gpsdata);
2182
0
        }
2183
0
    }
2184
2185
0
    session->nmea.last_gsv_talker = GSV_TALKER;
2186
0
    session->nmea.last_gsv_sigid = ubx_sigid;  // UNUSED
2187
0
    switch (GSV_TALKER) {
2188
0
    case 'A':        // GA Galileo
2189
0
        nmea_gnssid = 3;
2190
        // Quectel LC79D can have sigid 6 (L1-A) and 1 (E5a)
2191
0
        session->nmea.seen_gagsv = true;
2192
0
        break;
2193
0
    case 'B':        // GB BeiDou
2194
0
        FALLTHROUGH
2195
0
    case 'D':        // BD BeiDou
2196
0
        nmea_gnssid = 4;
2197
0
        session->nmea.seen_bdgsv = true;
2198
0
        break;
2199
0
    case 'I':        // GI IRNSS
2200
0
        nmea_gnssid = 6;
2201
0
        session->nmea.seen_gigsv = true;
2202
0
        break;
2203
0
    case 'L':        // GL GLONASS
2204
0
        nmea_gnssid = 2;
2205
0
        session->nmea.seen_glgsv = true;
2206
0
        break;
2207
0
    case 'N':        // GN GNSS
2208
0
        session->nmea.seen_gngsv = true;
2209
0
        break;
2210
0
    case 'P':        // GP GPS
2211
0
        session->nmea.seen_gpgsv = true;
2212
0
        break;
2213
0
    case 'Q':        // $GQ, and $PQ (Quectel Querk) QZSS
2214
0
        if ('P' == field[0][0] &&
2215
0
            0 != nmea_gnssid) {
2216
            /* Quectel EC25 & EC21 use PQGSV for BeiDou or QZSS
2217
             * 4 = BeiDou, 5 = QZSS
2218
             * nmea_gnssid set above, what about seen?
2219
             */
2220
0
            if (4 == nmea_gnssid) {
2221
0
                session->nmea.seen_bdgsv = true;
2222
0
            } else if (5 == nmea_gnssid) {
2223
0
                session->nmea.seen_qzgsv = true;
2224
0
            } else {
2225
0
                GPSD_LOG(LOG_ERROR, &session->context->errout,
2226
0
                         "NMEA0183: %s: invalid nmea_gnssid %d\n",
2227
0
                         field[0], nmea_gnssid);
2228
0
                return ONLINE_SET;
2229
0
            }
2230
0
            break;
2231
0
        }
2232
        // else $GQ
2233
0
        FALLTHROUGH
2234
0
    case 'Z':        // QZ QZSS
2235
0
        nmea_gnssid = 5;
2236
0
        session->nmea.seen_qzgsv = true;
2237
0
        break;
2238
0
    default:
2239
        // uh, what?
2240
0
        break;
2241
0
    }
2242
2243
0
    for (fldnum = 4; fldnum < count / 4 * 4;) {
2244
0
        struct satellite_t *sp;
2245
0
        int nmea_svid;
2246
2247
0
        if (MAXCHANNELS <= session->gpsdata.satellites_visible) {
2248
0
            GPSD_LOG(LOG_ERROR, &session->context->errout,
2249
0
                     "NMEA0183: %s: internal error - too many "
2250
0
                     "satellites [%d]!\n",
2251
0
                     field[0], session->gpsdata.satellites_visible);
2252
0
            gpsd_zero_satellites(&session->gpsdata);
2253
0
            break;
2254
0
        }
2255
0
        sp = &session->gpsdata.skyview[session->gpsdata.satellites_visible];
2256
0
        nmea_svid = atoi(field[fldnum++]);
2257
0
        if (0 == nmea_svid) {
2258
            // skip bogus fields
2259
0
            continue;
2260
0
        }
2261
0
        sp->PRN = (short)nmeaid_to_prn(field[0], nmea_svid, nmea_gnssid,
2262
0
                                       &sp->gnssid, &sp->svid);
2263
2264
0
        sp->elevation = (double)atoi(field[fldnum++]);
2265
0
        sp->azimuth = (double)atoi(field[fldnum++]);
2266
0
        sp->ss = (double)atoi(field[fldnum++]);
2267
0
        sp->used = false;
2268
0
        sp->sigid = ubx_sigid;
2269
2270
        /* sadly NMEA 4.1 does not tell us which sigid (L1, L2) is
2271
         * used.  So if the ss is zero, do not mark used */
2272
0
        if (0 < sp->PRN &&
2273
0
            0 < sp->ss) {
2274
0
            for (n = 0; n < MAXCHANNELS; n++) {
2275
0
                if (session->nmea.sats_used[n] == (unsigned short)sp->PRN) {
2276
0
                    sp->used = true;
2277
0
                    break;
2278
0
                }
2279
0
            }
2280
0
        }
2281
#if 0   // debug
2282
        GPSD_LOG(LOG_SHOUT, &session->context->errout,
2283
                 "NMEA0183: %s nmea_gnssid %d nmea_satnum %d ubx_gnssid %d "
2284
                 "ubx_svid %d nmea2_prn %d az %.1f el %.1f used %d\n",
2285
                 field[0], nmea_gnssid, nmea_svid, sp->gnssid, sp->svid,
2286
                 sp->PRN, sp->elevation, sp->azimuth, sp->used);
2287
#endif  // debug
2288
2289
        /*
2290
         * Incrementing this unconditionally falls afoul of chipsets like
2291
         * the Motorola Oncore GT+ that emit empty fields at the end of the
2292
         * last sentence in a GPGSV set if the number of satellites is not
2293
         * a multiple of 4.
2294
         */
2295
0
        session->gpsdata.satellites_visible++;
2296
0
    }
2297
2298
#if 0    // debug code
2299
    GPSD_LOG(LOG_SHOUT, &session->context->errout,
2300
        "NMEA0183: %s: vis %d bdgsv %d gagsv %d gigsv %d glgsv %d "
2301
        "gngsv %d qpgsv %d qzgsv %d\n",
2302
        field[0],
2303
        session->gpsdata.satellites_visible,
2304
        session->nmea.seen_bdgsv,
2305
        session->nmea.seen_gagsv,
2306
        session->nmea.seen_gigsv,
2307
        session->nmea.seen_glgsv,
2308
        session->nmea.seen_gngsv,
2309
        session->nmea.seen_gpgsv,
2310
        session->nmea.seen_qzgsv);
2311
#endif  // debug
2312
2313
    /*
2314
     * Alas, we can't sanity check field counts when there are multiple sat
2315
     * pictures, because the visible member counts *all* satellites - you
2316
     * get a bad result on the second and later SV spans.  Note, this code
2317
     * assumes that if any of the special sat pics occur they come right
2318
     * after a stock GPGSV one.
2319
     *
2320
     * FIXME: Add per-talker totals so we can do this check properly.
2321
     */
2322
0
    if (!(session->nmea.seen_bdgsv ||
2323
0
          session->nmea.seen_gagsv ||
2324
0
          session->nmea.seen_gigsv ||
2325
0
          session->nmea.seen_glgsv ||
2326
0
          session->nmea.seen_gngsv ||
2327
0
          session->nmea.seen_qzgsv)) {
2328
0
        if (session->nmea.part == session->nmea.await
2329
0
                && atoi(field[3]) != session->gpsdata.satellites_visible) {
2330
0
            GPSD_LOG(LOG_WARN, &session->context->errout,
2331
0
                     "NMEA0183: %s field 3 value of %d != actual count %d\n",
2332
0
                     field[0], atoi(field[3]),
2333
0
                     session->gpsdata.satellites_visible);
2334
0
        }
2335
0
    }
2336
2337
    // not valid data until we've seen a complete set of parts
2338
0
    if (session->nmea.part < session->nmea.await) {
2339
0
        GPSD_LOG(LOG_PROG, &session->context->errout,
2340
0
                 "NMEA0183: %s: Partial satellite data (%d of %d).\n",
2341
0
                 field[0], session->nmea.part, session->nmea.await);
2342
0
        session->nmea.gsx_more = true;
2343
0
        return ONLINE_SET;
2344
0
    }
2345
0
    session->nmea.gsx_more = false;
2346
    /*
2347
     * This sanity check catches an odd behavior of SiRFstarII receivers.
2348
     * When they can't see any satellites at all (like, inside a
2349
     * building) they sometimes cough up a hairball in the form of a
2350
     * GSV packet with all the azimuth entries 0 (but nonzero
2351
     * elevations).  This behavior was observed under SiRF firmware
2352
     * revision 231.000.000_A2.
2353
     */
2354
0
     for (n = 0; n < session->gpsdata.satellites_visible; n++) {
2355
0
        if (0 != session->gpsdata.skyview[n].azimuth) {
2356
            // odd?
2357
0
            goto sane;
2358
0
        }
2359
0
     }
2360
0
     GPSD_LOG(LOG_WARN, &session->context->errout,
2361
0
              "NMEA0183: %s: Satellite data no good (%d of %d).\n",
2362
0
              field[0], session->nmea.part, session->nmea.await);
2363
0
     gpsd_zero_satellites(&session->gpsdata);
2364
0
     return ONLINE_SET;
2365
0
   sane:
2366
0
    session->gpsdata.skyview_time.tv_sec = 0;
2367
0
    session->gpsdata.skyview_time.tv_nsec = 0;
2368
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
2369
0
             "NMEA0183: %s: Satellite data OK (%d of %d).\n",
2370
0
             field[0], session->nmea.part, session->nmea.await);
2371
2372
    /* assumes GLGSV or BDGSV group, if present, is emitted after the GPGSV
2373
     * An assumption that Quectel breaks;  The EG25 can send in one epoch:
2374
     * $GLGSV, $PQGSV, $GAGSV, then $GPGSV! */
2375
0
    if ((session->nmea.seen_bdgsv ||
2376
0
         session->nmea.seen_gagsv ||
2377
0
         session->nmea.seen_gigsv ||
2378
0
         session->nmea.seen_glgsv ||
2379
0
         session->nmea.seen_gngsv ||
2380
0
         session->nmea.seen_qzgsv) &&
2381
0
        ('P' == GSV_TALKER &&
2382
0
         'P' != session->nmea.end_gsv_talker)) {
2383
0
        GPSD_LOG(LOG_PROG, &session->context->errout,
2384
0
                 "NMEA0183: %s: not end talker %d\n",
2385
0
                 field[0], session->nmea.end_gsv_talker);
2386
0
        return ONLINE_SET;
2387
0
    }
2388
2389
#if 0   // debug code
2390
    {
2391
        char ts_buf[TIMESPEC_LEN];
2392
        char ts_buf1[TIMESPEC_LEN];
2393
        GPSD_LOG(LOG_SHOUT, &session->context->errout,
2394
            "NMEA0183: %s: set skyview_time %s frac_time %s\n",
2395
            field[0],
2396
            timespec_str(&session->gpsdata.skyview_time, ts_buf,
2397
                         sizeof(ts_buf)),
2398
            timespec_str(&session->nmea.this_frac_time, ts_buf1,
2399
                         sizeof(ts_buf1)));
2400
    }
2401
#endif  // debug
2402
2403
0
    return SATELLITE_SET;
2404
0
#undef GSV_TALKER
2405
0
}
2406
2407
static gps_mask_t processHDG(int c UNUSED, char *field[],
2408
                             struct gps_device_t *session)
2409
0
{
2410
    /*
2411
     *  $SDHDG,234.6,,,1.3,E*34
2412
     *
2413
     *  $--HDG,h.h,d.d,a,v.v,a*hh<CR><LF>
2414
     *  Magnetic sensor heading, degrees
2415
     *  Magnetic deviation, degrees E/W
2416
     *  Magnetic variation, degrees, E/W
2417
     *
2418
     *  1. To obtain Magnetic Heading:
2419
     *  Add Easterly deviation (E) to Magnetic Sensor Reading
2420
     *  Subtract Westerly deviation (W) from Magnetic Sensor Reading
2421
     *  2. To obtain True Heading:
2422
     *  Add Easterly variation (E) to Magnetic Heading
2423
     *  Subtract Westerly variation (W) from Magnetic Heading
2424
     *  3. Variation and deviation fields shall be null fields if unknown.
2425
     */
2426
2427
0
    gps_mask_t mask = ONLINE_SET;
2428
0
    double sensor_heading;
2429
0
    double magnetic_deviation;
2430
2431
0
    if ('\0' == field[1][0]) {
2432
        // no data
2433
0
        return mask;
2434
0
    }
2435
0
    sensor_heading = safe_atof(field[1]);
2436
0
    if ((0.0 > sensor_heading) ||
2437
0
        (360.0 < sensor_heading)) {
2438
        // bad data */
2439
0
        return mask;
2440
0
    }
2441
0
    magnetic_deviation = safe_atof(field[2]);
2442
0
    if ((0.0 > magnetic_deviation) ||
2443
0
        (360.0 < magnetic_deviation)) {
2444
        // bad data
2445
0
        return mask;
2446
0
    }
2447
0
    switch (field[2][0]) {
2448
0
    case 'E':
2449
0
        sensor_heading += magnetic_deviation;
2450
0
        break;
2451
0
    case 'W':
2452
0
        sensor_heading += magnetic_deviation;
2453
0
        break;
2454
0
    default:
2455
        // ignore
2456
0
        break;
2457
0
    }
2458
2459
    // good data
2460
0
    session->newdata.magnetic_track = sensor_heading;
2461
0
    mask |= MAGNETIC_TRACK_SET;
2462
2463
    // get magnetic variation
2464
0
    if ('\0' != field[3][0] &&
2465
0
        '\0' != field[4][0]) {
2466
0
        session->newdata.magnetic_var = safe_atof(field[3]);
2467
2468
0
        switch (field[4][0]) {
2469
0
        case 'E':
2470
            // no change
2471
0
            mask |= MAGNETIC_TRACK_SET;
2472
0
            break;
2473
0
        case 'W':
2474
0
            session->newdata.magnetic_var = -session->newdata.magnetic_var;
2475
0
            mask |= MAGNETIC_TRACK_SET;
2476
0
            break;
2477
0
        default:
2478
            // huh?
2479
0
            session->newdata.magnetic_var = NAN;
2480
0
            break;
2481
0
        }
2482
0
    }
2483
2484
2485
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
2486
0
             "NMEA0183: $SDHDG heading %lf var %.1f\n",
2487
0
             session->newdata.magnetic_track,
2488
0
             session->newdata.magnetic_var);
2489
0
    return mask;
2490
0
}
2491
2492
/* precessHDM() - process magnetic headingxxHDM messages
2493
 *
2494
 * Deprecated by NMEA in 2008
2495
 */
2496
static gps_mask_t processHDM(int c UNUSED, char *field[],
2497
                             struct gps_device_t *session)
2498
0
{
2499
    /*
2500
     * $APHDM,218.634,M*39
2501
     *
2502
     * 1) Magnetic heading
2503
     * 2) M == Magnetic
2504
     * )  checksum
2505
     *
2506
     */
2507
0
    gps_mask_t mask = ONLINE_SET;
2508
2509
0
    if ('\0' == field[1][0]) {
2510
        // no data
2511
0
        return mask;
2512
0
    }
2513
2514
    // assume good data
2515
0
    session->gpsdata.attitude.mheading = safe_atof(field[1]);
2516
0
    mask |= ATTITUDE_SET;
2517
2518
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
2519
0
             "NMEA0183: $xxHDM: Magnetic heading %f\n",
2520
0
             session->gpsdata.attitude.mheading);
2521
0
    return mask;
2522
0
}
2523
2524
static gps_mask_t processHDT(int c UNUSED, char *field[],
2525
                             struct gps_device_t *session)
2526
0
{
2527
    /*
2528
     * $HEHDT,341.8,T*21
2529
     *
2530
     * $xxHDT,x.x*hh<cr><lf>
2531
     *
2532
     * The only data field is true heading in degrees.
2533
     * The following field is required to be 'T' indicating a true heading.
2534
     * It is followed by a mandatory nmea_checksum.
2535
     */
2536
0
    gps_mask_t mask = ONLINE_SET;
2537
0
    double heading;
2538
2539
0
    if ('\0' == field[1][0]) {
2540
        // no data
2541
0
        return mask;
2542
0
    }
2543
0
    heading = safe_atof(field[1]);
2544
0
    if (0.0 > heading ||
2545
0
        360.0 < heading) {
2546
        // bad data
2547
0
        return mask;
2548
0
    }
2549
    // True heading
2550
0
    session->gpsdata.attitude.heading = heading;
2551
2552
0
    mask |= ATTITUDE_SET;
2553
2554
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
2555
0
             "NMEA0183: $xxHDT heading %lf.\n",
2556
0
             session->gpsdata.attitude.heading);
2557
0
    return mask;
2558
0
}
2559
2560
static gps_mask_t processMTW(int c UNUSED, char *field[],
2561
                              struct gps_device_t *session)
2562
0
{
2563
    /* Water temp in degrees C
2564
     * $--MTW,x.x,C*hh<CR><LF>
2565
     *
2566
     * Fields in order:
2567
     * 1. water temp degrees C
2568
     * 2. C
2569
     * *hh          mandatory nmea_checksum
2570
     */
2571
0
    gps_mask_t mask = ONLINE_SET;
2572
2573
0
    if ('\0' == field[1][0] ||
2574
0
        'C' != field[2][0]) {
2575
        // no temp
2576
0
        return mask;
2577
0
    }
2578
0
    session->newdata.wtemp = safe_atof(field[1]);
2579
2580
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
2581
0
        "NMEA0183: %s temp %.1f C\n",
2582
0
        field[0], session->newdata.wtemp);
2583
0
    return mask;
2584
0
}
2585
2586
static gps_mask_t processMWD(int c UNUSED, char *field[],
2587
                              struct gps_device_t *session)
2588
0
{
2589
    /*
2590
     * xxMWD - Wind direction and speed
2591
     * $xxMWD,x.x,T,x.x,M,x.x,N,x.x,M*hh<cr><lf>
2592
     * Fields in order:
2593
     * 1. wind direction, 0 to 359, True
2594
     * 2. T
2595
     * 3. wind direction, 0 to 359, Magnetic
2596
     * 4. M
2597
     * 5. wind speed, knots
2598
     * 6. N
2599
     * 7. wind speed, meters/sec
2600
     * 8. M
2601
     * *hh          mandatory nmea_checksum
2602
     */
2603
0
    gps_mask_t mask = ONLINE_SET;
2604
2605
0
    session->newdata.wanglet = safe_atof(field[1]);
2606
0
    session->newdata.wanglem = safe_atof(field[3]);
2607
0
    session->newdata.wspeedt = safe_atof(field[7]);
2608
0
    mask |= NAVDATA_SET;
2609
2610
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
2611
0
        "NMEA0183: xxMWD wanglet %.2f wanglem %.2f wspeedt %.2f\n",
2612
0
        session->newdata.wanglet,
2613
0
        session->newdata.wanglem,
2614
0
        session->newdata.wspeedt);
2615
0
    return mask;
2616
0
}
2617
2618
static gps_mask_t processMWV(int c UNUSED, char *field[],
2619
                              struct gps_device_t *session)
2620
0
{
2621
    /*
2622
     * xxMWV - Wind speed and angle
2623
     * $xxMWV,x.x,a,x.x,a,A*hh<cr><lf>
2624
     * Fields in order:
2625
     * 1. wind angle, 0 to 359, True
2626
     * 2. R = Relative (apparent), T = Theoretical (calculated)
2627
     *    Is T magnetic or true??
2628
     * 3. wind speed
2629
     * 4. wind speed units K/M/N/S
2630
     * 6. A = Valid, V = invalid
2631
     * *hh          mandatory nmea_checksum
2632
     */
2633
0
    gps_mask_t mask = ONLINE_SET;
2634
2635
0
    if (('R' == field[2][0]) &&
2636
0
        ('N' == field[4][0]) &&
2637
0
        ('A' == field[5][0])) {
2638
        // relative, knots, and valid
2639
0
        session->newdata.wangler = safe_atof(field[1]);
2640
0
        session->newdata.wspeedr = safe_atof(field[3]) * KNOTS_TO_MPS;
2641
0
        mask |= NAVDATA_SET;
2642
0
    }
2643
2644
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
2645
0
        "NMEA0183: xxMWV wangler %.2f wspeedr %.2f\n",
2646
0
        session->newdata.wangler,
2647
0
        session->newdata.wspeedr);
2648
0
    return mask;
2649
0
}
2650
2651
// PAIRxxx is Airoha, spunoff from Mediatek
2652
2653
// PAIR001 -- ACK/NAK
2654
static gps_mask_t processPAIR001(int c UNUSED, char *field[],
2655
                                 struct gps_device_t *session)
2656
0
{
2657
0
    int reason;
2658
0
    const char *reasons[] = {
2659
0
        "Success",
2660
0
        "In process, wait",
2661
0
        "Failed",
2662
0
        "Not supported",
2663
0
        "Busy, try again.",
2664
0
        "Unknown",             // gpsd only
2665
0
    };
2666
2667
    // ACK / NACK
2668
0
    reason = atoi(field[2]);
2669
0
    if (4 == reason) {
2670
        // ACK
2671
0
        GPSD_LOG(LOG_PROG, &session->context->errout,
2672
0
                 "NMEA0183: PAIR001, ACK: %s\n", field[1]);
2673
0
        return ONLINE_SET;
2674
0
    }
2675
2676
    // else, NACK
2677
0
    if (0 > reason ||
2678
0
        4 < reason) {
2679
        // WTF?
2680
0
        reason = 5;
2681
0
    }
2682
0
    GPSD_LOG(LOG_WARN, &session->context->errout,
2683
0
             "NMEA0183: PAIR NACK: %s, reason: %s\n",
2684
0
             field[1], reasons[reason]);
2685
2686
0
    return ONLINE_SET;
2687
0
}
2688
2689
// PAIR010 -- Request Aiding
2690
static gps_mask_t processPAIR010(int c UNUSED, char *field[],
2691
                                 struct gps_device_t *session)
2692
0
{
2693
0
    int type;
2694
0
    const char *types[] = {
2695
0
        "EPO data",
2696
0
        "Time",
2697
0
        "Location",
2698
0
        "Unknown",             // gpsd only
2699
0
    };
2700
2701
0
    int system;
2702
0
    const char *systems[] = {
2703
0
        "GPS",
2704
0
        "GLONASS",
2705
0
        "Galileo",
2706
0
        "BDS",
2707
0
        "QZSS",
2708
0
        "Unknown",             // gpsd only
2709
0
    };
2710
0
    int wn;         // week number
2711
0
    int tow;        // time of week
2712
2713
0
    type = atoi(field[1]);
2714
0
    if (0 > type ||
2715
0
        2 < type) {
2716
        // WTF?
2717
0
        type = 3;
2718
0
    }
2719
0
    system = atoi(field[2]);
2720
0
    if (0 > system ||
2721
0
        4 < system) {
2722
        // WTF?
2723
0
        system = 5;
2724
0
    }
2725
0
    wn = atoi(field[3]);
2726
0
    tow = atoi(field[4]);
2727
0
    GPSD_LOG(LOG_WARN, &session->context->errout,
2728
0
             "NMEA0183: PAIR010: Need %s for %s.  WN %d TOW %d\n",
2729
0
             types[type], systems[system], wn, tow);
2730
2731
0
    return ONLINE_SET;
2732
0
}
2733
2734
/* Ashtech sentences take this format:
2735
 * $PASHDR,type[,val[,val]]*CS
2736
 * type is an alphabetic subsentence type
2737
 *
2738
 * Oxford Technical Solutions (OxTS) also uses the $PASHR sentence,
2739
 * but with a very different sentence contents:
2740
 * $PASHR,HHMMSS.SSS,HHH.HH,T,RRR.RR,PPP.PP,aaa.aa,r.rrr,p.ppp,h.hhh,Q1,Q2*CS
2741
 *
2742
 * so field 1 in ASHTECH is always alphabetic and numeric in OXTS
2743
 *
2744
 */
2745
static gps_mask_t processPASHR(int c UNUSED, char *field[],
2746
                               struct gps_device_t *session)
2747
0
{
2748
0
    gps_mask_t mask = ONLINE_SET;
2749
0
    char ts_buf[TIMESPEC_LEN];
2750
2751
0
    if (0 == strcmp("ACK", field[1])) {
2752
        // ACK
2753
0
        GPSD_LOG(LOG_DATA, &session->context->errout, "NMEA0183: PASHR,ACK\n");
2754
0
        return ONLINE_SET;
2755
0
    } else if (0 == strcmp("MCA", field[1])) {
2756
        // MCA, raw data
2757
0
        GPSD_LOG(LOG_DATA, &session->context->errout, "NMEA0183: PASHR,MCA\n");
2758
0
        return ONLINE_SET;
2759
0
    } else if (0 == strcmp("NAK", field[1])) {
2760
        // NAK
2761
0
        GPSD_LOG(LOG_DATA, &session->context->errout, "NMEA0183: PASHR,NAK\n");
2762
0
        return ONLINE_SET;
2763
0
    } else if (0 == strcmp("PBN", field[1])) {
2764
        // PBN, position data
2765
        // FIXME: decode this for ECEF
2766
0
        GPSD_LOG(LOG_DATA, &session->context->errout, "NMEA0183: PASHR,PBN\n");
2767
0
        return ONLINE_SET;
2768
0
    } else if (0 == strcmp("POS", field[1])) {  // 3D Position
2769
        /* $PASHR,POS,
2770
         *
2771
         * 2: position type:
2772
         *      0 = autonomous
2773
         *      1 = position differentially corrected with RTCM code
2774
         *      2 = position differentially corrected with CPD float solution
2775
         *      3 = position is CPD fixed solution
2776
         */
2777
0
        mask |= MODE_SET | STATUS_SET | CLEAR_IS;
2778
0
        if ('\0' == field[2][0]) {
2779
            // empty first field means no 3D fix is available
2780
0
            session->newdata.status = STATUS_UNK;
2781
0
            session->newdata.mode = MODE_NO_FIX;
2782
0
        } else {
2783
2784
            // if we make it this far, we at least have a 3D fix
2785
0
            session->newdata.mode = MODE_3D;
2786
0
            if (1 <= atoi(field[2]))
2787
0
                session->newdata.status = STATUS_DGPS;
2788
0
            else
2789
0
                session->newdata.status = STATUS_GPS;
2790
2791
0
            session->nmea.gga_sats_used = atoi(field[3]);
2792
0
            if (0 == merge_hhmmss(field[4], session)) {
2793
0
                register_fractional_time(field[0], field[4], session);
2794
0
                mask |= TIME_SET;
2795
0
            }
2796
0
            if (0 == do_lat_lon(&field[5], &session->newdata)) {
2797
0
                mask |= LATLON_SET;
2798
0
                if ('\0' != field[9][0]) {
2799
                    // altitude is already WGS 84
2800
0
                    session->newdata.altHAE = safe_atof(field[9]);
2801
0
                    mask |= ALTITUDE_SET;
2802
0
                }
2803
0
            }
2804
0
            session->newdata.track = safe_atof(field[11]);
2805
0
            session->newdata.speed = safe_atof(field[12]) / MPS_TO_KPH;
2806
0
            session->newdata.climb = safe_atof(field[13]);
2807
0
            if ('\0' != field[14][0]) {
2808
0
                session->gpsdata.dop.pdop = safe_atof(field[14]);
2809
0
                mask |= DOP_SET;
2810
0
            }
2811
0
            if ('\0' != field[15][0]) {
2812
0
                session->gpsdata.dop.hdop = safe_atof(field[15]);
2813
0
                mask |= DOP_SET;
2814
0
            }
2815
0
            if ('\0' != field[16][0]) {
2816
0
                session->gpsdata.dop.vdop = safe_atof(field[16]);
2817
0
                mask |= DOP_SET;
2818
0
            }
2819
0
            if ('\0' != field[17][0]) {
2820
0
                session->gpsdata.dop.tdop = safe_atof(field[17]);
2821
0
                mask |= DOP_SET;
2822
0
            }
2823
0
            mask |= (SPEED_SET | TRACK_SET | CLIMB_SET);
2824
0
            GPSD_LOG(LOG_DATA, &session->context->errout,
2825
0
                     "NMEA0183: PASHR,POS: hhmmss=%s lat=%.2f lon=%.2f"
2826
0
                     " altHAE=%.f"
2827
0
                     " speed=%.2f track=%.2f climb=%.2f mode=%d status=%d"
2828
0
                     " pdop=%.2f hdop=%.2f vdop=%.2f tdop=%.2f used=%d\n",
2829
0
                     field[4], session->newdata.latitude,
2830
0
                     session->newdata.longitude, session->newdata.altHAE,
2831
0
                     session->newdata.speed, session->newdata.track,
2832
0
                     session->newdata.climb, session->newdata.mode,
2833
0
                     session->newdata.status, session->gpsdata.dop.pdop,
2834
0
                     session->gpsdata.dop.hdop, session->gpsdata.dop.vdop,
2835
0
                     session->gpsdata.dop.tdop, session->nmea.gga_sats_used);
2836
0
        }
2837
0
    } else if (0 == strcmp("RID", field[1])) {  // Receiver ID
2838
0
        (void)snprintf(session->subtype, sizeof(session->subtype) - 1,
2839
0
                       "%s ver %s", field[2], field[3]);
2840
0
        GPSD_LOG(LOG_DATA, &session->context->errout,
2841
0
                 "NMEA0183: PASHR,RID: subtype=%s mask={}\n",
2842
0
                 session->subtype);
2843
0
        return mask;
2844
0
    } else if (0 == strcmp("SAT", field[1])) {  // Satellite Status
2845
0
        struct satellite_t *sp;
2846
0
        int i, n = session->gpsdata.satellites_visible = atoi(field[2]);
2847
2848
0
        session->gpsdata.satellites_used = 0;
2849
0
        for (i = 0, sp = session->gpsdata.skyview;
2850
0
            sp < session->gpsdata.skyview + n; sp++, i++) {
2851
2852
0
            sp->PRN = (short)atoi(field[3 + i * 5 + 0]);
2853
0
            sp->azimuth = (double)atoi(field[3 + i * 5 + 1]);
2854
0
            sp->elevation = (double)atoi(field[3 + i * 5 + 2]);
2855
0
            sp->ss = safe_atof(field[3 + i * 5 + 3]);
2856
0
            sp->used = false;
2857
0
            if ('U' == field[3 + i * 5 + 4][0]) {
2858
0
                sp->used = true;
2859
0
                session->gpsdata.satellites_used++;
2860
0
            }
2861
0
        }
2862
0
        GPSD_LOG(LOG_DATA, &session->context->errout,
2863
0
                 "NMEA0183: PASHR,SAT: used=%d\n",
2864
0
                 session->gpsdata.satellites_used);
2865
0
        session->gpsdata.skyview_time.tv_sec = 0;
2866
0
        session->gpsdata.skyview_time.tv_nsec = 0;
2867
0
        mask |= SATELLITE_SET | USED_IS;
2868
2869
0
    } else if (0 == strcmp("T", field[3])) {   // Assume OxTS PASHR
2870
        // FIXME: decode OxTS $PASHDR, time is wrong, breaks cycle order
2871
0
        if (0 == merge_hhmmss(field[1], session)) {
2872
            // register_fractional_time(field[0], field[1], session);
2873
            // mask |= TIME_SET; confuses cycle order
2874
0
        }
2875
        // Assume true heading
2876
0
        session->gpsdata.attitude.heading = safe_atof(field[2]);
2877
0
        session->gpsdata.attitude.roll = safe_atof(field[4]);
2878
0
        session->gpsdata.attitude.pitch = safe_atof(field[5]);
2879
        // mask |= ATTITUDE_SET;  * confuses cycle order ??
2880
0
        GPSD_LOG(LOG_DATA, &session->context->errout,
2881
0
                 "NMEA0183: PASHR (OxTS) time %s, heading %lf.\n",
2882
0
                  timespec_str(&session->newdata.time, ts_buf, sizeof(ts_buf)),
2883
0
                  session->gpsdata.attitude.heading);
2884
0
    }
2885
0
    return mask;
2886
0
}
2887
2888
/* Android GNSS super message
2889
 * A stub.
2890
 */
2891
static gps_mask_t processPGLOR(int c UNUSED, char *field[],
2892
                               struct gps_device_t *session)
2893
0
{
2894
    /*
2895
     * $PGLOR,0,FIX,....
2896
     * 1    = sentence version (may not be present)
2897
     * 2    = message subtype
2898
     * ....
2899
     *
2900
     * subtypes:
2901
     *  $PGLOR,[],AGC - ??
2902
     *  $PGLOR,[],CPU - CPU Loading
2903
     *  $PGLOR,[],FIN - Request completion status
2904
     *  $PGLOR,0,FIX,seconds - Time To Fix
2905
     *  $PGLOR,[],FTS - Factory Test Status
2906
     *  $PGLOR,[],GFC - GeoFence Fix
2907
     *  $PGLOR,[],GLO - ??
2908
     *  $PGLOR,[],HLA - Value of HULA sensors
2909
     *  $PGLOR,[],IMS - IMES messages
2910
     *  $PGLOR,1,LSQ,hhmmss.ss  - Least squares GNSS fix
2911
     *  $PGLOR,NET    - Report network information
2912
     *  $PGLOR,[],NEW - Indicate new GPS request
2913
     *  $PGLOR,[],PFM - Platform Status
2914
     *  $PGLOR,[],PPS - Indicate PPS time corrections
2915
     *  $PGLOR,5,PWR i - Power consumption report
2916
     *                  Only have doc for 5, Quectel uses 4
2917
     *  $PGLOR,[],RID - Version Information
2918
     *  $PGLOR,2,SAT - GPS Satellite information
2919
     *  $PGLOR,[],SIO - Serial I/O status report
2920
     *  $PGLOR,[],SPA - Spectrum analyzer results
2921
     *  $PGLOR,0,SPD  - Speed, Steps, etc.
2922
     *  $PGLOR,SPL    - ??
2923
     *  $PGLOR,[],SPS - ??
2924
     *  $PGLOR,10,STA - GLL status
2925
     *  $PGLOR,[],SVC - ??
2926
     *  $PGLOR,[],SVD - SV Dopplers detected in the false alarm test.
2927
     *  $PGLOR,[],SMx - Report GPS Summary Information
2928
     *  $PGLOR,[],UNC - ??
2929
     *
2930
     * Are NET and SPL really so different?
2931
     *
2932
     */
2933
0
    gps_mask_t mask = ONLINE_SET;
2934
0
    int got_one = 0;
2935
2936
0
    switch (field[1][0]) {
2937
0
    case '0':
2938
0
        if (0 == strncmp("FIX", field[2], 3)) {
2939
0
            got_one = 1;
2940
            // field 3, time to first fix in seconds
2941
0
            GPSD_LOG(LOG_DATA, &session->context->errout,
2942
0
                     "NMEA0183: PGLOR: FIX, TTFF %s\n",
2943
0
                     field[3]);
2944
0
        } else if (0 == strncmp("SPD", field[2], 3)) {
2945
0
            got_one = 1;
2946
            // field 4, ddmmy.ss UTC
2947
            // field 5, hhmmss.ss UTC
2948
0
            GPSD_LOG(LOG_DATA, &session->context->errout,
2949
0
                     "NMEA0183: PGLOR: SPD, %s %s UTC\n",
2950
0
                     field[4], field[5]);
2951
0
        }
2952
0
        break;
2953
0
    case '1':
2954
0
        if (0 == strncmp("LSQ", field[2], 3)) {
2955
0
            got_one = 1;
2956
            // field 3, hhmmss.ss UTC, only field Quectel supplies
2957
0
            GPSD_LOG(LOG_DATA, &session->context->errout,
2958
0
                     "NMEA0183: PGLOR: LSQ %s UTC\n",
2959
0
                     field[3]);
2960
0
        } else if ('0' == field[1][1] &&
2961
0
                   0 == strncmp("STA", field[2], 3)) {
2962
            // version 10
2963
0
            got_one = 1;
2964
            // field 3, hhmmss.ss UTC
2965
            // field 7, Position uncertainty meters
2966
0
            GPSD_LOG(LOG_DATA, &session->context->errout,
2967
0
                     "NMEA0183: PGLOR: STA, UTC %s PosUncer  %s\n",
2968
0
                     field[3], field[7]);
2969
0
        }
2970
0
        break;
2971
0
    }
2972
0
    if (0 != got_one) {
2973
0
        GPSD_LOG(LOG_DATA, &session->context->errout,
2974
0
                 "NMEA0183: PGLOR: seq %s type %s\n",
2975
0
                 field[1], field[2]);
2976
0
    }
2977
0
    return mask;
2978
0
}
2979
2980
// Garmin Estimated Position Error
2981
static gps_mask_t processPGRME(int c UNUSED, char *field[],
2982
                               struct gps_device_t *session)
2983
0
{
2984
    /*
2985
     * $PGRME,15.0,M,45.0,M,25.0,M*22
2986
     * 1    = horizontal error estimate
2987
     * 2    = units
2988
     * 3    = vertical error estimate
2989
     * 4    = units
2990
     * 5    = spherical error estimate
2991
     * 6    = units
2992
     * *
2993
     * * Garmin won't say, but the general belief is that these are 50% CEP.
2994
     * * We follow the advice at <http://gpsinformation.net/main/errors.htm>.
2995
     * * If this assumption changes here, it should also change in garmin.c
2996
     * * where we scale error estimates from Garmin binary packets, and
2997
     * * in libgpsd_core.c where we generate $PGRME.
2998
     */
2999
0
    gps_mask_t mask = ONLINE_SET;
3000
3001
0
    if ('M' == field[2][0] &&
3002
0
        'M' == field[4][0] &&
3003
0
        'M' == field[6][0]) {
3004
0
        session->newdata.epx = session->newdata.epy =
3005
0
            safe_atof(field[1]) * (1 / sqrt(2))
3006
0
                      * (GPSD_CONFIDENCE / CEP50_SIGMA);
3007
0
        session->newdata.epv =
3008
0
            safe_atof(field[3]) * (GPSD_CONFIDENCE / CEP50_SIGMA);
3009
0
        session->newdata.sep =
3010
0
            safe_atof(field[5]) * (GPSD_CONFIDENCE / CEP50_SIGMA);
3011
0
        mask = HERR_SET | VERR_SET | PERR_IS;
3012
0
    }
3013
3014
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3015
0
             "NMEA0183: PGRME: epx=%.2f epy=%.2f sep=%.2f\n",
3016
0
             session->newdata.epx,
3017
0
             session->newdata.epy,
3018
0
             session->newdata.sep);
3019
0
    return mask;
3020
0
}
3021
3022
/* Garmin GPS Fix Data Sentence
3023
 *
3024
 * FIXME: seems to happen after cycle ender, so little happens...
3025
 */
3026
static gps_mask_t processPGRMF(int c UNUSED, char *field[],
3027
                               struct gps_device_t *session)
3028
0
{
3029
 /*
3030
  * $PGRMF,290,293895,160305,093802,13,5213.1439,N,02100.6511,E,A,2,0,226,2,1*11
3031
  *
3032
  * 1 = GPS week
3033
  * 2 = GPS seconds
3034
  * 3 = UTC Date ddmmyy
3035
  * 4 = UTC time hhmmss
3036
  * 5 = GPS leap seconds
3037
  * 6 = Latitude ddmm.mmmm
3038
  * 7 = N or S
3039
  * 8 = Longitude dddmm.mmmm
3040
  * 9 = E or W
3041
  * 10 = Mode, M = Manual, A = Automatic
3042
  * 11 = Fix type, 0 = No fix, 2 = 2D fix, 2 = 3D fix
3043
  * 12 = Ground Speed, 0 to 1151 km/hr
3044
  * 13 = Course over ground, 0 to 359 degrees true
3045
  * 14 = pdop, 0 to 9
3046
  * 15 = dop, 0 to 9
3047
  */
3048
0
    gps_mask_t mask = ONLINE_SET;
3049
0
    timespec_t ts_tow = {0, 0};
3050
3051
    /* Some garmin fail due to GPS Week Roll Over
3052
     * Ignore their UTC date/time, use their GPS week, GPS tow and
3053
     * leap seconds to decide the correct time */
3054
0
    if (isdigit((int)field[5][0])) {
3055
0
        session->context->leap_seconds = atoi(field[5]);
3056
0
        session->context->valid = LEAP_SECOND_VALID;
3057
0
    }
3058
0
    if (isdigit((int)field[1][0]) &&
3059
0
        isdigit((int)field[2][0]) &&
3060
0
        0 < session->context->leap_seconds) {
3061
        // have GPS week, tow and leap second
3062
0
        unsigned short week = atol(field[1]);
3063
0
        ts_tow.tv_sec = atol(field[2]);
3064
0
        ts_tow.tv_nsec = 0;
3065
0
        session->newdata.time = gpsd_gpstime_resolv(session, week, ts_tow);
3066
0
        mask |= TIME_SET;
3067
        // (long long) cast for 32/64 bit compat
3068
0
        GPSD_LOG(LOG_SPIN, &session->context->errout,
3069
0
                 "NMEA0183: PGRMF gps time %lld\n",
3070
0
                 (long long)session->newdata.time.tv_sec);
3071
0
    } else if (0 == merge_hhmmss(field[4], session) &&
3072
0
               0 == merge_ddmmyy(field[3], session)) {
3073
        // fall back to UTC if we need and can
3074
        // (long long) cast for 32/64 bit compat
3075
0
        GPSD_LOG(LOG_SPIN, &session->context->errout,
3076
0
                 "NMEA0183: PGRMF gps time %lld\n",
3077
0
                 (long long)session->newdata.time.tv_sec);
3078
0
        mask |= TIME_SET;
3079
0
    }
3080
0
    if ('A' != field[10][0]) {
3081
        // Huh?
3082
0
        return mask;
3083
0
    }
3084
0
    if (0 == do_lat_lon(&field[6], &session->newdata)) {
3085
0
        mask |= LATLON_SET;
3086
0
    }
3087
0
    switch (field[11][0]) {
3088
0
    default:
3089
        // Huh?
3090
0
        break;
3091
0
    case '0':
3092
0
        session->newdata.mode = MODE_NO_FIX;
3093
0
        mask |= MODE_SET;
3094
0
        break;
3095
0
    case '1':
3096
0
        session->newdata.mode = MODE_2D;
3097
0
        mask |= MODE_SET;
3098
0
        break;
3099
0
    case '2':
3100
0
        session->newdata.mode = MODE_3D;
3101
0
        mask |= MODE_SET;
3102
0
        break;
3103
0
    }
3104
0
    session->newdata.speed = safe_atof(field[12]) / MPS_TO_KPH;
3105
0
    session->newdata.track = safe_atof(field[13]);
3106
0
    mask |= SPEED_SET | TRACK_SET;
3107
0
    if ('\0' != field[14][0]) {
3108
0
        session->gpsdata.dop.pdop = safe_atof(field[14]);
3109
0
        mask |= DOP_SET;
3110
0
    }
3111
0
    if ('\0' != field[15][0]) {
3112
0
        session->gpsdata.dop.tdop = safe_atof(field[15]);
3113
0
        mask |= DOP_SET;
3114
0
    }
3115
3116
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3117
0
             "NMEA0183: PGRMF: pdop %.1f tdop %.1f \n",
3118
0
             session->gpsdata.dop.pdop,
3119
0
             session->gpsdata.dop.tdop);
3120
0
    return mask;
3121
0
}
3122
3123
/* Garmin Map Datum
3124
 *
3125
 * FIXME: seems to happen after cycle ender, so nothing happens...
3126
 */
3127
static gps_mask_t processPGRMM(int c UNUSED, char *field[],
3128
                               struct gps_device_t *session)
3129
0
{
3130
    /*
3131
     * $PGRMM,NAD83*29
3132
     * 1    = Map Datum
3133
     */
3134
0
    gps_mask_t mask = ONLINE_SET;
3135
3136
0
    if ('\0' != field[1][0]) {
3137
0
        strlcpy(session->newdata.datum, field[1],
3138
0
                sizeof(session->newdata.datum));
3139
0
    }
3140
3141
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3142
0
             "NMEA0183: PGRMM: datum=%.40s\n",
3143
0
             session->newdata.datum);
3144
0
    return mask;
3145
0
}
3146
3147
// Garmin Sensor Status Info
3148
static gps_mask_t processPGRMT(int c UNUSED, char *field[],
3149
                               struct gps_device_t *session)
3150
0
{
3151
    /*
3152
     * $PGRMT,GPS 15x-W software ver. 4.20,,,,,,,,*6A
3153
     * 1    = Product, model and software version
3154
     * 2    = ROM Checksum test P=pass, F=fail
3155
     * 3    = Receiver failure discrete, P=pass, F=fail
3156
     * 4    = Stored data lost, R=retained, L=lost
3157
     * 5    = Real time clock lost, R=retained, L=lost
3158
     * 6    = Oscillator drift discrete, P=pass, F=excessive drift detected
3159
     * 7    = Data collection discrete, C=collecting, null if not collecting
3160
     * 8    = GPS sensor temperature in degrees C
3161
     * 9    = GPS sensor configuration data, R=retained, L=lost
3162
     *
3163
     * Output once per minuite by default.
3164
     * 50 char max.
3165
     *
3166
     * As of October 2022, only ever seen field 1 populated
3167
     */
3168
0
    gps_mask_t mask = ONLINE_SET;
3169
3170
0
    strlcpy(session->subtype, field[1], sizeof(session->subtype));
3171
3172
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3173
0
             "NMEA0183: PGRMT: subtype %s\n",
3174
0
             session->subtype);
3175
0
    return mask;
3176
0
}
3177
3178
// Garmin 3D Velocity Information
3179
static gps_mask_t processPGRMV(int c UNUSED, char *field[],
3180
                               struct gps_device_t *session)
3181
0
{
3182
    /*
3183
     * $PGRMV,-2.4,-1.1,0.3*59
3184
     * 1    = true east velocity,  m/s
3185
     * 2    = true north velocity,  m/s
3186
     * 3    = true up velocity,  m/s
3187
     */
3188
0
    gps_mask_t mask = ONLINE_SET;
3189
3190
0
    if ('\0' == field[1][0] ||
3191
0
        '\0' == field[2][0] ||
3192
0
        '\0' == field[3][0]) {
3193
        // nothing to report
3194
0
        return mask;
3195
0
    }
3196
3197
0
    session->newdata.NED.velE = safe_atof(field[1]);
3198
0
    session->newdata.NED.velN = safe_atof(field[2]);
3199
0
    session->newdata.NED.velD = -safe_atof(field[3]);
3200
3201
0
    mask |= VNED_SET;
3202
3203
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3204
0
             "NMEA0183: PGRMV: velE %.2f velN %.2f velD %.2f\n",
3205
0
            session->newdata.NED.velE,
3206
0
            session->newdata.NED.velN,
3207
0
            session->newdata.NED.velD);
3208
0
    return mask;
3209
0
}
3210
3211
// Garmin Altitude Information
3212
static gps_mask_t processPGRMZ(int c UNUSED, char *field[],
3213
                               struct gps_device_t *session)
3214
0
{
3215
    /*
3216
     * $PGRMZ,246,f,3*1B
3217
     * 1    = Altitude (probably MSL) in feet
3218
     * 2    = f (feet)
3219
     * 3    = Mode
3220
     *         1 = No Fix
3221
     *         2 = 2D Fix
3222
     *         3 = 3D Fix
3223
     *
3224
     * From: Garmin Proprietary NMEA 0183 Sentences
3225
     *       technical Specifications
3226
     *       190-00684-00, Revision C December 2008
3227
     */
3228
0
    gps_mask_t mask = ONLINE_SET;
3229
3230
    // codacy does not like strlen()
3231
0
    if ('f' == field[2][0] &&
3232
0
        0 < strnlen(field[1], 20)) {
3233
        // have a GPS altitude, must be 3D
3234
        // seems to be altMSL.  regressions show this matches GPGGA MSL
3235
0
        session->newdata.altMSL = atoi(field[1]) * FEET_TO_METERS;
3236
0
        mask |= (ALTITUDE_SET);
3237
0
    }
3238
0
    switch (field[3][0]) {
3239
0
    default:
3240
        // Huh?
3241
0
        break;
3242
0
    case '1':
3243
0
        session->newdata.mode = MODE_NO_FIX;
3244
0
        mask |= MODE_SET;
3245
0
        break;
3246
0
    case '2':
3247
0
        session->newdata.mode = MODE_2D;
3248
0
        mask |= MODE_SET;
3249
0
        break;
3250
0
    case '3':
3251
0
        session->newdata.mode = MODE_3D;
3252
0
        mask |= MODE_SET;
3253
0
        break;
3254
0
    }
3255
3256
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
3257
0
             "NMEA0183: PGRMZ: altMSL %.2f mode %d\n",
3258
0
             session->newdata.altMSL,
3259
0
             session->newdata.mode);
3260
0
    return mask;
3261
0
}
3262
3263
// Magellan Status
3264
static gps_mask_t processPMGNST(int c UNUSED, char *field[],
3265
                                struct gps_device_t *session)
3266
0
{
3267
    /*
3268
     * $PMGNST,01.75,3,T,816,11.1,-00496,00*43
3269
     * 1 = Firmware version number
3270
     * 2 = Mode (1 = no fix, 2 = 2D fix, 3 = 3D fix)
3271
     * 3 = T if we have a fix
3272
     * 4 = battery percentage left in tenths of a percent
3273
     * 5 = time left on the GPS battery in hours
3274
     * 6 = numbers change (freq. compensation?)
3275
     * 7 = PRN number receiving current focus
3276
     */
3277
0
    gps_mask_t mask = ONLINE_SET;
3278
0
    int newmode = atoi(field[3]);
3279
3280
0
    if ('T' == field[4][0]) {
3281
0
        switch(newmode) {
3282
0
        default:
3283
0
            session->newdata.mode = MODE_NO_FIX;
3284
0
            break;
3285
0
        case 2:
3286
0
            session->newdata.mode = MODE_2D;
3287
0
            break;
3288
0
        case 3:
3289
0
            session->newdata.mode = MODE_3D;
3290
0
            break;
3291
0
        }
3292
0
    } else {
3293
        // Can report 3D fix, but 'F' for no fix
3294
0
        session->newdata.mode = MODE_NO_FIX;
3295
0
    }
3296
0
    mask |= MODE_SET;
3297
3298
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3299
0
             "NMEA0183: PMGNST: mode: %d\n",
3300
0
             session->newdata.mode);
3301
0
    return mask;
3302
0
}
3303
3304
static gps_mask_t processPMTK001(int c UNUSED, char *field[],
3305
                                 struct gps_device_t *session)
3306
0
{
3307
0
    int reason;
3308
0
    const char *mtk_reasons[] = {
3309
0
        "Invalid",
3310
0
        "Unsupported",
3311
0
        "Valid but Failed",
3312
0
        "Valid success",       // unused, see above
3313
0
        "Unknown",             // gpsd only
3314
0
    };
3315
3316
    // ACK / NACK
3317
0
    reason = atoi(field[2]);
3318
0
    if (4 == reason) {
3319
        // ACK
3320
0
        GPSD_LOG(LOG_PROG, &session->context->errout,
3321
0
                 "NMEA0183: MTK ACK: %s\n", field[1]);
3322
0
        return ONLINE_SET;
3323
0
    }
3324
3325
    // else, NACK
3326
0
    if (0 > reason ||
3327
0
        3 < reason) {
3328
        // WTF?
3329
0
        reason = 4;
3330
0
    }
3331
0
    GPSD_LOG(LOG_WARN, &session->context->errout,
3332
0
             "NMEA0183: MTK NACK: %s, reason: %s\n",
3333
0
             field[1], mtk_reasons[reason]);
3334
0
    return ONLINE_SET;
3335
0
}
3336
3337
static gps_mask_t processPMTK424(int c UNUSED, char *field[],
3338
                                 struct gps_device_t *session)
3339
0
{
3340
    // PPS pulse width response
3341
    /*
3342
     * Response will look something like: $PMTK424,0,0,1,0,69*12
3343
     * The pulse width is in field 5 (69 in this example).  This
3344
     * sentence is poorly documented at:
3345
     * http://www.trimble.com/embeddedsystems/condor-gps-module.aspx?dtID=documentation
3346
     *
3347
     * Packet Type: 324 PMTK_API_SET_OUTPUT_CTL
3348
     * Packet meaning
3349
     * Write the TSIP/antenna/PPS configuration data to the Flash memory.
3350
     * DataField [Data0]:TSIP Packet[on/off]
3351
     * 0 - Disable TSIP output (Default).
3352
     * 1 - Enable TSIP output.
3353
     * [Data1]:Antenna Detect[on/off]
3354
     * 0 - Disable antenna detect function (Default).
3355
     * 1 - Enable antenna detect function.
3356
     * [Data2]:PPS on/off
3357
     * 0 - Disable PPS function.
3358
     * 1 - Enable PPS function (Default).
3359
     * [Data3]:PPS output timing
3360
     * 0 - Always output PPS (Default).
3361
     * 1 - Only output PPS when GPS position is fixed.
3362
     * [Data4]:PPS pulse width
3363
     * 1~16367999: 61 ns~(61x 16367999) ns (Default = 69)
3364
     *
3365
     * The documentation does not give the units of the data field.
3366
     * Andy Walls <andy@silverblocksystems.net> says:
3367
     *
3368
     * "The best I can figure using an oscilloscope, is that it is
3369
     * in units of 16.368000 MHz clock cycles.  It may be
3370
     * different for any other unit other than the Trimble
3371
     * Condor. 69 cycles / 16368000 cycles/sec = 4.216 microseconds
3372
     * [which is the pulse width I have observed]"
3373
     *
3374
     * Support for this theory comes from the fact that crystal
3375
     * TXCOs with a 16.368MHZ period are commonly available from
3376
     * multiple vendors. Furthermore, 61*69 = 4209, which is
3377
     * close to the observed cycle time and suggests that the
3378
     * documentation is trying to indicate 61ns units.
3379
     *
3380
     * He continues:
3381
     *
3382
     * "I chose [127875] because to divides 16368000 nicely and the
3383
     * pulse width is close to 1/100th of a second.  Any number
3384
     * the user wants to use would be fine.  127875 cycles /
3385
     * 16368000 cycles/second = 1/128 seconds = 7.8125
3386
     * milliseconds"
3387
     */
3388
3389
    // too short?  Make it longer
3390
0
    if (127875 > atoi(field[5])) {
3391
0
        (void)nmea_send(session, "$PMTK324,0,0,1,0,127875");
3392
0
    }
3393
0
    return ONLINE_SET;
3394
0
}
3395
3396
static gps_mask_t processPMTK705(int count, char *field[],
3397
                                 struct gps_device_t *session)
3398
0
{
3399
    /* Trimble version:
3400
     * $PMTK705,AXN_1.30,0000,20090609,*20<CR><LF>
3401
     *
3402
     * 0 PMTK705
3403
     * 1 ReleaseStr - Firmware release name and version
3404
     * 2 Build_ID   - Build ID
3405
     * 3 Date code  - YYYYMMDD
3406
     * 4 Checksum
3407
     *
3408
     * Quectel Querk.  L26.
3409
     * $PMTK705,AXN_3.20_3333_13071501,0003,QUECTEL-L26,*1E<CR><LF>
3410
     *
3411
     * 0 PMTK705
3412
     * 1 ReleaseStr - Firmware release name and version
3413
     * 2 Build_ID   - Build ID
3414
     * 3 Date code  - Product Model
3415
     * 4 SDK Version (optional)
3416
     * * Checksum
3417
    */
3418
3419
    // set device subtype
3420
0
    if (4 == count) {
3421
0
        (void)snprintf(session->subtype, sizeof(session->subtype),
3422
0
                       "%s,%s,%s",
3423
0
                       field[1], field[2], field[3]);
3424
0
    } else {
3425
        // Once again Quectel goes their own way...
3426
0
        (void)snprintf(session->subtype, sizeof(session->subtype),
3427
0
                       "%s,%s,%s,%s",
3428
0
                       field[1], field[2], field[3], field[4]);
3429
0
    }
3430
3431
0
    if ('\0' == session->subtype1[0]) {
3432
        /* Query for the Quectel firmware version.
3433
         * Quectel GPS receivers containing an MTK chipset use
3434
         * this command to return their FW version.
3435
         * From
3436
         * https://forums.quectel.com/t/determine-nmea-version-of-l76-l/3882/5
3437
         * "$PQVERNO is an internal command and used to query Quectel FW
3438
         * version. We haven’t added this internal command in GNSS
3439
         * protocol spec."
3440
         */
3441
0
        (void)nmea_send(session, "$PQVERNO,R");
3442
0
    }
3443
3444
0
    return ONLINE_SET;
3445
0
}
3446
3447
static gps_mask_t processPQxERR(int c UNUSED, char* field[],
3448
                                struct gps_device_t* session)
3449
0
{
3450
    /* Quectel generic PQxxxERRROR message handler
3451
     * The messages are content free, not very useful.
3452
     *
3453
     * $PQTMCFGEINSMSGERROR*4A
3454
     * $PQTMCFGORIENTATIONERROR*54
3455
     * $PQTMCFGWHEELTICKERROR*44
3456
     * $PQTMQMPTERROR*58
3457
     */
3458
3459
0
    GPSD_LOG(LOG_WARN, &session->context->errout,
3460
0
             "NMEA0183: %s Error\n", field[0]);
3461
0
    return ONLINE_SET;
3462
0
}
3463
3464
static gps_mask_t processPQxOK(int c UNUSED, char* field[],
3465
                               struct gps_device_t* session)
3466
0
{
3467
    /* Quectel generic PQTMxxxOK message handler
3468
     * The messages are content free, not very useful.
3469
     *
3470
     * $PQTMCFGEINSMSGOK*16
3471
     * $PQTMCFGORIENTATIONOK*08
3472
     * $PQTMCFGWHEELTICKOK*18
3473
     */
3474
3475
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
3476
0
             "NMEA0183: %s OK\n", field[0]);
3477
0
    return ONLINE_SET;
3478
0
}
3479
3480
// Quectel $PQTMGPS - GNSS position stuff
3481
static gps_mask_t processPQTMGPS(int count UNUSED, char *field[],
3482
                                 struct gps_device_t *session)
3483
0
{
3484
    /*
3485
     * $PQTMGPS,671335,463792.000,31.822084600,117.115221100,59.4260,63.0420,
3486
     *  0.0270,-171.7101,5.9890,1.3300,2.1100,3,18,*75
3487
     *
3488
     * 1   Milliseconds since turn on. 32-bit unsigned integer.
3489
     * 2   Time of week. Seconds
3490
     * 3   Latitude. Degrees
3491
     * 4   Longitude. Degrees
3492
     * 5   Height above ellipsoid, Meters
3493
     * 6   Altitude above mean-sea-level. Meters
3494
     * 7   Ground speed (2D). Meters / sec
3495
     * 8   Heading (2D). Degrees.
3496
     * 9   Horizontal accuracy estimate. Meters.
3497
     * 10  HDOP
3498
     * 11  PDOP
3499
     * 12  Fix type.  0 = No fix.  2 = 2D fix.  3 = 3D fix.
3500
     * 13  Number of navigation satellites (seen? used?)
3501
     *
3502
     * Note: incomplete time stamp.
3503
     */
3504
0
    gps_mask_t mask = ONLINE_SET;
3505
0
    unsigned ts = atoi(field[1]);
3506
0
    unsigned tow = atoi(field[2]);
3507
0
    double lat = safe_atof(field[3]);
3508
0
    double lon = safe_atof(field[4]);
3509
0
    double hae = safe_atof(field[5]);
3510
0
    double msl = safe_atof(field[6]);
3511
0
    double speed = safe_atof(field[7]);
3512
0
    double heading = safe_atof(field[8]);
3513
0
    double hAcc = safe_atof(field[9]);
3514
0
    double hdop = safe_atof(field[10]);
3515
0
    double pdop = safe_atof(field[11]);
3516
0
    unsigned fix = atoi(field[12]);
3517
0
    unsigned numsat = atoi(field[13]);
3518
3519
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
3520
0
             "NMEA0183: PQTMGPS ts %u tow %u lat %.9f lon %.9f HAE %.4f "
3521
0
             "MSL %.4f speed %.4f head %.4f hacc %.4f hdop %.4f pdop %.4f "
3522
0
             "mode %u nsat %u\n",
3523
0
             ts, tow, lat, lon, hae, msl, speed, heading, hAcc, hdop,
3524
0
             pdop, fix, numsat);
3525
0
    return mask;
3526
0
}
3527
3528
// Quectel $PQTMIMU - IMU Raw Data
3529
static gps_mask_t processPQTMIMU(int count UNUSED, char *field[],
3530
                                 struct gps_device_t *session)
3531
0
{
3532
    /*
3533
     * $PQTMIMU,42634,-0.006832,-0.022814,1.014552,0.315000,-0.402500,
3534
       -0.332500,0,0*55
3535
     *
3536
     * 1   Milliseconds since turn on. 32-bit unsigned integer.
3537
     * 2   Acceleration in X-axis direction. g
3538
     * 3   Acceleration in Y-axis direction. g
3539
     * 4   Acceleration in A-axis direction. g
3540
     * 5   Angular rate in X-axis direction. Degrees / second
3541
     * 6   Angular rate in y-axis direction. Degrees / second
3542
     * 7   Angular rate in Z-axis direction. Degrees / second
3543
     * 8   Cumulative ticks
3544
     * 9   Timestamp of last tick
3545
     */
3546
0
    gps_mask_t mask = ONLINE_SET;
3547
0
    unsigned ts = atoi(field[1]);
3548
0
    double accX = safe_atof(field[2]);
3549
0
    double accY = safe_atof(field[3]);
3550
0
    double accZ = safe_atof(field[4]);
3551
0
    double rateX = safe_atof(field[5]);
3552
0
    double rateY = safe_atof(field[6]);
3553
0
    double rateZ = safe_atof(field[7]);
3554
0
    unsigned ticks = atoi(field[8]);
3555
0
    unsigned tick_ts = atoi(field[9]);
3556
3557
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
3558
0
             "NMEA0183: PQTMIMU ts %u accX %.6f accY %.6f accZ %.6f "
3559
0
             "rateX %.6f rateY %.6f rateZ %.6f ticks %u tick_ts %u\n",
3560
0
             ts, accX, accY, accZ, rateX, rateY, rateZ, ticks, tick_ts);
3561
0
    return mask;
3562
0
}
3563
3564
// Quectel $PQTMINS - DR Nav results
3565
static gps_mask_t processPQTMINS(int count UNUSED, char *field[],
3566
                                 struct gps_device_t *session)
3567
0
{
3568
    /*
3569
     * $PQTMINS,42529,1,31.822038000,117.115182800,67.681000,,,,-0.392663,
3570
        1.300793,0.030088*4D
3571
     *
3572
     * 1   Milliseconds since turn on. 32-bit unsigned integer.
3573
     * 2   Solution type, 0 = Pitch and Roll, 1 = GNSS, pitch, roll, heading
3574
     *                    2 = GNSS + DR, 3 = DR Only
3575
     * 3   Latitude. Degrees
3576
     * 4   Longitude. Degrees
3577
     * 5   Height (HAE?, MSL?) , Meters
3578
     * 6   Northward velocity
3579
     * 7   Eastward velocity
3580
     * 8   Downward velocity
3581
     * 9   Roll
3582
     * 10  Pitch
3583
     * 11  Heading
3584
     *
3585
     */
3586
0
    gps_mask_t mask = ONLINE_SET;
3587
0
    unsigned ts = atoi(field[1]);
3588
0
    unsigned sol = atoi(field[2]);
3589
0
    double lat = safe_atof(field[3]);
3590
0
    double lon = safe_atof(field[4]);
3591
0
    double alt = safe_atof(field[5]);
3592
0
    double velN = safe_atof(field[6]);
3593
0
    double velE = safe_atof(field[7]);
3594
0
    double velD = safe_atof(field[8]);
3595
0
    double roll = safe_atof(field[9]);
3596
0
    double pitch = safe_atof(field[10]);
3597
0
    double head = safe_atof(field[11]);
3598
3599
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
3600
0
             "NMEA0183: PQTMINS ts %u sol %u lat %.9f lon %.9f alt %.6f "
3601
0
             "velN %.6f velE %.6f velD %.6f roll %.6f pitch %.6f head %.6f\n",
3602
0
             ts, sol, lat, lon, alt, velN, velE, velD, roll, pitch, head);
3603
0
    return mask;
3604
0
}
3605
3606
// Quectel $PQTMVER - Firmware info
3607
static gps_mask_t processPQTMVER(int count UNUSED, char *field[],
3608
                                 struct gps_device_t *session)
3609
0
{
3610
    /*
3611
     * $PQTMVER,MODULE_L89HANR01A06S,2022/07/28,18:27:04*7A
3612
     *
3613
     * 1   Version
3614
     * 2   build date yyyy/mm/dd
3615
     * 3   build time hh:mm:ss
3616
     *
3617
     */
3618
0
    char obuf[128];                      // temp version string buffer
3619
0
    gps_mask_t mask = ONLINE_SET;
3620
3621
    // save as subtype
3622
0
    (void)snprintf(obuf, sizeof(obuf),
3623
0
             "%s %.12s %.10s",
3624
0
             field[1], field[2], field[3]);
3625
3626
    // save what we can
3627
0
    (void)strlcpy(session->subtype, obuf, sizeof(session->subtype) - 1);
3628
3629
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
3630
0
             "NMEA0183: PQTMVER %s\n",
3631
0
             session->subtype);
3632
3633
0
    return mask;
3634
0
}
3635
3636
static gps_mask_t processPQVERNO(int c UNUSED, char* field[],
3637
                                 struct gps_device_t* session)
3638
0
{
3639
    /* Example request & response are provided courtesy of Quectel below.
3640
     * This command is not publically documented, but Quectel support
3641
     * provided this description via email. This has been tested on
3642
     * Quectel version L70-M39, but all recent (2022) versions of Quectel
3643
     * support this command is well.
3644
     *
3645
     * Request:
3646
     * $PQVERNO,R*3F
3647
     *
3648
     * Response:
3649
     * $PQVERNO,R,L96NR01A03S,2018/07/30,04:17*6B
3650
     *
3651
     * Description of the 6 fields are below.
3652
     *
3653
     * 1. $PQVERNO,              Query command
3654
     * 2. R,                     Read
3655
     * 3. L96NR01A03S,           Quectel firmware version number
3656
     * 4. 2018/07/30,            Firmware build date
3657
     * 5. 04:17*                 Firmware build time
3658
     * 6. 6B                     Checksum
3659
     */
3660
3661
0
    if (0 == strncmp(session->nmea.field[0], "PQVERNO", sizeof("PQVERNO")) &&
3662
0
        '\0' != field[2][0]) {
3663
0
        (void)snprintf(session->subtype1, sizeof(session->subtype1),
3664
0
                       "%s,%s,%s",
3665
0
                       field[2], field[3], field[4]);
3666
0
    }
3667
3668
0
    return ONLINE_SET;
3669
0
}
3670
3671
/* smart watch sensors
3672
 * A stub.
3673
 */
3674
static gps_mask_t processPRHS(int c UNUSED, char *field[],
3675
                               struct gps_device_t *session)
3676
0
{
3677
    /*
3678
     * $PRHS ,type,....
3679
     *   type = message type
3680
     *
3681
     * Yes: $PRHS[space],
3682
     *
3683
     * types:
3684
     * $PRHS ,ACC,9.952756,0.37819514,1.3165021,20150305072428436*44
3685
     * $PRHS ,COM,238.09642,16.275442,82.198425,20150305072428824*43
3686
     * $PRHS ,GYR,0.0,0.0,0.0,20150305072428247*4D
3687
     * $PRHS ,LAC,0.23899937,0.009213656,0.02143073,20150305072428437*46
3688
     * $PRHS ,MAG,47.183502,-51.789,-2.7145,20150305072428614*41
3689
     * $PRHS ,ORI,187.86511,-2.1546898,-82.405205,20150305072428614*53
3690
     * $PRHS ,RMC,20150305072427985*55
3691
     *
3692
     */
3693
0
    gps_mask_t mask = ONLINE_SET;
3694
3695
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3696
0
             "NMEA0183: PRHS: type %s\n",
3697
0
             field[1]);
3698
0
    return mask;
3699
0
}
3700
3701
static gps_mask_t processPSRFEPE(int c UNUSED, char *field[],
3702
                               struct gps_device_t *session)
3703
0
{
3704
    /*
3705
     * $PSRFEPE,100542.000,A,0.7,6.82,10.69,0.0,180.0*24
3706
     * 1    = UTC Time hhmmss.sss
3707
     * 2    = Status.  A = Valid, V = Data not valid
3708
     * 3    = HDOP
3709
     * 4    = EHPE meters (Estimated Horizontal Position Error)
3710
     * 5    = EVPE meters (Estimated Vertical Position Error)
3711
     * 6    = EHVE meters (Estimated Speed Over Ground/Velocity Error)
3712
     * 7    = EHE degrees (Estimated Heading Error)
3713
     *
3714
     * SiRF won't say if these are 1-sigma or what...
3715
     */
3716
0
    gps_mask_t mask = STATUS_SET;
3717
3718
    // get time/ valid or not
3719
0
    if ('\0' != field[1][0]) {
3720
0
        if (0 == merge_hhmmss(field[1], session)) {
3721
0
            register_fractional_time(field[0], field[1], session);
3722
0
            if (0 == session->nmea.date.tm_year) {
3723
0
                GPSD_LOG(LOG_WARN, &session->context->errout,
3724
0
                         "NMEA0183: can't use PSRFEPE time until after ZDA "
3725
0
                         "or RMC has supplied a year.\n");
3726
0
            } else {
3727
0
                mask |= TIME_SET;
3728
0
            }
3729
0
        }
3730
0
    }
3731
0
    if ('A' != field[2][0]) {
3732
        // Huh?
3733
0
        return mask;
3734
0
    }
3735
3736
0
    if ('\0' != field[3][0]) {
3737
        /* This adds nothing, it just agrees with the gpsd calculation
3738
         * from the skyview.  Which is a nice confirmation. */
3739
0
        session->gpsdata.dop.hdop = safe_atof(field[3]);
3740
0
        mask |= DOP_SET;
3741
0
    }
3742
0
    if ('\0' != field[4][0]) {
3743
        // EHPE (Estimated Horizontal Position Error)
3744
0
        session->newdata.eph = safe_atof(field[4]);
3745
0
        mask |= HERR_SET;
3746
0
    }
3747
3748
0
    if ('\0' != field[5][0]) {
3749
        // Estimated Vertical Position Error (meters, 0.01 resolution)
3750
0
        session->newdata.epv = safe_atof(field[5]);
3751
0
        mask |= VERR_SET;
3752
0
    }
3753
3754
0
    if ('\0' != field[6][0]) {
3755
        // Estimated Horizontal Speed Error meters/sec
3756
0
        session->newdata.eps = safe_atof(field[6]);
3757
0
    }
3758
3759
0
    if ('\0' != field[7][0]) {
3760
        // Estimated Heading Error degrees
3761
0
        session->newdata.epd = safe_atof(field[7]);
3762
0
    }
3763
3764
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
3765
0
             "NMEA0183: PSRFEPE: hdop=%.1f eph=%.1f epv=%.1f eps=%.1f "
3766
0
             "epd=%.1f\n",
3767
0
             session->gpsdata.dop.hdop,
3768
0
             session->newdata.eph,
3769
0
             session->newdata.epv,
3770
0
             session->newdata.eps,
3771
0
             session->newdata.epd);
3772
0
    return mask;
3773
0
}
3774
3775
//  Recommended Minimum 3D GNSS Data
3776
static gps_mask_t processPSTI030(int count UNUSED, char *field[],
3777
                                 struct gps_device_t *session)
3778
0
{
3779
    /*
3780
     * $PSTI,030,hhmmss.sss,A,dddmm.mmmmmmm,a,dddmm.mmmmmmm,a,x.x,
3781
            x.x,x.x,x.x,ddmmyy,a.x.x,x.x*hh<CR><LF>
3782
     * 1     030          Sentence ID
3783
     * 2     225446.334   Time of fix 22:54:46 UTC
3784
     * 3     A            Status of Fix: A = Autonomous, valid;
3785
     *                                 V = invalid
3786
     * 4,5   4916.45,N    Latitude 49 deg. 16.45 min North
3787
     * 6,7   12311.12,W   Longitude 123 deg. 11.12 min West
3788
     * 8     103.323      Mean Sea Level meters
3789
     * 9     0.00         East Velocity meters/sec
3790
     * 10    0.00         North Velocity meters/sec
3791
     * 11    0.00         Up Velocity meters/sec
3792
     * 12    181194       Date of fix  18 November 1994
3793
     * 13    A            FAA mode indicator
3794
     *                        See faa_mode() for possible mode values.
3795
     * 14    1.2          RTK Age
3796
     * 15    4.2          RTK Ratio
3797
     * 16    *68          mandatory nmea_checksum
3798
     *
3799
     * In private email, SkyTraq says F mode is 10x more accurate
3800
     * than R mode.
3801
     */
3802
0
    gps_mask_t mask = ONLINE_SET;
3803
3804
0
    if (0 != strncmp(session->device_type->type_name, "Skytraq", 7)) {
3805
        // this is skytraq, but not marked yet, so probe for Skytraq
3806
        // Send MID 0x02, to get back MID 0x80
3807
0
        (void)gpsd_write(session, "\xA0\xA1\x00\x02\x02\x01\x03\x0d\x0a",9);
3808
0
    }
3809
3810
0
    if ('V' == field[3][0] ||
3811
0
        'N' == field[13][0]) {
3812
        // nav warning, or FAA not valid, ignore the rest of the data
3813
0
        session->newdata.status = STATUS_UNK;
3814
0
        session->newdata.mode = MODE_NO_FIX;
3815
0
        mask |= MODE_SET | STATUS_SET;
3816
0
    } else if ('A' == field[3][0]) {
3817
0
        double east, north, climb, age, ratio;
3818
3819
        // data valid
3820
0
        if ('\0' != field[2][0] &&
3821
0
            '\0' != field[12][0]) {
3822
            // good date and time
3823
0
            if (0 == merge_hhmmss(field[2], session) &&
3824
0
                0 == merge_ddmmyy(field[12], session)) {
3825
0
                mask |= TIME_SET;
3826
0
                register_fractional_time( "PSTI030", field[2], session);
3827
0
            }
3828
0
        }
3829
0
        if (0 == do_lat_lon(&field[4], &session->newdata)) {
3830
0
            session->newdata.mode = MODE_2D;
3831
0
            mask |= LATLON_SET;
3832
0
            if ('\0' != field[8][0]) {
3833
                // altitude is MSL
3834
0
                session->newdata.altMSL = safe_atof(field[8]);
3835
0
                mask |= ALTITUDE_SET;
3836
0
                session->newdata.mode = MODE_3D;
3837
                // Let gpsd_error_model() deal with geoid_sep and altHAE
3838
0
            }
3839
0
            mask |= MODE_SET;
3840
0
        }
3841
        /* convert ENU to track
3842
         * this has more precision than GPVTG, GPVTG comes earlier
3843
         * in the cycle */
3844
0
        east = safe_atof(field[9]);     // east velocity m/s
3845
0
        north = safe_atof(field[10]);   // north velocity m/s
3846
0
        climb = safe_atof(field[11]);   // up velocity m/s
3847
0
        age = safe_atof(field[14]);
3848
0
        ratio = safe_atof(field[15]);
3849
3850
0
        session->newdata.NED.velN = north;
3851
0
        session->newdata.NED.velE = east;
3852
0
        session->newdata.NED.velD = -climb;
3853
0
        if (0.05 < (age + ratio)) {
3854
            // don't report age == ratio == 0.0
3855
0
            session->newdata.dgps_age = age;
3856
0
            session->gpsdata.fix.base.ratio = ratio;
3857
0
        }
3858
3859
0
        mask |= VNED_SET | STATUS_SET;
3860
3861
0
        session->newdata.status = faa_mode(field[13][0]);
3862
0
        if (STATUS_RTK_FIX == session->newdata.status ||
3863
0
            STATUS_RTK_FLT == session->newdata.status) {
3864
            // RTK_FIX or RTK_FLT
3865
0
            session->gpsdata.fix.base.status = session->newdata.status;
3866
0
        }
3867
0
    }
3868
3869
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
3870
0
             "NMEA0183: PSTI,030: ddmmyy=%s hhmmss=%s lat=%.2f lon=%.2f "
3871
0
             "status=%d, RTK(Age=%.1f Ratio=%.1f)\n",
3872
0
             field[12], field[2],
3873
0
             session->newdata.latitude,
3874
0
             session->newdata.longitude,
3875
0
             session->newdata.status,
3876
0
             session->newdata.dgps_age,
3877
0
             session->newdata.base.ratio);
3878
0
    return mask;
3879
0
}
3880
3881
/* Skytraq RTK Baseline, fixed base to rover or moving base
3882
 * Same as $PSTI.035, except that is moving base to rover
3883
 * PX1172RH
3884
 */
3885
static gps_mask_t processPSTI032(int count UNUSED, char *field[],
3886
                                 struct gps_device_t *session)
3887
0
{
3888
    /*
3889
     * $PSTI,032,041457.000,170316,A,R,0.603,‐0.837,‐0.089,1.036,144.22,,,,,*1B
3890
     *
3891
     * 2  UTC time,  hhmmss.sss
3892
     * 3  UTC Date, ddmmyy
3893
     * 4  Status, ‘V’ = Void ‘A’ = Active
3894
     * 5  Mode indicator, 'O' = Float RTK, ‘F’ = Float RTK. ‘R’ = Fixed RTK
3895
     * 6  East‐projection of baseline, meters
3896
     * 7  North‐projection of baseline, meters
3897
     * 8  Up‐projection of baseline, meters
3898
     * 9  Baseline length, meters
3899
     * 10 Baseline course 144.22, true degrees
3900
     * 11 Reserved
3901
     * 12 Reserved
3902
     * 13 Reserved
3903
     * 14 Reserved
3904
     * 15 Reserved
3905
     * 16 Checksum
3906
     */
3907
0
    gps_mask_t mask = ONLINE_SET;
3908
0
    struct baseline_t *base = &session->gpsdata.fix.base;
3909
3910
0
    if ('A' != field[4][0]) {
3911
        //  status not valid
3912
0
        return mask;
3913
0
    }
3914
3915
    // Status Valid
3916
0
    if ('\0' != field[2][0] &&
3917
0
        '\0' != field[3][0]) {
3918
        // have date and time
3919
0
        if (0 == merge_hhmmss(field[2], session) &&
3920
0
            0 == merge_ddmmyy(field[3], session)) {
3921
            // good date and time
3922
0
            mask |= TIME_SET;
3923
0
            register_fractional_time("PSTI032", field[2], session);
3924
0
        }
3925
0
    }
3926
3927
0
    if ('F' == field[5][0] ||
3928
0
        'O' == field[5][0]) {
3929
        // Floating point RTK
3930
        // 'O' is undocuemented, private email says it is jsut a crappy 'F'.
3931
0
        base->status = STATUS_RTK_FLT;
3932
0
    } else if ('R' == field[5][0]) {
3933
        // Fixed point RTK
3934
0
        base->status = STATUS_RTK_FIX;
3935
0
    } else {
3936
        // WTF?
3937
0
        return mask;
3938
0
    }
3939
3940
0
    base->east = safe_atof(field[6]);
3941
0
    base->north = safe_atof(field[7]);
3942
0
    base->up = safe_atof(field[8]);
3943
0
    base->length = safe_atof(field[9]);
3944
0
    base->course = safe_atof(field[10]);
3945
3946
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
3947
0
             "NMEA0183: PSTI,032: RTK Baseline mode %d E %.3f  N %.3f  U %.3f "
3948
0
             "length %.3f course %.3f\n",
3949
0
             base->status, base->east, base->north, base->up,
3950
0
             base->length, base->course);
3951
0
    return mask;
3952
0
}
3953
3954
/* Skytraq  RTK RAW Measurement Monitoring Data
3955
 */
3956
static gps_mask_t processPSTI033(int count UNUSED, char *field[],
3957
                                 struct gps_device_t *session)
3958
0
{
3959
    /*
3960
     * $PSTI,033,hhmmss.sss,ddmmyy,x,R,x,G,x,x,,,C,x,x,,,E,x,x,,,R,x,x,,*hh
3961
     * $PSTI,033,110431.000,150517,2,R,1,G,1,0,,,C,0,0,,,E,0,0,,,R,0,0,,*72
3962
     *
3963
     * 2  UTC time,  hhmmss.sss
3964
     * 3  UTC Date, ddmmyy
3965
     * 4  "2", version
3966
     * 5  Receiver, R = Rover, B = Base
3967
     * 6  total cycle‐slipped raw measurements
3968
     * 7  "G", GPS
3969
     * 8  cycle slipped L1
3970
     * 9  cycle slipped L2
3971
     * 10 reserved
3972
     * 11 reserved
3973
     * 12 "C", BDS
3974
     * 12 cycle slipped B1
3975
     * 14 cycle slipped B2
3976
     * 15 reserved
3977
     * 16 reserved
3978
     * 17 "E", Galileo
3979
     * 18 cycle slipped E1
3980
     * 19 cycle slipped E5b
3981
     * 20 reserved
3982
     * 21 reserved
3983
     * 22 "R", GLONASS
3984
     * 23 cycle slipped G1
3985
     * 24 cycle slipped G2
3986
     * 25 reserved
3987
     * 26 reserved
3988
     * 27 Checksum
3989
     */
3990
0
    gps_mask_t mask = ONLINE_SET;
3991
0
    char receiver;
3992
0
    unsigned total, L1, L2, B1, B2, E1, E5b, G1, G2;
3993
3994
0
    if ('2' != field[4][0]) {
3995
        //  we only understand version 2
3996
0
        return mask;
3997
0
    }
3998
0
    if ('B' != field[5][0] &&
3999
0
        'R' != field[5][0]) {
4000
        //  Huh?  Rover or Base
4001
0
        return mask;
4002
0
    }
4003
0
    receiver = field[5][0];
4004
4005
0
    if ('\0' != field[2][0] &&
4006
0
        '\0' != field[3][0]) {
4007
        // have date and time
4008
0
        if (0 == merge_hhmmss(field[2], session) &&
4009
0
            0 == merge_ddmmyy(field[3], session)) {
4010
            // good date and time
4011
0
            mask |= TIME_SET;
4012
0
            register_fractional_time("PSTI033", field[2], session);
4013
0
        }
4014
0
    }
4015
0
    total = atoi(field[6]);
4016
0
    L1 = atoi(field[7]);
4017
0
    L2 = atoi(field[8]);
4018
0
    B1 = atoi(field[13]);
4019
0
    B2 = atoi(field[14]);
4020
0
    E1 = atoi(field[18]);
4021
0
    E5b = atoi(field[19]);
4022
0
    G1 = atoi(field[23]);
4023
0
    G2 = atoi(field[24]);
4024
4025
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
4026
0
             "NMEA0183: PSTI,033: RTK RAW receiver %c Slips: total %u L1 %u "
4027
0
             "L2 %u B1 %u B2 %u E1 %u E5b %u G1 %u G2 %u\n",
4028
0
             receiver, total, L1, L2, B1, B2, E1, E5b, G1, G2);
4029
0
    return mask;
4030
0
}
4031
4032
// Skytraq RTK Baseline, moving base to moving rover
4033
// PX1172RH
4034
static gps_mask_t processPSTI035(int count UNUSED, char *field[],
4035
                                 struct gps_device_t *session)
4036
0
{
4037
    /*
4038
     * $PSTI,035,041457.000,170316,A,R,0.603,‐0.837,‐0.089,1.036,144.22,,,,,*1B
4039
     *
4040
     * 2  UTC time,  hhmmss.sss
4041
     * 3  UTC Date, ddmmyy
4042
     * 4  Status, ‘V’ = Void ‘A’ = Active
4043
     * 5  Mode indicator, ‘F’ = Float RTK. ‘R’ = FIxed RTK
4044
     * 6  East‐projection of baseline, meters
4045
     * 7  North‐projection of baseline, meters
4046
     * 8  Up‐projection of baseline, meters
4047
     * 9  Baseline length, meters
4048
     * 10 Baseline course 144.22, true degrees
4049
     * 11 Reserved
4050
     * 12 Reserved
4051
     * 13 Reserved
4052
     * 14 Reserved
4053
     * 15 Reserved
4054
     * 16 Checksum
4055
     */
4056
4057
0
    gps_mask_t mask = ONLINE_SET;
4058
0
    struct baseline_t *base = &session->gpsdata.attitude.base;
4059
4060
    // RTK Baseline Data of Rover Moving Base Receiver
4061
0
    if ('\0' != field[2][0] &&
4062
0
        '\0' != field[3][0]) {
4063
        // good date and time
4064
0
        if (0 == merge_hhmmss(field[2], session) &&
4065
0
            0 == merge_ddmmyy(field[3], session)) {
4066
0
            mask |= TIME_SET;
4067
0
            register_fractional_time( "PSTI035", field[2], session);
4068
0
        }
4069
0
    }
4070
0
    if ('A' != field[4][0]) {
4071
        // No valid data, except time, sort of
4072
0
        GPSD_LOG(LOG_PROG, &session->context->errout,
4073
0
                 "NMEA0183: PSTI,035: not valid\n");
4074
0
        base->status = STATUS_UNK;
4075
0
        return mask;
4076
0
    }
4077
0
    if ('F' == field[5][0]) {
4078
        // Float RTX
4079
0
        base->status = STATUS_RTK_FLT;
4080
0
    } else if ('R' == field[5][0]) {
4081
        // Fix RTX
4082
0
        base->status = STATUS_RTK_FIX;
4083
0
    } // else ??
4084
4085
0
    base->east = safe_atof(field[6]);
4086
0
    base->north = safe_atof(field[7]);
4087
0
    base->up = safe_atof(field[8]);
4088
0
    base->length = safe_atof(field[9]);
4089
0
    base->course = safe_atof(field[10]);
4090
0
    mask |= ATTITUDE_SET;
4091
4092
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
4093
0
             "NMEA0183: PSTI,035: RTK Baseline mode %d E %.3f  N %.3f  U %.3f "
4094
0
             "length %.3f course %.3f\n",
4095
0
             base->status, base->east, base->north, base->up,
4096
0
             base->length, base->course);
4097
0
    return mask;
4098
0
}
4099
4100
// Skytraq PSTI,036 – Heading, Pitch and Roll
4101
// PX1172RH
4102
static gps_mask_t processPSTI036(int count UNUSED, char *field[],
4103
                                 struct gps_device_t *session)
4104
0
{
4105
    /*
4106
     * $PSTI,036,054314.000,030521,191.69,‐16.35,0.00,R*4D
4107
     *
4108
     * 2  UTC time,  hhmmss.sss
4109
     * 3  UTC Date, ddmmyy
4110
     * 4  Heading, 0 - 359.9, when mode == R, degrees
4111
     * 5  Pitch, -90 - 90, when mode == R, degrees
4112
     * 6  Roll, -90 - 90, when mode == R, degrees
4113
     * 7  Mode
4114
     *     'N’ = Data not valid
4115
     *     'A’ = Autonomous mode
4116
     *     'D’ = Differential mode
4117
     *     'E’ = Estimated (dead reckoning) mode
4118
     *     'M’ = Manual input mode
4119
     *     'S’ = Simulator mode
4120
     *     'F’ = Float RTK
4121
     *     'R’ = Fix RTK
4122
     * 8  Checksum
4123
     */
4124
4125
0
    gps_mask_t mask = ONLINE_SET;
4126
0
    int mode;
4127
4128
0
    if ('\0' != field[2][0] &&
4129
0
        '\0' != field[3][0]) {
4130
        // good date and time
4131
0
        if (0 == merge_hhmmss(field[2], session) &&
4132
0
            0 == merge_ddmmyy(field[3], session)) {
4133
0
            mask |= TIME_SET;
4134
0
            register_fractional_time("PSTI036", field[2], session);
4135
0
        }
4136
0
    }
4137
0
    if ('\0' == field[7][0] ||
4138
0
        'N' == field[7][0]) {
4139
        // No valid data, except time, sort of
4140
0
        GPSD_LOG(LOG_PROG, &session->context->errout,
4141
0
                 "NMEA0183: PSTI,036: not valid\n");
4142
0
        return mask;
4143
0
    }
4144
    // good attitude data to use
4145
0
    session->gpsdata.attitude.mtime = gpsd_utc_resolve(session);
4146
0
    session->gpsdata.attitude.heading = safe_atof(field[4]);
4147
0
    session->gpsdata.attitude.pitch = safe_atof(field[5]);
4148
0
    session->gpsdata.attitude.roll = safe_atof(field[6]);
4149
0
    mode = faa_mode(field[7][0]);
4150
4151
0
    mask |= ATTITUDE_SET;
4152
4153
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
4154
0
             "NMEA0183: PSTI,036: mode %d heading %.2f  pitch %.2f roll %.2f\n",
4155
0
             mode,
4156
0
             session->gpsdata.attitude.heading,
4157
0
             session->gpsdata.attitude.pitch,
4158
0
             session->gpsdata.attitude.roll);
4159
0
    return mask;
4160
0
}
4161
4162
// Recommend Minimum Course Specific GPS/TRANSIT Data
4163
static gps_mask_t processRMC(int count, char *field[],
4164
                             struct gps_device_t *session)
4165
0
{
4166
    /*
4167
     * RMC,225446.33,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E,A*68
4168
     * 1     225446.33    Time of fix 22:54:46 UTC
4169
     * 2     A            Status of Fix:
4170
     *                     A = Autonomous, valid;
4171
     *                     V = invalid
4172
     * 3,4   4916.45,N    Latitude 49 deg. 16.45 min North
4173
     * 5,6   12311.12,W   Longitude 123 deg. 11.12 min West
4174
     * 7     000.5        Speed over ground, Knots
4175
     * 8     054.7        Course Made Good, True north
4176
     * 9     181194       Date of fix ddmmyy.  18 November 1994
4177
     * 10,11 020.3,E      Magnetic variation 20.3 deg East
4178
     * 12    A            FAA mode indicator (NMEA 2.3 and later)
4179
     *                     see faa_mode() for possible mode values
4180
     * 13    V            Nav Status (NMEA 4.1 and later)
4181
     *                     A=autonomous,
4182
     *                     D=differential,
4183
     *                     E=Estimated,
4184
     *                     M=Manual input mode
4185
     *                     N=not valid,
4186
     *                     S=Simulator,
4187
     *                     V=Valid
4188
     * *68        mandatory nmea_checksum
4189
     *
4190
     * SiRF chipsets don't return either Mode Indicator or magnetic variation.
4191
     */
4192
0
    gps_mask_t mask = ONLINE_SET;
4193
0
    char status = field[2][0];
4194
0
    int newstatus;
4195
4196
    /* As of Dec 2023, the regressions only have A, or V in field 2.
4197
     * NMEA says only A, and V are valid
4198
     */
4199
0
    switch (status) {
4200
0
    default:
4201
        // missing, never seen this case.
4202
0
        FALLTHROUGH
4203
0
    case 'V':
4204
        // Invalid
4205
0
        session->newdata.mode = MODE_NO_FIX;
4206
0
        if ('\0' == field[1][0] ||
4207
0
            '\0' ==  field[9][0]) {
4208
            /* No time available. That breaks cycle end detector
4209
             * Force report to bypass cycle detector and get report out.
4210
             * To handle Querks (Quectel) like this:
4211
             *  $GPRMC,,V,,,,,,,,,,N*53
4212
             */
4213
0
            memset(&session->nmea.date, 0, sizeof(session->nmea.date));
4214
0
            session->cycle_end_reliable = false;
4215
0
            mask |= REPORT_IS | TIME_SET;
4216
0
        }
4217
0
        mask |= STATUS_SET | MODE_SET;
4218
0
        break;
4219
0
    case 'A':
4220
        // Valid Fix
4221
        /*
4222
         * The MTK3301, Royaltek RGM-3800, and possibly other
4223
         * devices deliver bogus time values when the navigation
4224
         * warning bit is set.
4225
         */
4226
        /* The Meinberg GPS164 only outputs GPRMC.  Do set status
4227
         * so it can increment fixcnt.
4228
         */
4229
0
        if ('\0' != field[1][0] &&
4230
0
            9 < count &&
4231
0
            '\0' !=  field[9][0]) {
4232
0
            if (0 == merge_hhmmss(field[1], session) &&
4233
0
                0 == merge_ddmmyy(field[9], session)) {
4234
                // got a good data/time
4235
0
                mask |= TIME_SET;
4236
0
                register_fractional_time(field[0], field[1], session);
4237
0
            }
4238
0
        }
4239
        // else, no point to the time only case, no regressions with that
4240
4241
0
        if (0 == do_lat_lon(&field[3], &session->newdata)) {
4242
0
            newstatus = STATUS_GPS;
4243
0
            mask |= LATLON_SET;
4244
0
            if (MODE_2D >= session->lastfix.mode) {
4245
                /* we have at least a 2D fix
4246
                 * might cause blinking */
4247
0
                session->newdata.mode = MODE_2D;
4248
0
            } else if (MODE_3D == session->lastfix.mode) {
4249
                // keep the 3D, this may be cycle starter
4250
                // might cause blinking
4251
0
                session->newdata.mode = MODE_3D;
4252
0
            }
4253
0
        } else {
4254
0
            newstatus = STATUS_UNK;
4255
0
            session->newdata.mode = MODE_NO_FIX;
4256
0
        }
4257
0
        mask |= MODE_SET;
4258
0
        if ('\0' != field[7][0]) {
4259
0
            session->newdata.speed = safe_atof(field[7]) * KNOTS_TO_MPS;
4260
0
            mask |= SPEED_SET;
4261
0
        }
4262
0
        if ('\0' != field[8][0]) {
4263
0
            session->newdata.track = safe_atof(field[8]);
4264
0
            mask |= TRACK_SET;
4265
0
        }
4266
4267
        // get magnetic variation
4268
0
        if ('\0' != field[10][0] &&
4269
0
            '\0' != field[11][0]) {
4270
0
            session->newdata.magnetic_var = safe_atof(field[10]);
4271
4272
0
            switch (field[11][0]) {
4273
0
            case 'E':
4274
                // no change
4275
0
                break;
4276
0
            case 'W':
4277
0
                session->newdata.magnetic_var = -session->newdata.magnetic_var;
4278
0
                break;
4279
0
            default:
4280
                // huh?
4281
0
                session->newdata.magnetic_var = NAN;
4282
0
                break;
4283
0
            }
4284
0
            if (0 == isfinite(session->newdata.magnetic_var) ||
4285
0
                0.09 >= fabs(session->newdata.magnetic_var)) {
4286
                // some GPS set 0.0,E, or 0,w instead of blank
4287
0
                session->newdata.magnetic_var = NAN;
4288
0
            } else {
4289
0
                mask |= MAGNETIC_TRACK_SET;
4290
0
            }
4291
0
        }
4292
4293
0
        if (12 < count) {
4294
0
            if ('\0' != field[12][0]) {
4295
                // Have FAA mode indicator (NMEA 2.3 and later)
4296
0
                newstatus = faa_mode(field[12][0]);
4297
0
            }
4298
            /*
4299
             * If present, can not be NUL:
4300
             * S = Safe
4301
             * C = Caution
4302
             * U == Unsafe
4303
             * V = invalid.
4304
             *
4305
             * In the regressions, as of Dec 2023, field 13 is
4306
             * always 'V', and field 2 is always 'A'.  That seems
4307
             * like an invalid combination.... */
4308
0
            if (13 < count) {
4309
0
                if ('\0' != field[13][0]) {
4310
0
                    ; // skip for now
4311
0
                }
4312
0
            }
4313
0
            GPSD_LOG(LOG_DATA, &session->context->errout,
4314
0
                     "NMEA0183: RMC: status %s(%d) faa mode %s faa status %s\n",
4315
0
                     field[2], newstatus, field[12], field[13]);
4316
0
        }
4317
4318
        /*
4319
         * This copes with GPSes like the Magellan EC-10X that *only* emit
4320
         * GPRMC. In this case we set mode and status here so the client
4321
         * code that relies on them won't mistakenly believe it has never
4322
         * received a fix.
4323
         */
4324
0
        if (3 < session->gpsdata.satellites_used) {
4325
            // 4 sats used means 3D
4326
0
            session->newdata.mode = MODE_3D;
4327
0
        } else if (0 != isfinite(session->gpsdata.fix.altHAE) ||
4328
0
                   0 != isfinite(session->gpsdata.fix.altMSL)) {
4329
            /* we probably have at least a 3D fix
4330
             * this handles old GPS that do not report 3D */
4331
0
            session->newdata.mode = MODE_3D;
4332
0
        }
4333
0
        session->newdata.status = newstatus;
4334
0
        mask |= STATUS_SET | MODE_SET;
4335
0
    }
4336
4337
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
4338
0
             "NMEA0183: RMC: ddmmyy=%s hhmmss=%s lat=%.2f lon=%.2f "
4339
0
             "speed=%.2f track=%.2f mode=%d var=%.1f status=%d\n",
4340
0
             field[9], field[1],
4341
0
             session->newdata.latitude,
4342
0
             session->newdata.longitude,
4343
0
             session->newdata.speed,
4344
0
             session->newdata.track,
4345
0
             session->newdata.mode,
4346
0
             session->newdata.magnetic_var,
4347
0
             session->newdata.status);
4348
0
    return mask;
4349
0
}
4350
4351
/* precessROT() - process Rate Of Turn
4352
 *
4353
 * Deprecated by NMEA in 2008
4354
 */
4355
static gps_mask_t processROT(int c UNUSED, char *field[],
4356
                             struct gps_device_t *session)
4357
0
{
4358
    /*
4359
     * $APROT,0.013,A*35
4360
     *
4361
     * 1) Rate of Turn deg/min
4362
     * 2) A = valid, V = Void
4363
     * )  checksum
4364
     *
4365
     */
4366
0
    gps_mask_t mask = ONLINE_SET;
4367
4368
0
    if ('\0' == field[1][0] ||
4369
0
        'A' != field[2][0]) {
4370
        // no data
4371
0
        return mask;
4372
0
    }
4373
4374
    // assume good data
4375
0
    session->gpsdata.attitude.rot = safe_atof(field[1]);
4376
0
    mask |= ATTITUDE_SET;
4377
4378
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
4379
0
             "NMEA0183: $xxROT:Rate of Turn %f\n",
4380
0
             session->gpsdata.attitude.rot);
4381
0
    return mask;
4382
0
}
4383
4384
/*
4385
 * Skytraq undocumented debug sentences take this format:
4386
 * $STI,type,val*CS
4387
 * type is a 2 char subsentence type
4388
 * Note: NO checksum
4389
 */
4390
static gps_mask_t processSTI(int count, char *field[],
4391
                             struct gps_device_t *session)
4392
0
{
4393
0
    gps_mask_t mask = ONLINE_SET;
4394
4395
0
    if (0 != strncmp(session->device_type->type_name, "Skytraq", 7)) {
4396
        // this is skytraq, but marked yet, so probe for Skytraq
4397
        // Send MID 0x02, to get back MID 0x80
4398
0
        (void)gpsd_write(session, "\xA0\xA1\x00\x02\x02\x01\x03\x0d\x0a",9);
4399
0
    }
4400
4401
0
    if ( 0 == strcmp( field[1], "IC") ) {
4402
        // $STI,IC,error=XX, this is always very bad, but undocumented
4403
0
        GPSD_LOG(LOG_ERROR, &session->context->errout,
4404
0
                 "NMEA0183: Skytraq: $STI,%s,%s\n", field[1], field[2]);
4405
0
        return mask;
4406
0
    }
4407
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
4408
0
             "NMEA0183: STI,%s: Unknown type, Count: %d\n", field[1], count);
4409
4410
0
    return mask;
4411
0
}
4412
4413
// SiRF Estimated Position Errors
4414
// $xxTHS -- True Heading and Status
4415
static gps_mask_t processTHS(int c UNUSED, char *field[],
4416
                             struct gps_device_t *session)
4417
0
{
4418
    /*
4419
     * $GNTHS,121.15.A*1F<CR><LF>
4420
     * 1  - Heading, degrees True
4421
     * 2  - Mode indicator
4422
     *      'A’ = Autonomous
4423
     *      'E’ = Estimated (dead reckoning)
4424
     *      'M’ = Manual input
4425
     *      'S’ = Simulator
4426
     *      'V’ = Data not valid
4427
     * 3  - Checksum
4428
     */
4429
0
    gps_mask_t mask = ONLINE_SET;
4430
0
    double heading;
4431
4432
0
    if ('\0' == field[1][0] ||
4433
0
        '\0' == field[2][0]) {
4434
        // no data
4435
0
        return mask;
4436
0
    }
4437
0
    if ('V' == field[2][0]) {
4438
        // invalid data
4439
        // ignore A, E, M and S for now
4440
0
        return mask;
4441
0
    }
4442
0
    heading = safe_atof(field[1]);
4443
0
    if ((0.0 > heading) ||
4444
0
        (360.0 < heading)) {
4445
        // bad data
4446
0
        return mask;
4447
0
    }
4448
4449
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
4450
0
             "NMEA0183: $xxTHS heading %lf mode %s\n",
4451
0
             heading, field[2]);
4452
4453
0
    return mask;
4454
0
}
4455
4456
static gps_mask_t processTNTA(int c UNUSED, char *field[],
4457
                              struct gps_device_t *session)
4458
0
{
4459
    /*
4460
     * Proprietary sentence for iSync GRClok/LNRClok.
4461
4462
     $PTNTA,20000102173852,1,T4,,,6,1,0*32
4463
4464
     1. Date/time in format year, month, day, hour, minute, second
4465
     2. Oscillator quality 0:warming up, 1:freerun, 2:disciplined.
4466
     3. Always T4. Format indicator.
4467
     4. Interval ppsref-ppsout in [ns]. Blank if no ppsref.
4468
     5. Fine phase comparator in approx. [ns]. Always close to -500 or
4469
        +500 if not disciplined. Blank if no ppsref.
4470
     6. iSync Status.  0:warming up or no light, 1:tracking set-up,
4471
        2:track to PPSREF, 3:synch to PPSREF, 4:Free Run. Track OFF,
4472
        5:FR. PPSREF unstable, 6:FR. No PPSREF, 7:FREEZE, 8:factory
4473
        used, 9:searching Rb line
4474
     7. GPS messages indicator. 0:do not take account, 1:take account,
4475
        but no message, 2:take account, partially ok, 3:take account,
4476
        totally ok.
4477
     8. Transfer quality of date/time. 0:no, 1:manual, 2:GPS, older
4478
        than x hours, 3:GPS, fresh.
4479
4480
     */
4481
0
    gps_mask_t mask = ONLINE_SET;
4482
4483
0
    if (0 == strcmp(field[3], "T4")) {
4484
0
        struct oscillator_t *osc = &session->gpsdata.osc;
4485
0
        unsigned int quality = atoi(field[2]);
4486
0
        unsigned int delta = atoi(field[4]);
4487
0
        unsigned int fine = atoi(field[5]);
4488
0
        unsigned int status = atoi(field[6]);
4489
0
        char deltachar = field[4][0];
4490
4491
0
        osc->running = (0 < quality);
4492
0
        osc->reference = (deltachar && (deltachar != '?'));
4493
0
        if (osc->reference) {
4494
0
            if (500 > delta) {
4495
0
                osc->delta = fine;
4496
0
            } else {
4497
0
                osc->delta = ((delta < 500000000) ? delta : 1000000000 - delta);
4498
0
            }
4499
0
        } else {
4500
0
            osc->delta = 0;
4501
0
        }
4502
0
        osc->disciplined = ((quality == 2) && (status == 3));
4503
0
        mask |= OSCILLATOR_SET;
4504
4505
0
        GPSD_LOG(LOG_DATA, &session->context->errout,
4506
0
                 "NMEA0183: PTNTA,T4: quality=%s, delta=%s, fine=%s,"
4507
0
                 "status=%s\n",
4508
0
                 field[2], field[4], field[5], field[6]);
4509
0
    }
4510
0
    return mask;
4511
0
}
4512
4513
static gps_mask_t processTNTHTM(int c UNUSED, char *field[],
4514
                                struct gps_device_t *session)
4515
0
{
4516
    /*
4517
     * Proprietary sentence for True North Technologies Magnetic Compass.
4518
     * This may also apply to some Honeywell units since they may have been
4519
     * designed by True North.
4520
4521
     $PTNTHTM,14223,N,169,N,-43,N,13641,2454*15
4522
4523
     HTM,x.x,a,x.x,a,x.x,a,x.x,x.x*hh<cr><lf>
4524
     Fields in order:
4525
     1. True heading (compass measurement + deviation + variation)
4526
     2. magnetometer status character:
4527
     C = magnetometer calibration alarm
4528
     L = low alarm
4529
     M = low warning
4530
     N = normal
4531
     O = high warning
4532
     P = high alarm
4533
     V = magnetometer voltage level alarm
4534
     3. pitch angle
4535
     4. pitch status character - see field 2 above
4536
     5. roll angle
4537
     6. roll status character - see field 2 above
4538
     7. dip angle
4539
     8. relative magnitude horizontal component of earth's magnetic field
4540
     *hh          mandatory nmea_checksum
4541
4542
     By default, angles are reported as 26-bit integers: weirdly, the
4543
     technical manual says either 0 to 65535 or -32768 to 32767 can
4544
     occur as a range.
4545
     */
4546
0
    gps_mask_t mask = ONLINE_SET;
4547
4548
    // True heading
4549
0
    session->gpsdata.attitude.heading = safe_atof(field[1]);
4550
0
    session->gpsdata.attitude.mag_st = *field[2];
4551
0
    session->gpsdata.attitude.pitch = safe_atof(field[3]);
4552
0
    session->gpsdata.attitude.pitch_st = *field[4];
4553
0
    session->gpsdata.attitude.roll = safe_atof(field[5]);
4554
0
    session->gpsdata.attitude.roll_st = *field[6];
4555
0
    session->gpsdata.attitude.dip = safe_atof(field[7]);
4556
0
    session->gpsdata.attitude.mag_x = safe_atof(field[8]);
4557
0
    mask |= (ATTITUDE_SET);
4558
4559
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
4560
0
             "NMEA0183: $PTNTHTM heading %lf (%c).\n",
4561
0
             session->gpsdata.attitude.heading,
4562
0
             session->gpsdata.attitude.mag_st);
4563
0
    return mask;
4564
0
}
4565
4566
// GPS Text message
4567
static gps_mask_t processTXT(int count, char *field[],
4568
                             struct gps_device_t *session)
4569
0
{
4570
    /*
4571
     * $GNTXT,01,01,01,PGRM inv format*2A
4572
     * 1                   Number of sentences for full data
4573
     * 1                   Sentence 1 of 1
4574
     * 01                  Message type
4575
     *       00 - error
4576
     *       01 - warning
4577
     *       02 - notice
4578
     *       07 - user
4579
     * PGRM inv format     ASCII text
4580
     *
4581
     * Can occur with talker IDs:
4582
     *   BD (Beidou),
4583
     *   GA (Galileo),
4584
     *   GB (Beidou),
4585
     *   GI (IRNSS
4586
     *   GL (GLONASS),
4587
     *   GN (GLONASS, any combination GNSS),
4588
     *   GP (GPS, SBAS, QZSS),
4589
     *   GQ (QZSS).
4590
     *   PQ (QZSS). Quectel Quirk
4591
     *   QZ (QZSS).
4592
     */
4593
0
    gps_mask_t mask = ONLINE_SET;
4594
0
    int msgType = 0;
4595
0
    char *msgType_txt = "Unknown";
4596
4597
0
    if (5 != count) {
4598
0
      return mask;
4599
0
    }
4600
4601
0
    msgType = atoi(field[3]);
4602
4603
0
    switch ( msgType ) {
4604
0
    case 0:
4605
0
        msgType_txt = "Error";
4606
0
        break;
4607
0
    case 1:
4608
0
        msgType_txt = "Warning";
4609
0
        break;
4610
0
    case 2:
4611
0
        msgType_txt = "Notice";
4612
0
        break;
4613
0
    case 7:
4614
0
        msgType_txt = "User";
4615
0
        break;
4616
0
    }
4617
4618
    // maximum text length unknown, guess 80
4619
0
    GPSD_LOG(LOG_WARN, &session->context->errout,
4620
0
             "NMEA0183: TXT: %.10s: %.80s\n",
4621
0
             msgType_txt, field[4]);
4622
0
    return mask;
4623
0
}
4624
4625
/* process xxVTG
4626
 *     $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K
4627
 *     $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K,A
4628
 *
4629
 * where:
4630
 *         1,2     054.7,T      True track made good (degrees)
4631
 *         3,4     034.4,M      Magnetic track made good
4632
 *         5,6     005.5,N      Ground speed, knots
4633
 *         7,8     010.2,K      Ground speed, Kilometers per hour
4634
 *         9       A            Mode Indicator (optional)
4635
 *                                see faa_mode() for possible mode values
4636
 *
4637
 * see also:
4638
 * https://gpsd.gitlab.io/gpsd/NMEA.html#_vtg_track_made_good_and_ground_speed
4639
 */
4640
static gps_mask_t processVTG(int count,
4641
                             char *field[],
4642
                             struct gps_device_t *session)
4643
0
{
4644
0
    gps_mask_t mask = ONLINE_SET;
4645
4646
0
    if( (field[1][0] == '\0') || (field[5][0] == '\0')){
4647
0
        return mask;
4648
0
    }
4649
4650
    // ignore empty/missing field, fix mode of last resort
4651
0
    if ((9 < count) &&
4652
0
        ('\0' != field[9][0])) {
4653
4654
0
        switch (field[9][0]) {
4655
0
        case 'A':
4656
            // Autonomous, 2D or 3D fix
4657
0
            FALLTHROUGH
4658
0
        case 'D':
4659
            // Differential, 2D or 3D fix
4660
            // MODE_SET here causes issues
4661
            // mask |= MODE_SET;
4662
0
            break;
4663
0
        case 'E':
4664
            // Estimated, DR only
4665
0
            FALLTHROUGH
4666
0
        case 'N':
4667
            // Not Valid
4668
            // MODE_SET here causes issues
4669
            // mask |= MODE_SET;
4670
            // nothing to use here, leave
4671
0
            return mask;
4672
0
        default:
4673
            // Huh?
4674
0
            break;
4675
0
        }
4676
0
    }
4677
4678
    // set true track
4679
0
    session->newdata.track = safe_atof(field[1]);
4680
0
    mask |= TRACK_SET;
4681
4682
    // set magnetic variation
4683
0
    if ('\0' != field[3][0]) {  // ignore empty fields
4684
0
        session->newdata.magnetic_track = safe_atof(field[3]);
4685
0
        mask |= MAGNETIC_TRACK_SET;
4686
0
    }
4687
4688
0
    session->newdata.speed = safe_atof(field[5]) * KNOTS_TO_MPS;
4689
0
    mask |= SPEED_SET;
4690
4691
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
4692
0
             "NMEA0183: VTG: course(T)=%.2f, course(M)=%.2f, speed=%.2f",
4693
0
             session->newdata.track, session->newdata.magnetic_track,
4694
0
             session->newdata.speed);
4695
0
    return mask;
4696
0
}
4697
4698
/* precessXDR() - process transducer messages
4699
 */
4700
static gps_mask_t processXDR(int count, char *field[],
4701
                             struct gps_device_t *session)
4702
0
{
4703
    /*
4704
     * $APXDR,A,0.135,D,PTCH*7C
4705
     * $APXDR,A,3.861,D,ROLL*65
4706
     *
4707
     * 1) Transducer type
4708
     *     A = Angular Displacement
4709
     * 2) Measurement data
4710
     * 3) Units of measure
4711
     *     D = degrees
4712
     * 4) Transducer ID
4713
     *     can be repeated...
4714
     * )  checksum
4715
     *
4716
     * TODO: stacked measurements, like the TNT Revolution:
4717
  $HCXDR,A,177,D,PITCH,A,-40,D,ROLL,G,358,,MAGX,G,2432,,MAGY,G,-8974,,MAGZ*47
4718
     *  the bund_zeus:
4719
  $IIXDR,C,,C,AIRTEMP,A,-3.0,D,HEEL,A,3.7,D,TRIM,P,,B,BARO,A,-4.2,D,RUDDER*28
4720
     *
4721
     */
4722
0
    gps_mask_t mask = ONLINE_SET;
4723
0
    unsigned int i;
4724
0
    unsigned int num_meas = count / 4;
4725
4726
0
    for (i = 0; i < num_meas; i++) {
4727
0
        double data = 0.0;
4728
0
        unsigned int j = i * 4;
4729
4730
0
        if ('\0' == field[j + 2][0]) {
4731
            // no data, skip it
4732
0
            GPSD_LOG(LOG_PROG, &session->context->errout,
4733
0
                     "NMEA0183: $xxXDR: Type %.10s Data %.10s Units %.10s "
4734
0
                     "ID %.10s\n",
4735
0
                     field[j + 1], field[j + 2], field[j + 3], field[j + 4]);
4736
0
            continue;
4737
0
        }
4738
4739
0
        data = safe_atof(field[j + 2]);
4740
4741
0
        switch (field[j + 1][0]) {
4742
0
        case 'A':
4743
            // angles
4744
0
            if ('D' != field[j + 3][0]) {
4745
                // not degrees
4746
0
                continue;
4747
0
            }
4748
0
            if (0 == strncmp( "HEEL", field[j + 4], 10)) {
4749
                // session->gpsdata.attitude.roll = data;
4750
                // mask |= ATTITUDE_SET;
4751
0
            } else if (0 == strncmp( "PTCH", field[j + 4], 10) ||
4752
0
                0 == strncmp( "PITCH", field[j + 4], 10)) {
4753
0
                session->gpsdata.attitude.pitch = data;
4754
0
                mask |= ATTITUDE_SET;
4755
0
            } else if (0 == strncmp( "ROLL", field[j + 4], 10)) {
4756
0
                session->gpsdata.attitude.roll = data;
4757
0
                mask |= ATTITUDE_SET;
4758
0
            } else if (0 == strncmp( "RUDDER", field[j + 4], 10)) {
4759
                // session->gpsdata.attitude.roll = data;
4760
                // mask |= ATTITUDE_SET;
4761
0
            } else if (0 == strncmp( "TRIM", field[j + 4], 10)) {
4762
                // session->gpsdata.attitude.roll = data;
4763
                // mask |= ATTITUDE_SET;
4764
0
            }
4765
            // else, unknown
4766
0
            break;
4767
0
        case 'G':
4768
            // G: TODO: G,358,,MAGX,G,2432,,MAGY,G,-8974,,MAGZ*47
4769
            // oddly field[j + 3][0] is NUL...
4770
4771
0
            if (0 == strncmp( "MAGX", field[j + 4], 10)) {
4772
                // unknown scale
4773
0
                session->gpsdata.attitude.mag_x = data;
4774
0
                mask |= ATTITUDE_SET;
4775
0
            } else if (0 == strncmp( "MAGY", field[j + 4], 10)) {
4776
                // unknown scale
4777
0
                session->gpsdata.attitude.mag_y = data;
4778
0
                mask |= ATTITUDE_SET;
4779
0
            } else if (0 == strncmp( "MAGZ", field[j + 4], 10)) {
4780
                // unknown scale
4781
0
                session->gpsdata.attitude.mag_z = data;
4782
0
                mask |= ATTITUDE_SET;
4783
0
            }
4784
0
            break;
4785
0
        case 'C':
4786
            // C,,C,AIRTEMP,
4787
0
            FALLTHROUGH
4788
0
        case 'P':
4789
            // Pressure: TODO: P,,B,BARO
4790
0
            FALLTHROUGH
4791
0
        default:
4792
0
            break;
4793
0
        }
4794
4795
0
        GPSD_LOG(LOG_PROG, &session->context->errout,
4796
0
                 "NMEA0183: $xxXDR: Type %.10s Data %f Units %.10s ID %.10s\n",
4797
0
                 field[j + 1], data, field[j + 3], field[j + 4]);
4798
0
    }
4799
0
    return mask;
4800
0
}
4801
4802
// Time & Date
4803
static gps_mask_t processZDA(int c UNUSED, char *field[],
4804
                               struct gps_device_t *session)
4805
0
{
4806
    /*
4807
     * $GPZDA,160012.71,11,03,2004,-1,00*7D
4808
     * 1) UTC time (hours, minutes, seconds, may have fractional subsecond)
4809
     * 2) Day, 01 to 31
4810
     * 3) Month, 01 to 12
4811
     * 4) Year (4 digits)
4812
     * 5) Local zone description, 00 to +- 13 hours
4813
     * 6) Local zone minutes description, apply same sign as local hours
4814
     * 7) Checksum
4815
     *
4816
     * Note: some devices, like the u-blox ANTARIS 4h, are known to ship ZDAs
4817
     * with some fields blank under poorly-understood circumstances (probably
4818
     * when they don't have satellite lock yet).
4819
     */
4820
0
    gps_mask_t mask = ONLINE_SET;
4821
0
    int year, mon, mday, century;
4822
0
    char ts_buf[TIMESPEC_LEN];
4823
4824
0
    if ('\0' == field[1][0] ||
4825
0
        '\0' == field[2][0] ||
4826
0
        '\0' == field[3][0] ||
4827
0
        '\0' == field[4][0]) {
4828
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
4829
0
                 "NMEA0183: ZDA fields are empty\n");
4830
0
        return mask;
4831
0
    }
4832
4833
0
    if (0 != merge_hhmmss(field[1], session)) {
4834
        // bad time
4835
0
        return mask;
4836
0
    }
4837
4838
    /*
4839
     * We didn't register fractional time here because we wanted to leave
4840
     * ZDA out of end-of-cycle detection. Some devices sensibly emit it only
4841
     * when they have a fix, so watching for it can make them look
4842
     * like they have a variable fix reporting cycle.  But later thought
4843
     * was to not throw out good data because it is inconvenient.
4844
     */
4845
0
    year = atoi(field[4]);
4846
0
    mon = atoi(field[3]);
4847
0
    mday = atoi(field[2]);
4848
0
    century = year - year % 100;
4849
0
    if (1900 > year  ||
4850
0
        2200 < year) {
4851
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
4852
0
                 "NMEA0183: malformed ZDA year: %s\n",  field[4]);
4853
0
    } else if (1 > mon ||
4854
0
               12 < mon) {
4855
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
4856
0
                 "NMEA0183: malformed ZDA month: %s\n",  field[3]);
4857
0
    } else if (1 > mday ||
4858
0
               31 < mday) {
4859
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
4860
0
                 "NMEA0183: malformed ZDA day: %s\n",  field[2]);
4861
0
    } else {
4862
0
        gpsd_century_update(session, century);
4863
0
        session->nmea.date.tm_year = year - 1900;
4864
0
        session->nmea.date.tm_mon = mon - 1;
4865
0
        session->nmea.date.tm_mday = mday;
4866
0
        session->newdata.time = gpsd_utc_resolve(session);
4867
0
        register_fractional_time(field[0], field[1], session);
4868
0
        mask = TIME_SET;
4869
0
    }
4870
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
4871
0
         "NMEA0183: ZDA time %s\n",
4872
0
          timespec_str(&session->newdata.time, ts_buf, sizeof(ts_buf)));
4873
0
    return mask;
4874
0
}
4875
4876
/**************************************************************************
4877
 *
4878
 * Entry points begin here
4879
 *
4880
 **************************************************************************/
4881
4882
// parse an NMEA sentence, unpack it into a session structure
4883
gps_mask_t nmea_parse(char *sentence, struct gps_device_t * session)
4884
0
{
4885
0
    typedef gps_mask_t(*nmea_decoder) (int count, char *f[],
4886
0
                                       struct gps_device_t * session);
4887
0
    static struct
4888
0
    {
4889
0
        char *name;
4890
0
        char *name1;            // 2nd field to match, as is $PSTI,030
4891
0
        int nf;                 // minimum number of fields required to parse
4892
0
        bool cycle_continue;    // cycle continuer?
4893
0
        nmea_decoder decoder;
4894
0
    } nmea_phrase[NMEA_NUM] = {
4895
0
        {"PGLOR", NULL, 2,  false, processPGLOR},  // Android something...
4896
0
        {"PGRMB", NULL, 0,  false, NULL},     // ignore Garmin DGPS Beacon Info
4897
0
        {"PGRMC", NULL, 0,  false, NULL},        // ignore Garmin Sensor Config
4898
0
        {"PGRME", NULL, 7,  false, processPGRME},
4899
0
        {"PGRMF", NULL, 15, false, processPGRMF},  // Garmin GPS Fix Data
4900
0
        {"PGRMH", NULL, 0,  false, NULL},     // ignore Garmin Aviation Height
4901
0
        {"PGRMI", NULL, 0,  false, NULL},          // ignore Garmin Sensor Init
4902
0
        {"PGRMM", NULL, 2,  false, processPGRMM},  // Garmin Map Datum
4903
0
        {"PGRMO", NULL, 0,  false, NULL},     // ignore Garmin Sentence Enable
4904
0
        {"PGRMT", NULL, 10, false, processPGRMT},  // Garmin Sensor Info
4905
0
        {"PGRMV", NULL, 4,  false, processPGRMV},  // Garmin 3D Velocity Info
4906
0
        {"PGRMZ", NULL, 4,  false, processPGRMZ},
4907
            /*
4908
             * Basic sentences must come after the PG* ones, otherwise
4909
             * Garmins can get stuck in a loop that looks like this:
4910
             *
4911
             * 1. A Garmin GPS in NMEA mode is detected.
4912
             *
4913
             * 2. PGRMC is sent to reconfigure to Garmin binary mode.
4914
             *    If successful, the GPS echoes the phrase.
4915
             *
4916
             * 3. nmea_parse() sees the echo as RMC because the talker
4917
             *    ID is ignored, and fails to recognize the echo as
4918
             *    PGRMC and ignore it.
4919
             *
4920
             * 4. The mode is changed back to NMEA, resulting in an
4921
             *    infinite loop.
4922
             */
4923
0
        {"AAM", NULL, 0,  false, NULL},    // ignore Waypoint Arrival Alarm
4924
0
        {"ACCURACY", NULL, 1,  true, processACCURACY},
4925
0
        {"ACN", NULL, 0,  false, NULL},    // Alert Command, 4.10+
4926
0
        {"ALC", NULL, 0,  false, NULL},    // Cyclic Alert List, 4.10+
4927
0
        {"ALF", NULL, 0,  false, NULL},    // Alert Sentence, 4.10+
4928
0
        {"ALM", NULL, 0,  false, NULL},    // GPS Almanac Data
4929
0
        {"APB", NULL, 0,  false, NULL},    // Autopilot Sentence B
4930
0
        {"ACF", NULL, 0,  false, NULL},    // Alert Command Refused, 4.10+
4931
0
        {"AVR", NULL, 0,  false, NULL},    // Same as $PTNL,AVR
4932
0
        {"BOD", NULL, 0,  false, NULL},    // Bearing Origin to Destination
4933
        // Bearing & Distance to Waypoint, Great Circle
4934
0
        {"BWC", NULL, 12, false, processBWC},
4935
0
        {"DBT", NULL, 7,  false, processDBT},  // depth
4936
0
        {"DPT", NULL, 4,  false, processDPT},  // depth
4937
0
        {"DTM", NULL, 2,  false, processDTM},  // datum
4938
0
        {"EPV", NULL, 0,  false, NULL},     // Command/report Prop Value, 4.10+
4939
0
        {"GBS", NULL, 7,  false, processGBS},  // GNSS Sat Fault Detection
4940
0
        {"GGA", NULL, 13, false, processGGA},  // GPS fix data
4941
0
        {"GGK", NULL, 0,  false, NULL},        // Same as $PTNL,GGK
4942
0
        {"GGQ", NULL, 0,  false, NULL},        // Leica Position
4943
0
        {"GLC", NULL, 0,  false, NULL},        // Geographic Position, LoranC
4944
0
        {"GLL", NULL, 7,  true, processGLL},   // Position, Lat/Lon
4945
0
        {"GMP", NULL, 0,  false, NULL},        // Map Projection
4946
0
        {"GNS", NULL, 13, false, processGNS},  // GNSS fix data
4947
0
        {"GRS", NULL, 4,  false, processGRS},  // GNSS Range Residuals
4948
0
        {"GSA", NULL, 18, false, processGSA},  // DOP and Active sats
4949
0
        {"GST", NULL, 8,  false, processGST},  // Pseudorange error stats
4950
0
        {"GSV", NULL, 4,  false, processGSV},  // Sats in view
4951
0
        {"HCR", NULL, 0,  false, NULL},        // Heading Correction, 4.10+
4952
        // Heading, Deviation and Variation
4953
0
        {"HDG", NULL, 0,  false, processHDG},
4954
0
        {"HDM", NULL, 3,  false, processHDM},   // $APHDM, Magnetic Heading
4955
0
        {"HDT", NULL, 1,  false, processHDT},   // Heading true
4956
        // Hell Andle, Roll Period, Roll Amplitude.  NMEA 4.10+
4957
0
        {"HRM", NULL, 0,  false, NULL},
4958
0
        {"HRP", NULL, 0, false, NULL},       // Serpentrio Headinf, Roll, Pitch
4959
0
        {"HWBIAS", NULL, 0, false, NULL},       // Unknown HuaWei sentence
4960
0
        {"LLK", NULL, 0, false, NULL},          // Leica local pos and GDOP
4961
0
        {"LLQ", NULL, 0, false, NULL},          // Leica local pos and quality
4962
0
        {"MLA", NULL, 0,  false, NULL},         // GLONASS Almana Data
4963
0
        {"MOB", NULL, 0,  false, NULL},         // Man Overboard, NMEA 4.10+
4964
0
        {"MSS", NULL, 0,  false, NULL},         // beacon receiver status
4965
0
        {"MTW", NULL, 3,  false, processMTW},   // Water Temperature
4966
0
        {"MWD", NULL, 0,  false, processMWD},   // Wind Direction and Speed
4967
0
        {"MWV", NULL, 0,  false, processMWV},   // Wind Speed and Angle
4968
0
        {"OHPR", NULL, 18, false, NULL},        // Oceanserver, not supported
4969
0
        {"OSD", NULL, 0,  false, NULL},             // ignore Own Ship Data
4970
        // general handler for Ashtech
4971
0
        {"PASHR", NULL, 3, false, processPASHR},
4972
        // Airoha proprietary
4973
0
        {"PAIR001", NULL, 3, false, processPAIR001},  // ACK/NAK
4974
0
        {"PAIR010", NULL, 5, false, processPAIR010},  // Request Aiding
4975
4976
0
        {"PEMT", NULL, 5, false, NULL},               // Evermore proprietary
4977
        // Furuno proprietary
4978
0
        {"PERDACK", NULL, 4, false, NULL},            // ACK
4979
        // {"PERDAPI", NULL, 3, false, NULL},         // Config Send
4980
0
        {"PERDCRD", NULL, 15, false, NULL},           // NLOSMASK?
4981
0
        {"PERDCRG", "DCR", 6, false, NULL},           // QZSS DC report
4982
0
        {"PERDCRJ", "FREQ", 9, false, NULL},          // Jamming Status
4983
0
        {"PERDCRP", NULL, 9, false, NULL},            // Position
4984
0
        {"PERDCRQ", NULL, 11, false, NULL},           // Galileo SAR
4985
0
        {"PERDCRW", "TPS1", 8, false, NULL},          // Time
4986
0
        {"PERDCRX", "TPS2", 12, false, NULL},         // PPS
4987
0
        {"PERDCRY", "TPS3", 11, false, NULL},         // Position Mode
4988
0
        {"PERDCRZ", "TPS4", 13, false, NULL},         // GCLK
4989
0
        {"PERDMSG", NULL, 3, false, NULL},            // Message
4990
0
        {"PERDSYS", "ANTSEL", 5, false, NULL},        // Antenna
4991
0
        {"PERDSYS", "FIXSESSION", 5, false, NULL},    // Fix Session
4992
0
        {"PERDSYS", "GPIO", 3, false, NULL},          // GPIO
4993
0
        {"PERDSYS", "VERSION", 6, false, NULL},       // Version
4994
        // Jackson Labs proprietary
4995
0
        {"PJLTS", NULL, 11,  false, NULL},            // GPSDO status
4996
0
        {"PJLTV", NULL, 4,  false, NULL},             // Time and 3D velocity
4997
        // GPS-320FW -- $PLCS
4998
0
        {"PMGNST", NULL, 8, false, processPMGNST},    // Magellan Status
4999
        // MediaTek proprietary, EOL.  Replaced by Airoha
5000
0
        {"PMTK001", NULL, 3, false, processPMTK001},  // ACK/NAK
5001
0
        {"PMTK010", NULL, 2, false, NULL},            // System Message
5002
0
        {"PMTK011", NULL, 2, false, NULL},            // Text Message
5003
0
        {"PMTK424", NULL, 3, false, processPMTK424},
5004
0
        {"PMTK705", NULL, 4, false, processPMTK705},
5005
        // MediaTek/Trimble Satellite Channel Status
5006
0
        {"PMTKCHN", NULL, 0, false, NULL},
5007
5008
        // MTK-3301 -- $POLYN
5009
5010
        // Quectel proprietary
5011
0
        {"PQTMCFGEINSMSGERROR", NULL, 1, false, processPQxERR},      // Error
5012
0
        {"PQTMCFGEINSMSGOK", NULL, 1, false, processPQxOK},          // OK
5013
0
        {"PQTMCFGORIENTATIONERROR", NULL, 1, false, processPQxERR},  // Error
5014
0
        {"PQTMCFGORIENTATION", NULL, 3, false, NULL},       // Orientation
5015
0
        {"PQTMCFGORIENTATIONOK", NULL, 1, false, processPQxOK},      // OK
5016
0
        {"PQTMCFGWHEELTICKERROR", NULL, 1, false, processPQxERR},    // Error
5017
0
        {"PQTMCFGWHEELTICKOK", NULL, 1, false, processPQxOK},        // OK
5018
0
        {"PQTMGPS", NULL, 14, false, processPQTMGPS},  // GPS Status
5019
0
        {"PQTMIMU", NULL, 10, false, processPQTMIMU},  // IMU Raw Data
5020
0
        {"PQTMINS", NULL, 11, false, processPQTMINS},  // INS Results
5021
0
        {"PQTMQMPTERROR", NULL, 1, false, processPQxERR},       // Error
5022
0
        {"PQTMQMPT", NULL, 2, false, NULL},            // Meters / tick
5023
0
        {"PQTMVEHMSG", NULL, 2, false, NULL},          // Vehicle Info
5024
0
        {"PQTMVER", NULL, 4, false, processPQTMVER},   // Firmware info
5025
5026
0
        {"PQVERNO", NULL, 5, false, processPQVERNO},   // Version
5027
        // smart watch sensors, Yes: space!
5028
0
        {"PRHS ", NULL, 2,  false, processPRHS},
5029
0
        {"PRWIZCH", NULL, 0, false, NULL},          // Rockwell Channel Status
5030
0
        {"PSRF140", NULL, 0, false, NULL},          // SiRF ephemeris
5031
0
        {"PSRF150", NULL, 0, false, NULL},          // SiRF flow control
5032
0
        {"PSRF151", NULL, 0, false, NULL},          // SiRF Power
5033
0
        {"PSRF152", NULL, 0, false, NULL},          // SiRF ephemeris
5034
0
        {"PSRF155", NULL, 0, false, NULL},          // SiRF proprietary
5035
0
        {"PSRFEPE", NULL, 7, false, processPSRFEPE},  // SiRF Estimated Errors
5036
5037
        /* Serpentrio
5038
         * $PSSN,HRP  -- Heading Pitch, Roll
5039
         * $PSSN,RBD  -- Rover-Base Direction
5040
         * $PSSN,RBP  -- Rover-Base Position
5041
         * $PSSN,RBV  -- Rover-Base Velocity
5042
         * $PSSN,SNC  -- NTRIP Client Status
5043
         * $PSSN,TFM  -- RTCM coordinate transform
5044
         */
5045
0
        {"PSSN", NULL, 0, false, NULL},          // $PSSN
5046
5047
        /*
5048
         * Skytraq sentences take this format:
5049
         * $PSTI,type[,val[,val]]*CS
5050
         * type is a 2 or 3 digit subsentence type
5051
         *
5052
         * Note: these sentences can be at least 105 chars long.
5053
         * That violates the NMEA 3.01 max of 82.
5054
         */
5055
        // 1 PPS Timing report ID
5056
0
        {"PSTI", "000", 4, false, NULL},
5057
        // Active Antenna Status Report
5058
0
        {"PSTI", "001", 2, false, NULL},
5059
        // GPIO 10 event-triggered time & position stamp.
5060
0
        {"PSTI", "005", 2, false, NULL},
5061
        //  Recommended Minimum 3D GNSS Data
5062
0
        {"PSTI", "030", 16, false, processPSTI030},
5063
        // RTK Baseline
5064
0
        {"PSTI", "032", 16, false, processPSTI032},
5065
        // RTK RAW Measurement Monitoring Data
5066
0
        {"PSTI", "033", 27, false,  processPSTI033},
5067
        // RTK Baseline Data of Rover Moving Base Receiver
5068
0
        {"PSTI", "035", 8, false, processPSTI035},
5069
        // Heading, Pitch and Roll Messages of vehicle
5070
0
        {"PSTI", "036", 2, false, processPSTI036},
5071
        // $PSTM ST Micro STA8088xx/STA8089xx/STA8090xx
5072
0
        {"PSTM", NULL, 0, false, NULL},
5073
        /* Kongsberg Seatex AS. Seapath 320
5074
         * $PSXN,20,horiz-qual,hgt-qual,head-qual,rp-qual*csum
5075
         * $PSXN,21,event*csum
5076
         * $PSXN,22,gyro-calib,gyro-offs*csum
5077
         * $PSXN,23,roll,pitch,head,heave*csum
5078
         * $PSXN,24,roll-rate,pitch-rate,yaw-rate,vertical-vel*csum
5079
         */
5080
0
        {"PSXN", NULL, 0, false, NULL},
5081
0
        {"PTFTTXT", NULL, 0, false, NULL},        // unknown uptime
5082
5083
        /* Trimble Propietary
5084
         * $PTNL,AVR
5085
         * $PTNL,GGK
5086
         */
5087
0
        {"PTNI", NULL, 0, false, NULL},
5088
5089
0
        {"PTKM", NULL, 0, false, NULL},           // Robertson RGC12 Gyro
5090
0
        {"PTNLRHVR", NULL, 0, false, NULL},       // Trimble Software Version
5091
0
        {"PTNLRPT", NULL, 0, false, NULL},        // Trimble Serial Port COnfig
5092
0
        {"PTNLRSVR", NULL, 0, false, NULL},       // Trimble Firmware Version
5093
0
        {"PTNLRZD", NULL, 0, false, NULL},        // Extended Time and Date
5094
0
        {"PTNTA", NULL, 8, false, processTNTA},
5095
0
        {"PTNTHTM", NULL, 9, false, processTNTHTM},
5096
0
        {"PUBX", NULL, 0, false, NULL},         // u-blox and Antaris
5097
0
        {"QSM", NULL, 3, false, NULL},          // QZSS DC Report
5098
0
        {"RBD", NULL, 0, false, NULL},       // Serpentrio rover-base direction
5099
0
        {"RBP", NULL, 0, false, NULL},       // Serpentrio rover-base position
5100
0
        {"RBV", NULL, 0, false, NULL},       // Serpentrio rover-base velocity
5101
0
        {"RLM", NULL, 0, false, NULL},       // Return Link Message, NMEA 4.10+
5102
        // ignore Recommended Minimum Navigation Info, waypoint
5103
0
        {"RMB", NULL, 0,  false, NULL},         // Recommended Min Nav Info
5104
0
        {"RMC", NULL, 8,  false, processRMC},   // Recommended Minimum Data
5105
0
        {"ROT", NULL, 3,  false, processROT},   // Rate of Turn
5106
0
        {"RPM", NULL, 0,  false, NULL},         // ignore Revolutions
5107
0
        {"RRT", NULL, 0, false, NULL},     // Report Route Transfer, NMEA 4.10+
5108
0
        {"RSA", NULL, 0,  false, NULL},         // Rudder Sensor Angle
5109
0
        {"RTE", NULL, 0,  false, NULL},         // ignore Routes
5110
0
        {"SM1", NULL, 0, false, NULL},     // SafteyNET, All Ships, NMEA 4.10+
5111
0
        {"SM2", NULL, 0, false, NULL},     // SafteyNET, Coastal, NMEA 4.10+
5112
0
        {"SM3", NULL, 0, false, NULL},     // SafteyNET, Circular, NMEA 4.10+
5113
0
        {"SM4", NULL, 0, false, NULL},     // SafteyNET, Rectangular, NMEA 4.10+
5114
0
        {"SMB", NULL, 0, false, NULL},     // SafteyNET, Msg Body, NMEA 4.10+
5115
0
        {"SPW", NULL, 0, false, NULL},     // Security Password, NMEA 4.10+
5116
0
        {"SNC", NULL, 0, false, NULL},       // Serpentrio NTRIP client status
5117
0
        {"STI", NULL, 2,  false, processSTI},   // $STI  Skytraq
5118
0
        {"TFM", NULL, 0, false, NULL},          // Serpentrio Coord Transform
5119
0
        {"THS", NULL, 0,  false, processTHS},   // True Heading and Status
5120
0
        {"TRL", NULL, 0, false, NULL},     // AIS Xmit offline, NMEA 4.10+
5121
0
        {"TXT", NULL, 5,  false, processTXT},
5122
0
        {"TXTbase", NULL, 0,  false, NULL},     // RTCM 1029 TXT
5123
0
        {"VBW", NULL, 0,  false, NULL},         // Dual Ground/Water Speed
5124
0
        {"VDO", NULL, 0,  false, NULL},         // Own Vessel's Information
5125
0
        {"VDR", NULL, 0,  false, NULL},         // Set and Drift
5126
0
        {"VHW", NULL, 0,  false, NULL},         // Water Speed and Heading
5127
0
        {"VLW", NULL, 0,  false, NULL},         // Dual ground/water distance
5128
0
        {"VTG", NULL, 5,  false, processVTG},   // Course/speed over ground
5129
        // $APXDR, $HCXDR, Transducer measurements
5130
0
        {"XDR", NULL, 5,  false, processXDR},
5131
0
        {"XTE", NULL, 0,  false, NULL},         // Cross-Track Error
5132
0
        {"ZDA", NULL ,4,  false, processZDA},   // Time and Date
5133
0
        {NULL, NULL,  0,  false, NULL},         // no more
5134
0
    };
5135
5136
0
    int count;
5137
0
    gps_mask_t mask = 0;
5138
0
    unsigned i, thistag = 0, lasttag;
5139
0
    char *p, *e;
5140
0
    volatile char *t;
5141
0
    char ts_buf1[TIMESPEC_LEN];
5142
0
    char ts_buf2[TIMESPEC_LEN];
5143
0
    bool skytraq_sti = false;
5144
0
    size_t mlen;
5145
5146
    /*
5147
     * We've had reports that on the Garmin GPS-10 the device sometimes
5148
     * (1:1000 or so) sends garbage packets that have a valid checksum
5149
     * but are like 2 successive NMEA packets merged together in one
5150
     * with some fields lost.  Usually these are much longer than the
5151
     * legal limit for NMEA, so we can cope by just tossing out overlong
5152
     * packets.  This may be a generic bug of all Garmin chipsets.
5153
     */
5154
    // codacy does not like strlen()
5155
0
    mlen = strnlen(sentence, NMEA_MAX + 1);
5156
0
    if (NMEA_MAX < mlen) {
5157
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
5158
0
                 "NMEA0183: Overlong packet of %zd+ chars rejected.\n",
5159
0
                 mlen);
5160
0
        return ONLINE_SET;
5161
0
    }
5162
5163
    // make an editable copy of the sentence
5164
0
    (void)strlcpy((char *)session->nmea.fieldcopy, sentence,
5165
0
                  sizeof(session->nmea.fieldcopy) - 1);
5166
    // discard the checksum part
5167
0
    for (p = (char *)session->nmea.fieldcopy;
5168
0
         ('*' != *p) && (' ' <= *p);) {
5169
0
        ++p;
5170
0
    }
5171
0
    if ('*' == *p) {
5172
0
        *p++ = ',';             // otherwise we drop the last field
5173
0
    }
5174
#ifdef SKYTRAQ_ENABLE_UNUSED
5175
    // $STI is special, no trailing *, or chacksum
5176
    if (0 != strncmp( "STI,", sentence, 4)) {
5177
        skytraq_sti = true;
5178
        *p++ = ',';             // otherwise we drop the last field
5179
    }
5180
#endif
5181
0
    *p = '\0';
5182
0
    e = p;
5183
5184
    // split sentence copy on commas, filling the field array
5185
0
    count = 0;
5186
0
    t = p;                      // end of sentence
5187
0
    p = (char *)session->nmea.fieldcopy + 1;  // beginning of tag, 'G' not '$'
5188
    // while there is a search string and we haven't run off the buffer...
5189
0
    while ((NULL != p) &&
5190
0
           (p <= t)) {
5191
0
        session->nmea.field[count] = p;      // we have a field. record it
5192
0
        if (NULL != (p = strchr(p, ','))) {  // search for the next delimiter
5193
0
            *p = '\0';                       // replace it with a NUL
5194
0
            count++;                         // bump the counters and continue
5195
0
            p++;
5196
0
        }
5197
0
    }
5198
5199
    // point remaining fields at empty string, just in case
5200
0
    for (i = (unsigned int)count; i < NMEA_MAX_FLD; i++) {
5201
0
        session->nmea.field[i] = e;
5202
0
    }
5203
5204
    // sentences handlers will tell us when they have fractional time
5205
0
    session->nmea.latch_frac_time = false;
5206
    // GSA and GSV will set this if more in that series to come.
5207
0
    session->nmea.gsx_more = false;
5208
5209
#ifdef __UNUSED
5210
    // debug
5211
    GPSD_LOG(0, &session->context->errout,
5212
             "NMEA0183: got %s\n", session->nmea.field[0]);
5213
#endif // __UNUSED
5214
5215
    // dispatch on field zero, the sentence tag
5216
0
    for (i = 0; i < NMEA_NUM; ++i) {
5217
0
        char *s = session->nmea.field[0];
5218
5219
        // CODACY #350416, wants explicit numeric end check
5220
0
        if ((NMEA_NUM - 1) <= i ||
5221
0
            NULL == nmea_phrase[i].name) {
5222
0
            mask = ONLINE_SET;
5223
0
            GPSD_LOG(LOG_DATA, &session->context->errout,
5224
0
                     "NMEA0183: Unknown sentence type %s\n",
5225
0
                     session->nmea.field[0]);
5226
0
            break;
5227
0
        }
5228
        // strnlen() to shut up codacy
5229
0
        if (3 == strnlen(nmea_phrase[i].name, 4) &&
5230
0
            !skytraq_sti) {
5231
            // $STI is special
5232
0
            s += 2;             // skip talker ID
5233
0
        }
5234
0
        if (0 != strcmp(nmea_phrase[i].name, s)) {
5235
            // no match
5236
0
            continue;
5237
0
        }
5238
0
        if (NULL != nmea_phrase[i].name1 &&
5239
0
            0 != strcmp(nmea_phrase[i].name1, session->nmea.field[1])) {
5240
            // no match on field 2.  As in $PSTI,030,
5241
0
            continue;
5242
0
        }
5243
        // got a match
5244
0
        if (NULL == nmea_phrase[i].decoder) {
5245
            // no decoder for this sentence
5246
0
            mask = ONLINE_SET;
5247
0
            GPSD_LOG(LOG_DATA, &session->context->errout,
5248
0
                     "NMEA0183: No decoder for sentence type %s\n",
5249
0
                     session->nmea.field[0]);
5250
0
            break;
5251
0
        }
5252
0
        if (count < nmea_phrase[i].nf) {
5253
            // sentence to short
5254
0
            mask = ONLINE_SET;
5255
0
            GPSD_LOG(LOG_DATA, &session->context->errout,
5256
0
                     "NMEA0183: Sentence %s too short\n",
5257
0
                     session->nmea.field[0]);
5258
0
            break;
5259
0
        }
5260
0
        mask = (nmea_phrase[i].decoder)(count, session->nmea.field,
5261
0
                                        session);
5262
0
        session->nmea.cycle_continue = nmea_phrase[i].cycle_continue;
5263
        /*
5264
         * Must force this to be nz, as we're going to rely on a zero
5265
         * value to mean "no previous tag" later.
5266
         */
5267
        // FIXME: this fails on Skytrak, $PSTI,xx, many different xx
5268
0
        thistag = i + 1;
5269
0
        break;
5270
0
    }
5271
5272
    // prevent overaccumulation of sat reports
5273
0
    if (!str_starts_with(session->nmea.field[0] + 2, "GSV")) {
5274
        // This assumes all $xxGSV are contiguous.
5275
0
        if (0 != session->nmea.last_gsv_talker) {
5276
0
            session->nmea.end_gsv_talker = session->nmea.last_gsv_talker;
5277
0
        }
5278
0
        session->nmea.last_gsv_talker = '\0';
5279
0
    }
5280
0
    if (!str_starts_with(session->nmea.field[0] + 2, "GSA")) {
5281
0
        session->nmea.last_gsa_talker = '\0';
5282
0
    }
5283
5284
    // timestamp recording for fixes happens here
5285
0
    if (0 != (mask & TIME_SET)) {
5286
0
        if (0 == session->nmea.date.tm_year &&
5287
0
            0 == session->nmea.date.tm_mday) {
5288
            // special case to time zero
5289
0
            session->newdata.time = (timespec_t){0, 0};
5290
0
        } else {
5291
0
            session->newdata.time = gpsd_utc_resolve(session);
5292
0
        }
5293
5294
0
        GPSD_LOG(LOG_DATA, &session->context->errout,
5295
0
                 "NMEA0183: %s newtime is %s = "
5296
0
                 "%d-%02d-%02dT%02d:%02d:%02d.%03ldZ\n",
5297
0
                 session->nmea.field[0],
5298
0
                 timespec_str(&session->newdata.time, ts_buf1, sizeof(ts_buf1)),
5299
0
                 1900 + session->nmea.date.tm_year,
5300
0
                 session->nmea.date.tm_mon + 1,
5301
0
                 session->nmea.date.tm_mday,
5302
0
                 session->nmea.date.tm_hour,
5303
0
                 session->nmea.date.tm_min,
5304
0
                 session->nmea.date.tm_sec,
5305
0
                 session->nmea.subseconds.tv_nsec / 1000000L);
5306
        /*
5307
         * If we have time and PPS is available, assume we have good time.
5308
         * Because this is a generic driver we don't really have enough
5309
         * information for a sharper test, so we'll leave it up to the
5310
         * PPS code to do its own sanity filtering.
5311
         */
5312
0
        mask |= NTPTIME_IS;
5313
0
    }
5314
5315
    /*
5316
     * The end-of-cycle detector.  This code depends on just one
5317
     * assumption: if a sentence with a timestamp occurs just before
5318
     * start of cycle, then it is always good to trigger a report on
5319
     * that sentence in the future.  For devices with a fixed cycle
5320
     * this should work perfectly, locking in detection after one
5321
     * cycle.  Most split-cycle devices (Garmin 48, for example) will
5322
     * work fine.  Problems will only arise if a a sentence that
5323
     * occurs just before timestamp increments also occurs in
5324
     * mid-cycle, as in the Garmin eXplorist 210; those might jitter.
5325
     */
5326
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
5327
0
             "NMEA0183: %s time %s last %s latch %d cont %d\n",
5328
0
             session->nmea.field[0],
5329
0
             timespec_str(&session->nmea.this_frac_time, ts_buf1,
5330
0
                          sizeof(ts_buf1)),
5331
0
             timespec_str(&session->nmea.last_frac_time, ts_buf2,
5332
0
                          sizeof(ts_buf2)),
5333
0
             session->nmea.latch_frac_time,
5334
0
             session->nmea.cycle_continue);
5335
0
    lasttag = session->nmea.lasttag;
5336
0
    if (session->nmea.gsx_more) {
5337
        // more to come, so ignore for cycle ender
5338
        // appears that GSA and GSV never start a cycle.
5339
0
    } else if (session->nmea.latch_frac_time) {
5340
0
        timespec_t ts_delta;
5341
0
        TS_SUB(&ts_delta, &session->nmea.this_frac_time,
5342
0
                          &session->nmea.last_frac_time);
5343
0
        if (0.01 < fabs(TSTONS(&ts_delta))) {
5344
            // time changed
5345
0
            mask |= CLEAR_IS;
5346
0
            GPSD_LOG(LOG_PROG, &session->context->errout,
5347
0
                     "NMEA0183: %s starts a reporting cycle. lasttag %d\n",
5348
0
                     session->nmea.field[0], lasttag);
5349
            /*
5350
             * Have we seen a previously timestamped NMEA tag?
5351
             * If so, designate as end-of-cycle marker.
5352
             * But not if there are continuation sentences;
5353
             * those get sorted after the last timestamped sentence
5354
             *
5355
             */
5356
0
            if (0 < lasttag &&
5357
0
                false == (session->nmea.cycle_enders[lasttag]) &&
5358
0
                !session->nmea.cycle_continue) {
5359
0
                session->nmea.cycle_enders[lasttag] = true;
5360
                // we might have a (somewhat) reliable end-of-cycle
5361
0
                session->cycle_end_reliable = true;
5362
0
                GPSD_LOG(LOG_PROG, &session->context->errout,
5363
0
                         "NMEA0183: tagged %s as a cycle ender. %u\n",
5364
0
                         nmea_phrase[lasttag - 1].name,
5365
0
                         lasttag);
5366
0
            }
5367
0
        }
5368
0
    } else {
5369
        // ignore multiple sequential, like GSV, GSA
5370
        // extend the cycle to an un-timestamped sentence?
5371
0
        if (true == session->nmea.cycle_enders[lasttag]) {
5372
0
            GPSD_LOG(LOG_PROG, &session->context->errout,
5373
0
                     "NMEA0183: %s is just after a cycle ender. (%s)\n",
5374
0
                     session->nmea.field[0],
5375
0
                     gps_maskdump(mask));
5376
0
            if (0 != (mask & ~ONLINE_SET)) {
5377
                // new data... after cycle ender
5378
0
                mask |= REPORT_IS;
5379
0
            }
5380
0
        }
5381
0
        if (session->nmea.cycle_continue) {
5382
0
            GPSD_LOG(LOG_PROG, &session->context->errout,
5383
0
                     "NMEA0183: %s extends the reporting cycle.\n",
5384
0
                     session->nmea.field[0]);
5385
            // change ender
5386
0
            session->nmea.cycle_enders[lasttag] = false;
5387
0
            session->nmea.cycle_enders[thistag] = true;
5388
            // have a cycle ender
5389
0
            session->cycle_end_reliable = true;
5390
0
        }
5391
0
    }
5392
5393
    // here's where we check for end-of-cycle
5394
0
    if ((session->nmea.latch_frac_time ||
5395
0
         session->nmea.cycle_continue) &&
5396
0
        (true == session->nmea.cycle_enders[thistag]) &&
5397
0
        !session->nmea.gsx_more) {
5398
0
        if (NULL == nmea_phrase[i].name1) {
5399
0
            GPSD_LOG(LOG_PROG, &session->context->errout,
5400
0
                     "NMEA0183: %s ends a reporting cycle.\n",
5401
0
                     session->nmea.field[0]);
5402
0
        } else {
5403
0
            GPSD_LOG(LOG_PROG, &session->context->errout,
5404
0
                     "NMEA0183: %s,%s ends a reporting cycle.\n",
5405
0
                     session->nmea.field[0],
5406
0
                     session->nmea.field[1]);
5407
0
        }
5408
0
        mask |= REPORT_IS;
5409
0
    }
5410
0
    if (session->nmea.latch_frac_time) {
5411
0
        session->nmea.lasttag = thistag;
5412
0
    }
5413
5414
    /* don't downgrade mode if holding previous fix
5415
     * usually because of xxRMC which does not report 2D/3D */
5416
0
    if (MODE_SET == (mask & MODE_SET) &&
5417
0
        MODE_3D == session->gpsdata.fix.mode &&
5418
0
        MODE_NO_FIX != session->newdata.mode &&
5419
0
        (0 != isfinite(session->lastfix.altHAE) ||
5420
0
         0 != isfinite(session->oldfix.altHAE) ||
5421
0
         0 != isfinite(session->lastfix.altMSL) ||
5422
0
         0 != isfinite(session->oldfix.altMSL))) {
5423
0
        session->newdata.mode = session->gpsdata.fix.mode;
5424
0
    }
5425
0
    return mask;
5426
0
}
5427
5428
5429
/* add NMEA checksum to a possibly terminated sentence
5430
 * if \0 terminated adds exactly 5 chars: "*XX\n\n"
5431
 * if *\0 terminated adds exactly 4 chars: "XX\n\n"
5432
 */
5433
void nmea_add_checksum(char *sentence)
5434
0
{
5435
0
    unsigned char sum = '\0';
5436
0
    char c, *p = sentence;
5437
5438
0
    if ('$' == *p ||
5439
0
        '!' == *p) {
5440
0
        p++;
5441
0
    }
5442
0
    while (('*' != (c = *p)) &&
5443
0
           ('\0' != c)) {
5444
0
        sum ^= c;
5445
0
        p++;
5446
0
    }
5447
0
    (void)snprintf(p, 6, "*%02X\r\n", (unsigned)sum);
5448
0
}
5449
5450
// ship a command to the GPS, adding * and correct checksum
5451
ssize_t nmea_write(struct gps_device_t *session, char *buf, size_t len UNUSED)
5452
0
{
5453
0
    (void)strlcpy(session->msgbuf, buf, sizeof(session->msgbuf));
5454
0
    if ('$' == session->msgbuf[0]) {
5455
0
        (void)strlcat(session->msgbuf, "*", sizeof(session->msgbuf));
5456
0
        nmea_add_checksum(session->msgbuf);
5457
0
    } else {
5458
0
        (void)strlcat(session->msgbuf, "\r\n", sizeof(session->msgbuf));
5459
0
    }
5460
    // codacy hates strlen()
5461
0
    session->msgbuflen = strnlen(session->msgbuf, sizeof(session->msgbuf));
5462
0
    return gpsd_write(session, session->msgbuf, session->msgbuflen);
5463
0
}
5464
5465
ssize_t nmea_send(struct gps_device_t * session, const char *fmt, ...)
5466
0
{
5467
0
    char buf[BUFSIZ];
5468
0
    va_list ap;
5469
5470
0
    va_start(ap, fmt);
5471
0
    (void)vsnprintf(buf, sizeof(buf) - 5, fmt, ap);
5472
0
    va_end(ap);
5473
    // codacy hates strlen()
5474
0
    return nmea_write(session, buf, strnlen(buf, sizeof(buf)));
5475
0
}
5476
5477
// vim: set expandtab shiftwidth=4