Coverage Report

Created: 2025-10-10 06:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gpsd/gpsd-3.26.2~dev/drivers/driver_zodiac.c
Line
Count
Source
1
/*
2
 * Handle the Rockwell binary packet format supported by the old Zodiac chipset
3
 *
4
 * Week counters are not limited to 10 bits. It's unknown what
5
 * the firmware is doing to disambiguate them, if anything; it might just
6
 * be adding a fixed offset based on a hidden epoch value, in which case
7
 * unhappy things will occur on the next rollover.
8
 *
9
 * This file is Copyright 2010 by the GPSD project
10
 * SPDX-License-Identifier: BSD-2-clause
11
 */
12
13
#include "../include/gpsd_config.h"  /* must be before all includes */
14
15
#include <math.h>
16
#include <stdbool.h>
17
#include <stdio.h>
18
#include <string.h>
19
#include <unistd.h>
20
21
#include "../include/gpsd.h"
22
#include "../include/bits.h"
23
#include "../include/strfuncs.h"
24
25
/* Zodiac protocol description uses 1-origin indexing by little-endian word */
26
0
#define get16z(buf, n)   ((buf[2*(n)-2]) | (buf[2*(n)-1] << 8))
27
0
#define getu16z(buf, n)  (uint16_t)((buf[2*(n)-2]) | (buf[2*(n)-1] << 8))
28
0
#define get32z(buf, n)   ((buf[2*(n)-2]) | (buf[2*(n)-1] << 8) | \
29
0
                          (buf[2*(n)+0] << 16) | (buf[2*(n)+1] << 24))
30
0
#define getu32z(buf, n)  (uint32_t)((buf[2*(n)-2]) | (buf[2*(n)-1] << 8) | \
31
0
                                    (buf[2*(n)+0] << 16) | (buf[2*(n)+1] << 24))
32
#define getstringz(to, from, s, e)                      \
33
0
    (void)memcpy(to, from+2*(s)-2, 2*((e)-(s)+1))
34
35
#ifdef ZODIAC_ENABLE
36
struct header
37
{
38
    unsigned short sync;
39
    unsigned short id;
40
    unsigned short ndata;
41
    unsigned short flags;
42
    unsigned short csum;
43
};
44
45
static unsigned short zodiac_checksum(unsigned short *w, int n)
46
0
{
47
0
    unsigned short csum = 0;
48
49
0
    while (n-- > 0)
50
0
        csum += *(w++);
51
0
    return -csum;
52
0
}
53
54
static ssize_t end_write(int fd, void *d, size_t len)
55
/* write an array of shorts in little-endian format */
56
0
{
57
0
    unsigned char buf[BUFSIZ];
58
0
    short *data = (short *)d;
59
60
0
    size_t n;
61
0
    for (n = 0; n < (size_t)(len/2); n++)
62
0
        putle16(buf, n*2, data[n]);
63
0
    return write(fd, (char*)buf, len);
64
0
}
65
66
/* zodiac_spew - Takes a message type, an array of data words, and a length
67
 * for the array, and prepends a 5 word header (including checksum).
68
 * The data words are expected to be checksummed.
69
 */
70
static ssize_t zodiac_spew(struct gps_device_t *session, unsigned short type,
71
                           unsigned short *dat, int dlen)
