xref: /freebsd/contrib/ntp/ntpd/refclock_heath.c (revision 5129159789cc9d7bc514e4546b88e3427695002d)
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