Coverage Report

Created: 2026-08-13 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gpsd/gpsd-3.27.6~dev/drivers/driver_navcom.c
Line
Count
Source
1
/*
2
 * Driver for Navcom receivers using proprietary NCT messages,
3
 * a binary protocol.
4
 *
5
 * Vendor website: http://www.navcomtech.com/
6
 * Technical references: Technical Reference Manual P/N 96-3120001-3001
7
 *
8
 * Tested with two SF-2040G models
9
 *
10
 * At this stage, this driver implements the following commands:
11
 *
12
 * 0x20: Data Request (tell the unit which responses you want)
13
 * 0x3f: LED Configuration (controls the front panel LEDs -- for testing)
14
 * 0x1c: Test Support Block (again, blinks the front panel lights)
15
 *
16
 * and it understands the following responses:
17
 *
18
 * 0x06: Acknowledgement (without error)
19
 * 0x15: Negative Acknowledge
20
 * 0x86: Channel Status
21
 * 0xae: Identification Block
22
 * 0xb0: Raw Meas. Data Block
23
 * 0xb1: PVT Block
24
 * 0xb5: Pseudorange Noise Statistics
25
 * 0xd3: LBM DSP Status Block
26
 * 0xef: Clock Drift and Offset
27
 *
28
 * By Diego Berge. Contact via web form at http://www.navlost.eu/contact
29
 *
30
 * Week counters are not limited to 10 bits. It's unknown what
31
 * the firmware is doing to disambiguate them, if anything; it might just
32
 * be adding a fixed offset based on a hidden epoch value, in which case
33
 * unhappy things will occur on the next rollover.
34
 *
35
 * This file is Copyright 2010 by the GPSD project
36
 * SPDX-License-Identifier: BSD-2-clause
37
 */
38
39
#include "../include/gpsd_config.h"  // must be before all includes
40
41
#include <math.h>
42
#include <stdbool.h>
43
#include <stdio.h>
44
#include <string.h>
45
46
#include "../include/gpsd.h"
47
48
#if defined(NAVCOM_ENABLE)
49
#include "../include/bits.h"
50
#include "../include/timespec.h"
51
52
// Have data which is 24 bits long
53
12.3k
#define getles3224(buf,off)  (int32_t)(((uint32_t)getub((buf), (off)+2)<<24 | (uint32_t)getub((buf), (off)+1)<<16 | (uint32_t)getub((buf), (off))<<8)>>8)
54
#define getleu3224(buf,off) (uint32_t)(((uint32_t)getub((buf), (off)+2)<<24 | (uint32_t)getub((buf), (off)+1)<<16 | (uint32_t)getub((buf), (off))<<8)>>8)
55
56
/* And just to be difficult, Navcom is little endian but the GPS data stream
57
   is big endian.  Some messages contain raw GPS data */
58
900
#define getles16_be(buf, off)   (int16_t)((((uint16_t)getub(buf, (off)) << 8) \
59
900
                                    | (uint16_t)getub(buf, (off)+1)))
60
1.50k
#define getleu16_be(buf, off)   (uint16_t)((((uint16_t)getub(buf, (off)) << 8) \
61
1.50k
                                    | (uint16_t)getub(buf, (off)+1)))
62
400
#define getles32_be(buf, off)   (int32_t)((((uint16_t)getleu16_be(buf, (off)) << 16) \
63
400
                                    | getleu16_be(buf, (off)+2)))
64
200
#define getleu32_be(buf, off)   (uint32_t)((((uint16_t)getleu16_be(buf, (off)) << 16) \
65
200
                                    | getleu16_be(buf, (off)+2)))
66
200
#define getles3224_be(buf,off)     (int32_t)(((uint32_t)getub((buf), (off))<<24 \
67
200
                                    | (uint32_t)getub((buf), (off)+1)<<16 \
68
200
                                    | (uint32_t)getub((buf), (off)+2)<<8)>>8)
69
70
#define NAVCOM_CHANNELS 12
71
72
static uint8_t checksum(unsigned char *buf, size_t len)
73
2.75k
{
74
2.75k
    size_t n;
75
2.75k
    uint8_t csum = (uint8_t)0x00;
76
36.0k
    for (n = 0; n < len; n++) {
77
33.2k
        csum ^= buf[n];
78
33.2k
    }
79
2.75k
    return csum;
80
2.75k
}
81
82
static bool navcom_send_cmd(struct gps_device_t *session, unsigned char *cmd,
83
                            size_t len)
84
2.75k
{
85
2.75k
    return (gpsd_write(session, (const char *)cmd, len) == (ssize_t) len);
86
2.75k
}
87
88
// Data Request
89
static void navcom_cmd_0x20(struct gps_device_t *session, uint8_t block_id,
90
                            uint16_t rate)
91
2.33k
{
92
2.33k
    unsigned char msg[18];
93
2.33k
    putbyte(msg, 0, 0x02);
94
2.33k
    putbyte(msg, 1, 0x99);
95
2.33k
    putbyte(msg, 2, 0x66);
96
2.33k
    putbyte(msg, 3, 0x20);      // Cmd ID
97
2.33k
    putle16(msg, 4, 0x000e);    // Length
98
2.33k
    putbyte(msg, 6, 0x00);      // Action
99
2.33k
    putbyte(msg, 7, 0x01);      // Count of blocks
100
2.33k
    putbyte(msg, 8, block_id);  // Data Block ID
101
2.33k
    putbyte(msg, 9, 0x02);      // Logical Ports
102
2.33k
    putle16(msg, 10, rate);     // Data rate
103
2.33k
    putbyte(msg, 12, 0x71);
104
2.33k
    putbyte(msg, 13, 0x00);
105
2.33k
    putle16(msg, 14, 0x0000);
106
2.33k
    putbyte(msg, 16, checksum(msg + 3, 13));
107
2.33k
    putbyte(msg, 17, 0x03);
108
2.33k
    (void)navcom_send_cmd(session, msg, 18);
109
2.33k
    GPSD_LOG(LOG_PROG, &session->context->errout,
110
2.33k
             "Navcom: sent command 0x20 (Data Request) "
111
2.33k
             "- data block id = %02x at rate %02x\n", block_id, rate);
112
2.33k
}
113
114
// cppcheck-suppress unusedFunction
115
static void UNUSED navcom_cmd_0x3f(struct gps_device_t *session)
116
// Changes the LED settings in the receiver
117
0
{
118
0
    unsigned char msg[12];
119
0
    putbyte(msg, 0, 0x02);
120
0
    putbyte(msg, 1, 0x99);
121
0
    putbyte(msg, 2, 0x66);
122
0
    putbyte(msg, 3, 0x3f);      // Cmd ID
123
0
    putle16(msg, 4, 0x0008);
124
0
    putbyte(msg, 6, 0x01);      // Action
125
0
    putbyte(msg, 7, 0x00);      // Reserved
126
0
    putbyte(msg, 8, 0x02);      // Link LED setting
127
0
    putbyte(msg, 9, 0x0a);      // Battery LED setting
128
0
    putbyte(msg, 10, checksum(msg + 3, 7));
129
0
    putbyte(msg, 11, 0x03);
130
0
    (void)navcom_send_cmd(session, msg, 12);
131
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
132
0
             "Navcom: sent command 0x3f (LED Configuration Block)\n");
133
0
}
134
135
// Test Support Block - Blinks the LEDs
136
static void navcom_cmd_0x1c(struct gps_device_t *session, uint8_t mode,
137
                            uint8_t length)
138
424
{
139
424
    unsigned char msg[12];
140
424
    putbyte(msg, 0, 0x02);
141
424
    putbyte(msg, 1, 0x99);
142
424
    putbyte(msg, 2, 0x66);
143
424
    putbyte(msg, 3, 0x1c);      // Cmd ID
144
424
    putle16(msg, 4, 0x0008);
145
424
    putbyte(msg, 6, 0x04);      // Use ACK/NAK
146
424
    putbyte(msg, 7, mode);      // 0x01 or 0x02
147
424
    putbyte(msg, 8, length);    // Only if mode == 0x01
148
424
    putbyte(msg, 9, 0x00);
149
424
    putbyte(msg, 10, checksum(msg + 3, 7));
150
424
    putbyte(msg, 11, 0x03);
151
424
    (void)navcom_send_cmd(session, msg, 12);
152
424
    GPSD_LOG(LOG_PROG, &session->context->errout,
153
424
             "Navcom: sent command 0x1c (Test Support Block)\n");
154
424
    GPSD_LOG(LOG_DATA, &session->context->errout,
155
424
             "Navcom: command 0x1c mode = %02x, length = %u\n",
156
424
             mode, length);
157
424
}
158
159
// Serial Port Configuration
160
static void navcom_cmd_0x11(struct gps_device_t *session,
161
                            uint8_t port_selection)
