1 /* 2 * refclock_heath - clock driver for Heath GC-1000 Most Accurate Clock 3 */ 4 #ifdef HAVE_CONFIG_H 5 #include <config.h> 6 #endif 7 8 #if defined(REFCLOCK) && defined(CLOCK_HEATH) 9 10 #include <stdio.h> 11 #include <ctype.h> 12 #ifdef TIME_WITH_SYS_TIME 13 # include <sys/time.h> 14 # include <time.h> 15 #else 16 # ifdef TM_IN_SYS_TIME 17 # include <sys/time.h> 18 # else 19 # include <time.h> 20 # endif 21 #endif 22 #ifdef HAVE_SYS_IOCTL_H 23 # include <sys/ioctl.h> 24 #endif /* not HAVE_SYS_IOCTL_H */ 25 26 #include "ntpd.h" 27 #include "ntp_io.h" 28 #include "ntp_refclock.h" 29 #include "ntp_stdlib.h" 30 31 /* 32 * This driver supports the Heath GC-1000 Most Accurate Clock, with 33 * RS232C Output Accessory. This is a WWV/WWVH receiver somewhat less 34 * robust than other supported receivers. Its claimed accuracy is 100 ms 35 * when actually synchronized to the broadcast signal, but this doesn't 36 * happen even most of the time, due to propagation conditions, ambient 37 * noise sources, etc. When not synchronized, the accuracy is at the 38 * whim of the internal clock oscillator, which can wander into the 39 * sunset without warning. Since the indicated precision is 100 ms, 40 * expect a host synchronized only to this thing to wander to and fro, 41 * occasionally being rudely stepped when the offset exceeds the default 42 * clock_max of 128 ms. 43 * 44 * The internal DIPswitches should be set to operate at 1200 baud in 45 * MANUAL mode and the current year. The external DIPswitches should be 46 * set to GMT and 24-hour format, or to the host local time zone (with 47 * DST) and 12-hour format. It is very important that the year be 48 * set correctly in the DIPswitches. Otherwise, the day of year will be 49 * incorrect after 28 April[?] of a normal or leap year. In 12-hour mode 50 * with DST selected the clock will be incorrect by an hour for an 51 * indeterminate amount of time between 0000Z and 0200 on the day DST 52 * changes. 53 * 54 * In MANUAL mode the clock responds to a rising edge of the request to 55 * send (RTS) modem control line by sending the timecode. Therefore, it 56 * is necessary that the operating system implement the TIOCMBIC and 57 * TIOCMBIS ioctl system calls and TIOCM_RTS control bit. Present 58 * restrictions require the use of a POSIX-compatible programming 59 * interface, although other interfaces may work as well. 60 * 61 * A simple hardware modification to the clock can be made which 62 * prevents the clock hearing the request to send (RTS) if the HI SPEC 63 * lamp is out. Route the HISPEC signal to the tone decoder board pin 64 * 19, from the display, pin 19. Isolate pin 19 of the decoder board 65 * first, but maintain connection with pin 10. Also isolate pin 38 of 66 * the CPU on the tone board, and use half an added 7400 to gate the 67 * original signal to pin 38 with that from pin 19. 68 * 69 * The clock message consists of 23 ASCII printing characters in the 70 * following format: 71 * 72 * hh:mm:ss.f AM dd/mm/yr<cr> 73 * 74 * hh:mm:ss.f = hours, minutes, seconds 75 * f = deciseconds ('?' when out of spec) 76 * AM/PM/bb = blank in 24-hour mode 77 * dd/mm/yr = day, month, year 78 * 79 * The alarm condition is indicated by '?', rather than a digit, at f. 80 * Note that 0?:??:??.? is displayed before synchronization is first 81 * established and hh:mm:ss.? once synchronization is established and 82 * then lost again for about a day. 83 * 84 * Fudge Factors 85 * 86 * A fudge time1 value of .04 s appears to center the clock offset 87 * residuals. The fudge time2 parameter is the local time offset east of 88 * Greenwich, which depends on DST. Sorry about that, but the clock 89 * gives no hint on what the DIPswitches say. 90 */ 91 92 /* 93 * Interface definitions 94 */ 95 #define DEVICE "/dev/heath%d" /* device name and unit */ 96 #define SPEED232 B1200 /* uart speed (1200 baud) */ 97 #define PRECISION (-4) /* precision assumed (about 100 ms) */ 98 #define REFID "WWV\0" /* reference ID */ 99 #define DESCRIPTION "Heath GC-1000 Most Accurate Clock" /* WRU */ 100 101 #define LENHEATH 23 /* min timecode length */ 102 103 /* 104 * Tables to compute the ddd of year form icky dd/mm timecode. Viva la 105 * leap. 106 */ 107 static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 108 static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 109 110 /* 111 * Unit control structure 112 */ 113 struct heathunit { 114 int pollcnt; /* poll message counter */ 115 l_fp tstamp; /* timestamp of last poll */ 116 }; 117 118 /* 119 * Function prototypes 120 */ 121 static int heath_start P((int, struct peer *)); 122 static void heath_shutdown P((int, struct peer *)); 123 static void heath_receive P((struct recvbuf *)); 124 static void heath_poll P((int, struct peer *)); 125 126 /* 127 * Transfer vector 128 */ 129 struct refclock refclock_heath = { 130 heath_start, /* start up driver */ 131 heath_shutdown, /* shut down driver */ 132 heath_poll, /* transmit poll message */ 133 noentry, /* not used (old heath_control) */ 134 noentry, /* initialize driver */ 135 noentry, /* not used (old heath_buginfo) */ 136 NOFLAGS /* not used */ 137 }; 138 139 #if 0 140 /* 141 * Gee, Unix so thoughfully omitted code to convert from a struct tm to 142 * a long, so I'll just have to ferret out the inverse myself, the hard way. 143 * (Newton's method.) 144 */ 145 #define timelocal(x) invert(x, localtime) 146 /* 147 * comparetm compares two tm structures and returns -1 if the first 148 * is less than the second, 0 if they are equal, and +1 if the first 149 * is greater than the second. Only the year, month, day, hour, minute 150 * and second are compared. The yearday (Julian), day of week, and isdst 151 * are not compared. 152 */ 153 154 static int 155 comparetm( 156 struct tm *a, 157 struct tm *b 158 ) 159 { 160 if (a->tm_year < b->tm_year ) return -1; 161 if (a->tm_year > b->tm_year ) return 1; 162 if (a->tm_mon < b->tm_mon ) return -1; 163 if (a->tm_mon > b->tm_mon ) return 1; 164 if (a->tm_mday < b->tm_mday ) return -1; 165 if (a->tm_mday > b->tm_mday ) return 1; 166 if (a->tm_hour < b->tm_hour ) return -1; 167 if (a->tm_hour > b->tm_hour ) return 1; 168 if (a->tm_min < b->tm_min ) return -1; 169 if (a->tm_min > b->tm_min ) return 1; 170 if (a->tm_sec < b->tm_sec ) return -1; 171 if (a->tm_sec > b->tm_sec ) return 1; 172 return 0; 173 } 174 175 static long 176 invert ( 177 struct tm *x, 178 struct tm *(*func)() 179 ) 180 { 181 struct tm *y; 182 int result; 183 long trial; 184 long lower=0L; 185 long upper=(long)((unsigned long)(~lower) >> 1); 186 187 do { 188 trial = (upper + lower) / 2L; 189 y = (*func)(&trial); 190 result = comparetm(x, y); 191 if (result < 0) upper = trial; 192 if (result > 0) lower = trial; 193 } while (result != 0); 194 return trial; 195 } 196 #endif /* 0 */ 197 198 199 /* 200 * heath_start - open the devices and initialize data for processing 201 */ 202 static int 203 heath_start( 204 int unit, 205 struct peer *peer 206 ) 207 { 208 register struct heathunit *up; 209 struct refclockproc *pp; 210 int fd; 211 char device[20]; 212 213 /* 214 * Open serial port 215 */ 216 (void)sprintf(device, DEVICE, unit); 217 if (!(fd = refclock_open(device, SPEED232, 0))) 218 return (0); 219 220 /* 221 * Allocate and initialize unit structure 222 */ 223 if (!(up = (struct heathunit *) 224 emalloc(sizeof(struct heathunit)))) { 225 (void) close(fd); 226 return (0); 227 } 228 memset((char *)up, 0, sizeof(struct heathunit)); 229 pp = peer->procptr; 230 pp->io.clock_recv = heath_receive; 231 pp->io.srcclock = (caddr_t)peer; 232 pp->io.datalen = 0; 233 pp->io.fd = fd; 234 if (!io_addclock(&pp->io)) { 235 (void) close(fd); 236 free(up); 237 return (0); 238 } 239 pp->unitptr = (caddr_t)up; 240 241 /* 242 * Initialize miscellaneous variables 243 */ 244 peer->precision = PRECISION; 245 peer->burst = NSTAGE; 246 pp->clockdesc = DESCRIPTION; 247 memcpy((char *)&pp->refid, REFID, 4); 248 up->pollcnt = 2; 249 return (1); 250 } 251 252 253 /* 254 * heath_shutdown - shut down the clock 255 */ 256 static void 257 heath_shutdown( 258 int unit, 259 struct peer *peer 260 ) 261 { 262 register struct heathunit *up; 263 struct refclockproc *pp; 264 265 pp = peer->procptr; 266 up = (struct heathunit *)pp->unitptr; 267 io_closeclock(&pp->io); 268 free(up); 269 } 270 271 272 /* 273 * heath_receive - receive data from the serial interface 274 */ 275 static void 276 heath_receive( 277 struct recvbuf *rbufp 278 ) 279 { 280 register struct heathunit *up; 281 struct refclockproc *pp; 282 struct peer *peer; 283 l_fp trtmp; 284 int month, day; 285 int i; 286 char dsec, a[5]; 287 288 /* 289 * Initialize pointers and read the timecode and timestamp 290 */ 291 peer = (struct peer *)rbufp->recv_srcclock; 292 pp = peer->procptr; 293 up = (struct heathunit *)pp->unitptr; 294 pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); 295 296 /* 297 * We get a buffer and timestamp for each <cr>; however, we use 298 * the timestamp captured at the RTS modem control line toggle 299 * on the assumption that's what the radio bases the timecode 300 * on. Apparently, the radio takes about a second to make up its 301 * mind to send a timecode, so the receive timestamp is 302 * worthless. 303 */ 304 pp->lastrec = up->tstamp; 305 up->pollcnt = 2; 306 #ifdef DEBUG 307 if (debug) 308 printf("heath: timecode %d %s\n", pp->lencode, 309 pp->a_lastcode); 310 #endif 311 312 /* 313 * We get down to business, check the timecode format and decode 314 * its contents. If the timecode has invalid length or is not in 315 * proper format, we declare bad format and exit. 316 */ 317 if (pp->lencode < LENHEATH) { 318 refclock_report(peer, CEVNT_BADREPLY); 319 return; 320 } 321 322 /* 323 * Timecode format: "hh:mm:ss.f AM mm/dd/yy" 324 */ 325 if (sscanf(pp->a_lastcode, "%2d:%2d:%2d.%c%5c%2d/%2d/%2d", 326 &pp->hour, &pp->minute, &pp->second, &dsec, a, &month, &day, 327 &pp->year) != 8) { 328 refclock_report(peer, CEVNT_BADREPLY); 329 return; 330 } 331 332 /* 333 * If AM or PM is received, assume the clock is displaying local 334 * time. First, convert to 24-hour format. 335 */ 336 337 switch (a[1]) { 338 case 'P': 339 if (12 > pp->hour) 340 pp->hour += 12; 341 break; 342 343 case 'A': 344 if (12 == pp->hour) 345 pp->hour -= 12; 346 break; 347 } 348 349 /* 350 * Now make a struct tm out of it, convert to UTC, and 351 * repopulate pp-> 352 */ 353 354 if (' ' != a[1]) { 355 struct tm t, *q; 356 time_t l; 357 358 t.tm_sec = pp->second; 359 t.tm_min = pp->minute; 360 t.tm_hour = pp->hour; 361 t.tm_mday = day; /* not converted to yday yet */ 362 t.tm_mon = month-1; /* ditto */ 363 t.tm_year = pp->year; 364 if ( t.tm_year < YEAR_PIVOT ) t.tm_year += 100; /* Y2KFixes */ 365 366 t.tm_wday = -1; /* who knows? */ 367 t.tm_yday = -1; /* who knows? */ 368 t.tm_isdst = -1; /* who knows? */ 369 370 l = mktime(&t); 371 if (l == -1) { 372 /* HMS: do we want to do this? */ 373 refclock_report(peer, CEVNT_BADTIME); 374 return; 375 } 376 q = gmtime(&l); 377 378 pp->year = q->tm_year; 379 month = q->tm_mon+1; 380 day = q->tm_mday; /* still not converted */ 381 pp->hour = q->tm_hour; 382 /* pp->minute = q->tm_min; GC-1000 cannot adjust timezone */ 383 /* pp->second = q->tm_sec; by other than hour increments */ 384 } 385 386 387 388 /* 389 * We determine the day of the year from the DIPswitches. This 390 * should be fixed, since somebody might forget to set them. 391 * Someday this hazard will be fixed by a fiendish scheme that 392 * looks at the timecode and year the radio shows, then computes 393 * the residue of the seconds mod the seconds in a leap cycle. 394 * If in the third year of that cycle and the third and later 395 * months of that year, add one to the day. Then, correct the 396 * timecode accordingly. Icky pooh. This bit of nonsense could 397 * be avoided if the engineers had been required to write a 398 * device driver before finalizing the timecode format. 399 * 400 * Yes, I know this code incorrectly thinks that 2000 is a leap 401 * year; but, the latest year that can be set by the DIPswitches 402 * is 1997 anyay. Life is short. 403 * Hey! Year 2000 IS a leap year! Y2KFixes 404 */ 405 if (month < 1 || month > 12 || day < 1) { 406 refclock_report(peer, CEVNT_BADTIME); 407 return; 408 } 409 if (pp->year % 4) { 410 if (day > day1tab[month - 1]) { 411 refclock_report(peer, CEVNT_BADTIME); 412 return; 413 } 414 for (i = 0; i < month - 1; i++) 415 day += day1tab[i]; 416 } else { 417 if (day > day2tab[month - 1]) { 418 refclock_report(peer, CEVNT_BADTIME); 419 return; 420 } 421 for (i = 0; i < month - 1; i++) 422 day += day2tab[i]; 423 } 424 pp->day = day; 425 426 /* 427 * Determine synchronization and last update 428 */ 429 if (!isdigit((int)dsec)) 430 pp->leap = LEAP_NOTINSYNC; 431 else { 432 pp->msec = (dsec - '0') * 100; 433 pp->leap = LEAP_NOWARNING; 434 } 435 if (!refclock_process(pp)) 436 refclock_report(peer, CEVNT_BADTIME); 437 } 438 439 440 /* 441 * heath_poll - called by the transmit procedure 442 */ 443 static void 444 heath_poll( 445 int unit, 446 struct peer *peer 447 ) 448 { 449 register struct heathunit *up; 450 struct refclockproc *pp; 451 int bits = TIOCM_RTS; 452 453 /* 454 * At each poll we check for timeout and toggle the RTS modem 455 * control line, then take a timestamp. Presumably, this is the 456 * event the radio captures to generate the timecode. 457 */ 458 pp = peer->procptr; 459 up = (struct heathunit *)pp->unitptr; 460 pp->polls++; 461 462 /* 463 * We toggle the RTS modem control lead to kick a timecode loose 464 * from the radio. This code works only for POSIX and SYSV 465 * interfaces. With bsd you are on your own. We take a timestamp 466 * between the up and down edges to lengthen the pulse, which 467 * should be about 50 usec on a Sun IPC. With hotshot CPUs, the 468 * pulse might get too short. Later. 469 */ 470 if (ioctl(pp->io.fd, TIOCMBIC, (char *)&bits) < 0) 471 refclock_report(peer, CEVNT_FAULT); 472 get_systime(&up->tstamp); 473 ioctl(pp->io.fd, TIOCMBIS, (char *)&bits); 474 if (peer->burst > 0) 475 return; 476 if (pp->coderecv == pp->codeproc) { 477 refclock_report(peer, CEVNT_TIMEOUT); 478 return; 479 } 480 record_clock_stats(&peer->srcadr, pp->a_lastcode); 481 refclock_receive(peer); 482 peer->burst = NSTAGE; 483 } 484 485 #else 486 int refclock_heath_bs; 487 #endif /* REFCLOCK */ 488