Coverage Report

Created: 2026-07-16 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gpsd/gpsd-3.27.6~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 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
1.75k
static unsigned char hex2uchar(unsigned char hex) {
42
43
1.75k
    if ('0' <= hex &&
44
1.50k
        '9' >= hex) {
45
525
        return hex - '0';
46
525
    }
47
1.22k
    if ('A' <= hex &&
48
656
        'F' >= hex) {
49
191
        return hex - 'A' + 10;
50
191
    }
51
1.03k
    if ('a' <= hex &&
52
301
        'f' >= hex) {
53
162
        return hex - 'a' + 10;
54
162
    }
55
    // fail
56
873
    return 0;
57
1.03k
}
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
6.10k
{
140
6.10k
    long degrees, minutes;
141
6.10k
    FLT_VOLATILE double full_minutes;
142
6.10k
    char *cp;
143
144
    // Get integer "minutes"
145
6.10k
    minutes = strtol(field, &cp, 10);
146
    // Must have decimal point
147
6.10k
    if ('.' != *cp) {
148
751
        return NAN;
149
751
    }
150
    // Extract degrees (scaled by 100)
151
5.35k
    degrees = minutes / 100;
152
    // Rescale degrees to normal factor of 60
153
5.35k
    minutes -= degrees * (100 - 60);
154
    // Add fractional minutes
155
5.35k
    full_minutes = minutes + safe_atof(cp);
156
    // Scale to degrees & return
157
5.35k
    return full_minutes * (1.0 / 60.0);
158
6.10k
}
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
6.39k
{
179
6.39k
    double lon;
180
6.39k
    double lat;
181
182
6.39k
    if ('\0' == field[0][0] ||
183
4.35k
        '\0' == field[1][0] ||
184
3.77k
        '\0' == field[2][0] ||
185
3.34k
        '\0' == field[3][0]) {
186
3.34k
        return 1;
187
3.34k
    }
188
189
3.05k
    lat = decode_lat_or_lon(field[0]);
190
3.05k
    if ('S' == field[1][0])
191
184
        lat = -lat;
192
193
3.05k
    lon = decode_lat_or_lon(field[2]);
194
3.05k
    if ('W' == field[3][0])
195
1.56k
        lon = -lon;
196
197
3.05k
    if (0 == isfinite(lat) ||
198
2.71k
        0 == isfinite(lon)) {
199
472
        return 2;
200
472
    }
201
202
2.58k
    out->latitude = lat;
203
2.58k
    out->longitude = lon;
204
2.58k
    return 0;
205
3.05k
}
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
2.74k
{
231
2.74k
    int newstatus = STATUS_GPS;
232
233
2.74k
    switch (mode) {
234
548
    case '\0':  // missing
235
548
        FALLTHROUGH
236
643
    case 'O':  // Skytraq ??
237
643
        FALLTHROUGH
238
844
    case 'V':   // Invalid
239
844
        newstatus = STATUS_UNK;
240
844
        break;
241
0
    case 'A':   // Autonomous
242
0
        FALLTHROUGH
243
990
    default:
244
990
        newstatus = STATUS_GPS;
245
990
        break;
246
148
    case 'D':   // Differential
247
148
        newstatus = STATUS_DGPS;
248
148
        break;
249
71
    case 'E':   // Estimated dead reckoning
250
71
        newstatus = STATUS_DR;
251
71
        break;
252
140
    case 'F':   // Float RTK
253
140
        newstatus = STATUS_RTK_FLT;
254
140
        break;
255
229
    case 'M':   // manual input.  Interpret as surveyed to better match GGA
256
229
        newstatus = STATUS_TIME;
257
229
        break;
258
97
    case 'N':   // Data Not Valid
259
        // already handled, for paranoia sake also here
260
97
        newstatus = STATUS_UNK;
261
97
        break;
262
65
    case 'P':   // Precise (NMEA 4+)
263
65
        newstatus = STATUS_DGPS;    // sort of DGPS
264
65
        break;
265
94
    case 'R':   // fixed RTK
266
94
        newstatus = STATUS_RTK_FIX;
267
94
        break;
268
69
    case 'S':   // simulator
269
69
        newstatus = STATUS_SIM;
270
69
        break;
271
2.74k
    }
272
2.74k
    return newstatus;
273
2.74k
}
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
37.3k
#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
1.88k
{
300
1.88k
    int mon;
301
1.88k
    int mday;
302
1.88k
    int year;
303
1.88k
    unsigned i;    // NetBSD complains about signed array index
304
305
1.88k
    if (NULL == ddmmyy ||
306
1.88k
        '\0' == ddmmyy[0]) {
307
118
        return 1;
308
118
    }
309
9.56k
    for (i = 0; i < 6; i++) {
310
        // NetBSD 6 wants the cast
311
8.61k
        if (0 == isdigit((int)ddmmyy[i])) {
312
            // catches NUL and non-digits
313
            // Telit HE910 can set year to "-1" (1999 - 2000)
314
814
            GPSD_LOG(LOG_WARN, &session->context->errout,
315
814
                     "NMEA0183: merge_ddmmyy(%s), malformed date\n",  ddmmyy);
316
814
            return 2;
317
814
        }
318
8.61k
    }
319
    // check for termination
320
955
    if ('\0' != ddmmyy[6]) {
321
        // missing NUL
322
79
        GPSD_LOG(LOG_WARN, &session->context->errout,
323
79
                 "NMEA0183: merge_ddmmyy(%s), malformed date\n",  ddmmyy);
324
79
        return 3;
325
79
    }
326
327
    // should be no defects left to segfault DD()
328
876
    mday = DD(ddmmyy);
329
876
    mon = DD(ddmmyy + 2);
330
876
    year = DD(ddmmyy + 4);
331
332
    // check for century wrap, so 1968 < year < 2069
333
876
    if (69 > year) {
334
518
        year += 100;
335
518
    }
336
337
876
    if (!IN(1, mon, 12)) {
338
169
        GPSD_LOG(LOG_WARN, &session->context->errout,
339
169
                 "NMEA0183: merge_ddmmyy(%s), malformed month\n",  ddmmyy);
340
169
        return 4;
341
169
    }  // else
342
707
    if (!IN(1, mday, 31)) {
343
218
        GPSD_LOG(LOG_WARN, &session->context->errout,
344
218
                 "NMEA0183: merge_ddmmyy(%s), malformed day\n",  ddmmyy);
345
218
        return 5;
346
218
    }  // else
347
348
489
    GPSD_LOG(LOG_DATA, &session->context->errout,
349
489
             "NMEA0183: merge_ddmmyy(%s) sets year %d\n",
350
489
             ddmmyy, year);
351
489
    date->tm_year = year;
352
489
    date->tm_mon = mon - 1;
353
489
    date->tm_mday = mday;
354
    // FIXME: check fractional time!
355
356
489
    GPSD_LOG(LOG_RAW, &session->context->errout,
357
489
             "NMEA0183: merge_ddmmyy(%s) %d %d %d\n",
358
489
             ddmmyy, date->tm_mon, date->tm_mday, date->tm_year);
359
489
    return 0;
360
707
}
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
1.82k
{
369
1.82k
    struct tm date = {0};
370
1.82k
    int retcode;
371
372
1.82k
    retcode = decode_ddmmyy(&date, ddmmyy, session);
373
1.82k
    if (0 != retcode) {
374
        // leave session->nmea untouched.
375
1.33k
        return retcode;
376
1.33k
    }
377
    // check for century wrap ??
378
    // Good time, merge it.
379
489
    session->nmea.date.tm_mday = date.tm_mday;
380
489
    session->nmea.date.tm_mon = date.tm_mon;
381
489
    session->nmea.date.tm_year = date.tm_year;
382
489
    return 0;
383
1.82k
}
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
15.3k
{
392
15.3k
    int old_hour = date->tm_hour;
393
15.3k
    unsigned i;
394
395
15.3k
    if (NULL == hhmmss ||
396
15.3k
        '\0' == hhmmss[0]) {
397
655
        return 1;
398
655
    }
399
86.0k
    for (i = 0; i < 6; i++) {
400
        // NetBSD 6 wants the cast
401
75.1k
        if (0 == isdigit((int)hhmmss[i])) {
402
            // catches NUL and non-digits
403
3.81k
            GPSD_LOG(LOG_WARN, &session->context->errout,
404
3.81k
                     "NMEA0183: decode_hhmmss(%s), malformed time\n",  hhmmss);
405
3.81k
            return 2;
406
3.81k
        }
407
75.1k
    }
408
    // don't check for termination, might have fractional seconds
409
410
10.8k
    date->tm_hour = DD(hhmmss);
411
10.8k
    if (date->tm_hour < old_hour) {  // midnight wrap
412
        // really??
413
0
        date->tm_mday++;
414
0
    }
415
10.8k
    date->tm_min = DD(hhmmss + 2);
416
10.8k
    date->tm_sec = DD(hhmmss + 4);
417
418
10.8k
    if ('.' == hhmmss[6] &&
419
        // NetBSD 6 wants the cast
420
2.56k
        0 != isdigit((int)hhmmss[7])) {
421
        // codacy hates strlen()
422
2.22k
        int sublen = strnlen(hhmmss + 7, 20);
423
2.22k
        i = atoi(hhmmss + 7);
424
2.22k
        *nsec = (long)i * (long)pow(10.0, 9 - sublen);
425
8.65k
    } else {
426
8.65k
        *nsec = 0;
427
8.65k
    }
428
10.8k
    GPSD_LOG(LOG_RAW, &session->context->errout,
429
10.8k
             "NMEA0183: decode_hhmmss(%s) %d %d %d %09ld\n",
430
10.8k
             hhmmss,
431
10.8k
             date->tm_hour, date->tm_min, date->tm_sec, *nsec);
432
433
10.8k
    return 0;
434
14.7k
}
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
10.4k
{
445
10.4k
    struct tm date = {0};
446
10.4k
    timespec_t ts = {0};
447
10.4k
    int retcode;
448
449
10.4k
    retcode = decode_hhmmss(&date, &ts.tv_nsec, hhmmss, session);
450
10.4k
    if (0 != retcode) {
451
        // leave session->nmea untouched.
452
3.99k
        return retcode;
453
3.99k
    }
454
    // Good time, merge it.
455
6.49k
    session->nmea.date.tm_hour = date.tm_hour;
456
6.49k
    session->nmea.date.tm_min = date.tm_min;
457
6.49k
    session->nmea.date.tm_sec = date.tm_sec;
458
6.49k
    session->nmea.subseconds.tv_sec = 0;
459
6.49k
    session->nmea.subseconds.tv_nsec = ts.tv_nsec;
460
461
6.49k
    return 0;
462
10.4k
}
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
4.57k
{
473
4.57k
    struct tm date = {0};
474
4.57k
    struct timespec ts = {0};
475
4.57k
    char ts_buf[TIMESPEC_LEN];
476
477
4.57k
    if (0 != decode_hhmmss(&date, &ts.tv_nsec, fld, session)) {
478
        // invalid time
479
291
        return;
480
291
    }
481
482
4.28k
    ts.tv_sec = date.tm_hour * 3600 + date.tm_min * 60 + date.tm_sec;
483
484
4.28k
    session->nmea.last_frac_time = session->nmea.this_frac_time;
485
4.28k
    session->nmea.this_frac_time = ts;
486
4.28k
    session->nmea.latch_frac_time = true;
487
4.28k
    GPSD_LOG(LOG_DATA, &session->context->errout,
488
4.28k
             "NMEA0183: %s: registers fractional time %s\n",
489
4.28k
             tag,
490
4.28k
             timespec_str(&session->nmea.this_frac_time, ts_buf,
491
4.28k
                          sizeof(ts_buf)));
492
4.28k
}
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
705
#define NMEA_GNSSIDS 7
500
705
#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
705
{
517
705
    unsigned char ubx_sigid = 0;
518
519
705
    if ((NMEA_GNSSIDS > nmea_gnssid) &&
520
705
        (NMEA_SIGIDS > nmea_sigid)) {
521
458
        ubx_sigid = nmea_to_ubx_table[nmea_gnssid][nmea_sigid];
522
458
        if (99 == ubx_sigid) {
523
155
            GPSD_LOG(LOG_WARN, &session->context->errout,
524
155
                     "NMEA0183: Unknown map nmea_gnssid:sigid %u:%d\n",
525
155
                     nmea_gnssid, nmea_sigid);
526
155
            ubx_sigid = 0;
527
155
        }
528
458
    } else {
529
247
        GPSD_LOG(LOG_WARN, &session->context->errout,
530
247
                 "NMEA0183: Unknown nmea_sigid %u with nmea_gnssid %u\n",
531
247
                 nmea_sigid, nmea_gnssid);
532
247
    }
533
534
705
    return ubx_sigid;
535
705
}
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
                         gnssid_t *ubx_gnssid,
552
                         unsigned char *ubx_svid)
553
11.3k
{
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
11.3k
    int nmea2_prn = nmea_satnum;
586
587
11.3k
    *ubx_gnssid = GNSSID_GPS;   // default to ubx_gnssid is GPS
588
11.3k
    *ubx_svid = 0;              // default to unknown ubx_svid
589
590
11.3k
    if (1 > nmea_satnum) {
591
        // uh, oh...
592
333
        nmea2_prn = 0;
593
11.0k
    } else if (0 < nmea_gnssid) {
594
        // this switch handles case where nmea_gnssid is known
595
7.33k
        switch (nmea_gnssid) {
596
3.88k
        case 1:
597
3.88k
            if (33 > nmea_satnum) {
598
                // 1 = GPS       1-32
599
2.98k
                *ubx_gnssid = GNSSID_GPS;
600
2.98k
                *ubx_svid = nmea_satnum;
601
2.98k
            } else if (65 > nmea_satnum) {
602
                // 1 = SBAS      33-64
603
83
                *ubx_gnssid = GNSSID_SBAS;
604
83
                *ubx_svid = nmea_satnum + 87;
605
814
            } else if (137 > nmea_satnum) {
606
                // 3 = Galileo, 101-136, NOT NMEA.  Quectel Querk
607
316
                *ubx_gnssid = GNSSID_GAL;
608
316
                *ubx_svid = nmea_satnum - 100;
609
498
            } else if (152 > nmea_satnum) {
610
                // Huh?
611
48
                *ubx_gnssid = GNSSID_GPS;
612
48
                *ubx_svid = 0;
613
48
                nmea2_prn = 0;
614
450
            } else if (158 > nmea_satnum) {
615
                // 1 = SBAS      152-158
616
35
                *ubx_gnssid = GNSSID_SBAS;
617
35
                *ubx_svid = nmea_satnum;
618
415
            } else if (193 > nmea_satnum) {
619
                // Huh?
620
73
                *ubx_gnssid = GNSSID_GPS;
621
73
                *ubx_svid = 0;
622
73
                nmea2_prn = 0;
623
342
            } else if (200 > nmea_satnum) {
624
                // 1 = QZSS      193-197
625
                // undocumented u-blox goes to 199
626
67
                *ubx_gnssid = GNSSID_QZSS;
627
67
                *ubx_svid = nmea_satnum - 192;
628
275
            } else if (265 > nmea_satnum) {
629
                // 3 = BeiDor, 201-264, NOT NMEA.  Quectel Querk
630
143
                *ubx_gnssid = GNSSID_BD;
631
143
                *ubx_svid = nmea_satnum - 200;
632
143
            } else {
633
                // Huh?
634
132
                *ubx_gnssid = GNSSID_GPS;
635
132
                *ubx_svid = 0;
636
132
                nmea2_prn = 0;
637
132
            }
638
3.88k
            break;
639
480
        case 2:
640
            //  2 = GLONASS   65-96, nul
641
480
            *ubx_gnssid = GNSSID_GLO;
642
480
            if (64 > nmea_satnum) {
643
                // NMEA svid 1 - 64
644
389
                *ubx_svid = nmea_satnum;
645
389
            } else {
646
                /* Jackson Labs Micro JLT, Quectel Querk, SiRF, Skytrak,
647
                 * u-blox quirk: GLONASS are  65 to 96 */
648
91
                *ubx_svid = nmea_satnum - 64;
649
91
            }
650
480
            nmea2_prn = 64 + *ubx_svid;
651
480
            break;
652
962
        case 3:
653
            //  3 = Galileo   1-36
654
962
            *ubx_gnssid = GNSSID_GAL;
655
962
            if (100 > nmea_satnum) {
656
                // NMEA
657
289
                *ubx_svid = nmea_satnum;
658
673
            } else if (100 < nmea_satnum &&
659
587
                       200 > nmea_satnum) {
660
                // Quectel Querk, NOT NMEA, 101 - 199
661
182
                *ubx_svid = nmea_satnum - 100;
662
491
            } else if (300 < nmea_satnum &&
663
332
                       400 > nmea_satnum) {
664
                // Jackson Labs quirk, NOT NMEA, 301 - 399
665
82
                *ubx_svid = nmea_satnum - 300;
666
82
            }
667
962
            nmea2_prn = 300 + *ubx_svid;    // 301 - 399
668
962
            break;
669
993
        case 4:
670
            //  4 - BeiDou    1-37
671
993
            *ubx_gnssid = GNSSID_BD;
672
993
            if (100 > nmea_satnum) {
673
                // NMEA 1 - 99
674
490
                *ubx_svid = nmea_satnum;
675
503
            } else if (200 < nmea_satnum &&
676
441
                       300 > nmea_satnum) {
677
                // Quectel Querk, NOT NMEA, 201 - 299
678
68
                *ubx_svid = nmea_satnum - 200;
679
435
            } else if (400 < nmea_satnum &&
680
204
                       500 > nmea_satnum) {
681
                // Jackson Labs quirk, NOT NMEA, 401 - 499
682
69
                *ubx_svid = nmea_satnum - 400;
683
69
            }
684
            // put it at 400+ where NMEA 4.11 wants it
685
993
            nmea2_prn = 400 + *ubx_svid;
686
993
            break;
687
443
        case 5:
688
            //  5 - QZSS, 1 - 10, NMEA 4.11
689
443
            *ubx_gnssid = GNSSID_QZSS;
690
443
            if (100 > nmea_satnum) {
691
                // NMEA 1 - 99
692
241
                *ubx_svid = nmea_satnum;
693
241
            } else {
694
                // Telit quirk, not NMEA 193 - 199
695
202
                *ubx_svid = nmea_satnum - 192;
696
202
            }
697
698
            // put it at 193 to 199 where NMEA 4.11 wants it
699
            // huh?  space for only 7?
700
443
            nmea2_prn = 192 + *ubx_svid;
701
443
            break;
702
554
        case 6:
703
            //  6 - NavIC (IRNSS)    1-15
704
554
            *ubx_gnssid = GNSSID_IRNSS;
705
554
            *ubx_svid = nmea_satnum;
706
554
            nmea2_prn = nmea_satnum + 500;  // This is wrong...
707
554
            break;
708
19
        default:
709
            // unknown
710
            // x = IMES                Not defined by NMEA 4.10
711
19
            nmea2_prn = 0;
712
19
            break;
713
7.33k
        }
714
715
    /* left with NMEA 2.x to NMEA 4.0 satnums
716
     * use talker ID to disambiguate */
717
7.33k
    } else if (32 >= nmea_satnum) {
718
1.85k
        *ubx_svid = nmea_satnum;
719
1.85k
        switch (talker[0]) {
720
135
        case 'G':
721
135
            switch (talker[1]) {
722
0
            case 'A':
723
                // Galileo
724
0
                nmea2_prn = 300 + nmea_satnum;
725
0
                *ubx_gnssid = GNSSID_GAL;
726
0
                break;
727
0
            case 'B':
728
                // map Beidou IDs 1..37 to 401..437
729
0
                *ubx_gnssid = GNSSID_BD;
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 = GNSSID_IRNSS;
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 = GNSSID_GLO;
741
0
                break;
742
0
            case 'Q':
743
                // GQ, QZSS, 1 - 10
744
0
                nmea2_prn = 192 + nmea_satnum;
745
0
                *ubx_gnssid = GNSSID_QZSS;
746
0
                break;
747
59
            case 'N':
748
                // all of them, but only GPS is 0 < PRN < 33
749
59
                FALLTHROUGH
750
135
            case 'P':
751
                // GPS,SBAS,QZSS, but only GPS is 0 < PRN < 33
752
135
                FALLTHROUGH
753
135
            default:
754
                // WTF?
755
135
                break;
756
135
            }  // else ??
757
135
            break;
758
232
        case 'B':
759
232
            if ('D' == talker[1]) {
760
                // map Beidou IDs
761
0
                nmea2_prn = 400 + nmea_satnum;
762
0
                *ubx_gnssid = GNSSID_BD;
763
0
            }  // else ??
764
232
            break;
765
1.11k
        case 'P':
766
            // Quectel EC25 & EC21 use PQxxx for BeiDou
767
1.11k
            if ('Q' == talker[1]) {
768
                // map Beidou IDs
769
76
                nmea2_prn = 400 + nmea_satnum;
770
76
                *ubx_gnssid = GNSSID_BD;
771
76
            }  // else ??
772
1.11k
            break;
773
72
        case 'Q':
774
72
            if ('Z' == talker[1]) {
775
                // QZSS
776
0
                nmea2_prn = 192 + nmea_satnum;
777
0
                *ubx_gnssid = GNSSID_QZSS;
778
0
            }  // else ?
779
72
            break;
780
303
        default:
781
            // huh?
782
303
            break;
783
1.85k
        }
784
1.85k
    } else if (64 >= nmea_satnum) {
785
        // NMEA-ID (33..64) to SBAS PRN 120-151.
786
        // SBAS
787
82
        *ubx_gnssid = GNSSID_SBAS;
788
82
        *ubx_svid = 87 + nmea_satnum;
789
1.75k
    } else if (96 >= nmea_satnum) {
790
        // GLONASS 65..96
791
141
        *ubx_gnssid = GNSSID_GLO;
792
141
        *ubx_svid = nmea_satnum - 64;
793
1.61k
    } else if (120 > nmea_satnum) {
794
        // Huh?
795
108
        *ubx_gnssid = GNSSID_GPS;
796
108
        *ubx_svid = 0;
797
108
        nmea2_prn = 0;
798
1.51k
    } else if (158 >= nmea_satnum) {
799
        // SBAS 120..158
800
83
        *ubx_gnssid = GNSSID_SBAS;
801
83
        *ubx_svid = nmea_satnum;
802
1.42k
    } else if (173 > nmea_satnum) {
803
        // Huh?
804
78
        *ubx_gnssid = GNSSID_GPS;
805
78
        *ubx_svid = 0;
806
78
        nmea2_prn = 0;
807
1.34k
    } else if (182 >= nmea_satnum) {
808
        // IMES 173..182
809
71
        *ubx_gnssid = GNSSID_IMES;
810
71
        *ubx_svid = nmea_satnum - 172;
811
1.27k
    } else if (193 > nmea_satnum) {
812
        // Huh?
813
74
        *ubx_gnssid = GNSSID_GPS;
814
74
        *ubx_svid = 0;
815
74
        nmea2_prn = 0;
816
1.20k
    } else if (197 >= nmea_satnum) {
817
        // QZSS 193..197
818
        // undocumented u-blox goes to 199
819
84
        *ubx_gnssid = GNSSID_QZSS;
820
84
        *ubx_svid = nmea_satnum - 192;
821
1.12k
    } else if (201 > nmea_satnum) {
822
        // Huh?
823
60
        *ubx_gnssid = GNSSID_GPS;
824
60
        *ubx_svid = 0;
825
60
        nmea2_prn = 0;
826
1.06k
    } 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
104
        *ubx_gnssid = GNSSID_BD;
830
104
        *ubx_svid = nmea_satnum - 200;
831
104
        nmea2_prn += 200;           // move up to 400 where NMEA 2.x wants it.
832
956
    } else if (301 > nmea_satnum) {
833
        // Huh?
834
73
        *ubx_gnssid = GNSSID_GPS;
835
73
        *ubx_svid = 0;
836
73
        nmea2_prn = 0;
837
883
    } else if (356 >= nmea_satnum) {
838
        // Galileo 301..356
839
63
        *ubx_gnssid = GNSSID_GAL;
840
63
        *ubx_svid = nmea_satnum - 300;
841
820
    } else if (401 > nmea_satnum) {
842
        // Huh?
843
127
        *ubx_gnssid = GNSSID_GPS;
844
127
        *ubx_svid = 0;
845
127
        nmea2_prn = 0;
846
693
    } else if (437 >= nmea_satnum) {
847
        // BeiDou
848
203
        *ubx_gnssid = GNSSID_BD;
849
203
        *ubx_svid = nmea_satnum - 400;
850
490
    } else if (499 >= nmea_satnum) {
851
        // 438 to 500??
852
67
        *ubx_gnssid = GNSSID_GPS;
853
67
        *ubx_svid = 0;
854
67
        nmea2_prn = 0;
855
423
    } else if (518 >= nmea_satnum) {
856
        // NavIC (IRNSS) IDs 1..18 to 510 - 509, not NMEA
857
140
        *ubx_gnssid = GNSSID_IRNSS;
858
140
        *ubx_svid = nmea_satnum - 500;
859
283
    } else if (900 >= nmea_satnum) {
860
        // 438 to 900??
861
76
        *ubx_gnssid = GNSSID_GPS;
862
76
        *ubx_svid = 0;
863
76
        nmea2_prn = 0;
864
207
    } else if (918 >= nmea_satnum) {
865
        // 900 to 918 NavIC (IRNSS), per ALLYSTAR (NMEA?)
866
83
        *ubx_gnssid = GNSSID_IRNSS;
867
83
        *ubx_svid = nmea_satnum - 900;
868
124
    } else {
869
        // greater than 437 Huh?
870
124
        *ubx_gnssid = GNSSID_GPS;
871
124
        *ubx_svid = 0;
872
124
        nmea2_prn = 0;
873
124
    }
874
875
11.3k
    return nmea2_prn;
876
11.3k
}
877
878
/**************************************************************************
879
 *
880
 * NMEA sentence handling begins here
881
 *
882
 **************************************************************************/
883
884
static gps_mask_t processACCURACY(unsigned count UNUSED, char *field[],
885
                                  struct gps_device_t *session)
886
118
{
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
118
    gps_mask_t mask = ONLINE_SET;
896
897
118
    if ('\0' == field[1][0]) {
898
        // no data
899
66
        return mask;
900
66
    }
901
902
52
    GPSD_LOG(LOG_DATA, &session->context->errout,
903
52
             "NMEA0183: $GPACCURACY: %10s.\n", field[1]);
904
52
    return mask;
905
118
}
906
907
// BWC - Bearing and Distance to Waypoint - Great Circle
908
static gps_mask_t processBWC(unsigned count, char *field[],
909
                             struct gps_device_t *session)
910
291
{
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
291
    gps_mask_t mask = ONLINE_SET;
932
933
291
    if ('\0' != field[1][0]) {
934
203
        if (0 == merge_hhmmss(field[1], session)) {
935
112
            if (0 == session->nmea.date.tm_year) {
936
53
                GPSD_LOG(LOG_WARN, &session->context->errout,
937
53
                         "NMEA0183: can't use BWC time until after ZDA or RMC"
938
53
                         " has supplied a year.\n");
939
59
            } else {
940
59
                mask = TIME_SET;
941
59
            }
942
112
        }
943
203
    }
944
291
    if (14 <= count) {
945
        // NMEA 2.3 and later
946
206
        session->newdata.status = faa_mode(field[13][0]);
947
206
    }
948
949
291
    GPSD_LOG(LOG_PROG, &session->context->errout,
950
291
             "NMEA0183: BWC: hhmmss=%s status %d faa mode %s(%s)\n",
951
291
             field[1], session->newdata.status,
952
291
             field[13], char2str(field[13][0], c_faa_mode));
953
291
    return mask;
954
291
}
955
956
static gps_mask_t processDBT(unsigned count UNUSED, char *field[],
957
                             struct gps_device_t *session)
958
272
{
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
272
    gps_mask_t mask = ONLINE_SET;
972
973
272
    if ('\0' != field[3][0]) {
974
71
        session->newdata.depth = safe_atof(field[3]);
975
71
        mask |= (ALTITUDE_SET);
976
201
    } else if ('\0' != field[1][0]) {
977
69
        session->newdata.depth = safe_atof(field[1]) * FEET_TO_METERS;
978
69
        mask |= (ALTITUDE_SET);
979
132
    } else if ('\0' != field[5][0]) {
980
68
        session->newdata.depth = safe_atof(field[5]) * FATHOMS_TO_METERS;
981
68
        mask |= (ALTITUDE_SET);
982
68
    }
983
984
272
    GPSD_LOG(LOG_PROG, &session->context->errout,
985
272
             "NMEA0183: %s mode %d, depth %lf.\n",
986
272
             field[0],
987
272
             session->newdata.mode,
988
272
             session->newdata.depth);
989
272
    return mask;
990
272
}
991
992
static gps_mask_t processDPT(unsigned count UNUSED, char *field[],
993
                             struct gps_device_t *session)
994
150
{
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
150
    double offset;
1007
150
    gps_mask_t mask = ONLINE_SET;
1008
1009
150
    if ('\0' == field[1][0]) {
1010
        // no depth
1011
72
        return mask;
1012
72
    }
1013
78
    session->newdata.depth = safe_atof(field[1]);
1014
78
    offset = safe_atof(field[2]);
1015
78
    if (0.0 > offset) {
1016
        // adjust to get depth from keel
1017
0
        session->newdata.depth -= offset;
1018
0
    }
1019
78
    mask |= ALTITUDE_SET;
1020
1021
78
    GPSD_LOG(LOG_PROG, &session->context->errout,
1022
78
             "NMEA0183: %s depth %.1f offset %s max %s\n",
1023
78
             field[0],
1024
78
             session->newdata.depth, field[2], field[3]);
1025
78
    return mask;
1026
150
}
1027
1028
/* NMEA Map Datum
1029
 *
1030
 * FIXME: seems to happen after cycle ender, so nothing happens...
1031
 */
1032
static gps_mask_t processDTM(unsigned count UNUSED, char *field[],
1033
                             struct gps_device_t *session)
1034
258
{
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
258
    unsigned i;
1061
258
    static struct
1062
258
    {
1063
258
        char *code;
1064
258
        char *name;
1065
258
    } codes[] = {
1066
258
        {"W84", "WGS84"},
1067
258
        {"W72", "WGS72"},
1068
258
        {"S85", "SGS85"},
1069
258
        {"P90", "PE90"},
1070
258
        {"999", "User Defined"},
1071
258
        {"", ""},
1072
258
    };
1073
1074
258
    gps_mask_t mask = ONLINE_SET;
1075
1076
258
    if ('\0' == field[1][0]) {
1077
68
        return mask;
1078
68
    }
1079
1080
790
    for (i = 0; ; i++) {
1081
790
        if ('\0' == codes[i].code[0]) {
1082
            // not found
1083
120
            strlcpy(session->newdata.datum, field[1],
1084
120
                    sizeof(session->newdata.datum));
1085
120
            break;
1086
120
        }
1087
670
        if (0 ==strcmp(codes[i].code, field[1])) {
1088
70
            strlcpy(session->newdata.datum, codes[i].name,
1089
70
                    sizeof(session->newdata.datum));
1090
70
            break;
1091
70
        }
1092
670
    }
1093
1094
190
    GPSD_LOG(LOG_DATA, &session->context->errout,
1095
190
             "NMEA0183: xxDTM: datum=%.40s\n",
1096
190
             session->newdata.datum);
1097
190
    return mask;
1098
258
}
1099
1100
// NMEA 3.0 Estimated Position Error
1101
static gps_mask_t processGBS(unsigned count UNUSED, char *field[],
1102
                             struct gps_device_t *session)
1103
461
{
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
461
    gps_mask_t mask = ONLINE_SET;
1121
1122
    // register fractional time for end-of-cycle detection
1123
461
    register_fractional_time(field[0], field[1], session);
1124
1125
    // check that we're associated with the current fix
1126
461
    if (session->nmea.date.tm_hour == DD(field[1]) &&
1127
360
        session->nmea.date.tm_min == DD(field[1] + 2) &&
1128
308
        session->nmea.date.tm_sec == DD(field[1] + 4)) {
1129
        // FIXME: check fractional time!
1130
160
        session->newdata.epy = safe_atof(field[2]);
1131
160
        session->newdata.epx = safe_atof(field[3]);
1132
160
        session->newdata.epv = safe_atof(field[4]);
1133
160
        GPSD_LOG(LOG_DATA, &session->context->errout,
1134
160
                 "NMEA0183: GBS: epx=%.2f epy=%.2f epv=%.2f\n",
1135
160
                 session->newdata.epx,
1136
160
                 session->newdata.epy,
1137
160
                 session->newdata.epv);
1138
160
        mask = HERR_SET | VERR_SET;
1139
301
    } else {
1140
301
        GPSD_LOG(LOG_PROG, &session->context->errout,
1141
301
                 "NMEA0183: second in $GPGBS error estimates doesn't match.\n");
1142
301
    }
1143
461
    return mask;
1144
461
}
1145
1146
// Global Positioning System Fix Data
1147
static gps_mask_t processGGA(unsigned count UNUSED, char *field[],
1148
                             struct gps_device_t *session)
1149
1.89k
{
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
1.89k
    gps_mask_t mask = ONLINE_SET;
1180
1.89k
    int newstatus;
1181
1.89k
    char last_last_gga_talker = session->nmea.last_gga_talker;
1182
1.89k
    int fix;              // a.k.a Quality flag
1183
1.89k
    session->nmea.last_gga_talker = field[0][1];
1184
1185
1.89k
    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
632
        fix = -1;
1190
1.26k
    } else {
1191
1.26k
        fix = atoi(field[6]);
1192
1.26k
    }
1193
    // Jackson Labs Micro JLT uses nonstadard fix flag, not handled
1194
1.89k
    switch (fix) {
1195
262
    case 0:     // no fix
1196
262
        newstatus = STATUS_UNK;
1197
262
        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
121
            memset(&session->nmea.date, 0, sizeof(session->nmea.date));
1204
121
            session->cycle_end_reliable = false;
1205
121
            mask |= REPORT_IS | TIME_SET;
1206
121
        }
1207
262
        break;
1208
80
    case 1:
1209
        // could be 2D, 3D, GNSSDR
1210
80
        newstatus = STATUS_GPS;
1211
80
        break;
1212
139
    case 2:     // differential
1213
139
        newstatus = STATUS_DGPS;
1214
139
        break;
1215
282
    case 3:
1216
        // GPS PPS, fix valid, could be 2D, 3D, GNSSDR
1217
282
        newstatus = STATUS_PPS_FIX;
1218
282
        break;
1219
76
    case 4:     // RTK integer
1220
76
        newstatus = STATUS_RTK_FIX;
1221
76
        break;
1222
94
    case 5:     // RTK float
1223
94
        newstatus = STATUS_RTK_FLT;
1224
94
        break;
1225
51
    case 6:
1226
        // dead reckoning, could be valid or invalid
1227
51
        newstatus = STATUS_DR;
1228
51
        break;
1229
67
    case 7:
1230
        // manual input, surveyed
1231
67
        newstatus = STATUS_TIME;
1232
67
        break;
1233
85
    case 8:
1234
        /* simulated mode
1235
         * Garmin GPSMAP and Gecko sends an 8, but undocumented why */
1236
85
        newstatus = STATUS_SIM;
1237
85
        break;
1238
632
    case -1:
1239
632
        FALLTHROUGH
1240
756
    default:
1241
756
        newstatus = -1;
1242
756
        break;
1243
1.89k
    }
1244
1.89k
    if (0 <= newstatus) {
1245
1.13k
        session->newdata.status = newstatus;
1246
1.13k
        mask = STATUS_SET;
1247
1.13k
    }
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
1.89k
    if ('\0' != last_last_gga_talker &&
1262
1.89k
        last_last_gga_talker != session->nmea.last_gga_talker) {
1263
        // skip the time check
1264
291
        session->nmea.latch_mode = 0;
1265
1.60k
    } else {
1266
1.60k
        session->nmea.latch_mode = strncmp(field[1],
1267
1.60k
                          session->nmea.last_gga_timestamp,
1268
1.60k
                          sizeof(session->nmea.last_gga_timestamp))==0;
1269
1.60k
    }
1270
1271
1.89k
    if (session->nmea.latch_mode) {
1272
1.27k
        session->newdata.status = STATUS_UNK;
1273
1.27k
        session->newdata.mode = MODE_NO_FIX;
1274
1.27k
        mask |= MODE_SET | STATUS_SET;
1275
1.27k
        GPSD_LOG(LOG_PROG, &session->context->errout,
1276
1.27k
                 "NMEA0183: xxGGA: latch mode\n");
1277
1.27k
    } else {
1278
622
        (void)strlcpy(session->nmea.last_gga_timestamp, field[1],
1279
622
                      sizeof(session->nmea.last_gga_timestamp));
1280
622
    }
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
1.89k
    session->nmea.gga_sats_used = atoi(field[7]);
