gpspipe.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. /*
  2. * gpspipe
  3. *
  4. * a simple program to connect to a gpsd daemon and dump the received data
  5. * to stdout
  6. *
  7. * This will dump the raw NMEA from gpsd to stdout
  8. * gpspipe -r
  9. *
  10. * This will dump the super-raw data (gps binary) from gpsd to stdout
  11. * gpspipe -R
  12. *
  13. * This will dump the GPSD sentences from gpsd to stdout
  14. * gpspipe -w
  15. *
  16. * This will dump the GPSD and the NMEA sentences from gpsd to stdout
  17. * gpspipe -wr
  18. *
  19. * Original code by: Gary E. Miller <gem@rellim.com>. Cleanup by ESR.
  20. *
  21. * This file is Copyright 2010 by the GPSD project
  22. * SPDX-License-Identifier: BSD-2-clause
  23. *
  24. */
  25. #include "../include/gpsd_config.h" /* must be before all includes */
  26. #include <errno.h>
  27. #include <fcntl.h>
  28. #ifdef HAVE_GETOPT_LONG
  29. #include <getopt.h> // for getopt_long()
  30. #endif
  31. #include <stdbool.h>
  32. #include <stdio.h>
  33. #include <stdlib.h>
  34. #include <string.h>
  35. #include <strings.h>
  36. #include <sys/select.h>
  37. #include <sys/stat.h>
  38. #include <sys/time.h>
  39. #include <sys/types.h>
  40. #include <time.h> /* for time_t */
  41. #include <unistd.h>
  42. #ifdef HAVE_SYS_SOCKET_H
  43. #include <sys/socket.h>
  44. #endif /* HAVE_SYS_SOCKET_H */
  45. #include <termios.h> /* for speed_t, and cfmakeraw() on some OS */
  46. #ifdef HAVE_WINSOCK2_H
  47. #include <winsock2.h>
  48. #endif /* HAVE_WINSOCK2_H */
  49. #include "../include/gpsd.h" // for os_daemon()
  50. #include "../include/gpsdclient.h"
  51. static struct gps_data_t gpsdata;
  52. static void spinner(unsigned int, unsigned int);
  53. /* NMEA-0183 standard baud rate */
  54. #define BAUDRATE B4800
  55. /* Serial port variables */
  56. static struct termios oldtio, newtio;
  57. static int fd_out = 1; /* output initially goes to standard output */
  58. static char serbuf[255];
  59. static int debug;
  60. /* open the serial port and set it up */
  61. static void open_serial(char *device)
  62. {
  63. /*
  64. * Open modem device for reading and writing and not as controlling
  65. * tty.
  66. */
  67. if ((fd_out = open(device, O_RDWR | O_NOCTTY)) == -1) {
  68. (void)fprintf(stderr, "gpspipe: error opening serial port\n");
  69. exit(EXIT_FAILURE);
  70. }
  71. /* Save current serial port settings for later */
  72. if (tcgetattr(fd_out, &oldtio) != 0) {
  73. (void)fprintf(stderr, "gpspipe: error reading serial port settings\n");
  74. exit(EXIT_FAILURE);
  75. }
  76. /* Clear struct for new port settings. */
  77. memset(&newtio, 0, sizeof(newtio));
  78. /* make it raw */
  79. (void)cfmakeraw(&newtio);
  80. /* set speed */
  81. (void)cfsetospeed(&newtio, BAUDRATE);
  82. /* Clear the modem line and activate the settings for the port. */
  83. (void)tcflush(fd_out, TCIFLUSH);
  84. if (tcsetattr(fd_out, TCSANOW, &newtio) != 0) {
  85. (void)fprintf(stderr, "gpspipe: error configuring serial port\n");
  86. exit(EXIT_FAILURE);
  87. }
  88. }
  89. static void usage(void)
  90. {
  91. (void)fprintf(stderr,
  92. "Usage: gpspipe [OPTIONS] [server[:port[:device]]]\n\n"
  93. #ifdef HAVE_GETOPT_LONG
  94. " --count COUNT Exit after COUNT packets.\n"
  95. " --daemonize Run as daemon.\n"
  96. " --help Show this help and exit.\n"
  97. " --json Dump gpsd native JSON data.\n"
  98. " --nmea Dump (psuedo) NMEA.\n"
  99. " --output FILE Write output to FILE.\n"
  100. " --pps Include PPS JSON in NMEA or raw mode.\n"
  101. " --profile Include profiling info in the JSON.\n"
  102. " --raw Dump super-raw mode, GPS binary and "
  103. "NMEA.\n"
  104. " --scaled Set scaled flag. For AIS and subframe "
  105. "data.\n"
  106. " --seconds Exit after SECONDS delay.\n"
  107. " --serial DEV Emulate a 4800bps NMEA GPS on "
  108. "DEV (use with '-r').\n"
  109. " --sleep Sleep for ten seconds before "
  110. "connecting to gpsd.\n"
  111. " --spinner Print a little spinner.\n"
  112. " --split24 Set the split24 flag.\n"
  113. " --timefmt Set the timestamp format "
  114. "(strftime(3)-like; implies '-t').\n"
  115. " --timestamp Time stamp the data.\n"
  116. " --usec Time stamp in usec, implies -t. "
  117. "Use twice for sec.usec\n"
  118. " --version Print version and exit.\n"
  119. " --zulu Set the timestamp format to iso8601, "
  120. "implies '-t'\n"
  121. #endif
  122. " -2 Set the split24 flag.\n"
  123. " -d Run as a daemon.\n"
  124. " -h Show this help and exit.\n"
  125. " -l Sleep for ten seconds before "
  126. "connecting to gpsd.\n"
  127. " -n COUNT Exit after count packets.\n"
  128. " -o FILE Write output to FILE.\n"
  129. " -P Include PPS JSON in NMEA or raw mode.\n"
  130. " -p Include profiling info in the JSON.\n"
  131. " -r Dump (pseudo) NMEA.\n"
  132. " -R Dump super-raw mode, GPS binary and "
  133. "NMEA.\n"
  134. " -s DEV Emulate a 4800bps NMEA GPS on "
  135. "DEV (use with '-r').\n"
  136. " -S Set scaled flag. For AIS and subframe "
  137. "data.\n"
  138. " -T FORMAT Set the timestamp format "
  139. "(strftime(3)-like; implies '-t')\n"
  140. " -t Time stamp the data.\n"
  141. " -u Time stamp in usec, implies -t. "
  142. "Use -uu to output sec.usec\n"
  143. " -v Print a little spinner.\n"
  144. " -V Print version and exit.\n"
  145. " -w Dump gpsd native JSON data.\n"
  146. " -x SECONDS Exit after SECONDS delay.\n"
  147. " -Z Set the timestamp format to iso8601, "
  148. "implies '-t'\n\n"
  149. "You must specify one, or more, of: "
  150. "--json, --nmea, --raw, -r, -R, or -w\n"
  151. "You must use -o if you use -d.\n");
  152. }
  153. int main(int argc, char **argv)
  154. {
  155. char buf[4096];
  156. bool timestamp = false;
  157. bool iso8601 = false;
  158. char *format = "%F %T";
  159. char *zulu_format = "%FT%T";
  160. char tmstr[200];
  161. bool daemonize = false;
  162. bool binary = false;
  163. bool sleepy = false;
  164. bool new_line = true;
  165. bool raw = false;
  166. bool watch = false;
  167. bool profile = false;
  168. int option_u = 0; // option to show uSeconds
  169. long count = -1;
  170. time_t exit_timer = 0;
  171. int ch;
  172. unsigned int vflag = 0, l = 0;
  173. FILE *fp;
  174. unsigned int flags;
  175. fd_set fds;
  176. struct fixsource_t source;
  177. char *serialport = NULL;
  178. char *outfile = NULL;
  179. const char *optstring = "2?dD:hln:o:pPrRwSs:tT:uvVx:Z";
  180. #ifdef HAVE_GETOPT_LONG
  181. int option_index = 0;
  182. static struct option long_options[] = {
  183. {"count", required_argument, NULL, 'n'},
  184. {"daemonize", no_argument, NULL, 'd'},
  185. {"help", no_argument, NULL, 'h'},
  186. {"json", no_argument, NULL, 'w'},
  187. {"nmea", no_argument, NULL, 'r' },
  188. {"output", required_argument, NULL, 'o'},
  189. {"pps", no_argument, NULL, 'P' },
  190. {"profile", no_argument, NULL, 'P' },
  191. {"scaled", no_argument, NULL, 'S' },
  192. {"seconds", required_argument, NULL, 'x'},
  193. {"serial", no_argument, NULL, 'r' },
  194. {"sleep", required_argument, NULL, 'i'},
  195. {"spinner", no_argument, NULL, 'v' },
  196. {"split24", no_argument, NULL, '2'},
  197. {"raw", no_argument, NULL, 'R' },
  198. {"timefmt", required_argument, NULL, 'T'},
  199. {"timestamp", required_argument, NULL, 't'},
  200. {"usec", no_argument, NULL, 'u'},
  201. {"version", no_argument, NULL, 'V' },
  202. {"zulu", no_argument, NULL, 'Z'},
  203. {NULL, 0, NULL, 0},
  204. };
  205. #endif
  206. flags = WATCH_ENABLE;
  207. while (1) {
  208. #ifdef HAVE_GETOPT_LONG
  209. ch = getopt_long(argc, argv, optstring, long_options, &option_index);
  210. #else
  211. ch = getopt(argc, argv, optstring);
  212. #endif
  213. if (ch == -1) {
  214. break;
  215. }
  216. switch (ch) {
  217. case '2':
  218. flags |= WATCH_SPLIT24;
  219. break;
  220. case 'D':
  221. debug = atoi(optarg);
  222. gps_enable_debug(debug, stderr);
  223. break;
  224. case 'd':
  225. daemonize = true;
  226. break;
  227. case 'l':
  228. sleepy = true;
  229. break;
  230. case 'n':
  231. count = strtol(optarg, 0, 0);
  232. break;
  233. case 'o':
  234. outfile = optarg;
  235. break;
  236. case 'P':
  237. flags |= WATCH_PPS;
  238. break;
  239. case 'p':
  240. profile = true;
  241. break;
  242. case 'R':
  243. flags |= WATCH_RAW;
  244. binary = true;
  245. break;
  246. case 'r':
  247. raw = true;
  248. /*
  249. * Yes, -r invokes NMEA mode rather than proper raw mode.
  250. * This emulates the behavior under the old protocol.
  251. */
  252. flags |= WATCH_NMEA;
  253. break;
  254. case 'S':
  255. flags |= WATCH_SCALED;
  256. break;
  257. case 's':
  258. serialport = optarg;
  259. break;
  260. case 'T':
  261. timestamp = true;
  262. format = optarg;
  263. break;
  264. case 't':
  265. timestamp = true;
  266. break;
  267. case 'u':
  268. timestamp = true;
  269. option_u++;
  270. break;
  271. case 'V':
  272. (void)fprintf(stderr, "%s: %s (revision %s)\n",
  273. argv[0], VERSION, REVISION);
  274. exit(EXIT_SUCCESS);
  275. case 'v':
  276. vflag++;
  277. break;
  278. case 'w':
  279. flags |= WATCH_JSON;
  280. watch = true;
  281. break;
  282. case 'x':
  283. exit_timer = time(NULL) + strtol(optarg, 0, 0);
  284. break;
  285. case 'Z':
  286. timestamp = true;
  287. format = zulu_format;
  288. iso8601 = true;
  289. break;
  290. case '?':
  291. case 'h':
  292. usage();
  293. exit(0);
  294. default:
  295. usage();
  296. exit(EXIT_FAILURE);
  297. }
  298. }
  299. /* Grok the server, port, and device. */
  300. if (optind < argc) {
  301. gpsd_source_spec(argv[optind], &source);
  302. } else
  303. gpsd_source_spec(NULL, &source);
  304. if (serialport != NULL && !raw) {
  305. (void)fprintf(stderr, "gpspipe: use of '-s' requires '-r'.\n");
  306. exit(EXIT_FAILURE);
  307. }
  308. if (outfile == NULL && daemonize) {
  309. (void)fprintf(stderr, "gpspipe: use of '-d' requires '-o'.\n");
  310. exit(EXIT_FAILURE);
  311. }
  312. if (!raw && !watch && !binary) {
  313. (void)fprintf(stderr,
  314. "gpspipe: one of '-R', '-r', or '-w' is required.\n");
  315. exit(EXIT_FAILURE);
  316. }
  317. /* Daemonize if the user requested it. */
  318. if (daemonize)
  319. if (os_daemon(0, 0) != 0)
  320. (void)fprintf(stderr,
  321. "gpspipe: daemonization failed: %s\n",
  322. strerror(errno));
  323. /* Sleep for ten seconds if the user requested it. */
  324. if (sleepy)
  325. (void)sleep(10);
  326. /* Open the output file if the user requested it. If the user
  327. * requested '-R', we use the 'b' flag in fopen() to "do the right
  328. * thing" in non-linux/unix OSes. */
  329. if (outfile == NULL) {
  330. fp = stdout;
  331. } else {
  332. if (binary)
  333. fp = fopen(outfile, "wb");
  334. else
  335. fp = fopen(outfile, "w");
  336. if (fp == NULL) {
  337. (void)fprintf(stderr,
  338. "gpspipe: unable to open output file: %s\n",
  339. outfile);
  340. exit(EXIT_FAILURE);
  341. }
  342. }
  343. /* Open the serial port and set it up. */
  344. if (serialport)
  345. open_serial(serialport);
  346. if (gps_open(source.server, source.port, &gpsdata) != 0) {
  347. (void)fprintf(stderr,
  348. "gpspipe: could not connect to gpsd %s:%s, %s(%d)\n",
  349. source.server, source.port, gps_errstr(errno), errno);
  350. exit(EXIT_FAILURE);
  351. }
  352. if (profile)
  353. flags |= WATCH_TIMING;
  354. if (source.device != NULL)
  355. flags |= WATCH_DEVICE;
  356. (void)gps_stream(&gpsdata, flags, source.device);
  357. if ((isatty(STDERR_FILENO) == 0) || daemonize)
  358. vflag = 0;
  359. for (;;) {
  360. int r = 0;
  361. struct timespec tv;
  362. tv.tv_sec = 0;
  363. tv.tv_nsec = 100000000;
  364. FD_ZERO(&fds);
  365. FD_SET(gpsdata.gps_fd, &fds);
  366. errno = 0;
  367. r = pselect(gpsdata.gps_fd+1, &fds, NULL, NULL, &tv, NULL);
  368. if (r >= 0 && exit_timer && time(NULL) >= exit_timer)
  369. break;
  370. if (r == -1 && errno != EINTR) {
  371. (void)fprintf(stderr, "gpspipe: select error %s(%d)\n",
  372. strerror(errno), errno);
  373. exit(EXIT_FAILURE);
  374. } else if (r == 0)
  375. continue;
  376. if (vflag)
  377. spinner(vflag, l++);
  378. /* reading directly from the socket avoids decode overhead */
  379. errno = 0;
  380. r = (int)recv(gpsdata.gps_fd, buf, sizeof(buf), 0);
  381. if (r > 0) {
  382. int i = 0;
  383. int j = 0;
  384. for (i = 0; i < r; i++) {
  385. char c = buf[i];
  386. if (j < (int)(sizeof(serbuf) - 1)) {
  387. serbuf[j++] = buf[i];
  388. }
  389. if (new_line && timestamp) {
  390. char tmstr_u[40]; // time with "usec" resolution
  391. struct timespec now;
  392. struct tm tmp_now;
  393. int written;
  394. (void)clock_gettime(CLOCK_REALTIME, &now);
  395. (void)gmtime_r((time_t *)&(now.tv_sec), &tmp_now);
  396. (void)strftime(tmstr, sizeof(tmstr), format, &tmp_now);
  397. new_line = 0;
  398. switch( option_u ) {
  399. case 2:
  400. if(iso8601){
  401. written = strlen(tmstr);
  402. tmstr[written] = 'Z';
  403. tmstr[written+1] = '\0';
  404. }
  405. (void)snprintf(tmstr_u, sizeof(tmstr_u),
  406. " %lld.%06ld",
  407. (long long)now.tv_sec,
  408. (long)now.tv_nsec/1000);
  409. break;
  410. case 1:
  411. written = snprintf(tmstr_u, sizeof(tmstr_u),
  412. ".%06ld", (long)now.tv_nsec/1000);
  413. if((0 < written) && (40 > written) && iso8601){
  414. tmstr_u[written-1] = 'Z';
  415. tmstr_u[written] = '\0';
  416. }
  417. break;
  418. default:
  419. *tmstr_u = '\0';
  420. break;
  421. }
  422. if (fprintf(fp, "%.24s%s: ", tmstr, tmstr_u) <= 0) {
  423. (void)fprintf(stderr,
  424. "gpspipe: write error, %s(%d)\n",
  425. strerror(errno), errno);
  426. exit(EXIT_FAILURE);
  427. }
  428. }
  429. if (fputc(c, fp) == EOF) {
  430. (void)fprintf(stderr, "gpspipe: write error, %s(%d)\n",
  431. strerror(errno), errno);
  432. exit(EXIT_FAILURE);
  433. }
  434. if (c == '\n') {
  435. if (serialport != NULL) {
  436. if (write(fd_out, serbuf, (size_t) j) == -1) {
  437. (void)fprintf(stderr,
  438. "gpspipe: serial port write error,"
  439. " %s(%d)\n",
  440. strerror(errno), errno);
  441. exit(EXIT_FAILURE);
  442. }
  443. j = 0;
  444. }
  445. new_line = true;
  446. /* flush after every good line */
  447. if (fflush(fp)) {
  448. (void)fprintf(stderr,
  449. "gpspipe: fflush error, %s(%d)\n",
  450. strerror(errno), errno);
  451. exit(EXIT_FAILURE);
  452. }
  453. if (count > 0) {
  454. if (0 >= --count) {
  455. /* completed count */
  456. exit(EXIT_SUCCESS);
  457. }
  458. }
  459. }
  460. }
  461. } else {
  462. if (r == -1) {
  463. if (errno == EAGAIN)
  464. continue;
  465. else
  466. (void)fprintf(stderr, "gpspipe: read error %s(%d)\n",
  467. strerror(errno), errno);
  468. exit(EXIT_FAILURE);
  469. } else {
  470. exit(EXIT_SUCCESS);
  471. }
  472. }
  473. }
  474. #ifdef __UNUSED__
  475. if (serialport != NULL) {
  476. /* Restore the old serial port settings. */
  477. if (tcsetattr(fd_out, TCSANOW, &oldtio) != 0) {
  478. (void)fprintf(stderr,
  479. "gpsipe: error restoring serial port settings\n");
  480. exit(EXIT_FAILURE);
  481. }
  482. }
  483. #endif /* __UNUSED__ */
  484. exit(EXIT_SUCCESS);
  485. }
  486. static void spinner(unsigned int v, unsigned int num)
  487. {
  488. char *spin = "|/-\\";
  489. (void)fprintf(stderr, "\010%c", spin[(num / (1 << (v - 1))) % 4]);
  490. (void)fflush(stderr);
  491. return;
  492. }
  493. // vim: set expandtab shiftwidth=4