72
0
{
73
0
    struct header h;
74
0
    int i;
75
0
    char buf[BUFSIZ];
76
77
0
    h.sync = 0x81ff;
78
0
    h.id = (unsigned short)type;
79
0
    h.ndata = (unsigned short)(dlen - 1);
80
0
    h.flags = 0;
81
0
    h.csum = zodiac_checksum((unsigned short *)&h, 4);
82
83
0
    if (!BAD_SOCKET(session->gpsdata.gps_fd)) {
84
0
        size_t hlen, datlen;
85
0
        hlen = sizeof(h);
86
0
        datlen = sizeof(unsigned short) * dlen;
87
0
        if (end_write(session->gpsdata.gps_fd, &h, hlen) != (ssize_t) hlen ||
88
0
            end_write(session->gpsdata.gps_fd, dat,
89
0
                      datlen) != (ssize_t) datlen) {
90
0
            GPSD_LOG(LOG_INFO, &session->context->errout,
91
0
                     "Reconfigure write failed\n");
92
0
            return -1;
93
0
        }
94
0
    }
95
96
0
    (void)snprintf(buf, sizeof(buf),
97
0
                   "%04x %04x %04x %04x %04x",
98
0
                   h.sync, h.id, h.ndata, h.flags, h.csum);
99
0
    for (i = 0; i < dlen; i++)
100
0
        str_appendf(buf, sizeof(buf), " %04x", dat[i]);
101
102
0
    GPSD_LOG(LOG_RAW, &session->context->errout,
103
0
             "Sent Zodiac packet: %s\n", buf);
104
105
0
    return 0;
106
0
}
107
108
static void send_rtcm(struct gps_device_t *session,
109
                      const char *rtcmbuf, size_t rtcmbytes)
110
0
{
111
0
    unsigned short data[34];
112
0
    int n = 1 + (int)(rtcmbytes / 2 + rtcmbytes % 2);
113
114
0
    if (session->driver.zodiac.sn++ > 32767)
115
0
        session->driver.zodiac.sn = 0;
116
117
0
    memset(data, 0, sizeof(data));
118
0
    data[0] = session->driver.zodiac.sn;        /* sequence number */
119
0
    memcpy(&data[1], rtcmbuf, rtcmbytes);
120
0
    data[n] = zodiac_checksum(data, n);
121
122
0
    (void)zodiac_spew(session, 1351, data, n + 1);
123
0
}
124
125
static ssize_t zodiac_send_rtcm(struct gps_device_t *session,
126
                                const char *rtcmbuf, size_t rtcmbytes)
