gpspipe.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  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 = STDOUT_FILENO; /* 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. " --debug LVL Set debug level to LVL.\n"
  97. " --help Show this help and exit.\n"
  98. " --json Dump gpsd native JSON data.\n"
  99. " --nmea Dump (pseudo) NMEA.\n"
  100. " --output FILE Write output to FILE.\n"
  101. " --pps Include PPS JSON in NMEA or raw mode.\n"
  102. " --profile Include profiling info in the JSON.\n"
  103. " --raw Dump super-raw mode, GPS binary and "
  104. "NMEA.\n"
  105. " --scaled Set scaled flag. For AIS and subframe "
  106. "data.\n"
  107. " --seconds SEC Exit after SEC seconds delay.\n"
  108. " --serial DEV Emulate a 4800bps NMEA GPS on "
  109. "DEV (use with '-r').\n"
  110. " --sleep Sleep for ten seconds before "
  111. "connecting to gpsd.\n"
  112. " --spinner Print a little spinner.\n"
  113. " --split24 Set the split24 flag.\n"
  114. " --timefmt FORMAT Set the timestamp format "
  115. "(strftime(3)-like; implies '-t').\n"
  116. " --timestamp Time stamp the data.\n"
  117. " --usec Time stamp in usec, implies '-t'. "
  118. "Use twice for sec.usec.\n"
  119. " --version Print version and exit.\n"
  120. " --zulu Set the timestamp format to iso8601, "
  121. "implies '-t'\n"
  122. #endif
  123. " -2 Set the split24 flag.\n"
  124. " -d Run as a daemon.\n"
  125. " -D LVL Set debug level to LVL.\n"
  126. " -h Show this help and exit.\n"
  127. " -l Sleep for ten seconds before "
  128. "connecting to gpsd.\n"
  129. " -n COUNT Exit after count packets.\n"
  130. " -o FILE Write output to FILE.\n"
  131. " -P Include PPS JSON in NMEA or raw mode.\n"
  132. " -p Include profiling info in the JSON.\n"
  133. " -r Dump (pseudo) NMEA.\n"
  134. " -R Dump super-raw mode, GPS binary and "
  135. "NMEA.\n"
  136. " -s DEV Emulate a 4800bps NMEA GPS on "
  137. "DEV (use with '-r').\n"
  138. " -S Set scaled flag. For AIS and subframe "
  139. "data.\n"
  140. " -T FORMAT Set the timestamp format "
  141. "(strftime(3)-like; implies '-t')\n"
  142. " -t Time stamp the data.\n"
  143. " -u Time stamp in usec, implies '-t'. "
  144. "Use -uu to output sec.usec.\n"
  145. " -v Print a little spinner.\n"
  146. " -V Print version and exit.\n"
  147. " -w Dump gpsd native JSON data.\n"
  148. " -x SEC Exit after SEC seconds delay.\n"
  149. " -Z Set the timestamp format to iso8601, "
  150. "implies '-t'.\n\n"
  151. "You must specify one, or more, of: "
  152. "--json, --nmea, --raw, -r, -R, or -w\n"
  153. "You must use -o if you use -d.\n");
  154. }
  155. int main(int argc, char **argv)
  156. {
  157. char buf[4096];
  158. bool timestamp = false;
  159. bool iso8601 = false;
  160. char *format = "%F %T";
  161. char *zulu_format = "%FT%T";
  162. char tmstr[200];
  163. bool daemonize = false;
  164. bool binary = false;
  165. bool sleepy = false;
  166. bool new_line = true;
  167. bool raw = false;
  168. bool watch = false;
  169. int option_u = 0; // option to show uSeconds
  170. long count = -1;
  171. time_t exit_timer = 0;
  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. {"debug", required_argument, NULL, 'D'},
  186. {"help", no_argument, NULL, 'h'},
  187. {"json", no_argument, NULL, 'w'},
  188. {"nmea", no_argument, NULL, 'r' },
  189. {"output", required_argument, NULL, 'o'},
  190. {"pps", no_argument, NULL, 'P' },
  191. {"profile", no_argument, NULL, 'p' },
  192. {"scaled", no_argument, NULL, 'S' },
  193. {"seconds", required_argument, NULL, 'x'},
  194. {"serial", no_argument, NULL, 'r' },
  195. {"sleep", required_argument, NULL, 'i'},
  196. {"spinner", no_argument, NULL, 'v' },
  197. {"split24", no_argument, NULL, '2'},
  198. {"raw", no_argument, NULL, 'R' },
  199. {"timefmt", required_argument, NULL, 'T'},
  200. {"timestamp", required_argument, NULL, 't'},
  201. {"usec", no_argument, NULL, 'u'},
  202. {"version", no_argument, NULL, 'V' },
  203. {"zulu", no_argument, NULL, 'Z'},
  204. {NULL, 0, NULL, 0},
  205. };
  206. #endif
  207. flags = WATCH_ENABLE;
  208. while (1) {
  209. int ch;
  210. #ifdef HAVE_GETOPT_LONG
  211. ch = getopt_long(argc, argv, optstring, long_options, &option_index);
  212. #else
  213. ch = getopt(argc, argv, optstring);
  214. #endif
  215. if (ch == -1) {
  216. break;
  217. }
  218. switch (ch) {
  219. case '2':
  220. flags |= WATCH_SPLIT24;
  221. break;
  222. case 'D':
  223. debug = atoi(optarg);
  224. gps_enable_debug(debug, stderr);
  225. break;
  226. case 'd':
  227. daemonize = true;
  228. break;
  229. case 'l':
  230. sleepy = true;
  231. break;
  232. case 'n':
  233. count = strtol(optarg, 0, 0);
  234. break;
  235. case 'o':
  236. outfile = optarg;
  237. break;
  238. case 'P':
  239. flags |= WATCH_PPS;
  240. break;
  241. case 'p':
  242. flags |= WATCH_TIMING;
  243. break;
  244. case 'R':
  245. flags |= WATCH_RAW;
  246. binary = true;
  247. break;
  248. case 'r':
  249. raw = true;
  250. /*
  251. * Yes, -r invokes NMEA mode rather than proper raw mode.
  252. * This emulates the behavior under the old protocol.
  253. */
  254. flags |= WATCH_NMEA;
  255. break;
  256. case 'S':
  257. flags |= WATCH_SCALED;
  258. break;
  259. case 's':
  260. serialport = optarg;
  261. break;
  262. case 'T':
  263. timestamp = true;
  264. format = optarg;
  265. break;
  266. case 't':
  267. timestamp = true;
  268. break;
  269. case 'u':
  270. timestamp = true;
  271. option_u++;
  272. break;
  273. case 'V':
  274. (void)fprintf(stderr, "%s: %s (revision %s)\n",
  275. argv[0], VERSION, REVISION);
  276. exit(EXIT_SUCCESS);
  277. case 'v':
  278. vflag++;
  279. break;
  280. case 'w':
  281. flags |= WATCH_JSON;
  282. watch = true;
  283. break;
  284. case 'x':
  285. exit_timer = time(NULL) + strtol(optarg, 0, 0);
  286. break;
  287. case 'Z':
  288. timestamp = true;
  289. format = zulu_format;
  290. iso8601 = true;
  291. break;
  292. case '?':
  293. case 'h':
  294. usage();
  295. exit(EXIT_SUCCESS);
  296. default:
  297. usage();
  298. exit(EXIT_FAILURE);
  299. }
  300. }
  301. /* Grok the server, port, and device. */
  302. if (optind < argc) {
  303. gpsd_source_spec(argv[optind], &source);
  304. } else {
  305. gpsd_source_spec(NULL, &source);
  306. }
  307. if (serialport != NULL && !raw) {
  308. (void)fprintf(stderr, "gpspipe: use of '-s' requires '-r'.\n");
  309. exit(EXIT_FAILURE);
  310. }
  311. if (outfile == NULL && daemonize) {
  312. (void)fprintf(stderr, "gpspipe: use of '-d' requires '-o'.\n");
  313. exit(EXIT_FAILURE);
  314. }
  315. if (!raw && !watch && !binary) {
  316. (void)fprintf(stderr,
  317. "gpspipe: one of '-R', '-r', or '-w' is required.\n");
  318. exit(EXIT_FAILURE);
  319. }
  320. /* Daemonize if the user requested it. */
  321. if (daemonize) {
  322. if (os_daemon(0, 0) != 0) {
  323. (void)fprintf(stderr,
  324. "gpspipe: daemonization failed: %s\n",
  325. strerror(errno));
  326. }
  327. }
  328. /* Sleep for ten seconds if the user requested it. */
  329. if (sleepy) {
  330. (void)sleep(10);
  331. }
  332. /* Open the output file if the user requested it. If the user
  333. * requested '-R', we use the 'b' flag in fopen() to "do the right
  334. * thing" in non-linux/unix OSes. */
  335. if (outfile == NULL) {
  336. fp = stdout;
  337. } else {
  338. fp = fopen(outfile, binary ? "wb" : "w");
  339. if (fp == NULL) {
  340. (void)fprintf(stderr,
  341. "gpspipe: unable to open output file: %s\n",
  342. outfile);
  343. exit(EXIT_FAILURE);
  344. }
  345. }
  346. /* Open the serial port and set it up. */
  347. if (serialport) {
  348. open_serial(serialport);
  349. }
  350. if (gps_open(source.server, source.port, &gpsdata) != 0) {
  351. (void)fprintf(stderr,
  352. "gpspipe: could not connect to gpsd %s:%s, %s(%d)\n",
  353. source.server, source.port, gps_errstr(errno), errno);
  354. exit(EXIT_FAILURE);
  355. }
  356. if (source.device != NULL) {
  357. flags |= WATCH_DEVICE;
  358. }
  359. (void)gps_stream(&gpsdata, flags, source.device);
  360. if ((isatty(STDERR_FILENO) == 0) || daemonize) {
  361. vflag = 0;
  362. }
  363. for (;;) {
  364. int r = 0;
  365. struct timespec tv;
  366. tv.tv_sec = 0;
  367. tv.tv_nsec = 100000000;
  368. FD_ZERO(&fds);
  369. FD_SET(gpsdata.gps_fd, &fds);
  370. errno = 0;
  371. r = pselect(gpsdata.gps_fd+1, &fds, NULL, NULL, &tv, NULL);
  372. if (r >= 0 && exit_timer && time(NULL) >= exit_timer) {
  373. break;
  374. }
  375. if (r == -1 && errno != EINTR) {
  376. (void)fprintf(stderr, "gpspipe: select error %s(%d)\n",
  377. strerror(errno), errno);
  378. exit(EXIT_FAILURE);
  379. } else if (r == 0) {
  380. continue;
  381. }
  382. if (vflag) {
  383. spinner(vflag, l++);
  384. }
  385. /* reading directly from the socket avoids decode overhead */
  386. errno = 0;
  387. r = (int)recv(gpsdata.gps_fd, buf, sizeof(buf), 0);
  388. if (r > 0) {
  389. int i = 0;
  390. int j = 0;
  391. for (i = 0; i < r; i++) {
  392. char c = buf[i];
  393. if (j < (int)(sizeof(serbuf) - 1)) {
  394. serbuf[j++] = buf[i];
  395. }
  396. if (new_line && timestamp) {
  397. char tmstr_u[40]; // time with "usec" resolution
  398. struct timespec now;
  399. struct tm tmp_now;
  400. int written;
  401. (void)clock_gettime(CLOCK_REALTIME, &now);
  402. (void)gmtime_r((time_t *)&(now.tv_sec), &tmp_now);
  403. (void)strftime(tmstr, sizeof(tmstr), format, &tmp_now);
  404. new_line = false;
  405. switch (option_u) {
  406. case 2:
  407. if (iso8601) {
  408. // codacy does not like strlen()
  409. written = strnlen(tmstr, sizeof(tmstr));
  410. tmstr[written] = 'Z';
  411. tmstr[written+1] = '\0';
  412. }
  413. (void)snprintf(tmstr_u, sizeof(tmstr_u),
  414. " %lld.%06ld",
  415. (long long)now.tv_sec,
  416. (long)now.tv_nsec/1000);
  417. break;
  418. case 1:
  419. written = snprintf(tmstr_u, sizeof(tmstr_u),
  420. ".%06ld", (long)now.tv_nsec/1000);
  421. if ((0 < written) && (40 > written) && iso8601) {
  422. tmstr_u[written-1] = 'Z';
  423. tmstr_u[written] = '\0';
  424. }
  425. break;
  426. default:
  427. *tmstr_u = '\0';
  428. }
  429. if (fprintf(fp, "%.24s%s: ", tmstr, tmstr_u) <= 0) {
  430. (void)fprintf(stderr,
  431. "gpspipe: write error, %s(%d)\n",
  432. strerror(errno), errno);
  433. exit(EXIT_FAILURE);
  434. }
  435. }
  436. if (fputc(c, fp) == EOF) {
  437. (void)fprintf(stderr, "gpspipe: write error, %s(%d)\n",
  438. strerror(errno), errno);
  439. exit(EXIT_FAILURE);
  440. }
  441. if (c == '\n') {
  442. if (serialport != NULL) {
  443. if (write(fd_out, serbuf, (size_t) j) == -1) {
  444. (void)fprintf(stderr,
  445. "gpspipe: serial port write error,"
  446. " %s(%d)\n",
  447. strerror(errno), errno);
  448. exit(EXIT_FAILURE);
  449. }
  450. j = 0;
  451. }
  452. new_line = true;
  453. /* flush after every good line */
  454. if (fflush(fp)) {
  455. (void)fprintf(stderr,
  456. "gpspipe: fflush error, %s(%d)\n",
  457. strerror(errno), errno);
  458. exit(EXIT_FAILURE);
  459. }
  460. if (count > 0) {
  461. if (0 >= --count) {
  462. /* completed count */
  463. exit(EXIT_SUCCESS);
  464. }
  465. }
  466. }
  467. }
  468. } else {
  469. if (r == -1) {
  470. if (errno == EAGAIN) {
  471. continue;
  472. } else {
  473. (void)fprintf(stderr, "gpspipe: read error %s(%d)\n",
  474. strerror(errno), errno);
  475. }
  476. exit(EXIT_FAILURE);
  477. } else {
  478. exit(EXIT_SUCCESS);
  479. }
  480. }
  481. }
  482. #ifdef __UNUSED__
  483. if (serialport != NULL) {
  484. /* Restore the old serial port settings. */
  485. if (tcsetattr(fd_out, TCSANOW, &oldtio) != 0) {
  486. (void)fprintf(stderr,
  487. "gpsipe: error restoring serial port settings\n");
  488. exit(EXIT_FAILURE);
  489. }
  490. }
  491. #endif /* __UNUSED__ */
  492. exit(EXIT_SUCCESS);
  493. }
  494. static void spinner(unsigned int v, unsigned int num)
  495. {
  496. char *spin = "|/-\\";
  497. (void)fprintf(stderr, "\010%c", spin[(num / (1 << (v - 1))) % 4]);
  498. (void)fflush(stderr);
  499. }
  500. // vim: set expandtab shiftwidth=4