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