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
27 extern 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 */
66 struct 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 */
77 static int dumbclock_start P((int, struct peer *));
78 static void dumbclock_shutdown P((int, struct peer *));
79 static void dumbclock_receive P((struct recvbuf *));
80 #if 0
81 static void dumbclock_poll P((int, struct peer *));
82 #endif
83
84 /*
85 * Transfer vector
86 */
87 struct 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 */
101 static int
dumbclock_start(int unit,struct peer * peer)102 dumbclock_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 */
177 static void
dumbclock_shutdown(int unit,struct peer * peer)178 dumbclock_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 */
196 static void
dumbclock_receive(struct recvbuf * rbufp)197 dumbclock_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 */
349 static void
350 dumbclock_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
385 int refclock_dumbclock_bs;
386 #endif /* REFCLOCK */
387