127
0
{
128
0
    while (rtcmbytes > 0) {
129
0
        size_t len = (size_t) (rtcmbytes > 64 ? 64 : rtcmbytes);
130
0
        send_rtcm(session, rtcmbuf, len);
131
0
        rtcmbytes -= len;
132
0
        rtcmbuf += len;
133
0
    }
134
0
    return 1;
135
0
}
136
137
0
#define getzword(n)     get16z(session->lexer.outbuffer, n)
138
0
#define getzu16(n)      getu16z(session->lexer.outbuffer, n)
139
0
#define getzlong(n)     get32z(session->lexer.outbuffer, n)
140
0
#define getzu32(n)      getu32z(session->lexer.outbuffer, n)
141
142
static gps_mask_t handle1000(struct gps_device_t *session)
143
/* time-position-velocity report */
144
0
{
145
0
    gps_mask_t mask;
146
0
    struct tm unpacked_date = {0};
147
0
    int datum;
148
0
    char ts_buf[TIMESPEC_LEN];
149
150
    // ticks                      = getzlong(6);
151
    // sequence                   = getzword(8);
152
    // measurement_sequence       = getzword(9);
153
0
    session->newdata.status = (getzword(10) & 0x1c) ? 0 : 1;
154
0
    if (0 != session->newdata.status) {
155
0
        session->newdata.mode = (getzword(10) & 1) ? MODE_2D : MODE_3D;
156
0
    } else {
157
0
        session->newdata.mode = MODE_NO_FIX;
158
0
    }
159
160
    // solution_type                 = getzword(11);
161
0
    session->gpsdata.satellites_used = (int)getzword(12);
162
    // polar_navigation              = getzword(13);
163
0
    session->context->gps_week = (unsigned short)getzword(14);
164
    // gps_seconds                   = getzlong(15);
165
    // gps_nanoseconds               = getzlong(17);
166
0
    unpacked_date.tm_mday = (int)getzword(19);
167
0
    unpacked_date.tm_mon = (int)getzword(20) - 1;
168
0
    unpacked_date.tm_year = (int)getzword(21) - 1900;
169
0
    unpacked_date.tm_hour = (int)getzword(22);
170
0
    unpacked_date.tm_min = (int)getzword(23);
171
0
    unpacked_date.tm_sec = (int)getzword(24);
172
0
    session->newdata.time.tv_sec = mkgmtime(&unpacked_date);
173
0
    session->newdata.time.tv_nsec = getzu32(25);
174
0
    session->newdata.latitude = ((long)getzlong(27)) * RAD_2_DEG * 1e-8;
175
0
    session->newdata.longitude = ((long)getzlong(29)) * RAD_2_DEG * 1e-8;
176
    /*
177
     * The Rockwell Jupiter TU30-D140 reports altitude as uncorrected height
178
     * above WGS84 geoid.  The Zodiac binary protocol manual does not
179
     * specify whether word 31 is geodetic or WGS 84.
180
     * Here we assume altitude is always wgs84.
181
     */
182
0
    session->newdata.altHAE = ((long)getzlong(31)) * 1e-2;
183
0
    session->newdata.geoid_sep = ((short)getzword(33)) * 1e-2;
184
0
    session->newdata.speed = (int)getzlong(34) * 1e-2;
185
0
    session->newdata.track = (int)getzword(36) * RAD_2_DEG * 1e-3;
186
0
    session->newdata.magnetic_var = ((short)getzword(37)) * RAD_2_DEG * 1e-4;
187
0
    session->newdata.climb = ((short)getzword(38)) * 1e-2;
188
0
    datum = getzword(39);
189
0
    datum_code_string(datum, session->newdata.datum,
190
0
                      sizeof(session->newdata.datum));
191
    /*
192
     * The manual says these are 1-sigma.  Device reports only eph, circular
193
     * error.  Let gpsd_model_error() do the rest
194
     */
195
0
    session->newdata.eph = (int)getzlong(40) * 1e-2 * GPSD_CONFIDENCE;
196
0
    session->newdata.epv = (int)getzlong(42) * 1e-2 * GPSD_CONFIDENCE;
197
0
    session->newdata.ept = (int)getzlong(44) * 1e-2 * GPSD_CONFIDENCE;
198
0
    session->newdata.eps = (int)getzword(46) * 1e-2 * GPSD_CONFIDENCE;
199
    /* clock_bias                  = (int)getzlong(47) * 1e-2; */
200
    /* clock_bias_sd               = (int)getzlong(49) * 1e-2; */
201
    /* clock_drift                 = (int)getzlong(51) * 1e-2; */
202
    /* clock_drift_sd              = (int)getzlong(53) * 1e-2; */
203
204
0
    mask = TIME_SET | NTPTIME_IS | LATLON_SET | ALTITUDE_SET | CLIMB_SET |
205
0
           SPEED_SET | TRACK_SET | STATUS_SET | MODE_SET |
206
0
           HERR_SET | SPEEDERR_SET | VERR_SET;
207
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
208
0
             "1000: time=%s lat=%.2f lon=%.2f altHAE=%.2f track=%.2f "
209
0
             "speed=%.2f climb=%.2f mode=%d status=%d\n",
210
0
             timespec_str(&session->newdata.time, ts_buf, sizeof(ts_buf)),
211
0
             session->newdata.latitude,
212
0
             session->newdata.longitude, session->newdata.altHAE,
213
0
             session->newdata.track, session->newdata.speed,
214
0
             session->newdata.climb, session->newdata.mode,
215
0
             session->newdata.status);
216
0
    return mask;
