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