summaryrefslogtreecommitdiff
path: root/navcom.c
diff options
context:
space:
mode:
Diffstat (limited to 'navcom.c')
-rw-r--r--navcom.c445
1 files changed, 445 insertions, 0 deletions
diff --git a/navcom.c b/navcom.c
new file mode 100644
index 00000000..8d12e1c5
--- /dev/null
+++ b/navcom.c
@@ -0,0 +1,445 @@
+/*
+ * Driver for Navcom receivers using propietary NCT messages, a binary protocol.
+ *
+ * Vendor website: http://www.navcomtech.com/
+ * Technical references: http://www.navcomtech.com/support/docs.cfm
+ *
+ * Tested with an SF-2040G model
+ *
+ * At this stage, this driver implements the following commands:
+ *
+ * 0x20: Data Request (tell the unit which responses you want)
+ * 0x3f: LED Configuration (controls the front panel LEDs -- for testing)
+ * 0x1c: Test Support Block (again, blinks the front panel lights)
+ *
+ * and it understands the following responses:
+ *
+ * 0xb1: PVT Block (pos., vel., time., DOPs)
+ * 0x86: Channel Status (satellites visible + tracked)
+ *
+ * FIXME - Position errors theoretically are being reported at the one-sigma level.
+ * However, field tests suggest the values to be more consistent with
+ * two-sigma. Need to clear this up.
+ * FIXME - I'm not too sure of the way I have computed the vertical positional error
+ * I have used FOM as a scaling factor for VDOP, thusly VRMS = FOM/HDOP*VDOP
+ * TODO - Read 0x83 blocks (Ionosphere and UTC data) for transforming GPS time to UTC
+ * TODO - Lots of other things in mind, but the important stuff seems to be there.
+ *
+ * By Diego Berge. Contact via web form at http://www.nippur.net/survey/xuc/contact
+ * (the form is in Catalan, but you'll figure it out)
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include "gpsd_config.h"
+#include "gpsd.h"
+
+#if defined(NAVCOM_ENABLE) && defined(BINARY_ENABLE)
+#define LITTLE_ENDIAN_PROTOCOL
+#include "bits.h"
+
+/* Have data which is 24 bits long */
+#define getsl24(buf,off) ((int32_t)((u_int32_t)getub((buf), (off)+2)<<24 | (u_int32_t)getub((buf), (off)+1)<<16 | (u_int32_t)getub((buf), (off))<<8)>>8)
+
+#define NAVCOM_CHANNELS 26
+
+static u_int8_t checksum(unsigned char *buf, size_t len)
+{
+ size_t n;
+ u_int8_t csum = 0x00;
+ for(n = 0; n < len; n++)
+ csum ^= buf[n];
+ return csum;
+}
+
+static bool navcom_send_cmd(struct gps_device_t *session, unsigned char *cmd, size_t len)
+{
+ gpsd_report(LOG_RAW, "Sending Navcom command 0x%02x: %s\n",
+ cmd[3], gpsd_hexdump(cmd, len));
+ return gpsd_write(session, cmd, len);
+}
+
+/* Data Request */
+static void navcom_cmd_0x20(struct gps_device_t *session, u_int8_t block_id, u_int16_t rate)
+{
+ unsigned char msg[14];
+ putbyte(msg, 0, 0x02);
+ putbyte(msg, 1, 0x99);
+ putbyte(msg, 2, 0x66);
+ putbyte(msg, 3, 0x20); /* Cmd ID */
+ putword(msg, 4, 0x000a); /* Length */
+ putbyte(msg, 6, 0x00); /* Action */
+ putbyte(msg, 7, 0x00); /* Count of blocks */
+ putbyte(msg, 8, block_id); /* Data Block ID */
+ putbyte(msg, 9, 0x02); /* Logical Ports */
+ putword(msg, 10, rate); /* Data rate */
+ putbyte(msg, 12, checksum(msg+3, 9));
+ putbyte(msg, 13, 0x03);
+ navcom_send_cmd(session, msg, 14);
+}
+
+/* Changes the LED settings in the receiver */
+static void navcom_cmd_0x3f(struct gps_device_t *session)
+{
+ unsigned char msg[12];
+ putbyte(msg, 0, 0x02);
+ putbyte(msg, 1, 0x99);
+ putbyte(msg, 2, 0x66);
+ putbyte(msg, 3, 0x3f); /* Cmd ID */
+ putword(msg, 4, 0x0008);
+ putbyte(msg, 6, 0x01); /* Action */
+ putbyte(msg, 7, 0x00); /* Reserved */
+ putbyte(msg, 8, 0x02); /* Link LED setting */
+ putbyte(msg, 9, 0x0a); /* Battery LED setting */
+ putbyte(msg, 10, checksum(msg+3, 7));
+ putbyte(msg, 11, 0x03);
+ navcom_send_cmd(session, msg, 12);
+}
+
+/* Test Support Block - Blinks the LEDs */
+static void navcom_cmd_0x1c(struct gps_device_t *session, u_int8_t mode)
+{
+ unsigned char msg[12];
+ putbyte(msg, 0, 0x02);
+ putbyte(msg, 1, 0x99);
+ putbyte(msg, 2, 0x66);
+ putbyte(msg, 3, 0x1c); /* Cmd ID */
+ putword(msg, 4, 0x0008);
+ putbyte(msg, 6, 0x00);
+ putbyte(msg, 7, mode); /* 0x01 or 0x02 */
+ putbyte(msg, 8, mode);
+ putbyte(msg, 9, 0x00);
+ putbyte(msg, 10, checksum(msg+3, 7));
+ putbyte(msg, 11, 0x03);
+ navcom_send_cmd(session, msg, 12);
+}
+
+
+static void navcom_probe_subtype(struct gps_device_t *session, unsigned int seq)
+{
+ /* Request the following messages: 0x83, 0x84, 0x86, 0xb0, 0xb1 */
+ if (!seq) {
+ navcom_cmd_0x3f(session);
+ navcom_cmd_0x1c(session, 0x02);
+ navcom_cmd_0x20(session, 0xb1, 0x000a);
+ navcom_cmd_0x20(session, 0xb0, 0x000a);
+ navcom_cmd_0x20(session, 0x86, 0x000a);
+ }
+#ifdef __UNUSED__
+ if ((seq % 20) == 0)
+ navcom_cmd_0x1c(session, 0x01);
+ else if ((seq % 10) == 0)
+ navcom_cmd_0x1c(session, 0x02);
+#endif /* __UNUSED__ */
+}
+
+/* PVT Block */
+static gps_mask_t handle_0xb1(struct gps_device_t *session)
+{
+ unsigned char *buf = session->packet.outbuffer + 3;
+ uint16_t week;
+ uint32_t tow;
+ uint32_t sats_used;
+ int32_t lat, lon;
+ /* Resolution of lat/lon values (2^-11) */
+ #define LL_RES (0.00048828125)
+ uint8_t lat_fraction, lon_fraction;
+ /* Resolution of lat/lon fractions (2^-15) */
+ #define LL_FRAC_RES (0.000030517578125)
+ uint8_t nav_mode;
+ int32_t ellips_height, altitude;
+ /* Resolution of height and altitude values (2.0^-10) */
+ #define EL_RES (0.0009765625)
+ long vel_north, vel_east, vel_up;
+ /* Resolution of velocity values (2.0^-10) */
+ #define VEL_RES (0.0009765625)
+ double track;
+ uint8_t fom, gdop, pdop, hdop, vdop, tdop;
+ /* This value means "undefined" */
+ #define DOP_UNDEFINED (255)
+
+#ifdef __UNUSED__
+ uint16_t max_dgps_age;
+ uint8_t dgps_conf;
+ uint8_t ext_nav_mode;
+ int16_t ant_height_adj;
+ int32_t set_delta_north, set_delta_east, set_delta_up;
+ uint8_t nav_failure_code;
+#endif /* __UNUSED__ */
+
+ /* FIXME - Need to read block 0x86 to get up-to-date leap seconds */
+ /* Timestamp */
+ week = getuw(buf, 3);
+ tow = getul(buf, 5);
+ session->gpsdata.fix.time = session->gpsdata.sentence_time = gpstime_to_unix(week, tow/1000.0) - session->context->leap_seconds;
+ gpsd_report(LOG_RAW+1, "Navcom packet type 0xb1 - week = %d tow=%f unixtime=%f\n",
+ week, tow/1000.0, session->gpsdata.fix.time);
+
+ /* Satellites used */
+ unsigned char n;
+ sats_used = getul(buf, 9);
+ session->gpsdata.satellites_used = 0;
+ for(n = 0; n < 31; n++) {
+ if (sats_used & (0x01 << n))
+ session->gpsdata.used[session->gpsdata.satellites_used++] = n+1;
+ }
+
+ /* Get latitude, longitude */
+ lat = getsl(buf, 13);
+ lon = getsl(buf, 17);
+ lat_fraction = (getub(buf, 21) >> 4);
+ lon_fraction = (getub(buf, 21) & 0x0f);
+
+ session->gpsdata.fix.latitude = (double)(lat * LL_RES + lat_fraction * LL_FRAC_RES ) / 3600;
+ session->gpsdata.fix.longitude = (double)(lon * LL_RES + lon_fraction * LL_FRAC_RES ) / 3600;
+ gpsd_report(LOG_RAW, "Navcom packet type 0xb1 - lat = %f (%d, %08x), lon = %f (%d, %08x)\n",
+ session->gpsdata.fix.latitude, lat, lat, session->gpsdata.fix.longitude, lon, lon);
+
+ /* Nav mode */
+ nav_mode = getub(buf, 22);
+ if ((nav_mode & 0xc0) == 0xc0) {
+ session->gpsdata.fix.mode = MODE_3D;
+ if (nav_mode & 0x03)
+ session->gpsdata.status = STATUS_DGPS_FIX;
+ else
+ session->gpsdata.status = STATUS_FIX;
+ }
+ else if (nav_mode & 0x80) {
+ session->gpsdata.fix.mode = MODE_2D;
+ if (nav_mode & 0x03)
+ session->gpsdata.status = STATUS_DGPS_FIX;
+ else
+ session->gpsdata.status = STATUS_FIX;
+ }
+ else {
+ session->gpsdata.fix.mode = MODE_NO_FIX;
+ session->gpsdata.status = STATUS_NO_FIX;
+ }
+
+ /* Height Data */
+ ellips_height = getsl(buf, 23);
+ altitude = getsl(buf, 27);
+
+ session->gpsdata.fix.altitude = (double)(altitude * EL_RES);
+ session->gpsdata.separation = (double)(ellips_height - altitude)*EL_RES;
+
+ /* Speed Data */
+ vel_north = getsl24(buf, 31);
+ vel_east = getsl24(buf, 34);
+ /* vel_up = getsl24(buf, 37); */
+ vel_up = getsl24(buf, 37);
+
+ track = atan2(vel_north, vel_east);
+ if (track < 0)
+ track += 2 * PI;
+ session->gpsdata.fix.track = track * RAD_2_DEG;
+ /* FIXME Confirm what the tech spec means by (2^-10 m/s) +/- 8192m/s */
+ session->gpsdata.fix.speed = sqrt(pow(vel_east,2) + pow(vel_north,2)) * VEL_RES;
+ session->gpsdata.fix.climb = vel_up * VEL_RES;
+ gpsd_report(LOG_RAW+1, "Navcom packet type 0xb1 - velocities - track = %f, speed = %f, climb = %f\n",
+ session->gpsdata.fix.track,
+ session->gpsdata.fix.speed,
+ session->gpsdata.fix.climb);
+
+ /* Quality indicators */
+ fom = getub(buf, 40);
+ gdop = getub(buf, 41);
+ pdop = getub(buf, 42);
+ hdop = getub(buf, 43);
+ vdop = getub(buf, 44);
+ tdop = getub(buf, 45);
+
+ session->gpsdata.fix.eph = fom/100.0;
+ /* FIXME This cannot possibly be right */
+ /* I cannot find where to get VRMS from in the Navcom output, though */
+ session->gpsdata.fix.epv = (double)fom/(double)hdop*(double)vdop/100.0;
+
+ if (gdop == DOP_UNDEFINED)
+ session->gpsdata.gdop = NAN;
+ else
+ session->gpsdata.gdop = gdop/10.0;
+ if (pdop == DOP_UNDEFINED)
+ session->gpsdata.pdop = NAN;
+ else
+ session->gpsdata.pdop = pdop/10.0;
+ if (hdop == DOP_UNDEFINED)
+ session->gpsdata.hdop = NAN;
+ else
+ session->gpsdata.hdop = hdop/10.0;
+ if (vdop == DOP_UNDEFINED)
+ session->gpsdata.vdop = NAN;
+ else
+ session->gpsdata.vdop = vdop/10.0;
+ if (tdop == DOP_UNDEFINED)
+ session->gpsdata.tdop = NAN;
+ else
+ session->gpsdata.tdop = tdop/10.0;
+
+ gpsd_report(LOG_RAW+1, "hrms = %f, gdop = %f, pdop = %f, hdop = %f, vdop = %f, tdop = %f\n",
+ session->gpsdata.fix.eph, session->gpsdata.gdop, session->gpsdata.pdop,
+ session->gpsdata.hdop, session->gpsdata.vdop, session->gpsdata.tdop);
+
+ return LATLON_SET | ALTITUDE_SET | CLIMB_SET | SPEED_SET | TRACK_SET | TIME_SET
+ | STATUS_SET | MODE_SET | USED_SET | HERR_SET | VERR_SET | DOP_SET | CYCLE_START_SET;
+}
+
+/* Channel Status */
+static gps_mask_t handle_0x86(struct gps_device_t *session)
+{
+ size_t n, i;
+ u_int8_t prn, tracking_status, ele, ca_snr, p2_snr, channel;
+ u_int16_t azm;
+ unsigned char *buf = session->packet.outbuffer + 3;
+ size_t msg_len = getuw(buf, 1);
+ u_int16_t week = getuw(buf, 3);
+ u_int32_t tow = getul(buf, 5);
+ u_int16_t status = getuw(buf, 10);
+ u_int8_t sats_visible = getub(buf, 12);
+ u_int8_t sats_tracked = getub(buf, 13);
+ u_int8_t sats_used = getub(buf, 14);
+ u_int8_t pdop = getub(buf, 15);
+
+ /* Timestamp and PDOP */
+ session-> gpsdata.fix.time = gpstime_to_unix(week, tow/1000.0) - session->context->leap_seconds;
+ session->gpsdata.pdop = pdop / 10.0;
+
+ /* Satellite count */
+ session->gpsdata.satellites = sats_visible;
+ session->gpsdata.satellites_used = sats_used;
+
+ /* Fix mode */
+ switch(status & 0x05)
+ {
+ case 0x05:
+ session->gpsdata.status = STATUS_DGPS_FIX;
+ break;
+ case 0x01:
+ session->gpsdata.status = STATUS_FIX;
+ break;
+ default:
+ session->gpsdata.status = STATUS_NO_FIX;
+ }
+
+ gpsd_report(LOG_RAW, "Navcom packet type 0x86 - satellites: visible = %u, tracked = %u, used = %u\n",
+ sats_visible, sats_tracked, sats_used);
+
+ /* Satellite details */
+ i = 0;
+ for(n = 17; n < msg_len; n += 14) {
+ if(i >= MAXCHANNELS) {
+ gpsd_report(LOG_ERROR, "internal error - too many satellites!\n");
+ gpsd_zero_satellites(&session->gpsdata);
+ return ERROR_SET;
+ }
+ prn = getub(buf, n);
+ tracking_status = getub(buf, n+1);
+ ele = getub(buf, n+5);
+ azm = getuw(buf, n+6);
+ ca_snr = getub(buf, n+8);
+ p2_snr = getub(buf, n+10);
+ channel = getub(buf, n+13);
+ if (tracking_status != 0x00) {
+ session->gpsdata.PRN[i] = (int)prn;
+ session->gpsdata.elevation[i] = ele;
+ session->gpsdata.azimuth[i] = azm;
+ session->gpsdata.ss[i++] = (p2_snr ? p2_snr : ca_snr) / 4.0;
+ gpsd_report(LOG_RAW+1, "prn = %02x, ele = %02x, azm = %04x, ss = %d\n",
+ prn, ele, azm, session->gpsdata.ss);
+ }
+ }
+
+ return TIME_SET | PDOP_SET | SATELLITE_SET | STATUS_SET;
+}
+
+
+/*@ +charint @*/
+gps_mask_t navcom_parse(struct gps_device_t *session, unsigned char *buf, size_t len)
+{
+ unsigned char cmd_id;
+ unsigned char *payload;
+ unsigned int msg_len;
+
+ if (len == 0)
+ return 0;
+
+ cmd_id = getub(buf, 3);
+ payload = &buf[6];
+ msg_len = getuw(buf, 4);
+
+ /*@ -usedef -compdef @*/
+ gpsd_report(LOG_RAW, "Navcom packet type 0x%02x, length %d: %s\n",
+ cmd_id, msg_len, gpsd_hexdump(buf, len));
+ /*@ +usedef +compdef @*/
+
+ (void)snprintf(session->gpsdata.tag, sizeof(session->gpsdata.tag),
+ "0x%02x",cmd_id);
+
+ switch (cmd_id)
+ {
+ case 0xb1:
+ return handle_0xb1(session);
+ case 0x86:
+ return handle_0x86(session);
+ default:
+ gpsd_report(LOG_IO, "Unknown or unimplemented Navcom packet id 0x%02x, length %d\n",
+ cmd_id, msg_len);
+ return 0;
+ }
+}
+/*@ -charint @*/
+
+static gps_mask_t navcom_parse_input(struct gps_device_t *session)
+{
+ gps_mask_t st;
+
+ if (session->packet.type == NAVCOM_PACKET){
+ st = navcom_parse(session, session->packet.outbuffer, session->packet.outbuflen);
+ session->gpsdata.driver_mode = 1; /* binary */
+ return st;
+#ifdef NMEA_ENABLE
+ } else if (session->packet.type == NMEA_PACKET) {
+ st = nmea_parse((char *)session->packet.outbuffer, session);
+ session->gpsdata.driver_mode = 0; /* NMEA */
+ return st;
+#endif /* NMEA_ENABLE */
+ } else
+ return 0;
+}
+
+
+/* this is everything we export */
+struct gps_type_t navcom_binary =
+{
+ .typename = "Navcom binary", /* full name of type */
+ .trigger = "\x02\x99\x66",
+ .channels = NAVCOM_CHANNELS, /* 12 L1 + 12 L2 + 2 L-Band */
+ .probe_wakeup = NULL, /* no wakeup to be done before hunt */
+ .probe_detect = NULL, /* no probe */
+ .probe_subtype = navcom_probe_subtype, /* subtype probing */
+#ifdef ALLOW_RECONFIGURE
+ .configurator = NULL, /* no reconfigure */
+#endif /* ALLOW_RECONFIGURE */
+ .get_packet = generic_get, /* use generic one */
+ .parse_packet = navcom_parse_input, /* parse message packets */
+ .rtcm_writer = pass_rtcm, /* send RTCM data straight */
+ .speed_switcher = NULL, /* we can change baud rates */
+ .mode_switcher = NULL, /* there is a mode switcher */
+ .rate_switcher = NULL, /* no sample-rate switcher */
+ .cycle_chars = -1, /* ignore, no rate switch */
+#ifdef ALLOW_RECONFIGURE
+ .revert = NULL, /* no reversion code */
+#endif /* ALLOW_RECONFIGURE */
+ .wrapup = NULL, /* ignore, no wrapup */
+ .cycle = 1, /* updates every second */
+};
+
+#endif /* defined(NAVCOM_ENABLE) && defined(BINARY_ENABLE) */