1288
1289
1.89k
    if ('\0' == field[1][0]) {
1290
732
        GPSD_LOG(LOG_DATA, &session->context->errout,
1291
732
                 "NMEA0183: GGA time missing.\n");
1292
1.16k
    } else if (0 == merge_hhmmss(field[1], session)) {
1293
558
        register_fractional_time(field[0], field[1], session);
1294
558
        if (0 == session->nmea.date.tm_year) {
1295
102
            GPSD_LOG(LOG_WARN, &session->context->errout,
1296
102
                     "NMEA0183: can't use GGA time until after ZDA or RMC"
1297
102
                     " has supplied a year.\n");
1298
456
        } else {
1299
456
            mask |= TIME_SET;
1300
456
        }
1301
558
    }
1302
1303
1.89k
    if (0 == do_lat_lon(&field[2], &session->newdata)) {
1304
662
        session->newdata.mode = MODE_2D;
1305
662
        mask |= LATLON_SET;
1306
662
        if ('\0' != field[11][0]) {
1307
388
            session->newdata.geoid_sep = safe_atof(field[11]);
1308
388
        } else {
1309
274
            session->newdata.geoid_sep = wgs84_separation(
1310
274
                session->newdata.latitude, session->newdata.longitude);
1311
274
        }
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
662
        if ('\0' != field[9][0]) {
1318
            // altitude is MSL
1319
557
            session->newdata.altMSL = safe_atof(field[9]);
1320
            // Let gpsd_error_model() deal with altHAE
1321
557
            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
557
            if (4 <= session->nmea.gga_sats_used) {
1332
279
                session->newdata.mode = MODE_3D;
1333
279
            }
1334
557
        }
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
662
        if (3 > session->nmea.gga_sats_used &&
1342
247
            'G' == field[0][0] &&
1343
232
            ('P' == field[0][1] ||
1344
131
             'N' == field[0][1])) {
1345
            // G[NP]GGA and not enough sats used for a fix.
1346
131
            session->newdata.mode = MODE_NO_FIX;
1347
131
        }
1348
1.23k
    } else {
1349
1.23k
        session->newdata.mode = MODE_NO_FIX;
1350
1.23k
    }
1351
1.89k
    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
1.89k
    if ('\0' != field[8][0]) {
1357
854
        double hdop;
1358
854
        hdop = safe_atof(field[8]);
1359
854
        if (IN(0.01, hdop, 89.99)) {
1360
            // why not to newdata?
1361
330
            session->gpsdata.dop.hdop = hdop;
1362
330
            mask |= DOP_SET;
1363
330
        }
1364
854
    }
1365
1366
    // get DGPS stuff
1367
1.89k
    if ('\0' != field[13][0] &&
1368
997
        '\0' != field[14][0]) {
1369
        // both, or neither
1370
602
        double age;
1371
602
        int station;
1372
1373
602
        age = safe_atof(field[13]);
1374
602
        station = atoi(field[14]);
1375
602
        if (0.09 < age ||
1376
408
            0 < station) {
1377
            // ignore both zeros
1378
317
            session->newdata.dgps_age = age;
1379
317
            session->newdata.dgps_station = station;
1380
317
        }
1381
602
    }
1382
1383
1.89k
    GPSD_LOG(LOG_PROG, &session->context->errout,
1384
1.89k
             "NMEA0183: GGA: hhmmss=%s lat=%.2f lon=%.2f altMSL=%.2f "
1385
1.89k
             "mode=%d status=%d\n",
1386
1.89k
             field[1],
1387
1.89k
             session->newdata.latitude,
1388
1.89k
             session->newdata.longitude,
1389
1.89k
             session->newdata.altMSL,
1390
1.89k
             session->newdata.mode,
1391
1.89k
             session->newdata.status);
1392
1.89k
    return mask;
1393
1.89k
}
1394
1395
// Geographic position - Latitude, Longitude
1396
static gps_mask_t processGLL(unsigned count, char *field[],
1397
                             struct gps_device_t *session)
1398
1.80k
{
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
1.80k
    char *status = field[7];
1428
1.80k
    gps_mask_t mask = ONLINE_SET;
1429
1430
1.80k
    if (field[5][0] != '\0') {
1431
1.69k
        if (0 == merge_hhmmss(field[5], session)) {
1432
1.14k
            register_fractional_time(field[0], field[5], session);
1433
1.14k
            if (0 == session->nmea.date.tm_year) {
1434
118
                GPSD_LOG(LOG_WARN, &session->context->errout,
1435
118
                         "NMEA0183: can't use GLL time until after ZDA or RMC"
1436
118
                         " has supplied a year.\n");
1437
1.02k
            } else {
1438
1.02k
                mask = TIME_SET;
1439
1.02k
            }
1440
1.14k
        }
1441
1.69k
    }
1442
1.80k
    if ('\0' == field[6][0] ||
1443
1.68k
        'V' == field[6][0]) {
1444
        // Invalid
1445
189
        session->newdata.status = STATUS_UNK;
1446
189
        session->newdata.mode = MODE_NO_FIX;
1447
1.61k
    } else if ('A' == field[6][0] &&
1448
1.29k
        (count < 8 || *status != 'N') &&
1449
1.12k
        0 == do_lat_lon(&field[1], &session->newdata)) {
1450
907
        int newstatus;
1451
1452
907
        mask |= LATLON_SET;
1453
1454
907
        newstatus = STATUS_GPS;
1455
907
        if (8 <= count) {
1456
838
            newstatus = faa_mode(*status);
1457
838
        }
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
907
        if (0 != isfinite(session->gpsdata.fix.altHAE) ||
1466
830
            0 != isfinite(session->gpsdata.fix.altMSL)) {
1467
159
            session->newdata.mode = MODE_3D;
1468
748
        } else if (3 < session->gpsdata.satellites_used) {
1469
            // 4 sats used means 3D
1470
198
            session->newdata.mode = MODE_3D;
1471
550
        } else if (MODE_2D > session->gpsdata.fix.mode ||
1472
368
                   (0 == isfinite(session->oldfix.altHAE) &&
1473
399
                    0 == isfinite(session->oldfix.altMSL))) {
1474
399
            session->newdata.mode = MODE_2D;
1475
399
        }
1476
907
        session->newdata.status = newstatus;
1477
907
    } else {
1478
704
        session->newdata.status = STATUS_UNK;
1479
704
        session->newdata.mode = MODE_NO_FIX;
1480
704
    }
1481
1.80k
    mask |= STATUS_SET | MODE_SET;
1482
1483
1.80k
    GPSD_LOG(LOG_PROG, &session->context->errout,
1484
1.80k
             "NMEA0183: GLL: hhmmss=%s lat=%.2f lon=%.2f mode=%d status=%d "
1485
1.80k
             "faa mode %s(%s)\n",
1486
1.80k
             field[5],
1487
1.80k
             session->newdata.latitude,
1488
1.80k
             session->newdata.longitude,
1489
1.80k
             session->newdata.mode,
1490
1.80k
             session->newdata.status,
1491
1.80k
             field[7], char2str(field[7][0], c_faa_mode));
1492
1.80k
    return mask;
1493
1.80k
}
1494
1495
// Geographic position - Latitude, Longitude, and more
1496
static gps_mask_t processGNS(unsigned count UNUSED, char *field[],
1497
                             struct gps_device_t *session)
1498
933
{
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
933
    int newstatus;
1537
933
    gps_mask_t mask = ONLINE_SET;
1538
1539
933
    if ('\0' != field[1][0]) {
1540
707
        if (0 == merge_hhmmss(field[1], session)) {
1541
559
            register_fractional_time(field[0], field[1], session);
1542
559
            if (0 == session->nmea.date.tm_year) {
1543
83
                GPSD_LOG(LOG_WARN, &session->context->errout,
1544
83
                         "NMEA0183: can't use GNS time until after ZDA or RMC"
1545
83
                         " has supplied a year.\n");
1546
476
            } else {
1547
476
                mask = TIME_SET;
1548
476
            }
1549
559
        }
1550
707
    }
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
933
    if ('\0' == field[6][0] ||      // FAA mode: missing
1556
867
        'N' == field[6][0]) {       // FAA mode: not valid
1557
132
        session->newdata.mode = MODE_NO_FIX;
1558
132
        mask |= MODE_SET;
1559
132
        return mask;
1560
132
    }
1561
    /* navigation status, assume S=safe and C=caution are OK
1562
     * can be missing on valid fix */
1563
801
    if ('U' == field[13][0] ||      // Unsafe
1564
733
        'V' == field[13][0]) {      // not valid
1565
136
        return mask;
1566
136
    }
1567
1568
665
    session->nmea.gga_sats_used = atoi(field[7]);
1569
1570
665
    if (0 == do_lat_lon(&field[2], &session->newdata)) {
1571
305
        mask |= LATLON_SET;
1572
305
        session->newdata.mode = MODE_2D;
1573
1574
305
        if ('\0' != field[9][0]) {
1575
            // altitude is MSL
1576
223
            session->newdata.altMSL = safe_atof(field[9]);
1577
223
            if (0 != isfinite(session->newdata.altMSL)) {
1578
150
                mask |= ALTITUDE_SET;
1579
150
                if (3 < session->nmea.gga_sats_used) {
1580
                    // more than 3 sats used means 3D
1581
87
                    session->newdata.mode = MODE_3D;
1582
87
                }
1583
150
            }
1584
            // only need geoid_sep if in 3D mode
1585
223
            if ('\0' != field[10][0]) {
1586
154
                session->newdata.geoid_sep = safe_atof(field[10]);
1587
154
            }
1588
            // Let gpsd_error_model() deal with geoid_sep and altHAE
1589
223
        }
1590
360
    } else {
1591
360
        session->newdata.mode = MODE_NO_FIX;
1592
360
        mask |= MODE_SET;
1593
360
    }
1594
1595
665
    if ('\0' != field[8][0]) {
1596
559
        session->gpsdata.dop.hdop = safe_atof(field[8]);
1597
559
        mask |= DOP_SET;
1598
559
    }
1599
1600
    // we ignore all but the leading mode indicator.
1601
665
    newstatus = faa_mode(field[6][0]);
1602
1603
665
    session->newdata.status = newstatus;
1604
665
    mask |= MODE_SET | STATUS_SET;
1605
1606
    // get DGPS stuff
1607
665
    if ('\0' != field[11][0] &&
1608
507
        '\0' != field[12][0]) {
1609
        // both, or neither
1610
176
        session->newdata.dgps_age = safe_atof(field[11]);
1611
176
        session->newdata.dgps_station = atoi(field[12]);
1612
176
    }
1613
1614
665
    GPSD_LOG(LOG_PROG, &session->context->errout,
1615
665
             "NMEA0183: GNS: hhmmss=%s lat=%.2f lon=%.2f mode=%d status=%d "
1616
665
             "faa mode %s(%s)\n",
1617
665
             field[1],
1618
665
             session->newdata.latitude,
1619
665
             session->newdata.longitude,
1620
665
             session->newdata.mode,
1621
665
             session->newdata.status,
1622
665
             field[6], char2str(field[6][0], c_faa_mode));
1623
665
    return mask;
1624
801
}
1625
1626
// GNSS Range residuals
1627
static gps_mask_t processGRS(unsigned count UNUSED, char *field[],
1628
                             struct gps_device_t *session)
1629
441
{
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
441
    int mode;
1644
441
    gps_mask_t mask = ONLINE_SET;
1645
1646
441
    if ('\0' == field[1][0] ||
1647
366
        0 != merge_hhmmss(field[1], session)) {
1648
        // bad time
1649
190
        return mask;
1650
190
    }
1651
1652
251
    mode = atoi(field[2]);
1653
251
    if (1 != mode &&
1654
194
        2 != mode) {
1655
        // bad mode
1656
125
        return mask;
1657
125
    }
1658
1659
    // FIXME: partial decode.  How to match sat numbers up with GSA?
1660
1661
126
    GPSD_LOG(LOG_DATA, &session->context->errout,
1662
126
             "NMEA0183: %s: mode %d count %d\n",
1663
126
             field[0], mode, count);
1664
126
    return mask;
1665
251
}
1666
1667
// GPS DOP and Active Satellites
1668
static gps_mask_t processGSA(unsigned count, char *field[],
1669
                             struct gps_device_t *session)
1670
2.39k
{
1671
9.60k
#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
2.39k
    gps_mask_t mask = ONLINE_SET;
1746
2.39k
    char last_last_gsa_talker = session->nmea.last_gsa_talker;
1747
2.39k
    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
2.39k
    if (session->nmea.latch_mode) {
1757
        // last GGA had a non-advancing timestamp; don't trust this GSA
1758
941
        GPSD_LOG(LOG_PROG, &session->context->errout,
1759
941
                 "NMEA0183: %s: non-advancing timestamp\n", field[0]);
1760
        // FIXME: return here?
1761
1.45k
    } else {
1762
1.45k
        unsigned i;
1763
1764
1.45k
        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
1.45k
        if (1 <= i &&
1771
439
            3 >= i) {
1772
233
            session->newdata.mode = i;
1773
233
            mask = MODE_SET;
1774
1775
233
            GPSD_LOG(LOG_PROG, &session->context->errout,
1776
233
                     "NMEA0183: %s sets mode %d\n",
1777
233
                     field[0], session->newdata.mode);
1778
233
        }
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
1.45k
        if (19 < count) {
1785
414
            GPSD_LOG(LOG_WARN, &session->context->errout,
1786
414
                     "NMEA0183: %s: count %d too long!\n", field[0], count);
1787
1.03k
        } else {
1788
1.03k
            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
1.03k
            if ('\0' != field[15][0]) {
1796
688
                dop = safe_atof(field[15]);
1797
688
                if (IN(0.01, dop, 89.99)) {
1798
315
                    session->gpsdata.dop.pdop = dop;
1799
315
                    mask |= DOP_SET;
1800
315
                }
1801
688
            }
1802
1.03k
            if ('\0' != field[16][0]) {
1803
658
                dop = safe_atof(field[16]);
1804
658
                if (IN(0.01, dop, 89.99)) {
1805
250
                    session->gpsdata.dop.hdop = dop;
1806
250
                    mask |= DOP_SET;
1807
250
                }
1808
658
            }
1809
1.03k
            if ('\0' != field[17][0]) {
1810
709
                dop = safe_atof(field[17]);
1811
709
                if (IN(0.01, dop, 89.99)) {
1812
379
                    session->gpsdata.dop.vdop = dop;
1813
379
                    mask |= DOP_SET;
1814
379
                }
1815
709
            }
1816
1.03k
            if (19 == count &&
1817
427
                '\0' != field[18][0]) {
1818
398
                if (NULL != strchr(field[18], '.')) {
1819
                    // SiRF TriG puts a floating point in field 18
1820
150
                    GPSD_LOG(LOG_WARN, &session->context->errout,
1821
150
                             "NMEA0183: %s: illegal field 18 (%s)!\n",
1822
150
                             field[0], field[18]);
1823
248
                } else {
1824
                    // get the NMEA 4.10, or $PQGSA, system ID
1825
248
                    nmea_gnssid = atoi(field[18]);
1826
248
                }
1827
398
            }
1828
1.03k
        }
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
1.45k
        if ('\0' == session->nmea.last_gsa_talker ||
1837
1.19k
            (GSA_TALKER == session->nmea.last_gsa_talker &&
1838
1.15k
             'N' != GSA_TALKER &&
1839
1.07k
             'Q' != GSA_TALKER) ) {
1840
1.07k
            session->gpsdata.satellites_used = 0;
1841
1.07k
            memset(session->nmea.sats_used, 0, sizeof(session->nmea.sats_used));
1842
1.07k
            GPSD_LOG(LOG_PROG, &session->context->errout,
1843
1.07k
                     "NMEA0183: %s: clear sats_used\n", field[0]);
1844
1.07k
        }
1845
1.45k
        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
1.45k
        switch (session->nmea.last_gsa_talker) {
1850
88
        case 'A':
1851
            // GA Galileo
1852
88
            nmea_gnssid = 3;
1853
88
            session->nmea.seen_gagsa = true;
1854
88
            break;
1855
21
        case 'B':
1856
            // GB BeiDou
1857
21
            FALLTHROUGH
1858
107
        case 'D':
1859
            // BD BeiDou
1860
107
            nmea_gnssid = 4;
1861
107
            session->nmea.seen_bdgsa = true;
1862
107
            break;
1863
101
        case 'I':
1864
            // GI IRNSS
1865
101
            nmea_gnssid = 6;
1866
101
            session->nmea.seen_gigsa = true;
1867
101
            break;
1868
61
        case 'L':
1869
            // GL GLONASS
1870
61
            nmea_gnssid = 2;
1871
61
            session->nmea.seen_glgsa = true;
1872
61
            break;
1873
189
        case 'N':
1874
            // GN GNSS
1875
189
            session->nmea.seen_gngsa = true;
1876
            // field 18 is the NMEA gnssid in 4.10 and up.
1877
            // nmea_gnssid set above
1878
189
            break;
1879
562
        case 'P':
1880
            // GP GPS
1881
562
            session->nmea.seen_gpgsa = true;
1882
562
            nmea_gnssid = 1;
1883
562
            break;
1884
192
        case 'Q':
1885
            // Quectel EC25 & EC21 use PQGSA for QZSS and GLONASS
1886
192
            if ('P' == field[0][0] &&
1887
192
                0 != nmea_gnssid) {
1888
                /* Quectel EC25 & EC21 use PQGSV for BeiDou or QZSS
1889
                 * nmea_gnssid set above.  What about seen?
1890
                 */
1891
91
                break;
1892
91
            }
1893
101
            FALLTHROUGH
1894
145
        case 'Z':        // QZ QZSS
1895
            // NMEA 4.11 GQGSA for QZSS
1896
145
            nmea_gnssid = 5;
1897
145
            session->nmea.seen_qzgsa = true;
1898
145
            break;
1899
1.45k
        }
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
24.0k
        for (i = 0; i < count - 6; i++) {
1904
22.7k
            int prn;
1905
22.7k
            int n;
1906
22.7k
            int nmea_satnum;            // almost svid...
1907
22.7k
            gnssid_t ubx_gnssid;        // UNUSED
1908
22.7k
            unsigned char ubx_svid;     // UNUSED
1909
1910
            // skip empty fields, otherwise empty becomes prn=200
1911
22.7k
            if ('\0' == field[i + 3][0]) {
1912
13.9k
                continue;
1913
13.9k
            }
1914
8.85k
            if (NULL != strchr(field[i + 3], '.')) {
1915
                // found a float, must be PDOP, done.
1916
175
                break;
1917
175
            }
1918
8.67k
            nmea_satnum = atoi(field[i + 3]);
1919
8.67k
            if (1 > nmea_satnum ||
1920
5.64k
                600 < nmea_satnum) {
1921
3.27k
                continue;
1922
3.27k
            }
1923
5.39k
            prn = nmeaid_to_prn(field[0], nmea_satnum, nmea_gnssid,
1924
5.39k
                                &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
5.39k
            if (0 >= prn) {
1935
                // huh?
1936
286
                continue;
1937
286
            }
1938
            // check first BEFORE over-writing memory
1939
5.11k
            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
10
                GPSD_LOG(LOG_ERROR, &session->context->errout,
1944
10
                         "NMEA0183: %s used > MAXCHANNELS!\n", field[0]);
1945
10
                break;
1946
10
            }
1947
            /* check for duplicate.
1948
             * Often GPS in both $GPGSA and $GNGSA, for example Quectel. */
1949
19.5k
            for (n = 0; n < MAXCHANNELS; n++) {
1950
19.5k
                if ( 0 == session->nmea.sats_used[n]) {
1951
                    // unused slot, use it.
1952
4.22k
                    session->nmea.sats_used[n] = (unsigned short)prn;
1953
4.22k
                    session->gpsdata.satellites_used = n + 1;
1954
4.22k
                    break;
1955
4.22k
                }
1956
15.3k
                if (session->nmea.sats_used[n] == (unsigned short)prn) {
1957
                    // Duplicate!
1958
878
                    break;
1959
878
                }
1960
15.3k
            }
1961
5.10k
        }
1962
1.45k
        mask |= USED_IS;
1963
1.45k
        GPSD_LOG(LOG_PROG, &session->context->errout,
1964
1.45k
                 "NMEA0183: %s: mode=%d used=%d pdop=%.2f hdop=%.2f "
1965
1.45k
                 "vdop=%.2f nmea_gnssid %d\n",
1966
1.45k
                 field[0], session->newdata.mode,
1967
1.45k
                 session->gpsdata.satellites_used,
1968
1.45k
                 session->gpsdata.dop.pdop,
1969
1.45k
                 session->gpsdata.dop.hdop,
1970
1.45k
                 session->gpsdata.dop.vdop, nmea_gnssid);
1971
1.45k
    }
1972
    // assumes GLGSA or BDGSA, if present, is emitted directly after the GPGSA
1973
2.39k
    if ((session->nmea.seen_bdgsa ||
1974
1
         session->nmea.seen_gagsa ||
1975
1
         session->nmea.seen_gigsa ||
1976
1
         session->nmea.seen_glgsa ||
1977
1
         session->nmea.seen_gngsa ||
1978
1
         session->nmea.seen_qzgsa) &&
1979
2.39k
         GSA_TALKER == 'P') {
1980
987
        mask = ONLINE_SET;
1981
1.40k
    } else if ('N' != last_last_gsa_talker &&
1982
1.24k
               'N' == GSA_TALKER) {
1983
        /* first of two GNGSA
1984
         * if mode == 1 some GPS only output 1 GNGSA, so ship mode always */
1985
117
        mask =  ONLINE_SET | MODE_SET;
1986
117
    }
1987
1988
    // cast for 32/64 compatibility
1989
2.39k
    GPSD_LOG(LOG_PROG, &session->context->errout,
1990
2.39k
             "NMEA0183: %s: count %d visible %d used %d mask %#llx\n",
1991
2.39k
             field[0], count, session->gpsdata.satellites_visible,
1992
2.39k
             session->gpsdata.satellites_used,
1993
2.39k
             (long long unsigned)mask);
1994
2.39k
    return mask;
1995
2.39k
#undef GSA_TALKER
1996
2.39k
}
1997
1998
// GST - GPS Pseudorange Noise Statistics
1999
static gps_mask_t processGST(unsigned count, char *field[],
2000
                             struct gps_device_t *session)
2001
281
{
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
281
    struct tm date = {0};
2015
281
    timespec_t ts;
2016
281
    int ret;
2017
281
    char ts_buf[TIMESPEC_LEN];
2018
281
    gps_mask_t mask = ONLINE_SET;
2019
2020
281
    if (9 > count) {
2021
70
      return mask;
2022
70
    }
2023
2024
    // since it is NOT current time, do not register_fractional_time()
2025
    // compute start of today
2026
211
    if (0 < session->nmea.date.tm_year) {
2027
        // Do not bother if no current year
2028
137
        memset(&date, 0, sizeof(date));
2029
137
        date.tm_year = session->nmea.date.tm_year;
2030
137
        date.tm_mon = session->nmea.date.tm_mon;
2031
137
        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
137
        ret = decode_hhmmss(&date, &ts.tv_nsec, field[1], session);
2038
137
    } else {
2039
74
        ret = 1;
2040
74
    }
2041
211
    if (0 == ret) {
2042
        // convert to timespec_t , tv_nsec already set
2043
48
        session->gpsdata.gst.utctime.tv_sec = mkgmtime(&date);
2044
48
        session->gpsdata.gst.utctime.tv_nsec = ts.tv_nsec;
2045
163
    } else {
2046
        // no idea of UTC time now
2047
163
        session->gpsdata.gst.utctime.tv_sec = 0;
2048
163
        session->gpsdata.gst.utctime.tv_nsec = 0;
2049
163
    }
2050
211
    session->gpsdata.gst.rms_deviation       = safe_atof(field[2]);
2051
211
    session->gpsdata.gst.smajor_deviation    = safe_atof(field[3]);
2052
211
    session->gpsdata.gst.sminor_deviation    = safe_atof(field[4]);
2053
211
    session->gpsdata.gst.smajor_orientation  = safe_atof(field[5]);
2054
211
    session->gpsdata.gst.lat_err_deviation   = safe_atof(field[6]);
2055
211
    session->gpsdata.gst.lon_err_deviation   = safe_atof(field[7]);
2056
211
    session->gpsdata.gst.alt_err_deviation   = safe_atof(field[8]);
2057
2058
211
    GPSD_LOG(LOG_DATA, &session->context->errout,
2059
211
             "NMEA0183: GST: utc = %s, rms = %.2f, maj = %.2f, min = %.2f,"
2060
211
             " ori = %.2f, lat = %.2f, lon = %.2f, alt = %.2f\n",
2061
211
             timespec_str(&session->gpsdata.gst.utctime, ts_buf,
2062
211
                          sizeof(ts_buf)),
2063
211
             session->gpsdata.gst.rms_deviation,
2064
211
             session->gpsdata.gst.smajor_deviation,
2065
211
             session->gpsdata.gst.sminor_deviation,
2066
211
             session->gpsdata.gst.smajor_orientation,
2067
211
             session->gpsdata.gst.lat_err_deviation,
2068
211
             session->gpsdata.gst.lon_err_deviation,
2069
211
             session->gpsdata.gst.alt_err_deviation);
2070
2071
211
    mask = GST_SET | ONLINE_SET;
2072
211
    return mask;
2073
281
}
2074
2075
// xxGSV -  GPS Satellites in View
2076
static gps_mask_t processGSV(unsigned count, char *field[],
2077
                             struct gps_device_t *session)
2078
5.60k
{
2079
13.5k
#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
5.60k
    unsigned n, fldnum;
2180
5.60k
    unsigned char  nmea_sigid = 0;
2181
5.60k
    int nmea_gnssid = 0;
2182
5.60k
    unsigned char  ubx_sigid = 0;
2183
5.60k
    int sane = 0;
2184
2185
5.60k
    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
5.60k
    session->nmea.await = atoi(field[1]);
2193
5.60k
    if (1 > session->nmea.await ||
2194
5.50k
        10 < session->nmea.await) {
2195
        // numbeof sentences is either 0, or too many
2196
193
        session->nmea.await = 0;
2197
193
        GPSD_LOG(LOG_WARN, &session->context->errout,
2198
193
                 "NMEA0183: %s: bad number of sentences %d\n",
2199
193
                 field[0], session->nmea.await);
2200
193
        gpsd_zero_satellites(&session->gpsdata);
2201
193
        return ONLINE_SET;
2202
193
    }
2203
5.41k
    session->nmea.part = atoi(field[2]);
2204
5.41k
    if (1 > session->nmea.part ||
2205
5.29k
        session->nmea.await < session->nmea.part) {
2206
221
        GPSD_LOG(LOG_WARN, &session->context->errout,
2207
221
                 "NMEA0183: %s: bad part %d of %d\n",
2208
221
                 field[0], session->nmea.part, session->nmea.await);
2209
221
        gpsd_zero_satellites(&session->gpsdata);
2210
221
        return ONLINE_SET;
2211
221
    }
2212
2213
5.19k
    GPSD_LOG(LOG_PROG, &session->context->errout,
2214
5.19k
             "NMEA0183: %s: part %s of %s, last_gsv_talker '%#x' "
2215
5.19k
             " last_gsv_sigid %u\n",
2216
5.19k
             field[0], field[2], field[1],
2217
5.19k
             session->nmea.last_gsv_talker,
2218
5.19k
             session->nmea.last_gsv_sigid);
2219
2220
    /*
2221
     * This check used to be !=0, but we have loosen it a little to let by
2222
     * NMEA 4.1 GSVs with an extra signal-ID field at the end.  Then loosen
2223
     * some more for Quectel  Querky $PQGSV.
2224
     */
2225
5.19k
    switch (count % 4) {
2226
3.38k
    case 0:
2227
        // normal, pre-NMEA 4.10
2228
3.38k
        break;
2229
1.20k
    case 1:
2230
        // NMEA 4.10, and later, get the signal ID
2231
1.20k
        nmea_sigid = hex2uchar(field[count - 1][0]);
2232
1.20k
        break;
2233
549
    case 2:
2234
        // Quectel Querk. $PQGSV, get the signal ID, and system ID
2235
549
        nmea_sigid = hex2uchar(field[count - 2][0]);
2236
549
        nmea_gnssid = atoi(field[count - 1]);
2237
549
        if (4 > nmea_gnssid ||
2238
457
            5 < nmea_gnssid) {
2239
            // Quectel says only 4 or 5
2240
394
            GPSD_LOG(LOG_WARN, &session->context->errout,
2241
394
                     "NMEA0183: %sm invalid nmea_gnssid %d\n",
2242
394
                     field[0], nmea_gnssid);
2243
394
            return ONLINE_SET;
2244
394
        }
2245
155
        break;
2246
155
    default:
2247
        // bad count
2248
56
        GPSD_LOG(LOG_WARN, &session->context->errout,
2249
56
                 "NMEA0183: malformed %s - fieldcount(%d)\n",
2250
56
                 field[0], count);
2251
56
        gpsd_zero_satellites(&session->gpsdata);
2252
56
        return ONLINE_SET;
2253
5.19k
    }
2254
2255
4.74k
    if (1 == session->nmea.part) {
2256
        /*
2257
         * might have gone from GPGSV to GLGSV/BDGSV/QZGSV,
2258
         * in which case accumulate
2259
         *
2260
         * NMEA 4.1 might have gone from GPGVS,sigid=x to GPGSV,sigid=y
2261
         *
2262
         * Quectel EG25 can go GLGSV, PQGSV, GAGSV, GPGSV, in one cycle.
2263
         *
2264
         * session->nmea.last_gsv_talker is zero at cycle start
2265
         */
2266
621
        if ('\0' == session->nmea.last_gsv_talker) {
2267
            // Assume all xxGSV in same epoch.  Clear at 1st in eopoch.
2268
143
            GPSD_LOG(LOG_PROG, &session->context->errout,
2269
143
                     "NMEA0183: %s: new part %d, last_gsv_talker '%#x', "
2270
143
                     "zeroing\n",
2271
143
                     field[0],
2272
143
                     session->nmea.part,
2273
143
                     session->nmea.last_gsv_talker);
2274
143
            gpsd_zero_satellites(&session->gpsdata);
2275
143
        }
2276
621
    }
2277
2278
4.74k
    session->nmea.last_gsv_talker = GSV_TALKER;
2279
4.74k
    switch (GSV_TALKER) {
2280
1.15k
    case 'A':        // GA Galileo
2281
1.15k
        nmea_gnssid = 3;
2282
        // Quectel LC79D can have sigid 6 (L1-A) and 1 (E5a)
2283
1.15k
        session->nmea.seen_gagsv = true;
2284
1.15k
        break;
2285
488
    case 'B':        // GB BeiDou
2286
488
        FALLTHROUGH
2287
595
    case 'D':        // BD BeiDou
2288
595
        nmea_gnssid = 4;
2289
595
        session->nmea.seen_bdgsv = true;
2290
595
        break;
2291
172
    case 'I':        // GI IRNSS
2292
172
        nmea_gnssid = 6;
2293
172
        session->nmea.seen_gigsv = true;
2294
172
        break;
2295
139
    case 'L':        // GL GLONASS
2296
139
        nmea_gnssid = 2;
2297
139
        session->nmea.seen_glgsv = true;
2298
139
        break;
2299
46
    case 'N':        // GN GNSS
2300
46
        session->nmea.seen_gngsv = true;
2301
46
        break;
2302
982
    case 'P':        // GP GPS
2303
982
        session->nmea.seen_gpgsv = true;
2304
982
        break;
2305
301
    case 'Q':        // $GQ, and $PQ (Quectel Querk) QZSS
2306
301
        if ('P' == field[0][0] &&
2307
259
            0 != nmea_gnssid) {
2308
            /* Quectel EC25 & EC21 use PQGSV for BeiDou or QZSS
2309
             * 4 = BeiDou, 5 = QZSS
2310
             * nmea_gnssid set above, what about seen?
2311
             */
2312
138
            if (4 == nmea_gnssid) {
2313
67
                session->nmea.seen_bdgsv = true;
2314
71
            } else if (5 == nmea_gnssid) {
2315
71
                session->nmea.seen_qzgsv = true;
2316
71
            } else {
2317
0
                GPSD_LOG(LOG_WARN, &session->context->errout,
2318
0
                         "NMEA0183: %s: invalid nmea_gnssid %d\n",
2319
0
                         field[0], nmea_gnssid);
2320
0
                return ONLINE_SET;
2321
0
            }
2322
138
            break;
2323
138
        }
2324
        // else $GQ
2325
163
        FALLTHROUGH
2326
229
    case 'Z':        // QZ QZSS
2327
229
        nmea_gnssid = 5;
2328
229
        session->nmea.seen_qzgsv = true;
2329
229
        break;
2330
1.28k
    default:
2331
        // uh, what?
2332
1.28k
        GPSD_LOG(LOG_WARN, &session->context->errout,
2333
1.28k
                 "NMEA0183: %s: unknown nmea_gnssid %d\n",
2334
1.28k
                 field[0], nmea_gnssid);
2335
1.28k
        break;
2336
4.74k
    }
2337
2338
    // If NMEA 4.10, or later,then, or Quectel
2339
4.74k
    if (0 != nmea_sigid) {
2340
        // get ubx sig_id from nmea_gnssid, nmea_sigid, get from talker ID
2341
705
        ubx_sigid = nmea_sigid_to_ubx(session, nmea_gnssid, nmea_sigid);
2342
705
    }
2343
4.74k
    session->nmea.last_gsv_sigid = ubx_sigid;  // UNUSED
2344
2345
4.74k
    GPSD_LOG(LOG_PROG, &session->context->errout,
2346
4.74k
             "NMEA0183: %s: part %d of %d nmea_gnssid %d nmea_sigid %d "
2347
4.74k
             "ubx_sigid %d\n",
2348
4.74k
             field[0], session->nmea.part, session->nmea.await,
2349
4.74k
             nmea_gnssid, nmea_sigid, ubx_sigid);
2350
2351
18.5k
    for (fldnum = 4; fldnum < count / 4 * 4;) {
2352
13.8k
        struct satellite_t *sp;
2353
13.8k
        int nmea_svid;
2354
2355
13.8k
        if (MAXCHANNELS < session->gpsdata.satellites_visible) {
2356
50
            GPSD_LOG(LOG_ERROR, &session->context->errout,
2357
50
                     "NMEA0183: %s: internal error - too many "
2358
50
                     "satellites [%d]!\n",
2359
50
                     field[0], session->gpsdata.satellites_visible);
2360
50
            gpsd_zero_satellites(&session->gpsdata);
2361
50
            break;
2362
50
        }
2363
13.8k
        sp = &session->gpsdata.skyview[session->gpsdata.satellites_visible];
2364
13.8k
        nmea_svid = atoi(field[fldnum++]);
2365
13.8k
        if (0 == nmea_svid) {
2366
            // skip bogus fields
2367
7.84k
            continue;
2368
7.84k
        }
2369
5.96k
        sp->PRN = (short)nmeaid_to_prn(field[0], nmea_svid, nmea_gnssid,
2370
5.96k
                                       &sp->gnssid, &sp->svid);
2371
2372
5.96k
        sp->elevation = (double)atoi(field[fldnum++]);
2373
5.96k
        sp->azimuth = (double)atoi(field[fldnum++]);
2374
5.96k
        sp->ss = (double)atoi(field[fldnum++]);
2375
5.96k
        sp->used = false;
2376
5.96k
        sp->sigid = ubx_sigid;
2377
2378
        /* sadly NMEA 4.1 does not tell us which sigid (L1, L2) is
2379
         * used.  So if the ss is zero, do not mark used */
2380
5.96k
        if (0 < sp->PRN &&
2381
4.85k
            0 < sp->ss) {
2382
330k
            for (n = 0; n < MAXCHANNELS; n++) {
2383
328k
                if (session->nmea.sats_used[n] == (unsigned short)sp->PRN) {
2384
331
                    sp->used = true;
2385
331
                    break;
2386
331
                }
2387
328k
            }
2388
1.75k
        }
2389
#if 0   // debug
2390
        GPSD_LOG(LOG_SHOUT, &session->context->errout,
2391
                 "NMEA0183: %s nmea_gnssid %d nmea_satnum %d ubx_gnssid %d "
2392
                 "ubx_svid %d nmea2_prn %d az %.1f el %.1f used %d\n",
2393
                 field[0], nmea_gnssid, nmea_svid, sp->gnssid, sp->svid,
2394
                 sp->PRN, sp->elevation, sp->azimuth, sp->used);
2395
#endif  // debug
2396
2397
        /*
2398
         * Incrementing this unconditionally falls afoul of chipsets like
2399
         * the Motorola Oncore GT+ that emit empty fields at the end of the
2400
         * last sentence in a GPGSV set if the number of satellites is not
2401
         * a multiple of 4.
2402
         */
2403
5.96k
        session->gpsdata.satellites_visible++;
2404
5.96k
    }
2405
2406
#if 0    // debug code
2407
    GPSD_LOG(LOG_SHOUT, &session->context->errout,
2408
        "NMEA0183: %s: vis %d bdgsv %d gagsv %d gigsv %d glgsv %d "
2409
        "gngsv %d qpgsv %d qzgsv %d\n",
2410
        field[0],
2411
        session->gpsdata.satellites_visible,
2412
        session->nmea.seen_bdgsv,
2413
        session->nmea.seen_gagsv,
2414
        session->nmea.seen_gigsv,
2415
        session->nmea.seen_glgsv,
2416
        session->nmea.seen_gngsv,
2417
        session->nmea.seen_gpgsv,
2418
        session->nmea.seen_qzgsv);
2419
#endif  // debug
2420
2421
#if 0
2422
    /*
2423
     * Alas, we can't sanity check field counts when there are multiple sat
2424
     * pictures, because the visible member counts *all* satellites - you
2425
     * get a bad result on the second and later SV spans.  Note, this code
2426
     * assumes that if any of the special sat pics occur they come right
2427
     * after a stock GPGSV one.
2428
     *
2429
     * FIXME: Add per-talker totals so we can do this check properly.
2430
     */
2431
    if (!(session->nmea.seen_bdgsv ||
2432
          session->nmea.seen_gagsv ||
2433
          session->nmea.seen_gigsv ||
2434
          session->nmea.seen_glgsv ||
2435
          session->nmea.seen_gngsv ||
2436
          session->nmea.seen_qzgsv)) {
2437
        if (session->nmea.part == session->nmea.await
2438
                && atoi(field[3]) != session->gpsdata.satellites_visible) {
2439
            GPSD_LOG(LOG_WARN, &session->context->errout,
2440
                     "NMEA0183: %s field 3 value of %d != actual count %d\n",
2441
                     field[0], atoi(field[3]),
2442
                     session->gpsdata.satellites_visible);
2443
        }
2444
    }
2445
#endif    // FIXME
2446
2447
    // not valid data until we've seen a complete set of parts
2448
4.74k
    if (session->nmea.part < session->nmea.await) {
2449
2.16k
        GPSD_LOG(LOG_PROG, &session->context->errout,
2450
2.16k
                 "NMEA0183: %s: Partial satellite data (%d of %d).\n",
2451
2.16k
                 field[0], session->nmea.part, session->nmea.await);
2452
2.16k
        session->nmea.gsx_more = true;
2453
2.16k
        return ONLINE_SET;
2454
2.16k
    }
2455
2.57k
    session->nmea.gsx_more = false;
2456
2.57k
    if (MAXCHANNELS < session->gpsdata.satellites_visible) {
2457
64
        GPSD_LOG(LOG_WARN, &session->context->errout,
2458
64
                "NMEA0183: %s too many satellites %d\n", field[0],
2459
64
                 session->gpsdata.satellites_visible);
2460
64
        session->gpsdata.satellites_visible = MAXCHANNELS;
2461
64
    }
2462
    /*
2463
     * This sanity check catches an odd behavior of SiRFstarII receivers.
2464
     * When they can't see any satellites at all (like, inside a
2465
     * building) they sometimes cough up a hairball in the form of a
2466
     * GSV packet with all the azimuth entries 0 (but nonzero
2467
     * elevations).  This behavior was observed under SiRF firmware
2468
     * revision 231.000.000_A2.
2469
     */
2470
2.57k
    sane = 0;
2471
5.13k
    for (n = 0; n < (unsigned)session->gpsdata.satellites_visible; n++) {
2472
4.57k
        if (0 != session->gpsdata.skyview[n].azimuth) {
2473
2.02k
            sane = 1;
2474
2.02k
            break;
2475
2.02k
        }
2476
4.57k
    }
2477
2478
2.57k
    if (0 == sane) {
2479
556
        GPSD_LOG(LOG_WARN, &session->context->errout,
2480
556
                 "NMEA0183: %s: Satellite data no good (%d of %d).\n",
2481
556
                 field[0], session->nmea.part, session->nmea.await);
2482
556
        gpsd_zero_satellites(&session->gpsdata);
2483
556
        return ONLINE_SET;
2484
556
    }
2485
2486
2.02k
    session->gpsdata.skyview_time.tv_sec = 0;
2487
2.02k
    session->gpsdata.skyview_time.tv_nsec = 0;
2488
2.02k
    GPSD_LOG(LOG_PROG, &session->context->errout,
2489
2.02k
             "NMEA0183: %s: Satellite data OK (%d of %d).\n",
2490
2.02k
             field[0], session->nmea.part, session->nmea.await);
2491
2492
    /* assumes GLGSV or BDGSV group, if present, is emitted after the GPGSV
2493
     * An assumption that Quectel breaks;  The EG25 can send in one epoch:
2494
     * $GLGSV, $PQGSV, $GAGSV, then $GPGSV! */
2495
2.02k
    if ((session->nmea.seen_bdgsv ||
2496
0
         session->nmea.seen_gagsv ||
2497
0
         session->nmea.seen_gigsv ||
2498
0
         session->nmea.seen_glgsv ||
2499
0
         session->nmea.seen_gngsv ||
2500
0
         session->nmea.seen_qzgsv) &&
2501
2.02k
        ('P' == GSV_TALKER &&
2502
737
         'P' != session->nmea.end_gsv_talker)) {
2503
427
        GPSD_LOG(LOG_PROG, &session->context->errout,
2504
427
                 "NMEA0183: %s: not end talker %d\n",
2505
427
                 field[0], session->nmea.end_gsv_talker);
2506
427
        return ONLINE_SET;
2507
427
    }
2508
2509
#if 0   // debug code
2510
    {
2511
        char ts_buf[TIMESPEC_LEN];
2512
        char ts_buf1[TIMESPEC_LEN];
2513
        GPSD_LOG(LOG_SHOUT, &session->context->errout,
2514
            "NMEA0183: %s: set skyview_time %s frac_time %s\n",
2515
            field[0],
2516
            timespec_str(&session->gpsdata.skyview_time, ts_buf,
2517
                         sizeof(ts_buf)),
2518
            timespec_str(&session->nmea.this_frac_time, ts_buf1,
2519
                         sizeof(ts_buf1)));
2520
    }
2521
#endif  // debug
2522
2523
1.59k
    return SATELLITE_SET;
2524
2.02k
#undef GSV_TALKER
2525
2.02k
}
2526
2527
/*
2528
 * Unicore $GYOACC  MEMS Sensor DAta
2529
 * Note: Invalid sender: $GY
2530
 */
