diff options
author | Gary E. Miller <gem@rellim.com> | 2018-11-09 09:48:13 -0800 |
---|---|---|
committer | Gary E. Miller <gem@rellim.com> | 2018-11-09 09:48:13 -0800 |
commit | 52c9138aacbaf0300af6b3a3ffbc77271f9d5019 (patch) | |
tree | e472d999a717d85d38f78c1c94b8932d319aa520 /gpsrinex.c | |
parent | ed2ccb1317e825acb42fa4d852255757cb48685f (diff) | |
download | gpsd-52c9138aacbaf0300af6b3a3ffbc77271f9d5019.tar.gz |
gpsrinex: A new tool to create RINEX 3 obs files.
Diffstat (limited to 'gpsrinex.c')
-rw-r--r-- | gpsrinex.c | 862 |
1 files changed, 862 insertions, 0 deletions
diff --git a/gpsrinex.c b/gpsrinex.c new file mode 100644 index 00000000..6a46cccc --- /dev/null +++ b/gpsrinex.c @@ -0,0 +1,862 @@ +/* + * gpsrinex: read "RAW" messages from a gpsd and output a RINEX 3 obs file. + * + * This file is Copyright (c) 2010 by the GPSD project + * SPDX-License-Identifier: BSD-2-clause + * + * gpsrinex will read live data from gpsd and create a file of RINEX 3 + * observations. Currently this only works if the GPS is a u-blox + * GPS and is sending UBX-RXM-RAWX messages. + * + * The u-blox must be configured for u-blox binary messages. GLONASS, + * GALILEO, and BEIDOU must be off. Optionally SBAS on, but can be + * flakey. + * + * Too much data for 9600! + * + * To configure a u-blox to output the proper data: + * # gpsctl -s 115200 + * # sleep 2 + * # ubxtool -d NMEA + * # ubstool -e BINARY + * # ubxtool -d GLONASS + * # ubxtool -d BEIDOU + * # ubxtool -d GALILEO + * # ubxtool -d SBAS + * # ubxtool -e RAWX + * + * After collecting the default number of observations, gpsrinex will + * create the file 'gpsrinx.obx' and exit. Upload this file to an + * offline processing service to get cm accuracy. + * + * One service known to work with obsrinex output is: + * https://webapp.geod.nrcan.gc.ca/geod/tools-outils/ppp.php + * + * Examples: + * To collect 4 hours of samples as 30 second intervals: + * # gpsrinex -i 30 -n 480 + * + * To generate RINEX 3 from a u-blox capture file: + * Grab 4 hours of raw live data: + * # gpspipe -x 14400 -R > 4h-raw.ubx + * Feed that data to gpsfake: + * # gpsfake -1 -P 3000 4h-raw.ubx + * In another window, convert that raw to RINEX 3: + * # gpsrinex -i 1 -n 1000000 + * + * See also: + * [1] RINEX: The Receiver Independent Exchange Format, Version 3.03 + * ftp://igs.org/pub/data/format/rinex303.pdf + * + * [2] GPSTk, http://www.gpstk.org/ + * + * [3] Nischan, Thomas (2016): + * GFZRNX - RINEX GNSS Data Conversion and Manipulation Toolbox. + * GFZ Data Services. http://dx.doi.org/10.5880/GFZ.1.1.2016.002 + * + * [4] RTKLIB: An Open Source Program Package for GNSS Positioning + * http://www.rtklib.com/ + */ + +#ifndef _XOPEN_SOURCE +/* need >= 500 for strdup() */ +#define _XOPEN_SOURCE 500 +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <math.h> +#include <time.h> +#include <errno.h> +#include <libgen.h> +#include <signal.h> +#include <assert.h> +#include <unistd.h> + +#include "gps.h" +#include "gpsd_config.h" +#include "gpsdclient.h" +#include "revision.h" +#include "os_compat.h" + +static char *progname; +static struct fixsource_t source; +static double ecefx = 0.0; +static double ecefy = 0.0; +static double ecefz = 0.0; +static timespec_t start_time = {0}; /* report gen time, UTC */ +static timespec_t first_mtime = {0}; /* GPS time, not UTC */ +static timespec_t last_mtime = {0}; /* GPS time, not UTC */ + +/* total count of observations by gnssid [0-6] + * 0 = GPS RINEX G + * 1 = SBAS RINEX S + * 2 = Galileo RINEX E + * 3 - BeiDou RINEX C + * 4 = IMES not supported by RINEX + * 5 = QZSS RINEX J + * 6 = GLONASS RINEX R + * IRNSS RINEX I + */ + +/* observation codes: + * C1C L1 Pseudorange + * D1C L1 Doppler + * L1C L1 carrier phase + */ +typedef enum {C1C = 0, D1C, L1C, CODEMAX} obs_codes; +/* structure to hold count of observations by gnssid:svid + * MAXCHANNEL*4 is just a WAG of max size */ +#define MAXCNT (MAXCHANNELS * 4) +static struct obs_cnt_t { + unsigned char gnssid; + unsigned char svid; /* svid of 0 means unused slot */ + obs_codes obs_cnts[CODEMAX]; + unsigned int count; /* count of obscode */ +} obs_cnt[MAXCNT] = {0}; + +static FILE * tmp_file; /* file handle for temp file */ +static int sample_count = 20; /* number of measurement sets to get */ +/* seconds between measurement sets */ +static unsigned int sample_interval = 30; +static int debug = 0; /* debug level */ +static struct gps_data_t gpsdata; +static FILE *log_file; + +/* convert a gnssid to the RINEX 3 constellation code + * see [1] Section 3.5 + */ +static char gnssid2rinex(int gnssid) +{ + switch (gnssid) { + case 0: /* 0 = GPS */ + return 'G'; + case 1: /* 1 = SBAS */ + return 'S'; + case 2: /* 2 = Galileo */ + return 'E'; + case 3: /* 3 = BeiDou */ + return 'C'; + case 4: /* 4 = IMES - unsupported */ + return 'X'; + case 5: /* 5 = QZSS */ + return 'J'; + case 6: /* 6 = GLONASS */ + return 'R'; + default: /* Huh? */ + return 'x'; + } +} + +/* obs_cnt_inc() + * + * increment an observation count + */ +static void obs_cnt_inc(unsigned char gnssid, unsigned char svid, + obs_codes obs_code) +{ + int i; + + if (CODEMAX <= obs_code) { + /* should never happen... */ + fprintf(stderr, "ERROR: obs_code_inc() obs_code %d out of range\n", + obs_code); + exit(1); + } + /* yeah, slow and ugly, linear search. */ + for (i = 0; i < MAXCNT; i++) { + if (0 == obs_cnt[i].svid) { + /* end of list, not found, so add this item */ + obs_cnt[i].gnssid = gnssid; + obs_cnt[i].svid = svid; + obs_cnt[i].obs_cnts[obs_code] = 1; + break; + } + if (obs_cnt[i].gnssid != gnssid) { + continue; + } + if (obs_cnt[i].svid != svid) { + continue; + } + /* found it, increment it */ + obs_cnt[i].obs_cnts[obs_code]++; + if (99999 < obs_cnt[i].obs_cnts[obs_code]) { + /* RINEX 3 max is 99999 */ + obs_cnt[i].obs_cnts[obs_code] = 99999; + } + break; + } + /* fell out because table full, item added, or item incremented */ + return; +} + +/* compare two obs_cnt, for sorting by gnssid, and svid */ +static int compare_obs_cnt(const void *A, const void *B) +{ + const struct obs_cnt_t *a = (const struct obs_cnt_t *)A; + const struct obs_cnt_t *b = (const struct obs_cnt_t *)B; + unsigned char a_svid; + unsigned char b_svid; + + if (a->gnssid != b->gnssid) { + return a->gnssid - b->gnssid; + } + /* put unused last */ + if (a->svid != b->svid) { + a_svid = a->svid; + b_svid = b->svid; + if (0 == a_svid) { + a_svid = 255; + } + if (0 == b_svid) { + b_svid = 255; + } + return a_svid - b_svid; + } + /* two blank records */ + return 0; +} + +/* return number of unique PRN in a gnssid from obs_cnt. + * return all PRNs if 255 == gnssid */ +static int obs_cnt_prns(unsigned char gnssid) +{ + int i; + int prn_cnt = 0; + + for (i = 0; i < MAXCNT; i++) { + if (0 == obs_cnt[i].svid) { + /* end of list, done */ + break; + } + if ((255 != gnssid) && (gnssid != obs_cnt[i].gnssid)) { + /* wrong gnssid */ + continue; + } + prn_cnt++; + } + /* fell out because table full, item added, or item incremented */ + return prn_cnt; +} + +/* print_rinex_header() + * Print a RINEX 3 header to the file "log_file". + * Some of the data in the header is only known after processing all + * the raw data. + */ +static void print_rinex_header(void) +{ + int i, j; + char tmstr[40]; /* time: yyyymmdd hhmmss UTC */ + struct tm *report_time; + struct tm *first_time; + struct tm *last_time; + int cnt; /* number of obs for one sat */ + int prn_count[7] = {0}; /* count of PRN per gnssid */ + + if ( 3 < debug) { + (void)fprintf(stderr,"doing header\n"); + } + + report_time = gmtime(&(start_time.tv_sec)); + (void)strftime(tmstr, sizeof(tmstr), "%Y%m%d %H%M%S UTC", report_time); + + (void)fprintf(log_file, + "%9s%11s%-20s%-20s%-20s\n", + "3.03", "", "OBSERVATION DATA", "M: Mixed", "RINEX VERSION / TYPE"); + (void)fprintf(log_file, + "%-20s%-20s%-20s%-20s\n", + "gpsrinex 3.19-dev", "", tmstr, + "PGM / RUN BY / DATE"); + (void)fprintf(log_file, "%-60s%-20s\n", + "Source: gpsd live data", "COMMENT"); + (void)fprintf(log_file, "%-60s%-20s\n", "XXXX", "MARKER NAME"); + (void)fprintf(log_file, "%-60s%-20s\n", "NON_PHYSICAL", "MARKER TYPE"); + (void)fprintf(log_file, "%-20s%-20s%-20s%-20s\n", + "Unknown", "Unknown", "", "OBSERVER / AGENCY"); + (void)fprintf(log_file, "%-20s%-20s%-20s%-20s\n", + "", "", "", "REC # / TYPE / VERS"); + (void)fprintf(log_file, "%-20s%-20s%-16s%4s%-20s\n", + "", "", "", "NONE", "ANT # / TYPE"); + (void)fprintf(log_file, "%14.4f%14.4f%14.4f%18s%-20s\n", + ecefx, ecefy, ecefz, "", "APPROX POSITION XYZ"); + (void)fprintf(log_file, "%14.4f%14.4f%14.4f%18s%-20s\n", + 0.0, 0.0, 0.0, "", "ANTENNA: DELTA H/E/N"); + (void)fprintf(log_file, "%6d%6d%48s%-20s\n", 1, 1, + "", "WAVELENGTH FACT L1/2"); + + /* get PRN stats */ + qsort(obs_cnt, MAXCNT, sizeof(struct obs_cnt_t), compare_obs_cnt); + for (i = 0; i < 7; i++ ) { + prn_count[i] = obs_cnt_prns(i); + } + /* CSRS-PPP needs C1C, L1C or C1C, L1C, D1C */ + /* convbin wants C1C, L1C, D1C */ + /* for some reason gfzrnx_lx wants C1C, D1C, L1C, not C1C, L1C, D1C */ + if (0 < prn_count[0]) { + /* GPS */ + (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n", + gnssid2rinex(0), 3, "C1C", "L1C", "D1C", "", "", "", "", "", "", + "SYS / # / OBS TYPES"); + } + if (0 < prn_count[1]) { + /* SBAS */ + (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n", + gnssid2rinex(1), 3, "C1C", "L1C", "D1C", "", "", "", "", "", "", + "SYS / # / OBS TYPES"); + } + if (0 < prn_count[2]) { + /* GALILEO */ + (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n", + gnssid2rinex(2), 3, "C1A", "L1A", "D1A", "", "", "", "", "", "", + "SYS / # / OBS TYPES"); + } + if (0 < prn_count[3]) { + /* BeiDou, BDS */ + (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n", + gnssid2rinex(3), 3, "C1I", "L1I", "D1I", "", "", "", "", "", "", + "SYS / # / OBS TYPES"); + } + if (0 < prn_count[5]) { + /* QZSS */ + (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n", + gnssid2rinex(5), 3, "C1C", "L1C", "D1C", "", "", "", "", "", "", + "SYS / # / OBS TYPES"); + } + if (0 < prn_count[6]) { + /* GLONASS */ + (void)fprintf(log_file, "%c%5d%4s%4s%4s%4s%4s%4s%4s%4s%22s%-20s\n", + gnssid2rinex(6), 3, "C1C", "L1C", "D1C", "", "", "", "", "", "", + "SYS / # / OBS TYPES"); + } + + (void)fprintf(log_file, "%6d%54s%-20s\n", obs_cnt_prns(255), + "", "# OF SATELLITES"); + /* get all the PRN / # OF OBS */ + for (i = 0; i <= MAXCNT; i++) { + cnt = 0; + + if (0 == obs_cnt[i].svid) { + /* done */ + break; + } + for (j = 0; j <= CODEMAX; j++) { + cnt += obs_cnt[i].obs_cnts[j]; + } + if (0 > cnt) { + /* no counts for this sat */ + continue; + } + (void)fprintf(log_file," %c%02d%6u%6u%6u%36s%-20s\n", + gnssid2rinex(obs_cnt[i].gnssid), obs_cnt[i].svid, + obs_cnt[i].obs_cnts[0], + obs_cnt[i].obs_cnts[1], + obs_cnt[i].obs_cnts[2], + "", "PRN / # OF OBS"); + } + + (void)fprintf(log_file, "%10.3f%50s%-20s\n", + (double)sample_interval, "", "INTERVAL"); + + /* GPS time not UTC */ + first_time = gmtime(&(first_mtime.tv_sec)); + (void)fprintf(log_file, "%6d%6d%6d%6d%6d%5d.%07ld%8s%9s%-20s\n", + first_time->tm_year + 1900, + first_time->tm_mon + 1, + first_time->tm_mday, + first_time->tm_hour, + first_time->tm_min, + first_time->tm_sec, + (long)(first_mtime.tv_nsec / 100), + "GPS", "", + "TIME OF FIRST OBS"); + + /* GPS time not UTC */ + last_time = gmtime(&(last_mtime.tv_sec)); + (void)fprintf(log_file, "%6d%6d%6d%6d%6d%5d.%07ld%8s%9s%-20s\n", + last_time->tm_year + 1900, + last_time->tm_mon + 1, + last_time->tm_mday, + last_time->tm_hour, + last_time->tm_min, + last_time->tm_sec, + (long)(last_mtime.tv_nsec / 100), + "GPS", "", + "TIME OF LAST OBS"); + if (0 < prn_count[0]) { + /* GPS */ + (void)fprintf(log_file, "%-60s%-20s\n", + "G L1C", "SYS / PHASE SHIFT"); + } + if (0 < prn_count[1]) { + /* SBAS */ + (void)fprintf(log_file, "%-60s%-20s\n", + "S L1C", "SYS / PHASE SHIFT"); + } + if (0 < prn_count[2]) { + /* GALILEO */ + (void)fprintf(log_file, "%-60s%-20s\n", + "E L1C", "SYS / PHASE SHIFT"); + } + if (0 < prn_count[3]) { + /* BeiDou */ + (void)fprintf(log_file, "%-60s%-20s\n", + "B L1C", "SYS / PHASE SHIFT"); + } + if (0 < prn_count[5]) { + /* QZSS */ + (void)fprintf(log_file, "%-60s%-20s\n", + "J L1C", "SYS / PHASE SHIFT"); + } + if (0 < prn_count[6]) { + /* GLONASS */ + (void)fprintf(log_file, "%-60s%-20s\n", + "R L1I", "SYS / PHASE SHIFT"); + } + (void)fprintf(log_file, "%-60s%-20s\n", + "", "END OF HEADER"); + if ( 3 < debug) { + (void)fprintf(stderr,"done header\n"); + } + return; +} + +/* print_rinex_footer() + * print a RINEX 3 footer to the file "log_file". + * Except RINEX 3 has no footer. So what this really does is + * call the header function, then move the processed observations from + * "tmp_file" to "log_file". + */ +static void print_rinex_footer(void) +{ + char buffer[4096]; + + /* print the header */ + print_rinex_header(); + /* now replay the data in the tmp_file into the output */ + (void)fflush(tmp_file); + rewind(tmp_file); + while (true) { + size_t count; + + count = fread(buffer, 1, sizeof(buffer), tmp_file); + if (0 >= count ) { + break; + } + (void)fwrite(buffer, 1, count, log_file); + } + (void)fclose(tmp_file); + (void)fclose(log_file); + (void)gps_close(&gpsdata); +} + +/* compare two meas_t, for sorting by gnssid, svid, and sigid */ +static int compare_meas(const void *A, const void *B) +{ + const struct meas_t *a = (const struct meas_t*)A; + const struct meas_t *b = (const struct meas_t*)B; + + if (a->gnssid != b->gnssid) { + return a->gnssid - b->gnssid; + } + if (a->svid != b->svid) { + return a->svid - b->svid; + } + if (a->sigid != b->sigid) { + return a->sigid - b->sigid; + } + /* two blank records */ + return 0; +} + + +/* convert an observation item and return it as a (F14,3,I1,I1) + * in a static buffer */ +static const char * fmt_obs(double val, unsigned char lli, unsigned char snr) +{ + static char buf[20]; + char lli_c; /* set zero lli to blank */ + char snr_c; /* set zero snr to blank */ + + if (!isfinite(val)) { + /* bad value, return 16 blanks */ + return " "; + } + switch (lli) { + case 0: + default: + lli_c = ' '; + break; + case 1: + lli_c = '1'; + break; + case 2: + lli_c = '2'; + break; + case 3: + lli_c = '3'; + break; + } + if ((1 > snr) || (9 < snr)) { + snr_c = ' '; + } else { + snr_c = 48 + snr; + } + (void)snprintf(buf, sizeof(buf), "%14.3f%c%1c", val, lli_c, snr_c); + return buf; +} + +/* print_raw() + * print one epoch of observations into "tmp_file" + */ +static void print_raw(struct gps_data_t *gpsdata) +{ + struct tm *tmp_now; + int nsat = 0; + int i; + + if ( (last_mtime.tv_sec + sample_interval) > gpsdata->raw.mtime.tv_sec ) { + /* not time yet */ + return; + } + + /* go through list twice, first just to get a count */ + for (i = 0; i < MAXCHANNELS; i++) { + if (0 == gpsdata->raw.meas[i].svid) { + continue; + } + if (4 == gpsdata->raw.meas[i].gnssid) { + /* skip IMES */ + continue; + } + if (6 < gpsdata->raw.meas[i].gnssid) { + /* invalid gnssid */ + continue; + } + nsat++; + } + if (0 >= nsat) { + /* nothing to do */ + return; + } + + /* save time of last measurement, GPS time, not UTC */ + last_mtime = gpsdata->raw.mtime; /* structure copy */ + if (0 == first_mtime.tv_sec) { + /* save time of first measurement */ + first_mtime = last_mtime; /* structure copy */ + } + + tmp_now = gmtime(&(last_mtime.tv_sec)); + (void)fprintf(tmp_file,"> %4d %02d %02d %02d %02d %02d.%07ld 0%3d\n", + tmp_now->tm_year + 1900, + tmp_now->tm_mon + 1, + tmp_now->tm_mday, + tmp_now->tm_hour, + tmp_now->tm_min, + tmp_now->tm_sec, + (long)(last_mtime.tv_nsec / 100), nsat); + + /* RINEX 3 wants records in each epoch sorted by gnssid. + * To look nice: sort by gnssid and svid */ + qsort(gpsdata->raw.meas, MAXCHANNELS, sizeof(gpsdata->raw.meas[0]), + compare_meas); + for (i = 0; i < MAXCHANNELS; i++) { + char gnssid; + char svid; + unsigned char snr; + + if (0 == gpsdata->raw.meas[i].svid) { + continue; + } + + svid = gpsdata->raw.meas[i].svid; + gnssid = gnssid2rinex(gpsdata->raw.meas[i].gnssid); + + switch (gpsdata->raw.meas[i].gnssid) { + case 0: + /* GPS */ + break; + case 1: + /* SBAS, per section 8.4 of RINEX 3.03 spec */ + break; + case 2: + /* GALILEO */ + break; + case 3: + /* Beidou */ + break; + case 4: + /* IMES */ + /* FALLTHROUGH */ + default: + /* huh? */ + continue; + case 5: + /* QZSS */ + break; + case 6: + /* GLONASS */ + break; + } + + /* map snr to RINEX snr flag [1-9] */ + if ( 12 > gpsdata->raw.meas[i].snr) { + snr = 1; + } else if ( 18 >= gpsdata->raw.meas[i].snr) { + snr = 2; + } else if ( 23 >= gpsdata->raw.meas[i].snr) { + snr = 3; + } else if ( 29 >= gpsdata->raw.meas[i].snr) { + snr = 4; + } else if ( 35 >= gpsdata->raw.meas[i].snr) { + snr = 5; + } else if ( 41 >= gpsdata->raw.meas[i].snr) { + snr = 6; + } else if ( 47 >= gpsdata->raw.meas[i].snr) { + snr = 7; + } else if ( 53 >= gpsdata->raw.meas[i].snr) { + snr = 8; + } else { + /* snr >= 54 */ + snr = 9; + } + /* check for slip */ + /* FIXME: use actual interval */ + if (gpsdata->raw.meas[i].locktime < (sample_interval * 1000)) { + gpsdata->raw.meas[i].lli |= 2; + } + + if (0 != isfinite(gpsdata->raw.meas[i].pseudorange)) { + obs_cnt_inc(gpsdata->raw.meas[i].gnssid, gpsdata->raw.meas[i].svid, + C1C); + } + + if (0 != isfinite(gpsdata->raw.meas[i].doppler)) { + obs_cnt_inc(gpsdata->raw.meas[i].gnssid, gpsdata->raw.meas[i].svid, + D1C); + } + + if (0 != isfinite(gpsdata->raw.meas[i].carrierphase)) { + obs_cnt_inc(gpsdata->raw.meas[i].gnssid, gpsdata->raw.meas[i].svid, + L1C); + } + + (void)fprintf(tmp_file,"%c%02d", gnssid, svid); + (void)fputs(fmt_obs(gpsdata->raw.meas[i].pseudorange, 0, snr), + tmp_file); + (void)fputs(fmt_obs(gpsdata->raw.meas[i].carrierphase, + gpsdata->raw.meas[i].lli, 0), tmp_file); + (void)fputs(fmt_obs(gpsdata->raw.meas[i].doppler, 0, 0), + tmp_file); + (void)fputs("\n", tmp_file); + + nsat--; + } + sample_count--; + if (0 != nsat) { + (void)fprintf(stderr,"ERROR: satellite count mismatch %d\n", nsat); + exit(1); + } +} + +/* quit_handler() + * quit nicely on ^C. That is: print the header and observation records + * gathered so far. Then exit. + */ +static void quit_handler(int signum) +{ + /* don't clutter the logs on Ctrl-C */ + if (signum != SIGINT) + syslog(LOG_INFO, "exiting, signal %d received", signum); + print_rinex_footer(); + (void)gps_close(&gpsdata); + exit(EXIT_SUCCESS); +} + +/* conditionally_log_fix() + * take the new gpsdata and decide what to do with it. + */ +static void conditionally_log_fix(struct gps_data_t *gpsdata) +{ + if ( 4 < debug) { + (void)fprintf(tmp_file,"mode %d set %lx\n", gpsdata->fix.mode, + gpsdata->set); + } + + /* mostly we don't care if 2D or 3D fix, let the post processor + * decide */ + + if ((MODE_2D < gpsdata->fix.mode) && + (LATLON_SET & gpsdata->set)) { + /* got a good 3D fix */ + if (isfinite(gpsdata->fix.ecef.x) && + isfinite(gpsdata->fix.ecef.y) && + isfinite(gpsdata->fix.ecef.z)) { + /* save ecef for "APPROX POS" */ + ecefx = gpsdata->fix.ecef.x; + ecefy = gpsdata->fix.ecef.y; + ecefz = gpsdata->fix.ecef.z; + } + if ( 3 < debug) { + (void)fprintf(stderr,"got ECEF\n"); + } + } + + if (RAW_SET & gpsdata->set) { + if ( 3 < debug) { + (void)fprintf(stderr,"got RAW\n"); + } + print_raw(gpsdata); + } + return; +} + +/* usage() + * print usages, and exit + */ +static void usage(void) +{ + (void)fprintf(stderr, + "Usage: %s [OPTIONS] [server[:port:[device]]]\n" + " [-D debuglevel] Set debug level, default 0\n" + " [-f filename] out to filename\n" + " gpsrinexYYYYDDDDHHMM.obs\n" + " [-h] print this usage and exit\n" + " [-i interval] time between samples\n" + " [-n count] number samples to collect, default: %d\n" + " [-V] print version and exit\n" + "defaults to '%s -n %d -i %d localhost:2947'\n", + progname, sample_count, progname, sample_count, sample_interval); + exit(EXIT_FAILURE); +} + +/* + * + * Main + * + */ +int main(int argc, char **argv) +{ + char tmstr[40]; /* time: YYYYDDDMMHH */ + struct tm *report_time; + int ch; + unsigned int flags = WATCH_ENABLE; + char *fname = NULL; + int timeout = 10; + + progname = argv[0]; + + log_file = stdout; + while ((ch = getopt(argc, argv, "D:f:hi:n:V")) != -1) { + switch (ch) { + case 'D': + debug = atoi(optarg); + gps_enable_debug(debug, log_file); + break; + case 'f': /* Output file name. */ + fname = strdup(optarg); + break; + case 'i': /* set sampling interval */ + sample_interval = (time_t) atoi(optarg); + if (sample_interval < 1) + sample_interval = 1; + if (sample_interval >= 3600) + (void)fprintf(stderr, + "WARNING: saample interval is an hour or more!\n"); + break; + case 'n': + sample_count = atoi(optarg); + break; + case 'V': + (void)fprintf(stderr, "%s: version %s (revision %s)\n", + progname, VERSION, REVISION); + exit(EXIT_SUCCESS); + default: + usage(); + /* NOTREACHED */ + } + } + + if ( 1 ) { + source.server = (char *)"localhost"; + source.port = (char *)DEFAULT_GPSD_PORT; + source.device = NULL; + } + + if (optind < argc) { + /* in this case, switch to the method "socket" always */ + gpsd_source_spec(argv[optind], &source); + } + if ( 2 < debug ) { + (void)fprintf(stderr,"INFO: server: %s port: %s device: %s\n", + source.server, source.port, source.device); + } + + /* save start time of report */ + (void)clock_gettime(CLOCK_REALTIME, &start_time); + report_time = gmtime(&(start_time.tv_sec)); + + /* open the output file */ + if (NULL == fname) { + (void)strftime(tmstr, sizeof(tmstr), "gpsrinex%Y%j%H%M%S.obs", + report_time); + fname = tmstr; + } + log_file = fopen(fname, "w"); + if (log_file == NULL) { + syslog(LOG_ERR, "ERROR: Failed to open %s: %s", + fname, strerror(errno)); + exit(3); + } + + /* clear the counts */ + memset(obs_cnt, 0, sizeof(obs_cnt)); + + /* catch all interesting signals */ + (void)signal(SIGTERM, quit_handler); + (void)signal(SIGQUIT, quit_handler); + (void)signal(SIGINT, quit_handler); + + if (gps_open(source.server, source.port, &gpsdata) != 0) { + (void)fprintf(stderr, "%s: no gpsd running or network error: %d, %s\n", + progname, errno, gps_errstr(errno)); + exit(EXIT_FAILURE); + } + + if (source.device != NULL) + flags |= WATCH_DEVICE; + (void)gps_stream(&gpsdata, flags, source.device); + + tmp_file = tmpfile(); + if (NULL == tmp_file) { + (void)fprintf(stderr, "ERROR: could not open temp file: %s\n", + strerror(errno)); + exit(2); + } + + for (;;) { + /* wait for gpsd */ + if (!gps_waiting(&gpsdata, timeout * 1000000)) { + (void)fprintf(stderr, "gpsrinex: timeout\n"); + syslog(LOG_INFO, "timeout;"); + break; + } + (void)gps_read(&gpsdata, NULL, 0); + if (ERROR_SET & gpsdata.set) { + fprintf(stderr, "gps_read() error '%s'\n", gpsdata.error); + exit(6); + } + conditionally_log_fix(&gpsdata); + if ( 0 >= sample_count) { + /* done */ + break; + } + } + + print_rinex_footer(); + + exit(EXIT_SUCCESS); +} |