217
0
}
218
219
/* Message 1002: Channel Summary Message */
220
static gps_mask_t handle1002(struct gps_device_t *session)
221
0
{
222
0
    int i;
223
0
    timespec_t ts_tow;
224
225
    /* ticks                      = getzlong(6); */
226
    /* sequence                   = getzword(8); */
227
    /* measurement_sequence       = getzword(9); */
228
0
    unsigned short gps_week = getzu16(10);
229
0
    time_t gps_seconds = (time_t)getzu32(11);
230
0
    unsigned long gps_nanoseconds = getzu32(13);
231
0
    char ts_buf[TIMESPEC_LEN];
232
233
    /* Note: this week counter is not limited to 10 bits. */
234
0
    session->context->gps_week = gps_week;
235
0
    session->gpsdata.satellites_used = 0;
236
0
    for (i = 0; i < ZODIAC_CHANNELS; i++) {
237
0
        int status, prn;
238
0
        session->driver.zodiac.Zv[i] = status = (int)getzword(15 + (3 * i));
239
0
        session->driver.zodiac.Zs[i] = prn = (int)getzword(16 + (3 * i));
240
241
0
        if (status & 1)
242
0
            session->gpsdata.satellites_used++;
243
244
0
        session->gpsdata.skyview[i].PRN = (short)prn;
245
0
        session->gpsdata.skyview[i].ss = (float)getzword(17 + (3 * i));
246
0
        session->gpsdata.skyview[i].used = (bool)(status & 1);
247
0
    }
248
0
    ts_tow.tv_sec = gps_seconds;
249
0
    ts_tow.tv_nsec = gps_nanoseconds;
250
0
    session->gpsdata.skyview_time = gpsd_gpstime_resolv(session, gps_week,
251
0
                                                        ts_tow);
252
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
253
0
             "1002: visible=%d used=%d mask={SATELLITE|USED} time %s\n",
254
0
             session->gpsdata.satellites_visible,
255
0
             session->gpsdata.satellites_used,
256
0
             timespec_str(&session->gpsdata.skyview_time, ts_buf,
257
0
                          sizeof(ts_buf)));
258
0
    return SATELLITE_SET | USED_IS;
259
0
}
260
261
static gps_mask_t handle1003(struct gps_device_t *session)
262
/* skyview report */
263
0
{
264
0
    int i, n;
265
0
    gps_mask_t mask = 0;
266
267
    /* The Polaris (and probably the DAGR) emit some strange variant of
268
     * this message which causes gpsd to crash filtering on impossible
269
     * number of satellites avoids this */
270
0
    n = (int)getzword(14);
271
0
    if ((n < 0) || (n > 12))
272
0
        return 0;
273
274
0
    gpsd_zero_satellites(&session->gpsdata);
275
276
    /* ticks              = getzlong(6); */
277
    /* sequence           = getzword(8); */
278
0
    session->gpsdata.dop.gdop = (unsigned int)getzword(9) * 1e-2;
279
0
    session->gpsdata.dop.pdop = (unsigned int)getzword(10) * 1e-2;
280
0
    session->gpsdata.dop.hdop = (unsigned int)getzword(11) * 1e-2;
281
0
    session->gpsdata.dop.vdop = (unsigned int)getzword(12) * 1e-2;
282
0
    session->gpsdata.dop.tdop = (unsigned int)getzword(13) * 1e-2;
283
0
    mask |= DOP_SET;
284
0
    session->gpsdata.satellites_visible = n;
285
286
0
    for (i = 0; i < ZODIAC_CHANNELS; i++) {
287
0
        if (i < session->gpsdata.satellites_visible) {
288
0
            session->gpsdata.skyview[i].PRN = (short)getzword(15 + (3 * i));
289
0
            session->gpsdata.skyview[i].azimuth =
290
0
                (((double)getzword(16 + (3 * i))) * RAD_2_DEG * 1e-4);
291
0
            if (session->gpsdata.skyview[i].azimuth < 0)
292
0
                session->gpsdata.skyview[i].azimuth += 360;
293
0
            session->gpsdata.skyview[i].elevation =
294
0
                (((double)getzword(17 + (3 * i))) * RAD_2_DEG * 1e-4);
295
0
        } else {
296
0
            session->gpsdata.skyview[i].PRN = 0;
297
0
            session->gpsdata.skyview[i].azimuth = NAN;
298
0
            session->gpsdata.skyview[i].elevation = NAN;
299
0
            session->gpsdata.skyview[i].ss = NAN;
300
0
        }
301
0
    }
302
0
    session->gpsdata.skyview_time.tv_sec = 0;
303
0
    session->gpsdata.skyview_time.tv_nsec = 0;
304
0
    mask |= SATELLITE_SET;
305
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
306
0
             "NAVDOP: visible=%d gdop=%.2f pdop=%.2f "
307
0
             "hdop=%.2f vdop=%.2f tdop=%.2f mask={SATELLITE|DOP}\n",
308
0
             session->gpsdata.satellites_visible,
309
0
             session->gpsdata.dop.gdop,
310
0
             session->gpsdata.dop.hdop,
311
0
             session->gpsdata.dop.vdop,
312
0
             session->gpsdata.dop.pdop, session->gpsdata.dop.tdop);
