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