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