summaryrefslogtreecommitdiff
path: root/ntpd/refclock_chronolog.c
blob: ee54b43a612639c1afa94e81eb0250af895d8401 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
/*
 * refclock_chronolog - clock driver for Chronolog K-series WWVB receiver.
 */

/*
 * Must interpolate back to local time.  Very annoying.
 */
#define GET_LOCALTIME

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#if defined(REFCLOCK) && defined(CLOCK_CHRONOLOG)

#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_refclock.h"
#include "ntp_calendar.h"
#include "ntp_stdlib.h"

#include <stdio.h>
#include <ctype.h>

/*
 * This driver supports the Chronolog K-series WWVB receiver.
 *
 * Input format:
 *
 *	Y YY/MM/DD<cr><lf>
 *      Z hh:mm:ss<cr><lf>
 *
 * YY/MM/DD -- what you'd expect.  This arrives a few seconds before the
 * timestamp.
 * hh:mm:ss -- what you'd expect.  We take time on the <cr>.
 *
 * Our Chronolog writes time out at 2400 bps 8/N/1, but it can be configured
 * otherwise.  The clock seems to appear every 60 seconds, which doesn't make
 * for good statistics collection.
 *
 * The original source of this module was the WWVB module.
 */

/*
 * Interface definitions
 */
#define	DEVICE		"/dev/chronolog%d" /* device name and unit */
#define	SPEED232	B2400	/* uart speed (2400 baud) */
#define	PRECISION	(-13)	/* precision assumed (about 100 us) */
#define	REFID		"chronolog"	/* reference ID */
#define	DESCRIPTION	"Chrono-log K" /* WRU */

#define MONLIN		15	/* number of monitoring lines */

/*
 * Chrono-log unit control structure
 */
struct chronolog_unit {
	u_char	tcswitch;	/* timecode switch */
	l_fp	laststamp;	/* last receive timestamp */
	u_char	lasthour;	/* last hour (for monitor) */
	int   	year;	        /* Y2K-adjusted year */
	int   	day;	        /* day-of-month */
        int   	month;	        /* month-of-year */
};

/*
 * Function prototypes
 */
static	int	chronolog_start		(int, struct peer *);
static	void	chronolog_shutdown	(int, struct peer *);
static	void	chronolog_receive	(struct recvbuf *);
static	void	chronolog_poll		(int, struct peer *);

/*
 * Transfer vector
 */
struct	refclock refclock_chronolog = {
	chronolog_start,	/* start up driver */
	chronolog_shutdown,	/* shut down driver */
	chronolog_poll,		/* poll the driver -- a nice fabrication */
	noentry,		/* not used */
	noentry,		/* not used */
	noentry,		/* not used */
	NOFLAGS			/* not used */
};


/*
 * chronolog_start - open the devices and initialize data for processing
 */
static int
chronolog_start(
	int unit,
	struct peer *peer
	)
{
	register struct chronolog_unit *up;
	struct refclockproc *pp;
	int fd;
	char device[20];

	/*
	 * Open serial port. Don't bother with CLK line discipline, since
	 * it's not available.
	 */
	snprintf(device, sizeof(device), DEVICE, unit);
#ifdef DEBUG
	if (debug)
		printf ("starting Chronolog with device %s\n",device);
#endif
	fd = refclock_open(device, SPEED232, 0);
	if (fd <= 0)
		return (0);

	/*
	 * Allocate and initialize unit structure
	 */
	up = emalloc_zero(sizeof(*up));
	pp = peer->procptr;
	pp->unitptr = up;
	pp->io.clock_recv = chronolog_receive;
	pp->io.srcclock = peer;
	pp->io.datalen = 0;
	pp->io.fd = fd;
	if (!io_addclock(&pp->io)) {
		close(fd);
		pp->io.fd = -1;
		free(up);
		pp->unitptr = NULL;
		return (0);
	}

	/*
	 * Initialize miscellaneous variables
	 */
	peer->precision = PRECISION;
	pp->clockdesc = DESCRIPTION;
	memcpy((char *)&pp->refid, REFID, 4);
	return (1);
}


/*
 * chronolog_shutdown - shut down the clock
 */
static void
chronolog_shutdown(
	int unit,
	struct peer *peer
	)
{
	register struct chronolog_unit *up;
	struct refclockproc *pp;

	pp = peer->procptr;
	up = pp->unitptr;
	if (-1 != pp->io.fd)
		io_closeclock(&pp->io);
	if (NULL != up)
		free(up);
}


/*
 * chronolog_receive - receive data from the serial interface
 */