162
0
{
163
    /* NOTE - We only allow changing one port at a time,
164
     * although the message supports doing both at once. */
165
0
    unsigned char msg[12];
166
0
    putbyte(msg, 0, 0x02);
167
0
    putbyte(msg, 1, 0x99);
168
0
    putbyte(msg, 2, 0x66);
169
0
    putbyte(msg, 3, 0x11);      // Cmd ID
170
0
    putle16(msg, 4, 0x0008);    // Length
171
0
    putbyte(msg, 6, 0x04);      // Action - Use ACK/NAK)
172
0
    putbyte(msg, 7, port_selection);
173
0
    putbyte(msg, 8, 0x00);      // Reserved
174
0
    putbyte(msg, 9, 0x00);      // Reserved
175
0
    putbyte(msg, 10, checksum(msg + 3, 7));
176
0
    putbyte(msg, 11, 0x03);
177
0
    (void)navcom_send_cmd(session, msg, 12);
178
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
179
0
             "Navcom: sent command 0x11 (Serial Port Configuration)\n");
180
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
181
0
             "Navcom: serial port selection: 0x%02x\n", port_selection);
182
0
}
183
184
static void navcom_event_hook(struct gps_device_t *session, event_t event)
185
11.3k
{
186
11.3k
    if (session->context->readonly) {
187
4.51k
        return;
188
4.51k
    }
189
190
    // Request the following messages:
191
6.86k
    if (event == EVENT_IDENTIFIED) {
192
        /* NOTE - Channel Status allows us to know into which of the
193
         * unit's various serial ports we are connected.
194
         * Its value gets updated every time we receive a 0x06 (Ack)
195
         * message.  Note that if commands are being fed into the
196
         * unit from more than one port (which is entirely possible
197
         * although not necessarily a bright idea), there is a good
198
         * chance that we might misidentify our port */
199
212
        navcom_cmd_0x1c(session, 0x02, 0);      // Test Support Block
200
212
        navcom_cmd_0x20(session, 0xae, 0x0000); // Identification Block
201
212
        navcom_cmd_0x20(session, 0x86, 0x000a); // Channel Status
202
212
        navcom_cmd_0x1c(session, 0x01, 5);      // Blink LEDs on receiver
203
        // Identification Block - send every 10 min
204
212
        navcom_cmd_0x20(session, 0xae, 0x1770);
205
212
        navcom_cmd_0x20(session, 0xb1, 0x4000); // PVT Block
206
        // Pseudorange Noise Statistics - send every 20s
207
212
        navcom_cmd_0x20(session, 0xb5, 0x00c8);
208
212
        navcom_cmd_0x20(session, 0xb0, 0x4000); // Raw Meas Data Block
209
        // Packed Ephemeris Data - send once
210
212
        navcom_cmd_0x20(session, 0x81, 0x0000);
211
212
        navcom_cmd_0x20(session, 0x81, 0x4000); // Packed Ephemeris Data
212
212
        navcom_cmd_0x20(session, 0x86, 0x4000); // Channel Status
213
212
        navcom_cmd_0x20(session, 0x83, 0x4000); // Ionosphere and UTC Data
214
        // Clock Drift - send every 5 min
215
212
        navcom_cmd_0x20(session, 0xef, 0x0bb8);
216
212
    }
217
6.86k
}
218
219
// Ionosphere and UTC Data
220
static gps_mask_t handle_0x83(struct gps_device_t *session)
221
169
{
222
    /* NOTE - At the present moment this is only being used
223
     * for determining the GPS-UTC time difference,
224
     * for which the iono data is not needed as far
225
     * as we are concerned.  However, I am still
226
     * reporting it (if debuglevel >= LOG_DATA) as a
227
     * matter of interest */
228
// 2^-30
229
169
#define SF_A0 (0.000000000931322574615478515625)
230
// 2^-50
231
169
#define SF_A1 (0.000000000000000888178419700125)
232
// 2^12
233
169
#define SF_TOT (4096)
234
// 2^-30
235
169
#define SF_ALPHA0 (0.000000000931322574615478515625)
236
// 2^-27
237
169
#define SF_ALPHA1 (0.000000007450580596923828125)
238
// 2^-24
239
169
#define SF_ALPHA2 (0.000000059604644775390625)
240
// 2^-24
241
169
#define SF_ALPHA3 (0.000000059604644775390625)
242
// 2^11
243
169
#define SF_BETA0 (2048)
244
// 2^14
245
169
#define SF_BETA1 (16384)
246
// 2^16
247
169
#define SF_BETA2 (65536)
248
// 2^16
249
169
#define SF_BETA3 (65536)
250
169
    unsigned char *buf = session->lexer.outbuffer + 3;
251
169
    uint16_t week = getleu16(buf, 3);
252
169
    uint32_t tow = getleu32(buf, 5);
253
169
    int8_t alpha0 = getsb(buf, 9);
254
169
    int8_t alpha1 = getsb(buf, 10);
255
169
    int8_t alpha2 = getsb(buf, 11);
256
169
    int8_t alpha3 = getsb(buf, 12);
257
169
    int8_t beta0 = getsb(buf, 13);
258
169
    int8_t beta1 = getsb(buf, 14);
259
169
    int8_t beta2 = getsb(buf, 15);
260
169
    int8_t beta3 = getsb(buf, 16);
261
169
    int32_t a1 = getles32(buf, 17);
262
169
    int32_t a0 = getles32(buf, 21);
263
169
    uint8_t tot = getub(buf, 25);
264
169
    uint8_t wnt = getub(buf, 26);
265
169
    int8_t dtls = getsb(buf, 27);
266
169
    uint8_t wnlsf = getub(buf, 28);
267
169
    uint8_t dn = getub(buf, 29);
268
169
    int8_t dtlsf = getsb(buf, 30);
269
270
    // Ref.: ICD-GPS-200C 20.3.3.5.2.4
271
169
    if ((week % 256) * 604800 + tow / 1000.0 < wnlsf * 604800 + dn * 86400) {
272
        // Effectivity time is in the future, use dtls
273
84
        session->context->leap_seconds = (int)dtls;
274
85
    } else {
275
        // Effectivity time is not in the future, use dtlsf
276
85
        session->context->leap_seconds = (int)dtlsf;
277
85
    }
278
279
169
    GPSD_LOG(LOG_PROG, &session->context->errout,
280
169
             "Navcom: received packet type 0x83 (Ionosphere and UTC Data)\n");
281
169
    GPSD_LOG(LOG_DATA, &session->context->errout,
282
169
             "Navcom: Scaled parameters follow:\n");
283
169
    GPSD_LOG(LOG_DATA, &session->context->errout,
284
169
             "Navcom: GPS Week: %u, GPS Time of Week: %u (GPS Time: %f)\n",
285
169
             week, tow, week * 604800 + tow / 1000.0);
286
169
    GPSD_LOG(LOG_DATA, &session->context->errout,
287
169
             "Navcom: a0: %12.4E, a1: %12.4E, a2: %12.4E, a3: %12.4E, "
288
169
             "b0: %12.4E, b1: %12.4E, b2: %12.4E, b3: %12.4E\n",
289
169
             (double)alpha0 * SF_ALPHA0, (double)alpha1 * SF_ALPHA1,
290
169
             (double)alpha2 * SF_ALPHA2, (double)alpha3 * SF_ALPHA3,
291
169
             (double)beta0 * SF_BETA0, (double)beta1 * SF_BETA1,
292
169
             (double)beta2 * SF_BETA2, (double)beta3 * SF_BETA3);
293
169
    GPSD_LOG(LOG_DATA, &session->context->errout,
294
169
             "Navcom: A0: %19.12E, A1: %19.12E\n", (double)a0 * SF_A0,
295
169
             (double)a1 * SF_A1);
296
169
    GPSD_LOG(LOG_DATA, &session->context->errout,
297
169
             "Navcom: UTC Ref. Time: %lu, UTC Ref. Week: %u, dTls: %d\n",
298
169
             (unsigned long)tot * SF_TOT, wnt, dtls);
299
169
    GPSD_LOG(LOG_DATA, &session->context->errout,
300
169
             "Navcom: Week of leap seconds: %u, Day number of leap seconds: %u, dTlsf: %d\n",
301
169
             wnlsf, dn, dtlsf);
302
303
169
    return 0;     // No flag for update of leap seconds (Not part of a fix)
304
305
169
#undef SF_A0
306
169
#undef SF_A1
307
169
#undef SF_TOT
308
169
#undef SF_ALPHA0
309
169
#undef SF_ALPHA1
310
169
#undef SF_ALPHA2
311
169
#undef SF_ALPHA3
312
169
#undef SF_BETA0
313
169
#undef SF_BETA1
314
169
#undef SF_BETA2
315
169
#undef SF_BETA3
316
169
}
317
318
// Acknowledgement (without error)
319
static gps_mask_t handle_0x06(struct gps_device_t *session)
320
153
{
321
153
    unsigned char *buf = session->lexer.outbuffer + 3;
322
153
    uint8_t cmd_id = getub(buf, 3);
323
153
    uint8_t port = getub(buf, 4);
324
325
    // This tells us which serial port was used last
326
153
    session->driver.navcom.physical_port = port;
327
153
    GPSD_LOG(LOG_PROG, &session->context->errout,
328
153
             "Navcom: received packet type 0x06 (Acknowledgement (without "
329
153
             "error))\n");
330
153
    GPSD_LOG(LOG_DATA, &session->context->errout,
331
153
             "Navcom: acknowledged command id 0x%02x on port %c\n",
332
153
             cmd_id, (port == 0 ? 'A' : (port == 1 ? 'B' : '?')));
333
153
    return 0;                   // Nothing updated
334
153
}
335
336
// Negative Acknowledge*/
337
static gps_mask_t handle_0x15(struct gps_device_t *session)
338
546
{
339
546
    unsigned n;
340
546
    unsigned char *buf = session->lexer.outbuffer + 3;
341
546
    unsigned msg_len = getleu16(buf, 1);
342
546
    uint8_t port, cmd_id = getub(buf, 3);
343
344
546
    GPSD_LOG(LOG_PROG, &session->context->errout,
345
546
             "Navcom: received packet type 0x15 (Negative Acknowledge)\n");
346
347
546
    if (18 < msg_len) {
348
133
        GPSD_LOG(LOG_PROG, &session->context->errout,
349
133
                 "Navcom: 0x15 too long %u\n", msg_len);
350
133
        return 0;
351
133
    }
352
1.49k
    for (n = 4; n < (msg_len - 2); n += 2) {
353
1.08k
        uint8_t err_id = getub(buf, n);
354
1.08k
        uint8_t err_desc = getub(buf, n + 1);
355
1.08k
        GPSD_LOG(LOG_DATA, &session->context->errout,
356
1.08k
                 "Navcom: error id = 0x%02x, error description = 0x%02x\n",
357
1.08k
                 err_id, err_desc);
358
1.08k
    }
359
413
    port = getub(buf, n);
360
413
    GPSD_LOG(LOG_DATA, &session->context->errout,
361
413
             "Navcom: negative acknowledge was for command id 0x%02x "
362
413
             "on port %c\n",
363
413
             cmd_id, (port == 0 ? 'A' : (port == 1 ? 'B' : '?')));
364
413
    return 0;                   // Nothing updated*/
365
546
}
366
367
// PVT Block*/
368
static gps_mask_t handle_0xb1(struct gps_device_t *session)
369
228
{
370
228
    gps_mask_t mask = 0;
371
228
    unsigned char *buf = session->lexer.outbuffer + 3;
372
228
    uint16_t week;
373
228
    uint32_t tow;
374
228
    timespec_t ts_tow = {0, 0};      // pacify codacy
375
228
    int32_t lat, lon;
376
228
    char ts_buf[TIMESPEC_LEN];
377
    // Resolution of lat/lon values (2^-11)*/
378
456
#define LL_RES (0.00048828125)
379
228
    uint8_t lat_fraction, lon_fraction;
380
    // Resolution of lat/lon fractions (2^-15)*/
381
456
#define LL_FRAC_RES (0.000030517578125)
382
228
    uint8_t nav_mode;
383
228
    int32_t ellips_height, altitude;
384
    // Resolution of height and altitude values (2.0^-10)*/
385
456
#define EL_RES (0.0009765625)
386
228
    double vel_north, vel_east, vel_up;
387
228
    uint8_t gdop, pdop, hdop, vdop, tdop;
388
    // This value means "undefined"*/
389
1.14k
#define DOP_UNDEFINED (255)
390
391
228
    int16_t ant_height_adj;
392
228
    int32_t set_delta_up;
393
    /* Resolution of delta north, east, and up,
394
     * and ant. height adjustment values (1mm) */
395
912
#define D_RES (0.001)
396
397
#ifdef __UNUSED__
398
    /* Other values provided by the PVT block which we
399
     * may want to provide in the future.
400
     */
401
    uint8_t dgps_conf;
402
    uint16_t max_dgps_age;
403
    uint8_t ext_nav_mode;
404
    int32_t set_delta_north, set_delta_east;
405
    uint8_t nav_failure_code;
406
#endif  // __UNUSED__*/
407
408
    // Timestamp
409
228
    week = (uint16_t) getleu16(buf, 3);
410
228
    tow = (uint32_t) getleu32(buf, 5);        // tow in ms
411
228
    MSTOTS(&ts_tow, tow);
412
228
    session->newdata.time = gpsd_gpstime_resolv(session, week, ts_tow);
413
414
    // Get latitude, longitude
415
228
    lat = getles32(buf, 13);
416
228
    lon = getles32(buf, 17);
417
228
    lat_fraction = (uint8_t) (getub(buf, 21) >> 4);
418
228
    lon_fraction = (uint8_t) (getub(buf, 21) & 0x0f);
419
420
228
    session->newdata.latitude =
421
228
        (double)(lat * LL_RES + lat_fraction * LL_FRAC_RES) / 3600;
422
228
    session->newdata.longitude =
423
228
        (double)(lon * LL_RES + lon_fraction * LL_FRAC_RES) / 3600;
424
425
    // Nav mode
426
228
    nav_mode = (uint8_t) getub(buf, 22);
427
228
    if (-nav_mode & 0x80) {
428
96
        session->newdata.status = STATUS_UNK;
429
96
        session->newdata.mode = MODE_NO_FIX;
430
132
    } else {
431
132
        session->newdata.mode = ((nav_mode & 0x40)!=0 ? MODE_3D : MODE_2D);
432
132
        session->newdata.status =
433
132
            ((nav_mode & 0x03)!=0 ? STATUS_DGPS : STATUS_GPS);
434
132
    }
435
436
    // altHAE
437
228
    ellips_height = getles32(buf, 23);
438
    // altMSL
439
228
    altitude = getles32(buf, 27);
440
441
228
    ant_height_adj = getles16(buf, 51);
442
228
    set_delta_up = getles32(buf, 79);
443
444
228
    session->newdata.altMSL = (double)(altitude * EL_RES)
445
228
        + (ant_height_adj * D_RES) + (set_delta_up * D_RES);
446
228
    session->newdata.altHAE = (double)(ellips_height) * EL_RES
447
228
        + (ant_height_adj * D_RES) + (set_delta_up * D_RES);
448
    // Let gpsd_error_model() deal with geoid_sep
449
450
    // Speed Data
451
228
    vel_north = (double)getles3224(buf, 31);
452
228
    vel_east = (double)getles3224(buf, 34);
453
228
    vel_up = (double)getles3224(buf, 37);
454
455
228
    session->newdata.NED.velN = vel_north * 0.1;
456
228
    session->newdata.NED.velE = vel_east * 0.1;
457
228
    session->newdata.NED.velD = -vel_up * 0.1;
458
459
    // Quality indicators
460
    // UNUSED fom = getub(buf, 40);     * FOM is DRMS
461
228
    gdop = getub(buf, 41);
462
228
    pdop = getub(buf, 42);
463
228
    hdop = getub(buf, 43);
464
228
    vdop = getub(buf, 44);
465
228
    tdop = getub(buf, 45);
466
    // UNUSED tfom = getub(buf, 46);    * tfom == 10 * TDOP
467
468
    // let gpsd_error_model() do the error estimates
469
470
228
    if (gdop != DOP_UNDEFINED) {
471
139
        session->gpsdata.dop.gdop = gdop / 10.0;
472
139
        mask |= DOP_SET;
473
139
    }
474
228
    if (pdop != DOP_UNDEFINED) {
475
139
        session->gpsdata.dop.pdop = pdop / 10.0;
476
139
        mask |= DOP_SET;
477
139
    }
478
228
    if (hdop != DOP_UNDEFINED) {
479
138
        session->gpsdata.dop.hdop = hdop / 10.0;
480
138
        mask |= DOP_SET;
481
138
    }
482
228
    if (vdop != DOP_UNDEFINED) {
483
131
        session->gpsdata.dop.vdop = vdop / 10.0;
484
131
        mask |= DOP_SET;
485
131
    }
486
228
    if (tdop != DOP_UNDEFINED) {
487
143
        session->gpsdata.dop.tdop = tdop / 10.0;
488
143
        mask |= DOP_SET;
489
143
    }
490
491
228
    GPSD_LOG(LOG_PROG, &session->context->errout,
492
228
             "Navcom: received packet type 0xb1 (PVT Report)\n");
493
228
    GPSD_LOG(LOG_DATA, &session->context->errout,
494
228
             "Navcom: navigation mode %s (0x%02x) - %s - %s\n",
495
228
             ((-nav_mode & 0x80)!='\0' ? "invalid" : "valid"), nav_mode,
496
228
             ((nav_mode & 0x40)!='\0' ? "3D" : "2D"),
497
228
             ((nav_mode & 0x03)!='\0' ? "DGPS" : "GPS"));
498
228
    GPSD_LOG(LOG_DATA, &session->context->errout,
499
228
             "Navcom: velocities: north = %f east = %f up = %f\n",
500
228
             session->newdata.NED.velN,
501
228
             session->newdata.NED.velE,
502
228
             -session->newdata.NED.velD);
503
228
#undef D_RES
504
228
#undef LL_RES
505
228
#undef LL_FRAC_RES
506
228
#undef EL_RES
507
228
#undef VEL_RES
508
228
#undef DOP_UNDEFINED
509
510
228
    mask |= LATLON_SET | ALTITUDE_SET | STATUS_SET | MODE_SET | USED_IS |
511
228
           HERR_SET | TIMERR_SET | VNED_SET | TIME_SET | NTPTIME_IS;
512
228
    GPSD_LOG(LOG_DATA, &session->context->errout,
513
228
             "PVT 0xb1: time=%s, lat=%.2f lon=%.2f altHAE=%.2f "
514
228
             "altMSL %.2f mode=%d status=%d gdop=%.2f pdop=%.2f hdop=%.2f "
515
228
             "vdop=%.2f tdop=%.2f mask={%s}\n",
516
228
             timespec_str(&session->newdata.time, ts_buf, sizeof(ts_buf)),
517
228
             session->newdata.latitude,
518
228
             session->newdata.longitude,
519
228
             session->newdata.altHAE,
520
228
             session->newdata.altMSL,
521
228
             session->newdata.mode,
522
228
             session->newdata.status,
523
228
             session->gpsdata.dop.gdop,
524
228
             session->gpsdata.dop.pdop,
525
228
             session->gpsdata.dop.hdop,
526
228
             session->gpsdata.dop.vdop, session->gpsdata.dop.tdop,
527
228
             gps_maskdump(mask));
528
228
    return mask | CLEAR_IS | REPORT_IS;
529
228
}
530
531
// Packed Ephemeris Data
532
static gps_mask_t handle_0x81(struct gps_device_t *session)
533
100
{
534
    // Scale factors for everything
535
    // 2^-31
536
100
#define SF_TGD       (.000000000465661287307739257812)
537
    // 2^4
538
100
#define SF_TOC     (16)
539
    // 2^-55
540
100
#define SF_AF2       (.000000000000000027755575615628)
541
    // 2^-43
542
100
#define SF_AF1       (.000000000000113686837721616029)
543
    // 2^-31
544
100
#define SF_AF0       (.000000000465661287307739257812)
545
    // 2^-5
546
100
#define SF_CRS       (.031250000000000000000000000000)
547
    // 2^-43
548
100
#define SF_DELTA_N   (.000000000000113686837721616029)
549
    // 2^-31
550
100
#define SF_M0   (.000000000465661287307739257812)
551
    // 2^-29
552
100
#define SF_CUC       (.000000001862645149230957031250)
553
    // 2^-33
554
100
#define SF_E     (.000000000116415321826934814453)
555
    // 2^-29
556
100
#define SF_CUS       (.000000001862645149230957031250)
557
    // 2^-19
558
100
#define SF_SQRT_A    (.000001907348632812500000000000)
559
    // 2^4
560
100
#define SF_TOE     (16)
561
    // 2^-29
562
100
#define SF_CIC       (.000000001862645149230957031250)
563
    // 2^-31
564
100
#define SF_OMEGA0    (.000000000465661287307739257812)
565
    // 2^-29
566
100
#define SF_CIS       (.000000001862645149230957031250)
567
    // 2^-31
568
100
#define SF_I0   (.000000000465661287307739257812)
569
    // 2^-5
570
100
#define SF_CRC       (.031250000000000000000000000000)
571
    // 2^-31
572
100
#define SF_OMEGA     (.000000000465661287307739257812)
573
    // 2^-43
574
100
#define SF_OMEGADOT  (.000000000000113686837721616029)
575
    // 2^-43
576
100
#define SF_IDOT      (.000000000000113686837721616029)
577
578
100
    char ts_buf[TIMESPEC_LEN];
579
100
    unsigned char *buf = session->lexer.outbuffer + 3;
580
100
    uint8_t prn = getub(buf, 3);
581
100
    uint16_t week = getleu16(buf, 4);
582
100
    uint32_t tow = getleu32(buf, 6);
583
100
    uint16_t iodc = getleu16(buf, 10);
584
    /* And now the fun starts... everything that follows is
585
     * raw GPS data minus parity */
586
    // Subframe 1, words 3 to 10 minus parity
587
100
    uint16_t wn = (getleu16_be(buf, 12) & 0xffc0) >> 6;
588
100
    uint8_t cl2 = (getub(buf, 13) & 0x30) >> 4;
589
100
    uint8_t ura = getub(buf, 13) & 0x0f;
590
100
    uint8_t svh = (getub(buf, 14) & 0xfc) >> 2;
591
    /* We already have IODC from earlier in the message, so
592
     * we do not decode again */
593
//    uint16_t iodc = (getub(buf, 14)&0x03)<<8;*/
594
100
    uint8_t l2pd = (getub(buf, 15) & 0x80) >> 7;
595
100
    int8_t tgd = getsb(buf, 26);
596
//    iodc |= getub(buf, 27);*/
597
100
    uint16_t toc = getleu16_be(buf, 28);
598
100
    int8_t af2 = getsb(buf, 30);
599
100
    int16_t af1 = getles16_be(buf, 31);
600
100
    int32_t af0 = getles3224_be(buf, 33) >> 2;
601
    // Subframe 2, words 3 to 10 minus parity
602
100
    uint8_t iode = getub(buf, 36);
603
100
    int16_t crs = getles16_be(buf, 37);
604
100
    int16_t delta_n = getles16_be(buf, 39);
605
100
    int32_t m0 = getles32_be(buf, 41);
606
100
    int16_t cuc = getles16_be(buf, 45);
607
100
    uint32_t e = getleu32_be(buf, 47);
608
100
    int16_t cus = getles16_be(buf, 51);
609
100
    uint32_t sqrt_a = getleu32_be(buf, 53);
610
100
    uint16_t toe = getleu16_be(buf, 57);
611
    // NOTE - Fit interval & AODO not collected
612
    // Subframe 3, words 3 to 10 minus parity
613
100
    int16_t cic = getles16_be(buf, 60);
614
100
    int32_t Omega0 = getles32_be(buf, 62);
615
100
    int16_t cis = getles16_be(buf, 66);
616
100
    int32_t i0 = getles32_be(buf, 68);
617
100
    int16_t crc = getles16_be(buf, 72);
618
100
    int32_t omega = getles32_be(buf, 74);
619
100
    int32_t Omegadot = getles3224_be(buf, 78);
620
    /* Question: What is the proper way of shifting a signed int 2 bits to
621
     * the right, preserving sign? Answer: integer division by 4. */
622
100
    int16_t idot = (int16_t) (((getles16_be(buf, 82) & 0xfffc) / 4) |
623
100
                              ((getub(buf, 82) & 80) ? 0xc000 : 0x0000));
624
100
    session->context->gps_week = (unsigned short)wn;
625
100
    DTOTS(&session->context->gps_tow, (double)(toc * SF_TOC));
626
    // leap second?
627
100
    GPSD_LOG(LOG_PROG, &session->context->errout,
628
100
             "Navcom: received packet type 0x81 (Packed Ephemeris Data)\n");
629
100
    GPSD_LOG(LOG_DATA, &session->context->errout,
630
100
             "Navcom: PRN: %u, Week: %u, TOW: %s "
631
100
             "SV clock bias/drift/drift rate: %#19.12E/%#19.12E/%#19.12E\n",
632
100
             prn,
633
100
             session->context->gps_week,
634
100
             timespec_str(&session->context->gps_tow, ts_buf, sizeof(ts_buf)),
635
100
             ((double)af0) * SF_AF0,
636
100
             ((double)af1) * SF_AF1, ((double)af2) * SF_AF2);
637
100
    GPSD_LOG(LOG_DATA, &session->context->errout,
638
100
             "Navcom: IODE (!AODE): %u Crs: %19.12e, Delta n: %19.12e, M0: %19.12e\n",
639
100
             iode, (double)crs * SF_CRS,
640
100
             (double)delta_n * SF_DELTA_N * GPS_PI,
641
100
             (double)m0 * SF_M0 * GPS_PI);
642
100
    GPSD_LOG(LOG_DATA, &session->context->errout,
643
100
             "Navcom: Cuc: %19.12e, Eccentricity: %19.12e, Cus: %19.12e, A^1/2: %19.12e\n",
644
100
             (double)cuc * SF_CUC, (double)e * SF_E, (double)cus * SF_CUS,
645
100
             (double)sqrt_a * SF_SQRT_A);
646
100
    GPSD_LOG(LOG_DATA, &session->context->errout,
647
100
             "Navcom: TOE: %u, Cic: %19.12e, Omega %19.12e, Cis: %19.12e\n",
648
100
             toe * SF_TOE, (double)cic * SF_CIC,
649
100
             (double)Omega0 * SF_OMEGA0 * GPS_PI, (double)cis * SF_CIS);
650
100
    GPSD_LOG(LOG_DATA, &session->context->errout,
651
100
             "Navcom: i0: %19.12e, Crc: %19.12e, omega: %19.12e, Omega dot: %19.12e\n",
652
100
             (double)i0 * SF_I0 * GPS_PI, (double)crc * SF_CRC,
653
100
             (double)omega * SF_OMEGA * GPS_PI,
654
100
             (double)Omegadot * SF_OMEGADOT * GPS_PI);
655
100
    GPSD_LOG(LOG_DATA, &session->context->errout,
656
100
             "Navcom: IDOT: %19.12e, Codes on L2: 0x%x, GPS Week: %u, L2 P data flag: %x\n",
657
100
             (double)idot * SF_IDOT * GPS_PI, cl2,
658
100
             week - (week % 1024) + wn, l2pd);
659
100
    GPSD_LOG(LOG_DATA, &session->context->errout,
660
100
             "Navcom: SV accuracy: 0x%x, SV health: 0x%x, TGD: %f, IODC (!AODC): %u\n",
661
100
             ura, svh, (double)tgd * SF_TGD, iodc);
662
100
    GPSD_LOG(LOG_DATA, &session->context->errout,
663
100
             "Navcom: Transmission time: %u\n", tow);
664
665
100
#undef SF_TGD
666
100
#undef SF_TOC
667
100
#undef SF_AF2
668
100
#undef SF_AF1
669
100
#undef SF_AF0
670
100
#undef SF_CRS
671
100
#undef SF_DELTA_N
672
100
#undef SF_M0
673
100
#undef SF_CUC
674
100
#undef SF_E
675
100
#undef SF_CUS
676
100
#undef SF_SQRT_A
677
100
#undef SF_TOE
678
100
#undef SF_CIC
679
100
#undef SF_OMEGA0
680
100
#undef SF_CIS
681
100
#undef SF_I0
682
100
#undef SF_CRC
683
100
#undef SF_OMEGA
684
100
#undef SF_OMEGADOT
685
100
#undef SF_IDOT
686
687
100
    return 0;
688
100
}
689
690
// Channel Status
691
static gps_mask_t handle_0x86(struct gps_device_t *session)
692
1.19k
{
693
1.19k
    size_t n, i, nsu;
694
1.19k
    unsigned char *buf = session->lexer.outbuffer + 3;
695
1.19k
    unsigned msg_len = getleu16(buf, 1);
696
1.19k
    unsigned short week = getleu16(buf, 3);
697
1.19k
    uint32_t tow = getleu32(buf, 5);
698
1.19k
    uint8_t eng_status = getub(buf, 9);
699
1.19k
    uint16_t sol_status = getleu16(buf, 10);
700
1.19k
    uint8_t sats_visible = getub(buf, 12);
701
    //uint8_t sats_tracked = getub(buf, 13);
702
    //uint8_t used_sats = getub(buf, 14);
703
    //uint8_t pdop = getub(buf, 15);
704
1.19k
    timespec_t ts_tow;
705
706
1.19k
    MSTOTS(&ts_tow, tow);
707
708
    // Timestamp
709
1.19k
    session->gpsdata.skyview_time = gpsd_gpstime_resolv(session, week, ts_tow);
710
711
    // Give this driver a single point of truth about DOPs
712
    //session->gpsdata.dop.pdop = (int)pdop / 10.0;
713
714
    // Satellite count
715
1.19k
    session->gpsdata.satellites_visible = sats_visible;
716
1.19k
    if (MAXCHANNELS < session->gpsdata.satellites_visible) {
717
471
        GPSD_LOG(LOG_WARN, &session->context->errout,
718
471
                "NAVCOM: too many satellites %d\n",
719
471
                 session->gpsdata.satellites_visible);
720
471
        session->gpsdata.satellites_visible = MAXCHANNELS;
721
471
    }
722
723
    // Fix mode
724
1.19k
    switch (sol_status & 0x05) {
725
213
    case 0x05:
726
213
        session->newdata.status = STATUS_DGPS;
727
213
        break;
728
246
    case 0x01:
729
246
        session->newdata.status = STATUS_GPS;
730
246
        break;
731
740
    default:
732
740
        session->newdata.status = STATUS_UNK;
733
1.19k
    }
734
735
1.19k
    GPSD_LOG(LOG_DATA, &session->context->errout,
736
1.19k
             "Navcom: engine status 0x%x almanac %s time 0x%x pos 0x%x\n",
737
1.19k
             eng_status & 0x07, ((eng_status & 0x08) ? "valid" : "invalid"),
738
1.19k
             eng_status & 0x30 >> 4, eng_status & 0xc0 >> 6);
739
740
    // Satellite details
741
1.19k
    i = nsu = 0;
742
1.19k
    if (298 < msg_len) {
743
        /* pasify coverity
744
         * msg_len = 18 + (14 * nsat)
745
         * assume 20 sats max */
746
52
        msg_len = 298;
747
52
    }
748
4.59k
    for (n = 17; n < msg_len; n += 14) {
749
3.39k
        uint8_t prn, ele, ca_snr, p2_snr, log_channel, hw_channel, s, stat;
750
3.39k
        uint16_t azm, dgps_age;
751
3.39k
        if (i >= MAXCHANNELS) {
752
0
            GPSD_LOG(LOG_ERROR, &session->context->errout,
753
0
                     "Navcom: packet type 0x86: too many satellites!\n");
754
0
            gpsd_zero_satellites(&session->gpsdata);
755
0
            return 0;
756
0
        }
757
3.39k
        prn = getub(buf, n);
758
        /*
759
         * This field is described in the Technical Reference as follows:
760
         *
761
         * Channel Tracking Status:
762
         * B0-B1: C/A tracking status
763
         * B2-B3: P1 tracking status
764
         * B4-B5: P2 tracking status
765
         *    00 Acquisition or reacquisition
766
         *    01 Code loop locked
767
         *    02 Costas loop locked
768
         *    11 Full tracking with aiding and active
769
         *       multipath reduction - all data is valid
770
         * B6=1: C/A Bit sync
771
         * B7=1: C/A Frame sync
772
         *
773
         * By observation, the satellite is in use if this status is 0xff.
774
         * But errors here are not very serious, all they can affect is
775
         * the coverance-matrix calculation for error modeling.
776
         */
777
3.39k
        stat = getub(buf, n + 1);
778
3.39k
        log_channel = getub(buf, n + 2);
779
3.39k
        ele = getub(buf, n + 5);
780
3.39k
        azm = getleu16(buf, n + 6);
781
3.39k
        ca_snr = getub(buf, n + 8);
782
3.39k
        p2_snr = getub(buf, n + 10);
783
3.39k
        dgps_age = getleu16(buf, n + 11);
784
3.39k
        hw_channel = getub(buf, n + 13);
785
3.39k
        s = (unsigned char)0;
786
        /* NOTE - In theory, I think one would check for hw channel number to
787
         * see if one is dealing with a GPS or other satellite, but the
788
         * channel numbers reported bear no resemblance to what the spec
789
         * says should be.  So I check for the fact that if all three
790
         * values below are zero, one is not interested in this satellite */
791
3.39k
        if (!(ele == 0 && azm == 0 && dgps_age == 0)) {
792
3.17k
            session->gpsdata.skyview[i].PRN = (short)prn;
793
3.17k
            session->gpsdata.skyview[i].elevation = (double)ele;
794
3.17k
            session->gpsdata.skyview[i].azimuth = (double)azm;
795
3.17k
            s = session->gpsdata.skyview[i].ss =
796
3.17k
                (p2_snr ? p2_snr : ca_snr) / 4.0;
797
3.17k
            session->gpsdata.skyview[i++].used = (stat == 0xff);
798
3.17k
            if (0xff == stat) {
799
445
                nsu++;
800
445
            }
801
3.17k
        }
802
3.39k
        session->gpsdata.satellites_used = nsu;
803
3.39k
        GPSD_LOG(LOG_DATA, &session->context->errout,
804
3.39k
                 "Navcom: prn = %3u, ele = %02u, azm = %03u, snr = %d (%s), "
805
3.39k
                 "dgps age = %.1fs, log ch = %d, hw ch = 0x%02x\n",
806
3.39k
                 prn, ele, azm, s, (p2_snr ? "P2" : "C/A"),
807
3.39k
                 (double)dgps_age * 0.1, log_channel & 0x3f, hw_channel);
808
3.39k
        GPSD_LOG(LOG_DATA, &session->context->errout,
809
3.39k
                 "Navcom:           sol. valid = %c, clock = %s, pos. = %s, "
810
3.39k
                 "height = %s, err. code = 0x%x\n",
811
3.39k
                 ((sol_status & 0x01) ? 'Y' : 'N'),
812
3.39k
                 ((sol_status & 0x02) ? "stable" : "unstable"),
813
3.39k
                 ((sol_status & 0x04) ? "dgps" : "unaided"),
814
3.39k
                 ((sol_status & 0x08) ? "solved" : "constrained"),
815
3.39k
                 ((sol_status & 0x01) ? 0x00 : sol_status & 0x0f00 >> 8));
816
3.39k
    }
817
818
1.19k
    GPSD_LOG(LOG_DATA, &session->context->errout,
819
1.19k
             "CS 0x86: visible=%d, used=%d, mask={SATELLITE|STATUS}\n",
820
1.19k
             session->gpsdata.satellites_visible,
821
1.19k
             session->gpsdata.satellites_used);
822
1.19k
    return SATELLITE_SET | STATUS_SET;
823
1.19k
}
824
825
/* Raw Meas. Data Block
826
 * Size 4 + 8 + (16 * numSat) = 524
827
 */
