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 P((int, struct peer *)); 95 static void pst_shutdown P((int, struct peer *)); 96 static void pst_receive P((struct recvbuf *)); 97 static void pst_poll P((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 (void)sprintf(device, DEVICE, unit); 131 if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) 132 return (0); 133 134 /* 135 * Allocate and initialize unit structure 136 */ 137 if (!(up = (struct pstunit *)emalloc(sizeof(struct pstunit)))) { 138 (void) close(fd); 139 return (0); 140 } 141 memset((char *)up, 0, sizeof(struct pstunit)); 142 pp = peer->procptr; 143 pp->io.clock_recv = pst_receive; 144 pp->io.srcclock = (caddr_t)peer; 145 pp->io.datalen = 0; 146 pp->io.fd = fd; 147 if (!io_addclock(&pp->io)) { 148 (void) close(fd); 149 free(up); 150 return (0); 151 } 152 pp->unitptr = (caddr_t)up; 153 154 /* 155 * Initialize miscellaneous variables 156 */ 157 peer->precision = PRECISION; 158 pp->clockdesc = DESCRIPTION; 159 memcpy((char *)&pp->refid, WWVREFID, 4); 160 peer->burst = MAXSTAGE; 161 return (1); 162 } 163 164 165 /* 166 * pst_shutdown - shut down the clock 167 */ 168 static void 169 pst_shutdown( 170 int unit, 171 struct peer *peer 172 ) 173 { 174 register struct pstunit *up; 175 struct refclockproc *pp; 176 177 pp = peer->procptr; 178 up = (struct pstunit *)pp->unitptr; 179 io_closeclock(&pp->io); 180 free(up); 181 } 182 183 184 /* 185 * pst_receive - receive data from the serial interface 186 */ 187 static void 188 pst_receive( 189 struct recvbuf *rbufp 190 ) 191 { 192 register struct pstunit *up; 193 struct refclockproc *pp; 194 struct peer *peer; 195 l_fp trtmp; 196 u_long ltemp; 197 char ampmchar; /* AM/PM indicator */ 198 char daychar; /* standard/daylight indicator */ 199 char junque[10]; /* "yy/dd/mm/" discard */ 200 char info[14]; /* "frdzycchhSSFT" clock info */ 201 202 /* 203 * Initialize pointers and read the timecode and timestamp 204 */ 205 peer = (struct peer *)rbufp->recv_srcclock; 206 pp = peer->procptr; 207 up = (struct pstunit *)pp->unitptr; 208 up->lastptr += refclock_gtlin(rbufp, up->lastptr, pp->a_lastcode 209 + BMAX - 2 - up->lastptr, &trtmp); 210 *up->lastptr++ = ' '; 211 *up->lastptr = '\0'; 212 213 /* 214 * Note we get a buffer and timestamp for each <cr>, but only 215 * the first timestamp is retained. 216 */ 217 if (up->tcswitch == 0) 218 pp->lastrec = trtmp; 219 up->tcswitch++; 220 pp->lencode = up->lastptr - pp->a_lastcode; 221 if (up->tcswitch < 3) 222 return; 223 224 /* 225 * We get down to business, check the timecode format and decode 226 * its contents. If the timecode has invalid length or is not in 227 * proper format, we declare bad format and exit. 228 */ 229 if (pp->lencode < LENPST) { 230 refclock_report(peer, CEVNT_BADREPLY); 231 return; 232 } 233 234 /* 235 * Timecode format: 236 * "ahh:mm:ss.fffs yy/dd/mm/ddd frdzycchhSSFTttttuuxx" 237 */ 238 if (sscanf(pp->a_lastcode, 239 "%c%2d:%2d:%2d.%3ld%c %9s%3d%13s%4ld", 240 &mchar, &pp->hour, &pp->minute, &pp->second, &pp->nsec, 241 &daychar, junque, &pp->day, info, <emp) != 10) { 242 refclock_report(peer, CEVNT_BADREPLY); 243 return; 244 } 245 pp->nsec *= 1000000; 246 247 /* 248 * Decode synchronization, quality and last update. If 249 * unsynchronized, set the leap bits accordingly and exit. Once 250 * synchronized, the dispersion depends only on when the clock 251 * was last heard, which depends on the time since last update, 252 * as reported by the clock. 253 */ 254 if (info[9] != '8') 255 pp->leap = LEAP_NOTINSYNC; 256 if (info[12] == 'H') 257 memcpy((char *)&pp->refid, WWVHREFID, 4); 258 else 259 memcpy((char *)&pp->refid, WWVREFID, 4); 260 if (peer->stratum <= 1) 261 peer->refid = pp->refid; 262 if (ltemp == 0) 263 pp->lastref = pp->lastrec; 264 pp->disp = PST_PHI * ltemp * 60; 265 266 /* 267 * Process the new sample in the median filter and determine the 268 * timecode timestamp. 269 */ 270 if (!refclock_process(pp)) 271 refclock_report(peer, CEVNT_BADTIME); 272 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 = (struct pstunit *)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 (peer->burst > 0) 303 return; 304 if (pp->coderecv == pp->codeproc) { 305 refclock_report(peer, CEVNT_TIMEOUT); 306 return; 307 } 308 refclock_receive(peer); 309 record_clock_stats(&peer->srcadr, pp->a_lastcode); 310 #ifdef DEBUG 311 if (debug) 312 printf("pst: timecode %d %s\n", pp->lencode, 313 pp->a_lastcode); 314 #endif 315 peer->burst = MAXSTAGE; 316 pp->polls++; 317 } 318 319 #else 320 int refclock_pst_int; 321 #endif /* REFCLOCK */ 322