2531
static gps_mask_t processGYOACC(unsigned count UNUSED, char *field[],
2532
                                struct gps_device_t *session)
2533
160
{
2534
    /*
2535
     * $GYOACC,050624,002133.10,0.004634,0.000273,0.004348,100,-4.666065,
2536
     *    -3.466573,7.960348,100,31,0,100,0*02
2537
     */
2538
160
    gps_mask_t mask = ONLINE_SET;
2539
160
    double gyroX = safe_atof(field[3]);       // deg/s
2540
160
    double gyroY = safe_atof(field[4]);       // deg/s
2541
160
    double gyroZ = safe_atof(field[5]);       // deg/s
2542
160
    unsigned gyroPeriod = atoi(field[6]);     // period in ms
2543
160
    double accX = safe_atof(field[7]);        // m/s^2
2544
160
    double accY = safe_atof(field[8]);        // m/s^2
2545
160
    double accZ = safe_atof(field[9]);        // m/s^2
2546
160
    unsigned accPeriod = atoi(field[10]);     // period in ms
2547
160
    int temp = atoi(field[11]);               // temperature C
2548
160
    unsigned speed = atoi(field[12]);         // pulses
2549
160
    unsigned pulsePeriod = atoi(field[13]);   // period in ms
2550
160
    unsigned fwd = atoi(field[14]);           // 0 == forward, 1 == reverse
2551
160
    struct tm date = {0};
2552
160
    timespec_t ts = {0};
2553
2554
    // Not at the same rate at the GNSS epoch. So do not use session->nmea
2555
160
    if (0 == decode_hhmmss(&date, &ts.tv_nsec, field[2], session) &&
2556
63
        0 == decode_ddmmyy(&date, field[1], session)) {
2557
2558
0
        session->gpsdata.attitude.mtime.tv_sec = mkgmtime(&date);
2559
0
        session->gpsdata.attitude.mtime.tv_nsec = ts.tv_nsec;
2560
160
    } else {
2561
160
        session->gpsdata.attitude.mtime.tv_sec = 0;
2562
160
        session->gpsdata.attitude.mtime.tv_nsec = 0;
2563
160
    }
2564
2565
160
    session->gpsdata.attitude.gyro_x = gyroX;
2566
160
    session->gpsdata.attitude.gyro_y = gyroY;
2567
160
    session->gpsdata.attitude.gyro_z = gyroZ;
2568
160
    session->gpsdata.attitude.acc_x = accX;
2569
160
    session->gpsdata.attitude.acc_y = accY;
2570
160
    session->gpsdata.attitude.acc_z = accZ;
2571
160
    mask |= ATTITUDE_SET;
2572
2573
160
    GPSD_LOG(LOG_DATA, &session->context->errout,
2574
160
             "NMEA0183: $GYOACC time %lld.%09lld "
2575
160
             "gyro X %.6f Y %.6f Z %.6f per %u "
2576
160
             "acc X %.6f Y %.6f Z %.6f per %u "
2577
160
             "temp %d speed %u per %u fwd %u\n",
2578
160
             (long long)session->gpsdata.attitude.mtime.tv_sec,
2579
160
             (long long)session->gpsdata.attitude.mtime.tv_nsec,
2580
160
             gyroX, gyroY, gyroZ, gyroPeriod,
2581
160
             accX, accY, accZ, accPeriod,
2582
160
             temp, speed, pulsePeriod, fwd);
2583
160
    return mask;
2584
160
}
2585
2586
static gps_mask_t processHDG(unsigned count UNUSED, char *field[],
2587
                             struct gps_device_t *session)
2588
1.04k
{
2589
    /*
2590
     *  $SDHDG,234.6,,,1.3,E*34
2591
     *
2592
     *  $--HDG,h.h,d.d,a,v.v,a*hh<CR><LF>
2593
     *  Magnetic sensor heading, degrees
2594
     *  Magnetic deviation, degrees E/W
2595
     *  Magnetic variation, degrees, E/W
2596
     *
2597
     *  1. To obtain Magnetic Heading:
2598
     *  Add Easterly deviation (E) to Magnetic Sensor Reading
2599
     *  Subtract Westerly deviation (W) from Magnetic Sensor Reading
2600
     *  2. To obtain True Heading:
2601
     *  Add Easterly variation (E) to Magnetic Heading
2602
     *  Subtract Westerly variation (W) from Magnetic Heading
2603
     *  3. Variation and deviation fields shall be null fields if unknown.
2604
     */
2605
2606
1.04k
    gps_mask_t mask = ONLINE_SET;
2607
1.04k
    double sensor_heading;
2608
1.04k
    double magnetic_deviation;
2609
2610
1.04k
    if ('\0' == field[1][0]) {
2611
        // no data
2612
83
        return mask;
2613
83
    }
2614
960
    sensor_heading = safe_atof(field[1]);
2615
960
    if ((0.0 > sensor_heading) ||
2616
890
        (360.0 < sensor_heading)) {
2617
        // bad data */
2618
178
        return mask;
2619
178
    }
2620
782
    magnetic_deviation = safe_atof(field[2]);
2621
782
    if ((0.0 > magnetic_deviation) ||
2622
574
        (360.0 < magnetic_deviation)) {
2623
        // bad data
2624
279
        return mask;
2625
279
    }
2626
503
    switch (field[2][0]) {
2627
101
    case 'E':
2628
101
        sensor_heading += magnetic_deviation;
2629
101
        break;
2630
85
    case 'W':
2631
85
        sensor_heading += magnetic_deviation;
2632
85
        break;
2633
317
    default:
2634
        // ignore
2635
317
        break;
2636
503
    }
2637
2638
    // good data
2639
503
    session->newdata.magnetic_track = sensor_heading;
2640
503
    mask |= MAGNETIC_TRACK_SET;
2641
2642
    // get magnetic variation
2643
503
    if ('\0' != field[3][0] &&
2644
423
        '\0' != field[4][0]) {
2645
283
        session->newdata.magnetic_var = safe_atof(field[3]);
2646
2647
283
        switch (field[4][0]) {
2648
117
        case 'E':
2649
            // no change
2650
117
            mask |= MAGNETIC_TRACK_SET;
2651
117
            break;
2652
72
        case 'W':
2653
72
            session->newdata.magnetic_var = -session->newdata.magnetic_var;
2654
72
            mask |= MAGNETIC_TRACK_SET;
2655
72
            break;
2656
94
        default:
2657
            // huh?
2658
94
            session->newdata.magnetic_var = NAN;
2659
94
            break;
2660
283
        }
2661
283
    }
2662
2663
2664
503
    GPSD_LOG(LOG_DATA, &session->context->errout,
2665
503
             "NMEA0183: $SDHDG heading %lf var %.1f\n",
2666
503
             session->newdata.magnetic_track,
2667
503
             session->newdata.magnetic_var);
2668
503
    return mask;
2669
503
}
2670
2671
/* precessHDM() - process magnetic headingxxHDM messages
2672
 *
2673
 * Deprecated by NMEA in 2008
2674
 */
2675
static gps_mask_t processHDM(unsigned count UNUSED, char *field[],
2676
                             struct gps_device_t *session)
2677
148
{
2678
    /*
2679
     * $APHDM,218.634,M*39
2680
     *
2681
     * 1) Magnetic heading
2682
     * 2) M == Magnetic
2683
     * )  checksum
2684
     *
2685
     */
2686
148
    gps_mask_t mask = ONLINE_SET;
2687
2688
148
    if ('\0' == field[1][0]) {
2689
        // no data
2690
71
        return mask;
2691
71
    }
2692
2693
    // assume good data
2694
77
    session->gpsdata.attitude.mheading = safe_atof(field[1]);
2695
77
    mask |= ATTITUDE_SET;
2696
2697
77
    GPSD_LOG(LOG_PROG, &session->context->errout,
2698
77
             "NMEA0183: $xxHDM: Magnetic heading %f\n",
2699
77
             session->gpsdata.attitude.mheading);
2700
77
    return mask;
2701
148
}
2702
2703
static gps_mask_t processHDT(unsigned count UNUSED, char *field[],
2704
                             struct gps_device_t *session)
2705
228
{
2706
    /*
2707
     * $HEHDT,341.8,T*21
2708
     *
2709
     * $xxHDT,x.x*hh<cr><lf>
2710
     *
2711
     * The only data field is true heading in degrees.
2712
     * The following field is required to be 'T' indicating a true heading.
2713
     * It is followed by a mandatory nmea_checksum.
2714
     */
2715
228
    gps_mask_t mask = ONLINE_SET;
2716
228
    double heading;
2717
2718
228
    if ('\0' == field[1][0]) {
2719
        // no data
2720
68
        return mask;
2721
68
    }
2722
160
    heading = safe_atof(field[1]);
2723
160
    if (0.0 > heading ||
2724
93
        360.0 < heading) {
2725
        // bad data
2726
89
        return mask;
2727
89
    }
2728
    // True heading
2729
71
    session->gpsdata.attitude.heading = heading;
2730
2731
71
    mask |= ATTITUDE_SET;
2732
2733
71
    GPSD_LOG(LOG_PROG, &session->context->errout,
2734
71
             "NMEA0183: $xxHDT heading %lf.\n",
2735
71
             session->gpsdata.attitude.heading);
2736
71
    return mask;
2737
160
}
2738
2739
/* $INFO, Inertial Sense product info
2740
 * Not a legal NMEA message name
2741
 * https://docs.inertialsense.com/user-manual/com-protocol/nmea/#info
2742
 */
2743
static gps_mask_t processINFO(unsigned count UNUSED, char *field[],
2744
                              struct gps_device_t *session)
2745
358
{
2746
    /*
2747
     * $INFO,928404541,1.0.2.0,2.2.2.0,-377462659,2.0.0.0,-53643429,
2748
     *    Inertial Sense Inc,2025-01-10,16:06:13.50,GPX -1,4,0, *7D
2749
     *
2750
     * 1  Serial number    Manufacturer serial number
2751
     * 2  Hardware version Hardware version
2752
     * 3  Firmware version Firmware version
2753
     * 4  Build number     Firmware build number
2754
     * 5  Protocol version Communications protocol version
2755
     * 6  Repo revision    Repository revision number
2756
     * 7  Manufacturer     Manufacturer name
2757
     * 8  Build date       Build date
2758
     * 9  Build time       Build time
2759
     * 10 Add Info         Additional information
2760
     * 11 Hardware         Hardware: 1=uINS, 2=EVB, 3=IMX, 4=GPX
2761
     * 12 Reserved         Reserved for internal purpose.
2762
     * 13 Build type       Build type:
2763
     *  'a'=ALPHA, 'b'=BETA, 'c'=RELEASE CANDIDATE, 'r'=PRODUCTION RELEASE,
2764
     *  'd'=debug, ' '= ????
2765
     */
2766
2767
    // hardwaare
2768
358
    static struct clist_t hardware[] = {
2769
358
        {'1', "uISN"},
2770
358
        {'2', "EVB"},
2771
358
        {'3', "INX"},
2772
358
        {'4', "GPX"},
2773
358
        {'\0', NULL},
2774
358
    };
2775
2776
358
    if ('\0' == session->subtype[0] &&
2777
120
        !session->context->passive) {
2778
        // first time seen, send init
2779
2780
54
        (void)nmea_send(session, "$STPC");   // stop all messages
2781
2782
        /* Enable all possible NMEA messages, at 1Hz
2783
         * 1 PIMU, 2 PPIMU, 3 PRIMU, 4 PINS1, 5 PINS2
2784
         * 6 PGPSP, 7 GGA, 8 GLL, 9 GSA, 10 RMC, 11 ZDA, 12 PASHR
2785
         * 13 PSTRB, 14 INFO, 15 GSV, 16 VTG
2786
         * there are many more...
2787
         */
2788
54
        (void)nmea_send(session,
2789
54
                        "$ASCE,0,"    // Set current port
2790
54
                        "1,0,"        // PIMU
2791
54
                        "2,0,"        // PPIMU
2792
54
                        "3,0,"        // PRIMU
2793
54
                        "4,0,"        // PINS1
2794
54
                        "5,0,"        // PINS2
2795
54
                        "6,5,"        // PGPSP
2796
54
                        "7,5,"        // GGA
2797
54
                        "8,5,"        // GLL
2798
54
                        "9,5,"        // GSA
2799
54
                        "10,5,"       // RMC
2800
54
                        "11,5,"       // ZDA
2801
54
                        "12,5,"       // PASHR
2802
54
                        "13,5,"       // PSTRB
2803
54
                        "14,0,"       // INFO
2804
54
                        "15,5,"       // GSV
2805
54
                        "16,5,"       // VTG
2806
54
                        "17,5,"       // ?
2807
54
                        "18,5");      // ?
2808
54
    }
2809
2810
    // save serial number
2811
358
    strlcpy(session->gpsdata.dev.sernum, field[1],
2812
358
            sizeof(session->gpsdata.dev.sernum));
2813
    // save HW as subtype
2814
358
    (void)snprintf(session->subtype, sizeof(session->subtype),
2815
358
                   "%s-%.11s",
2816
358
                   char2str(field[11][0], hardware), field[2]);
2817
    // save FW Version as subtype1
2818
358
    (void)snprintf(session->subtype1, sizeof(session->subtype1),
2819
358
                   "FW %.11s",
2820
358
                   field[3]);
2821
2822
358
    GPSD_LOG(LOG_WARN, &session->context->errout,
2823
358
             "NMEA0183: INFO: serial %s subtype %s subtype1 %s\n",
2824
358
             session->gpsdata.dev.sernum, session->subtype, session->subtype1);
2825
2826
358
    return ONLINE_SET;
2827
358
}
2828
2829
static gps_mask_t processMTW(unsigned count UNUSED, char *field[],
2830
                             struct gps_device_t *session)
2831
216
{
2832
    /* Water temp in degrees C
2833
     * $--MTW,x.x,C*hh<CR><LF>
2834
     *
2835
     * Fields in order:
2836
     * 1. water temp degrees C
2837
     * 2. C
2838
     * *hh          mandatory nmea_checksum
2839
     */
2840
216
    gps_mask_t mask = ONLINE_SET;
2841
2842
216
    if ('\0' == field[1][0] ||
2843
146
        'C' != field[2][0]) {
2844
        // no temp
2845
143
        return mask;
2846
143
    }
2847
73
    session->newdata.wtemp = safe_atof(field[1]);
2848
2849
73
    GPSD_LOG(LOG_PROG, &session->context->errout,
2850
73
        "NMEA0183: %s temp %.1f C\n",
2851
73
        field[0], session->newdata.wtemp);
2852
73
    return mask;
2853
216
}
2854
2855
static gps_mask_t processMWD(unsigned count UNUSED, char *field[],
2856
                             struct gps_device_t *session)
2857
137
{
2858
    /*
2859
     * xxMWD - Wind direction and speed
2860
     * $xxMWD,x.x,T,x.x,M,x.x,N,x.x,M*hh<cr><lf>
2861
     * Fields in order:
2862
     * 1. wind direction, 0 to 359, True
2863
     * 2. T
2864
     * 3. wind direction, 0 to 359, Magnetic
2865
     * 4. M
2866
     * 5. wind speed, knots
2867
     * 6. N
2868
     * 7. wind speed, meters/sec
2869
     * 8. M
2870
     * *hh          mandatory nmea_checksum
2871
     */
2872
137
    gps_mask_t mask = ONLINE_SET;
2873
2874
137
    session->newdata.wanglet = safe_atof(field[1]);
2875
137
    session->newdata.wanglem = safe_atof(field[3]);
2876
137
    session->newdata.wspeedt = safe_atof(field[7]);
2877
137
    mask |= NAVDATA_SET;
2878
2879
137
    GPSD_LOG(LOG_DATA, &session->context->errout,
2880
137
        "NMEA0183: xxMWD wanglet %.2f wanglem %.2f wspeedt %.2f\n",
2881
137
        session->newdata.wanglet,
2882
137
        session->newdata.wanglem,
2883
137
        session->newdata.wspeedt);
2884
137
    return mask;
2885
137
}
2886
2887
static gps_mask_t processMWV(unsigned count UNUSED, char *field[],
2888
                             struct gps_device_t *session)
2889
274
{
2890
    /*
2891
     * xxMWV - Wind speed and angle
2892
     * $xxMWV,x.x,a,x.x,a,A*hh<cr><lf>
2893
     * Fields in order:
2894
     * 1. wind angle, 0 to 359, True
2895
     * 2. R = Relative (apparent), T = Theoretical (calculated)
2896
     *    Is T magnetic or true??
2897
     * 3. wind speed
2898
     * 4. wind speed units K/M/N/S
2899
     * 6. A = Valid, V = invalid
2900
     * *hh          mandatory nmea_checksum
2901
     */
2902
274
    gps_mask_t mask = ONLINE_SET;
2903
2904
274
    if (('R' == field[2][0]) &&
2905
208
        ('N' == field[4][0]) &&
2906
139
        ('A' == field[5][0])) {
2907
        // relative, knots, and valid
2908
67
        session->newdata.wangler = safe_atof(field[1]);
2909
67
        session->newdata.wspeedr = safe_atof(field[3]) * KNOTS_TO_MPS;
2910
67
        mask |= NAVDATA_SET;
2911
67
    }
2912
2913
274
    GPSD_LOG(LOG_DATA, &session->context->errout,
2914
274
        "NMEA0183: xxMWV wangler %.2f wspeedr %.2f\n",
2915
274
        session->newdata.wangler,
2916
274
        session->newdata.wspeedr);
2917
274
    return mask;
2918
274
}
2919
2920
// PAIRxxx is Airoha, spunoff from Mediatek
2921
2922
// PAIR001 -- ACK/NAK
2923
static gps_mask_t processPAIR001(unsigned count UNUSED, char *field[],
2924
                                 struct gps_device_t *session)
2925
304
{
2926
304
    int reason;
2927
304
    const char *reasons[] = {
2928
304
        "Success",
2929
304
        "In process, wait",
2930
304
        "Failed",
2931
304
        "Not supported",
2932
304
        "Busy, try again.",
2933
304
        "Unknown",             // gpsd only
2934
304
    };
2935
2936
    // ACK / NACK
2937
304
    reason = atoi(field[2]);
2938
304
    if (4 == reason) {
2939
        // ACK
2940
63
        GPSD_LOG(LOG_PROG, &session->context->errout,
2941
63
                 "NMEA0183: PAIR001, ACK: %s\n", field[1]);
2942
63
        return ONLINE_SET;
2943
63
    }
2944
2945
    // else, NACK
2946
241
    if (0 > reason ||
2947
172
        4 < reason) {
2948
        // WTF?
2949
172
        reason = 5;
2950
172
    }
2951
241
    GPSD_LOG(LOG_WARN, &session->context->errout,
2952
241
             "NMEA0183: PAIR NACK: %s, reason: %s\n",
2953
241
             field[1], reasons[reason]);
2954
2955
241
    return ONLINE_SET;
2956
304
}
2957
2958
// PAIR010 -- Request Aiding
2959
static gps_mask_t processPAIR010(unsigned count UNUSED, char *field[],
2960
                                 struct gps_device_t *session)
2961
308
{
2962
308
    int type;
2963
308
    const char *types[] = {
2964
308
        "EPO data",
2965
308
        "Time",
2966
308
        "Location",
2967
308
        "Unknown",             // gpsd only
2968
308
    };
2969
2970
308
    int system;
2971
308
    const char *systems[] = {
2972
308
        "GPS",
2973
308
        "GLONASS",
2974
308
        "Galileo",
2975
308
        "BDS",
2976
308
        "QZSS",
2977
308
        "Unknown",             // gpsd only
2978
308
    };
2979
308
    int wn;         // week number
2980
308
    int tow;        // time of week
2981
2982
308
    type = atoi(field[1]);
2983
308
    if (0 > type ||
2984
242
        2 < type) {
2985
        // WTF?
2986
165
        type = 3;
2987
165
    }
2988
308
    system = atoi(field[2]);
2989
308
    if (0 > system ||
2990
224
        4 < system) {
2991
        // WTF?
2992
165
        system = 5;
2993
165
    }
2994
308
    wn = atoi(field[3]);
2995
308
    tow = atoi(field[4]);
2996
308
    GPSD_LOG(LOG_WARN, &session->context->errout,
2997
308
             "NMEA0183: PAIR010: Need %s for %s.  WN %d TOW %d\n",
2998
308
             types[type], systems[system], wn, tow);
2999
3000
308
    return ONLINE_SET;
3001
308
}
3002
3003
// PDTINFO Unicore Product Information
3004
static gps_mask_t processPDTINFO(unsigned count UNUSED, char *field[],
3005
                                 struct gps_device_t *session)
3006
50
{
3007
50
    (void)snprintf(session->subtype, sizeof(session->subtype),
3008
50
                   "%s, %s, %s",
3009
50
                   field[1], field[2], field[5]);
3010
    // save SW and HW Version as subtype1
3011
50
    (void)snprintf(session->subtype1, sizeof(session->subtype1),
3012
50
                   "SW %s,HW %s",
3013
50
                   field[4], field[3]);
3014
3015
50
    GPSD_LOG(LOG_WARN, &session->context->errout,
3016
50
             "NMEA0183: PDTINFO: subtype %s subtype1 %s\n",
3017
50
             session->subtype, session->subtype1);
3018
3019
50
    return ONLINE_SET;
3020
50
}
3021
3022
/* Ashtech sentences take this format:
3023
 * $PASHDR,type[,val[,val]]*CS
3024
 * type is an alphabetic subsentence type
3025
 *
3026
 * Oxford Technical Solutions (OxTS) also uses the $PASHR sentence,
3027
 * but with a very different sentence contents:
3028
 * $PASHR,HHMMSS.SSS,HHH.HH,T,RRR.RR,PPP.PP,aaa.aa,r.rrr,p.ppp,h.hhh,Q1,Q2*CS
3029
 *
3030
 * so field 1 in ASHTECH is always alphabetic and numeric in OXTS
3031
 *
3032
 */
3033
static gps_mask_t processPASHR(unsigned count UNUSED, char *field[],
3034
                               struct gps_device_t *session)
3035
2.20k
{
3036
2.20k
    gps_mask_t mask = ONLINE_SET;
3037
2.20k
    char ts_buf[TIMESPEC_LEN];
3038
3039
2.20k
    if (0 == strcmp("ACK", field[1])) {
3040
        // ACK
3041
69
        GPSD_LOG(LOG_DATA, &session->context->errout, "NMEA0183: PASHR,ACK\n");
3042
69
        return ONLINE_SET;
3043
2.13k
    } else if (0 == strcmp("MCA", field[1])) {
3044
        // MCA, raw data
3045
108
        GPSD_LOG(LOG_DATA, &session->context->errout, "NMEA0183: PASHR,MCA\n");
3046
108
        return ONLINE_SET;
3047
2.02k
    } else if (0 == strcmp("NAK", field[1])) {
3048
        // NAK
3049
44
        GPSD_LOG(LOG_DATA, &session->context->errout, "NMEA0183: PASHR,NAK\n");
3050
44
        return ONLINE_SET;
3051
1.98k
    } else if (0 == strcmp("PBN", field[1])) {
3052
        // PBN, position data
3053
        // FIXME: decode this for ECEF
3054
75
        GPSD_LOG(LOG_DATA, &session->context->errout, "NMEA0183: PASHR,PBN\n");
3055
75
        return ONLINE_SET;
3056
1.90k
    } else if (0 == strcmp("POS", field[1])) {  // 3D Position
3057
        /* $PASHR,POS,
3058
         *
3059
         * 2: position type:
3060
         *      0 = autonomous
3061
         *      1 = position differentially corrected with RTCM code
3062
         *      2 = position differentially corrected with CPD float solution
3063
         *      3 = position is CPD fixed solution
3064
         */
3065
1.02k
        mask |= MODE_SET | STATUS_SET | CLEAR_IS;
3066
1.02k
        if ('\0' == field[2][0]) {
3067
            // empty first field means no 3D fix is available
3068
97
            session->newdata.status = STATUS_UNK;
3069
97
            session->newdata.mode = MODE_NO_FIX;
3070
924
        } else {
3071
3072
            // if we make it this far, we at least have a 3D fix
3073
924
            session->newdata.mode = MODE_3D;
3074
924
            if (1 <= atoi(field[2]))
3075
195
                session->newdata.status = STATUS_DGPS;
3076
729
            else
3077
729
                session->newdata.status = STATUS_GPS;
3078
3079
924
            session->nmea.gga_sats_used = atoi(field[3]);
3080
924
            if (0 == merge_hhmmss(field[4], session)) {
3081
168
                register_fractional_time(field[0], field[4], session);
3082
168
                mask |= TIME_SET;
3083
168
            }
3084
924
            if (0 == do_lat_lon(&field[5], &session->newdata)) {
3085
179
                mask |= LATLON_SET;
3086
179
                if ('\0' != field[9][0]) {
3087
                    // altitude is already WGS 84
3088
86
                    session->newdata.altHAE = safe_atof(field[9]);
3089
86
                    mask |= ALTITUDE_SET;
3090
86
                }
3091
179
            }
3092
924
            session->newdata.track = safe_atof(field[11]);
3093
924
            session->newdata.speed = safe_atof(field[12]) / MPS_TO_KPH;
3094
924
            session->newdata.climb = safe_atof(field[13]);
3095
924
            if ('\0' != field[14][0]) {
3096
231
                session->gpsdata.dop.pdop = safe_atof(field[14]);
3097
231
                mask |= DOP_SET;
3098
231
            }
3099
924
            if ('\0' != field[15][0]) {
3100
161
                session->gpsdata.dop.hdop = safe_atof(field[15]);
3101
161
                mask |= DOP_SET;
3102
161
            }
3103
924
            if ('\0' != field[16][0]) {
3104
124
                session->gpsdata.dop.vdop = safe_atof(field[16]);
3105
124
                mask |= DOP_SET;
3106
124
            }
3107
924
            if ('\0' != field[17][0]) {
3108
134
                session->gpsdata.dop.tdop = safe_atof(field[17]);
3109
134
                mask |= DOP_SET;
3110
134
            }
3111
924
            mask |= (SPEED_SET | TRACK_SET | CLIMB_SET);
3112
924
            GPSD_LOG(LOG_DATA, &session->context->errout,
3113
924
                     "NMEA0183: PASHR,POS: hhmmss=%s lat=%.2f lon=%.2f"
3114
924
                     " altHAE=%.f"
3115
924
                     " speed=%.2f track=%.2f climb=%.2f mode=%d status=%d"
3116
924
                     " pdop=%.2f hdop=%.2f vdop=%.2f tdop=%.2f used=%d\n",
3117
924
                     field[4], session->newdata.latitude,
3118
924
                     session->newdata.longitude, session->newdata.altHAE,
3119
924
                     session->newdata.speed, session->newdata.track,
3120
924
                     session->newdata.climb, session->newdata.mode,
3121
924
                     session->newdata.status, session->gpsdata.dop.pdop,
3122
924
                     session->gpsdata.dop.hdop, session->gpsdata.dop.vdop,
3123
924
                     session->gpsdata.dop.tdop, session->nmea.gga_sats_used);
3124
924
        }
3125
1.02k
    } else if (0 == strcmp("RID", field[1])) {  // Receiver ID
3126
70
        (void)snprintf(session->subtype, sizeof(session->subtype) - 1,
3127
70
                       "%s ver %s", field[2], field[3]);
3128
70
        GPSD_LOG(LOG_DATA, &session->context->errout,
3129
70
                 "NMEA0183: PASHR,RID: subtype=%s mask={}\n",
3130
70
                 session->subtype);
3131
70
        return mask;
3132
815
    } else if (0 == strcmp("SAT", field[1])) {  // Satellite Status
3133
327
        struct satellite_t *sp;
3134
327
        unsigned i;
3135
327
        session->gpsdata.satellites_visible = atoi(field[2]);
3136
3137
327
        if (((NMEA_MAX_FLD - 15) / 5) < session->gpsdata.satellites_visible) {
3138
114
            GPSD_LOG(LOG_WARN, &session->context->errout,
3139
114
                    "NMEA0183: PASHR,SAT: too many satellites %d\n",
3140
114
                     session->gpsdata.satellites_visible);
3141
114
            session->gpsdata.satellites_visible = (NMEA_MAX_FLD - 15) / 5;
3142
114
        }
3143
327
        session->gpsdata.satellites_used = 0;
3144
327
        sp = session->gpsdata.skyview;
3145
2.89k
        for (i = 0; i < session->gpsdata.satellites_visible; i++) {
3146
2.56k
            sp[i].PRN = (short)atoi(field[3 + i * 5 + 0]);
3147
2.56k
            sp[i].azimuth = (double)atoi(field[3 + i * 5 + 1]);
3148
2.56k
            sp[i].elevation = (double)atoi(field[3 + i * 5 + 2]);
3149
2.56k
            sp[i].ss = safe_atof(field[3 + i * 5 + 3]);
3150
2.56k
            sp[i].used = false;
3151
2.56k
            if ('U' == field[3 + i * 5 + 4][0]) {
3152
256
                sp[i].used = true;
3153
256
                session->gpsdata.satellites_used++;
3154
256
            }
3155
2.56k
        }
3156
327
        GPSD_LOG(LOG_DATA, &session->context->errout,
3157
327
                 "NMEA0183: PASHR,SAT: used=%d\n",
3158
327
                 session->gpsdata.satellites_used);
3159
327
        session->gpsdata.skyview_time.tv_sec = 0;
3160
327
        session->gpsdata.skyview_time.tv_nsec = 0;
3161
327
        mask |= SATELLITE_SET | USED_IS;
3162
3163
488
    } else if (0 == strcmp("T", field[3])) {   // Assume OxTS PASHR
3164
        // FIXME: decode OxTS $PASHDR, time is wrong, breaks cycle order
3165
169
        if (0 == merge_hhmmss(field[1], session)) {
3166
            // register_fractional_time(field[0], field[1], session);
3167
            // mask |= TIME_SET; confuses cycle order
3168
101
        }
3169
        // Assume true heading
3170
169
        session->gpsdata.attitude.heading = safe_atof(field[2]);
3171
169
        session->gpsdata.attitude.roll = safe_atof(field[4]);
3172
169
        session->gpsdata.attitude.pitch = safe_atof(field[5]);
3173
        // mask |= ATTITUDE_SET;  * confuses cycle order ??
3174
169
        GPSD_LOG(LOG_DATA, &session->context->errout,
3175
169
                 "NMEA0183: PASHR (OxTS) time %s, heading %lf.\n",
3176
169
                  timespec_str(&session->newdata.time, ts_buf, sizeof(ts_buf)),
3177
169
                  session->gpsdata.attitude.heading);
3178
169
    }
3179
1.83k
    return mask;
3180
2.20k
}
3181
3182
/* Ericsson $PERC,FWsts - Firmware status
3183
 * Firmware version and status information for timing modules
3184
 *
3185
 * $PERC,FWsts,<grp1>,<grp2>,<grp3>,<state>,<substatus>,<type>*XX
3186
 *
3187
 * Field 1: grp1 - Status group 1 (6 digits, format XXYYZZ)
3188
 * Field 2: grp2 - Status group 2 (6 digits, format XXYYZZ)
3189
 * Field 3: grp3 - Status group 3 (6 digits, format XXYYZZ)
3190
 * Field 4: state - Firmware state (0-3)
3191
 * Field 5: substatus - Sub-status code (0=normal, 1=0xD, 2=0xE)
3192
 * Field 6: type - Type indicator
3193
 *
3194
 * Periodic sentence (~19s interval) providing firmware status details.
3195
 * Status groups contain device-specific diagnostic information.
3196
 */