828
static gps_mask_t handle_0xb0(struct gps_device_t *session)
829
624
{
830
624
    char ts_buf[TIMESPEC_LEN];
831
    /* L1 wavelength (299792458m/s / 1575420000Hz)
832
     * from their Technical reference Manual */
833
4.01k
#define LAMBDA_L1 (299792458.0 / 1575420000.0)
834
624
    unsigned n;
835
624
    unsigned char *buf = session->lexer.outbuffer + 3;
836
624
    unsigned msg_len = getleu16(buf, 1);
837
624
    uint16_t week = getleu16(buf, 3);
838
624
    uint32_t tow = getleu32(buf, 5);
839
624
    uint8_t tm_slew_acc = getub(buf, 9);
840
624
    uint8_t status = getub(buf, 10);
841
842
624
    session->context->gps_week = (unsigned short)week;
843
624
    MSTOTS(&session->context->gps_tow, tow);
844
845
624
    GPSD_LOG(LOG_PROG, &session->context->errout,
846
624
             "Navcom: received packet type 0xb0 (Raw Meas. Data Block)\n");
847
624
    GPSD_LOG(LOG_DATA, &session->context->errout,
848
624
             "Navcom: week = %u, tow = %s "
849
624
             "time slew accumulator = %u (1/1023mS), status = 0x%02x "
850
624
             "(%sclock %s - %u blocks follow) msg_len %u\n",
851
624
             session->context->gps_week,
852
624
             timespec_str(&session->context->gps_tow, ts_buf, sizeof(ts_buf)),
853
624
             tm_slew_acc, status,
854
624
             ((status & 0x80) ? "channel time set - " : ""),
855
624
             ((status & 0x40) ? "stable" : "not stable"), status & 0x0f,
856
624
             msg_len);
857
858
624
    if (530 < msg_len) {
859
36
        GPSD_LOG(LOG_PROG, &session->context->errout,
860
36
                 "Navcom: received packet type 0xb0, length %u too long\n",
861
36
                 msg_len);
862
36
        return 0;
863
36
    }
864
865
3.49k
    for (n = 11; n < (msg_len - 1); n += 16) {
866
2.90k
        uint8_t sv_status = getub(buf, n);
867
2.90k
        uint8_t ch_status = getub(buf, n + 1);
868
2.90k
        uint32_t ca_pseudorange = getleu32(buf, n + 2);
869
        // integer division by 16 is a sign-preserving right shift of 4 bits
870
2.90k
        int32_t l1_phase = getles3224(buf, n + 6) / 16;
871
2.90k
        uint8_t l1_slips = (uint8_t) (getles3224(buf, n + 6) & 0x0f);
872
2.90k
        int16_t p1_ca_pseudorange = getles16(buf, n + 9);
873
2.90k
        int16_t p2_ca_pseudorange = getles16(buf, n + 11);
874
2.90k
        int32_t l2_phase = getles3224(buf, n + 13) / 16;
875
2.90k
        uint8_t l2_slips = (uint8_t) (getles3224(buf, n + 13) & 0x0f);
876
2.90k
        double c1 =
877
2.90k
            ((sv_status & 0x80) ? (double)ca_pseudorange / 16.0 *
878
2.90k
             LAMBDA_L1 : NAN);
879
2.90k
        double l1 =
880
2.90k
            ((sv_status & 0x80) ? (double)ca_pseudorange / 16.0 +
881
2.90k
             (double)l1_phase / 256.0 : NAN);
882
2.90k
        double l2 =
883
2.90k
            ((sv_status & 0x20)
884
2.90k
             ? ((double)ca_pseudorange / 16.0 +
885
1.65k
                (double)p2_ca_pseudorange / 16.0) * (120.0 / 154.0)
886
2.90k
             + (double)l2_phase / 256.0 : NAN);
887
2.90k
        double p1 =
888
2.90k
            ((sv_status & 0x40) ? c1 +
889
2.90k
             (double)p1_ca_pseudorange / 16.0 * LAMBDA_L1 : NAN);
890
2.90k
        double p2 =
891
2.90k
            ((sv_status & 0x20) ? c1 +
892
2.90k
             (double)p2_ca_pseudorange / 16.0 * LAMBDA_L1 : NAN);
893
2.90k
        GPSD_LOG(LOG_SPIN, &session->context->errout,
894
2.90k
                 "Navcom: >> sv status = 0x%02x (PRN %u - C/A & L1 %s "
895
2.90k
                 "- P1 %s - P2 & L2 %s)\n",
896
2.90k
                 sv_status, (sv_status & 0x1f),
897
2.90k
                 ((sv_status & 0x80) ? "valid" : "invalid"),
898
2.90k
                 ((sv_status & 0x40) ? "valid" : "invalid"),
899
2.90k
                 ((sv_status & 0x20) ? "valid" : "invalid"));
900
2.90k
        GPSD_LOG(LOG_SPIN, &session->context->errout,
901
2.90k
                 "Navcom: >>> ch status = 0x%02x "
902
2.90k
                 "(Logical channel: %u - CA C/No: %u dBHz) "
903
2.90k
                 "sL1: %u, sL2: %u\n", ch_status, ch_status & 0x0f,
904
2.90k
                 ((ch_status & 0xf0) >> 4) + 35, l1_slips, l2_slips);
905
2.90k
        GPSD_LOG(LOG_SPIN, &session->context->errout,
906
2.90k
                 "Navcom: >>> C1: %14.3f L1: %14.3f L2: %14.3f "
907
2.90k
                 "P1: %14.3f P2: %14.3f\n",
908
2.90k
                 c1, l1, l2, p1, p2);
909
2.90k
    }
910
588
#undef LAMBDA_L1
911
588
    return 0;               // FIXME: use raw measurements
912
624
}
913
914
// Pseudorange Noise Statistics
915
static gps_mask_t handle_0xb5(struct gps_device_t *session)
916
86
{
917
86
    if (sizeof(double) == 8) {
918
86
        gps_mask_t mask = TIME_SET;
919
86
        char *buf = (char *)session->lexer.outbuffer + 3;
920
86
        uint16_t week = getleu16(buf, 3);
921
86
        uint32_t tow = getleu32(buf, 5);
922
86
        timespec_t ts_tow;
923
#ifdef __UNUSED__
924
        double rms = getled64(buf, 9);
925
        /* Reason why it's unused is these figures do not agree
926
         * with those obtained from the PVT report (handle_0xb1).
927
         * The figures from 0xb1 do agree with the values reported
928
         * by Navcom's PC utility */
929
        // let gpsd_error_model() handle this
930
        //double ellips_maj = getled64(buf, 17);
931
        //double ellips_min = getled64(buf, 25);
932
        //double ellips_azm = getled64(buf, 33);
933
        double lat_sd = getled64(buf, 41);
934
        double lon_sd = getled64(buf, 49);
935
        double alt_sd = getled64(buf, 57);
936
        double hrms = sqrt(pow(lat_sd, 2) + pow(lon_sd, 2));
937
        // Navcom doc unclear, this is likely sep?
938
        session->newdata.sep = rms * 1.96;
939
        session->newdata.eph = hrms * 1.96;
940
        session->newdata.epv = alt_sd * 1.96;
941
        mask |= HERR_SET;
942
#endif  //  __UNUSED__
943
86
        MSTOTS(&ts_tow, tow);
944
86
        session->newdata.time = gpsd_gpstime_resolv(session,
945
86
                                                  (unsigned short)week,
946
86
                                                  ts_tow);
947
86
        GPSD_LOG(LOG_PROG, &session->context->errout,
948
86
                 "Navcom: received packet type 0xb5 (Pseudorange Noise Statistics)\n");
949
86
        GPSD_LOG(LOG_DATA, &session->context->errout,
950
86
                 "Navcom: sep = %f\n", session->newdata.sep);
951
86
        return mask;
952
86
    } else {
953
        // Ignore this message block
954
0
        if (!session->driver.navcom.warned) {
955
0
            GPSD_LOG(LOG_WARN, &session->context->errout,
956
0
                     "Navcom: received packet type 0xb5 (Pseudorange "
957
0
                     "Noise Statistics) ignored "
958
0
                     " - sizeof(double) == 64 bits required\n");
959
0
            session->driver.navcom.warned = true;
960
0
        }
961
0
        return 0;               // Block ignored - wrong sizeof(double)
962
0
    }
963
86
}
964
965
// LBM DSP Status Block
966
static gps_mask_t handle_0xd3(struct gps_device_t *session UNUSED)
967
36
{
968
    /* This block contains status information about the
969
     * unit's L-band (Inmarsat) module.  There is nothing
970
     * interesting in it for our purposes so we do not deal
971
     * with it.  This callback is purely to a) stop
972
     * "unrecognised packet" messages appearing in the log
973
     * and b) explain what it is for the curious */
974
36
    return 0;                   // Nothing updated
975
36
}
976
977
// Identification Block
978
static gps_mask_t handle_0xae(struct gps_device_t *session)
979
1.43k
{
980
1.43k
    char *engconfstr, *asicstr;
981
1.43k
    unsigned char *buf = session->lexer.outbuffer + 3;
982
1.43k
    size_t msg_len = (size_t) getleu16(buf, 1);
983
1.43k
    uint8_t engconf = getub(buf, 3);
984
1.43k
    uint8_t asic = getub(buf, 4);
985
1.43k
    uint8_t swvermaj = getub(buf, 5);
986
1.43k
    uint8_t swvermin = getub(buf, 6);
987
1.43k
    uint16_t dcser = getleu16(buf, 7);
988
1.43k
    uint8_t dcclass = getub(buf, 9);
989
1.43k
    uint16_t rfcser = getleu16(buf, 10);
990
1.43k
    uint8_t rfcclass = getub(buf, 12);
991
1.43k
    uint8_t softtm[17] = {0};
992
1.43k
    uint8_t bootstr[17] = {0};
993
1.43k
    uint8_t ioptm[17] = {0};
994
1.43k
    uint8_t iopvermaj = (uint8_t) 0x00;
995
1.43k
    uint8_t iopvermin = (uint8_t) 0x00;
996
1.43k
    uint8_t picver = (uint8_t) 0x00;
997
1.43k
    uint8_t slsbn = (uint8_t) 0x00;
998
1.43k
    uint8_t iopsbn = (uint8_t) 0x00;
999
1000
1.43k
    memcpy(softtm, &buf[13], 16);
1001
1.43k
    memcpy(bootstr, &buf[29], 16);
1002
1.43k
    if (0x0037 == msg_len) {    // No IOP
1003
0
        slsbn = getub(buf, 53);
1004
1.43k
    } else {                    // IOP Present
1005
1.43k
        iopvermaj = getub(buf, 53);
1006
1.43k
        iopvermin = getub(buf, 54);
1007
1.43k
        memcpy(ioptm, &buf[55], 16);
1008
1.43k
        picver = getub(buf, 71);
1009
1.43k
        slsbn = getub(buf, 72);
1010
1.43k
        iopsbn = getub(buf, 73);
1011
1.43k
    }
1012
1013
1.43k
    switch (engconf) {
1014
105
    case 0x00:
1015
105
        engconfstr = "Unknown/Undefined";
1016
105
        break;
1017
101
    case 0x01:
1018
101
        engconfstr = "NCT 2000 S";
1019
101
        break;
1020
100
    case 0x02:
1021
100
        engconfstr = "NCT 2000 D";
1022
100
        break;
1023
77
    case 0x03:
1024
77
        engconfstr = "Startfire Single";
1025
77
        break;
1026
84
    case 0x04:
1027
84
        engconfstr = "Starfire Dual";
1028
84
        break;
1029
100
    case 0x05:
1030
100
        engconfstr = "Pole Mount RTK (Internal Radio)";
1031
100
        break;
1032
101
    case 0x06:
1033
101
        engconfstr = "Pole Mount GIS (LBM)";
1034
101
        break;
1035
84
    case 0x07:
1036
84
        engconfstr = "Black Box RTK (Internal Radio)";
1037
84
        break;
1038
103
    case 0x08:
1039
103
        engconfstr = "Black Box GIS (LBM)";
1040
103
        break;
1041
100
    case 0x80:
1042
100
        engconfstr = "R100";
1043
100
        break;
1044
101
    case 0x81:
1045
101
        engconfstr = "R200";
1046
101
        break;
1047
76
    case 0x82:
1048
76
        engconfstr = "R210";
1049
76
        break;
1050
100
    case 0x83:
1051
100
        engconfstr = "R300";
1052
100
        break;
1053
100
    case 0x84:
1054
100
        engconfstr = "R310";
1055
100
        break;
1056
100
    default:
1057
100
        engconfstr = "?";
1058
1.43k
    }
1059
1060
1.43k
    switch (asic) {
1061
170
    case 0x01:
1062
170
        asicstr = "A-ASIC";
1063
170
        break;
1064
178
    case 0x02:
1065
178
        asicstr = "B-ASIC";
1066
178
        break;
1067
246
    case 0x03:
1068
246
        asicstr = "C-ASIC";
1069
246
        break;
1070
195
    case 0x04:
1071
195
        asicstr = "M-ASIC";
1072
195
        break;
1073
643
    default:
1074
643
        asicstr = "?";
1075
1.43k
    }
1076
1077
1.43k
    GPSD_LOG(LOG_PROG, &session->context->errout,
1078
1.43k
             "Navcom: received packet type 0xae (Identification Block)\n");
1079
1.43k
    if (0x0037 == msg_len) {
1080
0
        GPSD_LOG(LOG_INF, &session->context->errout, "Navcom: ID Data: "
1081
0
                 "%s %s Ver. %u.%u.%u, DC S/N: %u.%u, RF S/N: %u.%u, "
1082
0
                 "Build ID: %s, Boot software: %s\n",
1083
0
                 engconfstr, asicstr, swvermaj, swvermin, slsbn, dcser,
1084
0
                 dcclass, rfcser, rfcclass, softtm, bootstr);
1085
1.43k
    } else {
1086
1.43k
        GPSD_LOG(LOG_INF, &session->context->errout, "Navcom: ID Data: "
1087
1.43k
                 "%s %s Ver. %u.%u.%u, DC S/N: %u.%u, RF S/N: %u.%u, "
1088
1.43k
                 "Build ID: %s, Boot software: %s, "
1089
1.43k
                 "IOP Ver.: %u.%u.%u, PIC: %u, IOP Build ID: %s\n",
1090
1.43k
                 engconfstr, asicstr, swvermaj, swvermin, slsbn, dcser,
1091
1.43k
                 dcclass, rfcser, rfcclass, softtm, bootstr, iopvermaj,
1092
1.43k
                 iopvermin, iopsbn, picver, ioptm);
1093
1.43k
    }
1094
1095
1.43k
    (void)snprintf(session->subtype, sizeof(session->subtype),
1096
1.43k
                   "%s %s Ver. %u.%u.%u S/N %u.%u %u.%u",
1097
1.43k
                   engconfstr, asicstr, swvermaj, swvermin, slsbn, dcser,
1098
1.43k
                   dcclass, rfcser, rfcclass);
1099
1.43k
    return DEVICEID_SET;
1100
1.43k
}
1101
1102
// Clock Drift and Offset
1103
static gps_mask_t handle_0xef(struct gps_device_t *session)
1104
52
{
1105
52
    unsigned char *buf = session->lexer.outbuffer + 3;
1106
    //uint16_t week = getleu16(buf, 3);
1107
    //uint32_t tow = getleu32(buf, 5);
1108
52
    int8_t osc_temp = getsb(buf, 9);
1109
52
    uint8_t nav_status = getub(buf, 10);
1110
52
    double nav_clock_offset;
1111
52
    float nav_clock_drift;
1112
52
    float osc_filter_drift_est;
1113
52
    char ts_buf[TIMESPEC_LEN];
1114
52
    int32_t time_slew = (int32_t) getles32(buf, 27);
1115
52
    if (sizeof(double) == 8) {
1116
52
        nav_clock_offset = getled64((char *)buf, 11);
1117
52
    } else {
1118
0
        nav_clock_offset = NAN;
1119
0
    }
1120
52
    if (sizeof(float) == 4) {
1121
52
        nav_clock_drift = getlef32((char *)buf, 19);
1122
52
        osc_filter_drift_est = getlef32((char *)buf, 23);
1123
52
    } else {
1124
0
        nav_clock_drift = NAN;
1125
0
        osc_filter_drift_est = NAN;
1126
0
    }
1127
1128
52
    GPSD_LOG(LOG_DATA, &session->context->errout,
1129
52
             "Navcom: oscillator temp. = %d, nav. status = 0x%02x, "
1130
52
             "nav. clock offset = %f, nav. clock drift = %f, "
1131
52
             "osc. filter drift est. = %f, acc.time slew value = %d\n",
1132
52
             osc_temp, nav_status, nav_clock_offset, nav_clock_drift,
1133
52
             osc_filter_drift_est, time_slew);
1134
52
    GPSD_LOG(LOG_DATA, &session->context->errout,
1135
52
             "CDO 0xef: time=%s mask={TIME}\n",
1136
52
             timespec_str(&session->newdata.time, ts_buf, sizeof(ts_buf)));
1137
52
    return 0;
1138
52
}
1139
1140
1141
gps_mask_t navcom_parse(struct gps_device_t * session, unsigned char *buf,
1142
                        size_t len)
