/* * refclock_ulink - clock driver for Ultralink WWVB receiver */ #ifdef HAVE_CONFIG_H #include #endif #if defined(REFCLOCK) && defined(CLOCK_ULINK) #include #include #include "ntpd.h" #include "ntp_io.h" #include "ntp_refclock.h" #include "ntp_stdlib.h" /* This driver supports ultralink Model 320,325,330,331,332 WWVB radios * * this driver was based on the refclock_wwvb.c driver * in the ntp distribution. * * Fudge Factors * * fudge flag1 0 don't poll clock * 1 send poll character * * revision history: * 99/9/09 j.c.lang original edit's * 99/9/11 j.c.lang changed timecode parse to * match what the radio actually * sends. * 99/10/11 j.c.lang added support for continous * time code mode (dipsw2) * 99/11/26 j.c.lang added support for 320 decoder * (taken from Dave Strout's * Model 320 driver) * 99/11/29 j.c.lang added fudge flag 1 to control * clock polling * 99/12/15 j.c.lang fixed 320 quality flag * 01/02/21 s.l.smith fixed 33x quality flag * added more debugging stuff * updated 33x time code explanation * 04/01/23 frank migge added support for 325 decoder * (tested with ULM325.F) * * Questions, bugs, ideas send to: * Joseph C. Lang * tcnojl1@earthlink.net * * Dave Strout * dstrout@linuxfoundry.com * * Frank Migge * frank.migge@oracle.com * * * on the Ultralink model 33X decoder Dip switch 2 controls * polled or continous timecode * set fudge flag1 if using polled (needed for model 320 and 325) * dont set fudge flag1 if dip switch 2 is set on model 33x decoder */ /* * Interface definitions */ #define DEVICE "/dev/wwvb%d" /* device name and unit */ #define SPEED232 B9600 /* uart speed (9600 baud) */ #define PRECISION (-10) /* precision assumed (about 10 ms) */ #define REFID "WWVB" /* reference ID */ #define DESCRIPTION "Ultralink WWVB Receiver" /* WRU */ #define LEN33X 32 /* timecode length Model 33X and 325 */ #define LEN320 24 /* timecode length Model 320 */ #define SIGLCHAR33x 'S' /* signal strength identifier char 325 */ #define SIGLCHAR325 'R' /* signal strength identifier char 33x */ /* * unit control structure */ struct ulinkunit { u_char tcswitch; /* timecode switch */ l_fp laststamp; /* last receive timestamp */ }; /* * Function prototypes */ static int ulink_start (int, struct peer *); static void ulink_shutdown (int, struct peer *); static void ulink_receive (struct recvbuf *); static void ulink_poll (int, struct peer *); /* * Transfer vector */ struct refclock refclock_ulink = { ulink_start, /* start up driver */ ulink_shutdown, /* shut down driver */ ulink_poll, /* transmit poll message */ noentry, /* not used */ noentry, /* not used */ noentry, /* not used */ NOFLAGS }; /* * ulink_start - open the devices and initialize data for processing */ static int ulink_start( int unit, struct peer *peer ) { register struct ulinkunit *up; struct refclockproc *pp; int fd; char device[20]; /* * Open serial port. Use CLK line discipline, if available. */ snprintf(device, sizeof(device), DEVICE, unit); fd = refclock_open(&peer->srcadr, device, SPEED232, LDISC_CLK); if (fd <= 0) return (0); /* * Allocate and initialize unit structure */ up = emalloc(sizeof(struct ulinkunit)); memset(up, 0, sizeof(struct ulinkunit)); pp = peer->procptr; pp->io.clock_recv = ulink_receive; pp->io.srcclock = peer; pp->io.datalen = 0; pp->io.fd = fd; if (!io_addclock(&pp->io)) { close(fd); pp->io.fd = -1; free(up); return (0); } pp->unitptr = up; /* * Initialize miscellaneous variables */ peer->precision = PRECISION; pp->clockdesc = DESCRIPTION; memcpy((char *)&pp->refid, REFID, 4); return (1); } /* * ulink_shutdown - shut down the clock */ static void ulink_shutdown( int unit, struct peer *peer ) { register struct ulinkunit *up; struct refclockproc *pp; pp = peer->procptr; up = pp->unitptr; if (pp->io.fd != -1) io_closeclock(&pp->io); if (up != NULL) free(up); } /* * ulink_receive - receive data from the serial interface */ static void ulink_receive( struct recvbuf *rbufp ) { struct ulinkunit *up; struct refclockproc *pp; struct peer *peer; l_fp trtmp; /* arrival timestamp */ int quality = INT_MAX; /* quality indicator */ int temp; /* int temp */ char syncchar; /* synchronization indicator */ char leapchar; /* leap indicator */ char modechar; /* model 320 mode flag */ char siglchar; /* model difference between 33x/325 */ char char_quality[2]; /* temp quality flag */ /* * Initialize pointers and read the timecode and timestamp */ peer = rbufp->recv_peer; pp = peer->procptr; up = pp->unitptr; temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); /* * Note we get a buffer and timestamp for both a and , * but only the timestamp is retained. */ if (temp == 0) { if (up->tcswitch == 0) { up->tcswitch = 1; up->laststamp = trtmp; } else up->tcswitch = 0; return; } pp->lencode = temp; pp->lastrec = up->laststamp; up->laststamp = trtmp; up->tcswitch = 1; #ifdef DEBUG if (debug) printf("ulink: timecode %d %s\n", pp->lencode, pp->a_lastcode); #endif /* * We get down to business, check the timecode format and decode * its contents. If the timecode has invalid length or is not in * proper format, we declare bad format and exit. */ syncchar = leapchar = modechar = siglchar = ' '; switch (pp->lencode ) { case LEN33X: /* * First we check if the format is 33x or 325: * S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5 (33x) * R5_1C00LYYYY+DDDUTCS HH:MM:SSL+5 (325) * simply by comparing if the signal level is 'S' or 'R' */ if (sscanf(pp->a_lastcode, "%c%*31c", &siglchar) == 1) { if(siglchar == SIGLCHAR325) { /* * decode for a Model 325 decoder. * Timecode format from January 23, 2004 datasheet is: * * R5_1C00LYYYY+DDDUTCS HH:MM:SSL+5 * * R WWVB decodersignal readability R1 - R5 * 5 R1 is unreadable, R5 is best * space a space (0x20) * 1 Data bit 0, 1, M (pos mark), or ? (unknown). * C Reception from either (C)olorado or (H)awaii * 00 Hours since last good WWVB frame sync. Will * be 00-99 * space Space char (0x20) or (0xa5) if locked to wwvb * YYYY Current year, 2000-2099 * + Leap year indicator. '+' if a leap year, * a space (0x20) if not. * DDD Day of year, 000 - 365. * UTC Timezone (always 'UTC'). * S Daylight savings indicator * S - standard time (STD) in effect * O - during STD to DST day 0000-2400 * D - daylight savings time (DST) in effect * I - during DST to STD day 0000-2400 * space Space character (0x20) * HH Hours 00-23 * : This is the REAL in sync indicator (: = insync) * MM Minutes 00-59 * : : = in sync ? = NOT in sync * SS Seconds 00-59 * L Leap second flag. Changes from space (0x20) * to 'I' or 'D' during month preceding leap * second adjustment. (I)nsert or (D)elete * +5 UT1 correction (sign + digit )) */ if (sscanf(pp->a_lastcode, "%*2c %*2c%2c%*c%4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c", char_quality, &pp->year, &pp->day, &pp->hour, &syncchar, &pp->minute, &pp->second, &leapchar) == 8) { if (char_quality[0] == '0') { quality = 0; } else if (char_quality[0] == '0') { quality = (char_quality[1] & 0x0f); } else { quality = 99; } if (leapchar == 'I' ) leapchar = '+'; if (leapchar == 'D' ) leapchar = '-'; /* #ifdef DEBUG if (debug) { printf("ulink: char_quality %c %c\n", char_quality[0], char_quality[1]); printf("ulink: quality %d\n", quality); printf("ulink: syncchar %x\n", syncchar); printf("ulink: leapchar %x\n", leapchar); } #endif */ } } if(siglchar == SIGLCHAR33x) { /* * We got a Model 33X decoder. * Timecode format from January 29, 2001 datasheet is: * S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5 * S WWVB decoder sync indicator. S for in-sync(?) * or N for noisy signal. * 9+ RF signal level in S-units, 0-9 followed by * a space (0x20). The space turns to '+' if the * level is over 9. * D Data bit 0, 1, 2 (position mark), or * 3 (unknown). * space Space character (0x20) * 00 Hours since last good WWVB frame sync. Will * be 00-23 hrs, or '1d' to '7d'. Will be 'Lk' * if currently in sync. * space Space character (0x20) * YYYY Current year, 1990-2089 * + Leap year indicator. '+' if a leap year, * a space (0x20) if not. * DDD Day of year, 001 - 366. * UTC Timezone (always 'UTC'). * S Daylight savings indicator * S - standard time (STD) in effect * O - during STD to DST day 0000-2400 * D - daylight savings time (DST) in effect * I - during DST to STD day 0000-2400 * space Space character (0x20) * HH Hours 00-23 * : This is the REAL in sync indicator (: = insync) * MM Minutes 00-59 * : : = in sync ? = NOT in sync * SS Seconds 00-59 * L Leap second flag. Changes from space (0x20) * to '+' or '-' during month preceding leap * second adjustment. * +5 UT1 correction (sign + digit )) */ if (sscanf(pp->a_lastcode, "%*4c %2c %4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c", char_quality, &pp->year, &pp->day, &pp->hour, &syncchar, &pp->minute, &pp->second, &leapchar) == 8) { if (char_quality[0] == 'L') { quality = 0; } else if (char_quality[0] == '0') { quality = (char_quality[1] & 0x0f); } else { quality = 99; } /* #ifdef DEBUG if (debug) { printf("ulink: char_quality %c %c\n", char_quality[0], char_quality[1]); printf("ulink: quality %d\n", quality); printf("ulink: syncchar %x\n", syncchar); printf("ulink: leapchar %x\n", leapchar); } #endif */ } } break; } case LEN320: /* * Model 320 Decoder * The timecode format is: * * SQRYYYYDDD+HH:MM:SS.mmLT * * where: * * S = 'S' -- sync'd in last hour, * '0'-'9' - hours x 10 since last update, * '?' -- not in sync * Q = Number of correlating time-frames, from 0 to 5 * R = 'R' -- reception in progress, * 'N' -- Noisy reception, * ' ' -- standby mode * YYYY = year from 1990 to 2089 * DDD = current day from 1 to 366 * + = '+' if current year is a leap year, else ' ' * HH = UTC hour 0 to 23 * MM = Minutes of current hour from 0 to 59 * SS = Seconds of current minute from 0 to 59 * mm = 10's milliseconds of the current second from 00 to 99 * L = Leap second pending at end of month * 'I' = insert, 'D'= delete * T = DST <-> STD transition indicators * */ if (sscanf(pp->a_lastcode, "%c%1d%c%4d%3d%*c%2d:%2d:%2d.%2ld%c", &syncchar, &quality, &modechar, &pp->year, &pp->day, &pp->hour, &pp->minute, &pp->second, &pp->nsec, &leapchar) == 10) { pp->nsec *= 10000000; /* M320 returns 10's of msecs */ if (leapchar == 'I' ) leapchar = '+'; if (leapchar == 'D' ) leapchar = '-'; if (syncchar != '?' ) syncchar = ':'; break; } default: refclock_report(peer, CEVNT_BADREPLY); return; } /* * Decode quality indicator * For the 325 & 33x series, the lower the number the "better" * the time is. I used the dispersion as the measure of time * quality. The quality indicator in the 320 is the number of * correlating time frames (the more the better) */ /* * The spec sheet for the 325 & 33x series states the clock will * maintain +/-0.002 seconds accuracy when locked to WWVB. This * is indicated by 'Lk' in the quality portion of the incoming * string. When not in lock, a drift of +/-0.015 seconds should * be allowed for. * With the quality indicator decoding scheme above, the 'Lk' * condition will produce a quality value of 0. If the quality * indicator starts with '0' then the second character is the * number of hours since we were last locked. If the first * character is anything other than 'L' or '0' then we have been * out of lock for more than 9 hours so we assume the worst and * force a quality value that selects the 'default' maximum * dispersion. The dispersion values below are what came with the * driver. They're not unreasonable so they've not been changed. */ if (pp->lencode == LEN33X) { switch (quality) { case 0 : pp->disp=.002; break; case 1 : pp->disp=.02; break; case 2 : pp->disp=.04; break; case 3 : pp->disp=.08; break; default: pp->disp=MAXDISPERSE; break; } } else { switch (quality) { case 5 : pp->disp=.002; break; case 4 : pp->disp=.02; break; case 3 : pp->disp=.04; break; case 2 : pp->disp=.08; break; case 1 : pp->disp=.16; break; default: pp->disp=MAXDISPERSE; break; } } /* * Decode synchronization, and leap characters. If * unsynchronized, set the leap bits accordingly and exit. * Otherwise, set the leap bits according to the leap character. */ if (syncchar != ':') pp->leap = LEAP_NOTINSYNC; else if (leapchar == '+') pp->leap = LEAP_ADDSECOND; else if (leapchar == '-') pp->leap = LEAP_DELSECOND; else pp->leap = LEAP_NOWARNING; /* * Process the new sample in the median filter and determine the * timecode timestamp. */ if (!refclock_process(pp)) { refclock_report(peer, CEVNT_BADTIME); } } /* * ulink_poll - called by the transmit procedure */ static void ulink_poll( int unit, struct peer *peer ) { struct refclockproc *pp; char pollchar; pp = peer->procptr; pollchar = 'T'; if (pp->sloppyclockflag & CLK_FLAG1) { if (write(pp->io.fd, &pollchar, 1) != 1) refclock_report(peer, CEVNT_FAULT); else pp->polls++; } else pp->polls++; if (pp->coderecv == pp->codeproc) { refclock_report(peer, CEVNT_TIMEOUT); return; } pp->lastref = pp->lastrec; refclock_receive(peer); record_clock_stats(&peer->srcadr, pp->a_lastcode); } #else NONEMPTY_TRANSLATION_UNIT #endif /* REFCLOCK */