gpsrinex.c 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354
  1. /*
  2. * gpsrinex: read "RAW" messages from a gpsd and output a RINEX 3 obs file.
  3. *
  4. * gpsrinex will read live data from gpsd and create a file of RINEX 3
  5. * observations. Currently this only works if the GPS is a u-blox
  6. * GPS and is sending UBX-RXM-RAWX messages.
  7. *
  8. * The u-blox must be configured for u-blox binary messages. GLONASS,
  9. * GALILEO, and BEIDOU must be off. Optionally SBAS on, but can be
  10. * flakey.
  11. *
  12. * Too much data for 9600!
  13. *
  14. * To configure a u-blox to output the proper data:
  15. * # gpsctl -s 115200
  16. * # sleep 2
  17. * # ubxtool -d NMEA
  18. * # ubstool -e BINARY
  19. * # ubxtool -d GLONASS
  20. * # ubxtool -d BEIDOU
  21. * # ubxtool -d GALILEO
  22. * # ubxtool -d SBAS
  23. * # ubxtool -e RAWX
  24. *
  25. * If you have a u-blox 9 then enable GLONASS as well.
  26. *
  27. * After collecting the default number of observations, gpsrinex will
  28. * create the RINEX .obs file and exit. Upload this file to an
  29. * offline processing service to get cm accuracy.
  30. *
  31. * One service known to work with obsrinex output is [CSRS-PPP]:
  32. * https://webapp.geod.nrcan.gc.ca/geod/tools-outils/ppp.php
  33. *
  34. * Examples:
  35. * To collect 4 hours of samples as 30 second intervals:
  36. * # gpsrinex -i 30 -n 480
  37. *
  38. * To generate RINEX 3 from a u-blox capture file:
  39. * Grab 4 hours of raw live data:
  40. * # gpspipe -x 14400 -R > 4h-raw.ubx
  41. * Feed that data to gpsfake:
  42. * # gpsfake -1 -P 3000 4h-raw.ubx
  43. * In another window, convert that raw to RINEX 3:
  44. * # gpsrinex -i 1 -n 1000000
  45. *
  46. * See also:
  47. * [1] RINEX: The Receiver Independent Exchange Format, Version 3.04
  48. * ftp://igs.org/pub/data/format/rinex304.pdf
  49. *
  50. * [2] GPSTk, http://www.gpstk.org/
  51. *
  52. * [3] Nischan, Thomas (2016):
  53. * GFZRNX - RINEX GNSS Data Conversion and Manipulation Toolbox.
  54. * GFZ Data Services. http://dx.doi.org/10.5880/GFZ.1.1.2016.002
  55. *
  56. * [4] RTKLIB: An Open Source Program Package for GNSS Positioning
  57. * http://www.rtklib.com/
  58. *
  59. * This file is Copyright 2018 by the GPSD project
  60. * SPDX-License-Identifier: BSD-2-clause
  61. *
  62. */
  63. #include "gpsd_config.h" /* must be before all includes */
  64. #include <assert.h>
  65. #include <errno.h>
  66. #include <libgen.h>
  67. #include <math.h>
  68. #include <signal.h>
  69. #include <stdbool.h>
  70. #include <stdio.h>
  71. #include <stdlib.h>
  72. #include <string.h>
  73. #include <sys/types.h> // for umask()
  74. #include <sys/stat.h> // for umask()
  75. #include <time.h>
  76. #include <unistd.h>
  77. #ifdef HAVE_GETOPT_LONG
  78. #include <getopt.h>
  79. #endif
  80. #include "compiler.h"
  81. #include "gps.h"
  82. #include "gpsdclient.h"
  83. #include "os_compat.h"
  84. #include "timespec.h"
  85. static char *progname;
  86. static struct fixsource_t source;
  87. static double ecefx = 0.0;
  88. static double ecefy = 0.0;
  89. static double ecefz = 0.0;
  90. static timespec_t start_time = {0}; /* report gen time, UTC */
  91. static timespec_t first_mtime = {0}; /* GPS time, not UTC */
  92. static timespec_t last_mtime = {0}; /* GPS time, not UTC */
  93. static int leap_seconds = 0; // set if non-zero
  94. // strings for the RINEX file
  95. static char agency[41] = "Unknown";
  96. static char ant_num[21] = "0";
  97. static char ant_type[21] = "UNKNOWN EXT NONE";
  98. static double ant_e = 0.0;
  99. static double ant_h = 0.0;
  100. static double ant_n = 0.0;
  101. static char marker_name[61] = "XXXX";
  102. static char marker_type[61] = "NON_PHYSICAL";
  103. static char observer[21] = "Unknown";
  104. static char rec_num[21] = "0";
  105. static char rec_type[21] = "Unknown";
  106. static char rec_vers[21] = "0";
  107. /* total count of observations by u-blox gnssid [0-7]
  108. * 0 = GPS RINEX G
  109. * 1 = SBAS RINEX S
  110. * 2 = Galileo RINEX E
  111. * 3 - BeiDou RINEX C
  112. * 4 = IMES not supported by RINEX
  113. * 5 = QZSS RINEX J
  114. * 6 = GLONASS RINEX R
  115. * 7 = IRNSS RINEX I
  116. *
  117. * RINEX 3 observation codes [1]:
  118. * C1C L1 C/A Pseudorange
  119. * C1P L1 P Pseudorange
  120. * C1W L1 Z-tracking Pseudorange
  121. * D1C L1 C/A Doppler
  122. * L1C L1 C/A Carrier Phase
  123. * L1P L1 P Carrier Phase
  124. * L1W L1 Z-tracking Carrier Phase
  125. * C2C L2 C/A Pseudorange
  126. * C2P L2 P Pseudorange
  127. * C2W L2 Z-tracking Pseudorange
  128. * D2C L2 C/A Doppler
  129. * L2C L2 C/A Carrier phase
  130. * L2P L1 P Carrier Phase
  131. * L2W L2 Z-tracking Carrier Phase
  132. *
  133. * C2L L2C (L), Pseudo Range, BeiDou
  134. * D2L L2C (L), Doppler, BeiDou
  135. * L2L L2C (L), Carrier Phase, BeiDou
  136. *
  137. * L5I L5 I Pseudo Range
  138. * C5I L5 I Carrier Phase
  139. * D5I L5 I Doppler
  140. *
  141. * CSRS-PPP supports:
  142. * GPS: C1C L1C C2C L2C C1W L1W C2W L2W
  143. * GLONASS : C1C L1C C2C L2C C1P L1P C2P L2P
  144. *
  145. */
  146. typedef enum {C1C = 0, D1C, L1C,
  147. C2C, D2C, L2C,
  148. C2L, D2L, L2L,
  149. C5I, D5I, L5I,
  150. C7I, D7I, L7I,
  151. C7Q, D7Q, L7Q, CODEMAX} obs_codes;
  152. /* structure to hold count of observations by gnssid:svid
  153. * MAXCHANNEL+1 is just a WAG of max size */
  154. #define MAXCNT (MAXCHANNELS + 1)
  155. static struct obs_cnt_t {
  156. unsigned char gnssid;
  157. unsigned char svid; /* svid of 0 means unused slot */
  158. unsigned int obs_cnts[CODEMAX+1]; /* count of obscode */
  159. } obs_cnt[MAXCNT] = {{0}};
  160. static FILE * tmp_file; /* file handle for temp file */
  161. static int sample_count = 20; /* number of measurement sets to get */
  162. /* timespec_t between measurement sets */
  163. static timespec_t sample_interval_ts = {30, 0};
  164. /* milli-seconds between measurement sets */
  165. static unsigned sample_interval_ms = 30000;
  166. #define DEBUG_QUIET 0
  167. #define DEBUG_INFO 1
  168. #define DEBUG_PROG 2
  169. #define DEBUG_RAW 3
  170. static int debug = DEBUG_INFO; /* debug level */
  171. static struct gps_data_t gpsdata;
  172. static FILE *log_file;
  173. /* convert a u-blox/gpsd gnssid to the RINEX 3 constellation code
  174. * see [1] Section 3.5
  175. */
  176. static char gnssid2rinex(int gnssid)
  177. {
  178. switch (gnssid) {
  179. case GNSSID_GPS: /* 0 = GPS */
  180. return 'G';
  181. case GNSSID_SBAS: /* 1 = SBAS */
  182. return 'S';
  183. case GNSSID_GAL: /* 2 = Galileo */
  184. return 'E';
  185. case GNSSID_BD: /* 3 = BeiDou */
  186. return 'C';
  187. case GNSSID_IMES: /* 4 = IMES - unsupported */
  188. return 'X';
  189. case GNSSID_QZSS: /* 5 = QZSS */
  190. return 'J';
  191. case GNSSID_GLO: /* 6 = GLONASS */
  192. return 'R';
  193. case GNSSID_IRNSS: /* 7 = IRNSS */
  194. return 'I';
  195. default: /* Huh? */
  196. return 'x';
  197. }
  198. }
  199. /* obs_cnt_inc()
  200. *
  201. * increment an observation count
  202. */
  203. static void obs_cnt_inc(unsigned char gnssid, unsigned char svid,
  204. obs_codes obs_code)
  205. {
  206. int i;
  207. if (CODEMAX <= obs_code) {
  208. /* should never happen... */
  209. fprintf(stderr, "ERROR: obs_code_inc() obs_code %d out of range\n",
  210. obs_code);
  211. exit(1);
  212. }
  213. /* yeah, slow and ugly, linear search. */
  214. for (i = 0; i < MAXCNT; i++) {
  215. if (0 == obs_cnt[i].svid) {
  216. /* end of list, not found, so add this gnssid:svid */
  217. obs_cnt[i].gnssid = gnssid;
  218. obs_cnt[i].svid = svid;
  219. obs_cnt[i].obs_cnts[obs_code] = 1;
  220. break;
  221. }
  222. if (obs_cnt[i].gnssid != gnssid) {
  223. continue;
  224. }
  225. if (obs_cnt[i].svid != svid) {
  226. continue;
  227. }
  228. /* found it, increment it */
  229. obs_cnt[i].obs_cnts[obs_code]++;
  230. if (99999 < obs_cnt[i].obs_cnts[obs_code]) {
  231. /* RINEX 3 max is 99999 */
  232. obs_cnt[i].obs_cnts[obs_code] = 99999;
  233. }
  234. break;
  235. }
  236. /* fell out because table full, item added, or item incremented */
  237. return;
  238. }
  239. /* compare two obs_cnt, for sorting by gnssid, and svid */
  240. static int compare_obs_cnt(const void *A, const void *B)
  241. {
  242. const struct obs_cnt_t *a = (const struct obs_cnt_t *)A;
  243. const struct obs_cnt_t *b = (const struct obs_cnt_t *)B;
  244. unsigned char a_gnssid = a->gnssid;
  245. unsigned char b_gnssid = b->gnssid;
  246. /* 0 = svid means unused, make those last */
  247. if (0 == a->svid) {
  248. a_gnssid = 255;
  249. }
  250. if (0 == b->svid) {
  251. b_gnssid = 255;
  252. }
  253. if (a_gnssid != b_gnssid) {
  254. return a_gnssid - b_gnssid;
  255. }
  256. /* put unused last */
  257. if (a->svid != b->svid) {
  258. return a->svid - b->svid;
  259. }
  260. /* two blank records */
  261. return 0;
  262. }
  263. /* return number of unique PRN in a gnssid from obs_cnt.
  264. * return all PRNs if 255 == gnssid */
  265. static int obs_cnt_prns(unsigned char gnssid)
  266. {
  267. int i;
  268. int prn_cnt = 0;
  269. for (i = 0; i < MAXCNT; i++) {
  270. if (0 == obs_cnt[i].svid) {
  271. /* end of list, done */
  272. break;
  273. }
  274. if ((255 != gnssid) && (gnssid != obs_cnt[i].gnssid)) {
  275. /* wrong gnssid */
  276. continue;
  277. }
  278. prn_cnt++;
  279. }
  280. /* fell out because table full, item added, or item incremented */
  281. return prn_cnt;
  282. }
  283. /* print_rinex_header()
  284. * Print a RINEX 3 header to the file "log_file".
  285. * Some of the data in the header is only known after processing all
  286. * the raw data.
  287. */
  288. static void print_rinex_header(void)
  289. {
  290. int i, j;
  291. char tmstr[40]; /* time: yyyymmdd hhmmss UTC */
  292. struct tm *report_time;
  293. struct tm *first_time;
  294. struct tm *last_time;
  295. struct tm tm_buf; // temp buffer for gmtime_r()
  296. int cnt; /* number of obs for one sat */
  297. int prn_count[GNSSID_CNT] = {0}; /* count of PRN per gnssid */
  298. if (DEBUG_PROG <= debug) {
  299. (void)fprintf(stderr, "doing header\n");
  300. }
  301. report_time = gmtime_r(&(start_time.tv_sec), &tm_buf);
  302. (void)strftime(tmstr, sizeof(tmstr), "%Y%m%d %H%M%S UTC", report_time);
  303. (void)fprintf(log_file,
  304. "%9s%11s%-20s%-20s%-20s\n",
  305. "3.03", "", "OBSERVATION DATA", "M: Mixed", "RINEX VERSION / TYPE");
  306. (void)fprintf(log_file,
  307. "%-20s%-20s%-20s%-20s\n",
  308. "gpsrinex " VERSION, "", tmstr,
  309. "PGM / RUN BY / DATE");
  310. (void)fprintf(log_file, "%-60s%-20s\n",
  311. "Source: gpsd live data", "COMMENT");
  312. (void)fprintf(log_file, "%-60s%-20s\n", marker_name, "MARKER NAME");
  313. (void)fprintf(log_file, "%-60s%-20s\n", marker_type, "MARKER TYPE");
  314. (void)fprintf(log_file, "%-20s%-40s%-20s\n",
  315. observer, agency, "OBSERVER / AGENCY");
  316. (void)fprintf(log_file, "%-20s%-20s%-20s%-20s\n",
  317. rec_num, rec_type, rec_vers, "REC # / TYPE / VERS");
  318. (void)fprintf(log_file, "%-20s%-20s%-20s%-20s\n",
  319. ant_num, ant_type, "" , "ANT # / TYPE");
  320. if (isfinite(ecefx) &&
  321. isfinite(ecefy) &&
  322. isfinite(ecefz)) {
  323. (void)fprintf(log_file, "%14.4f%14.4f%14.4f%18s%-20s\n",
  324. ecefx, ecefy, ecefz, "", "APPROX POSITION XYZ");
  325. } else if (DEBUG_INFO <= debug) {
  326. (void)fprintf(stderr, "INFO: missing ECEF\n");
  327. }
  328. (void)fprintf(log_file, "%14.4f%14.4f%14.4f%18s%-20s\n",
  329. ant_h, ant_e, ant_n, "", "ANTENNA: DELTA H/E/N");
  330. (void)fprintf(log_file, "%6d%6d%48s%-20s\n", 1, 1,
  331. "", "WAVELENGTH FACT L1/2");
  332. /* get PRN stats */
  333. qsort(obs_cnt, MAXCNT, sizeof(struct obs_cnt_t), compare_obs_cnt);
  334. for (i = 0; i < GNSSID_CNT; i++ ) {
  335. prn_count[i] = obs_cnt_prns(i);
  336. }
  337. /* CSRS-PPP needs C1C, L1C or C1C, L1C, D1C
  338. * CSRS-PPP refuses files with L1C first
  339. * convbin wants C1C, L1C, D1C
  340. * for some reason gfzrnx_lx wants C1C, D1C, L1C, not C1C, L1C, D1C */
  341. if (0 < prn_count[GNSSID_GPS]) {
  342. /* GPS, code G */
  343. (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n",
  344. gnssid2rinex(GNSSID_GPS), 6, "C1C", "L1C", "D1C", "C2C", "L2C",
  345. "D2C", "", "", "", "SYS / # / OBS TYPES");
  346. }
  347. if (0 < prn_count[GNSSID_SBAS]) {
  348. /* SBAS, L1 and L5 only, code S */
  349. (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n",
  350. gnssid2rinex(GNSSID_SBAS), 3, "C1C", "L1C", "D1C", "", "", "",
  351. "", "", "", "SYS / # / OBS TYPES");
  352. }
  353. if (0 < prn_count[GNSSID_GAL]) {
  354. /* Galileo, E1, E5 aand E6 only, code E */
  355. (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n",
  356. gnssid2rinex(GNSSID_GAL), 6, "C1C", "L1C", "D1C", "C7Q",
  357. "L7Q", "D7Q", "", "", "", "SYS / # / OBS TYPES");
  358. }
  359. if (0 < prn_count[GNSSID_BD]) {
  360. /* BeiDou, BDS, code C */
  361. (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n",
  362. gnssid2rinex(GNSSID_BD), 6, "C1C", "L1C", "D1C", "C7I", "L7I",
  363. "D7I", "", "", "", "SYS / # / OBS TYPES");
  364. }
  365. if (0 < prn_count[GNSSID_QZSS]) {
  366. /* QZSS, code J */
  367. (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n",
  368. gnssid2rinex(GNSSID_QZSS), 6, "C1C", "L1C", "D1C", "C2L",
  369. "L2L", "D2L", "", "", "", "SYS / # / OBS TYPES");
  370. }
  371. if (0 < prn_count[GNSSID_GLO]) {
  372. /* GLONASS, R */
  373. (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n",
  374. gnssid2rinex(GNSSID_GLO), 6, "C1C", "L1C", "D1C", "C2C", "L2C",
  375. "D2C", "", "", "", "SYS / # / OBS TYPES");
  376. }
  377. // FIXME: Add IRNSS...
  378. (void)fprintf(log_file, "%6d%54s%-20s\n", obs_cnt_prns(255),
  379. "", "# OF SATELLITES");
  380. /* get all the PRN / # OF OBS */
  381. for (i = 0; i < MAXCNT; i++) {
  382. cnt = 0;
  383. if (0 == obs_cnt[i].svid) {
  384. /* done */
  385. break;
  386. }
  387. for (j = 0; j < CODEMAX; j++) {
  388. cnt += obs_cnt[i].obs_cnts[j];
  389. }
  390. if (0 > cnt) {
  391. /* no counts for this sat */
  392. continue;
  393. }
  394. switch (obs_cnt[i].gnssid) {
  395. case GNSSID_GPS:
  396. /* GPS, code G */
  397. (void)fprintf(log_file," %c%02d%6u%6u%6u%6u%6u%6u%18s%-20s\n",
  398. gnssid2rinex(obs_cnt[i].gnssid), obs_cnt[i].svid,
  399. obs_cnt[i].obs_cnts[C1C],
  400. obs_cnt[i].obs_cnts[L1C],
  401. obs_cnt[i].obs_cnts[D1C],
  402. obs_cnt[i].obs_cnts[C2C],
  403. obs_cnt[i].obs_cnts[L2C],
  404. obs_cnt[i].obs_cnts[D2C],
  405. "", "PRN / # OF OBS");
  406. break;
  407. case GNSSID_SBAS:
  408. /* SBAS, L1C and L5C, code S */
  409. (void)fprintf(log_file," %c%02d%6u%6u%6u%6u%6u%6u%18s%-20s\n",
  410. gnssid2rinex(obs_cnt[i].gnssid), obs_cnt[i].svid,
  411. obs_cnt[i].obs_cnts[C1C],
  412. obs_cnt[i].obs_cnts[L1C],
  413. obs_cnt[i].obs_cnts[D1C],
  414. obs_cnt[i].obs_cnts[C5I],
  415. obs_cnt[i].obs_cnts[L5I],
  416. obs_cnt[i].obs_cnts[D5I],
  417. "", "PRN / # OF OBS");
  418. break;
  419. case GNSSID_GAL:
  420. /* Galileo, code E */
  421. (void)fprintf(log_file," %c%02d%6u%6u%6u%6u%6u%6u%18s%-20s\n",
  422. gnssid2rinex(obs_cnt[i].gnssid), obs_cnt[i].svid,
  423. obs_cnt[i].obs_cnts[C1C],
  424. obs_cnt[i].obs_cnts[L1C],
  425. obs_cnt[i].obs_cnts[D1C],
  426. obs_cnt[i].obs_cnts[C7Q],
  427. obs_cnt[i].obs_cnts[L7Q],
  428. obs_cnt[i].obs_cnts[D7Q],
  429. "", "PRN / # OF OBS");
  430. break;
  431. case GNSSID_BD:
  432. /* BeiDou, code C */
  433. (void)fprintf(log_file," %c%02d%6u%6u%6u%6u%6u%6u%18s%-20s\n",
  434. gnssid2rinex(obs_cnt[i].gnssid), obs_cnt[i].svid,
  435. obs_cnt[i].obs_cnts[C1C],
  436. obs_cnt[i].obs_cnts[L1C],
  437. obs_cnt[i].obs_cnts[D1C],
  438. obs_cnt[i].obs_cnts[C7I],
  439. obs_cnt[i].obs_cnts[L7I],
  440. obs_cnt[i].obs_cnts[D7I],
  441. "", "PRN / # OF OBS");
  442. break;
  443. case GNSSID_QZSS:
  444. /* QZSS, code J */
  445. (void)fprintf(log_file," %c%02d%6u%6u%6u%6u%6u%6u%18s%-20s\n",
  446. gnssid2rinex(obs_cnt[i].gnssid), obs_cnt[i].svid,
  447. obs_cnt[i].obs_cnts[C1C],
  448. obs_cnt[i].obs_cnts[L1C],
  449. obs_cnt[i].obs_cnts[D1C],
  450. obs_cnt[i].obs_cnts[C2L],
  451. obs_cnt[i].obs_cnts[L2L],
  452. obs_cnt[i].obs_cnts[D2L],
  453. "", "PRN / # OF OBS");
  454. break;
  455. case GNSSID_GLO:
  456. /* GLONASS, code R */
  457. (void)fprintf(log_file," %c%02d%6u%6u%6u%6u%6u%6u%18s%-20s\n",
  458. gnssid2rinex(obs_cnt[i].gnssid), obs_cnt[i].svid,
  459. obs_cnt[i].obs_cnts[C1C],
  460. obs_cnt[i].obs_cnts[L1C],
  461. obs_cnt[i].obs_cnts[D1C],
  462. obs_cnt[i].obs_cnts[C2C],
  463. obs_cnt[i].obs_cnts[L2C],
  464. obs_cnt[i].obs_cnts[D2C],
  465. "", "PRN / # OF OBS");
  466. break;
  467. // FIXME: Add GNSSID_IRNSS, L5A
  468. default:
  469. (void)fprintf(log_file," %c%02d%6u%6u%6u%6s%6s%24s%-20s\n",
  470. gnssid2rinex(obs_cnt[i].gnssid), obs_cnt[i].svid,
  471. obs_cnt[i].obs_cnts[C1C],
  472. obs_cnt[i].obs_cnts[L1C],
  473. obs_cnt[i].obs_cnts[D1C],
  474. "", "",
  475. "", "PRN / # OF OBS");
  476. }
  477. }
  478. (void)fprintf(log_file, "%-10s%50s%-20s\n",
  479. "DBHZ", "", "SIGNAL STRENGTH UNIT");
  480. (void)fprintf(log_file, "%10.3f%50s%-20s\n",
  481. (double)sample_interval_ms / 1000.0, "", "INTERVAL");
  482. /* GPS time not UTC */
  483. first_time = gmtime_r(&(first_mtime.tv_sec), &tm_buf);
  484. (void)fprintf(log_file, "%6d%6d%6d%6d%6d%5d.%07ld%8s%9s%-20s\n",
  485. first_time->tm_year + 1900,
  486. first_time->tm_mon + 1,
  487. first_time->tm_mday,
  488. first_time->tm_hour,
  489. first_time->tm_min,
  490. first_time->tm_sec,
  491. (long)(first_mtime.tv_nsec / 100),
  492. "GPS", "",
  493. "TIME OF FIRST OBS");
  494. /* GPS time not UTC */
  495. last_time = gmtime_r(&(last_mtime.tv_sec), &tm_buf);
  496. (void)fprintf(log_file, "%6d%6d%6d%6d%6d%5d.%07ld%8s%9s%-20s\n",
  497. last_time->tm_year + 1900,
  498. last_time->tm_mon + 1,
  499. last_time->tm_mday,
  500. last_time->tm_hour,
  501. last_time->tm_min,
  502. last_time->tm_sec,
  503. (long)(last_mtime.tv_nsec / 100),
  504. "GPS", "",
  505. "TIME OF LAST OBS");
  506. if (0 < prn_count[GNSSID_GPS]) {
  507. /* GPS, code G */
  508. (void)fprintf(log_file, "%-60s%-20s\n",
  509. "G L1C", "SYS / PHASE SHIFT");
  510. (void)fprintf(log_file, "%-60s%-20s\n",
  511. "G L2C", "SYS / PHASE SHIFT");
  512. }
  513. if (0 < prn_count[GNSSID_SBAS]) {
  514. /* SBAS, L1 and L5 only, code S */
  515. (void)fprintf(log_file, "%-60s%-20s\n",
  516. "S L1C", "SYS / PHASE SHIFT");
  517. (void)fprintf(log_file, "%-60s%-20s\n",
  518. "E L5Q", "SYS / PHASE SHIFT");
  519. }
  520. if (0 < prn_count[GNSSID_GAL]) {
  521. /* GALILEO, E1, E5 and E6, code E */
  522. (void)fprintf(log_file, "%-60s%-20s\n",
  523. "E L1C", "SYS / PHASE SHIFT");
  524. (void)fprintf(log_file, "%-60s%-20s\n",
  525. "E L7Q", "SYS / PHASE SHIFT");
  526. }
  527. if (0 < prn_count[GNSSID_BD]) {
  528. /* BeiDou, code C */
  529. (void)fprintf(log_file, "%-60s%-20s\n",
  530. "B L1C", "SYS / PHASE SHIFT");
  531. (void)fprintf(log_file, "%-60s%-20s\n",
  532. "B L7I", "SYS / PHASE SHIFT");
  533. }
  534. if (0 < prn_count[GNSSID_QZSS]) {
  535. /* QZSS, code J */
  536. (void)fprintf(log_file, "%-60s%-20s\n",
  537. "J L1C", "SYS / PHASE SHIFT");
  538. (void)fprintf(log_file, "%-60s%-20s\n",
  539. "J L2L", "SYS / PHASE SHIFT");
  540. }
  541. if (0 < prn_count[GNSSID_GLO]) {
  542. /* GLONASS, code R */
  543. (void)fprintf(log_file, "%-60s%-20s\n",
  544. "R L1C", "SYS / PHASE SHIFT");
  545. (void)fprintf(log_file, "%-60s%-20s\n",
  546. "R L2C", "SYS / PHASE SHIFT");
  547. }
  548. (void)fprintf(log_file, "%-60s%-20s\n",
  549. "", "END OF HEADER");
  550. if (DEBUG_PROG <= debug) {
  551. (void)fprintf(stderr,"done header\n");
  552. }
  553. return;
  554. }
  555. /* print_rinex_footer()
  556. * print a RINEX 3 footer to the file "log_file".
  557. * Except RINEX 3 has no footer. So what this really does is
  558. * call the header function, then move the processed observations from
  559. * "tmp_file" to "log_file".
  560. */
  561. static void print_rinex_footer(void)
  562. {
  563. char buffer[4096];
  564. /* print the header */
  565. print_rinex_header();
  566. /* now replay the data in the tmp_file into the output */
  567. (void)fflush(tmp_file);
  568. rewind(tmp_file);
  569. while (true) {
  570. size_t count;
  571. count = fread(buffer, 1, sizeof(buffer), tmp_file);
  572. if (0 >= count ) {
  573. break;
  574. }
  575. (void)fwrite(buffer, 1, count, log_file);
  576. }
  577. (void)fclose(tmp_file);
  578. (void)fclose(log_file);
  579. (void)gps_close(&gpsdata);
  580. }
  581. /* compare two meas_t, for sorting by gnssid, svid, and sigid */
  582. static int compare_meas(const void *A, const void *B)
  583. {
  584. const struct meas_t *a = (const struct meas_t*)A;
  585. const struct meas_t *b = (const struct meas_t*)B;
  586. if (a->gnssid != b->gnssid) {
  587. return a->gnssid - b->gnssid;
  588. }
  589. if (a->svid != b->svid) {
  590. return a->svid - b->svid;
  591. }
  592. if (a->sigid != b->sigid) {
  593. return a->sigid - b->sigid;
  594. }
  595. /* two blank records */
  596. return 0;
  597. }
  598. /* convert an observation item and return it as a (F14,3,I1,I1)
  599. * in a static buffer */
  600. static const char * fmt_obs(double val, unsigned char lli, unsigned char snr)
  601. {
  602. static char buf[20];
  603. char lli_c; /* set zero lli to blank */
  604. char snr_c; /* set zero snr to blank */
  605. if (!isfinite(val)) {
  606. /* bad value, return 16 blanks */
  607. return " ";
  608. }
  609. switch (lli) {
  610. case 0:
  611. default:
  612. lli_c = ' ';
  613. break;
  614. case 1:
  615. lli_c = '1';
  616. break;
  617. case 2:
  618. lli_c = '2';
  619. break;
  620. case 3:
  621. lli_c = '3';
  622. break;
  623. }
  624. if ((1 > snr) || (9 < snr)) {
  625. snr_c = ' ';
  626. } else {
  627. snr_c = 48 + snr;
  628. }
  629. (void)snprintf(buf, sizeof(buf), "%14.3f%c%1c", val, lli_c, snr_c);
  630. return buf;
  631. }
  632. /* one_sig() - print one signal
  633. *
  634. * one CxC s LxC DxC
  635. */
  636. static void one_sig(struct meas_t *meas)
  637. {
  638. unsigned char snr;
  639. unsigned gnssid = meas->gnssid;
  640. unsigned svid = meas->svid;
  641. unsigned sigid = meas->sigid;
  642. obs_codes cxx = C1C;
  643. obs_codes lxx = L1C;
  644. obs_codes dxx = D1C;
  645. if (DEBUG_PROG <= debug) {
  646. (void)fprintf(stderr, "INFO: one_sig() %c %u:%u:%u\n",
  647. gnssid2rinex(gnssid),
  648. gnssid, svid, sigid);
  649. }
  650. switch (sigid) {
  651. default:
  652. (void)fprintf(stderr, "ERROR: one_sig() gnmssid %u unknown sigid %u\n",
  653. gnssid, sigid);
  654. FALLTHROUGH
  655. case 0:
  656. /* L1C */
  657. cxx = C1C;
  658. lxx = L1C;
  659. dxx = D1C;
  660. break;
  661. case 2:
  662. /* GLONASS L2 OF or BeiDou B2I D1 */
  663. if (GNSSID_BD == gnssid) {
  664. /* WAG */
  665. cxx = C7I;
  666. lxx = L7I;
  667. dxx = D7I;
  668. } else {
  669. cxx = C2C;
  670. lxx = L2C;
  671. dxx = D2C;
  672. }
  673. break;
  674. case 3:
  675. /* GPS L2 or BD B2I D2 */
  676. cxx = C2C;
  677. lxx = L2C;
  678. dxx = D2C;
  679. break;
  680. case 5:
  681. /* QZSS L2C (L) */
  682. cxx = C2L;
  683. lxx = L2L;
  684. dxx = D2L;
  685. break;
  686. case 6:
  687. /* Galileo E5 bQ */
  688. cxx = C7Q;
  689. lxx = L7Q;
  690. dxx = D7Q;
  691. break;
  692. }
  693. /* map snr to RINEX snr flag [1-9] */
  694. if (0 == meas->snr) {
  695. snr = 0;
  696. } else if (12 > meas->snr) {
  697. snr = 1;
  698. } else if (18 >= meas->snr) {
  699. snr = 2;
  700. } else if (23 >= meas->snr) {
  701. snr = 3;
  702. } else if (29 >= meas->snr) {
  703. snr = 4;
  704. } else if (35 >= meas->snr) {
  705. snr = 5;
  706. } else if (41 >= meas->snr) {
  707. snr = 6;
  708. } else if (47 >= meas->snr) {
  709. snr = 7;
  710. } else if (53 >= meas->snr) {
  711. snr = 8;
  712. } else {
  713. /* snr >= 54 */
  714. snr = 9;
  715. }
  716. /* check for slip
  717. * FIXME: use actual interval
  718. * locktime is in milliseconds
  719. * sample_interval_ms is milli seconds */
  720. if (meas->locktime < sample_interval_ms) {
  721. meas->lli |= 2;
  722. }
  723. if (0 != isfinite(meas->pseudorange)) {
  724. obs_cnt_inc(gnssid, svid, cxx);
  725. }
  726. if (0 != isfinite(meas->carrierphase)) {
  727. obs_cnt_inc(gnssid, svid, lxx);
  728. }
  729. if (0 != isfinite(meas->doppler)) {
  730. obs_cnt_inc(gnssid, svid, dxx);
  731. }
  732. (void)fputs(fmt_obs(meas->pseudorange, 0, 0), tmp_file);
  733. // putting snr here, with phase, is deprecated.
  734. // it should be an S observation.
  735. (void)fputs(fmt_obs(meas->carrierphase, meas->lli, snr), tmp_file);
  736. (void)fputs(fmt_obs(meas->doppler, 0, 0), tmp_file);
  737. }
  738. /* print_raw()
  739. * print one epoch of observations into "tmp_file"
  740. */
  741. static void print_raw(struct gps_data_t *gpsdata)
  742. {
  743. struct tm *now_time;
  744. struct tm tm_buf; // temp buffer for gmtime_r()
  745. unsigned nrec = 0;
  746. unsigned nsat = 0;
  747. unsigned i;
  748. unsigned char last_gnssid = 0;
  749. unsigned char last_svid = 0;
  750. int need_nl = 0;
  751. int got_l1 = 0;
  752. time_t epoch_sec;
  753. timespec_t interval_ts;
  754. TS_SUB(&interval_ts, &gpsdata->raw.mtime, &last_mtime);
  755. if (!TS_GE(&interval_ts, &sample_interval_ts)) {
  756. /* not time yet */
  757. return;
  758. }
  759. #ifdef __UNUSED
  760. fprintf(stderr, "sample: %ld %ld\n", (long)sample_interval_ts.tv_sec,
  761. (long)sample_interval_ts.tv_nsec);
  762. fprintf(stderr, "epoch: %ld %ld\n", (long)gpsdata->raw.mtime.tv_sec,
  763. (long)gpsdata->raw.mtime.tv_nsec);
  764. #endif // __UNUSED
  765. // do modulo only for sample_interval of even seconds
  766. if (0 == sample_interval_ts.tv_nsec &&
  767. 0 < sample_interval_ts.tv_sec) {
  768. epoch_sec = gpsdata->raw.mtime.tv_sec;
  769. if (500000000 < gpsdata->raw.mtime.tv_nsec) {
  770. // round it up. To match convbin.
  771. // does this break opus?
  772. epoch_sec++;
  773. }
  774. /* opus insists (time % interval) = 0 */
  775. if (0 != (epoch_sec % sample_interval_ts.tv_sec)) {
  776. return;
  777. }
  778. }
  779. /* RINEX 3 wants records in each epoch sorted by gnssid.
  780. * To look nice: sort by gnssid and svid
  781. * To work nice, sort by gnssid, svid and sigid.
  782. * Each sigid is one record in RAW, but all sigid is one
  783. * record in RINEX
  784. */
  785. /* go through list three times, first just to get a count for sort */
  786. for (i = 0; i < MAXCHANNELS; i++) {
  787. if (0 == gpsdata->raw.meas[i].svid) {
  788. /* bad svid, end of list */
  789. break;
  790. }
  791. nrec++;
  792. }
  793. if (0 >= nrec) {
  794. /* nothing to do */
  795. return;
  796. }
  797. qsort(gpsdata->raw.meas, nrec, sizeof(gpsdata->raw.meas[0]),
  798. compare_meas);
  799. /* second just to get a count, needed for epoch header */
  800. for (i = 0; i < nrec; i++) {
  801. if (0 == gpsdata->raw.meas[i].svid) {
  802. /* bad svid */
  803. continue;
  804. }
  805. if (4 == gpsdata->raw.meas[i].gnssid) {
  806. /* skip IMES */
  807. continue;
  808. }
  809. if (GNSSID_CNT <= gpsdata->raw.meas[i].gnssid) {
  810. /* invalid gnssid */
  811. continue;
  812. }
  813. /* prevent separate sigid from double counting gnssid:svid */
  814. if ((last_gnssid == gpsdata->raw.meas[i].gnssid) &&
  815. (last_svid == gpsdata->raw.meas[i].svid)) {
  816. /* duplicate sat */
  817. continue;
  818. }
  819. last_gnssid = gpsdata->raw.meas[i].gnssid;
  820. last_svid = gpsdata->raw.meas[i].svid;
  821. nsat++;
  822. }
  823. if (0 >= nsat) {
  824. /* nothing to do */
  825. return;
  826. }
  827. /* save time of last measurement, GPS time, not UTC */
  828. last_mtime = gpsdata->raw.mtime; /* structure copy */
  829. if (0 == first_mtime.tv_sec) {
  830. /* save time of first measurement */
  831. first_mtime = last_mtime; /* structure copy */
  832. }
  833. /* print epoch header line */
  834. now_time = gmtime_r(&(last_mtime.tv_sec), &tm_buf);
  835. (void)fprintf(tmp_file,"> %4d %02d %02d %02d %02d %02d.%07ld 0%3u\n",
  836. now_time->tm_year + 1900,
  837. now_time->tm_mon + 1,
  838. now_time->tm_mday,
  839. now_time->tm_hour,
  840. now_time->tm_min,
  841. now_time->tm_sec,
  842. (long)(last_mtime.tv_nsec / 100), nsat);
  843. last_gnssid = 0;
  844. last_svid = 0;
  845. need_nl = 0;
  846. got_l1 = 0;
  847. /* Print the observations, one gnssid:svid per line.
  848. * The fun is merging consecutive records (new sigid) of
  849. * same gnssid:svid */
  850. for (i = 0; i < nrec; i++) {
  851. char rinex_gnssid;
  852. unsigned char gnssid;
  853. unsigned char svid;
  854. unsigned char sigid;
  855. gnssid = gpsdata->raw.meas[i].gnssid;
  856. rinex_gnssid = gnssid2rinex(gnssid);
  857. svid = gpsdata->raw.meas[i].svid;
  858. sigid = gpsdata->raw.meas[i].sigid;
  859. if (DEBUG_RAW <= debug) {
  860. (void)fprintf(stderr,"record: %u:%u:%u %s\n",
  861. gnssid, svid, sigid,
  862. gpsdata->raw.meas[i].obs_code);
  863. }
  864. if (0 == gpsdata->raw.meas[i].svid) {
  865. /* should not happen... */
  866. continue;
  867. }
  868. /* line can be longer than 80 chars in RINEX 3 */
  869. if ((last_gnssid != gpsdata->raw.meas[i].gnssid) ||
  870. (last_svid != gpsdata->raw.meas[i].svid)) {
  871. if (0 != need_nl) {
  872. (void)fputs("\n", tmp_file);
  873. }
  874. got_l1 = 0;
  875. /* new record line gnssid:svid preamble */
  876. (void)fprintf(tmp_file,"%c%02d", rinex_gnssid, svid);
  877. }
  878. last_gnssid = gpsdata->raw.meas[i].gnssid;
  879. last_svid = gpsdata->raw.meas[i].svid;
  880. /* L1x */
  881. switch (gpsdata->raw.meas[i].sigid) {
  882. case 0:
  883. /* L1 */
  884. one_sig(&gpsdata->raw.meas[i]);
  885. got_l1 = 1;
  886. break;
  887. case 2:
  888. /* GLONASS L2 OF or BD B2I D1 */
  889. if (0 == got_l1) {
  890. /* space to start of L2 */
  891. (void)fprintf(tmp_file, "%48s", "");
  892. }
  893. one_sig(&gpsdata->raw.meas[i]);
  894. break;
  895. case 3:
  896. /* GPS L2 or BD B2I D2 */
  897. if (0 == got_l1) {
  898. /* space to start of L2 */
  899. (void)fprintf(tmp_file, "%48s", "");
  900. }
  901. one_sig(&gpsdata->raw.meas[i]);
  902. break;
  903. case 5:
  904. /* QZSS L2C (L) */
  905. if (0 == got_l1) {
  906. /* space to start of L2 */
  907. (void)fprintf(tmp_file, "%48s", "");
  908. }
  909. one_sig(&gpsdata->raw.meas[i]);
  910. break;
  911. case 6:
  912. /* Galileo E5 bQ */
  913. if (0 == got_l1) {
  914. /* space to start of L2 */
  915. (void)fprintf(tmp_file, "%48s", "");
  916. }
  917. one_sig(&gpsdata->raw.meas[i]);
  918. break;
  919. default:
  920. (void)fprintf(stderr,
  921. "ERROR: print_raw() gnssid %u unknown sigid %u\n",
  922. gnssid, sigid);
  923. break;
  924. }
  925. need_nl = 1;
  926. }
  927. if (0 != need_nl) {
  928. (void)fputs("\n", tmp_file);
  929. }
  930. sample_count--;
  931. }
  932. /* quit_handler()
  933. * quit nicely on ^C. That is: print the header and observation records
  934. * gathered so far. Then exit.
  935. */
  936. static void quit_handler(int signum)
  937. {
  938. /* don't clutter the logs on Ctrl-C */
  939. if (signum != SIGINT)
  940. syslog(LOG_INFO, "exiting, signal %d received", signum);
  941. print_rinex_footer();
  942. (void)gps_close(&gpsdata);
  943. exit(EXIT_SUCCESS);
  944. }
  945. /* conditionally_log_fix()
  946. * take the new gpsdata and decide what to do with it.
  947. */
  948. static void conditionally_log_fix(struct gps_data_t *gpsdata)
  949. {
  950. if (0 == leap_seconds && 0 < gpsdata->leap_seconds) {
  951. // grab a static copy of the current leap second.
  952. leap_seconds = gpsdata->leap_seconds;
  953. }
  954. if (DEBUG_PROG <= debug) {
  955. /* The (long long unsigned) is for 32/64-bit compatibility */
  956. (void)fprintf(stderr, "mode %d set %llx leap %d\n",
  957. gpsdata->fix.mode,
  958. (long long unsigned)gpsdata->set,
  959. leap_seconds);
  960. }
  961. if (0 == leap_seconds) {
  962. // Can't do anything until we know the current leap second
  963. return;
  964. }
  965. /* mostly we don't care if 2D or 3D fix, let the post processor
  966. * decide */
  967. if (MODE_2D < gpsdata->fix.mode) {
  968. /* got a good 3D fix */
  969. if (1.0 > ecefx &&
  970. isfinite(gpsdata->fix.ecef.x) &&
  971. isfinite(gpsdata->fix.ecef.y) &&
  972. isfinite(gpsdata->fix.ecef.z)) {
  973. /* save ecef for "APPROX POS" */
  974. ecefx = gpsdata->fix.ecef.x;
  975. ecefy = gpsdata->fix.ecef.y;
  976. ecefz = gpsdata->fix.ecef.z;
  977. if (DEBUG_PROG <= debug) {
  978. (void)fprintf(stderr,"got ECEF\n");
  979. }
  980. }
  981. }
  982. if (RAW_SET & gpsdata->set) {
  983. if (DEBUG_RAW <= debug) {
  984. (void)fprintf(stderr,"got RAW\n");
  985. }
  986. /* RINEX 3 prefers GPS time. Accepts GLO (UTC) time.
  987. * NRCan does not accept GLO time
  988. * Remove the leap second to get GPS from UTC.
  989. */
  990. gpsdata->raw.mtime.tv_sec += leap_seconds;
  991. print_raw(gpsdata);
  992. }
  993. return;
  994. }
  995. /* usage()
  996. * print usages, and exit
  997. */
  998. static void usage(void)
  999. {
  1000. (void)fprintf(stderr,
  1001. "Usage: %s [OPTIONS] [server[:port:[device]]]\n"
  1002. "\n"
  1003. "Mandatory arguments to long options are mandatory for "
  1004. "short options too.\n"
  1005. " -D, --debug debuglevel Set debug level, default 0\n"
  1006. " -f, --fileout filename out to filename\n"
  1007. " default: gpsrinexYYYYDDDDHHMM.obs\n"
  1008. " -h, --help print this usage and exit\n"
  1009. " -i, --interval interval time between samples in seconds\n"
  1010. " default: %0.3f\n"
  1011. " -n, --count count number samples to collect\n"
  1012. " default: %d\n"
  1013. " -V, --version print version and exit\n"
  1014. "\nThese strings get placed in the generated RINEX 3 obs file\n"
  1015. " --agency [agency] agency\n"
  1016. " --ant_e [easting] antenna easting in meters\n"
  1017. " --ant_h [height] antenna height in meters\n"
  1018. " --ant_n [northing] antenna northing in meters\n"
  1019. " --ant_num [num] antenna number\n"
  1020. " --ant_type [type] antenna type\n"
  1021. " --marker_name [name] marker name\n"
  1022. " --marker_type [type] marker type\n"
  1023. " --observer [observer] observer\n"
  1024. " --rec_num [num] receiver number\n"
  1025. " --rec_type [type] receiver type\n"
  1026. " --rec_vers [vers] receiver vers\n"
  1027. "\n"
  1028. "defaults to '%s -n %d -i %0.3f localhost:2947'\n",
  1029. progname, (double)sample_interval_ms / 1000.0, sample_count, progname,
  1030. sample_count, (double)sample_interval_ms / 1000.0);
  1031. exit(EXIT_FAILURE);
  1032. }
  1033. // defines for getopt_long()
  1034. #define AGENCY 301
  1035. #define ANT_E 302
  1036. #define ANT_H 303
  1037. #define ANT_N 304
  1038. #define ANT_NUM 305
  1039. #define ANT_TYPE 306
  1040. #define MARKER_NAME 307
  1041. #define MARKER_TYPE 308
  1042. #define OBSERVER 309
  1043. #define REC_NUM 310
  1044. #define REC_TYPE 311
  1045. #define REC_VERS 312
  1046. /*
  1047. *
  1048. * Main
  1049. *
  1050. */
  1051. int main(int argc, char **argv)
  1052. {
  1053. char tmstr[40]; // time: YYYYDDDMMHH
  1054. char tmp_fname[32]; // temp file name, for mkstemp
  1055. int tmp_file_desc; // temp file descriptor
  1056. struct tm *report_time;
  1057. struct tm tm_buf; // temp buffer for gmtime_r()
  1058. int ch;
  1059. unsigned int flags = WATCH_ENABLE;
  1060. char *fname = NULL;
  1061. int timeout = 10;
  1062. double f;
  1063. progname = argv[0];
  1064. log_file = stdout;
  1065. while (1) {
  1066. const char *optstring = "D:f:hi:n:V";
  1067. #ifdef HAVE_GETOPT_LONG
  1068. int option_index = 0;
  1069. static struct option long_options[] = {
  1070. {"agency", required_argument, NULL, AGENCY},
  1071. {"ant_num", required_argument, NULL, ANT_NUM},
  1072. {"ant_type", required_argument, NULL, ANT_TYPE},
  1073. {"ant_e", required_argument, NULL, ANT_E},
  1074. {"ant_h", required_argument, NULL, ANT_H},
  1075. {"ant_n", required_argument, NULL, ANT_N},
  1076. {"count", required_argument, NULL, 'n' },
  1077. {"debug", required_argument, NULL, 'D' },
  1078. {"fileout", required_argument, NULL, 'f' },
  1079. {"help", no_argument, NULL, 'h' },
  1080. {"interval", required_argument, NULL, 'i' },
  1081. {"marker_name", required_argument, NULL, MARKER_NAME},
  1082. {"marker_type", required_argument, NULL, MARKER_TYPE},
  1083. {"observer", required_argument, NULL, OBSERVER},
  1084. {"rec_num", required_argument, NULL, REC_NUM},
  1085. {"rec_type", required_argument, NULL, REC_TYPE},
  1086. {"rec_vers", required_argument, NULL, REC_VERS},
  1087. {"version", no_argument, NULL, 'V' },
  1088. {NULL, 0, NULL, 0},
  1089. };
  1090. ch = getopt_long(argc, argv, optstring, long_options, &option_index);
  1091. #else
  1092. ch = getopt(argc, argv, optstring);
  1093. #endif
  1094. if (ch == -1) {
  1095. break;
  1096. }
  1097. switch (ch) {
  1098. case 'D':
  1099. debug = atoi(optarg);
  1100. gps_enable_debug(debug, log_file);
  1101. break;
  1102. case 'f': /* Output file name. */
  1103. fname = strdup(optarg);
  1104. break;
  1105. case 'i': /* set sampling interval */
  1106. f = safe_atof(optarg); // still in seconds
  1107. if (3600.0 <= f) {
  1108. (void)fprintf(stderr,
  1109. "WARNING: sample interval is an hour or more!\n");
  1110. }
  1111. sample_interval_ms = (unsigned)(1000 * f); // now in ms
  1112. if (0 == sample_interval_ms) {
  1113. // underflow
  1114. sample_interval_ms = 1;
  1115. }
  1116. MSTOTS(&sample_interval_ts, sample_interval_ms);
  1117. break;
  1118. case 'n':
  1119. sample_count = atoi(optarg);
  1120. break;
  1121. case 'V':
  1122. (void)fprintf(stderr, "%s: version %s (revision %s)\n",
  1123. progname, VERSION, REVISION);
  1124. exit(EXIT_SUCCESS);
  1125. case AGENCY:
  1126. strlcpy(agency, optarg, sizeof(agency));
  1127. break;
  1128. case ANT_E:
  1129. ant_e = safe_atof(optarg);
  1130. break;
  1131. case ANT_H:
  1132. ant_h = safe_atof(optarg);
  1133. break;
  1134. case ANT_N:
  1135. ant_n = safe_atof(optarg);
  1136. break;
  1137. case ANT_NUM:
  1138. strlcpy(ant_num, optarg, sizeof(ant_num));
  1139. break;
  1140. case ANT_TYPE:
  1141. strlcpy(ant_type, optarg, sizeof(ant_type));
  1142. break;
  1143. case MARKER_NAME:
  1144. strlcpy(marker_name, optarg, sizeof(marker_name));
  1145. break;
  1146. case MARKER_TYPE:
  1147. strlcpy(marker_type, optarg, sizeof(marker_type));
  1148. break;
  1149. case OBSERVER:
  1150. strlcpy(observer, optarg, sizeof(observer));
  1151. break;
  1152. case REC_NUM:
  1153. strlcpy(rec_num, optarg, sizeof(rec_num));
  1154. break;
  1155. case REC_TYPE:
  1156. strlcpy(rec_type, optarg, sizeof(rec_type));
  1157. break;
  1158. case REC_VERS:
  1159. strlcpy(rec_vers, optarg, sizeof(rec_vers));
  1160. break;
  1161. case 'h':
  1162. FALLTHROUGH
  1163. case '?':
  1164. FALLTHROUGH
  1165. default:
  1166. usage();
  1167. /* NOTREACHED */
  1168. }
  1169. }
  1170. /* init source defaults */
  1171. source.server = (char *)"localhost";
  1172. source.port = (char *)DEFAULT_GPSD_PORT;
  1173. source.device = NULL;
  1174. if (optind < argc) {
  1175. /* in this case, switch to the method "socket" always */
  1176. gpsd_source_spec(argv[optind], &source);
  1177. }
  1178. if (DEBUG_INFO <= debug) {
  1179. char *device;
  1180. if (NULL == source.device) {
  1181. device = "Default";
  1182. } else {
  1183. device = source.device;
  1184. }
  1185. (void)fprintf(stderr, "INFO: server: %s port: %s device: %s\n",
  1186. source.server, source.port, device);
  1187. }
  1188. /* save start time of report */
  1189. (void)clock_gettime(CLOCK_REALTIME, &start_time);
  1190. report_time = gmtime_r(&(start_time.tv_sec), &tm_buf);
  1191. /* open the output file */
  1192. if (NULL == fname) {
  1193. (void)strftime(tmstr, sizeof(tmstr), "gpsrinex%Y%j%H%M%S.obs",
  1194. report_time);
  1195. fname = tmstr;
  1196. }
  1197. log_file = fopen(fname, "w");
  1198. if (log_file == NULL) {
  1199. syslog(LOG_ERR, "ERROR: Failed to open %s: %s",
  1200. fname, strerror(errno));
  1201. exit(3);
  1202. }
  1203. /* clear the counts */
  1204. memset(obs_cnt, 0, sizeof(obs_cnt));
  1205. /* catch all interesting signals */
  1206. (void)signal(SIGTERM, quit_handler);
  1207. (void)signal(SIGQUIT, quit_handler);
  1208. (void)signal(SIGINT, quit_handler);
  1209. if (gps_open(source.server, source.port, &gpsdata) != 0) {
  1210. (void)fprintf(stderr, "%s: no gpsd running or network error: %d, %s\n",
  1211. progname, errno, gps_errstr(errno));
  1212. exit(EXIT_FAILURE);
  1213. }
  1214. if (source.device != NULL)
  1215. flags |= WATCH_DEVICE;
  1216. (void)gps_stream(&gpsdata, flags, source.device);
  1217. // create temp file, coverity does not like tmpfile()
  1218. // covarfity wants a umask
  1219. (void)umask(0177); // force rw-r--r--
  1220. strlcpy(tmp_fname, "/tmp/gpsrinexXXXXXX", sizeof(tmp_fname));
  1221. tmp_file_desc = mkstemp(tmp_fname);
  1222. if (0 > tmp_file_desc) {
  1223. (void)fprintf(stderr, "ERROR: mkstemp(%s) failed: %s\n",
  1224. tmp_fname, strerror(errno));
  1225. exit(2);
  1226. }
  1227. tmp_file = fdopen(tmp_file_desc, "w+");
  1228. if (NULL == tmp_file) {
  1229. (void)fprintf(stderr, "ERROR: fdopen() failed: %s\n",
  1230. strerror(errno));
  1231. exit(2);
  1232. }
  1233. for (;;) {
  1234. /* wait for gpsd */
  1235. if (!gps_waiting(&gpsdata, timeout * 1000000)) {
  1236. (void)fprintf(stderr, "gpsrinex: timeout\n");
  1237. syslog(LOG_INFO, "timeout;");
  1238. break;
  1239. }
  1240. (void)gps_read(&gpsdata, NULL, 0);
  1241. if (ERROR_SET & gpsdata.set) {
  1242. fprintf(stderr, "gps_read() error '%s'\n", gpsdata.error);
  1243. exit(6);
  1244. }
  1245. conditionally_log_fix(&gpsdata);
  1246. if (0 >= sample_count) {
  1247. /* done */
  1248. break;
  1249. }
  1250. }
  1251. print_rinex_footer();
  1252. // remove the temp file
  1253. (void)unlink(tmp_fname);
  1254. exit(EXIT_SUCCESS);
  1255. }
  1256. // vim: set expandtab shiftwidth=4