313
0
    return mask;
314
0
}
315
316
static void handle1005(struct gps_device_t *session UNUSED)
317
/* fix quality report */
318
0
{
319
    /* ticks              = getzlong(6); */
320
    /* sequence           = getzword(8); */
321
0
    int numcorrections = (int)getzword(12);
322
323
0
    if (MODE_NO_FIX == session->newdata.mode)
324
0
        session->newdata.status = STATUS_UNK;
325
0
    else if (0 == numcorrections)
326
0
        session->newdata.status = STATUS_GPS;
327
0
    else
328
0
        session->newdata.status = STATUS_DGPS;
329
0
}
330
331
static gps_mask_t handle1011(struct gps_device_t *session)
332
/* version report */
333
0
{
334
    /*
335
     * This is UNTESTED -- but harmless if buggy.  Added to support
336
     * client querying of the ID with firmware version in 2006.
337
     * The Zodiac is supposed to send one of these messages on startup.
338
     */
339
    /* software version field */
340
0
    getstringz(session->subtype, session->lexer.outbuffer, 19, 28);
341
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
342
0
             "1011: subtype=%s mask={DEVICEID}\n",
343
0
             session->subtype);
344
0
    return DEVICEID_SET;
345
0
}
346
347
348
static void handle1108(struct gps_device_t *session)
349
/* leap-second correction report */
350
0
{
351
    /* ticks              = getzlong(6); */
352
    /* sequence           = getzword(8); */
353
    /* utc_week_seconds   = getzlong(14); */
354
    /* leap_nanoseconds   = getzlong(17); */
355
0
    if ((int)(getzword(19) & 3) == 3) {
356
0
        session->context->valid |= LEAP_SECOND_VALID;
357
0
        session->context->leap_seconds = (int)getzword(16);
358
0
    }
359
0
}
360
361
static gps_mask_t zodiac_analyze(struct gps_device_t *session)
362
0
{
363
0
    unsigned int id =
364
0
        (unsigned int)((session->lexer.outbuffer[3] << 8) |
365
0
                       session->lexer.outbuffer[2]);
366
    /*
367
     * The guard looks superfluous, but it keeps the rather expensive
368
     * gpsd_packetdump() function from being called even when the debug
369
     * level does not actually require it.
370
     */
371
0
    if (session->context->errout.debug >= LOG_RAW)
372
0
        GPSD_LOG(LOG_RAW, &session->context->errout,
373
0
                 "Raw Zodiac packet type %d length %zd: %s\n",
374
0
                 id, session->lexer.outbuflen, gpsd_prettydump(session));
375
376
0
    if (session->lexer.outbuflen < 10)
377
0
        return 0;
378
379
    /*
380
     * Normal cycle for these devices is 1001 1002.
381
     * We count 1001 as end of cycle because 1002 doesn't
382
     * carry fix information.
383
     */
384
0
    session->cycle_end_reliable = true;
385
386
0
    switch (id) {
387
0
    case 1000:
388
0
        return handle1000(session) | (CLEAR_IS | REPORT_IS);
389
0
    case 1002:
390
0
        return handle1002(session);
391
0
    case 1003:
392
0
        return handle1003(session);
393
0
    case 1005:
394
0
        handle1005(session);
395
0
        return 0;
396
0
    case 1011:
397
0
        return handle1011(session);
398
0
    case 1108:
399
0
        handle1108(session);
400
0
        return 0;
401
0
    default:
402
0
        return 0;
403
0
    }
404
0
}
405
406
static ssize_t zodiac_control_send(struct gps_device_t *session,
407
                                   char *msg, size_t len)
