summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--contrib/README4
-rw-r--r--contrib/gpsdlcdd.c588
2 files changed, 592 insertions, 0 deletions
diff --git a/contrib/README b/contrib/README
index ecabb748..322af612 100644
--- a/contrib/README
+++ b/contrib/README
@@ -43,3 +43,7 @@ maxsats.pl analyzes a collection of "Y" messages and outputs records
describing periods of excellent visibility (ie. 10 or more PRNs tracked).
With over 30 SVs on orbit, there are periods where 14 PRNs are above the
horizon, 13 are tracking, and at least 12 are actually used in a solution.
+
+gpsdlcdd.c is a lcdproc client for gpsd that turns your car computer
+into a rudimentary GPS receiver (provided you have at least a 4x40 LCD
+screen plugged in and configured to work with lcdproc).
diff --git a/contrib/gpsdlcdd.c b/contrib/gpsdlcdd.c
new file mode 100644
index 00000000..10ea926b
--- /dev/null
+++ b/contrib/gpsdlcdd.c
@@ -0,0 +1,588 @@
+/*
+ * Copyright (c) 2005 Jeff Francis <jeff@gritch.org>
+ *
+ * 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.
+ */
+
+/*
+ Jeff Francis
+ jeff@gritch.org
+ $Id:$
+
+ A client that passes gpsd data to lcdproc, turning your car computer
+ into a very expensive feature-free GPS receiver ;^). Currently
+ assumes a 4x40 LCD and writes data formatted to fit that size
+ screen. Plans to later support 2x40, 2x20, and maybe even 2x16 (if
+ I can squeeze the output down that much). Also displays 6-character
+ Maidenhead grid square output, for the hams among us.
+
+ This program assumes that LCDd (lcdproc) is running locally on the
+ default (13666) port. An option will be added later (in my copious
+ free time) to talk to a remote LCDd on an arbitrary port. For now,
+ the #defines (LCDDHOST and LCDDPORT) can be changed, and the code
+ recompiled if something other than the default port on localhost is
+ required.
+*/
+
+#define LCDDHOST "localhost"
+#define LCDDPORT 13666
+
+/* Not tested. */
+#undef CPUTEMP 1
+#define CLIMB 3
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <math.h>
+#include <errno.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+
+#include <sys/time.h> /* select() */
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <termios.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+
+#include <signal.h>
+
+#include "gps.h"
+
+/* Macro for declaring function arguments unused. */
+#if defined(__GNUC__)
+# define UNUSED __attribute__((unused)) /* Flag variable as unused */
+#else /* not __GNUC__ */
+# define UNUSED
+#endif
+
+static struct gps_data_t *gpsdata;
+static float altfactor = METERS_TO_FEET;
+static float speedfactor = MPS_TO_MPH;
+static char *altunits = "ft";
+static char *speedunits = "mph";
+
+#ifdef CLIMB
+double avgclimb, climb[CLIMB];
+#endif
+
+/* Global socket descriptor for LCDd. */
+int sd;
+
+#ifdef CPUTEMP
+/* Get the CPU temp from ACPI. */
+int cputemp()
+{
+ int res, file, from=0, to=0, len, i, temp;
+ char tempstring[255];
+ char numstring[255];
+
+ /* Open the input file. */
+ if((file=fopen("/proc/acpi/thermal_zone/THRM/temperature","r"))==NULL) {
+ return(0);
+ }
+
+ /* Grab the data. */
+ if((res=fgets(tempstring,255,file))==EOF) {
+ return(0);
+ }
+
+ /* Close the file. */
+ fclose(file);
+
+ /* How long is the string? */
+ len=strlen(tempstring);
+ if(len>255) len=255;
+
+ /* Copy the numeric digits out and terminate the string. */
+ for(i=0;i<len;i++) {
+ if(isdigit(tempstring[from++])) {
+ numstring[to++]=tempstring[from-1];
+ }
+ }
+ numstring[to]=(char)0;
+
+ /* Convert the string to a number and convert from C to F. */
+ temp=((atoi(numstring)*9)/5)+32;
+
+ /* Return the result. */
+ return(temp);
+}
+#endif
+
+/* Convert lat/lon to Maidenhead. Lifted from QGrid -
+ http://users.pandora.be/on4qz/qgrid/ */
+void latlon2maidenhead(char *st,float n,float e)
+{
+ int t1;
+ e=e+180.0;
+ t1=(int)(e/20);
+ st[0]=t1+'A';
+ e-=(float)t1*20.0;
+ t1=(int)e/2;
+ st[2]=t1+'0';
+ e-=(float)t1*2;
+#ifndef CLIMB
+ st[4]=(int)(e*12.0+0.5)+'A';
+#endif
+
+ n=n+90.0;
+ t1=(int)(n/10.0);
+ st[1]=t1+'A';
+ n-=(float)t1*10.0;
+ st[3]=(int)n+'0';
+ n-=(int)n;
+ n*=24; // convert to 24 division
+#ifndef CLIMB
+ st[5]=(int)(n+0.5)+'A';
+#endif
+}
+
+/* Daemonize me. */
+static void daemonize(void) {
+ int i;
+
+ /* Run as my child. */
+ i=fork();
+ if (i<0) exit(1); /* fork error */
+ if (i>0) exit(0); /* parent exits */
+
+ /* Obtain a new process group. */
+ setsid();
+
+ /* Close all open descriptors. */
+ for(i=getdtablesize();i>=0;--i)
+ close(i);
+
+ /* Reopen STDIN, STDOUT, STDERR to /dev/null. */
+ i=open("/dev/null",O_RDWR); /* STDIN */
+ dup(i); /* STDOUT */
+ dup(i); /* STDERR */
+
+ /* Know thy mask. */
+ umask(027);
+
+ /* Run from a known spot. */
+ chdir("/");
+
+ /* Catch child sig */
+ signal(SIGCHLD,SIG_IGN);
+
+ /* Ignore tty signals */
+ signal(SIGTSTP,SIG_IGN);
+ signal(SIGTTOU,SIG_IGN);
+ signal(SIGTTIN,SIG_IGN);
+}
+
+/* Read a line from a socket */
+ssize_t sockreadline(int sockd,void *vptr,size_t maxlen) {
+ ssize_t n,rc;
+ char c,*buffer;
+
+ buffer=vptr;
+
+ for (n=1;n<maxlen;n++) {
+
+ if((rc=read(sockd,&c,1))==1) {
+ *buffer++=c;
+ if(c=='\n')
+ break;
+ }
+ else if(rc==0) {
+ if(n==1)
+ return(0);
+ else
+ break;
+ }
+ else {
+ if(errno==EINTR)
+ continue;
+ return(-1);
+ }
+ }
+
+ *buffer=0;
+ return(n);
+}
+
+/* Write a line to a socket */
+ssize_t sockwriteline(int sockd,const void *vptr,size_t n) {
+ size_t nleft;
+ ssize_t nwritten;
+ const char *buffer;
+
+ buffer=vptr;
+ nleft=n;
+
+ while(nleft>0) {
+ if((nwritten= write(sockd,buffer,nleft))<=0) {
+ if(errno==EINTR)
+ nwritten=0;
+ else
+ return(-1);
+ }
+ nleft-=nwritten;
+ buffer+=nwritten;
+ }
+
+ return(n);
+}
+
+/* send a command to the LCD */
+int send_lcd(char *buf) {
+
+ int res;
+ char rcvbuf[255];
+
+ /* Limit the size of outgoing strings. */
+ if(strlen(buf)>255) {
+ buf[255]=0;
+ }
+
+ /* send the command */
+ res=sockwriteline(sd,buf,strlen(buf));
+
+ /* TODO: check return status */
+
+ /* read the data */
+ res=sockreadline(sd,rcvbuf,255);
+
+ /* null-terminate the string before printing */
+ /* rcvbuf[res-1]=0; XXX not using this at the moment... */
+
+ /* return the result */
+ return(res);
+}
+
+/* reset the LCD */
+static void reset_lcd(void) {
+
+ /* Initialize. In theory, we should look at what's returned, as it
+ tells us info on the attached LCD module. TODO. */
+ send_lcd("hello\n");
+
+ /* Set up the screen */
+ send_lcd("client_set name {GPSD test}\n");
+ send_lcd("screen_add gpsd\n");
+ send_lcd("widget_add gpsd one string\n");
+ send_lcd("widget_add gpsd two string\n");
+ send_lcd("widget_add gpsd three string\n");
+ send_lcd("widget_add gpsd four string\n");
+#ifdef CPUTEMP
+ send_lcd("widget_add gpsd five string\n");
+#endif
+}
+
+static enum deg_str_type deg_type = deg_dd;
+
+/* This gets called once for each new sentence. */
+static void update_lcd(struct gps_data_t *gpsdata,
+ char *message,
+ size_t len,
+ int level UNUSED)
+{
+ char tmpbuf[255];
+#ifdef CLIMB
+ char maidenhead[5];
+ maidenhead[4]=0;
+ int n;
+#else
+ char maidenhead[7];
+ maidenhead[6]=0;
+#endif
+ char *s;
+
+ /* Get our location in Maidenhead. */
+ latlon2maidenhead(maidenhead,gpsdata->fix.latitude,gpsdata->fix.longitude);
+
+ /* Fill in the latitude and longitude. */
+ if (gpsdata->fix.mode >= MODE_2D) {
+
+ s = deg_to_str(deg_type, fabs(gpsdata->fix.latitude));
+ sprintf(tmpbuf,"widget_set gpsd one 1 1 {Lat: %s %c}\n", s, (gpsdata->fix.latitude < 0) ? 'S' : 'N');
+ send_lcd(tmpbuf);
+
+ s = deg_to_str(deg_type, fabs(gpsdata->fix.longitude));
+ sprintf(tmpbuf,"widget_set gpsd two 1 2 {Lon: %s %c}\n", s, (gpsdata->fix.longitude < 0) ? 'W' : 'E');
+ send_lcd(tmpbuf);
+
+ sprintf(tmpbuf,"widget_set gpsd three 1 3 {%.1f %s %d deg}\n",
+ gpsdata->fix.speed*speedfactor, speedunits,
+ (int)(gpsdata->fix.track));
+ send_lcd(tmpbuf);
+
+ } else {
+
+ send_lcd("widget_set gpsd one 1 1 {Lat: n/a}\n");
+ send_lcd("widget_set gpsd two 1 2 {Lon: n/a}\n");
+ send_lcd("widget_set gpsd three 1 3 {n/a}\n");
+ }
+
+ /* Fill in the altitude and fix status. */
+ if (gpsdata->fix.mode == MODE_3D) {
+#ifdef CLIMB
+ for(n=0;n<CLIMB-2;n++) climb[n]=climb[n+1];
+ climb[CLIMB-1]=gpsdata->fix.climb;
+ avgclimb=0.0;
+ for(n=0;n<CLIMB;n++) avgclimb+=climb[n];
+ avgclimb/=CLIMB;
+ sprintf(tmpbuf,"widget_set gpsd four 1 4 {%d %s %s %d fpm }\n",
+ (int)(gpsdata->fix.altitude*altfactor), altunits, maidenhead, (int)(avgclimb * METERS_TO_FEET * 60));
+#else
+ sprintf(tmpbuf,"widget_set gpsd four 1 4 {%.1f %s %s}\n",
+ gpsdata->fix.altitude*altfactor, altunits, maidenhead);
+#endif
+ } else {
+ sprintf(tmpbuf,"widget_set gpsd four 1 4 {n/a}\n");
+ }
+ send_lcd(tmpbuf);
+
+#ifdef CPUTEMP
+ sprintf(tmpbuf,"widget_set gpsd five 35 4 {%d}\n",cputemp());
+ send_lcd(tmpbuf);
+#endif
+}
+
+static void usage( char *prog)
+{
+ (void)fprintf(stderr,
+ "Usage: %s [-h] [-v] [-V] [-s] [-l {d|m|s}] [-u {i|m|n}] [server[:port:[device]]]\n\n"
+ " -h Show this help, then exit\n"
+ " -v Show version, then exit\n"
+ " -V Show version, then exit\n"
+ " -s Sleep for 10 seconds before starting\n"
+ " -l {d|m|s} Select lat/lon format\n"
+ " d = DD.dddddd (default)\n"
+ " m = DD MM.mmmm'\n"
+ " s = DD MM' SS.sss\"\n"
+ " -u {i|m|n} Select Units\n"
+ " i = Imperial (default)\n"
+ " m = Metric'\n"
+ " n = Nautical\"\n"
+ , prog);
+
+ exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+ int option;
+ char *arg = NULL, *colon1, *colon2, *device = NULL, *server = NULL, *port = DEFAULT_GPSD_PORT;
+ char *err_str = NULL;
+
+ int rc;
+ struct sockaddr_in localAddr, servAddr;
+ struct hostent *h;
+
+ struct timeval timeout;
+ fd_set rfds;
+ int data;
+
+#ifdef CLIMB
+ int n;
+ for(n=0;n<CLIMB;n++) climb[n]=0.0;
+#endif
+
+ /* Process the options. Print help if requested. */
+ while ((option = getopt(argc, argv, "hvsl:u:")) != -1) {
+ switch (option) {
+ case 'v':
+ case 'V':
+ (void)fprintf(stderr, "$Id:$\n");
+ exit(0);
+ case 's':
+ sleep(10);
+ continue;
+ case 'l':
+ switch ( optarg[0] ) {
+ case 'd':
+ deg_type = deg_dd;
+ continue;
+ case 'm':
+ deg_type = deg_ddmm;
+ continue;
+ case 's':
+ deg_type = deg_ddmmss;
+ continue;
+ default:
+ (void)fprintf(stderr, "Unknown -l argument: %s\n", optarg);
+ /*@ -casebreak @*/
+ }
+ case 'u':
+ switch ( optarg[0] ) {
+ case 'i':
+ altfactor = METERS_TO_FEET;
+ altunits = "ft";
+ speedfactor = MPS_TO_MPH;
+ speedunits = "mph";
+ continue;
+ case 'n':
+ altfactor = METERS_TO_FEET;
+ altunits = "ft";
+ speedfactor = MPS_TO_KNOTS;
+ speedunits = "knots";
+ continue;
+ case 'm':
+ altfactor = 1;
+ altunits = "m";
+ speedfactor = MPS_TO_KPH;
+ speedunits = "kph";
+ continue;
+ default:
+ (void)fprintf(stderr, "Unknown -u argument: %s\n", optarg);
+ /*@ -casebreak @*/
+ }
+ case 'h': default:
+ usage(argv[0]);
+ break;
+ }
+ }
+
+ /* Grok the server, port, and device. */
+ /*@ -branchstate @*/
+ if (optind < argc) {
+ arg = strdup(argv[optind]);
+ /*@i@*/colon1 = strchr(arg, ':');
+ server = arg;
+ if (colon1 != NULL) {
+ if (colon1 == arg)
+ server = NULL;
+ else
+ *colon1 = '\0';
+ port = colon1 + 1;
+ colon2 = strchr(port, ':');
+ if (colon2 != NULL) {
+ if (colon2 == port)
+ port = NULL;
+ else
+ *colon2 = '\0';
+ device = colon2 + 1;
+ }
+ }
+ colon1 = colon2 = NULL;
+ }
+ /*@ +branchstate @*/
+
+ /* Daemonize... */
+ daemonize();
+
+ /* Open the stream to gpsd. */
+ /*@i@*/gpsdata = gps_open(server, port);
+ if (!gpsdata) {
+ switch ( errno ) {
+ case NL_NOSERVICE: err_str = "can't get service entry"; break;
+ case NL_NOHOST: err_str = "can't get host entry"; break;
+ case NL_NOPROTO: err_str = "can't get protocol entry"; break;
+ case NL_NOSOCK: err_str = "can't create socket"; break;
+ case NL_NOSOCKOPT: err_str = "error SETSOCKOPT SO_REUSEADDR"; break;
+ case NL_NOCONNECT: err_str = "can't connect to host"; break;
+ default: err_str = "Unknown"; break;
+ }
+ (void)fprintf( stderr,
+ "cgps: no gpsd running or network error: %d, %s\n",
+ errno, err_str);
+ exit(2);
+ }
+
+ /* Connect to LCDd */
+ h = gethostbyname(LCDDHOST);
+ if(h==NULL) {
+ printf("%s: unknown host '%s'\n",argv[0],LCDDHOST);
+ exit(1);
+ }
+
+ servAddr.sin_family = h->h_addrtype;
+ memcpy((char *) &servAddr.sin_addr.s_addr, h->h_addr_list[0], h->h_length);
+ servAddr.sin_port = htons(LCDDPORT);
+
+ /* create socket */
+ sd = socket(AF_INET, SOCK_STREAM, 0);
+ if(sd<0) {
+ perror("cannot open socket ");
+ exit(1);
+ }
+
+ /* bind any port number */
+ localAddr.sin_family = AF_INET;
+ localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
+ localAddr.sin_port = htons(0);
+
+ rc = bind(sd, (struct sockaddr *) &localAddr, sizeof(localAddr));
+ if(rc<0) {
+ printf("%s: cannot bind port TCP %u\n",argv[0],LCDDPORT);
+ perror("error ");
+ exit(1);
+ }
+
+ /* connect to server */
+ rc = connect(sd, (struct sockaddr *) &servAddr, sizeof(servAddr));
+ if(rc<0) {
+ perror("cannot connect ");
+ exit(1);
+ }
+
+ /* Do the initial field label setup. */
+ reset_lcd();
+
+ /* Here's where updates go. */
+ gps_set_raw_hook(gpsdata, update_lcd);
+
+ /* If the user requested a specific device, try to change to it. */
+ if (device) {
+ char *channelcmd = (char *)malloc(strlen(device)+3);
+
+ if (channelcmd) {
+ /*@i@*/(void)strcpy(channelcmd, "F=");
+ (void)strcpy(channelcmd+2, device);
+ (void)gps_query(gpsdata, channelcmd);
+ (void)free(channelcmd);
+ }
+ }
+
+ /* Turn on jitter buffering. */
+ (void)gps_query(gpsdata, "j=1\n");
+
+ /* Request "w+x" data from gpsd. */
+ (void)gps_query(gpsdata, "w+x\n");
+
+ for (;;) { /* heart of the client */
+
+ /* watch to see when it has input */
+ FD_ZERO(&rfds);
+ FD_SET(gpsdata->gps_fd, &rfds);
+
+ /* wait up to five seconds. */
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+
+ /* check if we have new information */
+ data = select(gpsdata->gps_fd + 1, &rfds, NULL, NULL, &timeout);
+
+ if (data == -1) {
+ fprintf( stderr, "cgps: socket error\n");
+ exit(2);
+ }
+ else if( data ) {
+ /* code that calls gps_poll(gpsdata) */
+ (void)gps_poll(gpsdata);
+ }
+
+ }
+
+}