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