3197
static gps_mask_t processPERCFWsts(unsigned count UNUSED, char *field[],
3198
                                   struct gps_device_t *session)
3199
0
{
3200
0
    gps_mask_t mask = ONLINE_SET;
3201
0
    int grp1, grp2, grp3, state, substatus, type;
3202
3203
0
    static const struct vlist_t vfwsts_type[] = {
3204
0
        {0, "Normal"},
3205
0
        {1, "Type 1"},
3206
0
        {2, "Type 2"},
3207
0
        {3, "Type 3"},
3208
0
        {0, NULL},
3209
0
    };
3210
3211
    // field[0]="PERC", field[1]="FWsts", data starts at field[2]
3212
0
    grp1 = atoi(field[2]);
3213
0
    grp2 = atoi(field[3]);
3214
0
    grp3 = atoi(field[4]);
3215
0
    state = atoi(field[5]);
3216
0
    substatus = atoi(field[6]);
3217
0
    type = atoi(field[7]);
3218
3219
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3220
0
             "NMEA0183: PERC,FWsts: groups=%06d,%06d,%06d state=%d "
3221
0
             "substatus=%d type=%s(%d)\n",
3222
0
             grp1, grp2, grp3, state, substatus,
3223
0
             val2str(type, vfwsts_type), type);
3224
3225
0
    return mask;
3226
0
}
3227
3228
/* Ericsson $PERC,GPavp - Averaged position
3229
 * Position-hold reference coordinates for timing modules
3230
 *
3231
 * $PERC,GPavp,<lat>,<lat_ns>,<lon>,<lon_ew>,<alt>,<alt_unit>*XX
3232
 *
3233
 * Field 1: lat - Latitude in ddmm.mmmm format
3234
 * Field 2: lat_ns - Latitude hemisphere (N/S)
3235
 * Field 3: lon - Longitude in dddmm.mmmm format
3236
 * Field 4: lon_ew - Longitude hemisphere (E/W)
3237
 * Field 5: alt - Altitude in meters
3238
 * Field 6: alt_unit - Altitude units (M=meters)
3239
 *
3240
 * This sentence appears only in mode 2 (position-hold) and provides the
3241
 * surveyed average position that the timing module uses as its reference.
3242
 * Format matches standard NMEA position encoding.
3243
 */
3244
static gps_mask_t processPERCGPavp(unsigned count UNUSED, char *field[],
3245
                                   struct gps_device_t *session)
3246
0
{
3247
0
    gps_mask_t mask = ONLINE_SET;
3248
0
    double lat, lon, alt;
3249
3250
    // field[0]="PERC", field[1]="GPavp", data starts at field[2]
3251
    // Format: lat, lat_ns, lon, lon_ew, alt, alt_unit
3252
0
    lat = decode_lat_or_lon(field[2]);
3253
0
    if ('S' == field[3][0])
3254
0
        lat = -lat;
3255
3256
0
    lon = decode_lat_or_lon(field[4]);
3257
0
    if ('W' == field[5][0])
3258
0
        lon = -lon;
3259
3260
0
    alt = safe_atof(field[6]);
3261
3262
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3263
0
             "NMEA0183: PERC,GPavp: lat=%.6f lon=%.6f alt=%.1fm\n",
3264
0
             lat, lon, alt);
3265
3266
    // Save surveyed/averaged position (position-hold reference)
3267
0
    session->newdata.latitude = lat;
3268
0
    session->newdata.longitude = lon;
3269
0
    session->newdata.altHAE = alt;
3270
0
    mask |= LATLON_SET | ALTITUDE_SET;
3271
3272
0
    return mask;
3273
0
}
3274
3275
/* Ericsson $PERC,GPctr - Control/heartbeat
3276
 * Periodic heartbeat and configuration status message for timing modules
3277
 *
3278
 * $PERC,GPctr,<direction>,<data>,<f3>,<f4>,<config_byte>,<f6>,<f7>*XX
3279
 *
3280
 * Field 1: direction - 'R'=Read response, 'V'=Value notification
3281
 * Field 2: data - Configuration data (21 bytes per sentence)
3282
 * Field 3-5: Additional config fields
3283
 * Field 6: config_byte - Configuration byte
3284
 * Field 7: Reserved
3285
 *
3286
 * Periodic sentence (~15s interval) serving as keepalive/heartbeat.
3287
 * Provides configuration and status information.
3288
 */
3289
static gps_mask_t processPERCGPctr(unsigned count UNUSED, char *field[],
3290
                                   struct gps_device_t *session)
3291
0
{
3292
0
    gps_mask_t mask = ONLINE_SET;
3293
0
    char direction;
3294
0
    int config_byte = 0;
3295
3296
    // field[0]="PERC", field[1]="GPctr", data starts at field[2]
3297
0
    direction = field[2][0];
3298
0
    if (field[6][0] != '\0') {
3299
0
        config_byte = atoi(field[6]);
3300
0
    }
3301
3302
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3303
0
             "NMEA0183: PERC,GPctr: direction=%c data=%s config=%d\n",
3304
0
             direction, field[3], config_byte);
3305
3306
0
    return mask;
3307
0
}
3308
3309
/* Ericsson $PERC,GPdbg - Debug output
3310
 * Satellite tracking debug and status counter for Ericsson timing modules
3311
 *
3312
 * Two variants based on type field:
3313
 *
3314
 * Type 1: Satellite tracking debug (4 messages per second when enabled)
3315
 * $PERC,GPdbg,1,<page_count>,<page_num>,<sv_data>...*XX
3316
 *
3317
 * Pages 1-3: Up to 7 satellites per page in PPPSSXX format:
3318
 *   PPP = PRN (3 digits)
3319
 *   SS = SNR (2 digits)
3320
 *   XX = Status (2 hex digits: 00=solid, 01=weak, 05=acquiring)
3321
 *
3322
 * Page 10: Timing/phase measurements with bitmask
3323
 *   field[5] = Satellite bitmask (hex, e.g., 07FF = 11 sats)
3324
 *   fields[6-19] = Timing/phase/frequency measurements
3325
 *
3326
 * Type 2: Status counter debug
3327
 * $PERC,GPdbg,2,<counter>*XX
3328
 *
3329
 * Enabled via command: $PERC,GPdbg,1*43
3330
 * Provides real-time satellite tracking status beyond standard GPGSV.
3331
 */
3332
static gps_mask_t processPERCGPdbg(unsigned count UNUSED, char *field[],
3333
                                   struct gps_device_t *session)
3334
0
{
3335
0
    gps_mask_t mask = ONLINE_SET;
3336
0
    int type, page_count, page_num, i;
3337
3338
    // field[0]="PERC", field[1]="GPdbg", data starts at field[2]
3339
0
    type = atoi(field[2]);
3340
3341
0
    if (type == 1) {
3342
        // Satellite tracking debug
3343
0
        page_count = atoi(field[3]);
3344
0
        page_num = atoi(field[4]);
3345
3346
0
        if (page_num == 10) {
3347
            // Page 10: Special format with timing/phase data
3348
            // field[5]=bitmask (hex), field[6+]=timing fields
3349
0
            GPSD_LOG(LOG_DATA, &session->context->errout,
3350
0
                     "NMEA0183: PERC,GPdbg: type=1 page=%d/%d bitmask=%s "
3351
0
                     "timing=[%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s]\n",
3352
0
                     page_num, page_count, field[5],
3353
0
                     field[6], field[7], field[8], field[9], field[10],
3354
0
                     field[11], field[12], field[13], field[14], field[15],
3355
0
                     field[16], field[17], field[18], field[19]);
3356
0
        } else {
3357
            // Pages 1-3: Satellite tracking data
3358
            // Format: PRN(3)+SNR(2)+Status(2) = 7 digits per satellite
3359
            // Count non-empty satellite fields
3360
0
            int sv_count = 0;
3361
0
            for (i = 5; i < NMEA_MAX_FLD && field[i][0] != '\0'; i++) {
3362
0
                sv_count++;
3363
0
            }
3364
3365
0
            GPSD_LOG(LOG_DATA, &session->context->errout,
3366
0
                     "NMEA0183: PERC,GPdbg: type=1 page=%d/%d sv_count=%d\n",
3367
0
                     page_num, page_count, sv_count);
3368
3369
            // Log individual satellites (up to 7 per page)
3370
            // FIXME: save them in skyview[]
3371
0
            for (i = 5; i < 5 + 7 && field[i][0] != '\0'; i++) {
3372
0
                int prn = 0, snr = 0, status = 0;
3373
0
                if (7 <= strnlen(field[i], 8)) {
3374
                    // Parse PPPSSXX format
3375
0
                    char prn_str[4], snr_str[3], status_str[3];
3376
3377
0
                    strlcpy(prn_str, field[i], sizeof(prn_str));
3378
0
                    strlcpy(snr_str, field[i] + 3, sizeof(snr_str));
3379
0
                    strlcpy(status_str, field[i] + 5, sizeof(status_str));
3380
3381
0
                    prn = atoi(prn_str);
3382
0
                    snr = atoi(snr_str);
3383
0
                    status = (int)strtol(status_str, NULL, 16);
3384
3385
0
                    GPSD_LOG(LOG_DATA, &session->context->errout,
3386
0
                             "NMEA0183: PERC,GPdbg:   sv%d: PRN=%d SNR=%d "
3387
0
                             "status=0x%02x\n",
3388
0
                             i - 4, prn, snr, status);
3389
0
                }
3390
0
            }
3391
0
        }
3392
0
    } else if (type == 2) {
3393
        // Status counter debug
3394
0
        int counter = atoi(field[3]);
3395
0
        GPSD_LOG(LOG_DATA, &session->context->errout,
3396
0
                 "NMEA0183: PERC,GPdbg: type=2 counter=%d\n",
3397
0
                 counter);
3398
0
    } else {
3399
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
3400
0
                 "NMEA0183: PERC,GPdbg: unknown type=%d\n",
3401
0
                 type);
3402
0
    }
3403
3404
    // Debug data is logged but not stored in gps_data_t API structures.
3405
    // Per-satellite tracking status and page 10 timing measurements
3406
3407
    // FIXME: store into satellite_t and skyview
3408
3409
0
    return mask;
3410
0
}
3411
3412
/* Ericsson $PERC,GPppf - Position phase/frequency error
3413
 * Oscillator discipline quality for timing modules
3414
 *
3415
 * $PERC,GPppf,<phase_error>,<freq_error>,<status>,<leap_sec>,<quality>*XX
3416
 *
3417
 * Field 1: phase_error - Phase error in nanoseconds (signed)
3418
 * Field 2: freq_error - Frequency error in ppb (parts per billion, signed)
3419
 * Field 3: status - Status indicator (1=holdover/acquiring, 0=GPS-disciplined)
3420
 * Field 4: leap_sec - UTC-GPS leap second offset (e.g., 18 in 2026)
3421
 * Field 5: quality - PPS quality indicator (0=locked, 1=good, 2=degraded)
3422
 *
3423
 * This sentence provides real-time oscillator discipline quality metrics.
3424
 * Phase error indicates PPS timing offset, frequency error shows oscillator
3425
 * stability relative to GPS reference. Leap seconds typically appear ~2.5
3426
 * minutes after first fix.
3427
 */
3428
static gps_mask_t processPERCGPppf(unsigned count UNUSED, char *field[],
3429
                                   struct gps_device_t *session)
3430
0
{
3431
0
    gps_mask_t mask = ONLINE_SET;
3432
0
    double phase_error, freq_error;
3433
0
    int status, leap_sec, quality;
3434
3435
    // field[0]="PERC", field[1]="GPppf", data starts at field[2]
3436
0
    phase_error = safe_atof(field[2]);
3437
0
    freq_error = safe_atof(field[3]);
3438
0
    status = atoi(field[4]);
3439
0
    leap_sec = atoi(field[5]);
3440
0
    quality = atoi(field[6]);
3441
3442
    // Note: Always populate oscillator data - distros configure builds
3443
    // inconsistently. The oscillator field is part of the standard API.
3444
0
    session->gpsdata.osc.running = true;
3445
0
    session->gpsdata.osc.reference = true;
3446
0
    session->gpsdata.osc.disciplined = (status == 0);
3447
0
    session->gpsdata.osc.delta = (int)phase_error;
3448
0
    mask |= OSCILLATOR_SET;
3449
3450
    // Store leap seconds (can be negative in the future!)
3451
0
    session->gpsdata.leap_seconds = leap_sec;
3452
3453
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3454
0
             "NMEA0183: PERC,GPppf: phase=%.1fns freq=%.1fppb "
3455
0
             "disciplined=%d leap=%d quality=%d\n",
3456
0
             phase_error, freq_error, (status == 0), leap_sec, quality);
3457
3458
0
    return mask;
3459
0
}
3460
3461
/* Ericsson $PERC,GPppr - Position pulse reference
3462
 * GPS time reference and PPS quality for timing modules
3463
 *
3464
 * $PERC,GPppr,<tow_sec>,<gps_week>,<param>,<sv_count>,<pps_flag>,<reserved>*XX
3465
 *
3466
 * Field 1: tow_sec - GPS Time of Week in seconds (0-604799)
3467
 * Field 2: gps_week - GPS week number
3468
 * Field 3: param - Constant parameter (always 00050, likely cable delay in ns)
3469
 * Field 4: sv_count - Number of satellites used in solution
3470
 * Field 5: pps_flag - PPS quality indicator (0=OK/locked, 3=not locked)
3471
 * Field 6: reserved - Reserved field (always 0)
3472
 *
3473
 * Critical timing sentence providing GPS time reference and PPS lock status.
3474
 * The pps_flag field indicates whether PPS output is reliable.
3475
 */
3476
static gps_mask_t processPERCGPppr(unsigned count UNUSED, char *field[],
3477
                                   struct gps_device_t *session)
3478
0
{
3479
0
    gps_mask_t mask = ONLINE_SET;
3480
0
    unsigned int tow_sec, gps_week, param, sv_count, pps_flag, reserved;
3481
0
    timespec_t ts_tow;
3482
3483
0
    static const struct vlist_t vpercgpppr_pps[] = {
3484
0
        {0, "locked"},
3485
0
        {3, "unlocked"},
3486
0
        {0, NULL},
3487
0
    };
3488
3489
    // field[0]="PERC", field[1]="GPppr", data starts at field[2]
3490
0
    tow_sec = atoi(field[2]);
3491
0
    gps_week = atoi(field[3]);
3492
0
    param = atoi(field[4]);
3493
0
    sv_count = atoi(field[5]);
3494
0
    pps_flag = atoi(field[6]);
3495
    // field[7] is reserved (always 0)
3496
0
    reserved = atoi(field[7]);
3497
3498
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3499
0
             "NMEA0183: PERC,GPppr: week=%u tow=%u param=%u sats=%u "
3500
0
             "pps_flag=%s reserved=%d(%u)\n",
3501
0
             gps_week, tow_sec, param, sv_count,
3502
0
             val2str(pps_flag, vpercgpppr_pps), pps_flag, reserved);
3503
3504
    // Convert GPS week + TOW to Unix timestamp
3505
0
    ts_tow.tv_sec = tow_sec;
3506
0
    ts_tow.tv_nsec = 0;
3507
0
    session->newdata.time = gpsd_gpstime_resolv(session, gps_week, ts_tow);
3508
0
    mask |= TIME_SET;
3509
3510
    // Save satellite count
3511
0
    session->gpsdata.satellites_used = (int)sv_count;
3512
0
    mask |= SATELLITE_SET;
3513
3514
    /* Cable delay parameter (constant 50ns on GRU 04??)
3515
     * Logged for reference - no suitable API field for cable delay */
3516
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3517
0
             "NMEA0183: Cable delay compensation: %u\n", param);
3518
3519
    // Save PPS lock status to oscillator structure
3520
    // pps_flag: 0 = locked/OK, 3 = not locked
3521
0
    if (0 == pps_flag) {
3522
0
        session->gpsdata.osc.reference = true;
3523
0
    } else {
3524
0
        session->gpsdata.osc.reference = false;
3525
0
    }
3526
0
    mask |= OSCILLATOR_SET;
3527
3528
0
    return mask;
3529
0
}
3530
3531
/* Ericsson $PERC,GPreh - Receiver health
3532
 * Health status for timing modules
3533
 *
3534
 * $PERC,GPreh,<timestamp>,<health_code>*XX
3535
 *
3536
 * Field 1: timestamp - Time/date string
3537
                        (format varies, often null "00:00:00 00/00/0000")
3538
 * Field 2: health_code - Health status code (numeric)
3539
 *
3540
 * Periodic sentence (~19s interval) providing receiver health status.
3541
 * Health code interpretation is device-specific.
3542
 */
3543
static gps_mask_t processPERCGPreh(unsigned count UNUSED, char *field[],
3544
                                   struct gps_device_t *session)
3545
0
{
3546
0
    gps_mask_t mask = ONLINE_SET;
3547
0
    int health_code;
3548
3549
    // field[0]="PERC", field[1]="GPreh", data starts at field[2]
3550
0
    health_code = atoi(field[3]);
3551
3552
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3553
0
             "NMEA0183: PERC,GPreh: timestamp=%s health=%d\n",
3554
0
             field[2], health_code);
3555
3556
0
    return mask;
3557
0
}
3558
3559
/* Ericsson $PERC,GPsts - Receiver status
3560
 * Timing module operating mode and constellation information
3561
 *
3562
 * $PERC,GPsts,<mode>,<survey_flag>,<constellation>,<capabilities>*XX
3563
 *
3564
 * Field 1: mode - Operating mode (0=acquiring, 1=survey, 2=position-hold)
3565
 * Field 2: survey_flag - Survey status (0=position valid, 1=no position)
3566
 * Field 3: constellation - Constellation configuration (2=GPS+GLONASS)
3567
 * Field 4: capabilities - 18-digit capability bitmask "010011111111011111"
3568
 *
3569
 * Primary status sentence providing operating mode and system capabilities.
3570
 */
3571
static gps_mask_t processPERCGPsts(unsigned count UNUSED, char *field[],
3572
                                   struct gps_device_t *session)
3573
0
{
3574
0
    int mode, survey_flag, constellation;
3575
0
    gps_mask_t mask = ONLINE_SET;
3576
3577
0
    static const struct vlist_t vpercgpsts_mode[] = {
3578
0
        {0, "Acquiring"},
3579
0
        {1, "Survey"},
3580
0
        {2, "Position-hold"},
3581
0
        {3, "Overdetermined"},
3582
0
        {4, "Manual"},
3583
0
        {5, "3D-hold"},
3584
0
        {0, NULL},
3585
0
    };
3586
3587
    // field[0]="PERC", field[1]="GPsts", data starts at field[2]
3588
0
    mode = atoi(field[2]);
3589
0
    survey_flag = atoi(field[3]);
3590
0
    constellation = atoi(field[4]);
3591
3592
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
3593
0
             "NMEA0183: PERC,GPsts: mode=%s(%d) survey_flag=%d "
3594
0
             "constellation=%d capabilities=%s\n",
3595
0
             val2str(mode, vpercgpsts_mode), mode, survey_flag,
3596
0
             constellation, field[5]);
3597
3598
    /* Map receiver operating mode to fix status
3599
     * Survey and Position-hold modes are timing receiver states */
3600
0
    if (1 == mode ||
3601
0
        2 == mode ||
3602
0
        4 == mode) {
3603
        // Survey, Position-hold, or Manual → timing mode
3604
0
        session->newdata.status = STATUS_TIME;
3605
0
        mask |= STATUS_SET;
3606
0
    } else if (0 == mode) {
3607
        // Acquiring → GPS fix mode
3608
0
        session->newdata.status = STATUS_GPS;
3609
0
        mask |= STATUS_SET;
3610
0
    }
3611
    // Modes 3 (Overdetermined) and 5 (3D-hold) not mapped
3612
3613
0
    return mask;
3614
0
}
3615
3616
/* Ericsson $PERC,GPver - Receiver identification
3617
 * Returns hardware model and serial number
3618
 *
3619
 * $PERC,GPver,<model>,<part_num>,<hw_rev>,<serial>*XX
3620
 *
3621
 * Field 1: model - "GRU 04 01" or "GRU 04 02"
3622
 * Field 2: part_num - "NCD 901 65/1" or "NCD 901 78/1"
3623
 * Field 3: hw_rev - Hardware revision (e.g., "R1E")
3624
 * Field 4: serial - Serial number
3625
 */
3626
static gps_mask_t processPERCGPver(unsigned count UNUSED, char *field[],
3627
                                   struct gps_device_t *session)
3628
0
{
3629
0
    gps_mask_t mask = ONLINE_SET;
3630
0
    char new_serial[32];
3631
0
    char old_subtype[64];
3632
3633
    // field[0]="PERC", field[1]="GPver", data starts at field[2]
3634
    // Save old subtype for comparison
3635
0
    strlcpy(old_subtype, session->subtype, sizeof(old_subtype));
3636
0
    strlcpy(new_serial, field[5], sizeof(new_serial));
3637
3638
    // Build subtype directly into session->subtype
3639
0
    (void)snprintf(session->subtype, sizeof(session->subtype),
3640
0
                   "%s %s %s",
3641
0
                   field[2], field[3], field[4]);
3642
3643
    // Only log at high level if changed
3644
0
    if (0 != strcmp(old_subtype, session->subtype) ||
3645
0
        0 != strcmp(session->gpsdata.dev.sernum, new_serial)) {
3646
0
        GPSD_LOG(LOG_INF, &session->context->errout,
3647
0
                 "NMEA0183: PERC,GPver: new device %s serial %s\n",
3648
0
                 session->subtype, new_serial);
3649
0
        mask |= DEVICEID_SET;
3650
0
    } else {
3651
0
        GPSD_LOG(LOG_DATA, &session->context->errout,
3652
0
                 "NMEA0183: PERC,GPver: (unchanged)\n");
3653
0
    }
3654
3655
    // Update serial number
3656
0
    strlcpy(session->gpsdata.dev.sernum, new_serial,
3657
0
            sizeof(session->gpsdata.dev.sernum));
3658
3659
0
    return mask;
3660
0
}
3661
3662
/* Trimble $PTNLRNM - Receiver Navigation Mode
3663
 * Navigation mode status for Trimble/Ericsson receivers
3664
 *
3665
 * $PTNLRNM,<mode>*XX
3666
 *
3667
 * Field 1: mode - Navigation mode status character
3668
 *          A = Autonomous/Active
3669
 *          D = Differential
3670
 *          E = Estimated/Dead Reckoning
3671
 *          N = Data not valid
3672
 *
3673
 * Periodic sentence providing receiver navigation mode status.
3674
 * Typically outputs 'A' for autonomous operation.
3675
 */
3676
static gps_mask_t processPTNLRNM(unsigned count UNUSED, char *field[],
3677
                                 struct gps_device_t *session)
3678
318
{
3679
318
    gps_mask_t mask = ONLINE_SET;
3680
318
    char mode;
3681
3682
318
    static const struct vlist_t vptnlrnm_mode[] = {
3683
318
        {'A', "Autonomous"},
3684
318
        {'D', "Differential"},
3685
318
        {'E', "Estimated"},
3686
318
        {'N', "Invalid"},
3687
318
        {0, NULL},
3688
318
    };
3689
3690
    // field[0]="PTNLRNM", data starts at field[1]
3691
318
    mode = field[1][0];
3692
3693
318
    GPSD_LOG(LOG_DATA, &session->context->errout,
3694
318
             "NMEA0183: PTNLRNM: navigation mode=%c (%s)\n",
3695
318
             mode, val2str(mode, vptnlrnm_mode));
3696
3697
    // Map mode to GPS status
3698
318
    switch (mode) {
3699
65
    case 'A':
3700
65
        session->newdata.status = STATUS_GPS;
3701
65
        mask |= STATUS_SET;
3702
65
        break;
3703
68
    case 'D':
3704
68
        session->newdata.status = STATUS_DGPS;
3705
68
        mask |= STATUS_SET;
3706
68
        break;
3707
105
    case 'E':
3708
105
        session->newdata.status = STATUS_DR;
3709
105
        mask |= STATUS_SET;
3710
105
        break;
3711
80
    default:
3712
        // N or unknown - no status set
3713
80
        break;
3714
318
    }
3715
3716
318
    return mask;
3717
318
}
3718
3719
/* Trimble $PTNLRBA - Antenna status
3720
 * Antenna connection health monitoring for Trimble/Ericsson receivers
3721
 *
3722
 * $PTNLRBA,<status>,<flag>*XX
3723
 *
3724
 * Field 1: status - Antenna status (1=OK, 0=fault)
3725
 * Field 2: flag - Additional status flag
3726
 *
3727
 * Monitors antenna connection health. Important for timing applications
3728
 * as antenna problems directly affect signal quality and position accuracy.
3729
 */
3730
static gps_mask_t processPTNLRBA(unsigned count UNUSED, char *field[],
3731
                                 struct gps_device_t *session)
3732
132
{
3733
132
    gps_mask_t mask = ONLINE_SET;
3734
132
    int status, flag;
3735
3736
132
    static const struct vlist_t vptnlrba_status[] = {
3737
132
        {0, "Fault"},
3738
132
        {1, "OK"},
3739
132
        {0, NULL},
3740
132
    };
3741
3742
    // field[0]="PTNLRBA", data starts at field[1]
3743
132
    status = atoi(field[1]);
3744
132
    flag = atoi(field[2]);
3745
3746
132
    GPSD_LOG(LOG_DATA, &session->context->errout,
3747
132
             "NMEA0183: PTNLRBA: antenna_status=%s(%d) flag=%d\n",
3748
132
             val2str(status, vptnlrba_status), status, flag);
3749
3750
    // Save antenna status to fix structure
3751
132
    if (1 == status) {
3752
83
        session->newdata.ant_stat = ANT_OK;
3753
83
    } else {
3754
        // Status 0 = Fault (type unknown, map to generic fault)
3755
49
        session->newdata.ant_stat = ANT_SHORT;
3756
49
        mask |= ERROR_SET;
3757
49
    }
3758
3759
132
    return mask;
3760
132
}
3761
3762
/* Trimble $PTNLRTP - Receiver temperature
3763
 * Internal temperature monitoring for Trimble/Ericsson receivers
3764
 *
3765
 * $PTNLRTP,T,<temp>,<precision>*XX
3766
 *
3767
 * Field 1: type - Always 'T' for temperature
3768
 * Field 2: temp - Temperature in degrees Celsius
3769
 * Field 3: precision - Temperature measurement precision/accuracy
3770
 *
3771
 * Monitors receiver internal temperature. Useful for thermal stability
3772
 * analysis in timing applications. Observed range: 30-40°C typical.
3773
 */
3774
static gps_mask_t processPTNLRTP(unsigned count UNUSED, char *field[],
3775
                                 struct gps_device_t *session)
3776
34
{
3777
34
    gps_mask_t mask = ONLINE_SET;
3778
34
    double temp, precision;
3779
34
    char type;
3780
3781
    // field[0]="PTNLRTP", data starts at field[1]
3782
34
    type = field[1][0];
3783
34
    temp = safe_atof(field[2]);
3784
34
    precision = safe_atof(field[3]);
3785
3786
34
    GPSD_LOG(LOG_DATA, &session->context->errout,
3787
34
             "NMEA0183: PTNLRTP: type=%c temp=%.2f°C precision=%.1f\n",
3788
34
             type, temp, precision);
3789
3790
    // Store temperature in gps_fix_t.temp field.  What temperature is it?
3791
34
    session->newdata.temp = temp;
3792
    // No specific mask for temperature - aux data included in fix
3793
3794
34
    return mask;
3795
34
}
3796
3797
/* Trimble $PTNLRXO - Crystal oscillator status
3798
 * Oscillator lock and frequency offset for Trimble/Ericsson receivers
3799
 *
3800
 * $PTNLRXO,<status>,<offset>*XX
3801
 *
3802
 * Field 1: status - Oscillator lock status (1=locked, 0=unlocked)
3803
 * Field 2: offset - Frequency offset in ppb (parts per billion)
3804
 *
3805
 * Reports crystal oscillator disciplining status and frequency error.
3806
 * Complementary to $PERC,GPppf which provides phase error in nanoseconds.
3807
 * Typical offset: -450 to -500 ppb for this hardware.
3808
 */
3809
static gps_mask_t processPTNLRXO(unsigned count UNUSED, char *field[],
3810
                                 struct gps_device_t *session)
3811
68
{
3812
68
    gps_mask_t mask = ONLINE_SET;
3813
68
    int status;
3814
68
    double offset;
3815
3816
68
    static const struct vlist_t vptnlrxo_status[] = {
3817
68
        {0, "Unlocked"},
3818
68
        {1, "Locked"},
3819
68
        {0, NULL},
3820
68
    };
3821
3822
    // field[0]="PTNLRXO", data starts at field[1]
3823
68
    status = atoi(field[1]);
3824
68
    offset = safe_atof(field[2]);
3825
3826
68
    GPSD_LOG(LOG_DATA, &session->context->errout,
3827
68
             "NMEA0183: PTNLRXO: osc_status=%s(%d) offset=%.3f ppb\n",
3828
68
             val2str(status, vptnlrxo_status), status, offset);
3829
3830
    // Frequency offset could be stored alongside GPppf data if an
3831
    // extended oscillator_t structure is added to gps_data_t.
3832
    // Note: API extension needed for frequency offset storage.
3833
3834
68
    return mask;
3835
68
}
3836
3837
/* Android GNSS super message
3838
 * A stub.
3839
 */
3840
static gps_mask_t processPGLOR(unsigned count UNUSED, char *field[],
3841
                               struct gps_device_t *session)
3842
618
{
3843
    /*
3844
     * $PGLOR,0,FIX,....
3845
     * 1    = sentence version (may not be present)
3846
     * 2    = message subtype
3847
     * ....
3848
     *
3849
     * subtypes:
3850
     *  $PGLOR,[],AGC - ??
3851
     *  $PGLOR,[],CPU - CPU Loading
3852
     *  $PGLOR,[],FIN - Request completion status
3853
     *  $PGLOR,0,FIX,seconds - Time To Fix
3854
     *  $PGLOR,[],FTS - Factory Test Status
3855
     *  $PGLOR,[],GFC - GeoFence Fix
3856
     *  $PGLOR,[],GLO - ??
3857
     *  $PGLOR,[],HLA - Value of HULA sensors
3858
     *  $PGLOR,[],IMS - IMES messages
3859
     *  $PGLOR,1,LSQ,hhmmss.ss  - Least squares GNSS fix
3860
     *  $PGLOR,NET    - Report network information
3861
     *  $PGLOR,[],NEW - Indicate new GPS request
3862
     *  $PGLOR,[],PFM - Platform Status
3863
     *  $PGLOR,[],PPS - Indicate PPS time corrections
3864
     *  $PGLOR,5,PWR i - Power consumption report
3865
     *                  Only have doc for 5, Quectel uses 4
3866
     *  $PGLOR,[],RID - Version Information
3867
     *  $PGLOR,2,SAT - GPS Satellite information
3868
     *  $PGLOR,[],SIO - Serial I/O status report
3869
     *  $PGLOR,[],SPA - Spectrum analyzer results
3870
     *  $PGLOR,0,SPD  - Speed, Steps, etc.
3871
     *  $PGLOR,SPL    - ??
3872
     *  $PGLOR,[],SPS - ??
3873
     *  $PGLOR,10,STA - GLL status
3874
     *  $PGLOR,[],SVC - ??
3875
     *  $PGLOR,[],SVD - SV Dopplers detected in the false alarm test.
3876
     *  $PGLOR,[],SMx - Report GPS Summary Information
3877
     *  $PGLOR,[],UNC - ??
3878
     *
3879
     * Are NET and SPL really so different?
3880
     *
3881
     */
3882
618
    gps_mask_t mask = ONLINE_SET;
3883
618
    int got_one = 0;
3884
3885
618
    switch (field[1][0]) {
3886
274
    case '0':
3887
274
        if (0 == strncmp("FIX", field[2], 3)) {
3888
67
            got_one = 1;
3889
            // field 3, time to first fix in seconds
3890
67
            GPSD_LOG(LOG_DATA, &session->context->errout,
3891
67
                     "NMEA0183: PGLOR: FIX, TTFF %s\n",
3892
67
                     field[3]);
3893
207
        } else if (0 == strncmp("SPD", field[2], 3)) {
3894
78
            got_one = 1;
3895
            // field 4, ddmmy.ss UTC
3896
            // field 5, hhmmss.ss UTC
3897
78
            GPSD_LOG(LOG_DATA, &session->context->errout,
3898
78
                     "NMEA0183: PGLOR: SPD, %s %s UTC\n",
3899
78
                     field[4], field[5]);
3900
78
        }
3901
274
        break;
3902
278
    case '1':
3903
278
        if (0 == strncmp("LSQ", field[2], 3)) {
3904
58
            got_one = 1;
3905
            // field 3, hhmmss.ss UTC, only field Quectel supplies
3906
58
            GPSD_LOG(LOG_DATA, &session->context->errout,
3907
58
                     "NMEA0183: PGLOR: LSQ %s UTC\n",
3908
58
                     field[3]);
3909
220
        } else if ('0' == field[1][1] &&
3910
153
                   0 == strncmp("STA", field[2], 3)) {
3911
            // version 10
3912
66
            got_one = 1;
3913
            // field 3, hhmmss.ss UTC
3914
            // field 7, Position uncertainty meters
3915
66
            GPSD_LOG(LOG_DATA, &session->context->errout,
3916
66
                     "NMEA0183: PGLOR: STA, UTC %s PosUncer  %s\n",
3917
66
                     field[3], field[7]);
3918
66
        }
3919
278
        break;
3920
618
    }
3921
618
    if (0 != got_one) {
3922
269
        GPSD_LOG(LOG_DATA, &session->context->errout,
3923
269
                 "NMEA0183: PGLOR: seq %s type %s\n",
3924
269
                 field[1], field[2]);
3925
269
    }
3926
618
    return mask;
3927
618
}
3928
3929
// Inertial Sense GPS nav data, not a legal message name
3930
static gps_mask_t processPGPSP(unsigned count UNUSED, char *field[],
3931
                               struct gps_device_t *session)
