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