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 P((int, struct peer *));
110 static void ulink_shutdown P((int, struct peer *));
111 static void ulink_receive P((struct recvbuf *));
112 static void ulink_poll P((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
ulink_start(int unit,struct peer * peer)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
ulink_shutdown(int unit,struct peer * peer)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
ulink_receive(struct recvbuf * rbufp)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
ulink_poll(int unit,struct peer * peer)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 */
590