3932
877
{
3933
    /*
3934
     * $PGPSP,523970800,2351,778,44.06887670,-121.31410390,1114.07,1134.17,
3935
     * 2.55,4.32,11.26,0.13,0.52,0.25,0.10,25.7,0.0000,18*51
3936
     *
3937
     */
3938
877
    gps_mask_t mask = ONLINE_SET;
3939
877
    unsigned long i_tow = strtoul(field[1], NULL, 10);   // ms
3940
877
    int weeks = atoi(field[2]);
3941
877
    unsigned long status = strtoul(field[3], NULL, 10);
3942
877
    int used = status & 0x0ff;
3943
877
    int gpsStatus = (status >> 8) & 0x0ff;
3944
877
    int fixType = (status >> 16) & 0x0ff;
3945
877
    double lat = safe_atof(field[4]);
3946
877
    double lon = safe_atof(field[5]);
3947
877
    double altHAE = safe_atof(field[6]);
3948
877
    double altMSL = safe_atof(field[7]);
3949
877
    double pDOP = safe_atof(field[8]);
3950
877
    double hAcc = safe_atof(field[9]);
3951
877
    double vAcc = safe_atof(field[10]);
3952
877
    double vecefX = safe_atof(field[11]);
3953
877
    double vecefY = safe_atof(field[12]);
3954
877
    double vecefZ = safe_atof(field[13]);
3955
877
    double sAcc = safe_atof(field[14]);
3956
877
    double cnoMean = safe_atof(field[15]);
3957
877
    double towOffset = safe_atof(field[16]);
3958
877
    int leapS = atoi(field[17]);
3959
877
    char ts_buf[TIMESPEC_LEN];
3960
877
    char scr[128];
3961
3962
877
    switch (gpsStatus) {
3963
76
    case 0:
3964
        // no fix
3965
76
        session->newdata.status = STATUS_UNK;
3966
76
        session->newdata.mode = MODE_NO_FIX;
3967
76
        break;
3968
147
    case 1:
3969
        // DR
3970
147
        session->newdata.status = STATUS_DR;
3971
147
        session->newdata.mode = MODE_3D;
3972
147
        break;
3973
36
    case 2:
3974
        // 2D
3975
36
        session->newdata.status = STATUS_GPS;
3976
36
        session->newdata.mode = MODE_2D;
3977
36
        break;
3978
66
    case 3:
3979
        // 3D
3980
66
        session->newdata.status = STATUS_GPS;
3981
66
        session->newdata.mode = MODE_3D;
3982
66
        break;
3983
108
    case 4:
3984
        // GPSDR
3985
108
        session->newdata.status = STATUS_GNSSDR;
3986
108
        session->newdata.mode = MODE_3D;
3987
108
        break;
3988
67
    case 5:
3989
        // surveyed
3990
67
        session->newdata.status = STATUS_TIME;
3991
67
        session->newdata.mode = MODE_3D;
3992
67
        break;
3993
54
    case 8:
3994
        // DGPS
3995
54
        session->newdata.status = STATUS_DGPS;
3996
54
        session->newdata.mode = MODE_3D;
3997
54
        break;
3998
65
    case 9:
3999
        // SBAS ??
4000
65
        session->newdata.status = STATUS_GPS;
4001
65
        session->newdata.mode = MODE_3D;
4002
65
        break;
4003
72
    case 10:
4004
        // FTK SINGLE ??
4005
72
        session->newdata.status = STATUS_RTK_FLT;  // ??
4006
72
        session->newdata.mode = MODE_3D;
4007
72
        break;
4008
69
    case 11:
4009
        // FTK FLOAT
4010
69
        session->newdata.status = STATUS_RTK_FLT;
4011
69
        session->newdata.mode = MODE_3D;
4012
69
        break;
4013
67
    case 12:
4014
        // FTK FIX
4015
67
        session->newdata.status = STATUS_RTK_FIX;
4016
67
        session->newdata.mode = MODE_3D;
4017
67
        break;
4018
50
    default:
4019
        // Huh?
4020
50
        session->newdata.status = STATUS_UNK;
4021
50
        session->newdata.mode = MODE_NOT_SEEN;
4022
50
        break;
4023
877
    }
4024
877
    mask |= MODE_SET | STATUS_SET;
4025
4026
877
    if (MODE_2D == session->newdata.mode ||
4027
841
        MODE_3D == session->newdata.mode) {
4028
751
            timespec_t ts_tow;
4029
4030
751
            session->newdata.latitude = lat;
4031
751
            session->newdata.longitude = lon;
4032
751
            mask |= LATLON_SET;
4033
751
            if (MODE_3D == session->newdata.mode) {
4034
715
                session->newdata.altHAE = altHAE;
4035
715
                session->newdata.altMSL = altMSL;
4036
715
                mask |= ALTITUDE_SET;
4037
715
            }
4038
            // assume leapS is valid if we are 2D ???
4039
751
            session->context->leap_seconds = leapS;
4040
751
            session->context->valid |= LEAP_SECOND_VALID;
4041
4042
            // assume time is valid if we are 2D ???
4043
751
            MSTOTS(&ts_tow, i_tow);
4044
751
            session->newdata.time = gpsd_gpstime_resolv(session, weeks,
4045
751
                                                        ts_tow);
4046
4047
751
            mask |= (TIME_SET | NTPTIME_IS);
4048
751
    }
4049
4050
877
    GPSD_LOG(LOG_IO, &session->context->errout,
4051
877
             "NMEA0183: PGPSP: %s i_tow=%lu weeks=%d "
4052
877
             "status=x%lx used=%d gpsStatus=%d type=%d "
4053
877
             "lat=%.2f lon=%.2f "
4054
877
             "altHAE=%.2f altMSL=%.2f "
4055
877
             "pdop=%.2f hacc=%.2f vacc=%.2f sacc=%.2f "
4056
877
             "vecef: X=%.2f Y=%.2f Z=%.2f cnoMean=.%1f "
4057
877
             "towOffset=%.4f leapS=%d\n",
4058
877
             timespec_to_iso8601(session->newdata.time, scr, sizeof(scr)),
4059
877
             i_tow, weeks, status, used, gpsStatus, fixType, lat, lon,
4060
877
             altHAE, altMSL,
4061
877
             pDOP, hAcc, vAcc, sAcc,
4062
877
             vecefX, vecefY, vecefZ, cnoMean,
4063
877
             towOffset, leapS);
4064
4065
877
    GPSD_LOG(LOG_PROG, &session->context->errout,
4066
877
             "NMEA0183: PGPSP: time=%s lat=%.2f lon=%.2f "
4067
877
             "mode=%d status=%d\n",
4068
877
             timespec_str(&session->newdata.time, ts_buf, sizeof(ts_buf)),
4069
877
             session->newdata.latitude,
4070
877
             session->newdata.longitude,
4071
877
             session->newdata.mode,
4072
877
             session->newdata.status);
4073
877
    return mask;
4074
877
}
4075
4076
// Garmin Estimated Position Error
4077
static gps_mask_t processPGRME(unsigned count UNUSED, char *field[],
4078
                               struct gps_device_t *session)
4079
790
{
4080
    /*
4081
     * $PGRME,15.0,M,45.0,M,25.0,M*22
4082
     * 1    = horizontal error estimate
4083
     * 2    = units
4084
     * 3    = vertical error estimate
4085
     * 4    = units
4086
     * 5    = spherical error estimate
4087
     * 6    = units
4088
     * *
4089
     * * Garmin won't say, but the general belief is that these are 50% CEP.
4090
     * * We follow the advice at <http://gpsinformation.net/main/errors.htm>.
4091
     * * If this assumption changes here, it should also change in garmin.c
4092
     * * where we scale error estimates from Garmin binary packets, and
4093
     * * in libgpsd_core.c where we generate $PGRME.
4094
     */
4095
790
    gps_mask_t mask = ONLINE_SET;
4096
4097
790
    if ('M' == field[2][0] &&
4098
725
        'M' == field[4][0] &&
4099
659
        'M' == field[6][0]) {
4100
590
        session->newdata.epx = session->newdata.epy =
4101
590
            safe_atof(field[1]) * (1 / sqrt(2))
4102
590
                      * (GPSD_CONFIDENCE / CEP50_SIGMA);
4103
590
        session->newdata.epv =
4104
590
            safe_atof(field[3]) * (GPSD_CONFIDENCE / CEP50_SIGMA);
4105
590
        session->newdata.sep =
4106
590
            safe_atof(field[5]) * (GPSD_CONFIDENCE / CEP50_SIGMA);
4107
590
        mask = HERR_SET | VERR_SET | PERR_IS;
4108
590
    }
4109
4110
790
    GPSD_LOG(LOG_DATA, &session->context->errout,
4111
790
             "NMEA0183: PGRME: epx=%.2f epy=%.2f sep=%.2f\n",
4112
790
             session->newdata.epx,
4113
790
             session->newdata.epy,
4114
790
             session->newdata.sep);
4115
790
    return mask;
4116
790
}
4117
4118
/* Garmin GPS Fix Data Sentence
4119
 *
4120
 * FIXME: seems to happen after cycle ender, so little happens...
4121
 */
4122
static gps_mask_t processPGRMF(unsigned count UNUSED, char *field[],
4123
                               struct gps_device_t *session)
4124
696
{
4125
 /*
4126
  * $PGRMF,290,293895,160305,093802,13,5213.1439,N,02100.6511,E,A,2,0,226,2,1*11
4127
  *
4128
  * 1 = GPS week
4129
  * 2 = GPS seconds
4130
  * 3 = UTC Date ddmmyy
4131
  * 4 = UTC time hhmmss
4132
  * 5 = GPS leap seconds
4133
  * 6 = Latitude ddmm.mmmm
4134
  * 7 = N or S
4135
  * 8 = Longitude dddmm.mmmm
4136
  * 9 = E or W
4137
  * 10 = Mode, M = Manual, A = Automatic
4138
  * 11 = Fix type, 0 = No fix, 2 = 2D fix, 2 = 3D fix
4139
  * 12 = Ground Speed, 0 to 1151 km/hr
4140
  * 13 = Course over ground, 0 to 359 degrees true
4141
  * 14 = pdop, 0 to 9
4142
  * 15 = dop, 0 to 9
4143
  */
4144
696
    gps_mask_t mask = ONLINE_SET;
4145
696
    timespec_t ts_tow = {0, 0};
4146
4147
    /* Some garmin fail due to GPS Week Roll Over
4148
     * Ignore their UTC date/time, use their GPS week, GPS tow and
4149
     * leap seconds to decide the correct time */
4150
696
    if (isdigit((int)field[5][0])) {
4151
405
        session->context->leap_seconds = atoi(field[5]);
4152
405
        session->context->valid = LEAP_SECOND_VALID;
4153
405
    }
4154
696
    if (isdigit((int)field[1][0]) &&
4155
696
        isdigit((int)field[2][0]) &&
4156
196
        0 < session->context->leap_seconds) {
4157
        // have GPS week, tow and leap second
4158
95
        unsigned short week = atol(field[1]);
4159
95
        ts_tow.tv_sec = atol(field[2]);
4160
95
        ts_tow.tv_nsec = 0;
4161
95
        session->newdata.time = gpsd_gpstime_resolv(session, week, ts_tow);
4162
95
        mask |= TIME_SET;
4163
        // (long long) cast for 32/64 bit compat
4164
95
        GPSD_LOG(LOG_SPIN, &session->context->errout,
4165
95
                 "NMEA0183: PGRMF gps time %lld\n",
4166
95
                 (long long)session->newdata.time.tv_sec);
4167
601
    } else if (0 == merge_hhmmss(field[4], session) &&
4168
325
               0 == merge_ddmmyy(field[3], session)) {
4169
        // fall back to UTC if we need and can
4170
        // (long long) cast for 32/64 bit compat
4171
76
        GPSD_LOG(LOG_SPIN, &session->context->errout,
4172
76
                 "NMEA0183: PGRMF gps time %lld\n",
4173
76
                 (long long)session->newdata.time.tv_sec);
4174
76
        mask |= TIME_SET;
4175
76
    }
4176
696
    if ('A' != field[10][0]) {
4177
        // Huh?
4178
510
        return mask;
4179
510
    }
4180
186
    if (0 == do_lat_lon(&field[6], &session->newdata)) {
4181
37
        mask |= LATLON_SET;
4182
37
    }
4183
186
    switch (field[11][0]) {
4184
111
    default:
4185
        // Huh?
4186
111
        break;
4187
111
    case '0':
4188
66
        session->newdata.mode = MODE_NO_FIX;
4189
66
        mask |= MODE_SET;
4190
66
        break;
4191
9
    case '1':
4192
9
        session->newdata.mode = MODE_2D;
4193
9
        mask |= MODE_SET;
4194
9
        break;
4195
0
    case '2':
4196
0
        session->newdata.mode = MODE_3D;
4197
0
        mask |= MODE_SET;
4198
0
        break;
4199
186
    }
4200
186
    session->newdata.speed = safe_atof(field[12]) / MPS_TO_KPH;
4201
186
    session->newdata.track = safe_atof(field[13]);
4202
186
    mask |= SPEED_SET | TRACK_SET;
4203
186
    if ('\0' != field[14][0]) {
4204
111
        session->gpsdata.dop.pdop = safe_atof(field[14]);
4205
111
        mask |= DOP_SET;
4206
111
    }
4207
186
    if ('\0' != field[15][0]) {
4208
100
        session->gpsdata.dop.tdop = safe_atof(field[15]);
4209
100
        mask |= DOP_SET;
4210
100
    }
4211
4212
186
    GPSD_LOG(LOG_DATA, &session->context->errout,
4213
186
             "NMEA0183: PGRMF: pdop %.1f tdop %.1f \n",
4214
186
             session->gpsdata.dop.pdop,
4215
186
             session->gpsdata.dop.tdop);
4216
186
    return mask;
4217
186
}
4218
4219
/* Garmin Map Datum
4220
 *
4221
 * FIXME: seems to happen after cycle ender, so nothing happens...
4222
 */
4223
static gps_mask_t processPGRMM(unsigned count UNUSED, char *field[],
4224
                               struct gps_device_t *session)
4225
135
{
4226
    /*
4227
     * $PGRMM,NAD83*29
4228
     * 1    = Map Datum
4229
     */
4230
135
    gps_mask_t mask = ONLINE_SET;
4231
4232
135
    if ('\0' != field[1][0]) {
4233
68
        strlcpy(session->newdata.datum, field[1],
4234
68
                sizeof(session->newdata.datum));
4235
68
    }
4236
4237
135
    GPSD_LOG(LOG_DATA, &session->context->errout,
4238
135
             "NMEA0183: PGRMM: datum=%.40s\n",
4239
135
             session->newdata.datum);
4240
135
    return mask;
4241
135
}
4242
4243
// Garmin Sensor Status Info
4244
static gps_mask_t processPGRMT(unsigned count UNUSED, char *field[],
4245
                               struct gps_device_t *session)
4246
113
{
4247
    /*
4248
     * $PGRMT,GPS 15x-W software ver. 4.20,,,,,,,,*6A
4249
     * 1    = Product, model and software version
4250
     * 2    = ROM Checksum test P=pass, F=fail
4251
     * 3    = Receiver failure discrete, P=pass, F=fail
4252
     * 4    = Stored data lost, R=retained, L=lost
4253
     * 5    = Real time clock lost, R=retained, L=lost
4254
     * 6    = Oscillator drift discrete, P=pass, F=excessive drift detected
4255
     * 7    = Data collection discrete, C=collecting, null if not collecting
4256
     * 8    = GPS sensor temperature in degrees C
4257
     * 9    = GPS sensor configuration data, R=retained, L=lost
4258
     *
4259
     * Output once per minuite by default.
4260
     * 50 char max.
4261
     *
4262
     * As of October 2022, only ever seen field 1 populated
4263
     */
4264
113
    gps_mask_t mask = ONLINE_SET;
4265
4266
113
    strlcpy(session->subtype, field[1], sizeof(session->subtype));
4267
4268
113
    GPSD_LOG(LOG_DATA, &session->context->errout,
4269
113
             "NMEA0183: PGRMT: subtype %s\n",
4270
113
             session->subtype);
4271
113
    return mask;
4272
113
}
4273
4274
// Garmin 3D Velocity Information
4275
static gps_mask_t processPGRMV(unsigned count UNUSED, char *field[],
4276
                               struct gps_device_t *session)
4277
399
{
4278
    /*
4279
     * $PGRMV,-2.4,-1.1,0.3*59
4280
     * 1    = true east velocity,  m/s
4281
     * 2    = true north velocity,  m/s
4282
     * 3    = true up velocity,  m/s
4283
     */
4284
399
    gps_mask_t mask = ONLINE_SET;
4285
4286
399
    if ('\0' == field[1][0] ||
4287
330
        '\0' == field[2][0] ||
4288
265
        '\0' == field[3][0]) {
4289
        // nothing to report
4290
196
        return mask;
4291
196
    }
4292
4293
203
    session->newdata.NED.velE = safe_atof(field[1]);
4294
203
    session->newdata.NED.velN = safe_atof(field[2]);
4295
203
    session->newdata.NED.velD = -safe_atof(field[3]);
4296
4297
203
    mask |= VNED_SET;
4298
4299
203
    GPSD_LOG(LOG_DATA, &session->context->errout,
4300
203
             "NMEA0183: PGRMV: velE %.2f velN %.2f velD %.2f\n",
4301
203
            session->newdata.NED.velE,
4302
203
            session->newdata.NED.velN,
4303
203
            session->newdata.NED.velD);
4304
203
    return mask;
4305
399
}
4306
4307
// Garmin Altitude Information
4308
static gps_mask_t processPGRMZ(unsigned count UNUSED, char *field[],
4309
                               struct gps_device_t *session)
4310
538
{
4311
    /*
4312
     * $PGRMZ,246,f,3*1B
4313
     * 1    = Altitude (probably MSL) in feet
4314
     * 2    = f (feet)
4315
     * 3    = Mode
4316
     *         1 = No Fix
4317
     *         2 = 2D Fix
4318
     *         3 = 3D Fix
4319
     *
4320
     * From: Garmin Proprietary NMEA 0183 Sentences
4321
     *       technical Specifications
4322
     *       190-00684-00, Revision C December 2008
4323
     */
4324
538
    gps_mask_t mask = ONLINE_SET;
4325
4326
    // codacy does not like strlen()
4327
538
    if ('f' == field[2][0] &&
4328
430
        0 < strnlen(field[1], 20)) {
4329
        // have a GPS altitude, must be 3D
4330
        // seems to be altMSL.  regressions show this matches GPGGA MSL
4331
350
        session->newdata.altMSL = atoi(field[1]) * FEET_TO_METERS;
4332
350
        mask |= (ALTITUDE_SET);
4333
350
    }
4334
538
    switch (field[3][0]) {
4335
133
    default:
4336
        // Huh?
4337
133
        break;
4338
133
    case '1':
4339
56
        session->newdata.mode = MODE_NO_FIX;
4340
56
        mask |= MODE_SET;
4341
56
        break;
4342
5
    case '2':
4343
5
        session->newdata.mode = MODE_2D;
4344
5
        mask |= MODE_SET;
4345
5
        break;
4346
344
    case '3':
4347
344
        session->newdata.mode = MODE_3D;
4348
344
        mask |= MODE_SET;
4349
344
        break;
4350
538
    }
4351
4352
538
    GPSD_LOG(LOG_PROG, &session->context->errout,
4353
538
             "NMEA0183: PGRMZ: altMSL %.2f mode %d\n",
4354
538
             session->newdata.altMSL,
4355
538
             session->newdata.mode);
4356
538
    return mask;
4357
538
}
4358
4359
// Magellan Status
4360
static gps_mask_t processPMGNST(unsigned count UNUSED, char *field[],
4361
                                struct gps_device_t *session)
4362
0
{
4363
    /*
4364
     * $PMGNST,01.75,3,T,816,11.1,-00496,00*43
4365
     * 1 = Firmware version number
4366
     * 2 = Mode (1 = no fix, 2 = 2D fix, 3 = 3D fix)
4367
     * 3 = T if we have a fix
4368
     * 4 = battery percentage left in tenths of a percent
4369
     * 5 = time left on the GPS battery in hours
4370
     * 6 = numbers change (freq. compensation?)
4371
     * 7 = PRN number receiving current focus
4372
     */
4373
0
    gps_mask_t mask = ONLINE_SET;
4374
0
    int newmode = atoi(field[3]);
4375
4376
0
    if ('T' == field[4][0]) {
4377
0
        switch(newmode) {
4378
0
        default:
4379
0
            session->newdata.mode = MODE_NO_FIX;
4380
0
            break;
4381
0
        case 2:
4382
0
            session->newdata.mode = MODE_2D;
4383
0
            break;
4384
0
        case 3:
4385
0
            session->newdata.mode = MODE_3D;
4386
0
            break;
4387
0
        }
4388
0
    } else {
4389
        // Can report 3D fix, but 'F' for no fix
4390
0
        session->newdata.mode = MODE_NO_FIX;
4391
0
    }
4392
0
    mask |= MODE_SET;
4393
4394
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
4395
0
             "NMEA0183: PMGNST: mode: %d\n",
4396
0
             session->newdata.mode);
4397
0
    return mask;
4398
0
}
4399
4400
static gps_mask_t processPMTK001(unsigned count UNUSED, char *field[],
4401
                                 struct gps_device_t *session)
4402
283
{
4403
283
    int reason;
4404
283
    const char *mtk_reasons[] = {
4405
283
        "Invalid",
4406
283
        "Unsupported",
4407
283
        "Valid but Failed",
4408
283
        "Valid success",       // unused, see above
4409
283
        "Unknown",             // gpsd only
4410
283
    };
4411
4412
    // ACK / NACK
4413
283
    reason = atoi(field[2]);
4414
283
    if (4 == reason) {
4415
        // ACK
4416
115
        GPSD_LOG(LOG_PROG, &session->context->errout,
4417
115
                 "NMEA0183: MTK ACK: %s\n", field[1]);
4418
115
        return ONLINE_SET;
4419
115
    }
4420
4421
    // else, NACK
4422
168
    if (0 > reason ||
4423
130
        3 < reason) {
4424
        // WTF?
4425
119
        reason = 4;
4426
119
    }
4427
168
    GPSD_LOG(LOG_WARN, &session->context->errout,
4428
168
             "NMEA0183: MTK NACK: %s, reason: %s\n",
4429
168
             field[1], mtk_reasons[reason]);
4430
168
    return ONLINE_SET;
4431
283
}
4432
4433
static gps_mask_t processPMTK424(unsigned count UNUSED, char *field[],
4434
                                 struct gps_device_t *session)
4435
278
{
4436
    // PPS pulse width response
4437
    /*
4438
     * Response will look something like: $PMTK424,0,0,1,0,69*12
4439
     * The pulse width is in field 5 (69 in this example).  This
4440
     * sentence is poorly documented at:
4441
     * http://www.trimble.com/embeddedsystems/condor-gps-module.aspx?dtID=documentation
4442
     *
4443
     * Packet Type: 324 PMTK_API_SET_OUTPUT_CTL
4444
     * Packet meaning
4445
     * Write the TSIP/antenna/PPS configuration data to the Flash memory.
4446
     * DataField [Data0]:TSIP Packet[on/off]
4447
     * 0 - Disable TSIP output (Default).
4448
     * 1 - Enable TSIP output.
4449
     * [Data1]:Antenna Detect[on/off]
4450
     * 0 - Disable antenna detect function (Default).
4451
     * 1 - Enable antenna detect function.
4452
     * [Data2]:PPS on/off
4453
     * 0 - Disable PPS function.
4454
     * 1 - Enable PPS function (Default).
4455
     * [Data3]:PPS output timing
4456
     * 0 - Always output PPS (Default).
4457
     * 1 - Only output PPS when GPS position is fixed.
4458
     * [Data4]:PPS pulse width
4459
     * 1~16367999: 61 ns~(61x 16367999) ns (Default = 69)
4460
     *
4461
     * The documentation does not give the units of the data field.
4462
     * Andy Walls <andy@silverblocksystems.net> says:
4463
     *
4464
     * "The best I can figure using an oscilloscope, is that it is
4465
     * in units of 16.368000 MHz clock cycles.  It may be
4466
     * different for any other unit other than the Trimble
4467
     * Condor. 69 cycles / 16368000 cycles/sec = 4.216 microseconds
4468
     * [which is the pulse width I have observed]"
4469
     *
4470
     * Support for this theory comes from the fact that crystal
4471
     * TXCOs with a 16.368MHZ period are commonly available from
4472
     * multiple vendors. Furthermore, 61*69 = 4209, which is
4473
     * close to the observed cycle time and suggests that the
4474
     * documentation is trying to indicate 61ns units.
4475
     *
4476
     * He continues:
4477
     *
4478
     * "I chose [127875] because to divides 16368000 nicely and the
4479
     * pulse width is close to 1/100th of a second.  Any number
4480
     * the user wants to use would be fine.  127875 cycles /
4481
     * 16368000 cycles/second = 1/128 seconds = 7.8125
4482
     * milliseconds"
4483
     */
4484
4485
    // too short?  Make it longer
4486
278
    if (127875 > atoi(field[5])) {
4487
95
        (void)nmea_send(session, "$PMTK324,0,0,1,0,127875");
4488
95
    }
4489
278
    return ONLINE_SET;
4490
278
}
4491
4492
static gps_mask_t processPMTK705(unsigned count, char *field[],
4493
                                 struct gps_device_t *session)
4494
203
{
4495
    /* Trimble version:
4496
     * $PMTK705,AXN_1.30,0000,20090609,*20<CR><LF>
4497
     *
4498
     * 0 PMTK705
4499
     * 1 ReleaseStr - Firmware release name and version
4500
     * 2 Build_ID   - Build ID
4501
     * 3 Date code  - YYYYMMDD
4502
     * 4 Checksum
4503
     *
4504
     * Quectel Querk.  L26.
4505
     * $PMTK705,AXN_3.20_3333_13071501,0003,QUECTEL-L26,*1E<CR><LF>
4506
     *
4507
     * 0 PMTK705
4508
     * 1 ReleaseStr - Firmware release name and version
4509
     * 2 Build_ID   - Build ID
4510
     * 3 Date code  - Product Model
4511
     * 4 SDK Version (optional)
4512
     * * Checksum
4513
    */
4514
4515
    // set device subtype
4516
203
    if (4 == count) {
4517
68
        (void)snprintf(session->subtype, sizeof(session->subtype),
4518
68
                       "%s,%s,%s",
4519
68
                       field[1], field[2], field[3]);
4520
135
    } else {
4521
        // Once again Quectel goes their own way...
4522
135
        (void)snprintf(session->subtype, sizeof(session->subtype),
4523
135
                       "%s,%s,%s,%s",
4524
135
                       field[1], field[2], field[3], field[4]);
4525
135
    }
4526
4527
203
    if ('\0' == session->subtype1[0]) {
4528
        /* Query for the Quectel firmware version.
4529
         * Quectel GPS receivers containing an MTK chipset use
4530
         * this command to return their FW version.
4531
         * From
4532
         * https://forums.quectel.com/t/determine-nmea-version-of-l76-l/3882/5
4533
         * "$PQVERNO is an internal command and used to query Quectel FW
4534
         * version. We haven’t added this internal command in GNSS
4535
         * protocol spec."
4536
         */
4537
39
        (void)nmea_send(session, "$PQVERNO,R");
4538
39
    }
4539
4540
203
    return ONLINE_SET;
4541
203
}
4542
4543
static gps_mask_t processPQxERR(unsigned count UNUSED, char* field[],
4544
                                struct gps_device_t* session)
4545
105
{
4546
    /* Quectel generic PQxxxERRROR message handler
4547
     * The messages are content free, not very useful.
4548
     *
4549
     * $PQTMCFGEINSMSGERROR*4A
4550
     * $PQTMCFGORIENTATIONERROR*54
4551
     * $PQTMCFGWHEELTICKERROR*44
4552
     * $PQTMQMPTERROR*58
4553
     */
4554
4555
105
    GPSD_LOG(LOG_WARN, &session->context->errout,
4556
105
             "NMEA0183: %s Error\n", field[0]);
4557
105
    return ONLINE_SET;
4558
105
}
4559
4560
static gps_mask_t processPQxOK(unsigned count UNUSED, char* field[],
4561
                               struct gps_device_t* session)
4562
65
{
4563
    /* Quectel generic PQTMxxxOK message handler
4564
     * The messages are content free, not very useful.
4565
     *
4566
     * $PQTMCFGEINSMSGOK*16
4567
     * $PQTMCFGORIENTATIONOK*08
4568
     * $PQTMCFGWHEELTICKOK*18
4569
     */
4570
4571
65
    GPSD_LOG(LOG_PROG, &session->context->errout,
4572
65
             "NMEA0183: %s OK\n", field[0]);
4573
65
    return ONLINE_SET;
4574
65
}
4575
4576
// Quectel $PQTMGPS - GNSS position stuff
4577
static gps_mask_t processPQTMGPS(unsigned count UNUSED, char *field[],
4578
                                 struct gps_device_t *session)
4579
73
{
4580
    /*
4581
     * $PQTMGPS,671335,463792.000,31.822084600,117.115221100,59.4260,63.0420,
4582
     *  0.0270,-171.7101,5.9890,1.3300,2.1100,3,18,*75
4583
     *
4584
     * 1   Milliseconds since turn on. 32-bit unsigned integer.
4585
     * 2   Time of week. Seconds
4586
     * 3   Latitude. Degrees
4587
     * 4   Longitude. Degrees
4588
     * 5   Height above ellipsoid, Meters
4589
     * 6   Altitude above mean-sea-level. Meters
4590
     * 7   Ground speed (2D). Meters / sec
4591
     * 8   Heading (2D). Degrees.
4592
     * 9   Horizontal accuracy estimate. Meters.
4593
     * 10  HDOP
4594
     * 11  PDOP
4595
     * 12  Fix type.  0 = No fix.  2 = 2D fix.  3 = 3D fix.
4596
     * 13  Number of navigation satellites (seen? used?)
4597
     *
4598
     * Note: incomplete time stamp.
4599
     */
4600
73
    gps_mask_t mask = ONLINE_SET;
4601
73
    unsigned ts = atoi(field[1]);
4602
73
    unsigned tow = atoi(field[2]);
4603
73
    double lat = safe_atof(field[3]);
4604
73
    double lon = safe_atof(field[4]);
4605
73
    double hae = safe_atof(field[5]);
4606
73
    double msl = safe_atof(field[6]);
4607
73
    double speed = safe_atof(field[7]);
4608
73
    double heading = safe_atof(field[8]);
4609
73
    double hAcc = safe_atof(field[9]);
4610
73
    double hdop = safe_atof(field[10]);
4611
73
    double pdop = safe_atof(field[11]);
4612
73
    unsigned fix = atoi(field[12]);
4613
73
    unsigned numsat = atoi(field[13]);
4614
4615
73
    GPSD_LOG(LOG_PROG, &session->context->errout,
4616
73
             "NMEA0183: PQTMGPS ts %u tow %u lat %.9f lon %.9f HAE %.4f "
4617
73
             "MSL %.4f speed %.4f head %.4f hacc %.4f hdop %.4f pdop %.4f "
4618
73
             "mode %u nsat %u\n",
4619
73
             ts, tow, lat, lon, hae, msl, speed, heading, hAcc, hdop,
4620
73
             pdop, fix, numsat);
4621
73
    return mask;
4622
73
}
4623
4624
// Quectel $PQTMIMU - IMU Raw Data
4625
static gps_mask_t processPQTMIMU(unsigned count UNUSED, char *field[],
4626
                                 struct gps_device_t *session)
4627
39
{
4628
    /*
4629
     * $PQTMIMU,42634,-0.006832,-0.022814,1.014552,0.315000,-0.402500,
4630
       -0.332500,0,0*55
4631
     *
4632
     * 1   Milliseconds since turn on. 32-bit unsigned integer.
4633
     * 2   Acceleration in X-axis direction. g
4634
     * 3   Acceleration in Y-axis direction. g
4635
     * 4   Acceleration in A-axis direction. g
4636
     * 5   Angular rate in X-axis direction. Degrees / second
4637
     * 6   Angular rate in y-axis direction. Degrees / second
4638
     * 7   Angular rate in Z-axis direction. Degrees / second
4639
     * 8   Cumulative ticks
4640
     * 9   Timestamp of last tick
4641
     */
4642
39
    gps_mask_t mask = ONLINE_SET;
4643
39
    unsigned ts = atoi(field[1]);
4644
39
    double accX = safe_atof(field[2]);
4645
39
    double accY = safe_atof(field[3]);
4646
39
    double accZ = safe_atof(field[4]);
4647
39
    double rateX = safe_atof(field[5]);
4648
39
    double rateY = safe_atof(field[6]);
4649
39
    double rateZ = safe_atof(field[7]);
4650
39
    unsigned ticks = atoi(field[8]);
4651
39
    unsigned tick_ts = atoi(field[9]);
4652
4653
39
    GPSD_LOG(LOG_PROG, &session->context->errout,
4654
39
             "NMEA0183: PQTMIMU ts %u accX %.6f accY %.6f accZ %.6f "
4655
39
             "rateX %.6f rateY %.6f rateZ %.6f ticks %u tick_ts %u\n",
4656
39
             ts, accX, accY, accZ, rateX, rateY, rateZ, ticks, tick_ts);
4657
39
    return mask;
4658
39
}
4659
4660
// Quectel $PQTMINS - DR Nav results
4661
static gps_mask_t processPQTMINS(unsigned count UNUSED, char *field[],
4662
                                 struct gps_device_t *session)
4663
112
{
4664
    /*
4665
     * $PQTMINS,42529,1,31.822038000,117.115182800,67.681000,,,,-0.392663,
4666
        1.300793,0.030088*4D
4667
     *
4668
     * 1   Milliseconds since turn on. 32-bit unsigned integer.
4669
     * 2   Solution type, 0 = Pitch and Roll, 1 = GNSS, pitch, roll, heading
4670
     *                    2 = GNSS + DR, 3 = DR Only
4671
     * 3   Latitude. Degrees
4672
     * 4   Longitude. Degrees
4673
     * 5   Height (HAE?, MSL?) , Meters
4674
     * 6   Northward velocity
4675
     * 7   Eastward velocity
4676
     * 8   Downward velocity
4677
     * 9   Roll
4678
     * 10  Pitch
4679
     * 11  Heading
4680
     *
4681
     */
4682
112
    gps_mask_t mask = ONLINE_SET;
4683
112
    unsigned ts = atoi(field[1]);
4684
112
    unsigned sol = atoi(field[2]);
4685
112
    double lat = safe_atof(field[3]);
4686
112
    double lon = safe_atof(field[4]);
4687
112
    double alt = safe_atof(field[5]);
4688
112
    double velN = safe_atof(field[6]);
4689
112
    double velE = safe_atof(field[7]);
4690
112
    double velD = safe_atof(field[8]);
4691
112
    double roll = safe_atof(field[9]);
4692
112
    double pitch = safe_atof(field[10]);
4693
112
    double head = safe_atof(field[11]);
4694
4695
112
    GPSD_LOG(LOG_PROG, &session->context->errout,
4696
112
             "NMEA0183: PQTMINS ts %u sol %u lat %.9f lon %.9f alt %.6f "
4697
112
             "velN %.6f velE %.6f velD %.6f roll %.6f pitch %.6f head %.6f\n",
4698
112
             ts, sol, lat, lon, alt, velN, velE, velD, roll, pitch, head);
4699
112
    return mask;
4700
112
}
4701
4702
// Quectel $PQTMVER - Firmware info
4703
static gps_mask_t processPQTMVER(unsigned count UNUSED, char *field[],
4704
                                 struct gps_device_t *session)
4705
66
{
4706
    /*
4707
     * $PQTMVER,MODULE_L89HANR01A06S,2022/07/28,18:27:04*7A
4708
     *
4709
     * 1   Version
4710
     * 2   build date yyyy/mm/dd
4711
     * 3   build time hh:mm:ss
4712
     *
4713
     */
4714
66
    char obuf[128];                      // temp version string buffer
4715
66
    gps_mask_t mask = ONLINE_SET;
4716
4717
    // save as subtype
4718
66
    (void)snprintf(obuf, sizeof(obuf),
4719
66
             "%s %.12s %.10s",
4720
66
             field[1], field[2], field[3]);
4721
4722
    // save what we can
4723
66
    (void)strlcpy(session->subtype, obuf, sizeof(session->subtype) - 1);
4724
4725
66
    GPSD_LOG(LOG_PROG, &session->context->errout,
4726
66
             "NMEA0183: PQTMVER %s\n",
4727
66
             session->subtype);
4728
4729
66
    return mask;
4730
66
}
4731
4732
static gps_mask_t processPQVERNO(unsigned count UNUSED, char* field[],
4733
                                 struct gps_device_t* session)
