1: /*
2: * refclock_ulink - clock driver for Ultralink WWVB receiver
3: */
4:
5: /***********************************************************************
6: * *
7: * Copyright (c) David L. Mills 1992-1998 *
8: * *
9: * Permission to use, copy, modify, and distribute this software and *
10: * its documentation for any purpose and without fee is hereby *
11: * granted, provided that the above copyright notice appears in all *
12: * copies and that both the copyright notice and this permission *
13: * notice appear in supporting documentation, and that the name *
14: * University of Delaware not be used in advertising or publicity *
15: * pertaining to distribution of the software without specific, *
16: * written prior permission. The University of Delaware makes no *
17: * representations about the suitability this software for any *
18: * purpose. It is provided "as is" without express or implied *
19: * warranty. *
20: **********************************************************************/
21:
22: #ifdef HAVE_CONFIG_H
23: #include <config.h>
24: #endif
25:
26: #if defined(REFCLOCK) && defined(CLOCK_ULINK)
27:
28: #include <stdio.h>
29: #include <ctype.h>
30:
31: #include "ntpd.h"
32: #include "ntp_io.h"
33: #include "ntp_refclock.h"
34: #include "ntp_stdlib.h"
35:
36: /* This driver supports ultralink Model 320,325,330,331,332 WWVB radios
37: *
38: * this driver was based on the refclock_wwvb.c driver
39: * in the ntp distribution.
40: *
41: * Fudge Factors
42: *
43: * fudge flag1 0 don't poll clock
44: * 1 send poll character
45: *
46: * revision history:
47: * 99/9/09 j.c.lang original edit's
48: * 99/9/11 j.c.lang changed timecode parse to
49: * match what the radio actually
50: * sends.
51: * 99/10/11 j.c.lang added support for continous
52: * time code mode (dipsw2)
53: * 99/11/26 j.c.lang added support for 320 decoder
54: * (taken from Dave Strout's
55: * Model 320 driver)
56: * 99/11/29 j.c.lang added fudge flag 1 to control
57: * clock polling
58: * 99/12/15 j.c.lang fixed 320 quality flag
59: * 01/02/21 s.l.smith fixed 33x quality flag
60: * added more debugging stuff
61: * updated 33x time code explanation
62: * 04/01/23 frank migge added support for 325 decoder
63: * (tested with ULM325.F)
64: *
65: * Questions, bugs, ideas send to:
66: * Joseph C. Lang
67: * tcnojl1@earthlink.net
68: *
69: * Dave Strout
70: * dstrout@linuxfoundry.com
71: *
72: * Frank Migge
73: * frank.migge@oracle.com
74: *
75: *
76: * on the Ultralink model 33X decoder Dip switch 2 controls
77: * polled or continous timecode
78: * set fudge flag1 if using polled (needed for model 320 and 325)
79: * dont set fudge flag1 if dip switch 2 is set on model 33x decoder
80: */
81:
82:
83: /*
84: * Interface definitions
85: */
86: #define DEVICE "/dev/wwvb%d" /* device name and unit */
87: #define SPEED232 B9600 /* uart speed (9600 baud) */
88: #define PRECISION (-10) /* precision assumed (about 10 ms) */
89: #define REFID "WWVB" /* reference ID */
90: #define DESCRIPTION "Ultralink WWVB Receiver" /* WRU */
91:
92: #define LEN33X 32 /* timecode length Model 33X and 325 */
93: #define LEN320 24 /* timecode length Model 320 */
94:
95: #define SIGLCHAR33x 'S' /* signal strength identifier char 325 */
96: #define SIGLCHAR325 'R' /* signal strength identifier char 33x */
97:
98: /*
99: * unit control structure
100: */
101: struct ulinkunit {
102: u_char tcswitch; /* timecode switch */
103: l_fp laststamp; /* last receive timestamp */
104: };
105:
106: /*
107: * Function prototypes
108: */
109: static int ulink_start (int, struct peer *);
110: static void ulink_shutdown (int, struct peer *);
111: static void ulink_receive (struct recvbuf *);
112: static void ulink_poll (int, struct peer *);
113:
114: /*
115: * Transfer vector
116: */
117: struct refclock refclock_ulink = {
118: ulink_start, /* start up driver */
119: ulink_shutdown, /* shut down driver */
120: ulink_poll, /* transmit poll message */
121: noentry, /* not used */
122: noentry, /* not used */
123: noentry, /* not used */
124: NOFLAGS
125: };
126:
127:
128: /*
129: * ulink_start - open the devices and initialize data for processing
130: */
131: static int
132: ulink_start(
133: int unit,
134: struct peer *peer
135: )
136: {
137: register struct ulinkunit *up;
138: struct refclockproc *pp;
139: int fd;
140: char device[20];
141:
142: /*
143: * Open serial port. Use CLK line discipline, if available.
144: */
145: (void)sprintf(device, DEVICE, unit);
146: if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
147: return (0);
148:
149: /*
150: * Allocate and initialize unit structure
151: */
152: if (!(up = (struct ulinkunit *)
153: emalloc(sizeof(struct ulinkunit)))) {
154: (void) close(fd);
155: return (0);
156: }
157: memset((char *)up, 0, sizeof(struct ulinkunit));
158: pp = peer->procptr;
159: pp->unitptr = (caddr_t)up;
160: pp->io.clock_recv = ulink_receive;
161: pp->io.srcclock = (caddr_t)peer;
162: pp->io.datalen = 0;
163: pp->io.fd = fd;
164: if (!io_addclock(&pp->io)) {
165: (void) close(fd);
166: free(up);
167: return (0);
168: }
169:
170: /*
171: * Initialize miscellaneous variables
172: */
173: peer->precision = PRECISION;
174: peer->burst = NSTAGE;
175: pp->clockdesc = DESCRIPTION;
176: memcpy((char *)&pp->refid, REFID, 4);
177: return (1);
178: }
179:
180:
181: /*
182: * ulink_shutdown - shut down the clock
183: */
184: static void
185: ulink_shutdown(
186: int unit,
187: struct peer *peer
188: )
189: {
190: register struct ulinkunit *up;
191: struct refclockproc *pp;
192:
193: pp = peer->procptr;
194: up = (struct ulinkunit *)pp->unitptr;
195: io_closeclock(&pp->io);
196: free(up);
197: }
198:
199:
200: /*
201: * ulink_receive - receive data from the serial interface
202: */
203: static void
204: ulink_receive(
205: struct recvbuf *rbufp
206: )
207: {
208: struct ulinkunit *up;
209: struct refclockproc *pp;
210: struct peer *peer;
211:
212: l_fp trtmp; /* arrival timestamp */
213: int quality; /* quality indicator */
214: int temp; /* int temp */
215: char syncchar; /* synchronization indicator */
216: char leapchar; /* leap indicator */
217: char modechar; /* model 320 mode flag */
218: char siglchar; /* model difference between 33x/325 */
219: char char_quality[2]; /* temp quality flag */
220:
221: /*
222: * Initialize pointers and read the timecode and timestamp
223: */
224: peer = (struct peer *)rbufp->recv_srcclock;
225: pp = peer->procptr;
226: up = (struct ulinkunit *)pp->unitptr;
227: temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
228:
229: /*
230: * Note we get a buffer and timestamp for both a <cr> and <lf>,
231: * but only the <cr> timestamp is retained.
232: */
233: if (temp == 0) {
234: if (up->tcswitch == 0) {
235: up->tcswitch = 1;
236: up->laststamp = trtmp;
237: } else
238: up->tcswitch = 0;
239: return;
240: }
241: pp->lencode = temp;
242: pp->lastrec = up->laststamp;
243: up->laststamp = trtmp;
244: up->tcswitch = 1;
245: #ifdef DEBUG
246: if (debug)
247: printf("ulink: timecode %d %s\n", pp->lencode,
248: pp->a_lastcode);
249: #endif
250:
251: /*
252: * We get down to business, check the timecode format and decode
253: * its contents. If the timecode has invalid length or is not in
254: * proper format, we declare bad format and exit.
255: */
256: syncchar = leapchar = modechar = siglchar = ' ';
257: switch (pp->lencode ) {
258: case LEN33X:
259:
260: /*
261: * First we check if the format is 33x or 325:
262: * <CR><LF>S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5 (33x)
263: * <CR><LF>R5_1C00LYYYY+DDDUTCS HH:MM:SSL+5 (325)
264: * simply by comparing if the signal level is 'S' or 'R'
265: */
266:
267: if (sscanf(pp->a_lastcode, "%c%*31c",
268: &siglchar) == 1) {
269:
270: if(siglchar == SIGLCHAR325) {
271:
272: /*
273: * decode for a Model 325 decoder.
274: * Timecode format from January 23, 2004 datasheet is:
275: *
276: * <CR><LF>R5_1C00LYYYY+DDDUTCS HH:MM:SSL+5
277: *
278: * R WWVB decodersignal readability R1 - R5
279: * 5 R1 is unreadable, R5 is best
280: * space a space (0x20)
281: * 1 Data bit 0, 1, M (pos mark), or ? (unknown).
282: * C Reception from either (C)olorado or (H)awaii
283: * 00 Hours since last good WWVB frame sync. Will
284: * be 00-99
285: * space Space char (0x20) or (0xa5) if locked to wwvb
286: * YYYY Current year, 2000-2099
287: * + Leap year indicator. '+' if a leap year,
288: * a space (0x20) if not.
289: * DDD Day of year, 000 - 365.
290: * UTC Timezone (always 'UTC').
291: * S Daylight savings indicator
292: * S - standard time (STD) in effect
293: * O - during STD to DST day 0000-2400
294: * D - daylight savings time (DST) in effect
295: * I - during DST to STD day 0000-2400
296: * space Space character (0x20)
297: * HH Hours 00-23
298: * : This is the REAL in sync indicator (: = insync)
299: * MM Minutes 00-59
300: * : : = in sync ? = NOT in sync
301: * SS Seconds 00-59
302: * L Leap second flag. Changes from space (0x20)
303: * to 'I' or 'D' during month preceding leap
304: * second adjustment. (I)nsert or (D)elete
305: * +5 UT1 correction (sign + digit ))
306: */
307:
308: if (sscanf(pp->a_lastcode,
309: "%*2c %*2c%2c%*c%4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c",
310: char_quality, &pp->year, &pp->day,
311: &pp->hour, &syncchar, &pp->minute, &pp->second,
312: &leapchar) == 8) {
313:
314: if (char_quality[0] == '0') {
315: quality = 0;
316: } else if (char_quality[0] == '0') {
317: quality = (char_quality[1] & 0x0f);
318: } else {
319: quality = 99;
320: }
321:
322: if (leapchar == 'I' ) leapchar = '+';
323: if (leapchar == 'D' ) leapchar = '-';
324:
325: /*
326: #ifdef DEBUG
327: if (debug) {
328: printf("ulink: char_quality %c %c\n",
329: char_quality[0], char_quality[1]);
330: printf("ulink: quality %d\n", quality);
331: printf("ulink: syncchar %x\n", syncchar);
332: printf("ulink: leapchar %x\n", leapchar);
333: }
334: #endif
335: */
336:
337: }
338:
339: }
340: if(siglchar == SIGLCHAR33x) {
341:
342: /*
343: * We got a Model 33X decoder.
344: * Timecode format from January 29, 2001 datasheet is:
345: * <CR><LF>S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5
346: * S WWVB decoder sync indicator. S for in-sync(?)
347: * or N for noisy signal.
348: * 9+ RF signal level in S-units, 0-9 followed by
349: * a space (0x20). The space turns to '+' if the
350: * level is over 9.
351: * D Data bit 0, 1, 2 (position mark), or
352: * 3 (unknown).
353: * space Space character (0x20)
354: * 00 Hours since last good WWVB frame sync. Will
355: * be 00-23 hrs, or '1d' to '7d'. Will be 'Lk'
356: * if currently in sync.
357: * space Space character (0x20)
358: * YYYY Current year, 1990-2089
359: * + Leap year indicator. '+' if a leap year,
360: * a space (0x20) if not.
361: * DDD Day of year, 001 - 366.
362: * UTC Timezone (always 'UTC').
363: * S Daylight savings indicator
364: * S - standard time (STD) in effect
365: * O - during STD to DST day 0000-2400
366: * D - daylight savings time (DST) in effect
367: * I - during DST to STD day 0000-2400
368: * space Space character (0x20)
369: * HH Hours 00-23
370: * : This is the REAL in sync indicator (: = insync)
371: * MM Minutes 00-59
372: * : : = in sync ? = NOT in sync
373: * SS Seconds 00-59
374: * L Leap second flag. Changes from space (0x20)
375: * to '+' or '-' during month preceding leap
376: * second adjustment.
377: * +5 UT1 correction (sign + digit ))
378: */
379:
380: if (sscanf(pp->a_lastcode,
381: "%*4c %2c %4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c",
382: char_quality, &pp->year, &pp->day,
383: &pp->hour, &syncchar, &pp->minute, &pp->second,
384: &leapchar) == 8) {
385:
386: if (char_quality[0] == 'L') {
387: quality = 0;
388: } else if (char_quality[0] == '0') {
389: quality = (char_quality[1] & 0x0f);
390: } else {
391: quality = 99;
392: }
393:
394: /*
395: #ifdef DEBUG
396: if (debug) {
397: printf("ulink: char_quality %c %c\n",
398: char_quality[0], char_quality[1]);
399: printf("ulink: quality %d\n", quality);
400: printf("ulink: syncchar %x\n", syncchar);
401: printf("ulink: leapchar %x\n", leapchar);
402: }
403: #endif
404: */
405:
406: }
407: }
408: break;
409: }
410:
411: case LEN320:
412:
413: /*
414: * Model 320 Decoder
415: * The timecode format is:
416: *
417: * <cr><lf>SQRYYYYDDD+HH:MM:SS.mmLT<cr>
418: *
419: * where:
420: *
421: * S = 'S' -- sync'd in last hour,
422: * '0'-'9' - hours x 10 since last update,
423: * '?' -- not in sync
424: * Q = Number of correlating time-frames, from 0 to 5
425: * R = 'R' -- reception in progress,
426: * 'N' -- Noisy reception,
427: * ' ' -- standby mode
428: * YYYY = year from 1990 to 2089
429: * DDD = current day from 1 to 366
430: * + = '+' if current year is a leap year, else ' '
431: * HH = UTC hour 0 to 23
432: * MM = Minutes of current hour from 0 to 59
433: * SS = Seconds of current minute from 0 to 59
434: * mm = 10's milliseconds of the current second from 00 to 99
435: * L = Leap second pending at end of month
436: * 'I' = insert, 'D'= delete
437: * T = DST <-> STD transition indicators
438: *
439: */
440:
441: if (sscanf(pp->a_lastcode, "%c%1d%c%4d%3d%*c%2d:%2d:%2d.%2ld%c",
442: &syncchar, &quality, &modechar, &pp->year, &pp->day,
443: &pp->hour, &pp->minute, &pp->second,
444: &pp->nsec, &leapchar) == 10) {
445: pp->nsec *= 10000000; /* M320 returns 10's of msecs */
446: if (leapchar == 'I' ) leapchar = '+';
447: if (leapchar == 'D' ) leapchar = '-';
448: if (syncchar != '?' ) syncchar = ':';
449:
450: break;
451: }
452:
453: default:
454: refclock_report(peer, CEVNT_BADREPLY);
455: return;
456: }
457:
458: /*
459: * Decode quality indicator
460: * For the 325 & 33x series, the lower the number the "better"
461: * the time is. I used the dispersion as the measure of time
462: * quality. The quality indicator in the 320 is the number of
463: * correlating time frames (the more the better)
464: */
465:
466: /*
467: * The spec sheet for the 325 & 33x series states the clock will
468: * maintain +/-0.002 seconds accuracy when locked to WWVB. This
469: * is indicated by 'Lk' in the quality portion of the incoming
470: * string. When not in lock, a drift of +/-0.015 seconds should
471: * be allowed for.
472: * With the quality indicator decoding scheme above, the 'Lk'
473: * condition will produce a quality value of 0. If the quality
474: * indicator starts with '0' then the second character is the
475: * number of hours since we were last locked. If the first
476: * character is anything other than 'L' or '0' then we have been
477: * out of lock for more than 9 hours so we assume the worst and
478: * force a quality value that selects the 'default' maximum
479: * dispersion. The dispersion values below are what came with the
480: * driver. They're not unreasonable so they've not been changed.
481: */
482:
483: if (pp->lencode == LEN33X) {
484: switch (quality) {
485: case 0 :
486: pp->disp=.002;
487: break;
488: case 1 :
489: pp->disp=.02;
490: break;
491: case 2 :
492: pp->disp=.04;
493: break;
494: case 3 :
495: pp->disp=.08;
496: break;
497: default:
498: pp->disp=MAXDISPERSE;
499: break;
500: }
501: } else {
502: switch (quality) {
503: case 5 :
504: pp->disp=.002;
505: break;
506: case 4 :
507: pp->disp=.02;
508: break;
509: case 3 :
510: pp->disp=.04;
511: break;
512: case 2 :
513: pp->disp=.08;
514: break;
515: case 1 :
516: pp->disp=.16;
517: break;
518: default:
519: pp->disp=MAXDISPERSE;
520: break;
521: }
522:
523: }
524:
525: /*
526: * Decode synchronization, and leap characters. If
527: * unsynchronized, set the leap bits accordingly and exit.
528: * Otherwise, set the leap bits according to the leap character.
529: */
530:
531: if (syncchar != ':')
532: pp->leap = LEAP_NOTINSYNC;
533: else if (leapchar == '+')
534: pp->leap = LEAP_ADDSECOND;
535: else if (leapchar == '-')
536: pp->leap = LEAP_DELSECOND;
537: else
538: pp->leap = LEAP_NOWARNING;
539:
540: /*
541: * Process the new sample in the median filter and determine the
542: * timecode timestamp.
543: */
544: if (!refclock_process(pp)) {
545: refclock_report(peer, CEVNT_BADTIME);
546: }
547:
548: }
549:
550: /*
551: * ulink_poll - called by the transmit procedure
552: */
553:
554: static void
555: ulink_poll(
556: int unit,
557: struct peer *peer
558: )
559: {
560: struct refclockproc *pp;
561: char pollchar;
562:
563: pp = peer->procptr;
564: pollchar = 'T';
565: if (pp->sloppyclockflag & CLK_FLAG1) {
566: if (write(pp->io.fd, &pollchar, 1) != 1)
567: refclock_report(peer, CEVNT_FAULT);
568: else
569: pp->polls++;
570: }
571: else
572: pp->polls++;
573:
574: if (peer->burst > 0)
575: return;
576: if (pp->coderecv == pp->codeproc) {
577: refclock_report(peer, CEVNT_TIMEOUT);
578: return;
579: }
580: pp->lastref = pp->lastrec;
581: refclock_receive(peer);
582: record_clock_stats(&peer->srcadr, pp->a_lastcode);
583: peer->burst = NSTAGE;
584:
585: }
586:
587: #else
588: int refclock_ulink_bs;
589: #endif /* REFCLOCK */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>