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.

gpsrinex.c 33KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147
  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.03
  48. * ftp://igs.org/pub/data/format/rinex303.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 (c) 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 <stdio.h>
  65. #include <stdlib.h>
  66. #include <stdbool.h>
  67. #include <string.h>
  68. #include <math.h>
  69. #include <time.h>
  70. #include <errno.h>
  71. #include <libgen.h>
  72. #include <signal.h>
  73. #include <assert.h>
  74. #include <unistd.h>
  75. #include "gps.h"
  76. #include "gpsdclient.h"
  77. #include "revision.h"
  78. #include "os_compat.h"
  79. static char *progname;
  80. static struct fixsource_t source;
  81. static double ecefx = 0.0;
  82. static double ecefy = 0.0;
  83. static double ecefz = 0.0;
  84. static timespec_t start_time = {0}; /* report gen time, UTC */
  85. static timespec_t first_mtime = {0}; /* GPS time, not UTC */
  86. static timespec_t last_mtime = {0}; /* GPS time, not UTC */
  87. /* total count of observations by u-blox gnssid [0-6]
  88. * 0 = GPS RINEX G
  89. * 1 = SBAS RINEX S
  90. * 2 = Galileo RINEX E
  91. * 3 - BeiDou RINEX C
  92. * 4 = IMES not supported by RINEX
  93. * 5 = QZSS RINEX J
  94. * 6 = GLONASS RINEX R
  95. * 7 = IRNSS RINEX I
  96. *
  97. * RINEX 3 observation codes [1]:
  98. * C1C L1 C/A Pseudorange
  99. * C1P L1 P Pseudorange
  100. * C1W L1 Z-tracking Pseudorange
  101. * D1C L1 C/A Doppler
  102. * L1C L1 C/A Carrier Phase
  103. * L1P L1 P Carrier Phase
  104. * L1W L1 Z-tracking Carrier Phase
  105. * C2C L2 C/A Pseudorange
  106. * C2P L2 P Pseudorange
  107. * C2W L2 Z-tracking Pseudorange
  108. * D2C L2 C/A Doppler
  109. * L2C L2 C/A Carrier phase
  110. * L2P L1 P Carrier Phase
  111. * L2W L2 Z-tracking Carrier Phase
  112. *
  113. * C2L L2C (L), Pseudo Range, BeiDou
  114. * D2L L2C (L), Doppler, BeiDou
  115. * L2L L2C (L), Carrier Phase, BeiDou
  116. *
  117. * L5I L5 I Pseudo Range
  118. * C5I L5 I Carrier Phase
  119. * D5I L5 I Doppler
  120. *
  121. * CSRS-PPP supports:
  122. * GPS: C1C L1C C2C L2C C1W L1W C2W L2W
  123. * GLONASS : C1C L1C C2C L2C C1P L1P C2P L2P
  124. *
  125. */
  126. typedef enum {C1C = 0, D1C, L1C,
  127. C2C, D2C, L2C,
  128. C2L, D2L, L2L,
  129. C5I, D5I, L5I,
  130. C7I, D7I, L7I,
  131. C7Q, D7Q, L7Q, CODEMAX} obs_codes;
  132. /* structure to hold count of observations by gnssid:svid
  133. * MAXCHANNEL+1 is just a WAG of max size */
  134. #define MAXCNT (MAXCHANNELS + 1)
  135. static struct obs_cnt_t {
  136. unsigned char gnssid;
  137. unsigned char svid; /* svid of 0 means unused slot */
  138. unsigned int obs_cnts[CODEMAX+1]; /* count of obscode */
  139. } obs_cnt[MAXCNT] = {{0}};
  140. static FILE * tmp_file; /* file handle for temp file */
  141. static int sample_count = 20; /* number of measurement sets to get */
  142. /* seconds between measurement sets */
  143. static unsigned int sample_interval = 30;
  144. #define DEBUG_QUIET 0
  145. #define DEBUG_INFO 1
  146. #define DEBUG_PROG 2
  147. #define DEBUG_RAW 3
  148. static int debug = DEBUG_INFO; /* debug level */
  149. static struct gps_data_t gpsdata;
  150. static FILE *log_file;
  151. /* convert a u-blox/gpsd gnssid to the RINEX 3 constellation code
  152. * see [1] Section 3.5
  153. */
  154. static char gnssid2rinex(int gnssid)
  155. {
  156. switch (gnssid) {
  157. case GNSSID_GPS: /* 0 = GPS */
  158. return 'G';
  159. case GNSSID_SBAS: /* 1 = SBAS */
  160. return 'S';
  161. case GNSSID_GAL: /* 2 = Galileo */
  162. return 'E';
  163. case GNSSID_BD: /* 3 = BeiDou */
  164. return 'C';
  165. case GNSSID_IMES: /* 4 = IMES - unsupported */
  166. return 'X';
  167. case GNSSID_QZSS: /* 5 = QZSS */
  168. return 'J';
  169. case GNSSID_GLO: /* 6 = GLONASS */
  170. return 'R';
  171. case GNSSID_IRNSS: /* 7 = IRNSS */
  172. return 'I';
  173. default: /* Huh? */
  174. return 'x';
  175. }
  176. }
  177. /* obs_cnt_inc()
  178. *
  179. * increment an observation count
  180. */
  181. static void obs_cnt_inc(unsigned char gnssid, unsigned char svid,
  182. obs_codes obs_code)
  183. {
  184. int i;
  185. if (CODEMAX <= obs_code) {
  186. /* should never happen... */
  187. fprintf(stderr, "ERROR: obs_code_inc() obs_code %d out of range\n",
  188. obs_code);
  189. exit(1);
  190. }
  191. /* yeah, slow and ugly, linear search. */
  192. for (i = 0; i < MAXCNT; i++) {
  193. if (0 == obs_cnt[i].svid) {
  194. /* end of list, not found, so add this gnssid:svid */
  195. obs_cnt[i].gnssid = gnssid;
  196. obs_cnt[i].svid = svid;
  197. obs_cnt[i].obs_cnts[obs_code] = 1;
  198. break;
  199. }
  200. if (obs_cnt[i].gnssid != gnssid) {
  201. continue;
  202. }
  203. if (obs_cnt[i].svid != svid) {
  204. continue;
  205. }
  206. /* found it, increment it */
  207. obs_cnt[i].obs_cnts[obs_code]++;
  208. if (99999 < obs_cnt[i].obs_cnts[obs_code]) {
  209. /* RINEX 3 max is 99999 */
  210. obs_cnt[i].obs_cnts[obs_code] = 99999;
  211. }
  212. break;
  213. }
  214. /* fell out because table full, item added, or item incremented */
  215. return;
  216. }
  217. /* compare two obs_cnt, for sorting by gnssid, and svid */
  218. static int compare_obs_cnt(const void *A, const void *B)
  219. {
  220. const struct obs_cnt_t *a = (const struct obs_cnt_t *)A;
  221. const struct obs_cnt_t *b = (const struct obs_cnt_t *)B;
  222. unsigned char a_gnssid = a->gnssid;
  223. unsigned char b_gnssid = b->gnssid;
  224. /* 0 = svid means unused, make those last */
  225. if (0 == a->svid) {
  226. a_gnssid = 255;
  227. }
  228. if (0 == b->svid) {
  229. b_gnssid = 255;
  230. }
  231. if (a_gnssid != b_gnssid) {
  232. return a_gnssid - b_gnssid;
  233. }
  234. /* put unused last */
  235. if (a->svid != b->svid) {
  236. return a->svid - b->svid;
  237. }
  238. /* two blank records */
  239. return 0;
  240. }
  241. /* return number of unique PRN in a gnssid from obs_cnt.
  242. * return all PRNs if 255 == gnssid */
  243. static int obs_cnt_prns(unsigned char gnssid)
  244. {
  245. int i;
  246. int prn_cnt = 0;
  247. for (i = 0; i < MAXCNT; i++) {
  248. if (0 == obs_cnt[i].svid) {
  249. /* end of list, done */
  250. break;
  251. }
  252. if ((255 != gnssid) && (gnssid != obs_cnt[i].gnssid)) {
  253. /* wrong gnssid */
  254. continue;
  255. }
  256. prn_cnt++;
  257. }
  258. /* fell out because table full, item added, or item incremented */
  259. return prn_cnt;
  260. }
  261. /* print_rinex_header()
  262. * Print a RINEX 3 header to the file "log_file".
  263. * Some of the data in the header is only known after processing all
  264. * the raw data.
  265. */
  266. static void print_rinex_header(void)
  267. {
  268. int i, j;
  269. char tmstr[40]; /* time: yyyymmdd hhmmss UTC */
  270. struct tm *report_time;
  271. struct tm *first_time;
  272. struct tm *last_time;
  273. int cnt; /* number of obs for one sat */
  274. int prn_count[GNSSID_CNT] = {0}; /* count of PRN per gnssid */
  275. if (DEBUG_PROG <= debug) {
  276. (void)fprintf(stderr, "doing header\n");
  277. }
  278. report_time = gmtime(&(start_time.tv_sec));
  279. (void)strftime(tmstr, sizeof(tmstr), "%Y%m%d %H%M%S UTC", report_time);
  280. (void)fprintf(log_file,
  281. "%9s%11s%-20s%-20s%-20s\n",
  282. "3.03", "", "OBSERVATION DATA", "M: Mixed", "RINEX VERSION / TYPE");
  283. (void)fprintf(log_file,
  284. "%-20s%-20s%-20s%-20s\n",
  285. "gpsrinex 3.20", "", tmstr,
  286. "PGM / RUN BY / DATE");
  287. (void)fprintf(log_file, "%-60s%-20s\n",
  288. "Source: gpsd live data", "COMMENT");
  289. (void)fprintf(log_file, "%-60s%-20s\n", "XXXX", "MARKER NAME");
  290. (void)fprintf(log_file, "%-60s%-20s\n", "NON_PHYSICAL", "MARKER TYPE");
  291. (void)fprintf(log_file, "%-20s%-20s%-20s%-20s\n",
  292. "Unknown", "Unknown", "", "OBSERVER / AGENCY");
  293. (void)fprintf(log_file, "%-20s%-20s%-20s%-20s\n",
  294. "0", "UNKNOWN", "0", "REC # / TYPE / VERS");
  295. (void)fprintf(log_file, "%-20s%-20s%-20s%-20s\n",
  296. "0", "UNKNOWN EXT NONE", "" , "ANT # / TYPE");
  297. if (isfinite(ecefx) &&
  298. isfinite(ecefy) &&
  299. isfinite(ecefz)) {
  300. (void)fprintf(log_file, "%14.4f%14.4f%14.4f%18s%-20s\n",
  301. ecefx, ecefy, ecefz, "", "APPROX POSITION XYZ");
  302. } else if (DEBUG_INFO <= debug) {
  303. (void)fprintf(stderr, "INFO: missing ECEF\n");
  304. }
  305. (void)fprintf(log_file, "%14.4f%14.4f%14.4f%18s%-20s\n",
  306. 0.0, 0.0, 0.0, "", "ANTENNA: DELTA H/E/N");
  307. (void)fprintf(log_file, "%6d%6d%48s%-20s\n", 1, 1,
  308. "", "WAVELENGTH FACT L1/2");
  309. /* get PRN stats */
  310. qsort(obs_cnt, MAXCNT, sizeof(struct obs_cnt_t), compare_obs_cnt);
  311. for (i = 0; i < GNSSID_CNT; i++ ) {
  312. prn_count[i] = obs_cnt_prns(i);
  313. }
  314. /* CSRS-PPP needs C1C, L1C or C1C, L1C, D1C
  315. * CSRS-PPP refuses files with L1C first
  316. * convbin wants C1C, L1C, D1C
  317. * for some reason gfzrnx_lx wants C1C, D1C, L1C, not C1C, L1C, D1C */
  318. if (0 < prn_count[GNSSID_GPS]) {
  319. /* GPS, code G */
  320. (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n",
  321. gnssid2rinex(GNSSID_GPS), 5, "C1C", "L1C", "D1C", "C2C", "L2C",
  322. "D2C", "", "", "", "SYS / # / OBS TYPES");
  323. }
  324. if (0 < prn_count[GNSSID_SBAS]) {
  325. /* SBAS, L1 and L5 only, code S */
  326. (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n",
  327. gnssid2rinex(GNSSID_SBAS), 3, "C1C", "L1C", "D1C", "", "", "",
  328. "", "", "", "SYS / # / OBS TYPES");
  329. }
  330. if (0 < prn_count[GNSSID_GAL]) {
  331. /* Galileo, E1, E5 aand E6 only, code E */
  332. (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n",
  333. gnssid2rinex(GNSSID_GAL), 3, "C1C", "L1C", "D1C", "C7Q",
  334. "L7Q", "D7Q", "", "", "", "SYS / # / OBS TYPES");
  335. }
  336. if (0 < prn_count[GNSSID_BD]) {
  337. /* BeiDou, BDS, code C */
  338. (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n",
  339. gnssid2rinex(GNSSID_BD), 5, "C1C", "L1C", "D1C", "C7I", "L7I",
  340. "D7I", "", "", "", "SYS / # / OBS TYPES");
  341. }
  342. if (0 < prn_count[GNSSID_QZSS]) {
  343. /* QZSS, code J */
  344. (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n",
  345. gnssid2rinex(GNSSID_QZSS), 5, "C1C", "L1C", "D1C", "C2L",
  346. "L2L", "D2L", "", "", "", "SYS / # / OBS TYPES");
  347. }
  348. if (0 < prn_count[GNSSID_GLO]) {
  349. /* GLONASS, R */
  350. (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n",
  351. gnssid2rinex(GNSSID_GLO), 5, "C1C", "L1C", "D1C", "C2C", "L2C",
  352. "D2C", "", "", "", "SYS / # / OBS TYPES");
  353. }
  354. (void)fprintf(log_file, "%6d%54s%-20s\n", obs_cnt_prns(255),
  355. "", "# OF SATELLITES");
  356. /* get all the PRN / # OF OBS */
  357. for (i = 0; i < MAXCNT; i++) {
  358. cnt = 0;
  359. if (0 == obs_cnt[i].svid) {
  360. /* done */
  361. break;
  362. }
  363. for (j = 0; j < CODEMAX; j++) {
  364. cnt += obs_cnt[i].obs_cnts[j];
  365. }
  366. if (0 > cnt) {
  367. /* no counts for this sat */
  368. continue;
  369. }
  370. switch (obs_cnt[i].gnssid) {
  371. case GNSSID_GPS:
  372. /* GPS, code G */
  373. (void)fprintf(log_file," %c%02d%6u%6u%6u%6u%6u%6u%18s%-20s\n",
  374. gnssid2rinex(obs_cnt[i].gnssid), obs_cnt[i].svid,
  375. obs_cnt[i].obs_cnts[C1C],
  376. obs_cnt[i].obs_cnts[L1C],
  377. obs_cnt[i].obs_cnts[D1C],
  378. obs_cnt[i].obs_cnts[C2C],
  379. obs_cnt[i].obs_cnts[L2C],
  380. obs_cnt[i].obs_cnts[D2C],
  381. "", "PRN / # OF OBS");
  382. break;
  383. case GNSSID_SBAS:
  384. /* SBAS, L1C and L5C, code S */
  385. (void)fprintf(log_file," %c%02d%6u%6u%6u%6u%6u%6u%18s%-20s\n",
  386. gnssid2rinex(obs_cnt[i].gnssid), obs_cnt[i].svid,
  387. obs_cnt[i].obs_cnts[C1C],
  388. obs_cnt[i].obs_cnts[L1C],
  389. obs_cnt[i].obs_cnts[D1C],
  390. obs_cnt[i].obs_cnts[C5I],
  391. obs_cnt[i].obs_cnts[L5I],
  392. obs_cnt[i].obs_cnts[D5I],
  393. "", "PRN / # OF OBS");
  394. break;
  395. case GNSSID_GAL:
  396. /* Galileo, code E */
  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[C7Q],
  403. obs_cnt[i].obs_cnts[L7Q],
  404. obs_cnt[i].obs_cnts[D7Q],
  405. "", "PRN / # OF OBS");
  406. break;
  407. case GNSSID_BD:
  408. /* BeiDou, code C */
  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[C7I],
  415. obs_cnt[i].obs_cnts[L7I],
  416. obs_cnt[i].obs_cnts[D7I],
  417. "", "PRN / # OF OBS");
  418. break;
  419. case GNSSID_QZSS:
  420. /* QZSS, code J */
  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[C2L],
  427. obs_cnt[i].obs_cnts[L2L],
  428. obs_cnt[i].obs_cnts[D2L],
  429. "", "PRN / # OF OBS");
  430. break;
  431. case GNSSID_GLO:
  432. /* GLONASS, code R */
  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[C2C],
  439. obs_cnt[i].obs_cnts[L2C],
  440. obs_cnt[i].obs_cnts[D2C],
  441. "", "PRN / # OF OBS");
  442. break;
  443. default:
  444. (void)fprintf(log_file," %c%02d%6u%6u%6u%6s%6s%24s%-20s\n",
  445. gnssid2rinex(obs_cnt[i].gnssid), obs_cnt[i].svid,
  446. obs_cnt[i].obs_cnts[C1C],
  447. obs_cnt[i].obs_cnts[L1C],
  448. obs_cnt[i].obs_cnts[D1C],
  449. "", "",
  450. "", "PRN / # OF OBS");
  451. }
  452. }
  453. (void)fprintf(log_file, "%10.3f%50s%-20s\n",
  454. (double)sample_interval, "", "INTERVAL");
  455. /* GPS time not UTC */
  456. first_time = gmtime(&(first_mtime.tv_sec));
  457. (void)fprintf(log_file, "%6d%6d%6d%6d%6d%5d.%07ld%8s%9s%-20s\n",
  458. first_time->tm_year + 1900,
  459. first_time->tm_mon + 1,
  460. first_time->tm_mday,
  461. first_time->tm_hour,
  462. first_time->tm_min,
  463. first_time->tm_sec,
  464. (long)(first_mtime.tv_nsec / 100),
  465. "GPS", "",
  466. "TIME OF FIRST OBS");
  467. /* GPS time not UTC */
  468. last_time = gmtime(&(last_mtime.tv_sec));
  469. (void)fprintf(log_file, "%6d%6d%6d%6d%6d%5d.%07ld%8s%9s%-20s\n",
  470. last_time->tm_year + 1900,
  471. last_time->tm_mon + 1,
  472. last_time->tm_mday,
  473. last_time->tm_hour,
  474. last_time->tm_min,
  475. last_time->tm_sec,
  476. (long)(last_mtime.tv_nsec / 100),
  477. "GPS", "",
  478. "TIME OF LAST OBS");
  479. if (0 < prn_count[GNSSID_GPS]) {
  480. /* GPS, code G */
  481. (void)fprintf(log_file, "%-60s%-20s\n",
  482. "G L1C", "SYS / PHASE SHIFT");
  483. (void)fprintf(log_file, "%-60s%-20s\n",
  484. "G L2C", "SYS / PHASE SHIFT");
  485. }
  486. if (0 < prn_count[GNSSID_SBAS]) {
  487. /* SBAS, L1 and L5 only, code S */
  488. (void)fprintf(log_file, "%-60s%-20s\n",
  489. "S L1C", "SYS / PHASE SHIFT");
  490. (void)fprintf(log_file, "%-60s%-20s\n",
  491. "E L5Q", "SYS / PHASE SHIFT");
  492. }
  493. if (0 < prn_count[GNSSID_GAL]) {
  494. /* GALILEO, E1, E5 and E6, code E */
  495. (void)fprintf(log_file, "%-60s%-20s\n",
  496. "E L1C", "SYS / PHASE SHIFT");
  497. (void)fprintf(log_file, "%-60s%-20s\n",
  498. "E L7Q", "SYS / PHASE SHIFT");
  499. }
  500. if (0 < prn_count[GNSSID_BD]) {
  501. /* BeiDou, code C */
  502. (void)fprintf(log_file, "%-60s%-20s\n",
  503. "B L1C", "SYS / PHASE SHIFT");
  504. (void)fprintf(log_file, "%-60s%-20s\n",
  505. "B L7I", "SYS / PHASE SHIFT");
  506. }
  507. if (0 < prn_count[GNSSID_QZSS]) {
  508. /* QZSS, code J */
  509. (void)fprintf(log_file, "%-60s%-20s\n",
  510. "J L1C", "SYS / PHASE SHIFT");
  511. (void)fprintf(log_file, "%-60s%-20s\n",
  512. "J L2L", "SYS / PHASE SHIFT");
  513. }
  514. if (0 < prn_count[GNSSID_GLO]) {
  515. /* GLONASS, code R */
  516. (void)fprintf(log_file, "%-60s%-20s\n",
  517. "R L1C", "SYS / PHASE SHIFT");
  518. (void)fprintf(log_file, "%-60s%-20s\n",
  519. "R L2C", "SYS / PHASE SHIFT");
  520. }
  521. (void)fprintf(log_file, "%-60s%-20s\n",
  522. "", "END OF HEADER");
  523. if (DEBUG_PROG <= debug) {
  524. (void)fprintf(stderr,"done header\n");
  525. }
  526. return;
  527. }
  528. /* print_rinex_footer()
  529. * print a RINEX 3 footer to the file "log_file".
  530. * Except RINEX 3 has no footer. So what this really does is
  531. * call the header function, then move the processed observations from
  532. * "tmp_file" to "log_file".
  533. */
  534. static void print_rinex_footer(void)
  535. {
  536. char buffer[4096];
  537. /* print the header */
  538. print_rinex_header();
  539. /* now replay the data in the tmp_file into the output */
  540. (void)fflush(tmp_file);
  541. rewind(tmp_file);
  542. while (true) {
  543. size_t count;
  544. count = fread(buffer, 1, sizeof(buffer), tmp_file);
  545. if (0 >= count ) {
  546. break;
  547. }
  548. (void)fwrite(buffer, 1, count, log_file);
  549. }
  550. (void)fclose(tmp_file);
  551. (void)fclose(log_file);
  552. (void)gps_close(&gpsdata);
  553. }
  554. /* compare two meas_t, for sorting by gnssid, svid, and sigid */
  555. static int compare_meas(const void *A, const void *B)
  556. {
  557. const struct meas_t *a = (const struct meas_t*)A;
  558. const struct meas_t *b = (const struct meas_t*)B;
  559. if (a->gnssid != b->gnssid) {
  560. return a->gnssid - b->gnssid;
  561. }
  562. if (a->svid != b->svid) {
  563. return a->svid - b->svid;
  564. }
  565. if (a->sigid != b->sigid) {
  566. return a->sigid - b->sigid;
  567. }
  568. /* two blank records */
  569. return 0;
  570. }
  571. /* convert an observation item and return it as a (F14,3,I1,I1)
  572. * in a static buffer */
  573. static const char * fmt_obs(double val, unsigned char lli, unsigned char snr)
  574. {
  575. static char buf[20];
  576. char lli_c; /* set zero lli to blank */
  577. char snr_c; /* set zero snr to blank */
  578. if (!isfinite(val)) {
  579. /* bad value, return 16 blanks */
  580. return " ";
  581. }
  582. switch (lli) {
  583. case 0:
  584. default:
  585. lli_c = ' ';
  586. break;
  587. case 1:
  588. lli_c = '1';
  589. break;
  590. case 2:
  591. lli_c = '2';
  592. break;
  593. case 3:
  594. lli_c = '3';
  595. break;
  596. }
  597. if ((1 > snr) || (9 < snr)) {
  598. snr_c = ' ';
  599. } else {
  600. snr_c = 48 + snr;
  601. }
  602. (void)snprintf(buf, sizeof(buf), "%14.3f%c%1c", val, lli_c, snr_c);
  603. return buf;
  604. }
  605. /* one_sig() - print one signal
  606. *
  607. * one CxC s LxC DxC
  608. */
  609. static void one_sig(struct meas_t *meas)
  610. {
  611. unsigned char snr;
  612. unsigned gnssid = meas->gnssid;
  613. unsigned svid = meas->svid;
  614. unsigned sigid = meas->sigid;
  615. obs_codes cxx = C1C;
  616. obs_codes lxx = L1C;
  617. obs_codes dxx = D1C;
  618. if (DEBUG_PROG <= debug) {
  619. (void)fprintf(stderr, "INFO: one_sig() %c %u:%u:%u\n",
  620. gnssid2rinex(gnssid),
  621. gnssid, svid, sigid);
  622. }
  623. switch (sigid) {
  624. default:
  625. (void)fprintf(stderr, "ERROR: one_sig() gnmssid %u unknown sigid %u\n",
  626. gnssid, sigid);
  627. /* FALLTHROUGH */
  628. case 0:
  629. /* L1C */
  630. cxx = C1C;
  631. lxx = L1C;
  632. dxx = D1C;
  633. break;
  634. case 2:
  635. /* GLONASS L2 OF or BeiDou B2I D1 */
  636. if (GNSSID_BD == gnssid) {
  637. /* WAG */
  638. cxx = C7I;
  639. lxx = L7I;
  640. dxx = D7I;
  641. } else {
  642. cxx = C2C;
  643. lxx = L2C;
  644. dxx = D2C;
  645. }
  646. break;
  647. case 3:
  648. /* GPS L2 or BD B2I D2 */
  649. cxx = C2C;
  650. lxx = L2C;
  651. dxx = D2C;
  652. break;
  653. case 5:
  654. /* QZSS L2C (L) */
  655. cxx = C2L;
  656. lxx = L2L;
  657. dxx = D2L;
  658. break;
  659. case 6:
  660. /* Galileo E5 bQ */
  661. cxx = C7Q;
  662. lxx = L7Q;
  663. dxx = D7Q;
  664. break;
  665. }
  666. /* map snr to RINEX snr flag [1-9] */
  667. if (0 == meas->snr) {
  668. snr = 0;
  669. } else if (12 > meas->snr) {
  670. snr = 1;
  671. } else if (18 >= meas->snr) {
  672. snr = 2;
  673. } else if (23 >= meas->snr) {
  674. snr = 3;
  675. } else if (29 >= meas->snr) {
  676. snr = 4;
  677. } else if (35 >= meas->snr) {
  678. snr = 5;
  679. } else if (41 >= meas->snr) {
  680. snr = 6;
  681. } else if (47 >= meas->snr) {
  682. snr = 7;
  683. } else if (53 >= meas->snr) {
  684. snr = 8;
  685. } else {
  686. /* snr >= 54 */
  687. snr = 9;
  688. }
  689. /* check for slip */
  690. /* FIXME: use actual interval */
  691. if (meas->locktime < (sample_interval * 1000)) {
  692. meas->lli |= 2;
  693. }
  694. if (0 != isfinite(meas->pseudorange)) {
  695. obs_cnt_inc(gnssid, svid, cxx);
  696. }
  697. if (0 != isfinite(meas->carrierphase)) {
  698. obs_cnt_inc(gnssid, svid, lxx);
  699. }
  700. if (0 != isfinite(meas->doppler)) {
  701. obs_cnt_inc(gnssid, svid, dxx);
  702. }
  703. (void)fputs(fmt_obs(meas->pseudorange, 0, snr), tmp_file);
  704. (void)fputs(fmt_obs(meas->carrierphase, meas->lli, 0), tmp_file);
  705. (void)fputs(fmt_obs(meas->doppler, 0, 0), tmp_file);
  706. }
  707. /* print_raw()
  708. * print one epoch of observations into "tmp_file"
  709. */
  710. static void print_raw(struct gps_data_t *gpsdata)
  711. {
  712. struct tm *tmp_now;
  713. unsigned nrec = 0;
  714. unsigned nsat = 0;
  715. unsigned i;
  716. unsigned char last_gnssid = 0;
  717. unsigned char last_svid = 0;
  718. int need_nl = 0;
  719. int got_l1 = 0;
  720. if ((last_mtime.tv_sec + (time_t)sample_interval) >
  721. gpsdata->raw.mtime.tv_sec) {
  722. /* not time yet */
  723. return;
  724. }
  725. /* opus insists (time % interval) = 0 */
  726. if (0 != (gpsdata->raw.mtime.tv_sec % sample_interval)) {
  727. return;
  728. }
  729. /* RINEX 3 wants records in each epoch sorted by gnssid.
  730. * To look nice: sort by gnssid and svid
  731. * To work nice, sort by gnssid, svid and sigid.
  732. * Each sigid is one record in RAW, but all sigid is one
  733. * record in RINEX
  734. */
  735. /* go through list three times, first just to get a count for sort */
  736. for (i = 0; i < MAXCHANNELS; i++) {
  737. if (0 == gpsdata->raw.meas[i].svid) {
  738. /* bad svid, end of list */
  739. break;
  740. }
  741. nrec++;
  742. }
  743. if (0 >= nrec) {
  744. /* nothing to do */
  745. return;
  746. }
  747. qsort(gpsdata->raw.meas, nrec, sizeof(gpsdata->raw.meas[0]),
  748. compare_meas);
  749. /* second just to get a count, needed for epoch header */
  750. for (i = 0; i < nrec; i++) {
  751. if (0 == gpsdata->raw.meas[i].svid) {
  752. /* bad svid */
  753. continue;
  754. }
  755. if (4 == gpsdata->raw.meas[i].gnssid) {
  756. /* skip IMES */
  757. continue;
  758. }
  759. if (GNSSID_CNT <= gpsdata->raw.meas[i].gnssid) {
  760. /* invalid gnssid */
  761. continue;
  762. }
  763. /* prevent separate sigid from double counting gnssid:svid */
  764. if ((last_gnssid == gpsdata->raw.meas[i].gnssid) &&
  765. (last_svid == gpsdata->raw.meas[i].svid)) {
  766. /* duplicate sat */
  767. continue;
  768. }
  769. last_gnssid = gpsdata->raw.meas[i].gnssid;
  770. last_svid = gpsdata->raw.meas[i].svid;
  771. nsat++;
  772. }
  773. if (0 >= nsat) {
  774. /* nothing to do */
  775. return;
  776. }
  777. /* save time of last measurement, GPS time, not UTC */
  778. last_mtime = gpsdata->raw.mtime; /* structure copy */
  779. if (0 == first_mtime.tv_sec) {
  780. /* save time of first measurement */
  781. first_mtime = last_mtime; /* structure copy */
  782. }
  783. /* print epoch header line */
  784. tmp_now = gmtime(&(last_mtime.tv_sec));
  785. (void)fprintf(tmp_file,"> %4d %02d %02d %02d %02d %02d.%07ld 0%3u\n",
  786. tmp_now->tm_year + 1900,
  787. tmp_now->tm_mon + 1,
  788. tmp_now->tm_mday,
  789. tmp_now->tm_hour,
  790. tmp_now->tm_min,
  791. tmp_now->tm_sec,
  792. (long)(last_mtime.tv_nsec / 100), nsat);
  793. last_gnssid = 0;
  794. last_svid = 0;
  795. need_nl = 0;
  796. got_l1 = 0;
  797. /* Print the observations, one gnssid:svid per line.
  798. * The fun is merging consecutive records (new sigid) of
  799. * same gnssid:svid */
  800. for (i = 0; i < nrec; i++) {
  801. char rinex_gnssid;
  802. unsigned char gnssid;
  803. unsigned char svid;
  804. unsigned char sigid;
  805. gnssid = gpsdata->raw.meas[i].gnssid;
  806. rinex_gnssid = gnssid2rinex(gnssid);
  807. svid = gpsdata->raw.meas[i].svid;
  808. sigid = gpsdata->raw.meas[i].sigid;
  809. if (DEBUG_RAW <= debug) {
  810. (void)fprintf(stderr,"record: %u:%u:%u %s\n",
  811. gnssid, svid, sigid,
  812. gpsdata->raw.meas[i].obs_code);
  813. }
  814. if (0 == gpsdata->raw.meas[i].svid) {
  815. /* should not happen... */
  816. continue;
  817. }
  818. /* line can be longer than 80 chars in RINEX 3 */
  819. if ((last_gnssid != gpsdata->raw.meas[i].gnssid) ||
  820. (last_svid != gpsdata->raw.meas[i].svid)) {
  821. if (0 != need_nl) {
  822. (void)fputs("\n", tmp_file);
  823. }
  824. got_l1 = 0;
  825. /* new record line gnssid:svid preamble */
  826. (void)fprintf(tmp_file,"%c%02d", rinex_gnssid, svid);
  827. }
  828. last_gnssid = gpsdata->raw.meas[i].gnssid;
  829. last_svid = gpsdata->raw.meas[i].svid;
  830. /* L1x */
  831. switch (gpsdata->raw.meas[i].sigid) {
  832. case 0:
  833. /* L1 */
  834. one_sig(&gpsdata->raw.meas[i]);
  835. got_l1 = 1;
  836. break;
  837. case 2:
  838. /* GLONASS L2 OF or BD B2I D1 */
  839. if (0 == got_l1) {
  840. /* space to start of L2 */
  841. (void)fprintf(tmp_file, "%48s", "");
  842. }
  843. one_sig(&gpsdata->raw.meas[i]);
  844. break;
  845. case 3:
  846. /* GPS L2 or BD B2I D2 */
  847. if (0 == got_l1) {
  848. /* space to start of L2 */
  849. (void)fprintf(tmp_file, "%48s", "");
  850. }
  851. one_sig(&gpsdata->raw.meas[i]);
  852. break;
  853. case 5:
  854. /* QZSS L2C (L) */
  855. if (0 == got_l1) {
  856. /* space to start of L2 */
  857. (void)fprintf(tmp_file, "%48s", "");
  858. }
  859. one_sig(&gpsdata->raw.meas[i]);
  860. break;
  861. case 6:
  862. /* Galileo E5 bQ */
  863. if (0 == got_l1) {
  864. /* space to start of L2 */
  865. (void)fprintf(tmp_file, "%48s", "");
  866. }
  867. one_sig(&gpsdata->raw.meas[i]);
  868. break;
  869. default:
  870. (void)fprintf(stderr,
  871. "ERROR: print_raw() gnssid %u unknown sigid %u\n",
  872. gnssid, sigid);
  873. break;
  874. }
  875. need_nl = 1;
  876. }
  877. if (0 != need_nl) {
  878. (void)fputs("\n", tmp_file);
  879. }
  880. sample_count--;
  881. }
  882. /* quit_handler()
  883. * quit nicely on ^C. That is: print the header and observation records
  884. * gathered so far. Then exit.
  885. */
  886. static void quit_handler(int signum)
  887. {
  888. /* don't clutter the logs on Ctrl-C */
  889. if (signum != SIGINT)
  890. syslog(LOG_INFO, "exiting, signal %d received", signum);
  891. print_rinex_footer();
  892. (void)gps_close(&gpsdata);
  893. exit(EXIT_SUCCESS);
  894. }
  895. /* conditionally_log_fix()
  896. * take the new gpsdata and decide what to do with it.
  897. */
  898. static void conditionally_log_fix(struct gps_data_t *gpsdata)
  899. {
  900. if (DEBUG_PROG <= debug) {
  901. /* The (long long unsigned) is for 32/64-bit compatibility */
  902. (void)fprintf(stderr, "mode %d set %llx\n", gpsdata->fix.mode,
  903. (long long unsigned)gpsdata->set);
  904. }
  905. /* mostly we don't care if 2D or 3D fix, let the post processor
  906. * decide */
  907. if (MODE_2D < gpsdata->fix.mode) {
  908. /* got a good 3D fix */
  909. if (1.0 > ecefx &&
  910. isfinite(gpsdata->fix.ecef.x) &&
  911. isfinite(gpsdata->fix.ecef.y) &&
  912. isfinite(gpsdata->fix.ecef.z)) {
  913. /* save ecef for "APPROX POS" */
  914. ecefx = gpsdata->fix.ecef.x;
  915. ecefy = gpsdata->fix.ecef.y;
  916. ecefz = gpsdata->fix.ecef.z;
  917. if (DEBUG_PROG <= debug) {
  918. (void)fprintf(stderr,"got ECEF\n");
  919. }
  920. }
  921. }
  922. if (RAW_SET & gpsdata->set) {
  923. if (DEBUG_RAW <= debug) {
  924. (void)fprintf(stderr,"got RAW\n");
  925. }
  926. print_raw(gpsdata);
  927. }
  928. return;
  929. }
  930. /* usage()
  931. * print usages, and exit
  932. */
  933. static void usage(void)
  934. {
  935. (void)fprintf(stderr,
  936. "Usage: %s [OPTIONS] [server[:port:[device]]]\n"
  937. " [-D debuglevel] Set debug level, default 0\n"
  938. " [-f filename] out to filename\n"
  939. " gpsrinexYYYYDDDDHHMM.obs\n"
  940. " [-h] print this usage and exit\n"
  941. " [-i interval] time between samples, default: %d\n"
  942. " [-n count] number samples to collect, default: %d\n"
  943. " [-V] print version and exit\n"
  944. "\n"
  945. "defaults to '%s -n %d -i %d localhost:2947'\n",
  946. progname, sample_interval, sample_count, progname, sample_count,
  947. sample_interval);
  948. exit(EXIT_FAILURE);
  949. }
  950. /*
  951. *
  952. * Main
  953. *
  954. */
  955. int main(int argc, char **argv)
  956. {
  957. char tmstr[40]; /* time: YYYYDDDMMHH */
  958. struct tm *report_time;
  959. int ch;
  960. unsigned int flags = WATCH_ENABLE;
  961. char *fname = NULL;
  962. int timeout = 10;
  963. progname = argv[0];
  964. log_file = stdout;
  965. while ((ch = getopt(argc, argv, "D:f:hi:n:V")) != -1) {
  966. switch (ch) {
  967. case 'D':
  968. debug = atoi(optarg);
  969. gps_enable_debug(debug, log_file);
  970. break;
  971. case 'f': /* Output file name. */
  972. fname = strdup(optarg);
  973. break;
  974. case 'i': /* set sampling interval */
  975. sample_interval = (time_t) atoi(optarg);
  976. if (sample_interval < 1)
  977. sample_interval = 1;
  978. if (sample_interval >= 3600)
  979. (void)fprintf(stderr,
  980. "WARNING: saample interval is an hour or more!\n");
  981. break;
  982. case 'n':
  983. sample_count = atoi(optarg);
  984. break;
  985. case 'V':
  986. (void)fprintf(stderr, "%s: version %s (revision %s)\n",
  987. progname, VERSION, REVISION);
  988. exit(EXIT_SUCCESS);
  989. default:
  990. usage();
  991. /* NOTREACHED */
  992. }
  993. }
  994. /* init source defaults */
  995. source.server = (char *)"localhost";
  996. source.port = (char *)DEFAULT_GPSD_PORT;
  997. source.device = NULL;
  998. if (optind < argc) {
  999. /* in this case, switch to the method "socket" always */
  1000. gpsd_source_spec(argv[optind], &source);
  1001. }
  1002. if (DEBUG_INFO <= debug) {
  1003. (void)fprintf(stderr, "INFO: server: %s port: %s device: %s\n",
  1004. source.server, source.port, source.device);
  1005. }
  1006. /* save start time of report */
  1007. (void)clock_gettime(CLOCK_REALTIME, &start_time);
  1008. report_time = gmtime(&(start_time.tv_sec));
  1009. /* open the output file */
  1010. if (NULL == fname) {
  1011. (void)strftime(tmstr, sizeof(tmstr), "gpsrinex%Y%j%H%M%S.obs",
  1012. report_time);
  1013. fname = tmstr;
  1014. }
  1015. log_file = fopen(fname, "w");
  1016. if (log_file == NULL) {
  1017. syslog(LOG_ERR, "ERROR: Failed to open %s: %s",
  1018. fname, strerror(errno));
  1019. exit(3);
  1020. }
  1021. /* clear the counts */
  1022. memset(obs_cnt, 0, sizeof(obs_cnt));
  1023. /* catch all interesting signals */
  1024. (void)signal(SIGTERM, quit_handler);
  1025. (void)signal(SIGQUIT, quit_handler);
  1026. (void)signal(SIGINT, quit_handler);
  1027. if (gps_open(source.server, source.port, &gpsdata) != 0) {
  1028. (void)fprintf(stderr, "%s: no gpsd running or network error: %d, %s\n",
  1029. progname, errno, gps_errstr(errno));
  1030. exit(EXIT_FAILURE);
  1031. }
  1032. if (source.device != NULL)
  1033. flags |= WATCH_DEVICE;
  1034. (void)gps_stream(&gpsdata, flags, source.device);
  1035. tmp_file = tmpfile();
  1036. if (NULL == tmp_file) {
  1037. (void)fprintf(stderr, "ERROR: could not open temp file: %s\n",
  1038. strerror(errno));
  1039. exit(2);
  1040. }
  1041. for (;;) {
  1042. /* wait for gpsd */
  1043. if (!gps_waiting(&gpsdata, timeout * 1000000)) {
  1044. (void)fprintf(stderr, "gpsrinex: timeout\n");
  1045. syslog(LOG_INFO, "timeout;");
  1046. break;
  1047. }
  1048. (void)gps_read(&gpsdata, NULL, 0);
  1049. if (ERROR_SET & gpsdata.set) {
  1050. fprintf(stderr, "gps_read() error '%s'\n", gpsdata.error);
  1051. exit(6);
  1052. }
  1053. conditionally_log_fix(&gpsdata);
  1054. if (0 >= sample_count) {
  1055. /* done */
  1056. break;
  1057. }
  1058. }
  1059. print_rinex_footer();
  1060. exit(EXIT_SUCCESS);
  1061. }