You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

gpspipe.c 11KB

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