1143
8.88k
{
1144
8.88k
    unsigned char cmd_id;
1145
8.88k
    unsigned msg_len;
1146
8.88k
    unsigned bad_len = 0;
1147
8.88k
    gps_mask_t mask = 0;
1148
1149
8.88k
    if (7 > len) {
1150
        // min msg: STX, 0x99, 0x66, cmd_id, msg_len(2), ..., checksum
1151
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
1152
0
                 "Navcom: too short, len %zu\n", len);
1153
0
        return 0;
1154
0
    }
1155
    // Navcom checksum already checked in gpsd/parser.c
1156
1157
8.88k
    cmd_id = (unsigned char)getub(buf, 3);
1158
    //payload = &buf[6];
1159
8.88k
    msg_len = getleu16(buf, 4);
1160
1161
8.88k
    if (4 > msg_len ||
1162
8.74k
        (msg_len + 4) > len) {
1163
        /* min payload: cmd_id, msg_len(2), cksum
1164
         * Fuzzers love to do this. */
1165
1.21k
        GPSD_LOG(LOG_WARN, &session->context->errout,
1166
1.21k
                 "Navcom: bad lenghts, msg_len %u len %zu\n", msg_len, len);
1167
1.21k
        return 0;
1168
1.21k
    }
1169
1170
7.67k
    GPSD_LOG(LOG_RAW, &session->context->errout,
1171
7.67k
             "Navcom: packet type x%02x msg_len %u len %zu\n",
1172
7.67k
             cmd_id, msg_len, len);
