cleanup specfile for packaging
[profile/ivi/gpsd.git] / cgps.c
1 /*
2  * Copyright (c) 2005 Jeff Francis <jeff@gritch.org>
3  * BSD terms apply: see the filr COPYING in the distribution root for details.
4  */
5
6 /*
7   Jeff Francis
8   jeff@gritch.org
9
10   Kind of a curses version of xgps for use with gpsd.
11 */
12
13 /*
14  * The True North compass fails with current gpsd versions for reasons
15  * the dev team has been unable to diagnose due to not having test hardware.
16  * The sup[port for it is conditioned out in order to simplify moving 
17  * to the new JSON-based oprotocol and reduce startup time.
18  */
19 #undef TRUENORTH
20
21 /* ==================================================================
22    These #defines should be modified if changing the number of fields
23    to be displayed.
24    ================================================================== */
25
26 /* This defines how much overhead is contained in the 'datawin' window
27    (eg, box around the window takes two lines). */
28 #define DATAWIN_OVERHEAD 2
29
30 /* This defines how much overhead is contained in the 'satellites'
31    window (eg, box around the window takes two lines, plus the column
32    headers take another line). */
33 #define SATWIN_OVERHEAD 3
34
35 /* This is how many display fields are output in the 'datawin' window
36    when in GPS mode.  Change this value if you add or remove fields
37    from the 'datawin' window for the GPS mode. */
38 #define DATAWIN_GPS_FIELDS 9
39
40 /* This is how many display fields are output in the 'datawin' window
41    when in COMPASS mode.  Change this value if you add or remove fields
42    from the 'datawin' window for the COMPASS mode. */
43 #define DATAWIN_COMPASS_FIELDS 6
44
45 /* This is how far over in the 'datawin' window to indent the field
46    descriptions. */
47 #define DATAWIN_DESC_OFFSET 5
48
49 /* This is how far over in the 'datawin' window to indent the field
50    values. */
51 #define DATAWIN_VALUE_OFFSET 17
52
53 /* This is the width of the 'datawin' window.  It's recommended to
54    keep DATAWIN_WIDTH + SATELLITES_WIDTH <= 80 so it'll fit on a
55    "standard" 80x24 screen. */
56 #define DATAWIN_WIDTH 45
57
58 /* This is the width of the 'satellites' window.  It's recommended to
59    keep DATAWIN_WIDTH + SATELLITES_WIDTH <= 80 so it'll fit on a
60    "standard" 80x24 screen. */
61 #define SATELLITES_WIDTH 35
62
63 /* ================================================================
64    You shouldn't have to modify any #define values below this line.
65    ================================================================ */
66
67 /* This is the minimum size we'll accept for the 'datawin' window in
68    GPS mode. */
69 #define MIN_GPS_DATAWIN_SIZE (DATAWIN_GPS_FIELDS + DATAWIN_OVERHEAD)
70
71 /* This is the minimum size we'll accept for the 'datawin' window in
72    COMPASS mode. */
73 #define MIN_COMPASS_DATAWIN_SIZE (DATAWIN_COMPASS_FIELDS + DATAWIN_OVERHEAD)
74
75 /* This is the maximum number of satellites gpsd can track. */
76 #define MAX_POSSIBLE_SATS (MAXCHANNELS - 2)
77
78 /* This is the maximum size we need for the 'satellites' window. */
79 #define MAX_SATWIN_SIZE (MAX_POSSIBLE_SATS + SATWIN_OVERHEAD)
80
81 #include <sys/types.h>
82 #include <sys/select.h>
83 #ifndef S_SPLINT_S
84 #include <sys/socket.h>
85 #include <unistd.h>
86 #endif /* S_SPLINT_S */
87 #include <stdio.h>
88 #include <stdlib.h>
89 #include <string.h>
90 #include <ctype.h>
91 #include <math.h>
92 #include <errno.h>
93 #include <assert.h>
94 #include <signal.h>
95 #include <stdbool.h>
96
97 #include "gpsd_config.h"
98 #ifdef HAVE_NCURSES_H
99 #include <ncurses.h>
100 #else
101 #include <curses.h>
102 #endif /* HAVE_NCURSES_H */
103
104 #include "gps.h"
105 #include "gpsdclient.h"
106 #include "revision.h"
107
108 static struct gps_data_t gpsdata;
109 static time_t status_timer;     /* Time of last state change. */
110 static int state = 0;           /* or MODE_NO_FIX=1, MODE_2D=2, MODE_3D=3 */
111 static float altfactor = METERS_TO_FEET;
112 static float speedfactor = MPS_TO_MPH;
113 static char *altunits = "ft";
114 static char *speedunits = "mph";
115 static struct fixsource_t source;
116 #ifdef CLIENTDEBUG_ENABLE
117 static int debug;
118 #endif /* CLIENTDEBUG_ENABLE */
119
120 static WINDOW *datawin, *satellites, *messages;
121
122 static bool raw_flag = false;
123 static bool silent_flag = false;
124 static bool magnetic_flag = false;
125 static int window_length;
126 static int display_sats;
127 #ifdef TRUENORTH
128 static bool compass_flag = false;
129 #endif /* TRUENORTH */
130
131 /* pseudo-signals indicating reason for termination */
132 #define CGPS_QUIT       0       /* voluntary yterminastion */
133 #define GPS_GONE        -1      /* GPS device went away */
134 #define GPS_ERROR       -2      /* low-level failure in GPS read */
135
136 /* Convert true heading to magnetic.  Taken from the Aviation
137    Formulary v1.43.  Valid to within two degrees within the
138    continiental USA except for the following airports: MO49 MO86 MO50
139    3K6 02K and KOOA.  AK correct to better than one degree.  Western
140    Europe correct to within 0.2 deg.
141
142    If you're not in one of these areas, I apologize, I don't have the
143    math to compute your varation.  This is obviously extremely
144    floating-point heavy, so embedded people, beware of using.
145
146    Note that there are issues with using magnetic heading.  This code
147    does not account for the possibility of travelling into or out of
148    an area of valid calculation beyond forcing the magnetic conversion
149    off.  A better way to communicate this to the user is probably
150    desirable (in case the don't notice the subtle change from "(mag)"
151    to "(true)" on their display).
152  */
153 static float true2magnetic(double lat, double lon, double heading)
154 {
155     /* Western Europe */
156     /*@ -evalorder +relaxtypes @*/
157     if ((lat > 36.0) && (lat < 68.0) && (lon > -10.0) && (lon < 28.0)) {
158         heading =
159             (10.4768771667158 - (0.507385322418858 * lon) +
160              (0.00753170031703826 * pow(lon, 2))
161              - (1.40596203924748e-05 * pow(lon, 3)) -
162              (0.535560699962353 * lat)
163              + (0.0154348808069955 * lat * lon) -
164              (8.07756425110592e-05 * lat * pow(lon, 2))
165              + (0.00976887198864442 * pow(lat, 2)) -
166              (0.000259163929798334 * lon * pow(lat, 2))
167              - (3.69056939266123e-05 * pow(lat, 3)) + heading);
168     }
169     /* USA */
170     else if ((lat > 24.0) && (lat < 50.0) && (lon > 66.0) && (lon < 125.0)) {
171         lon = 0.0 - lon;
172         heading =
173             ((-65.6811) + (0.99 * lat) + (0.0128899 * pow(lat, 2)) -
174              (0.0000905928 * pow(lat, 3)) + (2.87622 * lon)
175              - (0.0116268 * lat * lon) - (0.00000603925 * lon * pow(lat, 2)) -
176              (0.0389806 * pow(lon, 2))
177              - (0.0000403488 * lat * pow(lon, 2)) +
178              (0.000168556 * pow(lon, 3)) + heading);
179     }
180     /* AK */
181     else if ((lat > 54.0) && (lon > 130.0) && (lon < 172.0)) {
182         lon = 0.0 - lon;
183         heading =
184             (618.854 + (2.76049 * lat) - (0.556206 * pow(lat, 2)) +
185              (0.00251582 * pow(lat, 3)) - (12.7974 * lon)
186              + (0.408161 * lat * lon) + (0.000434097 * lon * pow(lat, 2)) -
187              (0.00602173 * pow(lon, 2))
188              - (0.00144712 * lat * pow(lon, 2)) +
189              (0.000222521 * pow(lon, 3)) + heading);
190     } else {
191         /* We don't know how to compute magnetic heading for this
192          * location. */
193         magnetic_flag = false;
194     }
195
196     /* No negative headings. */
197     if (heading < 0.0)
198         heading += 360.0;
199
200     return (heading);
201     /*@ +evalorder -relaxtypes @*/
202 }
203
204 /* Function to call when we're all done.  Does a bit of clean-up. */
205 static void die(int sig)
206 {
207     /* Ignore signals. */
208     (void)signal(SIGINT, SIG_IGN);
209     (void)signal(SIGHUP, SIG_IGN);
210
211     /* Move the cursor to the bottom left corner. */
212     (void)mvcur(0, COLS - 1, LINES - 1, 0);
213
214     /* Put input attributes back the way they were. */
215     (void)echo();
216
217     /* Done with curses. */
218     (void)endwin();
219
220     /* We're done talking to gpsd. */
221     (void)gps_close(&gpsdata);
222
223     switch (sig) {
224     case CGPS_QUIT:
225         break;
226     case GPS_GONE:
227         (void)fprintf(stderr, "cgps: GPS hung up.\n");
228         break;
229     case GPS_ERROR:
230         (void)fprintf(stderr, "cgps: GPS read returned error\n");
231         break;
232     default:
233         (void)fprintf(stderr, "cgps: caught signal %d\n", sig);
234     }
235
236     /* Bye! */
237     exit(0);
238 }
239
240
241 static enum deg_str_type deg_type = deg_dd;
242
243 /*@ -globstate @*/
244 static void windowsetup(void)
245 {
246     /* Set the window sizes per the following criteria:
247      * 
248      * 1.  Set the window size to display the maximum number of
249      * satellites possible, but not more than the size required to
250      * display the maximum number of satellites gpsd is capable of
251      * tracking (MAXCHANNELS - 2).
252      * 
253      * 2.  If the screen size will not allow for the full complement of
254      * satellites to be displayed, set the windows sizes smaller, but
255      * not smaller than the number of lines necessary to display all of
256      * the fields in the 'datawin'.  The list of displayed satellites
257      * will be truncated to fit the available window size.  (TODO: If
258      * the satellite list is truncated, omit the satellites not used to
259      * obtain the current fix.)
260      * 
261      * 3.  If the screen is large enough to display all possible
262      * satellites (MAXCHANNELS - 2) with space still left at the bottom,
263      * add a window at the bottom in which to scroll raw gpsd data.
264      */
265     int xsize, ysize;
266
267     getmaxyx(stdscr, ysize, xsize);
268
269 #ifdef TRUENORTH
270     if (compass_flag) {
271         if (ysize == MIN_COMPASS_DATAWIN_SIZE) {
272             raw_flag = false;
273             window_length = MIN_COMPASS_DATAWIN_SIZE;
274         } else if (ysize > MIN_COMPASS_DATAWIN_SIZE) {
275             raw_flag = true;
276             window_length = MIN_COMPASS_DATAWIN_SIZE;
277         } else {
278             (void)mvprintw(0, 0,
279                            "Your screen must be at least 80x%d to run cgps.",
280                            MIN_COMPASS_DATAWIN_SIZE);
281             /*@ -nullpass @*/
282             (void)refresh();
283             /*@ +nullpass @*/
284             (void)sleep(5);
285             die(0);
286         }
287     } else
288 #endif /* TRUENORTH */
289     {
290         if (ysize == MAX_SATWIN_SIZE) {
291             raw_flag = false;
292             window_length = MAX_SATWIN_SIZE;
293             display_sats = MAX_POSSIBLE_SATS;
294         } else if (ysize == MAX_SATWIN_SIZE + 1) {
295             raw_flag = true;
296             window_length = MAX_SATWIN_SIZE;
297             display_sats = MAX_POSSIBLE_SATS;
298         } else if (ysize > MAX_SATWIN_SIZE + 2) {
299             raw_flag = true;
300             window_length = MAX_SATWIN_SIZE;
301             display_sats = MAX_POSSIBLE_SATS;
302         } else if (ysize > MIN_GPS_DATAWIN_SIZE) {
303             raw_flag = false;
304             window_length = ysize - (int)raw_flag;
305             display_sats = window_length - SATWIN_OVERHEAD - (int)raw_flag;
306         } else if (ysize == MIN_GPS_DATAWIN_SIZE) {
307             raw_flag = false;
308             window_length = MIN_GPS_DATAWIN_SIZE;
309             display_sats = window_length - SATWIN_OVERHEAD - 1;
310         } else {
311             (void)mvprintw(0, 0,
312                            "Your screen must be at least 80x%d to run cgps.",
313                            MIN_GPS_DATAWIN_SIZE);
314             /*@ -nullpass @*/
315             (void)refresh();
316             /*@ +nullpass @*/
317             (void)sleep(5);
318             die(0);
319         }
320     }
321
322 #ifdef TRUENORTH
323     /* Set up the screen for either a compass or a gps receiver. */
324     if (compass_flag) {
325         /* We're a compass, set up accordingly. */
326
327         /*@ -onlytrans @*/
328         datawin = newwin(window_length, DATAWIN_WIDTH, 0, 0);
329         (void)nodelay(datawin, (bool) TRUE);
330         if (raw_flag) {
331             messages = newwin(0, 0, window_length, 0);
332
333             /*@ +onlytrans @*/
334             (void)scrollok(messages, true);
335             (void)wsetscrreg(messages, 0, ysize - (window_length));
336         }
337
338         /*@ -nullpass @*/
339         (void)refresh();
340         /*@ +nullpass @*/
341
342         /* Do the initial field label setup. */
343         (void)mvwprintw(datawin, 1, DATAWIN_DESC_OFFSET, "Time:");
344         (void)mvwprintw(datawin, 2, DATAWIN_DESC_OFFSET, "Heading:");
345         (void)mvwprintw(datawin, 3, DATAWIN_DESC_OFFSET, "Pitch:");
346         (void)mvwprintw(datawin, 4, DATAWIN_DESC_OFFSET, "Roll:");
347         (void)mvwprintw(datawin, 5, DATAWIN_DESC_OFFSET, "Dip:");
348         (void)mvwprintw(datawin, 6, DATAWIN_DESC_OFFSET, "Rcvr Type:");
349         (void)wborder(datawin, 0, 0, 0, 0, 0, 0, 0, 0);
350
351     } else
352 #endif /* TRUENORTH */
353     {
354         /* We're a GPS, set up accordingly. */
355
356         /*@ -onlytrans @*/
357         datawin = newwin(window_length, DATAWIN_WIDTH, 0, 0);
358         satellites =
359             newwin(window_length, SATELLITES_WIDTH, 0, DATAWIN_WIDTH);
360         (void)nodelay(datawin, (bool) TRUE);
361         if (raw_flag) {
362             messages =
363                 newwin(ysize - (window_length), xsize, window_length, 0);
364
365             /*@ +onlytrans @*/
366             (void)scrollok(messages, true);
367             (void)wsetscrreg(messages, 0, ysize - (window_length));
368         }
369
370         /*@ -nullpass @*/
371         (void)refresh();
372         /*@ +nullpass @*/
373
374         /* Do the initial field label setup. */
375         (void)mvwprintw(datawin, 1, DATAWIN_DESC_OFFSET, "Time:");
376         (void)mvwprintw(datawin, 2, DATAWIN_DESC_OFFSET, "Latitude:");
377         (void)mvwprintw(datawin, 3, DATAWIN_DESC_OFFSET, "Longitude:");
378         (void)mvwprintw(datawin, 4, DATAWIN_DESC_OFFSET, "Altitude:");
379         (void)mvwprintw(datawin, 5, DATAWIN_DESC_OFFSET, "Speed:");
380         (void)mvwprintw(datawin, 6, DATAWIN_DESC_OFFSET, "Heading:");
381         (void)mvwprintw(datawin, 7, DATAWIN_DESC_OFFSET, "Climb:");
382         (void)mvwprintw(datawin, 8, DATAWIN_DESC_OFFSET, "Status:");
383         (void)mvwprintw(datawin, 9, DATAWIN_DESC_OFFSET, "GPS Type:");
384
385         /* Note that the following four fields are exceptions to the
386          * sizing rule.  The minimum window size does not include these
387          * fields, if the window is too small, they get excluded.  This
388          * may or may not change if/when the output for these fields is
389          * fixed and/or people request their permanance.  They're only
390          * there in the first place because I arbitrarily thought they
391          * sounded interesting. ;^) */
392
393         if (window_length >= (MIN_GPS_DATAWIN_SIZE + 5)) {
394             (void)mvwprintw(datawin, 10, DATAWIN_DESC_OFFSET,
395                             "Longitude Err:");
396             (void)mvwprintw(datawin, 11, DATAWIN_DESC_OFFSET,
397                             "Latitude Err:");
398             (void)mvwprintw(datawin, 12, DATAWIN_DESC_OFFSET,
399                             "Altitude Err:");
400             (void)mvwprintw(datawin, 13, DATAWIN_DESC_OFFSET, "Course Err:");
401             (void)mvwprintw(datawin, 14, DATAWIN_DESC_OFFSET, "Speed Err:");
402         }
403
404         (void)wborder(datawin, 0, 0, 0, 0, 0, 0, 0, 0);
405         (void)mvwprintw(satellites, 1, 1, "PRN:   Elev:  Azim:  SNR:  Used:");
406         (void)wborder(satellites, 0, 0, 0, 0, 0, 0, 0, 0);
407     }
408 }
409
410 /*@ +globstate @*/
411
412 #ifdef TRUENORTH
413 /* This gets called once for each new compass sentence. */
414 static void update_compass_panel(struct gps_data_t *gpsdata,
415                                  char *message, size_t len UNUSED)
416 {
417     char scr[128];
418     /* Print time/date. */
419     if (isnan(gpsdata->fix.time) == 0) {
420         (void)unix_to_iso8601(gpsdata->fix.time, scr, sizeof(scr));
421     } else
422         (void)snprintf(scr, sizeof(scr), "n/a");
423     (void)mvwprintw(datawin, 1, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
424
425     /* Fill in the heading. */
426     if (isnan(gpsdata->fix.track) == 0) {
427         (void)snprintf(scr, sizeof(scr), "%.1f degrees", gpsdata->fix.track);
428     } else
429         (void)snprintf(scr, sizeof(scr), "n/a");
430     (void)mvwprintw(datawin, 2, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
431
432     /* Fill in the pitch. */
433     if (isnan(gpsdata->fix.climb) == 0) {
434         (void)snprintf(scr, sizeof(scr), "%.1f", gpsdata->fix.climb);
435     } else
436         (void)snprintf(scr, sizeof(scr), "n/a");
437     (void)mvwprintw(datawin, 3, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
438
439     /* Fill in the roll. */
440     if (isnan(gpsdata->fix.speed) == 0)
441         (void)snprintf(scr, sizeof(scr), "%.1f", gpsdata->fix.speed);
442     else
443         (void)snprintf(scr, sizeof(scr), "n/a");
444     (void)mvwprintw(datawin, 4, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
445
446     /* Fill in the speed. */
447     if (isnan(gpsdata->fix.altitude) == 0)
448         (void)snprintf(scr, sizeof(scr), "%.1f", gpsdata->fix.altitude);
449     else
450         (void)snprintf(scr, sizeof(scr), "n/a");
451     (void)mvwprintw(datawin, 5, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
452
453     /* When we need to fill in receiver type again, do it here. */
454     (void)mvwprintw(datawin, 6, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
455
456     /* Be quiet if the user requests silence. */
457     if (!silent_flag && raw_flag) {
458         (void)waddstr(messages, message);
459     }
460
461     (void)wrefresh(datawin);
462     if (raw_flag) {
463         (void)wrefresh(messages);
464     }
465 }
466 #endif /* TRUENORTH */
467
468 /* This gets called once for each new GPS sentence. */
469 static void update_gps_panel(struct gps_data_t *gpsdata,
470                              char *message, size_t len UNUSED)
471 {
472     int i, j, n;
473     int newstate;
474     char scr[128];
475     bool usedflags[MAXCHANNELS];
476
477     /* this is where we implement source-device filtering */
478     if (gpsdata->dev.path[0] != '\0' && source.device != NULL
479         && strcmp(source.device, gpsdata->dev.path) != 0)
480         return;
481
482     /* must build bit vector of which statellites are used from list */
483     for (i = 0; i < MAXCHANNELS; i++) {
484         usedflags[i] = false;
485         for (j = 0; j < gpsdata->satellites_used; j++)
486             if (gpsdata->used[j] == gpsdata->PRN[i])
487                 usedflags[i] = true;
488     }
489
490     /* This is for the satellite status display.  Originally lifted from
491      * xgps.c.  Note that the satellite list may be truncated based on
492      * available screen size, or may only show satellites used for the
493      * fix.  */
494     if (gpsdata->satellites_visible != 0) {
495         if (display_sats >= MAX_POSSIBLE_SATS) {
496             for (i = 0; i < MAX_POSSIBLE_SATS; i++) {
497                 if (i < gpsdata->satellites_visible) {
498                     (void)snprintf(scr, sizeof(scr),
499                                    " %3d    %02d    %03d    %02d      %c",
500                                    gpsdata->PRN[i],
501                                    gpsdata->elevation[i], gpsdata->azimuth[i],
502                                    (int)gpsdata->ss[i],
503                                    usedflags[i] ? 'Y' : 'N');
504                 } else {
505                     (void)strlcpy(scr, "", sizeof(scr));
506                 }
507                 (void)mvwprintw(satellites, i + 2, 1, "%-*s",
508                                 SATELLITES_WIDTH - 3, scr);
509             }
510         } else {
511             n = 0;
512             for (i = 0; i < MAX_POSSIBLE_SATS; i++) {
513                 if (n < display_sats) {
514                     if ((i < gpsdata->satellites_visible)
515                         && ((gpsdata->used[i] != 0)
516                             || (gpsdata->satellites_visible <= display_sats))) {
517                         (void)snprintf(scr, sizeof(scr),
518                                        " %3d    %02d    %03d    %02d      %c",
519                                        gpsdata->PRN[i], gpsdata->elevation[i],
520                                        gpsdata->azimuth[i],
521                                        (int)gpsdata->ss[i],
522                                        gpsdata->used[i] ? 'Y' : 'N');
523                         (void)mvwprintw(satellites, n + 2, 1, "%-*s",
524                                         SATELLITES_WIDTH - 3, scr);
525                         n++;
526                     }
527                 }
528             }
529
530             if (n < display_sats) {
531                 for (i = n; i <= display_sats; i++) {
532                     (void)mvwprintw(satellites, i + 2, 1, "%-*s",
533                                     SATELLITES_WIDTH - 3, "");
534                 }
535             }
536
537         }
538     }
539
540     /* Print time/date. */
541     if (isnan(gpsdata->fix.time) == 0) {
542         (void)unix_to_iso8601(gpsdata->fix.time, scr, sizeof(scr));
543     } else
544         (void)snprintf(scr, sizeof(scr), "n/a");
545     (void)mvwprintw(datawin, 1, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
546
547
548     /* Fill in the latitude. */
549     if (gpsdata->fix.mode >= MODE_2D && isnan(gpsdata->fix.latitude) == 0) {
550         (void)snprintf(scr, sizeof(scr), "%s %c",
551                        deg_to_str(deg_type, fabs(gpsdata->fix.latitude)),
552                        (gpsdata->fix.latitude < 0) ? 'S' : 'N');
553     } else
554         (void)snprintf(scr, sizeof(scr), "n/a");
555     (void)mvwprintw(datawin, 2, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
556
557     /* Fill in the longitude. */
558     if (gpsdata->fix.mode >= MODE_2D && isnan(gpsdata->fix.longitude) == 0) {
559         (void)snprintf(scr, sizeof(scr), "%s %c",
560                        deg_to_str(deg_type, fabs(gpsdata->fix.longitude)),
561                        (gpsdata->fix.longitude < 0) ? 'W' : 'E');
562     } else
563         (void)snprintf(scr, sizeof(scr), "n/a");
564     (void)mvwprintw(datawin, 3, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
565
566     /* Fill in the altitude. */
567     if (gpsdata->fix.mode == MODE_3D && isnan(gpsdata->fix.altitude) == 0)
568         (void)snprintf(scr, sizeof(scr), "%.1f %s",
569                        gpsdata->fix.altitude * altfactor, altunits);
570     else
571         (void)snprintf(scr, sizeof(scr), "n/a");
572     (void)mvwprintw(datawin, 4, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
573
574     /* Fill in the speed. */
575     if (gpsdata->fix.mode >= MODE_2D && isnan(gpsdata->fix.track) == 0)
576         (void)snprintf(scr, sizeof(scr), "%.1f %s",
577                        gpsdata->fix.speed * speedfactor, speedunits);
578     else
579         (void)snprintf(scr, sizeof(scr), "n/a");
580     (void)mvwprintw(datawin, 5, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
581
582     /* Fill in the heading. */
583     if (gpsdata->fix.mode >= MODE_2D && isnan(gpsdata->fix.track) == 0)
584         if (!magnetic_flag) {
585             (void)snprintf(scr, sizeof(scr), "%.1f deg (true)",
586                            gpsdata->fix.track);
587         } else {
588             (void)snprintf(scr, sizeof(scr), "%.1f deg (mag) ",
589                            true2magnetic(gpsdata->fix.latitude,
590                                          gpsdata->fix.longitude,
591                                          gpsdata->fix.track));
592     } else
593         (void)snprintf(scr, sizeof(scr), "n/a");
594     (void)mvwprintw(datawin, 6, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
595
596     /* Fill in the rate of climb. */
597     if (gpsdata->fix.mode == MODE_3D && isnan(gpsdata->fix.climb) == 0)
598         (void)snprintf(scr, sizeof(scr), "%.1f %s/min",
599                        gpsdata->fix.climb * altfactor * 60, altunits);
600     else
601         (void)snprintf(scr, sizeof(scr), "n/a");
602     (void)mvwprintw(datawin, 7, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
603
604     /* Fill in the GPS status and the time since the last state
605      * change. */
606     if (gpsdata->online == 0) {
607         newstate = 0;
608         (void)snprintf(scr, sizeof(scr), "OFFLINE");
609     } else {
610         newstate = gpsdata->fix.mode;
611         switch (gpsdata->fix.mode) {
612         case MODE_2D:
613             (void)snprintf(scr, sizeof(scr), "2D %sFIX (%d secs)",
614                            (gpsdata->status ==
615                             STATUS_DGPS_FIX) ? "DIFF " : "",
616                            (int)(time(NULL) - status_timer));
617             break;
618         case MODE_3D:
619             (void)snprintf(scr, sizeof(scr), "3D %sFIX (%d secs)",
620                            (gpsdata->status ==
621                             STATUS_DGPS_FIX) ? "DIFF " : "",
622                            (int)(time(NULL) - status_timer));
623             break;
624         default:
625             (void)snprintf(scr, sizeof(scr), "NO FIX (%d secs)",
626                            (int)(time(NULL) - status_timer));
627             break;
628         }
629     }
630     (void)mvwprintw(datawin, 8, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
631
632     /* Fill in receiver type. */
633     if (gpsdata->set & (DEVICE_SET | DEVICELIST_SET)) {
634 #ifdef CLIENTDEBUG_ENABLE
635         if (debug > 0)
636             (void)fprintf(stderr, "Device ID or list set.\n");
637 #endif
638         if (gpsdata->set & DEVICE_SET) {
639             (void)snprintf(scr, sizeof(scr), "%s", gpsdata->dev.driver);
640         } else if (gpsdata->devices.ndevices == 1) {
641             (void)snprintf(scr, sizeof(scr), "%s",
642                            gpsdata->devices.list[0].driver);
643         } else {
644             (void)snprintf(scr, sizeof(scr), "%d devices",
645                            gpsdata->devices.ndevices);
646         }
647         (void)mvwprintw(datawin, 9, DATAWIN_VALUE_OFFSET, "%-*s", 27, scr);
648     }
649     /* Note that the following four fields are exceptions to the
650      * sizing rule.  The minimum window size does not include these
651      * fields, if the window is too small, they get excluded.  This
652      * may or may not change if/when the output for these fields is
653      * fixed and/or people request their permanance.  They're only
654      * there in the first place because I arbitrarily thought they
655      * sounded interesting. ;^) */
656
657     if (window_length >= (MIN_GPS_DATAWIN_SIZE + 4)) {
658
659         /* Fill in the estimated horizontal position error. */
660         if (isnan(gpsdata->fix.epx) == 0)
661             (void)snprintf(scr, sizeof(scr), "+/- %d %s",
662                            (int)(gpsdata->fix.epx * altfactor), altunits);
663         else
664             (void)snprintf(scr, sizeof(scr), "n/a");
665         (void)mvwprintw(datawin, 10, DATAWIN_VALUE_OFFSET + 5, "%-*s", 22,
666                         scr);
667
668         if (isnan(gpsdata->fix.epy) == 0)
669             (void)snprintf(scr, sizeof(scr), "+/- %d %s",
670                            (int)(gpsdata->fix.epy * altfactor), altunits);
671         else
672             (void)snprintf(scr, sizeof(scr), "n/a");
673         (void)mvwprintw(datawin, 11, DATAWIN_VALUE_OFFSET + 5, "%-*s", 22,
674                         scr);
675
676         /* Fill in the estimated vertical position error. */
677         if (isnan(gpsdata->fix.epv) == 0)
678             (void)snprintf(scr, sizeof(scr), "+/- %d %s",
679                            (int)(gpsdata->fix.epv * altfactor), altunits);
680         else
681             (void)snprintf(scr, sizeof(scr), "n/a");
682         (void)mvwprintw(datawin, 12, DATAWIN_VALUE_OFFSET + 5, "%-*s", 22,
683                         scr);
684
685         /* Fill in the estimated track error. */
686         if (isnan(gpsdata->fix.epd) == 0)
687             (void)snprintf(scr, sizeof(scr), "+/- %d deg",
688                            (int)(gpsdata->fix.epd));
689         else
690             (void)snprintf(scr, sizeof(scr), "n/a");
691         (void)mvwprintw(datawin, 13, DATAWIN_VALUE_OFFSET + 5, "%-*s", 22,
692                         scr);
693
694         /* Fill in the estimated speed error. */
695         if (isnan(gpsdata->fix.eps) == 0)
696             (void)snprintf(scr, sizeof(scr), "+/- %d %s",
697                            (int)(gpsdata->fix.eps * speedfactor), speedunits);
698         else
699             (void)snprintf(scr, sizeof(scr), "n/a");
700         (void)mvwprintw(datawin, 14, DATAWIN_VALUE_OFFSET + 5, "%-*s", 22,
701                         scr);
702     }
703
704     /* Be quiet if the user requests silence. */
705     if (!silent_flag && raw_flag) {
706         (void)waddstr(messages, message);
707     }
708
709     /* Reset the status_timer if the state has changed. */
710     if (newstate != state) {
711         status_timer = time(NULL);
712         state = newstate;
713     }
714
715     (void)wrefresh(datawin);
716     (void)wrefresh(satellites);
717     if (raw_flag) {
718         (void)wrefresh(messages);
719     }
720 }
721
722 static void usage(char *prog)
723 {
724     (void)fprintf(stderr,
725                   "Usage: %s [-h] [-V] [-l {d|m|s}] [server[:port:[device]]]\n\n"
726                   "  -h   Show this help, then exit\n"
727                   "  -V   Show version, then exit\n"
728                   "  -s   Be silent (don't print raw gpsd data)\n"
729                   "  -l {d|m|s}  Select lat/lon format\n"
730                   "             d = DD.dddddd\n"
731                   "             m = DD MM.mmmm'\n"
732                   "             s = DD MM' SS.sss\"\n"
733                   " -m      Display heading as the estimated magnetic heading\n"
734                   "         Valid only for USA (Lower 48 + AK) and Western Europe.\n",
735                   prog);
736
737     exit(1);
738 }
739
740 /*
741  * No protocol dependencies above this line
742  */
743
744 int main(int argc, char *argv[])
745 {
746     int option;
747     int c;
748
749     struct timeval timeout;
750     fd_set rfds;
751     int data;
752
753     /*@ -observertrans @*/
754     switch (gpsd_units()) {
755     case imperial:
756         altfactor = METERS_TO_FEET;
757         altunits = "ft";
758         speedfactor = MPS_TO_MPH;
759         speedunits = "mph";
760         break;
761     case nautical:
762         altfactor = METERS_TO_FEET;
763         altunits = "ft";
764         speedfactor = MPS_TO_KNOTS;
765         speedunits = "knots";
766         break;
767     case metric:
768         altfactor = 1;
769         altunits = "m";
770         speedfactor = MPS_TO_KPH;
771         speedunits = "kph";
772         break;
773     default:
774         /* leave the default alone */
775         break;
776     }
777     /*@ +observertrans @*/
778
779     /* Process the options.  Print help if requested. */
780     while ((option = getopt(argc, argv, "hVl:smuD:")) != -1) {
781         switch (option) {
782 #ifdef CLIENTDEBUG_ENABLE
783         case 'D':
784             debug = atoi(optarg);
785             gps_enable_debug(debug, stderr);
786             break;
787 #endif /* CLIENTDEBUG_ENABLE */
788         case 'm':
789             magnetic_flag = true;
790             break;
791         case 's':
792             silent_flag = true;
793             break;
794         case 'u':
795             /*@ -observertrans @*/
796             switch (optarg[0]) {
797             case 'i':
798                 altfactor = METERS_TO_FEET;
799                 altunits = "ft";
800                 speedfactor = MPS_TO_MPH;
801                 speedunits = "mph";
802                 continue;
803             case 'n':
804                 altfactor = METERS_TO_FEET;
805                 altunits = "ft";
806                 speedfactor = MPS_TO_KNOTS;
807                 speedunits = "knots";
808                 continue;
809             case 'm':
810                 altfactor = 1;
811                 altunits = "m";
812                 speedfactor = MPS_TO_KPH;
813                 speedunits = "kph";
814                 continue;
815             default:
816                 (void)fprintf(stderr, "Unknown -u argument: %s\n", optarg);
817             }
818             break;
819             /*@ +observertrans @*/
820         case 'V':
821             (void)fprintf(stderr, "cgps: %s (revision %s)\n",
822                           VERSION, REVISION);
823             exit(0);
824         case 'l':
825             switch (optarg[0]) {
826             case 'd':
827                 deg_type = deg_dd;
828                 continue;
829             case 'm':
830                 deg_type = deg_ddmm;
831                 continue;
832             case 's':
833                 deg_type = deg_ddmmss;
834                 continue;
835             default:
836                 (void)fprintf(stderr, "Unknown -l argument: %s\n", optarg);
837                 /*@ -casebreak @*/
838             }
839             break;
840         case 'h':
841         default:
842             usage(argv[0]);
843             break;
844         }
845     }
846
847     /* Grok the server, port, and device. */
848     if (optind < argc) {
849         gpsd_source_spec(argv[optind], &source);
850     } else
851         gpsd_source_spec(NULL, &source);
852
853     /* Open the stream to gpsd. */
854     if (gps_open_r(source.server, source.port, &gpsdata) != 0) {
855         (void)fprintf(stderr,
856                       "cgps: no gpsd running or network error: %d, %s\n",
857                       errno, gps_errstr(errno));
858         exit(2);
859     }
860
861     /* Fire up curses. */
862     (void)initscr();
863     (void)noecho();
864     (void)signal(SIGINT, die);
865     (void)signal(SIGHUP, die);
866
867     windowsetup();
868
869     /* Here's where updates go now that things are established. */
870 #ifdef TRUENORTH
871     if (compass_flag) {
872         gps_set_raw_hook(&gpsdata, update_compass_panel);
873     } else
874 #endif /* TRUENORTH */
875     {
876         gps_set_raw_hook(&gpsdata, update_gps_panel);
877     }
878
879     status_timer = time(NULL);
880
881     (void)gps_stream(&gpsdata, WATCH_ENABLE, NULL);
882
883     /* heart of the client */
884     for (;;) {
885
886         /* watch to see when it has input */
887         FD_ZERO(&rfds);
888         FD_SET(gpsdata.gps_fd, &rfds);
889
890         /* wait up to five seconds. */
891         timeout.tv_sec = 5;
892         timeout.tv_usec = 0;
893
894         /* check if we have new information */
895         data = select(gpsdata.gps_fd + 1, &rfds, NULL, NULL, &timeout);
896
897         if (data == -1) {
898             fprintf(stderr, "cgps: socket error 3\n");
899             exit(2);
900         } else if (data) {
901             errno = 0;
902             if (gps_read(&gpsdata) == -1) {
903                 fprintf(stderr, "cgps: socket error 4\n");
904                 die(errno == 0 ? GPS_GONE : GPS_ERROR);
905             }
906         }
907
908         /* Check for user input. */
909         c = wgetch(datawin);
910
911         switch (c) {
912             /* Quit */
913         case 'q':
914             die(CGPS_QUIT);
915             break;
916
917             /* Toggle spewage of raw gpsd data. */
918         case 's':
919             silent_flag = !silent_flag;
920             break;
921
922             /* Clear the spewage area. */
923         case 'c':
924             (void)werase(messages);
925             break;
926
927         default:
928             break;
929         }
930
931     }
932 }