4734
131
{
4735
    /* Example request & response are provided courtesy of Quectel below.
4736
     * This command is not publicly documented, but Quectel support
4737
     * provided this description via email. This has been tested on
4738
     * Quectel version L70-M39, but all recent (2022) versions of Quectel
4739
     * support this command is well.
4740
     *
4741
     * Request:
4742
     * $PQVERNO,R*3F
4743
     *
4744
     * Response:
4745
     * $PQVERNO,R,L96NR01A03S,2018/07/30,04:17*6B
4746
     *
4747
     * Description of the 6 fields are below.
4748
     *
4749
     * 1. $PQVERNO,              Query command
4750
     * 2. R,                     Read
4751
     * 3. L96NR01A03S,           Quectel firmware version number
4752
     * 4. 2018/07/30,            Firmware build date
4753
     * 5. 04:17*                 Firmware build time
4754
     * 6. 6B                     Checksum
4755
     */
4756
4757
131
    if (0 == strncmp(session->nmea.field[0], "PQVERNO", sizeof("PQVERNO")) &&
4758
131
        '\0' != field[2][0]) {
4759
68
        (void)snprintf(session->subtype1, sizeof(session->subtype1),
4760
68
                       "%s,%s,%s",
4761
68
                       field[2], field[3], field[4]);
4762
68
    }
4763
4764
131
    return ONLINE_SET;
4765
131
}
4766
4767
/* smart watch sensors
4768
 * A stub.
4769
 */
4770
static gps_mask_t processPRHS(unsigned count UNUSED, char *field[],
4771
                              struct gps_device_t *session)
4772
0
{
4773
    /*
4774
     * $PRHS ,type,....
4775
     *   type = message type
4776
     *
4777
     * Yes: $PRHS[space],
4778
     *
4779
     * types:
4780
     * $PRHS ,ACC,9.952756,0.37819514,1.3165021,20150305072428436*44
4781
     * $PRHS ,COM,238.09642,16.275442,82.198425,20150305072428824*43
4782
     * $PRHS ,GYR,0.0,0.0,0.0,20150305072428247*4D
4783
     * $PRHS ,LAC,0.23899937,0.009213656,0.02143073,20150305072428437*46
4784
     * $PRHS ,MAG,47.183502,-51.789,-2.7145,20150305072428614*41
4785
     * $PRHS ,ORI,187.86511,-2.1546898,-82.405205,20150305072428614*53
4786
     * $PRHS ,RMC,20150305072427985*55
4787
     *
4788
     */
4789
0
    gps_mask_t mask = ONLINE_SET;
4790
4791
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
4792
0
             "NMEA0183: PRHS: type %s\n",
4793
0
             field[1]);
4794
0
    return mask;
4795
0
}
4796
4797
static gps_mask_t processPSRFEPE(unsigned count UNUSED, char *field[],
4798
                                 struct gps_device_t *session)
4799
492
{
4800
    /*
4801
     * $PSRFEPE,100542.000,A,0.7,6.82,10.69,0.0,180.0*24
4802
     * 1    = UTC Time hhmmss.sss
4803
     * 2    = Status.  A = Valid, V = Data not valid
4804
     * 3    = HDOP
4805
     * 4    = EHPE meters (Estimated Horizontal Position Error)
4806
     * 5    = EVPE meters (Estimated Vertical Position Error)
4807
     * 6    = EHVE meters (Estimated Speed Over Ground/Velocity Error)
4808
     * 7    = EHE degrees (Estimated Heading Error)
4809
     *
4810
     * SiRF won't say if these are 1-sigma or what...
4811
     */
4812
492
    gps_mask_t mask = STATUS_SET;
4813
4814
    // get time/ valid or not
4815
492
    if ('\0' != field[1][0]) {
4816
287
        if (0 == merge_hhmmss(field[1], session)) {
4817
200
            register_fractional_time(field[0], field[1], session);
4818
200
            if (0 == session->nmea.date.tm_year) {
4819
48
                GPSD_LOG(LOG_WARN, &session->context->errout,
4820
48
                         "NMEA0183: can't use PSRFEPE time until after ZDA "
4821
48
                         "or RMC has supplied a year.\n");
4822
152
            } else {
4823
152
                mask |= TIME_SET;
4824
152
            }
4825
200
        }
4826
287
    }
4827
492
    if ('A' != field[2][0]) {
4828
        // Huh?
4829
230
        return mask;
4830
230
    }
4831
4832
262
    if ('\0' != field[3][0]) {
4833
        /* This adds nothing, it just agrees with the gpsd calculation
4834
         * from the skyview.  Which is a nice confirmation. */
4835
176
        session->gpsdata.dop.hdop = safe_atof(field[3]);
4836
176
        mask |= DOP_SET;
4837
176
    }
4838
262
    if ('\0' != field[4][0]) {
4839
        // EHPE (Estimated Horizontal Position Error)
4840
104
        session->newdata.eph = safe_atof(field[4]);
4841
104
        mask |= HERR_SET;
4842
104
    }
4843
4844
262
    if ('\0' != field[5][0]) {
4845
        // Estimated Vertical Position Error (meters, 0.01 resolution)
4846
64
        session->newdata.epv = safe_atof(field[5]);
4847
64
        mask |= VERR_SET;
4848
64
    }
4849
4850
262
    if ('\0' != field[6][0]) {
4851
        // Estimated Horizontal Speed Error meters/sec
4852
176
        session->newdata.eps = safe_atof(field[6]);
4853
176
    }
4854
4855
262
    if ('\0' != field[7][0]) {
4856
        // Estimated Heading Error degrees
4857
118
        session->newdata.epd = safe_atof(field[7]);
4858
118
    }
4859
4860
262
    GPSD_LOG(LOG_PROG, &session->context->errout,
4861
262
             "NMEA0183: PSRFEPE: hdop=%.1f eph=%.1f epv=%.1f eps=%.1f "
4862
262
             "epd=%.1f\n",
4863
262
             session->gpsdata.dop.hdop,
4864
262
             session->newdata.eph,
4865
262
             session->newdata.epv,
4866
262
             session->newdata.eps,
4867
262
             session->newdata.epd);
4868
262
    return mask;
4869
492
}
4870
4871
/*  Recommended Minimum 3D GNSS Data
4872
 *  Skytaq
4873
 */
4874
static gps_mask_t processPSTI030(unsigned count UNUSED, char *field[],
4875
                                 struct gps_device_t *session)
4876
761
{
4877
    /*
4878
     * $PSTI,030,hhmmss.sss,A,dddmm.mmmmmmm,a,dddmm.mmmmmmm,a,x.x,
4879
            x.x,x.x,x.x,ddmmyy,a.x.x,x.x*hh<CR><LF>
4880
     * 1     030          Sentence ID
4881
     * 2     225446.334   Time of fix 22:54:46 UTC
4882
     * 3     A            Status of Fix: A = Autonomous, valid;
4883
     *                                 V = invalid
4884
     * 4,5   4916.45,N    Latitude 49 deg. 16.45 min North
4885
     * 6,7   12311.12,W   Longitude 123 deg. 11.12 min West
4886
     * 8     103.323      Mean Sea Level meters
4887
     * 9     0.00         East Velocity meters/sec
4888
     * 10    0.00         North Velocity meters/sec
4889
     * 11    0.00         Up Velocity meters/sec
4890
     * 12    181194       Date of fix  18 November 1994
4891
     * 13    A            FAA mode indicator
4892
     *                        See faa_mode() for possible mode values.
4893
     * 14    1.2          RTK Age
4894
     * 15    4.2          RTK Ratio
4895
     * 16    *68          mandatory nmea_checksum
4896
     *
4897
     * In private email, SkyTraq says F mode is 10x more accurate
4898
     * than R mode.
4899
     */
4900
761
    gps_mask_t mask = ONLINE_SET;
4901
4902
761
    if (0 != strncmp(session->device_type->type_name, "Skytraq", 7)) {
4903
        // this is skytraq, but not marked yet, so probe for Skytraq
4904
        // Send MID 0x02, to get back MID 0x80
4905
667
        (void)gpsd_write(session, "\xA0\xA1\x00\x02\x02\x01\x03\x0d\x0a",9);
4906
667
    }
4907
4908
761
    if ('V' == field[3][0] ||
4909
719
        'N' == field[13][0]) {
4910
        // nav warning, or FAA not valid, ignore the rest of the data
4911
112
        session->newdata.status = STATUS_UNK;
4912
112
        session->newdata.mode = MODE_NO_FIX;
4913
112
        mask |= MODE_SET | STATUS_SET;
4914
649
    } else if ('A' == field[3][0]) {
4915
570
        double east, north, climb, age, ratio;
4916
4917
        // data valid
4918
570
        if ('\0' != field[2][0] &&
4919
433
            '\0' != field[12][0]) {
4920
            // good date and time
4921
394
            if (0 == merge_hhmmss(field[2], session) &&
4922
323
                0 == merge_ddmmyy(field[12], session)) {
4923
66
                mask |= TIME_SET;
4924
66
                register_fractional_time( "PSTI030", field[2], session);
4925
66
            }
4926
394
        }
4927
570
        if (0 == do_lat_lon(&field[4], &session->newdata)) {
4928
194
            session->newdata.mode = MODE_2D;
4929
194
            mask |= LATLON_SET;
4930
194
            if ('\0' != field[8][0]) {
4931
                // altitude is MSL
4932
114
                session->newdata.altMSL = safe_atof(field[8]);
4933
114
                mask |= ALTITUDE_SET;
4934
114
                session->newdata.mode = MODE_3D;
4935
                // Let gpsd_error_model() deal with geoid_sep and altHAE
4936
114
            }
4937
194
            mask |= MODE_SET;
4938
194
        }
4939
        /* convert ENU to track
4940
         * this has more precision than GPVTG, GPVTG comes earlier
4941
         * in the cycle */
4942
570
        east = safe_atof(field[9]);     // east velocity m/s
4943
570
        north = safe_atof(field[10]);   // north velocity m/s
4944
570
        climb = safe_atof(field[11]);   // up velocity m/s
4945
570
        age = safe_atof(field[14]);
4946
570
        ratio = safe_atof(field[15]);
4947
4948
570
        session->newdata.NED.velN = north;
4949
570
        session->newdata.NED.velE = east;
4950
570
        session->newdata.NED.velD = -climb;
4951
570
        if (0.05 < (age + ratio)) {
4952
            // don't report age == ratio == 0.0
4953
187
            session->newdata.dgps_age = age;
4954
187
            session->newdata.base.ratio = ratio;
4955
187
        }
4956
4957
570
        mask |= VNED_SET | STATUS_SET;
4958
4959
570
        session->newdata.status = faa_mode(field[13][0]);
4960
570
        if (STATUS_RTK_FIX == session->newdata.status ||
4961
497
            STATUS_RTK_FLT == session->newdata.status) {
4962
            // RTK_FIX or RTK_FLT
4963
139
            session->gpsdata.fix.base.status = session->newdata.status;
4964
139
        }
4965
570
    }
4966
4967
761
    GPSD_LOG(LOG_PROG, &session->context->errout,
4968
761
             "NMEA0183: PSTI,030: ddmmyy=%s hhmmss=%s lat=%.2f lon=%.2f "
4969
761
             "status=%d, RTK(Age=%.1f Ratio=%.1f) faa mode %s(%s)\n",
4970
761
             field[12], field[2],
4971
761
             session->newdata.latitude,
4972
761
             session->newdata.longitude,
4973
761
             session->newdata.status,
4974
761
             session->newdata.dgps_age,
4975
761
             session->newdata.base.ratio,
4976
761
             field[13], char2str(field[13][0], c_faa_mode));
4977
761
    return mask;
4978
761
}
4979
4980
/* Skytraq RTK Baseline, fixed base to rover or moving base
4981
 * Same as $PSTI.035, except that is moving base to rover
4982
 * PX1172RH
4983
 */
4984
static gps_mask_t processPSTI032(unsigned count UNUSED, char *field[],
4985
                                 struct gps_device_t *session)
4986
656
{
4987
    /*
4988
     * $PSTI,032,041457.000,170316,A,R,0.603,‐0.837,‐0.089,1.036,144.22,,,,,*1B
4989
     *
4990
     * 2  UTC time,  hhmmss.sss
4991
     * 3  UTC Date, ddmmyy
4992
     * 4  Status, ‘V’ = Void ‘A’ = Active
4993
     * 5  Mode indicator, 'O' = Float RTK, ‘F’ = Float RTK. ‘R’ = Fixed RTK
4994
     * 6  East‐projection of baseline, meters
4995
     * 7  North‐projection of baseline, meters
4996
     * 8  Up‐projection of baseline, meters
4997
     * 9  Baseline length, meters
4998
     * 10 Baseline course 144.22, true degrees
4999
     * 11 Reserved
5000
     * 12 Reserved
5001
     * 13 Reserved
5002
     * 14 Reserved
5003
     * 15 Reserved
5004
     * 16 Checksum
5005
     */
5006
656
    gps_mask_t mask = ONLINE_SET;
5007
656
    struct baseline_t *base = &session->newdata.base;
5008
5009
656
    if ('A' != field[4][0]) {
5010
        //  status not valid
5011
70
        return mask;
5012
70
    }
5013
5014
    // Status Valid
5015
586
    if ('\0' != field[2][0] &&
5016
346
        '\0' != field[3][0]) {
5017
        // have date and time
5018
280
        if (0 == merge_hhmmss(field[2], session) &&
5019
66
            0 == merge_ddmmyy(field[3], session)) {
5020
            // good date and time
5021
0
            mask |= TIME_SET;
5022
0
            register_fractional_time("PSTI032", field[2], session);
5023
0
        }
5024
280
    }
5025
5026
586
    if ('F' == field[5][0] ||
5027
505
        'O' == field[5][0]) {
5028
        // Floating point RTK
5029
        // 'O' is undocuemented, private email says it is just a crappy 'F'.
5030
281
        base->status = STATUS_RTK_FLT;
5031
305
    } else if ('R' == field[5][0]) {
5032
        // Fixed point RTK
5033
116
        base->status = STATUS_RTK_FIX;
5034
189
    } else {
5035
        // WTF?
5036
189
        return mask;
5037
189
    }
5038
5039
397
    base->east = safe_atof(field[6]);
5040
397
    base->north = safe_atof(field[7]);
5041
397
    base->up = safe_atof(field[8]);
5042
397
    base->length = safe_atof(field[9]);
5043
397
    base->course = safe_atof(field[10]);
5044
5045
397
    GPSD_LOG(LOG_PROG, &session->context->errout,
5046
397
             "NMEA0183: PSTI,032: RTK Baseline mode %d E %.3f  N %.3f  U %.3f "
5047
397
             "length %.3f course %.3f\n",
5048
397
             base->status, base->east, base->north, base->up,
5049
397
             base->length, base->course);
5050
397
    return mask;
5051
586
}
5052
5053
/* Skytraq  RTK RAW Measurement Monitoring Data
5054
 */
5055
static gps_mask_t processPSTI033(unsigned count UNUSED, char *field[],
5056
                                 struct gps_device_t *session)
5057
498
{
5058
    /*
5059
     * $PSTI,033,hhmmss.sss,ddmmyy,x,R,x,G,x,x,,,C,x,x,,,E,x,x,,,R,x,x,,*hh
5060
     * $PSTI,033,110431.000,150517,2,R,1,G,1,0,,,C,0,0,,,E,0,0,,,R,0,0,,*72
5061
     *
5062
     * 2  UTC time,  hhmmss.sss
5063
     * 3  UTC Date, ddmmyy
5064
     * 4  "2", version
5065
     * 5  Receiver, R = Rover, B = Base
5066
     * 6  total cycle‐slipped raw measurements
5067
     * 7  "G", GPS
5068
     * 8  cycle slipped L1
5069
     * 9  cycle slipped L2
5070
     * 10 reserved
5071
     * 11 reserved
5072
     * 12 "C", BDS
5073
     * 12 cycle slipped B1
5074
     * 14 cycle slipped B2
5075
     * 15 reserved
5076
     * 16 reserved
5077
     * 17 "E", Galileo
5078
     * 18 cycle slipped E1
5079
     * 19 cycle slipped E5b
5080
     * 20 reserved
5081
     * 21 reserved
5082
     * 22 "R", GLONASS
5083
     * 23 cycle slipped G1
5084
     * 24 cycle slipped G2
5085
     * 25 reserved
5086
     * 26 reserved
5087
     * 27 Checksum
5088
     */
5089
498
    gps_mask_t mask = ONLINE_SET;
5090
498
    char receiver;
5091
498
    unsigned total, L1, L2, B1, B2, E1, E5b, G1, G2;
5092
5093
498
    if ('2' != field[4][0]) {
5094
        //  we only understand version 2
5095
35
        return mask;
5096
35
    }
5097
463
    if ('B' != field[5][0] &&
5098
193
        'R' != field[5][0]) {
5099
        //  Huh?  Rover or Base
5100
143
        return mask;
5101
143
    }
5102
320
    receiver = field[5][0];
5103
5104
320
    if ('\0' != field[2][0] &&
5105
256
        '\0' != field[3][0]) {
5106
        // have date and time
5107
179
        if (0 == merge_hhmmss(field[2], session) &&
5108
67
            0 == merge_ddmmyy(field[3], session)) {
5109
            // good date and time
5110
0
            mask |= TIME_SET;
5111
0
            register_fractional_time("PSTI033", field[2], session);
5112
0
        }
5113
179
    }
5114
320
    total = atoi(field[6]);
5115
320
    L1 = atoi(field[7]);
5116
320
    L2 = atoi(field[8]);
5117
320
    B1 = atoi(field[13]);
5118
320
    B2 = atoi(field[14]);
5119
320
    E1 = atoi(field[18]);
5120
320
    E5b = atoi(field[19]);
5121
320
    G1 = atoi(field[23]);
5122
320
    G2 = atoi(field[24]);
5123
5124
320
    GPSD_LOG(LOG_PROG, &session->context->errout,
5125
320
             "NMEA0183: PSTI,033: RTK RAW receiver %c Slips: total %u L1 %u "
5126
320
             "L2 %u B1 %u B2 %u E1 %u E5b %u G1 %u G2 %u\n",
5127
320
             receiver, total, L1, L2, B1, B2, E1, E5b, G1, G2);
5128
320
    return mask;
5129
463
}
5130
5131
/* Skytraq RTK Baseline, moving base to rover
5132
 * Same as $PSTI.032, except that is moving base to rover
5133
 * PX1172RH
5134
 */
5135
static gps_mask_t processPSTI035(unsigned count UNUSED, char *field[],
5136
                                 struct gps_device_t *session)
5137
607
{
5138
    /*
5139
     * $PSTI,035,041457.000,170316,A,R,0.603,‐0.837,‐0.089,1.036,144.22,,,,,*1B
5140
     *
5141
     * 2  UTC time,  hhmmss.sss
5142
     * 3  UTC Date, ddmmyy
5143
     * 4  Status, ‘V’ = Void ‘A’ = Active
5144
     * 5  Mode indicator, ‘F’ = Float RTK. ‘R’ = FIxed RTK
5145
     * 6  East‐projection of baseline, meters
5146
     * 7  North‐projection of baseline, meters
5147
     * 8  Up‐projection of baseline, meters
5148
     * 9  Baseline length, meters
5149
     * 10 Baseline course 144.22, true degrees
5150
     * 11 Reserved
5151
     * 12 Reserved
5152
     * 13 Reserved
5153
     * 14 Reserved
5154
     * 15 Reserved
5155
     * 16 Checksum
5156
     */
5157
5158
607
    gps_mask_t mask = ONLINE_SET;
5159
    // should this be fix, not attitude??
5160
607
    struct baseline_t *base = &session->gpsdata.attitude.base;
5161
5162
    // RTK Baseline Data of Rover Moving Base Receiver
5163
607
    if ('\0' != field[2][0] &&
5164
478
        '\0' != field[3][0]) {
5165
        // good date and time
5166
408
        if (0 == merge_hhmmss(field[2], session) &&
5167
116
            0 == merge_ddmmyy(field[3], session)) {
5168
67
            mask |= TIME_SET;
5169
67
            register_fractional_time( "PSTI035", field[2], session);
5170
67
        }
5171
408
    }
5172
607
    if ('A' != field[4][0]) {
5173
        // No valid data, except time, sort of
5174
315
        GPSD_LOG(LOG_PROG, &session->context->errout,
5175
315
                 "NMEA0183: PSTI,035: not valid\n");
5176
315
        base->status = STATUS_UNK;
5177
315
        return mask;
5178
315
    }
5179
292
    if ('F' == field[5][0]) {
5180
        // Float RTX
5181
66
        base->status = STATUS_RTK_FLT;
5182
226
    } else if ('R' == field[5][0]) {
5183
        // Fix RTX
5184
67
        base->status = STATUS_RTK_FIX;
5185
67
    } // else ??
5186
5187
292
    base->east = safe_atof(field[6]);
5188
292
    base->north = safe_atof(field[7]);
5189
292
    base->up = safe_atof(field[8]);
5190
292
    base->length = safe_atof(field[9]);
5191
292
    base->course = safe_atof(field[10]);
5192
292
    mask |= ATTITUDE_SET;
5193
5194
292
    GPSD_LOG(LOG_PROG, &session->context->errout,
5195
292
             "NMEA0183: PSTI,035: RTK Baseline mode %d E %.3f  N %.3f  U %.3f "
5196
292
             "length %.3f course %.3f\n",
5197
292
             base->status, base->east, base->north, base->up,
5198
292
             base->length, base->course);
5199
292
    return mask;
5200
607
}
5201
5202
// Skytraq PSTI,036 – Heading, Pitch and Roll
5203
// PX1172RH
5204
static gps_mask_t processPSTI036(unsigned count UNUSED, char *field[],
5205
                                 struct gps_device_t *session)
5206
1.03k
{
5207
    /*
5208
     * $PSTI,036,054314.000,030521,191.69,‐16.35,0.00,R*4D
5209
     *
5210
     * 2  UTC time,  hhmmss.sss
5211
     * 3  UTC Date, ddmmyy
5212
     * 4  Heading, 0 - 359.9, when mode == R, degrees
5213
     * 5  Pitch, -90 - 90, when mode == R, degrees
5214
     * 6  Roll, -90 - 90, when mode == R, degrees
5215
     * 7  Mode
5216
     *     'N’ = Data not valid
5217
     *     'A’ = Autonomous mode
5218
     *     'D’ = Differential mode
5219
     *     'E’ = Estimated (dead reckoning) mode
5220
     *     'M’ = Manual input mode
5221
     *     'S’ = Simulator mode
5222
     *     'F’ = Float RTK
5223
     *     'R’ = Fix RTK
5224
     * 8  Checksum
5225
     */
5226
5227
1.03k
    gps_mask_t mask = ONLINE_SET;
5228
1.03k
    int mode;
5229
5230
1.03k
    if ('\0' != field[2][0] &&
5231
928
        '\0' != field[3][0]) {
5232
        // good date and time
5233
807
        if (0 == merge_hhmmss(field[2], session) &&
5234
621
            0 == merge_ddmmyy(field[3], session)) {
5235
143
            mask |= TIME_SET;
5236
143
            register_fractional_time("PSTI036", field[2], session);
5237
143
        }
5238
807
    }
5239
1.03k
    if ('\0' == field[7][0] ||
5240
728
        'N' == field[7][0]) {
5241
        // No valid data, except time, sort of
5242
728
        GPSD_LOG(LOG_PROG, &session->context->errout,
5243
728
                 "NMEA0183: PSTI,036: not valid\n");
5244
728
        return mask;
5245
728
    }
5246
    // good attitude data to use
5247
302
    session->gpsdata.attitude.mtime = gpsd_utc_resolve(session);
5248
302
    session->gpsdata.attitude.heading = safe_atof(field[4]);
5249
302
    session->gpsdata.attitude.pitch = safe_atof(field[5]);
5250
302
    session->gpsdata.attitude.roll = safe_atof(field[6]);
5251
302
    mode = faa_mode(field[7][0]);
5252
5253
302
    mask |= ATTITUDE_SET;
5254
5255
302
    GPSD_LOG(LOG_PROG, &session->context->errout,
5256
302
             "NMEA0183: PSTI,036: mode %d heading %.2f  pitch %.2f roll %.2f "
5257
302
             "faa mode %s(%s)\n",
5258
302
             mode,
5259
302
             session->gpsdata.attitude.heading,
5260
302
             session->gpsdata.attitude.pitch,
5261
302
             session->gpsdata.attitude.roll,
5262
302
             field[7], char2str(field[7][0], c_faa_mode));
5263
302
    return mask;
5264
1.03k
}
5265
5266
/* decoce $PSTMCPU CPU load
5267
 * Private STM
5268
 * Present in ST Teseo liv4f
5269
 *
5270
 */
5271
static gps_mask_t processPSTMCPU(unsigned count UNUSED, char *field[],
5272
                                 struct gps_device_t *session)
5273
0
{
5274
    /*
5275
     * $PSTMCPU,<CPU_Usage>,<PLL_ON_OFF>,<CPU_Speed>*<checksum><cr><lf>
5276
     *
5277
     *  CPU_Usage  %
5278
     *
5279
     *  PLL_ON_OFF
5280
     *      0 = PLL Disabled
5281
     *      1 = PLL Enabled
5282
     *      -1 = Not supported
5283
     *
5284
     *  CPU_Speed  decimal digits
5285
     */
5286
5287
0
    static const struct vlist_t pll[] = {
5288
0
        {0, "PLL Disabled"},
5289
0
        {1, "PLL Ensabled"},
5290
0
        {-1, "Not Supported"},
5291
0
        {0, NULL},
5292
0
    };
5293
5294
0
    gps_mask_t mask = ONLINE_SET;
5295
0
    double cpu_usage = atof(field[1]);
5296
0
    int pll_on_off = atoi(field[2]);
5297
0
    unsigned cpu_speed = atoi(field[3]);
5298
5299
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
5300
0
            "NMEA0183: PSTMCPU cpu_usage %.2f pll %s(%d) cpu_speed %u\n",
5301
0
            cpu_usage, val2str(pll_on_off, pll), pll_on_off, cpu_speed);
5302
5303
0
    return mask;
5304
0
}
5305
5306
/* decoce $PSTMANTENNASTATUS antenna status
5307
 * Private STM
5308
 * Also used bysome Quectel.
5309
 * Present in ST Teseo liv4f
5310
 *
5311
 */
5312
static gps_mask_t processPSTMANTENNASTATUS(unsigned count UNUSED,
5313
                                           char *field[],
5314
                                           struct gps_device_t *session)
5315
394
{
5316
    /*
5317
     * $PSTMANTENNASTATUS,<ant_status>,<op_mode>,<rf_path>,<pwr_switch>*<chk>
5318
     * $PSTMANTENNASTATUS,0,0,0,0*51
5319
     *
5320
     *  ant_status Decimal Current
5321
     *      0 = Normal condition
5322
     *      1 = Open condition
5323
     *      2 = Short condition
5324
     *
5325
     *  op_mode Decimal
5326
     *  Current antenna detection operating mode
5327
     *      0 = Automatic mode
5328
     *      1 = Manual mode
5329
     *
5330
     * rf_path Decimal
5331
     * Current RF path
5332
     *      0 = External antenna
5333
     *      1 = Internal antenna
5334
     *
5335
     * pwr_switch Decimal
5336
     * Current antenna power status
5337
     *      0 = Antenna power is on
5338
     *      1 = Antenna power is off
5339
     */
5340
5341
394
    static const struct vlist_t vop_mode[] = {
5342
394
        {0, "Auto"},
5343
394
        {1, "Manual"},
5344
394
        {0, NULL},
5345
394
    };
5346
5347
394
    static const struct vlist_t vpwr_switch[] = {
5348
394
        {0, "On"},
5349
394
        {1, "Off"},
5350
394
        {0, NULL},
5351
394
    };
5352
5353
394
    static const struct vlist_t vrf_path[] = {
5354
394
        {0, "External"},
5355
394
        {1, "Internal"},
5356
394
        {0, NULL},
5357
394
    };
5358
5359
394
    gps_mask_t mask = ONLINE_SET;
5360
394
    int ant_status = atoi(field[1]);
5361
394
    int op_mode = atoi(field[2]);
5362
394
    int rf_path = atoi(field[3]);
5363
394
    int pwr_switch = atoi(field[4]);
5364
5365
394
    switch(ant_status) {
5366
138
    case 0:
5367
138
        session->newdata.ant_stat = ANT_OK;
5368
138
        break;
5369
60
    case 1:
5370
60
        session->newdata.ant_stat = ANT_OPEN;
5371
60
        break;
5372
67
    case 2:
5373
67
        session->newdata.ant_stat = ANT_SHORT;
5374
67
        break;
5375
129
    default:
5376
129
        session->newdata.ant_stat = ANT_UNK;
5377
129
        GPSD_LOG(LOG_PROG, &session->context->errout,
5378
129
                "NMEA0183: ant_stat: UNKNOWN(%d)\n", ant_status);
5379
129
        break;
5380
394
    }
5381
5382
394
    if (ANT_UNK != session->newdata.ant_stat) {
5383
265
        mask |= STATUS_SET;
5384
265
    }
5385
5386
394
    if (0 > op_mode ||
5387
321
        1 < op_mode) {
5388
216
        GPSD_LOG(LOG_WARN, &session->context->errout,
5389
216
                "NMEA0183: malformed PSTMANTENNASTATUS op_mode: %s\n",
5390
216
                field[2]);
5391
216
    }
5392
5393
394
    GPSD_LOG(LOG_PROG, &session->context->errout,
5394
394
            "NMEA0183: PSTMANTENNASTATUS ant_status:%d op_mode:%d "
5395
394
            "rf_path:%d pwr_switch:%d\n",
5396
394
            ant_status, op_mode, rf_path, pwr_switch);
5397
394
    GPSD_LOG(LOG_IO, &session->context->errout,
5398
394
             "NMEA0183: PSTMANTENNASTATUS ant_status:%s(%d) op_mode:%s(%d) "
5399
394
             "rf_path:%s(%d) pwer_switch:%s(%d)\n",
5400
394
             val2str(session->newdata.ant_stat, vant_status),
5401
394
             session->newdata.ant_stat,
5402
394
             val2str(op_mode, vop_mode), op_mode,
5403
394
             val2str(rf_path, vrf_path), rf_path,
5404
394
             val2str(pwr_switch, vpwr_switch), pwr_switch);
5405
5406
394
    return mask;
5407
394
}
5408
5409
/* decoce $PSTMVER
5410
 * Private STM
5411
 * Present in ST Teseo liv4f
5412
 *
5413
 * Response to $PSTMGETVER,255
5414
 *
5415
 */
5416
static gps_mask_t processPSTMVER(unsigned count UNUSED, char *field[],
5417
                                 struct gps_device_t *session)
5418
578
{
5419
    /*
5420
    * $PSTMVER,<SW name and version>*<checksum>
5421
    *
5422
    * $PSTMVER,FreeRTOS_V10.4.3_ARM*57
5423
    * $PSTMVER,BINIMG_STA8041_4.6.6.5.6_ARM*0C
5424
    * $PSTMVER,SWCFG_86065331*62
5425
    * $PSTMVER,GNSSLIB_8.4.8.13_ARM*7F
5426
    * $PSTMVER,OS20LIB_4.3.0_ARM*47
5427
    * $PSTMVER,GPSAPP_2.2.1_ARM*1D
5428
    * $PSTMVER,SWCFG_8102510d*35
5429
    * $PSTMVER,WAASLIB_2.18.0_ARM*61
5430
    * $PSTMVER,STAGPSLIB_5.0.0_ARM*59
5431
    * $PSTMVER,STA8090_622bc043*6F
5432
    */
5433
5434
578
    gps_mask_t mask = ONLINE_SET;
5435
578
    size_t m_len =  strnlen(field[1], 40) + 2;
5436
578
    size_t st_left =  (sizeof(session->subtype) -
5437
578
                       strnlen(session->subtype, sizeof(session->subtype)));
5438
578
    size_t st1_left =  (sizeof(session->subtype1) -
5439
578
                        strnlen(session->subtype1, sizeof(session->subtype1)));
5440
5441
578
    if (NULL != strstr(session->subtype, field[1]) ||
5442
578
        NULL != strstr(session->subtype1, field[1])) {
5443
        // already haev it, ignore.
5444
312
    } else if (m_len < st_left) {
5445
        // room in subtype
5446
201
        if ('\0' == session->subtype[0]) {
5447
59
            (void)strncat(session->subtype, "STM,",
5448
59
                          sizeof(session->subtype) - 1);
5449
142
        } else {
5450
142
            (void)strncat(session->subtype, ",",
5451
142
                          sizeof(session->subtype) - 1);
5452
142
        }
5453
201
        (void)strncat(session->subtype, field[1],
5454
201
                      sizeof(session->subtype) - 1);
5455
201
    } else if (m_len < st1_left) {
5456
        // room in subtype1
5457
68
        if ('\0' != session->subtype1[0]) {
5458
49
            (void)strncat(session->subtype1, ",",
5459
49
                          sizeof(session->subtype1) - 1);
5460
49
        }
5461
68
        (void)strncat(session->subtype1, field[1],
5462
68
                      sizeof(session->subtype1) - 1);
5463
68
    } else {
5464
        // else no room.  log it
5465
43
        GPSD_LOG(LOG_WARN, &session->context->errout,
5466
43
                "NMEA0183: $PSTMVER: no room for: %s\n", field[1]);
5467
43
    }
5468
5469
578
    GPSD_LOG(LOG_PROG, &session->context->errout,
5470
578
            "NMEA0183: $PSTMVER: %s, %s\n",
5471
578
            session->subtype, session->subtype1);
5472
5473
578
    return mask;
5474
578
}
5475
5476
// Recommend Minimum Course Specific GPS/TRANSIT Data
5477
static gps_mask_t processRMC(unsigned count, char *field[],
5478
                             struct gps_device_t *session)
