Coverage Report

Created: 2025-10-13 07:02

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