/*
 * Copyright (c) 2005 Chris Kuethe <chris.kuethe@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/cdefs.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BS 512

#define NUM 8
char *poll = "SPAMDQTV\n";
char *host = "127.0.0.1";
unsigned int want_exit = 0;
unsigned short port = 2947;
unsigned int sl = 5;

char *progname;

void process(char *);
void usage(void);
void dnserr(void);
void bye(int);
void process(char *);
void write_record(void);
void header(void);
void footer(void);
void track_start(void);
void track_end(void);
int tracking = 0;

struct {
	double latitude;
	double longitude;
	float altitude;
	float speed;
	float course;
	float hdop;
	short svs;
	char status;
	char mode;
	char time[32];
} gps_ctx;


int
main(int argc, char **argv){
	int ch, fd, l, rl;
	char *buf;
	struct in_addr addr;
	struct sockaddr_in sa;
	struct hostent *he;
	struct timeval tv;
	fd_set fds;

	progname = argv[0];
	while ((ch = getopt(argc, argv, "hVi:s:p:")) != -1){
	switch (ch) {
	case 'i':
		sl = (unsigned int)atoi(optarg);
		if (sl < 1)
			sl = 1;
		if (sl >= 3600)
			fprintf(stderr, "WARNING: polling interval is an hour or more!\n");
		break;
	case 's':
		host = optarg;
		break;
	case 'p':
		port = (unsigned short)atoi(optarg);
		break;
	case 'V':
		(void)fprintf(stderr, "SVN ID: $Id: cgpxlogger.c$ \n");
		exit(0);
	default:
		usage();
		/* NOTREACHED */
	}
	}

	argc -= optind;
	argv += optind;

	bzero((char *)&sa, sizeof(sa));
	if( inet_aton(host, &addr) ){
		bcopy(&addr, (char *)&sa.sin_addr, sizeof(addr));
	} else {
		he = gethostbyname(host);
		if (he != NULL){
			bcopy(he->h_addr_list[0], (char *)&sa.sin_addr, he->h_length);
		} else {
			dnserr();
			/* NOTREACHED */
		}
	}

	if ((buf = malloc( BS )) == NULL){
		perror(NULL);
		exit(1);
	}

	sa.sin_port= htons(port);
	sa.sin_family = AF_INET;

	if ((fd = socket(AF_INET,SOCK_STREAM,0)) == -1){
		perror(NULL);
		exit(1);
	}

	if (connect(fd,(struct sockaddr *)&sa,sizeof(sa)) == -1){
		perror(NULL);
		close(fd);
		exit(1);
	}

	l = strlen(poll);

	FD_ZERO(&fds);
	FD_SET(fd, &fds);

	signal(SIGINT, bye);
	signal(SIGTERM, bye);
	signal(SIGQUIT, bye);
	signal(SIGHUP, bye);

	header();
	for(;;){
		if (want_exit){
			footer();
			fprintf(stderr, "Exiting on signal %d!\n", want_exit);
			fflush(NULL);
			shutdown(fd, SHUT_RDWR);
			close(fd);
			exit(0);
		}

		write(fd, poll, l);

		tv.tv_usec = 250000;
		tv.tv_sec = 0;
		select(fd + 1, &fds, NULL, NULL, &tv);

		bzero(buf, BS);
		if ((rl = read(fd, buf, BS - 1)) != -1){
			process(buf);
		} else {
			if ((errno != EINTR) && (errno != EAGAIN)){
				/* ignore EINTR and EAGAIN */
				want_exit = SIGPIPE;
				sl = 1;
				fprintf(stderr,"%s\n", strerror(errno));
			}
		}
		sleep(sl);
	}
}

void usage(){
	fprintf(stderr, "Usage: %s [-h] [-s server] [-p port] [-i interval]\n\t", progname);
	fprintf(stderr, "\tdefaults to '%s -s 127.0.0.1 -p 2947 -i 5'\n", progname);
	exit(1);
}

void dnserr(){
	herror(progname);
	exit(1);
}

void bye(int signum){ want_exit = signum; }

