1*eabc0478Schristos /* $NetBSD: refclock_wwvb.c,v 1.8 2024/08/18 20:47:19 christos Exp $ */ 2abb0f93cSkardel 3abb0f93cSkardel /* 4abb0f93cSkardel * refclock_wwvb - clock driver for Spectracom WWVB and GPS receivers 5abb0f93cSkardel */ 6abb0f93cSkardel 7abb0f93cSkardel #ifdef HAVE_CONFIG_H 8abb0f93cSkardel #include <config.h> 9abb0f93cSkardel #endif 10abb0f93cSkardel 11abb0f93cSkardel #if defined(REFCLOCK) && defined(CLOCK_SPECTRACOM) 12abb0f93cSkardel 13abb0f93cSkardel #include "ntpd.h" 14abb0f93cSkardel #include "ntp_io.h" 15abb0f93cSkardel #include "ntp_refclock.h" 16abb0f93cSkardel #include "ntp_calendar.h" 17abb0f93cSkardel #include "ntp_stdlib.h" 18abb0f93cSkardel 19abb0f93cSkardel #include <stdio.h> 20abb0f93cSkardel #include <ctype.h> 21abb0f93cSkardel 22abb0f93cSkardel #ifdef HAVE_PPSAPI 23abb0f93cSkardel #include "ppsapi_timepps.h" 24abb0f93cSkardel #include "refclock_atom.h" 25abb0f93cSkardel #endif /* HAVE_PPSAPI */ 26abb0f93cSkardel 27abb0f93cSkardel /* 28abb0f93cSkardel * This driver supports the Spectracom Model 8170 and Netclock/2 WWVB 29abb0f93cSkardel * Synchronized Clocks and the Netclock/GPS Master Clock. Both the WWVB 30abb0f93cSkardel * and GPS clocks have proven reliable sources of time; however, the 31abb0f93cSkardel * WWVB clocks have proven vulnerable to high ambient conductive RF 32abb0f93cSkardel * interference. The claimed accuracy of the WWVB clocks is 100 us 33abb0f93cSkardel * relative to the broadcast signal, while the claimed accuracy of the 34abb0f93cSkardel * GPS clock is 50 ns; however, in most cases the actual accuracy is 35abb0f93cSkardel * limited by the resolution of the timecode and the latencies of the 36abb0f93cSkardel * serial interface and operating system. 37abb0f93cSkardel * 38abb0f93cSkardel * The WWVB and GPS clocks should be configured for 24-hour display, 39abb0f93cSkardel * AUTO DST off, time zone 0 (UTC), data format 0 or 2 (see below) and 40abb0f93cSkardel * baud rate 9600. If the clock is to used as the source for the IRIG 41abb0f93cSkardel * Audio Decoder (refclock_irig.c in this distribution), it should be 42abb0f93cSkardel * configured for AM IRIG output and IRIG format 1 (IRIG B with 43abb0f93cSkardel * signature control). The GPS clock can be configured either to respond 44abb0f93cSkardel * to a 'T' poll character or left running continuously. 45abb0f93cSkardel * 46abb0f93cSkardel * There are two timecode formats used by these clocks. Format 0, which 47abb0f93cSkardel * is available with both the Netclock/2 and 8170, and format 2, which 48abb0f93cSkardel * is available only with the Netclock/2, specially modified 8170 and 49abb0f93cSkardel * GPS. 50abb0f93cSkardel * 51abb0f93cSkardel * Format 0 (22 ASCII printing characters): 52abb0f93cSkardel * 53abb0f93cSkardel * <cr><lf>i ddd hh:mm:ss TZ=zz<cr><lf> 54abb0f93cSkardel * 55abb0f93cSkardel * on-time = first <cr> 56abb0f93cSkardel * hh:mm:ss = hours, minutes, seconds 57abb0f93cSkardel * i = synchronization flag (' ' = in synch, '?' = out of synch) 58abb0f93cSkardel * 59f003fb54Skardel * The alarm condition is indicated by other than ' ' at i, which occurs 60abb0f93cSkardel * during initial synchronization and when received signal is lost for 61abb0f93cSkardel * about ten hours. 62abb0f93cSkardel * 63abb0f93cSkardel * Format 2 (24 ASCII printing characters): 64abb0f93cSkardel * 65abb0f93cSkardel * <cr><lf>iqyy ddd hh:mm:ss.fff ld 66abb0f93cSkardel * 67abb0f93cSkardel * on-time = <cr> 68abb0f93cSkardel * i = synchronization flag (' ' = in synch, '?' = out of synch) 69abb0f93cSkardel * q = quality indicator (' ' = locked, 'A'...'D' = unlocked) 70abb0f93cSkardel * yy = year (as broadcast) 71abb0f93cSkardel * ddd = day of year 72abb0f93cSkardel * hh:mm:ss.fff = hours, minutes, seconds, milliseconds 73abb0f93cSkardel * 74f003fb54Skardel * The alarm condition is indicated by other than ' ' at i, which occurs 75abb0f93cSkardel * during initial synchronization and when received signal is lost for 76abb0f93cSkardel * about ten hours. The unlock condition is indicated by other than ' ' 77abb0f93cSkardel * at q. 78abb0f93cSkardel * 79abb0f93cSkardel * The q is normally ' ' when the time error is less than 1 ms and a 80abb0f93cSkardel * character in the set 'A'...'D' when the time error is less than 10, 81abb0f93cSkardel * 100, 500 and greater than 500 ms respectively. The l is normally ' ', 82abb0f93cSkardel * but is set to 'L' early in the month of an upcoming UTC leap second 83abb0f93cSkardel * and reset to ' ' on the first day of the following month. The d is 84abb0f93cSkardel * set to 'S' for standard time 'I' on the day preceding a switch to 85abb0f93cSkardel * daylight time, 'D' for daylight time and 'O' on the day preceding a 86abb0f93cSkardel * switch to standard time. The start bit of the first <cr> is 87abb0f93cSkardel * synchronized to the indicated time as returned. 88abb0f93cSkardel * 89abb0f93cSkardel * This driver does not need to be told which format is in use - it 90abb0f93cSkardel * figures out which one from the length of the message. The driver 91abb0f93cSkardel * makes no attempt to correct for the intrinsic jitter of the radio 92abb0f93cSkardel * itself, which is a known problem with the older radios. 93abb0f93cSkardel * 94abb0f93cSkardel * PPS Signal Processing 95abb0f93cSkardel * 96abb0f93cSkardel * When PPS signal processing is enabled, and when the system clock has 97abb0f93cSkardel * been set by this or another driver and the PPS signal offset is 98abb0f93cSkardel * within 0.4 s of the system clock offset, the PPS signal replaces the 99abb0f93cSkardel * timecode for as long as the PPS signal is active. If for some reason 100abb0f93cSkardel * the PPS signal fails for one or more poll intervals, the driver 101abb0f93cSkardel * reverts to the timecode. If the timecode fails for one or more poll 102abb0f93cSkardel * intervals, the PPS signal is disconnected. 103abb0f93cSkardel * 104abb0f93cSkardel * Fudge Factors 105abb0f93cSkardel * 106abb0f93cSkardel * This driver can retrieve a table of quality data maintained 107abb0f93cSkardel * internally by the Netclock/2 clock. If flag4 of the fudge 108abb0f93cSkardel * configuration command is set to 1, the driver will retrieve this 109abb0f93cSkardel * table and write it to the clockstats file when the first timecode 110abb0f93cSkardel * message of a new day is received. 111abb0f93cSkardel * 112abb0f93cSkardel * PPS calibration fudge time 1: format 0 .003134, format 2 .004034 113abb0f93cSkardel */ 114abb0f93cSkardel /* 115abb0f93cSkardel * Interface definitions 116abb0f93cSkardel */ 117abb0f93cSkardel #define DEVICE "/dev/wwvb%d" /* device name and unit */ 118abb0f93cSkardel #define SPEED232 B9600 /* uart speed (9600 baud) */ 119abb0f93cSkardel #define PRECISION (-13) /* precision assumed (about 100 us) */ 120abb0f93cSkardel #define PPS_PRECISION (-13) /* precision assumed (about 100 us) */ 121abb0f93cSkardel #define REFID "WWVB" /* reference ID */ 122abb0f93cSkardel #define DESCRIPTION "Spectracom WWVB/GPS Receiver" /* WRU */ 123abb0f93cSkardel 124abb0f93cSkardel #define LENWWVB0 22 /* format 0 timecode length */ 125abb0f93cSkardel #define LENWWVB2 24 /* format 2 timecode length */ 126abb0f93cSkardel #define LENWWVB3 29 /* format 3 timecode length */ 127abb0f93cSkardel #define MONLIN 15 /* number of monitoring lines */ 128abb0f93cSkardel 129abb0f93cSkardel /* 130abb0f93cSkardel * WWVB unit control structure 131abb0f93cSkardel */ 132abb0f93cSkardel struct wwvbunit { 133abb0f93cSkardel #ifdef HAVE_PPSAPI 134abb0f93cSkardel struct refclock_atom atom; /* PPSAPI structure */ 135abb0f93cSkardel int ppsapi_tried; /* attempt PPSAPI once */ 136abb0f93cSkardel int ppsapi_lit; /* time_pps_create() worked */ 137abb0f93cSkardel int tcount; /* timecode sample counter */ 138abb0f93cSkardel int pcount; /* PPS sample counter */ 139abb0f93cSkardel #endif /* HAVE_PPSAPI */ 140f003fb54Skardel l_fp laststamp; /* last <CR> timestamp */ 141f003fb54Skardel int prev_eol_cr; /* was last EOL <CR> (not <LF>)? */ 142abb0f93cSkardel u_char lasthour; /* last hour (for monitor) */ 143abb0f93cSkardel u_char linect; /* count ignored lines (for monitor */ 144abb0f93cSkardel }; 145abb0f93cSkardel 146abb0f93cSkardel /* 147abb0f93cSkardel * Function prototypes 148abb0f93cSkardel */ 149abb0f93cSkardel static int wwvb_start (int, struct peer *); 150abb0f93cSkardel static void wwvb_shutdown (int, struct peer *); 151abb0f93cSkardel static void wwvb_receive (struct recvbuf *); 152abb0f93cSkardel static void wwvb_poll (int, struct peer *); 153abb0f93cSkardel static void wwvb_timer (int, struct peer *); 154abb0f93cSkardel #ifdef HAVE_PPSAPI 1552950cc38Schristos static void wwvb_control (int, const struct refclockstat *, 156abb0f93cSkardel struct refclockstat *, struct peer *); 157abb0f93cSkardel #define WWVB_CONTROL wwvb_control 158abb0f93cSkardel #else 1592950cc38Schristos #define WWVB_CONTROL (void)(*) 1602950cc38Schristos noentry 161abb0f93cSkardel #endif /* HAVE_PPSAPI */ 162abb0f93cSkardel 163abb0f93cSkardel /* 164abb0f93cSkardel * Transfer vector 165abb0f93cSkardel */ 166abb0f93cSkardel struct refclock refclock_wwvb = { 167abb0f93cSkardel wwvb_start, /* start up driver */ 168abb0f93cSkardel wwvb_shutdown, /* shut down driver */ 169abb0f93cSkardel wwvb_poll, /* transmit poll message */ 170abb0f93cSkardel WWVB_CONTROL, /* fudge set/change notification */ 171abb0f93cSkardel noentry, /* initialize driver (not used) */ 172abb0f93cSkardel noentry, /* not used (old wwvb_buginfo) */ 173abb0f93cSkardel wwvb_timer /* called once per second */ 174abb0f93cSkardel }; 175abb0f93cSkardel 176abb0f93cSkardel 177abb0f93cSkardel /* 178abb0f93cSkardel * wwvb_start - open the devices and initialize data for processing 179abb0f93cSkardel */ 180abb0f93cSkardel static int 181abb0f93cSkardel wwvb_start( 182abb0f93cSkardel int unit, 183abb0f93cSkardel struct peer *peer 184abb0f93cSkardel ) 185abb0f93cSkardel { 186abb0f93cSkardel register struct wwvbunit *up; 187abb0f93cSkardel struct refclockproc *pp; 188abb0f93cSkardel int fd; 189abb0f93cSkardel char device[20]; 190abb0f93cSkardel 191abb0f93cSkardel /* 192abb0f93cSkardel * Open serial port. Use CLK line discipline, if available. 193abb0f93cSkardel */ 194f003fb54Skardel snprintf(device, sizeof(device), DEVICE, unit); 195*eabc0478Schristos fd = refclock_open(&peer->srcadr, device, SPEED232, LDISC_CLK); 196f003fb54Skardel if (fd <= 0) 197abb0f93cSkardel return (0); 198abb0f93cSkardel 199abb0f93cSkardel /* 200abb0f93cSkardel * Allocate and initialize unit structure 201abb0f93cSkardel */ 202f003fb54Skardel up = emalloc_zero(sizeof(*up)); 203abb0f93cSkardel pp = peer->procptr; 204abb0f93cSkardel pp->io.clock_recv = wwvb_receive; 2052950cc38Schristos pp->io.srcclock = peer; 206abb0f93cSkardel pp->io.datalen = 0; 207abb0f93cSkardel pp->io.fd = fd; 208abb0f93cSkardel if (!io_addclock(&pp->io)) { 209abb0f93cSkardel close(fd); 210f003fb54Skardel pp->io.fd = -1; 211abb0f93cSkardel free(up); 212abb0f93cSkardel return (0); 213abb0f93cSkardel } 214f003fb54Skardel pp->unitptr = up; 215abb0f93cSkardel 216abb0f93cSkardel /* 217abb0f93cSkardel * Initialize miscellaneous variables 218abb0f93cSkardel */ 219abb0f93cSkardel peer->precision = PRECISION; 220abb0f93cSkardel pp->clockdesc = DESCRIPTION; 221f003fb54Skardel memcpy(&pp->refid, REFID, 4); 222abb0f93cSkardel return (1); 223abb0f93cSkardel } 224abb0f93cSkardel 225abb0f93cSkardel 226abb0f93cSkardel /* 227abb0f93cSkardel * wwvb_shutdown - shut down the clock 228abb0f93cSkardel */ 229abb0f93cSkardel static void 230abb0f93cSkardel wwvb_shutdown( 231abb0f93cSkardel int unit, 232abb0f93cSkardel struct peer *peer 233abb0f93cSkardel ) 234abb0f93cSkardel { 235abb0f93cSkardel struct refclockproc * pp; 2362950cc38Schristos struct wwvbunit * up; 237abb0f93cSkardel 238abb0f93cSkardel pp = peer->procptr; 239f003fb54Skardel up = pp->unitptr; 240f003fb54Skardel if (-1 != pp->io.fd) 241abb0f93cSkardel io_closeclock(&pp->io); 242f003fb54Skardel if (NULL != up) 243abb0f93cSkardel free(up); 244abb0f93cSkardel } 245abb0f93cSkardel 246abb0f93cSkardel 247abb0f93cSkardel /* 248abb0f93cSkardel * wwvb_receive - receive data from the serial interface 249abb0f93cSkardel */ 250abb0f93cSkardel static void 251abb0f93cSkardel wwvb_receive( 252abb0f93cSkardel struct recvbuf *rbufp 253abb0f93cSkardel ) 254abb0f93cSkardel { 255abb0f93cSkardel struct wwvbunit *up; 256abb0f93cSkardel struct refclockproc *pp; 257abb0f93cSkardel struct peer *peer; 258abb0f93cSkardel 259abb0f93cSkardel l_fp trtmp; /* arrival timestamp */ 260abb0f93cSkardel int tz; /* time zone */ 261abb0f93cSkardel int day, month; /* ddd conversion */ 262abb0f93cSkardel int temp; /* int temp */ 263abb0f93cSkardel char syncchar; /* synchronization indicator */ 264abb0f93cSkardel char qualchar; /* quality indicator */ 265abb0f93cSkardel char leapchar; /* leap indicator */ 266abb0f93cSkardel char dstchar; /* daylight/standard indicator */ 267abb0f93cSkardel char tmpchar; /* trashbin */ 268abb0f93cSkardel 269abb0f93cSkardel /* 270abb0f93cSkardel * Initialize pointers and read the timecode and timestamp 271abb0f93cSkardel */ 272f003fb54Skardel peer = rbufp->recv_peer; 273abb0f93cSkardel pp = peer->procptr; 274f003fb54Skardel up = pp->unitptr; 275abb0f93cSkardel temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); 276abb0f93cSkardel 277abb0f93cSkardel /* 278abb0f93cSkardel * Note we get a buffer and timestamp for both a <cr> and <lf>, 279abb0f93cSkardel * but only the <cr> timestamp is retained. Note: in format 0 on 280abb0f93cSkardel * a Netclock/2 or upgraded 8170 the start bit is delayed 100 281abb0f93cSkardel * +-50 us relative to the pps; however, on an unmodified 8170 282abb0f93cSkardel * the start bit can be delayed up to 10 ms. In format 2 the 283abb0f93cSkardel * reading precision is only to the millisecond. Thus, unless 284abb0f93cSkardel * you have a PPS gadget and don't have to have the year, format 285abb0f93cSkardel * 0 provides the lowest jitter. 286f003fb54Skardel * Save the timestamp of each <CR> in up->laststamp. Lines with 287f003fb54Skardel * no characters occur for every <LF>, and for some <CR>s when 288f003fb54Skardel * format 0 is used. Format 0 starts and ends each cycle with a 289f003fb54Skardel * <CR><LF> pair, format 2 starts each cycle with its only pair. 290f003fb54Skardel * The preceding <CR> is the on-time character for both formats. 291f003fb54Skardel * The timestamp provided with non-empty lines corresponds to 292f003fb54Skardel * the <CR> following the timecode, which is ultimately not used 293f003fb54Skardel * with format 0 and is used for the following timecode for 294f003fb54Skardel * format 2. 295abb0f93cSkardel */ 296abb0f93cSkardel if (temp == 0) { 297f003fb54Skardel if (up->prev_eol_cr) { 298f003fb54Skardel DPRINTF(2, ("wwvb: <LF> @ %s\n", 299f003fb54Skardel prettydate(&trtmp))); 300f003fb54Skardel } else { 301abb0f93cSkardel up->laststamp = trtmp; 302f003fb54Skardel DPRINTF(2, ("wwvb: <CR> @ %s\n", 303f003fb54Skardel prettydate(&trtmp))); 304f003fb54Skardel } 305f003fb54Skardel up->prev_eol_cr = !up->prev_eol_cr; 306abb0f93cSkardel return; 307abb0f93cSkardel } 308abb0f93cSkardel pp->lencode = temp; 309abb0f93cSkardel pp->lastrec = up->laststamp; 310f003fb54Skardel up->laststamp = trtmp; 311f003fb54Skardel up->prev_eol_cr = TRUE; 312f003fb54Skardel DPRINTF(2, ("wwvb: code @ %s\n" 313f003fb54Skardel " using %s minus one char\n", 314f003fb54Skardel prettydate(&trtmp), prettydate(&pp->lastrec))); 315f003fb54Skardel if (L_ISZERO(&pp->lastrec)) 316f003fb54Skardel return; 317abb0f93cSkardel 318abb0f93cSkardel /* 319abb0f93cSkardel * We get down to business, check the timecode format and decode 320abb0f93cSkardel * its contents. This code uses the timecode length to determine 321abb0f93cSkardel * format 0, 2 or 3. If the timecode has invalid length or is 322abb0f93cSkardel * not in proper format, we declare bad format and exit. 323abb0f93cSkardel */ 324abb0f93cSkardel syncchar = qualchar = leapchar = dstchar = ' '; 325abb0f93cSkardel tz = 0; 326abb0f93cSkardel switch (pp->lencode) { 327abb0f93cSkardel 328abb0f93cSkardel case LENWWVB0: 329abb0f93cSkardel 330abb0f93cSkardel /* 331abb0f93cSkardel * Timecode format 0: "I ddd hh:mm:ss DTZ=nn" 332abb0f93cSkardel */ 333abb0f93cSkardel if (sscanf(pp->a_lastcode, 334abb0f93cSkardel "%c %3d %2d:%2d:%2d%c%cTZ=%2d", 335abb0f93cSkardel &syncchar, &pp->day, &pp->hour, &pp->minute, 336f003fb54Skardel &pp->second, &tmpchar, &dstchar, &tz) == 8) { 337abb0f93cSkardel pp->nsec = 0; 338abb0f93cSkardel break; 339f003fb54Skardel } 340f003fb54Skardel goto bad_format; 341abb0f93cSkardel 342abb0f93cSkardel case LENWWVB2: 343abb0f93cSkardel 344abb0f93cSkardel /* 345abb0f93cSkardel * Timecode format 2: "IQyy ddd hh:mm:ss.mmm LD" */ 346abb0f93cSkardel if (sscanf(pp->a_lastcode, 347abb0f93cSkardel "%c%c %2d %3d %2d:%2d:%2d.%3ld %c", 348abb0f93cSkardel &syncchar, &qualchar, &pp->year, &pp->day, 349abb0f93cSkardel &pp->hour, &pp->minute, &pp->second, &pp->nsec, 350f003fb54Skardel &leapchar) == 9) { 351abb0f93cSkardel pp->nsec *= 1000000; 352abb0f93cSkardel break; 353f003fb54Skardel } 354f003fb54Skardel goto bad_format; 355abb0f93cSkardel 356abb0f93cSkardel case LENWWVB3: 357abb0f93cSkardel 358abb0f93cSkardel /* 359abb0f93cSkardel * Timecode format 3: "0003I yyyymmdd hhmmss+0000SL#" 360f003fb54Skardel * WARNING: Undocumented, and the on-time character # is 361f003fb54Skardel * not yet handled correctly by this driver. It may be 362f003fb54Skardel * as simple as compensating for an additional 1/960 s. 363abb0f93cSkardel */ 364abb0f93cSkardel if (sscanf(pp->a_lastcode, 365abb0f93cSkardel "0003%c %4d%2d%2d %2d%2d%2d+0000%c%c", 366abb0f93cSkardel &syncchar, &pp->year, &month, &day, &pp->hour, 367abb0f93cSkardel &pp->minute, &pp->second, &dstchar, &leapchar) == 8) 368abb0f93cSkardel { 369abb0f93cSkardel pp->day = ymd2yd(pp->year, month, day); 370abb0f93cSkardel pp->nsec = 0; 371abb0f93cSkardel break; 372abb0f93cSkardel } 373f003fb54Skardel goto bad_format; 374abb0f93cSkardel 375abb0f93cSkardel default: 376f003fb54Skardel bad_format: 377abb0f93cSkardel 378abb0f93cSkardel /* 379abb0f93cSkardel * Unknown format: If dumping internal table, record 380abb0f93cSkardel * stats; otherwise, declare bad format. 381abb0f93cSkardel */ 382abb0f93cSkardel if (up->linect > 0) { 383abb0f93cSkardel up->linect--; 384abb0f93cSkardel record_clock_stats(&peer->srcadr, 385abb0f93cSkardel pp->a_lastcode); 386abb0f93cSkardel } else { 387abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 388abb0f93cSkardel } 389abb0f93cSkardel return; 390abb0f93cSkardel } 391abb0f93cSkardel 392abb0f93cSkardel /* 393abb0f93cSkardel * Decode synchronization, quality and leap characters. If 394abb0f93cSkardel * unsynchronized, set the leap bits accordingly and exit. 395abb0f93cSkardel * Otherwise, set the leap bits according to the leap character. 396abb0f93cSkardel * Once synchronized, the dispersion depends only on the 397abb0f93cSkardel * quality character. 398abb0f93cSkardel */ 399abb0f93cSkardel switch (qualchar) { 400abb0f93cSkardel 401abb0f93cSkardel case ' ': 402abb0f93cSkardel pp->disp = .001; 403abb0f93cSkardel pp->lastref = pp->lastrec; 404abb0f93cSkardel break; 405abb0f93cSkardel 406abb0f93cSkardel case 'A': 407abb0f93cSkardel pp->disp = .01; 408abb0f93cSkardel break; 409abb0f93cSkardel 410abb0f93cSkardel case 'B': 411abb0f93cSkardel pp->disp = .1; 412abb0f93cSkardel break; 413abb0f93cSkardel 414abb0f93cSkardel case 'C': 415abb0f93cSkardel pp->disp = .5; 416abb0f93cSkardel break; 417abb0f93cSkardel 418abb0f93cSkardel case 'D': 419abb0f93cSkardel pp->disp = MAXDISPERSE; 420abb0f93cSkardel break; 421abb0f93cSkardel 422abb0f93cSkardel default: 423abb0f93cSkardel pp->disp = MAXDISPERSE; 424abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 425abb0f93cSkardel break; 426abb0f93cSkardel } 427abb0f93cSkardel if (syncchar != ' ') 428abb0f93cSkardel pp->leap = LEAP_NOTINSYNC; 429abb0f93cSkardel else if (leapchar == 'L') 430abb0f93cSkardel pp->leap = LEAP_ADDSECOND; 431abb0f93cSkardel else 432abb0f93cSkardel pp->leap = LEAP_NOWARNING; 433abb0f93cSkardel 434abb0f93cSkardel /* 435abb0f93cSkardel * Process the new sample in the median filter and determine the 436abb0f93cSkardel * timecode timestamp, but only if the PPS is not in control. 437abb0f93cSkardel */ 438abb0f93cSkardel #ifdef HAVE_PPSAPI 439abb0f93cSkardel up->tcount++; 440abb0f93cSkardel if (peer->flags & FLAG_PPS) 441abb0f93cSkardel return; 442abb0f93cSkardel 443abb0f93cSkardel #endif /* HAVE_PPSAPI */ 444abb0f93cSkardel if (!refclock_process_f(pp, pp->fudgetime2)) 445abb0f93cSkardel refclock_report(peer, CEVNT_BADTIME); 446abb0f93cSkardel } 447abb0f93cSkardel 448abb0f93cSkardel 449abb0f93cSkardel /* 450abb0f93cSkardel * wwvb_timer - called once per second by the transmit procedure 451abb0f93cSkardel */ 452abb0f93cSkardel static void 453abb0f93cSkardel wwvb_timer( 454abb0f93cSkardel int unit, 455abb0f93cSkardel struct peer *peer 456abb0f93cSkardel ) 457abb0f93cSkardel { 458abb0f93cSkardel register struct wwvbunit *up; 459abb0f93cSkardel struct refclockproc *pp; 460abb0f93cSkardel char pollchar; /* character sent to clock */ 4612950cc38Schristos #ifdef DEBUG 462f003fb54Skardel l_fp now; 4632950cc38Schristos #endif 464abb0f93cSkardel 465abb0f93cSkardel /* 466abb0f93cSkardel * Time to poll the clock. The Spectracom clock responds to a 467abb0f93cSkardel * 'T' by returning a timecode in the format(s) specified above. 468abb0f93cSkardel * Note there is no checking on state, since this may not be the 469abb0f93cSkardel * only customer reading the clock. Only one customer need poll 470abb0f93cSkardel * the clock; all others just listen in. 471abb0f93cSkardel */ 472abb0f93cSkardel pp = peer->procptr; 473f003fb54Skardel up = pp->unitptr; 474abb0f93cSkardel if (up->linect > 0) 475abb0f93cSkardel pollchar = 'R'; 476abb0f93cSkardel else 477abb0f93cSkardel pollchar = 'T'; 478abb0f93cSkardel if (write(pp->io.fd, &pollchar, 1) != 1) 479abb0f93cSkardel refclock_report(peer, CEVNT_FAULT); 480f003fb54Skardel #ifdef DEBUG 481f003fb54Skardel get_systime(&now); 482f003fb54Skardel if (debug) 483f003fb54Skardel printf("%c poll at %s\n", pollchar, prettydate(&now)); 484f003fb54Skardel #endif 485abb0f93cSkardel #ifdef HAVE_PPSAPI 486abb0f93cSkardel if (up->ppsapi_lit && 487abb0f93cSkardel refclock_pps(peer, &up->atom, pp->sloppyclockflag) > 0) { 488abb0f93cSkardel up->pcount++, 489abb0f93cSkardel peer->flags |= FLAG_PPS; 490abb0f93cSkardel peer->precision = PPS_PRECISION; 491abb0f93cSkardel } 492abb0f93cSkardel #endif /* HAVE_PPSAPI */ 493abb0f93cSkardel } 494abb0f93cSkardel 495abb0f93cSkardel 496abb0f93cSkardel /* 497abb0f93cSkardel * wwvb_poll - called by the transmit procedure 498abb0f93cSkardel */ 499abb0f93cSkardel static void 500abb0f93cSkardel wwvb_poll( 501abb0f93cSkardel int unit, 502abb0f93cSkardel struct peer *peer 503abb0f93cSkardel ) 504abb0f93cSkardel { 505abb0f93cSkardel register struct wwvbunit *up; 506abb0f93cSkardel struct refclockproc *pp; 507abb0f93cSkardel 508abb0f93cSkardel /* 509abb0f93cSkardel * Sweep up the samples received since the last poll. If none 510abb0f93cSkardel * are received, declare a timeout and keep going. 511abb0f93cSkardel */ 512abb0f93cSkardel pp = peer->procptr; 513f003fb54Skardel up = pp->unitptr; 514abb0f93cSkardel pp->polls++; 515abb0f93cSkardel 516abb0f93cSkardel /* 517abb0f93cSkardel * If the monitor flag is set (flag4), we dump the internal 518abb0f93cSkardel * quality table at the first timecode beginning the day. 519abb0f93cSkardel */ 520abb0f93cSkardel if (pp->sloppyclockflag & CLK_FLAG4 && pp->hour < 521abb0f93cSkardel (int)up->lasthour) 522abb0f93cSkardel up->linect = MONLIN; 523abb0f93cSkardel up->lasthour = (u_char)pp->hour; 524abb0f93cSkardel 525abb0f93cSkardel /* 526abb0f93cSkardel * Process median filter samples. If none received, declare a 527abb0f93cSkardel * timeout and keep going. 528abb0f93cSkardel */ 529abb0f93cSkardel #ifdef HAVE_PPSAPI 530abb0f93cSkardel if (up->pcount == 0) { 531abb0f93cSkardel peer->flags &= ~FLAG_PPS; 532abb0f93cSkardel peer->precision = PRECISION; 533abb0f93cSkardel } 534abb0f93cSkardel if (up->tcount == 0) { 535abb0f93cSkardel pp->coderecv = pp->codeproc; 536abb0f93cSkardel refclock_report(peer, CEVNT_TIMEOUT); 537abb0f93cSkardel return; 538abb0f93cSkardel } 539abb0f93cSkardel up->pcount = up->tcount = 0; 540abb0f93cSkardel #else /* HAVE_PPSAPI */ 541abb0f93cSkardel if (pp->coderecv == pp->codeproc) { 542abb0f93cSkardel refclock_report(peer, CEVNT_TIMEOUT); 543abb0f93cSkardel return; 544abb0f93cSkardel } 545abb0f93cSkardel #endif /* HAVE_PPSAPI */ 546abb0f93cSkardel refclock_receive(peer); 547abb0f93cSkardel record_clock_stats(&peer->srcadr, pp->a_lastcode); 548abb0f93cSkardel #ifdef DEBUG 549abb0f93cSkardel if (debug) 550abb0f93cSkardel printf("wwvb: timecode %d %s\n", pp->lencode, 551abb0f93cSkardel pp->a_lastcode); 552abb0f93cSkardel #endif 553abb0f93cSkardel } 554abb0f93cSkardel 555abb0f93cSkardel 556abb0f93cSkardel /* 557abb0f93cSkardel * wwvb_control - fudge parameters have been set or changed 558abb0f93cSkardel */ 559abb0f93cSkardel #ifdef HAVE_PPSAPI 560abb0f93cSkardel static void 561abb0f93cSkardel wwvb_control( 562abb0f93cSkardel int unit, 5632950cc38Schristos const struct refclockstat *in_st, 564abb0f93cSkardel struct refclockstat *out_st, 565abb0f93cSkardel struct peer *peer 566abb0f93cSkardel ) 567abb0f93cSkardel { 568abb0f93cSkardel register struct wwvbunit *up; 569abb0f93cSkardel struct refclockproc *pp; 570abb0f93cSkardel 571abb0f93cSkardel pp = peer->procptr; 572f003fb54Skardel up = pp->unitptr; 573abb0f93cSkardel 574abb0f93cSkardel if (!(pp->sloppyclockflag & CLK_FLAG1)) { 575abb0f93cSkardel if (!up->ppsapi_tried) 576abb0f93cSkardel return; 577abb0f93cSkardel up->ppsapi_tried = 0; 578abb0f93cSkardel if (!up->ppsapi_lit) 579abb0f93cSkardel return; 580abb0f93cSkardel peer->flags &= ~FLAG_PPS; 581abb0f93cSkardel peer->precision = PRECISION; 582abb0f93cSkardel time_pps_destroy(up->atom.handle); 583abb0f93cSkardel up->atom.handle = 0; 584abb0f93cSkardel up->ppsapi_lit = 0; 585abb0f93cSkardel return; 586abb0f93cSkardel } 587abb0f93cSkardel 588abb0f93cSkardel if (up->ppsapi_tried) 589abb0f93cSkardel return; 590abb0f93cSkardel /* 591abb0f93cSkardel * Light up the PPSAPI interface. 592abb0f93cSkardel */ 593abb0f93cSkardel up->ppsapi_tried = 1; 594abb0f93cSkardel if (refclock_ppsapi(pp->io.fd, &up->atom)) { 595abb0f93cSkardel up->ppsapi_lit = 1; 596abb0f93cSkardel return; 597abb0f93cSkardel } 598abb0f93cSkardel 599abb0f93cSkardel msyslog(LOG_WARNING, "%s flag1 1 but PPSAPI fails", 600abb0f93cSkardel refnumtoa(&peer->srcadr)); 601abb0f93cSkardel } 602abb0f93cSkardel #endif /* HAVE_PPSAPI */ 603abb0f93cSkardel 604abb0f93cSkardel #else 605*eabc0478Schristos NONEMPTY_TRANSLATION_UNIT 606abb0f93cSkardel #endif /* REFCLOCK */ 607