#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <math.h>
#include <sys/time.h>
#include <stdarg.h>

#include "gpsd.h"

extern struct gps_type_t zodiac_binary;

#if defined(NMEA_ENABLE) || defined(SIRFII_ENABLL) || defined(EVERMORE_ENABLE)  || defined(ITALK_ENABLE) 
ssize_t pass_rtcm(struct gps_device_t *session, char *buf, size_t rtcmbytes)
/* most GPSes take their RTCM corrections straight up */
{
    return write(session->gpsdata.gps_fd, buf, rtcmbytes);
}
#endif

#ifdef NMEA_ENABLE
/**************************************************************************
 *
 * Generic driver -- straight NMEA 0183
 *
 **************************************************************************/

gps_mask_t nmea_parse_input(struct gps_device_t *session)
{
    if (session->packet_type == SIRF_PACKET) {
	gpsd_report(2, "SiRF packet seen when NMEA expected.\n");
#ifdef SIRFII_ENABLE
	return sirf_parse(session, session->outbuffer, session->outbuflen);
#else
	return 0;
#endif /* SIRFII_ENABLE */
    } else if (session->packet_type == EVERMORE_PACKET) {
	gpsd_report(2, "EverMore packet seen when NMEA expected.\n");
#ifdef EVERMORE_ENABLE
	/* we might never see a $PEMT, have this as a backstop */
	(void)gpsd_switch_driver(session, "EverMore binary");
	return evermore_parse(session, session->outbuffer, session->outbuflen);
#else
	return 0;
#endif /* EVERMORE_ENABLE */
    } else if (session->packet_type == NMEA_PACKET) {
	gps_mask_t st = 0;
	gpsd_report(2, "<= GPS: %s", session->outbuffer);
	if ((st=nmea_parse((char *)session->outbuffer, session))==0) {
#ifdef NON_NMEA_ENABLE
	    struct gps_type_t **dp;

	    /* maybe this is a trigger string for a driver we know about? */
	    for (dp = gpsd_drivers; *dp; dp++) {
		char	*trigger = (*dp)->trigger;

		if (trigger!=NULL && strncmp((char *)session->outbuffer, trigger, strlen(trigger))==0 && isatty(session->gpsdata.gps_fd)!=0) {
		    gpsd_report(1, "found %s.\n", trigger);
		    (void)gpsd_switch_driver(session, (*dp)->typename);
		    return 1;
		}
	    }
#endif /* NON_NMEA_ENABLE */
	    gpsd_report(1, "unknown sentence: \"%s\"\n", session->outbuffer);
	}
#ifdef NTPSHM_ENABLE
	/* this magic number is derived from observation */
     if ((st & TIME_SET) != 0 &&
         (session->gpsdata.fix.time != session->driver.nmea.last_fixtime)) {
             /* this magic number is derived from observation */
             (void)ntpshm_put(session, session->gpsdata.fix.time + 0.675);
             session->driver.nmea.last_fixtime = session->gpsdata.fix.time;
     }
#endif /* NTPSHM_ENABLE */
	return st;
    } else
	return 0;
}

static void nmea_initializer(struct gps_device_t *session)
{
#ifdef NMEA_ENABLE
    /*
     * Tell an FV18 to send GSAs so we'll know if 3D is accurate.
     * Suppress GLL and VTG.  Enable ZDA so dates will be accurate for replay.
     */
#define FV18_PROBE	"$PFEC,GPint,GSA01,DTM00,ZDA01,RMC01,GLL00,VTG00,GSV05"
    (void)nmea_send(session->gpsdata.gps_fd, FV18_PROBE);
    /* Sony CXD2951 chips: +GGA, -GLL, +GSA, +GSV, +RMC, -VTG, +ZDA, -PSGSA */
    (void)nmea_send(session->gpsdata.gps_fd, "@NC10151010");
    /* enable GPZDA on a Motorola Oncore GT+ */
    (void)nmea_send(session->gpsdata.gps_fd, "$PMOTG,ZDA,1");
    /* probe for Garmin serial GPS */
    (void)nmea_send(session->gpsdata.gps_fd, "$PGRMCE");
#endif /* NMEA_ENABLE */
#ifdef SIRFII_ENABLE
    /* probe for SiRF-II */
    (void)nmea_send(session->gpsdata.gps_fd, "$PSRF105,1");
#endif /* SIRFII_ENABLE */
#ifdef ITRAX_ENABLE
    /* probe for iTrax, looking for "OK" */
    (void)nmea_send(session->gpsdata.gps_fd, "$PFST");
#endif /* ITRAX_ENABLE */
#ifdef EVERMORE_ENABLE
    /* probe for EverMore by trying to read the LogConfig */
    (void)gpsd_write(session, "\x10\x02\x06\x8d\x00\x01\x00\x8e\x10\x03", 10);
#endif /* EVERMORE_ENABLE */
}

