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 | */ |
---|
112 | extern u_long current_time; /* current time (s) */ |
---|
113 | |
---|
114 | /* |
---|
115 | * Imported from ntpd module |
---|
116 | */ |
---|
117 | extern int debug; /* global debug flag */ |
---|
118 | |
---|
119 | /* |
---|
120 | * WWVB unit control structure |
---|
121 | */ |
---|
122 | struct 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 | */ |
---|
134 | static int wwvb_start P((int, struct peer *)); |
---|
135 | static void wwvb_shutdown P((int, struct peer *)); |
---|
136 | static void wwvb_receive P((struct recvbuf *)); |
---|
137 | static void wwvb_poll P((int, struct peer *)); |
---|
138 | |
---|
139 | /* |
---|
140 | * Transfer vector |
---|
141 | */ |
---|
142 | struct 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 | */ |
---|
156 | static int |
---|
157 | wwvb_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 | */ |
---|
222 | static void |
---|
223 | wwvb_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 | */ |
---|
240 | static void |
---|
241 | wwvb_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 | */ |
---|
450 | static void |
---|
451 | wwvb_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 |
---|