void process(char *buf){
	char *answers[NUM + 2], **ap;
	int i, j;
	char c;

	if (strncmp("GPSD,", buf, 5) != 0)
		return; /* lines should start with "GPSD," */

	/* nuke them pesky trailing CR & LF */
	i = strlen(buf);
	if((buf[i - 1] == '\r') || (buf[i - 1] == '\n'))
		buf[i - 1] = '\0';

	i = strlen(buf);
	if((buf[i - 1] == '\r') || (buf[i - 1] == '\n'))
		buf[i - 1] = '\0';

	/* tokenize the string at the commas */
 	for (ap = answers; ap < &answers[NUM+1] &&
 		(*ap = strsep(&buf, ",")) != NULL;) {
 		if (**ap != '\0')
 			ap++;
 	}
 	*ap = NULL;

	bzero( &gps_ctx, sizeof(gps_ctx));
	/* do stuff with each of the strings */
	for(i = 0; i < NUM+1 ; i++){
		c = answers[i][0];
		switch(c){
		case 'S':
			sscanf(answers[i], "S=%d", &j);
			gps_ctx.status = j;
			break;
		case 'P':
			sscanf(answers[i], "P=%lf %lf", &gps_ctx.latitude, &gps_ctx.longitude);
			break;
		case 'A':
			sscanf(answers[i], "A=%f", &gps_ctx.altitude);
			break;
		case 'M':
			sscanf(answers[i], "M=%d", &j);
			gps_ctx.mode = j;
			break;
		case 'Q':
			sscanf(answers[i], "Q=%hd %*s %f", &gps_ctx.svs, &gps_ctx.hdop );
			break;
		case 'T':
			sscanf(answers[i], "T=%f", &gps_ctx.course);
			break;
		case 'V':
			sscanf(answers[i], "V=%f", &gps_ctx.speed);
			break;
		case 'D':
			sscanf(answers[i], "D=%s", (char *)&gps_ctx.time);
			break;
		default: /* no-op */ ;
		}
	}
	if ((gps_ctx.mode > 1) && (gps_ctx.status > 0))
		write_record();
	else
		track_end();
}

void write_record(){
	track_start();
	printf("      <trkpt lat=\"%.6f\" ", gps_ctx.latitude );
	printf("lon=\"%.6f\">\n", gps_ctx.longitude );

	if ((gps_ctx.status >= 2) && (gps_ctx.mode >= 3)){ /* dgps or pps */
		if (gps_ctx.mode == 4) { /* military pps */
			printf("        <fix>pps</fix>\n");
		} else { /* civilian dgps or sbas */
			printf("        <fix>dgps</fix>\n");
		}
	} else { /* no dgps or pps */
		if (gps_ctx.mode == 3) {
			printf("        <fix>3d</fix>\n");
		} else if (gps_ctx.mode == 2) {
			printf("        <fix>2d</fix>\n");
		} else if (gps_ctx.mode == 1) {
			printf("        <fix>none</fix>\n");
		} /* don't print anything if no fix indicator */
	}

	/* print altitude if we have a fix and it's 3d of some sort */
	if ((gps_ctx.mode >= 3) && (gps_ctx.status >= 1))
		printf("        <ele>%.2f</ele>\n", gps_ctx.altitude);

	/* SiRF reports HDOP in 0.2 steps and the lowest I've seen is 0.6 */
	if (gps_ctx.svs >= 0.2)
		printf("        <hdop>%.1f</hdop>\n", gps_ctx.hdop);

	/* print # satellites used in fix, if reasonable to do so */
	if ((gps_ctx.svs > 0) && (gps_ctx.mode >= 2))
		printf("        <sat>%d</sat>\n", gps_ctx.svs);

	if (strlen(gps_ctx.time)) /* plausible timestamp */
		printf("        <time>%s</time>\n", gps_ctx.time);
	printf("      </trkpt>\n");
	fflush(stdout);
}

void header(){
	printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
	printf("<gpx version=\"1.1\" creator=\"GPX GPSD client\"\n");
	printf("        xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
	printf("        xmlns=\"http://www.topografix.com/GPX/1.1\"\n");
	printf("        xsi:schemaLocation=\"http://www.topografix.com/GPS/1/1\n");
	printf("        http://www.topografix.com/GPX/1/1/gpx.xsd\">\n");
	printf("  <metadata>\n");
	printf("    <name>GPX GPSD client</name>\n");
	printf("    <author>Chris Kuethe (chris.kuethe@gmail.com)</author>\n");
	printf("    <copyright>2-clause BSD License</copyright>\n");
	printf("  </metadata>\n");
	printf("\n");
	printf("\n");
}

void footer(){
	track_end();
	printf("</gpx>\n");
}

void track_start(){
	if (tracking != 0)
		return;
	printf("<!-- track start -->\n  <trk>\n    <trkseg>\n");
	tracking = 1;
}


void track_end(){
	if (tracking == 0)
		return;
	printf("    </trkseg>\n  </trk>\n<!-- track end -->\n");
	tracking = 0;
}



syntax highlighted by Code2HTML, v. 0.9.1