154359Sroberto/*
254359Sroberto * refclock_pst - clock driver for PSTI/Traconex WWV/WWVH receivers
354359Sroberto */
454359Sroberto
554359Sroberto#ifdef HAVE_CONFIG_H
654359Sroberto#include <config.h>
754359Sroberto#endif
854359Sroberto
954359Sroberto#if defined(REFCLOCK) && defined(CLOCK_PST)
1054359Sroberto
1154359Sroberto#include "ntpd.h"
1254359Sroberto#include "ntp_io.h"
1354359Sroberto#include "ntp_refclock.h"
1454359Sroberto#include "ntp_stdlib.h"
1554359Sroberto
1682498Sroberto#include <stdio.h>
1782498Sroberto#include <ctype.h>
1882498Sroberto
1954359Sroberto/*
2054359Sroberto * This driver supports the PSTI 1010 and Traconex 1020 WWV/WWVH
2154359Sroberto * Receivers. No specific claim of accuracy is made for these receiver,
2254359Sroberto * but actual experience suggests that 10 ms would be a conservative
2354359Sroberto * assumption.
2454359Sroberto *
2554359Sroberto * The DIPswitches should be set for 9600 bps line speed, 24-hour day-
2654359Sroberto * of-year format and UTC time zone. Automatic correction for DST should
2754359Sroberto * be disabled. It is very important that the year be set correctly in
2854359Sroberto * the DIPswitches; otherwise, the day of year will be incorrect after
2954359Sroberto * 28 April of a normal or leap year. The propagation delay DIPswitches
3054359Sroberto * should be set according to the distance from the transmitter for both
3154359Sroberto * WWV and WWVH, as described in the instructions. While the delay can
3254359Sroberto * be set only to within 11 ms, the fudge time1 parameter can be used
3354359Sroberto * for vernier corrections.
3454359Sroberto *
3554359Sroberto * Using the poll sequence QTQDQM, the response timecode is in three
3654359Sroberto * sections totalling 50 ASCII printing characters, as concatenated by
3754359Sroberto * the driver, in the following format:
3854359Sroberto *
3954359Sroberto * ahh:mm:ss.fffs<cr> yy/dd/mm/ddd<cr> frdzycchhSSFTttttuuxx<cr>
4054359Sroberto *
4154359Sroberto *	on-time = first <cr>
4254359Sroberto *	hh:mm:ss.fff = hours, minutes, seconds, milliseconds
4354359Sroberto *	a = AM/PM indicator (' ' for 24-hour mode)
4454359Sroberto *	yy = year (from internal switches)
4554359Sroberto *	dd/mm/ddd = day of month, month, day of year
4654359Sroberto *	s = daylight-saving indicator (' ' for 24-hour mode)
4754359Sroberto *	f = frequency enable (O = all frequencies enabled)
4854359Sroberto *	r = baud rate (3 = 1200, 6 = 9600)
4954359Sroberto *	d = features indicator (@ = month/day display enabled)
5054359Sroberto *	z = time zone (0 = UTC)
5154359Sroberto *	y = year (5 = 91)
5254359Sroberto *	cc = WWV propagation delay (52 = 22 ms)
5354359Sroberto *	hh = WWVH propagation delay (81 = 33 ms)
5454359Sroberto *	SS = status (80 or 82 = operating correctly)
5554359Sroberto *	F = current receive frequency (4 = 15 MHz)
5654359Sroberto *	T = transmitter (C = WWV, H = WWVH)
5754359Sroberto *	tttt = time since last update (0000 = minutes)
5854359Sroberto *	uu = flush character (03 = ^c)
5954359Sroberto *	xx = 94 (unknown)
6054359Sroberto *
6154359Sroberto * The alarm condition is indicated by other than '8' at A, which occurs
6254359Sroberto * during initial synchronization and when received signal is lost for
6354359Sroberto * an extended period; unlock condition is indicated by other than
6454359Sroberto * "0000" in the tttt subfield at Q.
6554359Sroberto *
6654359Sroberto * Fudge Factors
6754359Sroberto *
6854359Sroberto * There are no special fudge factors other than the generic.
6954359Sroberto */
7054359Sroberto
7154359Sroberto/*
7254359Sroberto * Interface definitions
7354359Sroberto */
74132451Sroberto#define	DEVICE		"/dev/wwv%d" /* device name and unit */
7554359Sroberto#define	SPEED232	B9600	/* uart speed (9600 baud) */
7654359Sroberto#define	PRECISION	(-10)	/* precision assumed (about 1 ms) */
7754359Sroberto#define	WWVREFID	"WWV\0"	/* WWV reference ID */
7854359Sroberto#define	WWVHREFID	"WWVH"	/* WWVH reference ID */
7954359Sroberto#define	DESCRIPTION	"PSTI/Traconex WWV/WWVH Receiver" /* WRU */
8054359Sroberto#define PST_PHI		(10e-6)	/* max clock oscillator offset */
8154359Sroberto#define LENPST		46	/* min timecode length */
8254359Sroberto
8354359Sroberto/*
8454359Sroberto * Unit control structure
8554359Sroberto */
8654359Srobertostruct pstunit {
87132451Sroberto	int	tcswitch;	/* timecode switch */
8854359Sroberto	char	*lastptr;	/* pointer to timecode data */
8954359Sroberto};
9054359Sroberto
9154359Sroberto/*
9254359Sroberto * Function prototypes
9354359Sroberto */
94285612Sdelphijstatic	int	pst_start	(int, struct peer *);
95285612Sdelphijstatic	void	pst_shutdown	(int, struct peer *);
96285612Sdelphijstatic	void	pst_receive	(struct recvbuf *);
97285612Sdelphijstatic	void	pst_poll	(int, struct peer *);
9854359Sroberto
9954359Sroberto/*
10054359Sroberto * Transfer vector
10154359Sroberto */
10254359Srobertostruct	refclock refclock_pst = {
10354359Sroberto	pst_start,		/* start up driver */
10454359Sroberto	pst_shutdown,		/* shut down driver */
10554359Sroberto	pst_poll,		/* transmit poll message */
10654359Sroberto	noentry,		/* not used (old pst_control) */
10754359Sroberto	noentry,		/* initialize driver */
10854359Sroberto	noentry,		/* not used (old pst_buginfo) */
10954359Sroberto	NOFLAGS			/* not used */
11054359Sroberto};
11154359Sroberto
11254359Sroberto
11354359Sroberto/*
11454359Sroberto * pst_start - open the devices and initialize data for processing
11554359Sroberto */
11654359Srobertostatic int
11754359Srobertopst_start(
11854359Sroberto	int unit,
11954359Sroberto	struct peer *peer
12054359Sroberto	)
12154359Sroberto{
12254359Sroberto	register struct pstunit *up;
12354359Sroberto	struct refclockproc *pp;
12454359Sroberto	int fd;
12554359Sroberto	char device[20];
12654359Sroberto
12754359Sroberto	/*
12854359Sroberto	 * Open serial port. Use CLK line discipline, if available.
12954359Sroberto	 */
130285612Sdelphij	snprintf(device, sizeof(device), DEVICE, unit);
131285612Sdelphij	fd = refclock_open(device, SPEED232, LDISC_CLK);
132285612Sdelphij	if (fd <= 0)
13354359Sroberto		return (0);
13454359Sroberto
13554359Sroberto	/*
13654359Sroberto	 * Allocate and initialize unit structure
13754359Sroberto	 */
138285612Sdelphij	up = emalloc_zero(sizeof(*up));
13954359Sroberto	pp = peer->procptr;
14054359Sroberto	pp->io.clock_recv = pst_receive;
141285612Sdelphij	pp->io.srcclock = peer;
14254359Sroberto	pp->io.datalen = 0;
14354359Sroberto	pp->io.fd = fd;
14454359Sroberto	if (!io_addclock(&pp->io)) {
145285612Sdelphij		close(fd);
146285612Sdelphij		pp->io.fd = -1;
14754359Sroberto		free(up);
14854359Sroberto		return (0);
14954359Sroberto	}
150285612Sdelphij	pp->unitptr = up;
15154359Sroberto
15254359Sroberto	/*
15354359Sroberto	 * Initialize miscellaneous variables
15454359Sroberto	 */
15554359Sroberto	peer->precision = PRECISION;
15654359Sroberto	pp->clockdesc = DESCRIPTION;
15754359Sroberto	memcpy((char *)&pp->refid, WWVREFID, 4);
15854359Sroberto	return (1);
15954359Sroberto}
16054359Sroberto
16154359Sroberto
16254359Sroberto/*
16354359Sroberto * pst_shutdown - shut down the clock
16454359Sroberto */
16554359Srobertostatic void
16654359Srobertopst_shutdown(
16754359Sroberto	int unit,
16854359Sroberto	struct peer *peer
16954359Sroberto	)
17054359Sroberto{
17154359Sroberto	register struct pstunit *up;
17254359Sroberto	struct refclockproc *pp;
17354359Sroberto
17454359Sroberto	pp = peer->procptr;
175285612Sdelphij	up = pp->unitptr;
176285612Sdelphij	if (-1 != pp->io.fd)
177285612Sdelphij		io_closeclock(&pp->io);
178285612Sdelphij	if (NULL != up)
179285612Sdelphij		free(up);
18054359Sroberto}
18154359Sroberto
18254359Sroberto
18354359Sroberto/*
18454359Sroberto * pst_receive - receive data from the serial interface
18554359Sroberto */
18654359Srobertostatic void
18754359Srobertopst_receive(
18854359Sroberto	struct recvbuf *rbufp
18954359Sroberto	)
19054359Sroberto{
19154359Sroberto	register struct pstunit *up;
19254359Sroberto	struct refclockproc *pp;
19354359Sroberto	struct peer *peer;
19454359Sroberto	l_fp trtmp;
19554359Sroberto	u_long ltemp;
19654359Sroberto	char ampmchar;		/* AM/PM indicator */
19754359Sroberto	char daychar;		/* standard/daylight indicator */
19854359Sroberto	char junque[10];	/* "yy/dd/mm/" discard */
19954359Sroberto	char info[14];		/* "frdzycchhSSFT" clock info */
20054359Sroberto
20154359Sroberto	/*
20254359Sroberto	 * Initialize pointers and read the timecode and timestamp
20354359Sroberto	 */
204285612Sdelphij	peer = rbufp->recv_peer;
20554359Sroberto	pp = peer->procptr;
206285612Sdelphij	up = pp->unitptr;
20754359Sroberto	up->lastptr += refclock_gtlin(rbufp, up->lastptr, pp->a_lastcode
20854359Sroberto	    + BMAX - 2 - up->lastptr, &trtmp);
20954359Sroberto	*up->lastptr++ = ' ';
21054359Sroberto	*up->lastptr = '\0';
21154359Sroberto
21254359Sroberto	/*
21354359Sroberto	 * Note we get a buffer and timestamp for each <cr>, but only
21454359Sroberto	 * the first timestamp is retained.
21554359Sroberto	 */
216132451Sroberto	if (up->tcswitch == 0)
21754359Sroberto		pp->lastrec = trtmp;
21854359Sroberto	up->tcswitch++;
21954359Sroberto	pp->lencode = up->lastptr - pp->a_lastcode;
22054359Sroberto	if (up->tcswitch < 3)
22154359Sroberto		return;
22254359Sroberto
22354359Sroberto	/*
22454359Sroberto	 * We get down to business, check the timecode format and decode
22554359Sroberto	 * its contents. If the timecode has invalid length or is not in
22654359Sroberto	 * proper format, we declare bad format and exit.
22754359Sroberto	 */
22854359Sroberto	if (pp->lencode < LENPST) {
22954359Sroberto		refclock_report(peer, CEVNT_BADREPLY);
23054359Sroberto		return;
23154359Sroberto	}
23254359Sroberto
23354359Sroberto	/*
23454359Sroberto	 * Timecode format:
23554359Sroberto	 * "ahh:mm:ss.fffs yy/dd/mm/ddd frdzycchhSSFTttttuuxx"
23654359Sroberto	 */
237132451Sroberto	if (sscanf(pp->a_lastcode,
238132451Sroberto	    "%c%2d:%2d:%2d.%3ld%c %9s%3d%13s%4ld",
239132451Sroberto	    &ampmchar, &pp->hour, &pp->minute, &pp->second, &pp->nsec,
240132451Sroberto	    &daychar, junque, &pp->day, info, &ltemp) != 10) {
24154359Sroberto		refclock_report(peer, CEVNT_BADREPLY);
24254359Sroberto		return;
24354359Sroberto	}
244132451Sroberto	pp->nsec *= 1000000;
24554359Sroberto
24654359Sroberto	/*
24754359Sroberto	 * Decode synchronization, quality and last update. If
24854359Sroberto	 * unsynchronized, set the leap bits accordingly and exit. Once
24954359Sroberto	 * synchronized, the dispersion depends only on when the clock
25054359Sroberto	 * was last heard, which depends on the time since last update,
25154359Sroberto	 * as reported by the clock.
25254359Sroberto	 */
25354359Sroberto	if (info[9] != '8')
25454359Sroberto		pp->leap = LEAP_NOTINSYNC;
25554359Sroberto	if (info[12] == 'H')
25654359Sroberto		memcpy((char *)&pp->refid, WWVHREFID, 4);
25754359Sroberto	else
25854359Sroberto		memcpy((char *)&pp->refid, WWVREFID, 4);
25954359Sroberto	if (peer->stratum <= 1)
26054359Sroberto		peer->refid = pp->refid;
261132451Sroberto	if (ltemp == 0)
262132451Sroberto		pp->lastref = pp->lastrec;
263132451Sroberto	pp->disp = PST_PHI * ltemp * 60;
26454359Sroberto
26554359Sroberto	/*
26654359Sroberto	 * Process the new sample in the median filter and determine the
26754359Sroberto	 * timecode timestamp.
26854359Sroberto	 */
26954359Sroberto	if (!refclock_process(pp))
27054359Sroberto		refclock_report(peer, CEVNT_BADTIME);
271182007Sroberto	else if (peer->disp > MAXDISTANCE)
272182007Sroberto		refclock_receive(peer);
27354359Sroberto}
27454359Sroberto
27554359Sroberto
27654359Sroberto/*
27754359Sroberto * pst_poll - called by the transmit procedure
27854359Sroberto */
27954359Srobertostatic void
28054359Srobertopst_poll(
28154359Sroberto	int unit,
28254359Sroberto	struct peer *peer
28354359Sroberto	)
28454359Sroberto{
28554359Sroberto	register struct pstunit *up;
28654359Sroberto	struct refclockproc *pp;
28754359Sroberto
28854359Sroberto	/*
28954359Sroberto	 * Time to poll the clock. The PSTI/Traconex clock responds to a
29054359Sroberto	 * "QTQDQMT" by returning a timecode in the format specified
291132451Sroberto	 * above. Note there is no checking on state, since this may not
292132451Sroberto	 * be the only customer reading the clock. Only one customer
293132451Sroberto	 * need poll the clock; all others just listen in. If the clock
294132451Sroberto	 * becomes unreachable, declare a timeout and keep going.
29554359Sroberto	 */
29654359Sroberto	pp = peer->procptr;
297285612Sdelphij	up = pp->unitptr;
29854359Sroberto	up->tcswitch = 0;
29954359Sroberto	up->lastptr = pp->a_lastcode;
30054359Sroberto	if (write(pp->io.fd, "QTQDQMT", 6) != 6)
30154359Sroberto		refclock_report(peer, CEVNT_FAULT);
30254359Sroberto	if (pp->coderecv == pp->codeproc) {
30354359Sroberto		refclock_report(peer, CEVNT_TIMEOUT);
30454359Sroberto		return;
30554359Sroberto	}
306132451Sroberto	refclock_receive(peer);
30754359Sroberto	record_clock_stats(&peer->srcadr, pp->a_lastcode);
308132451Sroberto#ifdef DEBUG
309132451Sroberto	if (debug)
310132451Sroberto		printf("pst: timecode %d %s\n", pp->lencode,
311132451Sroberto		    pp->a_lastcode);
312132451Sroberto#endif
313132451Sroberto	pp->polls++;
31454359Sroberto}
31554359Sroberto
31654359Sroberto#else
31754359Srobertoint refclock_pst_int;
31854359Sroberto#endif /* REFCLOCK */
319