Annotation of embedaddon/ntp/ntpd/refclock_heath.c, revision 1.1
1.1 ! misho 1: /*
! 2: * refclock_heath - clock driver for Heath GC-1000
! 3: * (but no longer the GC-1001 Model II, which apparently never worked)
! 4: */
! 5:
! 6: #ifdef HAVE_CONFIG_H
! 7: # include <config.h>
! 8: #endif
! 9:
! 10: #if defined(REFCLOCK) && defined(CLOCK_HEATH)
! 11:
! 12: #include "ntpd.h"
! 13: #include "ntp_io.h"
! 14: #include "ntp_refclock.h"
! 15: #include "ntp_stdlib.h"
! 16:
! 17: #include <stdio.h>
! 18: #include <ctype.h>
! 19:
! 20: #ifdef HAVE_SYS_IOCTL_H
! 21: # include <sys/ioctl.h>
! 22: #endif /* not HAVE_SYS_IOCTL_H */
! 23:
! 24: /*
! 25: * This driver supports the Heath GC-1000 Most Accurate Clock, with
! 26: * RS232C Output Accessory. This is a WWV/WWVH receiver somewhat less
! 27: * robust than other supported receivers. Its claimed accuracy is 100 ms
! 28: * when actually synchronized to the broadcast signal, but this doesn't
! 29: * happen even most of the time, due to propagation conditions, ambient
! 30: * noise sources, etc. When not synchronized, the accuracy is at the
! 31: * whim of the internal clock oscillator, which can wander into the
! 32: * sunset without warning. Since the indicated precision is 100 ms,
! 33: * expect a host synchronized only to this thing to wander to and fro,
! 34: * occasionally being rudely stepped when the offset exceeds the default
! 35: * clock_max of 128 ms.
! 36: *
! 37: * There were two GC-1000 versions supported by this driver. The original
! 38: * GC-1000 with RS-232 output first appeared in 1983, but dissapeared
! 39: * from the market a few years later. The GC-1001 II with RS-232 output
! 40: * first appeared circa 1990, but apparently is no longer manufactured.
! 41: * The two models differ considerably, both in interface and commands.
! 42: * The GC-1000 has a pseudo-bipolar timecode output triggered by a RTS
! 43: * transition. The timecode includes both the day of year and time of
! 44: * day. The GC-1001 II has a true bipolar output and a complement of
! 45: * single character commands. The timecode includes only the time of
! 46: * day.
! 47: *
! 48: * The GC-1001 II was apparently never tested and, based on a Coverity
! 49: * scan, apparently never worked [Bug 689]. Related code has been disabled.
! 50: *
! 51: * GC-1000
! 52: *
! 53: * The internal DIPswitches should be set to operate in MANUAL mode. The
! 54: * external DIPswitches should be set to GMT and 24-hour format.
! 55: *
! 56: * In MANUAL mode the clock responds to a rising edge of the request to
! 57: * send (RTS) modem control line by sending the timecode. Therefore, it
! 58: * is necessary that the operating system implement the TIOCMBIC and
! 59: * TIOCMBIS ioctl system calls and TIOCM_RTS control bit. Present
! 60: * restrictions require the use of a POSIX-compatible programming
! 61: * interface, although other interfaces may work as well.
! 62: *
! 63: * A simple hardware modification to the clock can be made which
! 64: * prevents the clock hearing the request to send (RTS) if the HI SPEC
! 65: * lamp is out. Route the HISPEC signal to the tone decoder board pin
! 66: * 19, from the display, pin 19. Isolate pin 19 of the decoder board
! 67: * first, but maintain connection with pin 10. Also isolate pin 38 of
! 68: * the CPU on the tone board, and use half an added 7400 to gate the
! 69: * original signal to pin 38 with that from pin 19.
! 70: *
! 71: * The clock message consists of 23 ASCII printing characters in the
! 72: * following format:
! 73: *
! 74: * hh:mm:ss.f AM dd/mm/yr<cr>
! 75: *
! 76: * hh:mm:ss.f = hours, minutes, seconds
! 77: * f = deciseconds ('?' when out of spec)
! 78: * AM/PM/bb = blank in 24-hour mode
! 79: * dd/mm/yr = day, month, year
! 80: *
! 81: * The alarm condition is indicated by '?', rather than a digit, at f.
! 82: * Note that 0?:??:??.? is displayed before synchronization is first
! 83: * established and hh:mm:ss.? once synchronization is established and
! 84: * then lost again for about a day.
! 85: *
! 86: * GC-1001 II
! 87: *
! 88: * Commands consist of a single letter and are case sensitive. When
! 89: * enterred in lower case, a description of the action performed is
! 90: * displayed. When enterred in upper case the action is performed.
! 91: * Following is a summary of descriptions as displayed by the clock:
! 92: *
! 93: * The clock responds with a command The 'A' command returns an ASCII
! 94: * local time string: HH:MM:SS.T xx<CR>, where
! 95: *
! 96: * HH = hours
! 97: * MM = minutes
! 98: * SS = seconds
! 99: * T = tenths-of-seconds
! 100: * xx = 'AM', 'PM', or ' '
! 101: * <CR> = carriage return
! 102: *
! 103: * The 'D' command returns 24 pairs of bytes containing the variable
! 104: * divisor value at the end of each of the previous 24 hours. This
! 105: * allows the timebase trimming process to be observed. UTC hour 00 is
! 106: * always returned first. The first byte of each pair is the high byte
! 107: * of (variable divisor * 16); the second byte is the low byte of
! 108: * (variable divisor * 16). For example, the byte pair 3C 10 would be
! 109: * returned for a divisor of 03C1 hex (961 decimal).
! 110: *
! 111: * The 'I' command returns: | TH | TL | ER | DH | DL | U1 | I1 | I2 | ,
! 112: * where
! 113: *
! 114: * TH = minutes since timebase last trimmed (high byte)
! 115: * TL = minutes since timebase last trimmed (low byte)
! 116: * ER = last accumulated error in 1.25 ms increments
! 117: * DH = high byte of (current variable divisor * 16)
! 118: * DL = low byte of (current variable divisor * 16)
! 119: * U1 = UT1 offset (/.1 s): | + | 4 | 2 | 1 | 0 | 0 | 0 | 0 |
! 120: * I1 = information byte 1: | W | C | D | I | U | T | Z | 1 | ,
! 121: * where
! 122: *
! 123: * W = set by WWV(H)
! 124: * C = CAPTURE LED on
! 125: * D = TRIM DN LED on
! 126: * I = HI SPEC LED on
! 127: * U = TRIM UP LED on
! 128: * T = DST switch on
! 129: * Z = UTC switch on
! 130: * 1 = UT1 switch on
! 131: *
! 132: * I2 = information byte 2: | 8 | 8 | 4 | 2 | 1 | D | d | S | ,
! 133: * where
! 134: *
! 135: * 8, 8, 4, 2, 1 = TIME ZONE switch settings
! 136: * D = DST bit (#55) in last-received frame
! 137: * d = DST bit (#2) in last-received frame
! 138: * S = clock is in simulation mode
! 139: *
! 140: * The 'P' command returns 24 bytes containing the number of frames
! 141: * received without error during UTC hours 00 through 23, providing an
! 142: * indication of hourly propagation. These bytes are updated each hour
! 143: * to reflect the previous 24 hour period. UTC hour 00 is always
! 144: * returned first.
! 145: *
! 146: * The 'T' command returns the UTC time: | HH | MM | SS | T0 | , where
! 147: * HH = tens-of-hours and hours (packed BCD)
! 148: * MM = tens-of-minutes and minutes (packed BCD)
! 149: * SS = tens-of-seconds and seconds (packed BCD)
! 150: * T = tenths-of-seconds (BCD)
! 151: *
! 152: * Fudge Factors
! 153: *
! 154: * A fudge time1 value of .04 s appears to center the clock offset
! 155: * residuals. The fudge time2 parameter is the local time offset east of
! 156: * Greenwich, which depends on DST. Sorry about that, but the clock
! 157: * gives no hint on what the DIPswitches say.
! 158: */
! 159:
! 160: /*
! 161: * Interface definitions
! 162: */
! 163: #define DEVICE "/dev/heath%d" /* device name and unit */
! 164: #define PRECISION (-4) /* precision assumed (about 100 ms) */
! 165: #define REFID "WWV\0" /* reference ID */
! 166: #define DESCRIPTION "Heath GC-1000 Most Accurate Clock" /* WRU */
! 167:
! 168: #define LENHEATH1 23 /* min timecode length */
! 169: #if 0 /* BUG 689 */
! 170: #define LENHEATH2 13 /* min timecode length */
! 171: #endif
! 172:
! 173: /*
! 174: * Tables to compute the ddd of year form icky dd/mm timecode. Viva la
! 175: * leap.
! 176: */
! 177: static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
! 178: static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
! 179:
! 180: /*
! 181: * Baud rate table. The GC-1000 supports 1200, 2400 and 4800; the
! 182: * GC-1001 II supports only 9600.
! 183: */
! 184: static int speed[] = {B1200, B2400, B4800, B9600};
! 185:
! 186: /*
! 187: * Function prototypes
! 188: */
! 189: static int heath_start (int, struct peer *);
! 190: static void heath_shutdown (int, struct peer *);
! 191: static void heath_receive (struct recvbuf *);
! 192: static void heath_poll (int, struct peer *);
! 193:
! 194: /*
! 195: * Transfer vector
! 196: */
! 197: struct refclock refclock_heath = {
! 198: heath_start, /* start up driver */
! 199: heath_shutdown, /* shut down driver */
! 200: heath_poll, /* transmit poll message */
! 201: noentry, /* not used (old heath_control) */
! 202: noentry, /* initialize driver */
! 203: noentry, /* not used (old heath_buginfo) */
! 204: NOFLAGS /* not used */
! 205: };
! 206:
! 207:
! 208: /*
! 209: * heath_start - open the devices and initialize data for processing
! 210: */
! 211: static int
! 212: heath_start(
! 213: int unit,
! 214: struct peer *peer
! 215: )
! 216: {
! 217: struct refclockproc *pp;
! 218: int fd;
! 219: char device[20];
! 220:
! 221: /*
! 222: * Open serial port
! 223: */
! 224: snprintf(device, sizeof(device), DEVICE, unit);
! 225: if (!(fd = refclock_open(device, speed[peer->ttl & 0x3],
! 226: LDISC_REMOTE)))
! 227: return (0);
! 228: pp = peer->procptr;
! 229: pp->io.clock_recv = heath_receive;
! 230: pp->io.srcclock = (caddr_t)peer;
! 231: pp->io.datalen = 0;
! 232: pp->io.fd = fd;
! 233: if (!io_addclock(&pp->io)) {
! 234: close(fd);
! 235: pp->io.fd = -1;
! 236: return (0);
! 237: }
! 238:
! 239: /*
! 240: * Initialize miscellaneous variables
! 241: */
! 242: peer->precision = PRECISION;
! 243: peer->burst = NSTAGE;
! 244: pp->clockdesc = DESCRIPTION;
! 245: memcpy(&pp->refid, REFID, 4);
! 246: return (1);
! 247: }
! 248:
! 249:
! 250: /*
! 251: * heath_shutdown - shut down the clock
! 252: */
! 253: static void
! 254: heath_shutdown(
! 255: int unit,
! 256: struct peer *peer
! 257: )
! 258: {
! 259: struct refclockproc *pp;
! 260:
! 261: pp = peer->procptr;
! 262: if (-1 != pp->io.fd)
! 263: io_closeclock(&pp->io);
! 264: }
! 265:
! 266:
! 267: /*
! 268: * heath_receive - receive data from the serial interface
! 269: */
! 270: static void
! 271: heath_receive(
! 272: struct recvbuf *rbufp
! 273: )
! 274: {
! 275: struct refclockproc *pp;
! 276: struct peer *peer;
! 277: l_fp trtmp;
! 278: int month, day;
! 279: int i;
! 280: char dsec, a[5];
! 281:
! 282: /*
! 283: * Initialize pointers and read the timecode and timestamp
! 284: */
! 285: peer = (struct peer *)rbufp->recv_srcclock;
! 286: pp = peer->procptr;
! 287: pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX,
! 288: &trtmp);
! 289:
! 290: /*
! 291: * We get down to business, check the timecode format and decode
! 292: * its contents. If the timecode has invalid length or is not in
! 293: * proper format, we declare bad format and exit.
! 294: */
! 295: switch (pp->lencode) {
! 296:
! 297: /*
! 298: * GC-1000 timecode format: "hh:mm:ss.f AM mm/dd/yy"
! 299: * GC-1001 II timecode format: "hh:mm:ss.f "
! 300: */
! 301: case LENHEATH1:
! 302: if (sscanf(pp->a_lastcode,
! 303: "%2d:%2d:%2d.%c%5c%2d/%2d/%2d", &pp->hour,
! 304: &pp->minute, &pp->second, &dsec, a, &month, &day,
! 305: &pp->year) != 8) {
! 306: refclock_report(peer, CEVNT_BADREPLY);
! 307: return;
! 308: }
! 309: break;
! 310:
! 311: #if 0 /* BUG 689 */
! 312: /*
! 313: * GC-1001 II timecode format: "hh:mm:ss.f "
! 314: */
! 315: case LENHEATH2:
! 316: if (sscanf(pp->a_lastcode, "%2d:%2d:%2d.%c", &pp->hour,
! 317: &pp->minute, &pp->second, &dsec) != 4) {
! 318: refclock_report(peer, CEVNT_BADREPLY);
! 319: return;
! 320: } else {
! 321: struct tm *tm_time_p;
! 322: time_t now;
! 323:
! 324: time(&now); /* we should grab 'now' earlier */
! 325: tm_time_p = gmtime(&now);
! 326: /*
! 327: * There is a window of time around midnight
! 328: * where this will Do The Wrong Thing.
! 329: */
! 330: if (tm_time_p) {
! 331: month = tm_time_p->tm_mon + 1;
! 332: day = tm_time_p->tm_mday;
! 333: } else {
! 334: refclock_report(peer, CEVNT_FAULT);
! 335: return;
! 336: }
! 337: }
! 338: break;
! 339: #endif
! 340:
! 341: default:
! 342: refclock_report(peer, CEVNT_BADREPLY);
! 343: return;
! 344: }
! 345:
! 346: /*
! 347: * We determine the day of the year from the DIPswitches. This
! 348: * should be fixed, since somebody might forget to set them.
! 349: * Someday this hazard will be fixed by a fiendish scheme that
! 350: * looks at the timecode and year the radio shows, then computes
! 351: * the residue of the seconds mod the seconds in a leap cycle.
! 352: * If in the third year of that cycle and the third and later
! 353: * months of that year, add one to the day. Then, correct the
! 354: * timecode accordingly. Icky pooh. This bit of nonsense could
! 355: * be avoided if the engineers had been required to write a
! 356: * device driver before finalizing the timecode format.
! 357: */
! 358: if (month < 1 || month > 12 || day < 1) {
! 359: refclock_report(peer, CEVNT_BADTIME);
! 360: return;
! 361: }
! 362: if (pp->year % 4) {
! 363: if (day > day1tab[month - 1]) {
! 364: refclock_report(peer, CEVNT_BADTIME);
! 365: return;
! 366: }
! 367: for (i = 0; i < month - 1; i++)
! 368: day += day1tab[i];
! 369: } else {
! 370: if (day > day2tab[month - 1]) {
! 371: refclock_report(peer, CEVNT_BADTIME);
! 372: return;
! 373: }
! 374: for (i = 0; i < month - 1; i++)
! 375: day += day2tab[i];
! 376: }
! 377: pp->day = day;
! 378:
! 379: /*
! 380: * Determine synchronization and last update
! 381: */
! 382: if (!isdigit((int)dsec))
! 383: pp->leap = LEAP_NOTINSYNC;
! 384: else {
! 385: pp->nsec = (dsec - '0') * 100000000;
! 386: pp->leap = LEAP_NOWARNING;
! 387: }
! 388: if (!refclock_process(pp))
! 389: refclock_report(peer, CEVNT_BADTIME);
! 390: }
! 391:
! 392:
! 393: /*
! 394: * heath_poll - called by the transmit procedure
! 395: */
! 396: static void
! 397: heath_poll(
! 398: int unit,
! 399: struct peer *peer
! 400: )
! 401: {
! 402: struct refclockproc *pp;
! 403: int bits = TIOCM_RTS;
! 404:
! 405: /*
! 406: * At each poll we check for timeout and toggle the RTS modem
! 407: * control line, then take a timestamp. Presumably, this is the
! 408: * event the radio captures to generate the timecode.
! 409: * Apparently, the radio takes about a second to make up its
! 410: * mind to send a timecode, so the receive timestamp is
! 411: * worthless.
! 412: */
! 413: pp = peer->procptr;
! 414:
! 415: /*
! 416: * We toggle the RTS modem control lead (GC-1000) and sent a T
! 417: * (GC-1001 II) to kick a timecode loose from the radio. This
! 418: * code works only for POSIX and SYSV interfaces. With bsd you
! 419: * are on your own. We take a timestamp between the up and down
! 420: * edges to lengthen the pulse, which should be about 50 usec on
! 421: * a Sun IPC. With hotshot CPUs, the pulse might get too short.
! 422: * Later.
! 423: *
! 424: * Bug 689: Even though we no longer support the GC-1001 II,
! 425: * I'm leaving the 'T' write in for timing purposes.
! 426: */
! 427: if (ioctl(pp->io.fd, TIOCMBIC, (char *)&bits) < 0)
! 428: refclock_report(peer, CEVNT_FAULT);
! 429: get_systime(&pp->lastrec);
! 430: if (write(pp->io.fd, "T", 1) != 1)
! 431: refclock_report(peer, CEVNT_FAULT);
! 432: ioctl(pp->io.fd, TIOCMBIS, (char *)&bits);
! 433: if (peer->burst > 0)
! 434: return;
! 435: if (pp->coderecv == pp->codeproc) {
! 436: refclock_report(peer, CEVNT_TIMEOUT);
! 437: return;
! 438: }
! 439: pp->lastref = pp->lastrec;
! 440: refclock_receive(peer);
! 441: record_clock_stats(&peer->srcadr, pp->a_lastcode);
! 442: #ifdef DEBUG
! 443: if (debug)
! 444: printf("heath: timecode %d %s\n", pp->lencode,
! 445: pp->a_lastcode);
! 446: #endif
! 447: peer->burst = MAXSTAGE;
! 448: pp->polls++;
! 449: }
! 450:
! 451: #else
! 452: int refclock_heath_bs;
! 453: #endif /* REFCLOCK */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>