summaryrefslogtreecommitdiff
path: root/driver_greis.c
diff options
context:
space:
mode:
authorGregory Fong <gregory.fong@virginorbit.com>2018-07-29 17:21:24 -0700
committerGary E. Miller <gem@rellim.com>2018-07-29 17:21:24 -0700
commitf41d577b6352e80919469ee1724a0b9ad4359e67 (patch)
tree7a96d9194937855ebaecc14bb254236d3ccc5940 /driver_greis.c
parentd1f79455a50f92b3dbf68c2ad5196423d80ed78a (diff)
downloadgpsd-f41d577b6352e80919469ee1724a0b9ad4359e67.tar.gz
Add GREIS (Javad) GPS driver.
All functional changes inside "#ifdef GREIS_ENABLE" Includes new regression tests. All regressions tests pass. Developed by Gregory Fong, with help and support from Virgin Orbit. Signed-off-by: Gary E. Miller <gem@rellim.com>
Diffstat (limited to 'driver_greis.c')
-rw-r--r--driver_greis.c963
1 files changed, 963 insertions, 0 deletions
diff --git a/driver_greis.c b/driver_greis.c
new file mode 100644
index 00000000..fd360fe8
--- /dev/null
+++ b/driver_greis.c
@@ -0,0 +1,963 @@
+/*
+ * A Javad GNSS Receiver External Interface Specification (GREIS) driver.
+ *
+ * Author(s):
+ * - Gregory Fong <gregory.fong@virginorbit.com>
+ *
+ * Documentation for GREIS can be found at:
+ * http://www.javad.com/downloads/javadgnss/manuals/GREIS/GREIS_Reference_Guide.pdf
+ *
+ * The version used for reference is that which
+ * "Reflects Firmware Version 3.6.7, Last revised: August 25, 2016".
+ *
+ * This assumes little endian byte order in messages, which is the default, but
+ * that is configurable. A future improvement could change to read the
+ * information in [MF] Message Format.
+ *
+ * This file is Copyright (c) 2017 Virgin Orbit
+ * SPDX-License-Identifier: BSD-2-clause
+ */
+
+#include <assert.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/select.h>
+
+#include "bits.h"
+#include "driver_greis.h"
+#include "gpsd.h"
+
+#if defined(GREIS_ENABLE) && defined(BINARY_ENABLE)
+
+#define HEADER_LENGTH 5
+
+static ssize_t greis_write(struct gps_device_t *session,
+ const char *msg, size_t msglen);
+static const char disable_messages[] = "\%dm\%dm";
+static const char get_vendor[] = "\%vendor\%print,/par/rcv/vendor";
+static const char get_ver[] = "\%ver\%print,rcv/ver";
+static const char set_update_rate_4hz[] = "\%msint\%set,/par/raw/msint,250";
+
+/* Where applicable, the order here is how these will be received per cycle. */
+/* TODO: stop hardcoding the cycle time, make it selectable */
+static const char enable_messages_4hz[] =
+ "\%em\%em,,jps/{RT,UO,GT,PV,SG,DP,SI,EL,AZ,EC,SS,ET}:0.25";
+
+/*
+ * GREIS message handlers. The checksum has been already confirmed valid in the
+ * packet acceptance logic, so we don't need to retest it here.
+ */
+
+/**
+ * Handle the message [RE] Reply
+ */
+static gps_mask_t greis_msg_RE(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ if (0 == memcmp(buf, "%ver%", 5)) {
+ strlcpy(session->subtype, (const char*)&buf[5],
+ sizeof(session->subtype));
+ gpsd_log(&session->context->errout, LOG_DATA,
+ "GREIS: RE, ->subtype: %s\n", session->subtype);
+ return DEVICEID_SET;
+ }
+
+ gpsd_log(&session->context->errout, LOG_INFO,
+ "GREIS: RE %3zd, reply: %.*s\n", len, (int)len, buf);
+ return 0;
+}
+
+/**
+ * Handle the message [ER] Reply
+ */
+static gps_mask_t greis_msg_ER(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: ER %3zd, reply: %.*s\n", len, (int)len, buf);
+ return 0;
+}
+
+/**
+ * Handle the message [~~](RT) Receiver Time.
+ */
+static gps_mask_t greis_msg_RT(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ if (len < 5) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: RT bad len %zu\n", len);
+ return 0;
+ }
+
+ session->driver.greis.rt_tod = getleu32(buf, 0);
+
+ session->driver.greis.seen_rt = true;
+ session->driver.greis.seen_az = false;
+ session->driver.greis.seen_ec = false;
+ session->driver.greis.seen_el = false;
+ session->driver.greis.seen_si = false;
+ gpsd_log(&session->context->errout, LOG_DATA,
+ "GREIS: RT, tod: %lu\n",
+ (unsigned long)session->driver.greis.rt_tod);
+
+ return CLEAR_IS;
+}
+
+/**
+ * Handle the message [UO] GPS UTC Time Parameters.
+ */
+static gps_mask_t greis_msg_UO(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ /*
+ * For additional details on these parameters and the computation done using
+ * them, refer to the Javad GREIS spec mentioned at the top of this file and
+ * also to ICD-GPS-200C, Revision IRN-200C-004 April 12, 2000. At the time
+ * of writing, that could be found at
+ * https://www.navcen.uscg.gov/pubs/gps/icd200/ICD200Cw1234.pdf .
+ */
+ uint32_t tot; /* Reference time of week [s] */
+ uint16_t wnt; /* Reference week number [dimensionless] */
+ int8_t dtls; /* Delta time due to leap seconds [s] */
+ uint8_t dn; /* 'Future' reference day number [1..7] */
+ uint16_t wnlsf; /* 'Future' reference week number [dimensionless] */
+ int8_t dtlsf; /* 'Future' delta time due to leap seconds [s] */
+
+ if (len < 24) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: UO bad len %zu\n", len);
+ return 0;
+ }
+
+ tot = getleu32(buf, 12);
+ wnt = getleu16(buf, 16);
+ dtls = getsb(buf, 18);
+ dn = getub(buf, 19);
+ wnlsf = getleu16(buf, 20);
+ dtlsf = getsb(buf, 22);
+ session->driver.greis.seen_uo = true;
+
+ /*
+ * See ICD-GPS-200C 20.3.3.5.2.4 "Universal Coordinated Time (UTC)".
+ * I totally ripped this off of driver_navcom.c. Might want to dedupe at
+ * some point.
+ */
+ if ((wnt % 256U) * 604800U + tot < wnlsf * 604800U + dn * 86400U) {
+ /* Current time is before effectivity time of the leap second event */
+ session->context->leap_seconds = dtls;
+ } else {
+ session->context->leap_seconds = dtlsf;
+ }
+
+ gpsd_log(&session->context->errout, LOG_DATA,
+ "GREIS: UO, leap_seconds: %d\n", session->context->leap_seconds);
+
+ return 0;
+}
+
+/**
+ * Handle the message [GT] GPS Time.
+ */
+static gps_mask_t greis_msg_GT(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ uint32_t tow; /* Time of week [ms] */
+ uint16_t wn; /* GPS week number (modulo 1024) [dimensionless] */
+
+ if (len < 7) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: GT bad len %zu\n", len);
+ return 0;
+ }
+
+ if (!session->driver.greis.seen_uo) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: can't use GT until after UO has supplied leap second data\n");
+ return 0;
+ }
+
+ tow = getleu32(buf, 0);
+ wn = getleu16(buf, 4);
+
+ session->newdata.time = gpsd_gpstime_resolve(session, wn, tow / 1000.0);
+
+ gpsd_log(&session->context->errout, LOG_DATA,
+ "GREIS: GT, tow: %u, wn: %u, time: %.2f\n", tow, wn,
+ session->newdata.time);
+
+ return TIME_SET | NTPTIME_IS | ONLINE_SET;
+}
+
+/**
+ * Handle the message [PV] Cartesian Position and Velocity.
+ */
+static gps_mask_t greis_msg_PV(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ double x, y, z; /* Cartesian coordinates [m] */
+ float p_sigma; /* Position spherical error probability (SEP) [m] */
+ float vx, vy, vz; /* Cartesian velocities [m/s] */
+ float v_sigma; /* Velocity SEP [m/s] */
+ uint8_t solution_type;
+
+ if (len < 46) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: PV bad len %zu\n", len);
+ return 0;
+ }
+
+ x = getled64((char *)buf, 0);
+ y = getled64((char *)buf, 8);
+ z = getled64((char *)buf, 16);
+ p_sigma = getlef32((char *)buf, 24);
+ vx = getlef32((char *)buf, 28);
+ vy = getlef32((char *)buf, 32);
+ vz = getlef32((char *)buf, 36);
+ v_sigma = getlef32((char *)buf, 40);
+ solution_type = getub(buf, 44);
+
+ session->newdata.ecef.x = x;
+ session->newdata.ecef.y = y;
+ session->newdata.ecef.z = z;
+ session->newdata.ecef.pAcc = p_sigma;
+ session->newdata.ecef.vx = vx;
+ session->newdata.ecef.vy = vy;
+ session->newdata.ecef.vz = vz;
+ session->newdata.ecef.vAcc = v_sigma;
+ ecef_to_wgs84fix(&session->newdata, &session->gpsdata.separation,
+ x, y, z, vx, vy, vz);
+
+ /* GREIS Reference Guide 3.4.2 "General Notes" part "Solution Types" */
+ if (solution_type > 0 && solution_type < 5) {
+ session->newdata.mode = MODE_3D;
+ if (solution_type > 1)
+ session->gpsdata.status = STATUS_DGPS_FIX;
+ else
+ session->gpsdata.status = STATUS_FIX;
+ }
+
+ gpsd_log(&session->context->errout, LOG_DATA,
+ "GREIS: PV, ECEF x=%.2f y=%.2f z=%.2f pAcc=%.2f\n",
+ session->newdata.ecef.x,
+ session->newdata.ecef.y,
+ session->newdata.ecef.z,
+ session->newdata.ecef.pAcc);
+
+ gpsd_log(&session->context->errout, LOG_DATA,
+ "GREIS: PV, ECEF vx=%.2f vy=%.2f vz=%.2f vAcc=%.2f\n",
+ session->newdata.ecef.vx,
+ session->newdata.ecef.vy,
+ session->newdata.ecef.vz,
+ session->newdata.ecef.vAcc);
+
+ gpsd_log(&session->context->errout, LOG_DATA,
+ "GREIS: PV, lat: %.2f, lon: %.2f, alt: %.2f, solution_type: %d\n",
+ session->newdata.latitude,
+ session->newdata.longitude, session->newdata.altitude,
+ solution_type);
+
+ return LATLON_SET | ALTITUDE_SET | CLIMB_SET | TRACK_SET | SPEED_SET |
+ MODE_SET | STATUS_SET | ECEF_SET | VECEF_SET;
+}
+
+/**
+ * Handle the message [SG] Position and Velocity RMS Errors.
+ */
+static gps_mask_t greis_msg_SG(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ float hpos; /* Horizontal position RMS error [m] */
+ float vpos; /* Vertical position RMS error [m] */
+ float hvel; /* Horizontal velocity RMS error [m/s] */
+ float vvel; /* Vertical velocity RMS error [m/s] */
+
+ if (len < 18) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: SG bad len %zu\n", len);
+ return 0;
+ }
+
+ hpos = getlef32((char *)buf, 0);
+ vpos = getlef32((char *)buf, 4);
+ hvel = getlef32((char *)buf, 8);
+ vvel = getlef32((char *)buf, 12);
+
+ /*
+ * All errors are RMS which can be approximated as 1 sigma, so we can just
+ * multiply to get the length used for GPSD confidence level.
+ *
+ * Make the simplifying assumption that error is the same for latitude and
+ * longitude, since GREIS does not provide those as precomputed components.
+ * A future improvement might be to compute the individual errors from the
+ * position covariance matrix, but only if this proves insufficient.
+ */
+ session->newdata.epx = session->newdata.epy =
+ hpos * (1 / sqrt(2)) * GPSD_CONFIDENCE;
+ session->newdata.epv = vpos * GPSD_CONFIDENCE;
+ session->newdata.eps = hvel * GPSD_CONFIDENCE;
+ session->newdata.epc = vvel * GPSD_CONFIDENCE;
+
+ gpsd_log(&session->context->errout, LOG_DATA,
+ "GREIS: SG, epx: %.2f, epy: %.2f, eps: %.2f, epc: %.2f\n",
+ session->newdata.epx, session->newdata.epy,
+ session->newdata.eps, session->newdata.epc);
+
+ return HERR_SET | VERR_SET | SPEEDERR_SET | CLIMBERR_SET;
+}
+
+/**
+ * Handle the message [DP] Dilution of Precision.
+ * Note that fill_dop() will handle the unset dops later.
+ */
+static gps_mask_t greis_msg_DP(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ if (len < 18) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: DP bad len %zu\n", len);
+ return 0;
+ }
+
+ /* clear so that computed DOPs get recomputed. */
+ gps_clear_dop(&session->gpsdata.dop);
+
+ session->gpsdata.dop.hdop = getlef32((char *)buf, 0);
+ session->gpsdata.dop.vdop = getlef32((char *)buf, 4);
+ session->gpsdata.dop.tdop = getlef32((char *)buf, 8);
+
+ session->gpsdata.dop.pdop = sqrt(pow(session->gpsdata.dop.hdop, 2) +
+ pow(session->gpsdata.dop.vdop, 2));
+
+ gpsd_log(&session->context->errout, LOG_DATA,
+ "GREIS: DP, hdop: %.2f, vdop: %.2f, tdop: %.2f, pdop: %.2f\n",
+ session->gpsdata.dop.hdop, session->gpsdata.dop.vdop,
+ session->gpsdata.dop.tdop, session->gpsdata.dop.pdop);
+
+ return DOP_SET;
+}
+
+/**
+ * Handle the message [SI] Satellite Indices.
+ *
+ * This message tells us how many satellites are seen and contains their
+ * Universal Satellite Identifier (USI).
+ */
+static gps_mask_t greis_msg_SI(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ int i;
+
+ if (len < 1) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: SI bad len %zu\n", len);
+ return 0;
+ }
+
+ gpsd_zero_satellites(&session->gpsdata);
+ /* FIXME: check against MAXCHANNELS? */
+ session->gpsdata.satellites_visible = len - 1;
+ for (i = 0; i < session->gpsdata.satellites_visible; i++) {
+ /* This isn't really PRN, this is USI. Do we need to convert? */
+ session->gpsdata.skyview[i].PRN = getub(buf, i);
+ }
+
+ session->driver.greis.seen_si = true;
+ gpsd_log(&session->context->errout, LOG_DATA,
+ "GREIS: SI, satellites_visible: %d\n",
+ session->gpsdata.satellites_visible);
+
+ return 0;
+}
+
+/**
+ * Handle the message [EL] Satellite Elevations.
+ */
+static gps_mask_t greis_msg_EL(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ int i;
+
+ if (!session->driver.greis.seen_si) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: can't use EL until after SI provides indices\n");
+ return 0;
+ }
+
+ /* check against number of satellites + checksum */
+ if (len < session->gpsdata.satellites_visible + 1U) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: EL bad len %zu, needed at least %d\n", len,
+ session->gpsdata.satellites_visible + 1);
+ return 0;
+ }
+
+ for (i = 0; i < session->gpsdata.satellites_visible; i++) {
+ short elevation;
+
+ /* GREIS elevation is -90 to 90 degrees */
+ /* GREIS uses 127 for n/a */
+ /* gpsd uses -91 for n/a, so adjust acordingly */
+ elevation = getub(buf, i);
+ if ((-90 > elevation) || (90 < elevation)) elevation = -91;
+ session->gpsdata.skyview[i].elevation = elevation;
+ }
+
+ session->driver.greis.seen_el = true;
+ gpsd_log(&session->context->errout, LOG_DATA, "GREIS: EL\n");
+
+ return 0;
+}
+
+/**
+ * Handle the message [AZ] Satellite Azimuths.
+ */
+static gps_mask_t greis_msg_AZ(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ int i;
+
+ if (!session->driver.greis.seen_si) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: can't use AZ until after SI provides indices\n");
+ return 0;
+ }
+
+ /* check against number of satellites + checksum */
+ if (len < session->gpsdata.satellites_visible + 1U) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: AZ bad len %zu, needed at least %d\n", len,
+ session->gpsdata.satellites_visible + 1);
+ return 0;
+ }
+
+ for (i = 0; i < session->gpsdata.satellites_visible; i++) {
+ short azimuth;
+
+ /* GREIS azimuth is 0 to 180, multiply by 2 for 0 to 360 */
+ /* GREIS uses 255 for n/a */
+ /* gpsd azimuth is 0 to 359, so adjust acordingly */
+ azimuth = getub(buf, i) * 2;
+ if (360 == azimuth) azimuth = 0;
+ else if (360 < azimuth) azimuth = -1;
+ session->gpsdata.skyview[i].azimuth = azimuth;
+ }
+
+ session->driver.greis.seen_az = true;
+ gpsd_log(&session->context->errout, LOG_DATA, "GREIS: AZ\n");
+
+ return 0;
+}
+
+/**
+ * Handle the message [EC] SNR (CA/L1).
+ * EC really outputs CNR, but what gpsd refers to as SNR _is_ CNR.
+ */
+static gps_mask_t greis_msg_EC(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ int i;
+
+ if (!session->driver.greis.seen_si) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: can't use EC until after SI provides indices\n");
+ return 0;
+ }
+
+ /* check against number of satellites + checksum */
+ if (len < session->gpsdata.satellites_visible + 1U) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: EC bad len %zu, needed at least %d\n", len,
+ session->gpsdata.satellites_visible + 1);
+ return 0;
+ }
+
+ for (i = 0; i < session->gpsdata.satellites_visible; i++)
+ session->gpsdata.skyview[i].ss = getub(buf, i);
+
+ session->driver.greis.seen_ec = true;
+ gpsd_log(&session->context->errout, LOG_DATA, "GREIS: EC\n");
+
+ return 0;
+}
+
+/**
+ * Handle the message [SS] Satellite Navigation Status.
+ */
+static gps_mask_t greis_msg_SS(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ int i;
+ int used_count = 0;
+
+ if (!session->driver.greis.seen_si) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: can't use SS until after SI provides indices\n");
+ return 0;
+ }
+
+ /* check against number of satellites + solution type + checksum */
+ if (len < session->gpsdata.satellites_visible + 2U) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: SI bad len %zu, needed at least %d\n", len,
+ session->gpsdata.satellites_visible + 2);
+ return 0;
+ }
+
+ for (i = 0; i < session->gpsdata.satellites_visible; i++) {
+ /*
+ * From the GREIS Reference Guide: "Codes [0...3], [40...62], and
+ * [64...255] indicate that given satellite is used in position
+ * computation and show which measurements are used. The rest of codes
+ * indicate that satellite is not used in position computation and
+ * indicate why this satellite is excluded from position computation."
+ * Refer to Table 3-4 "Satellite Navigation Status" for the specific
+ * code meanings.
+ */
+ uint8_t nav_status = getub(buf, i);
+ session->gpsdata.skyview[i].used =
+ (nav_status <= 3) ||
+ (nav_status >= 40 && nav_status <= 62) ||
+ (nav_status >= 64);
+
+ if (session->gpsdata.skyview[i].used)
+ used_count++;
+ }
+ session->gpsdata.satellites_used = used_count;
+
+ gpsd_log(&session->context->errout, LOG_DATA,
+ "GREIS: SS, satellites_used: %d\n",
+ session->gpsdata.satellites_used);
+
+ return used_count ? USED_IS : 0;
+}
+
+
+/**
+ * Handle the message [::](ET) Epoch Time.
+ * This should be kept as the last message in each epoch.
+ */
+static gps_mask_t greis_msg_ET(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ uint32_t tod;
+ gps_mask_t mask = 0;
+
+ if (len < 5) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: ET bad len %zu\n", len);
+ return 0;
+ }
+
+ if (!session->driver.greis.seen_rt) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: got ET, but no preceding RT for epoch\n");
+ return 0;
+ }
+
+ tod = getleu32(buf, 0);
+ if (tod != session->driver.greis.rt_tod) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: broken epoch, RT had %lu, but ET has %lu\n",
+ (unsigned long)session->driver.greis.rt_tod,
+ (unsigned long)tod);
+ return 0;
+ }
+
+ /* Skyview time does not differ from time in GT message */
+ session->gpsdata.skyview_time = NAN;
+
+ gpsd_log(&session->context->errout, LOG_DEBUG,
+ "GREIS: ET, seen: az %d, ec %d, el %d, rt %d, si %d, uo %d\n",
+ (int)session->driver.greis.seen_az,
+ (int)session->driver.greis.seen_ec,
+ (int)session->driver.greis.seen_el,
+ (int)session->driver.greis.seen_rt,
+ (int)session->driver.greis.seen_si,
+ (int)session->driver.greis.seen_uo);
+
+ /* Make sure we got the satellite data, then report it. */
+ if ((session->driver.greis.seen_az && session->driver.greis.seen_ec &&
+ session->driver.greis.seen_el && session->driver.greis.seen_si)) {
+ /* Skyview seen, update it. Go even if no seen_ss or none visible */
+ mask |= SATELLITE_SET;
+ } else {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: ET: missing satellite details in this epoch\n");
+ }
+
+ gpsd_log(&session->context->errout, LOG_DATA, "GREIS: ET, tod: %lu\n",
+ (unsigned long)tod);
+
+ /* This is a good place to poll firmware version if we need it.
+ * Waited until now to avoid the startup rush and out of
+ * critical time path
+ */
+ if (0 == strlen(session->subtype)) {
+ /* get version */
+ (void)greis_write(session, get_ver, sizeof(get_ver) - 1);
+ }
+ /* The driver waits for ET to send any reports
+ * Just REPORT_IS is not enough to trigger sending of reports to clients.
+ * STATUS_SET seems best, if no status by now the status is no fix */
+ return mask | REPORT_IS | STATUS_SET;
+}
+
+struct dispatch_table_entry {
+ char id0;
+ char id1;
+ gps_mask_t (*handler)(struct gps_device_t *, unsigned char *, size_t);
+};
+
+static struct dispatch_table_entry dispatch_table[] = {
+ {':', ':', greis_msg_ET},
+ {'A', 'Z', greis_msg_AZ},
+ {'D', 'P', greis_msg_DP},
+ {'E', 'C', greis_msg_EC},
+ {'E', 'R', greis_msg_ER},
+ {'E', 'L', greis_msg_EL},
+ {'G', 'T', greis_msg_GT},
+ {'P', 'V', greis_msg_PV},
+ {'R', 'E', greis_msg_RE},
+ {'S', 'G', greis_msg_SG},
+ {'S', 'I', greis_msg_SI},
+ {'S', 'S', greis_msg_SS},
+ {'U', 'O', greis_msg_UO},
+ {'~', '~', greis_msg_RT},
+};
+
+#define dispatch_table_size (sizeof(dispatch_table) / sizeof(dispatch_table[0]))
+
+/**
+ * Parse the data from the device
+ */
+static gps_mask_t greis_dispatch(struct gps_device_t *session,
+ unsigned char *buf, size_t len)
+{
+ size_t i;
+ char id0, id1;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * This is set because the device reliably signals end of cycle.
+ * The core library zeroes it just before it calls each driver's
+ * packet analyzer.
+ */
+ session->cycle_end_reliable = true;
+
+ /* Length should have already been checked in packet.c, but just in case */
+ if (len < HEADER_LENGTH) {
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: Packet length %zu shorter than min length\n", len);
+ return 0;
+ }
+
+ /* we may need to dump the raw packet */
+ gpsd_log(&session->context->errout, LOG_RAW,
+ "GREIS: raw packet id '%c%c'\n", buf[0], buf[1]);
+
+ id0 = buf[0];
+ id1 = buf[1];
+ len -= HEADER_LENGTH;
+ buf += HEADER_LENGTH;
+
+ for (i = 0; i < dispatch_table_size; i++) {
+ struct dispatch_table_entry *entry = &dispatch_table[i];
+
+ if (id0 == entry->id0 && id1 == entry->id1) {
+ return entry->handler(session, buf, len);
+ }
+ }
+
+ gpsd_log(&session->context->errout, LOG_WARN,
+ "GREIS: unknown packet id '%c%c' length %zu\n", id0, id1, len);
+ return 0;
+}
+
+/**********************************************************
+ *
+ * Externally called routines below here
+ *
+ **********************************************************/
+
+/**
+ * Write data to the device with checksum.
+ * Returns number of bytes written on successful write, -1 otherwise.
+ */
+static ssize_t greis_write(struct gps_device_t *session,
+ const char *msg, size_t msglen)
+{
+ char checksum_str[3] = {0};
+ ssize_t count;
+
+ if (session->context->readonly) {
+ /* readonly mode, do not write anything */
+ return -1;
+ }
+
+ if (NULL == msg) {
+ /* We do sometimes write zero length to wake up GPS,
+ * so just test for NULL msg, not zero length message */
+ gpsd_log(&session->context->errout, LOG_ERROR,
+ "GREIS: nothing to write\n");
+ return -1;
+ }
+
+ /* Account for length + checksum marker + checksum + \r + \n + \0 */
+ if (msglen + 6 > sizeof(session->msgbuf)) {
+ gpsd_log(&session->context->errout, LOG_ERROR,
+ "GREIS: msgbuf is smaller than write length %zu\n", msglen);
+ return -1;
+ }
+
+ if (msg != NULL)
+ memcpy(&session->msgbuf[0], msg, msglen);
+
+ if (msglen == 0) {
+ /* This is a dummy write, don't give a checksum. */
+ session->msgbuf[0] = '\n';
+ session->msgbuflen = 1;
+ gpsd_log(&session->context->errout, LOG_PROG,
+ "GREIS: Dummy write\n");
+ } else {
+ unsigned char checksum;
+
+ session->msgbuflen = msglen;
+ session->msgbuf[session->msgbuflen++] = '@'; /* checksum marker */
+
+ /* calculate checksum with @, place at end, and set length to write */
+ checksum = greis_checksum((unsigned char *)session->msgbuf,
+ session->msgbuflen);
+ (void)snprintf(checksum_str, sizeof(checksum_str), "%02X", checksum);
+ session->msgbuf[session->msgbuflen++] = checksum_str[0];
+ session->msgbuf[session->msgbuflen++] = checksum_str[1];
+ session->msgbuf[session->msgbuflen++] = '\r';
+ session->msgbuf[session->msgbuflen++] = '\n';
+
+ gpsd_log(&session->context->errout, LOG_PROG,
+ "GREIS: Writing command '%.*s', checksum: %s\n",
+ (int)msglen, msg, checksum_str);
+ }
+ session->msgbuf[session->msgbuflen] = '\0';
+ count = gpsd_write(session, session->msgbuf, session->msgbuflen);
+
+ if (count != (ssize_t)session->msgbuflen)
+ return -1;
+ else
+ return count;
+}
+
+#ifdef CONTROLSEND_ENABLE
+/**
+ * Write data to the device, doing any required padding or checksumming
+ */
+static ssize_t greis_control_send(struct gps_device_t *session,
+ char *msg, size_t msglen)
+{
+ return greis_write(session, msg, msglen);
+}
+#endif /* CONTROLSEND_ENABLE */
+
+#ifdef RECONFIGURE_ENABLE
+static void greis_event_hook(struct gps_device_t *session, event_t event)
+{
+ if (session->context->readonly)
+ return;
+
+ if (event == event_wakeup) {
+ /*
+ * Code to make the device ready to communicate. Only needed if the
+ * device is in some kind of sleeping state, and only shipped to
+ * RS232C, so that gpsd won't send strings to unidentified USB devices
+ * that might not be GPSes at all.
+ */
+
+ /*
+ * Disable any existing messages, then request vendor for
+ * identification.
+ */
+ (void)greis_write(session, disable_messages,
+ sizeof(disable_messages) - 1);
+ (void)greis_write(session, get_vendor, sizeof(get_vendor) - 1);
+ } else if (event == event_identified || event == event_reactivate) {
+ /*
+ * Fires when the first full packet is recognized from a previously
+ * unidentified device OR the device is reactivated after close. The
+ * session.lexer counter is zeroed.
+ *
+ * TODO: If possible, get the software version and store it in
+ * session->subtype.
+ */
+ (void)greis_write(session, disable_messages,
+ sizeof(disable_messages) - 1);
+ (void)greis_write(session, set_update_rate_4hz,
+ sizeof(set_update_rate_4hz) - 1);
+ (void)greis_write(session, enable_messages_4hz,
+ sizeof(enable_messages_4hz) - 1);
+
+ /* Store cycle time (seconds) */
+ session->gpsdata.dev.cycle = 0.25;
+ } else if (event == event_driver_switch) {
+ /*
+ * Fires when the driver on a device is changed *after* it
+ * has been identified.
+ */
+ } else if (event == event_deactivate) {
+ /*
+ * Fires when the device is deactivated. Use this to revert
+ * whatever was done at event_identified and event_configure
+ * time.
+ */
+ (void)greis_write(session, disable_messages,
+ sizeof(disable_messages) - 1);
+ }
+}
+
+/**
+ * This is the entry point to the driver. When the packet sniffer recognizes
+ * a packet for this driver, it calls this method which passes the packet to
+ * the binary processor or the nmea processor, depending on the session type.
+ */
+static gps_mask_t greis_parse_input(struct gps_device_t *session)
+{
+ if (session->lexer.type == GREIS_PACKET) {
+ return greis_dispatch(session, session->lexer.outbuffer,
+ session->lexer.outbuflen);
+#ifdef NMEA0183_ENABLE
+ } else if (session->lexer.type == NMEA_PACKET) {
+ return nmea_parse((char *)session->lexer.outbuffer, session);
+#endif /* NMEA0183_ENABLE */
+ } else
+ return 0;
+}
+
+/**
+ * Set port operating mode, speed, parity, stopbits etc. here.
+ * Note: parity is passed as 'N'/'E'/'O', but you should program
+ * defensively and allow 0/1/2 as well.
+ */
+static bool greis_set_speed(struct gps_device_t *session,
+ speed_t speed, char parity, int stopbits)
+{
+ /* change on current port */
+ static const char set_rate[] = "set,/par/cur/term/rate,";
+ static const char set_parity[] = "set,/par/cur/term/parity,";
+ static const char set_stops[] = "set,/par/cur/term/stops,";
+ static const char parity_none[] = "N";
+ static const char parity_even[] = "even";
+ static const char parity_odd[] = "odd";
+
+ char command[BUFSIZ] = {0};
+ const char *selected_parity = NULL;
+
+ switch (parity) {
+ case 'N':
+ case 0:
+ selected_parity = parity_none;
+ break;
+ case 'E':
+ case 1:
+ selected_parity = parity_even;
+ break;
+ case 'O':
+ case 2:
+ selected_parity = parity_odd;
+ break;
+ default:
+ return false;
+ }
+
+ (void)snprintf(command, sizeof(command) - 1, "%s%u && %s%s && %s%d",
+ set_rate, speed, set_parity, selected_parity,
+ set_stops, stopbits);
+ return (bool)greis_write(session, command, strlen(command));
+}
+
+#if 0
+/**
+ * TODO: Switch between NMEA and binary mode
+ */
+static void greis_set_mode(struct gps_device_t *session, int mode)
+{
+ if (mode == MODE_NMEA) {
+ /* send a mode switch control string */
+ } else {
+ /* send a mode switch control string */
+ session->back_to_nmea = false;
+ }
+}
+#endif
+
+#endif /* RECONFIGURE_ENABLE */
+
+#ifdef TIMEHINT_ENABLE
+#if 0 /* TODO */
+static double greis_time_offset(struct gps_device_t *session)
+{
+ /*
+ * If NTP notification is enabled, the GPS will occasionally NTP
+ * its notion of the time. This will lag behind actual time by
+ * some amount which has to be determined by observation vs. (say
+ * WWVB radio broadcasts) and, furthermore, may differ by baud
+ * rate. This method is for computing the NTP fudge factor. If
+ * it's absent, an offset of 0.0 will be assumed, effectively
+ * falling back on what's in ntp.conf. When it returns NAN,
+ * nothing will be sent to NTP.
+ */
+ return MAGIC_CONSTANT;
+}
+#endif
+#endif /* TIMEHINT_ENABLE */
+
+/* This is everything we export */
+/* *INDENT-OFF* */
+const struct gps_type_t driver_greis = {
+ /* Full name of type */
+ .type_name = "GREIS",
+ /* Associated lexer packet type */
+ .packet_type = GREIS_PACKET,
+ /* Driver type flags */
+ .flags = DRIVER_STICKY,
+ /* Response string that identifies device (not active) */
+ .trigger = NULL,
+ /* Number of satellite channels supported by the device */
+ .channels = 128,
+ /* Startup-time device detector */
+ .probe_detect = NULL,
+ /* Packet getter (using default routine) */
+ .get_packet = generic_get,
+ /* Parse message packets */
+ .parse_packet = greis_parse_input,
+ /* non-perturbing initial query (e.g. for version) */
+ .init_query = NULL,
+ /* fire on various lifetime events */
+ .event_hook = greis_event_hook,
+#ifdef RECONFIGURE_ENABLE
+ /* Speed (baudrate) switch */
+ .speed_switcher = greis_set_speed,
+#if 0 /* TODO */
+ /* Switch to NMEA mode */
+ .mode_switcher = greis_set_mode,
+#endif
+ /* Message delivery rate switcher (not active) */
+ .rate_switcher = NULL,
+ /* Minimum cycle time of the device.
+ * Default is 1/100, but this is tunable using /par/raw/msint . */
+ .min_cycle = 0.01,
+#endif /* RECONFIGURE_ENABLE */
+#ifdef CONTROLSEND_ENABLE
+ /* Control string sender - should provide checksum and headers/trailer */
+ .control_send = greis_control_send,
+#endif /* CONTROLSEND_ENABLE */
+#ifdef TIMEHINT_ENABLE
+#if 0 /* TODO */
+ .time_offset = greis_time_offset,
+#endif
+#endif /* TIMEHINT_ENABLE */
+/* *INDENT-ON* */
+};
+#endif /* defined(GREIS_ENABLE) && defined(BINARY_ENABLE) */