xref: /freebsd/contrib/ntp/ntpd/refclock_chronolog.c (revision 1b6c76a2fe091c74f08427e6c870851025a9cf67)
1 /*
2  * refclock_chronolog - clock driver for Chronolog K-series WWVB receiver.
3  */
4 
5 /*
6  * Must interpolate back to local time.  Very annoying.
7  */
8 #define GET_LOCALTIME
9 
10 #ifdef HAVE_CONFIG_H
11 #include <config.h>
12 #endif
13 
14 #if defined(REFCLOCK) && defined(CLOCK_CHRONOLOG)
15 
16 #include <stdio.h>
17 #include <ctype.h>
18 #include <sys/time.h>
19 #include <time.h>
20 
21 #include "ntpd.h"
22 #include "ntp_io.h"
23 #include "ntp_refclock.h"
24 #include "ntp_calendar.h"
25 #include "ntp_stdlib.h"
26 
27 /*
28  * This driver supports the Chronolog K-series WWVB receiver.
29  *
30  * Input format:
31  *
32  *	Y YY/MM/DD<cr><lf>
33  *      Z hh:mm:ss<cr><lf>
34  *
35  * YY/MM/DD -- what you'd expect.  This arrives a few seconds before the
36  * timestamp.
37  * hh:mm:ss -- what you'd expect.  We take time on the <cr>.
38  *
39  * Our Chronolog writes time out at 2400 bps 8/N/1, but it can be configured
40  * otherwise.  The clock seems to appear every 60 seconds, which doesn't make
41  * for good statistics collection.
42  *
43  * The original source of this module was the WWVB module.
44  */
45 
46 /*
47  * Interface definitions
48  */
49 #define	DEVICE		"/dev/chronolog%d" /* device name and unit */
50 #define	SPEED232	B2400	/* uart speed (2400 baud) */
51 #define	PRECISION	(-13)	/* precision assumed (about 100 us) */
52 #define	REFID		"chronolog"	/* reference ID */
53 #define	DESCRIPTION	"Chrono-log K" /* WRU */
54 
55 #define MONLIN		15	/* number of monitoring lines */
56 
57 /*
58  * Chrono-log unit control structure
59  */
60 struct chronolog_unit {
61 	u_char	tcswitch;	/* timecode switch */
62 	l_fp	laststamp;	/* last receive timestamp */
63 	u_char	lasthour;	/* last hour (for monitor) */
64 	int   	year;	        /* Y2K-adjusted year */
65 	int   	day;	        /* day-of-month */
66         int   	month;	        /* month-of-year */
67 };
68 
69 /*
70  * Function prototypes
71  */
72 static	int	chronolog_start		P((int, struct peer *));
73 static	void	chronolog_shutdown	P((int, struct peer *));
74 static	void	chronolog_receive	P((struct recvbuf *));
75 static	void	chronolog_poll		P((int, struct peer *));
76 
77 /*
78  * Transfer vector
79  */
80 struct	refclock refclock_chronolog = {
81 	chronolog_start,	/* start up driver */
82 	chronolog_shutdown,	/* shut down driver */
83 	chronolog_poll,		/* poll the driver -- a nice fabrication */
84 	noentry,		/* not used */
85 	noentry,		/* not used */
86 	noentry,		/* not used */
87 	NOFLAGS			/* not used */
88 };
89 
90 
91 /*
92  * chronolog_start - open the devices and initialize data for processing
93  */
94 static int
95 chronolog_start(
96 	int unit,
97 	struct peer *peer
98 	)
99 {
100 	register struct chronolog_unit *up;
101 	struct refclockproc *pp;
102 	int fd;
103 	char device[20];
104 
105 	/*
106 	 * Open serial port. Don't bother with CLK line discipline, since
107 	 * it's not available.
108 	 */
109 	(void)sprintf(device, DEVICE, unit);
110 #ifdef DEBUG
111 	if (debug)
112 		printf ("starting Chronolog with device %s\n",device);
113 #endif
114 	if (!(fd = refclock_open(device, SPEED232, 0)))
115 		return (0);
116 
117 	/*
118 	 * Allocate and initialize unit structure
119 	 */
120 	if (!(up = (struct chronolog_unit *)
121 	      emalloc(sizeof(struct chronolog_unit)))) {
122 		(void) close(fd);
123 		return (0);
124 	}
125 	memset((char *)up, 0, sizeof(struct chronolog_unit));
126 	pp = peer->procptr;
127 	pp->unitptr = (caddr_t)up;
128 	pp->io.clock_recv = chronolog_receive;
129 	pp->io.srcclock = (caddr_t)peer;
130 	pp->io.datalen = 0;
131 	pp->io.fd = fd;
132 	if (!io_addclock(&pp->io)) {
133 		(void) close(fd);
134 		free(up);
135 		return (0);
136 	}
137 
138 	/*
139 	 * Initialize miscellaneous variables
140 	 */
141 	peer->precision = PRECISION;
142 	pp->clockdesc = DESCRIPTION;
143 	memcpy((char *)&pp->refid, REFID, 4);
144 	return (1);
145 }
146 
147 
148 /*
149  * chronolog_shutdown - shut down the clock
150  */
151 static void
152 chronolog_shutdown(
153 	int unit,
154 	struct peer *peer
155 	)
156 {
157 	register struct chronolog_unit *up;
158 	struct refclockproc *pp;
159 
160 	pp = peer->procptr;
161 	up = (struct chronolog_unit *)pp->unitptr;
162 	io_closeclock(&pp->io);
163 	free(up);
164 }
165 
166 
167 /*
168  * chronolog_receive - receive data from the serial interface
169  */
170 static void
171 chronolog_receive(
172 	struct recvbuf *rbufp
173 	)
174 {
175 	struct chronolog_unit *up;
176 	struct refclockproc *pp;
177 	struct peer *peer;
178 
179 	l_fp	     trtmp;	/* arrival timestamp */
180 	int          hours;	/* hour-of-day */
181 	int	     minutes;	/* minutes-past-the-hour */
182 	int          seconds;	/* seconds */
183 	int	     temp;	/* int temp */
184 	int          got_good;	/* got a good time flag */
185 
186 	/*
187 	 * Initialize pointers and read the timecode and timestamp
188 	 */
189 	peer = (struct peer *)rbufp->recv_srcclock;
190 	pp = peer->procptr;
191 	up = (struct chronolog_unit *)pp->unitptr;
192 	temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
193 
194 	if (temp == 0) {
195 		if (up->tcswitch == 0) {
196 			up->tcswitch = 1;
197 			up->laststamp = trtmp;
198 		} else
199 		    up->tcswitch = 0;
200 		return;
201 	}
202 	pp->lencode = temp;
203 	pp->lastrec = up->laststamp;
204 	up->laststamp = trtmp;
205 	up->tcswitch = 1;
206 
207 #ifdef DEBUG
208 	if (debug)
209 		printf("chronolog: timecode %d %s\n", pp->lencode,
210 		    pp->a_lastcode);
211 #endif
212 
213 	/*
214 	 * We get down to business. Check the timecode format and decode
215 	 * its contents. This code uses the first character to see whether
216 	 * we're looking at a date or a time.  We store data data across
217 	 * calls since it is transmitted a few seconds ahead of the
218 	 * timestamp.
219 	 */
220 	pp->msec = 0;
221 	got_good=0;
222 	if (sscanf(pp->a_lastcode, "Y %d/%d/%d", &up->year,&up->month,&up->day))
223 	{
224 	    /*
225 	     * Y2K convert the 2-digit year
226 	     */
227 	    up->year = up->year >= 69 ? up->year : up->year + 100;
228 	    return;
229 	}
230 	if (sscanf(pp->a_lastcode,"Z %02d:%02d:%02d",
231 		   &hours,&minutes,&seconds) == 3)
232 	{
233 #ifdef GET_LOCALTIME
234 	    struct tm  local;
235 	    struct tm *gmtp;
236 	    time_t     unixtime;
237 	    int        adjyear;
238 	    int        adjmon;
239 
240 	    /*
241 	     * Convert to GMT for sites that distribute localtime.  This
242              * means we have to do Y2K conversion on the 2-digit year;
243 	     * otherwise, we get the time wrong.
244 	     */
245 
246 	    local.tm_year  = up->year;
247 	    local.tm_mon   = up->month-1;
248 	    local.tm_mday  = up->day;
249 	    local.tm_hour  = hours;
250 	    local.tm_min   = minutes;
251 	    local.tm_sec   = seconds;
252 	    local.tm_isdst = -1;
253 
254 	    unixtime = mktime (&local);
255 	    if ((gmtp = gmtime (&unixtime)) == NULL)
256 	    {
257 		refclock_report (peer, CEVNT_FAULT);
258 		return;
259 	    }
260 	    adjyear = gmtp->tm_year+1900;
261 	    adjmon  = gmtp->tm_mon+1;
262 	    pp->day = ymd2yd (adjyear, adjmon, gmtp->tm_mday);
263 	    pp->hour   = gmtp->tm_hour;
264 	    pp->minute = gmtp->tm_min;
265 	    pp->second = gmtp->tm_sec;
266 #ifdef DEBUG
267 	    if (debug)
268 		printf ("time is %04d/%02d/%02d %02d:%02d:%02d UTC\n",
269 			adjyear,adjmon,gmtp->tm_mday,pp->hour,pp->minute,
270 			pp->second);
271 #endif
272 
273 #else
274 	    /*
275 	     * For more rational sites distributing UTC
276 	     */
277 	    pp->day    = ymd2yd(year+1900,month,day);
278 	    pp->hour   = hours;
279 	    pp->minute = minutes;
280 	    pp->second = seconds;
281 
282 #endif
283 	    got_good=1;
284 	}
285 
286 	if (!got_good)
287 	    return;
288 
289 
290 	/*
291 	 * Process the new sample in the median filter and determine the
292 	 * timecode timestamp.
293 	 */
294 	if (!refclock_process(pp)) {
295 		refclock_report(peer, CEVNT_BADTIME);
296 		return;
297 	}
298 	record_clock_stats(&peer->srcadr, pp->a_lastcode);
299 	refclock_receive(peer);
300 	up->lasthour = pp->hour;
301 }
302 
303 
304 /*
305  * chronolog_poll - called by the transmit procedure
306  */
307 static void
308 chronolog_poll(
309 	int unit,
310 	struct peer *peer
311 	)
312 {
313 	/*
314 	 * Time to poll the clock. The Chrono-log clock is supposed to
315 	 * respond to a 'T' by returning a timecode in the format(s)
316 	 * specified above.  Ours does (can?) not, but this seems to be
317 	 * an installation-specific problem.  This code is dyked out,
318 	 * but may be re-enabled if anyone ever finds a Chrono-log that
319 	 * actually listens to this command.
320 	 */
321 #if 0
322 	register struct chronolog_unit *up;
323 	struct refclockproc *pp;
324 	char pollchar;
325 
326 	pp = peer->procptr;
327 	up = (struct chronolog_unit *)pp->unitptr;
328 	if (peer->burst == 0 && peer->reach == 0)
329 		refclock_report(peer, CEVNT_TIMEOUT);
330 	if (up->linect > 0)
331 		pollchar = 'R';
332 	else
333 		pollchar = 'T';
334 	if (write(pp->io.fd, &pollchar, 1) != 1)
335 		refclock_report(peer, CEVNT_FAULT);
336 	else
337 		pp->polls++;
338 #endif
339 }
340 
341 #else
342 int refclock_chronolog_bs;
343 #endif /* REFCLOCK */
344