"Initial commit to Gerrit"
[profile/ivi/gpsd.git] / lcdgps.c
1 /*
2  * Copyright (c) 2005 Jeff Francis <jeff@gritch.org>
3  *
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.
7  *
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.
15  */
16
17 /*
18   Jeff Francis
19   jeff@gritch.org
20
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.
26
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.
30 */
31
32 #define LCDDHOST "localhost"
33 #define LCDDPORT 13666
34
35 #define CLIMB 3
36
37 #include <stdlib.h>
38 #include "gpsd_config.h"
39 #include <stdio.h>
40 #include <string.h>
41 #include <ctype.h>
42 #ifndef S_SPLINT_S
43 #include <unistd.h>
44 #endif /* S_SPLINT_S */
45 #include <math.h>
46 #include <errno.h>
47 #ifdef HAVE_SYS_SELECT_H
48  #include <sys/select.h>
49 #endif /* HAVE_SYS_SELECT_H */
50 #ifndef S_SPLINT_S
51  #ifdef HAVE_SYS_SOCKET_H
52   #include <sys/socket.h>
53  #endif /* HAVE_SYS_SOCKET_H */
54 #endif /* S_SPLINT_S */
55
56 #include <sys/time.h> /* select() */
57 #include <sys/stat.h>
58 #include <fcntl.h>
59 #ifdef HAVE_TERMIOS_H
60  #include <termios.h>
61 #endif /* HAVE_TERMIOS_H */
62
63 #ifndef S_SPLINT_S
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 */
70  #ifdef HAVE_NETDB_H
71   #include <netdb.h>
72  #endif /* HAVE_NETDB_H */
73 #endif /* S_SPLINT_S */
74 #include <errno.h>
75
76 #include <signal.h>
77
78 #include "gps.h"
79 #include "gpsdclient.h"
80 #include "revision.h"
81
82 /* Macro for declaring function arguments unused. */
83 #if defined(__GNUC__)
84 #  define UNUSED __attribute__((unused)) /* Flag variable as unused */
85 #else /* not __GNUC__ */
86 #  define UNUSED
87 #endif
88
89 /* Prototypes. */
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);
95
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";
102
103 #ifdef CLIMB
104 double avgclimb, climb[CLIMB];
105 #endif
106
107 /* Global socket descriptor for LCDd. */
108 int sd;
109
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)
113 {
114   int t1;
115   e=e+180.0;
116   t1=(int)(e/20);
117   st[0]=t1+'A';
118   e-=(float)t1*20.0;
119   t1=(int)e/2;
120   st[2]=t1+'0';
121   e-=(float)t1*2;
122 #ifndef CLIMB
123   st[4]=(int)(e*12.0+0.5)+'A';
124 #endif
125
126   n=n+90.0;
127   t1=(int)(n/10.0);
128   st[1]=t1+'A';
129   n-=(float)t1*10.0;
130   st[3]=(int)n+'0';
131   n-=(int)n;
132   n*=24; // convert to 24 division
133 #ifndef CLIMB
134   st[5]=(int)(n+0.5)+'A';
135 #endif
136 }
137
138 /* Daemonize me. */
139 static void daemonize(void) {
140   int i, ignore;
141
142   /* Run as my child. */
143   i=fork();
144   if (i == -1) exit(1); /* fork error */
145   if (i > 0) exit(0); /* parent exits */
146
147   /* Obtain a new process group. */
148   setsid();
149
150   /* Close all open descriptors. */
151   for(i=getdtablesize();i>=0;--i)
152     close(i);
153
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 */
158
159   /* Know thy mask. */
160   umask(027);
161
162   /* Run from a known location. */
163   ignore=chdir("/");
164
165   /* Catch child sig */
166   signal(SIGCHLD,SIG_IGN);
167
168   /* Ignore tty signals */
169   signal(SIGTSTP,SIG_IGN);
170   signal(SIGTTOU,SIG_IGN);
171   signal(SIGTTIN,SIG_IGN);
172 }
173
174 /*  Read a line from a socket  */
175 ssize_t sockreadline(int sockd,void *vptr,size_t maxlen) {
176   ssize_t n,rc;
177   char    c,*buffer;
178
179   buffer=vptr;
180
181   for (n = 1; n < (ssize_t)maxlen; n++) {
182
183     if((rc=read(sockd,&c,1))==1) {
184       *buffer++=c;
185       if(c=='\n')
186         break;
187     }
188     else if(rc==0) {
189       if(n==1)
190         return(0);
191       else
192         break;
193     }
194     else {
195       if(errno==EINTR)
196         continue;
197       return(-1);
198     }
199   }
200
201   *buffer=0;
202   return(n);
203 }
204
205 /*  Write a line to a socket  */
206 ssize_t sockwriteline(int sockd,const void *vptr,size_t n) {
207   size_t      nleft;
208   ssize_t     nwritten;
209   const char *buffer;
210
211   buffer=vptr;
212   nleft=n;
213
214   while(nleft>0) {
215     if((nwritten= write(sockd,buffer,nleft))<=0) {
216       if(errno==EINTR)
217         nwritten=0;
218       else
219         return(-1);
220     }
221     nleft-=nwritten;
222     buffer+=nwritten;
223   }
224
225   return(n);
226 }
227
228 /* send a command to the LCD */
229 int send_lcd(char *buf) {
230
231   int res;
232   char rcvbuf[256];
233   size_t outlen;
234
235   /* Limit the size of outgoing strings. */
236   outlen = strlen(buf);
237   if(outlen > 255) {
238     outlen = 256;
239   }
240
241   /* send the command */
242   res=sockwriteline(sd,buf,outlen);
243
244   /* TODO:  check return status */
245
246   /* read the data */
247   res=sockreadline(sd,rcvbuf,255);
248
249   /* null-terminate the string before printing */
250   /* rcvbuf[res-1]=0; FIX-ME: not using this at the moment... */
251
252   /* return the result */
253   return(res);
254 }
255
256 /* reset the LCD */
257 static void reset_lcd(void) {
258
259   /* Initialize.  In theory, we should look at what's returned, as it
260      tells us info on the attached LCD module.  TODO. */
261   send_lcd("hello\n");
262
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");
270 }
271
272 static enum deg_str_type deg_type = deg_dd;
273
274 /* This gets called once for each new sentence. */
275 static void update_lcd(struct gps_data_t *gpsdata,
276                        char *message UNUSED,
277                        size_t len UNUSED)
278 {
279   char tmpbuf[255];
280 #ifdef CLIMB
281   char maidenhead[5];
282   maidenhead[4]=0;
283   int n;
284 #else
285   char maidenhead[7];
286   maidenhead[6]=0;
287 #endif
288   char *s;
289   int track;
290
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)
293       return;
294
295   /* Get our location in Maidenhead. */
296   latlon2maidenhead(maidenhead,gpsdata->fix.latitude,gpsdata->fix.longitude);
297
298   /* Fill in the latitude and longitude. */
299   if (gpsdata->fix.mode >= MODE_2D) {
300
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');
303     send_lcd(tmpbuf);
304
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');
307     send_lcd(tmpbuf);
308
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;
313
314     snprintf(tmpbuf, 254, "widget_set gpsd three 1 3 {%.1f %s %d deg}\n",
315              gpsdata->fix.speed*speedfactor, speedunits,
316              track);
317     send_lcd(tmpbuf);
318
319   } else {
320
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");
324   }
325
326   /* Fill in the altitude and fix status. */
327   if (gpsdata->fix.mode == MODE_3D) {
328 #ifdef CLIMB
329     for(n=0;n<CLIMB-2;n++) climb[n]=climb[n+1];
330     climb[CLIMB-1]=gpsdata->fix.climb;
331     avgclimb=0.0;
332     for(n=0;n<CLIMB;n++) avgclimb+=climb[n];
333     avgclimb/=CLIMB;
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));
336 #else
337     snprintf(tmpbuf, 254, "widget_set gpsd four 1 4 {%.1f %s  %s}\n",
338             gpsdata->fix.altitude*altfactor, altunits, maidenhead);
339 #endif
340   } else {
341     snprintf(tmpbuf, 254, "widget_set gpsd four 1 4 {n/a}\n");
342   }
343   send_lcd(tmpbuf);
344 }
345
346 static void usage( char *prog)
347 {
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"
356                 "                m = DD MM.mmmm'\n"
357                 "                s = DD MM' SS.sss\"\n"
358                 "  -u {i|m|n}  Select Units\n"
359                 "                i = Imperial (default)\n"
360                 "                m = Metric'\n"
361                 "                n = Nautical\"\n"
362                 , prog);
363
364   exit(1);
365 }
366
367 int main(int argc, char *argv[]) 
368 {
369     int option, rc;
370     struct sockaddr_in localAddr, servAddr;
371     struct hostent *h;
372
373     struct timeval timeout;
374     fd_set rfds;
375     int data;
376
377 #ifdef CLIMB
378     int n;
379     for(n=0;n<CLIMB;n++) climb[n]=0.0;
380 #endif 
381
382     /*@ -observertrans @*/
383     switch (gpsd_units())
384     {
385     case imperial:
386         altfactor = METERS_TO_FEET;
387         altunits = "ft";
388         speedfactor = MPS_TO_MPH;
389         speedunits = "mph";
390         break;
391     case nautical:
392         altfactor = METERS_TO_FEET;
393         altunits = "ft";
394         speedfactor = MPS_TO_KNOTS;
395         speedunits = "knots";
396         break;
397     case metric:
398         altfactor = 1;
399         altunits = "m";
400         speedfactor = MPS_TO_KPH;
401         speedunits = "kph";
402         break;
403     default:
404         /* leave the default alone */
405         break;
406     }
407     /*@ +observertrans @*/
408
409     /* Process the options.  Print help if requested. */
410     while ((option = getopt(argc, argv, "Vhl:su:")) != -1) {
411         switch (option) {
412         case 'V':
413             (void)fprintf(stderr, "lcdgs revision " REVISION "\n");
414             exit(0);
415         case 'h':
416         default:
417             usage(argv[0]);
418             break;
419         case 'l':
420             switch ( optarg[0] ) {
421             case 'd':
422                 deg_type = deg_dd;
423                 continue;
424             case 'm':
425                 deg_type = deg_ddmm;
426                 continue;
427             case 's':
428                 deg_type = deg_ddmmss;
429                 continue;
430             default:
431                 (void)fprintf(stderr, "Unknown -l argument: %s\n", optarg);
432                 /*@ -casebreak @*/
433             }
434         case 's':
435             sleep(10);
436             continue;
437         case 'u':
438             switch ( optarg[0] ) {
439             case 'i':
440                 altfactor = METERS_TO_FEET;
441                 altunits = "ft";
442                 speedfactor = MPS_TO_MPH;
443                 speedunits = "mph";
444                 continue;
445             case 'n':
446                 altfactor = METERS_TO_FEET;
447                 altunits = "ft";
448                 speedfactor = MPS_TO_KNOTS;
449                 speedunits = "knots";
450                 continue;
451             case 'm':
452                 altfactor = 1;
453                 altunits = "m";
454                 speedfactor = MPS_TO_KPH;
455                 speedunits = "kph";
456                 continue;
457             default:
458                 (void)fprintf(stderr, "Unknown -u argument: %s\n", optarg);
459                 /*@ -casebreak @*/
460             }
461         }
462     }
463
464     /* Grok the server, port, and device. */
465   if (optind < argc) {
466       gpsd_source_spec(argv[optind], &source);
467   } else
468       gpsd_source_spec(NULL, &source);
469
470     /* Daemonize... */
471     daemonize();
472
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));
478         exit(2);
479     }
480
481     /* Connect to LCDd */
482     h = gethostbyname(LCDDHOST);
483     if(h==NULL) {
484         printf("%s: unknown host '%s'\n",argv[0],LCDDHOST);
485         exit(1);
486     }
487
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);
491
492     /* create socket */
493     sd = socket(AF_INET, SOCK_STREAM, 0);
494     if(sd == -1) {
495         perror("cannot open socket ");
496         exit(1);
497     }
498
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);
503
504     rc = bind(sd, (struct sockaddr *) &localAddr, sizeof(localAddr));
505     if(rc == -1) {
506         printf("%s: cannot bind port TCP %u\n",argv[0],LCDDPORT);
507         perror("error ");
508         exit(1);
509     }
510
511     /* connect to server */
512     rc = connect(sd, (struct sockaddr *) &servAddr, sizeof(servAddr));
513     if(rc == -1) {
514         perror("cannot connect ");
515         exit(1);
516     }
517
518     /* Do the initial field label setup. */
519     reset_lcd();
520
521     /* Here's where updates go. */
522     gps_set_raw_hook(&gpsdata, update_lcd);
523     gps_stream(&gpsdata, WATCH_ENABLE, NULL);
524
525     for (;;) { /* heart of the client */
526
527         /* watch to see when it has input */
528         FD_ZERO(&rfds);
529         FD_SET(gpsdata.gps_fd, &rfds);
530
531         /* wait up to five seconds. */
532         timeout.tv_sec = 5;
533         timeout.tv_usec = 0;
534
535         /* check if we have new information */
536         data = select(gpsdata.gps_fd + 1, &rfds, NULL, NULL, &timeout);
537
538         if (data == -1) {
539             fprintf( stderr, "cgps: socket error\n");
540             exit(2);
541         }
542         else if (data) {
543             (void)gps_read(&gpsdata);
544         }
545
546     }
547 }