1173
1174
7.67k
    session->cycle_end_reliable = true;
1175
1176
7.67k
    switch (cmd_id) {
1177
287
    case 0x06:
1178
287
        if (6 > msg_len) {
1179
134
            bad_len = 6;
1180
134
            break;
1181
134
        }
1182
153
        mask = handle_0x06(session);
1183
153
        break;
1184
999
    case 0x15:
1185
999
        if (6 > msg_len) {
1186
            // 6 + n * 2
1187
453
            bad_len = 6;
1188
453
            break;
1189
453
        }
1190
546
        mask = handle_0x15(session);
1191
546
        break;
1192
232
    case 0x81:    // Packed Ephemeris Data
1193
232
        if (86 > msg_len) {
1194
132
            bad_len = 86;
1195
132
            break;
1196
132
        }
1197
100
        mask = handle_0x81(session);
1198
100
        break;
1199
301
    case 0x83:
1200
301
        if (32 > msg_len) {
1201
132
            bad_len = 32;
1202
132
            break;
1203
132
        }
1204
169
        mask = handle_0x83(session);
1205
169
        break;
1206
1.34k
    case 0x86:
1207
1.34k
        if (18 > msg_len) {
1208
            // 18 + (14 * N)
1209
145
            bad_len = 18;
1210
145
            break;
1211
145
        }
1212
1.19k
        mask = handle_0x86(session);
1213
1.19k
        break;
1214
1.73k
    case 0xae:
1215
1.73k
        if (55 > msg_len ||
1216
1.48k
            75 > msg_len) {
1217
306
            bad_len = 55;
1218
306
            break;
1219
306
        }
1220
1.43k
        mask = handle_0xae(session);
1221
1.43k
        break;
1222
1.10k
    case 0xb0:
1223
1.10k
        if (12 > msg_len) {
1224
            // 4 + 8 + (16 * N)
1225
476
            bad_len = 12;
1226
476
            break;
1227
476
        }
1228
624
        mask = handle_0xb0(session);
1229
624
        break;
1230
788
    case 0xb1:
1231
788
        if (86 > msg_len) {
1232
560
            bad_len = 86;
1233
560
            break;
1234
560
        }
1235
228
        mask = handle_0xb1(session);
1236
228
        break;
1237
222
    case 0xb5:
1238
222
        if (66 > msg_len) {
1239
136
            bad_len = 66;
1240
136
            break;
1241
136
        }
1242
86
        mask = handle_0xb5(session);
1243
86
        break;
1244
262
    case 0xd3:
1245
262
        if (70 > msg_len) {
1246
226
            bad_len = 70;
1247
226
            break;
1248
226
        }
1249
36
        mask = handle_0xd3(session);
1250
36
        break;
1251
246
    case 0xef:
1252
246
        if (32 > msg_len) {
1253
194
            bad_len = 32;
1254
194
            break;
1255
194
        }
1256
52
        mask = handle_0xef(session);
1257
52
        break;
1258
0
    case 0x44:    // Packed Almanac Loading
1259
0
        FALLTHROUGH
1260
155
    default:
1261
155
        GPSD_LOG(LOG_PROG, &session->context->errout,
1262
155
                 "Navcom: received packet type 0x%02x, msg_len %d - "
1263
155
                 "unknown or unimplemented\n",
1264
155
                 cmd_id, msg_len);
1265
155
        break;
1266
7.67k
    }
