1 /* libgps.c -- client interface library for the gpsd daemon
3 * This file is Copyright (c) 2010 by the GPSD project
4 * BSD terms apply: see the file COPYING in the distribution root for details.
10 #endif /* S_SPLINT_S */
11 #include <sys/types.h>
26 #ifdef HAVE_SYS_SOCKET_H
27 #include <sys/socket.h>
32 #if defined (HAVE_SYS_SELECT_H)
33 #include <sys/select.h>
40 extern char *strtok_r(char *, const char *, char **);
41 #endif /* S_SPLINT_S */
43 #if defined(TESTMAIN) || defined(CLIENTDEBUG_ENABLE)
45 #endif /* defined(TESTMAIN) || defined(CLIENTDEBUG_ENABLE) */
51 char buffer[GPS_JSON_RESPONSE_MAX * 2];
53 #define PRIVATE(gpsdata) ((struct privdata_t *)gpsdata->privdata)
56 static int debuglevel = 0;
57 static int waitcount = 0;
60 void gps_enable_debug(int level, FILE * fp)
61 /* control the level and destination of debug trace messages */
65 #ifdef CLIENTDEBUG_ENABLE
66 json_enable_debug(level - 2, fp);
70 static void gps_trace(int errlevel, const char *fmt, ...)
71 /* assemble command in printf(3) style */
73 if (errlevel <= debuglevel) {
77 (void)strlcpy(buf, "libgps: ", BUFSIZ);
79 (void)vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt,
83 (void)fputs(buf, debugfp);
87 # define libgps_debug_trace(args) (void) gps_trace args
89 # define libgps_debug_trace(args) /*@i1@*/do { } while (0)
90 #endif /* LIBGPS_DEBUG */
93 int gps_open_r(/*@null@*/const char *host, /*@null@*/const char *port,
94 /*@out@*/ struct gps_data_t *gpsdata)
102 port = DEFAULT_GPSD_PORT;
104 libgps_debug_trace((1, "gps_open_r(%s, %s)\n", host, port));
107 if ((gpsdata->gps_fd =
108 netlib_connectsock(AF_UNSPEC, host, port, "tcp")) < 0) {
109 errno = gpsdata->gps_fd;
113 QTcpSocket *sock = new QTcpSocket();
114 gpsdata->gps_fd = sock;
115 sock->connectToHost(host, QString(port).toInt());
116 if (!sock->waitForConnected())
117 qDebug() << "libgps::connect error: " << sock->errorString();
119 qDebug() << "libgps::connected!";
123 gpsdata->status = STATUS_NO_FIX;
124 gps_clear_fix(&gpsdata->fix);
126 /* set up for line-buffered I/O over the daemon socket */
127 gpsdata->privdata = (void *)malloc(sizeof(struct privdata_t));
128 if (gpsdata->privdata == NULL)
130 PRIVATE(gpsdata)->newstyle = false;
131 PRIVATE(gpsdata)->waiting = 0;
132 /*@i2@*/ PRIVATE(gpsdata)->buffer[0] = '\0';
138 /*@-compmempass -immediatetrans@*/
139 struct gps_data_t *gps_open(const char *host, const char *port)
140 /* open a connection to a gpsd daemon */
142 static struct gps_data_t gpsdata;
143 if (gps_open_r(host, port, &gpsdata) == -1)
149 /*@+compmempass +immediatetrans@*/
151 /*@-compdef -usereleased@*/
152 int gps_close(struct gps_data_t *gpsdata)
153 /* close a gpsd connection */
155 libgps_debug_trace((1, "gps_close()\n"));
157 free(PRIVATE(gpsdata));
158 (void)close(gpsdata->gps_fd);
159 gpsdata->gps_fd = -1;
161 QTcpSocket *sock = (QTcpSocket *) gpsdata->gps_fd;
162 sock->disconnectFromHost();
164 gpsdata->gps_fd = NULL;
170 /*@+compdef +usereleased@*/
172 void gps_set_raw_hook(struct gps_data_t *gpsdata,
173 void (*hook) (struct gps_data_t *, char *, size_t len))
175 gpsdata->raw_hook = hook;
179 static void libgps_dump_state(struct gps_data_t *collect, time_t now)
181 char *status_values[] = { "NO_FIX", "FIX", "DGPS_FIX" };
182 char *mode_values[] = { "", "NO_FIX", "MODE_2D", "MODE_3D" };
184 /* no need to dump the entire state, this is a sanity check */
186 (void)fprintf(debugfp, "flags: (0x%04x) %s\n",
187 collect->set, gps_maskdump(collect->set));
189 if (collect->set & ONLINE_SET)
190 (void)fprintf(debugfp, "ONLINE: %lf\n", collect->online);
191 if (collect->set & TIME_SET)
192 (void)fprintf(debugfp, "TIME: %lf\n", collect->fix.time);
193 if (collect->set & LATLON_SET)
194 (void)fprintf(debugfp, "LATLON: lat/lon: %lf %lf\n",
195 collect->fix.latitude, collect->fix.longitude);
196 if (collect->set & ALTITUDE_SET)
197 (void)fprintf(debugfp, "ALTITUDE: altitude: %lf U: climb: %lf\n",
198 collect->fix.altitude, collect->fix.climb);
199 if (collect->set & SPEED_SET)
200 (void)fprintf(debugfp, "SPEED: %lf\n", collect->fix.speed);
201 if (collect->set & TRACK_SET)
202 (void)fprintf(debugfp, "TRACK: track: %lf\n", collect->fix.track);
203 if (collect->set & CLIMB_SET)
204 (void)fprintf(debugfp, "CLIMB: climb: %lf\n", collect->fix.climb);
205 if (collect->set & STATUS_SET)
206 (void)fprintf(debugfp, "STATUS: status: %d (%s)\n",
207 collect->status, status_values[collect->status]);
208 if (collect->set & MODE_SET)
209 (void)fprintf(debugfp, "MODE: mode: %d (%s)\n",
210 collect->fix.mode, mode_values[collect->fix.mode]);
211 if (collect->set & DOP_SET)
212 (void)fprintf(debugfp,
213 "DOP: satellites %d, pdop=%lf, hdop=%lf, vdop=%lf\n",
214 collect->satellites_used, collect->dop.pdop,
215 collect->dop.hdop, collect->dop.vdop);
216 if (collect->set & VERSION_SET)
217 (void)fprintf(debugfp, "VERSION: release=%s rev=%s proto=%d.%d\n",
218 collect->version.release,
219 collect->version.rev,
220 collect->version.proto_major,
221 collect->version.proto_minor);
222 if (collect->set & POLICY_SET)
223 (void)fprintf(debugfp,
224 "POLICY: watcher=%s nmea=%s raw=%d scaled=%s timing=%s, devpath=%s\n",
225 collect->policy.watcher ? "true" : "false",
226 collect->policy.nmea ? "true" : "false",
228 collect->policy.scaled ? "true" : "false",
229 collect->policy.timing ? "true" : "false",
230 collect->policy.devpath);
231 if (collect->set & SATELLITE_SET) {
234 (void)fprintf(debugfp, "SKY: satellites in view: %d\n",
235 collect->satellites_visible);
236 for (i = 0; i < collect->satellites_visible; i++) {
237 (void)fprintf(debugfp, " %2.2d: %2.2d %3.3d %3.0f %c\n",
238 collect->PRN[i], collect->elevation[i],
239 collect->azimuth[i], collect->ss[i],
240 collect->used[i] ? 'Y' : 'N');
243 if (collect->set & DEVICE_SET)
244 (void)fprintf(debugfp, "DEVICE: Device is '%s', driver is '%s'\n",
245 collect->dev.path, collect->dev.driver);
246 #ifdef OLDSTYLE_ENABLE
247 if (collect->set & DEVICEID_SET)
248 (void)fprintf(debugfp, "GPSD ID is %s\n", collect->dev.subtype);
249 #endif /* OLDSTYLE_ENABLE */
250 if (collect->set & DEVICELIST_SET) {
252 (void)fprintf(debugfp, "DEVICELIST:%d devices:\n",
253 collect->devices.ndevices);
254 for (i = 0; i < collect->devices.ndevices; i++) {
255 (void)fprintf(debugfp, "%d: path='%s' driver='%s'\n",
256 collect->devices.ndevices,
257 collect->devices.list[i].path,
258 collect->devices.list[i].driver);
263 #endif /* LIBGPS_DEBUG */
266 /*@ -branchstate -usereleased -mustfreefresh -nullstate -usedef @*/
267 int gps_unpack(char *buf, struct gps_data_t *gpsdata)
268 /* unpack a gpsd response into a status structure, buf must be writeable */
270 libgps_debug_trace((1, "gps_unpack(%s)\n", buf));
272 /* detect and process a JSON response */
274 const char *jp = buf, **next = &jp;
275 while (next != NULL && *next != NULL && next[0][0] != '\0') {
276 libgps_debug_trace((1,
277 "gps_unpack() segment parse '%s'\n", *next));
278 if (libgps_json_unpack(*next, gpsdata, next) == -1)
282 libgps_dump_state(gpsdata, time(NULL));
283 #endif /* LIBGPS_DEBUG */
286 #ifdef OLDSTYLE_ENABLE
287 if (PRIVATE(gpsdata) != NULL)
288 PRIVATE(gpsdata)->newstyle = true;
289 #endif /* OLDSTYLE_ENABLE */
291 #ifdef OLDSTYLE_ENABLE
294 * Get the decimal separator for the current application locale.
295 * This looks thread-unsafe, but it's not. The key is that
296 * character assignment is atomic.
300 static char decimal_point = '\0';
301 if (decimal_point == '\0') {
302 struct lconv *locale_data = localeconv();
303 if (locale_data != NULL && locale_data->decimal_point[0] != '.')
304 decimal_point = locale_data->decimal_point[0];
307 for (ns = buf; ns; ns = strstr(ns + 1, "GPSD")) {
308 if ( /*@i1@*/ strncmp(ns, "GPSD", 4) == 0) {
310 /* the following should execute each time we have a good next sp */
311 for (sp = ns + 5; *sp != '\0'; sp = tp + 1) {
312 tp = sp + strcspn(sp, ",\r\n");
313 eol = *tp == '\r' || *tp == '\n';
320 * The daemon always emits the Anglo-American and SI
321 * decimal point. Hack these into whatever the
322 * application locale requires if it's not the same.
323 * This has to happen *after* we grab the next
324 * comma-delimited response, or we'll lose horribly
325 * in locales where the decimal separator is comma.
327 if (decimal_point != '\0') {
329 for (cp = sp; cp < tp; cp++)
334 /* note, there's a bit of skip logic after the switch */
337 case 'F': /*@ -mustfreeonly */
339 gpsdata->dev.path[0] = '\0';
341 /*@ -mayaliasunique @*/
342 strncpy(gpsdata->dev.path, sp + 2,
343 sizeof(gpsdata->dev.path));
344 /*@ +mayaliasunique @*/
345 gpsdata->set |= DEVICE_SET;
352 gpsdata->dev.subtype[0] = '\0';
354 (void)strlcpy(gpsdata->dev.subtype, sp + 2,
355 sizeof(gpsdata->dev.subtype));
356 gpsdata->set |= DEVICEID_SET;
362 gpsdata->set = MODE_SET | STATUS_SET;
363 gpsdata->status = STATUS_NO_FIX;
364 gps_clear_fix(&gpsdata->fix);
367 char tag[MAXTAGLEN + 1], alt[20];
368 char eph[20], epv[20], track[20], speed[20],
370 char epd[20], eps[20], epc[20], mode[2];
371 char timestr[20], ept[20], lat[20], lon[20];
372 int st = sscanf(sp + 2,
373 "%8s %19s %19s %19s %19s %19s %19s %19s %19s %19s %19s %19s %19s %19s %1s",
374 tag, timestr, ept, lat, lon,
375 alt, eph, epv, track, speed,
377 epd, eps, epc, mode);
379 #define DEFAULT(val) (val[0] == '?') ? NAN : atof(val)
381 nf.time = DEFAULT(timestr);
382 nf.latitude = DEFAULT(lat);
383 nf.longitude = DEFAULT(lon);
384 nf.ept = DEFAULT(ept);
385 nf.altitude = DEFAULT(alt);
386 /* designed before we split eph into epx+epy */
387 nf.epx = nf.epy = DEFAULT(eph) / sqrt(2);
388 nf.epv = DEFAULT(epv);
389 nf.track = DEFAULT(track);
390 nf.speed = DEFAULT(speed);
391 nf.climb = DEFAULT(climb);
392 nf.epd = DEFAULT(epd);
393 nf.eps = DEFAULT(eps);
394 nf.epc = DEFAULT(epc);
400 '?') ? MODE_NOT_SEEN : atoi(mode);
403 (alt[0] == '?') ? MODE_2D : MODE_3D;
405 gpsdata->set |= ALTITUDE_SET | CLIMB_SET;
406 if (isnan(nf.epx) == 0 && isnan(nf.epy) == 0)
407 gpsdata->set |= HERR_SET;
408 if (isnan(nf.epv) == 0)
409 gpsdata->set |= VERR_SET;
410 if (isnan(nf.track) == 0)
411 gpsdata->set |= TRACK_SET | SPEED_SET;
412 if (isnan(nf.eps) == 0)
413 gpsdata->set |= SPEEDERR_SET;
414 if (isnan(nf.epc) == 0)
415 gpsdata->set |= CLIMBERR_SET;
417 (void)strlcpy(gpsdata->tag, tag,
420 TIME_SET | TIMERR_SET | LATLON_SET |
422 gpsdata->status = STATUS_FIX;
423 gpsdata->set |= STATUS_SET;
429 gpsdata->online = -1;
431 (void)sscanf(sp, "X=%lf", &gpsdata->online);
432 gpsdata->set |= ONLINE_SET;
437 gpsdata->satellites_visible = 0;
439 int j, i1, i2, i3, i5;
440 int PRN[MAXCHANNELS];
441 int elevation[MAXCHANNELS], azimuth[MAXCHANNELS];
442 int used[MAXCHANNELS];
443 double ss[MAXCHANNELS], f4;
444 char tag[MAXTAGLEN + 1], timestamp[21];
446 (void)sscanf(sp, "Y=%8s %20s %d ",
448 &gpsdata->satellites_visible);
449 (void)strncpy(gpsdata->tag, tag, MAXTAGLEN);
450 if (timestamp[0] != '?') {
451 gpsdata->set |= TIME_SET;
453 for (j = 0; j < gpsdata->satellites_visible; j++) {
454 PRN[j] = elevation[j] = azimuth[j] = used[j] =
458 for (j = 0, gpsdata->satellites_used = 0;
459 j < gpsdata->satellites_visible; j++) {
461 && ((sp = strchr(sp, ':')) != NULL)) {
463 (void)sscanf(sp, "%d %d %d %lf %d", &i1,
471 gpsdata->satellites_used++;
475 memcpy(gpsdata->PRN, PRN, sizeof(PRN));
476 memcpy(gpsdata->elevation, elevation,
478 memcpy(gpsdata->azimuth, azimuth,
480 memcpy(gpsdata->ss, ss, sizeof(ss));
481 memcpy(gpsdata->used, used, sizeof(used));
484 gpsdata->set |= SATELLITE_SET;
490 libgps_dump_state(gpsdata, time(NULL));
491 #endif /* LIBGPS_DEBUG */
494 * Skip to next GPSD when we see \r or \n;
495 * we don't want to try interpreting stuff
496 * in between that might be raw mode data.
504 #endif /* OLDSTYLE_ENABLE */
507 if (gpsdata->raw_hook) {
508 //libgps_debug_trace((stderr, "libgps: raw hook called on '%s'\n", buf));
509 gpsdata->raw_hook(gpsdata, buf, strlen(buf));
512 libgps_debug_trace((1, "final flags: (0x%04x) %s\n", gpsdata->set,
513 gps_maskdump(gpsdata->set)));
519 /*@ -branchstate +usereleased +mustfreefresh +nullstate +usedef @*/
521 bool gps_waiting(struct gps_data_t * gpsdata)
522 /* is there input waiting from the GPS? */
528 libgps_debug_trace((1, "gps_waiting(): %d\n", waitcount++));
529 if (PRIVATE(gpsdata)->waiting > 0)
533 FD_SET(gpsdata->gps_fd, &rfds);
536 /* all error conditions return "not waiting" -- crude but effective */
537 return (select(gpsdata->gps_fd + 1, &rfds, NULL, NULL, &tv) == 1);
539 return ((QTcpSocket *) (gpsdata->gps_fd))->waitForReadyRead(250);
543 /*@-compdef -usedef -uniondef@*/
544 int gps_read(/*@out@*/struct gps_data_t *gpsdata)
545 /* wait for and read data being streamed from the daemon */
549 ssize_t response_length;
551 struct privdata_t *priv = PRIVATE(gpsdata);
553 gpsdata->set &= ~PACKET_SET;
554 for (eol = priv->buffer;
555 *eol != '\n' && eol < priv->buffer + priv->waiting; eol++)
562 /* read data: return -1 if no data waiting or buffered, 0 otherwise */
563 status = (int)recv(gpsdata->gps_fd,
564 priv->buffer + priv->waiting,
565 sizeof(priv->buffer) - priv->waiting, 0);
568 ((QTcpSocket *) (gpsdata->gps_fd))->read(priv->buffer +
570 sizeof(priv->buffer) -
574 /* if we just received data from the socket, it's in the buffer */
576 priv->waiting += status;
577 /* buffer is empty - implies no data was read */
578 if (priv->waiting == 0) {
580 * If we received 0 bytes, other side of socket is closing.
581 * Return -1 as end-of-data indication.
586 /* count transient errors as success, we'll retry later */
587 else if (errno == EINTR || errno == EAGAIN
588 || errno == EWOULDBLOCK)
591 /* hard error return of -1, pass it along */
595 /* there's buffered data waiting to be returned */
596 for (eol = priv->buffer;
597 *eol != '\n' && eol < priv->buffer + priv->waiting; eol++)
607 response_length = eol - priv->buffer + 1;
608 received = gpsdata->online = timestamp();
609 status = gps_unpack(priv->buffer, gpsdata);
610 /*@+matchanyintegral@*/
611 memmove(priv->buffer,
612 priv->buffer + response_length, priv->waiting - response_length);
613 /*@-matchanyintegral@*/
614 priv->waiting -= response_length;
615 gpsdata->set |= PACKET_SET;
619 /*@+compdef -usedef +uniondef@*/
621 int gps_poll(/*@out@*/struct gps_data_t *gpsdata)
622 /* for backwards compatibility */
624 int status = gps_read(gpsdata);
632 int gps_send(struct gps_data_t *gpsdata, const char *fmt, ...)
633 /* send a command to the gpsd instance */
639 (void)vsnprintf(buf, sizeof(buf) - 2, fmt, ap);
641 if (buf[strlen(buf) - 1] != '\n')
642 (void)strlcat(buf, "\n", BUFSIZ);
644 if (write(gpsdata->gps_fd, buf, strlen(buf)) == (ssize_t) strlen(buf))
649 QTcpSocket *sock = (QTcpSocket *) gpsdata->gps_fd;
650 sock->write(buf, strlen(buf));
651 if (sock->waitForBytesWritten())
654 qDebug() << "libgps::send error: " << sock->errorString();
660 int gps_stream(struct gps_data_t *gpsdata, unsigned int flags,
662 /* ask gpsd to stream reports at you, hiding the command details */
664 char buf[GPS_JSON_COMMAND_MAX];
666 if ((flags & (WATCH_JSON | WATCH_OLDSTYLE | WATCH_NMEA | WATCH_RAW)) == 0) {
670 if (flags & POLL_NONBLOCK)
671 (void)fcntl(gpsdata->gps_fd, F_SETFL, O_NONBLOCK);
673 if ((flags & WATCH_DISABLE) != 0) {
674 if ((flags & WATCH_OLDSTYLE) != 0) {
675 (void)strlcpy(buf, "w-", sizeof(buf));
676 if (gpsdata->raw_hook != NULL || (flags & WATCH_NMEA) != 0)
677 (void)strlcat(buf, "r-", sizeof(buf));
679 (void)strlcpy(buf, "?WATCH={\"enable\":false,", sizeof(buf));
680 if (flags & WATCH_JSON)
681 (void)strlcat(buf, "\"json\":false,", sizeof(buf));
682 if (flags & WATCH_NMEA)
683 (void)strlcat(buf, "\"nmea\":false,", sizeof(buf));
684 if (flags & WATCH_RAW)
685 (void)strlcat(buf, "\"raw\":1,", sizeof(buf));
686 if (flags & WATCH_RARE)
687 (void)strlcat(buf, "\"raw\":0,", sizeof(buf));
688 if (flags & WATCH_SCALED)
689 (void)strlcat(buf, "\"scaled\":false,", sizeof(buf));
690 if (buf[strlen(buf) - 1] == ',')
691 buf[strlen(buf) - 1] = '\0';
692 (void)strlcat(buf, "};", sizeof(buf));
694 libgps_debug_trace((1, "gps_stream() disable command: %s\n", buf));
695 return gps_send(gpsdata, buf);
696 } else { /* if ((flags & WATCH_ENABLE) != 0) */
698 if ((flags & WATCH_OLDSTYLE) != 0) {
699 (void)strlcpy(buf, "w+x", sizeof(buf));
700 if (gpsdata->raw_hook != NULL || (flags & WATCH_NMEA) != 0)
701 (void)strlcat(buf, "r+", sizeof(buf));
703 (void)strlcpy(buf, "?WATCH={\"enable\":true,", sizeof(buf));
704 if (flags & WATCH_JSON)
705 (void)strlcat(buf, "\"json\":true,", sizeof(buf));
706 if (flags & WATCH_NMEA)
707 (void)strlcat(buf, "\"nmea\":true,", sizeof(buf));
708 if (flags & WATCH_RARE)
709 (void)strlcat(buf, "\"raw\":1,", sizeof(buf));
710 if (flags & WATCH_RAW)
711 (void)strlcat(buf, "\"raw\":2,", sizeof(buf));
712 if (flags & WATCH_SCALED)
713 (void)strlcat(buf, "\"scaled\":true,", sizeof(buf));
714 /*@-nullpass@*//* shouldn't be needed, splint has a bug */
715 if (flags & WATCH_DEVICE)
716 (void)snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
717 "\"device\":%s,", (char *)d);
719 if (buf[strlen(buf) - 1] == ',')
720 buf[strlen(buf) - 1] = '\0';
721 (void)strlcat(buf, "};", sizeof(buf));
723 libgps_debug_trace((1, "gps_stream() enable command: %s\n", buf));
724 return gps_send(gpsdata, buf);
728 extern char /*@observer@*/ *gps_errstr(const int err)
731 * We might add our own error codes in the future, e.g for
732 * protocol compatibility checks
735 return netlib_errstr(err);
743 * A simple command-line exerciser for the library.
744 * Not really useful for anything but debugging.
747 static void dumpline(struct gps_data_t *ud UNUSED,
748 char *buf, size_t ulen UNUSED)
755 #endif /* S_SPLINT_S */
759 static void onsig(int sig)
761 (void)fprintf(stderr, "libgps: died with signal %d\n", sig);
765 /* must start zeroed, otherwise the unit test will try to chase garbage pointer fields. */
766 static struct gps_data_t gpsdata;
768 int main(int argc, char *argv[])
770 struct gps_data_t *collect;
773 bool batchmode = false;
776 (void)signal(SIGSEGV, onsig);
777 (void)signal(SIGBUS, onsig);
779 while ((option = getopt(argc, argv, "bhsD:?")) != -1) {
787 ("Sizes: gpsdata=%zd rtcm2=%zd rtcm3=%zd ais=%zd compass=%zd raw=%zd devices=%zd policy=%zd version=%zd\n",
788 sizeof(struct gps_data_t), sizeof(struct rtcm2_t),
789 sizeof(struct rtcm3_t), sizeof(struct ais_t),
790 sizeof(struct attitude_t), sizeof(struct rawdata_t),
791 sizeof(collect->devices), sizeof(struct policy_t),
792 sizeof(struct version_t));
795 debug = atoi(optarg);
800 (void)fputs("usage: libgps [-b] [-d lvl] [-s]\n", stderr);
805 gps_enable_debug(debug, stdout);
807 while (fgets(buf, sizeof(buf), stdin) != NULL) {
808 if (buf[0] == '{' || isalpha(buf[0])) {
809 gps_unpack(buf, &gpsdata);
810 libgps_dump_state(&gpsdata, time(NULL));
813 } else if ((collect = gps_open(NULL, 0)) == NULL) {
814 (void)fputs("Daemon is not running.\n", stdout);
816 } else if (optind < argc) {
817 gps_set_raw_hook(collect, dumpline);
818 (void)strlcpy(buf, argv[optind], BUFSIZ);
819 (void)strlcat(buf, "\n", BUFSIZ);
820 (void)gps_send(collect, buf);
821 (void)gps_read(collect);
822 libgps_dump_state(collect, time(NULL));
823 (void)gps_close(collect);
827 gps_set_raw_hook(collect, dumpline);
829 (void)fputs("This is the gpsd exerciser.\n", stdout);
832 (void)fputs("> ", stdout);
833 if (fgets(buf, sizeof(buf), stdin) == NULL) {
839 (void)gps_send(collect, buf);
840 (void)gps_read(collect);
841 libgps_dump_state(collect, time(NULL));
843 (void)gps_close(collect);
851 #endif /* TESTMAIN */