5479
1.27k
{
5480
    /*
5481
     * RMC,225446.33,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E,A*68
5482
     * 1     225446.33    Time of fix 22:54:46 UTC
5483
     * 2     A            Status of Fix:
5484
     *                     A = Autonomous, valid;
5485
     *                     V = invalid
5486
     * 3,4   4916.45,N    Latitude 49 deg. 16.45 min North
5487
     * 5,6   12311.12,W   Longitude 123 deg. 11.12 min West
5488
     * 7     000.5        Speed over ground, Knots
5489
     * 8     054.7        Course Made Good, True north
5490
     * 9     181194       Date of fix ddmmyy.  18 November 1994
5491
     * 10,11 020.3,E      Magnetic variation 20.3 deg East
5492
     * 12    A            FAA mode indicator (NMEA 2.3 and later)
5493
     *                     see faa_mode() for possible mode values
5494
     * 13    V            Nav Status (NMEA 4.1 and later)
5495
     *                     A = autonomous,
5496
     *                     D = differential,
5497
     *                     E = Estimated (DR),
5498
     *                     F = RTK Float
5499
     *                     M = Manual input mode
5500
     *                     N = No fix.  Not valid,
5501
     *                     P = High Precision Mode
5502
     *                     R = RTK Integer
5503
     *                     S = Simulator,
5504
     *                     V = Invalid
5505
     * *68        mandatory nmea_checksum
5506
     *
5507
     * SiRF chipsets don't return either Mode Indicator or magnetic variation.
5508
     */
5509
1.27k
    gps_mask_t mask = ONLINE_SET;
5510
1.27k
    char status = field[2][0];
5511
1.27k
    int newstatus;
5512
5513
    /* As of Dec 2023, the regressions only have A, or V in field 2.
5514
     * NMEA says only A, and V are valid
5515
     */
5516
1.27k
    switch (status) {
5517
235
    default:
5518
        // missing, never seen this case.
5519
235
        FALLTHROUGH
5520
235
    case 'V':
5521
        // Invalid
5522
235
        session->newdata.mode = MODE_NO_FIX;
5523
235
        if ('\0' == field[1][0] ||
5524
172
            '\0' ==  field[9][0]) {
5525
            /* No time available. That breaks cycle end detector
5526
             * Force report to bypass cycle detector and get report out.
5527
             * To handle Querks (Quectel) like this:
5528
             *  $GPRMC,,V,,,,,,,,,,N*53
5529
             */
5530
151
            memset(&session->nmea.date, 0, sizeof(session->nmea.date));
5531
151
            session->cycle_end_reliable = false;
5532
151
            mask |= REPORT_IS | TIME_SET;
5533
151
        }
5534
235
        mask |= STATUS_SET | MODE_SET;
5535
235
        break;
5536
1.03k
    case 'A':
5537
        // Valid Fix
5538
        /*
5539
         * The MTK3301, Royaltek RGM-3800, and possibly other
5540
         * devices deliver bogus time values when the navigation
5541
         * warning bit is set.
5542
         */
5543
        /* The Meinberg GPS164 only outputs GPRMC.  Do set status
5544
         * so it can increment fixcnt.
5545
         */
5546
1.03k
        if ('\0' != field[1][0] &&
5547
999
            9 < count &&
5548
941
            '\0' !=  field[9][0]) {
5549
650
            if (0 == merge_hhmmss(field[1], session) &&
5550
306
                0 == merge_ddmmyy(field[9], session)) {
5551
                // got a good data/time
5552
137
                mask |= TIME_SET;
5553
137
                register_fractional_time(field[0], field[1], session);
5554
137
            }
5555
650
        }
5556
        // else, no point to the time only case, no regressions with that
5557
5558
1.03k
        if (0 == do_lat_lon(&field[3], &session->newdata)) {
5559
296
            newstatus = STATUS_GPS;
5560
296
            mask |= LATLON_SET;
5561
296
            if (MODE_2D >= session->lastfix.mode) {
5562
                /* we have at least a 2D fix
5563
                 * might cause blinking */
5564
135
                session->newdata.mode = MODE_2D;
5565
161
            } else if (MODE_3D == session->lastfix.mode) {
5566
                // keep the 3D, this may be cycle starter
5567
                // might cause blinking
5568
161
                session->newdata.mode = MODE_3D;
5569
161
            }
5570
741
        } else {
5571
741
            newstatus = STATUS_UNK;
5572
741
            session->newdata.mode = MODE_NO_FIX;
5573
741
        }
5574
1.03k
        mask |= MODE_SET;
5575
1.03k
        if ('\0' != field[7][0]) {
5576
535
            session->newdata.speed = safe_atof(field[7]) * KNOTS_TO_MPS;
5577
535
            mask |= SPEED_SET;
5578
535
        }
5579
1.03k
        if ('\0' != field[8][0]) {
5580
449
            session->newdata.track = safe_atof(field[8]);
5581
449
            mask |= TRACK_SET;
5582
449
        }
5583
5584
        // get magnetic variation
5585
1.03k
        if ('\0' != field[10][0] &&
5586
857
            '\0' != field[11][0]) {
5587
580
            session->newdata.magnetic_var = safe_atof(field[10]);
5588
5589
580
            switch (field[11][0]) {
5590
58
            case 'E':
5591
                // no change
5592
58
                break;
5593
411
            case 'W':
5594
411
                session->newdata.magnetic_var = -session->newdata.magnetic_var;
5595
411
                break;
5596
111
            default:
5597
                // huh?
5598
111
                session->newdata.magnetic_var = NAN;
5599
111
                break;
5600
580
            }
5601
580
            if (0 == isfinite(session->newdata.magnetic_var) ||
5602
331
                0.09 >= fabs(session->newdata.magnetic_var)) {
5603
                // some GPS set 0.0,E, or 0,w instead of blank
5604
319
                session->newdata.magnetic_var = NAN;
5605
319
            } else {
5606
261
                mask |= MAGNETIC_TRACK_SET;
5607
261
            }
5608
580
        }
5609
5610
1.03k
        if (12 < count) {
5611
314
            if ('\0' != field[12][0]) {
5612
                // Have FAA mode indicator (NMEA 2.3 and later)
5613
166
                newstatus = faa_mode(field[12][0]);
5614
166
            }
5615
            /*
5616
             * Navigation Status
5617
             * If present, can not be NUL:
5618
             * S = Safe
5619
             * C = Caution
5620
             * U = Unsafe
5621
             * V = invalid.
5622
             *
5623
             * In the regressions, as of Dec 2023, field 13 is
5624
             * always 'V', and field 2 is always 'A'.  That seems
5625
             * like an invalid combination.... */
5626
314
            if (13 < count) {
5627
151
                if ('\0' != field[13][0]) {
5628
69
                    ;  // skip for now
5629
69
                }
5630
151
            }
5631
314
            GPSD_LOG(LOG_PROG, &session->context->errout,
5632
314
                     "NMEA0183: RMC: status %s(%d) faa mode %s(%s) "
5633
314
                     "faa status %s\n",
5634
314
                     field[2], newstatus, field[12],
5635
314
                     char2str(field[12][0], c_faa_mode), field[13]);
5636
314
        }
5637
5638
        /*
5639
         * This copes with GPSes like the Magellan EC-10X that *only* emit
5640
         * GPRMC. In this case we set mode and status here so the client
5641
         * code that relies on them won't mistakenly believe it has never
5642
         * received a fix.
5643
         */
5644
1.03k
        if (3 < session->gpsdata.satellites_used) {
5645
            // 4 sats used means 3D
5646
331
            session->newdata.mode = MODE_3D;
5647
706
        } else if (0 != isfinite(session->gpsdata.fix.altHAE) ||
5648
590
                   0 != isfinite(session->gpsdata.fix.altMSL)) {
5649
            /* we probably have at least a 3D fix
5650
             * this handles old GPS that do not report 3D */
5651
162
            session->newdata.mode = MODE_3D;
5652
162
        }
5653
1.03k
        session->newdata.status = newstatus;
5654
1.03k
        mask |= STATUS_SET | MODE_SET;
5655
1.27k
    }
5656
5657
1.27k
    GPSD_LOG(LOG_PROG, &session->context->errout,
5658
1.27k
             "NMEA0183: RMC: ddmmyy=%s hhmmss=%s lat=%.2f lon=%.2f "
5659
1.27k
             "speed=%.2f track=%.2f mode=%d var=%.1f status=%d\n",
5660
1.27k
             field[9], field[1],
5661
1.27k
             session->newdata.latitude,
5662
1.27k
             session->newdata.longitude,
5663
1.27k
             session->newdata.speed,
5664
1.27k
             session->newdata.track,
5665
1.27k
             session->newdata.mode,
5666
1.27k
             session->newdata.magnetic_var,
5667
1.27k
             session->newdata.status);
5668
1.27k
    return mask;
5669
1.27k
}
5670
5671
/* precessROT() - process Rate Of Turn
5672
 *
5673
 * Deprecated by NMEA in 2008
5674
 */
5675
static gps_mask_t processROT(unsigned count UNUSED, char *field[],
5676
                             struct gps_device_t *session)
5677
234
{
5678
    /*
5679
     * $APROT,0.013,A*35
5680
     *
5681
     * 1) Rate of Turn deg/min
5682
     * 2) A = valid, V = Void
5683
     * )  checksum
5684
     *
5685
     */
5686
234
    gps_mask_t mask = ONLINE_SET;
5687
5688
234
    if ('\0' == field[1][0] ||
5689
166
        'A' != field[2][0]) {
5690
        // no data
5691
166
        return mask;
5692
166
    }
5693
5694
    // assume good data
5695
68
    session->gpsdata.attitude.rot = safe_atof(field[1]);
5696
68
    mask |= ATTITUDE_SET;
5697
5698
68
    GPSD_LOG(LOG_PROG, &session->context->errout,
5699
68
             "NMEA0183: $xxROT:Rate of Turn %f\n",
5700
68
             session->gpsdata.attitude.rot);
5701
68
    return mask;
5702
234
}
5703
5704
/*
5705
 * Unicore $SNRSTAT  Sensor status
5706
 * Note: Invalid sender: $SN
5707
 */
5708
static gps_mask_t processSNRSTAT(unsigned count UNUSED, char *field[],
5709
                                 struct gps_device_t *session)
5710
157
{
5711
    /*
5712
     * $SNRSTAT,1,1,0,0*5D
5713
     */
5714
5715
157
    gps_mask_t mask = ONLINE_SET;
5716
157
    static char probe[] = "$PDTINFO\r\n";
5717
157
    static char type[] = "Unicore";
5718
157
    int insstatus = atoi(field[1]);     // IMU status
5719
157
    int odostatus = atoi(field[2]);     // Odometer Status
5720
157
    int InstallState = atoi(field[3]);  // Install State
5721
157
    int mapstat = atoi(field[4]);       // PAP status
5722
5723
157
    GPSD_LOG(LOG_PROG, &session->context->errout,
5724
157
             "NMEA0183: SNRSTAT insstatus %d obsstatus %d InstallState %d "
5725
157
             "mapstat %d\n",
5726
157
             insstatus, odostatus, InstallState, mapstat);
5727
5728
157
    GPSD_LOG(LOG_IO, &session->context->errout,
5729
157
             "NMEA0183: SNRSTAT insstatus %s obsstatus %s InstallState %s "
5730
157
             "mapstat %s\n",
5731
157
             val2str(insstatus, vsnrstat_insstatus),
5732
157
             val2str(odostatus, vsnrstat_odostatus),
5733
157
             val2str(InstallState, vsnrstat_InstallState),
5734
157
             val2str(mapstat, vsnrstat_mapstat));
5735
5736
157
    if ('\0' == session->subtype[0]) {
5737
        // this is Unicore
5738
        // Send $PDTINFO to get back $PDTINFO,....
5739
84
        (void)gpsd_write(session, probe, sizeof(probe));
5740
        // mark so we don't ask twice
5741
84
        (void)strlcpy(session->subtype, type, sizeof(session->subtype) - 1);
5742
84
    }
5743
157
    return mask;
5744
157
}
5745
5746
5747
/*
5748
 * Skytraq undocumented debug sentences take this format:
5749
 * $STI,type,val*CS
5750
 * type is a 2 char subsentence type
5751
 * Note: NO checksum
5752
 */
5753
static gps_mask_t processSTI(unsigned count, char *field[],
5754
                             struct gps_device_t *session)
5755
357
{
5756
357
    gps_mask_t mask = ONLINE_SET;
5757
5758
357
    if (0 != strncmp(session->device_type->type_name, "Skytraq", 7)) {
5759
        // this is skytraq, but marked yet, so probe for Skytraq
5760
        // Send MID 0x02, to get back MID 0x80
5761
239
        (void)gpsd_write(session, "\xA0\xA1\x00\x02\x02\x01\x03\x0d\x0a",9);
5762
239
    }
5763
5764
357
    if ( 0 == strcmp( field[1], "IC") ) {
5765
        // $STI,IC,error=XX, this is always very bad, but undocumented
5766
68
        GPSD_LOG(LOG_ERROR, &session->context->errout,
5767
68
                 "NMEA0183: Skytraq: $STI,%s,%s\n", field[1], field[2]);
5768
68
        return mask;
5769
68
    }
5770
289
    GPSD_LOG(LOG_PROG, &session->context->errout,
5771
289
             "NMEA0183: STI,%s: Unknown type, Count: %d\n", field[1], count);
5772
5773
289
    return mask;
5774
357
}
5775
5776
// SiRF Estimated Position Errors
5777
// $xxTHS -- True Heading and Status
5778
static gps_mask_t processTHS(unsigned count UNUSED, char *field[],
5779
                             struct gps_device_t *session)
5780
397
{
5781
    /*
5782
     * $GNTHS,121.15.A*1F<CR><LF>
5783
     * 1  - Heading, degrees True
5784
     * 2  - Mode indicator
5785
     *      'A’ = Autonomous
5786
     *      'E’ = Estimated (dead reckoning)
5787
     *      'M’ = Manual input
5788
     *      'S’ = Simulator
5789
     *      'V’ = Data not valid
5790
     * 3  - Checksum
5791
     */
5792
397
    gps_mask_t mask = ONLINE_SET;
5793
397
    double heading;
5794
5795
397
    if ('\0' == field[1][0] ||
5796
331
        '\0' == field[2][0]) {
5797
        // no data
5798
138
        return mask;
5799
138
    }
5800
259
    if ('V' == field[2][0]) {
5801
        // invalid data
5802
        // ignore A, E, M and S for now
5803
57
        return mask;
5804
57
    }
5805
202
    heading = safe_atof(field[1]);
5806
202
    if ((0.0 > heading) ||
5807
135
        (360.0 < heading)) {
5808
        // bad data
5809
128
        return mask;
5810
128
    }
5811
5812
74
    GPSD_LOG(LOG_PROG, &session->context->errout,
5813
74
             "NMEA0183: $xxTHS heading %lf mode %s\n",
5814
74
             heading, field[2]);
5815
5816
74
    return mask;
5817
202
}
5818
5819
static gps_mask_t processTNTA(unsigned count UNUSED, char *field[],
5820
                              struct gps_device_t *session)
5821
550
{
5822
    /*
5823
     * Proprietary sentence for iSync GRClok/LNRClok.
5824
5825
     $PTNTA,20000102173852,1,T4,,,6,1,0*32
5826
5827
     1. Date/time in format year, month, day, hour, minute, second
5828
     2. Oscillator quality 0:warming up, 1:freerun, 2:disciplined.
5829
     3. Always T4. Format indicator.
5830
     4. Interval ppsref-ppsout in [ns]. Blank if no ppsref.
5831
     5. Fine phase comparator in approx. [ns]. Always close to -500 or
5832
        +500 if not disciplined. Blank if no ppsref.
5833
     6. iSync Status.  0:warming up or no light, 1:tracking set-up,
5834
        2:track to PPSREF, 3:synch to PPSREF, 4:Free Run. Track OFF,
5835
        5:FR. PPSREF unstable, 6:FR. No PPSREF, 7:FREEZE, 8:factory
5836
        used, 9:searching Rb line
5837
     7. GPS messages indicator. 0:do not take account, 1:take account,
5838
        but no message, 2:take account, partially ok, 3:take account,
5839
        totally ok.
5840
     8. Transfer quality of date/time. 0:no, 1:manual, 2:GPS, older
5841
        than x hours, 3:GPS, fresh.
5842
5843
     */
5844
550
    gps_mask_t mask = ONLINE_SET;
5845
5846
550
    if (0 == strcmp(field[3], "T4")) {
5847
477
        struct oscillator_t *osc = &session->gpsdata.osc;
5848
477
        unsigned int quality = atoi(field[2]);
5849
477
        unsigned int delta = atoi(field[4]);
5850
477
        unsigned int fine = atoi(field[5]);
5851
477
        unsigned int status = atoi(field[6]);
5852
477
        char deltachar = field[4][0];
5853
5854
477
        osc->running = (0 < quality);
5855
477
        osc->reference = (deltachar && (deltachar != '?'));
5856
477
        if (osc->reference) {
5857
353
            if (500 > delta) {
5858
134
                osc->delta = fine;
5859
219
            } else {
5860
219
                osc->delta = ((delta < 500000000) ? delta : 1000000000 - delta);
5861
219
            }
5862
353
        } else {
5863
124
            osc->delta = 0;
5864
124
        }
5865
477
        osc->disciplined = ((quality == 2) && (status == 3));
5866
477
        mask |= OSCILLATOR_SET;
5867
5868
477
        GPSD_LOG(LOG_DATA, &session->context->errout,
5869
477
                 "NMEA0183: PTNTA,T4: quality=%s, delta=%s, fine=%s,"
5870
477
                 "status=%s\n",
5871
477
                 field[2], field[4], field[5], field[6]);
5872
477
    }
5873
550
    return mask;
5874
550
}
5875
5876
static gps_mask_t processTNTHTM(unsigned count UNUSED, char *field[],
5877
                                struct gps_device_t *session)
5878
86
{
5879
    /*
5880
     * Proprietary sentence for True North Technologies Magnetic Compass.
5881
     * This may also apply to some Honeywell units since they may have been
5882
     * designed by True North.
5883
5884
     $PTNTHTM,14223,N,169,N,-43,N,13641,2454*15
5885
5886
     HTM,x.x,a,x.x,a,x.x,a,x.x,x.x*hh<cr><lf>
5887
     Fields in order:
5888
     1. True heading (compass measurement + deviation + variation)
5889
     2. magnetometer status character:
5890
     C = magnetometer calibration alarm
5891
     L = low alarm
5892
     M = low warning
5893
     N = normal
5894
     O = high warning
5895
     P = high alarm
5896
     V = magnetometer voltage level alarm
5897
     3. pitch angle
5898
     4. pitch status character - see field 2 above
5899
     5. roll angle
5900
     6. roll status character - see field 2 above
5901
     7. dip angle
5902
     8. relative magnitude horizontal component of earth's magnetic field
5903
     *hh          mandatory nmea_checksum
5904
5905
     By default, angles are reported as 26-bit integers: weirdly, the
5906
     technical manual says either 0 to 65535 or -32768 to 32767 can
5907
     occur as a range.
5908
     */
5909
86
    gps_mask_t mask = ONLINE_SET;
5910
5911
    // True heading
5912
86
    session->gpsdata.attitude.heading = safe_atof(field[1]);
5913
86
    session->gpsdata.attitude.mag_st = *field[2];
5914
86
    session->gpsdata.attitude.pitch = safe_atof(field[3]);
5915
86
    session->gpsdata.attitude.pitch_st = *field[4];
5916
86
    session->gpsdata.attitude.roll = safe_atof(field[5]);
5917
86
    session->gpsdata.attitude.roll_st = *field[6];
5918
86
    session->gpsdata.attitude.dip = safe_atof(field[7]);
5919
86
    session->gpsdata.attitude.mag_x = safe_atof(field[8]);
5920
86
    mask |= (ATTITUDE_SET);
5921
5922
86
    GPSD_LOG(LOG_DATA, &session->context->errout,
5923
86
             "NMEA0183: $PTNTHTM heading %lf (%c).\n",
5924
86
             session->gpsdata.attitude.heading,
5925
86
             session->gpsdata.attitude.mag_st);
5926
86
    return mask;
5927
86
}
5928
5929
// GPS Text message
5930
static gps_mask_t processTXT(unsigned count, char *field[],
5931
                             struct gps_device_t *session)
5932
204
{
5933
    /*
5934
     * $GNTXT,01,01,01,PGRM inv format*2A
5935
     * 1                   Number of sentences for full data
5936
     * 1                   Sentence 1 of 1
5937
     * 01                  Message type
5938
     *       00 - error
5939
     *       01 - warning
5940
     *       02 - notice
5941
     *       07 - user
5942
     * PGRM inv format     ASCII text
5943
     *
5944
     * Can occur with talker IDs:
5945
     *   BD (Beidou),
5946
     *   GA (Galileo),
5947
     *   GB (Beidou),
5948
     *   GI (IRNSS
5949
     *   GL (GLONASS),
5950
     *   GN (GLONASS, any combination GNSS),
5951
     *   GP (GPS, SBAS, QZSS),
5952
     *   GQ (QZSS).
5953
     *   PQ (QZSS). Quectel Quirk
5954
     *   QZ (QZSS).
5955
     *
5956
     * Unicore undcumented:
5957
     *
5958
     * $GNTXT,01,01,01,0,500482,0000,80A0,80A0,-45.277,0*6B
5959
     * $GNTXT,01,01,02,0,00,10000,00,01,17,01,0001,0000,0.000*57
5960
     */
5961
204
    gps_mask_t mask = ONLINE_SET;
5962
204
    int msgType = 0;
5963
204
    char *msgType_txt = "Unknown";
5964
5965
204
    if (5 != count) {
5966
69
      return mask;
5967
69
    }
5968
5969
135
    msgType = atoi(field[3]);
5970
5971
135
    switch ( msgType ) {
5972
53
    case 0:
5973
53
        msgType_txt = "Error";
5974
53
        break;
5975
10
    case 1:
5976
10
        msgType_txt = "Warning";
5977
10
        break;
5978
4
    case 2:
5979
4
        msgType_txt = "Notice";
5980
4
        break;
5981
2
    case 7:
5982
2
        msgType_txt = "User";
5983
2
        break;
5984
135
    }
5985
5986
    // maximum text length unknown, guess 80
5987
135
    GPSD_LOG(LOG_WARN, &session->context->errout,
5988
135
             "NMEA0183: TXT: %.10s: %.80s\n",
5989
135
             msgType_txt, field[4]);
5990
135
    return mask;
5991
135
}
5992
5993
/* process xxVTG
5994
 *     $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K
5995
 *     $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K,A
5996
 *
5997
 * where:
5998
 *         1,2     054.7,T      True track made good (degrees)
5999
 *         3,4     034.4,M      Magnetic track made good
6000
 *         5,6     005.5,N      Ground speed, knots
6001
 *         7,8     010.2,K      Ground speed, Kilometers per hour
6002
 *         9       A            Mode Indicator (optional)
6003
 *                                see faa_mode() for possible mode values
6004
 *
6005
 * see also:
6006
 * https://gpsd.gitlab.io/gpsd/NMEA.html#_vtg_track_made_good_and_ground_speed
6007
 */
6008
static gps_mask_t processVTG(unsigned count,
6009
                             char *field[],
6010
                             struct gps_device_t *session)
6011
508
{
6012
508
    gps_mask_t mask = ONLINE_SET;
6013
6014
508
    if( (field[1][0] == '\0') || (field[5][0] == '\0')){
6015
113
        return mask;
6016
113
    }
6017
6018
    // ignore empty/missing field, fix mode of last resort
6019
395
    if ((9 < count) &&
6020
292
        ('\0' != field[9][0])) {
6021
6022
208
        switch (field[9][0]) {
6023
0
        case 'A':
6024
            // Autonomous, 2D or 3D fix
6025
0
            FALLTHROUGH
6026
0
        case 'D':
6027
            // Differential, 2D or 3D fix
6028
            // MODE_SET here causes issues
6029
            // mask |= MODE_SET;
6030
0
            break;
6031
67
        case 'E':
6032
            // Estimated, DR only
6033
67
            FALLTHROUGH
6034
134
        case 'N':
6035
            // Not Valid
6036
            // MODE_SET here causes issues
6037
            // mask |= MODE_SET;
6038
            // nothing to use here, leave
6039
134
            return mask;
6040
74
        default:
6041
            // Huh?
6042
74
            break;
6043
208
        }
6044
208
    }
6045
6046
    // set true track
6047
261
    session->newdata.track = safe_atof(field[1]);
6048
261
    mask |= TRACK_SET;
6049
6050
    // set magnetic variation
6051
261
    if ('\0' != field[3][0]) {  // ignore empty fields
6052
133
        session->newdata.magnetic_track = safe_atof(field[3]);
6053
133
        mask |= MAGNETIC_TRACK_SET;
6054
133
    }
6055
6056
261
    session->newdata.speed = safe_atof(field[5]) * KNOTS_TO_MPS;
6057
261
    mask |= SPEED_SET;
6058
6059
261
    GPSD_LOG(LOG_DATA, &session->context->errout,
6060
261
             "NMEA0183: VTG: course(T)=%.2f, course(M)=%.2f, speed=%.2f",
6061
261
             session->newdata.track, session->newdata.magnetic_track,
6062
261
             session->newdata.speed);
6063
261
    return mask;
6064
395
}
6065
6066
/* precessXDR() - process transducer messages
6067
 */
6068
static gps_mask_t processXDR(unsigned count, char *field[],
6069
                             struct gps_device_t *session)
6070
469
{
6071
    /*
6072
     * $APXDR,A,0.135,D,PTCH*7C
6073
     * $APXDR,A,3.861,D,ROLL*65
6074
     *
6075
     * 1) Transducer type
6076
     *     A = Angular Displacement
6077
     * 2) Measurement data
6078
     * 3) Units of measure
6079
     *     D = degrees
6080
     * 4) Transducer ID
6081
     *     can be repeated...
6082
     * The previsou 4 messages can be repeated at least 9 more times.
6083
     * )  checksum
6084
     *
6085
     * TODO: stacked measurements, like the TNT Revolution:
6086
  $HCXDR,A,177,D,PITCH,A,-40,D,ROLL,G,358,,MAGX,G,2432,,MAGY,G,-8974,,MAGZ*47
6087
     *  the bund_zeus:
6088
  $IIXDR,C,,C,AIRTEMP,A,-3.0,D,HEEL,A,3.7,D,TRIM,P,,B,BARO,A,-4.2,D,RUDDER*28
6089
     *
6090
     */
6091
469
    gps_mask_t mask = ONLINE_SET;
6092
469
    unsigned i;
6093
469
    unsigned num_meas = count / 4;
6094
6095
469
    if (10 < num_meas) {
6096
        // nodocumented limit of measurements, we pick 10
6097
35
        num_meas = 10;
6098
35
    }
6099
6100
2.16k
    for (i = 0; i < num_meas; i++) {
6101
1.69k
        double data = 0.0;
6102
1.69k
        unsigned j = i * 4;
6103
6104
1.69k
        if ('\0' == field[j + 2][0]) {
6105
            // no data, skip it
6106
646
            GPSD_LOG(LOG_PROG, &session->context->errout,
6107
646
                     "NMEA0183: $xxXDR: Type %.10s Data %.10s Units %.10s "
6108
646
                     "ID %.10s\n",
6109
646
                     field[j + 1], field[j + 2], field[j + 3], field[j + 4]);
6110
646
            continue;
6111
646
        }
6112
6113
1.04k
        data = safe_atof(field[j + 2]);
6114
6115
1.04k
        switch (field[j + 1][0]) {
6116
508
        case 'A':
6117
            // angles
6118
508
            if ('D' != field[j + 3][0]) {
6119
                // not degrees
6120
206
                continue;
6121
206
            }
6122
302
            if (0 == strncmp( "HEEL", field[j + 4], 10)) {
6123
                // session->gpsdata.attitude.roll = data;
6124
                // mask |= ATTITUDE_SET;
6125
302
            } else if (0 == strncmp( "PTCH", field[j + 4], 10) ||
6126
302
                0 == strncmp( "PITCH", field[j + 4], 10)) {
6127
0
                session->gpsdata.attitude.pitch = data;
6128
0
                mask |= ATTITUDE_SET;
6129
302
            } else if (0 == strncmp( "ROLL", field[j + 4], 10)) {
6130
0
                session->gpsdata.attitude.roll = data;
6131
0
                mask |= ATTITUDE_SET;
6132
302
            } else if (0 == strncmp( "RUDDER", field[j + 4], 10)) {
6133
                // session->gpsdata.attitude.roll = data;
6134
                // mask |= ATTITUDE_SET;
6135
302
            } else if (0 == strncmp( "TRIM", field[j + 4], 10)) {
6136
                // session->gpsdata.attitude.roll = data;
6137
                // mask |= ATTITUDE_SET;
6138
0
            }
6139
            // else, unknown
6140
302
            break;
6141
93
        case 'G':
6142
            // G: TODO: G,358,,MAGX,G,2432,,MAGY,G,-8974,,MAGZ*47
6143
            // oddly field[j + 3][0] is NUL...
6144
6145
93
            if (0 == strncmp( "MAGX", field[j + 4], 10)) {
6146
                // unknown scale
6147
0
                session->gpsdata.attitude.mag_x = data;
6148
0
                mask |= ATTITUDE_SET;
6149
93
            } else if (0 == strncmp( "MAGY", field[j + 4], 10)) {
6150
                // unknown scale
6151
0
                session->gpsdata.attitude.mag_y = data;
6152
0
                mask |= ATTITUDE_SET;
6153
93
            } else if (0 == strncmp( "MAGZ", field[j + 4], 10)) {
6154
                // unknown scale
6155
0
                session->gpsdata.attitude.mag_z = data;
6156
0
                mask |= ATTITUDE_SET;
6157
0
            }
6158
93
            break;
6159
0
        case 'C':
6160
            // C,,C,AIRTEMP,
6161
0
            FALLTHROUGH
6162
0
        case 'P':
6163
            // Pressure: TODO: P,,B,BARO
6164
0
            FALLTHROUGH
6165
447
        default:
6166
447
            break;
6167
1.04k
        }
6168
6169
842
        GPSD_LOG(LOG_PROG, &session->context->errout,
6170
842
                 "NMEA0183: $xxXDR: Type %.10s Data %f Units %.10s ID %.10s\n",
6171
842
                 field[j + 1], data, field[j + 3], field[j + 4]);
6172
842
    }
6173
469
    return mask;
6174
469
}
6175
6176
// Time & Date
6177
static gps_mask_t processZDA(unsigned count UNUSED, char *field[],
6178
                             struct gps_device_t *session)
6179
1.92k
{
6180
    /*
6181
     * $GPZDA,160012.71,11,03,2004,-1,00*7D
6182
     * 1) UTC time (hours, minutes, seconds, may have fractional subsecond)
6183
     * 2) Day, 01 to 31
6184
     * 3) Month, 01 to 12
6185
     * 4) Year (4 digits)
6186
     * 5) Local zone description, 00 to +- 13 hours
6187
     * 6) Local zone minutes description, apply same sign as local hours
6188
     * 7) Checksum
6189
     *
6190
     * Note: some devices, like the u-blox ANTARIS 4h, are known to ship ZDAs
6191
     * with some fields blank under poorly-understood circumstances (probably
6192
     * when they don't have satellite lock yet).
6193
     */
6194
1.92k
    gps_mask_t mask = ONLINE_SET;
6195
1.92k
    int year, mon, mday, century;
6196
1.92k
    char ts_buf[TIMESPEC_LEN];
6197
6198
1.92k
    if ('\0' == field[1][0] ||
6199
1.86k
        '\0' == field[2][0] ||
6200
1.79k
        '\0' == field[3][0] ||
6201
1.72k
        '\0' == field[4][0]) {
6202
270
        GPSD_LOG(LOG_WARN, &session->context->errout,
6203
270
                 "NMEA0183: ZDA fields are empty\n");
6204
270
        return mask;
6205
270
    }
6206
6207
1.65k
    if (0 != merge_hhmmss(field[1], session)) {
6208
        // bad time
6209
77
        return mask;
6210
77
    }
6211
6212
    /*
6213
     * We didn't register fractional time here because we wanted to leave
6214
     * ZDA out of end-of-cycle detection. Some devices sensibly emit it only
6215
     * when they have a fix, so watching for it can make them look
6216
     * like they have a variable fix reporting cycle.  But later thought
6217
     * was to not throw out good data because it is inconvenient.
6218
     */
6219
1.58k
    mday = atoi(field[2]);
6220
1.58k
    mon = atoi(field[3]);
6221
1.58k
    year = atoi(field[4]);
6222
1.58k
    century = year - year % 100;
6223
1.58k
    if (1900 > year  ||
6224
1.49k
        2200 < year) {
6225
193
        GPSD_LOG(LOG_WARN, &session->context->errout,
6226
193
                 "NMEA0183: malformed ZDA year: %s\n",  field[4]);
6227
1.38k
    } else if (1 > mon ||
6228
1.31k
               12 < mon) {
6229
164
        GPSD_LOG(LOG_WARN, &session->context->errout,
6230
164
                 "NMEA0183: malformed ZDA month: %s\n",  field[3]);
6231
1.22k
    } else if (1 > mday ||
6232
1.15k
               31 < mday) {
6233
152
        GPSD_LOG(LOG_WARN, &session->context->errout,
6234
152
                 "NMEA0183: malformed ZDA day: %s\n",  field[2]);
6235
1.07k
    } else {
6236
1.07k
        gpsd_century_update(session, century);
6237
1.07k
        session->nmea.date.tm_year = year - 1900;
6238
1.07k
        session->nmea.date.tm_mon = mon - 1;
6239
1.07k
        session->nmea.date.tm_mday = mday;
6240
1.07k
        session->newdata.time = gpsd_utc_resolve(session);
6241
1.07k
        register_fractional_time(field[0], field[1], session);
6242
1.07k
        mask = TIME_SET;
6243
1.07k
    }
6244
1.58k
    GPSD_LOG(LOG_DATA, &session->context->errout,
6245
1.58k
         "NMEA0183: ZDA time %s\n",
6246
1.58k
          timespec_str(&session->newdata.time, ts_buf, sizeof(ts_buf)));
6247
1.58k
    return mask;
6248
1.65k
}
6249
6250
6251
6252
/**************************************************************************
6253
 *
6254
 * Entry points begin here
6255
 *
6256
 **************************************************************************/
