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