1 /* $NetBSD: refclock_pst.c,v 1.2 2003/12/04 16:23:37 drochner Exp $ */
4 * refclock_pst - clock driver for PSTI/Traconex WWV/WWVH receivers
11 #if defined(REFCLOCK) && defined(CLOCK_PST)
15 #include "ntp_refclock.h"
16 #include "ntp_stdlib.h"
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
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.
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:
41 * ahh:mm:ss.fffs<cr> yy/dd/mm/ddd<cr> frdzycchhSSFTttttuuxx<cr>
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)
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)
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.
70 * There are no special fudge factors other than the generic.
74 * Interface definitions
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 */
86 * Unit control structure
89 int tcswitch
; /* timecode switch */
90 char *lastptr
; /* pointer to timecode data */
96 static int pst_start
P((int, struct peer
*));
97 static void pst_shutdown
P((int, struct peer
*));
98 static void pst_receive
P((struct recvbuf
*));
99 static void pst_poll
P((int, struct peer
*));
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 */
116 * pst_start - open the devices and initialize data for processing
124 register struct pstunit
*up
;
125 struct refclockproc
*pp
;
130 * Open serial port. Use CLK line discipline, if available.
132 (void)sprintf(device
, DEVICE
, unit
);
133 if (!(fd
= refclock_open(device
, SPEED232
, LDISC_CLK
)))
137 * Allocate and initialize unit structure
139 if (!(up
= (struct pstunit
*)emalloc(sizeof(struct pstunit
)))) {
143 memset((char *)up
, 0, sizeof(struct pstunit
));
145 pp
->io
.clock_recv
= pst_receive
;
146 pp
->io
.srcclock
= (caddr_t
)peer
;
149 if (!io_addclock(&pp
->io
)) {
154 pp
->unitptr
= (caddr_t
)up
;
157 * Initialize miscellaneous variables
159 peer
->precision
= PRECISION
;
160 pp
->clockdesc
= DESCRIPTION
;
161 memcpy((char *)&pp
->refid
, WWVREFID
, 4);
162 peer
->burst
= MAXSTAGE
;
168 * pst_shutdown - shut down the clock
176 register struct pstunit
*up
;
177 struct refclockproc
*pp
;
180 up
= (struct pstunit
*)pp
->unitptr
;
181 io_closeclock(&pp
->io
);
187 * pst_receive - receive data from the serial interface
191 struct recvbuf
*rbufp
194 register struct pstunit
*up
;
195 struct refclockproc
*pp
;
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 */
205 * Initialize pointers and read the timecode and timestamp
207 peer
= (struct peer
*)rbufp
->recv_srcclock
;
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
++ = ' ';
216 * Note we get a buffer and timestamp for each <cr>, but only
217 * the first timestamp is retained.
219 if (up
->tcswitch
== 0)
222 pp
->lencode
= up
->lastptr
- pp
->a_lastcode
;
223 if (up
->tcswitch
< 3)
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.
231 if (pp
->lencode
< LENPST
) {
232 refclock_report(peer
, CEVNT_BADREPLY
);
238 * "ahh:mm:ss.fffs yy/dd/mm/ddd frdzycchhSSFTttttuuxx"
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
);
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.
257 pp
->leap
= LEAP_NOTINSYNC
;
259 memcpy((char *)&pp
->refid
, WWVHREFID
, 4);
261 memcpy((char *)&pp
->refid
, WWVREFID
, 4);
262 if (peer
->stratum
<= 1)
263 peer
->refid
= pp
->refid
;
265 pp
->lastref
= pp
->lastrec
;
266 pp
->disp
= PST_PHI
* ltemp
* 60;
269 * Process the new sample in the median filter and determine the
270 * timecode timestamp.
272 if (!refclock_process(pp
))
273 refclock_report(peer
, CEVNT_BADTIME
);
274 else if (peer
->disp
> MAXDISTANCE
)
275 refclock_receive(peer
);
280 * pst_poll - called by the transmit procedure
288 register struct pstunit
*up
;
289 struct refclockproc
*pp
;
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.
300 up
= (struct pstunit
*)pp
->unitptr
;
302 up
->lastptr
= pp
->a_lastcode
;
303 if (write(pp
->io
.fd
, "QTQDQMT", 6) != 6)
304 refclock_report(peer
, CEVNT_FAULT
);
307 if (pp
->coderecv
== pp
->codeproc
) {
308 refclock_report(peer
, CEVNT_TIMEOUT
);
311 refclock_receive(peer
);
312 record_clock_stats(&peer
->srcadr
, pp
->a_lastcode
);
315 printf("pst: timecode %d %s\n", pp
->lencode
,
318 peer
->burst
= MAXSTAGE
;
323 int refclock_pst_int
;
324 #endif /* REFCLOCK */