6257
6258
// parse an NMEA sentence, unpack it into a session structure
6259
gps_mask_t nmea_parse(char *sentence, struct gps_device_t * session)
6260
43.1k
{
6261
43.1k
    typedef gps_mask_t(*nmea_decoder) (unsigned count, char *f[],
6262
43.1k
                                       struct gps_device_t * session);
6263
43.1k
    static struct
6264
43.1k
    {
6265
43.1k
        char *name;
6266
43.1k
        char *name1;            // 2nd field to match, as is $PSTI,030
6267
43.1k
        int nf;                 // minimum number of fields required to parse
6268
43.1k
        bool cycle_continue;    // cycle continuer?
6269
43.1k
        nmea_decoder decoder;
6270
43.1k
    } nmea_phrase[NMEA_NUM] = {
6271
43.1k
        {"PGLOR", NULL, 2,  false, processPGLOR},  // Android something...
6272
        // Ericsson firmware status
6273
43.1k
        {"PERC", "FWsts", 6, false, processPERCFWsts},
6274
        // Ericsson averaged position
6275
43.1k
        {"PERC", "GPavp", 6, false, processPERCGPavp},
6276
        // Ericsson control/heartbeat
6277
43.1k
        {"PERC", "GPctr", 6, false, processPERCGPctr},
6278
        // Ericsson debug output
6279
43.1k
        {"PERC", "GPdbg", 19, false, processPERCGPdbg},
6280
        // Ericsson oscillator phase/freq
6281
43.1k
        {"PERC", "GPppf", 5, false, processPERCGPppf},
6282
        // Ericsson GPS time reference
6283
43.1k
        {"PERC", "GPppr", 6, false, processPERCGPppr},
6284
        // Ericsson receiver health
6285
43.1k
        {"PERC", "GPreh", 2, false, processPERCGPreh},
6286
        // Ericsson receiver status
6287
43.1k
        {"PERC", "GPsts", 4, false, processPERCGPsts},
6288
        // Ericsson version info
6289
43.1k
        {"PERC", "GPver", 4, false, processPERCGPver},
6290
43.1k
        {"PGRMB", NULL, 0,  false, NULL},     // ignore Garmin DGPS Beacon Info
6291
43.1k
        {"PGRMC", NULL, 0,  false, NULL},        // ignore Garmin Sensor Config
6292
43.1k
        {"PGRME", NULL, 7,  false, processPGRME},
6293
43.1k
        {"PGRMF", NULL, 15, false, processPGRMF},  // Garmin GPS Fix Data
6294
43.1k
        {"PGRMH", NULL, 0,  false, NULL},     // ignore Garmin Aviation Height
6295
43.1k
        {"PGRMI", NULL, 0,  false, NULL},          // ignore Garmin Sensor Init
6296
43.1k
        {"PGRMM", NULL, 2,  false, processPGRMM},  // Garmin Map Datum
6297
43.1k
        {"PGRMO", NULL, 0,  false, NULL},     // ignore Garmin Sentence Enable
6298
43.1k
        {"PGRMT", NULL, 10, false, processPGRMT},  // Garmin Sensor Info
6299
43.1k
        {"PGRMV", NULL, 4,  false, processPGRMV},  // Garmin 3D Velocity Info
6300
43.1k
        {"PGRMZ", NULL, 4,  false, processPGRMZ},
6301
            /*
6302
             * Basic sentences must come after the PG* ones, otherwise
6303
             * Garmins can get stuck in a loop that looks like this:
6304
             *
6305
             * 1. A Garmin GPS in NMEA mode is detected.
6306
             *
6307
             * 2. PGRMC is sent to reconfigure to Garmin binary mode.
6308
             *    If successful, the GPS echoes the phrase.
6309
             *
6310
             * 3. nmea_parse() sees the echo as RMC because the talker
6311
             *    ID is ignored, and fails to recognize the echo as
6312
             *    PGRMC and ignore it.
6313
             *
6314
             * 4. The mode is changed back to NMEA, resulting in an
6315
             *    infinite loop.
6316
             */
6317
43.1k
        {"AAM", NULL, 0,  false, NULL},    // ignore Waypoint Arrival Alarm
6318
43.1k
        {"ACCURACY", NULL, 1,  true, processACCURACY},
6319
43.1k
        {"ACN", NULL, 0,  false, NULL},    // Alert Command, 4.10+
6320
43.1k
        {"ALC", NULL, 0,  false, NULL},    // Cyclic Alert List, 4.10+
6321
43.1k
        {"ALF", NULL, 0,  false, NULL},    // Alert Sentence, 4.10+
6322
43.1k
        {"ALM", NULL, 0,  false, NULL},    // GPS Almanac Data
6323
43.1k
        {"APB", NULL, 0,  false, NULL},    // Autopilot Sentence B
6324
43.1k
        {"ACF", NULL, 0,  false, NULL},    // Alert Command Refused, 4.10+
6325
43.1k
        {"AVR", NULL, 0,  false, NULL},    // Same as $PTNL,AVR
6326
43.1k
        {"BOD", NULL, 0,  false, NULL},    // Bearing Origin to Destination
6327
        // Bearing & Distance to Waypoint, Great Circle
6328
43.1k
        {"BWC", NULL, 12, false, processBWC},
6329
43.1k
        {"DBT", NULL, 7,  false, processDBT},  // depth
6330
43.1k
        {"DPT", NULL, 4,  false, processDPT},  // depth
6331
43.1k
        {"DTM", NULL, 2,  false, processDTM},  // datum
6332
43.1k
        {"EPV", NULL, 0,  false, NULL},     // Command/report Prop Value, 4.10+
6333
43.1k
        {"GBS", NULL, 7,  false, processGBS},  // GNSS Sat Fault Detection
6334
43.1k
        {"GGA", NULL, 13, false, processGGA},  // GPS fix data
6335
43.1k
        {"GGK", NULL, 0,  false, NULL},        // Same as $PTNL,GGK
6336
43.1k
        {"GGQ", NULL, 0,  false, NULL},        // Leica Position
6337
43.1k
        {"GLC", NULL, 0,  false, NULL},        // Geographic Position, LoranC
6338
43.1k
        {"GLL", NULL, 7,  true, processGLL},   // Position, Lat/Lon
6339
43.1k
        {"GMP", NULL, 0,  false, NULL},        // Map Projection
6340
43.1k
        {"GNS", NULL, 13, false, processGNS},  // GNSS fix data
6341
43.1k
        {"GRS", NULL, 4,  false, processGRS},  // GNSS Range Residuals
6342
43.1k
        {"GSA", NULL, 18, false, processGSA},  // DOP and Active sats
6343
43.1k
        {"GST", NULL, 8,  false, processGST},  // Pseudorange error stats
6344
43.1k
        {"GSV", NULL, 4,  false, processGSV},  // Sats in view
6345
        // UNICORE MEMES sensor data
6346
43.1k
        {"GYOACC", NULL, 14,  false, processGYOACC},
6347
        // Inertial Sense info, over long
6348
        // INFO,928404541,1.0.2.0,2.2.2.0,-377462659,2.0.0.0,-53643429,
6349
        // Inertial Sense Inc,2025-01-10,16:06:13.50,GPX -1,4,0, *7D
6350
43.1k
        {"INFO", NULL, 14,  false, processINFO},
6351
43.1k
        {"HCR", NULL, 0,  false, NULL},        // Heading Correction, 4.10+
6352
        // Heading, Deviation and Variation
6353
43.1k
        {"HDG", NULL, 0,  false, processHDG},
6354
43.1k
        {"HDM", NULL, 3,  false, processHDM},   // $APHDM, Magnetic Heading
6355
43.1k
        {"HDT", NULL, 1,  false, processHDT},   // Heading true
6356
        // Hell Andle, Roll Period, Roll Amplitude.  NMEA 4.10+
6357
43.1k
        {"HRM", NULL, 0,  false, NULL},
6358
43.1k
        {"HRP", NULL, 0, false, NULL},       // Serpentrio Headinf, Roll, Pitch
6359
43.1k
        {"HWBIAS", NULL, 0, false, NULL},       // Unknown HuaWei sentence
6360
43.1k
        {"LLK", NULL, 0, false, NULL},          // Leica local pos and GDOP
6361
43.1k
        {"LLQ", NULL, 0, false, NULL},          // Leica local pos and quality
6362
43.1k
        {"MLA", NULL, 0,  false, NULL},         // GLONASS Almana Data
6363
43.1k
        {"MOB", NULL, 0,  false, NULL},         // Man Overboard, NMEA 4.10+
6364
43.1k
        {"MSS", NULL, 0,  false, NULL},         // beacon receiver status
6365
43.1k
        {"MTW", NULL, 3,  false, processMTW},   // Water Temperature
6366
43.1k
        {"MWD", NULL, 0,  false, processMWD},   // Wind Direction and Speed
6367
43.1k
        {"MWV", NULL, 0,  false, processMWV},   // Wind Speed and Angle
6368
43.1k
        {"OHPR", NULL, 18, false, NULL},        // Oceanserver, not supported
6369
43.1k
        {"OSD", NULL, 0,  false, NULL},             // ignore Own Ship Data
6370
        // general handler for Ashtech
6371
43.1k
        {"PASHR", NULL, 3, false, processPASHR},
6372
        // Airoha proprietary
6373
43.1k
        {"PAIR001", NULL, 3, false, processPAIR001},  // ACK/NAK
6374
43.1k
        {"PAIR010", NULL, 5, false, processPAIR010},  // Request Aiding
6375
6376
        // Unicore proprietary
6377
43.1k
        {"PDTINFO", NULL, 6, false, processPDTINFO},  // Product ID
6378
6379
43.1k
        {"PEMT", NULL, 5, false, NULL},               // Evermore proprietary
6380
        // Furuno proprietary
6381
43.1k
        {"PERDACK", NULL, 4, false, NULL},            // ACK
6382
        // {"PERDAPI", NULL, 3, false, NULL},         // Config Send
6383
43.1k
        {"PERDCRD", NULL, 15, false, NULL},           // NLOSMASK?
6384
43.1k
        {"PERDCRG", "DCR", 6, false, NULL},           // QZSS DC report
6385
43.1k
        {"PERDCRJ", "FREQ", 9, false, NULL},          // Jamming Status
6386
43.1k
        {"PERDCRP", NULL, 9, false, NULL},            // Position
6387
43.1k
        {"PERDCRQ", NULL, 11, false, NULL},           // Galileo SAR
6388
43.1k
        {"PERDCRW", "TPS1", 8, false, NULL},          // Time
6389
43.1k
        {"PERDCRX", "TPS2", 12, false, NULL},         // PPS
6390
43.1k
        {"PERDCRY", "TPS3", 11, false, NULL},         // Position Mode
6391
43.1k
        {"PERDCRZ", "TPS4", 13, false, NULL},         // GCLK
6392
43.1k
        {"PERDMSG", NULL, 3, false, NULL},            // Message
6393
43.1k
        {"PERDSYS", "ANTSEL", 5, false, NULL},        // Antenna
6394
43.1k
        {"PERDSYS", "FIXSESSION", 5, false, NULL},    // Fix Session
6395
43.1k
        {"PERDSYS", "GPIO", 3, false, NULL},          // GPIO
6396
43.1k
        {"PERDSYS", "VERSION", 6, false, NULL},       // Version
6397
6398
        // Inertial Sense
6399
43.1k
        {"PGPSP", NULL, 18,  false, processPGPSP},     // GPS nav data
6400
6401
        // Jackson Labs proprietary
6402
43.1k
        {"PJLTS", NULL, 11,  false, NULL},            // GPSDO status
6403
43.1k
        {"PJLTV", NULL, 4,  false, NULL},             // Time and 3D velocity
6404
        // GPS-320FW -- $PLCS
6405
43.1k
        {"PMGNST", NULL, 8, false, processPMGNST},    // Magellan Status
6406
        // MediaTek proprietary, EOL.  Replaced by Airoha
6407
43.1k
        {"PMTK001", NULL, 3, false, processPMTK001},  // ACK/NAK
6408
43.1k
        {"PMTK010", NULL, 2, false, NULL},            // System Message
6409
43.1k
        {"PMTK011", NULL, 2, false, NULL},            // Text Message
6410
43.1k
        {"PMTK424", NULL, 3, false, processPMTK424},
6411
43.1k
        {"PMTK705", NULL, 4, false, processPMTK705},
6412
        // MediaTek/Trimble Satellite Channel Status
6413
43.1k
        {"PMTKCHN", NULL, 0, false, NULL},
6414
6415
        // MTK-3301 -- $POLYN
6416
6417
        // Quectel proprietary
6418
43.1k
        {"PQTMCFGEINSMSGERROR", NULL, 1, false, processPQxERR},      // Error
6419
43.1k
        {"PQTMCFGEINSMSGOK", NULL, 1, false, processPQxOK},          // OK
6420
43.1k
        {"PQTMCFGORIENTATIONERROR", NULL, 1, false, processPQxERR},  // Error
6421
43.1k
        {"PQTMCFGORIENTATION", NULL, 3, false, NULL},       // Orientation
6422
43.1k
        {"PQTMCFGORIENTATIONOK", NULL, 1, false, processPQxOK},      // OK
6423
43.1k
        {"PQTMCFGWHEELTICKERROR", NULL, 1, false, processPQxERR},    // Error
6424
43.1k
        {"PQTMCFGWHEELTICKOK", NULL, 1, false, processPQxOK},        // OK
6425
43.1k
        {"PQTMGPS", NULL, 14, false, processPQTMGPS},  // GPS Status
6426
43.1k
        {"PQTMIMU", NULL, 10, false, processPQTMIMU},  // IMU Raw Data
6427
43.1k
        {"PQTMINS", NULL, 11, false, processPQTMINS},  // INS Results
6428
43.1k
        {"PQTMQMPTERROR", NULL, 1, false, processPQxERR},       // Error
6429
43.1k
        {"PQTMQMPT", NULL, 2, false, NULL},            // Meters / tick
6430
43.1k
        {"PQTMVEHMSG", NULL, 2, false, NULL},          // Vehicle Info
6431
43.1k
        {"PQTMVER", NULL, 4, false, processPQTMVER},   // Firmware info
6432
6433
43.1k
        {"PQVERNO", NULL, 5, false, processPQVERNO},   // Version
6434
        // smart watch sensors, Yes: space!
6435
43.1k
        {"PRHS ", NULL, 2,  false, processPRHS},
6436
43.1k
        {"PRWIZCH", NULL, 0, false, NULL},          // Rockwell Channel Status
6437
43.1k
        {"PSRF140", NULL, 0, false, NULL},          // SiRF ephemeris
6438
43.1k
        {"PSRF150", NULL, 0, false, NULL},          // SiRF flow control
6439
43.1k
        {"PSRF151", NULL, 0, false, NULL},          // SiRF Power
6440
43.1k
        {"PSRF152", NULL, 0, false, NULL},          // SiRF ephemeris
6441
43.1k
        {"PSRF155", NULL, 0, false, NULL},          // SiRF proprietary
6442
43.1k
        {"PSRFEPE", NULL, 7, false, processPSRFEPE},  // SiRF Estimated Errors
6443
6444
        /* Serpentrio
6445
         * $PSSN,HRP  -- Heading Pitch, Roll
6446
         * $PSSN,RBD  -- Rover-Base Direction
6447
         * $PSSN,RBP  -- Rover-Base Position
6448
         * $PSSN,RBV  -- Rover-Base Velocity
6449
         * $PSSN,SNC  -- NTRIP Client Status
6450
         * $PSSN,TFM  -- RTCM coordinate transform
6451
         */
6452
43.1k
        {"PSSN", NULL, 0, false, NULL},          // $PSSN
6453
6454
        /*
6455
         * Skytraq sentences take this format:
6456
         * $PSTI,type[,val[,val]]*CS
6457
         * type is a 2 or 3 digit subsentence type
6458
         *
6459
         * Note: these sentences can be at least 105 chars long.
6460
         * That violates the NMEA 3.01 max of 82.
6461
         */
6462
        // 1 PPS Timing report ID
6463
43.1k
        {"PSTI", "000", 4, false, NULL},
6464
        // Active Antenna Status Report
6465
43.1k
        {"PSTI", "001", 2, false, NULL},
6466
        // GPIO 10 event-triggered time & position stamp.
6467
43.1k
        {"PSTI", "005", 2, false, NULL},
6468
        //  Recommended Minimum 3D GNSS Data
6469
43.1k
        {"PSTI", "030", 16, false, processPSTI030},
6470
        // RTK Baseline
6471
43.1k
        {"PSTI", "032", 16, false, processPSTI032},
6472
        // RTK RAW Measurement Monitoring Data
6473
43.1k
        {"PSTI", "033", 27, false,  processPSTI033},
6474
        // RTK Baseline Data of Rover Moving Base Receiver
6475
43.1k
        {"PSTI", "035", 8, false, processPSTI035},
6476
        // Heading, Pitch and Roll Messages of vehicle
6477
43.1k
        {"PSTI", "036", 2, false, processPSTI036},
6478
        // $PSTM ST Micro STA8088xx/STA8089xx/STA8090xx
6479
43.1k
        {"PSTM", NULL, 0, false, NULL},
6480
        // STM messages
6481
43.1k
        {"PSTMCPU", NULL, 4, false, processPSTMCPU},
6482
43.1k
        {"PSTMANTENNASTATUS", NULL, 4, false, processPSTMANTENNASTATUS},
6483
43.1k
        {"PSTMVER", NULL, 1, false, processPSTMVER},
6484
6485
        /* Kongsberg Seatex AS. Seapath 320
6486
         * $PSXN,20,horiz-qual,hgt-qual,head-qual,rp-qual*csum
6487
         * $PSXN,21,event*csum
6488
         * $PSXN,22,gyro-calib,gyro-offs*csum
6489
         * $PSXN,23,roll,pitch,head,heave*csum
6490
         * $PSXN,24,roll-rate,pitch-rate,yaw-rate,vertical-vel*csum
6491
         */
6492
43.1k
        {"PSXN", NULL, 0, false, NULL},
6493
43.1k
        {"PTFTTXT", NULL, 0, false, NULL},        // unknown uptime
6494
6495
        /* Trimble Proprietary
6496
         * $PTNL,AVR
6497
         * $PTNL,GGK
6498
         */
6499
43.1k
        {"PTNI", NULL, 0, false, NULL},
6500
6501
43.1k
        {"PTKM", NULL, 0, false, NULL},           // Robertson RGC12 Gyro
6502
43.1k
        {"PTNLRBA", NULL, 2, false, processPTNLRBA},  // Trimble/Ericsson antenna status
6503
43.1k
        {"PTNLRHVR", NULL, 0, false, NULL},       // Trimble Software Version
6504
43.1k
        {"PTNLRNM", NULL, 1, false, processPTNLRNM},  // Trimble receiver navigation mode
6505
43.1k
        {"PTNLRPT", NULL, 0, false, NULL},        // Trimble Serial Port COnfig
6506
43.1k
        {"PTNLRSVR", NULL, 0, false, NULL},       // Trimble Firmware Version
6507
43.1k
        {"PTNLRTP", NULL, 3, false, processPTNLRTP},  // Trimble/Ericsson temperature
6508
43.1k
        {"PTNLRXO", NULL, 2, false, processPTNLRXO},  // Trimble/Ericsson oscillator status
6509
43.1k
        {"PTNLRZD", NULL, 0, false, NULL},        // Extended Time and Date
6510
43.1k
        {"PTNTA", NULL, 8, false, processTNTA},
6511
43.1k
        {"PTNTHTM", NULL, 9, false, processTNTHTM},
6512
43.1k
        {"PUBX", NULL, 0, false, NULL},         // u-blox and Antaris
6513
43.1k
        {"QSM", NULL, 3, false, NULL},          // QZSS DC Report
6514
43.1k
        {"RBD", NULL, 0, false, NULL},       // Serpentrio rover-base direction
6515
43.1k
        {"RBP", NULL, 0, false, NULL},       // Serpentrio rover-base position
6516
43.1k
        {"RBV", NULL, 0, false, NULL},       // Serpentrio rover-base velocity
6517
43.1k
        {"RLM", NULL, 0, false, NULL},       // Return Link Message, NMEA 4.10+
6518
        // ignore Recommended Minimum Navigation Info, waypoint
6519
43.1k
        {"RMB", NULL, 0,  false, NULL},         // Recommended Min Nav Info
6520
43.1k
        {"RMC", NULL, 8,  false, processRMC},   // Recommended Minimum Data
6521
43.1k
        {"ROT", NULL, 3,  false, processROT},   // Rate of Turn
6522
43.1k
        {"RPM", NULL, 0,  false, NULL},         // ignore Revolutions
6523
43.1k
        {"RRT", NULL, 0, false, NULL},     // Report Route Transfer, NMEA 4.10+
6524
43.1k
        {"RSA", NULL, 0,  false, NULL},         // Rudder Sensor Angle
6525
43.1k
        {"RTE", NULL, 0,  false, NULL},         // ignore Routes
6526
        // UNICORE, Sensor Status invalid sender (SN)
6527
43.1k
        {"SNRSTAT", NULL, 5,  false, processSNRSTAT},
6528
43.1k
        {"SM1", NULL, 0, false, NULL},     // SafteyNET, All Ships, NMEA 4.10+
6529
43.1k
        {"SM2", NULL, 0, false, NULL},     // SafteyNET, Coastal, NMEA 4.10+
6530
43.1k
        {"SM3", NULL, 0, false, NULL},     // SafteyNET, Circular, NMEA 4.10+
6531
43.1k
        {"SM4", NULL, 0, false, NULL},     // SafteyNET, Rectangular, NMEA 4.10+
6532
43.1k
        {"SMB", NULL, 0, false, NULL},     // SafteyNET, Msg Body, NMEA 4.10+
6533
43.1k
        {"SPW", NULL, 0, false, NULL},     // Security Password, NMEA 4.10+
6534
43.1k
        {"SNC", NULL, 0, false, NULL},       // Serpentrio NTRIP client status
6535
43.1k
        {"STI", NULL, 2,  false, processSTI},   // $STI  Skytraq
6536
43.1k
        {"TFM", NULL, 0, false, NULL},          // Serpentrio Coord Transform
6537
43.1k
        {"THS", NULL, 0,  false, processTHS},   // True Heading and Status
6538
43.1k
        {"TRL", NULL, 0, false, NULL},     // AIS Xmit offline, NMEA 4.10+
6539
43.1k
        {"TXT", NULL, 5,  false, processTXT},
6540
43.1k
        {"TXTbase", NULL, 0,  false, NULL},     // RTCM 1029 TXT
6541
43.1k
        {"VBW", NULL, 0,  false, NULL},         // Dual Ground/Water Speed
6542
43.1k
        {"VDO", NULL, 0,  false, NULL},         // Own Vessel's Information
6543
43.1k
        {"VDR", NULL, 0,  false, NULL},         // Set and Drift
6544
43.1k
        {"VHW", NULL, 0,  false, NULL},         // Water Speed and Heading
6545
43.1k
        {"VLW", NULL, 0,  false, NULL},         // Dual ground/water distance
6546
43.1k
        {"VTG", NULL, 5,  false, processVTG},   // Course/speed over ground
6547
        // $APXDR, $HCXDR, Transducer measurements
6548
43.1k
        {"XDR", NULL, 5,  false, processXDR},
6549
43.1k
        {"XTE", NULL, 0,  false, NULL},         // Cross-Track Error
6550
43.1k
        {"ZDA", NULL ,4,  false, processZDA},   // Time and Date
6551
43.1k
        {NULL, NULL,  0,  false, NULL},         // no more
6552
43.1k
    };
6553
6554
43.1k
    unsigned count;
6555
43.1k
    gps_mask_t mask = 0;
6556
43.1k
    unsigned i, thistag = 0, lasttag;
6557
43.1k
    char *p, *e;
6558
43.1k
    volatile char *t;
6559
43.1k
    char ts_buf1[TIMESPEC_LEN];
6560
43.1k
    char ts_buf2[TIMESPEC_LEN];
6561
43.1k
    bool skytraq_sti = false;
6562
43.1k
    size_t mlen;
6563
6564
    /*
6565
     * We've had reports that on the Garmin GPS-10 the device sometimes
6566
     * (1:1000 or so) sends garbage packets that have a valid checksum
6567
     * but are like 2 successive NMEA packets merged together in one
6568
     * with some fields lost.  Usually these are much longer than the
6569
     * legal limit for NMEA, so we can cope by just tossing out overlong
6570
     * packets.  This may be a generic bug of all Garmin chipsets.
6571
     */
6572
    // codacy does not like strlen()
6573
43.1k
    mlen = strnlen(sentence, NMEA_MAX + 1);
6574
43.1k
    if (NMEA_MAX < mlen) {
6575
74
        GPSD_LOG(LOG_WARN, &session->context->errout,
6576
74
                 "NMEA0183: Overlong packet of %zd+ chars rejected.\n",
6577
74
                 mlen);
6578
74
        return ONLINE_SET;
6579
74
    }
6580
6581
    // make an editable copy of the sentence
6582
43.0k
    (void)strlcpy((char *)session->nmea.fieldcopy, sentence,
6583
43.0k
                  sizeof(session->nmea.fieldcopy) - 1);
6584
    // discard the checksum part
6585
43.0k
    for (p = (char *)session->nmea.fieldcopy;
6586
1.65M
         ('*' != *p) && (' ' <= *p);) {
6587
1.60M
        ++p;
6588
1.60M
    }
6589
43.0k
    if ('*' == *p) {
6590
37.5k
        *p++ = ',';             // otherwise we drop the last field
6591
37.5k
    }
6592
#ifdef SKYTRAQ_ENABLE_UNUSED
6593
    // $STI is special, no trailing *, or chacksum
6594
    if (0 != strncmp( "STI,", sentence, 4)) {
6595
        skytraq_sti = true;
6596
        *p++ = ',';             // otherwise we drop the last field
6597
    }
6598
#endif
6599
43.0k
    *p = '\0';
6600
43.0k
    e = p;
6601
6602
    // split sentence copy on commas, filling the field array
6603
43.0k
    count = 0;
6604
43.0k
    t = p;                      // end of sentence
6605
43.0k
    p = (char *)session->nmea.fieldcopy + 1;  // beginning of tag, 'G' not '$'
6606
    // while there is a search string and we haven't run off the buffer...
6607
542k
    while ((NULL != p) &&
6608
500k
           (p <= t)) {
6609
499k
        session->nmea.field[count] = p;      // we have a field. record it
6610
499k
        if (NULL != (p = strchr(p, ','))) {  // search for the next delimiter
6611
457k
            *p = '\0';                       // replace it with a NUL
6612
457k
            count++;                         // bump the counters and continue
6613
457k
            p++;
6614
457k
            if (NMEA_MAX_FLD <= count) {
6615
                // ensure no overflow
6616
68
                break;
6617
68
            }
6618
457k
        }
6619
499k
    }
6620
43.0k
    if (NMEA_MAX_FLD < count) {
6621
        // ensure no overflow, OSS Fuzz 515100083
6622
0
        count = NMEA_MAX_FLD;
6623
0
    }
6624
6625
    // point remaining fields at empty string, just in case
6626
3.89M
    for (i = count; i < NMEA_MAX_FLD; i++) {
6627
3.84M
        session->nmea.field[i] = e;
6628
3.84M
    }
6629
6630
    // sentences handlers will tell us when they have fractional time
6631
43.0k
    session->nmea.latch_frac_time = false;
6632
    // GSA and GSV will set this if more in that series to come.
6633
43.0k
    session->nmea.gsx_more = false;
6634
6635
#ifdef __UNUSED
6636
    // debug
6637
    GPSD_LOG(0, &session->context->errout,
6638
             "NMEA0183: got %s\n", session->nmea.field[0]);
6639
#endif // __UNUSED
6640
6641
    // dispatch on field zero, the sentence tag
6642
3.86M
    for (i = 0; i < NMEA_NUM; ++i) {
6643
3.86M
        char *s = session->nmea.field[0];
6644
6645
        // CODACY #350416, wants explicit numeric end check
6646
3.86M
        if ((NMEA_NUM - 1) <= i ||
6647
3.86M
            NULL == nmea_phrase[i].name) {
6648
4.35k
            mask = ONLINE_SET;
6649
4.35k
            GPSD_LOG(LOG_DATA, &session->context->errout,
6650
4.35k
                     "NMEA0183: Unknown sentence type %s\n",
6651
4.35k
                     session->nmea.field[0]);
6652
4.35k
            break;
6653
4.35k
        }
6654
        // strnlen() to shut up codacy
6655
3.86M
        if (3 == strnlen(nmea_phrase[i].name, 4) &&
6656
1.56M
            !skytraq_sti) {
6657
            // $STI is special
6658
1.56M
            s += 2;             // skip talker ID
6659
1.56M
        }
6660
3.86M
        if (0 != strcmp(nmea_phrase[i].name, s)) {
6661
            // no match
6662
3.80M
            continue;
6663
3.80M
        }
6664
57.1k
        if (NULL != nmea_phrase[i].name1 &&
6665
22.0k
            0 != strcmp(nmea_phrase[i].name1, session->nmea.field[1])) {
6666
            // no match on field 2.  As in $PSTI,030,
6667
18.4k
            continue;
6668
18.4k
        }
6669
        // got a match
6670
38.7k
        if (NULL == nmea_phrase[i].decoder) {
6671
            // no decoder for this sentence
6672
609
            mask = ONLINE_SET;
6673
609
            GPSD_LOG(LOG_DATA, &session->context->errout,
6674
609
                     "NMEA0183: No decoder for sentence type %s\n",
6675
609
                     session->nmea.field[0]);
6676
609
            break;
6677
609
        }
6678
38.0k
        if (count < (unsigned)nmea_phrase[i].nf) {
6679
            // sentence too short
6680
515
            mask = ONLINE_SET;
6681
515
            GPSD_LOG(LOG_DATA, &session->context->errout,
6682
515
                     "NMEA0183: Sentence %s too short\n",
6683
515
                     session->nmea.field[0]);
6684
515
            break;
6685
515
        }
6686
37.5k
        mask = (nmea_phrase[i].decoder)(count, session->nmea.field,
6687
37.5k
                                        session);
6688
37.5k
        session->nmea.cycle_continue = nmea_phrase[i].cycle_continue;
6689
        /*
6690
         * Must force this to be nz, as we're going to rely on a zero
6691
         * value to mean "no previous tag" later.
6692
         */
6693
        // FIXME: this fails on Skytrak, $PSTI,xx, many different xx
6694
37.5k
        thistag = i + 1;
6695
37.5k
        break;
6696
38.0k
    }
6697
6698
    // prevent overaccumulation of sat reports
6699
43.0k
    if (!str_starts_with(session->nmea.field[0] + 2, "GSV")) {
6700
        // This assumes all $xxGSV are contiguous.
6701
37.4k
        if (0 != session->nmea.last_gsv_talker) {
6702
838
            session->nmea.end_gsv_talker = session->nmea.last_gsv_talker;
6703
838
        }
6704
37.4k
        session->nmea.last_gsv_talker = '\0';
6705
37.4k
    }
6706
43.0k
    if (!str_starts_with(session->nmea.field[0] + 2, "GSA")) {
6707
40.6k
        session->nmea.last_gsa_talker = '\0';
6708
40.6k
    }
6709
6710
    // timestamp recording for fixes happens here
6711
43.0k
    if (0 != (mask & TIME_SET)) {
6712
4.89k
        if (0 == session->nmea.date.tm_year &&
6713
305
            0 == session->nmea.date.tm_mday) {
6714
            // special case to time zero
6715
305
            session->newdata.time = (timespec_t){0, 0};
6716
4.58k
        } else {
6717
4.58k
            session->newdata.time = gpsd_utc_resolve(session);
6718
4.58k
        }
6719
6720
4.89k
        GPSD_LOG(LOG_DATA, &session->context->errout,
6721
4.89k
                 "NMEA0183: %s newtime is %s = "
6722
4.89k
                 "%d-%02d-%02dT%02d:%02d:%02d.%03ldZ\n",
6723
4.89k
                 session->nmea.field[0],
6724
4.89k
                 timespec_str(&session->newdata.time, ts_buf1, sizeof(ts_buf1)),
6725
4.89k
                 1900 + session->nmea.date.tm_year,
6726
4.89k
                 session->nmea.date.tm_mon + 1,
6727
4.89k
                 session->nmea.date.tm_mday,
6728
4.89k
                 session->nmea.date.tm_hour,
6729
4.89k
                 session->nmea.date.tm_min,
6730
4.89k
                 session->nmea.date.tm_sec,
6731
4.89k
                 session->nmea.subseconds.tv_nsec / 1000000L);
6732
        /*
6733
         * If we have time and PPS is available, assume we have good time.
6734
         * Because this is a generic driver we don't really have enough
6735
         * information for a sharper test, so we'll leave it up to the
6736
         * PPS code to do its own sanity filtering.
6737
         */
6738
4.89k
        mask |= NTPTIME_IS;
6739
4.89k
    }
6740
6741
    /*
6742
     * The end-of-cycle detector.  This code depends on just one
6743
     * assumption: if a sentence with a timestamp occurs just before
6744
     * start of cycle, then it is always good to trigger a report on
6745
     * that sentence in the future.  For devices with a fixed cycle
6746
     * this should work perfectly, locking in detection after one
6747
     * cycle.  Most split-cycle devices (Garmin 48, for example) will
6748
     * work fine.  Problems will only arise if a a sentence that
6749
     * occurs just before timestamp increments also occurs in
6750
     * mid-cycle, as in the Garmin eXplorist 210; those might jitter.
6751
     */
6752
43.0k
    GPSD_LOG(LOG_DATA, &session->context->errout,
6753
43.0k
             "NMEA0183: %s time %s last %s latch %d cont %d\n",
6754
43.0k
             session->nmea.field[0],
6755
43.0k
             timespec_str(&session->nmea.this_frac_time, ts_buf1,
6756
43.0k
                          sizeof(ts_buf1)),
6757
43.0k
             timespec_str(&session->nmea.last_frac_time, ts_buf2,
6758
43.0k
                          sizeof(ts_buf2)),
6759
43.0k
             session->nmea.latch_frac_time,
6760
43.0k
             session->nmea.cycle_continue);
6761
43.0k
    lasttag = session->nmea.lasttag;
6762
43.0k
    if (session->nmea.gsx_more) {
6763
        // more to come, so ignore for cycle ender
6764
        // appears that GSA and GSV never start a cycle.
6765
40.8k
    } else if (session->nmea.latch_frac_time) {
6766
4.28k
        timespec_t ts_delta;
6767
4.28k
        TS_SUB(&ts_delta, &session->nmea.this_frac_time,
6768
4.28k
                          &session->nmea.last_frac_time);
6769
4.28k
        if (0.01 < fabs(TSTONS(&ts_delta))) {
6770
            // time changed
6771
1.51k
            mask |= CLEAR_IS;
6772
1.51k
            GPSD_LOG(LOG_PROG, &session->context->errout,
6773
1.51k
                     "NMEA0183: %s starts a reporting cycle. lasttag %d\n",
6774
1.51k
                     session->nmea.field[0], lasttag);
6775
            /*
6776
             * Have we seen a previously timestamped NMEA tag?
6777
             * If so, designate as end-of-cycle marker.
6778
             * But not if there are continuation sentences;
6779
             * those get sorted after the last timestamped sentence
6780
             *
6781
             */
6782
1.51k
            if (0 < lasttag &&
6783
1.51k
                false == (session->nmea.cycle_enders[lasttag]) &&
6784
236
                !session->nmea.cycle_continue) {
6785
124
                session->nmea.cycle_enders[lasttag] = true;
6786
                // we might have a (somewhat) reliable end-of-cycle
6787
124
                session->cycle_end_reliable = true;
6788
124
                GPSD_LOG(LOG_PROG, &session->context->errout,
6789
124
                         "NMEA0183: tagged %s as a cycle ender. %u\n",
6790
124
                         nmea_phrase[lasttag - 1].name,
6791
124
                         lasttag);
6792
124
            }
6793
1.51k
        }
6794
36.6k
    } else {
6795
        // ignore multiple sequential, like GSV, GSA
6796
        // extend the cycle to an un-timestamped sentence?
6797
36.6k
        if (true == session->nmea.cycle_enders[lasttag]) {
6798
27.7k
            GPSD_LOG(LOG_PROG, &session->context->errout,
6799
27.7k
                     "NMEA0183: %s is just after a cycle ender. (%s)\n",
6800
27.7k
                     session->nmea.field[0],
6801
27.7k
                     gps_maskdump(mask));
6802
27.7k
            if (0 != (mask & ~ONLINE_SET)) {
6803
                // new data... after cycle ender
6804
10.6k
                mask |= REPORT_IS;
6805
10.6k
            }
6806
27.7k
        }
6807
36.6k
        if (session->nmea.cycle_continue) {
6808
1.12k
            GPSD_LOG(LOG_PROG, &session->context->errout,
6809
1.12k
                     "NMEA0183: %s extends the reporting cycle.\n",
6810
1.12k
                     session->nmea.field[0]);
6811
            // change ender
6812
1.12k
            session->nmea.cycle_enders[lasttag] = false;
6813
1.12k
            session->nmea.cycle_enders[thistag] = true;
6814
            // have a cycle ender
6815
1.12k
            session->cycle_end_reliable = true;
6816
1.12k
        }
6817
36.6k
    }
6818
6819
    // here's where we check for end-of-cycle
6820
43.0k
    if ((session->nmea.latch_frac_time ||
6821
38.7k
         session->nmea.cycle_continue) &&
6822
5.41k
        (true == session->nmea.cycle_enders[thistag]) &&
6823
5.07k
        !session->nmea.gsx_more) {
6824
5.07k
        if (NULL == nmea_phrase[i].name1) {
6825
4.81k
            GPSD_LOG(LOG_PROG, &session->context->errout,
6826
4.81k
                     "NMEA0183: %s ends a reporting cycle.\n",
6827
4.81k
                     session->nmea.field[0]);
6828
4.81k
        } else {
6829
256
            GPSD_LOG(LOG_PROG, &session->context->errout,
6830
256
                     "NMEA0183: %s,%s ends a reporting cycle.\n",
6831
256
                     session->nmea.field[0],
6832
256
                     session->nmea.field[1]);
6833
256
        }
6834
5.07k
        mask |= REPORT_IS;
6835
5.07k
    }
6836
43.0k
    if (session->nmea.latch_frac_time) {
6837
4.28k
        session->nmea.lasttag = thistag;
6838
4.28k
    }
6839
6840
    /* don't downgrade mode if holding previous fix
6841
     * usually because of xxRMC which does not report 2D/3D */
6842
43.0k
    if (MODE_SET == (mask & MODE_SET) &&
6843
8.58k
        MODE_3D == session->gpsdata.fix.mode &&
6844
3.00k
        MODE_NO_FIX != session->newdata.mode &&
6845
2.87k
        (0 != isfinite(session->lastfix.altHAE) ||
6846
1.80k
         0 != isfinite(session->oldfix.altHAE) ||
6847
905
         0 != isfinite(session->lastfix.altMSL) ||
6848
2.23k
         0 != isfinite(session->oldfix.altMSL))) {
6849
2.23k
        session->newdata.mode = session->gpsdata.fix.mode;
6850
2.23k
    }
6851
43.0k
    return mask;
6852
43.1k
}
6853
6854
6855
/* add NMEA checksum to a possibly terminated sentence
6856
 * if \0 terminated adds exactly 5 chars: "*XX\n\n"
6857
 * if *\0 terminated adds exactly 4 chars: "XX\n\n"
6858
 */
6859
void nmea_add_checksum(char *sentence)
6860
11.2k
{
6861
11.2k
    unsigned char sum = '\0';
6862
11.2k
    char c, *p = sentence;
6863
6864
11.2k
    if ('$' == *p ||
6865
11.2k
        '!' == *p) {
6866
11.2k
        p++;
6867
11.2k
    }
6868
158k
    while (('*' != (c = *p)) &&
6869
147k
           ('\0' != c)) {
6870
147k
        sum ^= c;
6871
147k
        p++;
6872
147k
    }
6873
11.2k
    (void)snprintf(p, 6, "*%02X\r\n", (unsigned)sum);
6874
11.2k
}
6875
6876
// ship a command to the GPS, adding * and correct checksum
6877
ssize_t nmea_write(struct gps_device_t *session, char *buf, size_t len UNUSED)
6878
11.9k
{
6879
11.9k
    (void)strlcpy(session->msgbuf, buf, sizeof(session->msgbuf));
6880
11.9k
    if ('$' == session->msgbuf[0]) {
6881
11.2k
        (void)strlcat(session->msgbuf, "*", sizeof(session->msgbuf));
6882
11.2k
        nmea_add_checksum(session->msgbuf);
6883
11.2k
    } else {
6884
760
        (void)strlcat(session->msgbuf, "\r\n", sizeof(session->msgbuf));
6885
760
    }
6886
    // codacy hates strlen()
6887
11.9k
    session->msgbuflen = strnlen(session->msgbuf, sizeof(session->msgbuf));
6888
11.9k
    return gpsd_write(session, session->msgbuf, session->msgbuflen);
6889
11.9k
}
6890
6891
ssize_t nmea_send(struct gps_device_t * session, const char *fmt, ...)
6892
11.9k
{
6893
11.9k
    char buf[BUFSIZ];
6894
11.9k
    va_list ap;
6895
6896
11.9k
    va_start(ap, fmt);
6897
11.9k
    (void)vsnprintf(buf, sizeof(buf) - 5, fmt, ap);
6898
11.9k
    va_end(ap);
6899
    // codacy hates strlen()
6900
11.9k
    return nmea_write(session, buf, strnlen(buf, sizeof(buf)));
6901
11.9k
}
6902
6903
// vim: set expandtab shiftwidth=4