static struct gps_type_t nmea = {
    .typename       = "Generic NMEA",	/* full name of type */
    .trigger        = NULL,		/* it's the default */
    .channels       = 12,		/* consumer-grade GPS */
    .probe          = NULL,		/* no probe */
    .initializer    = nmea_initializer,	/* probe for special types */
    .get_packet     = packet_get,		/* use generic packet getter */
    .parse_packet   = nmea_parse_input,	/* how to interpret a packet */
    .rtcm_writer    = pass_rtcm,	/* write RTCM data straight */
    .speed_switcher = NULL,		/* no speed switcher */
    .mode_switcher  = NULL,		/* no mode switcher */
    .rate_switcher  = NULL,		/* no sample-rate switcher */
    .cycle_chars    = -1,		/* not relevant, no rate switch */
    .wrapup         = NULL,		/* no wrapup */
    .cycle          = 1,		/* updates every second */
};

static void garmin_initializer(struct gps_device_t *session)
{
#ifdef NMEA_ENABLE
    /* reset some config, AutoFix, WGS84, PPS */
    (void)nmea_send(session->gpsdata.gps_fd, "$PGRMC,A,,100,,,,,,A,,1,2,4,30");
    /* once a sec, no averaging, NMEA 2.3, WAAS */
    (void)nmea_send(session->gpsdata.gps_fd, "$PGRMC1,1,1,2,,,,2,W,N");
    /* get some more config info */
    (void)nmea_send(session->gpsdata.gps_fd, "$PGRMC1E");
    /* turn off all output */
    (void)nmea_send(session->gpsdata.gps_fd, "$PGRMO,,2");
    /* enable GPGGA, GPGSA, GPGSV, GPRMC on Garmin serial GPS */
    (void)nmea_send(session->gpsdata.gps_fd, "$PGRMO,GPGGA,1");
    (void)nmea_send(session->gpsdata.gps_fd, "$PGRMO,GPGSA,1");
    (void)nmea_send(session->gpsdata.gps_fd, "$PGRMO,GPGSV,1");
    (void)nmea_send(session->gpsdata.gps_fd, "$PGRMO,GPRMC,1");
#endif /* NMEA_ENABLE */
}

static struct gps_type_t garmin = {
    .typename       = "Garmin Serial",	/* full name of type */
    .trigger        = "$PGRMC",		/* Garmin private */
    .channels       = 12,		/* consumer-grade GPS */
    .probe          = NULL,		/* no probe */
    .initializer    = garmin_initializer,/* probe for special types */
    .get_packet     = packet_get,	/* use generic packet getter */
    .parse_packet   = nmea_parse_input,	/* how to interpret a packet */
    .rtcm_writer    = NULL,		/* some do, some don't, skip for now */
    .speed_switcher = NULL,		/* no speed switcher */
    .mode_switcher  = NULL,		/* no mode switcher */
    .rate_switcher  = NULL,		/* no sample-rate switcher */
    .cycle_chars    = -1,		/* not relevant, no rate switch */
    .wrapup         = NULL,		/* no wrapup */
    .cycle          = 1,		/* updates every second */
};

