1 /* 2 * refclock_ulink - clock driver for Ultralink WWVB receiver 3 * 4 */ 5 6 /*********************************************************************** 7 * * 8 * Copyright (c) David L. Mills 1992-1998 * 9 * * 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 * 20 * warranty. * 21 **********************************************************************/ 22 23 #ifdef HAVE_CONFIG_H 24 #include <config.h> 25 #endif 26 27 #if defined(REFCLOCK) && defined(CLOCK_ULINK) 28 29 #include <stdio.h> 30 #include <ctype.h> 31 32 #include "ntpd.h" 33 #include "ntp_io.h" 34 #include "ntp_refclock.h" 35 #include "ntp_calendar.h" 36 #include "ntp_stdlib.h" 37 38 /* 39 * This driver supports ultralink Model 320,330,331,332 WWVB radios 40 * 41 * this driver was based on the refclock_wwvb.c driver 42 * in the ntp distribution. 43 * 44 * Fudge Factors 45 * 46 * fudge flag1 0 don't poll clock 47 * 1 send poll character 48 * 49 * revision history: 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 53 * sends. 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 58 * Model 320 driver) 59 * 99/11/29 j.c.lang added fudge flag 1 to control 60 * clock polling 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 65 * 66 * Questions, bugs, ideas send to: 67 * Joseph C. Lang 68 * tcnojl1@earthlink.net 69 * 70 * Dave Strout 71 * dstrout@linuxfoundry.com 72 * 73 * 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 78 */ 79 80 81 /* 82 * Interface definitions 83 */ 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 */ 89 90 #define LEN33X 32 /* timecode length Model 325 & 33X */ 91 #define LEN320 24 /* timecode length Model 320 */ 92 93 /* 94 * unit control structure 95 */ 96 struct ulinkunit { 97 u_char tcswitch; /* timecode switch */ 98 l_fp laststamp; /* last receive timestamp */ 99 }; 100 101 /* 102 * Function prototypes 103 */ 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 *)); 108 109 /* 110 * Transfer vector 111 */ 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 */ 119 NOFLAGS 120 }; 121 122 123 /* 124 * ulink_start - open the devices and initialize data for processing 125 */ 126 static int 127 ulink_start( 128 int unit, 129 struct peer *peer 130 ) 131 { 132 register struct ulinkunit *up; 133 struct refclockproc *pp; 134 int fd; 135 char device[20]; 136 137 /* 138 * Open serial port. Use CLK line discipline, if available. 139 */ 140 (void)sprintf(device, DEVICE, unit); 141 if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) 142 return (0); 143 144 /* 145 * Allocate and initialize unit structure 146 */ 147 if (!(up = (struct ulinkunit *) 148 emalloc(sizeof(struct ulinkunit)))) { 149 (void) close(fd); 150 return (0); 151 } 152 memset((char *)up, 0, sizeof(struct ulinkunit)); 153 pp = peer->procptr; 154 pp->unitptr = (caddr_t)up; 155 pp->io.clock_recv = ulink_receive; 156 pp->io.srcclock = (caddr_t)peer; 157 pp->io.datalen = 0; 158 pp->io.fd = fd; 159 if (!io_addclock(&pp->io)) { 160 (void) close(fd); 161 free(up); 162 return (0); 163 } 164 165 /* 166 * Initialize miscellaneous variables 167 */ 168 peer->precision = PRECISION; 169 peer->burst = NSTAGE; 170 pp->clockdesc = DESCRIPTION; 171 memcpy((char *)&pp->refid, REFID, 4); 172 return (1); 173 } 174 175 176 /* 177 * ulink_shutdown - shut down the clock 178 */ 179 static void 180 ulink_shutdown( 181 int unit, 182 struct peer *peer 183 ) 184 { 185 register struct ulinkunit *up; 186 struct refclockproc *pp; 187 188 pp = peer->procptr; 189 up = (struct ulinkunit *)pp->unitptr; 190 io_closeclock(&pp->io); 191 free(up); 192 } 193 194 195 /* 196 * ulink_receive - receive data from the serial interface 197 */ 198 static void 199 ulink_receive( 200 struct recvbuf *rbufp 201 ) 202 { 203 struct ulinkunit *up; 204 struct refclockproc *pp; 205 struct peer *peer; 206 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 */ 214 215 /* 216 * Initialize pointers and read the timecode and timestamp 217 */ 218 peer = (struct peer *)rbufp->recv_srcclock; 219 pp = peer->procptr; 220 up = (struct ulinkunit *)pp->unitptr; 221 temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); 222 223 /* 224 * Note we get a buffer and timestamp for both a <cr> and <lf>, 225 * but only the <cr> timestamp is retained. 226 */ 227 if (temp == 0) { 228 if (up->tcswitch == 0) { 229 up->tcswitch = 1; 230 up->laststamp = trtmp; 231 } else 232 up->tcswitch = 0; 233 return; 234 } 235 pp->lencode = temp; 236 pp->lastrec = up->laststamp; 237 up->laststamp = trtmp; 238 up->tcswitch = 1; 239 #ifdef DEBUG 240 if (debug) 241 printf("ulink: timecode %d %s\n", pp->lencode, 242 pp->a_lastcode); 243 #endif 244 245 /* 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. 249 */ 250 syncchar = leapchar = modechar = ' '; 251 switch (pp->lencode ) { 252 case LEN33X: 253 /* 254 * Model 33X decoder: 255 * Timecode format from January 29, 2001 datasheet is: 256 * <CR><LF>S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5 257 * S WWVB decoder sync indicator. S for in-sync(?) 258 * or N for noisy signal. 259 * 9+ RF signal level in S-units, 0-9 followed by 260 * a space (0x20). The space turns to '+' if the 261 * level is over 9. 262 * D Data bit 0, 1, 2 (position mark), or 263 * 3 (unknown). 264 * space Space character (0x20) 265 * 00 Hours since last good WWVB frame sync. Will 266 * be 00-23 hrs, or '1d' to '7d'. Will be 'Lk' 267 * if currently in sync. 268 * space Space character (0x20) 269 * YYYY Current year, 1990-2089 270 * + Leap year indicator. '+' if a leap year, 271 * a space (0x20) if not. 272 * DDD Day of year, 001 - 366. 273 * UTC Timezone (always 'UTC'). 274 * S Daylight savings indicator 275 * S - standard time (STD) in effect 276 * O - during STD to DST day 0000-2400 277 * D - daylight savings time (DST) in effect 278 * I - during DST to STD day 0000-2400 279 * space Space character (0x20) 280 * HH Hours 00-23 281 * : This is the REAL in sync indicator (: = insync) 282 * MM Minutes 00-59 283 * : : = in sync ? = NOT in sync 284 * SS Seconds 00-59 285 * L Leap second flag. Changes from space (0x20) 286 * to '+' or '-' during month preceding leap 287 * second adjustment. 288 * +5 UT1 correction (sign + digit )) 289 */ 290 291 if (sscanf(pp->a_lastcode, 292 "%*4c %2c %4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c", 293 char_quality, &pp->year, &pp->day, 294 &pp->hour, &syncchar, &pp->minute, &pp->second, 295 &leapchar) == 8) { 296 297 if (char_quality[0] == 'L') { 298 quality = 0; 299 } else if (char_quality[0] == '0') { 300 quality = (char_quality[1] & 0x0f); 301 } else { 302 quality = 99; 303 } 304 305 /* 306 #ifdef DEBUG 307 if (debug) { 308 printf("ulink: char_quality %c %c\n", 309 char_quality[0], char_quality[1]); 310 printf("ulink: quality %d\n", quality); 311 printf("ulink: syncchar %x\n", syncchar); 312 printf("ulink: leapchar %x\n", leapchar); 313 } 314 #endif 315 */ 316 317 break; 318 } 319 320 case LEN320: 321 /* 322 * Model 320 Decoder 323 * The timecode format is: 324 * 325 * <cr><lf>SQRYYYYDDD+HH:MM:SS.mmLT<cr> 326 * 327 * where: 328 * 329 * S = 'S' -- sync'd in last hour, 330 * '0'-'9' - hours x 10 since last update, 331 * '?' -- not in sync 332 * Q = Number of correlating time-frames, from 0 to 5 333 * R = 'R' -- reception in progress, 334 * 'N' -- Noisy reception, 335 * ' ' -- standby mode 336 * YYYY = year from 1990 to 2089 337 * DDD = current day from 1 to 366 338 * + = '+' if current year is a leap year, else ' ' 339 * HH = UTC hour 0 to 23 340 * MM = Minutes of current hour from 0 to 59 341 * SS = Seconds of current minute from 0 to 59 342 * mm = 10's milliseconds of the current second from 00 to 99 343 * L = Leap second pending at end of month 344 * 'I' = insert, 'D'= delete 345 * T = DST <-> STD transition indicators 346 * 347 */ 348 if (sscanf(pp->a_lastcode, "%c%1d%c%4d%3d%*c%2d:%2d:%2d.%2ld%c", 349 &syncchar, &quality, &modechar, &pp->year, &pp->day, 350 &pp->hour, &pp->minute, &pp->second, 351 &pp->nsec, &leapchar) == 10) { 352 pp->nsec *= 10000000; /* M320 returns 10's of msecs */ 353 if (leapchar == 'I' ) leapchar = '+'; 354 if (leapchar == 'D' ) leapchar = '-'; 355 if (syncchar != '?' ) syncchar = ':'; 356 357 break; 358 } 359 360 default: 361 refclock_report(peer, CEVNT_BADREPLY); 362 return; 363 } 364 365 366 /* 367 * Decode quality indicator 368 * For the 325 & 33x series, the lower the number the "better" 369 * the time is. I used the dispersion as the measure of time 370 * quality. The quality indicator in the 320 is the number of 371 * correlating time frames (the more the better) 372 */ 373 374 /* 375 * The spec sheet for the 325 & 33x series states the clock will 376 * maintain +/-0.002 seconds accuracy when locked to WWVB. This 377 * is indicated by 'Lk' in the quality portion of the incoming 378 * string. When not in lock, a drift of +/-0.015 seconds should 379 * be allowed for. 380 * With the quality indicator decoding scheme above, the 'Lk' 381 * condition will produce a quality value of 0. If the quality 382 * indicator starts with '0' then the second character is the 383 * number of hours since we were last locked. If the first 384 * character is anything other than 'L' or '0' then we have been 385 * out of lock for more than 9 hours so we assume the worst and 386 * force a quality value that selects the 'default' maximum 387 * dispersion. The dispersion values below are what came with the 388 * driver. They're not unreasonable so they've not been changed. 389 */ 390 391 if (pp->lencode == LEN33X) { 392 switch (quality) { 393 case 0 : 394 pp->disp=.002; 395 break; 396 case 1 : 397 pp->disp=.02; 398 break; 399 case 2 : 400 pp->disp=.04; 401 break; 402 case 3 : 403 pp->disp=.08; 404 break; 405 default: 406 pp->disp=MAXDISPERSE; 407 break; 408 } 409 } else { 410 switch (quality) { 411 case 5 : 412 pp->disp=.002; 413 break; 414 case 4 : 415 pp->disp=.02; 416 break; 417 case 3 : 418 pp->disp=.04; 419 break; 420 case 2 : 421 pp->disp=.08; 422 break; 423 case 1 : 424 pp->disp=.16; 425 break; 426 default: 427 pp->disp=MAXDISPERSE; 428 break; 429 } 430 431 } 432 433 /* 434 * Decode synchronization, and leap characters. If 435 * unsynchronized, set the leap bits accordingly and exit. 436 * Otherwise, set the leap bits according to the leap character. 437 */ 438 439 if (syncchar != ':') 440 pp->leap = LEAP_NOTINSYNC; 441 else if (leapchar == '+') 442 pp->leap = LEAP_ADDSECOND; 443 else if (leapchar == '-') 444 pp->leap = LEAP_DELSECOND; 445 else 446 pp->leap = LEAP_NOWARNING; 447 448 /* 449 * Process the new sample in the median filter and determine the 450 * timecode timestamp. 451 */ 452 if (!refclock_process(pp)) { 453 refclock_report(peer, CEVNT_BADTIME); 454 } 455 456 } 457 458 459 /* 460 * ulink_poll - called by the transmit procedure 461 */ 462 static void 463 ulink_poll( 464 int unit, 465 struct peer *peer 466 ) 467 { 468 struct refclockproc *pp; 469 char pollchar; 470 471 pp = peer->procptr; 472 pollchar = 'T'; 473 if (pp->sloppyclockflag & CLK_FLAG1) { 474 if (write(pp->io.fd, &pollchar, 1) != 1) 475 refclock_report(peer, CEVNT_FAULT); 476 else 477 pp->polls++; 478 } 479 else 480 pp->polls++; 481 482 if (peer->burst > 0) 483 return; 484 if (pp->coderecv == pp->codeproc) { 485 refclock_report(peer, CEVNT_TIMEOUT); 486 return; 487 } 488 pp->lastref = pp->lastrec; 489 refclock_receive(peer); 490 record_clock_stats(&peer->srcadr, pp->a_lastcode); 491 peer->burst = NSTAGE; 492 493 } 494 495 #else 496 int refclock_ulink_bs; 497 #endif /* REFCLOCK */ 498