1267
7.67k
    if (bad_len) {
1268
2.89k
        GPSD_LOG(LOG_WARN, &session->context->errout,
1269
2.89k
                 "Navcom: %u: runt payload len %u s/b %zd",
1270
2.89k
                 cmd_id, bad_len, session->lexer.outbuflen);
1271
2.89k
    }
1272
7.67k
    return mask;
1273
7.67k
}
1274
1275
1276
static gps_mask_t navcom_parse_input(struct gps_device_t *session)
1277
8.88k
{
1278
8.88k
    if (NAVCOM_PACKET == session->lexer.type) {
1279
8.88k
        return navcom_parse(session, session->lexer.outbuffer,
1280
8.88k
                          session->lexer.outbuflen);
1281
8.88k
    }
1282
0
    if (NMEA_PACKET == session->lexer.type) {
1283
0
        return nmea_parse((char *)session->lexer.outbuffer, session);
1284
0
    }
1285
0
    return 0;
1286
0
}
1287
1288
static ssize_t navcom_control_send(struct gps_device_t *session,
1289
                                   char *buf, size_t len)
1290
0
{
1291
0
    putbyte(session->msgbuf, 0, 0x02);
1292
0
    putbyte(session->msgbuf, 1, 0x99);
1293
0
    putbyte(session->msgbuf, 2, 0x66);
1294
0
    putbyte(session->msgbuf, 3, buf[0]);        // Cmd ID
1295
0
    putle16(session->msgbuf, 4, len + 4);       // Length
1296
0
    memcpy(session->msgbuf, buf + 6, len - 1);
1297
0
    putbyte(session->msgbuf, 6 + len,
1298
0
            checksum((unsigned char *)session->msgbuf + 3, len + 5));
1299
0
    putbyte(session->msgbuf, 7 + len, 0x03);
1300
0
    session->msgbuflen = len + 9;
1301
0
    return gpsd_write(session, session->msgbuf, session->msgbuflen);
1302
0
}
1303
1304
static bool navcom_speed(struct gps_device_t *session,
1305
                         speed_t speed, char parity, int stopbits)
