gpspipe.c 14 KB

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