#if FV18_ENABLE
/**************************************************************************
 *
 * FV18 -- uses 2 stop bits, needs to be told to send GSAs
 *
 **************************************************************************/

static struct gps_type_t fv18 = {
    .typename       = "San Jose Navigation FV18",	/* full name of type */
    .trigger        = FV18_PROBE,	/* FV18s should echo the probe */
    .channels       = 12,		/* consumer-grade GPS */
    .probe          = NULL,		/* mo probe */
    .initializer    = NULL,		/* to be sent unconditionally */
    .get_packet     = packet_get,	/* how to get a packet */
    .parse_packet   = nmea_parse_input,	/* how to interpret a packet */
    .rtcm_writer    = pass_rtcm,	/* write RTCM data straight */
    .speed_switcher = NULL,		/* no speed switcher */
    .mode_switcher  = NULL,		/* no mode switcher */
    .rate_switcher  = NULL,		/* no sample-rate switcher */
    .cycle_chars    = -1,		/* not relevant, no rate switch */
    .wrapup         = NULL,		/* no wrapup */
    .cycle          = 1,		/* updates every second */
};
#endif /* FV18_ENABLE */

/**************************************************************************
 *
 * SiRF-II NMEA
 *
 * This NMEA -mode driver is a fallback in case the SiRF chipset has
 * firmware too old for binary to be useful, or we're not compiling in
 * the SiRF binary driver at all.
 *
 **************************************************************************/

static void sirf_initializer(struct gps_device_t *session)
{
    /* (void)nmea_send(session->gpsdata.gps_fd, "$PSRF105,0"); */
    (void)nmea_send(session->gpsdata.gps_fd, "$PSRF105,0");
    (void)nmea_send(session->gpsdata.gps_fd, "$PSRF103,05,00,00,01"); /* no VTG */
    (void)nmea_send(session->gpsdata.gps_fd, "$PSRF103,01,00,00,01"); /* no GLL */
}

static bool sirf_switcher(struct gps_device_t *session, int nmea, unsigned int speed) 
/* switch GPS to specified mode at 8N1, optionally to binary */
{
    if (nmea_send(session->gpsdata.gps_fd, "$PSRF100,%d,%d,8,1,0", nmea,speed) < 0)
	return false;
    return true;
}

static bool sirf_speed(struct gps_device_t *session, unsigned int speed)
/* change the baud rate, remaining in SiRF NMWA mode */
{
    return sirf_switcher(session, 1, speed);
}

static void sirf_mode(struct gps_device_t *session, int mode)
/* change mode to SiRF binary, speed unchanged */
{
    if (mode == 1) {
	(void)gpsd_switch_driver(session, "SiRF-II binary");
	session->gpsdata.driver_mode = (unsigned int)sirf_switcher(session, 0, session->gpsdata.baudrate);
    } else
	session->gpsdata.driver_mode = 0;
}

static struct gps_type_t sirfII_nmea = {
    .typename      = "SiRF-II NMEA",	/* full name of type */
#ifndef SIRFII_ENABLE
    .trigger       = "$Ack Input105.",	/* expected response to SiRF PSRF105 */
#else
    .trigger       = NULL,		/* let the binary driver have it */
#endif /* SIRFII_ENABLE */
    .channels      = 12,		/* consumer-grade GPS */
    .probe         = NULL,		/* no probe */
    .initializer   = sirf_initializer,	/* turn off debugging messages */
    .get_packet    = packet_get,	/* how to get a packet */
    .parse_packet  = nmea_parse_input,	/* how to interpret a packet */
    .rtcm_writer   = pass_rtcm,		/* write RTCM data straight */
    .speed_switcher= sirf_speed,	/* we can change speeds */
    .mode_switcher = sirf_mode,		/* there's a mode switch */
    .rate_switcher = NULL,		/* no sample-rate switcher */
    .cycle_chars   = -1,		/* not relevant, no rate switch */
    .wrapup         = NULL,		/* no wrapup */
    .cycle          = 1,		/* updates every second */
};

#if TRIPMATE_ENABLE
/**************************************************************************
 *
 * TripMate -- extended NMEA, gets faster fix when primed with lat/long/time
 *
 **************************************************************************/

