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