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