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