408
0
{
409
0
    unsigned short shortwords[256];
410
411
0
#define min(x,y)        ((x) < (y) ? x : y)
412
    /*
413
     * We used to just cast msg to an unsigned short pointer.
414
     * This can fail on word-oriented architectures like a SPARC.
415
     */
416
0
    memcpy((char *)shortwords, msg, min(sizeof(shortwords), len));
417
418
    /* and if len isn't even, it's your own fault */
419
0
    return zodiac_spew(session, shortwords[0], shortwords + 1,
420
0
                       (int)(len / 2 - 1));
421
0
}
422
423
static bool zodiac_speed_switch(struct gps_device_t *session,
424
                                speed_t speed, char parity, int stopbits)
425
0
{
426
0
    unsigned short data[15];
427
428
0
    if (session->driver.zodiac.sn++ > 32767)
429
0
        session->driver.zodiac.sn = 0;
430
431
0
    switch (parity) {
432
0
    case 'E':
433
0
    case 2:
434
0
        parity = (char)2;
435
0
        break;
436
0
    case 'O':
437
0
    case 1:
438
0
        parity = (char)1;
439
0
        break;
440
0
    case 'N':
441
0
    case 0:
442
0
    default:
443
0
        parity = (char)0;
444
0
        break;
445
0
    }
446
447
0
    memset(data, 0, sizeof(data));
448
    /* data is the part of the message starting at word 6 */
449
0
    data[0] = session->driver.zodiac.sn;        /* sequence number */
450
0
    data[1] = 1;                /* port 1 data valid */
451
0
    data[2] = (unsigned short)parity;   /* port 1 character width (8 bits) */
452
    /* port 1 stop bits (1 stopbit) */
453
0
    data[3] = (unsigned short)(stopbits - 1);
454
0
    data[4] = 0;                /* port 1 parity (none) */
455
    /* port 1 speed */
456
0
    data[5] = (unsigned short)(round(log((double)speed / 300) / GPS_LN2) + 1);
457
0
    data[14] = zodiac_checksum(data, 14);
458
459
0
    (void)zodiac_spew(session, 1330, data, 15);
460
0
    return true;                /* it would be nice to error-check this */
461
0
}
462
463
static double zodiac_time_offset(struct gps_device_t *session UNUSED)
464
0
{
465
    /* Removing/changing the magic number below is likely to disturb
466
     * the handling of the 1pps signal from the gps device. The regression
467
     * tests and simple gps applications do not detect this. A live test
468
     * with the 1pps signal active is required. */
469
0
    return 1.1;
470
0
}
471
472
/* this is everything we export */
473
/* *INDENT-OFF* */
474
const struct gps_type_t driver_zodiac =
475
{
476
    .type_name      = "Zodiac",            // full name of type
477
    .packet_type    = ZODIAC_PACKET,       // associated lexer packet type
478
    .flags          = DRIVER_STICKY,       // no flags set
479
    .trigger        = NULL,                // no trigger
480
    .channels       = 12,                  // consumer-grade GPS
481
    .probe_detect   = NULL,                // no probe
482
    .get_packet     = packet_get1,         // use the generic packet getter
483
    .parse_packet   = zodiac_analyze,      // parse message packets
484
    .rtcm_writer    = zodiac_send_rtcm,    // send DGPS correction
485
    .init_query     = NULL,                // non-perturbing initial query
486
    .event_hook     = NULL,                // no configuration
487
    .speed_switcher = zodiac_speed_switch, // we can change baud rate
488
    .mode_switcher  = NULL,                // no mode switcher
489
    .rate_switcher  = NULL,                // no sample-rate switcher
490
    .min_cycle.tv_sec  = 1,                // not relevant, no rate switch
491
    .min_cycle.tv_nsec = 0,                // not relevant, no rate switch
492
    .control_send   = zodiac_control_send, // for gpsctl and friends
493
    .time_offset     = zodiac_time_offset, // compute NTO fudge factor
494
};
495
/* *INDENT-ON* */
496
497
#endif /* ZODIAC_ENABLE */
498
499
// vim: set expandtab shiftwidth=4