1 /*
2  * Copyright (c) 2005 Jeff Francis <jeff@gritch.org>
3  *
4  * This file is Copyright (c) 2010-20189 by the GPSD project
5  * SPDX-License-Identifier: BSD-2-clause
6  *
7  */
8 
9 /*
10   Jeff Francis
11   jeff@gritch.org
12 
13   A client that passes gpsd data to lcdproc, turning your car computer
14   into a very expensive feature-free GPS receiver ;^).  Currently
15   assumes a 4x40 LCD and writes data formatted to fit that size
16   screen.  Also displays Maidenhead grid square output for the hams among us.
17 
18   This program assumes that LCDd (lcdproc) is running locally on the
19   default (13666) port.  The #defines LCDDHOST and LCDDPORT can be
20   changed to talk to a different host and TCP port.
21 */
22 
23 #define LCDDHOST "localhost"
24 #define LCDDPORT 13666
25 
26 #define CLIMB 3
27 
28 #include "gpsd_config.h"  /* must be before all includes */
29 
30 #include <arpa/inet.h>
31 #include <errno.h>
32 #include <math.h>
33 #include <netdb.h>        /* for gethostbyname() */
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 
39 #ifndef AF_UNSPEC
40 #include <sys/socket.h>
41 #include <sys/stat.h>
42 #include <sys/types.h>
43 #endif /* AF_UNSPEC */
44 #ifndef INADDR_ANY
45 #include <netinet/in.h>
46 #endif /* INADDR_ANY */
47 
48 #include "gps.h"
49 #include "gpsdclient.h"
50 #include "revision.h"
51 #include "os_compat.h"
52 
53 /* Prototypes. */
54 ssize_t sockreadline(int sockd,void *vptr,size_t maxlen);
55 ssize_t sockwriteline(int sockd,const void *vptr,size_t n);
56 int send_lcd(char *buf);
57 
58 static struct fixsource_t source;
59 static struct gps_data_t gpsdata;
60 static float altfactor = METERS_TO_FEET;
61 static float speedfactor = MPS_TO_MPH;
62 static char *altunits = "ft";
63 static char *speedunits = "mph";
64 double avgclimb, climb[CLIMB];
65 
66 /* Global socket descriptor for LCDd. */
67 int sd;
68 
69 /*  Read a line from a socket  */
sockreadline(int sockd,void * vptr,size_t maxlen)70 ssize_t sockreadline(int sockd,void *vptr,size_t maxlen) {
71   ssize_t n;
72   char    c,*buffer;
73 
74   buffer=vptr;
75 
76   for (n = 1; n < (ssize_t)maxlen; n++) {
77     ssize_t rc;
78     if ((rc=read(sockd,&c,1))==1) {
79       *buffer++=c;
80       if (c=='\n')
81         break;
82     }
83     else if (rc==0) {
84       if (n==1)
85         return(0);
86       else
87         break;
88     }
89     else {
90       if (errno==EINTR)
91         continue;
92       return(-1);
93     }
94   }
95 
96   *buffer=0;
97   return(n);
98 }
99 
100 /*  Write a line to a socket  */
sockwriteline(int sockd,const void * vptr,size_t n)101 ssize_t sockwriteline(int sockd,const void *vptr,size_t n) {
102   size_t      nleft;
103   const char *buffer;
104 
105   buffer=vptr;
106   nleft=n;
107 
108   while (nleft>0) {
109     ssize_t     nwritten;
110     if ((nwritten= write(sockd,buffer,nleft))<=0) {
111       if (errno==EINTR)
112         nwritten=0;
113       else
114         return(-1);
115     }
116     nleft-=nwritten;
117     buffer+=nwritten;
118   }
119 
120   return(n);
121 }
122 
123 /* send a command to the LCD */
send_lcd(char * buf)124 int send_lcd(char *buf) {
125 
126   int res;
127   char rcvbuf[256];
128   size_t outlen;
129 
130   /* Limit the size of outgoing strings. */
131   outlen = strlen(buf);
132   if (outlen > 255) {
133     outlen = 256;
134   }
135 
136   /* send the command */
137   (void)sockwriteline(sd,buf,outlen);
138 
139   /* TODO:  check return status */
140 
141   /* read the data */
142   res=sockreadline(sd,rcvbuf,sizeof(rcvbuf)-1);
143 
144   /* null-terminate the string before printing */
145   /* rcvbuf[res-1]=0; FIX-ME: not using this at the moment... */
146 
147   /* return the result */
148   return(res);
149 }
150 
151 /* reset the LCD */
reset_lcd(void)152 static void reset_lcd(void) {
153 
154   /* Initialize.  In theory, we should look at what's returned, as it
155      tells us info on the attached LCD module.  TODO. */
156   send_lcd("hello\n");
157 
158   /* Set up the screen */
159   send_lcd("client_set name {GPSD test}\n");
160   send_lcd("screen_add gpsd\n");
161   send_lcd("widget_add gpsd one string\n");
162   send_lcd("widget_add gpsd two string\n");
163   send_lcd("widget_add gpsd three string\n");
164   send_lcd("widget_add gpsd four string\n");
165 }
166 
167 static enum deg_str_type deg_type = deg_dd;
168 
169 /* This gets called once for each new sentence. */
update_lcd(struct gps_data_t * gpsdata)170 static void update_lcd(struct gps_data_t *gpsdata)
171 {
172   char tmpbuf[255];
173   char *gridsquare;
174 
175   /* Get our location in Maidenhead. */
176   gridsquare = maidenhead(gpsdata->fix.latitude,gpsdata->fix.longitude);
177 
178   /* Fill in the latitude and longitude. */
179   if (gpsdata->fix.mode >= MODE_2D) {
180     int track;
181     char *s;
182 
183     s = deg_to_str(deg_type, gpsdata->fix.latitude);
184     snprintf(tmpbuf, sizeof(tmpbuf) - 1,
185              "widget_set gpsd one 1 1 {Lat: %s %c}\n", s,
186              (gpsdata->fix.latitude < 0) ? 'S' : 'N');
187     send_lcd(tmpbuf);
188 
189     s = deg_to_str(deg_type, gpsdata->fix.longitude);
190     snprintf(tmpbuf, sizeof(tmpbuf) - 1,
191              "widget_set gpsd two 1 2 {Lon: %s %c}\n", s,
192              (gpsdata->fix.longitude < 0) ? 'W' : 'E');
193     send_lcd(tmpbuf);
194 
195     /* As a pilot, a heading of "0" gives me the heebie-jeebies (ie, 0
196        == "invalid heading data", 360 == "North"). */
197     track=(int)(gpsdata->fix.track);
198     if (track == 0) track = 360;
199 
200     snprintf(tmpbuf, sizeof(tmpbuf) - 1,
201              "widget_set gpsd three 1 3 {%.1f %s %d deg}\n",
202              gpsdata->fix.speed*speedfactor, speedunits,
203              track);
204     send_lcd(tmpbuf);
205 
206   } else {
207 
208     send_lcd("widget_set gpsd one 1 1 {Lat: n/a}\n");
209     send_lcd("widget_set gpsd two 1 2 {Lon: n/a}\n");
210     send_lcd("widget_set gpsd three 1 3 {n/a}\n");
211   }
212 
213   /* Fill in the altitude and fix status. */
214   if (gpsdata->fix.mode == MODE_3D) {
215     int n;
216     for(n=0;n<CLIMB-2;n++) climb[n]=climb[n+1];
217     climb[CLIMB-1]=gpsdata->fix.climb;
218     avgclimb=0.0;
219     for(n=0;n<CLIMB;n++) avgclimb+=climb[n];
220     avgclimb/=CLIMB;
221     snprintf(tmpbuf, sizeof(tmpbuf) - 1,
222              "widget_set gpsd four 1 4 {%d %s %s %d fpm       }\n",
223             (int)(gpsdata->fix.altMSL * altfactor), altunits,
224             gridsquare, (int)(avgclimb * METERS_TO_FEET * 60));
225   } else {
226     snprintf(tmpbuf, sizeof(tmpbuf) - 1, "widget_set gpsd four 1 4 {n/a}\n");
227   }
228   send_lcd(tmpbuf);
229 }
230 
usage(char * prog)231 static void usage( char *prog)
232 {
233   (void)fprintf(stderr,
234         "Usage: %s [OPTIONS] [server[:port:[device]]]\n\n"
235         "  -h          Show this help, then exit\n"
236         "  -j          Turn on anti-jitter buffering\n"
237         "  -l {d|m|s}  Select lat/lon format\n"
238         "                d = DD.dddddd (default)\n"
239         "                m = DD MM.mmmm'\n"
240         "                s = DD MM' SS.sss\"\n"
241         "  -s          Sleep for 10 seconds before starting\n"
242         "  -u {i|m|m}  Select Units\n"
243         "                i = Imperial (default)\n"
244         "                n = Nautical\n"
245         "                m = Metric\n"
246         "  -V          Show version, then exit\n"
247         , prog);
248 
249   exit(EXIT_FAILURE);
250 }
251 
main(int argc,char * argv[])252 int main(int argc, char *argv[])
253 {
254     int option, rc;
255     struct sockaddr_in localAddr, servAddr;
256     struct hostent *h;
257 
258     int n;
259     for(n=0;n<CLIMB;n++) climb[n]=0.0;
260 
261     switch (gpsd_units())
262     {
263     case imperial:
264 	altfactor = METERS_TO_FEET;
265 	altunits = "ft";
266 	speedfactor = MPS_TO_MPH;
267 	speedunits = "mph";
268 	break;
269     case nautical:
270 	altfactor = METERS_TO_FEET;
271 	altunits = "ft";
272 	speedfactor = MPS_TO_KNOTS;
273 	speedunits = "knots";
274 	break;
275     case metric:
276 	altfactor = 1;
277 	altunits = "m";
278 	speedfactor = MPS_TO_KPH;
279 	speedunits = "kph";
280 	break;
281     default:
282 	/* leave the default alone */
283 	break;
284     }
285 
286     /* Process the options.  Print help if requested. */
287     while ((option = getopt(argc, argv, "hl:su:V")) != -1) {
288 	switch (option) {
289 	case 'h':
290 	default:
291 	    usage(argv[0]);
292 	    break;
293 	case 'l':
294 	    switch ( optarg[0] ) {
295 	    case 'd':
296 		deg_type = deg_dd;
297 		continue;
298 	    case 'm':
299 		deg_type = deg_ddmm;
300 		continue;
301 	    case 's':
302 		deg_type = deg_ddmmss;
303 		continue;
304 	    default:
305 		(void)fprintf(stderr, "Unknown -l argument: %s\n", optarg);
306 	    }
307 	    break;
308 	case 's':
309 	    sleep(10);
310 	    continue;
311 	case 'u':
312 	    switch ( optarg[0] ) {
313 	    case 'i':
314 		altfactor = METERS_TO_FEET;
315 		altunits = "ft";
316 		speedfactor = MPS_TO_MPH;
317 		speedunits = "mph";
318 		continue;
319 	    case 'n':
320 		altfactor = METERS_TO_FEET;
321 		altunits = "ft";
322 		speedfactor = MPS_TO_KNOTS;
323 		speedunits = "knots";
324 		continue;
325 	    case 'm':
326 		altfactor = 1;
327 		altunits = "m";
328 		speedfactor = MPS_TO_KPH;
329 		speedunits = "kph";
330 		continue;
331 	    default:
332                 break;
333 	    }
334             (void)fprintf(stderr, "Unknown -u argument: %s\n", optarg);
335             break;
336 	case 'V':
337 	    (void)fprintf(stderr, "lcdgps revision " REVISION "\n");
338 	    exit(EXIT_SUCCESS);
339 	}
340     }
341 
342     /* Grok the server, port, and device. */
343   if (optind < argc) {
344       gpsd_source_spec(argv[optind], &source);
345   } else
346       gpsd_source_spec(NULL, &source);
347 
348     /* Daemonize... */
349   if (os_daemon(0, 0) != 0)
350       (void)fprintf(stderr,
351 		    "lcdgps: daemonization failed: %s\n",
352 		    strerror(errno));
353 
354     /* Open the stream to gpsd. */
355     if (gps_open(source.server, source.port, &gpsdata) != 0) {
356 	(void)fprintf( stderr,
357 		       "lcdgps: no gpsd running or network error: %d, %s\n",
358 		       errno, gps_errstr(errno));
359 	exit(EXIT_FAILURE);
360     }
361 
362     /* Connect to LCDd */
363     h = gethostbyname(LCDDHOST);
364     if (h==NULL) {
365 	printf("%s: unknown host '%s'\n",argv[0],LCDDHOST);
366 	exit(EXIT_FAILURE);
367     }
368 
369     servAddr.sin_family = h->h_addrtype;
370     memcpy((char *) &servAddr.sin_addr.s_addr, h->h_addr_list[0], h->h_length);
371     servAddr.sin_port = htons(LCDDPORT);
372 
373     /* create socket */
374     sd = socket(AF_INET, SOCK_STREAM, 0);
375     if (BAD_SOCKET(sd)) {
376 	perror("cannot open socket ");
377 	exit(EXIT_FAILURE);
378     }
379 
380     /* bind any port number */
381     localAddr.sin_family = AF_INET;
382     localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
383     localAddr.sin_port = htons(0);
384 
385     /* coverity[uninit_use_in_call] */
386     rc = bind(sd, (struct sockaddr *) &localAddr, sizeof(localAddr));
387     if (rc == -1) {
388 	printf("%s: cannot bind port TCP %d\n",argv[0],LCDDPORT);
389 	perror("error ");
390 	exit(EXIT_FAILURE);
391     }
392 
393     /* connect to server */
394     /* coverity[uninit_use_in_call] */
395     rc = connect(sd, (struct sockaddr *) &servAddr, sizeof(servAddr));
396     if (rc == -1) {
397 	perror("cannot connect ");
398 	exit(EXIT_FAILURE);
399     }
400 
401     /* Do the initial field label setup. */
402     reset_lcd();
403 
404     /* Here's where updates go. */
405     unsigned int flags = WATCH_ENABLE;
406     if (source.device != NULL)
407 	flags |= WATCH_DEVICE;
408     (void)gps_stream(&gpsdata, flags, source.device);
409 
410     for (;;) { /* heart of the client */
411 	if (!gps_waiting(&gpsdata, 50000000)) {
412 	    (void)fprintf(stderr, "lcdgps: error while waiting\n");
413 	    exit(EXIT_FAILURE);
414 	} else {
415 	    (void)gps_read(&gpsdata, NULL, 0);
416 	    update_lcd(&gpsdata);
417 	}
418 
419     }
420 }
421