1*eabc0478Schristos /* $NetBSD: refclock_chronolog.c,v 1.6 2024/08/18 20:47:18 christos Exp $ */ 2abb0f93cSkardel 3abb0f93cSkardel /* 4abb0f93cSkardel * refclock_chronolog - clock driver for Chronolog K-series WWVB receiver. 5abb0f93cSkardel */ 6abb0f93cSkardel 7abb0f93cSkardel /* 8abb0f93cSkardel * Must interpolate back to local time. Very annoying. 9abb0f93cSkardel */ 10abb0f93cSkardel #define GET_LOCALTIME 11abb0f93cSkardel 12abb0f93cSkardel #ifdef HAVE_CONFIG_H 13abb0f93cSkardel #include <config.h> 14abb0f93cSkardel #endif 15abb0f93cSkardel 16abb0f93cSkardel #if defined(REFCLOCK) && defined(CLOCK_CHRONOLOG) 17abb0f93cSkardel 18abb0f93cSkardel #include "ntpd.h" 19abb0f93cSkardel #include "ntp_io.h" 20abb0f93cSkardel #include "ntp_refclock.h" 21abb0f93cSkardel #include "ntp_calendar.h" 22abb0f93cSkardel #include "ntp_stdlib.h" 23abb0f93cSkardel 24abb0f93cSkardel #include <stdio.h> 25abb0f93cSkardel #include <ctype.h> 26abb0f93cSkardel 27abb0f93cSkardel /* 28abb0f93cSkardel * This driver supports the Chronolog K-series WWVB receiver. 29abb0f93cSkardel * 30abb0f93cSkardel * Input format: 31abb0f93cSkardel * 32abb0f93cSkardel * Y YY/MM/DD<cr><lf> 33abb0f93cSkardel * Z hh:mm:ss<cr><lf> 34abb0f93cSkardel * 35abb0f93cSkardel * YY/MM/DD -- what you'd expect. This arrives a few seconds before the 36abb0f93cSkardel * timestamp. 37abb0f93cSkardel * hh:mm:ss -- what you'd expect. We take time on the <cr>. 38abb0f93cSkardel * 39abb0f93cSkardel * Our Chronolog writes time out at 2400 bps 8/N/1, but it can be configured 40abb0f93cSkardel * otherwise. The clock seems to appear every 60 seconds, which doesn't make 41abb0f93cSkardel * for good statistics collection. 42abb0f93cSkardel * 43abb0f93cSkardel * The original source of this module was the WWVB module. 44abb0f93cSkardel */ 45abb0f93cSkardel 46abb0f93cSkardel /* 47abb0f93cSkardel * Interface definitions 48abb0f93cSkardel */ 49abb0f93cSkardel #define DEVICE "/dev/chronolog%d" /* device name and unit */ 50abb0f93cSkardel #define SPEED232 B2400 /* uart speed (2400 baud) */ 51abb0f93cSkardel #define PRECISION (-13) /* precision assumed (about 100 us) */ 52abb0f93cSkardel #define REFID "chronolog" /* reference ID */ 53abb0f93cSkardel #define DESCRIPTION "Chrono-log K" /* WRU */ 54abb0f93cSkardel 55abb0f93cSkardel #define MONLIN 15 /* number of monitoring lines */ 56abb0f93cSkardel 57abb0f93cSkardel /* 58abb0f93cSkardel * Chrono-log unit control structure 59abb0f93cSkardel */ 60abb0f93cSkardel struct chronolog_unit { 61abb0f93cSkardel u_char tcswitch; /* timecode switch */ 62abb0f93cSkardel l_fp laststamp; /* last receive timestamp */ 63abb0f93cSkardel u_char lasthour; /* last hour (for monitor) */ 64abb0f93cSkardel int year; /* Y2K-adjusted year */ 65abb0f93cSkardel int day; /* day-of-month */ 66abb0f93cSkardel int month; /* month-of-year */ 67abb0f93cSkardel }; 68abb0f93cSkardel 69abb0f93cSkardel /* 70abb0f93cSkardel * Function prototypes 71abb0f93cSkardel */ 72abb0f93cSkardel static int chronolog_start (int, struct peer *); 73abb0f93cSkardel static void chronolog_shutdown (int, struct peer *); 74abb0f93cSkardel static void chronolog_receive (struct recvbuf *); 75abb0f93cSkardel static void chronolog_poll (int, struct peer *); 76abb0f93cSkardel 77abb0f93cSkardel /* 78abb0f93cSkardel * Transfer vector 79abb0f93cSkardel */ 80abb0f93cSkardel struct refclock refclock_chronolog = { 81abb0f93cSkardel chronolog_start, /* start up driver */ 82abb0f93cSkardel chronolog_shutdown, /* shut down driver */ 83abb0f93cSkardel chronolog_poll, /* poll the driver -- a nice fabrication */ 84abb0f93cSkardel noentry, /* not used */ 85abb0f93cSkardel noentry, /* not used */ 86abb0f93cSkardel noentry, /* not used */ 87abb0f93cSkardel NOFLAGS /* not used */ 88abb0f93cSkardel }; 89abb0f93cSkardel 90abb0f93cSkardel 91abb0f93cSkardel /* 92abb0f93cSkardel * chronolog_start - open the devices and initialize data for processing 93abb0f93cSkardel */ 94abb0f93cSkardel static int 95abb0f93cSkardel chronolog_start( 96abb0f93cSkardel int unit, 97abb0f93cSkardel struct peer *peer 98abb0f93cSkardel ) 99abb0f93cSkardel { 100abb0f93cSkardel register struct chronolog_unit *up; 101abb0f93cSkardel struct refclockproc *pp; 102abb0f93cSkardel int fd; 103abb0f93cSkardel char device[20]; 104abb0f93cSkardel 105abb0f93cSkardel /* 106abb0f93cSkardel * Open serial port. Don't bother with CLK line discipline, since 107abb0f93cSkardel * it's not available. 108abb0f93cSkardel */ 109f003fb54Skardel snprintf(device, sizeof(device), DEVICE, unit); 110abb0f93cSkardel #ifdef DEBUG 111abb0f93cSkardel if (debug) 112abb0f93cSkardel printf ("starting Chronolog with device %s\n",device); 113abb0f93cSkardel #endif 114*eabc0478Schristos fd = refclock_open(&peer->srcadr, device, SPEED232, 0); 1158585484eSchristos if (fd <= 0) 116abb0f93cSkardel return (0); 117abb0f93cSkardel 118abb0f93cSkardel /* 119abb0f93cSkardel * Allocate and initialize unit structure 120abb0f93cSkardel */ 1218585484eSchristos up = emalloc_zero(sizeof(*up)); 122abb0f93cSkardel pp = peer->procptr; 1238585484eSchristos pp->unitptr = up; 124abb0f93cSkardel pp->io.clock_recv = chronolog_receive; 1258585484eSchristos pp->io.srcclock = peer; 126abb0f93cSkardel pp->io.datalen = 0; 127abb0f93cSkardel pp->io.fd = fd; 128abb0f93cSkardel if (!io_addclock(&pp->io)) { 129f003fb54Skardel close(fd); 130f003fb54Skardel pp->io.fd = -1; 131abb0f93cSkardel free(up); 132f003fb54Skardel pp->unitptr = NULL; 133abb0f93cSkardel return (0); 134abb0f93cSkardel } 135abb0f93cSkardel 136abb0f93cSkardel /* 137abb0f93cSkardel * Initialize miscellaneous variables 138abb0f93cSkardel */ 139abb0f93cSkardel peer->precision = PRECISION; 140abb0f93cSkardel pp->clockdesc = DESCRIPTION; 141abb0f93cSkardel memcpy((char *)&pp->refid, REFID, 4); 142abb0f93cSkardel return (1); 143abb0f93cSkardel } 144abb0f93cSkardel 145abb0f93cSkardel 146abb0f93cSkardel /* 147abb0f93cSkardel * chronolog_shutdown - shut down the clock 148abb0f93cSkardel */ 149abb0f93cSkardel static void 150abb0f93cSkardel chronolog_shutdown( 151abb0f93cSkardel int unit, 152abb0f93cSkardel struct peer *peer 153abb0f93cSkardel ) 154abb0f93cSkardel { 155abb0f93cSkardel register struct chronolog_unit *up; 156abb0f93cSkardel struct refclockproc *pp; 157abb0f93cSkardel 158abb0f93cSkardel pp = peer->procptr; 1598585484eSchristos up = pp->unitptr; 160f003fb54Skardel if (-1 != pp->io.fd) 161abb0f93cSkardel io_closeclock(&pp->io); 162f003fb54Skardel if (NULL != up) 163abb0f93cSkardel free(up); 164abb0f93cSkardel } 165abb0f93cSkardel 166abb0f93cSkardel 167abb0f93cSkardel /* 168abb0f93cSkardel * chronolog_receive - receive data from the serial interface 169abb0f93cSkardel */ 170abb0f93cSkardel static void 171abb0f93cSkardel chronolog_receive( 172abb0f93cSkardel struct recvbuf *rbufp 173abb0f93cSkardel ) 174abb0f93cSkardel { 175abb0f93cSkardel struct chronolog_unit *up; 176abb0f93cSkardel struct refclockproc *pp; 177abb0f93cSkardel struct peer *peer; 178abb0f93cSkardel 179abb0f93cSkardel l_fp trtmp; /* arrival timestamp */ 180abb0f93cSkardel int hours; /* hour-of-day */ 181abb0f93cSkardel int minutes; /* minutes-past-the-hour */ 182abb0f93cSkardel int seconds; /* seconds */ 183abb0f93cSkardel int temp; /* int temp */ 184abb0f93cSkardel int got_good; /* got a good time flag */ 185abb0f93cSkardel 186abb0f93cSkardel /* 187abb0f93cSkardel * Initialize pointers and read the timecode and timestamp 188abb0f93cSkardel */ 1898585484eSchristos peer = rbufp->recv_peer; 190abb0f93cSkardel pp = peer->procptr; 1918585484eSchristos up = pp->unitptr; 192abb0f93cSkardel temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); 193abb0f93cSkardel 194abb0f93cSkardel if (temp == 0) { 195abb0f93cSkardel if (up->tcswitch == 0) { 196abb0f93cSkardel up->tcswitch = 1; 197abb0f93cSkardel up->laststamp = trtmp; 198abb0f93cSkardel } else 199abb0f93cSkardel up->tcswitch = 0; 200abb0f93cSkardel return; 201abb0f93cSkardel } 202abb0f93cSkardel pp->lencode = temp; 203abb0f93cSkardel pp->lastrec = up->laststamp; 204abb0f93cSkardel up->laststamp = trtmp; 205abb0f93cSkardel up->tcswitch = 1; 206abb0f93cSkardel 207abb0f93cSkardel #ifdef DEBUG 208abb0f93cSkardel if (debug) 209abb0f93cSkardel printf("chronolog: timecode %d %s\n", pp->lencode, 210abb0f93cSkardel pp->a_lastcode); 211abb0f93cSkardel #endif 212abb0f93cSkardel 213abb0f93cSkardel /* 214abb0f93cSkardel * We get down to business. Check the timecode format and decode 215abb0f93cSkardel * its contents. This code uses the first character to see whether 216abb0f93cSkardel * we're looking at a date or a time. We store data data across 217abb0f93cSkardel * calls since it is transmitted a few seconds ahead of the 218abb0f93cSkardel * timestamp. 219abb0f93cSkardel */ 220abb0f93cSkardel got_good=0; 221abb0f93cSkardel if (sscanf(pp->a_lastcode, "Y %d/%d/%d", &up->year,&up->month,&up->day)) 222abb0f93cSkardel { 223abb0f93cSkardel /* 224abb0f93cSkardel * Y2K convert the 2-digit year 225abb0f93cSkardel */ 226abb0f93cSkardel up->year = up->year >= 69 ? up->year : up->year + 100; 227abb0f93cSkardel return; 228abb0f93cSkardel } 229abb0f93cSkardel if (sscanf(pp->a_lastcode,"Z %02d:%02d:%02d", 230abb0f93cSkardel &hours,&minutes,&seconds) == 3) 231abb0f93cSkardel { 232abb0f93cSkardel #ifdef GET_LOCALTIME 233abb0f93cSkardel struct tm local; 234abb0f93cSkardel struct tm *gmtp; 235abb0f93cSkardel time_t unixtime; 236abb0f93cSkardel int adjyear; 237abb0f93cSkardel int adjmon; 238abb0f93cSkardel 239abb0f93cSkardel /* 240abb0f93cSkardel * Convert to GMT for sites that distribute localtime. This 241abb0f93cSkardel * means we have to do Y2K conversion on the 2-digit year; 242abb0f93cSkardel * otherwise, we get the time wrong. 243abb0f93cSkardel */ 244abb0f93cSkardel 245abb0f93cSkardel memset(&local, 0, sizeof(local)); 246abb0f93cSkardel 247abb0f93cSkardel local.tm_year = up->year; 248abb0f93cSkardel local.tm_mon = up->month-1; 249abb0f93cSkardel local.tm_mday = up->day; 250abb0f93cSkardel local.tm_hour = hours; 251abb0f93cSkardel local.tm_min = minutes; 252abb0f93cSkardel local.tm_sec = seconds; 253abb0f93cSkardel local.tm_isdst = -1; 254abb0f93cSkardel 255abb0f93cSkardel unixtime = mktime (&local); 256abb0f93cSkardel if ((gmtp = gmtime (&unixtime)) == NULL) 257abb0f93cSkardel { 258abb0f93cSkardel refclock_report (peer, CEVNT_FAULT); 259abb0f93cSkardel return; 260abb0f93cSkardel } 261abb0f93cSkardel adjyear = gmtp->tm_year+1900; 262abb0f93cSkardel adjmon = gmtp->tm_mon+1; 263abb0f93cSkardel pp->day = ymd2yd (adjyear, adjmon, gmtp->tm_mday); 264abb0f93cSkardel pp->hour = gmtp->tm_hour; 265abb0f93cSkardel pp->minute = gmtp->tm_min; 266abb0f93cSkardel pp->second = gmtp->tm_sec; 267abb0f93cSkardel #ifdef DEBUG 268abb0f93cSkardel if (debug) 269abb0f93cSkardel printf ("time is %04d/%02d/%02d %02d:%02d:%02d UTC\n", 270abb0f93cSkardel adjyear,adjmon,gmtp->tm_mday,pp->hour,pp->minute, 271abb0f93cSkardel pp->second); 272abb0f93cSkardel #endif 273abb0f93cSkardel 274abb0f93cSkardel #else 275abb0f93cSkardel /* 276abb0f93cSkardel * For more rational sites distributing UTC 277abb0f93cSkardel */ 278abb0f93cSkardel pp->day = ymd2yd(year+1900,month,day); 279abb0f93cSkardel pp->hour = hours; 280abb0f93cSkardel pp->minute = minutes; 281abb0f93cSkardel pp->second = seconds; 282abb0f93cSkardel 283abb0f93cSkardel #endif 284abb0f93cSkardel got_good=1; 285abb0f93cSkardel } 286abb0f93cSkardel 287abb0f93cSkardel if (!got_good) 288abb0f93cSkardel return; 289abb0f93cSkardel 290abb0f93cSkardel 291abb0f93cSkardel /* 292abb0f93cSkardel * Process the new sample in the median filter and determine the 293abb0f93cSkardel * timecode timestamp. 294abb0f93cSkardel */ 295abb0f93cSkardel if (!refclock_process(pp)) { 296abb0f93cSkardel refclock_report(peer, CEVNT_BADTIME); 297abb0f93cSkardel return; 298abb0f93cSkardel } 299abb0f93cSkardel pp->lastref = pp->lastrec; 300abb0f93cSkardel refclock_receive(peer); 301abb0f93cSkardel record_clock_stats(&peer->srcadr, pp->a_lastcode); 3028585484eSchristos up->lasthour = (u_char)pp->hour; 303abb0f93cSkardel } 304abb0f93cSkardel 305abb0f93cSkardel 306abb0f93cSkardel /* 307abb0f93cSkardel * chronolog_poll - called by the transmit procedure 308abb0f93cSkardel */ 309abb0f93cSkardel static void 310abb0f93cSkardel chronolog_poll( 311abb0f93cSkardel int unit, 312abb0f93cSkardel struct peer *peer 313abb0f93cSkardel ) 314abb0f93cSkardel { 315abb0f93cSkardel /* 316abb0f93cSkardel * Time to poll the clock. The Chrono-log clock is supposed to 317abb0f93cSkardel * respond to a 'T' by returning a timecode in the format(s) 318abb0f93cSkardel * specified above. Ours does (can?) not, but this seems to be 319abb0f93cSkardel * an installation-specific problem. This code is dyked out, 320abb0f93cSkardel * but may be re-enabled if anyone ever finds a Chrono-log that 321abb0f93cSkardel * actually listens to this command. 322abb0f93cSkardel */ 323abb0f93cSkardel #if 0 324abb0f93cSkardel register struct chronolog_unit *up; 325abb0f93cSkardel struct refclockproc *pp; 326abb0f93cSkardel char pollchar; 327abb0f93cSkardel 328abb0f93cSkardel pp = peer->procptr; 3298585484eSchristos up = pp->unitptr; 330abb0f93cSkardel if (peer->burst == 0 && peer->reach == 0) 331abb0f93cSkardel refclock_report(peer, CEVNT_TIMEOUT); 332abb0f93cSkardel if (up->linect > 0) 333abb0f93cSkardel pollchar = 'R'; 334abb0f93cSkardel else 335abb0f93cSkardel pollchar = 'T'; 336abb0f93cSkardel if (write(pp->io.fd, &pollchar, 1) != 1) 337abb0f93cSkardel refclock_report(peer, CEVNT_FAULT); 338abb0f93cSkardel else 339abb0f93cSkardel pp->polls++; 340abb0f93cSkardel #endif 341abb0f93cSkardel } 342abb0f93cSkardel 343abb0f93cSkardel #else 344*eabc0478Schristos NONEMPTY_TRANSLATION_UNIT 345abb0f93cSkardel #endif /* REFCLOCK */ 346