/* * 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 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. Also displays 4- or 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. The #defines LCDDHOST and LCDDPORT can be changed to talk to a different host and TCP port. */ #define LCDDHOST "localhost" #define LCDDPORT 13666 #define CLIMB 3 #include #include #ifndef S_SPLINT_S #include #ifndef AF_UNSPEC #include #endif /* AF_UNSPEC */ #endif /* S_SPLINT_S */ #ifndef INADDR_ANY #include #endif /* INADDR_ANY */ #include /* for select() */ #include #include #include #include #include #include #include #ifndef S_SPLINT_S #include #include #include #endif /* S_SPLINT_S */ #include "gps.h" #include "gpsdclient.h" #include "revision.h" /* Prototypes. */ void latlon2maidenhead(char *st,float n,float e); ssize_t sockreadline(int sockd,void *vptr,size_t maxlen); ssize_t sockwriteline(int sockd,const void *vptr,size_t n); int send_lcd(char *buf); static struct fixsource_t source; 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; /* 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 } /* 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 < (ssize_t)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[256]; size_t outlen; /* Limit the size of outgoing strings. */ outlen = strlen(buf); if(outlen > 255) { outlen = 256; } /* send the command */ res=sockwriteline(sd,buf,outlen); /* TODO: check return status */ /* read the data */ res=sockreadline(sd,rcvbuf,255); /* null-terminate the string before printing */ /* rcvbuf[res-1]=0; FIX-ME: 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"); } 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 UNUSED, size_t len UNUSED) { char tmpbuf[255]; #ifdef CLIMB char maidenhead[5]; maidenhead[4]=0; int n; #else char maidenhead[7]; maidenhead[6]=0; #endif char *s; int track; /* this is where we implement source-device filtering */ if (gpsdata->dev.path[0] && source.device!=NULL && strcmp(source.device, gpsdata->dev.path) != 0) return; /* 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)); snprintf(tmpbuf, 254, "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)); snprintf(tmpbuf, 254, "widget_set gpsd two 1 2 {Lon: %s %c}\n", s, (gpsdata->fix.longitude < 0) ? 'W' : 'E'); send_lcd(tmpbuf); /* As a pilot, a heading of "0" gives me the heebie-jeebies (ie, 0 == "invalid heading data", 360 == "North"). */ track=(int)(gpsdata->fix.track); if(track == 0) track = 360; snprintf(tmpbuf, 254, "widget_set gpsd three 1 3 {%.1f %s %d deg}\n", gpsdata->fix.speed*speedfactor, speedunits, 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 snprintf(tmpbuf, 254, "widget_set gpsd four 1 4 {%.1f %s %s}\n", gpsdata->fix.altitude*altfactor, altunits, maidenhead); #endif } else { snprintf(tmpbuf, 254, "widget_set gpsd four 1 4 {n/a}\n"); } send_lcd(tmpbuf); } 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" " -s Sleep for 10 seconds before starting\n" " -j Turn on anti-jitter buffering\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, 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 == -1) { 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 == -1) { 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 == -1) { 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); gps_stream(&gpsdata, WATCH_ENABLE, NULL); 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, "lcdgps: socket error\n"); exit(2); } else if (data) { (void)gps_read(&gpsdata); } } }