xref: /freebsd/contrib/ntp/ntpd/refclock_mx4200.c (revision f5f40dd63bc7acbb5312b26ac1ea1103c12352a6)
1c0b746e5SOllivier Robert /*
2c0b746e5SOllivier Robert  * This software was developed by the Computer Systems Engineering group
3c0b746e5SOllivier Robert  * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66.
4c0b746e5SOllivier Robert  *
5c0b746e5SOllivier Robert  * Copyright (c) 1992 The Regents of the University of California.
6c0b746e5SOllivier Robert  * All rights reserved.
7c0b746e5SOllivier Robert  *
8c0b746e5SOllivier Robert  * Redistribution and use in source and binary forms, with or without
9c0b746e5SOllivier Robert  * modification, are permitted provided that the following conditions
10c0b746e5SOllivier Robert  * are met:
11c0b746e5SOllivier Robert  * 1. Redistributions of source code must retain the above copyright
12c0b746e5SOllivier Robert  *    notice, this list of conditions and the following disclaimer.
13c0b746e5SOllivier Robert  * 2. Redistributions in binary form must reproduce the above copyright
14c0b746e5SOllivier Robert  *    notice, this list of conditions and the following disclaimer in the
15c0b746e5SOllivier Robert  *    documentation and/or other materials provided with the distribution.
16c0b746e5SOllivier Robert  * 3. All advertising materials mentioning features or use of this software
17c0b746e5SOllivier Robert  *    must display the following acknowledgement:
18c0b746e5SOllivier Robert  *	This product includes software developed by the University of
19c0b746e5SOllivier Robert  *	California, Lawrence Berkeley Laboratory.
20c0b746e5SOllivier Robert  * 4. The name of the University may not be used to endorse or promote
21c0b746e5SOllivier Robert  *    products derived from this software without specific prior
22c0b746e5SOllivier Robert  *    written permission.
23c0b746e5SOllivier Robert  *
24c0b746e5SOllivier Robert  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25c0b746e5SOllivier Robert  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26c0b746e5SOllivier Robert  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27c0b746e5SOllivier Robert  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28c0b746e5SOllivier Robert  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29c0b746e5SOllivier Robert  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30c0b746e5SOllivier Robert  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31c0b746e5SOllivier Robert  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32c0b746e5SOllivier Robert  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33c0b746e5SOllivier Robert  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34c0b746e5SOllivier Robert  * SUCH DAMAGE.
35c0b746e5SOllivier Robert  */
36c0b746e5SOllivier Robert 
37c0b746e5SOllivier Robert /*
38c0b746e5SOllivier Robert  * Modified: Marc Brett <marc.brett@westgeo.com>   Sept, 1999.
39c0b746e5SOllivier Robert  *
40c0b746e5SOllivier Robert  * 1. Added support for alternate PPS schemes, with code mostly
41c0b746e5SOllivier Robert  *    copied from the Oncore driver (Thanks, Poul-Henning Kamp).
42c0b746e5SOllivier Robert  *    This code runs on SunOS 4.1.3 with ppsclock-1.6a1 and Solaris 7.
43c0b746e5SOllivier Robert  */
44c0b746e5SOllivier Robert 
45c0b746e5SOllivier Robert 
46c0b746e5SOllivier Robert #ifdef HAVE_CONFIG_H
47c0b746e5SOllivier Robert # include <config.h>
48c0b746e5SOllivier Robert #endif
49c0b746e5SOllivier Robert 
50224ba2bdSOllivier Robert #if defined(REFCLOCK) && defined(CLOCK_MX4200) && defined(HAVE_PPSAPI)
51c0b746e5SOllivier Robert 
52c0b746e5SOllivier Robert #include "ntpd.h"
53c0b746e5SOllivier Robert #include "ntp_io.h"
54c0b746e5SOllivier Robert #include "ntp_refclock.h"
55c0b746e5SOllivier Robert #include "ntp_unixtime.h"
56c0b746e5SOllivier Robert #include "ntp_stdlib.h"
57c0b746e5SOllivier Robert 
58224ba2bdSOllivier Robert #include <stdio.h>
59224ba2bdSOllivier Robert #include <ctype.h>
60224ba2bdSOllivier Robert 
61c0b746e5SOllivier Robert #include "mx4200.h"
62c0b746e5SOllivier Robert 
63c0b746e5SOllivier Robert #ifdef HAVE_SYS_TERMIOS_H
64c0b746e5SOllivier Robert # include <sys/termios.h>
65c0b746e5SOllivier Robert #endif
66c0b746e5SOllivier Robert #ifdef HAVE_SYS_PPSCLOCK_H
67c0b746e5SOllivier Robert # include <sys/ppsclock.h>
68c0b746e5SOllivier Robert #endif
69c0b746e5SOllivier Robert 
70c0b746e5SOllivier Robert #ifndef HAVE_STRUCT_PPSCLOCKEV
71c0b746e5SOllivier Robert struct ppsclockev {
72224ba2bdSOllivier Robert # ifdef HAVE_STRUCT_TIMESPEC
73c0b746e5SOllivier Robert 	struct timespec tv;
74c0b746e5SOllivier Robert # else
75c0b746e5SOllivier Robert 	struct timeval tv;
76c0b746e5SOllivier Robert # endif
77c0b746e5SOllivier Robert 	u_int serial;
78c0b746e5SOllivier Robert };
79c0b746e5SOllivier Robert #endif /* ! HAVE_STRUCT_PPSCLOCKEV */
80c0b746e5SOllivier Robert 
81ea906c41SOllivier Robert #ifdef HAVE_PPSAPI
82ea906c41SOllivier Robert # include "ppsapi_timepps.h"
83ea906c41SOllivier Robert #endif /* HAVE_PPSAPI */
84224ba2bdSOllivier Robert 
85c0b746e5SOllivier Robert /*
86c0b746e5SOllivier Robert  * This driver supports the Magnavox Model MX 4200 GPS Receiver
87c0b746e5SOllivier Robert  * adapted to precision timing applications.  It requires the
88c0b746e5SOllivier Robert  * ppsclock line discipline or streams module described in the
89c0b746e5SOllivier Robert  * Line Disciplines and Streams Drivers page. It also requires a
90c0b746e5SOllivier Robert  * gadget box and 1-PPS level converter, such as described in the
91c0b746e5SOllivier Robert  * Pulse-per-second (PPS) Signal Interfacing page.
92c0b746e5SOllivier Robert  *
93c0b746e5SOllivier Robert  * It's likely that other compatible Magnavox receivers such as the
94c0b746e5SOllivier Robert  * MX 4200D, MX 9212, MX 9012R, MX 9112 will be supported by this code.
95c0b746e5SOllivier Robert  */
96c0b746e5SOllivier Robert 
97c0b746e5SOllivier Robert /*
98c0b746e5SOllivier Robert  * Check this every time you edit the code!
99c0b746e5SOllivier Robert  */
100224ba2bdSOllivier Robert #define YEAR_LAST_MODIFIED 2000
101c0b746e5SOllivier Robert 
102c0b746e5SOllivier Robert /*
103c0b746e5SOllivier Robert  * GPS Definitions
104c0b746e5SOllivier Robert  */
105c0b746e5SOllivier Robert #define	DEVICE		"/dev/gps%d"	/* device name and unit */
106c0b746e5SOllivier Robert #define	SPEED232	B4800		/* baud */
107c0b746e5SOllivier Robert 
108c0b746e5SOllivier Robert /*
109c0b746e5SOllivier Robert  * Radio interface parameters
110c0b746e5SOllivier Robert  */
111c0b746e5SOllivier Robert #define	PRECISION	(-18)	/* precision assumed (about 4 us) */
112c0b746e5SOllivier Robert #define	REFID	"GPS\0"		/* reference id */
113c0b746e5SOllivier Robert #define	DESCRIPTION	"Magnavox MX4200 GPS Receiver" /* who we are */
114c0b746e5SOllivier Robert #define	DEFFUDGETIME	0	/* default fudge time (ms) */
115c0b746e5SOllivier Robert 
116c0b746e5SOllivier Robert #define	SLEEPTIME	32	/* seconds to wait for reconfig to complete */
117c0b746e5SOllivier Robert 
118c0b746e5SOllivier Robert /*
119c0b746e5SOllivier Robert  * Position Averaging.
120c0b746e5SOllivier Robert  */
121c0b746e5SOllivier Robert #define INTERVAL	1	/* Interval between position measurements (s) */
122c0b746e5SOllivier Robert #define AVGING_TIME	24	/* Number of hours to average */
123c0b746e5SOllivier Robert #define NOT_INITIALIZED	-9999.	/* initial pivot longitude */
124c0b746e5SOllivier Robert 
125c0b746e5SOllivier Robert /*
126c0b746e5SOllivier Robert  * MX4200 unit control structure.
127c0b746e5SOllivier Robert  */
128c0b746e5SOllivier Robert struct mx4200unit {
129c0b746e5SOllivier Robert 	u_int  pollcnt;			/* poll message counter */
130c0b746e5SOllivier Robert 	u_int  polled;			/* Hand in a time sample? */
131c0b746e5SOllivier Robert 	u_int  lastserial;		/* last pps serial number */
132c0b746e5SOllivier Robert 	struct ppsclockev ppsev;	/* PPS control structure */
133c0b746e5SOllivier Robert 	double avg_lat;			/* average latitude */
134c0b746e5SOllivier Robert 	double avg_lon;			/* average longitude */
135c0b746e5SOllivier Robert 	double avg_alt;			/* average height */
136c0b746e5SOllivier Robert 	double central_meridian;	/* central meridian */
137224ba2bdSOllivier Robert 	double N_fixes;			/* Number of position measurements */
138c0b746e5SOllivier Robert 	int    last_leap;		/* leap second warning */
139c0b746e5SOllivier Robert 	u_int  moving;			/* mobile platform? */
140c0b746e5SOllivier Robert 	u_long sloppyclockflag;		/* fudge flags */
141c0b746e5SOllivier Robert 	u_int  known;			/* position known yet? */
142c0b746e5SOllivier Robert 	u_long clamp_time;		/* when to stop postion averaging */
143c0b746e5SOllivier Robert 	u_long log_time;		/* when to print receiver status */
144224ba2bdSOllivier Robert 	pps_handle_t	pps_h;
145224ba2bdSOllivier Robert 	pps_params_t	pps_p;
146224ba2bdSOllivier Robert 	pps_info_t	pps_i;
147c0b746e5SOllivier Robert };
148c0b746e5SOllivier Robert 
149c0b746e5SOllivier Robert static char pmvxg[] = "PMVXG";
150c0b746e5SOllivier Robert 
151c0b746e5SOllivier Robert /* XXX should be somewhere else */
152c0b746e5SOllivier Robert #ifdef __GNUC__
153c0b746e5SOllivier Robert #if __GNUC__ < 2  || (__GNUC__ == 2 && __GNUC_MINOR__ < 5)
154c0b746e5SOllivier Robert #ifndef __attribute__
155c0b746e5SOllivier Robert #define __attribute__(args)
156a151a66cSOllivier Robert #endif /* __attribute__ */
157a151a66cSOllivier Robert #endif /* __GNUC__ < 2  || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) */
158c0b746e5SOllivier Robert #else
159c0b746e5SOllivier Robert #ifndef __attribute__
160c0b746e5SOllivier Robert #define __attribute__(args)
161a151a66cSOllivier Robert #endif /* __attribute__ */
162a151a66cSOllivier Robert #endif /* __GNUC__ */
163c0b746e5SOllivier Robert /* XXX end */
164c0b746e5SOllivier Robert 
165c0b746e5SOllivier Robert /*
166c0b746e5SOllivier Robert  * Function prototypes
167c0b746e5SOllivier Robert  */
1682b15cb3dSCy Schubert static	int	mx4200_start	(int, struct peer *);
1692b15cb3dSCy Schubert static	void	mx4200_shutdown	(int, struct peer *);
1702b15cb3dSCy Schubert static	void	mx4200_receive	(struct recvbuf *);
1712b15cb3dSCy Schubert static	void	mx4200_poll	(int, struct peer *);
172c0b746e5SOllivier Robert 
1732b15cb3dSCy Schubert static	char *	mx4200_parse_t	(struct peer *);
1742b15cb3dSCy Schubert static	char *	mx4200_parse_p	(struct peer *);
1752b15cb3dSCy Schubert static	char *	mx4200_parse_s	(struct peer *);
1762b15cb3dSCy Schubert int	mx4200_cmpl_fp	(const void *, const void *);
1772b15cb3dSCy Schubert static	int	mx4200_config	(struct peer *);
1782b15cb3dSCy Schubert static	void	mx4200_ref	(struct peer *);
1792b15cb3dSCy Schubert static	void	mx4200_send	(struct peer *, char *, ...)
180c0b746e5SOllivier Robert     __attribute__ ((format (printf, 2, 3)));
1812b15cb3dSCy Schubert static	u_char	mx4200_cksum	(char *, int);
1822b15cb3dSCy Schubert static	int	mx4200_jday	(int, int, int);
1832b15cb3dSCy Schubert static	void	mx4200_debug	(struct peer *, char *, ...)
184c0b746e5SOllivier Robert     __attribute__ ((format (printf, 2, 3)));
1852b15cb3dSCy Schubert static	int	mx4200_pps	(struct peer *);
186c0b746e5SOllivier Robert 
187c0b746e5SOllivier Robert /*
188c0b746e5SOllivier Robert  * Transfer vector
189c0b746e5SOllivier Robert  */
190c0b746e5SOllivier Robert struct	refclock refclock_mx4200 = {
191c0b746e5SOllivier Robert 	mx4200_start,		/* start up driver */
192c0b746e5SOllivier Robert 	mx4200_shutdown,	/* shut down driver */
193c0b746e5SOllivier Robert 	mx4200_poll,		/* transmit poll message */
194c0b746e5SOllivier Robert 	noentry,		/* not used (old mx4200_control) */
195c0b746e5SOllivier Robert 	noentry,		/* initialize driver (not used) */
196c0b746e5SOllivier Robert 	noentry,		/* not used (old mx4200_buginfo) */
197c0b746e5SOllivier Robert 	NOFLAGS			/* not used */
198c0b746e5SOllivier Robert };
199c0b746e5SOllivier Robert 
200c0b746e5SOllivier Robert 
201c0b746e5SOllivier Robert 
202c0b746e5SOllivier Robert /*
203c0b746e5SOllivier Robert  * mx4200_start - open the devices and initialize data for processing
204c0b746e5SOllivier Robert  */
205c0b746e5SOllivier Robert static int
206c0b746e5SOllivier Robert mx4200_start(
207c0b746e5SOllivier Robert 	int unit,
208c0b746e5SOllivier Robert 	struct peer *peer
209c0b746e5SOllivier Robert 	)
210c0b746e5SOllivier Robert {
211c0b746e5SOllivier Robert 	register struct mx4200unit *up;
212c0b746e5SOllivier Robert 	struct refclockproc *pp;
213c0b746e5SOllivier Robert 	int fd;
214c0b746e5SOllivier Robert 	char gpsdev[20];
215c0b746e5SOllivier Robert 
216c0b746e5SOllivier Robert 	/*
217c0b746e5SOllivier Robert 	 * Open serial port
218c0b746e5SOllivier Robert 	 */
2192b15cb3dSCy Schubert 	snprintf(gpsdev, sizeof(gpsdev), DEVICE, unit);
220a466cc55SCy Schubert 	fd = refclock_open(&peer->srcadr, gpsdev, SPEED232, LDISC_PPS);
2212b15cb3dSCy Schubert 	if (fd <= 0)
2222b15cb3dSCy Schubert 		return 0;
223c0b746e5SOllivier Robert 
224c0b746e5SOllivier Robert 	/*
225c0b746e5SOllivier Robert 	 * Allocate unit structure
226c0b746e5SOllivier Robert 	 */
2272b15cb3dSCy Schubert 	up = emalloc_zero(sizeof(*up));
228c0b746e5SOllivier Robert 	pp = peer->procptr;
229c0b746e5SOllivier Robert 	pp->io.clock_recv = mx4200_receive;
2302b15cb3dSCy Schubert 	pp->io.srcclock = peer;
231c0b746e5SOllivier Robert 	pp->io.datalen = 0;
232c0b746e5SOllivier Robert 	pp->io.fd = fd;
233c0b746e5SOllivier Robert 	if (!io_addclock(&pp->io)) {
2342b15cb3dSCy Schubert 		close(fd);
2352b15cb3dSCy Schubert 		pp->io.fd = -1;
236c0b746e5SOllivier Robert 		free(up);
237c0b746e5SOllivier Robert 		return (0);
238c0b746e5SOllivier Robert 	}
2392b15cb3dSCy Schubert 	pp->unitptr = up;
240c0b746e5SOllivier Robert 
241c0b746e5SOllivier Robert 	/*
242c0b746e5SOllivier Robert 	 * Initialize miscellaneous variables
243c0b746e5SOllivier Robert 	 */
244c0b746e5SOllivier Robert 	peer->precision = PRECISION;
245c0b746e5SOllivier Robert 	pp->clockdesc = DESCRIPTION;
246c0b746e5SOllivier Robert 	memcpy((char *)&pp->refid, REFID, 4);
247c0b746e5SOllivier Robert 
248c0b746e5SOllivier Robert 	/* Ensure the receiver is properly configured */
249224ba2bdSOllivier Robert 	return mx4200_config(peer);
250c0b746e5SOllivier Robert }
251c0b746e5SOllivier Robert 
252c0b746e5SOllivier Robert 
253c0b746e5SOllivier Robert /*
254c0b746e5SOllivier Robert  * mx4200_shutdown - shut down the clock
255c0b746e5SOllivier Robert  */
256c0b746e5SOllivier Robert static void
257c0b746e5SOllivier Robert mx4200_shutdown(
258c0b746e5SOllivier Robert 	int unit,
259c0b746e5SOllivier Robert 	struct peer *peer
260c0b746e5SOllivier Robert 	)
261c0b746e5SOllivier Robert {
262c0b746e5SOllivier Robert 	register struct mx4200unit *up;
263c0b746e5SOllivier Robert 	struct refclockproc *pp;
264c0b746e5SOllivier Robert 
265c0b746e5SOllivier Robert 	pp = peer->procptr;
2662b15cb3dSCy Schubert 	up = pp->unitptr;
2672b15cb3dSCy Schubert 	if (-1 != pp->io.fd)
268c0b746e5SOllivier Robert 		io_closeclock(&pp->io);
2692b15cb3dSCy Schubert 	if (NULL != up)
270c0b746e5SOllivier Robert 		free(up);
271c0b746e5SOllivier Robert }
272c0b746e5SOllivier Robert 
273c0b746e5SOllivier Robert 
274c0b746e5SOllivier Robert /*
275c0b746e5SOllivier Robert  * mx4200_config - Configure the receiver
276c0b746e5SOllivier Robert  */
277224ba2bdSOllivier Robert static int
278c0b746e5SOllivier Robert mx4200_config(
279c0b746e5SOllivier Robert 	struct peer *peer
280c0b746e5SOllivier Robert 	)
281c0b746e5SOllivier Robert {
282c0b746e5SOllivier Robert 	char tr_mode;
283c0b746e5SOllivier Robert 	int add_mode;
284c0b746e5SOllivier Robert 	register struct mx4200unit *up;
285c0b746e5SOllivier Robert 	struct refclockproc *pp;
286224ba2bdSOllivier Robert 	int mode;
287c0b746e5SOllivier Robert 
288c0b746e5SOllivier Robert 	pp = peer->procptr;
2892b15cb3dSCy Schubert 	up = pp->unitptr;
290c0b746e5SOllivier Robert 
291c0b746e5SOllivier Robert 	/*
292c0b746e5SOllivier Robert 	 * Initialize the unit variables
293c0b746e5SOllivier Robert 	 *
294c0b746e5SOllivier Robert 	 * STRANGE BEHAVIOUR WARNING: The fudge flags are not available
295c0b746e5SOllivier Robert 	 * at the time mx4200_start is called.  These are set later,
296c0b746e5SOllivier Robert 	 * and so the code must be prepared to handle changing flags.
297c0b746e5SOllivier Robert 	 */
298c0b746e5SOllivier Robert 	up->sloppyclockflag = pp->sloppyclockflag;
299c0b746e5SOllivier Robert 	if (pp->sloppyclockflag & CLK_FLAG2) {
300c0b746e5SOllivier Robert 		up->moving   = 1;	/* Receiver on mobile platform */
301c0b746e5SOllivier Robert 		msyslog(LOG_DEBUG, "mx4200_config: mobile platform");
302c0b746e5SOllivier Robert 	} else {
303c0b746e5SOllivier Robert 		up->moving   = 0;	/* Static Installation */
304c0b746e5SOllivier Robert 	}
305c0b746e5SOllivier Robert 	up->pollcnt     	= 2;
306c0b746e5SOllivier Robert 	up->polled      	= 0;
307c0b746e5SOllivier Robert 	up->known       	= 0;
308c0b746e5SOllivier Robert 	up->avg_lat     	= 0.0;
309c0b746e5SOllivier Robert 	up->avg_lon     	= 0.0;
310c0b746e5SOllivier Robert 	up->avg_alt     	= 0.0;
311c0b746e5SOllivier Robert 	up->central_meridian	= NOT_INITIALIZED;
312224ba2bdSOllivier Robert 	up->N_fixes    		= 0.0;
313c0b746e5SOllivier Robert 	up->last_leap   	= 0;	/* LEAP_NOWARNING */
314c0b746e5SOllivier Robert 	up->clamp_time  	= current_time + (AVGING_TIME * 60 * 60);
315c0b746e5SOllivier Robert 	up->log_time    	= current_time + SLEEPTIME;
316c0b746e5SOllivier Robert 
317224ba2bdSOllivier Robert 	if (time_pps_create(pp->io.fd, &up->pps_h) < 0) {
318224ba2bdSOllivier Robert 		perror("time_pps_create");
319224ba2bdSOllivier Robert 		msyslog(LOG_ERR,
320224ba2bdSOllivier Robert 			"mx4200_config: time_pps_create failed: %m");
321224ba2bdSOllivier Robert 		return (0);
322224ba2bdSOllivier Robert 	}
323224ba2bdSOllivier Robert 	if (time_pps_getcap(up->pps_h, &mode) < 0) {
324224ba2bdSOllivier Robert 		msyslog(LOG_ERR,
325224ba2bdSOllivier Robert 			"mx4200_config: time_pps_getcap failed: %m");
326224ba2bdSOllivier Robert 		return (0);
327224ba2bdSOllivier Robert 	}
328224ba2bdSOllivier Robert 
329224ba2bdSOllivier Robert 	if (time_pps_getparams(up->pps_h, &up->pps_p) < 0) {
330224ba2bdSOllivier Robert 		msyslog(LOG_ERR,
331224ba2bdSOllivier Robert 			"mx4200_config: time_pps_getparams failed: %m");
332224ba2bdSOllivier Robert 		return (0);
333224ba2bdSOllivier Robert 	}
334224ba2bdSOllivier Robert 
335224ba2bdSOllivier Robert 	/* nb. only turn things on, if someone else has turned something
336224ba2bdSOllivier Robert 	 *      on before we get here, leave it alone!
337224ba2bdSOllivier Robert 	 */
338224ba2bdSOllivier Robert 
339224ba2bdSOllivier Robert 	up->pps_p.mode = PPS_CAPTUREASSERT | PPS_TSFMT_TSPEC;
340224ba2bdSOllivier Robert 	up->pps_p.mode &= mode;		/* only set what is legal */
341224ba2bdSOllivier Robert 
342224ba2bdSOllivier Robert 	if (time_pps_setparams(up->pps_h, &up->pps_p) < 0) {
343224ba2bdSOllivier Robert 		perror("time_pps_setparams");
344224ba2bdSOllivier Robert 		msyslog(LOG_ERR,
345224ba2bdSOllivier Robert 			"mx4200_config: time_pps_setparams failed: %m");
346224ba2bdSOllivier Robert 		exit(1);
347224ba2bdSOllivier Robert 	}
348224ba2bdSOllivier Robert 
349224ba2bdSOllivier Robert 	if (time_pps_kcbind(up->pps_h, PPS_KC_HARDPPS, PPS_CAPTUREASSERT,
350224ba2bdSOllivier Robert 			PPS_TSFMT_TSPEC) < 0) {
351224ba2bdSOllivier Robert 		perror("time_pps_kcbind");
352224ba2bdSOllivier Robert 		msyslog(LOG_ERR,
353224ba2bdSOllivier Robert 			"mx4200_config: time_pps_kcbind failed: %m");
354224ba2bdSOllivier Robert 		exit(1);
355224ba2bdSOllivier Robert 	}
356224ba2bdSOllivier Robert 
357224ba2bdSOllivier Robert 
358c0b746e5SOllivier Robert 	/*
359c0b746e5SOllivier Robert 	 * "007" Control Port Configuration
360c0b746e5SOllivier Robert 	 * Zero the output list (do it twice to flush possible junk)
361c0b746e5SOllivier Robert 	 */
362c0b746e5SOllivier Robert 	mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg,
363c0b746e5SOllivier Robert 	    PMVXG_S_PORTCONF,
364c0b746e5SOllivier Robert 	    /* control port output block Label */
365c0b746e5SOllivier Robert 	    1);		/* clear current output control list (1=yes) */
366c0b746e5SOllivier Robert 	/* add/delete sentences from list */
367c0b746e5SOllivier Robert 	/* must be null */
368c0b746e5SOllivier Robert 	/* sentence output rate (sec) */
369c0b746e5SOllivier Robert 	/* precision for position output */
370c0b746e5SOllivier Robert 	/* nmea version for cga & gll output */
371c0b746e5SOllivier Robert 	/* pass-through control */
372c0b746e5SOllivier Robert 	mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg,
373c0b746e5SOllivier Robert 	    PMVXG_S_PORTCONF, 1);
374c0b746e5SOllivier Robert 
375c0b746e5SOllivier Robert 	/*
376c0b746e5SOllivier Robert 	 * Request software configuration so we can syslog the firmware version
377c0b746e5SOllivier Robert 	 */
378c0b746e5SOllivier Robert 	mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_SOFTCONF);
379c0b746e5SOllivier Robert 
380c0b746e5SOllivier Robert 	/*
381c0b746e5SOllivier Robert 	 * "001" Initialization/Mode Control, Part A
382c0b746e5SOllivier Robert 	 * Where ARE we?
383c0b746e5SOllivier Robert 	 */
384c0b746e5SOllivier Robert 	mx4200_send(peer, "%s,%03d,,,,,,,,,,", pmvxg,
385c0b746e5SOllivier Robert 	    PMVXG_S_INITMODEA);
386c0b746e5SOllivier Robert 	/* day of month */
387c0b746e5SOllivier Robert 	/* month of year */
388c0b746e5SOllivier Robert 	/* year */
389c0b746e5SOllivier Robert 	/* gmt */
390c0b746e5SOllivier Robert 	/* latitude   DDMM.MMMM */
391c0b746e5SOllivier Robert 	/* north/south */
392c0b746e5SOllivier Robert 	/* longitude DDDMM.MMMM */
393c0b746e5SOllivier Robert 	/* east/west */
394c0b746e5SOllivier Robert 	/* height */
395c0b746e5SOllivier Robert 	/* Altitude Reference 1=MSL */
396c0b746e5SOllivier Robert 
397c0b746e5SOllivier Robert 	/*
398c0b746e5SOllivier Robert 	 * "001" Initialization/Mode Control, Part B
399c0b746e5SOllivier Robert 	 * Start off in 2d/3d coast mode, holding altitude to last known
400c0b746e5SOllivier Robert 	 * value if only 3 satellites available.
401c0b746e5SOllivier Robert 	 */
402c0b746e5SOllivier Robert 	mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
403c0b746e5SOllivier Robert 	    pmvxg, PMVXG_S_INITMODEB,
404c0b746e5SOllivier Robert 	    3,		/* 2d/3d coast */
405c0b746e5SOllivier Robert 	    /* reserved */
406c0b746e5SOllivier Robert 	    0.1,	/* hor accel fact as per Steve (m/s**2) */
407c0b746e5SOllivier Robert 	    0.1,	/* ver accel fact as per Steve (m/s**2) */
408c0b746e5SOllivier Robert 	    10,		/* vdop */
409c0b746e5SOllivier Robert 	    10,		/* hdop limit as per Steve */
410c0b746e5SOllivier Robert 	    5,		/* elevation limit as per Steve (deg) */
411c0b746e5SOllivier Robert 	    'U',	/* time output mode (UTC) */
412c0b746e5SOllivier Robert 	    0);		/* local time offset from gmt (HHHMM) */
413c0b746e5SOllivier Robert 
414c0b746e5SOllivier Robert 	/*
415c0b746e5SOllivier Robert 	 * "023" Time Recovery Configuration
416c0b746e5SOllivier Robert 	 * Get UTC time from a stationary receiver.
417c0b746e5SOllivier Robert 	 * (Set field 1 'D' == dynamic if we are on a moving platform).
418c0b746e5SOllivier Robert 	 * (Set field 1 'S' == static  if we are not moving).
419c0b746e5SOllivier Robert 	 * (Set field 1 'K' == known position if we can initialize lat/lon/alt).
420c0b746e5SOllivier Robert 	 */
421c0b746e5SOllivier Robert 
422c0b746e5SOllivier Robert 	if (pp->sloppyclockflag & CLK_FLAG2)
423c0b746e5SOllivier Robert 		up->moving   = 1;	/* Receiver on mobile platform */
424c0b746e5SOllivier Robert 	else
425c0b746e5SOllivier Robert 		up->moving   = 0;	/* Static Installation */
426c0b746e5SOllivier Robert 
427c0b746e5SOllivier Robert 	up->pollcnt  = 2;
428c0b746e5SOllivier Robert 	if (up->moving) {
429c0b746e5SOllivier Robert 		/* dynamic: solve for pos, alt, time, while moving */
430c0b746e5SOllivier Robert 		tr_mode = 'D';
431c0b746e5SOllivier Robert 	} else {
432c0b746e5SOllivier Robert 		/* static: solve for pos, alt, time, while stationary */
433c0b746e5SOllivier Robert 		tr_mode = 'S';
434c0b746e5SOllivier Robert 	}
435c0b746e5SOllivier Robert 	mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg,
436c0b746e5SOllivier Robert 	    PMVXG_S_TRECOVCONF,
437c0b746e5SOllivier Robert 	    tr_mode,	/* time recovery mode (see above ) */
438c0b746e5SOllivier Robert 	    'U',	/* synchronize to UTC */
439c0b746e5SOllivier Robert 	    'A',	/* always output a time pulse */
440c0b746e5SOllivier Robert 	    500,	/* max time error in ns */
441c0b746e5SOllivier Robert 	    0,		/* user bias in ns */
442c0b746e5SOllivier Robert 	    1);		/* output "830" sentences to control port */
443c0b746e5SOllivier Robert 			/* Multi-satellite mode */
444c0b746e5SOllivier Robert 
445c0b746e5SOllivier Robert 	/*
446c0b746e5SOllivier Robert 	 * Output position information (to calculate fixed installation
447c0b746e5SOllivier Robert 	 * location) only if we are not moving
448c0b746e5SOllivier Robert 	 */
449c0b746e5SOllivier Robert 	if (up->moving) {
450c0b746e5SOllivier Robert 		add_mode = 2;	/* delete from list */
451c0b746e5SOllivier Robert 	} else {
452c0b746e5SOllivier Robert 		add_mode = 1;	/* add to list */
453c0b746e5SOllivier Robert 	}
454c0b746e5SOllivier Robert 
455c0b746e5SOllivier Robert 
456c0b746e5SOllivier Robert 	/*
457c0b746e5SOllivier Robert 	 * "007" Control Port Configuration
458c0b746e5SOllivier Robert 	 * Output "021" position, height, velocity reports
459c0b746e5SOllivier Robert 	 */
460c0b746e5SOllivier Robert 	mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg,
461c0b746e5SOllivier Robert 	    PMVXG_S_PORTCONF,
462c0b746e5SOllivier Robert 	    PMVXG_D_PHV, /* control port output block Label */
463c0b746e5SOllivier Robert 	    0,		/* clear current output control list (0=no) */
464c0b746e5SOllivier Robert 	    add_mode,	/* add/delete sentences from list (1=add, 2=del) */
465c0b746e5SOllivier Robert 	    		/* must be null */
466c0b746e5SOllivier Robert 	    INTERVAL);	/* sentence output rate (sec) */
467c0b746e5SOllivier Robert 			/* precision for position output */
468c0b746e5SOllivier Robert 			/* nmea version for cga & gll output */
469c0b746e5SOllivier Robert 			/* pass-through control */
470224ba2bdSOllivier Robert 
471224ba2bdSOllivier Robert 	return (1);
472c0b746e5SOllivier Robert }
473c0b746e5SOllivier Robert 
474c0b746e5SOllivier Robert /*
475c0b746e5SOllivier Robert  * mx4200_ref - Reconfigure unit as a reference station at a known position.
476c0b746e5SOllivier Robert  */
477c0b746e5SOllivier Robert static void
478c0b746e5SOllivier Robert mx4200_ref(
479c0b746e5SOllivier Robert 	struct peer *peer
480c0b746e5SOllivier Robert 	)
481c0b746e5SOllivier Robert {
482c0b746e5SOllivier Robert 	register struct mx4200unit *up;
483c0b746e5SOllivier Robert 	struct refclockproc *pp;
484c0b746e5SOllivier Robert 	double minute, lat, lon, alt;
485c0b746e5SOllivier Robert 	char lats[16], lons[16];
486c0b746e5SOllivier Robert 	char nsc, ewc;
487c0b746e5SOllivier Robert 
488c0b746e5SOllivier Robert 	pp = peer->procptr;
4892b15cb3dSCy Schubert 	up = pp->unitptr;
490c0b746e5SOllivier Robert 
491c0b746e5SOllivier Robert 	/* Should never happen! */
492c0b746e5SOllivier Robert 	if (up->moving) return;
493c0b746e5SOllivier Robert 
494c0b746e5SOllivier Robert 	/*
495c0b746e5SOllivier Robert 	 * Set up to output status information in the near future
496c0b746e5SOllivier Robert 	 */
497c0b746e5SOllivier Robert 	up->log_time    = current_time + SLEEPTIME;
498c0b746e5SOllivier Robert 
499c0b746e5SOllivier Robert 	/*
500c0b746e5SOllivier Robert 	 * "007" Control Port Configuration
501c0b746e5SOllivier Robert 	 * Stop outputting "021" position, height, velocity reports
502c0b746e5SOllivier Robert 	 */
503c0b746e5SOllivier Robert 	mx4200_send(peer, "%s,%03d,%03d,%d,%d,,,,,", pmvxg,
504c0b746e5SOllivier Robert 	    PMVXG_S_PORTCONF,
505c0b746e5SOllivier Robert 	    PMVXG_D_PHV, /* control port output block Label */
506c0b746e5SOllivier Robert 	    0,		/* clear current output control list (0=no) */
507c0b746e5SOllivier Robert 	    2);		/* add/delete sentences from list (2=delete) */
508c0b746e5SOllivier Robert 			/* must be null */
509c0b746e5SOllivier Robert 	    		/* sentence output rate (sec) */
510c0b746e5SOllivier Robert 			/* precision for position output */
511c0b746e5SOllivier Robert 			/* nmea version for cga & gll output */
512c0b746e5SOllivier Robert 			/* pass-through control */
513c0b746e5SOllivier Robert 
514c0b746e5SOllivier Robert 	/*
515c0b746e5SOllivier Robert 	 * "001" Initialization/Mode Control, Part B
516c0b746e5SOllivier Robert 	 * Put receiver in fully-constrained 2d nav mode
517c0b746e5SOllivier Robert 	 */
518c0b746e5SOllivier Robert 	mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
519c0b746e5SOllivier Robert 	    pmvxg, PMVXG_S_INITMODEB,
520c0b746e5SOllivier Robert 	    2,		/* 2d nav */
521c0b746e5SOllivier Robert 	    /* reserved */
522c0b746e5SOllivier Robert 	    0.1,	/* hor accel fact as per Steve (m/s**2) */
523c0b746e5SOllivier Robert 	    0.1,	/* ver accel fact as per Steve (m/s**2) */
524c0b746e5SOllivier Robert 	    10,		/* vdop */
525c0b746e5SOllivier Robert 	    10,		/* hdop limit as per Steve */
526c0b746e5SOllivier Robert 	    5,		/* elevation limit as per Steve (deg) */
527c0b746e5SOllivier Robert 	    'U',	/* time output mode (UTC) */
528c0b746e5SOllivier Robert 	    0);		/* local time offset from gmt (HHHMM) */
529c0b746e5SOllivier Robert 
530c0b746e5SOllivier Robert 	/*
531c0b746e5SOllivier Robert 	 * "023" Time Recovery Configuration
532c0b746e5SOllivier Robert 	 * Get UTC time from a stationary receiver.  Solve for time only.
533c0b746e5SOllivier Robert 	 * This should improve the time resolution dramatically.
534c0b746e5SOllivier Robert 	 */
535c0b746e5SOllivier Robert 	mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg,
536c0b746e5SOllivier Robert 	    PMVXG_S_TRECOVCONF,
537c0b746e5SOllivier Robert 	    'K',	/* known position: solve for time only */
538c0b746e5SOllivier Robert 	    'U',	/* synchronize to UTC */
539c0b746e5SOllivier Robert 	    'A',	/* always output a time pulse */
540c0b746e5SOllivier Robert 	    500,	/* max time error in ns */
541c0b746e5SOllivier Robert 	    0,		/* user bias in ns */
542c0b746e5SOllivier Robert 	    1);		/* output "830" sentences to control port */
543c0b746e5SOllivier Robert 	/* Multi-satellite mode */
544c0b746e5SOllivier Robert 
545c0b746e5SOllivier Robert 	/*
546c0b746e5SOllivier Robert 	 * "000" Initialization/Mode Control - Part A
547c0b746e5SOllivier Robert 	 * Fix to our averaged position.
548c0b746e5SOllivier Robert 	 */
549c0b746e5SOllivier Robert 	if (up->central_meridian != NOT_INITIALIZED) {
550c0b746e5SOllivier Robert 		up->avg_lon += up->central_meridian;
551c0b746e5SOllivier Robert 		if (up->avg_lon < -180.0) up->avg_lon += 360.0;
552c0b746e5SOllivier Robert 		if (up->avg_lon >  180.0) up->avg_lon -= 360.0;
553c0b746e5SOllivier Robert 	}
554c0b746e5SOllivier Robert 
555c0b746e5SOllivier Robert 	if (up->avg_lat >= 0.0) {
556c0b746e5SOllivier Robert 		lat = up->avg_lat;
557c0b746e5SOllivier Robert 		nsc = 'N';
558c0b746e5SOllivier Robert 	} else {
559c0b746e5SOllivier Robert 		lat = up->avg_lat * (-1.0);
560c0b746e5SOllivier Robert 		nsc = 'S';
561c0b746e5SOllivier Robert 	}
562c0b746e5SOllivier Robert 	if (up->avg_lon >= 0.0) {
563c0b746e5SOllivier Robert 		lon = up->avg_lon;
564c0b746e5SOllivier Robert 		ewc = 'E';
565c0b746e5SOllivier Robert 	} else {
566c0b746e5SOllivier Robert 		lon = up->avg_lon * (-1.0);
567c0b746e5SOllivier Robert 		ewc = 'W';
568c0b746e5SOllivier Robert 	}
569c0b746e5SOllivier Robert 	alt = up->avg_alt;
570c0b746e5SOllivier Robert 	minute = (lat - (double)(int)lat) * 60.0;
5712b15cb3dSCy Schubert 	snprintf(lats, sizeof(lats), "%02d%02.4f", (int)lat, minute);
572c0b746e5SOllivier Robert 	minute = (lon - (double)(int)lon) * 60.0;
5732b15cb3dSCy Schubert 	snprintf(lons, sizeof(lons), "%03d%02.4f", (int)lon, minute);
574c0b746e5SOllivier Robert 
575a151a66cSOllivier Robert 	mx4200_send(peer, "%s,%03d,,,,,%s,%c,%s,%c,%.2f,%d", pmvxg,
576c0b746e5SOllivier Robert 	    PMVXG_S_INITMODEA,
577c0b746e5SOllivier Robert 	    /* day of month */
578c0b746e5SOllivier Robert 	    /* month of year */
579c0b746e5SOllivier Robert 	    /* year */
580c0b746e5SOllivier Robert 	    /* gmt */
581c0b746e5SOllivier Robert 	    lats,	/* latitude   DDMM.MMMM */
582c0b746e5SOllivier Robert 	    nsc,	/* north/south */
583c0b746e5SOllivier Robert 	    lons,	/* longitude DDDMM.MMMM */
584c0b746e5SOllivier Robert 	    ewc,	/* east/west */
585a151a66cSOllivier Robert 	    alt,	/* Altitude */
586a151a66cSOllivier Robert 	    1);		/* Altitude Reference (0=WGS84 ellipsoid, 1=MSL geoid)*/
587c0b746e5SOllivier Robert 
588c0b746e5SOllivier Robert 	msyslog(LOG_DEBUG,
589c0b746e5SOllivier Robert 	    "mx4200: reconfig to fixed location: %s %c, %s %c, %.2f m",
590c0b746e5SOllivier Robert 		lats, nsc, lons, ewc, alt );
591c0b746e5SOllivier Robert 
592c0b746e5SOllivier Robert }
593c0b746e5SOllivier Robert 
594c0b746e5SOllivier Robert /*
595c0b746e5SOllivier Robert  * mx4200_poll - mx4200 watchdog routine
596c0b746e5SOllivier Robert  */
597c0b746e5SOllivier Robert static void
598c0b746e5SOllivier Robert mx4200_poll(
599c0b746e5SOllivier Robert 	int unit,
600c0b746e5SOllivier Robert 	struct peer *peer
601c0b746e5SOllivier Robert 	)
602c0b746e5SOllivier Robert {
603c0b746e5SOllivier Robert 	register struct mx4200unit *up;
604c0b746e5SOllivier Robert 	struct refclockproc *pp;
605c0b746e5SOllivier Robert 
606c0b746e5SOllivier Robert 	pp = peer->procptr;
6072b15cb3dSCy Schubert 	up = pp->unitptr;
608c0b746e5SOllivier Robert 
609c0b746e5SOllivier Robert 	/*
610c0b746e5SOllivier Robert 	 * You don't need to poll this clock.  It puts out timecodes
611c0b746e5SOllivier Robert 	 * once per second.  If asked for a timestamp, take note.
612c0b746e5SOllivier Robert 	 * The next time a timecode comes in, it will be fed back.
613c0b746e5SOllivier Robert 	 */
614c0b746e5SOllivier Robert 
615c0b746e5SOllivier Robert 	/*
616c0b746e5SOllivier Robert 	 * If we haven't had a response in a while, reset the receiver.
617c0b746e5SOllivier Robert 	 */
618c0b746e5SOllivier Robert 	if (up->pollcnt > 0) {
619c0b746e5SOllivier Robert 		up->pollcnt--;
620c0b746e5SOllivier Robert 	} else {
621c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_TIMEOUT);
622c0b746e5SOllivier Robert 
623c0b746e5SOllivier Robert 		/*
624c0b746e5SOllivier Robert 		 * Request a "000" status message which should trigger a
625c0b746e5SOllivier Robert 		 * reconfig
626c0b746e5SOllivier Robert 		 */
627c0b746e5SOllivier Robert 		mx4200_send(peer, "%s,%03d",
628c0b746e5SOllivier Robert 		    "CDGPQ",		/* query from CDU to GPS */
629c0b746e5SOllivier Robert 		    PMVXG_D_STATUS);	/* label of desired sentence */
630c0b746e5SOllivier Robert 	}
631c0b746e5SOllivier Robert 
632c0b746e5SOllivier Robert 	/*
633c0b746e5SOllivier Robert 	 * polled every 64 seconds. Ask mx4200_receive to hand in
634c0b746e5SOllivier Robert 	 * a timestamp.
635c0b746e5SOllivier Robert 	 */
636c0b746e5SOllivier Robert 	up->polled = 1;
637c0b746e5SOllivier Robert 	pp->polls++;
638c0b746e5SOllivier Robert 
639c0b746e5SOllivier Robert 	/*
640c0b746e5SOllivier Robert 	 * Output receiver status information.
641c0b746e5SOllivier Robert 	 */
642c0b746e5SOllivier Robert 	if ((up->log_time > 0) && (current_time > up->log_time)) {
643c0b746e5SOllivier Robert 		up->log_time = 0;
644c0b746e5SOllivier Robert 		/*
645c0b746e5SOllivier Robert 		 * Output the following messages once, for debugging.
646c0b746e5SOllivier Robert 		 *    "004" Mode Data
647c0b746e5SOllivier Robert 		 *    "523" Time Recovery Parameters
648c0b746e5SOllivier Robert 		 */
649c0b746e5SOllivier Robert 		mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_MODEDATA);
650c0b746e5SOllivier Robert 		mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_TRECOVUSEAGE);
651c0b746e5SOllivier Robert 	}
652c0b746e5SOllivier Robert }
653c0b746e5SOllivier Robert 
654c0b746e5SOllivier Robert static char char2hex[] = "0123456789ABCDEF";
655c0b746e5SOllivier Robert 
656c0b746e5SOllivier Robert /*
657c0b746e5SOllivier Robert  * mx4200_receive - receive gps data
658c0b746e5SOllivier Robert  */
659c0b746e5SOllivier Robert static void
660c0b746e5SOllivier Robert mx4200_receive(
661c0b746e5SOllivier Robert 	struct recvbuf *rbufp
662c0b746e5SOllivier Robert 	)
663c0b746e5SOllivier Robert {
664c0b746e5SOllivier Robert 	register struct mx4200unit *up;
665c0b746e5SOllivier Robert 	struct refclockproc *pp;
666c0b746e5SOllivier Robert 	struct peer *peer;
667c0b746e5SOllivier Robert 	char *cp;
668c0b746e5SOllivier Robert 	int sentence_type;
669c0b746e5SOllivier Robert 	u_char ck;
670c0b746e5SOllivier Robert 
671c0b746e5SOllivier Robert 	/*
672c0b746e5SOllivier Robert 	 * Initialize pointers and read the timecode and timestamp.
673c0b746e5SOllivier Robert 	 */
6742b15cb3dSCy Schubert 	peer = rbufp->recv_peer;
675c0b746e5SOllivier Robert 	pp = peer->procptr;
6762b15cb3dSCy Schubert 	up = pp->unitptr;
677c0b746e5SOllivier Robert 
678c0b746e5SOllivier Robert 	/*
679c0b746e5SOllivier Robert 	 * If operating mode has been changed, then reinitialize the receiver
680c0b746e5SOllivier Robert 	 * before doing anything else.
681c0b746e5SOllivier Robert 	 */
682c0b746e5SOllivier Robert 	if ((pp->sloppyclockflag & CLK_FLAG2) !=
683c0b746e5SOllivier Robert 	    (up->sloppyclockflag & CLK_FLAG2)) {
684c0b746e5SOllivier Robert 		up->sloppyclockflag = pp->sloppyclockflag;
685c0b746e5SOllivier Robert 		mx4200_debug(peer,
686c0b746e5SOllivier Robert 		    "mx4200_receive: mode switch: reset receiver\n");
687c0b746e5SOllivier Robert 		mx4200_config(peer);
688c0b746e5SOllivier Robert 		return;
689c0b746e5SOllivier Robert 	}
690c0b746e5SOllivier Robert 	up->sloppyclockflag = pp->sloppyclockflag;
691c0b746e5SOllivier Robert 
692c0b746e5SOllivier Robert 	/*
693c0b746e5SOllivier Robert 	 * Read clock output.  Automatically handles STREAMS, CLKLDISC.
694c0b746e5SOllivier Robert 	 */
695c0b746e5SOllivier Robert 	pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec);
696c0b746e5SOllivier Robert 
697c0b746e5SOllivier Robert 	/*
698c0b746e5SOllivier Robert 	 * There is a case where <cr><lf> generates 2 timestamps.
699c0b746e5SOllivier Robert 	 */
700c0b746e5SOllivier Robert 	if (pp->lencode == 0)
701c0b746e5SOllivier Robert 		return;
702c0b746e5SOllivier Robert 
703c0b746e5SOllivier Robert 	up->pollcnt = 2;
704c0b746e5SOllivier Robert 	pp->a_lastcode[pp->lencode] = '\0';
705c0b746e5SOllivier Robert 	record_clock_stats(&peer->srcadr, pp->a_lastcode);
706c0b746e5SOllivier Robert 	mx4200_debug(peer, "mx4200_receive: %d %s\n",
707c0b746e5SOllivier Robert 		     pp->lencode, pp->a_lastcode);
708c0b746e5SOllivier Robert 
709c0b746e5SOllivier Robert 	/*
710c0b746e5SOllivier Robert 	 * The structure of the control port sentences is based on the
711c0b746e5SOllivier Robert 	 * NMEA-0183 Standard for interfacing Marine Electronics
712c0b746e5SOllivier Robert 	 * Navigation Devices (Version 1.5)
713c0b746e5SOllivier Robert 	 *
714c0b746e5SOllivier Robert 	 *	$PMVXG,XXX, ....................*CK<cr><lf>
715c0b746e5SOllivier Robert 	 *
716c0b746e5SOllivier Robert 	 *		$	Sentence Start Identifier (reserved char)
717c0b746e5SOllivier Robert 	 *			   (Start-of-Sentence Identifier)
718c0b746e5SOllivier Robert 	 *		P	Special ID (Proprietary)
719c0b746e5SOllivier Robert 	 *		MVX	Originator ID (Magnavox)
720c0b746e5SOllivier Robert 	 *		G	Interface ID (GPS)
721c0b746e5SOllivier Robert 	 *		,	Field Delimiters (reserved char)
722c0b746e5SOllivier Robert 	 *		XXX	Sentence Type
723c0b746e5SOllivier Robert 	 *		......	Data
724c0b746e5SOllivier Robert 	 *		*	Checksum Field Delimiter (reserved char)
725c0b746e5SOllivier Robert 	 *		CK	Checksum
726c0b746e5SOllivier Robert 	 *		<cr><lf> Carriage-Return/Line Feed (reserved chars)
727c0b746e5SOllivier Robert 	 *			   (End-of-Sentence Identifier)
728c0b746e5SOllivier Robert 	 *
729c0b746e5SOllivier Robert 	 * Reject if any important landmarks are missing.
730c0b746e5SOllivier Robert 	 */
731c0b746e5SOllivier Robert 	cp = pp->a_lastcode + pp->lencode - 3;
732c0b746e5SOllivier Robert 	if (cp < pp->a_lastcode || *pp->a_lastcode != '$' || cp[0] != '*' ) {
733c0b746e5SOllivier Robert 		mx4200_debug(peer, "mx4200_receive: bad format\n");
734c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_BADREPLY);
735c0b746e5SOllivier Robert 		return;
736c0b746e5SOllivier Robert 	}
737c0b746e5SOllivier Robert 
738c0b746e5SOllivier Robert 	/*
739c0b746e5SOllivier Robert 	 * Check and discard the checksum
740c0b746e5SOllivier Robert 	 */
741c0b746e5SOllivier Robert 	ck = mx4200_cksum(&pp->a_lastcode[1], pp->lencode - 4);
742c0b746e5SOllivier Robert 	if (char2hex[ck >> 4] != cp[1] || char2hex[ck & 0xf] != cp[2]) {
743c0b746e5SOllivier Robert 		mx4200_debug(peer, "mx4200_receive: bad checksum\n");
744c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_BADREPLY);
745c0b746e5SOllivier Robert 		return;
746c0b746e5SOllivier Robert 	}
747c0b746e5SOllivier Robert 	*cp = '\0';
748c0b746e5SOllivier Robert 
749c0b746e5SOllivier Robert 	/*
750c0b746e5SOllivier Robert 	 * Get the sentence type.
751c0b746e5SOllivier Robert 	 */
752c0b746e5SOllivier Robert 	sentence_type = 0;
753c0b746e5SOllivier Robert 	if ((cp = strchr(pp->a_lastcode, ',')) == NULL) {
754c0b746e5SOllivier Robert 		mx4200_debug(peer, "mx4200_receive: no sentence\n");
755c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_BADREPLY);
756c0b746e5SOllivier Robert 		return;
757c0b746e5SOllivier Robert 	}
758c0b746e5SOllivier Robert 	cp++;
759c0b746e5SOllivier Robert 	sentence_type = strtol(cp, &cp, 10);
760c0b746e5SOllivier Robert 
761c0b746e5SOllivier Robert 	/*
762224ba2bdSOllivier Robert 	 * Process the sentence according to its type.
763224ba2bdSOllivier Robert 	 */
764224ba2bdSOllivier Robert 	switch (sentence_type) {
765224ba2bdSOllivier Robert 
766224ba2bdSOllivier Robert 	/*
767c0b746e5SOllivier Robert 	 * "000" Status message
768c0b746e5SOllivier Robert 	 */
769224ba2bdSOllivier Robert 	case PMVXG_D_STATUS:
770c0b746e5SOllivier Robert 		/*
771c0b746e5SOllivier Robert 		 * XXX
772c0b746e5SOllivier Robert 		 * Since we configure the receiver to not give us status
773c0b746e5SOllivier Robert 		 * messages and since the receiver outputs status messages by
774c0b746e5SOllivier Robert 		 * default after being reset to factory defaults when sent the
775c0b746e5SOllivier Robert 		 * "$PMVXG,018,C\r\n" message, any status message we get
776c0b746e5SOllivier Robert 		 * indicates the reciever needs to be initialized; thus, it is
777c0b746e5SOllivier Robert 		 * not necessary to decode the status message.
778c0b746e5SOllivier Robert 		 */
779c0b746e5SOllivier Robert 		if ((cp = mx4200_parse_s(peer)) != NULL) {
780c0b746e5SOllivier Robert 			mx4200_debug(peer,
781c0b746e5SOllivier Robert 				     "mx4200_receive: status: %s\n", cp);
782c0b746e5SOllivier Robert 		}
783c0b746e5SOllivier Robert 		mx4200_debug(peer, "mx4200_receive: reset receiver\n");
784c0b746e5SOllivier Robert 		mx4200_config(peer);
785224ba2bdSOllivier Robert 		break;
786c0b746e5SOllivier Robert 
787c0b746e5SOllivier Robert 	/*
788c0b746e5SOllivier Robert 	 * "021" Position, Height, Velocity message,
789c0b746e5SOllivier Robert 	 *  if we are still averaging our position
790c0b746e5SOllivier Robert 	 */
791224ba2bdSOllivier Robert 	case PMVXG_D_PHV:
792224ba2bdSOllivier Robert 		if (!up->known) {
793c0b746e5SOllivier Robert 			/*
794c0b746e5SOllivier Robert 			 * Parse the message, calculating our averaged position.
795c0b746e5SOllivier Robert 			 */
796c0b746e5SOllivier Robert 			if ((cp = mx4200_parse_p(peer)) != NULL) {
797c0b746e5SOllivier Robert 				mx4200_debug(peer, "mx4200_receive: pos: %s\n", cp);
798c0b746e5SOllivier Robert 				return;
799c0b746e5SOllivier Robert 			}
800c0b746e5SOllivier Robert 			mx4200_debug(peer,
801224ba2bdSOllivier Robert 			    "mx4200_receive: position avg %f %.9f %.9f %.4f\n",
802224ba2bdSOllivier Robert 			    up->N_fixes, up->avg_lat, up->avg_lon, up->avg_alt);
803c0b746e5SOllivier Robert 			/*
804c0b746e5SOllivier Robert 			 * Reinitialize as a reference station
805c0b746e5SOllivier Robert 			 * if position is well known.
806c0b746e5SOllivier Robert 			 */
807c0b746e5SOllivier Robert 			if (current_time > up->clamp_time) {
808c0b746e5SOllivier Robert 				up->known++;
809c0b746e5SOllivier Robert 				mx4200_debug(peer, "mx4200_receive: reconfiguring!\n");
810c0b746e5SOllivier Robert 				mx4200_ref(peer);
811c0b746e5SOllivier Robert 			}
812c0b746e5SOllivier Robert 		}
813224ba2bdSOllivier Robert 		break;
814c0b746e5SOllivier Robert 
815c0b746e5SOllivier Robert 	/*
816c0b746e5SOllivier Robert 	 * Print to the syslog:
817c0b746e5SOllivier Robert 	 * "004" Mode Data
818c0b746e5SOllivier Robert 	 * "030" Software Configuration
819c0b746e5SOllivier Robert 	 * "523" Time Recovery Parameters Currently in Use
820c0b746e5SOllivier Robert 	 */
821224ba2bdSOllivier Robert 	case PMVXG_D_MODEDATA:
822224ba2bdSOllivier Robert 	case PMVXG_D_SOFTCONF:
823224ba2bdSOllivier Robert 	case PMVXG_D_TRECOVUSEAGE:
824224ba2bdSOllivier Robert 
825c0b746e5SOllivier Robert 		if ((cp = mx4200_parse_s(peer)) != NULL) {
826c0b746e5SOllivier Robert 			mx4200_debug(peer,
827c0b746e5SOllivier Robert 				     "mx4200_receive: multi-record: %s\n", cp);
828c0b746e5SOllivier Robert 		}
829224ba2bdSOllivier Robert 		break;
830c0b746e5SOllivier Robert 
831c0b746e5SOllivier Robert 	/*
832c0b746e5SOllivier Robert 	 * "830" Time Recovery Results message
833c0b746e5SOllivier Robert 	 */
834224ba2bdSOllivier Robert 	case PMVXG_D_TRECOVOUT:
835c0b746e5SOllivier Robert 
836c0b746e5SOllivier Robert 		/*
837c0b746e5SOllivier Robert 		 * Capture the last PPS signal.
838c0b746e5SOllivier Robert 		 * Precision timestamp is returned in pp->lastrec
839c0b746e5SOllivier Robert 		 */
8402b15cb3dSCy Schubert 		if (0 != mx4200_pps(peer)) {
841c0b746e5SOllivier Robert 			mx4200_debug(peer, "mx4200_receive: pps failure\n");
842c0b746e5SOllivier Robert 			refclock_report(peer, CEVNT_FAULT);
843c0b746e5SOllivier Robert 			return;
844c0b746e5SOllivier Robert 		}
845c0b746e5SOllivier Robert 
846c0b746e5SOllivier Robert 
847c0b746e5SOllivier Robert 		/*
848c0b746e5SOllivier Robert 		 * Parse the time recovery message, and keep the info
849c0b746e5SOllivier Robert 		 * to print the pretty billboards.
850c0b746e5SOllivier Robert 		 */
851c0b746e5SOllivier Robert 		if ((cp = mx4200_parse_t(peer)) != NULL) {
852c0b746e5SOllivier Robert 			mx4200_debug(peer, "mx4200_receive: time: %s\n", cp);
853c0b746e5SOllivier Robert 			refclock_report(peer, CEVNT_BADREPLY);
854c0b746e5SOllivier Robert 			return;
855c0b746e5SOllivier Robert 		}
856c0b746e5SOllivier Robert 
857c0b746e5SOllivier Robert 		/*
858c0b746e5SOllivier Robert 		 * Add the new sample to a median filter.
859c0b746e5SOllivier Robert 		 */
860c0b746e5SOllivier Robert 		if (!refclock_process(pp)) {
861c0b746e5SOllivier Robert 			mx4200_debug(peer,"mx4200_receive: offset: %.6f\n",
862c0b746e5SOllivier Robert 			    pp->offset);
863c0b746e5SOllivier Robert 			refclock_report(peer, CEVNT_BADTIME);
864c0b746e5SOllivier Robert 			return;
865c0b746e5SOllivier Robert 		}
866c0b746e5SOllivier Robert 
867c0b746e5SOllivier Robert 		/*
868c0b746e5SOllivier Robert 		 * The clock will blurt a timecode every second but we only
869c0b746e5SOllivier Robert 		 * want one when polled.  If we havn't been polled, bail out.
870c0b746e5SOllivier Robert 		 */
871c0b746e5SOllivier Robert 		if (!up->polled)
872c0b746e5SOllivier Robert 			return;
873c0b746e5SOllivier Robert 
874c0b746e5SOllivier Robert 		/*
875c0b746e5SOllivier Robert 		 * Return offset and dispersion to control module.  We use
876c0b746e5SOllivier Robert 		 * lastrec as both the reference time and receive time in
877c0b746e5SOllivier Robert 		 * order to avoid being cute, like setting the reference time
878c0b746e5SOllivier Robert 		 * later than the receive time, which may cause a paranoid
879c0b746e5SOllivier Robert 		 * protocol module to chuck out the data.
880c0b746e5SOllivier Robert 		 */
881c0b746e5SOllivier Robert 		mx4200_debug(peer, "mx4200_receive: process time: ");
882c0b746e5SOllivier Robert 		mx4200_debug(peer, "%4d-%03d %02d:%02d:%02d at %s, %.6f\n",
883c0b746e5SOllivier Robert 		    pp->year, pp->day, pp->hour, pp->minute, pp->second,
884c0b746e5SOllivier Robert 		    prettydate(&pp->lastrec), pp->offset);
8859c2daa00SOllivier Robert 		pp->lastref = pp->lastrec;
886c0b746e5SOllivier Robert 		refclock_receive(peer);
887c0b746e5SOllivier Robert 
888c0b746e5SOllivier Robert 		/*
889c0b746e5SOllivier Robert 		 * We have succeeded in answering the poll.
890c0b746e5SOllivier Robert 		 * Turn off the flag and return
891c0b746e5SOllivier Robert 		 */
892c0b746e5SOllivier Robert 		up->polled = 0;
893224ba2bdSOllivier Robert 		break;
894c0b746e5SOllivier Robert 
895c0b746e5SOllivier Robert 	/*
896c0b746e5SOllivier Robert 	 * Ignore all other sentence types
897c0b746e5SOllivier Robert 	 */
898224ba2bdSOllivier Robert 	default:
899224ba2bdSOllivier Robert 		break;
900224ba2bdSOllivier Robert 
901224ba2bdSOllivier Robert 	} /* switch (sentence_type) */
902224ba2bdSOllivier Robert 
903c0b746e5SOllivier Robert 	return;
904c0b746e5SOllivier Robert }
905c0b746e5SOllivier Robert 
906c0b746e5SOllivier Robert 
907c0b746e5SOllivier Robert /*
908c0b746e5SOllivier Robert  * Parse a mx4200 time recovery message. Returns a string if error.
909c0b746e5SOllivier Robert  *
910c0b746e5SOllivier Robert  * A typical message looks like this.  Checksum has already been stripped.
911c0b746e5SOllivier Robert  *
912c0b746e5SOllivier Robert  *    $PMVXG,830,T,YYYY,MM,DD,HH:MM:SS,U,S,FFFFFF,PPPPP,BBBBBB,LL
913c0b746e5SOllivier Robert  *
914c0b746e5SOllivier Robert  *	Field	Field Contents
915c0b746e5SOllivier Robert  *	-----	--------------
916c0b746e5SOllivier Robert  *		Block Label: $PMVXG
917c0b746e5SOllivier Robert  *		Sentence Type: 830=Time Recovery Results
918c0b746e5SOllivier Robert  *			This sentence is output approximately 1 second
919c0b746e5SOllivier Robert  *			preceding the 1PPS output.  It indicates the
920c0b746e5SOllivier Robert  *			exact time of the next pulse, whether or not the
921c0b746e5SOllivier Robert  *			time mark will be valid (based on operator-specified
922c0b746e5SOllivier Robert  *			error tolerance), the time to which the pulse is
923c0b746e5SOllivier Robert  *			synchronized, the receiver operating mode,
924c0b746e5SOllivier Robert  *			and the time error of the *last* 1PPS output.
925c0b746e5SOllivier Robert  *	1  char Time Mark Valid: T=Valid, F=Not Valid
926c0b746e5SOllivier Robert  *	2  int  Year: 1993-
927c0b746e5SOllivier Robert  *	3  int  Month of Year: 1-12
928c0b746e5SOllivier Robert  *	4  int  Day of Month: 1-31
929c0b746e5SOllivier Robert  *	5  int  Time of Day: HH:MM:SS
930c0b746e5SOllivier Robert  *	6  char Time Synchronization: U=UTC, G=GPS
931c0b746e5SOllivier Robert  *	7  char Time Recovery Mode: D=Dynamic, S=Static,
932c0b746e5SOllivier Robert  *			K=Known Position, N=No Time Recovery
933c0b746e5SOllivier Robert  *	8  int  Oscillator Offset: The filter's estimate of the oscillator
934c0b746e5SOllivier Robert  *			frequency error, in parts per billion (ppb).
935c0b746e5SOllivier Robert  *	9  int  Time Mark Error: The computed error of the *last* pulse
936c0b746e5SOllivier Robert  *			output, in nanoseconds.
937c0b746e5SOllivier Robert  *	10 int  User Time Bias: Operator specified bias, in nanoseconds
938c0b746e5SOllivier Robert  *	11 int  Leap Second Flag: Indicates that a leap second will
939c0b746e5SOllivier Robert  *			occur.  This value is usually zero, except during
9409c2daa00SOllivier Robert  *			the week prior to the leap second occurrence, when
941c0b746e5SOllivier Robert  *			this value will be set to +1 or -1.  A value of
942c0b746e5SOllivier Robert  *			+1 indicates that GPS time will be 1 second
943c0b746e5SOllivier Robert  *			further ahead of UTC time.
944c0b746e5SOllivier Robert  *
945c0b746e5SOllivier Robert  */
946c0b746e5SOllivier Robert static char *
947c0b746e5SOllivier Robert mx4200_parse_t(
948c0b746e5SOllivier Robert 	struct peer *peer
949c0b746e5SOllivier Robert 	)
950c0b746e5SOllivier Robert {
951c0b746e5SOllivier Robert 	struct refclockproc *pp;
952c0b746e5SOllivier Robert 	struct mx4200unit *up;
953c0b746e5SOllivier Robert 	char   time_mark_valid, time_sync, op_mode;
954c0b746e5SOllivier Robert 	int    sentence_type, valid;
955224ba2bdSOllivier Robert 	int    year, day_of_year, month, day_of_month;
9562b15cb3dSCy Schubert 	int    hour, minute, second, leapsec_warn;
957c0b746e5SOllivier Robert 	int    oscillator_offset, time_mark_error, time_bias;
958c0b746e5SOllivier Robert 
959c0b746e5SOllivier Robert 	pp = peer->procptr;
9602b15cb3dSCy Schubert 	up = pp->unitptr;
961c0b746e5SOllivier Robert 
9622b15cb3dSCy Schubert 	leapsec_warn = 0;  /* Not all receivers output leap second warnings (!) */
963224ba2bdSOllivier Robert 	sscanf(pp->a_lastcode,
964224ba2bdSOllivier Robert 		"$PMVXG,%d,%c,%d,%d,%d,%d:%d:%d,%c,%c,%d,%d,%d,%d",
965c0b746e5SOllivier Robert 		&sentence_type, &time_mark_valid, &year, &month, &day_of_month,
966224ba2bdSOllivier Robert 		&hour, &minute, &second, &time_sync, &op_mode,
9672b15cb3dSCy Schubert 		&oscillator_offset, &time_mark_error, &time_bias, &leapsec_warn);
968c0b746e5SOllivier Robert 
969c0b746e5SOllivier Robert 	if (sentence_type != PMVXG_D_TRECOVOUT)
970c0b746e5SOllivier Robert 		return ("wrong rec-type");
971c0b746e5SOllivier Robert 
972c0b746e5SOllivier Robert 	switch (time_mark_valid) {
973c0b746e5SOllivier Robert 		case 'T':
974c0b746e5SOllivier Robert 			valid = 1;
975c0b746e5SOllivier Robert 			break;
976c0b746e5SOllivier Robert 		case 'F':
977c0b746e5SOllivier Robert 			valid = 0;
978c0b746e5SOllivier Robert 			break;
979c0b746e5SOllivier Robert 		default:
980c0b746e5SOllivier Robert 			return ("bad pulse-valid");
981c0b746e5SOllivier Robert 	}
982c0b746e5SOllivier Robert 
983c0b746e5SOllivier Robert 	switch (time_sync) {
984c0b746e5SOllivier Robert 		case 'G':
985c0b746e5SOllivier Robert 			return ("synchronized to GPS; should be UTC");
986c0b746e5SOllivier Robert 		case 'U':
987c0b746e5SOllivier Robert 			break; /* UTC -> ok */
988c0b746e5SOllivier Robert 		default:
989c0b746e5SOllivier Robert 			return ("not synchronized to UTC");
990c0b746e5SOllivier Robert 	}
991c0b746e5SOllivier Robert 
992c0b746e5SOllivier Robert 	/*
993c0b746e5SOllivier Robert 	 * Check for insane time (allow for possible leap seconds)
994c0b746e5SOllivier Robert 	 */
995c0b746e5SOllivier Robert 	if (second > 60 || minute > 59 || hour > 23 ||
996c0b746e5SOllivier Robert 	    second <  0 || minute <  0 || hour <  0) {
997c0b746e5SOllivier Robert 		mx4200_debug(peer,
998c0b746e5SOllivier Robert 		    "mx4200_parse_t: bad time %02d:%02d:%02d",
999c0b746e5SOllivier Robert 		    hour, minute, second);
10002b15cb3dSCy Schubert 		if (leapsec_warn != 0)
10012b15cb3dSCy Schubert 			mx4200_debug(peer, " (leap %+d\n)", leapsec_warn);
1002c0b746e5SOllivier Robert 		mx4200_debug(peer, "\n");
1003c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_BADTIME);
1004c0b746e5SOllivier Robert 		return ("bad time");
1005c0b746e5SOllivier Robert 	}
1006c0b746e5SOllivier Robert 	if ( second == 60 ) {
1007c0b746e5SOllivier Robert 		msyslog(LOG_DEBUG,
1008c0b746e5SOllivier Robert 		    "mx4200: leap second! %02d:%02d:%02d",
1009c0b746e5SOllivier Robert 		    hour, minute, second);
1010c0b746e5SOllivier Robert 	}
1011c0b746e5SOllivier Robert 
1012c0b746e5SOllivier Robert 	/*
1013c0b746e5SOllivier Robert 	 * Check for insane date
1014c0b746e5SOllivier Robert 	 * (Certainly can't be any year before this code was last altered!)
1015c0b746e5SOllivier Robert 	 */
1016c0b746e5SOllivier Robert 	if (day_of_month > 31 || month > 12 ||
1017224ba2bdSOllivier Robert 	    day_of_month <  1 || month <  1 || year < YEAR_LAST_MODIFIED) {
1018c0b746e5SOllivier Robert 		mx4200_debug(peer,
1019c0b746e5SOllivier Robert 		    "mx4200_parse_t: bad date (%4d-%02d-%02d)\n",
1020c0b746e5SOllivier Robert 		    year, month, day_of_month);
1021c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_BADDATE);
1022c0b746e5SOllivier Robert 		return ("bad date");
1023c0b746e5SOllivier Robert 	}
1024c0b746e5SOllivier Robert 
1025c0b746e5SOllivier Robert 	/*
1026c0b746e5SOllivier Robert 	 * Silly Hack for MX4200:
1027c0b746e5SOllivier Robert 	 * ASCII message is for *next* 1PPS signal, but we have the
1028c0b746e5SOllivier Robert 	 * timestamp for the *last* 1PPS signal.  So we have to subtract
1029c0b746e5SOllivier Robert 	 * a second.  Discard if we are on a month boundary to avoid
1030c0b746e5SOllivier Robert 	 * possible leap seconds and leap days.
1031c0b746e5SOllivier Robert 	 */
1032c0b746e5SOllivier Robert 	second--;
1033c0b746e5SOllivier Robert 	if (second < 0) {
1034c0b746e5SOllivier Robert 		second = 59;
1035c0b746e5SOllivier Robert 		minute--;
1036c0b746e5SOllivier Robert 		if (minute < 0) {
1037c0b746e5SOllivier Robert 			minute = 59;
1038c0b746e5SOllivier Robert 			hour--;
1039c0b746e5SOllivier Robert 			if (hour < 0) {
1040c0b746e5SOllivier Robert 				hour = 23;
1041c0b746e5SOllivier Robert 				day_of_month--;
1042c0b746e5SOllivier Robert 				if (day_of_month < 1) {
1043c0b746e5SOllivier Robert 					return ("sorry, month boundary");
1044c0b746e5SOllivier Robert 				}
1045c0b746e5SOllivier Robert 			}
1046c0b746e5SOllivier Robert 		}
1047c0b746e5SOllivier Robert 	}
1048c0b746e5SOllivier Robert 
1049c0b746e5SOllivier Robert 	/*
1050c0b746e5SOllivier Robert 	 * Calculate Julian date
1051c0b746e5SOllivier Robert 	 */
1052c0b746e5SOllivier Robert 	if (!(day_of_year = mx4200_jday(year, month, day_of_month))) {
1053c0b746e5SOllivier Robert 		mx4200_debug(peer,
1054c0b746e5SOllivier Robert 		    "mx4200_parse_t: bad julian date %d (%4d-%02d-%02d)\n",
1055c0b746e5SOllivier Robert 		    day_of_year, year, month, day_of_month);
1056c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_BADDATE);
1057c0b746e5SOllivier Robert 		return("invalid julian date");
1058c0b746e5SOllivier Robert 	}
1059c0b746e5SOllivier Robert 
1060c0b746e5SOllivier Robert 	/*
1061c0b746e5SOllivier Robert 	 * Setup leap second indicator
1062c0b746e5SOllivier Robert 	 */
10632b15cb3dSCy Schubert 	switch (leapsec_warn) {
1064c0b746e5SOllivier Robert 		case 0:
1065c0b746e5SOllivier Robert 			pp->leap = LEAP_NOWARNING;
1066c0b746e5SOllivier Robert 			break;
1067c0b746e5SOllivier Robert 		case 1:
1068c0b746e5SOllivier Robert 			pp->leap = LEAP_ADDSECOND;
1069c0b746e5SOllivier Robert 			break;
1070c0b746e5SOllivier Robert 		case -1:
1071c0b746e5SOllivier Robert 			pp->leap = LEAP_DELSECOND;
1072c0b746e5SOllivier Robert 			break;
1073c0b746e5SOllivier Robert 		default:
1074c0b746e5SOllivier Robert 			pp->leap = LEAP_NOTINSYNC;
1075c0b746e5SOllivier Robert 	}
1076c0b746e5SOllivier Robert 
1077c0b746e5SOllivier Robert 	/*
1078c0b746e5SOllivier Robert 	 * Any change to the leap second warning status?
1079c0b746e5SOllivier Robert 	 */
10802b15cb3dSCy Schubert 	if (leapsec_warn != up->last_leap ) {
1081c0b746e5SOllivier Robert 		msyslog(LOG_DEBUG,
1082c0b746e5SOllivier Robert 		    "mx4200: leap second warning: %d to %d (%d)",
10832b15cb3dSCy Schubert 		    up->last_leap, leapsec_warn, pp->leap);
1084c0b746e5SOllivier Robert 	}
10852b15cb3dSCy Schubert 	up->last_leap = leapsec_warn;
1086c0b746e5SOllivier Robert 
1087c0b746e5SOllivier Robert 	/*
1088c0b746e5SOllivier Robert 	 * Copy time data for billboard monitoring.
1089c0b746e5SOllivier Robert 	 */
1090c0b746e5SOllivier Robert 
1091c0b746e5SOllivier Robert 	pp->year   = year;
1092c0b746e5SOllivier Robert 	pp->day    = day_of_year;
1093c0b746e5SOllivier Robert 	pp->hour   = hour;
1094c0b746e5SOllivier Robert 	pp->minute = minute;
1095c0b746e5SOllivier Robert 	pp->second = second;
1096c0b746e5SOllivier Robert 
1097c0b746e5SOllivier Robert 	/*
1098c0b746e5SOllivier Robert 	 * Toss if sentence is marked invalid
1099c0b746e5SOllivier Robert 	 */
1100c0b746e5SOllivier Robert 	if (!valid || pp->leap == LEAP_NOTINSYNC) {
1101c0b746e5SOllivier Robert 		mx4200_debug(peer, "mx4200_parse_t: time mark not valid\n");
1102c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_BADTIME);
1103c0b746e5SOllivier Robert 		return ("pulse invalid");
1104c0b746e5SOllivier Robert 	}
1105c0b746e5SOllivier Robert 
1106c0b746e5SOllivier Robert 	return (NULL);
1107c0b746e5SOllivier Robert }
1108c0b746e5SOllivier Robert 
1109c0b746e5SOllivier Robert /*
1110c0b746e5SOllivier Robert  * Calculate the checksum
1111c0b746e5SOllivier Robert  */
1112c0b746e5SOllivier Robert static u_char
1113c0b746e5SOllivier Robert mx4200_cksum(
1114c0b746e5SOllivier Robert 	register char *cp,
1115c0b746e5SOllivier Robert 	register int n
1116c0b746e5SOllivier Robert 	)
1117c0b746e5SOllivier Robert {
1118c0b746e5SOllivier Robert 	register u_char ck;
1119c0b746e5SOllivier Robert 
1120c0b746e5SOllivier Robert 	for (ck = 0; n-- > 0; cp++)
1121c0b746e5SOllivier Robert 		ck ^= *cp;
1122c0b746e5SOllivier Robert 	return (ck);
1123c0b746e5SOllivier Robert }
1124c0b746e5SOllivier Robert 
1125c0b746e5SOllivier Robert /*
1126c0b746e5SOllivier Robert  * Tables to compute the day of year.  Viva la leap.
1127c0b746e5SOllivier Robert  */
1128c0b746e5SOllivier Robert static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1129c0b746e5SOllivier Robert static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1130c0b746e5SOllivier Robert 
1131c0b746e5SOllivier Robert /*
1132c0b746e5SOllivier Robert  * Calculate the the Julian Day
1133c0b746e5SOllivier Robert  */
1134c0b746e5SOllivier Robert static int
1135c0b746e5SOllivier Robert mx4200_jday(
1136c0b746e5SOllivier Robert 	int year,
1137c0b746e5SOllivier Robert 	int month,
1138c0b746e5SOllivier Robert 	int day_of_month
1139c0b746e5SOllivier Robert 	)
1140c0b746e5SOllivier Robert {
1141c0b746e5SOllivier Robert 	register int day, i;
1142c0b746e5SOllivier Robert 	int leap_year;
1143c0b746e5SOllivier Robert 
1144c0b746e5SOllivier Robert 	/*
1145c0b746e5SOllivier Robert 	 * Is this a leap year ?
1146c0b746e5SOllivier Robert 	 */
1147c0b746e5SOllivier Robert 	if (year % 4) {
1148c0b746e5SOllivier Robert 		leap_year = 0; /* FALSE */
1149c0b746e5SOllivier Robert 	} else {
1150c0b746e5SOllivier Robert 		if (year % 100) {
1151c0b746e5SOllivier Robert 			leap_year = 1; /* TRUE */
1152c0b746e5SOllivier Robert 		} else {
1153c0b746e5SOllivier Robert 			if (year % 400) {
1154c0b746e5SOllivier Robert 				leap_year = 0; /* FALSE */
1155c0b746e5SOllivier Robert 			} else {
1156c0b746e5SOllivier Robert 				leap_year = 1; /* TRUE */
1157c0b746e5SOllivier Robert 			}
1158c0b746e5SOllivier Robert 		}
1159c0b746e5SOllivier Robert 	}
1160c0b746e5SOllivier Robert 
1161c0b746e5SOllivier Robert 	/*
1162c0b746e5SOllivier Robert 	 * Calculate the Julian Date
1163c0b746e5SOllivier Robert 	 */
1164c0b746e5SOllivier Robert 	day = day_of_month;
1165c0b746e5SOllivier Robert 
1166c0b746e5SOllivier Robert 	if (leap_year) {
1167c0b746e5SOllivier Robert 		/* a leap year */
1168c0b746e5SOllivier Robert 		if (day > day2tab[month - 1]) {
1169c0b746e5SOllivier Robert 			return (0);
1170c0b746e5SOllivier Robert 		}
1171c0b746e5SOllivier Robert 		for (i = 0; i < month - 1; i++)
1172c0b746e5SOllivier Robert 		    day += day2tab[i];
1173c0b746e5SOllivier Robert 	} else {
1174c0b746e5SOllivier Robert 		/* not a leap year */
1175c0b746e5SOllivier Robert 		if (day > day1tab[month - 1]) {
1176c0b746e5SOllivier Robert 			return (0);
1177c0b746e5SOllivier Robert 		}
1178c0b746e5SOllivier Robert 		for (i = 0; i < month - 1; i++)
1179c0b746e5SOllivier Robert 		    day += day1tab[i];
1180c0b746e5SOllivier Robert 	}
1181c0b746e5SOllivier Robert 	return (day);
1182c0b746e5SOllivier Robert }
1183c0b746e5SOllivier Robert 
1184c0b746e5SOllivier Robert /*
1185c0b746e5SOllivier Robert  * Parse a mx4200 position/height/velocity sentence.
1186c0b746e5SOllivier Robert  *
1187c0b746e5SOllivier Robert  * A typical message looks like this.  Checksum has already been stripped.
1188c0b746e5SOllivier Robert  *
1189c0b746e5SOllivier Robert  * $PMVXG,021,SSSSSS.SS,DDMM.MMMM,N,DDDMM.MMMM,E,HHHHH.H,GGGG.G,EEEE.E,WWWW.W,MM
1190c0b746e5SOllivier Robert  *
1191c0b746e5SOllivier Robert  *	Field	Field Contents
1192c0b746e5SOllivier Robert  *	-----	--------------
1193c0b746e5SOllivier Robert  *		Block Label: $PMVXG
1194c0b746e5SOllivier Robert  *		Sentence Type: 021=Position, Height Velocity Data
1195c0b746e5SOllivier Robert  *			This sentence gives the receiver position, height,
1196c0b746e5SOllivier Robert  *			navigation mode, and velocity north/east.
1197c0b746e5SOllivier Robert  *			*This sentence is intended for post-analysis
1198c0b746e5SOllivier Robert  *			applications.*
1199c0b746e5SOllivier Robert  *	1 float UTC measurement time (seconds into week)
1200c0b746e5SOllivier Robert  *	2 float WGS-84 Lattitude (degrees, minutes)
1201c0b746e5SOllivier Robert  *	3  char N=North, S=South
1202c0b746e5SOllivier Robert  *	4 float WGS-84 Longitude (degrees, minutes)
1203c0b746e5SOllivier Robert  *	5  char E=East, W=West
1204c0b746e5SOllivier Robert  *	6 float Altitude (meters above mean sea level)
1205c0b746e5SOllivier Robert  *	7 float Geoidal height (meters)
1206c0b746e5SOllivier Robert  *	8 float East velocity (m/sec)
1207c0b746e5SOllivier Robert  *	9 float West Velocity (m/sec)
1208c0b746e5SOllivier Robert  *	10  int Navigation Mode
1209c0b746e5SOllivier Robert  *		    Mode if navigating:
1210c0b746e5SOllivier Robert  *			1 = Position from remote device
1211c0b746e5SOllivier Robert  *			2 = 2-D position
1212c0b746e5SOllivier Robert  *			3 = 3-D position
1213c0b746e5SOllivier Robert  *			4 = 2-D differential position
1214c0b746e5SOllivier Robert  *			5 = 3-D differential position
1215c0b746e5SOllivier Robert  *			6 = Static
1216c0b746e5SOllivier Robert  *			8 = Position known -- reference station
1217c0b746e5SOllivier Robert  *			9 = Position known -- Navigator
1218c0b746e5SOllivier Robert  *		    Mode if not navigating:
1219c0b746e5SOllivier Robert  *			51 = Too few satellites
1220c0b746e5SOllivier Robert  *			52 = DOPs too large
1221c0b746e5SOllivier Robert  *			53 = Position STD too large
1222c0b746e5SOllivier Robert  *			54 = Velocity STD too large
1223c0b746e5SOllivier Robert  *			55 = Too many iterations for velocity
1224c0b746e5SOllivier Robert  *			56 = Too many iterations for position
1225c0b746e5SOllivier Robert  *			57 = 3 sat startup failed
1226c0b746e5SOllivier Robert  *			58 = Command abort
1227c0b746e5SOllivier Robert  */
1228c0b746e5SOllivier Robert static char *
1229c0b746e5SOllivier Robert mx4200_parse_p(
1230c0b746e5SOllivier Robert 	struct peer *peer
1231c0b746e5SOllivier Robert 	)
1232c0b746e5SOllivier Robert {
1233c0b746e5SOllivier Robert 	struct refclockproc *pp;
1234c0b746e5SOllivier Robert 	struct mx4200unit *up;
1235c0b746e5SOllivier Robert 	int sentence_type, mode;
1236224ba2bdSOllivier Robert 	double mtime, lat, lon, alt, geoid, vele, veln;
1237c0b746e5SOllivier Robert 	char   north_south, east_west;
1238c0b746e5SOllivier Robert 
1239c0b746e5SOllivier Robert 	pp = peer->procptr;
12402b15cb3dSCy Schubert 	up = pp->unitptr;
1241c0b746e5SOllivier Robert 
1242c0b746e5SOllivier Robert 	/* Should never happen! */
1243c0b746e5SOllivier Robert 	if (up->moving) return ("mobile platform - no pos!");
1244c0b746e5SOllivier Robert 
1245224ba2bdSOllivier Robert 	sscanf ( pp->a_lastcode,
1246224ba2bdSOllivier Robert 		"$PMVXG,%d,%lf,%lf,%c,%lf,%c,%lf,%lf,%lf,%lf,%d",
1247224ba2bdSOllivier Robert 		&sentence_type, &mtime, &lat, &north_south, &lon, &east_west,
1248224ba2bdSOllivier Robert 		&alt, &geoid, &vele, &veln, &mode);
1249c0b746e5SOllivier Robert 
1250c0b746e5SOllivier Robert 	/* Sentence type */
1251c0b746e5SOllivier Robert 	if (sentence_type != PMVXG_D_PHV)
1252c0b746e5SOllivier Robert 		return ("wrong rec-type");
1253c0b746e5SOllivier Robert 
1254c0b746e5SOllivier Robert 	/*
1255c0b746e5SOllivier Robert 	 * return if not navigating
1256c0b746e5SOllivier Robert 	 */
1257c0b746e5SOllivier Robert 	if (mode > 10)
1258c0b746e5SOllivier Robert 		return ("not navigating");
1259c0b746e5SOllivier Robert 	if (mode != 3 && mode != 5)
1260c0b746e5SOllivier Robert 		return ("not navigating in 3D");
1261c0b746e5SOllivier Robert 
1262c0b746e5SOllivier Robert 	/* Latitude (always +ve) and convert DDMM.MMMM to decimal */
1263c0b746e5SOllivier Robert 	if (lat <  0.0) return ("negative latitude");
1264c0b746e5SOllivier Robert 	if (lat > 9000.0) lat = 9000.0;
1265c0b746e5SOllivier Robert 	lat *= 0.01;
1266c0b746e5SOllivier Robert 	lat = ((int)lat) + (((lat - (int)lat)) * 1.6666666666666666);
1267c0b746e5SOllivier Robert 
1268c0b746e5SOllivier Robert 	/* North/South */
1269c0b746e5SOllivier Robert 	switch (north_south) {
1270c0b746e5SOllivier Robert 		case 'N':
1271c0b746e5SOllivier Robert 			break;
1272c0b746e5SOllivier Robert 		case 'S':
1273c0b746e5SOllivier Robert 			lat *= -1.0;
1274c0b746e5SOllivier Robert 			break;
1275c0b746e5SOllivier Robert 		default:
1276c0b746e5SOllivier Robert 			return ("invalid north/south indicator");
1277c0b746e5SOllivier Robert 	}
1278c0b746e5SOllivier Robert 
1279c0b746e5SOllivier Robert 	/* Longitude (always +ve) and convert DDDMM.MMMM to decimal */
1280c0b746e5SOllivier Robert 	if (lon <   0.0) return ("negative longitude");
1281c0b746e5SOllivier Robert 	if (lon > 180.0) lon = 180.0;
1282c0b746e5SOllivier Robert 	lon *= 0.01;
1283c0b746e5SOllivier Robert 	lon = ((int)lon) + (((lon - (int)lon)) * 1.6666666666666666);
1284c0b746e5SOllivier Robert 
1285c0b746e5SOllivier Robert 	/* East/West */
1286c0b746e5SOllivier Robert 	switch (east_west) {
1287c0b746e5SOllivier Robert 		case 'E':
1288c0b746e5SOllivier Robert 			break;
1289c0b746e5SOllivier Robert 		case 'W':
1290c0b746e5SOllivier Robert 			lon *= -1.0;
1291c0b746e5SOllivier Robert 			break;
1292c0b746e5SOllivier Robert 		default:
1293c0b746e5SOllivier Robert 			return ("invalid east/west indicator");
1294c0b746e5SOllivier Robert 	}
1295c0b746e5SOllivier Robert 
1296c0b746e5SOllivier Robert 	/*
1297c0b746e5SOllivier Robert 	 * Normalize longitude to near 0 degrees.
1298c0b746e5SOllivier Robert 	 * Assume all data are clustered around first reading.
1299c0b746e5SOllivier Robert 	 */
1300c0b746e5SOllivier Robert 	if (up->central_meridian == NOT_INITIALIZED) {
1301c0b746e5SOllivier Robert 		up->central_meridian = lon;
1302c0b746e5SOllivier Robert 		mx4200_debug(peer,
1303c0b746e5SOllivier Robert 		    "mx4200_receive: central meridian =  %.9f \n",
1304c0b746e5SOllivier Robert 		    up->central_meridian);
1305c0b746e5SOllivier Robert 	}
1306c0b746e5SOllivier Robert 	lon -= up->central_meridian;
1307c0b746e5SOllivier Robert 	if (lon < -180.0) lon += 360.0;
1308c0b746e5SOllivier Robert 	if (lon >  180.0) lon -= 360.0;
1309c0b746e5SOllivier Robert 
1310c0b746e5SOllivier Robert 	/*
1311224ba2bdSOllivier Robert 	 * Calculate running averages
1312c0b746e5SOllivier Robert 	 */
1313c0b746e5SOllivier Robert 
1314224ba2bdSOllivier Robert 	up->avg_lon = (up->N_fixes * up->avg_lon) + lon;
1315224ba2bdSOllivier Robert 	up->avg_lat = (up->N_fixes * up->avg_lat) + lat;
1316224ba2bdSOllivier Robert 	up->avg_alt = (up->N_fixes * up->avg_alt) + alt;
1317c0b746e5SOllivier Robert 
1318224ba2bdSOllivier Robert 	up->N_fixes += 1.0;
1319224ba2bdSOllivier Robert 
1320224ba2bdSOllivier Robert 	up->avg_lon /= up->N_fixes;
1321224ba2bdSOllivier Robert 	up->avg_lat /= up->N_fixes;
1322224ba2bdSOllivier Robert 	up->avg_alt /= up->N_fixes;
1323c0b746e5SOllivier Robert 
1324c0b746e5SOllivier Robert 	mx4200_debug(peer,
1325224ba2bdSOllivier Robert 	    "mx4200_receive: position rdg %.0f: %.9f %.9f %.4f (CM=%.9f)\n",
1326224ba2bdSOllivier Robert 	    up->N_fixes, lat, lon, alt, up->central_meridian);
1327c0b746e5SOllivier Robert 
1328c0b746e5SOllivier Robert 	return (NULL);
1329c0b746e5SOllivier Robert }
1330c0b746e5SOllivier Robert 
1331c0b746e5SOllivier Robert /*
1332c0b746e5SOllivier Robert  * Parse a mx4200 Status sentence
1333c0b746e5SOllivier Robert  * Parse a mx4200 Mode Data sentence
1334c0b746e5SOllivier Robert  * Parse a mx4200 Software Configuration sentence
1335c0b746e5SOllivier Robert  * Parse a mx4200 Time Recovery Parameters Currently in Use sentence
1336c0b746e5SOllivier Robert  * (used only for logging raw strings)
1337c0b746e5SOllivier Robert  *
1338c0b746e5SOllivier Robert  * A typical message looks like this.  Checksum has already been stripped.
1339c0b746e5SOllivier Robert  *
1340c0b746e5SOllivier Robert  * $PMVXG,000,XXX,XX,X,HHMM,X
1341c0b746e5SOllivier Robert  *
1342c0b746e5SOllivier Robert  *	Field	Field Contents
1343c0b746e5SOllivier Robert  *	-----	--------------
1344c0b746e5SOllivier Robert  *		Block Label: $PMVXG
1345c0b746e5SOllivier Robert  *		Sentence Type: 000=Status.
1346c0b746e5SOllivier Robert  *			Returns status of the receiver to the controller.
1347c0b746e5SOllivier Robert  *	1	Current Receiver Status:
1348c0b746e5SOllivier Robert  *		ACQ = Satellite re-acquisition
1349c0b746e5SOllivier Robert  *		ALT = Constellation selection
1350c0b746e5SOllivier Robert  *		COR = Providing corrections (for reference stations only)
1351c0b746e5SOllivier Robert  *		IAC = Initial acquisition
1352c0b746e5SOllivier Robert  *		IDL = Idle, no satellites
1353c0b746e5SOllivier Robert  *		NAV = Navigation
1354c0b746e5SOllivier Robert  *		STS = Search the Sky (no almanac available)
1355c0b746e5SOllivier Robert  *		TRK = Tracking
1356c0b746e5SOllivier Robert  *	2	Number of satellites that should be visible
1357c0b746e5SOllivier Robert  *	3	Number of satellites being tracked
1358c0b746e5SOllivier Robert  *	4	Time since last navigation status if not currently navigating
1359c0b746e5SOllivier Robert  *		(hours, minutes)
1360c0b746e5SOllivier Robert  *	5	Initialization status:
1361c0b746e5SOllivier Robert  *		0 = Waiting for initialization parameters
1362c0b746e5SOllivier Robert  *		1 = Initialization completed
1363c0b746e5SOllivier Robert  *
1364c0b746e5SOllivier Robert  * A typical message looks like this.  Checksum has already been stripped.
1365c0b746e5SOllivier Robert  *
1366c0b746e5SOllivier Robert  * $PMVXG,004,C,R,D,H.HH,V.VV,TT,HHHH,VVVV,T
1367c0b746e5SOllivier Robert  *
1368c0b746e5SOllivier Robert  *	Field	Field Contents
1369c0b746e5SOllivier Robert  *	-----	--------------
1370c0b746e5SOllivier Robert  *		Block Label: $PMVXG
1371c0b746e5SOllivier Robert  *		Sentence Type: 004=Software Configuration.
1372c0b746e5SOllivier Robert  *			Defines the navigation mode and criteria for
1373c0b746e5SOllivier Robert  *			acceptable navigation for the receiver.
1374c0b746e5SOllivier Robert  *	1	Constrain Altitude Mode:
1375c0b746e5SOllivier Robert  *		0 = Auto.  Constrain altitude (2-D solution) and use
1376c0b746e5SOllivier Robert  *		    manual altitude input when 3 sats avalable.  Do
1377c0b746e5SOllivier Robert  *		    not constrain altitude (3-D solution) when 4 sats
1378c0b746e5SOllivier Robert  *		    available.
1379c0b746e5SOllivier Robert  *		1 = Always constrain altitude (2-D solution).
1380c0b746e5SOllivier Robert  *		2 = Never constrain altitude (3-D solution).
1381c0b746e5SOllivier Robert  *		3 = Coast.  Constrain altitude (2-D solution) and use
1382c0b746e5SOllivier Robert  *		    last GPS altitude calculation when 3 sats avalable.
1383c0b746e5SOllivier Robert  *		    Do not constrain altitude (3-D solution) when 4 sats
1384c0b746e5SOllivier Robert  *		    available.
1385c0b746e5SOllivier Robert  *	2	Altitude Reference: (always 0 for MX4200)
1386c0b746e5SOllivier Robert  *		0 = Ellipsoid
1387c0b746e5SOllivier Robert  *		1 = Geoid (MSL)
1388c0b746e5SOllivier Robert  *	3	Differential Navigation Control:
1389c0b746e5SOllivier Robert  *		0 = Disabled
1390c0b746e5SOllivier Robert  *		1 = Enabled
1391c0b746e5SOllivier Robert  *	4	Horizontal Acceleration Constant (m/sec**2)
1392c0b746e5SOllivier Robert  *	5	Vertical Acceleration Constant (m/sec**2) (0 for MX4200)
1393c0b746e5SOllivier Robert  *	6	Tracking Elevation Limit (degrees)
1394c0b746e5SOllivier Robert  *	7	HDOP Limit
1395c0b746e5SOllivier Robert  *	8	VDOP Limit
1396c0b746e5SOllivier Robert  *	9	Time Output Mode:
1397c0b746e5SOllivier Robert  *		U = UTC
1398c0b746e5SOllivier Robert  *		L = Local time
1399c0b746e5SOllivier Robert  *	10	Local Time Offset (minutes) (absent on MX4200)
1400c0b746e5SOllivier Robert  *
1401c0b746e5SOllivier Robert  * A typical message looks like this.  Checksum has already been stripped.
1402c0b746e5SOllivier Robert  *
1403c0b746e5SOllivier Robert  * $PMVXG,030,NNNN,FFF
1404c0b746e5SOllivier Robert  *
1405c0b746e5SOllivier Robert  *	Field	Field Contents
1406c0b746e5SOllivier Robert  *	-----	--------------
1407c0b746e5SOllivier Robert  *		Block Label: $PMVXG
1408c0b746e5SOllivier Robert  *		Sentence Type: 030=Software Configuration.
1409c0b746e5SOllivier Robert  *			This sentence contains the navigation processor
1410c0b746e5SOllivier Robert  *			and baseband firmware version numbers.
1411c0b746e5SOllivier Robert  *	1	Nav Processor Version Number
1412c0b746e5SOllivier Robert  *	2	Baseband Firmware Version Number
1413c0b746e5SOllivier Robert  *
1414c0b746e5SOllivier Robert  * A typical message looks like this.  Checksum has already been stripped.
1415c0b746e5SOllivier Robert  *
1416c0b746e5SOllivier Robert  * $PMVXG,523,M,S,M,EEEE,BBBBBB,C,R
1417c0b746e5SOllivier Robert  *
1418c0b746e5SOllivier Robert  *	Field	Field Contents
1419c0b746e5SOllivier Robert  *	-----	--------------
1420c0b746e5SOllivier Robert  *		Block Label: $PMVXG
1421c0b746e5SOllivier Robert  *		Sentence Type: 523=Time Recovery Parameters Currently in Use.
1422c0b746e5SOllivier Robert  *			This sentence contains the configuration of the
1423c0b746e5SOllivier Robert  *			time recovery feature of the receiver.
1424c0b746e5SOllivier Robert  *	1	Time Recovery Mode:
1425c0b746e5SOllivier Robert  *		D = Dynamic; solve for position and time while moving
1426c0b746e5SOllivier Robert  *		S = Static; solve for position and time while stationary
1427c0b746e5SOllivier Robert  *		K = Known position input, solve for time only
1428c0b746e5SOllivier Robert  *		N = No time recovery
1429c0b746e5SOllivier Robert  *	2	Time Synchronization:
1430c0b746e5SOllivier Robert  *		U = UTC time
1431c0b746e5SOllivier Robert  *		G = GPS time
1432c0b746e5SOllivier Robert  *	3	Time Mark Mode:
1433c0b746e5SOllivier Robert  *		A = Always output a time pulse
1434c0b746e5SOllivier Robert  *		V = Only output time pulse if time is valid (as determined
1435c0b746e5SOllivier Robert  *		    by Maximum Time Error)
1436c0b746e5SOllivier Robert  *	4	Maximum Time Error - the maximum error (in nanoseconds) for
1437c0b746e5SOllivier Robert  *		which a time mark will be considered valid.
1438c0b746e5SOllivier Robert  *	5	User Time Bias - external bias in nanoseconds
1439c0b746e5SOllivier Robert  *	6	Time Message Control:
1440c0b746e5SOllivier Robert  *		0 = Do not output the time recovery message
1441c0b746e5SOllivier Robert  *		1 = Output the time recovery message (record 830) to
1442c0b746e5SOllivier Robert  *		    Control port
1443c0b746e5SOllivier Robert  *		2 = Output the time recovery message (record 830) to
1444c0b746e5SOllivier Robert  *		    Equipment port
1445c0b746e5SOllivier Robert  *	7	Reserved
1446c0b746e5SOllivier Robert  *	8	Position Known PRN (absent on MX 4200)
1447c0b746e5SOllivier Robert  *
1448c0b746e5SOllivier Robert  */
1449c0b746e5SOllivier Robert static char *
1450c0b746e5SOllivier Robert mx4200_parse_s(
1451c0b746e5SOllivier Robert 	struct peer *peer
1452c0b746e5SOllivier Robert 	)
1453c0b746e5SOllivier Robert {
1454c0b746e5SOllivier Robert 	struct refclockproc *pp;
1455c0b746e5SOllivier Robert 	struct mx4200unit *up;
1456c0b746e5SOllivier Robert 	int sentence_type;
1457c0b746e5SOllivier Robert 
1458c0b746e5SOllivier Robert 	pp = peer->procptr;
14592b15cb3dSCy Schubert 	up = pp->unitptr;
1460c0b746e5SOllivier Robert 
1461c0b746e5SOllivier Robert         sscanf ( pp->a_lastcode, "$PMVXG,%d", &sentence_type);
1462c0b746e5SOllivier Robert 
1463c0b746e5SOllivier Robert 	/* Sentence type */
1464c0b746e5SOllivier Robert 	switch (sentence_type) {
1465c0b746e5SOllivier Robert 
1466c0b746e5SOllivier Robert 		case PMVXG_D_STATUS:
1467c0b746e5SOllivier Robert 			msyslog(LOG_DEBUG,
1468c0b746e5SOllivier Robert 			  "mx4200: status: %s", pp->a_lastcode);
1469c0b746e5SOllivier Robert 			break;
1470c0b746e5SOllivier Robert 		case PMVXG_D_MODEDATA:
1471c0b746e5SOllivier Robert 			msyslog(LOG_DEBUG,
1472c0b746e5SOllivier Robert 			  "mx4200: mode data: %s", pp->a_lastcode);
1473c0b746e5SOllivier Robert 			break;
1474c0b746e5SOllivier Robert 		case PMVXG_D_SOFTCONF:
1475c0b746e5SOllivier Robert 			msyslog(LOG_DEBUG,
1476c0b746e5SOllivier Robert 			  "mx4200: firmware configuration: %s", pp->a_lastcode);
1477c0b746e5SOllivier Robert 			break;
1478c0b746e5SOllivier Robert 		case PMVXG_D_TRECOVUSEAGE:
1479c0b746e5SOllivier Robert 			msyslog(LOG_DEBUG,
1480c0b746e5SOllivier Robert 			  "mx4200: time recovery parms: %s", pp->a_lastcode);
1481c0b746e5SOllivier Robert 			break;
1482c0b746e5SOllivier Robert 		default:
1483c0b746e5SOllivier Robert 			return ("wrong rec-type");
1484c0b746e5SOllivier Robert 	}
1485c0b746e5SOllivier Robert 
1486c0b746e5SOllivier Robert 	return (NULL);
1487c0b746e5SOllivier Robert }
1488c0b746e5SOllivier Robert 
1489c0b746e5SOllivier Robert /*
1490224ba2bdSOllivier Robert  * Process a PPS signal, placing a timestamp in pp->lastrec.
1491c0b746e5SOllivier Robert  */
1492c0b746e5SOllivier Robert static int
1493c0b746e5SOllivier Robert mx4200_pps(
1494c0b746e5SOllivier Robert 	struct peer *peer
1495c0b746e5SOllivier Robert 	)
1496c0b746e5SOllivier Robert {
1497c0b746e5SOllivier Robert 	int temp_serial;
1498c0b746e5SOllivier Robert 	struct refclockproc *pp;
1499c0b746e5SOllivier Robert 	struct mx4200unit *up;
1500c0b746e5SOllivier Robert 
1501224ba2bdSOllivier Robert 	struct timespec timeout;
1502c0b746e5SOllivier Robert 
1503c0b746e5SOllivier Robert 	pp = peer->procptr;
15042b15cb3dSCy Schubert 	up = pp->unitptr;
1505c0b746e5SOllivier Robert 
1506c0b746e5SOllivier Robert 	/*
1507c0b746e5SOllivier Robert 	 * Grab the timestamp of the PPS signal.
1508c0b746e5SOllivier Robert 	 */
1509224ba2bdSOllivier Robert 	temp_serial = up->pps_i.assert_sequence;
1510224ba2bdSOllivier Robert 	timeout.tv_sec  = 0;
1511224ba2bdSOllivier Robert 	timeout.tv_nsec = 0;
1512224ba2bdSOllivier Robert 	if (time_pps_fetch(up->pps_h, PPS_TSFMT_TSPEC, &(up->pps_i),
1513224ba2bdSOllivier Robert 			&timeout) < 0) {
1514c0b746e5SOllivier Robert 		mx4200_debug(peer,
15152b15cb3dSCy Schubert 		  "mx4200_pps: time_pps_fetch: serial=%lu, %m\n",
15162b15cb3dSCy Schubert 		     (unsigned long)up->pps_i.assert_sequence);
1517c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_FAULT);
1518c0b746e5SOllivier Robert 		return(1);
1519c0b746e5SOllivier Robert 	}
1520224ba2bdSOllivier Robert 	if (temp_serial == up->pps_i.assert_sequence) {
1521c0b746e5SOllivier Robert 		mx4200_debug(peer,
15222b15cb3dSCy Schubert 		   "mx4200_pps: assert_sequence serial not incrementing: %lu\n",
1523ea906c41SOllivier Robert 			(unsigned long)up->pps_i.assert_sequence);
1524c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_FAULT);
1525c0b746e5SOllivier Robert 		return(1);
1526c0b746e5SOllivier Robert 	}
1527c0b746e5SOllivier Robert 	/*
1528c0b746e5SOllivier Robert 	 * Check pps serial number against last one
1529c0b746e5SOllivier Robert 	 */
1530224ba2bdSOllivier Robert 	if (up->lastserial + 1 != up->pps_i.assert_sequence &&
1531224ba2bdSOllivier Robert 	    up->lastserial != 0) {
1532224ba2bdSOllivier Robert 		if (up->pps_i.assert_sequence == up->lastserial) {
1533c0b746e5SOllivier Robert 			mx4200_debug(peer, "mx4200_pps: no new pps event\n");
1534224ba2bdSOllivier Robert 		} else {
15352b15cb3dSCy Schubert 			mx4200_debug(peer, "mx4200_pps: missed %lu pps events\n",
1536ea906c41SOllivier Robert 			    up->pps_i.assert_sequence - up->lastserial - 1UL);
1537224ba2bdSOllivier Robert 		}
1538c0b746e5SOllivier Robert 		refclock_report(peer, CEVNT_FAULT);
1539c0b746e5SOllivier Robert 	}
1540224ba2bdSOllivier Robert 	up->lastserial = up->pps_i.assert_sequence;
1541c0b746e5SOllivier Robert 
1542c0b746e5SOllivier Robert 	/*
1543c0b746e5SOllivier Robert 	 * Return the timestamp in pp->lastrec
1544c0b746e5SOllivier Robert 	 */
1545224ba2bdSOllivier Robert 
1546224ba2bdSOllivier Robert 	pp->lastrec.l_ui = up->pps_i.assert_timestamp.tv_sec +
1547224ba2bdSOllivier Robert 			   (u_int32) JAN_1970;
1548224ba2bdSOllivier Robert 	pp->lastrec.l_uf = ((double)(up->pps_i.assert_timestamp.tv_nsec) *
1549224ba2bdSOllivier Robert 			   4.2949672960) + 0.5;
1550c0b746e5SOllivier Robert 
1551c0b746e5SOllivier Robert 	return(0);
1552c0b746e5SOllivier Robert }
1553c0b746e5SOllivier Robert 
1554c0b746e5SOllivier Robert /*
1555c0b746e5SOllivier Robert  * mx4200_debug - print debug messages
1556c0b746e5SOllivier Robert  */
1557c0b746e5SOllivier Robert static void
1558c0b746e5SOllivier Robert mx4200_debug(struct peer *peer, char *fmt, ...)
1559c0b746e5SOllivier Robert {
1560ea906c41SOllivier Robert #ifdef DEBUG
1561c0b746e5SOllivier Robert 	va_list ap;
1562c0b746e5SOllivier Robert 	struct refclockproc *pp;
1563c0b746e5SOllivier Robert 	struct mx4200unit *up;
1564c0b746e5SOllivier Robert 
1565c0b746e5SOllivier Robert 	if (debug) {
1566c0b746e5SOllivier Robert 		va_start(ap, fmt);
1567c0b746e5SOllivier Robert 
1568c0b746e5SOllivier Robert 		pp = peer->procptr;
15692b15cb3dSCy Schubert 		up = pp->unitptr;
1570c0b746e5SOllivier Robert 
1571c0b746e5SOllivier Robert 		/*
1572c0b746e5SOllivier Robert 		 * Print debug message to stdout
1573c0b746e5SOllivier Robert 		 * In the future, we may want to get get more creative...
1574c0b746e5SOllivier Robert 		 */
15752b15cb3dSCy Schubert 		mvprintf(fmt, ap);
1576c0b746e5SOllivier Robert 
1577c0b746e5SOllivier Robert 		va_end(ap);
1578c0b746e5SOllivier Robert 	}
1579ea906c41SOllivier Robert #endif
1580c0b746e5SOllivier Robert }
1581c0b746e5SOllivier Robert 
1582c0b746e5SOllivier Robert /*
1583c0b746e5SOllivier Robert  * Send a character string to the receiver.  Checksum is appended here.
1584c0b746e5SOllivier Robert  */
1585a151a66cSOllivier Robert #if defined(__STDC__)
1586c0b746e5SOllivier Robert static void
1587c0b746e5SOllivier Robert mx4200_send(struct peer *peer, char *fmt, ...)
1588c0b746e5SOllivier Robert #else
1589a151a66cSOllivier Robert static void
1590c0b746e5SOllivier Robert mx4200_send(peer, fmt, va_alist)
1591c0b746e5SOllivier Robert      struct peer *peer;
1592c0b746e5SOllivier Robert      char *fmt;
1593c0b746e5SOllivier Robert      va_dcl
1594c0b746e5SOllivier Robert #endif /* __STDC__ */
1595c0b746e5SOllivier Robert {
1596c0b746e5SOllivier Robert 	struct refclockproc *pp;
1597c0b746e5SOllivier Robert 	struct mx4200unit *up;
1598c0b746e5SOllivier Robert 
1599f0574f5cSXin LI 	register char *cp, *ep;
1600c0b746e5SOllivier Robert 	register int n, m;
1601c0b746e5SOllivier Robert 	va_list ap;
1602c0b746e5SOllivier Robert 	char buf[1024];
1603c0b746e5SOllivier Robert 	u_char ck;
1604c0b746e5SOllivier Robert 
1605f0574f5cSXin LI 	pp = peer->procptr;
1606f0574f5cSXin LI 	up = pp->unitptr;
1607f0574f5cSXin LI 
1608f0574f5cSXin LI 	cp = buf;
1609f0574f5cSXin LI 	ep = cp + sizeof(buf);
1610f0574f5cSXin LI 	*cp++ = '$';
1611f0574f5cSXin LI 
1612a151a66cSOllivier Robert #if defined(__STDC__)
1613c0b746e5SOllivier Robert 	va_start(ap, fmt);
1614c0b746e5SOllivier Robert #else
1615c0b746e5SOllivier Robert 	va_start(ap);
1616c0b746e5SOllivier Robert #endif /* __STDC__ */
1617f0574f5cSXin LI 	n = VSNPRINTF((cp, (size_t)(ep - cp), fmt, ap));
1618f0574f5cSXin LI 	va_end(ap);
1619f0574f5cSXin LI 	if (n < 0 || (size_t)n >= (size_t)(ep - cp))
1620f0574f5cSXin LI 		goto overflow;
1621c0b746e5SOllivier Robert 
1622c0b746e5SOllivier Robert 	ck = mx4200_cksum(cp, n);
1623c0b746e5SOllivier Robert 	cp += n;
1624f0574f5cSXin LI 	n = SNPRINTF((cp, (size_t)(ep - cp), "*%02X\r\n", ck));
1625f0574f5cSXin LI 	if (n < 0 || (size_t)n >= (size_t)(ep - cp))
1626f0574f5cSXin LI 		goto overflow;
1627f0574f5cSXin LI 	cp += n;
1628f0574f5cSXin LI 	m = write(pp->io.fd, buf, (unsigned)(cp - buf));
1629c0b746e5SOllivier Robert 	if (m < 0)
1630c0b746e5SOllivier Robert 		msyslog(LOG_ERR, "mx4200_send: write: %m (%s)", buf);
1631c0b746e5SOllivier Robert 	mx4200_debug(peer, "mx4200_send: %d %s\n", m, buf);
1632f0574f5cSXin LI 
1633f0574f5cSXin LI   overflow:
1634f0574f5cSXin LI 	msyslog(LOG_ERR, "mx4200_send: %s", "data exceeds buffer size");
1635c0b746e5SOllivier Robert }
1636c0b746e5SOllivier Robert 
1637c0b746e5SOllivier Robert #else
1638*f5f40dd6SCy Schubert NONEMPTY_TRANSLATION_UNIT
1639c0b746e5SOllivier Robert #endif /* REFCLOCK */
1640