2 * Copyright (c) 2005 Jeff Francis <jeff@gritch.org>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 A client that passes gpsd data to lcdproc, turning your car computer
22 into a very expensive feature-free GPS receiver ;^). Currently
23 assumes a 4x40 LCD and writes data formatted to fit that size
24 screen. Also displays 4- or 6-character Maidenhead grid square
25 output for the hams among us.
27 This program assumes that LCDd (lcdproc) is running locally on the
28 default (13666) port. The #defines LCDDHOST and LCDDPORT can be
29 changed to talk to a different host and TCP port.
32 #define LCDDHOST "localhost"
33 #define LCDDPORT 13666
38 #include "gpsd_config.h"
44 #endif /* S_SPLINT_S */
47 #ifdef HAVE_SYS_SELECT_H
48 #include <sys/select.h>
49 #endif /* HAVE_SYS_SELECT_H */
51 #ifdef HAVE_SYS_SOCKET_H
52 #include <sys/socket.h>
53 #endif /* HAVE_SYS_SOCKET_H */
54 #endif /* S_SPLINT_S */
56 #include <sys/time.h> /* select() */
61 #endif /* HAVE_TERMIOS_H */
64 #ifdef HAVE_NETINET_IN_H
65 #include <netinet/in.h>
66 #endif /* HAVE_NETINET_IN_H */
67 #ifdef HAVE_ARPA_INET_H
68 #include <arpa/inet.h>
69 #endif /* HAVE_ARPA_INET_H */
72 #endif /* HAVE_NETDB_H */
73 #endif /* S_SPLINT_S */
79 #include "gpsdclient.h"
82 /* Macro for declaring function arguments unused. */
84 # define UNUSED __attribute__((unused)) /* Flag variable as unused */
85 #else /* not __GNUC__ */
90 void latlon2maidenhead(char *st,float n,float e);
91 static void daemonize(void);
92 ssize_t sockreadline(int sockd,void *vptr,size_t maxlen);
93 ssize_t sockwriteline(int sockd,const void *vptr,size_t n);
94 int send_lcd(char *buf);
96 static struct fixsource_t source;
97 static struct gps_data_t gpsdata;
98 static float altfactor = METERS_TO_FEET;
99 static float speedfactor = MPS_TO_MPH;
100 static char *altunits = "ft";
101 static char *speedunits = "mph";
104 double avgclimb, climb[CLIMB];
107 /* Global socket descriptor for LCDd. */
110 /* Convert lat/lon to Maidenhead. Lifted from QGrid -
111 http://users.pandora.be/on4qz/qgrid/ */
112 void latlon2maidenhead(char *st,float n,float e)
123 st[4]=(int)(e*12.0+0.5)+'A';
132 n*=24; // convert to 24 division
134 st[5]=(int)(n+0.5)+'A';
139 static void daemonize(void) {
142 /* Run as my child. */
144 if (i == -1) exit(1); /* fork error */
145 if (i > 0) exit(0); /* parent exits */
147 /* Obtain a new process group. */
150 /* Close all open descriptors. */
151 for(i=getdtablesize();i>=0;--i)
154 /* Reopen STDIN, STDOUT, STDERR to /dev/null. */
155 i=open("/dev/null",O_RDWR); /* STDIN */
156 ignore=dup(i); /* STDOUT */
157 ignore=dup(i); /* STDERR */
162 /* Run from a known location. */
165 /* Catch child sig */
166 signal(SIGCHLD,SIG_IGN);
168 /* Ignore tty signals */
169 signal(SIGTSTP,SIG_IGN);
170 signal(SIGTTOU,SIG_IGN);
171 signal(SIGTTIN,SIG_IGN);
174 /* Read a line from a socket */
175 ssize_t sockreadline(int sockd,void *vptr,size_t maxlen) {
181 for (n = 1; n < (ssize_t)maxlen; n++) {
183 if((rc=read(sockd,&c,1))==1) {
205 /* Write a line to a socket */
206 ssize_t sockwriteline(int sockd,const void *vptr,size_t n) {
215 if((nwritten= write(sockd,buffer,nleft))<=0) {
228 /* send a command to the LCD */
229 int send_lcd(char *buf) {
235 /* Limit the size of outgoing strings. */
236 outlen = strlen(buf);
241 /* send the command */
242 res=sockwriteline(sd,buf,outlen);
244 /* TODO: check return status */
247 res=sockreadline(sd,rcvbuf,255);
249 /* null-terminate the string before printing */
250 /* rcvbuf[res-1]=0; FIX-ME: not using this at the moment... */
252 /* return the result */
257 static void reset_lcd(void) {
259 /* Initialize. In theory, we should look at what's returned, as it
260 tells us info on the attached LCD module. TODO. */
263 /* Set up the screen */
264 send_lcd("client_set name {GPSD test}\n");
265 send_lcd("screen_add gpsd\n");
266 send_lcd("widget_add gpsd one string\n");
267 send_lcd("widget_add gpsd two string\n");
268 send_lcd("widget_add gpsd three string\n");
269 send_lcd("widget_add gpsd four string\n");
272 static enum deg_str_type deg_type = deg_dd;
274 /* This gets called once for each new sentence. */
275 static void update_lcd(struct gps_data_t *gpsdata,
276 char *message UNUSED,
291 /* this is where we implement source-device filtering */
292 if (gpsdata->dev.path[0] && source.device!=NULL && strcmp(source.device, gpsdata->dev.path) != 0)
295 /* Get our location in Maidenhead. */
296 latlon2maidenhead(maidenhead,gpsdata->fix.latitude,gpsdata->fix.longitude);
298 /* Fill in the latitude and longitude. */
299 if (gpsdata->fix.mode >= MODE_2D) {
301 s = deg_to_str(deg_type, fabs(gpsdata->fix.latitude));
302 snprintf(tmpbuf, 254, "widget_set gpsd one 1 1 {Lat: %s %c}\n", s, (gpsdata->fix.latitude < 0) ? 'S' : 'N');
305 s = deg_to_str(deg_type, fabs(gpsdata->fix.longitude));
306 snprintf(tmpbuf, 254, "widget_set gpsd two 1 2 {Lon: %s %c}\n", s, (gpsdata->fix.longitude < 0) ? 'W' : 'E');
309 /* As a pilot, a heading of "0" gives me the heebie-jeebies (ie, 0
310 == "invalid heading data", 360 == "North"). */
311 track=(int)(gpsdata->fix.track);
312 if(track == 0) track = 360;
314 snprintf(tmpbuf, 254, "widget_set gpsd three 1 3 {%.1f %s %d deg}\n",
315 gpsdata->fix.speed*speedfactor, speedunits,
321 send_lcd("widget_set gpsd one 1 1 {Lat: n/a}\n");
322 send_lcd("widget_set gpsd two 1 2 {Lon: n/a}\n");
323 send_lcd("widget_set gpsd three 1 3 {n/a}\n");
326 /* Fill in the altitude and fix status. */
327 if (gpsdata->fix.mode == MODE_3D) {
329 for(n=0;n<CLIMB-2;n++) climb[n]=climb[n+1];
330 climb[CLIMB-1]=gpsdata->fix.climb;
332 for(n=0;n<CLIMB;n++) avgclimb+=climb[n];
334 snprintf(tmpbuf, 254, "widget_set gpsd four 1 4 {%d %s %s %d fpm }\n",
335 (int)(gpsdata->fix.altitude*altfactor), altunits, maidenhead, (int)(avgclimb * METERS_TO_FEET * 60));
337 snprintf(tmpbuf, 254, "widget_set gpsd four 1 4 {%.1f %s %s}\n",
338 gpsdata->fix.altitude*altfactor, altunits, maidenhead);
341 snprintf(tmpbuf, 254, "widget_set gpsd four 1 4 {n/a}\n");
346 static void usage( char *prog)
348 (void)fprintf(stderr,
349 "Usage: %s [-h] [-v] [-V] [-s] [-l {d|m|s}] [-u {i|m|n}] [server[:port:[device]]]\n\n"
350 " -h Show this help, then exit\n"
351 " -V Show version, then exit\n"
352 " -s Sleep for 10 seconds before starting\n"
353 " -j Turn on anti-jitter buffering\n"
354 " -l {d|m|s} Select lat/lon format\n"
355 " d = DD.dddddd (default)\n"
357 " s = DD MM' SS.sss\"\n"
358 " -u {i|m|n} Select Units\n"
359 " i = Imperial (default)\n"
367 int main(int argc, char *argv[])
370 struct sockaddr_in localAddr, servAddr;
373 struct timeval timeout;
379 for(n=0;n<CLIMB;n++) climb[n]=0.0;
382 /*@ -observertrans @*/
383 switch (gpsd_units())
386 altfactor = METERS_TO_FEET;
388 speedfactor = MPS_TO_MPH;
392 altfactor = METERS_TO_FEET;
394 speedfactor = MPS_TO_KNOTS;
395 speedunits = "knots";
400 speedfactor = MPS_TO_KPH;
404 /* leave the default alone */
407 /*@ +observertrans @*/
409 /* Process the options. Print help if requested. */
410 while ((option = getopt(argc, argv, "Vhl:su:")) != -1) {
413 (void)fprintf(stderr, "lcdgs revision " REVISION "\n");
420 switch ( optarg[0] ) {
428 deg_type = deg_ddmmss;
431 (void)fprintf(stderr, "Unknown -l argument: %s\n", optarg);
438 switch ( optarg[0] ) {
440 altfactor = METERS_TO_FEET;
442 speedfactor = MPS_TO_MPH;
446 altfactor = METERS_TO_FEET;
448 speedfactor = MPS_TO_KNOTS;
449 speedunits = "knots";
454 speedfactor = MPS_TO_KPH;
458 (void)fprintf(stderr, "Unknown -u argument: %s\n", optarg);
464 /* Grok the server, port, and device. */
466 gpsd_source_spec(argv[optind], &source);
468 gpsd_source_spec(NULL, &source);
473 /* Open the stream to gpsd. */
474 if (gps_open_r(source.server, source.port, &gpsdata) != 0) {
475 (void)fprintf( stderr,
476 "cgps: no gpsd running or network error: %d, %s\n",
477 errno, gps_errstr(errno));
481 /* Connect to LCDd */
482 h = gethostbyname(LCDDHOST);
484 printf("%s: unknown host '%s'\n",argv[0],LCDDHOST);
488 servAddr.sin_family = h->h_addrtype;
489 memcpy((char *) &servAddr.sin_addr.s_addr, h->h_addr_list[0], h->h_length);
490 servAddr.sin_port = htons(LCDDPORT);
493 sd = socket(AF_INET, SOCK_STREAM, 0);
495 perror("cannot open socket ");
499 /* bind any port number */
500 localAddr.sin_family = AF_INET;
501 localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
502 localAddr.sin_port = htons(0);
504 rc = bind(sd, (struct sockaddr *) &localAddr, sizeof(localAddr));
506 printf("%s: cannot bind port TCP %u\n",argv[0],LCDDPORT);
511 /* connect to server */
512 rc = connect(sd, (struct sockaddr *) &servAddr, sizeof(servAddr));
514 perror("cannot connect ");
518 /* Do the initial field label setup. */
521 /* Here's where updates go. */
522 gps_set_raw_hook(&gpsdata, update_lcd);
523 gps_stream(&gpsdata, WATCH_ENABLE, NULL);
525 for (;;) { /* heart of the client */
527 /* watch to see when it has input */
529 FD_SET(gpsdata.gps_fd, &rfds);
531 /* wait up to five seconds. */
535 /* check if we have new information */
536 data = select(gpsdata.gps_fd + 1, &rfds, NULL, NULL, &timeout);
539 fprintf( stderr, "cgps: socket error\n");
543 (void)gps_read(&gpsdata);