/* / _____) _ | | ( (____ _____ ____ _| |_ _____ ____| |__ \____ \| ___ | (_ _) ___ |/ ___) _ \ _____) ) ____| | | || |_| ____( (___| | | | (______/|_____)_|_|_| \__)_____)\____)_| |_| (C)2013 Semtech-Cycleo Description: Library of functions to manage a GNSS module (typically GPS) for accurate timestamping of packets and synchronisation of gateways. A limited set of module brands/models are supported. License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Michael Coracin */ /* -------------------------------------------------------------------------- */ /* --- DEPENDANCIES --------------------------------------------------------- */ #define _GNU_SOURCE /* needed for qsort_r to be defined */ #include /* C99 types */ #include /* bool type */ #include /* printf fprintf */ #include /* memcpy */ #include /* struct timespec */ #include /* open */ #include /* tcflush */ #include /* modf */ #include #include "loragw_gps.h" /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #if DEBUG_GPS == 1 #define DEBUG_MSG(args...) fprintf(stderr, args) #define DEBUG_ARRAY(a,b,c) for(a=0;a= buff_size) { DEBUG_MSG("Maximum length reached for nmea_checksum\n"); return -1; } } /* Convert checksum value to 2 hexadecimal characters */ checksum[0] = nibble_to_hexchar(check_num / 16); /* upper nibble */ checksum[1] = nibble_to_hexchar(check_num % 16); /* lower nibble */ return i + 1; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ static char nibble_to_hexchar(uint8_t a) { if (a < 10) { return '0' + a; } else if (a < 16) { return 'A' + (a-10); } else { return '?'; } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* Calculate the checksum of a NMEA frame and compare it to the checksum that is present at the end of it. Return true if it matches */ static bool validate_nmea_checksum(const char *serial_buff, int buff_size) { int checksum_index; char checksum[2]; /* 2 characters to calculate NMEA checksum */ checksum_index = nmea_checksum(serial_buff, buff_size, checksum); /* could we calculate a verification checksum ? */ if (checksum_index < 0) { DEBUG_MSG("ERROR: IMPOSSIBLE TO PARSE NMEA SENTENCE\n"); return false; } /* check if there are enough char in the serial buffer to read checksum */ if (checksum_index >= (buff_size - 2)) { DEBUG_MSG("ERROR: IMPOSSIBLE TO READ NMEA SENTENCE CHECKSUM\n"); return false; } /* check the checksum per se */ if ((serial_buff[checksum_index] == checksum[0]) && (serial_buff[checksum_index+1] == checksum[1])) { return true; } else { DEBUG_MSG("ERROR: NMEA CHECKSUM %c%c DOESN'T MATCH VERIFICATION CHECKSUM %c%c\n", serial_buff[checksum_index], serial_buff[checksum_index+1], checksum[0], checksum[1]); return false; } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* Return true if the "label" string (can contain wildcard characters) matches the begining of the "s" string */ static bool match_label(const char *s, char *label, int size, char wildcard) { int i; for (i=0; i < size; i++) { if (label[i] == wildcard) continue; if (label[i] != s[i]) return false; } return true; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* Chop a string into smaller strings Replace every separator in the input character buffer by a null character so that all s[index] are valid strings. Populate an array of integer 'idx_ary' representing indexes of token in the string. buff_size and max_idx are there to prevent segfaults. Return the number of token found (number of idx_ary filled). */ int str_chop(char *s, int buff_size, char separator, int *idx_ary, int max_idx) { int i = 0; /* index in the string */ int j = 0; /* index in the result array */ if ((s == NULL) || (buff_size < 0) || (separator == 0) || (idx_ary == NULL) || (max_idx < 0)) { /* unsafe to do anything */ return -1; } if ((buff_size == 0) || (max_idx == 0)) { /* nothing to do */ return 0; } s[buff_size - 1] = 0; /* add string terminator at the end of the buffer, just to be sure */ idx_ary[j] = 0; j += 1; /* loop until string terminator is reached */ while (s[i] != 0) { if (s[i] == separator) { s[i] = 0; /* replace separator by string terminator */ if (j >= max_idx) { /* no more room in the index array */ return j; } idx_ary[j] = i+1; /* next token start after replaced separator */ ++j; } ++i; } return j; } /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ int lgw_gps_enable(char *tty_path, char *gps_family, speed_t target_brate, int *fd_ptr) { int i; struct termios ttyopt; /* serial port options */ int gps_tty_dev; /* file descriptor to the serial port of the GNSS module */ uint8_t ubx_cmd_timegps[UBX_MSG_NAVTIMEGPS_LEN] = { 0xB5, 0x62, /* UBX Sync Chars */ 0x06, 0x01, /* CFG-MSG Class/ID */ 0x08, 0x00, /* Payload length */ 0x01, 0x20, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, /* Enable NAV-TIMEGPS output on serial */ 0x32, 0x94 }; /* Checksum */ ssize_t num_written; /* check input parameters */ CHECK_NULL(tty_path); CHECK_NULL(fd_ptr); /* open TTY device */ gps_tty_dev = open(tty_path, O_RDWR | O_NOCTTY); if (gps_tty_dev <= 0) { DEBUG_MSG("ERROR: TTY PORT FAIL TO OPEN, CHECK PATH AND ACCESS RIGHTS\n"); return LGW_GPS_ERROR; } *fd_ptr = gps_tty_dev; /* manage the different GPS modules families */ if (gps_family == NULL) { DEBUG_MSG("WARNING: this version of GPS module may not be supported\n"); } else if (strncmp(gps_family, "ubx7", 4) != 0) { /* The current implementation relies on proprietary messages from U-Blox */ /* GPS modules (UBX, NAV-TIMEGPS...) and has only be tested with a u-blox 7. */ /* Those messages allow to get NATIVE GPS time (no leap seconds) required */ /* for class-B handling and GPS synchronization */ /* see lgw_parse_ubx() function for details */ DEBUG_MSG("WARNING: this version of GPS module may not be supported\n"); } /* manage the target bitrate */ if (target_brate != 0) { DEBUG_MSG("WARNING: target_brate parameter ignored for now\n"); // TODO } /* get actual serial port configuration */ i = tcgetattr(gps_tty_dev, &ttyopt); if (i != 0) { DEBUG_MSG("ERROR: IMPOSSIBLE TO GET TTY PORT CONFIGURATION\n"); return LGW_GPS_ERROR; } /* Save current serial port configuration for restoring later */ memcpy(&ttyopt_restore, &ttyopt, sizeof ttyopt); /* update baudrates */ cfsetispeed(&ttyopt, DEFAULT_BAUDRATE); cfsetospeed(&ttyopt, DEFAULT_BAUDRATE); /* update terminal parameters */ /* The following configuration should allow to: - Get ASCII NMEA messages - Get UBX binary messages - Send UBX binary commands Note: as binary data have to be read/written, we need to disable various character processing to avoid loosing data */ /* Control Modes */ ttyopt.c_cflag |= CLOCAL; /* local connection, no modem control */ ttyopt.c_cflag |= CREAD; /* enable receiving characters */ ttyopt.c_cflag |= CS8; /* 8 bit frames */ ttyopt.c_cflag &= ~PARENB; /* no parity */ ttyopt.c_cflag &= ~CSTOPB; /* one stop bit */ /* Input Modes */ ttyopt.c_iflag |= IGNPAR; /* ignore bytes with parity errors */ ttyopt.c_iflag &= ~ICRNL; /* do not map CR to NL on input*/ ttyopt.c_iflag &= ~IGNCR; /* do not ignore carriage return on input */ ttyopt.c_iflag &= ~IXON; /* disable Start/Stop output control */ ttyopt.c_iflag &= ~IXOFF; /* do not send Start/Stop characters */ /* Output Modes */ ttyopt.c_oflag = 0; /* disable everything on output as we only write binary */ /* Local Modes */ ttyopt.c_lflag &= ~ICANON; /* disable canonical input - cannot use with binary input */ ttyopt.c_lflag &= ~ISIG; /* disable check for INTR, QUIT, SUSP special characters */ ttyopt.c_lflag &= ~IEXTEN; /* disable any special control character */ ttyopt.c_lflag &= ~ECHO; /* do not echo back every character typed */ ttyopt.c_lflag &= ~ECHOE; /* does not erase the last character in current line */ ttyopt.c_lflag &= ~ECHOK; /* do not echo NL after KILL character */ /* settings for non-canonical mode read will block for until the lesser of VMIN or requested chars have been received */ ttyopt.c_cc[VMIN] = LGW_GPS_MIN_MSG_SIZE; ttyopt.c_cc[VTIME] = 0; /* set new serial ports parameters */ i = tcsetattr(gps_tty_dev, TCSANOW, &ttyopt); if (i != 0){ DEBUG_MSG("ERROR: IMPOSSIBLE TO UPDATE TTY PORT CONFIGURATION\n"); return LGW_GPS_ERROR; } tcflush(gps_tty_dev, TCIOFLUSH); /* Send UBX CFG NAV-TIMEGPS message to tell GPS module to output native GPS time */ /* This is a binary message, serial port has to be properly configured to handle this */ num_written = write (gps_tty_dev, ubx_cmd_timegps, UBX_MSG_NAVTIMEGPS_LEN); if (num_written != UBX_MSG_NAVTIMEGPS_LEN) { DEBUG_MSG("ERROR: Failed to write on serial port (written=%d)\n", (int) num_written); } /* get timezone info */ tzset(); /* initialize global variables */ gps_time_ok = false; gps_pos_ok = false; gps_mod = 'N'; return LGW_GPS_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_gps_disable(int fd) { int i; /* restore serial ports parameters */ i = tcsetattr(fd, TCSANOW, &ttyopt_restore); if (i != 0){ DEBUG_MSG("ERROR: IMPOSSIBLE TO RESTORE TTY PORT CONFIGURATION\n"); return LGW_GPS_ERROR; } tcflush(fd, TCIOFLUSH); i = close(fd); if (i <= 0) { DEBUG_MSG("ERROR: TTY PORT FAIL TO CLOSE\n"); return LGW_GPS_ERROR; } return LGW_GPS_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ enum gps_msg lgw_parse_ubx(const char *serial_buff, size_t buff_size, size_t *msg_size) { bool valid = 0; /* iTOW, fTOW and week validity */ unsigned int payload_length; uint8_t ck_a, ck_b; uint8_t ck_a_rcv, ck_b_rcv; unsigned int i; *msg_size = 0; /* ensure msg_size alway receives a value */ /* check input parameters */ if (serial_buff == NULL) { return IGNORED; } if (buff_size < 8) { DEBUG_MSG("ERROR: TOO SHORT TO BE A VALID UBX MESSAGE\n"); return IGNORED; } /* display received serial data and checksum */ DEBUG_MSG("Note: parsing UBX frame> "); for (i=0; i (int)(sizeof(parser_buf) - 1)) { DEBUG_MSG("Note: input string to big for parsing\n"); return INVALID; } /* look for some NMEA sentences in particular */ if (buff_size < 8) { DEBUG_MSG("ERROR: TOO SHORT TO BE A VALID NMEA SENTENCE\n"); return UNKNOWN; } else if (!validate_nmea_checksum(serial_buff, buff_size)) { DEBUG_MSG("Warning: invalid NMEA sentence (bad checksum)\n"); return INVALID; } else if (match_label(serial_buff, "$G?RMC", 6, '?')) { /* NMEA sentence format: $xxRMC,time,status,lat,NS,long,EW,spd,cog,date,mv,mvEW,posMode*cs Valid fix: $GPRMC,083559.34,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A*00 No fix: $GPRMC,,V,,,,,,,,,,N*00 */ memcpy(parser_buf, serial_buff, buff_size); parser_buf[buff_size] = '\0'; nb_fields = str_chop(parser_buf, buff_size, ',', str_index, ARRAY_SIZE(str_index)); if (nb_fields != 13) { DEBUG_MSG("Warning: invalid RMC sentence (number of fields)\n"); return IGNORED; } /* parse GPS status */ gps_mod = *(parser_buf + str_index[12]); /* get first character, no need to bother with sscanf */ if ((gps_mod != 'N') && (gps_mod != 'A') && (gps_mod != 'D')) { gps_mod = 'N'; } /* parse complete time */ i = sscanf(parser_buf + str_index[1], "%2hd%2hd%2hd%4f", &gps_hou, &gps_min, &gps_sec, &gps_fra); j = sscanf(parser_buf + str_index[9], "%2hd%2hd%2hd", &gps_day, &gps_mon, &gps_yea); if ((i == 4) && (j == 3)) { if ((gps_mod == 'A') || (gps_mod == 'D')) { gps_time_ok = true; DEBUG_MSG("Note: Valid RMC sentence, GPS locked, date: 20%02d-%02d-%02dT%02d:%02d:%06.3fZ\n", gps_yea, gps_mon, gps_day, gps_hou, gps_min, gps_fra + (float)gps_sec); } else { gps_time_ok = false; DEBUG_MSG("Note: Valid RMC sentence, no satellite fix, estimated date: 20%02d-%02d-%02dT%02d:%02d:%06.3fZ\n", gps_yea, gps_mon, gps_day, gps_hou, gps_min, gps_fra + (float)gps_sec); } } else { /* could not get a valid hour AND date */ gps_time_ok = false; DEBUG_MSG("Note: Valid RMC sentence, mode %c, no date\n", gps_mod); } return NMEA_RMC; } else if (match_label(serial_buff, "$G?GGA", 6, '?')) { /* NMEA sentence format: $xxGGA,time,lat,NS,long,EW,quality,numSV,HDOP,alt,M,sep,M,diffAge,diffStation*cs Valid fix: $GPGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*5B */ memcpy(parser_buf, serial_buff, buff_size); parser_buf[buff_size] = '\0'; nb_fields = str_chop(parser_buf, buff_size, ',', str_index, ARRAY_SIZE(str_index)); if (nb_fields != 15) { DEBUG_MSG("Warning: invalid GGA sentence (number of fields)\n"); return IGNORED; } /* parse number of satellites used for fix */ sscanf(parser_buf + str_index[7], "%hd", &gps_sat); /* parse 3D coordinates */ i = sscanf(parser_buf + str_index[2], "%2hd%10lf", &gps_dla, &gps_mla); gps_ola = *(parser_buf + str_index[3]); j = sscanf(parser_buf + str_index[4], "%3hd%10lf", &gps_dlo, &gps_mlo); gps_olo = *(parser_buf + str_index[5]); k = sscanf(parser_buf + str_index[9], "%hd", &gps_alt); if ((i == 2) && (j == 2) && (k == 1) && ((gps_ola=='N')||(gps_ola=='S')) && ((gps_olo=='E')||(gps_olo=='W'))) { gps_pos_ok = true; DEBUG_MSG("Note: Valid GGA sentence, %d sat, lat %02ddeg %06.3fmin %c, lon %03ddeg%06.3fmin %c, alt %d\n", gps_sat, gps_dla, gps_mla, gps_ola, gps_dlo, gps_mlo, gps_olo, gps_alt); } else { /* could not get a valid latitude, longitude AND altitude */ gps_pos_ok = false; DEBUG_MSG("Note: Valid GGA sentence, %d sat, no coordinates\n", gps_sat); } return NMEA_GGA; } else { DEBUG_MSG("Note: ignored NMEA sentence\n"); /* quite verbose */ return IGNORED; } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_gps_get(struct timespec *utc, struct timespec *gps_time, struct coord_s *loc, struct coord_s *err) { struct tm x; time_t y; double intpart, fractpart; if (utc != NULL) { if (!gps_time_ok) { DEBUG_MSG("ERROR: NO VALID TIME TO RETURN\n"); return LGW_GPS_ERROR; } memset(&x, 0, sizeof(x)); if (gps_yea < 100) { /* 2-digits year, 20xx */ x.tm_year = gps_yea + 100; /* 100 years offset to 1900 */ } else { /* 4-digits year, Gregorian calendar */ x.tm_year = gps_yea - 1900; } x.tm_mon = gps_mon - 1; /* tm_mon is [0,11], gps_mon is [1,12] */ x.tm_mday = gps_day; x.tm_hour = gps_hou; x.tm_min = gps_min; x.tm_sec = gps_sec; y = mktime(&x) - timezone; /* need to substract timezone bc mktime assumes time vector is local time */ if (y == (time_t)(-1)) { DEBUG_MSG("ERROR: FAILED TO CONVERT BROKEN-DOWN TIME\n"); return LGW_GPS_ERROR; } utc->tv_sec = y; utc->tv_nsec = (int32_t)(gps_fra * 1e9); } if (gps_time != NULL) { if (!gps_time_ok) { DEBUG_MSG("ERROR: NO VALID TIME TO RETURN\n"); return LGW_GPS_ERROR; } fractpart = modf(((double)gps_iTOW / 1E3) + ((double)gps_fTOW / 1E9), &intpart); /* Number of seconds since beginning on current GPS week */ gps_time->tv_sec = (time_t)intpart; /* Number of seconds since GPS epoch 06.Jan.1980 */ gps_time->tv_sec += (time_t)gps_week * 604800; /* day*hours*minutes*secondes: 7*24*60*60; */ /* Fractional part in nanoseconds */ gps_time->tv_nsec = (long)(fractpart * 1E9); } if (loc != NULL) { if (!gps_pos_ok) { DEBUG_MSG("ERROR: NO VALID POSITION TO RETURN\n"); return LGW_GPS_ERROR; } loc->lat = ((double)gps_dla + (gps_mla/60.0)) * ((gps_ola == 'N')?1.0:-1.0); loc->lon = ((double)gps_dlo + (gps_mlo/60.0)) * ((gps_olo == 'E')?1.0:-1.0); loc->alt = gps_alt; } if (err != NULL) { DEBUG_MSG("Warning: localization error processing not implemented yet\n"); err->lat = 0.0; err->lon = 0.0; err->alt = 0; } return LGW_GPS_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_gps_sync(struct tref *ref, uint32_t count_us, struct timespec utc, struct timespec gps_time) { double cnt_diff; /* internal concentrator time difference (in seconds) */ double utc_diff; /* UTC time difference (in seconds) */ double slope; /* time slope between new reference and old reference (for sanity check) */ bool aber_n0; /* is the update value for synchronization aberrant or not ? */ static bool aber_min1 = false; /* keep track of whether value at sync N-1 was aberrant or not */ static bool aber_min2 = false; /* keep track of whether value at sync N-2 was aberrant or not */ CHECK_NULL(ref); /* calculate the slope */ cnt_diff = (double)(count_us - ref->count_us) / (double)(TS_CPS); /* uncorrected by xtal_err */ utc_diff = (double)(utc.tv_sec - (ref->utc).tv_sec) + (1E-9 * (double)(utc.tv_nsec - (ref->utc).tv_nsec)); /* detect aberrant points by measuring if slope limits are exceeded */ if (utc_diff != 0) { // prevent divide by zero slope = cnt_diff/utc_diff; if ((slope > PLUS_10PPM) || (slope < MINUS_10PPM)) { DEBUG_MSG("Warning: correction range exceeded\n"); aber_n0 = true; } else { aber_n0 = false; } } else { DEBUG_MSG("Warning: aberrant UTC value for synchronization\n"); aber_n0 = true; } /* watch if the 3 latest sync point were aberrant or not */ if (aber_n0 == false) { /* value no aberrant -> sync with smoothed slope */ ref->systime = time(NULL); ref->count_us = count_us; ref->utc.tv_sec = utc.tv_sec; ref->utc.tv_nsec = utc.tv_nsec; ref->gps.tv_sec = gps_time.tv_sec; ref->gps.tv_nsec = gps_time.tv_nsec; ref->xtal_err = slope; aber_min2 = aber_min1; aber_min1 = aber_n0; return LGW_GPS_SUCCESS; } else if (aber_n0 && aber_min1 && aber_min2) { /* 3 successive aberrant values -> sync reset (keep xtal_err) */ ref->systime = time(NULL); ref->count_us = count_us; ref->utc.tv_sec = utc.tv_sec; ref->utc.tv_nsec = utc.tv_nsec; ref->gps.tv_sec = gps_time.tv_sec; ref->gps.tv_nsec = gps_time.tv_nsec; /* reset xtal_err only if the present value is out of range */ if ((ref->xtal_err > PLUS_10PPM) || (ref->xtal_err < MINUS_10PPM)) { ref->xtal_err = 1.0; } DEBUG_MSG("Warning: 3 successive aberrant sync attempts, sync reset\n"); aber_min2 = aber_min1; aber_min1 = aber_n0; return LGW_GPS_SUCCESS; } else { /* only 1 or 2 successive aberrant values -> ignore and return an error */ aber_min2 = aber_min1; aber_min1 = aber_n0; return LGW_GPS_ERROR; } return LGW_GPS_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_cnt2utc(struct tref ref, uint32_t count_us, struct timespec *utc) { double delta_sec; double intpart, fractpart; long tmp; CHECK_NULL(utc); if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) { DEBUG_MSG("ERROR: INVALID REFERENCE FOR CNT -> UTC CONVERSION\n"); return LGW_GPS_ERROR; } /* calculate delta in seconds between reference count_us and target count_us */ delta_sec = (double)(count_us - ref.count_us) / (TS_CPS * ref.xtal_err); /* now add that delta to reference UTC time */ fractpart = modf (delta_sec , &intpart); tmp = ref.utc.tv_nsec + (long)(fractpart * 1E9); if (tmp < (long)1E9) { /* the nanosecond part doesn't overflow */ utc->tv_sec = ref.utc.tv_sec + (time_t)intpart; utc->tv_nsec = tmp; } else { /* must carry one second */ utc->tv_sec = ref.utc.tv_sec + (time_t)intpart + 1; utc->tv_nsec = tmp - (long)1E9; } return LGW_GPS_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_utc2cnt(struct tref ref, struct timespec utc, uint32_t *count_us) { double delta_sec; CHECK_NULL(count_us); if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) { DEBUG_MSG("ERROR: INVALID REFERENCE FOR UTC -> CNT CONVERSION\n"); return LGW_GPS_ERROR; } /* calculate delta in seconds between reference utc and target utc */ delta_sec = (double)(utc.tv_sec - ref.utc.tv_sec); delta_sec += 1E-9 * (double)(utc.tv_nsec - ref.utc.tv_nsec); /* now convert that to internal counter tics and add that to reference counter value */ *count_us = ref.count_us + (uint32_t)(delta_sec * TS_CPS * ref.xtal_err); return LGW_GPS_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_cnt2gps(struct tref ref, uint32_t count_us, struct timespec *gps_time) { double delta_sec; double intpart, fractpart; long tmp; CHECK_NULL(gps_time); if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) { DEBUG_MSG("ERROR: INVALID REFERENCE FOR CNT -> GPS CONVERSION\n"); return LGW_GPS_ERROR; } /* calculate delta in milliseconds between reference count_us and target count_us */ delta_sec = (double)(count_us - ref.count_us) / (TS_CPS * ref.xtal_err); /* now add that delta to reference GPS time */ fractpart = modf (delta_sec , &intpart); tmp = ref.gps.tv_nsec + (long)(fractpart * 1E9); if (tmp < (long)1E9) { /* the nanosecond part doesn't overflow */ gps_time->tv_sec = ref.gps.tv_sec + (time_t)intpart; gps_time->tv_nsec = tmp; } else { /* must carry one second */ gps_time->tv_sec = ref.gps.tv_sec + (time_t)intpart + 1; gps_time->tv_nsec = tmp - (long)1E9; } return LGW_GPS_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_gps2cnt(struct tref ref, struct timespec gps_time, uint32_t *count_us) { double delta_sec; CHECK_NULL(count_us); if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) { DEBUG_MSG("ERROR: INVALID REFERENCE FOR GPS -> CNT CONVERSION\n"); return LGW_GPS_ERROR; } /* calculate delta in seconds between reference gps time and target gps time */ delta_sec = (double)(gps_time.tv_sec - ref.gps.tv_sec); delta_sec += 1E-9 * (double)(gps_time.tv_nsec - ref.gps.tv_nsec); /* now convert that to internal counter tics and add that to reference counter value */ *count_us = ref.count_us + (uint32_t)(delta_sec * TS_CPS * ref.xtal_err); return LGW_GPS_SUCCESS; } /* --- EOF ------------------------------------------------------------------ */