1*eabc0478Schristos /* $NetBSD: refclock_pst.c,v 1.6 2024/08/18 20:47:18 christos Exp $ */ 2abb0f93cSkardel 3abb0f93cSkardel /* 4abb0f93cSkardel * refclock_pst - clock driver for PSTI/Traconex WWV/WWVH receivers 5abb0f93cSkardel */ 6abb0f93cSkardel 7abb0f93cSkardel #ifdef HAVE_CONFIG_H 8abb0f93cSkardel #include <config.h> 9abb0f93cSkardel #endif 10abb0f93cSkardel 11abb0f93cSkardel #if defined(REFCLOCK) && defined(CLOCK_PST) 12abb0f93cSkardel 13abb0f93cSkardel #include "ntpd.h" 14abb0f93cSkardel #include "ntp_io.h" 15abb0f93cSkardel #include "ntp_refclock.h" 16abb0f93cSkardel #include "ntp_stdlib.h" 17abb0f93cSkardel 18abb0f93cSkardel #include <stdio.h> 19abb0f93cSkardel #include <ctype.h> 20abb0f93cSkardel 21abb0f93cSkardel /* 22abb0f93cSkardel * This driver supports the PSTI 1010 and Traconex 1020 WWV/WWVH 23abb0f93cSkardel * Receivers. No specific claim of accuracy is made for these receiver, 24abb0f93cSkardel * but actual experience suggests that 10 ms would be a conservative 25abb0f93cSkardel * assumption. 26abb0f93cSkardel * 27abb0f93cSkardel * The DIPswitches should be set for 9600 bps line speed, 24-hour day- 28abb0f93cSkardel * of-year format and UTC time zone. Automatic correction for DST should 29abb0f93cSkardel * be disabled. It is very important that the year be set correctly in 30abb0f93cSkardel * the DIPswitches; otherwise, the day of year will be incorrect after 31abb0f93cSkardel * 28 April of a normal or leap year. The propagation delay DIPswitches 32abb0f93cSkardel * should be set according to the distance from the transmitter for both 33abb0f93cSkardel * WWV and WWVH, as described in the instructions. While the delay can 34abb0f93cSkardel * be set only to within 11 ms, the fudge time1 parameter can be used 35abb0f93cSkardel * for vernier corrections. 36abb0f93cSkardel * 37abb0f93cSkardel * Using the poll sequence QTQDQM, the response timecode is in three 38abb0f93cSkardel * sections totalling 50 ASCII printing characters, as concatenated by 39abb0f93cSkardel * the driver, in the following format: 40abb0f93cSkardel * 41abb0f93cSkardel * ahh:mm:ss.fffs<cr> yy/dd/mm/ddd<cr> frdzycchhSSFTttttuuxx<cr> 42abb0f93cSkardel * 43abb0f93cSkardel * on-time = first <cr> 44abb0f93cSkardel * hh:mm:ss.fff = hours, minutes, seconds, milliseconds 45abb0f93cSkardel * a = AM/PM indicator (' ' for 24-hour mode) 46abb0f93cSkardel * yy = year (from internal switches) 47abb0f93cSkardel * dd/mm/ddd = day of month, month, day of year 48abb0f93cSkardel * s = daylight-saving indicator (' ' for 24-hour mode) 49abb0f93cSkardel * f = frequency enable (O = all frequencies enabled) 50abb0f93cSkardel * r = baud rate (3 = 1200, 6 = 9600) 51abb0f93cSkardel * d = features indicator (@ = month/day display enabled) 52abb0f93cSkardel * z = time zone (0 = UTC) 53abb0f93cSkardel * y = year (5 = 91) 54abb0f93cSkardel * cc = WWV propagation delay (52 = 22 ms) 55abb0f93cSkardel * hh = WWVH propagation delay (81 = 33 ms) 56abb0f93cSkardel * SS = status (80 or 82 = operating correctly) 57abb0f93cSkardel * F = current receive frequency (4 = 15 MHz) 58abb0f93cSkardel * T = transmitter (C = WWV, H = WWVH) 59abb0f93cSkardel * tttt = time since last update (0000 = minutes) 60abb0f93cSkardel * uu = flush character (03 = ^c) 61abb0f93cSkardel * xx = 94 (unknown) 62abb0f93cSkardel * 63abb0f93cSkardel * The alarm condition is indicated by other than '8' at A, which occurs 64abb0f93cSkardel * during initial synchronization and when received signal is lost for 65abb0f93cSkardel * an extended period; unlock condition is indicated by other than 66abb0f93cSkardel * "0000" in the tttt subfield at Q. 67abb0f93cSkardel * 68abb0f93cSkardel * Fudge Factors 69abb0f93cSkardel * 70abb0f93cSkardel * There are no special fudge factors other than the generic. 71abb0f93cSkardel */ 72abb0f93cSkardel 73abb0f93cSkardel /* 74abb0f93cSkardel * Interface definitions 75abb0f93cSkardel */ 76abb0f93cSkardel #define DEVICE "/dev/wwv%d" /* device name and unit */ 77abb0f93cSkardel #define SPEED232 B9600 /* uart speed (9600 baud) */ 78abb0f93cSkardel #define PRECISION (-10) /* precision assumed (about 1 ms) */ 79abb0f93cSkardel #define WWVREFID "WWV\0" /* WWV reference ID */ 80abb0f93cSkardel #define WWVHREFID "WWVH" /* WWVH reference ID */ 81abb0f93cSkardel #define DESCRIPTION "PSTI/Traconex WWV/WWVH Receiver" /* WRU */ 82abb0f93cSkardel #define PST_PHI (10e-6) /* max clock oscillator offset */ 83abb0f93cSkardel #define LENPST 46 /* min timecode length */ 84abb0f93cSkardel 85abb0f93cSkardel /* 86abb0f93cSkardel * Unit control structure 87abb0f93cSkardel */ 88abb0f93cSkardel struct pstunit { 89abb0f93cSkardel int tcswitch; /* timecode switch */ 90abb0f93cSkardel char *lastptr; /* pointer to timecode data */ 91abb0f93cSkardel }; 92abb0f93cSkardel 93abb0f93cSkardel /* 94abb0f93cSkardel * Function prototypes 95abb0f93cSkardel */ 96abb0f93cSkardel static int pst_start (int, struct peer *); 97abb0f93cSkardel static void pst_shutdown (int, struct peer *); 98abb0f93cSkardel static void pst_receive (struct recvbuf *); 99abb0f93cSkardel static void pst_poll (int, struct peer *); 100abb0f93cSkardel 101abb0f93cSkardel /* 102abb0f93cSkardel * Transfer vector 103abb0f93cSkardel */ 104abb0f93cSkardel struct refclock refclock_pst = { 105abb0f93cSkardel pst_start, /* start up driver */ 106abb0f93cSkardel pst_shutdown, /* shut down driver */ 107abb0f93cSkardel pst_poll, /* transmit poll message */ 108abb0f93cSkardel noentry, /* not used (old pst_control) */ 109abb0f93cSkardel noentry, /* initialize driver */ 110abb0f93cSkardel noentry, /* not used (old pst_buginfo) */ 111abb0f93cSkardel NOFLAGS /* not used */ 112abb0f93cSkardel }; 113abb0f93cSkardel 114abb0f93cSkardel 115abb0f93cSkardel /* 116abb0f93cSkardel * pst_start - open the devices and initialize data for processing 117abb0f93cSkardel */ 118abb0f93cSkardel static int 119abb0f93cSkardel pst_start( 120abb0f93cSkardel int unit, 121abb0f93cSkardel struct peer *peer 122abb0f93cSkardel ) 123abb0f93cSkardel { 124abb0f93cSkardel register struct pstunit *up; 125abb0f93cSkardel struct refclockproc *pp; 126abb0f93cSkardel int fd; 127abb0f93cSkardel char device[20]; 128abb0f93cSkardel 129abb0f93cSkardel /* 130abb0f93cSkardel * Open serial port. Use CLK line discipline, if available. 131abb0f93cSkardel */ 132f003fb54Skardel snprintf(device, sizeof(device), DEVICE, unit); 133*eabc0478Schristos fd = refclock_open(&peer->srcadr, device, SPEED232, LDISC_CLK); 1348585484eSchristos if (fd <= 0) 135abb0f93cSkardel return (0); 136abb0f93cSkardel 137abb0f93cSkardel /* 138abb0f93cSkardel * Allocate and initialize unit structure 139abb0f93cSkardel */ 1408585484eSchristos up = emalloc_zero(sizeof(*up)); 141abb0f93cSkardel pp = peer->procptr; 142abb0f93cSkardel pp->io.clock_recv = pst_receive; 1438585484eSchristos pp->io.srcclock = peer; 144abb0f93cSkardel pp->io.datalen = 0; 145abb0f93cSkardel pp->io.fd = fd; 146abb0f93cSkardel if (!io_addclock(&pp->io)) { 147f003fb54Skardel close(fd); 148f003fb54Skardel pp->io.fd = -1; 149abb0f93cSkardel free(up); 150abb0f93cSkardel return (0); 151abb0f93cSkardel } 1528585484eSchristos pp->unitptr = up; 153abb0f93cSkardel 154abb0f93cSkardel /* 155abb0f93cSkardel * Initialize miscellaneous variables 156abb0f93cSkardel */ 157abb0f93cSkardel peer->precision = PRECISION; 158abb0f93cSkardel pp->clockdesc = DESCRIPTION; 159abb0f93cSkardel memcpy((char *)&pp->refid, WWVREFID, 4); 160abb0f93cSkardel return (1); 161abb0f93cSkardel } 162abb0f93cSkardel 163abb0f93cSkardel 164abb0f93cSkardel /* 165abb0f93cSkardel * pst_shutdown - shut down the clock 166abb0f93cSkardel */ 167abb0f93cSkardel static void 168abb0f93cSkardel pst_shutdown( 169abb0f93cSkardel int unit, 170abb0f93cSkardel struct peer *peer 171abb0f93cSkardel ) 172abb0f93cSkardel { 173abb0f93cSkardel register struct pstunit *up; 174abb0f93cSkardel struct refclockproc *pp; 175abb0f93cSkardel 176abb0f93cSkardel pp = peer->procptr; 1778585484eSchristos up = pp->unitptr; 178f003fb54Skardel if (-1 != pp->io.fd) 179abb0f93cSkardel io_closeclock(&pp->io); 180f003fb54Skardel if (NULL != up) 181abb0f93cSkardel free(up); 182abb0f93cSkardel } 183abb0f93cSkardel 184abb0f93cSkardel 185abb0f93cSkardel /* 186abb0f93cSkardel * pst_receive - receive data from the serial interface 187abb0f93cSkardel */ 188abb0f93cSkardel static void 189abb0f93cSkardel pst_receive( 190abb0f93cSkardel struct recvbuf *rbufp 191abb0f93cSkardel ) 192abb0f93cSkardel { 193abb0f93cSkardel register struct pstunit *up; 194abb0f93cSkardel struct refclockproc *pp; 195abb0f93cSkardel struct peer *peer; 196abb0f93cSkardel l_fp trtmp; 197abb0f93cSkardel u_long ltemp; 198abb0f93cSkardel char ampmchar; /* AM/PM indicator */ 199abb0f93cSkardel char daychar; /* standard/daylight indicator */ 200abb0f93cSkardel char junque[10]; /* "yy/dd/mm/" discard */ 201abb0f93cSkardel char info[14]; /* "frdzycchhSSFT" clock info */ 202abb0f93cSkardel 203abb0f93cSkardel /* 204abb0f93cSkardel * Initialize pointers and read the timecode and timestamp 205abb0f93cSkardel */ 2068585484eSchristos peer = rbufp->recv_peer; 207abb0f93cSkardel pp = peer->procptr; 2088585484eSchristos up = pp->unitptr; 209abb0f93cSkardel up->lastptr += refclock_gtlin(rbufp, up->lastptr, pp->a_lastcode 210abb0f93cSkardel + BMAX - 2 - up->lastptr, &trtmp); 211abb0f93cSkardel *up->lastptr++ = ' '; 212abb0f93cSkardel *up->lastptr = '\0'; 213abb0f93cSkardel 214abb0f93cSkardel /* 215abb0f93cSkardel * Note we get a buffer and timestamp for each <cr>, but only 216abb0f93cSkardel * the first timestamp is retained. 217abb0f93cSkardel */ 218abb0f93cSkardel if (up->tcswitch == 0) 219abb0f93cSkardel pp->lastrec = trtmp; 220abb0f93cSkardel up->tcswitch++; 221abb0f93cSkardel pp->lencode = up->lastptr - pp->a_lastcode; 222abb0f93cSkardel if (up->tcswitch < 3) 223abb0f93cSkardel return; 224abb0f93cSkardel 225abb0f93cSkardel /* 226abb0f93cSkardel * We get down to business, check the timecode format and decode 227abb0f93cSkardel * its contents. If the timecode has invalid length or is not in 228abb0f93cSkardel * proper format, we declare bad format and exit. 229abb0f93cSkardel */ 230abb0f93cSkardel if (pp->lencode < LENPST) { 231abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 232abb0f93cSkardel return; 233abb0f93cSkardel } 234abb0f93cSkardel 235abb0f93cSkardel /* 236abb0f93cSkardel * Timecode format: 237abb0f93cSkardel * "ahh:mm:ss.fffs yy/dd/mm/ddd frdzycchhSSFTttttuuxx" 238abb0f93cSkardel */ 239abb0f93cSkardel if (sscanf(pp->a_lastcode, 240abb0f93cSkardel "%c%2d:%2d:%2d.%3ld%c %9s%3d%13s%4ld", 241abb0f93cSkardel &mchar, &pp->hour, &pp->minute, &pp->second, &pp->nsec, 242abb0f93cSkardel &daychar, junque, &pp->day, info, <emp) != 10) { 243abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 244abb0f93cSkardel return; 245abb0f93cSkardel } 246abb0f93cSkardel pp->nsec *= 1000000; 247abb0f93cSkardel 248abb0f93cSkardel /* 249abb0f93cSkardel * Decode synchronization, quality and last update. If 250abb0f93cSkardel * unsynchronized, set the leap bits accordingly and exit. Once 251abb0f93cSkardel * synchronized, the dispersion depends only on when the clock 252abb0f93cSkardel * was last heard, which depends on the time since last update, 253abb0f93cSkardel * as reported by the clock. 254abb0f93cSkardel */ 255abb0f93cSkardel if (info[9] != '8') 256abb0f93cSkardel pp->leap = LEAP_NOTINSYNC; 257abb0f93cSkardel if (info[12] == 'H') 258abb0f93cSkardel memcpy((char *)&pp->refid, WWVHREFID, 4); 259abb0f93cSkardel else 260abb0f93cSkardel memcpy((char *)&pp->refid, WWVREFID, 4); 261abb0f93cSkardel if (peer->stratum <= 1) 262abb0f93cSkardel peer->refid = pp->refid; 263abb0f93cSkardel if (ltemp == 0) 264abb0f93cSkardel pp->lastref = pp->lastrec; 265abb0f93cSkardel pp->disp = PST_PHI * ltemp * 60; 266abb0f93cSkardel 267abb0f93cSkardel /* 268abb0f93cSkardel * Process the new sample in the median filter and determine the 269abb0f93cSkardel * timecode timestamp. 270abb0f93cSkardel */ 271abb0f93cSkardel if (!refclock_process(pp)) 272abb0f93cSkardel refclock_report(peer, CEVNT_BADTIME); 273abb0f93cSkardel else if (peer->disp > MAXDISTANCE) 274abb0f93cSkardel refclock_receive(peer); 275abb0f93cSkardel } 276abb0f93cSkardel 277abb0f93cSkardel 278abb0f93cSkardel /* 279abb0f93cSkardel * pst_poll - called by the transmit procedure 280abb0f93cSkardel */ 281abb0f93cSkardel static void 282abb0f93cSkardel pst_poll( 283abb0f93cSkardel int unit, 284abb0f93cSkardel struct peer *peer 285abb0f93cSkardel ) 286abb0f93cSkardel { 287abb0f93cSkardel register struct pstunit *up; 288abb0f93cSkardel struct refclockproc *pp; 289abb0f93cSkardel 290abb0f93cSkardel /* 291abb0f93cSkardel * Time to poll the clock. The PSTI/Traconex clock responds to a 292abb0f93cSkardel * "QTQDQMT" by returning a timecode in the format specified 293abb0f93cSkardel * above. Note there is no checking on state, since this may not 294abb0f93cSkardel * be the only customer reading the clock. Only one customer 295abb0f93cSkardel * need poll the clock; all others just listen in. If the clock 296abb0f93cSkardel * becomes unreachable, declare a timeout and keep going. 297abb0f93cSkardel */ 298abb0f93cSkardel pp = peer->procptr; 2998585484eSchristos up = pp->unitptr; 300abb0f93cSkardel up->tcswitch = 0; 301abb0f93cSkardel up->lastptr = pp->a_lastcode; 302abb0f93cSkardel if (write(pp->io.fd, "QTQDQMT", 6) != 6) 303abb0f93cSkardel refclock_report(peer, CEVNT_FAULT); 304abb0f93cSkardel if (pp->coderecv == pp->codeproc) { 305abb0f93cSkardel refclock_report(peer, CEVNT_TIMEOUT); 306abb0f93cSkardel return; 307abb0f93cSkardel } 308abb0f93cSkardel refclock_receive(peer); 309abb0f93cSkardel record_clock_stats(&peer->srcadr, pp->a_lastcode); 310abb0f93cSkardel #ifdef DEBUG 311abb0f93cSkardel if (debug) 312abb0f93cSkardel printf("pst: timecode %d %s\n", pp->lencode, 313abb0f93cSkardel pp->a_lastcode); 314abb0f93cSkardel #endif 315abb0f93cSkardel pp->polls++; 316abb0f93cSkardel } 317abb0f93cSkardel 318abb0f93cSkardel #else 319*eabc0478Schristos NONEMPTY_TRANSLATION_UNIT 320abb0f93cSkardel #endif /* REFCLOCK */ 321