123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- /*
- * Handle the Rockwell binary packet format supported by the old Zodiac chipset
- *
- * Week counters are not limited to 10 bits. It's unknown what
- * the firmware is doing to disambiguate them, if anything; it might just
- * be adding a fixed offset based on a hidden epoch value, in which case
- * unhappy things will occur on the next rollover.
- *
- * This file is Copyright 2010 by the GPSD project
- * SPDX-License-Identifier: BSD-2-clause
- */
- #include "gpsd_config.h" /* must be before all includes */
- #include <math.h>
- #include <stdbool.h>
- #include <stdio.h>
- #include <string.h>
- #include <unistd.h>
- #include "gpsd.h"
- #include "bits.h"
- #include "strfuncs.h"
- /* Zodiac protocol description uses 1-origin indexing by little-endian word */
- #define get16z(buf, n) ((buf[2*(n)-2]) | (buf[2*(n)-1] << 8))
- #define getu16z(buf, n) (uint16_t)((buf[2*(n)-2]) | (buf[2*(n)-1] << 8))
- #define get32z(buf, n) ((buf[2*(n)-2]) | (buf[2*(n)-1] << 8) | \
- (buf[2*(n)+0] << 16) | (buf[2*(n)+1] << 24))
- #define getu32z(buf, n) (uint32_t)((buf[2*(n)-2]) | (buf[2*(n)-1] << 8) | \
- (buf[2*(n)+0] << 16) | (buf[2*(n)+1] << 24))
- #define getstringz(to, from, s, e) \
- (void)memcpy(to, from+2*(s)-2, 2*((e)-(s)+1))
- #ifdef ZODIAC_ENABLE
- struct header
- {
- unsigned short sync;
- unsigned short id;
- unsigned short ndata;
- unsigned short flags;
- unsigned short csum;
- };
- static unsigned short zodiac_checksum(unsigned short *w, int n)
- {
- unsigned short csum = 0;
- while (n-- > 0)
- csum += *(w++);
- return -csum;
- }
- static ssize_t end_write(int fd, void *d, size_t len)
- /* write an array of shorts in little-endian format */
- {
- unsigned char buf[BUFSIZ];
- short *data = (short *)d;
- size_t n;
- for (n = 0; n < (size_t)(len/2); n++)
- putle16(buf, n*2, data[n]);
- return write(fd, (char*)buf, len);
- }
- /* zodiac_spew - Takes a message type, an array of data words, and a length
- * for the array, and prepends a 5 word header (including checksum).
- * The data words are expected to be checksummed.
- */
- static ssize_t zodiac_spew(struct gps_device_t *session, unsigned short type,
- unsigned short *dat, int dlen)
- {
- struct header h;
- int i;
- char buf[BUFSIZ];
- h.sync = 0x81ff;
- h.id = (unsigned short)type;
- h.ndata = (unsigned short)(dlen - 1);
- h.flags = 0;
- h.csum = zodiac_checksum((unsigned short *)&h, 4);
- if (!BAD_SOCKET(session->gpsdata.gps_fd)) {
- size_t hlen, datlen;
- hlen = sizeof(h);
- datlen = sizeof(unsigned short) * dlen;
- if (end_write(session->gpsdata.gps_fd, &h, hlen) != (ssize_t) hlen ||
- end_write(session->gpsdata.gps_fd, dat,
- datlen) != (ssize_t) datlen) {
- GPSD_LOG(LOG_INFO, &session->context->errout,
- "Reconfigure write failed\n");
- return -1;
- }
- }
- (void)snprintf(buf, sizeof(buf),
- "%04x %04x %04x %04x %04x",
- h.sync, h.id, h.ndata, h.flags, h.csum);
- for (i = 0; i < dlen; i++)
- str_appendf(buf, sizeof(buf), " %04x", dat[i]);
- GPSD_LOG(LOG_RAW, &session->context->errout,
- "Sent Zodiac packet: %s\n", buf);
- return 0;
- }
- static void send_rtcm(struct gps_device_t *session,
- const char *rtcmbuf, size_t rtcmbytes)
- {
- unsigned short data[34];
- int n = 1 + (int)(rtcmbytes / 2 + rtcmbytes % 2);
- if (session->driver.zodiac.sn++ > 32767)
- session->driver.zodiac.sn = 0;
- memset(data, 0, sizeof(data));
- data[0] = session->driver.zodiac.sn; /* sequence number */
- memcpy(&data[1], rtcmbuf, rtcmbytes);
- data[n] = zodiac_checksum(data, n);
- (void)zodiac_spew(session, 1351, data, n + 1);
- }
- static ssize_t zodiac_send_rtcm(struct gps_device_t *session,
- const char *rtcmbuf, size_t rtcmbytes)
- {
- while (rtcmbytes > 0) {
- size_t len = (size_t) (rtcmbytes > 64 ? 64 : rtcmbytes);
- send_rtcm(session, rtcmbuf, len);
- rtcmbytes -= len;
- rtcmbuf += len;
- }
- return 1;
- }
- #define getzword(n) get16z(session->lexer.outbuffer, n)
- #define getzu16(n) getu16z(session->lexer.outbuffer, n)
- #define getzlong(n) get32z(session->lexer.outbuffer, n)
- #define getzu32(n) getu32z(session->lexer.outbuffer, n)
- static gps_mask_t handle1000(struct gps_device_t *session)
- /* time-position-velocity report */
- {
- gps_mask_t mask;
- struct tm unpacked_date;
- int datum;
- char ts_buf[TIMESPEC_LEN];
- /* ticks = getzlong(6); */
- /* sequence = getzword(8); */
- /* measurement_sequence = getzword(9); */
- session->newdata.status = (getzword(10) & 0x1c) ? 0 : 1;
- if (session->newdata.status != 0)
- session->newdata.mode = (getzword(10) & 1) ? MODE_2D : MODE_3D;
- else
- session->newdata.mode = MODE_NO_FIX;
- /* solution_type = getzword(11); */
- session->gpsdata.satellites_used = (int)getzword(12);
- /* polar_navigation = getzword(13); */
- session->context->gps_week = (unsigned short)getzword(14);
- /* gps_seconds = getzlong(15); */
- /* gps_nanoseconds = getzlong(17); */
- unpacked_date.tm_mday = (int)getzword(19);
- unpacked_date.tm_mon = (int)getzword(20) - 1;
- unpacked_date.tm_year = (int)getzword(21) - 1900;
- unpacked_date.tm_hour = (int)getzword(22);
- unpacked_date.tm_min = (int)getzword(23);
- unpacked_date.tm_sec = (int)getzword(24);
- unpacked_date.tm_isdst = 0;
- session->newdata.time.tv_sec = mkgmtime(&unpacked_date);
- session->newdata.time.tv_nsec = getzu32(25);
- session->newdata.latitude = ((long)getzlong(27)) * RAD_2_DEG * 1e-8;
- session->newdata.longitude = ((long)getzlong(29)) * RAD_2_DEG * 1e-8;
- /*
- * The Rockwell Jupiter TU30-D140 reports altitude as uncorrected height
- * above WGS84 geoid. The Zodiac binary protocol manual does not
- * specify whether word 31 is geodetic or WGS 84.
- * Here we assume altitude is always wgs84.
- */
- session->newdata.altHAE = ((long)getzlong(31)) * 1e-2;
- session->newdata.geoid_sep = ((short)getzword(33)) * 1e-2;
- session->newdata.speed = (int)getzlong(34) * 1e-2;
- session->newdata.track = (int)getzword(36) * RAD_2_DEG * 1e-3;
- session->newdata.magnetic_var = ((short)getzword(37)) * RAD_2_DEG * 1e-4;
- session->newdata.climb = ((short)getzword(38)) * 1e-2;
- datum = getzword(39);
- datum_code_string(datum, session->newdata.datum,
- sizeof(session->newdata.datum));
- /*
- * The manual says these are 1-sigma. Device reports only eph, circular
- * error. Let gpsd_model_error() do the rest
- */
- session->newdata.eph = (int)getzlong(40) * 1e-2 * GPSD_CONFIDENCE;
- session->newdata.epv = (int)getzlong(42) * 1e-2 * GPSD_CONFIDENCE;
- session->newdata.ept = (int)getzlong(44) * 1e-2 * GPSD_CONFIDENCE;
- session->newdata.eps = (int)getzword(46) * 1e-2 * GPSD_CONFIDENCE;
- /* clock_bias = (int)getzlong(47) * 1e-2; */
- /* clock_bias_sd = (int)getzlong(49) * 1e-2; */
- /* clock_drift = (int)getzlong(51) * 1e-2; */
- /* clock_drift_sd = (int)getzlong(53) * 1e-2; */
- mask = TIME_SET | NTPTIME_IS | LATLON_SET | ALTITUDE_SET | CLIMB_SET |
- SPEED_SET | TRACK_SET | STATUS_SET | MODE_SET |
- HERR_SET | SPEEDERR_SET | VERR_SET;
- GPSD_LOG(LOG_DATA, &session->context->errout,
- "1000: time=%s lat=%.2f lon=%.2f altHAE=%.2f track=%.2f "
- "speed=%.2f climb=%.2f mode=%d status=%d\n",
- timespec_str(&session->newdata.time, ts_buf, sizeof(ts_buf)),
- session->newdata.latitude,
- session->newdata.longitude, session->newdata.altHAE,
- session->newdata.track, session->newdata.speed,
- session->newdata.climb, session->newdata.mode,
- session->newdata.status);
- return mask;
- }
- /* Message 1002: Channel Summary Message */
- static gps_mask_t handle1002(struct gps_device_t *session)
- {
- int i;
- timespec_t ts_tow;
- /* ticks = getzlong(6); */
- /* sequence = getzword(8); */
- /* measurement_sequence = getzword(9); */
- unsigned short gps_week = getzu16(10);
- time_t gps_seconds = (time_t)getzu32(11);
- unsigned long gps_nanoseconds = getzu32(13);
- char ts_buf[TIMESPEC_LEN];
- /* Note: this week counter is not limited to 10 bits. */
- session->context->gps_week = gps_week;
- session->gpsdata.satellites_used = 0;
- for (i = 0; i < ZODIAC_CHANNELS; i++) {
- int status, prn;
- session->driver.zodiac.Zv[i] = status = (int)getzword(15 + (3 * i));
- session->driver.zodiac.Zs[i] = prn = (int)getzword(16 + (3 * i));
- if (status & 1)
- session->gpsdata.satellites_used++;
- session->gpsdata.skyview[i].PRN = (short)prn;
- session->gpsdata.skyview[i].ss = (float)getzword(17 + (3 * i));
- session->gpsdata.skyview[i].used = (bool)(status & 1);
- }
- ts_tow.tv_sec = gps_seconds;
- ts_tow.tv_nsec = gps_nanoseconds;
- session->gpsdata.skyview_time = gpsd_gpstime_resolv(session, gps_week,
- ts_tow);
- GPSD_LOG(LOG_DATA, &session->context->errout,
- "1002: visible=%d used=%d mask={SATELLITE|USED} time %s\n",
- session->gpsdata.satellites_visible,
- session->gpsdata.satellites_used,
- timespec_str(&session->gpsdata.skyview_time, ts_buf,
- sizeof(ts_buf)));
- return SATELLITE_SET | USED_IS;
- }
- static gps_mask_t handle1003(struct gps_device_t *session)
- /* skyview report */
- {
- int i, n;
- /* The Polaris (and probably the DAGR) emit some strange variant of
- * this message which causes gpsd to crash filtering on impossible
- * number of satellites avoids this */
- n = (int)getzword(14);
- if ((n < 0) || (n > 12))
- return 0;
- gpsd_zero_satellites(&session->gpsdata);
- /* ticks = getzlong(6); */
- /* sequence = getzword(8); */
- session->gpsdata.dop.gdop = (unsigned int)getzword(9) * 1e-2;
- session->gpsdata.dop.pdop = (unsigned int)getzword(10) * 1e-2;
- session->gpsdata.dop.hdop = (unsigned int)getzword(11) * 1e-2;
- session->gpsdata.dop.vdop = (unsigned int)getzword(12) * 1e-2;
- session->gpsdata.dop.tdop = (unsigned int)getzword(13) * 1e-2;
- session->gpsdata.satellites_visible = n;
- for (i = 0; i < ZODIAC_CHANNELS; i++) {
- if (i < session->gpsdata.satellites_visible) {
- session->gpsdata.skyview[i].PRN = (short)getzword(15 + (3 * i));
- session->gpsdata.skyview[i].azimuth =
- (((double)getzword(16 + (3 * i))) * RAD_2_DEG * 1e-4);
- if (session->gpsdata.skyview[i].azimuth < 0)
- session->gpsdata.skyview[i].azimuth += 360;
- session->gpsdata.skyview[i].elevation =
- (((double)getzword(17 + (3 * i))) * RAD_2_DEG * 1e-4);
- } else {
- session->gpsdata.skyview[i].PRN = 0;
- session->gpsdata.skyview[i].azimuth = NAN;
- session->gpsdata.skyview[i].elevation = NAN;
- session->gpsdata.skyview[i].ss = NAN;
- }
- }
- session->gpsdata.skyview_time.tv_sec = 0;
- session->gpsdata.skyview_time.tv_nsec = 0;
- GPSD_LOG(LOG_DATA, &session->context->errout,
- "NAVDOP: visible=%d gdop=%.2f pdop=%.2f "
- "hdop=%.2f vdop=%.2f tdop=%.2f mask={SATELLITE|DOP}\n",
- session->gpsdata.satellites_visible,
- session->gpsdata.dop.gdop,
- session->gpsdata.dop.hdop,
- session->gpsdata.dop.vdop,
- session->gpsdata.dop.pdop, session->gpsdata.dop.tdop);
- return SATELLITE_SET | DOP_SET;
- }
- static void handle1005(struct gps_device_t *session UNUSED)
- /* fix quality report */
- {
- /* ticks = getzlong(6); */
- /* sequence = getzword(8); */
- int numcorrections = (int)getzword(12);
- if (session->newdata.mode == MODE_NO_FIX)
- session->newdata.status = STATUS_NO_FIX;
- else if (numcorrections == 0)
- session->newdata.status = STATUS_FIX;
- else
- session->newdata.status = STATUS_DGPS_FIX;
- }
- static gps_mask_t handle1011(struct gps_device_t *session)
- /* version report */
- {
- /*
- * This is UNTESTED -- but harmless if buggy. Added to support
- * client querying of the ID with firmware version in 2006.
- * The Zodiac is supposed to send one of these messages on startup.
- */
- /* software version field */
- getstringz(session->subtype, session->lexer.outbuffer, 19, 28);
- GPSD_LOG(LOG_DATA, &session->context->errout,
- "1011: subtype=%s mask={DEVICEID}\n",
- session->subtype);
- return DEVICEID_SET;
- }
- static void handle1108(struct gps_device_t *session)
- /* leap-second correction report */
- {
- /* ticks = getzlong(6); */
- /* sequence = getzword(8); */
- /* utc_week_seconds = getzlong(14); */
- /* leap_nanoseconds = getzlong(17); */
- if ((int)(getzword(19) & 3) == 3) {
- session->context->valid |= LEAP_SECOND_VALID;
- session->context->leap_seconds = (int)getzword(16);
- }
- }
- static gps_mask_t zodiac_analyze(struct gps_device_t *session)
- {
- unsigned int id =
- (unsigned int)((session->lexer.outbuffer[3] << 8) |
- session->lexer.outbuffer[2]);
- /*
- * The guard looks superfluous, but it keeps the rather expensive
- * gpsd_packetdump() function from being called even when the debug
- * level does not actually require it.
- */
- if (session->context->errout.debug >= LOG_RAW)
- GPSD_LOG(LOG_RAW, &session->context->errout,
- "Raw Zodiac packet type %d length %zd: %s\n",
- id, session->lexer.outbuflen, gpsd_prettydump(session));
- if (session->lexer.outbuflen < 10)
- return 0;
- /*
- * Normal cycle for these devices is 1001 1002.
- * We count 1001 as end of cycle because 1002 doesn't
- * carry fix information.
- */
- session->cycle_end_reliable = true;
- switch (id) {
- case 1000:
- return handle1000(session) | (CLEAR_IS | REPORT_IS);
- case 1002:
- return handle1002(session);
- case 1003:
- return handle1003(session);
- case 1005:
- handle1005(session);
- return 0;
- case 1011:
- return handle1011(session);
- case 1108:
- handle1108(session);
- return 0;
- default:
- return 0;
- }
- }
- static ssize_t zodiac_control_send(struct gps_device_t *session,
- char *msg, size_t len)
- {
- unsigned short shortwords[256];
- #define min(x,y) ((x) < (y) ? x : y)
- /*
- * We used to just cast msg to an unsigned short pointer.
- * This can fail on word-oriented architectures like a SPARC.
- */
- memcpy((char *)shortwords, msg, min(sizeof(shortwords), len));
- /* and if len isn't even, it's your own fault */
- return zodiac_spew(session, shortwords[0], shortwords + 1,
- (int)(len / 2 - 1));
- }
- static bool zodiac_speed_switch(struct gps_device_t *session,
- speed_t speed, char parity, int stopbits)
- {
- unsigned short data[15];
- if (session->driver.zodiac.sn++ > 32767)
- session->driver.zodiac.sn = 0;
- switch (parity) {
- case 'E':
- case 2:
- parity = (char)2;
- break;
- case 'O':
- case 1:
- parity = (char)1;
- break;
- case 'N':
- case 0:
- default:
- parity = (char)0;
- break;
- }
- memset(data, 0, sizeof(data));
- /* data is the part of the message starting at word 6 */
- data[0] = session->driver.zodiac.sn; /* sequence number */
- data[1] = 1; /* port 1 data valid */
- data[2] = (unsigned short)parity; /* port 1 character width (8 bits) */
- /* port 1 stop bits (1 stopbit) */
- data[3] = (unsigned short)(stopbits - 1);
- data[4] = 0; /* port 1 parity (none) */
- /* port 1 speed */
- data[5] = (unsigned short)(round(log((double)speed / 300) / GPS_LN2) + 1);
- data[14] = zodiac_checksum(data, 14);
- (void)zodiac_spew(session, 1330, data, 15);
- return true; /* it would be nice to error-check this */
- }
- static double zodiac_time_offset(struct gps_device_t *session UNUSED)
- {
- /* Removing/changing the magic number below is likely to disturb
- * the handling of the 1pps signal from the gps device. The regression
- * tests and simple gps applications do not detect this. A live test
- * with the 1pps signal active is required. */
- return 1.1;
- }
- /* this is everything we export */
- /* *INDENT-OFF* */
- const struct gps_type_t driver_zodiac =
- {
- .type_name = "Zodiac", /* full name of type */
- .packet_type = ZODIAC_PACKET, /* associated lexer packet type */
- .flags = DRIVER_STICKY, /* no flags set */
- .trigger = NULL, /* no trigger */
- .channels = 12, /* consumer-grade GPS */
- .probe_detect = NULL, /* no probe */
- .get_packet = generic_get, /* use the generic packet getter */
- .parse_packet = zodiac_analyze, /* parse message packets */
- .rtcm_writer = zodiac_send_rtcm, /* send DGPS correction */
- .init_query = NULL, /* non-perturbing initial query */
- .event_hook = NULL, /* no configuration */
- .speed_switcher = zodiac_speed_switch,/* we can change baud rate */
- .mode_switcher = NULL, /* no mode switcher */
- .rate_switcher = NULL, /* no sample-rate switcher */
- .min_cycle.tv_sec = 1, /* not relevant, no rate switch */
- .min_cycle.tv_nsec = 0, /* not relevant, no rate switch */
- .control_send = zodiac_control_send, /* for gpsctl and friends */
- .time_offset = zodiac_time_offset, /* compute NTO fudge factor */
- };
- /* *INDENT-ON* */
- #endif /* ZODIAC_ENABLE */
- // vim: set expandtab shiftwidth=4
|