2 * refclock_heath - clock driver for Heath GC-1000 Most Accurate Clock
8 #if defined(REFCLOCK) && defined(CLOCK_HEATH)
12 #ifdef TIME_WITH_SYS_TIME
13 # include <sys/time.h>
16 # ifdef TM_IN_SYS_TIME
17 # include <sys/time.h>
22 #ifdef HAVE_SYS_IOCTL_H
23 # include <sys/ioctl.h>
24 #endif /* not HAVE_SYS_IOCTL_H */
28 #include "ntp_refclock.h"
29 #include "ntp_stdlib.h"
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.
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
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.
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.
69 * The clock message consists of 23 ASCII printing characters in the
72 * hh:mm:ss.f AM dd/mm/yr<cr>
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
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.
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.
93 * Interface definitions
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 */
101 #define LENHEATH 23 /* min timecode length */
104 * Tables to compute the ddd of year form icky dd/mm timecode. Viva la
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};
111 * Unit control structure
114 int pollcnt; /* poll message counter */
115 l_fp tstamp; /* timestamp of last poll */
119 * Function prototypes
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 *));
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 */
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.
145 #define timelocal(x) invert(x, localtime)
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
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;
185 long upper=(long)((unsigned long)(~lower) >> 1);
188 trial = (upper + lower) / 2L;
190 result = comparetm(x, y);
191 if (result < 0) upper = trial;
192 if (result > 0) lower = trial;
193 } while (result != 0);
200 * heath_start - open the devices and initialize data for processing
208 register struct heathunit *up;
209 struct refclockproc *pp;
216 (void)sprintf(device, DEVICE, unit);
217 if (!(fd = refclock_open(device, SPEED232, 0)))
221 * Allocate and initialize unit structure
223 if (!(up = (struct heathunit *)
224 emalloc(sizeof(struct heathunit)))) {
228 memset((char *)up, 0, sizeof(struct heathunit));
230 pp->io.clock_recv = heath_receive;
231 pp->io.srcclock = (caddr_t)peer;
234 if (!io_addclock(&pp->io)) {
239 pp->unitptr = (caddr_t)up;
242 * Initialize miscellaneous variables
244 peer->precision = PRECISION;
245 peer->burst = NSTAGE;
246 pp->clockdesc = DESCRIPTION;
247 memcpy((char *)&pp->refid, REFID, 4);
254 * heath_shutdown - shut down the clock
262 register struct heathunit *up;
263 struct refclockproc *pp;
266 up = (struct heathunit *)pp->unitptr;
267 io_closeclock(&pp->io);
273 * heath_receive - receive data from the serial interface
277 struct recvbuf *rbufp
280 register struct heathunit *up;
281 struct refclockproc *pp;
289 * Initialize pointers and read the timecode and timestamp
291 peer = (struct peer *)rbufp->recv_srcclock;
293 up = (struct heathunit *)pp->unitptr;
294 pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
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
304 pp->lastrec = up->tstamp;
308 printf("heath: timecode %d %s\n", pp->lencode,
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.
317 if (pp->lencode < LENHEATH) {
318 refclock_report(peer, CEVNT_BADREPLY);
323 * Timecode format: "hh:mm:ss.f AM mm/dd/yy"
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,
328 refclock_report(peer, CEVNT_BADREPLY);
333 * If AM or PM is received, assume the clock is displaying local
334 * time. First, convert to 24-hour format.
350 * Now make a struct tm out of it, convert to UTC, and
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 */
366 t.tm_wday = -1; /* who knows? */
367 t.tm_yday = -1; /* who knows? */
368 t.tm_isdst = -1; /* who knows? */
372 /* HMS: do we want to do this? */
373 refclock_report(peer, CEVNT_BADTIME);
378 pp->year = q->tm_year;
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 */
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.
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
405 if (month < 1 || month > 12 || day < 1) {
406 refclock_report(peer, CEVNT_BADTIME);
410 if (day > day1tab[month - 1]) {
411 refclock_report(peer, CEVNT_BADTIME);
414 for (i = 0; i < month - 1; i++)
417 if (day > day2tab[month - 1]) {
418 refclock_report(peer, CEVNT_BADTIME);
421 for (i = 0; i < month - 1; i++)
427 * Determine synchronization and last update
429 if (!isdigit((int)dsec))
430 pp->leap = LEAP_NOTINSYNC;
432 pp->msec = (dsec - '0') * 100;
433 pp->leap = LEAP_NOWARNING;
435 if (!refclock_process(pp))
436 refclock_report(peer, CEVNT_BADTIME);
441 * heath_poll - called by the transmit procedure
449 register struct heathunit *up;
450 struct refclockproc *pp;
451 int bits = TIOCM_RTS;
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.
459 up = (struct heathunit *)pp->unitptr;
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.
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);
476 if (pp->coderecv == pp->codeproc) {
477 refclock_report(peer, CEVNT_TIMEOUT);
480 record_clock_stats(&peer->srcadr, pp->a_lastcode);
481 refclock_receive(peer);
482 peer->burst = NSTAGE;
486 int refclock_heath_bs;
487 #endif /* REFCLOCK */