1/*
2 * refclock_dumbclock - clock driver for a unknown time distribution system
3 * that only provides hh:mm:ss (in local time, yet!).
4 */
5
6/*
7 * Must interpolate back to local time.  Very annoying.
8 */
9#define GET_LOCALTIME
10
11#ifdef HAVE_CONFIG_H
12#include <config.h>
13#endif
14
15#if defined(REFCLOCK) && defined(CLOCK_DUMBCLOCK)
16
17#include "ntpd.h"
18#include "ntp_io.h"
19#include "ntp_refclock.h"
20#include "ntp_calendar.h"
21#include "ntp_stdlib.h"
22
23#include <stdio.h>
24#include <ctype.h>
25
26/*
27 * This driver supports a generic dumb clock that only outputs hh:mm:ss,
28 * in local time, no less.
29 *
30 * Input format:
31 *
32 *	hh:mm:ss   <cr>
33 *
34 * hh:mm:ss -- what you'd expect, with a 24 hour clock.  (Heck, that's the only
35 * way it could get stupider.)  We take time on the <cr>.
36 *
37 * The original source of this module was the WWVB module.
38 */
39
40/*
41 * Interface definitions
42 */
43#define	DEVICE		"/dev/dumbclock%d" /* device name and unit */
44#define	SPEED232	B9600	/* uart speed (9600 baud) */
45#define	PRECISION	(-13)	/* precision assumed (about 100 us) */
46#define	REFID		"dumbclock"	/* reference ID */
47#define	DESCRIPTION	"Dumb clock" /* WRU */
48
49
50/*
51 * Insanity check.  Since the time is local, we need to make sure that during midnight
52 * transitions, we can convert back to Unix time.  If the conversion results in some number
53 * worse than this number of seconds away, assume the next day and retry.
54 */
55#define INSANE_SECONDS 3600
56
57/*
58 * Dumb clock control structure
59 */
60struct dumbclock_unit {
61	u_char	  tcswitch;	/* timecode switch */
62	l_fp	  laststamp;	/* last receive timestamp */
63	u_char	  lasthour;	/* last hour (for monitor) */
64	u_char	  linect;	/* count ignored lines (for monitor */
65	struct tm ymd;		/* struct tm for y/m/d only */
66};
67
68/*
69 * Function prototypes
70 */
71static	int	dumbclock_start		(int, struct peer *);
72static	void	dumbclock_shutdown	(int, struct peer *);
73static	void	dumbclock_receive	(struct recvbuf *);
74#if 0
75static	void	dumbclock_poll		(int, struct peer *);
76#endif
77
78/*
79 * Transfer vector
80 */
81struct	refclock refclock_dumbclock = {
82	dumbclock_start,		     /* start up driver */
83	dumbclock_shutdown,		     /* shut down driver */
84	noentry,			     /* poll the driver -- a nice fabrication */
85	noentry,			     /* not used */
86	noentry,			     /* not used */
87	noentry,			     /* not used */
88	NOFLAGS				     /* not used */
89};
90
91
92/*
93 * dumbclock_start - open the devices and initialize data for processing
94 */
95static int
96dumbclock_start(
97	int unit,
98	struct peer *peer
99	)
100{
101	register struct dumbclock_unit *up;
102	struct refclockproc *pp;
103	int fd;
104	char device[20];
105	struct tm *tm_time_p;
106	time_t     now;
107
108	/*
109	 * Open serial port. Don't bother with CLK line discipline, since
110	 * it's not available.
111	 */
112	snprintf(device, sizeof(device), DEVICE, unit);
113#ifdef DEBUG
114	if (debug)
115		printf ("starting Dumbclock with device %s\n",device);
116#endif
117	fd = refclock_open(&peer->srcadr, device, SPEED232, 0);
118	if (fd <= 0)
119		return (0);
120
121	/*
122	 * Allocate and initialize unit structure
123	 */
124	up = emalloc_zero(sizeof(*up));
125	pp = peer->procptr;
126	pp->unitptr = up;
127	pp->io.clock_recv = dumbclock_receive;
128	pp->io.srcclock = peer;
129	pp->io.datalen = 0;
130	pp->io.fd = fd;
131	if (!io_addclock(&pp->io)) {
132		close(fd);
133		pp->io.fd = -1;
134		free(up);
135		pp->unitptr = NULL;
136		return (0);
137	}
138
139
140	time(&now);
141#ifdef GET_LOCALTIME
142	tm_time_p = localtime(&now);
143#else
144	tm_time_p = gmtime(&now);
145#endif
146	if (tm_time_p)
147		up->ymd = *tm_time_p;
148	else
149		return 0;
150
151	/*
152	 * Initialize miscellaneous variables
153	 */
154	peer->precision = PRECISION;
155	pp->clockdesc = DESCRIPTION;
156	memcpy((char *)&pp->refid, REFID, 4);
157	return (1);
158}
159
160
161/*
162 * dumbclock_shutdown - shut down the clock
163 */
164static void
165dumbclock_shutdown(
166	int unit,
167	struct peer *peer
168	)
169{
170	register struct dumbclock_unit *up;
171	struct refclockproc *pp;
172
173	pp = peer->procptr;
174	up = pp->unitptr;
175	if (-1 != pp->io.fd)
176		io_closeclock(&pp->io);
177	if (NULL != up)
178		free(up);
179}
180
181
182/*
183 * dumbclock_receive - receive data from the serial interface
184 */
185static void
186dumbclock_receive(
187	struct recvbuf *rbufp
188	)
189{
190	struct dumbclock_unit *up;
191	struct refclockproc *pp;
192	struct peer *peer;
193
194	l_fp	trtmp;		/* arrival timestamp */
195	int	hours;		/* hour-of-day */
196	int	minutes;	/* minutes-past-the-hour */
197	int	seconds;	/* seconds */
198	int	temp;		/* int temp */
199	int	got_good;	/* got a good time flag */
200
201	/*
202	 * Initialize pointers and read the timecode and timestamp
203	 */
204	peer = rbufp->recv_peer;
205	pp = peer->procptr;
206	up = pp->unitptr;
207	temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
208
209	if (temp == 0) {
210		if (up->tcswitch == 0) {
211			up->tcswitch = 1;
212			up->laststamp = trtmp;
213		} else
214			up->tcswitch = 0;
215		return;
216	}
217	pp->lencode = (u_short)temp;
218	pp->lastrec = up->laststamp;
219	up->laststamp = trtmp;
220	up->tcswitch = 1;
221
222#ifdef DEBUG
223	if (debug)
224		printf("dumbclock: timecode %d %s\n",
225		       pp->lencode, pp->a_lastcode);
226#endif
227
228	/*
229	 * We get down to business. Check the timecode format...
230	 */
231	got_good=0;
232	if (sscanf(pp->a_lastcode,"%02d:%02d:%02d",
233		   &hours,&minutes,&seconds) == 3)
234	{
235	    struct tm *gmtp;
236	    struct tm *lt_p;
237	    time_t     asserted_time;	     /* the SPM time based on the composite time+date */
238	    struct tm  asserted_tm;	     /* the struct tm of the same */
239	    int        adjyear;
240	    int        adjmon;
241	    time_t     reality_delta;
242	    time_t     now;
243
244
245	    /*
246	     * Convert to GMT for sites that distribute localtime.  This
247	     * means we have to figure out what day it is.  Easier said
248	     * than done...
249	     */
250
251	    memset(&asserted_tm, 0, sizeof(asserted_tm));
252
253	    asserted_tm.tm_year  = up->ymd.tm_year;
254	    asserted_tm.tm_mon   = up->ymd.tm_mon;
255	    asserted_tm.tm_mday  = up->ymd.tm_mday;
256	    asserted_tm.tm_hour  = hours;
257	    asserted_tm.tm_min   = minutes;
258	    asserted_tm.tm_sec   = seconds;
259	    asserted_tm.tm_isdst = -1;
260
261#ifdef GET_LOCALTIME
262	    asserted_time = mktime (&asserted_tm);
263	    time(&now);
264#else
265#include "GMT unsupported for dumbclock!"
266#endif
267	    reality_delta = asserted_time - now;
268
269	    /*
270	     * We assume that if the time is grossly wrong, it's because we got the
271	     * year/month/day wrong.
272	     */
273	    if (reality_delta > INSANE_SECONDS)
274	    {
275		asserted_time -= SECSPERDAY; /* local clock behind real time */
276	    }
277	    else if (-reality_delta > INSANE_SECONDS)
278	    {
279		asserted_time += SECSPERDAY; /* local clock ahead of real time */
280	    }
281	    lt_p = localtime(&asserted_time);
282	    if (lt_p)
283	    {
284		up->ymd = *lt_p;
285	    }
286	    else
287	    {
288		refclock_report (peer, CEVNT_FAULT);
289		return;
290	    }
291
292	    if ((gmtp = gmtime (&asserted_time)) == NULL)
293	    {
294		refclock_report (peer, CEVNT_FAULT);
295		return;
296	    }
297	    adjyear = gmtp->tm_year+1900;
298	    adjmon  = gmtp->tm_mon+1;
299	    pp->day = ymd2yd (adjyear, adjmon, gmtp->tm_mday);
300	    pp->hour   = gmtp->tm_hour;
301	    pp->minute = gmtp->tm_min;
302	    pp->second = gmtp->tm_sec;
303#ifdef DEBUG
304	    if (debug)
305		printf ("time is %04d/%02d/%02d %02d:%02d:%02d UTC\n",
306			adjyear,adjmon,gmtp->tm_mday,pp->hour,pp->minute,
307			pp->second);
308#endif
309
310	    got_good=1;
311	}
312
313	if (!got_good)
314	{
315	    if (up->linect > 0)
316	    	up->linect--;
317	    else
318	    	refclock_report(peer, CEVNT_BADREPLY);
319	    return;
320	}
321
322	/*
323	 * Process the new sample in the median filter and determine the
324	 * timecode timestamp.
325	 */
326	if (!refclock_process(pp)) {
327		refclock_report(peer, CEVNT_BADTIME);
328		return;
329	}
330	pp->lastref = pp->lastrec;
331	refclock_receive(peer);
332	record_clock_stats(&peer->srcadr, pp->a_lastcode);
333	up->lasthour = (u_char)pp->hour;
334}
335
336#if 0
337/*
338 * dumbclock_poll - called by the transmit procedure
339 */
340static void
341dumbclock_poll(
342	int unit,
343	struct peer *peer
344	)
345{
346	register struct dumbclock_unit *up;
347	struct refclockproc *pp;
348	char pollchar;
349
350	/*
351	 * Time to poll the clock. The Chrono-log clock is supposed to
352	 * respond to a 'T' by returning a timecode in the format(s)
353	 * specified above.  Ours does (can?) not, but this seems to be
354	 * an installation-specific problem.  This code is dyked out,
355	 * but may be re-enabled if anyone ever finds a Chrono-log that
356	 * actually listens to this command.
357	 */
358#if 0
359	pp = peer->procptr;
360	up = pp->unitptr;
361	if (peer->reach == 0)
362		refclock_report(peer, CEVNT_TIMEOUT);
363	if (up->linect > 0)
364		pollchar = 'R';
365	else
366		pollchar = 'T';
367	if (refclock_fdwrite(peer, pp->io.fd, &pollchar, 1) != 1)
368		refclock_report(peer, CEVNT_FAULT);
369	else
370		pp->polls++;
371#endif
372}
373#endif
374
375#else
376NONEMPTY_TRANSLATION_UNIT
377#endif	/* defined(REFCLOCK) && defined(CLOCK_DUMBCLOCK) */
378