Annotation of embedaddon/ntp/ntpd/refclock_nmea.c, revision 1.1
1.1 ! misho 1: /*
! 2: * refclock_nmea.c - clock driver for an NMEA GPS CLOCK
! 3: * Michael Petry Jun 20, 1994
! 4: * based on refclock_heathn.c
! 5: *
! 6: * Updated to add support for Accord GPS Clock
! 7: * Venu Gopal Dec 05, 2007
! 8: * neo.venu@gmail.com, venugopal_d@pgad.gov.in
! 9: *
! 10: * Updated to process 'time1' fudge factor
! 11: * Venu Gopal May 05, 2008
! 12: *
! 13: * Converted to common PPSAPI code, separate PPS fudge time1
! 14: * from serial timecode fudge time2.
! 15: * Dave Hart July 1, 2009
! 16: * hart@ntp.org, davehart@davehart.com
! 17: */
! 18:
! 19: #ifdef HAVE_CONFIG_H
! 20: #include <config.h>
! 21: #endif
! 22:
! 23: #if defined(REFCLOCK) && defined(CLOCK_NMEA)
! 24:
! 25: #include <sys/stat.h>
! 26: #include <stdio.h>
! 27: #include <ctype.h>
! 28:
! 29: #include "ntpd.h"
! 30: #include "ntp_io.h"
! 31: #include "ntp_unixtime.h"
! 32: #include "ntp_refclock.h"
! 33: #include "ntp_stdlib.h"
! 34: #include "ntp_calendar.h"
! 35:
! 36: #ifdef HAVE_PPSAPI
! 37: # include "ppsapi_timepps.h"
! 38: # include "refclock_atom.h"
! 39: #endif /* HAVE_PPSAPI */
! 40:
! 41: #ifdef SYS_WINNT
! 42: #undef write /* ports/winnt/include/config.h: #define write _write */
! 43: extern int async_write(int, const void *, unsigned int);
! 44: #define write(fd, data, octets) async_write(fd, data, octets)
! 45: #endif
! 46:
! 47: #ifndef TIMESPECTOTS
! 48: #define TIMESPECTOTS(ptspec, pts) \
! 49: do { \
! 50: DTOLFP((ptspec)->tv_nsec * 1.0e-9, pts); \
! 51: (pts)->l_ui += (u_int32)((ptspec)->tv_sec) + JAN_1970; \
! 52: } while (0)
! 53: #endif
! 54:
! 55:
! 56: /*
! 57: * This driver supports NMEA-compatible GPS receivers
! 58: *
! 59: * Prototype was refclock_trak.c, Thanks a lot.
! 60: *
! 61: * The receiver used spits out the NMEA sentences for boat navigation.
! 62: * And you thought it was an information superhighway. Try a raging river
! 63: * filled with rapids and whirlpools that rip away your data and warp time.
! 64: *
! 65: * If HAVE_PPSAPI is defined code to use the PPSAPI will be compiled in.
! 66: * On startup if initialization of the PPSAPI fails, it will fall back
! 67: * to the "normal" timestamps.
! 68: *
! 69: * The PPSAPI part of the driver understands fudge flag2 and flag3. If
! 70: * flag2 is set, it will use the clear edge of the pulse. If flag3 is
! 71: * set, kernel hardpps is enabled.
! 72: *
! 73: * GPS sentences other than RMC (the default) may be enabled by setting
! 74: * the relevent bits of 'mode' in the server configuration line
! 75: * server 127.127.20.x mode X
! 76: *
! 77: * bit 0 - enables RMC (1)
! 78: * bit 1 - enables GGA (2)
! 79: * bit 2 - enables GLL (4)
! 80: * bit 3 - enables ZDA (8) - Standard Time & Date
! 81: * bit 3 - enables ZDG (8) - Accord GPS Clock's custom sentence with GPS time
! 82: * very close to standard ZDA
! 83: *
! 84: * Multiple sentences may be selected except when ZDG/ZDA is selected.
! 85: *
! 86: * bit 4/5/6 - selects the baudrate for serial port :
! 87: * 0 for 4800 (default)
! 88: * 1 for 9600
! 89: * 2 for 19200
! 90: * 3 for 38400
! 91: * 4 for 57600
! 92: * 5 for 115200
! 93: */
! 94: #define NMEA_MESSAGE_MASK_OLD 0x07
! 95: #define NMEA_MESSAGE_MASK_SINGLE 0x08
! 96: #define NMEA_MESSAGE_MASK (NMEA_MESSAGE_MASK_OLD | NMEA_MESSAGE_MASK_SINGLE)
! 97:
! 98: #define NMEA_BAUDRATE_MASK 0x70
! 99: #define NMEA_BAUDRATE_SHIFT 4
! 100:
! 101: /*
! 102: * Definitions
! 103: */
! 104: #define DEVICE "/dev/gps%d" /* GPS serial device */
! 105: #define PPSDEV "/dev/gpspps%d" /* PPSAPI device override */
! 106: #define SPEED232 B4800 /* uart speed (4800 bps) */
! 107: #define PRECISION (-9) /* precision assumed (about 2 ms) */
! 108: #define PPS_PRECISION (-20) /* precision assumed (about 1 us) */
! 109: #define REFID "GPS\0" /* reference id */
! 110: #define DESCRIPTION "NMEA GPS Clock" /* who we are */
! 111: #ifndef O_NOCTTY
! 112: #define M_NOCTTY 0
! 113: #else
! 114: #define M_NOCTTY O_NOCTTY
! 115: #endif
! 116: #ifndef O_NONBLOCK
! 117: #define M_NONBLOCK 0
! 118: #else
! 119: #define M_NONBLOCK O_NONBLOCK
! 120: #endif
! 121: #define PPSOPENMODE (O_RDWR | M_NOCTTY | M_NONBLOCK)
! 122:
! 123: /* NMEA sentence array indexes for those we use */
! 124: #define NMEA_GPRMC 0 /* recommended min. nav. */
! 125: #define NMEA_GPGGA 1 /* fix and quality */
! 126: #define NMEA_GPGLL 2 /* geo. lat/long */
! 127: #define NMEA_GPZDA 3 /* date/time */
! 128: /*
! 129: * $GPZDG is a proprietary sentence that violates the spec, by not
! 130: * using $P and an assigned company identifier to prefix the sentence
! 131: * identifier. When used with this driver, the system needs to be
! 132: * isolated from other NTP networks, as it operates in GPS time, not
! 133: * UTC as is much more common. GPS time is >15 seconds different from
! 134: * UTC due to not respecting leap seconds since 1970 or so. Other
! 135: * than the different timebase, $GPZDG is similar to $GPZDA.
! 136: */
! 137: #define NMEA_GPZDG 4
! 138: #define NMEA_ARRAY_SIZE (NMEA_GPZDG + 1)
! 139:
! 140: /*
! 141: * Sentence selection mode bits
! 142: */
! 143: #define USE_ALL 0 /* any/all */
! 144: #define USE_GPRMC 1
! 145: #define USE_GPGGA 2
! 146: #define USE_GPGLL 4
! 147: #define USE_GPZDA_ZDG 8 /* affects both */
! 148:
! 149: /* mapping from sentence index to controlling mode bit */
! 150: u_char sentence_mode[NMEA_ARRAY_SIZE] =
! 151: {
! 152: USE_GPRMC,
! 153: USE_GPGGA,
! 154: USE_GPGLL,
! 155: USE_GPZDA_ZDG,
! 156: USE_GPZDA_ZDG
! 157: };
! 158:
! 159: /*
! 160: * Unit control structure
! 161: */
! 162: struct nmeaunit {
! 163: #ifdef HAVE_PPSAPI
! 164: struct refclock_atom atom; /* PPSAPI structure */
! 165: int ppsapi_tried; /* attempt PPSAPI once */
! 166: int ppsapi_lit; /* time_pps_create() worked */
! 167: int ppsapi_fd; /* fd used with PPSAPI */
! 168: int ppsapi_gate; /* allow edge detection processing */
! 169: int tcount; /* timecode sample counter */
! 170: int pcount; /* PPS sample counter */
! 171: #endif /* HAVE_PPSAPI */
! 172: l_fp tstamp; /* timestamp of last poll */
! 173: int gps_time; /* 0 UTC, 1 GPS time */
! 174: /* per sentence checksum seen flag */
! 175: struct calendar used; /* hh:mm:ss of used sentence */
! 176: u_char cksum_seen[NMEA_ARRAY_SIZE];
! 177: };
! 178:
! 179: /*
! 180: * Function prototypes
! 181: */
! 182: static int nmea_start (int, struct peer *);
! 183: static void nmea_shutdown (int, struct peer *);
! 184: static void nmea_receive (struct recvbuf *);
! 185: static void nmea_poll (int, struct peer *);
! 186: #ifdef HAVE_PPSAPI
! 187: static void nmea_control (int, struct refclockstat *,
! 188: struct refclockstat *, struct peer *);
! 189: static void nmea_timer (int, struct peer *);
! 190: #define NMEA_CONTROL nmea_control
! 191: #define NMEA_TIMER nmea_timer
! 192: #else
! 193: #define NMEA_CONTROL noentry
! 194: #define NMEA_TIMER noentry
! 195: #endif /* HAVE_PPSAPI */
! 196: static void gps_send (int, const char *, struct peer *);
! 197: static char * field_parse (char *, int);
! 198: static int nmea_checksum_ok(const char *);
! 199: static void nmea_day_unfold(struct calendar*);
! 200: static void nmea_century_unfold(struct calendar*);
! 201:
! 202: /*
! 203: * Transfer vector
! 204: */
! 205: struct refclock refclock_nmea = {
! 206: nmea_start, /* start up driver */
! 207: nmea_shutdown, /* shut down driver */
! 208: nmea_poll, /* transmit poll message */
! 209: NMEA_CONTROL, /* fudge control */
! 210: noentry, /* initialize driver */
! 211: noentry, /* buginfo */
! 212: NMEA_TIMER /* called once per second */
! 213: };
! 214:
! 215: /*
! 216: * nmea_start - open the GPS devices and initialize data for processing
! 217: */
! 218: static int
! 219: nmea_start(
! 220: int unit,
! 221: struct peer *peer
! 222: )
! 223: {
! 224: register struct nmeaunit *up;
! 225: struct refclockproc *pp;
! 226: int fd;
! 227: char device[20];
! 228: int baudrate;
! 229: char *baudtext;
! 230:
! 231: pp = peer->procptr;
! 232:
! 233: /*
! 234: * Open serial port. Use CLK line discipline, if available.
! 235: */
! 236: snprintf(device, sizeof(device), DEVICE, unit);
! 237:
! 238: /*
! 239: * Opening the serial port with appropriate baudrate
! 240: * based on the value of bit 4/5/6
! 241: */
! 242: switch ((peer->ttl & NMEA_BAUDRATE_MASK) >> NMEA_BAUDRATE_SHIFT) {
! 243: case 0:
! 244: case 6:
! 245: case 7:
! 246: default:
! 247: baudrate = SPEED232;
! 248: baudtext = "4800";
! 249: break;
! 250: case 1:
! 251: baudrate = B9600;
! 252: baudtext = "9600";
! 253: break;
! 254: case 2:
! 255: baudrate = B19200;
! 256: baudtext = "19200";
! 257: break;
! 258: case 3:
! 259: baudrate = B38400;
! 260: baudtext = "38400";
! 261: break;
! 262: #ifdef B57600
! 263: case 4:
! 264: baudrate = B57600;
! 265: baudtext = "57600";
! 266: break;
! 267: #endif
! 268: #ifdef B115200
! 269: case 5:
! 270: baudrate = B115200;
! 271: baudtext = "115200";
! 272: break;
! 273: #endif
! 274: }
! 275:
! 276: fd = refclock_open(device, baudrate, LDISC_CLK);
! 277:
! 278: if (fd <= 0) {
! 279: #ifdef HAVE_READLINK
! 280: /* nmead support added by Jon Miner (cp_n18@yahoo.com)
! 281: *
! 282: * See http://home.hiwaay.net/~taylorc/gps/nmea-server/
! 283: * for information about nmead
! 284: *
! 285: * To use this, you need to create a link from /dev/gpsX
! 286: * to the server:port where nmead is running. Something
! 287: * like this:
! 288: *
! 289: * ln -s server:port /dev/gps1
! 290: */
! 291: char buffer[80];
! 292: char *nmea_host, *nmea_tail;
! 293: int nmea_port;
! 294: int len;
! 295: struct hostent *he;
! 296: struct protoent *p;
! 297: struct sockaddr_in so_addr;
! 298:
! 299: if ((len = readlink(device,buffer,sizeof(buffer))) == -1)
! 300: return(0);
! 301: buffer[len] = 0;
! 302:
! 303: if ((nmea_host = strtok(buffer,":")) == NULL)
! 304: return(0);
! 305: if ((nmea_tail = strtok(NULL,":")) == NULL)
! 306: return(0);
! 307:
! 308: nmea_port = atoi(nmea_tail);
! 309:
! 310: if ((he = gethostbyname(nmea_host)) == NULL)
! 311: return(0);
! 312: if ((p = getprotobyname("tcp")) == NULL)
! 313: return(0);
! 314: memset(&so_addr, 0, sizeof(so_addr));
! 315: so_addr.sin_family = AF_INET;
! 316: so_addr.sin_port = htons(nmea_port);
! 317: so_addr.sin_addr = *((struct in_addr *) he->h_addr);
! 318:
! 319: if ((fd = socket(PF_INET,SOCK_STREAM,p->p_proto)) == -1)
! 320: return(0);
! 321: if (connect(fd,(struct sockaddr *)&so_addr, sizeof(so_addr)) == -1) {
! 322: close(fd);
! 323: return (0);
! 324: }
! 325: #else
! 326: pp->io.fd = -1;
! 327: return (0);
! 328: #endif
! 329: }
! 330:
! 331: msyslog(LOG_NOTICE, "%s serial %s open at %s bps",
! 332: refnumtoa(&peer->srcadr), device, baudtext);
! 333:
! 334: /*
! 335: * Allocate and initialize unit structure
! 336: */
! 337: up = emalloc(sizeof(*up));
! 338: memset(up, 0, sizeof(*up));
! 339: pp->io.clock_recv = nmea_receive;
! 340: pp->io.srcclock = (caddr_t)peer;
! 341: pp->io.datalen = 0;
! 342: pp->io.fd = fd;
! 343: if (!io_addclock(&pp->io)) {
! 344: pp->io.fd = -1;
! 345: close(fd);
! 346: free(up);
! 347: return (0);
! 348: }
! 349: pp->unitptr = (caddr_t)up;
! 350:
! 351: /*
! 352: * Initialize miscellaneous variables
! 353: */
! 354: peer->precision = PRECISION;
! 355: pp->clockdesc = DESCRIPTION;
! 356: memcpy(&pp->refid, REFID, 4);
! 357:
! 358: gps_send(fd,"$PMOTG,RMC,0000*1D\r\n", peer);
! 359:
! 360: return (1);
! 361: }
! 362:
! 363:
! 364: /*
! 365: * nmea_shutdown - shut down a GPS clock
! 366: *
! 367: * NOTE this routine is called after nmea_start() returns failure,
! 368: * as well as during a normal shutdown due to ntpq :config unpeer.
! 369: */
! 370: static void
! 371: nmea_shutdown(
! 372: int unit,
! 373: struct peer *peer
! 374: )
! 375: {
! 376: register struct nmeaunit *up;
! 377: struct refclockproc *pp;
! 378:
! 379: UNUSED_ARG(unit);
! 380:
! 381: pp = peer->procptr;
! 382: up = (struct nmeaunit *)pp->unitptr;
! 383: if (up != NULL) {
! 384: #ifdef HAVE_PPSAPI
! 385: if (up->ppsapi_lit) {
! 386: time_pps_destroy(up->atom.handle);
! 387: if (up->ppsapi_fd != pp->io.fd)
! 388: close(up->ppsapi_fd);
! 389: }
! 390: #endif
! 391: free(up);
! 392: }
! 393: if (-1 != pp->io.fd)
! 394: io_closeclock(&pp->io);
! 395: }
! 396:
! 397: /*
! 398: * nmea_control - configure fudge params
! 399: */
! 400: #ifdef HAVE_PPSAPI
! 401: static void
! 402: nmea_control(
! 403: int unit,
! 404: struct refclockstat *in_st,
! 405: struct refclockstat *out_st,
! 406: struct peer *peer
! 407: )
! 408: {
! 409: char device[32];
! 410: register struct nmeaunit *up;
! 411: struct refclockproc *pp;
! 412: int pps_fd;
! 413:
! 414: UNUSED_ARG(in_st);
! 415: UNUSED_ARG(out_st);
! 416:
! 417: pp = peer->procptr;
! 418: up = (struct nmeaunit *)pp->unitptr;
! 419:
! 420: if (!(CLK_FLAG1 & pp->sloppyclockflag)) {
! 421: if (!up->ppsapi_tried)
! 422: return;
! 423: up->ppsapi_tried = 0;
! 424: if (!up->ppsapi_lit)
! 425: return;
! 426: peer->flags &= ~FLAG_PPS;
! 427: peer->precision = PRECISION;
! 428: time_pps_destroy(up->atom.handle);
! 429: if (up->ppsapi_fd != pp->io.fd)
! 430: close(up->ppsapi_fd);
! 431: up->atom.handle = 0;
! 432: up->ppsapi_lit = 0;
! 433: up->ppsapi_fd = -1;
! 434: return;
! 435: }
! 436:
! 437: if (up->ppsapi_tried)
! 438: return;
! 439: /*
! 440: * Light up the PPSAPI interface.
! 441: */
! 442: up->ppsapi_tried = 1;
! 443:
! 444: /*
! 445: * if /dev/gpspps$UNIT can be opened that will be used for
! 446: * PPSAPI. Otherwise, the GPS serial device /dev/gps$UNIT
! 447: * already opened is used for PPSAPI as well.
! 448: */
! 449: snprintf(device, sizeof(device), PPSDEV, unit);
! 450:
! 451: pps_fd = open(device, PPSOPENMODE, S_IRUSR | S_IWUSR);
! 452:
! 453: if (-1 == pps_fd)
! 454: pps_fd = pp->io.fd;
! 455:
! 456: if (refclock_ppsapi(pps_fd, &up->atom)) {
! 457: up->ppsapi_lit = 1;
! 458: up->ppsapi_fd = pps_fd;
! 459: /* prepare to use the PPS API for our own purposes now. */
! 460: refclock_params(pp->sloppyclockflag, &up->atom);
! 461: return;
! 462: }
! 463:
! 464: NLOG(NLOG_CLOCKINFO)
! 465: msyslog(LOG_WARNING, "%s flag1 1 but PPSAPI fails",
! 466: refnumtoa(&peer->srcadr));
! 467: }
! 468: #endif /* HAVE_PPSAPI */
! 469:
! 470:
! 471: /*
! 472: * nmea_timer - called once per second, fetches PPS
! 473: * timestamp and stuffs in median filter.
! 474: */
! 475: #ifdef HAVE_PPSAPI
! 476: static void
! 477: nmea_timer(
! 478: int unit,
! 479: struct peer * peer
! 480: )
! 481: {
! 482: struct nmeaunit *up;
! 483: struct refclockproc *pp;
! 484:
! 485: UNUSED_ARG(unit);
! 486:
! 487: pp = peer->procptr;
! 488: up = (struct nmeaunit *)pp->unitptr;
! 489:
! 490: if (up->ppsapi_lit && up->ppsapi_gate &&
! 491: refclock_pps(peer, &up->atom, pp->sloppyclockflag) > 0) {
! 492: up->pcount++,
! 493: peer->flags |= FLAG_PPS;
! 494: peer->precision = PPS_PRECISION;
! 495: }
! 496: }
! 497: #endif /* HAVE_PPSAPI */
! 498:
! 499: #ifdef HAVE_PPSAPI
! 500: /*
! 501: * This function is used to correlate a receive time stamp and a
! 502: * reference time with a PPS edge time stamp. It applies the necessary
! 503: * fudges (fudge1 for PPS, fudge2 for receive time) and then tries to
! 504: * move the receive time stamp to the corresponding edge. This can
! 505: * warp into future, if a transmission delay of more than 500ms is not
! 506: * compensated with a corresponding fudge time2 value, because then
! 507: * the next PPS edge is nearer than the last. (Similiar to what the
! 508: * PPS ATOM driver does, but we deal with full time stamps here, not
! 509: * just phase shift information.) Likewise, a negative fudge time2
! 510: * value must be used if the reference time stamp correlates with the
! 511: * *following* PPS pulse.
! 512: *
! 513: * Note that the receive time fudge value only needs to move the receive
! 514: * stamp near a PPS edge but that close proximity is not required;
! 515: * +/-100ms precision should be enough. But since the fudge value will
! 516: * probably also be used to compensate the transmission delay when no PPS
! 517: * edge can be related to the time stamp, it's best to get it as close
! 518: * as possible.
! 519: *
! 520: * It should also be noted that the typical use case is matching to
! 521: * the preceeding edge, as most units relate their sentences to the
! 522: * current second.
! 523: *
! 524: * The function returns PPS_RELATE_NONE (0) if no PPS edge correlation
! 525: * can be fixed; PPS_RELATE_EDGE (1) when a PPS edge could be fixed, but
! 526: * the distance to the reference time stamp is too big (exceeds +/-400ms)
! 527: * and the ATOM driver PLL cannot be used to fix the phase; and
! 528: * PPS_RELATE_PHASE (2) when the ATOM driver PLL code can be used.
! 529: *
! 530: * On output, the receive time stamp is replaced with the
! 531: * corresponding PPS edge time if a fix could be made; the PPS fudge
! 532: * is updated to reflect the proper fudge time to apply. (This implies
! 533: * that 'refclock_process_f()' must be used!)
! 534: */
! 535: #define PPS_RELATE_NONE 0 /* no pps correlation possible */
! 536: #define PPS_RELATE_EDGE 1 /* recv time fixed, no phase lock */
! 537: #define PPS_RELATE_PHASE 2 /* recv time fixed, phase lock ok */
! 538:
! 539: static int
! 540: refclock_ppsrelate(
! 541: const struct refclockproc *pp , /* for sanity */
! 542: const struct refclock_atom *ap , /* for PPS io */
! 543: const l_fp *reftime ,
! 544: l_fp *rd_stamp, /* i/o read stamp */
! 545: double pp_fudge, /* pps fudge */
! 546: double *rd_fudge) /* i/o read fudge */
! 547: {
! 548: pps_info_t pps_info;
! 549: struct timespec timeout;
! 550: l_fp pp_stamp, pp_delta;
! 551: double delta, idelta;
! 552:
! 553: if (pp->leap == LEAP_NOTINSYNC)
! 554: return PPS_RELATE_NONE; /* clock is insane, no chance */
! 555:
! 556: memset(&timeout, 0, sizeof(timeout));
! 557: memset(&pps_info, 0, sizeof(pps_info_t));
! 558:
! 559: if (time_pps_fetch(ap->handle, PPS_TSFMT_TSPEC,
! 560: &pps_info, &timeout) < 0)
! 561: return PPS_RELATE_NONE;
! 562:
! 563: /* get last active PPS edge before receive */
! 564: if (ap->pps_params.mode & PPS_CAPTUREASSERT)
! 565: timeout = pps_info.assert_timestamp;
! 566: else if (ap->pps_params.mode & PPS_CAPTURECLEAR)
! 567: timeout = pps_info.clear_timestamp;
! 568: else
! 569: return PPS_RELATE_NONE;
! 570:
! 571: /* get delta between receive time and PPS time */
! 572: TIMESPECTOTS(&timeout, &pp_stamp);
! 573: pp_delta = *rd_stamp;
! 574: L_SUB(&pp_delta, &pp_stamp);
! 575: LFPTOD(&pp_delta, delta);
! 576: delta += pp_fudge - *rd_fudge;
! 577: if (fabs(delta) > 1.5)
! 578: return PPS_RELATE_NONE; /* PPS timeout control */
! 579:
! 580: /* eventually warp edges, check phase */
! 581: idelta = floor(delta + 0.5);
! 582: pp_fudge -= idelta;
! 583: delta -= idelta;
! 584: if (fabs(delta) > 0.45)
! 585: return PPS_RELATE_NONE; /* dead band control */
! 586:
! 587: /* we actually have a PPS edge to relate with! */
! 588: *rd_stamp = pp_stamp;
! 589: *rd_fudge = pp_fudge;
! 590:
! 591: /* if whole system out-of-sync, do not try to PLL */
! 592: if (sys_leap == LEAP_NOTINSYNC)
! 593: return PPS_RELATE_EDGE; /* cannot PLL with atom code */
! 594:
! 595: /* check against reftime if ATOM PLL can be used */
! 596: pp_delta = *reftime;
! 597: L_SUB(&pp_delta, &pp_stamp);
! 598: LFPTOD(&pp_delta, delta);
! 599: delta += pp_fudge;
! 600: if (fabs(delta) > 0.45)
! 601: return PPS_RELATE_EDGE; /* cannot PLL with atom code */
! 602:
! 603: /* all checks passed, gets an AAA rating here! */
! 604: return PPS_RELATE_PHASE; /* can PLL with atom code */
! 605: }
! 606: #endif /* HAVE_PPSAPI */
! 607:
! 608: /*
! 609: * nmea_receive - receive data from the serial interface
! 610: */
! 611: static void
! 612: nmea_receive(
! 613: struct recvbuf *rbufp
! 614: )
! 615: {
! 616: register struct nmeaunit *up;
! 617: struct refclockproc *pp;
! 618: struct peer *peer;
! 619: char *cp, *dp, *msg;
! 620: u_char sentence;
! 621: /* Use these variables to hold data until we decide its worth
! 622: * keeping */
! 623: char rd_lastcode[BMAX];
! 624: l_fp rd_timestamp, reftime;
! 625: int rd_lencode;
! 626: double rd_fudge;
! 627: struct calendar date;
! 628:
! 629: /*
! 630: * Initialize pointers and read the timecode and timestamp
! 631: */
! 632: peer = rbufp->recv_peer;
! 633: pp = peer->procptr;
! 634: up = (struct nmeaunit *)pp->unitptr;
! 635:
! 636: rd_lencode = refclock_gtlin(
! 637: rbufp,
! 638: rd_lastcode,
! 639: sizeof(rd_lastcode),
! 640: &rd_timestamp);
! 641:
! 642: /*
! 643: * There is a case that a <CR><LF> gives back a "blank" line.
! 644: * We can't have a well-formed sentence with less than 8 chars.
! 645: */
! 646: if (0 == rd_lencode)
! 647: return;
! 648:
! 649: if (rd_lencode < 8) {
! 650: refclock_report(peer, CEVNT_BADREPLY);
! 651: return;
! 652: }
! 653:
! 654: DPRINTF(1, ("nmea: gpsread %d %s\n", rd_lencode, rd_lastcode));
! 655:
! 656: /*
! 657: * We check the timecode format and decode its contents. The
! 658: * we only care about a few of them. The most important being
! 659: * the $GPRMC format
! 660: * $GPRMC,hhmmss,a,fddmm.xx,n,dddmmm.xx,w,zz.z,yyy.,ddmmyy,dd,v*CC
! 661: * mode (0,1,2,3) selects sentence ANY/ALL, RMC, GGA, GLL, ZDA
! 662: * $GPGLL,3513.8385,S,14900.7851,E,232420.594,A*21
! 663: * $GPGGA,232420.59,3513.8385,S,14900.7851,E,1,05,3.4,00519,M,,,,*3F
! 664: * $GPRMC,232418.19,A,3513.8386,S,14900.7853,E,00.0,000.0,121199,12.,E*77
! 665: *
! 666: * Defining GPZDA to support Standard Time & Date
! 667: * sentence. The sentence has the following format
! 668: *
! 669: * $--ZDA,HHMMSS.SS,DD,MM,YYYY,TH,TM,*CS<CR><LF>
! 670: *
! 671: * Apart from the familiar fields,
! 672: * 'TH' Time zone Hours
! 673: * 'TM' Time zone Minutes
! 674: *
! 675: * Defining GPZDG to support Accord GPS Clock's custom NMEA
! 676: * sentence. The sentence has the following format
! 677: *
! 678: * $GPZDG,HHMMSS.S,DD,MM,YYYY,AA.BB,V*CS<CR><LF>
! 679: *
! 680: * It contains the GPS timestamp valid for next PPS pulse.
! 681: * Apart from the familiar fields,
! 682: * 'AA.BB' denotes the signal strength( should be < 05.00 )
! 683: * 'V' denotes the GPS sync status :
! 684: * '0' indicates INVALID time,
! 685: * '1' indicates accuracy of +/-20 ms
! 686: * '2' indicates accuracy of +/-100 ns
! 687: */
! 688:
! 689: cp = rd_lastcode;
! 690: if (cp[0] == '$') {
! 691: /* Allow for GLGGA and GPGGA etc. */
! 692: msg = cp + 3;
! 693:
! 694: if (strncmp(msg, "RMC", 3) == 0)
! 695: sentence = NMEA_GPRMC;
! 696: else if (strncmp(msg, "GGA", 3) == 0)
! 697: sentence = NMEA_GPGGA;
! 698: else if (strncmp(msg, "GLL", 3) == 0)
! 699: sentence = NMEA_GPGLL;
! 700: else if (strncmp(msg, "ZDG", 3) == 0)
! 701: sentence = NMEA_GPZDG;
! 702: else if (strncmp(msg, "ZDA", 3) == 0)
! 703: sentence = NMEA_GPZDA;
! 704: else
! 705: return;
! 706: } else
! 707: return;
! 708:
! 709: /* See if I want to process this message type */
! 710: if ((peer->ttl & NMEA_MESSAGE_MASK) &&
! 711: !(peer->ttl & sentence_mode[sentence]))
! 712: return;
! 713:
! 714: /*
! 715: * $GPZDG provides GPS time not UTC, and the two mix poorly.
! 716: * Once have processed a $GPZDG, do not process any further
! 717: * UTC sentences (all but $GPZDG currently).
! 718: */
! 719: if (up->gps_time && NMEA_GPZDG != sentence)
! 720: return;
! 721:
! 722: /*
! 723: * Apparently, older NMEA specifications (which are expensive)
! 724: * did not require the checksum for all sentences. $GPMRC is
! 725: * the only one so far identified which has always been required
! 726: * to include a checksum.
! 727: *
! 728: * Today, most NMEA GPS receivers checksum every sentence. To
! 729: * preserve its error-detection capabilities with modern GPSes
! 730: * while allowing operation without checksums on all but $GPMRC,
! 731: * we keep track of whether we've ever seen a checksum on a
! 732: * given sentence, and if so, reject future checksum failures.
! 733: */
! 734: if (nmea_checksum_ok(rd_lastcode)) {
! 735: up->cksum_seen[sentence] = TRUE;
! 736: } else if (NMEA_GPRMC == sentence || up->cksum_seen[sentence]) {
! 737: refclock_report(peer, CEVNT_BADREPLY);
! 738: return;
! 739: }
! 740:
! 741: cp = rd_lastcode;
! 742:
! 743: /* Grab field depending on clock string type */
! 744: memset(&date, 0, sizeof(date));
! 745: switch (sentence) {
! 746:
! 747: case NMEA_GPRMC:
! 748: /*
! 749: * Test for synchronization. Check for quality byte.
! 750: */
! 751: dp = field_parse(cp, 2);
! 752: if (dp[0] != 'A')
! 753: pp->leap = LEAP_NOTINSYNC;
! 754: else
! 755: pp->leap = LEAP_NOWARNING;
! 756:
! 757: /* Now point at the time field */
! 758: dp = field_parse(cp, 1);
! 759: break;
! 760:
! 761: case NMEA_GPGGA:
! 762: /*
! 763: * Test for synchronization. Check for quality byte.
! 764: */
! 765: dp = field_parse(cp, 6);
! 766: if (dp[0] == '0')
! 767: pp->leap = LEAP_NOTINSYNC;
! 768: else
! 769: pp->leap = LEAP_NOWARNING;
! 770:
! 771: /* Now point at the time field */
! 772: dp = field_parse(cp, 1);
! 773: break;
! 774:
! 775: case NMEA_GPGLL:
! 776: /*
! 777: * Test for synchronization. Check for quality byte.
! 778: */
! 779: dp = field_parse(cp, 6);
! 780: if (dp[0] != 'A')
! 781: pp->leap = LEAP_NOTINSYNC;
! 782: else
! 783: pp->leap = LEAP_NOWARNING;
! 784:
! 785: /* Now point at the time field */
! 786: dp = field_parse(cp, 5);
! 787: break;
! 788:
! 789: case NMEA_GPZDG:
! 790: /* For $GPZDG check for validity of GPS time. */
! 791: dp = field_parse(cp, 6);
! 792: if (dp[0] == '0')
! 793: pp->leap = LEAP_NOTINSYNC;
! 794: else
! 795: pp->leap = LEAP_NOWARNING;
! 796: /* fall through to NMEA_GPZDA */
! 797:
! 798: case NMEA_GPZDA:
! 799: if (NMEA_GPZDA == sentence)
! 800: pp->leap = LEAP_NOWARNING;
! 801:
! 802: /* Now point at the time field */
! 803: dp = field_parse(cp, 1);
! 804: break;
! 805:
! 806: default:
! 807: return;
! 808: }
! 809:
! 810: /*
! 811: * Check time code format of NMEA
! 812: */
! 813: if (!isdigit((int)dp[0]) ||
! 814: !isdigit((int)dp[1]) ||
! 815: !isdigit((int)dp[2]) ||
! 816: !isdigit((int)dp[3]) ||
! 817: !isdigit((int)dp[4]) ||
! 818: !isdigit((int)dp[5])) {
! 819:
! 820: DPRINTF(1, ("NMEA time code %c%c%c%c%c%c non-numeric",
! 821: dp[0], dp[1], dp[2], dp[3], dp[4], dp[5]));
! 822: refclock_report(peer, CEVNT_BADTIME);
! 823: return;
! 824: }
! 825:
! 826: /*
! 827: * Convert time and check values.
! 828: */
! 829: date.hour = ((dp[0] - '0') * 10) + dp[1] - '0';
! 830: date.minute = ((dp[2] - '0') * 10) + dp[3] - '0';
! 831: date.second = ((dp[4] - '0') * 10) + dp[5] - '0';
! 832: /*
! 833: * Default to 0 milliseconds, if decimal convert milliseconds in
! 834: * one, two or three digits
! 835: */
! 836: pp->nsec = 0;
! 837: if (dp[6] == '.') {
! 838: if (isdigit((int)dp[7])) {
! 839: pp->nsec = (dp[7] - '0') * 100000000;
! 840: if (isdigit((int)dp[8])) {
! 841: pp->nsec += (dp[8] - '0') * 10000000;
! 842: if (isdigit((int)dp[9])) {
! 843: pp->nsec += (dp[9] - '0') * 1000000;
! 844: }
! 845: }
! 846: }
! 847: }
! 848:
! 849: if (date.hour > 23 || date.minute > 59 ||
! 850: date.second > 59 || pp->nsec > 1000000000) {
! 851:
! 852: DPRINTF(1, ("NMEA hour/min/sec/nsec range %02d:%02d:%02d.%09ld\n",
! 853: pp->hour, pp->minute, pp->second, pp->nsec));
! 854: refclock_report(peer, CEVNT_BADTIME);
! 855: return;
! 856: }
! 857:
! 858: /*
! 859: * Used only the first recognized sentence each second.
! 860: */
! 861: if (date.hour == up->used.hour &&
! 862: date.minute == up->used.minute &&
! 863: date.second == up->used.second)
! 864: return;
! 865:
! 866: pp->lencode = (u_short)rd_lencode;
! 867: memcpy(pp->a_lastcode, rd_lastcode, pp->lencode + 1);
! 868: up->tstamp = rd_timestamp;
! 869: pp->lastrec = up->tstamp;
! 870: DPRINTF(1, ("nmea: timecode %d %s\n", pp->lencode, pp->a_lastcode));
! 871:
! 872: /*
! 873: * Convert date and check values.
! 874: */
! 875: if (NMEA_GPRMC == sentence) {
! 876:
! 877: dp = field_parse(cp,9);
! 878: date.monthday = 10 * (dp[0] - '0') + (dp[1] - '0');
! 879: date.month = 10 * (dp[2] - '0') + (dp[3] - '0');
! 880: date.year = 10 * (dp[4] - '0') + (dp[5] - '0');
! 881: nmea_century_unfold(&date);
! 882:
! 883: } else if (NMEA_GPZDA == sentence || NMEA_GPZDG == sentence) {
! 884:
! 885: dp = field_parse(cp, 2);
! 886: date.monthday = 10 * (dp[0] - '0') + (dp[1] - '0');
! 887: dp = field_parse(cp, 3);
! 888: date.month = 10 * (dp[0] - '0') + (dp[1] - '0');
! 889: dp = field_parse(cp, 4);
! 890: date.year = 1000 * (dp[0] - '0') + 100 * (dp[1] - '0')
! 891: + 10 * (dp[2] - '0') + (dp[3] - '0');
! 892:
! 893: } else
! 894: nmea_day_unfold(&date);
! 895:
! 896: if (date.month < 1 || date.month > 12 ||
! 897: date.monthday < 1 || date.monthday > 31) {
! 898: refclock_report(peer, CEVNT_BADDATE);
! 899: return;
! 900: }
! 901:
! 902: up->used.hour = date.hour;
! 903: up->used.minute = date.minute;
! 904: up->used.second = date.second;
! 905:
! 906: /*
! 907: * If "fudge 127.127.20.__ flag4 1" is configured in ntp.conf,
! 908: * remove the location and checksum from the NMEA sentence
! 909: * recorded as the last timecode and visible to remote users
! 910: * with:
! 911: *
! 912: * ntpq -c clockvar <server>
! 913: *
! 914: * Note that this also removes the location from the clockstats
! 915: * log (if it is enabled). Some NTP operators monitor their
! 916: * NMEA GPS using the change in location in clockstats over
! 917: * time as as a proxy for the quality of GPS reception and
! 918: * thereby time reported.
! 919: */
! 920: if (CLK_FLAG4 & pp->sloppyclockflag) {
! 921: /*
! 922: * Start by pointing cp and dp at the fields with
! 923: * longitude and latitude in the last timecode.
! 924: */
! 925: switch (sentence) {
! 926:
! 927: case NMEA_GPGLL:
! 928: cp = field_parse(pp->a_lastcode, 1);
! 929: dp = field_parse(cp, 2);
! 930: break;
! 931:
! 932: case NMEA_GPGGA:
! 933: cp = field_parse(pp->a_lastcode, 2);
! 934: dp = field_parse(cp, 2);
! 935: break;
! 936:
! 937: case NMEA_GPRMC:
! 938: cp = field_parse(pp->a_lastcode, 3);
! 939: dp = field_parse(cp, 2);
! 940: break;
! 941:
! 942: case NMEA_GPZDA:
! 943: case NMEA_GPZDG:
! 944: default:
! 945: cp = dp = NULL;
! 946: }
! 947:
! 948: /* Blank the entire latitude & longitude. */
! 949: while (cp) {
! 950: while (',' != *cp) {
! 951: if ('.' != *cp)
! 952: *cp = '_';
! 953: cp++;
! 954: }
! 955:
! 956: /* Longitude at cp then latitude at dp */
! 957: if (cp < dp)
! 958: cp = dp;
! 959: else
! 960: cp = NULL;
! 961: }
! 962:
! 963: /* Blank the checksum, the last two characters */
! 964: if (dp) {
! 965: cp = pp->a_lastcode + pp->lencode - 2;
! 966: if (0 == cp[2])
! 967: cp[0] = cp[1] = '_';
! 968: }
! 969:
! 970: }
! 971:
! 972: /*
! 973: * Get the reference time stamp from the calendar buffer.
! 974: * Process the new sample in the median filter and determine
! 975: * the timecode timestamp, but only if the PPS is not in
! 976: * control.
! 977: */
! 978: rd_fudge = pp->fudgetime2;
! 979: date.yearday = 0; /* make sure it's not used */
! 980: DTOLFP(pp->nsec * 1.0e-9, &reftime);
! 981: reftime.l_ui += caltontp(&date);
! 982:
! 983: /* $GPZDG postprocessing first... */
! 984: if (NMEA_GPZDG == sentence) {
! 985: /*
! 986: * Note if we're only using GPS timescale from now on.
! 987: */
! 988: if (!up->gps_time) {
! 989: up->gps_time = 1;
! 990: NLOG(NLOG_CLOCKINFO)
! 991: msyslog(LOG_INFO, "%s using only $GPZDG",
! 992: refnumtoa(&peer->srcadr));
! 993: }
! 994: /*
! 995: * $GPZDG indicates the second after the *next* PPS
! 996: * pulse. So we remove 1 second from the reference
! 997: * time now.
! 998: */
! 999: reftime.l_ui--;
! 1000: }
! 1001:
! 1002: #ifdef HAVE_PPSAPI
! 1003: up->tcount++;
! 1004: /*
! 1005: * If we have PPS running, we try to associate the sentence with
! 1006: * the last active edge of the PPS signal.
! 1007: */
! 1008: if (up->ppsapi_lit)
! 1009: switch (refclock_ppsrelate(pp, &up->atom, &reftime,
! 1010: &rd_timestamp, pp->fudgetime1,
! 1011: &rd_fudge))
! 1012: {
! 1013: case PPS_RELATE_EDGE:
! 1014: up->ppsapi_gate = 0;
! 1015: break;
! 1016: case PPS_RELATE_PHASE:
! 1017: up->ppsapi_gate = 1;
! 1018: break;
! 1019: default:
! 1020: break;
! 1021: }
! 1022: else
! 1023: up->ppsapi_gate = 0;
! 1024:
! 1025: if (up->ppsapi_gate && (peer->flags & FLAG_PPS))
! 1026: return;
! 1027: #endif /* HAVE_PPSAPI */
! 1028:
! 1029: refclock_process_offset(pp, reftime, rd_timestamp, rd_fudge);
! 1030: }
! 1031:
! 1032:
! 1033: /*
! 1034: * nmea_poll - called by the transmit procedure
! 1035: *
! 1036: * We go to great pains to avoid changing state here, since there may be
! 1037: * more than one eavesdropper receiving the same timecode.
! 1038: */
! 1039: static void
! 1040: nmea_poll(
! 1041: int unit,
! 1042: struct peer *peer
! 1043: )
! 1044: {
! 1045: register struct nmeaunit *up;
! 1046: struct refclockproc *pp;
! 1047:
! 1048: pp = peer->procptr;
! 1049: up = (struct nmeaunit *)pp->unitptr;
! 1050:
! 1051: /*
! 1052: * Process median filter samples. If none received, declare a
! 1053: * timeout and keep going.
! 1054: */
! 1055: #ifdef HAVE_PPSAPI
! 1056: if (up->pcount == 0) {
! 1057: peer->flags &= ~FLAG_PPS;
! 1058: peer->precision = PRECISION;
! 1059: }
! 1060: if (up->tcount == 0) {
! 1061: pp->coderecv = pp->codeproc;
! 1062: refclock_report(peer, CEVNT_TIMEOUT);
! 1063: return;
! 1064: }
! 1065: up->pcount = up->tcount = 0;
! 1066: #else /* HAVE_PPSAPI */
! 1067: if (pp->coderecv == pp->codeproc) {
! 1068: refclock_report(peer, CEVNT_TIMEOUT);
! 1069: return;
! 1070: }
! 1071: #endif /* HAVE_PPSAPI */
! 1072:
! 1073: pp->polls++;
! 1074: pp->lastref = pp->lastrec;
! 1075: refclock_receive(peer);
! 1076: record_clock_stats(&peer->srcadr, pp->a_lastcode);
! 1077:
! 1078: /*
! 1079: * usually nmea_receive can get a timestamp every second,
! 1080: * but at least one Motorola unit needs prompting each
! 1081: * time.
! 1082: */
! 1083:
! 1084: gps_send(pp->io.fd,"$PMOTG,RMC,0000*1D\r\n", peer);
! 1085: }
! 1086:
! 1087:
! 1088: /*
! 1089: *
! 1090: * gps_send(fd,cmd, peer) Sends a command to the GPS receiver.
! 1091: * as gps_send(fd,"rqts,u\r", peer);
! 1092: *
! 1093: * We don't currently send any data, but would like to send
! 1094: * RTCM SC104 messages for differential positioning. It should
! 1095: * also give us better time. Without a PPS output, we're
! 1096: * Just fooling ourselves because of the serial code paths
! 1097: *
! 1098: */
! 1099: static void
! 1100: gps_send(
! 1101: int fd,
! 1102: const char *cmd,
! 1103: struct peer *peer
! 1104: )
! 1105: {
! 1106: if (write(fd, cmd, strlen(cmd)) == -1) {
! 1107: refclock_report(peer, CEVNT_FAULT);
! 1108: }
! 1109: }
! 1110:
! 1111:
! 1112: static char *
! 1113: field_parse(
! 1114: char *cp,
! 1115: int fn
! 1116: )
! 1117: {
! 1118: char *tp;
! 1119: int i = fn;
! 1120:
! 1121: for (tp = cp; i && *tp; tp++)
! 1122: if (*tp == ',')
! 1123: i--;
! 1124:
! 1125: return tp;
! 1126: }
! 1127:
! 1128:
! 1129: /*
! 1130: * nmea_checksum_ok verifies 8-bit XOR checksum is correct then returns 1
! 1131: *
! 1132: * format is $XXXXX,1,2,3,4*ML
! 1133: *
! 1134: * 8-bit XOR of characters between $ and * noninclusive is transmitted
! 1135: * in last two chars M and L holding most and least significant nibbles
! 1136: * in hex representation such as:
! 1137: *
! 1138: * $GPGLL,5057.970,N,00146.110,E,142451,A*27
! 1139: * $GPVTG,089.0,T,,,15.2,N,,*7F
! 1140: */
! 1141: int
! 1142: nmea_checksum_ok(
! 1143: const char *sentence
! 1144: )
! 1145: {
! 1146: u_char my_cs;
! 1147: u_long input_cs;
! 1148: const char *p;
! 1149:
! 1150: my_cs = 0;
! 1151: p = sentence;
! 1152:
! 1153: if ('$' != *p++)
! 1154: return 0;
! 1155:
! 1156: for ( ; *p && '*' != *p; p++) {
! 1157:
! 1158: my_cs ^= *p;
! 1159: }
! 1160:
! 1161: if ('*' != *p++)
! 1162: return 0;
! 1163:
! 1164: if (0 == p[0] || 0 == p[1] || 0 != p[2])
! 1165: return 0;
! 1166:
! 1167: if (0 == hextoint(p, &input_cs))
! 1168: return 0;
! 1169:
! 1170: if (my_cs != input_cs)
! 1171: return 0;
! 1172:
! 1173: return 1;
! 1174: }
! 1175:
! 1176: /*
! 1177: * -------------------------------------------------------------------
! 1178: * funny calendar-oriented stuff -- a bit hard to grok.
! 1179: * -------------------------------------------------------------------
! 1180: */
! 1181: /*
! 1182: * Do a periodic unfolding of a truncated value around a given pivot
! 1183: * value.
! 1184: * The result r will hold to pivot <= r < pivot+period (period>0) or
! 1185: * pivot+period < r <= pivot (period < 0) and value % period == r % period,
! 1186: * using floor division convention.
! 1187: */
! 1188: static time_t
! 1189: nmea_periodic_unfold(
! 1190: time_t pivot,
! 1191: time_t value,
! 1192: time_t period)
! 1193: {
! 1194: /*
! 1195: * This will only work as long as 'value - pivot%period' does
! 1196: * not create a signed overflow condition.
! 1197: */
! 1198: value = (value - (pivot % period)) % period;
! 1199: if (value && (value ^ period) < 0)
! 1200: value += period;
! 1201: return pivot + value;
! 1202: }
! 1203:
! 1204: /*
! 1205: * Unfold a time-of-day (seconds since midnight) around the current
! 1206: * system time in a manner that guarantees an absolute difference of
! 1207: * less than 12hrs.
! 1208: *
! 1209: * This function is used for NMEA sentences that contain no date
! 1210: * information. This requires the system clock to be in +/-12hrs
! 1211: * around the true time, or the clock will synchronize the system 1day
! 1212: * off if not augmented with a time sources that also provide the
! 1213: * necessary date information.
! 1214: *
! 1215: * The function updates the refclockproc structure is also uses as
! 1216: * input to fetch the time from.
! 1217: */
! 1218: static void
! 1219: nmea_day_unfold(
! 1220: struct calendar *jd)
! 1221: {
! 1222: time_t value, pivot;
! 1223: struct tm *tdate;
! 1224:
! 1225: value = ((time_t)jd->hour * MINSPERHR
! 1226: + (time_t)jd->minute) * SECSPERMIN
! 1227: + (time_t)jd->second;
! 1228: pivot = time(NULL) - SECSPERDAY/2;
! 1229:
! 1230: value = nmea_periodic_unfold(pivot, value, SECSPERDAY);
! 1231: tdate = gmtime(&value);
! 1232: if (tdate) {
! 1233: jd->year = tdate->tm_year + 1900;
! 1234: jd->yearday = tdate->tm_yday + 1;
! 1235: jd->month = tdate->tm_mon + 1;
! 1236: jd->monthday = tdate->tm_mday;
! 1237: jd->hour = tdate->tm_hour;
! 1238: jd->minute = tdate->tm_min;
! 1239: jd->second = tdate->tm_sec;
! 1240: } else {
! 1241: jd->year = 0;
! 1242: jd->yearday = 0;
! 1243: jd->month = 0;
! 1244: jd->monthday = 0;
! 1245: }
! 1246: }
! 1247:
! 1248: /*
! 1249: * Unfold a 2-digit year into full year spec around the current year
! 1250: * of the system time. This requires the system clock to be in -79/+19
! 1251: * years around the true time, or the result will be off by
! 1252: * 100years. The assymetric behaviour was chosen to enable inital sync
! 1253: * for systems that do not have a battery-backup-clock and start with
! 1254: * a date that is typically years in the past.
! 1255: *
! 1256: * The function updates the calendar structure that is also used as
! 1257: * input to fetch the year from.
! 1258: */
! 1259: static void
! 1260: nmea_century_unfold(
! 1261: struct calendar *jd)
! 1262: {
! 1263: time_t pivot_time;
! 1264: struct tm *pivot_date;
! 1265: time_t pivot_year;
! 1266:
! 1267: /* get warp limit and century start of pivot from system time */
! 1268: pivot_time = time(NULL);
! 1269: pivot_date = gmtime(&pivot_time);
! 1270: pivot_year = pivot_date->tm_year + 1900 - 20;
! 1271: jd->year = nmea_periodic_unfold(pivot_year, jd->year, 100);
! 1272: }
! 1273:
! 1274: #else
! 1275: int refclock_nmea_bs;
! 1276: #endif /* REFCLOCK && CLOCK_NMEA */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>