/*
 * Some technical FAQs on the TripMate:
 * http://vancouver-webpages.com/pub/peter/tripmate.faq
 * http://www.asahi-net.or.jp/~KN6Y-GTU/tripmate/trmfaqe.html
 * The TripMate was discontinued sometime before November 1998
 * and was replaced by the Zodiac EarthMate.
 */

static void tripmate_initializer(struct gps_device_t *session)
{
    /* TripMate requires this response to the ASTRAL it sends at boot time */
    (void)nmea_send(session->gpsdata.gps_fd, "$IIGPQ,ASTRAL");
    /* stop it sending PRWIZCH */
    (void)nmea_send(session->gpsdata.gps_fd, "$PRWIILOG,ZCH,V,,");
}

static struct gps_type_t tripmate = {
    .typename      = "Delorme TripMate",	/* full name of type */
    .trigger       ="ASTRAL",			/* tells us to switch */
    .channels      = 12,			/* consumer-grade GPS */
    .probe         = NULL,			/* no probe */
    .initializer   = tripmate_initializer,	/* send unconfitionally */
    .get_packet    = packet_get,		/* how to get a packet */
    .parse_packet  = nmea_parse_input,		/* how to interpret a packet */
    .rtcm_writer   = pass_rtcm,			/* send RTCM data straight */
    .speed_switcher= NULL,			/* no speed switcher */
    .mode_switcher = NULL,			/* no mode switcher */
    .rate_switcher = NULL,			/* no sample-rate switcher */
    .cycle_chars   = -1,			/* no rate switch */
    .wrapup         = NULL,			/* no wrapup */
    .cycle          = 1,			/* updates every second */
};
#endif /* TRIPMATE_ENABLE */

#ifdef EARTHMATE_ENABLE
/**************************************************************************
 *
 * Zodiac EarthMate textual mode
 *
 * Note: This is the pre-2003 version using Zodiac binary protocol.
 * It has been replaced with a design that uses a SiRF-II chipset.
 *
 **************************************************************************/

static struct gps_type_t earthmate;

/*
 * There is a good HOWTO at <http://www.hamhud.net/ka9mva/earthmate.htm>.
 */

static void earthmate_close(struct gps_device_t *session)
{
    /*@i@*/session->device_type = &earthmate;
}

static void earthmate_initializer(struct gps_device_t *session)
{
    (void)write(session->gpsdata.gps_fd, "EARTHA\r\n", 8);
    (void)usleep(10000);
    /*@i@*/session->device_type = &zodiac_binary;
    zodiac_binary.wrapup = earthmate_close;
    if (zodiac_binary.initializer) zodiac_binary.initializer(session);
}

/*@ -redef @*/
static struct gps_type_t earthmate = {
    .typename      = "Delorme EarthMate (pre-2003, Zodiac chipset)",
    .trigger       = "EARTHA",			/* Earthmate trigger string */
    .channels      = 12,			/* consumer-grade GPS */
    .probe         = NULL,			/* no probe */
    .initializer   = earthmate_initializer,	/* switch us to Zodiac mode */
    .get_packet    = packet_get,		/* how to get a packet */
    .parse_packet  = nmea_parse_input,		/* how to interpret a packet */
    .rtcm_writer   = NULL,			/* don't send RTCM data */
    .speed_switcher= NULL,			/* no speed switcher */
    .mode_switcher = NULL,			/* no mode switcher */
    .rate_switcher = NULL,			/* no sample-rate switcher */
    .cycle_chars   = -1,			/* no rate switch */
    .wrapup         = NULL,			/* no wrapup code */
    .cycle          = 1,			/* updates every second */
};
/*@ -redef @*/
#endif /* EARTHMATE_ENABLE */


#ifdef ITRAX_ENABLE
/**************************************************************************
 *
 * The NMEA mode of the iTrax chipset, as used in the FastTrax and others.
 *
 * As described by v1.31 of the NMEA Protocol Specification for the
 * iTrax02 Evaluation Kit, 2003-06-12.
 * v1.18 of the  manual, 2002-19-6, describes effectively
 * the same protocol, but without ZDA.
 *
 **************************************************************************/

