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