static void
chronolog_receive(
	struct recvbuf *rbufp
	)
{
	struct chronolog_unit *up;
	struct refclockproc *pp;
	struct peer *peer;

	l_fp	     trtmp;	/* arrival timestamp */
	int          hours;	/* hour-of-day */
	int	     minutes;	/* minutes-past-the-hour */
	int          seconds;	/* seconds */
	int	     temp;	/* int temp */
	int          got_good;	/* got a good time flag */

	/*
	 * Initialize pointers and read the timecode and timestamp
	 */
	peer = rbufp->recv_peer;
	pp = peer->procptr;
	up = pp->unitptr;
	temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);

	if (temp == 0) {
		if (up->tcswitch == 0) {
			up->tcswitch = 1;
			up->laststamp = trtmp;
		} else
		    up->tcswitch = 0;
		return;
	}
	pp->lencode = temp;
	pp->lastrec = up->laststamp;
	up->laststamp = trtmp;
	up->tcswitch = 1;

#ifdef DEBUG
	if (debug)
		printf("chronolog: timecode %d %s\n", pp->lencode,
		    pp->a_lastcode);
#endif

	/*
	 * We get down to business. Check the timecode format and decode
	 * its contents. This code uses the first character to see whether
	 * we're looking at a date or a time.  We store data data across
	 * calls since it is transmitted a few seconds ahead of the
	 * timestamp.
	 */
	got_good=0;
	if (sscanf(pp->a_lastcode, "Y %d/%d/%d", &up->year,&up->month,&up->day))
	{
	    /*
	     * Y2K convert the 2-digit year
	     */
	    up->year = up->year >= 69 ? up->year : up->year + 100;
	    return;
	}
	if (sscanf(pp->a_lastcode,"Z %02d:%02d:%02d",
		   &hours,&minutes,&seconds) == 3)
	{
#ifdef GET_LOCALTIME
	    struct tm  local;
	    struct tm *gmtp;
	    time_t     unixtime;
	    int        adjyear;
	    int        adjmon;

	    /*
	     * Convert to GMT for sites that distribute localtime.  This
             * means we have to do Y2K conversion on the 2-digit year;
	     * otherwise, we get the time wrong.
	     */
	    
	    memset(&local, 0, sizeof(local));

	    local.tm_year  = up->year;
	    local.tm_mon   = up->month-1;
	    local.tm_mday  = up->day;
	    local.tm_hour  = hours;
	    local.tm_min   = minutes;
	    local.tm_sec   = seconds;
	    local.tm_isdst = -1;

	    unixtime = mktime (&local);
	    if ((gmtp = gmtime (&unixtime)) == NULL)
	    {
		refclock_report (peer, CEVNT_FAULT);
		return;
	    }
	    adjyear = gmtp->tm_year+1900;
	    adjmon  = gmtp->tm_mon+1;
	    pp->day = ymd2yd (adjyear, adjmon, gmtp->tm_mday);
	    pp->hour   = gmtp->tm_hour;
	    pp->minute = gmtp->tm_min;
	    pp->second = gmtp->tm_sec;
#ifdef DEBUG
	    if (debug)
		printf ("time is %04d/%02d/%02d %02d:%02d:%02d UTC\n",
			adjyear,adjmon,gmtp->tm_mday,pp->hour,pp->minute,
			pp->second);
#endif
	    
#else
	    /*
	     * For more rational sites distributing UTC
	     */
	    pp->day    = ymd2yd(year+1900,month,day);
	    pp->hour   = hours;
	    pp->minute = minutes;
	    pp->second = seconds;

#endif
	    got_good=1;
	}

	if (!got_good)
	    return;


	/*
	 * Process the new sample in the median filter and determine the
	 * timecode timestamp.
	 */
	if (!refclock_process(pp)) {
		refclock_report(peer, CEVNT_BADTIME);
		return;
	}
	pp->lastref = pp->lastrec;
	refclock_receive(peer);
	record_clock_stats(&peer->srcadr, pp->a_lastcode);
	up->lasthour = (u_char)pp->hour;
}


/*
 * chronolog_poll - called by the transmit procedure
 */
static void
chronolog_poll(
	int unit,
	struct peer *peer
	)
{
	/*
	 * Time to poll the clock. The Chrono-log clock is supposed to
	 * respond to a 'T' by returning a timecode in the format(s)
	 * specified above.  Ours does (can?) not, but this seems to be
	 * an installation-specific problem.  This code is dyked out,
	 * but may be re-enabled if anyone ever finds a Chrono-log that
	 * actually listens to this command.
	 */
#if 0
	register struct chronolog_unit *up;
	struct refclockproc *pp;
	char pollchar;

	pp = peer->procptr;
	up = pp->unitptr;
	if (peer->burst == 0 && peer->reach == 0)
		refclock_report(peer, CEVNT_TIMEOUT);
	if (up->linect > 0)
		pollchar = 'R';
	else
		pollchar = 'T';
	if (write(pp->io.fd, &pollchar, 1) != 1)
		refclock_report(peer, CEVNT_FAULT);
	else
		pp->polls++;
#endif
}

#else
int refclock_chronolog_bs;
#endif /* REFCLOCK */