source: trunk/third/xntp/xntpd/refclock_wwvb.c @ 10832

Revision 10832, 12.5 KB checked in by brlewis, 27 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r10831, which included commits to RCS files with non-trunk default branches.
Line 
1/*
2 * refclock_wwvb - clock driver for Spectracom WWVB receivers
3 */
4
5#ifdef HAVE_CONFIG_H
6#include <config.h>
7#endif
8
9#if defined(REFCLOCK) && defined(WWVB)
10
11#include <stdio.h>
12#include <ctype.h>
13#include <sys/time.h>
14#include <time.h>
15
16#include "ntpd.h"
17#include "ntp_io.h"
18#include "ntp_refclock.h"
19#include "ntp_stdlib.h"
20#include "ntp_calendar.h"
21
22/*
23 * This driver supports the Spectracom Model 8170 and Netclock/2 WWVB
24 * Synchronized Clock. This clock has proven a reliable source of time,
25 * except in some cases of high ambient conductive RF interference. The
26 * claimed accuracy of the clock is 100 usec relative to the broadcast
27 * signal; however, in most cases the actual accuracy is limited by the
28 * precision of the timecode and the latencies of the serial interface
29 * and operating system.
30 *
31 * The DIPswitches on this clock should be set to 24-hour display, AUTO
32 * DST off, time zone 0 (UTC), data format 0 or 2 (see below) and baud
33 * rate 9600. If this clock is to used as the source for the IRIG Audio
34 * Decoder (refclock_irig.c in this distribution), set the DIPswitches
35 * for AM IRIG output and IRIG format 1 (IRIG B with signature control).
36 *
37 * There are two timecode formats used by these clocks. Format 0, which
38 * is available with both the Netclock/2 and 8170, and format 2, which
39 * is available only with the Netclock/2 and specially modified 8170.
40 *
41 * Format 0 (22 ASCII printing characters):
42 *
43 * <cr><lf>i  ddd hh:mm:ss  TZ=zz<cr><lf>
44 *
45 *      on-time = first <cr> * hh:mm:ss = hours, minutes, seconds
46 *      i = synchronization flag (' ' = in synch, '?' = out of synch)
47 *
48 * The alarm condition is indicated by other than ' ' at A, which occurs
49 * during initial synchronization and when received signal is lost for
50 * about ten hours.
51 *
52 * Format 2 (24 ASCII printing characters):
53 *
54 * <cr><lf>iqyy ddd hh:mm:ss.fff ld
55 *
56 *      on-time = <cr>
57 *      i = synchronization flag (' ' = in synch, '?' = out of synch)
58 *      q = quality indicator (' ' = locked, 'A'...'D' = unlocked)
59 *      yy = year (as broadcast)
60 *      ddd = day of year
61 *      hh:mm:ss.fff = hours, minutes, seconds, milliseconds
62 *
63 * The alarm condition is indicated by other than ' ' at A, which occurs
64 * during initial synchronization and when received signal is lost for
65 * about ten hours. The unlock condition is indicated by other than ' '
66 * at Q.
67 *
68 * The Q is normally ' ' when the time error is less than 1 ms and a
69 * character in the set 'A'...'D' when the time error is less than 10,
70 * 100, 500 and greater than 500 ms respectively. The L is normally ' ',
71 * but is set to 'L' early in the month of an upcoming UTC leap second
72 * and reset to ' ' on the first day of the following month. The D is
73 * set to 'S' for standard time 'I' on the day preceding a switch to
74 * daylight time, 'D' for daylight time and 'O' on the day preceding a
75 * switch to standard time. The start bit of the first <cr> is
76 * synchronized to the indicated time as returned.
77 *
78 * This driver does not need to be told which format is in use - it
79 * figures out which one from the length of the message. A three-stage
80 * median filter is used to reduce jitter and provide a dispersion
81 * measure. The driver makes no attempt to correct for the intrinsic
82 * jitter of the radio itself, which is a known problem with the older
83 * radios.
84 *
85 * Fudge Factors
86 *
87 * This driver can retrieve a table of quality data maintained
88 * internally by the Netclock/2 receiver. If flag4 of the fudge
89 * configuration command is set to 1, the driver will retrieve this
90 * table and write it to the clockstats file on when the first timecode
91 * message of a new day is received.
92 */
93
94/*
95 * Interface definitions
96 */
97#define DEVICE          "/dev/wwvb%d" /* device name and unit */
98#define SPEED232        B9600   /* uart speed (9600 baud) */
99#define PRECISION       (-10)   /* precision assumed (about 1 ms) */
100#define REFID           "WWVB"  /* reference ID */
101#define DESCRIPTION     "Spectracom WWVB Receiver" /* WRU */
102
103#define NSAMPLES        3       /* stages of median filter */
104#define LENWWVB0        22      /* format 0 timecode length */
105#define LENWWVB2        24      /* format 2 timecode length */
106#define LENWWVB3        29      /* format 3 timecode length */
107#define MONLIN          15      /* number of monitoring lines */
108
109/*
110 * Imported from ntp_timer module
111 */
112extern  u_long  current_time;   /* current time (s) */
113
114/*
115 * Imported from ntpd module
116 */
117extern  int     debug;          /* global debug flag */
118
119/*
120 * WWVB unit control structure
121 */
122struct wwvbunit {
123        int     pollcnt;        /* poll message counter */
124
125        u_char  tcswitch;       /* timecode switch */
126        l_fp    laststamp;      /* last receive timestamp */
127        u_char  lasthour;       /* last hour (for monitor) */
128        u_char  linect;         /* count ignored lines (for monitor */
129};
130
131/*
132 * Function prototypes
133 */
134static  int     wwvb_start      P((int, struct peer *));
135static  void    wwvb_shutdown   P((int, struct peer *));
136static  void    wwvb_receive    P((struct recvbuf *));
137static  void    wwvb_poll       P((int, struct peer *));
138
139/*
140 * Transfer vector
141 */
142struct  refclock refclock_wwvb = {
143        wwvb_start,             /* start up driver */
144        wwvb_shutdown,          /* shut down driver */
145        wwvb_poll,              /* transmit poll message */
146        noentry,                /* not used (old wwvb_control) */
147        noentry,                /* initialize driver (not used) */
148        noentry,                /* not used (old wwvb_buginfo) */
149        NOFLAGS                 /* not used */
150};
151
152
153/*
154 * wwvb_start - open the devices and initialize data for processing
155 */
156static int
157wwvb_start(unit, peer)
158        int unit;
159        struct peer *peer;
160{
161        register struct wwvbunit *up;
162        struct refclockproc *pp;
163        int fd;
164        char device[20];
165
166#ifdef DEBUG
167        if (debug)
168            printf("inside wwvb_start\n");
169#endif
170        /*
171         * Open serial port. Use CLK line discipline, if available.
172         */
173        (void)sprintf(device, DEVICE, unit);
174#ifdef TTYCLK
175        if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
176#else
177        if (!(fd = refclock_open(device, SPEED232, 0)))
178#endif /* TTYCLK */
179        {
180#ifdef DEBUG
181            if (debug)
182                printf ("refclock_open barfed\n");
183#endif
184            return (0);
185        }
186
187        /*
188         * Allocate and initialize unit structure
189         */
190        if (!(up = (struct wwvbunit *)
191            emalloc(sizeof(struct wwvbunit)))) {
192                (void) close(fd);
193                return (0);
194        }
195        memset((char *)up, 0, sizeof(struct wwvbunit));
196        pp = peer->procptr;
197        pp->io.clock_recv = wwvb_receive;
198        pp->io.srcclock = (caddr_t)peer;
199        pp->io.datalen = 0;
200        pp->io.fd = fd;
201        if (!io_addclock(&pp->io)) {
202                (void) close(fd);
203                free(up);
204                return (0);
205        }
206        pp->unitptr = (caddr_t)up;
207
208        /*
209         * Initialize miscellaneous variables
210         */
211        peer->precision = PRECISION;
212        pp->clockdesc = DESCRIPTION;
213        memcpy((char *)&pp->refid, REFID, 4);
214        up->pollcnt = 2;
215        return (1);
216}
217
218
219/*
220 * wwvb_shutdown - shut down the clock
221 */
222static void
223wwvb_shutdown(unit, peer)
224        int unit;
225        struct peer *peer;
226{
227        register struct wwvbunit *up;
228        struct refclockproc *pp;
229
230        pp = peer->procptr;
231        up = (struct wwvbunit *)pp->unitptr;
232        io_closeclock(&pp->io);
233        free(up);
234}
235
236
237/*
238 * wwvb_receive - receive data from the serial interface
239 */
240static void
241wwvb_receive(rbufp)
242        struct recvbuf *rbufp;
243{
244        register struct wwvbunit *up;
245        struct refclockproc *pp;
246        struct peer *peer;
247        l_fp trtmp;
248        u_long ltemp;
249        int temp;
250        char    syncchar;       /* synchronization indicator */
251        char    qualchar;       /* quality indicator */
252        char    leapchar;       /* leap indicator */
253
254#ifdef DEBUG
255        if (debug)
256            printf ("in wwvb_receive\n");
257#endif
258        /*
259         * Initialize pointers and read the timecode and timestamp
260         */
261        peer = (struct peer *)rbufp->recv_srcclock;
262        pp = peer->procptr;
263        up = (struct wwvbunit *)pp->unitptr;
264        temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
265
266        /*
267         * Note we get a buffer and timestamp for both a <cr> and <lf>,
268         * but only the <cr> timestamp is retained. Note: in format 0 on
269         * a Netclock/2 or upgraded 8170 the start bit is delayed 100
270         * +-50 us relative to the pps; however, on an unmodified 8170
271         * the start bit can be delayed up to 10 ms. In format 2 the
272         * reading precision is only to the millisecond. Thus, unless
273         * you have a pps gadget and don't have to have the year, format
274         * 0 provides the lowest jitter.
275         */
276        if (temp == 0) {
277                if (up->tcswitch == 0) {
278                        up->tcswitch = 1;
279                        up->laststamp = trtmp;
280                } else
281                        up->tcswitch = 0;
282                return;
283        }
284        pp->lencode = temp;
285        pp->lastrec = up->laststamp;
286        up->laststamp = trtmp;
287        up->tcswitch = 1;
288        up->pollcnt = 2;
289        record_clock_stats(&peer->srcadr, pp->a_lastcode);
290#ifdef DEBUG
291        if (debug)
292                printf("wwvb: timecode %d %s\n", pp->lencode,
293                    pp->a_lastcode);
294#endif
295
296        /*
297         * We get down to business, check the timecode format and decode
298         * its contents. This code uses the timecode length to determine
299         * whether format 0 or format 2. If the timecode has invalid
300         * length or is not in proper format, we declare bad format and
301         * exit.
302         */
303        switch (pp->lencode) {
304                int tz = 0;
305
306                case LENWWVB0:
307
308                /*
309                 * Timecode format 0: "I  ddd hh:mm:ss  TZ=nn"
310                 */
311                qualchar = leapchar = ' ';
312                if (sscanf(pp->a_lastcode, "%c %3d %2d:%2d:%2d TZ=%2d",
313                    &syncchar, &pp->day, &pp->hour, &pp->minute,
314                    &pp->second, &tz) == 6)
315                  {
316                    int hr;
317
318                    hr = pp->hour + tz;
319                    pp->day += hr / 24;
320                    pp->hour = hr % 24;
321                    break;
322                  }
323
324                case LENWWVB2:
325
326                /*
327                 * Timecode format 2: "IQyy ddd hh:mm:ss.mmm LD"
328                 */
329                if (sscanf(pp->a_lastcode, "%c%c %2d %3d %2d:%2d:%2d.%3d %c",
330                    &syncchar, &qualchar, &pp->year, &pp->day,
331                    &pp->hour, &pp->minute, &pp->second, &pp->msec,
332                    &leapchar) == 9)
333                        break;
334
335                case LENWWVB3:
336                    /*
337                     * Timecode format 3: "0003I yyyymmdd hhmmss+0000SL#"
338                     */
339                    {
340                        int             month,day;
341                        int             matched;
342       
343                        qualchar = ' ';
344                        matched =
345                            sscanf(pp->a_lastcode,"0003%c %4d%2d%2d %2d%2d%2d%*c%c",
346                                   &syncchar,&pp->year,&month,&day,
347                                   &pp->hour,&pp->minute,&pp->second,&leapchar);
348                        if (matched==8)
349                        {
350                            pp->msec = 0;
351                            pp->day = 31*(month - 1) + day;
352                            if (month > 2)
353                            {
354                                pp->day = pp->day - (4*month + 23)/10;
355                                if (is_leapyear(pp->year))
356                                    pp->day++;
357                            }
358                            break;
359                        }
360#ifdef DEBUG
361                        if (debug)
362                            printf ("wwvb_receive: Surrender, Dorothy!\n");
363#endif
364                    }
365                default:
366
367                if (up->linect > 0)
368                        up->linect--;
369                else
370                        refclock_report(peer, CEVNT_BADREPLY);
371                return;
372        }
373
374        /*
375         * Decode synchronization, quality and leap characters. If
376         * unsynchronized, set the leap bits accordingly and exit.
377         * Otherwise, set the leap bits according to the leap character.
378         * Once synchronized, the dispersion depends only on when the
379         * clock was last heard. The first time the clock is heard, the
380         * time last heard is faked based on the quality indicator. The
381         * magic numbers (in seconds) are from the clock specifications.
382         */
383        switch (qualchar) {
384
385                case ' ':
386                ltemp = 0;
387                break;
388
389                case 'A':
390                ltemp = 800;
391                break;
392
393                case 'B':
394                ltemp = 5300;
395                break;
396
397                case 'C':
398                ltemp = 25300;
399                break;
400
401                case 'D':
402                ltemp = NTP_MAXAGE;
403                break;
404
405                default:
406                refclock_report(peer, CEVNT_BADREPLY);
407                return;
408        }
409        if (syncchar != ' ')
410                pp->leap = LEAP_NOTINSYNC;
411        else {
412                if (leapchar == 'L')
413                        pp->leap = LEAP_ADDSECOND;
414                else
415                        pp->leap = 0;
416                pp->lasttime = current_time - ltemp;
417        }
418
419        /*
420         * If the monitor flag is set (flag4), we dump the internal
421         * quality table at the first timecode beginning the day.
422         */
423        if (pp->sloppyclockflag & CLK_FLAG4 && pp->hour <
424            (int)up->lasthour)
425                up->linect = MONLIN;
426        up->lasthour = pp->hour;
427
428        /*
429         * Process the new sample in the median filter and determine the
430         * reference clock offset and dispersion. We use lastrec as both
431         * the reference time and receive time in order to avoid being
432         * cute, like setting the reference time later than the receive
433         * time, which may cause a paranoid protocol module to chuck out
434         * the data.
435         */
436        if (!refclock_process(pp, NSAMPLES, NSAMPLES)) {
437                refclock_report(peer, CEVNT_BADTIME);
438                return;
439        }
440        trtmp = pp->lastrec;
441        trtmp.l_ui -= ltemp;
442        refclock_receive(peer, &pp->offset, 0, pp->dispersion,
443            &trtmp, &pp->lastrec, pp->leap);
444}
445
446
447/*
448 * wwvb_poll - called by the transmit procedure
449 */
450static void
451wwvb_poll(unit, peer)
452        int unit;
453        struct peer *peer;
454{
455        register struct wwvbunit *up;
456        struct refclockproc *pp;
457        char poll;
458
459        /*
460         * Time to poll the clock. The Spectracom clock responds to a
461         * 'T' by returning a timecode in the format(s) specified above.
462         * Note there is no checking on state, since this may not be the
463         * only customer reading the clock. Only one customer need poll
464         * the clock; all others just listen in. If nothing is heard
465         * from the clock for two polls, declare a timeout and keep
466         * going.
467         */
468        pp = peer->procptr;
469        up = (struct wwvbunit *)pp->unitptr;
470        if (up->pollcnt == 0)
471                refclock_report(peer, CEVNT_TIMEOUT);
472        else
473                up->pollcnt--;
474        if (up->linect > 0)
475                poll = 'R';
476        else
477                poll = 'T';
478        if (write(pp->io.fd, &poll, 1) != 1) {
479                refclock_report(peer, CEVNT_FAULT);
480        } else
481                pp->polls++;
482}
483
484#endif
Note: See TracBrowser for help on using the repository browser.