summaryrefslogtreecommitdiff
path: root/driver_zodiac.c
diff options
context:
space:
mode:
Diffstat (limited to 'driver_zodiac.c')
-rw-r--r--driver_zodiac.c496
1 files changed, 496 insertions, 0 deletions
diff --git a/driver_zodiac.c b/driver_zodiac.c
new file mode 100644
index 00000000..4afcc211
--- /dev/null
+++ b/driver_zodiac.c
@@ -0,0 +1,496 @@
+/* $Id$ */
+/*
+ * Handle the Rockwell binary packet format supported by the old Zodiac chipset
+ */
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <math.h>
+#include "gpsd_config.h"
+#include "gpsd.h"
+
+#include "bits.h"
+
+#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;
+}
+
+/* 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 */
+#if defined (WORDS_BIGENDIAN)
+/* data is assumed to contain len/2 unsigned short words
+ * we change the endianness to little, when needed.
+ */
+static int end_write(int fd, void *d, int len)
+{
+ char buf[BUFSIZ];
+ char *p = buf;
+ char *data = (char *)d;
+ size_t n = (size_t)len;
+
+ while (n>0) {
+ *p++ = *(data+1); *p++ = *data;
+ data += 2; n -= 2;
+ }
+ return write(fd, buf, len);
+}
+#else
+#define end_write write
+#endif
+
+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);
+
+#ifdef ALLOW_RECONFIGURE
+ if (session->gpsdata.gps_fd != -1) {
+ 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_report(LOG_RAW, "Reconfigure write failed\n");
+ return -1;
+ }
+ }
+#endif /* ALLOW_RECONFIGURE */
+
+ (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++)
+ (void)snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
+ " %04x", dat[i]);
+
+ gpsd_report(LOG_RAW, "Sent Zodiac packet: %s\n",buf);
+
+ return 0;
+}
+
+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) */
+ data[3] = (unsigned short)(stopbits-1); /* port 1 stop bits (1 stopbit) */
+ data[4] = 0; /* port 1 parity (none) */
+ data[5] = (unsigned short)(round(log((double)speed/300)/M_LN2)+1); /* port 1 speed */
+ data[14] = zodiac_checksum(data, 14);
+
+ (void)zodiac_spew(session, 1330, data, 15);
+#ifdef ALLOW_RECONFIGURE
+ return true; /* it would be nice to error-check this */
+#else
+ return false;
+#endif /* ALLOW_RECONFIGURE */
+}
+
+static ssize_t zodiac_control_send(struct gps_device_t *session,
+ char *msg, size_t len)
+{
+ unsigned short *shortwords = (unsigned short *)msg;
+
+ /* and if len isn't even, it's your own fault */
+ return zodiac_spew(session, shortwords[0], shortwords+1, (int)(len/2-1));
+}
+
+static void send_rtcm(struct gps_device_t *session,
+ 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,
+ char *rtcmbuf, size_t rtcmbytes)
+{
+ size_t len;
+
+ while (rtcmbytes > 0) {
+ len = (size_t)(rtcmbytes>64?64:rtcmbytes);
+ send_rtcm(session, rtcmbuf, len);
+ rtcmbytes -= len;
+ rtcmbuf += len;
+ }
+ return 1;
+}
+
+#define getzword(n) getwordz(session->packet.outbuffer, n)
+#define getzlong(n) getlongz(session->packet.outbuffer, n)
+
+static gps_mask_t handle1000(struct gps_device_t *session)
+{
+ double subseconds;
+ struct tm unpacked_date;
+ /* ticks = getzlong(6); */
+ /* sequence = getzword(8); */
+ /* measurement_sequence = getzword(9); */
+ /*@ -boolops -predboolothers @*/
+ session->gpsdata.status = (getzword(10) & 0x1c) ? 0 : 1;
+ if (session->gpsdata.status != 0)
+ session->gpsdata.fix.mode = (getzword(10) & 1) ? MODE_2D : MODE_3D;
+ else
+ session->gpsdata.fix.mode = MODE_NO_FIX;
+ /*@ +boolops -predboolothers @*/
+
+ /* solution_type = getzword(11); */
+ session->gpsdata.satellites_used = (int)getzword(12);
+ /* polar_navigation = getzword(13); */
+ /* gps_week = 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);
+ subseconds = (int)getzlong(25) / 1e9;
+ /*@ -compdef */
+ session->gpsdata.fix.time = session->gpsdata.sentence_time =
+ (double)mkgmtime(&unpacked_date) + subseconds;
+ /*@ +compdef */
+#ifdef NTPSHM_ENABLE
+ /* 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. */
+ if (session->context->enable_ntpshm && session->gpsdata.fix.mode > MODE_NO_FIX)
+ (void)ntpshm_put(session, session->gpsdata.fix.time + 1.1);
+#endif
+ /*@ -type @*/
+ session->gpsdata.fix.latitude = ((long)getzlong(27)) * RAD_2_DEG * 1e-8;
+ session->gpsdata.fix.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.
+ */
+ session->gpsdata.fix.altitude = ((long)getzlong(31)) * 1e-2;
+ /*@ +type @*/
+ session->gpsdata.separation = ((short)getzword(33)) * 1e-2;
+ session->gpsdata.fix.altitude -= session->gpsdata.separation;
+ session->gpsdata.fix.speed = (int)getzlong(34) * 1e-2;
+ session->gpsdata.fix.track = (int)getzword(36) * RAD_2_DEG * 1e-3;
+ session->mag_var = ((short)getzword(37)) * RAD_2_DEG * 1e-4;
+ session->gpsdata.fix.climb = ((short)getzword(38)) * 1e-2;
+ /* map_datum = getzword(39); */
+ /* manual says these are 1-sigma */
+ session->gpsdata.fix.eph = (int)getzlong(40) * 1e-2 * GPSD_CONFIDENCE;
+ session->gpsdata.fix.epv = (int)getzlong(42) * 1e-2 * GPSD_CONFIDENCE;
+ session->gpsdata.fix.ept = (int)getzlong(44) * 1e-2 * GPSD_CONFIDENCE;
+ session->gpsdata.fix.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; */
+
+#if 0
+ gpsd_report(LOG_INF, "date: %lf\n", session->gpsdata.fix.time);
+ gpsd_report(LOG_INF, " solution invalid:\n");
+ gpsd_report(LOG_INF, " altitude: %d\n", (getzword(10) & 1) ? 1 : 0);
+ gpsd_report(LOG_INF, " no diff gps: %d\n", (getzword(10) & 2) ? 1 : 0);
+ gpsd_report(LOG_INF, " not enough satellites: %d\n", (getzword(10) & 4) ? 1 : 0);
+ gpsd_report(LOG_INF, " exceed max EHPE: %d\n", (getzword(10) & 8) ? 1 : 0);
+ gpsd_report(LOG_INF, " exceed max EVPE: %d\n", (getzword(10) & 16) ? 1 : 0);
+ gpsd_report(LOG_INF, " solution type:\n");
+ gpsd_report(LOG_INF, " propagated: %d\n", (getzword(11) & 1) ? 1 : 0);
+ gpsd_report(LOG_INF, " altitude: %d\n", (getzword(11) & 2) ? 1 : 0);
+ gpsd_report(LOG_INF, " differential: %d\n", (getzword(11) & 4) ? 1 : 0);
+ gpsd_report(LOG_INF, "Number of measurements in solution: %d\n", getzword(12));
+ gpsd_report(LOG_INF, "Lat: %f\n", getzlong(27) * RAD_2_DEG * 1e-8);
+ gpsd_report(LOG_INF, "Lon: %f\n", getzlong(29) * RAD_2_DEG * 1e-8);
+ gpsd_report(LOG_INF, "Alt: %f\n", (double) getzlong(31) * 1e-2);
+ gpsd_report(LOG_INF, "Speed: %f\n", (double) getzlong(34) * 1e-2 * MPS_TO_KNOTS);
+ gpsd_report(LOG_INF, "Map datum: %d\n", getzword(39));
+ gpsd_report(LOG_INF, "Magnetic variation: %f\n", getzword(37) * RAD_2_DEG * 1e-4);
+ gpsd_report(LOG_INF, "Course: %f\n", getzword(36) * RAD_2_DEG * 1e-4);
+ gpsd_report(LOG_INF, "Separation: %f\n", getzword(33) * 1e-2);
+#endif
+
+ session->gpsdata.sentence_length = 55;
+ return TIME_SET|LATLON_SET|ALTITUDE_SET|CLIMB_SET|SPEED_SET|TRACK_SET|STATUS_SET|MODE_SET|CYCLE_START_SET; /* |HERR_SET|VERR_SET|SPEEDERR_SET */
+}
+
+static gps_mask_t handle1002(struct gps_device_t *session)
+{
+ int i, j, status, prn;
+
+ session->gpsdata.satellites_used = 0;
+ memset(session->gpsdata.used,0,sizeof(session->gpsdata.used));
+ /* ticks = getzlong(6); */
+ /* sequence = getzword(8); */
+ /* measurement_sequence = getzword(9); */
+ /* gps_week = getzword(10); */
+ /* gps_seconds = getzlong(11); */
+ /* gps_nanoseconds = getzlong(13); */
+ for (i = 0; i < ZODIAC_CHANNELS; i++) {
+ /*@ -type @*/
+ session->driver.zodiac.Zv[i] = status = (int)getzword(15 + (3 * i));
+ session->driver.zodiac.Zs[i] = prn = (int)getzword(16 + (3 * i));
+ /*@ +type @*/
+#if 0
+ gpsd_report(LOG_INF, "Sat%02d:\n", i);
+ gpsd_report(LOG_INF, " used:%d\n", (status & 1) ? 1 : 0);
+ gpsd_report(LOG_INF, " eph:%d\n", (status & 2) ? 1 : 0);
+ gpsd_report(LOG_INF, " val:%d\n", (status & 4) ? 1 : 0);
+ gpsd_report(LOG_INF, " dgps:%d\n", (status & 8) ? 1 : 0);
+ gpsd_report(LOG_INF, " PRN:%d\n", prn);
+ gpsd_report(LOG_INF, " C/No:%d\n", getzword(17 + (3 * i)));
+#endif
+ if (status & 1)
+ session->gpsdata.used[session->gpsdata.satellites_used++] = prn;
+ for (j = 0; j < ZODIAC_CHANNELS; j++) {
+ if (session->gpsdata.PRN[j] != prn)
+ continue;
+ session->gpsdata.ss[j] = (int)getzword(17 + (3 * i));
+ break;
+ }
+ }
+ return SATELLITE_SET | USED_SET;
+}
+
+static gps_mask_t handle1003(struct gps_device_t *session)
+{
+ int i;
+
+ /* ticks = getzlong(6); */
+ /* sequence = getzword(8); */
+ session->gpsdata.gdop = (unsigned int)getzword(9) * 1e-2;
+ session->gpsdata.pdop = (unsigned int)getzword(10) * 1e-2;
+ session->gpsdata.hdop = (unsigned int)getzword(11) * 1e-2;
+ session->gpsdata.vdop = (unsigned int)getzword(12) * 1e-2;
+ session->gpsdata.tdop = (unsigned int)getzword(13) * 1e-2;
+ session->gpsdata.satellites = (int)getzword(14);
+
+ for (i = 0; i < ZODIAC_CHANNELS; i++) {
+ if (i < session->gpsdata.satellites) {
+ session->gpsdata.PRN[i] = (int)getzword(15 + (3 * i));
+ session->gpsdata.azimuth[i] = (int)(((short)getzword(16 + (3 * i))) * RAD_2_DEG * 1e-4);
+ if (session->gpsdata.azimuth[i] < 0)
+ session->gpsdata.azimuth[i] += 360;
+ session->gpsdata.elevation[i] = (int)(((short)getzword(17 + (3 * i))) * RAD_2_DEG * 1e-4);
+#if 0
+ gpsd_report(LOG_INF, "Sat%02d: PRN:%d az:%d el:%d\n",
+ i, getzword(15+(3 * i)),getzword(16+(3 * i)),getzword(17+(3 * i)));
+#endif
+ } else {
+ session->gpsdata.PRN[i] = 0;
+ session->gpsdata.azimuth[i] = 0;
+ session->gpsdata.elevation[i] = 0;
+ }
+ }
+ return SATELLITE_SET | HDOP_SET | VDOP_SET | PDOP_SET;
+}
+
+static void handle1005(struct gps_device_t *session UNUSED)
+{
+ /* ticks = getzlong(6); */
+ /* sequence = getzword(8); */
+ int numcorrections = (int)getzword(12);
+#if 0
+ int i;
+
+ gpsd_report(LOG_INF, "Packet: %d\n", session->driver.zodiac.sn);
+ gpsd_report(LOG_INF, "Station bad: %d\n", (getzword(9) & 1) ? 1 : 0);
+ gpsd_report(LOG_INF, "User disabled: %d\n", (getzword(9) & 2) ? 1 : 0);
+ gpsd_report(LOG_INF, "Station ID: %d\n", getzword(10));
+ gpsd_report(LOG_INF, "Age of last correction in seconds: %d\n", getzword(11));
+ gpsd_report(LOG_INF, "Number of corrections: %d\n", getzword(12));
+ for (i = 0; i < numcorrections; i++) {
+ gpsd_report(LOG_INF, "Sat%02d:\n", getzword(13+i) & 0x3f);
+ gpsd_report(LOG_INF, "ephemeris:%d\n", (getzword(13+i) & 64) ? 1 : 0);
+ gpsd_report(LOG_INF, "rtcm corrections:%d\n", (getzword(13+i) & 128) ? 1 : 0);
+ gpsd_report(LOG_INF, "rtcm udre:%d\n", (getzword(13+i) & 256) ? 1 : 0);
+ gpsd_report(LOG_INF, "sat health:%d\n", (getzword(13+i) & 512) ? 1 : 0);
+ gpsd_report(LOG_INF, "rtcm sat health:%d\n", (getzword(13+i) & 1024) ? 1 : 0);
+ gpsd_report(LOG_INF, "corrections state:%d\n", (getzword(13+i) & 2048) ? 1 : 0);
+ gpsd_report(LOG_INF, "iode mismatch:%d\n", (getzword(13+i) & 4096) ? 1 : 0);
+ }
+#endif
+ if (session->gpsdata.fix.mode == MODE_NO_FIX)
+ session->gpsdata.status = STATUS_NO_FIX;
+ else if (numcorrections == 0)
+ session->gpsdata.status = STATUS_FIX;
+ else
+ session->gpsdata.status = STATUS_DGPS_FIX;
+}
+
+static gps_mask_t handle1011(struct gps_device_t *session)
+{
+ /*
+ * 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.
+ */
+ getstringz(session->subtype,
+ session->packet.outbuffer,
+ 19, 28); /* software version field */
+ gpsd_report(LOG_INF, "Software version: %s\n", session->subtype);
+ return DEVICEID_SET;
+}
+
+
+static void handle1108(struct gps_device_t *session)
+{
+ /* ticks = getzlong(6); */
+ /* sequence = getzword(8); */
+ /* utc_week_seconds = getzlong(14); */
+ /* leap_nanoseconds = getzlong(17); */
+ if ((int)(getzword(19) & 3) == 3)
+ session->context->leap_seconds = (int)getzword(16);
+#if 0
+ gpsd_report(LOG_INF, "Leap seconds: %d.%09d\n", getzword(16), getzlong(17));
+ gpsd_report(LOG_INF, "UTC validity: %d\n", getzword(19) & 3);
+#endif
+}
+
+static gps_mask_t zodiac_analyze(struct gps_device_t *session)
+{
+ char buf[BUFSIZ];
+ int i;
+ unsigned int id = (unsigned int)((session->packet.outbuffer[3]<<8) | session->packet.outbuffer[2]);
+
+ if (session->packet.type != ZODIAC_PACKET) {
+ const struct gps_type_t **dp;
+ gpsd_report(LOG_PROG, "zodiac_analyze packet type %d\n",session->packet.type);
+ // Wrong packet type ?
+ // Maybe find a trigger just in case it's an Earthmate
+ gpsd_report(LOG_RAW+4, "Is this a trigger: %s ?\n", (char*)session->packet.outbuffer);
+
+ for (dp = gpsd_drivers; *dp; dp++) {
+ char *trigger = (*dp)->trigger;
+
+ if (trigger!=NULL && strncmp((char *)session->packet.outbuffer, trigger, strlen(trigger))==0 && isatty(session->gpsdata.gps_fd)!=0) {
+ gpsd_report(LOG_PROG, "found %s.\n", trigger);
+
+ (void)gpsd_switch_driver(session, (*dp)->type_name);
+ return 0;
+ }
+ }
+ return 0;
+ }
+
+ buf[0] = '\0';
+ for (i = 0; i < (int)session->packet.outbuflen; i++)
+ (void)snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
+ "%02x", (unsigned int)session->packet.outbuffer[i]);
+ (void)strlcat(buf, "\n", BUFSIZ);
+ gpsd_report(LOG_RAW, "Raw Zodiac packet type %d length %zd: %s\n",
+ id, session->packet.outbuflen, buf);
+
+ if (session->packet.outbuflen < 10)
+ return 0;
+
+ (void)snprintf(session->gpsdata.tag,sizeof(session->gpsdata.tag),"%u",id);
+
+ switch (id) {
+ case 1000:
+ return handle1000(session);
+ 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;
+ }
+}
+
+/* caller needs to specify a wrapup function */
+
+/* this is everything we export */
+const struct gps_type_t zodiac_binary =
+{
+ .type_name = "Zodiac binary", /* full name of type */
+ .packet_type = ZODIAC_PACKET, /* associated lexer packet type */
+ .trigger = NULL, /* no trigger */
+ .channels = 12, /* consumer-grade GPS */
+ .control_send = zodiac_control_send, /* for gpsctl and friends */
+ .probe_wakeup = NULL, /* no probe on baud rate change */
+ .probe_detect = NULL, /* no probe */
+ .probe_subtype = NULL, /* no initialization */
+#ifdef ALLOW_RECONFIGURE
+ .configurator = NULL, /* no configuration */
+#endif /* ALLOW_RECONFIGURE */
+ .get_packet = generic_get, /* use the generic packet getter */
+ .parse_packet = zodiac_analyze, /* parse message packets */
+ .rtcm_writer = zodiac_send_rtcm, /* send DGPS correction */
+ .speed_switcher = zodiac_speed_switch,/* we can change baud rate */
+ .mode_switcher = NULL, /* no mode switcher */
+ .rate_switcher = NULL, /* no sample-rate switcher */
+ .cycle_chars = -1, /* not relevant, no rate switch */
+#ifdef ALLOW_RECONFIGURE
+ .revert = NULL, /* no reversion hook */
+#endif /* ALLOW_RECONFIGURE */
+ .wrapup = NULL, /* caller might supply a close hook */
+ .cycle = 1, /* updates every second */
+};
+
+#endif /* ZODIAC_ENABLE */