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