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 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 */ 590