/*
 * Enable GGA=0x2000, RMC=0x8000, GSA=0x0002, GSV=0x0001, ZDA=0x0004.
 * Disable GLL=0x1000, VTG=0x4000, FOM=0x0020, PPS=0x0010.
 * This is 82+75+67+(3*60)+34 = 438 characters 
 * 
 * 1200   => at most 1 fix per 4 seconds
 * 2400   => at most 1 fix per 2 seconds
 * 4800   => at most 1 fix per 1 seconds
 * 9600   => at most 2 fixes per second
 * 19200  => at most 4 fixes per second
 * 57600  => at most 13 fixes per second
 * 115200 => at most 26 fixes per second
 *
 * We'd use FOM, but they don't specify a confidence interval.
 */
#define ITRAX_MODESTRING	"$PFST,NMEA,A007,%d\r\n"

static int literal_send(int fd, const char *fmt, ... )
/* ship a raw command to the GPS */
{
    int status;
    char buf[BUFSIZ];
    va_list ap;

    va_start(ap, fmt) ;
    (void)vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);
    status = (int)write(fd, buf, strlen(buf));
    if (status == (int)strlen(buf)) {
	gpsd_report(2, "=> GPS: %s\n", buf);
	return status;
    } else {
	gpsd_report(2, "=> GPS: %s FAILED\n", buf);
	return -1;
    }
}

static void itrax_initializer(struct gps_device_t *session)
/* start navigation and synchronous mode */
{
    /* initialize GPS clock with current system time */ 
    struct tm when;
    double integral, fractional;
    time_t intfixtime;
    char buf[31], frac[6];
    fractional = modf(timestamp(), &integral);
    intfixtime = (time_t)integral;
    (void)gmtime_r(&intfixtime, &when);
    (void)strftime(buf, sizeof(buf), "$PFST,INITAID,%H%M%S.XX,%d%m%y\r\n", &when);
    (void)snprintf(frac, sizeof(frac), "%.2f", fractional);
    buf[21] = frac[2]; buf[22] = frac[3];
    (void)literal_send(session->gpsdata.gps_fd, buf);

    (void)literal_send(session->gpsdata.gps_fd, "$PFST,START\r\n");
    (void)literal_send(session->gpsdata.gps_fd, "$PFST,SYNCMODE,1\r\n");
    (void)literal_send(session->gpsdata.gps_fd, 
		    ITRAX_MODESTRING, session->gpsdata.baudrate);
}

static bool itrax_speed(struct gps_device_t *session, speed_t speed)
/* change the baud rate */
{
    return literal_send(session->gpsdata.gps_fd, ITRAX_MODESTRING, speed) >= 0;
}

static bool itrax_rate(struct gps_device_t *session, double rate)
/* change the sample rate of the GPS */
{
    return literal_send(session->gpsdata.gps_fd, "$PSFT,FIXRATE,%d\r\n", rate) >= 0;
}

static void itrax_wrap(struct gps_device_t *session)
/* stop navigation, this cuts the power drain */
{
    (void)literal_send(session->gpsdata.gps_fd, "$PFST,SYNCMODE,0\r\n");
    (void)literal_send(session->gpsdata.gps_fd, "$PFST,STOP\r\n");
}

/*@ -redef @*/
static struct gps_type_t itrax = {
    .typename      = "iTrax",		/* full name of type */
    .trigger       = "$PFST,OK",	/* tells us to switch to Itrax */
    .channels      = 12,		/* consumer-grade GPS */
    .probe         = NULL,		/* no probe */
    .initializer   = itrax_initializer,	/* initialize */
    .get_packet    = packet_get,	/* how to get a packet */
    .parse_packet  = nmea_parse_input,	/* how to interpret a packet */
    .rtcm_writer   = NULL,		/* iTrax doesn't support DGPS/WAAS/EGNOS */
    .speed_switcher= itrax_speed,	/* no speed switcher */
    .mode_switcher = NULL,		/* no mode switcher */
    .rate_switcher = itrax_rate,	/* there's a sample-rate switcher */
    .cycle_chars   = 438,		/* not relevant, no rate switch */
    .wrapup         = itrax_wrap,	/* sleep the receiver */
    .cycle          = 1,		/* updates every second */
};
/*@ -redef @*/
#endif /* ITRAX_ENABLE */
#endif /* NMEA_ENABLE */

