154359Sroberto/* 254359Sroberto * refclock_nmea.c - clock driver for an NMEA GPS CLOCK 354359Sroberto * Michael Petry Jun 20, 1994 454359Sroberto * based on refclock_heathn.c 5280849Scy * 6280849Scy * Updated to add support for Accord GPS Clock 7280849Scy * Venu Gopal Dec 05, 2007 8280849Scy * neo.venu@gmail.com, venugopal_d@pgad.gov.in 9280849Scy * 10280849Scy * Updated to process 'time1' fudge factor 11280849Scy * Venu Gopal May 05, 2008 12280849Scy * 13280849Scy * Converted to common PPSAPI code, separate PPS fudge time1 14280849Scy * from serial timecode fudge time2. 15280849Scy * Dave Hart July 1, 2009 16280849Scy * hart@ntp.org, davehart@davehart.com 1754359Sroberto */ 18280849Scy 1954359Sroberto#ifdef HAVE_CONFIG_H 2054359Sroberto#include <config.h> 2154359Sroberto#endif 2254359Sroberto 23280849Scy#include "ntp_types.h" 24280849Scy 2554359Sroberto#if defined(REFCLOCK) && defined(CLOCK_NMEA) 2654359Sroberto 27280849Scy#define NMEA_WRITE_SUPPORT 0 /* no write support at the moment */ 28280849Scy 29280849Scy#include <sys/stat.h> 30182007Sroberto#include <stdio.h> 31182007Sroberto#include <ctype.h> 32280849Scy#ifdef HAVE_SYS_SOCKET_H 33280849Scy#include <sys/socket.h> 34280849Scy#endif 35182007Sroberto 3654359Sroberto#include "ntpd.h" 3754359Sroberto#include "ntp_io.h" 3882498Sroberto#include "ntp_unixtime.h" 3954359Sroberto#include "ntp_refclock.h" 4054359Sroberto#include "ntp_stdlib.h" 41358659Scy#include "ntp_calgps.h" 42280849Scy#include "timespecops.h" 4354359Sroberto 4482498Sroberto#ifdef HAVE_PPSAPI 45182007Sroberto# include "ppsapi_timepps.h" 46280849Scy# include "refclock_atom.h" 4782498Sroberto#endif /* HAVE_PPSAPI */ 4882498Sroberto 49200576Sroberto 5054359Sroberto/* 51280849Scy * This driver supports NMEA-compatible GPS receivers 5254359Sroberto * 53280849Scy * Prototype was refclock_trak.c, Thanks a lot. 5454359Sroberto * 5554359Sroberto * The receiver used spits out the NMEA sentences for boat navigation. 56280849Scy * And you thought it was an information superhighway. Try a raging river 5754359Sroberto * filled with rapids and whirlpools that rip away your data and warp time. 5882498Sroberto * 5982498Sroberto * If HAVE_PPSAPI is defined code to use the PPSAPI will be compiled in. 6082498Sroberto * On startup if initialization of the PPSAPI fails, it will fall back 6182498Sroberto * to the "normal" timestamps. 6282498Sroberto * 6382498Sroberto * The PPSAPI part of the driver understands fudge flag2 and flag3. If 6482498Sroberto * flag2 is set, it will use the clear edge of the pulse. If flag3 is 6582498Sroberto * set, kernel hardpps is enabled. 6682498Sroberto * 6782498Sroberto * GPS sentences other than RMC (the default) may be enabled by setting 6882498Sroberto * the relevent bits of 'mode' in the server configuration line 6982498Sroberto * server 127.127.20.x mode X 70358659Scy * 7182498Sroberto * bit 0 - enables RMC (1) 7282498Sroberto * bit 1 - enables GGA (2) 7382498Sroberto * bit 2 - enables GLL (4) 74280849Scy * bit 3 - enables ZDA (8) - Standard Time & Date 75358659Scy * bit 3 - enables ZDG (8) - Accord GPS Clock's custom sentence with GPS time 76280849Scy * very close to standard ZDA 77358659Scy * 78280849Scy * Multiple sentences may be selected except when ZDG/ZDA is selected. 79280849Scy * 80280849Scy * bit 4/5/6 - selects the baudrate for serial port : 81358659Scy * 0 for 4800 (default) 82358659Scy * 1 for 9600 83358659Scy * 2 for 19200 84358659Scy * 3 for 38400 85358659Scy * 4 for 57600 86358659Scy * 5 for 115200 8754359Sroberto */ 88280849Scy#define NMEA_MESSAGE_MASK 0x0000FF0FU 89280849Scy#define NMEA_BAUDRATE_MASK 0x00000070U 90280849Scy#define NMEA_BAUDRATE_SHIFT 4 9154359Sroberto 92358659Scy#define NMEA_DELAYMEAS_MASK 0x00000080U 93280849Scy#define NMEA_EXTLOG_MASK 0x00010000U 94358659Scy#define NMEA_QUIETPPS_MASK 0x00020000U 95358659Scy#define NMEA_DATETRUST_MASK 0x00040000U 96280849Scy 97358659Scy#define NMEA_PROTO_IDLEN 4 /* tag name must be at least 4 chars */ 98280849Scy#define NMEA_PROTO_MINLEN 6 /* min chars in sentence, excluding CS */ 99280849Scy#define NMEA_PROTO_MAXLEN 80 /* max chars in sentence, excluding CS */ 100280849Scy#define NMEA_PROTO_FIELDS 32 /* not official; limit on fields per record */ 101280849Scy 10254359Sroberto/* 103280849Scy * We check the timecode format and decode its contents. We only care 104280849Scy * about a few of them, the most important being the $GPRMC format: 105280849Scy * 106280849Scy * $GPRMC,hhmmss,a,fddmm.xx,n,dddmmm.xx,w,zz.z,yyy.,ddmmyy,dd,v*CC 107280849Scy * 108280849Scy * mode (0,1,2,3) selects sentence ANY/ALL, RMC, GGA, GLL, ZDA 109280849Scy * $GPGLL,3513.8385,S,14900.7851,E,232420.594,A*21 110280849Scy * $GPGGA,232420.59,3513.8385,S,14900.7851,E,1,05,3.4,00519,M,,,,*3F 111280849Scy * $GPRMC,232418.19,A,3513.8386,S,14900.7853,E,00.0,000.0,121199,12.,E*77 112280849Scy * 113280849Scy * Defining GPZDA to support Standard Time & Date 114358659Scy * sentence. The sentence has the following format 115358659Scy * 116280849Scy * $--ZDA,HHMMSS.SS,DD,MM,YYYY,TH,TM,*CS<CR><LF> 117280849Scy * 118358659Scy * Apart from the familiar fields, 119280849Scy * 'TH' Time zone Hours 120280849Scy * 'TM' Time zone Minutes 121280849Scy * 122358659Scy * Defining GPZDG to support Accord GPS Clock's custom NMEA 123358659Scy * sentence. The sentence has the following format 124358659Scy * 125280849Scy * $GPZDG,HHMMSS.S,DD,MM,YYYY,AA.BB,V*CS<CR><LF> 126280849Scy * 127280849Scy * It contains the GPS timestamp valid for next PPS pulse. 128358659Scy * Apart from the familiar fields, 129358659Scy * 'AA.BB' denotes the signal strength( should be < 05.00 ) 130358659Scy * 'V' denotes the GPS sync status : 131358659Scy * '0' indicates INVALID time, 132280849Scy * '1' indicates accuracy of +/-20 ms 133280849Scy * '2' indicates accuracy of +/-100 ns 134280849Scy * 135280849Scy * Defining PGRMF for Garmin GPS Fix Data 136280849Scy * $PGRMF,WN,WS,DATE,TIME,LS,LAT,LAT_DIR,LON,LON_DIR,MODE,FIX,SPD,DIR,PDOP,TDOP 137280849Scy * WN -- GPS week number (weeks since 1980-01-06, mod 1024) 138280849Scy * WS -- GPS seconds in week 139280849Scy * LS -- GPS leap seconds, accumulated ( UTC + LS == GPS ) 140280849Scy * FIX -- Fix type: 0=nofix, 1=2D, 2=3D 141280849Scy * DATE/TIME are standard date/time strings in UTC time scale 142280849Scy * 143280849Scy * The GPS time can be used to get the full century for the truncated 144280849Scy * date spec. 145280849Scy */ 146280849Scy 147280849Scy/* 14854359Sroberto * Definitions 14954359Sroberto */ 150280849Scy#define DEVICE "/dev/gps%d" /* GPS serial device */ 151280849Scy#define PPSDEV "/dev/gpspps%d" /* PPSAPI device override */ 15254359Sroberto#define SPEED232 B4800 /* uart speed (4800 bps) */ 15354359Sroberto#define PRECISION (-9) /* precision assumed (about 2 ms) */ 15482498Sroberto#define PPS_PRECISION (-20) /* precision assumed (about 1 us) */ 155358659Scy#define DATE_HOLD 16 /* seconds to hold on provided GPS date */ 156358659Scy#define DATE_HLIM 4 /* when do we take ANY date format */ 15754359Sroberto#define REFID "GPS\0" /* reference id */ 15854359Sroberto#define DESCRIPTION "NMEA GPS Clock" /* who we are */ 159280849Scy#ifndef O_NOCTTY 160280849Scy#define M_NOCTTY 0 161280849Scy#else 162280849Scy#define M_NOCTTY O_NOCTTY 163280849Scy#endif 164280849Scy#ifndef O_NONBLOCK 165280849Scy#define M_NONBLOCK 0 166280849Scy#else 167280849Scy#define M_NONBLOCK O_NONBLOCK 168280849Scy#endif 169280849Scy#define PPSOPENMODE (O_RDWR | M_NOCTTY | M_NONBLOCK) 17054359Sroberto 171280849Scy/* NMEA sentence array indexes for those we use */ 172280849Scy#define NMEA_GPRMC 0 /* recommended min. nav. */ 173280849Scy#define NMEA_GPGGA 1 /* fix and quality */ 174280849Scy#define NMEA_GPGLL 2 /* geo. lat/long */ 175280849Scy#define NMEA_GPZDA 3 /* date/time */ 176280849Scy/* 177280849Scy * $GPZDG is a proprietary sentence that violates the spec, by not 178280849Scy * using $P and an assigned company identifier to prefix the sentence 179280849Scy * identifier. When used with this driver, the system needs to be 180280849Scy * isolated from other NTP networks, as it operates in GPS time, not 181280849Scy * UTC as is much more common. GPS time is >15 seconds different from 182280849Scy * UTC due to not respecting leap seconds since 1970 or so. Other 183280849Scy * than the different timebase, $GPZDG is similar to $GPZDA. 184280849Scy */ 185280849Scy#define NMEA_GPZDG 4 186280849Scy#define NMEA_PGRMF 5 187358659Scy#define NMEA_PUBX04 6 188358659Scy#define NMEA_ARRAY_SIZE (NMEA_PUBX04 + 1) 18954359Sroberto 19054359Sroberto/* 191280849Scy * Sentence selection mode bits 19254359Sroberto */ 193280849Scy#define USE_GPRMC 0x00000001u 194280849Scy#define USE_GPGGA 0x00000002u 195280849Scy#define USE_GPGLL 0x00000004u 196280849Scy#define USE_GPZDA 0x00000008u 197280849Scy#define USE_PGRMF 0x00000100u 198358659Scy#define USE_PUBX04 0x00000200u 19954359Sroberto 200280849Scy/* mapping from sentence index to controlling mode bit */ 201280849Scystatic const u_int32 sentence_mode[NMEA_ARRAY_SIZE] = 202280849Scy{ 203280849Scy USE_GPRMC, 204280849Scy USE_GPGGA, 205280849Scy USE_GPGLL, 206280849Scy USE_GPZDA, 207280849Scy USE_GPZDA, 208358659Scy USE_PGRMF, 209358659Scy USE_PUBX04 210280849Scy}; 211280849Scy 212280849Scy/* date formats we support */ 213280849Scyenum date_fmt { 214280849Scy DATE_1_DDMMYY, /* use 1 field with 2-digit year */ 215280849Scy DATE_3_DDMMYYYY /* use 3 fields with 4-digit year */ 216280849Scy}; 217280849Scy 218358659Scy/* date type */ 219358659Scyenum date_type { 220358659Scy DTYP_NONE, 221358659Scy DTYP_Y2D, /* 2-digit year */ 222358659Scy DTYP_W10B, /* 10-bit week in GPS epoch */ 223358659Scy DTYP_Y4D, /* 4-digit (full) year */ 224358659Scy DTYP_WEXT /* extended week in GPS epoch */ 225358659Scy}; 226358659Scy 227280849Scy/* results for 'field_init()' 228280849Scy * 229280849Scy * Note: If a checksum is present, the checksum test must pass OK or the 230280849Scy * sentence is tagged invalid. 231280849Scy */ 232280849Scy#define CHECK_EMPTY -1 /* no data */ 233280849Scy#define CHECK_INVALID 0 /* not a valid NMEA sentence */ 234280849Scy#define CHECK_VALID 1 /* valid but without checksum */ 235280849Scy#define CHECK_CSVALID 2 /* valid with checksum OK */ 236280849Scy 23754359Sroberto/* 23854359Sroberto * Unit control structure 23954359Sroberto */ 240358659Scystruct refclock_atom; 241358659Scytypedef struct refclock_atom TAtomUnit; 242280849Scytypedef struct { 243358659Scy# ifdef HAVE_PPSAPI 244358659Scy TAtomUnit atom; /* PPSAPI structure */ 245358659Scy int ppsapi_fd; /* fd used with PPSAPI */ 246358659Scy u_char ppsapi_tried; /* attempt PPSAPI once */ 247358659Scy u_char ppsapi_lit; /* time_pps_create() worked */ 248358659Scy# endif /* HAVE_PPSAPI */ 249358659Scy uint16_t rcvtout; /* one-shot for sample expiration */ 250358659Scy u_char ppsapi_gate; /* system is on PPS */ 251358659Scy u_char gps_time; /* use GPS time, not UTC */ 252358659Scy l_fp last_reftime; /* last processed reference stamp */ 253358659Scy TNtpDatum last_gpsdate; /* last processed split date/time */ 254358659Scy u_short hold_gpsdate; /* validity ticker for above */ 255358659Scy u_short type_gpsdate; /* date info type for above */ 256280849Scy /* tally stats, reset each poll cycle */ 257280849Scy struct 258280849Scy { 259280849Scy u_int total; 260280849Scy u_int accepted; 261280849Scy u_int rejected; /* GPS said not enough signal */ 262280849Scy u_int malformed; /* Bad checksum, invalid date or time */ 263280849Scy u_int filtered; /* mode bits, not GPZDG, same second */ 264280849Scy u_int pps_used; 265358659Scy } 266280849Scy tally; 267280849Scy /* per sentence checksum seen flag */ 268358659Scy u_char cksum_type[NMEA_ARRAY_SIZE]; 269358659Scy 270358659Scy /* line assembly buffer (NMEAD support) */ 271358659Scy u_short lb_len; 272358659Scy char lb_buf[BMAX]; /* assembly buffer */ 273280849Scy} nmea_unit; 27454359Sroberto 27554359Sroberto/* 276280849Scy * helper for faster field access 277280849Scy */ 278280849Scytypedef struct { 279280849Scy char *base; /* buffer base */ 280280849Scy char *cptr; /* current field ptr */ 281280849Scy int blen; /* buffer length */ 282280849Scy int cidx; /* current field index */ 283280849Scy} nmea_data; 284280849Scy 285280849Scy/* 28654359Sroberto * Function prototypes 28754359Sroberto */ 288280849Scystatic int nmea_start (int, struct peer *); 289280849Scystatic void nmea_shutdown (int, struct peer *); 290280849Scystatic void nmea_receive (struct recvbuf *); 291280849Scystatic void nmea_poll (int, struct peer *); 292362716Scystatic void nmea_procrec (struct peer * const, l_fp); 29382498Sroberto#ifdef HAVE_PPSAPI 294358659Scystatic double tabsdiffd (l_fp, l_fp); 295280849Scystatic void nmea_control (int, const struct refclockstat *, 296280849Scy struct refclockstat *, struct peer *); 297280849Scy#define NMEA_CONTROL nmea_control 298280849Scy#else 299280849Scy#define NMEA_CONTROL noentry 30082498Sroberto#endif /* HAVE_PPSAPI */ 301280849Scystatic void nmea_timer (int, struct peer *); 30254359Sroberto 303280849Scy/* parsing helpers */ 304280849Scystatic int field_init (nmea_data * data, char * cp, int len); 305280849Scystatic char * field_parse (nmea_data * data, int fn); 306280849Scystatic void field_wipe (nmea_data * data, ...); 307280849Scystatic u_char parse_qual (nmea_data * data, int idx, 308280849Scy char tag, int inv); 309358659Scystatic int parse_time (TCivilDate * jd, l_fp * fofs, 310280849Scy nmea_data *, int idx); 311358659Scystatic int parse_date (TCivilDate * jd, nmea_data *, 312280849Scy int idx, enum date_fmt fmt); 313358659Scystatic int parse_gpsw (TGpsDatum *, nmea_data *, 314280849Scy int weekidx, int timeidx, int leapidx); 315280849Scy 316280849Scystatic int nmead_open (const char * device); 317280849Scy 31854359Sroberto/* 319358659Scy * If we want the driver to output sentences, too: re-enable the send 320280849Scy * support functions by defining NMEA_WRITE_SUPPORT to non-zero... 321280849Scy */ 322280849Scy#if NMEA_WRITE_SUPPORT 323280849Scy 324280849Scystatic void gps_send(int, const char *, struct peer *); 325280849Scy# ifdef SYS_WINNT 326280849Scy# undef write /* ports/winnt/include/config.h: #define write _write */ 327280849Scyextern int async_write(int, const void *, unsigned int); 328280849Scy# define write(fd, data, octets) async_write(fd, data, octets) 329280849Scy# endif /* SYS_WINNT */ 330280849Scy 331280849Scy#endif /* NMEA_WRITE_SUPPORT */ 332280849Scy 333280849Scy/* 334280849Scy * ------------------------------------------------------------------- 33554359Sroberto * Transfer vector 336280849Scy * ------------------------------------------------------------------- 33754359Sroberto */ 338280849Scystruct refclock refclock_nmea = { 33954359Sroberto nmea_start, /* start up driver */ 340280849Scy nmea_shutdown, /* shut down driver */ 34154359Sroberto nmea_poll, /* transmit poll message */ 342280849Scy NMEA_CONTROL, /* fudge control */ 343358659Scy noentry, /* initialize driver */ 34454359Sroberto noentry, /* buginfo */ 345280849Scy nmea_timer /* called once per second */ 34654359Sroberto}; 34754359Sroberto 348280849Scy 349280849Scy/* 350280849Scy * ------------------------------------------------------------------- 35154359Sroberto * nmea_start - open the GPS devices and initialize data for processing 352280849Scy * 353280849Scy * return 0 on error, 1 on success. Even on error the peer structures 354280849Scy * must be in a state that permits 'nmea_shutdown()' to clean up all 355280849Scy * resources, because it will be called immediately to do so. 356280849Scy * ------------------------------------------------------------------- 35754359Sroberto */ 35854359Srobertostatic int 35954359Srobertonmea_start( 360280849Scy int unit, 361280849Scy struct peer * peer 36254359Sroberto ) 36354359Sroberto{ 364280849Scy struct refclockproc * const pp = peer->procptr; 365280849Scy nmea_unit * const up = emalloc_zero(sizeof(*up)); 366280849Scy char device[20]; 367280849Scy size_t devlen; 368280849Scy u_int32 rate; 369280849Scy int baudrate; 370280849Scy const char * baudtext; 37154359Sroberto 37256746Sroberto 373280849Scy /* Get baudrate choice from mode byte bits 4/5/6 */ 374280849Scy rate = (peer->ttl & NMEA_BAUDRATE_MASK) >> NMEA_BAUDRATE_SHIFT; 37554359Sroberto 376280849Scy switch (rate) { 377280849Scy case 0: 378280849Scy baudrate = SPEED232; 379280849Scy baudtext = "4800"; 380280849Scy break; 381280849Scy case 1: 382280849Scy baudrate = B9600; 383280849Scy baudtext = "9600"; 384280849Scy break; 385280849Scy case 2: 386280849Scy baudrate = B19200; 387280849Scy baudtext = "19200"; 388280849Scy break; 389280849Scy case 3: 390280849Scy baudrate = B38400; 391280849Scy baudtext = "38400"; 392280849Scy break; 393358659Scy# ifdef B57600 394280849Scy case 4: 395280849Scy baudrate = B57600; 396280849Scy baudtext = "57600"; 397280849Scy break; 398358659Scy# endif 399358659Scy# ifdef B115200 400280849Scy case 5: 401280849Scy baudrate = B115200; 402280849Scy baudtext = "115200"; 403280849Scy break; 404358659Scy# endif 405280849Scy default: 406280849Scy baudrate = SPEED232; 407280849Scy baudtext = "4800 (fallback)"; 408280849Scy break; 409280849Scy } 410182007Sroberto 411280849Scy /* Allocate and initialize unit structure */ 412280849Scy pp->unitptr = (caddr_t)up; 413280849Scy pp->io.fd = -1; 41454359Sroberto pp->io.clock_recv = nmea_receive; 415280849Scy pp->io.srcclock = peer; 41654359Sroberto pp->io.datalen = 0; 417280849Scy /* force change detection on first valid message */ 418280849Scy memset(&up->last_reftime, 0xFF, sizeof(up->last_reftime)); 419358659Scy memset(&up->last_gpsdate, 0x00, sizeof(up->last_gpsdate)); 420280849Scy /* force checksum on GPRMC, see below */ 421280849Scy up->cksum_type[NMEA_GPRMC] = CHECK_CSVALID; 422358659Scy# ifdef HAVE_PPSAPI 423280849Scy up->ppsapi_fd = -1; 424358659Scy# endif /* HAVE_PPSAPI */ 425280849Scy ZERO(up->tally); 42654359Sroberto 427280849Scy /* Initialize miscellaneous variables */ 42882498Sroberto peer->precision = PRECISION; 42954359Sroberto pp->clockdesc = DESCRIPTION; 430280849Scy memcpy(&pp->refid, REFID, 4); 43154359Sroberto 432280849Scy /* Open serial port. Use CLK line discipline, if available. */ 433280849Scy devlen = snprintf(device, sizeof(device), DEVICE, unit); 434280849Scy if (devlen >= sizeof(device)) { 435280849Scy msyslog(LOG_ERR, "%s clock device name too long", 436280849Scy refnumtoa(&peer->srcadr)); 437280849Scy return FALSE; /* buffer overflow */ 43882498Sroberto } 439280849Scy pp->io.fd = refclock_open(device, baudrate, LDISC_CLK); 440280849Scy if (0 >= pp->io.fd) { 441280849Scy pp->io.fd = nmead_open(device); 442280849Scy if (-1 == pp->io.fd) 443280849Scy return FALSE; 444280849Scy } 445280849Scy LOGIF(CLOCKINFO, (LOG_NOTICE, "%s serial %s open at %s bps", 446280849Scy refnumtoa(&peer->srcadr), device, baudtext)); 447280849Scy 448280849Scy /* succeed if this clock can be added */ 449280849Scy return io_addclock(&pp->io) != 0; 45054359Sroberto} 45154359Sroberto 45254359Sroberto/* 453280849Scy * ------------------------------------------------------------------- 45454359Sroberto * nmea_shutdown - shut down a GPS clock 455358659Scy * 456280849Scy * NOTE this routine is called after nmea_start() returns failure, 457280849Scy * as well as during a normal shutdown due to ntpq :config unpeer. 458280849Scy * ------------------------------------------------------------------- 45954359Sroberto */ 46054359Srobertostatic void 46154359Srobertonmea_shutdown( 462280849Scy int unit, 463280849Scy struct peer * peer 46454359Sroberto ) 46554359Sroberto{ 466280849Scy struct refclockproc * const pp = peer->procptr; 467280849Scy nmea_unit * const up = (nmea_unit *)pp->unitptr; 46854359Sroberto 469280849Scy UNUSED_ARG(unit); 470280849Scy 471280849Scy if (up != NULL) { 472358659Scy# ifdef HAVE_PPSAPI 473280849Scy if (up->ppsapi_lit) 474280849Scy time_pps_destroy(up->atom.handle); 475280849Scy if (up->ppsapi_tried && up->ppsapi_fd != pp->io.fd) 476280849Scy close(up->ppsapi_fd); 477358659Scy# endif 478280849Scy free(up); 479280849Scy } 480280849Scy pp->unitptr = (caddr_t)NULL; 481280849Scy if (-1 != pp->io.fd) 482280849Scy io_closeclock(&pp->io); 483280849Scy pp->io.fd = -1; 48454359Sroberto} 48554359Sroberto 48654359Sroberto/* 487280849Scy * ------------------------------------------------------------------- 488280849Scy * nmea_control - configure fudge params 489280849Scy * ------------------------------------------------------------------- 49082498Sroberto */ 491280849Scy#ifdef HAVE_PPSAPI 49282498Srobertostatic void 49382498Srobertonmea_control( 494280849Scy int unit, 495280849Scy const struct refclockstat * in_st, 496280849Scy struct refclockstat * out_st, 497280849Scy struct peer * peer 49882498Sroberto ) 49982498Sroberto{ 500280849Scy struct refclockproc * const pp = peer->procptr; 501280849Scy nmea_unit * const up = (nmea_unit *)pp->unitptr; 50282498Sroberto 503280849Scy char device[32]; 504280849Scy size_t devlen; 505358659Scy 506280849Scy UNUSED_ARG(in_st); 507280849Scy UNUSED_ARG(out_st); 508280849Scy 509280849Scy /* 510280849Scy * PPS control 511280849Scy * 512280849Scy * If /dev/gpspps$UNIT can be opened that will be used for 513280849Scy * PPSAPI. Otherwise, the GPS serial device /dev/gps$UNIT 514280849Scy * already opened is used for PPSAPI as well. (This might not 515280849Scy * work, in which case the PPS API remains unavailable...) 516280849Scy */ 517280849Scy 518280849Scy /* Light up the PPSAPI interface if not yet attempted. */ 519280849Scy if ((CLK_FLAG1 & pp->sloppyclockflag) && !up->ppsapi_tried) { 520280849Scy up->ppsapi_tried = TRUE; 521280849Scy devlen = snprintf(device, sizeof(device), PPSDEV, unit); 522280849Scy if (devlen < sizeof(device)) { 523280849Scy up->ppsapi_fd = open(device, PPSOPENMODE, 524280849Scy S_IRUSR | S_IWUSR); 525280849Scy } else { 526280849Scy up->ppsapi_fd = -1; 527280849Scy msyslog(LOG_ERR, "%s PPS device name too long", 528280849Scy refnumtoa(&peer->srcadr)); 529280849Scy } 530280849Scy if (-1 == up->ppsapi_fd) 531358659Scy up->ppsapi_fd = pp->io.fd; 532280849Scy if (refclock_ppsapi(up->ppsapi_fd, &up->atom)) { 533280849Scy /* use the PPS API for our own purposes now. */ 534280849Scy up->ppsapi_lit = refclock_params( 535280849Scy pp->sloppyclockflag, &up->atom); 536280849Scy if (!up->ppsapi_lit) { 537280849Scy /* failed to configure, drop PPS unit */ 538280849Scy time_pps_destroy(up->atom.handle); 539280849Scy msyslog(LOG_WARNING, 540280849Scy "%s set PPSAPI params fails", 541358659Scy refnumtoa(&peer->srcadr)); 542280849Scy } 543280849Scy /* note: the PPS I/O handle remains valid until 544358659Scy * flag1 is cleared or the clock is shut down. 545280849Scy */ 546280849Scy } else { 547280849Scy msyslog(LOG_WARNING, 548280849Scy "%s flag1 1 but PPSAPI fails", 549280849Scy refnumtoa(&peer->srcadr)); 550280849Scy } 551280849Scy } 552280849Scy 553280849Scy /* shut down PPS API if activated */ 554358659Scy if ( !(CLK_FLAG1 & pp->sloppyclockflag) && up->ppsapi_tried) { 555280849Scy /* shutdown PPS API */ 556280849Scy if (up->ppsapi_lit) 557280849Scy time_pps_destroy(up->atom.handle); 558280849Scy up->atom.handle = 0; 559280849Scy /* close/drop PPS fd */ 560280849Scy if (up->ppsapi_fd != pp->io.fd) 561280849Scy close(up->ppsapi_fd); 562280849Scy up->ppsapi_fd = -1; 563280849Scy 564280849Scy /* clear markers and peer items */ 565280849Scy up->ppsapi_gate = FALSE; 566280849Scy up->ppsapi_lit = FALSE; 567280849Scy up->ppsapi_tried = FALSE; 568280849Scy 569280849Scy peer->flags &= ~FLAG_PPS; 570280849Scy peer->precision = PRECISION; 571280849Scy } 57282498Sroberto} 573358659Scy#endif /* HAVE_PPSAPI */ 57482498Sroberto 57582498Sroberto/* 576280849Scy * ------------------------------------------------------------------- 577280849Scy * nmea_timer - called once per second 578280849Scy * 579280849Scy * Usually 'nmea_receive()' can get a timestamp every second, but at 580280849Scy * least one Motorola unit needs prompting each time. Doing so in 581280849Scy * 'nmea_poll()' gives only one sample per poll cycle, which actually 582280849Scy * defeats the purpose of the median filter. Polling once per second 583280849Scy * seems a much better idea. 584358659Scy * 585358659Scy * Also takes care of sample expiration if the receiver fails to 586358659Scy * provide new input data. 587280849Scy * ------------------------------------------------------------------- 58882498Sroberto */ 589280849Scystatic void 590280849Scynmea_timer( 591280849Scy int unit, 592280849Scy struct peer * peer 59382498Sroberto ) 59482498Sroberto{ 595280849Scy struct refclockproc * const pp = peer->procptr; 596358659Scy nmea_unit * const up = (nmea_unit *)pp->unitptr; 59782498Sroberto 598280849Scy UNUSED_ARG(unit); 59982498Sroberto 600358659Scy# if NMEA_WRITE_SUPPORT 601358659Scy 602280849Scy if (-1 != pp->io.fd) /* any mode bits to evaluate here? */ 603280849Scy gps_send(pp->io.fd, "$PMOTG,RMC,0000*1D\r\n", peer); 60482498Sroberto 605358659Scy# endif /* NMEA_WRITE_SUPPORT */ 606280849Scy 607358659Scy /* receive timeout occurred? */ 608358659Scy if (up->rcvtout) { 609358659Scy --up->rcvtout; 610358659Scy } else if (pp->codeproc != pp->coderecv) { 611358659Scy /* expire one (the oldest) sample, if any */ 612358659Scy refclock_samples_expire(pp, 1); 613358659Scy /* reset message assembly buffer */ 614358659Scy up->lb_buf[0] = '\0'; 615358659Scy up->lb_len = 0; 616358659Scy } 61782498Sroberto 618358659Scy if (up->hold_gpsdate && (--up->hold_gpsdate < DATE_HLIM)) 619358659Scy up->type_gpsdate = DTYP_NONE; 62082498Sroberto} 62182498Sroberto 62282498Sroberto/* 623280849Scy * ------------------------------------------------------------------- 624358659Scy * nmea_procrec - receive data from the serial interface 625280849Scy * 626280849Scy * This is the workhorse for NMEA data evaluation: 627280849Scy * 628280849Scy * + it checks all NMEA data, and rejects sentences that are not valid 629280849Scy * NMEA sentences 630280849Scy * + it checks whether a sentence is known and to be used 631280849Scy * + it parses the time and date data from the NMEA data string and 632358659Scy * augments the missing bits. (century in date, whole date, ...) 633280849Scy * + it rejects data that is not from the first accepted sentence in a 634280849Scy * burst 635280849Scy * + it eventually replaces the receive time with the PPS edge time. 636280849Scy * + it feeds the data to the internal processing stages. 637358659Scy * 638358659Scy * This function assumes a non-empty line in the unit line buffer. 639280849Scy * ------------------------------------------------------------------- 64054359Sroberto */ 64154359Srobertostatic void 642358659Scynmea_procrec( 643358659Scy struct peer * const peer, 644358659Scy l_fp rd_timestamp 64554359Sroberto ) 64654359Sroberto{ 647358659Scy /* declare & init control structure pointers */ 648280849Scy struct refclockproc * const pp = peer->procptr; 649280849Scy nmea_unit * const up = (nmea_unit*)pp->unitptr; 650280849Scy 65182498Sroberto /* Use these variables to hold data until we decide its worth keeping */ 652280849Scy nmea_data rdata; 653358659Scy l_fp rd_reftime; 65454359Sroberto 655280849Scy /* working stuff */ 656358659Scy TCivilDate date; /* to keep & convert the time stamp */ 657358659Scy TGpsDatum wgps; /* week time storage */ 658358659Scy TNtpDatum dntp; 659358659Scy l_fp tofs; /* offset to full-second reftime */ 660280849Scy /* results of sentence/date/time parsing */ 661280849Scy u_char sentence; /* sentence tag */ 662280849Scy int checkres; 663358659Scy int warp; /* warp to GPS base date */ 664280849Scy char * cp; 665358659Scy int rc_date, rc_time; 666358659Scy u_short rc_dtyp; 667358659Scy# ifdef HAVE_PPSAPI 668358659Scy int withpps = 0; 669358659Scy# endif /* HAVE_PPSAPI */ 67054359Sroberto 671280849Scy /* make sure data has defined pristine state */ 672280849Scy ZERO(tofs); 673280849Scy ZERO(date); 674358659Scy ZERO(wgps); 675358659Scy ZERO(dntp); 676289764Sglebius 677358659Scy /* 678358659Scy * Read the timecode and timestamp, then initialize field 679280849Scy * processing. The <CR><LF> at the NMEA line end is translated 680280849Scy * to <LF><LF> by the terminal input routines on most systems, 681280849Scy * and this gives us one spurious empty read per record which we 682280849Scy * better ignore silently. 68354359Sroberto */ 684358659Scy checkres = field_init(&rdata, up->lb_buf, up->lb_len); 685280849Scy switch (checkres) { 68654359Sroberto 687280849Scy case CHECK_INVALID: 688280849Scy DPRINTF(1, ("%s invalid data: '%s'\n", 689358659Scy refnumtoa(&peer->srcadr), up->lb_buf)); 690280849Scy refclock_report(peer, CEVNT_BADREPLY); 691280849Scy return; 69254359Sroberto 693280849Scy case CHECK_EMPTY: 694280849Scy return; 695280849Scy 696280849Scy default: 697280849Scy DPRINTF(1, ("%s gpsread: %d '%s'\n", 698358659Scy refnumtoa(&peer->srcadr), up->lb_len, 699358659Scy up->lb_buf)); 700280849Scy break; 701280849Scy } 702280849Scy up->tally.total++; 703280849Scy 704358659Scy /* 705280849Scy * --> below this point we have a valid NMEA sentence <-- 706280849Scy * 707280849Scy * Check sentence name. Skip first 2 chars (talker ID) in most 708280849Scy * cases, to allow for $GLGGA and $GPGGA etc. Since the name 709280849Scy * field has at least 5 chars we can simply shift the field 710280849Scy * start. 71154359Sroberto */ 712280849Scy cp = field_parse(&rdata, 0); 713280849Scy if (strncmp(cp + 2, "RMC,", 4) == 0) 714280849Scy sentence = NMEA_GPRMC; 715280849Scy else if (strncmp(cp + 2, "GGA,", 4) == 0) 716280849Scy sentence = NMEA_GPGGA; 717280849Scy else if (strncmp(cp + 2, "GLL,", 4) == 0) 718280849Scy sentence = NMEA_GPGLL; 719280849Scy else if (strncmp(cp + 2, "ZDA,", 4) == 0) 720280849Scy sentence = NMEA_GPZDA; 721280849Scy else if (strncmp(cp + 2, "ZDG,", 4) == 0) 722280849Scy sentence = NMEA_GPZDG; 723358659Scy else if (strncmp(cp, "PGRMF,", 6) == 0) 724280849Scy sentence = NMEA_PGRMF; 725358659Scy else if (strncmp(cp, "PUBX,04,", 8) == 0) 726358659Scy sentence = NMEA_PUBX04; 72754359Sroberto else 728280849Scy return; /* not something we know about */ 72954359Sroberto 730280849Scy /* Eventually output delay measurement now. */ 731280849Scy if (peer->ttl & NMEA_DELAYMEAS_MASK) { 732280849Scy mprintf_clock_stats(&peer->srcadr, "delay %0.6f %.*s", 733280849Scy ldexp(rd_timestamp.l_uf, -32), 734358659Scy (int)(strchr(up->lb_buf, ',') - up->lb_buf), 735358659Scy up->lb_buf); 736280849Scy } 737358659Scy 73882498Sroberto /* See if I want to process this message type */ 739280849Scy if ((peer->ttl & NMEA_MESSAGE_MASK) && 740280849Scy !(peer->ttl & sentence_mode[sentence])) { 741280849Scy up->tally.filtered++; 74282498Sroberto return; 743280849Scy } 74482498Sroberto 745358659Scy /* 746280849Scy * make sure it came in clean 747280849Scy * 748280849Scy * Apparently, older NMEA specifications (which are expensive) 749280849Scy * did not require the checksum for all sentences. $GPMRC is 750280849Scy * the only one so far identified which has always been required 751280849Scy * to include a checksum. 752280849Scy * 753280849Scy * Today, most NMEA GPS receivers checksum every sentence. To 754280849Scy * preserve its error-detection capabilities with modern GPSes 755280849Scy * while allowing operation without checksums on all but $GPMRC, 756280849Scy * we keep track of whether we've ever seen a valid checksum on 757280849Scy * a given sentence, and if so, reject future instances without 758280849Scy * checksum. ('up->cksum_type[NMEA_GPRMC]' is set in 759280849Scy * 'nmea_start()' to enforce checksums for $GPRMC right from the 760280849Scy * start.) 761280849Scy */ 762280849Scy if (up->cksum_type[sentence] <= (u_char)checkres) { 763280849Scy up->cksum_type[sentence] = (u_char)checkres; 764280849Scy } else { 765280849Scy DPRINTF(1, ("%s checksum missing: '%s'\n", 766358659Scy refnumtoa(&peer->srcadr), up->lb_buf)); 767280849Scy refclock_report(peer, CEVNT_BADREPLY); 768280849Scy up->tally.malformed++; 769280849Scy return; 770280849Scy } 77182498Sroberto 772280849Scy /* 773280849Scy * $GPZDG provides GPS time not UTC, and the two mix poorly. 774280849Scy * Once have processed a $GPZDG, do not process any further UTC 775280849Scy * sentences (all but $GPZDG currently). 776358659Scy */ 777358659Scy if (sentence == NMEA_GPZDG) { 778358659Scy if (!up->gps_time) { 779358659Scy msyslog(LOG_INFO, 780358659Scy "%s using GPS time as if it were UTC", 781358659Scy refnumtoa(&peer->srcadr)); 782358659Scy up->gps_time = 1; 783358659Scy } 784358659Scy } else { 785358659Scy if (up->gps_time) { 786358659Scy up->tally.filtered++; 787358659Scy return; 788358659Scy } 789280849Scy } 79082498Sroberto 791280849Scy DPRINTF(1, ("%s processing %d bytes, timecode '%s'\n", 792358659Scy refnumtoa(&peer->srcadr), up->lb_len, up->lb_buf)); 79382498Sroberto 794280849Scy /* 795280849Scy * Grab fields depending on clock string type and possibly wipe 796280849Scy * sensitive data from the last timecode. 797280849Scy */ 798358659Scy rc_date = -1; /* assume we have to do day-time mapping */ 799358659Scy rc_dtyp = DTYP_NONE; 800358659Scy switch (sentence) { 80182498Sroberto 802280849Scy case NMEA_GPRMC: 803280849Scy /* Check quality byte, fetch data & time */ 804358659Scy rc_time = parse_time(&date, &tofs, &rdata, 1); 805280849Scy pp->leap = parse_qual(&rdata, 2, 'A', 0); 806358659Scy if (up->type_gpsdate <= DTYP_Y2D) { 807358659Scy rc_date = parse_date(&date, &rdata, 9, DATE_1_DDMMYY); 808358659Scy rc_dtyp = DTYP_Y2D; 809358659Scy } 810358659Scy if (CLK_FLAG4 & pp->sloppyclockflag) 811280849Scy field_wipe(&rdata, 3, 4, 5, 6, -1); 812280849Scy break; 81382498Sroberto 814280849Scy case NMEA_GPGGA: 815280849Scy /* Check quality byte, fetch time only */ 816358659Scy rc_time = parse_time(&date, &tofs, &rdata, 1); 817280849Scy pp->leap = parse_qual(&rdata, 6, '0', 1); 818280849Scy if (CLK_FLAG4 & pp->sloppyclockflag) 819280849Scy field_wipe(&rdata, 2, 4, -1); 82082498Sroberto break; 82182498Sroberto 822280849Scy case NMEA_GPGLL: 823280849Scy /* Check quality byte, fetch time only */ 824358659Scy rc_time = parse_time(&date, &tofs, &rdata, 5); 825280849Scy pp->leap = parse_qual(&rdata, 6, 'A', 0); 826280849Scy if (CLK_FLAG4 & pp->sloppyclockflag) 827280849Scy field_wipe(&rdata, 1, 3, -1); 828280849Scy break; 829358659Scy 830280849Scy case NMEA_GPZDA: 831280849Scy /* No quality. Assume best, fetch time & full date */ 832358659Scy rc_time = parse_time(&date, &tofs, &rdata, 1); 833358659Scy if (up->type_gpsdate <= DTYP_Y4D) { 834358659Scy rc_date = parse_date(&date, &rdata, 2, DATE_3_DDMMYYYY); 835358659Scy rc_dtyp = DTYP_Y4D; 836358659Scy } 837280849Scy break; 83882498Sroberto 839280849Scy case NMEA_GPZDG: 840280849Scy /* Check quality byte, fetch time & full date */ 841358659Scy rc_time = parse_time(&date, &tofs, &rdata, 1); 842280849Scy pp->leap = parse_qual(&rdata, 4, '0', 1); 843358659Scy --tofs.l_ui; /* GPZDG gives *following* second */ 844358659Scy if (up->type_gpsdate <= DTYP_Y4D) { 845358659Scy rc_date = parse_date(&date, &rdata, 2, DATE_3_DDMMYYYY); 846358659Scy rc_dtyp = DTYP_Y4D; 847358659Scy } 84882498Sroberto break; 84982498Sroberto 850280849Scy case NMEA_PGRMF: 851358659Scy /* get time, qualifier and GPS weektime. */ 852358659Scy rc_time = parse_time(&date, &tofs, &rdata, 4); 853358659Scy if (up->type_gpsdate <= DTYP_W10B) { 854358659Scy rc_date = parse_gpsw(&wgps, &rdata, 1, 2, 5); 855358659Scy rc_dtyp = DTYP_W10B; 856358659Scy } 857358659Scy pp->leap = parse_qual(&rdata, 11, '0', 1); 858280849Scy if (CLK_FLAG4 & pp->sloppyclockflag) 859280849Scy field_wipe(&rdata, 6, 8, -1); 86082498Sroberto break; 861358659Scy 862358659Scy case NMEA_PUBX04: 863358659Scy /* PUBX,04 is peculiar. The UTC time-of-week is the *internal* 864358659Scy * time base, which is not exactly on par with the fix time. 865358659Scy */ 866358659Scy rc_time = parse_time(&date, &tofs, &rdata, 2); 867358659Scy if (up->type_gpsdate <= DTYP_WEXT) { 868358659Scy rc_date = parse_gpsw(&wgps, &rdata, 5, 4, -1); 869358659Scy rc_dtyp = DTYP_WEXT; 870358659Scy } 871358659Scy break; 872358659Scy 873280849Scy default: 874280849Scy INVARIANT(0); /* Coverity 97123 */ 875280849Scy return; 876280849Scy } 87782498Sroberto 878358659Scy /* check clock sanity; [bug 2143] */ 879358659Scy if (pp->leap == LEAP_NOTINSYNC) { /* no good status? */ 880358659Scy checkres = CEVNT_PROP; 881358659Scy up->tally.rejected++; 882358659Scy } 883280849Scy /* Check sanity of time-of-day. */ 884358659Scy else if (rc_time == 0) { /* no time or conversion error? */ 885280849Scy checkres = CEVNT_BADTIME; 886280849Scy up->tally.malformed++; 887280849Scy } 888280849Scy /* Check sanity of date. */ 889358659Scy else if (rc_date == 0) { /* no date or conversion error? */ 890280849Scy checkres = CEVNT_BADDATE; 891280849Scy up->tally.malformed++; 892280849Scy } 893358659Scy else { 894358659Scy checkres = -1; 895280849Scy } 89682498Sroberto 897280849Scy if (checkres != -1) { 898358659Scy refclock_save_lcode(pp, up->lb_buf, up->lb_len); 899280849Scy refclock_report(peer, checkres); 90082498Sroberto return; 901280849Scy } 90282498Sroberto 903358659Scy /* See if we can augment the receive time stamp. If not, apply 904358659Scy * fudge time 2 to the receive time stamp directly. 905358659Scy */ 906358659Scy# ifdef HAVE_PPSAPI 907358659Scy if (up->ppsapi_lit && pp->leap != LEAP_NOTINSYNC) 908358659Scy withpps = refclock_ppsaugment( 909358659Scy &up->atom, &rd_timestamp, 910358659Scy pp->fudgetime2, pp->fudgetime1); 911358659Scy else 912358659Scy# endif /* HAVE_PPSAPI */ 913358659Scy rd_timestamp = ntpfp_with_fudge( 914358659Scy rd_timestamp, pp->fudgetime2); 915280849Scy 916358659Scy /* set the GPS base date, if possible */ 917358659Scy warp = !(peer->ttl & NMEA_DATETRUST_MASK); 918358659Scy if (rc_dtyp != DTYP_NONE) { 919358659Scy DPRINTF(1, ("%s saving date, type=%hu\n", 920358659Scy refnumtoa(&peer->srcadr), rc_dtyp)); 921358659Scy switch (rc_dtyp) { 922358659Scy case DTYP_W10B: 923358659Scy up->last_gpsdate = gpsntp_from_gpscal_ex( 924358659Scy &wgps, (warp = TRUE)); 925358659Scy break; 926358659Scy case DTYP_WEXT: 927358659Scy up->last_gpsdate = gpsntp_from_gpscal_ex( 928358659Scy &wgps, warp); 929358659Scy break; 930358659Scy default: 931358659Scy up->last_gpsdate = gpsntp_from_calendar_ex( 932358659Scy &date, tofs, warp); 933358659Scy break; 934358659Scy } 935358659Scy up->type_gpsdate = rc_dtyp; 936358659Scy up->hold_gpsdate = DATE_HOLD; 93782498Sroberto } 938358659Scy /* now convert and possibly extend/expand the time stamp. */ 939358659Scy if (up->hold_gpsdate) { /* time of day, based */ 940358659Scy dntp = gpsntp_from_daytime2_ex( 941358659Scy &date, tofs, &up->last_gpsdate, warp); 942358659Scy } else { /* time of day, floating */ 943358659Scy dntp = gpsntp_from_daytime1_ex( 944358659Scy &date, tofs, rd_timestamp, warp); 945358659Scy } 946358659Scy 947358659Scy if (debug) { 948358659Scy /* debug print time stamp */ 949358659Scy gpsntp_to_calendar(&date, &dntp); 950358659Scy# ifdef HAVE_PPSAPI 951358659Scy DPRINTF(1, ("%s effective timecode: %s (%s PPS)\n", 952358659Scy refnumtoa(&peer->srcadr), 953358659Scy ntpcal_iso8601std(NULL, 0, &date), 954358659Scy (withpps ? "with" : "without"))); 955358659Scy# else /* ?HAVE_PPSAPI */ 956358659Scy DPRINTF(1, ("%s effective timecode: %s\n", 957358659Scy refnumtoa(&peer->srcadr), 958358659Scy ntpcal_iso8601std(NULL, 0, &date))); 959358659Scy# endif /* !HAVE_PPSAPI */ 960358659Scy } 961358659Scy 962358659Scy /* Get the reference time stamp from the calendar buffer. 963280849Scy * Process the new sample in the median filter and determine the 964280849Scy * timecode timestamp, but only if the PPS is not in control. 965280849Scy * Discard sentence if reference time did not change. 966280849Scy */ 967358659Scy rd_reftime = ntpfp_from_ntpdatum(&dntp); 968280849Scy if (L_ISEQU(&up->last_reftime, &rd_reftime)) { 969280849Scy /* Do not touch pp->a_lastcode on purpose! */ 970280849Scy up->tally.filtered++; 971280849Scy return; 972280849Scy } 973280849Scy up->last_reftime = rd_reftime; 97482498Sroberto 975280849Scy DPRINTF(1, ("%s using '%s'\n", 976358659Scy refnumtoa(&peer->srcadr), up->lb_buf)); 97754359Sroberto 978280849Scy /* Data will be accepted. Update stats & log data. */ 979280849Scy up->tally.accepted++; 980358659Scy refclock_save_lcode(pp, up->lb_buf, up->lb_len); 981280849Scy pp->lastrec = rd_timestamp; 982280849Scy 983358659Scy /* If we have PPS augmented receive time, we *must* have a 984358659Scy * working PPS source and we must set the flags accordingly. 985280849Scy */ 986358659Scy# ifdef HAVE_PPSAPI 987358659Scy if (withpps) { 988358659Scy up->ppsapi_gate = TRUE; 989358659Scy peer->precision = PPS_PRECISION; 990358659Scy if (tabsdiffd(rd_reftime, rd_timestamp) < 0.5) { 991358659Scy if ( ! (peer->ttl & NMEA_QUIETPPS_MASK)) 992358659Scy peer->flags |= FLAG_PPS; 993280849Scy DPRINTF(2, ("%s PPS_RELATE_PHASE\n", 994280849Scy refnumtoa(&peer->srcadr))); 995280849Scy up->tally.pps_used++; 996358659Scy } else { 997280849Scy DPRINTF(2, ("%s PPS_RELATE_EDGE\n", 998280849Scy refnumtoa(&peer->srcadr))); 99954359Sroberto } 1000358659Scy /* !Note! 'FLAG_PPS' is reset in 'nmea_poll()' */ 1001358659Scy } 1002358659Scy# endif /* HAVE_PPSAPI */ 1003358659Scy /* Whether the receive time stamp is PPS-augmented or not, 1004358659Scy * the proper fudge offset is already applied. There's no 1005358659Scy * residual fudge to process. 1006358659Scy */ 1007358659Scy refclock_process_offset(pp, rd_reftime, rd_timestamp, 0.0); 1008358659Scy up->rcvtout = 2; 1009358659Scy} 101054359Sroberto 1011358659Scy/* 1012358659Scy * ------------------------------------------------------------------- 1013358659Scy * nmea_receive - receive data from the serial interface 1014358659Scy * 1015358659Scy * With serial IO only, a single call to 'refclock_gtlin()' to get the 1016358659Scy * string would suffice to get the NMEA data. When using NMEAD, this 1017358659Scy * does unfortunately no longer hold, since TCP is stream oriented and 1018358659Scy * not line oriented, and there's no one to do the line-splitting work 1019358659Scy * of the TTY driver in line/cooked mode. 1020358659Scy * 1021358659Scy * So we have to do this manually here, and we have to live with the 1022358659Scy * fact that there could be more than one sentence in a receive buffer. 1023358659Scy * Likewise, there can be partial messages on either end. (Strictly 1024358659Scy * speaking, a receive buffer could also contain just a single fragment, 1025358659Scy * though that's unlikely.) 1026358659Scy * 1027358659Scy * We deal with that by scanning the input buffer, copying bytes from 1028358659Scy * the receive buffer to the assembly buffer as we go and calling the 1029358659Scy * record processor every time we hit a CR/LF, provided the resulting 1030358659Scy * line is not empty. Any leftovers are kept for the next round. 1031358659Scy * 1032358659Scy * Note: When used with a serial data stream, there's no change to the 1033358659Scy * previous line-oriented input: One line is copied to the buffer and 1034358659Scy * processed per call. Only with NMEAD the behavior changes, and the 1035358659Scy * timing is badly affected unless a PPS channel is also associated with 1036358659Scy * the clock instance. TCP leaves us nothing to improve on here. 1037358659Scy * ------------------------------------------------------------------- 1038358659Scy */ 1039358659Scystatic void 1040358659Scynmea_receive( 1041358659Scy struct recvbuf * rbufp 1042358659Scy ) 1043358659Scy{ 1044358659Scy /* declare & init control structure pointers */ 1045358659Scy struct peer * const peer = rbufp->recv_peer; 1046358659Scy struct refclockproc * const pp = peer->procptr; 1047358659Scy nmea_unit * const up = (nmea_unit*)pp->unitptr; 1048358659Scy 1049358659Scy const char *sp, *se; 1050358659Scy char *dp, *de; 1051358659Scy 1052358659Scy /* paranoia check: */ 1053358659Scy if (up->lb_len >= sizeof(up->lb_buf)) 1054358659Scy up->lb_len = 0; 1055358659Scy 1056358659Scy /* pick up last assembly position; leave room for NUL */ 1057358659Scy dp = up->lb_buf + up->lb_len; 1058358659Scy de = up->lb_buf + sizeof(up->lb_buf) - 1; 1059358659Scy /* set up input range */ 1060358659Scy sp = (const char *)rbufp->recv_buffer; 1061358659Scy se = sp + rbufp->recv_length; 1062358659Scy 1063358659Scy /* walk over the input data, dropping parity bits and control 1064358659Scy * chars as we go, and calling the record processor for each 1065358659Scy * complete non-empty line. 1066358659Scy */ 1067358659Scy while (sp != se) { 1068358659Scy char ch = (*sp++ & 0x7f); 1069358659Scy if (dp == up->lb_buf) { 1070358659Scy if (ch == '$') 1071358659Scy *dp++ = ch; 1072358659Scy } else if (dp > de) { 1073358659Scy dp = up->lb_buf; 1074358659Scy } else if (ch == '\n' || ch == '\r') { 1075358659Scy *dp = '\0'; 1076358659Scy up->lb_len = (int)(dp - up->lb_buf); 1077358659Scy dp = up->lb_buf; 1078358659Scy nmea_procrec(peer, rbufp->recv_time); 1079358659Scy } else if (ch >= 0x20 && ch < 0x7f) { 1080358659Scy *dp++ = ch; 1081358659Scy } 1082358659Scy } 1083358659Scy /* update state to keep for next round */ 1084358659Scy *dp = '\0'; 1085358659Scy up->lb_len = (int)(dp - up->lb_buf); 1086280849Scy} 108782498Sroberto 1088280849Scy/* 1089280849Scy * ------------------------------------------------------------------- 1090280849Scy * nmea_poll - called by the transmit procedure 1091280849Scy * 1092280849Scy * Does the necessary bookkeeping stuff to keep the reported state of 1093280849Scy * the clock in sync with reality. 1094280849Scy * 1095280849Scy * We go to great pains to avoid changing state here, since there may 1096280849Scy * be more than one eavesdropper receiving the same timecode. 1097280849Scy * ------------------------------------------------------------------- 1098280849Scy */ 1099280849Scystatic void 1100280849Scynmea_poll( 1101280849Scy int unit, 1102280849Scy struct peer * peer 1103280849Scy ) 1104280849Scy{ 1105280849Scy struct refclockproc * const pp = peer->procptr; 1106280849Scy nmea_unit * const up = (nmea_unit *)pp->unitptr; 1107358659Scy 110882498Sroberto /* 1109280849Scy * Process median filter samples. If none received, declare a 1110280849Scy * timeout and keep going. 111182498Sroberto */ 1112358659Scy# ifdef HAVE_PPSAPI 1113280849Scy /* 1114280849Scy * If we don't have PPS pulses and time stamps, turn PPS down 1115280849Scy * for now. 1116280849Scy */ 1117280849Scy if (!up->ppsapi_gate) { 1118280849Scy peer->flags &= ~FLAG_PPS; 1119280849Scy peer->precision = PRECISION; 1120280849Scy } else { 1121280849Scy up->ppsapi_gate = FALSE; 1122280849Scy } 1123358659Scy# endif /* HAVE_PPSAPI */ 1124280849Scy 1125280849Scy /* 1126280849Scy * If the median filter is empty, claim a timeout. Else process 1127280849Scy * the input data and keep the stats going. 1128280849Scy */ 1129280849Scy if (pp->coderecv == pp->codeproc) { 1130358659Scy peer->flags &= ~FLAG_PPS; 1131358659Scy if (pp->currentstatus < CEVNT_TIMEOUT) 1132358659Scy refclock_report(peer, CEVNT_TIMEOUT); 1133358659Scy memset(&up->last_gpsdate, 0, sizeof(up->last_gpsdate)); 1134280849Scy } else { 1135280849Scy pp->polls++; 1136280849Scy pp->lastref = pp->lastrec; 1137280849Scy refclock_receive(peer); 1138358659Scy if (pp->currentstatus > CEVNT_NOMINAL) 1139358659Scy refclock_report(peer, CEVNT_NOMINAL); 1140280849Scy } 1141358659Scy 1142280849Scy /* 1143280849Scy * If extended logging is required, write the tally stats to the 1144280849Scy * clockstats file; otherwise just do a normal clock stats 1145280849Scy * record. Clear the tally stats anyway. 114682498Sroberto */ 1147280849Scy if (peer->ttl & NMEA_EXTLOG_MASK) { 1148280849Scy /* Log & reset counters with extended logging */ 1149280849Scy const char *nmea = pp->a_lastcode; 1150280849Scy if (*nmea == '\0') nmea = "(none)"; 1151280849Scy mprintf_clock_stats( 1152280849Scy &peer->srcadr, "%s %u %u %u %u %u %u", 1153280849Scy nmea, 1154280849Scy up->tally.total, up->tally.accepted, 1155280849Scy up->tally.rejected, up->tally.malformed, 1156280849Scy up->tally.filtered, up->tally.pps_used); 1157280849Scy } else { 1158280849Scy record_clock_stats(&peer->srcadr, pp->a_lastcode); 115982498Sroberto } 1160280849Scy ZERO(up->tally); 1161280849Scy} 116282498Sroberto 1163280849Scy#if NMEA_WRITE_SUPPORT 1164280849Scy/* 1165280849Scy * ------------------------------------------------------------------- 1166280849Scy * gps_send(fd, cmd, peer) Sends a command to the GPS receiver. 1167280849Scy * as in gps_send(fd, "rqts,u", peer); 1168280849Scy * 1169280849Scy * If 'cmd' starts with a '$' it is assumed that this command is in raw 1170280849Scy * format, that is, starts with '$', ends with '<cr><lf>' and that any 1171280849Scy * checksum is correctly provided; the command will be send 'as is' in 1172280849Scy * that case. Otherwise the function will create the necessary frame 1173280849Scy * (start char, chksum, final CRLF) on the fly. 1174280849Scy * 1175280849Scy * We don't currently send any data, but would like to send RTCM SC104 1176280849Scy * messages for differential positioning. It should also give us better 1177280849Scy * time. Without a PPS output, we're Just fooling ourselves because of 1178280849Scy * the serial code paths 1179280849Scy * ------------------------------------------------------------------- 1180280849Scy */ 1181280849Scystatic void 1182280849Scygps_send( 1183280849Scy int fd, 1184280849Scy const char * cmd, 1185280849Scy struct peer * peer 1186280849Scy ) 1187280849Scy{ 1188280849Scy /* $...*xy<CR><LF><NUL> add 7 */ 1189280849Scy char buf[NMEA_PROTO_MAXLEN + 7]; 1190280849Scy int len; 1191280849Scy u_char dcs; 1192280849Scy const u_char *beg, *end; 1193280849Scy 1194280849Scy if (*cmd != '$') { 1195280849Scy /* get checksum and length */ 1196280849Scy beg = end = (const u_char*)cmd; 1197280849Scy dcs = 0; 1198280849Scy while (*end >= ' ' && *end != '*') 1199280849Scy dcs ^= *end++; 1200280849Scy len = end - beg; 1201280849Scy /* format into output buffer with overflow check */ 1202280849Scy len = snprintf(buf, sizeof(buf), "$%.*s*%02X\r\n", 1203280849Scy len, beg, dcs); 1204280849Scy if ((size_t)len >= sizeof(buf)) { 1205280849Scy DPRINTF(1, ("%s gps_send: buffer overflow for command '%s'\n", 1206280849Scy refnumtoa(&peer->srcadr), cmd)); 1207280849Scy return; /* game over player 1 */ 1208280849Scy } 1209280849Scy cmd = buf; 1210280849Scy } else { 1211280849Scy len = strlen(cmd); 121254359Sroberto } 121354359Sroberto 1214280849Scy DPRINTF(1, ("%s gps_send: '%.*s'\n", refnumtoa(&peer->srcadr), 1215280849Scy len - 2, cmd)); 121682498Sroberto 1217280849Scy /* send out the whole stuff */ 1218280849Scy if (write(fd, cmd, len) == -1) 1219280849Scy refclock_report(peer, CEVNT_FAULT); 1220280849Scy} 1221280849Scy#endif /* NMEA_WRITE_SUPPORT */ 1222280849Scy 1223280849Scy/* 1224280849Scy * ------------------------------------------------------------------- 1225280849Scy * helpers for faster field splitting 1226280849Scy * ------------------------------------------------------------------- 1227280849Scy * 1228280849Scy * set up a field record, check syntax and verify checksum 1229280849Scy * 1230280849Scy * format is $XXXXX,1,2,3,4*ML 1231280849Scy * 1232280849Scy * 8-bit XOR of characters between $ and * noninclusive is transmitted 1233280849Scy * in last two chars M and L holding most and least significant nibbles 1234280849Scy * in hex representation such as: 1235280849Scy * 1236280849Scy * $GPGLL,5057.970,N,00146.110,E,142451,A*27 1237280849Scy * $GPVTG,089.0,T,,,15.2,N,,*7F 1238280849Scy * 1239280849Scy * Some other constraints: 1240358659Scy * + The field name must be at least 5 upcase characters or digits and 1241358659Scy * must start with a character. 1242280849Scy * + The checksum (if present) must be uppercase hex digits. 1243280849Scy * + The length of a sentence is limited to 80 characters (not including 1244280849Scy * the final CR/LF nor the checksum, but including the leading '$') 1245280849Scy * 1246280849Scy * Return values: 1247280849Scy * + CHECK_INVALID 1248280849Scy * The data does not form a valid NMEA sentence or a checksum error 1249280849Scy * occurred. 1250280849Scy * + CHECK_VALID 1251280849Scy * The data is a valid NMEA sentence but contains no checksum. 1252280849Scy * + CHECK_CSVALID 1253280849Scy * The data is a valid NMEA sentence and passed the checksum test. 1254280849Scy * ------------------------------------------------------------------- 1255280849Scy */ 1256280849Scystatic int 1257280849Scyfield_init( 1258280849Scy nmea_data * data, /* context structure */ 1259280849Scy char * cptr, /* start of raw data */ 1260280849Scy int dlen /* data len, not counting trailing NUL */ 1261280849Scy ) 1262280849Scy{ 1263280849Scy u_char cs_l; /* checksum local computed */ 1264280849Scy u_char cs_r; /* checksum remote given */ 1265280849Scy char * eptr; /* buffer end end pointer */ 1266280849Scy char tmp; /* char buffer */ 1267358659Scy 1268280849Scy cs_l = 0; 1269280849Scy cs_r = 0; 1270280849Scy /* some basic input constraints */ 1271280849Scy if (dlen < 0) 1272280849Scy dlen = 0; 1273280849Scy eptr = cptr + dlen; 1274280849Scy *eptr = '\0'; 1275358659Scy 1276358659Scy /* load data context */ 1277280849Scy data->base = cptr; 1278280849Scy data->cptr = cptr; 1279280849Scy data->cidx = 0; 1280280849Scy data->blen = dlen; 1281280849Scy 1282280849Scy /* syntax check follows here. check allowed character 1283280849Scy * sequences, updating the local computed checksum as we go. 1284280849Scy * 1285280849Scy * regex equiv: '^\$[A-Z][A-Z0-9]{4,}[^*]*(\*[0-9A-F]{2})?$' 128654359Sroberto */ 1287280849Scy 1288280849Scy /* -*- start character: '^\$' */ 1289280849Scy if (*cptr == '\0') 1290280849Scy return CHECK_EMPTY; 1291280849Scy if (*cptr++ != '$') 1292280849Scy return CHECK_INVALID; 1293280849Scy 1294280849Scy /* -*- advance context beyond start character */ 1295280849Scy data->base++; 1296280849Scy data->cptr++; 1297280849Scy data->blen--; 1298358659Scy 1299280849Scy /* -*- field name: '[A-Z][A-Z0-9]{4,},' */ 1300280849Scy if (*cptr < 'A' || *cptr > 'Z') 1301280849Scy return CHECK_INVALID; 1302280849Scy cs_l ^= *cptr++; 1303280849Scy while ((*cptr >= 'A' && *cptr <= 'Z') || 1304280849Scy (*cptr >= '0' && *cptr <= '9') ) 1305280849Scy cs_l ^= *cptr++; 1306280849Scy if (*cptr != ',' || (cptr - data->base) < NMEA_PROTO_IDLEN) 1307280849Scy return CHECK_INVALID; 1308280849Scy cs_l ^= *cptr++; 1309280849Scy 1310280849Scy /* -*- data: '[^*]*' */ 1311280849Scy while (*cptr && *cptr != '*') 1312280849Scy cs_l ^= *cptr++; 1313358659Scy 1314280849Scy /* -*- checksum field: (\*[0-9A-F]{2})?$ */ 1315280849Scy if (*cptr == '\0') 1316280849Scy return CHECK_VALID; 1317280849Scy if (*cptr != '*' || cptr != eptr - 3 || 1318280849Scy (cptr - data->base) >= NMEA_PROTO_MAXLEN) 1319280849Scy return CHECK_INVALID; 1320280849Scy 1321280849Scy for (cptr++; (tmp = *cptr) != '\0'; cptr++) { 1322280849Scy if (tmp >= '0' && tmp <= '9') 1323280849Scy cs_r = (cs_r << 4) + (tmp - '0'); 1324280849Scy else if (tmp >= 'A' && tmp <= 'F') 1325280849Scy cs_r = (cs_r << 4) + (tmp - 'A' + 10); 1326280849Scy else 1327280849Scy break; 132854359Sroberto } 1329280849Scy 1330280849Scy /* -*- make sure we are at end of string and csum matches */ 1331280849Scy if (cptr != eptr || cs_l != cs_r) 1332280849Scy return CHECK_INVALID; 1333280849Scy 1334280849Scy return CHECK_CSVALID; 1335280849Scy} 1336280849Scy 1337280849Scy/* 1338280849Scy * ------------------------------------------------------------------- 1339280849Scy * fetch a data field by index, zero being the name field. If this 1340280849Scy * function is called repeatedly with increasing indices, the total load 1341280849Scy * is O(n), n being the length of the string; if it is called with 1342280849Scy * decreasing indices, the total load is O(n^2). Try not to go backwards 1343280849Scy * too often. 1344280849Scy * ------------------------------------------------------------------- 1345280849Scy */ 1346280849Scystatic char * 1347280849Scyfield_parse( 1348280849Scy nmea_data * data, 1349280849Scy int fn 1350280849Scy ) 1351280849Scy{ 1352280849Scy char tmp; 1353280849Scy 1354280849Scy if (fn < data->cidx) { 1355280849Scy data->cidx = 0; 1356280849Scy data->cptr = data->base; 135782498Sroberto } 1358280849Scy while ((fn > data->cidx) && (tmp = *data->cptr) != '\0') { 1359280849Scy data->cidx += (tmp == ','); 1360280849Scy data->cptr++; 1361280849Scy } 1362280849Scy return data->cptr; 1363280849Scy} 136454359Sroberto 1365280849Scy/* 1366280849Scy * ------------------------------------------------------------------- 1367280849Scy * Wipe (that is, overwrite with '_') data fields and the checksum in 1368280849Scy * the last timecode. The list of field indices is given as integers 1369358659Scy * in a varargs list, preferably in ascending order, in any case 1370280849Scy * terminated by a negative field index. 1371280849Scy * 1372280849Scy * A maximum number of 8 fields can be overwritten at once to guard 1373280849Scy * against runaway (that is, unterminated) argument lists. 1374280849Scy * 1375280849Scy * This function affects what a remote user can see with 1376280849Scy * 1377280849Scy * ntpq -c clockvar <server> 1378280849Scy * 1379280849Scy * Note that this also removes the wiped fields from any clockstats 1380280849Scy * log. Some NTP operators monitor their NMEA GPS using the change in 1381280849Scy * location in clockstats over time as as a proxy for the quality of 1382280849Scy * GPS reception and thereby time reported. 1383280849Scy * ------------------------------------------------------------------- 1384280849Scy */ 1385280849Scystatic void 1386280849Scyfield_wipe( 1387280849Scy nmea_data * data, 1388280849Scy ... 1389280849Scy ) 1390280849Scy{ 1391280849Scy va_list va; /* vararg index list */ 1392280849Scy int fcnt; /* safeguard against runaway arglist */ 1393280849Scy int fidx; /* field to nuke, or -1 for checksum */ 1394280849Scy char * cp; /* overwrite destination */ 1395358659Scy 1396280849Scy fcnt = 8; 1397280849Scy cp = NULL; 1398280849Scy va_start(va, data); 1399280849Scy do { 1400280849Scy fidx = va_arg(va, int); 1401280849Scy if (fidx >= 0 && fidx <= NMEA_PROTO_FIELDS) { 1402280849Scy cp = field_parse(data, fidx); 1403280849Scy } else { 1404280849Scy cp = data->base + data->blen; 1405280849Scy if (data->blen >= 3 && cp[-3] == '*') 1406280849Scy cp -= 2; 1407280849Scy } 1408280849Scy for ( ; '\0' != *cp && '*' != *cp && ',' != *cp; cp++) 1409280849Scy if ('.' != *cp) 1410280849Scy *cp = '_'; 1411280849Scy } while (fcnt-- && fidx >= 0); 1412358659Scy va_end(va); 1413280849Scy} 1414280849Scy 1415280849Scy/* 1416280849Scy * ------------------------------------------------------------------- 1417280849Scy * PARSING HELPERS 1418280849Scy * ------------------------------------------------------------------- 1419358659Scy */ 1420358659Scytypedef unsigned char const UCC; 1421358659Scy 1422358659Scystatic char const * const s_eof_chars = ",*\r\n"; 1423358659Scy 1424358659Scystatic int field_length(UCC *cp, unsigned int nfields) 1425358659Scy{ 1426358659Scy char const * ep = (char const*)cp; 1427358659Scy ep = strpbrk(ep, s_eof_chars); 1428358659Scy if (ep && nfields) 1429358659Scy while (--nfields && ep && *ep == ',') 1430358659Scy ep = strpbrk(ep + 1, s_eof_chars); 1431358659Scy return (ep) 1432358659Scy ? (int)((UCC*)ep - cp) 1433358659Scy : (int)strlen((char const*)cp); 1434358659Scy} 1435358659Scy 1436358659Scy/* /[,*\r\n]/ --> skip */ 1437358659Scystatic int _parse_eof(UCC *cp, UCC ** ep) 1438358659Scy{ 1439358659Scy int rc = (strchr(s_eof_chars, *(char const*)cp) != NULL); 1440358659Scy *ep = cp + rc; 1441358659Scy return rc; 1442358659Scy} 1443358659Scy 1444358659Scy/* /,/ --> skip */ 1445358659Scystatic int _parse_sep(UCC *cp, UCC ** ep) 1446358659Scy{ 1447358659Scy int rc = (*cp == ','); 1448358659Scy *ep = cp + rc; 1449358659Scy return rc; 1450358659Scy} 1451358659Scy 1452358659Scy/* /[[:digit:]]{2}/ --> uint16_t */ 1453358659Scystatic int _parse_num2d(UCC *cp, UCC ** ep, uint16_t *into) 1454358659Scy{ 1455358659Scy int rc = FALSE; 1456358659Scy 1457358659Scy if (isdigit(cp[0]) && isdigit(cp[1])) { 1458358659Scy *into = (cp[0] - '0') * 10 + (cp[1] - '0'); 1459358659Scy cp += 2; 1460358659Scy rc = TRUE; 1461358659Scy } 1462358659Scy *ep = cp; 1463358659Scy return rc; 1464358659Scy} 1465358659Scy 1466358659Scy/* /[[:digit:]]+/ --> uint16_t */ 1467358659Scystatic int _parse_u16(UCC *cp, UCC **ep, uint16_t *into, unsigned int ndig) 1468358659Scy{ 1469358659Scy uint16_t num = 0; 1470358659Scy int rc = FALSE; 1471358659Scy if (isdigit(*cp) && ndig) { 1472358659Scy rc = TRUE; 1473358659Scy do 1474358659Scy num = (num * 10) + (*cp - '0'); 1475358659Scy while (isdigit(*++cp) && --ndig); 1476358659Scy *into = num; 1477358659Scy } 1478358659Scy *ep = cp; 1479358659Scy return rc; 1480358659Scy} 1481358659Scy 1482358659Scy/* /[[:digit:]]+/ --> uint32_t */ 1483358659Scystatic int _parse_u32(UCC *cp, UCC **ep, uint32_t *into, unsigned int ndig) 1484358659Scy{ 1485358659Scy uint32_t num = 0; 1486358659Scy int rc = FALSE; 1487358659Scy if (isdigit(*cp) && ndig) { 1488358659Scy rc = TRUE; 1489358659Scy do 1490358659Scy num = (num * 10) + (*cp - '0'); 1491358659Scy while (isdigit(*++cp) && --ndig); 1492358659Scy *into = num; 1493358659Scy } 1494358659Scy *ep = cp; 1495358659Scy return rc; 1496358659Scy} 1497358659Scy 1498358659Scy/* /(\.[[:digit:]]*)?/ --> l_fp{0, f} 1499358659Scy * read fractional seconds, convert to l_fp 1500280849Scy * 1501358659Scy * Only the first 9 decimal digits are evaluated; any excess is parsed 1502358659Scy * away but silently ignored. (--> truncation to 1 nanosecond) 1503358659Scy */ 1504358659Scystatic int _parse_frac(UCC *cp, UCC **ep, l_fp *into) 1505358659Scy{ 1506358659Scy static const uint32_t powtab[10] = { 1507358659Scy 0, 1508358659Scy 100000000, 10000000, 1000000, 1509358659Scy 100000, 10000, 1000, 1510358659Scy 100, 10, 1 1511358659Scy }; 1512358659Scy 1513358659Scy struct timespec ts; 1514358659Scy ZERO(ts); 1515358659Scy if (*cp == '.') { 1516358659Scy uint32_t fval = 0; 1517358659Scy UCC * sp = cp + 1; 1518358659Scy if (_parse_u32(sp, &cp, &fval, 9)) 1519358659Scy ts.tv_nsec = fval * powtab[(size_t)(cp - sp)]; 1520358659Scy while (isdigit(*cp)) 1521358659Scy ++cp; 1522358659Scy } 1523358659Scy 1524358659Scy *ep = cp; 1525358659Scy *into = tspec_intv_to_lfp(ts); 1526358659Scy return TRUE; 1527358659Scy} 1528358659Scy 1529358659Scy/* /[[:digit:]]{6}/ --> time-of-day 1530358659Scy * parses a number string representing 'HHMMSS' 1531358659Scy */ 1532358659Scystatic int _parse_time(UCC *cp, UCC ** ep, TCivilDate *into) 1533358659Scy{ 1534358659Scy uint16_t s, m, h; 1535358659Scy int rc; 1536358659Scy UCC * xp = cp; 1537358659Scy 1538358659Scy rc = _parse_num2d(cp, &cp, &h) && (h < 24) 1539358659Scy && _parse_num2d(cp, &cp, &m) && (m < 60) 1540358659Scy && _parse_num2d(cp, &cp, &s) && (s < 61); /* leap seconds! */ 1541358659Scy 1542358659Scy if (rc) { 1543358659Scy into->hour = (uint8_t)h; 1544358659Scy into->minute = (uint8_t)m; 1545358659Scy into->second = (uint8_t)s; 1546358659Scy *ep = cp; 1547358659Scy } else { 1548358659Scy *ep = xp; 1549358659Scy DPRINTF(1, ("nmea: invalid time code: '%.*s'\n", 1550358659Scy field_length(xp, 1), xp)); 1551358659Scy } 1552358659Scy return rc; 1553358659Scy} 1554358659Scy 1555358659Scy/* /[[:digit:]]{6}/ --> civil date 1556358659Scy * parses a number string representing 'ddmmyy' 1557358659Scy */ 1558358659Scystatic int _parse_date1(UCC *cp, UCC **ep, TCivilDate *into) 1559358659Scy{ 1560358659Scy unsigned short d, m, y; 1561358659Scy int rc; 1562358659Scy UCC * xp = cp; 1563358659Scy 1564358659Scy rc = _parse_num2d(cp, &cp, &d) && (d - 1 < 31) 1565358659Scy && _parse_num2d(cp, &cp, &m) && (m - 1 < 12) 1566358659Scy && _parse_num2d(cp, &cp, &y) 1567358659Scy && _parse_eof(cp, ep); 1568358659Scy if (rc) { 1569358659Scy into->monthday = (uint8_t )d; 1570358659Scy into->month = (uint8_t )m; 1571358659Scy into->year = (uint16_t)y; 1572358659Scy *ep = cp; 1573358659Scy } else { 1574358659Scy *ep = xp; 1575358659Scy DPRINTF(1, ("nmea: invalid date code: '%.*s'\n", 1576358659Scy field_length(xp, 1), xp)); 1577358659Scy } 1578358659Scy return rc; 1579358659Scy} 1580358659Scy 1581358659Scy/* /[[:digit:]]+,[[:digit:]]+,[[:digit:]]+/ --> civil date 1582358659Scy * parses three successive numeric fields as date: day,month,year 1583358659Scy */ 1584358659Scystatic int _parse_date3(UCC *cp, UCC **ep, TCivilDate *into) 1585358659Scy{ 1586358659Scy uint16_t d, m, y; 1587358659Scy int rc; 1588358659Scy UCC * xp = cp; 1589358659Scy 1590358659Scy rc = _parse_u16(cp, &cp, &d, 2) && (d - 1 < 31) 1591358659Scy && _parse_sep(cp, &cp) 1592358659Scy && _parse_u16(cp, &cp, &m, 2) && (m - 1 < 12) 1593358659Scy && _parse_sep(cp, &cp) 1594358659Scy && _parse_u16(cp, &cp, &y, 4) && (y > 1980) 1595358659Scy && _parse_eof(cp, ep); 1596358659Scy if (rc) { 1597358659Scy into->monthday = (uint8_t )d; 1598358659Scy into->month = (uint8_t )m; 1599358659Scy into->year = (uint16_t)y; 1600358659Scy *ep = cp; 1601358659Scy } else { 1602358659Scy *ep = xp; 1603358659Scy DPRINTF(1, ("nmea: invalid date code: '%.*s'\n", 1604358659Scy field_length(xp, 3), xp)); 1605358659Scy } 1606358659Scy return rc; 1607358659Scy} 1608358659Scy 1609358659Scy/* 1610358659Scy * ------------------------------------------------------------------- 1611280849Scy * Check sync status 1612280849Scy * 1613280849Scy * If the character at the data field start matches the tag value, 1614280849Scy * return LEAP_NOWARNING and LEAP_NOTINSYNC otherwise. If the 'inverted' 1615280849Scy * flag is given, just the opposite value is returned. If there is no 1616280849Scy * data field (*cp points to the NUL byte) the result is LEAP_NOTINSYNC. 1617280849Scy * ------------------------------------------------------------------- 1618280849Scy */ 1619280849Scystatic u_char 1620280849Scyparse_qual( 1621280849Scy nmea_data * rd, 1622280849Scy int idx, 1623280849Scy char tag, 1624280849Scy int inv 1625280849Scy ) 1626280849Scy{ 1627358659Scy static const u_char table[2] = { 1628358659Scy LEAP_NOTINSYNC, LEAP_NOWARNING }; 1629280849Scy 1630358659Scy char * dp = field_parse(rd, idx); 1631358659Scy 1632280849Scy return table[ *dp && ((*dp == tag) == !inv) ]; 1633280849Scy} 1634280849Scy 1635280849Scy/* 1636280849Scy * ------------------------------------------------------------------- 1637280849Scy * Parse a time stamp in HHMMSS[.sss] format with error checking. 1638280849Scy * 1639280849Scy * returns 1 on success, 0 on failure 1640280849Scy * ------------------------------------------------------------------- 1641280849Scy */ 1642280849Scystatic int 1643280849Scyparse_time( 1644280849Scy struct calendar * jd, /* result calendar pointer */ 1645358659Scy l_fp * fofs, /* storage for nsec fraction */ 1646280849Scy nmea_data * rd, 1647280849Scy int idx 1648280849Scy ) 1649280849Scy{ 1650358659Scy UCC * dp = (UCC*)field_parse(rd, idx); 1651280849Scy 1652358659Scy return _parse_time(dp, &dp, jd) 1653358659Scy && _parse_frac(dp, &dp, fofs) 1654358659Scy && _parse_eof (dp, &dp); 1655280849Scy} 1656280849Scy 1657280849Scy/* 1658280849Scy * ------------------------------------------------------------------- 1659280849Scy * Parse a date string from an NMEA sentence. This could either be a 1660280849Scy * partial date in DDMMYY format in one field, or DD,MM,YYYY full date 1661280849Scy * spec spanning three fields. This function does some extensive error 1662280849Scy * checking to make sure the date string was consistent. 1663280849Scy * 1664280849Scy * returns 1 on success, 0 on failure 1665280849Scy * ------------------------------------------------------------------- 1666280849Scy */ 1667280849Scystatic int 1668280849Scyparse_date( 1669280849Scy struct calendar * jd, /* result pointer */ 1670280849Scy nmea_data * rd, 1671280849Scy int idx, 1672280849Scy enum date_fmt fmt 1673280849Scy ) 1674280849Scy{ 1675358659Scy UCC * dp = (UCC*)field_parse(rd, idx); 1676358659Scy 1677280849Scy switch (fmt) { 1678280849Scy case DATE_1_DDMMYY: 1679358659Scy return _parse_date1(dp, &dp, jd); 1680280849Scy case DATE_3_DDMMYYYY: 1681358659Scy return _parse_date3(dp, &dp, jd); 1682280849Scy default: 1683280849Scy DPRINTF(1, ("nmea: invalid parse format: %d\n", fmt)); 1684358659Scy break; 168554359Sroberto } 1686358659Scy return FALSE; 1687280849Scy} 168882498Sroberto 1689280849Scy/* 1690280849Scy * ------------------------------------------------------------------- 1691280849Scy * Parse GPS week time info from an NMEA sentence. This info contains 1692280849Scy * the GPS week number, the GPS time-of-week and the leap seconds GPS 1693280849Scy * to UTC. 1694280849Scy * 1695280849Scy * returns 1 on success, 0 on failure 1696280849Scy * ------------------------------------------------------------------- 1697280849Scy */ 1698280849Scystatic int 1699358659Scyparse_gpsw( 1700358659Scy TGpsDatum * wd, 1701358659Scy nmea_data * rd, 1702280849Scy int weekidx, 1703280849Scy int timeidx, 1704280849Scy int leapidx 1705280849Scy ) 1706280849Scy{ 1707358659Scy uint32_t secs; 1708358659Scy uint16_t week, leap = 0; 1709358659Scy l_fp fofs; 1710358659Scy int rc; 1711280849Scy 1712358659Scy UCC * dpw = (UCC*)field_parse(rd, weekidx); 1713358659Scy UCC * dps = (UCC*)field_parse(rd, timeidx); 1714358659Scy 1715358659Scy rc = _parse_u16 (dpw, &dpw, &week, 5) 1716358659Scy && _parse_eof (dpw, &dpw) 1717358659Scy && _parse_u32 (dps, &dps, &secs, 9) 1718358659Scy && _parse_frac(dps, &dps, &fofs) 1719358659Scy && _parse_eof (dps, &dps) 1720358659Scy && (secs < 7*SECSPERDAY); 1721358659Scy if (rc && leapidx > 0) { 1722358659Scy UCC * dpl = (UCC*)field_parse(rd, leapidx); 1723358659Scy rc = _parse_u16 (dpl, &dpl, &leap, 5) 1724358659Scy && _parse_eof (dpl, &dpl); 172554359Sroberto } 1726358659Scy if (rc) { 1727358659Scy fofs.l_ui -= leap; 1728358659Scy *wd = gpscal_from_gpsweek(week, secs, fofs); 1729358659Scy } else { 1730358659Scy DPRINTF(1, ("nmea: parse_gpsw: invalid weektime spec\n")); 1731358659Scy } 1732358659Scy return rc; 1733280849Scy} 173482498Sroberto 173582498Sroberto 1736358659Scy#ifdef HAVE_PPSAPI 1737358659Scystatic double 1738358659Scytabsdiffd( 1739358659Scy l_fp t1, 1740358659Scy l_fp t2 1741280849Scy ) 1742280849Scy{ 1743358659Scy double dd; 1744358659Scy L_SUB(&t1, &t2); 1745358659Scy LFPTOD(&t1, dd); 1746358659Scy return fabs(dd); 174754359Sroberto} 1748358659Scy#endif /* HAVE_PPSAPI */ 174954359Sroberto 175054359Sroberto/* 1751280849Scy * =================================================================== 1752280849Scy * 1753280849Scy * NMEAD support 1754280849Scy * 1755280849Scy * original nmead support added by Jon Miner (cp_n18@yahoo.com) 1756280849Scy * 1757280849Scy * See http://home.hiwaay.net/~taylorc/gps/nmea-server/ 1758280849Scy * for information about nmead 1759280849Scy * 1760280849Scy * To use this, you need to create a link from /dev/gpsX to 1761280849Scy * the server:port where nmead is running. Something like this: 1762280849Scy * 1763280849Scy * ln -s server:port /dev/gps1 1764280849Scy * 1765280849Scy * Split into separate function by Juergen Perlinger 1766280849Scy * (perlinger-at-ntp-dot-org) 1767280849Scy * 1768280849Scy * =================================================================== 1769280849Scy */ 1770280849Scystatic int 1771280849Scynmead_open( 1772280849Scy const char * device 177354359Sroberto ) 177454359Sroberto{ 1775280849Scy int fd = -1; /* result file descriptor */ 1776358659Scy 1777358659Scy# ifdef HAVE_READLINK 1778280849Scy char host[80]; /* link target buffer */ 1779280849Scy char * port; /* port name or number */ 1780280849Scy int rc; /* result code (several)*/ 1781280849Scy int sh; /* socket handle */ 1782280849Scy struct addrinfo ai_hint; /* resolution hint */ 1783280849Scy struct addrinfo *ai_list; /* resolution result */ 1784280849Scy struct addrinfo *ai; /* result scan ptr */ 178554359Sroberto 1786280849Scy fd = -1; 1787358659Scy 1788280849Scy /* try to read as link, make sure no overflow occurs */ 1789280849Scy rc = readlink(device, host, sizeof(host)); 1790280849Scy if ((size_t)rc >= sizeof(host)) 1791280849Scy return fd; /* error / overflow / truncation */ 1792280849Scy host[rc] = '\0'; /* readlink does not place NUL */ 1793280849Scy 1794280849Scy /* get port */ 1795280849Scy port = strchr(host, ':'); 1796280849Scy if (!port) 1797280849Scy return fd; /* not 'host:port' syntax ? */ 1798280849Scy *port++ = '\0'; /* put in separator */ 1799358659Scy 1800280849Scy /* get address infos and try to open socket 1801280849Scy * 1802280849Scy * This getaddrinfo() is naughty in ntpd's nonblocking main 1803280849Scy * thread, but you have to go out of your wary to use this code 1804280849Scy * and typically the blocking is at startup where its impact is 1805280849Scy * reduced. The same holds for the 'connect()', as it is 1806280849Scy * blocking, too... 1807280849Scy */ 1808280849Scy ZERO(ai_hint); 1809280849Scy ai_hint.ai_protocol = IPPROTO_TCP; 1810280849Scy ai_hint.ai_socktype = SOCK_STREAM; 1811280849Scy if (getaddrinfo(host, port, &ai_hint, &ai_list)) 1812280849Scy return fd; 1813358659Scy 1814280849Scy for (ai = ai_list; ai && (fd == -1); ai = ai->ai_next) { 1815280849Scy sh = socket(ai->ai_family, ai->ai_socktype, 1816280849Scy ai->ai_protocol); 1817280849Scy if (INVALID_SOCKET == sh) 1818280849Scy continue; 1819280849Scy rc = connect(sh, ai->ai_addr, ai->ai_addrlen); 1820280849Scy if (-1 != rc) 1821280849Scy fd = sh; 1822280849Scy else 1823280849Scy close(sh); 182454359Sroberto } 1825280849Scy freeaddrinfo(ai_list); 1826358659Scy if (fd != -1) 1827358659Scy make_socket_nonblocking(fd); 1828358659Scy# else 1829280849Scy fd = -1; 1830358659Scy# endif 1831280849Scy 1832280849Scy return fd; 183354359Sroberto} 183454359Sroberto#else 1835280849ScyNONEMPTY_TRANSLATION_UNIT 1836280849Scy#endif /* REFCLOCK && CLOCK_NMEA */ 1837