"Initial commit to Gerrit"
[profile/ivi/gpsd.git] / monitor_nmea.c
1 /*
2  * monitor_nmea.c - gpsmon support for NMEA devices.
3  *
4  * To do: Support for GPGLL, GPGBS, GPZDA, PASHR NMEA sentences.
5  *
6  * This file is Copyright (c) 2010 by the GPSD project
7  * BSD terms apply: see the file COPYING in the distribution root for details.
8  */
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <math.h>
13 #include <ctype.h>
14 #ifndef S_SPLINT_S
15 #include <unistd.h>
16 #endif /* S_SPLINT_S */
17 #include <stdarg.h>
18 #include <stdbool.h>
19 #include <assert.h>
20
21 #include "gpsd_config.h"
22
23 #ifdef HAVE_NCURSES_H
24 #include <ncurses.h>
25 #else
26 #include <curses.h>
27 #endif /* HAVE_NCURSES_H */
28 #include "gpsd.h"
29
30 #include "bits.h"
31 #include "gpsmon.h"
32 #include "gpsdclient.h"
33
34 #ifdef NMEA_ENABLE
35 extern const struct gps_type_t nmea;
36
37 static WINDOW *cookedwin, *nmeawin, *satwin, *gprmcwin, *gpggawin, *gpgsawin;
38 static double last_tick, tick_interval;
39
40 /*****************************************************************************
41  *
42  * Generic NMEA support
43  *
44  *****************************************************************************/
45
46 #define SENTENCELINE    1       /* index of sentences line in the NMEA window */
47 #define MAXSATS         12      /* max satellites we can display */
48
49 static bool nmea_initialize(void)
50 {
51     int i;
52
53     /*@ -onlytrans @*/
54     cookedwin = derwin(devicewin, 3, 80, 0, 0);
55     (void)wborder(cookedwin, 0, 0, 0, 0, 0, 0, 0, 0);
56     (void)syncok(cookedwin, true);
57     wattrset(cookedwin, A_BOLD);
58     mvwaddstr(cookedwin, 1, 1, "Time: ");
59     mvwaddstr(cookedwin, 1, 31, "Lat: ");
60     mvwaddstr(cookedwin, 1, 55, "Lon: ");
61     mvwaddstr(cookedwin, 2, 34, " Cooked PVT ");
62     wattrset(cookedwin, A_NORMAL);
63
64     nmeawin = derwin(devicewin, 3, 80, 3, 0);
65     (void)wborder(nmeawin, 0, 0, 0, 0, 0, 0, 0, 0);
66     (void)syncok(nmeawin, true);
67     wattrset(nmeawin, A_BOLD);
68     mvwaddstr(nmeawin, 2, 34, " Sentences ");
69     wattrset(nmeawin, A_NORMAL);
70
71     satwin = derwin(devicewin, MAXSATS + 3, 20, 6, 0);
72     (void)wborder(satwin, 0, 0, 0, 0, 0, 0, 0, 0), (void)syncok(satwin, true);
73     (void)wattrset(satwin, A_BOLD);
74     (void)mvwprintw(satwin, 1, 1, "Ch PRN  Az El S/N");
75     for (i = 0; i < MAXSATS; i++)
76         (void)mvwprintw(satwin, (int)(i + 2), 1, "%2d", i);
77     (void)mvwprintw(satwin, 14, 7, " GSV ");
78     (void)wattrset(satwin, A_NORMAL);
79
80     gprmcwin = derwin(devicewin, 9, 30, 6, 20);
81     (void)wborder(gprmcwin, 0, 0, 0, 0, 0, 0, 0, 0),
82         (void)syncok(gprmcwin, true);
83     (void)wattrset(gprmcwin, A_BOLD);
84     (void)mvwprintw(gprmcwin, 1, 1, "Time: ");
85     (void)mvwprintw(gprmcwin, 2, 1, "Latitude: ");
86     (void)mvwprintw(gprmcwin, 3, 1, "Longitude: ");
87     (void)mvwprintw(gprmcwin, 4, 1, "Speed: ");
88     (void)mvwprintw(gprmcwin, 5, 1, "Course: ");
89     (void)mvwprintw(gprmcwin, 6, 1, "Status:            FAA: ");
90     (void)mvwprintw(gprmcwin, 7, 1, "MagVar: ");
91     (void)mvwprintw(gprmcwin, 8, 12, " RMC ");
92     (void)wattrset(gprmcwin, A_NORMAL);
93
94     gpgsawin = derwin(devicewin, 5, 30, 15, 20);
95     (void)wborder(gpgsawin, 0, 0, 0, 0, 0, 0, 0, 0),
96         (void)syncok(gpgsawin, true);
97     (void)wattrset(gpgsawin, A_BOLD);
98     (void)mvwprintw(gpgsawin, 1, 1, "Mode: ");
99     (void)mvwprintw(gpgsawin, 2, 1, "Sats: ");
100     (void)mvwprintw(gpgsawin, 3, 1, "DOP: H=      V=      P=");
101     (void)mvwprintw(gpgsawin, 4, 12, " GSA ");
102     (void)wattrset(gpgsawin, A_NORMAL);
103
104     gpggawin = derwin(devicewin, 9, 30, 6, 50);
105     (void)wborder(gpggawin, 0, 0, 0, 0, 0, 0, 0, 0),
106         (void)syncok(gpggawin, true);
107     (void)wattrset(gpggawin, A_BOLD);
108     (void)mvwprintw(gpggawin, 1, 1, "Time: ");
109     (void)mvwprintw(gpggawin, 2, 1, "Latitude: ");
110     (void)mvwprintw(gpggawin, 3, 1, "Longitude: ");
111     (void)mvwprintw(gpggawin, 4, 1, "Altitude: ");
112     (void)mvwprintw(gpggawin, 5, 1, "Quality:       Sats: ");
113     (void)mvwprintw(gpggawin, 6, 1, "HDOP: ");
114     (void)mvwprintw(gpggawin, 7, 1, "Geoid: ");
115     (void)mvwprintw(gpggawin, 8, 12, " GGA ");
116     (void)wattrset(gpggawin, A_NORMAL);
117     /*@ +onlytrans @*/
118
119     last_tick = timestamp();
120
121     return (nmeawin != NULL);
122 }
123
124 static void cooked_pvt(void)
125 {
126     char scr[128];
127
128     if (isnan(session.gpsdata.fix.time) == 0) {
129         (void)unix_to_iso8601(session.gpsdata.fix.time, scr, sizeof(scr));
130     } else
131         (void)snprintf(scr, sizeof(scr), "n/a");
132     (void)mvwprintw(cookedwin, 1, 7, "%-22s", scr);
133
134
135     if (session.gpsdata.fix.mode >= MODE_2D
136         && isnan(session.gpsdata.fix.latitude) == 0) {
137         (void)snprintf(scr, sizeof(scr), "%s %c",
138                        deg_to_str(deg_ddmmss,
139                                   fabs(session.gpsdata.fix.latitude)),
140                        (session.gpsdata.fix.latitude < 0) ? 'S' : 'N');
141     } else
142         (void)snprintf(scr, sizeof(scr), "n/a");
143     (void)mvwprintw(cookedwin, 1, 36, "%-17s", scr);
144
145     if (session.gpsdata.fix.mode >= MODE_2D
146         && isnan(session.gpsdata.fix.longitude) == 0) {
147         (void)snprintf(scr, sizeof(scr), "%s %c",
148                        deg_to_str(deg_ddmmss,
149                                   fabs(session.gpsdata.fix.longitude)),
150                        (session.gpsdata.fix.longitude < 0) ? 'W' : 'E');
151     } else
152         (void)snprintf(scr, sizeof(scr), "n/a");
153     (void)mvwprintw(cookedwin, 1, 60, "%-17s", scr);
154 }
155
156
157 /*@ -globstate -nullpass (splint is confused) */
158 static void nmea_update(void)
159 {
160     static char sentences[NMEA_MAX];
161     char **fields;
162
163     assert(cookedwin != NULL);
164     assert(nmeawin != NULL);
165     assert(gpgsawin != NULL);
166     assert(gpggawin != NULL);
167     assert(gprmcwin != NULL);
168
169     fields = session.driver.nmea.field;
170
171     if (session.packet.outbuffer[0] == (unsigned char)'$') {
172         int ymax, xmax;
173         double now;
174         getmaxyx(nmeawin, ymax, xmax);
175         if (strstr(sentences, fields[0]) == NULL) {
176             char *s_end = sentences + strlen(sentences);
177             if ((int)(strlen(sentences) + strlen(fields[0])) < xmax - 2) {
178                 *s_end++ = ' ';
179                 (void)strlcpy(s_end, fields[0], NMEA_MAX);
180             } else {
181                 *--s_end = '.';
182                 *--s_end = '.';
183                 *--s_end = '.';
184             }
185             mvwaddstr(nmeawin, SENTENCELINE, 1, sentences);
186         }
187
188         /*
189          * If the interval between this and last update is
190          * the longest we've seen yet, boldify the corresponding
191          * tag.
192          */
193         now = timestamp();
194         if (now > last_tick && (now - last_tick) > tick_interval) {
195             char *findme = strstr(sentences, fields[0]);
196
197             tick_interval = now - last_tick;
198             if (findme != NULL) {
199                 mvwchgat(nmeawin, SENTENCELINE, 1, xmax - 13, A_NORMAL, 0,
200                          NULL);
201                 mvwchgat(nmeawin, SENTENCELINE, 1 + (findme - sentences),
202                          (int)strlen(fields[0]), A_BOLD, 0, NULL);
203             }
204         }
205         last_tick = now;
206
207         if (strcmp(fields[0], "GPGSV") == 0
208             || strcmp(fields[0], "GNGSV") == 0
209             || strcmp(fields[0], "GLGSV") == 0) {
210             int i;
211             int nsats =
212                 (session.gpsdata.satellites_visible <
213                  MAXSATS) ? session.gpsdata.satellites_visible : MAXSATS;
214
215             for (i = 0; i < nsats; i++) {
216                 (void)wmove(satwin, i + 2, 3);
217                 (void)wprintw(satwin, " %3d %3d%3d %3.0f",
218                               session.gpsdata.PRN[i],
219                               session.gpsdata.azimuth[i],
220                               session.gpsdata.elevation[i],
221                               session.gpsdata.ss[i]);
222             }
223             /* add overflow mark to the display */
224             if (nsats <= MAXSATS)
225                 (void)mvwaddch(satwin, MAXSATS + 2, 18, ACS_HLINE);
226             else
227                 (void)mvwaddch(satwin, MAXSATS + 2, 18, ACS_DARROW);
228         }
229
230         if (strcmp(fields[0], "GPRMC") == 0
231             || strcmp(fields[0], "GNRMC") == 0
232             || strcmp(fields[0], "GLRMC") == 0) {
233             /* time, lat, lon, course, speed */
234             (void)mvwaddstr(gprmcwin, 1, 12, fields[1]);
235             (void)mvwprintw(gprmcwin, 2, 12, "%12s %s", fields[3], fields[4]);
236             (void)mvwprintw(gprmcwin, 3, 12, "%12s %s", fields[5], fields[6]);
237             (void)mvwaddstr(gprmcwin, 4, 12, fields[7]);
238             (void)mvwaddstr(gprmcwin, 5, 12, fields[8]);
239             /* the status field, FAA code, and magnetic variation */
240             (void)mvwaddstr(gprmcwin, 6, 12, fields[2]);
241             (void)mvwaddstr(gprmcwin, 6, 25, fields[12]);
242             (void)mvwprintw(gprmcwin, 7, 12, "%-5s%s", fields[10],
243                             fields[11]);
244
245             cooked_pvt();       /* cooked version of PVT */
246         }
247
248         if (strcmp(fields[0], "GPGSA") == 0
249             || strcmp(fields[0], "GNGSA") == 0
250             || strcmp(fields[0], "GLGSA") == 0) {
251             char scr[128];
252             int i;
253             (void)mvwprintw(gpgsawin, 1, 7, "%1s %s", fields[1], fields[2]);
254             (void)wmove(gpgsawin, 2, 7);
255             (void)wclrtoeol(gpgsawin);
256             scr[0] = '\0';
257             for (i = 0; i < session.gpsdata.satellites_used; i++) {
258                 (void)snprintf(scr + strlen(scr), sizeof(scr) - strlen(scr),
259                                "%d ", session.gpsdata.used[i]);
260             }
261             getmaxyx(gpgsawin, ymax, xmax);
262             (void)mvwaddnstr(gpgsawin, 2, 7, scr, xmax - 2 - 7);
263             if (strlen(scr) >= (size_t) (xmax - 2)) {
264                 mvwaddch(gpgsawin, 2, xmax - 2 - 7, (chtype) '.');
265                 mvwaddch(gpgsawin, 2, xmax - 3 - 7, (chtype) '.');
266                 mvwaddch(gpgsawin, 2, xmax - 4 - 7, (chtype) '.');
267             }
268             monitor_fixframe(gpgsawin);
269             (void)mvwprintw(gpgsawin, 3, 8, "%-5s", fields[16]);
270             (void)mvwprintw(gpgsawin, 3, 16, "%-5s", fields[17]);
271             (void)mvwprintw(gpgsawin, 3, 24, "%-5s", fields[15]);
272             monitor_fixframe(gpgsawin);
273         }
274         if (strcmp(fields[0], "GPGGA") == 0
275             || strcmp(fields[0], "GNGGA") == 0
276             || strcmp(fields[0], "GLGGA") == 0) {
277             (void)mvwprintw(gpggawin, 1, 12, "%-17s", fields[1]);
278             (void)mvwprintw(gpggawin, 2, 12, "%-17s", fields[2]);
279             (void)mvwprintw(gpggawin, 3, 12, "%-17s", fields[4]);
280             (void)mvwprintw(gpggawin, 4, 12, "%-17s", fields[9]);
281             (void)mvwprintw(gpggawin, 5, 12, "%1.1s", fields[6]);
282             (void)mvwprintw(gpggawin, 5, 22, "%2.2s", fields[7]);
283             (void)mvwprintw(gpggawin, 6, 12, "%-5.5s", fields[8]);
284             (void)mvwprintw(gpggawin, 7, 12, "%-5.5s", fields[11]);
285         }
286     }
287 }
288
289 /*@ +globstate +nullpass */
290
291 #undef SENTENCELINE
292
293 static void nmea_wrap(void)
294 {
295     (void)delwin(nmeawin);
296     (void)delwin(gpgsawin);
297     (void)delwin(gpggawin);
298     (void)delwin(gprmcwin);
299 }
300
301 const struct monitor_object_t nmea_mmt = {
302     .initialize = nmea_initialize,
303     .update = nmea_update,
304     .command = NULL,
305     .wrap = nmea_wrap,
306     .min_y = 21,.min_x = 80,
307     .driver = &nmea,
308 };
309
310 /*****************************************************************************
311  *
312  * Extended NMEA support
313  *
314  *****************************************************************************/
315
316 #ifdef ALLOW_CONTROLSEND
317 static void monitor_nmea_send(const char *fmt, ...)
318 {
319     char buf[BUFSIZ];
320     va_list ap;
321
322     va_start(ap, fmt);
323     (void)vsnprintf(buf, sizeof(buf) - 5, fmt, ap);
324     va_end(ap);
325     (void)monitor_control_send((unsigned char *)buf, strlen(buf));
326 }
327 #endif /* ALLOW_CONTROLSEND */
328
329 /*
330  * Yes, it's OK for most of these to be clones of the generic NMEA monitor
331  * object except for the pointer to the GPSD driver.  That pointer makes
332  * a difference, as it will automatically enable stuff like speed-switcher
333  * and mode-switcher commands.  It's really only necessary to write a
334  * separate monitor object if you want to change the device-window
335  * display or implement device-specific commands.
336  */
337
338 #if defined(GARMIN_ENABLE) && defined(NMEA_ENABLE)
339 extern const struct gps_type_t garmin;
340
341 const struct monitor_object_t garmin_mmt = {
342     .initialize = nmea_initialize,
343     .update = nmea_update,
344     .command = NULL,
345     .wrap = nmea_wrap,
346     .min_y = 21,.min_x = 80,
347     .driver = &garmin,
348 };
349 #endif /* GARMIN_ENABLE && NMEA_ENABLE */
350
351 #ifdef ASHTECH_ENABLE
352 extern const struct gps_type_t ashtech;
353
354 #define ASHTECH_SPEED_9600 5
355 #define ASHTECH_SPEED_57600 8
356
357 #ifdef ALLOW_CONTROLSEND
358 static int ashtech_command(char line[])
359 {
360     switch (line[0]) {
361     case 'N':                   /* normal = 9600, GGA+GSA+GSV+RMC+ZDA */
362         monitor_nmea_send("$PASHS,NME,ALL,A,OFF");      /* silence outbound chatter */
363         monitor_nmea_send("$PASHS,NME,ALL,B,OFF");
364         monitor_nmea_send("$PASHS,NME,GGA,A,ON");
365         monitor_nmea_send("$PASHS,NME,GSA,A,ON");
366         monitor_nmea_send("$PASHS,NME,GSV,A,ON");
367         monitor_nmea_send("$PASHS,NME,RMC,A,ON");
368         monitor_nmea_send("$PASHS,NME,ZDA,A,ON");
369
370         monitor_nmea_send("$PASHS,INI,%d,%d,,,0,",
371                           ASHTECH_SPEED_9600, ASHTECH_SPEED_9600);
372         (void)sleep(6);         /* it takes 4-6 sec for the receiver to reboot */
373         monitor_nmea_send("$PASHS,WAS,ON");     /* enable WAAS */
374         break;
375
376     case 'R':                   /* raw = 57600, normal+XPG+POS+SAT+MCA+PBN+SNV */
377         monitor_nmea_send("$PASHS,NME,ALL,A,OFF");      /* silence outbound chatter */
378         monitor_nmea_send("$PASHS,NME,ALL,B,OFF");
379         monitor_nmea_send("$PASHS,NME,GGA,A,ON");
380         monitor_nmea_send("$PASHS,NME,GSA,A,ON");
381         monitor_nmea_send("$PASHS,NME,GSV,A,ON");
382         monitor_nmea_send("$PASHS,NME,RMC,A,ON");
383         monitor_nmea_send("$PASHS,NME,ZDA,A,ON");
384
385         monitor_nmea_send("$PASHS,INI,%d,%d,,,0,",
386                           ASHTECH_SPEED_57600, ASHTECH_SPEED_9600);
387         (void)sleep(6);         /* it takes 4-6 sec for the receiver to reboot */
388         monitor_nmea_send("$PASHS,WAS,ON");     /* enable WAAS */
389
390         monitor_nmea_send("$PASHS,NME,POS,A,ON");       /* Ashtech PVT solution */
391         monitor_nmea_send("$PASHS,NME,SAT,A,ON");       /* Ashtech Satellite status */
392         monitor_nmea_send("$PASHS,NME,MCA,A,ON");       /* MCA measurements */
393         monitor_nmea_send("$PASHS,NME,PBN,A,ON");       /* ECEF PVT solution */
394         monitor_nmea_send("$PASHS,NME,SNV,A,ON,10");    /* Almanac data */
395
396         monitor_nmea_send("$PASHS,NME,XMG,A,ON");       /* exception messages */
397         break;
398
399     default:
400         return COMMAND_UNKNOWN;
401     }
402
403     return COMMAND_UNKNOWN;
404 }
405 #endif /* ALLOW_CONTROLSEND */
406
407 const struct monitor_object_t ashtech_mmt = {
408     .initialize = nmea_initialize,
409     .update = nmea_update,
410 #ifdef ALLOW_CONTROLSEND
411     .command = ashtech_command,
412 #else
413     .command = NULL,
414 #endif /* ALLOW_CONTROLSEND */
415     .wrap = nmea_wrap,
416     .min_y = 21,.min_x = 80,
417     .driver = &ashtech,
418 };
419 #endif /* ASHTECH_ENABLE */
420
421 #ifdef FV18_ENABLE
422 extern const struct gps_type_t fv18;
423
424 const struct monitor_object_t fv18_mmt = {
425     .initialize = nmea_initialize,
426     .update = nmea_update,
427     .command = NULL,
428     .wrap = nmea_wrap,
429     .min_y = 21,.min_x = 80,
430     .driver = &fv18,
431 };
432 #endif /* FV18_ENABLE */
433
434 #ifdef GPSCLOCK_ENABLE
435 extern const struct gps_type_t gpsclock;
436
437 const struct monitor_object_t gpsclock_mmt = {
438     .initialize = nmea_initialize,
439     .update = nmea_update,
440     .command = NULL,
441     .wrap = nmea_wrap,
442     .min_y = 21,.min_x = 80,
443     .driver = &gpsclock,
444 };
445 #endif /* GPSCLOCK_ENABLE */
446
447 #ifdef MTK3301_ENABLE
448 extern const struct gps_type_t mtk3301;
449
450 const struct monitor_object_t mtk3301_mmt = {
451     .initialize = nmea_initialize,
452     .update = nmea_update,
453     .command = NULL,
454     .wrap = nmea_wrap,
455     .min_y = 21,.min_x = 80,
456     .driver = &mtk3301,
457 };
458 #endif /* MTK3301_ENABLE */
459
460 #endif /* NMEA_ENABLE */