#ifdef RTCM104_ENABLE
/**************************************************************************
 *
 * RTCM-104, used for broadcasting DGPS corrections and by DGPS radios
 *
 **************************************************************************/

static gps_mask_t rtcm104_analyze(struct gps_device_t *session)
{
    gpsd_report(5, "RTCM packet type 0x%02x length %d words: %s\n", 
		session->gpsdata.rtcm.type,
		session->gpsdata.rtcm.length+2,
		gpsd_hexdump(session->driver.isgps.buf, (session->gpsdata.rtcm.length+2)*sizeof(isgps30bits_t)));
    return RTCM_SET;
}

static struct gps_type_t rtcm104 = {
    .typename      = "RTCM104",		/* full name of type */
    .trigger       = NULL,		/* no recognition string */
    .channels      = 12,		/* consumer-grade GPS */
    .probe         = NULL,		/* no probe */
    .initializer   = NULL,		/* no initializer */
    .get_packet    = packet_get,	/* how to get a packet */
    .parse_packet  = rtcm104_analyze,	/* packet getter does the parsing */
    .rtcm_writer   = NULL,		/* don't send RTCM data,  */
    .speed_switcher= NULL,		/* no speed switcher */
    .mode_switcher = NULL,		/* no mode switcher */
    .rate_switcher = NULL,		/* no sample-rate switcher */
    .cycle_chars   = -1,		/* not relevant, no rate switch */
    .wrapup         = NULL,		/* no wrapup code */
    .cycle          = 1,		/* updates every second */
};
#endif /* RTCM104_ENABLE */

extern struct gps_type_t garmin_binary, sirf_binary, tsip_binary;
extern struct gps_type_t evermore_binary, italk_binary, trueNorth;

/*@ -nullassign @*/
/* the point of this rigamarole is to not have to export a table size */
static struct gps_type_t *gpsd_driver_array[] = {
#ifdef NMEA_ENABLE
    &nmea, 
    &sirfII_nmea,
#if FV18_ENABLE
    &fv18,
    &garmin,
#endif /* FV18_ENABLE */
#if TRIPMATE_ENABLE
    &tripmate,
#endif /* TRIPMATE_ENABLE */
#if EARTHMATE_ENABLE
    &earthmate, 
#endif /* EARTHMATE_ENABLE */
#if ITRAX_ENABLE
    &itrax, 
#endif /* ITRAX_ENABLE */
#endif /* NMEA_ENABLE */
#ifdef ZODIAC_ENABLE
    &zodiac_binary,
#endif /* ZODIAC_ENABLE */
#if GARMIN_ENABLE
    &garmin_binary,
#endif /* GARMIN_ENABLE */
#ifdef SIRFII_ENABLE
    &sirf_binary, 
#endif /* SIRFII_ENABLE */
#ifdef TSIP_ENABLE
    &tsip_binary, 
#endif /* TSIP_ENABLE */
#ifdef TNT_ENABLE
    &trueNorth,
#endif /* TSIP_ENABLE */
#ifdef EVERMORE_ENABLE
    &evermore_binary, 
#endif /* EVERMORE_ENABLE */
#ifdef ITALK_ENABLE
    &italk_binary, 
#endif /* ITALK_ENABLE */
#ifdef RTCM104_ENABLE
    &rtcm104, 
#endif /* RTCM104_ENABLE */
    NULL,
};
/*@ +nullassign @*/
struct gps_type_t **gpsd_drivers = &gpsd_driver_array[0];


syntax highlighted by Code2HTML, v. 0.9.1