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 */
60 struct 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 */
71 static int dumbclock_start (int, struct peer *);
72 static void dumbclock_shutdown (int, struct peer *);
73 static void dumbclock_receive (struct recvbuf *);
74 #if 0
75 static void dumbclock_poll (int, struct peer *);
76 #endif
77
78 /*
79 * Transfer vector
80 */
81 struct 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 */
95 static int
dumbclock_start(int unit,struct peer * peer)96 dumbclock_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 */
164 static void
dumbclock_shutdown(int unit,struct peer * peer)165 dumbclock_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 */
185 static void
dumbclock_receive(struct recvbuf * rbufp)186 dumbclock_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 */
340 static void
341 dumbclock_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
376 NONEMPTY_TRANSLATION_UNIT
377 #endif /* defined(REFCLOCK) && defined(CLOCK_DUMBCLOCK) */
378