/*
* This is the gpsd driver for EverMore GPSes operating in binary mode.
* About the only thing this gives us that NMEA won't is TDOP.
* But we'll get atomic position reports from it, which is good.
*
* The vendor site is <http://www.emt.com.tw>.
*
* This driver was written by Petr Slansky based on a framework by Eric S.
* Raymond. The following remarks are by Petr Slansky.
*
* Snooping on the serial the communication between a Windows program and
* an Evermore chipset reveals some messages not described in the vendor
* documentation (Issue C of Aug 2002):
*
* 10 02 06 84 00 00 00 84 10 03 switch to binary mode (84 00 00 00)
* 10 02 06 84 01 00 00 85 10 03 switch to NMEA mode (84 01 00 00)
*
* 10 02 06 89 01 00 00 8a 10 03 set baud rate 4800
* 10 02 06 89 01 01 00 8b 10 03 set baud rate 9600
* 10 02 06 89 01 02 00 8c 10 03 set baud rate 19200
* 10 02 06 89 01 03 00 8d 10 03 set baud rate 38400
*
* 10 02 06 8D 00 01 00 8E 10 03 switch to datum ID 001 (WGS-84)
* 10 02 06 8D 00 D8 00 65 10 03 switch to datum ID 217 (WGS-72)
*
* These don't entail a reset of GPS as the 0x80 message does.
*
* 10 02 04 38 85 bd 10 03 answer from GPS to 0x85 message; Like OK?
* 10 02 04 38 8d c5 10 03 answer from GPS to 0x8d message; Like OK?
* 10 02 04 38 8e c6 10 03 answer from GPS to 0x8e message; Like OK?
* 10 02 04 38 8f c7 10 03 answer from GPS to 0x8f message; Like OK?
*
* Message described as 0x89 in the manual is message 0x8f in the
* actual command set (manual error?). Message 0x89 is used to switch
* baud rate without reset.
*
* The chip sometimes sends vendor extension messages with the prefix
* $PEMT,100. After reset, it sends a $PEMT,100 message describing the
* chip's configuration. Here is a sample:
*
* $PEMT,100,05.42g,100303,180,05,1,20,15,08,0,0,2,1*5A
* 100 - message type
* 05.42g - firmware version
* 100303 - date of firmware release
* 180 - datum ID; 001 is WGS-84
* 05 - elevation mask; see message 0x86
* 1 - DOP mode (1 is auto DOP mask); see message 0x87
* 20 - GDOP; see message 0x87
* 15 - PDOP
* 08 - HDOP
* 0 - Normal mode, without 1PPS
* 0 - ??
* 2 - ??
* 1 - ??
*
* Message $PEMT,100 could be forced with message 0x85:
* 10 02 12 85 00 00 00 00 00 01 01 00 00 00 00 00 00 00 00 87 10 03
* This is some kind of initialisation but I don't understand the 0x85
* message in detail.
*
* With message 0x8e it is possible to define how often each NMEA
* message is sent (0-255 seconds). It is possible with message 0x8e
* to activate PEMT,101 messages that have information about time,
* position, velocity and HDOP.
*
* $PEMT,101,1,03,21.0,230705190757,5004.6612,N,01425.1359,E,00269,045,0000*24
* 101 - message type
* 1 - (1 or 2) I guess that this is flag that position is valid
* 03 - number of used satelites
* 21.0 - HDOP
* 230705190757 - date and time, UTC
* 5004.6612,N - Latitude (degree)
* 01425.1359,E - Longitude (degree)
* 00269 - Altitude (m)
* 045 - heading (degrees from true north)
* 0000 - ?? could be speed over ground
*
* This is an exampe of an 0x8e message that activates all NMEA sentences
* with 1s period:
* 10 02 12 8E 7F 01 01 01 01 01 01 01 01 00 00 00 00 00 00 15 10 03
*
* There is a way to probe for this chipset. When binary message 0x81 is sent:
* 10 02 04 81 13 94 10 03
*
* EverMore will reply with message like this:
* *10 *02 *0D *20 E1 00 00 *00 0A 00 1E 00 32 00 5B *10 *03
* bytes marked with * are fixed
* Message in reply is information about logging configuration of GPS
*
* Another way to detect the EverMore chipset is to send one of the messages
* 0x85, 0x8d, 0x8e or 0x8f and check for a reply.
* The reply message from an EverMore GPS will look like this:
* *10 *02 *04 *38 8d c5 *10 *03
* 8d indicates that message 0x8d was sent;
* c5 is EverMore checksum, other bytes are fixed
*
*/
#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 <stdio.h>
#include "gpsd.h"
#if defined(EVERMORE_ENABLE) && defined(BINARY_ENABLE)
#define LITTLE_ENDIAN_PROTOCOL
#define GET_ORIGIN 1
#define PUT_ORIGIN 0
#include "bits.h"
#define EVERMORE_CHANNELS 12
/*@ +charint -usedef -compdef @*/
static bool evermore_write(struct gps_device_t *session, unsigned char *msg, size_t msglen)
{
unsigned int crc;
size_t i, len;
unsigned char stuffed[MAX_PACKET_LENGTH*2], *cp;
bool ok;
/* prepare a DLE-stuffed copy of the message */
cp = stuffed;
*cp++ = 0x10; /* message starts with DLE STX */
*cp++ = 0x02;
len = (size_t)(msglen + 2); /* msglen < 254 !! */
*cp++ = (unsigned char)len; /* message length */
if (len == 0x10) *cp++ = 0x10;
/* payload */
crc = 0;
for (i = 0; i < msglen; i++) {
*cp++ = msg[i];
crc += msg[i];
if (msg[i] == 0x10) *cp++ = 0x10;
}
crc &= 0xff;
/* enter CRC after payload */
*cp++ = crc;
if (crc == 0x10) *cp++ = 0x10;
*cp++ = 0x10; /* message ends with DLE ETX */
*cp++ = 0x03;
len = (size_t)(cp - stuffed);
/* we may need to dump the message */
gpsd_report(4, "writing EverMore control type 0x%02x: %s\n", msg[0],
gpsd_hexdump(stuffed, len));
ok = gpsd_write(session, stuffed, len);
return ok;
}
/*@ -charint +usedef +compdef @*/
/*@ +charint @*/
gps_mask_t evermore_parse(struct gps_device_t *session, unsigned char *buf, size_t len)
{
unsigned char buf2[MAX_PACKET_LENGTH], *cp, *tp;
size_t i, datalen;
unsigned int used, visible, satcnt;
double version;
gps_mask_t mask = 0;
if (len == 0)
return 0;
/* time to unstuff it and discard the header and footer */
cp = buf + 2;
tp = buf2;
if (*cp == 0x10) cp++;
datalen = (size_t)*cp++;
gpsd_report(7, "raw EverMore packet type 0x%02x, length %d: %s\n", *cp, len, gpsd_hexdump(buf, len));
datalen -= 2;
for (i = 0; i < (size_t)datalen; i++) {
*tp = *cp++;
if (*tp == 0x10) cp++;
tp++;
}
/*@ -usedef -compdef @*/
gpsd_report(6, "EverMore packet type 0x%02x, length %d: %s\n", buf2[0], datalen, gpsd_hexdump(buf2, datalen));
/*@ +usedef +compdef @*/
(void)snprintf(session->gpsdata.tag, sizeof(session->gpsdata.tag),
"EID%d",(int)buf2[0]);
switch (getub(buf2, 1))
{
case 0x02: /* Navigation Data Output */
session->gpsdata.newdata.time = session->gpsdata.sentence_time
= gpstime_to_unix((int)getuw(buf2, 2), getul(buf2, 4)*0.01) - session->context->leap_seconds;
ecef_to_wgs84fix(&session->gpsdata,
getsl(buf2, 8)*1.0, getsl(buf2, 12)*1.0, getsl(buf2, 16)*1.0,
getsw(buf2, 20)/10.0, getsw(buf2, 22)/10.0, getsw(buf2, 24)/10.0);
used = getub(buf2, 26) & 0x0f;
visible = (getub(buf2, 26) & 0xf0) >> 4;
version = getuw(buf2, 27)/100.0;
/* that's all the information in this packet */
if (used < 3)
session->gpsdata.newdata.mode = MODE_NO_FIX;
else if (used == 3)
session->gpsdata.newdata.mode = MODE_2D;
else {
session->gpsdata.newdata.mode = MODE_3D;
mask |= ALTITUDE_SET | CLIMB_SET;
}
gpsd_report(4, "NDO 0x02: version %3.2f, mode=%d, status=%d, visible=%d, used=%d\n",
version,
session->gpsdata.newdata.mode,
session->gpsdata.status,
visible,
used);
mask |= TIME_SET | LATLON_SET | TRACK_SET | SPEED_SET | MODE_SET;
return mask;
case 0x04: /* DOP Data Output */
session->gpsdata.newdata.time = session->gpsdata.sentence_time
= gpstime_to_unix((int)getuw(buf2, 2), getul(buf2, 4)*0.01) - session->context->leap_seconds;
session->gpsdata.gdop = (double)getub(buf2, 8)*0.1;
session->gpsdata.pdop = (double)getub(buf2, 9)*0.1;
session->gpsdata.hdop = (double)getub(buf2, 10)*0.1;
session->gpsdata.vdop = (double)getub(buf2, 11)*0.1;
session->gpsdata.tdop = (double)getub(buf2, 12)*0.1;
switch (getub(buf2, 13)) {
case 0: /* no position fix */
case 1: /* manual calls this "1D navigation" */
session->gpsdata.status = STATUS_NO_FIX;
session->gpsdata.newdata.mode = MODE_NO_FIX;
break;
case 2: /* 2D navigation */
session->gpsdata.status = STATUS_FIX;
session->gpsdata.newdata.mode = MODE_2D;
break;
case 3: /* 3D navigation */
session->gpsdata.status = STATUS_FIX;
session->gpsdata.newdata.mode = MODE_3D;
break;
case 4: /* 3D navigation with DGPS */
session->gpsdata.status = STATUS_DGPS_FIX;
session->gpsdata.newdata.mode = MODE_3D;
break;
}
/* that's all the information in this packet */
gpsd_report(4, "DDO 0x04: mode=%d, status=%d\n",
session->gpsdata.newdata.mode,
session->gpsdata.status);
return TIME_SET | DOP_SET | MODE_SET | STATUS_SET;
case 0x06: /* Channel Status Output */
session->gpsdata.newdata.time = session->gpsdata.sentence_time
= gpstime_to_unix((int)getuw(buf2, 2), getul(buf2, 4)*0.01) - session->context->leap_seconds;
session->gpsdata.satellites = (int)getub(buf2, 8);
session->gpsdata.satellites_used = 0;
memset(session->gpsdata.used, 0, sizeof(session->gpsdata.used));
if (session->gpsdata.satellites > 12) {
gpsd_report(4, "Warning: EverMore packet has information about %d satellites!\n",
session->gpsdata.satellites);
}
if (session->gpsdata.satellites > EVERMORE_CHANNELS) session->gpsdata.satellites = EVERMORE_CHANNELS;
satcnt = 0;
for (i = 0; i < (size_t)session->gpsdata.satellites; i++) {
int prn;
// channel = getub(buf2, 7*i+7+2)
prn = (int)getub(buf2, 7*i+7+3);
if (prn == 0) continue; /* satellite record is not valid */
session->gpsdata.PRN[satcnt] = prn;
session->gpsdata.azimuth[satcnt] = (int)getuw(buf2, 7*i+7+4);
session->gpsdata.elevation[satcnt] = (int)getub(buf2, 7*i+7+6);
session->gpsdata.ss[satcnt] = (int)getub(buf2, 7*i+7+7);
/*
* Status bits at offset 8:
* bit0 = 1 satellite acquired
* bit1 = 1 code-tracking loop locked
* bit2 = 1 carrier-tracking loop locked
* bit3 = 1 data-bit synchronization done
* bit4 = 1 frame synchronization done
* bit5 = 1 ephemeris data collected
* bit6 = 1 used for position fix
*/
if (getub(buf2, 7*i+7+8) & 0x40) {
session->gpsdata.used[session->gpsdata.satellites_used++]=prn;
}
satcnt++;
}
session->gpsdata.satellites = (int)satcnt;
/* that's all the information in this packet */
gpsd_report(4, "CSO 0x04: %d satellites used\n",
session->gpsdata.satellites_used);
return TIME_SET | SATELLITE_SET | USED_SET;
case 0x08: /* Measurement Data Output */
// clock offset is a manufacturer diagnostic
// (int)getuw(buf2, 8); clock offset, 29000..29850 ??
session->gpsdata.newdata.time = session->gpsdata.sentence_time
= gpstime_to_unix((int)getuw(buf2, 2), getul(buf2, 4)*0.01) - session->context->leap_seconds;
visible = getub(buf2, 10);
/* FIXME: read full statellite status for each channel */
/* gpsd_report(4, "MDO 0x04: visible=%d\n", visible); */
gpsd_report(4, "MDO 0x04:\n");
return TIME_SET;
case 0x20: /* LogConfig Info, could be used as a probe for EverMore GPS */
gpsd_report(3, "LogConfig EverMore packet, length %d: %s\n", datalen, gpsd_hexdump(buf2, datalen));
return ONLINE_SET;
case 0x22: /* LogData */
gpsd_report(3, "LogData EverMore packet, length %d: %s\n", datalen, gpsd_hexdump(buf2, datalen));
return ONLINE_SET;
default:
gpsd_report(3, "unknown EverMore packet id 0x%02x, length %d: %s\n", buf2[0], datalen, gpsd_hexdump(buf2, datalen));
return 0;
}
}
/*@ -charint @*/
static gps_mask_t evermore_parse_input(struct gps_device_t *session)
{
gps_mask_t st;
if (session->packet_type == EVERMORE_PACKET){
st = evermore_parse(session, session->outbuffer, session->outbuflen);
session->gpsdata.driver_mode = 1; /* binary */
return st;
#ifdef NMEA_ENABLE
} else if (session->packet_type == NMEA_PACKET) {
st = nmea_parse((char *)session->outbuffer, session);
session->gpsdata.driver_mode = 0; /* NMEA */
return st;
#endif /* NMEA_ENABLE */
} else
return 0;
}
/* configure various EverMore settings to default */
static bool evermore_default(struct gps_device_t *session, int mode)
{
bool ok = true;
/*@ +charint @*/
unsigned char msg86[] = {
0x86, /* 0: msg ID */
5, /* 1: elevation Mask, degree 0..89 */
};
unsigned char msg87[] = {
0x87, /* 0: msg ID */
1, /* 1: DOP mask, GDOP(0), auto(1), PDOP(2), HDOP(3), no mask(4) */
20, /* 2: GDOP, 1..99 */
15, /* 3: PDOP, 1..99 */
8, /* 4: HDOP, 1..99 */
};
#ifdef __UNUSED__
unsigned char msg8f[] = {
0x8f, /* 0: msg ID */
0, /* 1: operation mode, normal(0), power save(1), 1PPS(2) */
1, /* 2: navigation update rate, 1/Hz, 1..10 */
0, /* 3: RF/GPSBBP On time, 160ms(0), 220(1), 280(2), 340(3), 440(4) */
};
unsigned char msg8e[] = {
0x8e, /* 0: msg ID */
0x1d, /* 1: NMEA sentence bitmask, GGA(0), GLL(1), GSA(2), GSV(3), ... */
0x01, /* 2: nmea checksum no(0), yes(1) */
1, /* 3: GPGGA, interval 0-255s */
0, /* 4: GPGLL, interval 0-255s */
1, /* 5: GPGSA, interval 0-255s */
1, /* 6: GPGSV, interval 0-255s */
1, /* 7: GPRMC, interval 0-255s */
0, /* 8: GPVTG, interval 0-255s */
0, /* 9: PEMT,101, interval 0-255s */
0, 0, 0, 0, 0, 0, /* 10: ?? */
};
unsigned char msg8d[] = {
0x8d, /* 0: msg ID */
0x00, /* 1: 0 (hi byte of datum ID) */
0x01, /* 2: datum ID, 1 is WGS-84 */
0x00, /* 3: ?? */
};
#endif /* __UNUSED__ */
unsigned char msg84[] = {
0x84, /* 0: msg ID */
0x01, /* 1: mode; binary(0), nmea(1) */
0x00, /* 2: ?? */
0x00, /* 3: ?? */
};
gpsd_report(5, "evermore_default call(%d)\n", mode);
ok &= evermore_write(session, msg86, sizeof(msg86));
ok &= evermore_write(session, msg87, sizeof(msg87));
#ifdef __UNUSED__
ok &= evermore_write(session, msg8f, sizeof(msg8f));
ok &= evermore_write(session, msg8e, sizeof(msg8e));
ok &= evermore_write(session, msg8d, sizeof(msg8d));
#endif /* __UNUSED__ */
if (mode == 1) {
gpsd_report(1, "Switching chip mode to EverMore binary.\n");
msg84[1] = 0; /* binary mode */
}
ok &= evermore_write(session, msg84, sizeof(msg84));
/*@ +charint @*/
return ok;
}
static bool evermore_set_mode(struct gps_device_t *session,
speed_t speed, bool mode)
{
u_int8_t tmp8;
double tow;
int week;
/*@ +charint @*/
unsigned char msg[] = {
0x80, /* 0: msg ID */
0x33, 0x05, /* 1: GPS week; when 0 is here, we finish with year 1985 */
0x00, 0x00, 0x00, 0x00, /* 3: GPS TOW */
0x00, 0x00, /* 7: Latitude */
0x00, 0x00, /* 9: Longitude */
0x00, 0x00, /* 11: Altitude */
0x00, 0x00, /* 13: Datum ID WGS84 */
0x01, /* 15: hot start */
0x5d, /* 16: bitmask; cksum(6), bin(7), GGA(0), GSA(2), GSV(3), RMC(4) */
0, /* 17: baud rate */
};
switch (speed) {
case 4800: tmp8 = 0; break;
case 9600: tmp8 = 1; break;
case 19200: tmp8 = 2; break;
case 38400: tmp8 = 3; break;
default: return false;
}
msg[17] = tmp8;
session->gpsdata.baudrate = (unsigned int)speed;
//gpsd_set_speed(session, speed, 'N', 1);
if (mode) {
gpsd_report(1, "Switching chip mode to EverMore binary.\n");
msg[16] |= 0x80; /* binary mode */
}
unix_to_gpstime(timestamp(), &week, &tow);
putword(msg, 1, (unsigned int) week);
putlong(msg, 3, (unsigned long) tow);
return evermore_write(session, msg, sizeof(msg));
/*@ +charint @*/
}
static bool evermore_speed(struct gps_device_t *session, speed_t speed)
{
#if 0
gpsd_report(5, "evermore_speed call (%d)\n", speed);
return evermore_set_mode(session, speed, true);
#else
unsigned char tmp8;
unsigned char msg[] = {
0x89, /* 0: msg ID */
0x01, /* 1: ??, maybe mode: normal mode(0), power save(1), 1PPS(2) */
0x00, /* 2: baud rate; 4800(0), 9600(1), 19200(2), 38400(3) */
0x00, /* 3: ??, maybe RF/GPSBB on time; valid only for power save mode */
};
gpsd_report(5, "evermore_speed call (%d)\n", speed);
switch (speed) {
case 4800: tmp8 = 0; break;
case 9600: tmp8 = 1; break;
case 19200: tmp8 = 2; break;
case 38400: tmp8 = 3; break;
default: return false;
}
msg[2] = tmp8;
return evermore_write(session, msg, sizeof(msg));
#endif
}
static void evermore_mode(struct gps_device_t *session, int mode)
{
gpsd_report(5, "evermore_mode call (%d)\n", mode);
if (mode == 0) {
(void)gpsd_switch_driver(session, "Generic NMEA");
// (void)evermore_set_mode(session, session->gpsdata.baudrate, false);
(void)evermore_default(session, 0);
session->gpsdata.driver_mode = 0;
} else {
(void)evermore_default(session, 1);
session->gpsdata.driver_mode = 1;
}
}
static void evermore_initializer(struct gps_device_t *session)
/* poll for software version in order to check for old firmware */
{
gpsd_report(5, "evermore_initializer call\n");
if (session->packet_type == NMEA_PACKET)
(void)evermore_set_mode(session, session->gpsdata.baudrate, true);
(void)evermore_default(session, 0);
}
#ifdef __UNUSED__
static void evermore_probe(struct gps_device_t *session)
/* send a binary message to probe for EverMore GPS */
/*
* There is a way to probe for EverMore chipset. When binary message 0x81 is sent:
* 10 02 04 81 13 94 10 03
*
* EverMore will reply with something like this:
* *10 *02 *0D *20 E1 00 00 *00 0A 00 1E 00 32 00 5B *10 *03
* bytes marked with * are fixed
* Message in reply is information about logging configuration of GPS
* */
{
unsigned char msg[] = {
0x81, /* 0: msg ID */
0x13, /* 1: LogRead = 0x13 */
};
bool ok;
gpsd_report(5, "evermore_probe call\n");
ok = evermore_write(session, msg, sizeof(msg));
return;
}
#endif /* __UNUSED__ */
#ifdef __UNUSED__
static void evermore_close(struct gps_device_t *session)
/* set GPS to NMEA, 4800, GGA, GSA, GSV, RMC (default) */
{
gpsd_report(5, "evermore_close call\n");
(void)evermore_set_mode(session, 4800, false);
}
#endif /* __UNUSED__ */
/* this is everything we export */
struct gps_type_t evermore_binary =
{
.typename = "EverMore binary", /* full name of type */
//.trigger = "$PEMT,100,05.", recognize the type */
.trigger = "\x10\x02\x04\x38\x8d\xc5\x10\x03",
.channels = EVERMORE_CHANNELS, /* consumer-grade GPS */
.probe = NULL, /* no probe */
.initializer = evermore_initializer, /* initialize the device */
.get_packet = packet_get, /* use generic one */
.parse_packet = evermore_parse_input, /* parse message packets */
.rtcm_writer = pass_rtcm, /* send RTCM data straight */
.speed_switcher = evermore_speed, /* we can change baud rates */
.mode_switcher = evermore_mode, /* there is a mode switcher */
.rate_switcher = NULL, /* no sample-rate switcher */
.cycle_chars = -1, /* ignore, no rate switch */
.wrapup = NULL, /* ignore, no wrapup */
.cycle = 1, /* updates every second */
};
#endif /* defined(EVERMORE_ENABLE) && defined(BINARY_ENABLE) */
syntax highlighted by Code2HTML, v. 0.9.1