1306
0
{
1307
    // parity and stopbit switching aren't implemented
1308
0
    if (parity != session->gpsdata.dev.parity
1309
0
        || stopbits != (int)session->gpsdata.dev.parity) {
1310
0
        return false;
1311
0
    } else {
1312
0
        uint8_t port, port_selection;
1313
0
        uint8_t baud;
1314
0
        if (session->driver.navcom.physical_port == (uint8_t) 0xFF) {
1315
            // We still don't know which port we're connected to
1316
0
            return false;
1317
0
        }
1318
0
        switch (speed) {
1319
            /* NOTE - The spec says that certain baud combinations
1320
             * on ports A and B are not allowed, those are
1321
             * 1200/115200, 2400/57600, and 2400/115200.
1322
             * To try and minimise the possibility of those
1323
             * occurring, we do not allow baud rates below
1324
             * 4800.  We could also disallow 57600 and 115200
1325
             * to totally prevent this, but I do not consider
1326
             * that reasonable.  Finding which baud speed the
1327
             * other port is set at would also be too much
1328
             * trouble, so we do not do it. */
1329
0
        case 4800:
1330
0
            baud = 0x04;
1331
0
            break;
1332
0
        case 9600:
1333
0
            baud = 0x06;
1334
0
            break;
1335
0
        case 19200:
1336
0
            baud = 0x08;
1337
0
            break;
1338
0
        case 38400:
1339
0
            baud = 0x0a;
1340
0
            break;
1341
0
        case 57600:
1342
0
            baud = 0x0c;
1343
0
            break;
1344
0
        case 115200:
1345
0
            baud = 0x0e;
1346
0
            break;
1347
0
        default:
1348
            // Unsupported speed
1349
0
            return false;
1350
0
        }
1351
1352
        // Proceed to construct our message
1353
0
        port = session->driver.navcom.physical_port;
1354
0
        port_selection = (port ? port : (uint8_t) 0xff) | baud;
1355
1356
        // Send it off
1357
0
        navcom_cmd_0x11(session, port_selection);
1358
1359
        /* And cheekily return true, even though we have
1360
         * no way to know if the speed change succeeded
1361
         * until and if we receive an ACK (message 0x06),
1362
         * which will be at the new baud speed if the
1363
         * command was successful.  Bottom line, the client
1364
         * should requery gpsd to see if the new speed is
1365
         * different than the old one */
1366
        return true;
1367
0
    }
1368
0
}
1369
1370
// this is everything we export
1371
// *INDENT-OFF*
1372
const struct gps_type_t driver_navcom =
1373
{
1374
    .type_name      = "Navcom NCT",             // full name of type
1375
    .packet_type    = NAVCOM_PACKET,            // lexer packet type
1376
    .flags          = DRIVER_STICKY,            // remember this
1377
    .trigger        = NULL,                     // none
1378
    .channels       = NAVCOM_CHANNELS,     // 12 L1 + 12 L2 + 2 Inmarsat L-Band
1379
    .probe_detect   = NULL,                     // no probe
1380
    .get_packet     = packet_get1,              // use generic one
1381
    .parse_packet   = navcom_parse_input,       // parse message packets
1382
    .rtcm_writer    = gpsd_write,               // send RTCM data straight
1383
    .init_query     = NULL,                     // non-perturbing query
1384
    .event_hook     = navcom_event_hook,        // lifetime event handler
1385
    .speed_switcher = navcom_speed,             // we do change baud rates
1386
    .mode_switcher  = NULL,                     // there is not a mode switcher
1387
    .rate_switcher  = NULL,                     // no sample-rate switcher
1388
    .min_cycle.tv_sec  = 1,             // not relevant, no rate switch
1389
    .min_cycle.tv_nsec = 0,             // not relevant, no rate switch
1390
    .control_send   = navcom_control_send,      // how to send a control string
1391
    .time_offset     = NULL,            // no method for NTP fudge factor
1392
};
1393
// *INDENT-ON*
1394
1395
#endif  // defined(NAVCOM_ENABLE)
1396
// vim: set expandtab shiftwidth=4