xref: /freebsd/contrib/ntp/ntpd/refclock_hpgps.c (revision f5f40dd63bc7acbb5312b26ac1ea1103c12352a6)
1c0b746e5SOllivier Robert /*
2c0b746e5SOllivier Robert  * refclock_hpgps - clock driver for HP 58503A GPS receiver
3c0b746e5SOllivier Robert  */
4224ba2bdSOllivier Robert 
5c0b746e5SOllivier Robert #ifdef HAVE_CONFIG_H
6c0b746e5SOllivier Robert # include <config.h>
7c0b746e5SOllivier Robert #endif
8c0b746e5SOllivier Robert 
9c0b746e5SOllivier Robert #if defined(REFCLOCK) && defined(CLOCK_HPGPS)
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 /* Version 0.1 April  1, 1995
20c0b746e5SOllivier Robert  *         0.2 April 25, 1995
21c0b746e5SOllivier Robert  *             tolerant of missing timecode response prompt and sends
22c0b746e5SOllivier Robert  *             clear status if prompt indicates error;
23c0b746e5SOllivier Robert  *             can use either local time or UTC from receiver;
24c0b746e5SOllivier Robert  *             can get receiver status screen via flag4
25c0b746e5SOllivier Robert  *
26c0b746e5SOllivier Robert  * WARNING!: This driver is UNDER CONSTRUCTION
27c0b746e5SOllivier Robert  * Everything in here should be treated with suspicion.
28c0b746e5SOllivier Robert  * If it looks wrong, it probably is.
29c0b746e5SOllivier Robert  *
30c0b746e5SOllivier Robert  * Comments and/or questions to: Dave Vitanye
31c0b746e5SOllivier Robert  *                               Hewlett Packard Company
32c0b746e5SOllivier Robert  *                               dave@scd.hp.com
33c0b746e5SOllivier Robert  *                               (408) 553-2856
34c0b746e5SOllivier Robert  *
35c0b746e5SOllivier Robert  * Thanks to the author of the PST driver, which was the starting point for
36c0b746e5SOllivier Robert  * this one.
37c0b746e5SOllivier Robert  *
38c0b746e5SOllivier Robert  * This driver supports the HP 58503A Time and Frequency Reference Receiver.
39c0b746e5SOllivier Robert  * This receiver uses HP SmartClock (TM) to implement an Enhanced GPS receiver.
40c0b746e5SOllivier Robert  * The receiver accuracy when locked to GPS in normal operation is better
41c0b746e5SOllivier Robert  * than 1 usec. The accuracy when operating in holdover is typically better
42c0b746e5SOllivier Robert  * than 10 usec. per day.
43c0b746e5SOllivier Robert  *
44ea906c41SOllivier Robert  * The same driver also handles the HP Z3801A which is available surplus
45ea906c41SOllivier Robert  * from the cell phone industry.  It's popular with hams.
46ea906c41SOllivier Robert  * It needs a different line setup: 19200 baud, 7 data bits, odd parity
47ea906c41SOllivier Robert  * That is selected by adding "mode 1" to the server line in ntp.conf
48ea906c41SOllivier Robert  * HP Z3801A code from Jeff Mock added by Hal Murray, Sep 2005
49ea906c41SOllivier Robert  *
50ea906c41SOllivier Robert  *
51c0b746e5SOllivier Robert  * The receiver should be operated with factory default settings.
52c0b746e5SOllivier Robert  * Initial driver operation: expects the receiver to be already locked
53c0b746e5SOllivier Robert  * to GPS, configured and able to output timecode format 2 messages.
54c0b746e5SOllivier Robert  *
55c0b746e5SOllivier Robert  * The driver uses the poll sequence :PTIME:TCODE? to get a response from
56c0b746e5SOllivier Robert  * the receiver. The receiver responds with a timecode string of ASCII
57c0b746e5SOllivier Robert  * printing characters, followed by a <cr><lf>, followed by a prompt string
58c0b746e5SOllivier Robert  * issued by the receiver, in the following format:
59c0b746e5SOllivier Robert  * T#yyyymmddhhmmssMFLRVcc<cr><lf>scpi >
60c0b746e5SOllivier Robert  *
61c0b746e5SOllivier Robert  * The driver processes the response at the <cr> and <lf>, so what the
62c0b746e5SOllivier Robert  * driver sees is the prompt from the previous poll, followed by this
63c0b746e5SOllivier Robert  * timecode. The prompt from the current poll is (usually) left unread until
64c0b746e5SOllivier Robert  * the next poll. So (except on the very first poll) the driver sees this:
65c0b746e5SOllivier Robert  *
66c0b746e5SOllivier Robert  * scpi > T#yyyymmddhhmmssMFLRVcc<cr><lf>
67c0b746e5SOllivier Robert  *
68c0b746e5SOllivier Robert  * The T is the on-time character, at 980 msec. before the next 1PPS edge.
69c0b746e5SOllivier Robert  * The # is the timecode format type. We look for format 2.
70c0b746e5SOllivier Robert  * Without any of the CLK or PPS stuff, then, the receiver buffer timestamp
71c0b746e5SOllivier Robert  * at the <cr> is 24 characters later, which is about 25 msec. at 9600 bps,
72c0b746e5SOllivier Robert  * so the first approximation for fudge time1 is nominally -0.955 seconds.
73c0b746e5SOllivier Robert  * This number probably needs adjusting for each machine / OS type, so far:
74c0b746e5SOllivier Robert  *  -0.955000 on an HP 9000 Model 712/80 HP-UX 9.05
75c0b746e5SOllivier Robert  *  -0.953175 on an HP 9000 Model 370    HP-UX 9.10
76c0b746e5SOllivier Robert  *
77c0b746e5SOllivier Robert  * This receiver also provides a 1PPS signal, but I haven't figured out
78c0b746e5SOllivier Robert  * how to deal with any of the CLK or PPS stuff yet. Stay tuned.
79c0b746e5SOllivier Robert  *
80c0b746e5SOllivier Robert  */
81c0b746e5SOllivier Robert 
82c0b746e5SOllivier Robert /*
83c0b746e5SOllivier Robert  * Fudge Factors
84c0b746e5SOllivier Robert  *
85c0b746e5SOllivier Robert  * Fudge time1 is used to accomodate the timecode serial interface adjustment.
86c0b746e5SOllivier Robert  * Fudge flag4 can be set to request a receiver status screen summary, which
87c0b746e5SOllivier Robert  * is recorded in the clockstats file.
88c0b746e5SOllivier Robert  */
89c0b746e5SOllivier Robert 
90c0b746e5SOllivier Robert /*
91c0b746e5SOllivier Robert  * Interface definitions
92c0b746e5SOllivier Robert  */
93c0b746e5SOllivier Robert #define	DEVICE		"/dev/hpgps%d" /* device name and unit */
94c0b746e5SOllivier Robert #define	SPEED232	B9600	/* uart speed (9600 baud) */
95ea906c41SOllivier Robert #define	SPEED232Z	B19200	/* uart speed (19200 baud) */
96c0b746e5SOllivier Robert #define	PRECISION	(-10)	/* precision assumed (about 1 ms) */
97c0b746e5SOllivier Robert #define	REFID		"GPS\0"	/*  reference ID */
98c0b746e5SOllivier Robert #define	DESCRIPTION	"HP 58503A GPS Time and Frequency Reference Receiver"
99c0b746e5SOllivier Robert 
100c0b746e5SOllivier Robert #define SMAX            23*80+1 /* for :SYSTEM:PRINT? status screen response */
101c0b746e5SOllivier Robert 
102c0b746e5SOllivier Robert #define MTZONE          2       /* number of fields in timezone reply */
103c0b746e5SOllivier Robert #define MTCODET2        12      /* number of fields in timecode format T2 */
104c0b746e5SOllivier Robert #define NTCODET2        21      /* number of chars to checksum in format T2 */
105c0b746e5SOllivier Robert 
106c0b746e5SOllivier Robert /*
107c0b746e5SOllivier Robert  * Tables to compute the day of year from yyyymmdd timecode.
108c0b746e5SOllivier Robert  * Viva la leap.
109c0b746e5SOllivier Robert  */
110c0b746e5SOllivier Robert static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
111c0b746e5SOllivier Robert static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
112c0b746e5SOllivier Robert 
113c0b746e5SOllivier Robert /*
114c0b746e5SOllivier Robert  * Unit control structure
115c0b746e5SOllivier Robert  */
116c0b746e5SOllivier Robert struct hpgpsunit {
117c0b746e5SOllivier Robert 	int	pollcnt;	/* poll message counter */
118c0b746e5SOllivier Robert 	int     tzhour;         /* timezone offset, hours */
119c0b746e5SOllivier Robert 	int     tzminute;       /* timezone offset, minutes */
120c0b746e5SOllivier Robert 	int     linecnt;        /* set for expected multiple line responses */
121c0b746e5SOllivier Robert 	char	*lastptr;	/* pointer to receiver response data */
122c0b746e5SOllivier Robert 	char    statscrn[SMAX]; /* receiver status screen buffer */
123c0b746e5SOllivier Robert };
124c0b746e5SOllivier Robert 
125c0b746e5SOllivier Robert /*
126c0b746e5SOllivier Robert  * Function prototypes
127c0b746e5SOllivier Robert  */
1282b15cb3dSCy Schubert static	int	hpgps_start	(int, struct peer *);
1292b15cb3dSCy Schubert static	void	hpgps_shutdown	(int, struct peer *);
1302b15cb3dSCy Schubert static	void	hpgps_receive	(struct recvbuf *);
1312b15cb3dSCy Schubert static	void	hpgps_poll	(int, struct peer *);
132c0b746e5SOllivier Robert 
133c0b746e5SOllivier Robert /*
134c0b746e5SOllivier Robert  * Transfer vector
135c0b746e5SOllivier Robert  */
136c0b746e5SOllivier Robert struct	refclock refclock_hpgps = {
137c0b746e5SOllivier Robert 	hpgps_start,		/* start up driver */
138c0b746e5SOllivier Robert 	hpgps_shutdown,		/* shut down driver */
139c0b746e5SOllivier Robert 	hpgps_poll,		/* transmit poll message */
140c0b746e5SOllivier Robert 	noentry,		/* not used (old hpgps_control) */
141c0b746e5SOllivier Robert 	noentry,		/* initialize driver */
142c0b746e5SOllivier Robert 	noentry,		/* not used (old hpgps_buginfo) */
143c0b746e5SOllivier Robert 	NOFLAGS			/* not used */
144c0b746e5SOllivier Robert };
145c0b746e5SOllivier Robert 
146c0b746e5SOllivier Robert 
147c0b746e5SOllivier Robert /*
148c0b746e5SOllivier Robert  * hpgps_start - open the devices and initialize data for processing
149c0b746e5SOllivier Robert  */
150c0b746e5SOllivier Robert static int
151c0b746e5SOllivier Robert hpgps_start(
152c0b746e5SOllivier Robert 	int unit,
153c0b746e5SOllivier Robert 	struct peer *peer
154c0b746e5SOllivier Robert 	)
155c0b746e5SOllivier Robert {
156c0b746e5SOllivier Robert 	register struct hpgpsunit *up;
157c0b746e5SOllivier Robert 	struct refclockproc *pp;
158c0b746e5SOllivier Robert 	int fd;
1592b15cb3dSCy Schubert 	int speed, ldisc;
160c0b746e5SOllivier Robert 	char device[20];
161c0b746e5SOllivier Robert 
162c0b746e5SOllivier Robert 	/*
163c0b746e5SOllivier Robert 	 * Open serial port. Use CLK line discipline, if available.
164ea906c41SOllivier Robert 	 * Default is HP 58503A, mode arg selects HP Z3801A
165c0b746e5SOllivier Robert 	 */
1662b15cb3dSCy Schubert 	snprintf(device, sizeof(device), DEVICE, unit);
1672b15cb3dSCy Schubert 	ldisc = LDISC_CLK;
1682b15cb3dSCy Schubert 	speed = SPEED232;
169ea906c41SOllivier Robert 	/* mode parameter to server config line shares ttl slot */
1702b15cb3dSCy Schubert 	if (1 == peer->ttl) {
1712b15cb3dSCy Schubert 		ldisc |= LDISC_7O1;
1722b15cb3dSCy Schubert 		speed = SPEED232Z;
173ea906c41SOllivier Robert 	}
174a466cc55SCy Schubert 	fd = refclock_open(&peer->srcadr, device, speed, ldisc);
1752b15cb3dSCy Schubert 	if (fd <= 0)
1762b15cb3dSCy Schubert 		return (0);
177c0b746e5SOllivier Robert 	/*
178c0b746e5SOllivier Robert 	 * Allocate and initialize unit structure
179c0b746e5SOllivier Robert 	 */
1802b15cb3dSCy Schubert 	up = emalloc_zero(sizeof(*up));
181c0b746e5SOllivier Robert 	pp = peer->procptr;
182c0b746e5SOllivier Robert 	pp->io.clock_recv = hpgps_receive;
1832b15cb3dSCy Schubert 	pp->io.srcclock = peer;
184c0b746e5SOllivier Robert 	pp->io.datalen = 0;
185c0b746e5SOllivier Robert 	pp->io.fd = fd;
186c0b746e5SOllivier Robert 	if (!io_addclock(&pp->io)) {
1872b15cb3dSCy Schubert 		close(fd);
1882b15cb3dSCy Schubert 		pp->io.fd = -1;
189c0b746e5SOllivier Robert 		free(up);
190c0b746e5SOllivier Robert 		return (0);
191c0b746e5SOllivier Robert 	}
1922b15cb3dSCy Schubert 	pp->unitptr = up;
193c0b746e5SOllivier Robert 
194c0b746e5SOllivier Robert 	/*
195c0b746e5SOllivier Robert 	 * Initialize miscellaneous variables
196c0b746e5SOllivier Robert 	 */
197c0b746e5SOllivier Robert 	peer->precision = PRECISION;
198c0b746e5SOllivier Robert 	pp->clockdesc = DESCRIPTION;
199c0b746e5SOllivier Robert 	memcpy((char *)&pp->refid, REFID, 4);
200c0b746e5SOllivier Robert 	up->tzhour = 0;
201c0b746e5SOllivier Robert 	up->tzminute = 0;
202c0b746e5SOllivier Robert 
203c0b746e5SOllivier Robert 	*up->statscrn = '\0';
204c0b746e5SOllivier Robert 	up->lastptr = up->statscrn;
205c0b746e5SOllivier Robert 	up->pollcnt = 2;
206c0b746e5SOllivier Robert 
207c0b746e5SOllivier Robert 	/*
208c0b746e5SOllivier Robert 	 * Get the identifier string, which is logged but otherwise ignored,
209c0b746e5SOllivier Robert 	 * and get the local timezone information
210c0b746e5SOllivier Robert 	 */
211c0b746e5SOllivier Robert 	up->linecnt = 1;
212a466cc55SCy Schubert 	if (refclock_write(peer, "*IDN?\r:PTIME:TZONE?\r", 20, NULL) != 20)
213c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_FAULT);
214c0b746e5SOllivier Robert 
215c0b746e5SOllivier Robert 	return (1);
216c0b746e5SOllivier Robert }
217c0b746e5SOllivier Robert 
218c0b746e5SOllivier Robert 
219c0b746e5SOllivier Robert /*
220c0b746e5SOllivier Robert  * hpgps_shutdown - shut down the clock
221c0b746e5SOllivier Robert  */
222c0b746e5SOllivier Robert static void
223c0b746e5SOllivier Robert hpgps_shutdown(
224c0b746e5SOllivier Robert 	int unit,
225c0b746e5SOllivier Robert 	struct peer *peer
226c0b746e5SOllivier Robert 	)
227c0b746e5SOllivier Robert {
228c0b746e5SOllivier Robert 	register struct hpgpsunit *up;
229c0b746e5SOllivier Robert 	struct refclockproc *pp;
230c0b746e5SOllivier Robert 
231c0b746e5SOllivier Robert 	pp = peer->procptr;
2322b15cb3dSCy Schubert 	up = pp->unitptr;
2332b15cb3dSCy Schubert 	if (-1 != pp->io.fd)
234c0b746e5SOllivier Robert 		io_closeclock(&pp->io);
2352b15cb3dSCy Schubert 	if (NULL != up)
236c0b746e5SOllivier Robert 		free(up);
237c0b746e5SOllivier Robert }
238c0b746e5SOllivier Robert 
239c0b746e5SOllivier Robert 
240c0b746e5SOllivier Robert /*
241c0b746e5SOllivier Robert  * hpgps_receive - receive data from the serial interface
242c0b746e5SOllivier Robert  */
243c0b746e5SOllivier Robert static void
244c0b746e5SOllivier Robert hpgps_receive(
245c0b746e5SOllivier Robert 	struct recvbuf *rbufp
246c0b746e5SOllivier Robert 	)
247c0b746e5SOllivier Robert {
248c0b746e5SOllivier Robert 	register struct hpgpsunit *up;
249c0b746e5SOllivier Robert 	struct refclockproc *pp;
250c0b746e5SOllivier Robert 	struct peer *peer;
251c0b746e5SOllivier Robert 	l_fp trtmp;
252c0b746e5SOllivier Robert 	char tcodechar1;        /* identifies timecode format */
253c0b746e5SOllivier Robert 	char tcodechar2;        /* identifies timecode format */
254c0b746e5SOllivier Robert 	char timequal;          /* time figure of merit: 0-9 */
255c0b746e5SOllivier Robert 	char freqqual;          /* frequency figure of merit: 0-3 */
256c0b746e5SOllivier Robert 	char leapchar;          /* leapsecond: + or 0 or - */
257c0b746e5SOllivier Robert 	char servchar;          /* request for service: 0 = no, 1 = yes */
258c0b746e5SOllivier Robert 	char syncchar;          /* time info is invalid: 0 = no, 1 = yes */
259c0b746e5SOllivier Robert 	short expectedsm;       /* expected timecode byte checksum */
260c0b746e5SOllivier Robert 	short tcodechksm;       /* computed timecode byte checksum */
261c0b746e5SOllivier Robert 	int i,m,n;
262c0b746e5SOllivier Robert 	int month, day, lastday;
263c0b746e5SOllivier Robert 	char *tcp;              /* timecode pointer (skips over the prompt) */
264c0b746e5SOllivier Robert 	char prompt[BMAX];      /* prompt in response from receiver */
265c0b746e5SOllivier Robert 
266c0b746e5SOllivier Robert 	/*
267c0b746e5SOllivier Robert 	 * Initialize pointers and read the receiver response
268c0b746e5SOllivier Robert 	 */
2692b15cb3dSCy Schubert 	peer = rbufp->recv_peer;
270c0b746e5SOllivier Robert 	pp = peer->procptr;
2712b15cb3dSCy Schubert 	up = pp->unitptr;
272c0b746e5SOllivier Robert 	*pp->a_lastcode = '\0';
273c0b746e5SOllivier Robert 	pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
274c0b746e5SOllivier Robert 
275c0b746e5SOllivier Robert #ifdef DEBUG
276c0b746e5SOllivier Robert 	if (debug)
277c0b746e5SOllivier Robert 	    printf("hpgps: lencode: %d timecode:%s\n",
278c0b746e5SOllivier Robert 		   pp->lencode, pp->a_lastcode);
279c0b746e5SOllivier Robert #endif
280c0b746e5SOllivier Robert 
281c0b746e5SOllivier Robert 	/*
282c0b746e5SOllivier Robert 	 * If there's no characters in the reply, we can quit now
283c0b746e5SOllivier Robert 	 */
284c0b746e5SOllivier Robert 	if (pp->lencode == 0)
285c0b746e5SOllivier Robert 	    return;
286c0b746e5SOllivier Robert 
287c0b746e5SOllivier Robert 	/*
288c0b746e5SOllivier Robert 	 * If linecnt is greater than zero, we are getting information only,
289c0b746e5SOllivier Robert 	 * such as the receiver identification string or the receiver status
290c0b746e5SOllivier Robert 	 * screen, so put the receiver response at the end of the status
291c0b746e5SOllivier Robert 	 * screen buffer. When we have the last line, write the buffer to
292c0b746e5SOllivier Robert 	 * the clockstats file and return without further processing.
293c0b746e5SOllivier Robert 	 *
294c0b746e5SOllivier Robert 	 * If linecnt is zero, we are expecting either the timezone
295c0b746e5SOllivier Robert 	 * or a timecode. At this point, also write the response
296c0b746e5SOllivier Robert 	 * to the clockstats file, and go on to process the prompt (if any),
297c0b746e5SOllivier Robert 	 * timezone, or timecode and timestamp.
298c0b746e5SOllivier Robert 	 */
299c0b746e5SOllivier Robert 
300c0b746e5SOllivier Robert 
301c0b746e5SOllivier Robert 	if (up->linecnt-- > 0) {
302c0b746e5SOllivier Robert 		if ((int)(pp->lencode + 2) <= (SMAX - (up->lastptr - up->statscrn))) {
303c0b746e5SOllivier Robert 			*up->lastptr++ = '\n';
3042b15cb3dSCy Schubert 			memcpy(up->lastptr, pp->a_lastcode, pp->lencode);
305c0b746e5SOllivier Robert 			up->lastptr += pp->lencode;
306c0b746e5SOllivier Robert 		}
307c0b746e5SOllivier Robert 		if (up->linecnt == 0)
308c0b746e5SOllivier Robert 		    record_clock_stats(&peer->srcadr, up->statscrn);
309c0b746e5SOllivier Robert 
310c0b746e5SOllivier Robert 		return;
311c0b746e5SOllivier Robert 	}
312c0b746e5SOllivier Robert 
313c0b746e5SOllivier Robert 	record_clock_stats(&peer->srcadr, pp->a_lastcode);
314c0b746e5SOllivier Robert 	pp->lastrec = trtmp;
315c0b746e5SOllivier Robert 
316c0b746e5SOllivier Robert 	up->lastptr = up->statscrn;
317c0b746e5SOllivier Robert 	*up->lastptr = '\0';
318c0b746e5SOllivier Robert 	up->pollcnt = 2;
319c0b746e5SOllivier Robert 
320c0b746e5SOllivier Robert 	/*
321c0b746e5SOllivier Robert 	 * We get down to business: get a prompt if one is there, issue
322c0b746e5SOllivier Robert 	 * a clear status command if it contains an error indication.
323c0b746e5SOllivier Robert 	 * Next, check for either the timezone reply or the timecode reply
324c0b746e5SOllivier Robert 	 * and decode it.  If we don't recognize the reply, or don't get the
325c0b746e5SOllivier Robert 	 * proper number of decoded fields, or get an out of range timezone,
326c0b746e5SOllivier Robert 	 * or if the timecode checksum is bad, then we declare bad format
327c0b746e5SOllivier Robert 	 * and exit.
328c0b746e5SOllivier Robert 	 *
329c0b746e5SOllivier Robert 	 * Timezone format (including nominal prompt):
330c0b746e5SOllivier Robert 	 * scpi > -H,-M<cr><lf>
331c0b746e5SOllivier Robert 	 *
332c0b746e5SOllivier Robert 	 * Timecode format (including nominal prompt):
333c0b746e5SOllivier Robert 	 * scpi > T2yyyymmddhhmmssMFLRVcc<cr><lf>
334c0b746e5SOllivier Robert 	 *
335c0b746e5SOllivier Robert 	 */
336c0b746e5SOllivier Robert 
3372b15cb3dSCy Schubert 	strlcpy(prompt, pp->a_lastcode, sizeof(prompt));
338c0b746e5SOllivier Robert 	tcp = strrchr(pp->a_lastcode,'>');
339c0b746e5SOllivier Robert 	if (tcp == NULL)
340c0b746e5SOllivier Robert 	    tcp = pp->a_lastcode;
341c0b746e5SOllivier Robert 	else
342c0b746e5SOllivier Robert 	    tcp++;
343c0b746e5SOllivier Robert 	prompt[tcp - pp->a_lastcode] = '\0';
344c0b746e5SOllivier Robert 	while ((*tcp == ' ') || (*tcp == '\t')) tcp++;
345c0b746e5SOllivier Robert 
346c0b746e5SOllivier Robert 	/*
347c0b746e5SOllivier Robert 	 * deal with an error indication in the prompt here
348c0b746e5SOllivier Robert 	 */
349c0b746e5SOllivier Robert 	if (strrchr(prompt,'E') > strrchr(prompt,'s')){
350c0b746e5SOllivier Robert #ifdef DEBUG
351c0b746e5SOllivier Robert 		if (debug)
352c0b746e5SOllivier Robert 			printf("hpgps: error indicated in prompt: %s\n", prompt);
353c0b746e5SOllivier Robert #endif
354a466cc55SCy Schubert 		if (refclock_write(peer, "*CLS\r\r", 6, NULL) != 6)
355c0b746e5SOllivier Robert 			refclock_report(peer, CEVNT_FAULT);
356c0b746e5SOllivier Robert 	}
357c0b746e5SOllivier Robert 
358c0b746e5SOllivier Robert 	/*
359c0b746e5SOllivier Robert 	 * make sure we got a timezone or timecode format and
360c0b746e5SOllivier Robert 	 * then process accordingly
361c0b746e5SOllivier Robert 	 */
362c0b746e5SOllivier Robert 	m = sscanf(tcp,"%c%c", &tcodechar1, &tcodechar2);
363c0b746e5SOllivier Robert 
364c0b746e5SOllivier Robert 	if (m != 2){
365c0b746e5SOllivier Robert #ifdef DEBUG
366c0b746e5SOllivier Robert 		if (debug)
367c0b746e5SOllivier Robert 		    printf("hpgps: no format indicator\n");
368c0b746e5SOllivier Robert #endif
369c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_BADREPLY);
370c0b746e5SOllivier Robert 		return;
371c0b746e5SOllivier Robert 	}
372c0b746e5SOllivier Robert 
373c0b746e5SOllivier Robert 	switch (tcodechar1) {
374c0b746e5SOllivier Robert 
375c0b746e5SOllivier Robert 	    case '+':
376c0b746e5SOllivier Robert 	    case '-':
377c0b746e5SOllivier Robert 		m = sscanf(tcp,"%d,%d", &up->tzhour, &up->tzminute);
378c0b746e5SOllivier Robert 		if (m != MTZONE) {
379c0b746e5SOllivier Robert #ifdef DEBUG
380c0b746e5SOllivier Robert 			if (debug)
381c0b746e5SOllivier Robert 			    printf("hpgps: only %d fields recognized in timezone\n", m);
382c0b746e5SOllivier Robert #endif
383c0b746e5SOllivier Robert 			refclock_report(peer, CEVNT_BADREPLY);
384c0b746e5SOllivier Robert 			return;
385c0b746e5SOllivier Robert 		}
386c0b746e5SOllivier Robert 		if ((up->tzhour < -12) || (up->tzhour > 13) ||
387c0b746e5SOllivier Robert 		    (up->tzminute < -59) || (up->tzminute > 59)){
388c0b746e5SOllivier Robert #ifdef DEBUG
389c0b746e5SOllivier Robert 			if (debug)
390c0b746e5SOllivier Robert 			    printf("hpgps: timezone %d, %d out of range\n",
391c0b746e5SOllivier Robert 				   up->tzhour, up->tzminute);
392c0b746e5SOllivier Robert #endif
393c0b746e5SOllivier Robert 			refclock_report(peer, CEVNT_BADREPLY);
394c0b746e5SOllivier Robert 			return;
395c0b746e5SOllivier Robert 		}
396c0b746e5SOllivier Robert 		return;
397c0b746e5SOllivier Robert 
398c0b746e5SOllivier Robert 	    case 'T':
399c0b746e5SOllivier Robert 		break;
400c0b746e5SOllivier Robert 
401c0b746e5SOllivier Robert 	    default:
402c0b746e5SOllivier Robert #ifdef DEBUG
403c0b746e5SOllivier Robert 		if (debug)
404c0b746e5SOllivier Robert 		    printf("hpgps: unrecognized reply format %c%c\n",
405c0b746e5SOllivier Robert 			   tcodechar1, tcodechar2);
406c0b746e5SOllivier Robert #endif
407c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_BADREPLY);
408c0b746e5SOllivier Robert 		return;
409c0b746e5SOllivier Robert 	} /* end of tcodechar1 switch */
410c0b746e5SOllivier Robert 
411c0b746e5SOllivier Robert 
412c0b746e5SOllivier Robert 	switch (tcodechar2) {
413c0b746e5SOllivier Robert 
414c0b746e5SOllivier Robert 	    case '2':
415c0b746e5SOllivier Robert 		m = sscanf(tcp,"%*c%*c%4d%2d%2d%2d%2d%2d%c%c%c%c%c%2hx",
416c0b746e5SOllivier Robert 			   &pp->year, &month, &day, &pp->hour, &pp->minute, &pp->second,
417c0b746e5SOllivier Robert 			   &timequal, &freqqual, &leapchar, &servchar, &syncchar,
418c0b746e5SOllivier Robert 			   &expectedsm);
419c0b746e5SOllivier Robert 		n = NTCODET2;
420c0b746e5SOllivier Robert 
421c0b746e5SOllivier Robert 		if (m != MTCODET2){
422c0b746e5SOllivier Robert #ifdef DEBUG
423c0b746e5SOllivier Robert 			if (debug)
424c0b746e5SOllivier Robert 			    printf("hpgps: only %d fields recognized in timecode\n", m);
425c0b746e5SOllivier Robert #endif
426c0b746e5SOllivier Robert 			refclock_report(peer, CEVNT_BADREPLY);
427c0b746e5SOllivier Robert 			return;
428c0b746e5SOllivier Robert 		}
429c0b746e5SOllivier Robert 		break;
430c0b746e5SOllivier Robert 
431c0b746e5SOllivier Robert 	    default:
432c0b746e5SOllivier Robert #ifdef DEBUG
433c0b746e5SOllivier Robert 		if (debug)
434c0b746e5SOllivier Robert 		    printf("hpgps: unrecognized timecode format %c%c\n",
435c0b746e5SOllivier Robert 			   tcodechar1, tcodechar2);
436c0b746e5SOllivier Robert #endif
437c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_BADREPLY);
438c0b746e5SOllivier Robert 		return;
439c0b746e5SOllivier Robert 	} /* end of tcodechar2 format switch */
440c0b746e5SOllivier Robert 
441c0b746e5SOllivier Robert 	/*
442c0b746e5SOllivier Robert 	 * Compute and verify the checksum.
443c0b746e5SOllivier Robert 	 * Characters are summed starting at tcodechar1, ending at just
444c0b746e5SOllivier Robert 	 * before the expected checksum.  Bail out if incorrect.
445c0b746e5SOllivier Robert 	 */
446c0b746e5SOllivier Robert 	tcodechksm = 0;
447c0b746e5SOllivier Robert 	while (n-- > 0) tcodechksm += *tcp++;
448c0b746e5SOllivier Robert 	tcodechksm &= 0x00ff;
449c0b746e5SOllivier Robert 
450c0b746e5SOllivier Robert 	if (tcodechksm != expectedsm) {
451c0b746e5SOllivier Robert #ifdef DEBUG
452c0b746e5SOllivier Robert 		if (debug)
453c0b746e5SOllivier Robert 		    printf("hpgps: checksum %2hX doesn't match %2hX expected\n",
454c0b746e5SOllivier Robert 			   tcodechksm, expectedsm);
455c0b746e5SOllivier Robert #endif
456c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_BADREPLY);
457c0b746e5SOllivier Robert 		return;
458c0b746e5SOllivier Robert 	}
459c0b746e5SOllivier Robert 
460c0b746e5SOllivier Robert 	/*
461c0b746e5SOllivier Robert 	 * Compute the day of year from the yyyymmdd format.
462c0b746e5SOllivier Robert 	 */
463c0b746e5SOllivier Robert 	if (month < 1 || month > 12 || day < 1) {
464c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_BADTIME);
465c0b746e5SOllivier Robert 		return;
466c0b746e5SOllivier Robert 	}
467c0b746e5SOllivier Robert 
468c0b746e5SOllivier Robert 	if ( ! isleap_4(pp->year) ) {				/* Y2KFixes */
469c0b746e5SOllivier Robert 		/* not a leap year */
470c0b746e5SOllivier Robert 		if (day > day1tab[month - 1]) {
471c0b746e5SOllivier Robert 			refclock_report(peer, CEVNT_BADTIME);
472c0b746e5SOllivier Robert 			return;
473c0b746e5SOllivier Robert 		}
474c0b746e5SOllivier Robert 		for (i = 0; i < month - 1; i++) day += day1tab[i];
475c0b746e5SOllivier Robert 		lastday = 365;
476c0b746e5SOllivier Robert 	} else {
477c0b746e5SOllivier Robert 		/* a leap year */
478c0b746e5SOllivier Robert 		if (day > day2tab[month - 1]) {
479c0b746e5SOllivier Robert 			refclock_report(peer, CEVNT_BADTIME);
480c0b746e5SOllivier Robert 			return;
481c0b746e5SOllivier Robert 		}
482c0b746e5SOllivier Robert 		for (i = 0; i < month - 1; i++) day += day2tab[i];
483c0b746e5SOllivier Robert 		lastday = 366;
484c0b746e5SOllivier Robert 	}
485c0b746e5SOllivier Robert 
486c0b746e5SOllivier Robert 	/*
487c0b746e5SOllivier Robert 	 * Deal with the timezone offset here. The receiver timecode is in
488c0b746e5SOllivier Robert 	 * local time = UTC + :PTIME:TZONE, so SUBTRACT the timezone values.
489c0b746e5SOllivier Robert 	 * For example, Pacific Standard Time is -8 hours , 0 minutes.
490c0b746e5SOllivier Robert 	 * Deal with the underflows and overflows.
491c0b746e5SOllivier Robert 	 */
492c0b746e5SOllivier Robert 	pp->minute -= up->tzminute;
493c0b746e5SOllivier Robert 	pp->hour -= up->tzhour;
494c0b746e5SOllivier Robert 
495c0b746e5SOllivier Robert 	if (pp->minute < 0) {
496c0b746e5SOllivier Robert 		pp->minute += 60;
497c0b746e5SOllivier Robert 		pp->hour--;
498c0b746e5SOllivier Robert 	}
499c0b746e5SOllivier Robert 	if (pp->minute > 59) {
500c0b746e5SOllivier Robert 		pp->minute -= 60;
501c0b746e5SOllivier Robert 		pp->hour++;
502c0b746e5SOllivier Robert 	}
503c0b746e5SOllivier Robert 	if (pp->hour < 0)  {
504c0b746e5SOllivier Robert 		pp->hour += 24;
505c0b746e5SOllivier Robert 		day--;
506c0b746e5SOllivier Robert 		if (day < 1) {
507c0b746e5SOllivier Robert 			pp->year--;
508c0b746e5SOllivier Robert 			if ( isleap_4(pp->year) )		/* Y2KFixes */
509c0b746e5SOllivier Robert 			    day = 366;
510c0b746e5SOllivier Robert 			else
511c0b746e5SOllivier Robert 			    day = 365;
512c0b746e5SOllivier Robert 		}
513c0b746e5SOllivier Robert 	}
514c0b746e5SOllivier Robert 
515c0b746e5SOllivier Robert 	if (pp->hour > 23) {
516c0b746e5SOllivier Robert 		pp->hour -= 24;
517c0b746e5SOllivier Robert 		day++;
518c0b746e5SOllivier Robert 		if (day > lastday) {
519c0b746e5SOllivier Robert 			pp->year++;
520c0b746e5SOllivier Robert 			day = 1;
521c0b746e5SOllivier Robert 		}
522c0b746e5SOllivier Robert 	}
523c0b746e5SOllivier Robert 
524c0b746e5SOllivier Robert 	pp->day = day;
525c0b746e5SOllivier Robert 
526c0b746e5SOllivier Robert 	/*
527c0b746e5SOllivier Robert 	 * Decode the MFLRV indicators.
528c0b746e5SOllivier Robert 	 * NEED TO FIGURE OUT how to deal with the request for service,
529c0b746e5SOllivier Robert 	 * time quality, and frequency quality indicators some day.
530c0b746e5SOllivier Robert 	 */
531c0b746e5SOllivier Robert 	if (syncchar != '0') {
532c0b746e5SOllivier Robert 		pp->leap = LEAP_NOTINSYNC;
533c0b746e5SOllivier Robert 	}
534c0b746e5SOllivier Robert 	else {
5352b15cb3dSCy Schubert 		pp->leap = LEAP_NOWARNING;
536c0b746e5SOllivier Robert 		switch (leapchar) {
537c0b746e5SOllivier Robert 
5382b15cb3dSCy Schubert 		    case '0':
5392b15cb3dSCy Schubert 			break;
5402b15cb3dSCy Schubert 
5412b15cb3dSCy Schubert 		    /* See http://bugs.ntp.org/1090
5422b15cb3dSCy Schubert 		     * Ignore leap announcements unless June or December.
5432b15cb3dSCy Schubert 		     * Better would be to use :GPSTime? to find the month,
5442b15cb3dSCy Schubert 		     * but that seems too likely to introduce other bugs.
5452b15cb3dSCy Schubert 		     */
546c0b746e5SOllivier Robert 		    case '+':
5472b15cb3dSCy Schubert 			if ((month==6) || (month==12))
548c0b746e5SOllivier Robert 			    pp->leap = LEAP_ADDSECOND;
549c0b746e5SOllivier Robert 			break;
550c0b746e5SOllivier Robert 
551c0b746e5SOllivier Robert 		    case '-':
5522b15cb3dSCy Schubert 			if ((month==6) || (month==12))
553c0b746e5SOllivier Robert 			    pp->leap = LEAP_DELSECOND;
554c0b746e5SOllivier Robert 			break;
555c0b746e5SOllivier Robert 
556c0b746e5SOllivier Robert 		    default:
557c0b746e5SOllivier Robert #ifdef DEBUG
558c0b746e5SOllivier Robert 			if (debug)
559c0b746e5SOllivier Robert 			    printf("hpgps: unrecognized leap indicator: %c\n",
560c0b746e5SOllivier Robert 				   leapchar);
561c0b746e5SOllivier Robert #endif
562c0b746e5SOllivier Robert 			refclock_report(peer, CEVNT_BADTIME);
563c0b746e5SOllivier Robert 			return;
564c0b746e5SOllivier Robert 		} /* end of leapchar switch */
565c0b746e5SOllivier Robert 	}
566c0b746e5SOllivier Robert 
567c0b746e5SOllivier Robert 	/*
568c0b746e5SOllivier Robert 	 * Process the new sample in the median filter and determine the
569c0b746e5SOllivier Robert 	 * reference clock offset and dispersion. We use lastrec as both
570c0b746e5SOllivier Robert 	 * the reference time and receive time in order to avoid being
571c0b746e5SOllivier Robert 	 * cute, like setting the reference time later than the receive
572c0b746e5SOllivier Robert 	 * time, which may cause a paranoid protocol module to chuck out
573c0b746e5SOllivier Robert 	 * the data.
574c0b746e5SOllivier Robert 	 */
575c0b746e5SOllivier Robert 	if (!refclock_process(pp)) {
576c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_BADTIME);
577c0b746e5SOllivier Robert 		return;
578c0b746e5SOllivier Robert 	}
5799c2daa00SOllivier Robert 	pp->lastref = pp->lastrec;
580c0b746e5SOllivier Robert 	refclock_receive(peer);
581c0b746e5SOllivier Robert 
582c0b746e5SOllivier Robert 	/*
583c0b746e5SOllivier Robert 	 * If CLK_FLAG4 is set, ask for the status screen response.
584c0b746e5SOllivier Robert 	 */
585c0b746e5SOllivier Robert 	if (pp->sloppyclockflag & CLK_FLAG4){
586c0b746e5SOllivier Robert 		up->linecnt = 22;
587a466cc55SCy Schubert 		if (refclock_write(peer, ":SYSTEM:PRINT?\r", 15, NULL) != 15)
588c0b746e5SOllivier Robert 			refclock_report(peer, CEVNT_FAULT);
589c0b746e5SOllivier Robert 	}
590c0b746e5SOllivier Robert }
591c0b746e5SOllivier Robert 
592c0b746e5SOllivier Robert 
593c0b746e5SOllivier Robert /*
594c0b746e5SOllivier Robert  * hpgps_poll - called by the transmit procedure
595c0b746e5SOllivier Robert  */
596c0b746e5SOllivier Robert static void
597c0b746e5SOllivier Robert hpgps_poll(
598c0b746e5SOllivier Robert 	int unit,
599c0b746e5SOllivier Robert 	struct peer *peer
600c0b746e5SOllivier Robert 	)
601c0b746e5SOllivier Robert {
602c0b746e5SOllivier Robert 	register struct hpgpsunit *up;
603c0b746e5SOllivier Robert 	struct refclockproc *pp;
604c0b746e5SOllivier Robert 
605c0b746e5SOllivier Robert 	/*
606c0b746e5SOllivier Robert 	 * Time to poll the clock. The HP 58503A responds to a
607c0b746e5SOllivier Robert 	 * ":PTIME:TCODE?" by returning a timecode in the format specified
608c0b746e5SOllivier Robert 	 * above. If nothing is heard from the clock for two polls,
609c0b746e5SOllivier Robert 	 * declare a timeout and keep going.
610c0b746e5SOllivier Robert 	 */
611c0b746e5SOllivier Robert 	pp = peer->procptr;
6122b15cb3dSCy Schubert 	up = pp->unitptr;
613c0b746e5SOllivier Robert 	if (up->pollcnt == 0)
614c0b746e5SOllivier Robert 	    refclock_report(peer, CEVNT_TIMEOUT);
615c0b746e5SOllivier Robert 	else
616c0b746e5SOllivier Robert 	    up->pollcnt--;
617a466cc55SCy Schubert 	if (refclock_write(peer, ":PTIME:TCODE?\r", 14, NULL) != 14) {
618c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_FAULT);
619c0b746e5SOllivier Robert 	}
620c0b746e5SOllivier Robert 	else
621c0b746e5SOllivier Robert 	    pp->polls++;
622c0b746e5SOllivier Robert }
623c0b746e5SOllivier Robert 
624c0b746e5SOllivier Robert #else
625*f5f40dd6SCy Schubert NONEMPTY_TRANSLATION_UNIT
626c0b746e5SOllivier Robert #endif /* REFCLOCK */
627