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 (int, struct peer *);
78 static void dumbclock_shutdown (int, struct peer *);
79 static void dumbclock_receive (struct recvbuf *);
80 #if 0
81 static void dumbclock_poll (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 snprintf(device, sizeof(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 = emalloc_zero(sizeof(*up));
131 pp = peer->procptr;
132 pp->unitptr = up;
133 pp->io.clock_recv = dumbclock_receive;
134 pp->io.srcclock = peer;
135 pp->io.datalen = 0;
136 pp->io.fd = fd;
137 if (!io_addclock(&pp->io)) {
138 close(fd);
139 pp->io.fd = -1;
140 free(up);
141 pp->unitptr = NULL;
142 return (0);
143 }
144
145
146 time(&now);
147 #ifdef GET_LOCALTIME
148 tm_time_p = localtime(&now);
149 #else
150 tm_time_p = gmtime(&now);
151 #endif
152 if (tm_time_p)
153 up->ymd = *tm_time_p;
154 else
155 return 0;
156
157 /*
158 * Initialize miscellaneous variables
159 */
160 peer->precision = PRECISION;
161 pp->clockdesc = DESCRIPTION;
162 memcpy((char *)&pp->refid, REFID, 4);
163 return (1);
164 }
165
166
167 /*
168 * dumbclock_shutdown - shut down the clock
169 */
170 static void
dumbclock_shutdown(int unit,struct peer * peer)171 dumbclock_shutdown(
172 int unit,
173 struct peer *peer
174 )
175 {
176 register struct dumbclock_unit *up;
177 struct refclockproc *pp;
178
179 pp = peer->procptr;
180 up = pp->unitptr;
181 if (-1 != pp->io.fd)
182 io_closeclock(&pp->io);
183 if (NULL != up)
184 free(up);
185 }
186
187
188 /*
189 * dumbclock_receive - receive data from the serial interface
190 */
191 static void
dumbclock_receive(struct recvbuf * rbufp)192 dumbclock_receive(
193 struct recvbuf *rbufp
194 )
195 {
196 struct dumbclock_unit *up;
197 struct refclockproc *pp;
198 struct peer *peer;
199
200 l_fp trtmp; /* arrival timestamp */
201 int hours; /* hour-of-day */
202 int minutes; /* minutes-past-the-hour */
203 int seconds; /* seconds */
204 int temp; /* int temp */
205 int got_good; /* got a good time flag */
206
207 /*
208 * Initialize pointers and read the timecode and timestamp
209 */
210 peer = rbufp->recv_peer;
211 pp = peer->procptr;
212 up = pp->unitptr;
213 temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
214
215 if (temp == 0) {
216 if (up->tcswitch == 0) {
217 up->tcswitch = 1;
218 up->laststamp = trtmp;
219 } else
220 up->tcswitch = 0;
221 return;
222 }
223 pp->lencode = (u_short)temp;
224 pp->lastrec = up->laststamp;
225 up->laststamp = trtmp;
226 up->tcswitch = 1;
227
228 #ifdef DEBUG
229 if (debug)
230 printf("dumbclock: timecode %d %s\n",
231 pp->lencode, pp->a_lastcode);
232 #endif
233
234 /*
235 * We get down to business. Check the timecode format...
236 */
237 got_good=0;
238 if (sscanf(pp->a_lastcode,"%02d:%02d:%02d",
239 &hours,&minutes,&seconds) == 3)
240 {
241 struct tm *gmtp;
242 struct tm *lt_p;
243 time_t asserted_time; /* the SPM time based on the composite time+date */
244 struct tm asserted_tm; /* the struct tm of the same */
245 int adjyear;
246 int adjmon;
247 time_t reality_delta;
248 time_t now;
249
250
251 /*
252 * Convert to GMT for sites that distribute localtime. This
253 * means we have to figure out what day it is. Easier said
254 * than done...
255 */
256
257 memset(&asserted_tm, 0, sizeof(asserted_tm));
258
259 asserted_tm.tm_year = up->ymd.tm_year;
260 asserted_tm.tm_mon = up->ymd.tm_mon;
261 asserted_tm.tm_mday = up->ymd.tm_mday;
262 asserted_tm.tm_hour = hours;
263 asserted_tm.tm_min = minutes;
264 asserted_tm.tm_sec = seconds;
265 asserted_tm.tm_isdst = -1;
266
267 #ifdef GET_LOCALTIME
268 asserted_time = mktime (&asserted_tm);
269 time(&now);
270 #else
271 #include "GMT unsupported for dumbclock!"
272 #endif
273 reality_delta = asserted_time - now;
274
275 /*
276 * We assume that if the time is grossly wrong, it's because we got the
277 * year/month/day wrong.
278 */
279 if (reality_delta > INSANE_SECONDS)
280 {
281 asserted_time -= SECSPERDAY; /* local clock behind real time */
282 }
283 else if (-reality_delta > INSANE_SECONDS)
284 {
285 asserted_time += SECSPERDAY; /* local clock ahead of real time */
286 }
287 lt_p = localtime(&asserted_time);
288 if (lt_p)
289 {
290 up->ymd = *lt_p;
291 }
292 else
293 {
294 refclock_report (peer, CEVNT_FAULT);
295 return;
296 }
297
298 if ((gmtp = gmtime (&asserted_time)) == NULL)
299 {
300 refclock_report (peer, CEVNT_FAULT);
301 return;
302 }
303 adjyear = gmtp->tm_year+1900;
304 adjmon = gmtp->tm_mon+1;
305 pp->day = ymd2yd (adjyear, adjmon, gmtp->tm_mday);
306 pp->hour = gmtp->tm_hour;
307 pp->minute = gmtp->tm_min;
308 pp->second = gmtp->tm_sec;
309 #ifdef DEBUG
310 if (debug)
311 printf ("time is %04d/%02d/%02d %02d:%02d:%02d UTC\n",
312 adjyear,adjmon,gmtp->tm_mday,pp->hour,pp->minute,
313 pp->second);
314 #endif
315
316 got_good=1;
317 }
318
319 if (!got_good)
320 {
321 if (up->linect > 0)
322 up->linect--;
323 else
324 refclock_report(peer, CEVNT_BADREPLY);
325 return;
326 }
327
328 /*
329 * Process the new sample in the median filter and determine the
330 * timecode timestamp.
331 */
332 if (!refclock_process(pp)) {
333 refclock_report(peer, CEVNT_BADTIME);
334 return;
335 }
336 pp->lastref = pp->lastrec;
337 refclock_receive(peer);
338 record_clock_stats(&peer->srcadr, pp->a_lastcode);
339 up->lasthour = (u_char)pp->hour;
340 }
341
342 #if 0
343 /*
344 * dumbclock_poll - called by the transmit procedure
345 */
346 static void
347 dumbclock_poll(
348 int unit,
349 struct peer *peer
350 )
351 {
352 register struct dumbclock_unit *up;
353 struct refclockproc *pp;
354 char pollchar;
355
356 /*
357 * Time to poll the clock. The Chrono-log clock is supposed to
358 * respond to a 'T' by returning a timecode in the format(s)
359 * specified above. Ours does (can?) not, but this seems to be
360 * an installation-specific problem. This code is dyked out,
361 * but may be re-enabled if anyone ever finds a Chrono-log that
362 * actually listens to this command.
363 */
364 #if 0
365 pp = peer->procptr;
366 up = pp->unitptr;
367 if (peer->reach == 0)
368 refclock_report(peer, CEVNT_TIMEOUT);
369 if (up->linect > 0)
370 pollchar = 'R';
371 else
372 pollchar = 'T';
373 if (write(pp->io.fd, &pollchar, 1) != 1)
374 refclock_report(peer, CEVNT_FAULT);
375 else
376 pp->polls++;
377 #endif
378 }
379 #endif
380
381 #else
382 int refclock_dumbclock_bs;
383 #endif /* defined(REFCLOCK) && defined(CLOCK_DUMBCLOCK) */
384