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 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 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 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