2 * refclock_ulink - clock driver for Ultralink WWVB receiver
6 /***********************************************************************
8 * Copyright (c) David L. Mills 1992-1998 *
10 * Permission to use, copy, modify, and distribute this software and *
11 * its documentation for any purpose and without fee is hereby *
12 * granted, provided that the above copyright notice appears in all *
13 * copies and that both the copyright notice and this permission *
14 * notice appear in supporting documentation, and that the name *
15 * University of Delaware not be used in advertising or publicity *
16 * pertaining to distribution of the software without specific, *
17 * written prior permission. The University of Delaware makes no *
18 * representations about the suitability this software for any *
19 * purpose. It is provided "as is" without express or implied *
21 **********************************************************************/
27 #if defined(REFCLOCK) && defined(CLOCK_ULINK)
34 #include "ntp_refclock.h"
35 #include "ntp_calendar.h"
36 #include "ntp_stdlib.h"
39 * This driver supports ultralink Model 320,330,331,332 WWVB radios
41 * this driver was based on the refclock_wwvb.c driver
42 * in the ntp distribution.
46 * fudge flag1 0 don't poll clock
47 * 1 send poll character
50 * 99/9/09 j.c.lang original edit's
51 * 99/9/11 j.c.lang changed timecode parse to
52 * match what the radio actually
54 * 99/10/11 j.c.lang added support for continous
55 * time code mode (dipsw2)
56 * 99/11/26 j.c.lang added support for 320 decoder
57 * (taken from Dave Strout's
59 * 99/11/29 j.c.lang added fudge flag 1 to control
61 * 99/12/15 j.c.lang fixed 320 quality flag
62 * 01/02/21 s.l.smith fixed 33x quality flag
63 * added more debugging stuff
64 * updated 33x time code explanation
66 * Questions, bugs, ideas send to:
68 * tcnojl1@earthlink.net
71 * dstrout@linuxfoundry.com
74 * on the Ultralink model 33X decoder Dip switch 2 controls
75 * polled or continous timecode
76 * set fudge flag1 if using polled (needed for model 320)
77 * dont set fudge flag1 if dip switch 2 is set on model 33x decoder
82 * Interface definitions
84 #define DEVICE "/dev/wwvb%d" /* device name and unit */
85 #define SPEED232 B9600 /* uart speed (9600 baud) */
86 #define PRECISION (-10) /* precision assumed (about 10 ms) */
87 #define REFID "WWVB" /* reference ID */
88 #define DESCRIPTION "Ultralink WWVB Receiver" /* WRU */
90 #define LEN33X 32 /* timecode length Model 325 & 33X */
91 #define LEN320 24 /* timecode length Model 320 */
94 * unit control structure
97 u_char tcswitch; /* timecode switch */
98 l_fp laststamp; /* last receive timestamp */
102 * Function prototypes
104 static int ulink_start P((int, struct peer *));
105 static void ulink_shutdown P((int, struct peer *));
106 static void ulink_receive P((struct recvbuf *));
107 static void ulink_poll P((int, struct peer *));
112 struct refclock refclock_ulink = {
113 ulink_start, /* start up driver */
114 ulink_shutdown, /* shut down driver */
115 ulink_poll, /* transmit poll message */
116 noentry, /* not used */
117 noentry, /* not used */
118 noentry, /* not used */
124 * ulink_start - open the devices and initialize data for processing
132 register struct ulinkunit *up;
133 struct refclockproc *pp;
138 * Open serial port. Use CLK line discipline, if available.
140 (void)sprintf(device, DEVICE, unit);
141 if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
145 * Allocate and initialize unit structure
147 if (!(up = (struct ulinkunit *)
148 emalloc(sizeof(struct ulinkunit)))) {
152 memset((char *)up, 0, sizeof(struct ulinkunit));
154 pp->unitptr = (caddr_t)up;
155 pp->io.clock_recv = ulink_receive;
156 pp->io.srcclock = (caddr_t)peer;
159 if (!io_addclock(&pp->io)) {
166 * Initialize miscellaneous variables
168 peer->precision = PRECISION;
169 peer->burst = NSTAGE;
170 pp->clockdesc = DESCRIPTION;
171 memcpy((char *)&pp->refid, REFID, 4);
177 * ulink_shutdown - shut down the clock
185 register struct ulinkunit *up;
186 struct refclockproc *pp;
189 up = (struct ulinkunit *)pp->unitptr;
190 io_closeclock(&pp->io);
196 * ulink_receive - receive data from the serial interface
200 struct recvbuf *rbufp
203 struct ulinkunit *up;
204 struct refclockproc *pp;
207 l_fp trtmp; /* arrival timestamp */
208 int quality; /* quality indicator */
209 int temp; /* int temp */
210 char syncchar; /* synchronization indicator */
211 char leapchar; /* leap indicator */
212 char modechar; /* model 320 mode flag */
213 char char_quality[2]; /* temp quality flag */
216 * Initialize pointers and read the timecode and timestamp
218 peer = (struct peer *)rbufp->recv_srcclock;
220 up = (struct ulinkunit *)pp->unitptr;
221 temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
224 * Note we get a buffer and timestamp for both a <cr> and <lf>,
225 * but only the <cr> timestamp is retained.
228 if (up->tcswitch == 0) {
230 up->laststamp = trtmp;
236 pp->lastrec = up->laststamp;
237 up->laststamp = trtmp;
241 printf("ulink: timecode %d %s\n", pp->lencode,
246 * We get down to business, check the timecode format and decode
247 * its contents. If the timecode has invalid length or is not in
248 * proper format, we declare bad format and exit.
250 syncchar = leapchar = modechar = ' ';
253 switch (pp->lencode ) {
257 * Timecode format from January 29, 2001 datasheet is:
258 * <CR><LF>S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5
259 * S WWVB decoder sync indicator. S for in-sync(?)
260 * or N for noisy signal.
261 * 9+ RF signal level in S-units, 0-9 followed by
262 * a space (0x20). The space turns to '+' if the
264 * D Data bit 0, 1, 2 (position mark), or
266 * space Space character (0x20)
267 * 00 Hours since last good WWVB frame sync. Will
268 * be 00-23 hrs, or '1d' to '7d'. Will be 'Lk'
269 * if currently in sync.
270 * space Space character (0x20)
271 * YYYY Current year, 1990-2089
272 * + Leap year indicator. '+' if a leap year,
273 * a space (0x20) if not.
274 * DDD Day of year, 001 - 366.
275 * UTC Timezone (always 'UTC').
276 * S Daylight savings indicator
277 * S - standard time (STD) in effect
278 * O - during STD to DST day 0000-2400
279 * D - daylight savings time (DST) in effect
280 * I - during DST to STD day 0000-2400
281 * space Space character (0x20)
283 * : This is the REAL in sync indicator (: = insync)
285 * : : = in sync ? = NOT in sync
287 * L Leap second flag. Changes from space (0x20)
288 * to '+' or '-' during month preceding leap
290 * +5 UT1 correction (sign + digit ))
293 if (sscanf(pp->a_lastcode,
294 "%*4c %2c %4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c",
295 char_quality, &pp->year, &pp->day,
296 &pp->hour, &syncchar, &pp->minute, &pp->second,
299 if (char_quality[0] == 'L') {
301 } else if (char_quality[0] == '0') {
302 quality = (char_quality[1] & 0x0f);
310 printf("ulink: char_quality %c %c\n",
311 char_quality[0], char_quality[1]);
312 printf("ulink: quality %d\n", quality);
313 printf("ulink: syncchar %x\n", syncchar);
314 printf("ulink: leapchar %x\n", leapchar);
325 * The timecode format is:
327 * <cr><lf>SQRYYYYDDD+HH:MM:SS.mmLT<cr>
331 * S = 'S' -- sync'd in last hour,
332 * '0'-'9' - hours x 10 since last update,
334 * Q = Number of correlating time-frames, from 0 to 5
335 * R = 'R' -- reception in progress,
336 * 'N' -- Noisy reception,
337 * ' ' -- standby mode
338 * YYYY = year from 1990 to 2089
339 * DDD = current day from 1 to 366
340 * + = '+' if current year is a leap year, else ' '
341 * HH = UTC hour 0 to 23
342 * MM = Minutes of current hour from 0 to 59
343 * SS = Seconds of current minute from 0 to 59
344 * mm = 10's milliseconds of the current second from 00 to 99
345 * L = Leap second pending at end of month
346 * 'I' = insert, 'D'= delete
347 * T = DST <-> STD transition indicators
350 if (sscanf(pp->a_lastcode, "%c%1d%c%4d%3d%*c%2d:%2d:%2d.%2d%c",
351 &syncchar, &quality, &modechar, &pp->year, &pp->day,
352 &pp->hour, &pp->minute, &pp->second,
353 &pp->msec,&leapchar) == 10) {
354 pp->msec *= 10; /* M320 returns 10's of msecs */
355 if (leapchar == 'I' ) leapchar = '+';
356 if (leapchar == 'D' ) leapchar = '-';
357 if (syncchar != '?' ) syncchar = ':';
363 refclock_report(peer, CEVNT_BADREPLY);
369 * Decode quality indicator
370 * For the 325 & 33x series, the lower the number the "better"
371 * the time is. I used the dispersion as the measure of time
372 * quality. The quality indicator in the 320 is the number of
373 * correlating time frames (the more the better)
377 * The spec sheet for the 325 & 33x series states the clock will
378 * maintain +/-0.002 seconds accuracy when locked to WWVB. This
379 * is indicated by 'Lk' in the quality portion of the incoming
380 * string. When not in lock, a drift of +/-0.015 seconds should
382 * With the quality indicator decoding scheme above, the 'Lk'
383 * condition will produce a quality value of 0. If the quality
384 * indicator starts with '0' then the second character is the
385 * number of hours since we were last locked. If the first
386 * character is anything other than 'L' or '0' then we have been
387 * out of lock for more than 9 hours so we assume the worst and
388 * force a quality value that selects the 'default' maximum
389 * dispersion. The dispersion values below are what came with the
390 * driver. They're not unreasonable so they've not been changed.
393 if (pp->lencode == LEN33X) {
408 pp->disp=MAXDISPERSE;
429 pp->disp=MAXDISPERSE;
436 * Decode synchronization, and leap characters. If
437 * unsynchronized, set the leap bits accordingly and exit.
438 * Otherwise, set the leap bits according to the leap character.
442 pp->leap = LEAP_NOTINSYNC;
443 else if (leapchar == '+')
444 pp->leap = LEAP_ADDSECOND;
445 else if (leapchar == '-')
446 pp->leap = LEAP_DELSECOND;
448 pp->leap = LEAP_NOWARNING;
451 * Process the new sample in the median filter and determine the
452 * timecode timestamp.
454 if (!refclock_process(pp)) {
455 refclock_report(peer, CEVNT_BADTIME);
462 * ulink_poll - called by the transmit procedure
470 struct refclockproc *pp;
475 if (pp->sloppyclockflag & CLK_FLAG1) {
476 if (write(pp->io.fd, &pollchar, 1) != 1)
477 refclock_report(peer, CEVNT_FAULT);
486 if (pp->coderecv == pp->codeproc) {
487 refclock_report(peer, CEVNT_TIMEOUT);
490 record_clock_stats(&peer->srcadr, pp->a_lastcode);
491 refclock_receive(peer);
492 peer->burst = NSTAGE;
497 int refclock_ulink_bs;
498 #endif /* REFCLOCK */