gpxlogger.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. /*
  2. * This file is Copyright (c) 2010-2018 by the GPSD project
  3. * SPDX-License-Identifier: BSD-2-clause
  4. */
  5. #include "gpsd_config.h" /* must be before all includes */
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include <stdbool.h>
  9. #include <string.h>
  10. #include <math.h>
  11. #include <time.h>
  12. #include <errno.h>
  13. #include <libgen.h>
  14. #include <signal.h>
  15. #include <assert.h>
  16. #include <unistd.h>
  17. #include "gps.h"
  18. #include "gpsdclient.h"
  19. #include "revision.h"
  20. #include "os_compat.h"
  21. #include "timespec.h"
  22. static char *progname;
  23. static struct fixsource_t source;
  24. /**************************************************************************
  25. *
  26. * Transport-layer-independent functions
  27. *
  28. **************************************************************************/
  29. static struct gps_data_t gpsdata;
  30. static FILE *logfile;
  31. static bool intrack = false;
  32. static time_t timeout = 5; /* seconds */
  33. static double minmove = 0; /* meters */
  34. #ifdef CLIENTDEBUG_ENABLE
  35. static int debug;
  36. #endif /* CLIENTDEBUG_ENABLE */
  37. static void print_gpx_header(void)
  38. {
  39. char tbuf[CLIENT_DATE_MAX+1];
  40. (void)fprintf(logfile, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
  41. (void)fprintf(logfile, "<gpx version=\"1.1\" creator=\"GPSD %s - %s\"\n",
  42. VERSION, GPSD_URL);
  43. (void)fprintf(logfile,
  44. " xmlns:xsi=\"https://www.w3.org/2001/XMLSchema-instance\"\n");
  45. (void)fprintf(logfile,
  46. " xmlns=\"http://www.topografix.com/GPX/1/1\"\n");
  47. (void)fprintf(logfile
  48. ," xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1\n");
  49. (void)fprintf(logfile
  50. ," http://www.topografix.com/GPX/1/1/gpx.xsd\">\n");
  51. (void)fprintf(logfile, " <metadata>\n");
  52. (void)fprintf(logfile, " <time>%s</time>\n",
  53. now_to_iso8601(tbuf, sizeof(tbuf)));
  54. (void)fprintf(logfile," </metadata>\n");
  55. (void)fflush(logfile);
  56. }
  57. static void print_gpx_trk_end(void)
  58. {
  59. (void)fprintf(logfile," </trkseg>\n");
  60. (void)fprintf(logfile," </trk>\n");
  61. (void)fflush(logfile);
  62. }
  63. static void print_gpx_footer(void)
  64. {
  65. if (intrack)
  66. print_gpx_trk_end();
  67. (void)fprintf(logfile,"</gpx>\n");
  68. (void)fclose(logfile);
  69. }
  70. static void print_gpx_trk_start(void)
  71. {
  72. (void)fprintf(logfile," <trk>\n");
  73. (void)fprintf(logfile," <src>GPSD %s</src>\n", VERSION);
  74. (void)fprintf(logfile," <trkseg>\n");
  75. (void)fflush(logfile);
  76. }
  77. static void print_fix(struct gps_data_t *gpsdata, timespec_t ts_time)
  78. {
  79. char tbuf[CLIENT_DATE_MAX+1];
  80. /* lat/lon/ele are all WGS84, no altMSL */
  81. (void)fprintf(logfile," <trkpt lat=\"%f\" lon=\"%f\">\n",
  82. gpsdata->fix.latitude, gpsdata->fix.longitude);
  83. if ((isfinite(gpsdata->fix.altHAE) != 0))
  84. (void)fprintf(logfile," <ele>%f</ele>\n", gpsdata->fix.altHAE);
  85. (void)fprintf(logfile," <time>%s</time>\n",
  86. timespec_to_iso8601(ts_time, tbuf, sizeof(tbuf)));
  87. if (gpsdata->status == STATUS_DGPS_FIX)
  88. (void)fprintf(logfile," <fix>dgps</fix>\n");
  89. else
  90. switch (gpsdata->fix.mode) {
  91. case MODE_3D:
  92. (void)fprintf(logfile," <fix>3d</fix>\n");
  93. break;
  94. case MODE_2D:
  95. (void)fprintf(logfile," <fix>2d</fix>\n");
  96. break;
  97. case MODE_NO_FIX:
  98. (void)fprintf(logfile," <fix>none</fix>\n");
  99. break;
  100. default:
  101. /* don't print anything if no fix indicator */
  102. break;
  103. }
  104. if ((gpsdata->fix.mode > MODE_NO_FIX) && (gpsdata->satellites_used > 0))
  105. (void)fprintf(logfile," <sat>%d</sat>\n", gpsdata->satellites_used);
  106. if (isfinite(gpsdata->dop.hdop) != 0)
  107. (void)fprintf(logfile," <hdop>%.1f</hdop>\n", gpsdata->dop.hdop);
  108. if (isfinite(gpsdata->dop.vdop) != 0)
  109. (void)fprintf(logfile," <vdop>%.1f</vdop>\n", gpsdata->dop.vdop);
  110. if (isfinite(gpsdata->dop.pdop) != 0)
  111. (void)fprintf(logfile," <pdop>%.1f</pdop>\n", gpsdata->dop.pdop);
  112. (void)fprintf(logfile," </trkpt>\n");
  113. (void)fflush(logfile);
  114. }
  115. static void conditionally_log_fix(struct gps_data_t *gpsdata)
  116. {
  117. static timespec_t ts_time, old_ts_time, ts_diff;
  118. static double old_lat, old_lon;
  119. static bool first = true;
  120. ts_time = gpsdata->fix.time;
  121. if (TS_EQ(&ts_time, &old_ts_time) || gpsdata->fix.mode < MODE_2D)
  122. return;
  123. /* may not be worth logging if we've moved only a very short distance */
  124. if (0 < minmove && !first && earth_distance(
  125. gpsdata->fix.latitude,
  126. gpsdata->fix.longitude,
  127. old_lat, old_lon) < minmove)
  128. return;
  129. /*
  130. * Make new track if the jump in time is above
  131. * timeout. Handle jumps both forward and
  132. * backwards in time. The clock sometimes jumps
  133. * backward when gpsd is submitting junk on the
  134. * dbus.
  135. */
  136. TS_SUB(&ts_diff, &ts_time, &old_ts_time);
  137. if (labs((long)ts_diff.tv_sec) > timeout && !first) {
  138. print_gpx_trk_end();
  139. intrack = false;
  140. }
  141. if (!intrack) {
  142. print_gpx_trk_start();
  143. intrack = true;
  144. if (first)
  145. first = false;
  146. }
  147. old_ts_time = ts_time;
  148. if (0 < minmove) {
  149. old_lat = gpsdata->fix.latitude;
  150. old_lon = gpsdata->fix.longitude;
  151. }
  152. print_fix(gpsdata, ts_time);
  153. }
  154. static void quit_handler(int signum)
  155. {
  156. /* don't clutter the logs on Ctrl-C */
  157. if (signum != SIGINT)
  158. syslog(LOG_INFO, "exiting, signal %d received", signum);
  159. print_gpx_footer();
  160. (void)gps_close(&gpsdata);
  161. exit(EXIT_SUCCESS);
  162. }
  163. /**************************************************************************
  164. *
  165. * Main sequence
  166. *
  167. **************************************************************************/
  168. static void usage(void)
  169. {
  170. (void)fprintf(stderr,
  171. "Usage: %s [-V] [-h] [-l] [-d] [-D debuglevel]"
  172. " [-i timeout] [-f filename] [-m minmove]\n"
  173. "\t[-r] [-e exportmethod] [server[:port:[device]]]\n\n"
  174. "defaults to '%s -i 5 -e %s localhost:2947'\n",
  175. progname, progname, export_default()->name);
  176. exit(EXIT_FAILURE);
  177. }
  178. int main(int argc, char **argv)
  179. {
  180. int ch;
  181. bool daemonize = false;
  182. bool reconnect = false;
  183. unsigned int flags = WATCH_ENABLE;
  184. struct exportmethod_t *method = NULL;
  185. progname = argv[0];
  186. method = export_default();
  187. if (method == NULL) {
  188. (void)fprintf(stderr, "%s: no export methods.\n", progname);
  189. exit(EXIT_FAILURE);
  190. }
  191. logfile = stdout;
  192. while ((ch = getopt(argc, argv, "dD:e:f:hi:lm:rV")) != -1) {
  193. switch (ch) {
  194. case 'd':
  195. openlog(basename(progname), LOG_PID | LOG_PERROR, LOG_DAEMON);
  196. daemonize = true;
  197. break;
  198. #ifdef CLIENTDEBUG_ENABLE
  199. case 'D':
  200. debug = atoi(optarg);
  201. gps_enable_debug(debug, logfile);
  202. break;
  203. #endif /* CLIENTDEBUG_ENABLE */
  204. case 'e':
  205. method = export_lookup(optarg);
  206. if (method == NULL) {
  207. (void)fprintf(stderr,
  208. "%s: %s is not a known export method.\n",
  209. progname, optarg);
  210. exit(EXIT_FAILURE);
  211. }
  212. break;
  213. case 'f': /* Output file name. */
  214. {
  215. char *fname = NULL;
  216. time_t t;
  217. size_t s = 0;
  218. size_t fnamesize = strlen(optarg) + 1;
  219. t = time(NULL);
  220. while (s == 0) {
  221. char *newfname = realloc(fname, fnamesize);
  222. if (newfname == NULL) {
  223. syslog(LOG_ERR, "realloc failed.");
  224. goto bailout;
  225. } else {
  226. fname = newfname;
  227. }
  228. s = strftime(fname, fnamesize-1, optarg, localtime(&t));
  229. if (!s) {
  230. /* expanded filename did not fit in string, try
  231. * a bigger string */
  232. fnamesize += 1024;
  233. }
  234. }
  235. fname[s] = '\0';;
  236. logfile = fopen(fname, "w");
  237. if (logfile == NULL) {
  238. syslog(LOG_ERR,
  239. "Failed to open %s: %s, logging to stdout.",
  240. fname, strerror(errno));
  241. logfile = stdout;
  242. }
  243. bailout:
  244. free(fname);
  245. break;
  246. }
  247. case 'i': /* set polling interval */
  248. timeout = (time_t) atoi(optarg);
  249. if (timeout < 1)
  250. timeout = 1;
  251. if (timeout >= 3600)
  252. (void)fprintf(stderr,
  253. "WARNING: track timeout is an hour or more!\n");
  254. break;
  255. case 'l':
  256. export_list(stderr);
  257. exit(EXIT_SUCCESS);
  258. case 'm':
  259. minmove = (double )atoi(optarg);
  260. break;
  261. case 'r':
  262. reconnect = true;
  263. break;
  264. case 'V':
  265. (void)fprintf(stderr, "%s: version %s (revision %s)\n",
  266. progname, VERSION, REVISION);
  267. exit(EXIT_SUCCESS);
  268. default:
  269. usage();
  270. /* NOTREACHED */
  271. }
  272. }
  273. if (daemonize && logfile == stdout) {
  274. syslog(LOG_ERR, "Daemon mode with no valid logfile name - exiting.");
  275. exit(EXIT_FAILURE);
  276. }
  277. if (method->magic != NULL) {
  278. source.server = (char *)method->magic;
  279. source.port = NULL;
  280. source.device = NULL;
  281. } else {
  282. source.server = (char *)"localhost";
  283. source.port = (char *)DEFAULT_GPSD_PORT;
  284. source.device = NULL;
  285. }
  286. if (optind < argc) {
  287. /* in this case, switch to the method "socket" always */
  288. gpsd_source_spec(argv[optind], &source);
  289. }
  290. #if 0
  291. (void)fprintf(logfile,"<!-- server: %s port: %s device: %s -->\n",
  292. source.server, source.port, source.device);
  293. #endif
  294. /* catch all interesting signals */
  295. (void)signal(SIGTERM, quit_handler);
  296. (void)signal(SIGQUIT, quit_handler);
  297. (void)signal(SIGINT, quit_handler);
  298. /* might be time to daemonize */
  299. if (daemonize) {
  300. /* not SuS/POSIX portable, but we have our own fallback version */
  301. if (os_daemon(0, 0) != 0)
  302. (void) fprintf(stderr,"daemonization failed: %s\n", strerror(errno));
  303. }
  304. //syslog (LOG_INFO, "---------- STARTED ----------");
  305. if (gps_open(source.server, source.port, &gpsdata) != 0) {
  306. (void)fprintf(stderr,
  307. "%s: no gpsd running or network error: %d, %s\n",
  308. progname, errno, gps_errstr(errno));
  309. exit(EXIT_FAILURE);
  310. }
  311. if (source.device != NULL)
  312. flags |= WATCH_DEVICE;
  313. (void)gps_stream(&gpsdata, flags, source.device);
  314. print_gpx_header();
  315. while (gps_mainloop(&gpsdata, timeout * 1000000, conditionally_log_fix) < 0 &&
  316. reconnect) {
  317. /* avoid busy-calling gps_mainloop() */
  318. (void)sleep(timeout);
  319. syslog(LOG_INFO, "timeout; about to reconnect");
  320. }
  321. print_gpx_footer();
  322. (void)gps_close(&gpsdata);
  323. exit(EXIT_SUCCESS);
  324. }