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>