/* * Copyright (c) 2005 Jeff Francis * * 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 #include #include #include #include #include #include #include #include #include /* select() */ #include #include #include #include #include #include #include #include #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;i0) 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;n0) { 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;nfix.climb; avgclimb=0.0; for(n=0;nfix.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;nh_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); } } }