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 */
86struct pstunit {
87	int	tcswitch;	/* timecode switch */
88	char	*lastptr;	/* pointer to timecode data */
89};
90
91/*
92 * Function prototypes
93 */
94static	int	pst_start	(int, struct peer *);
95static	void	pst_shutdown	(int, struct peer *);
96static	void	pst_receive	(struct recvbuf *);
97static	void	pst_poll	(int, struct peer *);
98
99/*
100 * Transfer vector
101 */
102struct	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 */
116static int
117pst_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 */
168static void
169pst_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 */
187static void
188pst_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	    &ampmchar, &pp->hour, &pp->minute, &pp->second, &pp->nsec,
241	    &daychar, junque, &pp->day, info, &ltemp) != 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	else if (peer->disp > MAXDISTANCE)
273		refclock_receive(peer);
274}
275
276
277/*
278 * pst_poll - called by the transmit procedure
279 */
280static void
281pst_poll(
282	int unit,
283	struct peer *peer
284	)
285{
286	register struct pstunit *up;
287	struct refclockproc *pp;
288
289	/*
290	 * Time to poll the clock. The PSTI/Traconex clock responds to a
291	 * "QTQDQMT" by returning a timecode in the format specified
292	 * above. Note there is no checking on state, since this may not
293	 * be the only customer reading the clock. Only one customer
294	 * need poll the clock; all others just listen in. If the clock
295	 * becomes unreachable, 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	if (peer->burst > 0)
304		return;
305	if (pp->coderecv == pp->codeproc) {
306		refclock_report(peer, CEVNT_TIMEOUT);
307		return;
308	}
309	refclock_receive(peer);
310	record_clock_stats(&peer->srcadr, pp->a_lastcode);
311#ifdef DEBUG
312	if (debug)
313		printf("pst: timecode %d %s\n", pp->lencode,
314		    pp->a_lastcode);
315#endif
316	peer->burst = MAXSTAGE;
317	pp->polls++;
318}
319
320#else
321int refclock_pst_int;
322#endif /* REFCLOCK */
323