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