Forwarded: Wed, 12 Feb 1997 19:09:34 -0500
Forwarded: "mills@udel.edu "
Replied: Wed, 12 Feb 1997 19:08:47 -0500
Replied: "Marc Brett <ltso@london.waii.com> Marc.Brett@phoenix.london.waii.com,
Replied: Frank.Vance@phoenix.london.waii.com, leres@ee.lbl.gov"
Received: from snow-white.ee.udel.edu by whimsy.udel.edu id aa10102;
          12 Feb 97 14:48 GMT
Received: by diamond.waii.com; id IAA27406; Wed, 12 Feb 1997 08:48:09 -0600
Received: from mail.wg.waii.com(137.144.128.17) by diamond.waii.com via smap (3.2)
	id xma027391; Wed, 12 Feb 97 08:47:20 -0600
Received: from merlin.london.waii.com (merlin.london.waii.com [136.250.33.1]) by mail1.wg.waii.com (8.7.5/8.7.3) with SMTP id IAA21980; Wed, 12 Feb 1997 08:47:16 -0600
Received: from phoenix (phoenix.london.waii.com) by merlin.london.waii.com with SMTP id AA43837
  (5.65c/IDA-1.4.4); Wed, 12 Feb 1997 14:47:11 GMT
Received: by phoenix (4.1/SMI-4.1)
	id AA08374; Wed, 12 Feb 97 14:47:06 GMT
From: Marc Brett <ltso@london.waii.com>
Message-Id: <9702121447.AA08374@phoenix>
Subject: MX4200 driver rewrite
To: stenn@whimsy.udel.edu
Date: Wed, 12 Feb 1997 14:47:05 +0000 (GMT)
Cc: Marc.Brett@phoenix.london.waii.com, Frank.Vance@phoenix.london.waii.com, 
    leres@ee.lbl.gov
X-Mailer: ELM [version 2.4 PL23]
Mime-Version: 1.0
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 7bit


Harlan,

Here is the MX4200 (Type 9) driver rewritten into the "new style"
driver format.  Along the way, there have been some major and
minor enhancements.  These include:

	1. Revamped I/O.  This uses what I presume are standard
	   calls to NTP libraries: refclock_open, refclock_gtlin,
	   and friends, rather than reading the passed buffer
	   arguments.  This is working very well on my setup
	   (SPARC IPX, SunOS 4.1.3, STREAMS support from
	   ppsclock-1.6a1), but it has not been tested on
	   any other machine.  Would be grateful for some
	   assistance in this area (Craig?).

	2. Reworked the filters slightly to examine the last
	   64 time samples in a roll-along fashion.

	3. Filled out the pp structure with proper data, so xntpdc
	   clkbug 127.127.9.0 responds with reasonable values.
	   The last 10 values are the minimum and maximum offsets,
	   followed by the 8 survivors of the median filter algorithm.

	4. Added position-averaging logic to filter out effects
	   of GPS SA, and switch to time-only mode after 24 hours.
	   This should improve the accuracy of the time samples
	   marginally.

	5. Numerous code reorganizations, tweaks, comments, etc.
	   Incorporates all recent changes up to xntp3-5.89.5.
	   Better to send the whole lot rather than diffs.

There are three files to change.  include/mx4200.h had a minor
error in a definition which isn't even used.  html/driver9.html
is the new documentation, and xntpd/refclock_mx4200.c is the
new code.  Diffs for the first 2, and the complete file for the
last.
Best Regards,

--
Marc Brett              Marc.Brett@waii.com
Western Geophysical     Tel: +44 181 560 3160 ext. 4178




*** include/mx4200.h.orig	Sat Jun 11 20:53:40 1994
--- include/mx4200.h	Tue Feb 11 18:01:57 1997
***************
*** 5,11 ****
  #define PMVXG_S_SATHEALTH	2	/* satellite health control */
  #define PMVXG_S_DIFFNAV		3	/* differential navigation control */
  #define PMVXG_S_PORTCONF	7	/* control port configuration */
! #define PMVXG_S_GETSELFTEST	3	/* self test (request results) */
  #define PMVXG_S_RTCMCONF	16	/* RTCM port configuration */
  #define PMVXG_S_PASSTHRU	17	/* equipment port pass-thru config */
  #define PMVXG_S_RESTART		18	/* restart control */
--- 5,11 ----
  #define PMVXG_S_SATHEALTH	2	/* satellite health control */
  #define PMVXG_S_DIFFNAV		3	/* differential navigation control */
  #define PMVXG_S_PORTCONF	7	/* control port configuration */
! #define PMVXG_S_GETSELFTEST	13	/* self test (request results) */
  #define PMVXG_S_RTCMCONF	16	/* RTCM port configuration */
  #define PMVXG_S_PASSTHRU	17	/* equipment port pass-thru config */
  #define PMVXG_S_RESTART		18	/* restart control */





*** html/driver9.html.orig.html	Thu Oct 10 18:20:39 1996
--- html/driver9.html	Tue Feb 11 19:58:18 1997
***************
*** 7,22 ****
  
  <p><h4>Synopsis</h4>
  
! <p>Address: 127.127.5.<var>u</var>
  <br>Reference ID: GPS
  <br>Driver ID: GPS-MX4200
! <br>Serial Port: <code>/dev/gps<var>u</var></code>; 9600 baud, 8-bits,
  no parity
  <br>Features: <code>ppsclock</code> (required)
  
  <p><h4>Description</h4>
  
! <p>This driver supports the Magnavox MX4200 Navigation Receiver adapted
  to precision timing applications. It requires the <code>ppsclock</code>
  line discipline or streams module described in the <a
  href="ldisc.html">Line Disciplines and Streams Drivers</a> page. It also
--- 7,22 ----
  
  <p><h4>Synopsis</h4>
  
! <p>Address: 127.127.9.<var>u</var>
  <br>Reference ID: GPS
  <br>Driver ID: GPS-MX4200
! <br>Serial Port: <code>/dev/gps<var>u</var></code>; 4800 baud, 8-bits,
  no parity
  <br>Features: <code>ppsclock</code> (required)
  
  <p><h4>Description</h4>
  
! <p>This driver supports the Magnavox MX 4200 Navigation Receiver adapted
  to precision timing applications. It requires the <code>ppsclock</code>
  line discipline or streams module described in the <a
  href="ldisc.html">Line Disciplines and Streams Drivers</a> page. It also
***************
*** 24,33 ****
  the <a href="pps.html">Pulse-per-second (PPS) Signal Interfacing</a>
  page.
  
  <p><h4>Monitor Data</h4>
  
! <p>No <code>filegen clockstats</code> monitor data are produced by this
! driver.
  
  <p><h4>Fudge Factors</h4>
  
--- 24,50 ----
  the <a href="pps.html">Pulse-per-second (PPS) Signal Interfacing</a>
  page.
  
+ <p>This driver supports all compatible receivers such as the 6-channel
+ MX 4200, MX 4200D, and the 12-channel MX 9212, MX 9012R, MX 9112.
+ 
+ <p>This driver assumes that the GPS antenna is in a fixed location.  The
+ receiver is initially placed in a "Static, 3D Nav" mode, where latitude,
+ longitude, elevation and time are calculated for a fixed station.  A
+ DOP-weighted running average position is calculated from this data.
+ After 24 hours, the receiver is placed into a "Known Position" mode,
+ initialized with the calculated position, and then solves only for time.
+ 
+ <p>The requirement for a fixed installation precludes operation of
+ this driver aboard moving ships, aircraft, or land vehicles.  Also,
+ the position averaging algorithm does not take into account boundary
+ conditions, so operation very near the international date line or the
+ poles is not recomended.
+ 
  <p><h4>Monitor Data</h4>
  
! <p>The driver writes each timecode as received to the
! <code>clockstats</code> file.
! 
  
  <p><h4>Fudge Factors</h4>
  






-rw-rw-r--  1 brett       45302 Feb 12 13:26 xntpd/refclock_mx4200.c

/*
 * This software was developed by the Computer Systems Engineering group
 * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66.
 *
 * Copyright (c) 1992 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Lawrence Berkeley Laboratory.
 * 4. The name of the University may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#if defined(REFCLOCK) && defined(MX4200) && defined(PPS)

#include <stdio.h>
#include <ctype.h>
#include <sys/time.h>
#include <errno.h>

#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_refclock.h"
#include "ntp_unixtime.h"
#include "ntp_stdlib.h"

#include "mx4200.h"

#if __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif /* __STDC__ */

#ifdef PPS
#include <sys/ppsclock.h>
#endif /* PPS */

/*
 * This driver supports the Magnavox Model MX 4200 GPS Receiver
 * adapted to precision timing applications.  It requires the
 * ppsclock line discipline or streams module described in the
 * Line Disciplines and Streams Drivers page. It also requires a
 * gadget box and 1-PPS level converter, such as described in the
 * Pulse-per-second (PPS) Signal Interfacing page.
 *
 * It's likely that other compatible Magnavox receivers such as the
 * MX 4200D, MX 9212, MX 9012R, MX 9112 will be supported by this code.
 */

/*
 * GPS Definitions
 */
#define	DEVICE		"/dev/gps%d"	/* device name and unit */
#define	SPEED232	B4800		/* baud */

/*
 * The number of raw samples which we acquire to derive a single estimate.
 * NSAMPLES ideally should not exceed the default poll interval 64.
 * NKEEP must be a power of 2 to simplify the averaging process.
 */
#define NSAMPLES	64
#define NKEEP		8
#define REFCLOCKMAXDISPERSE (FP_SECOND/4) /* max sample dispersion */

/*
 * Radio interface parameters
 */
#define	PRECISION	(-18)	/* precision assumed (about 4 us) */
#define	REFID	"GPS\0"		/* reference id */
#define	DESCRIPTION	"Magnavox MX4200 GPS Receiver" /* who we are */
#define	DEFFUDGETIME	0	/* default fudge time (ms) */

/*
 * Position Averaging.
 * Reference: Dr. Thomas A. Clark's Totally Accurate Clock (TAC) files at
 * ftp://aleph.gsfc.nasa.gov/GPS/totally.accurate.clock/
 */
#define MAX_VEL_SQUARED	(1.0)	/* Maximum acceptable velocity (squared) (m/s)*/
#define INTERVAL	1	/* Interval between position measurements (s) */
#define AVGING_TIME	24	/* Number of hours to average */
#define USUAL_EDOP	0.75	/* used for normalizing EDOP */
#define USUAL_NDOP	0.75	/* used for normalizing NDOP */
#define USUAL_VDOP	1.70	/* used for normalizing VDOP */

/*
 * Imported from the ntp_timer module
 */
extern u_long current_time;	/* current time (s) */

/*
 * Imported from ntpd module
 */
extern int debug;		/* global debug flag */

#ifdef PPS
/*
 * Imported from loop_filter module
 */
extern int fdpps;		/* ppsclock file descriptor */
#endif /* PPS */

/*
 * Imported from perror(3)
 */
extern int  sys_nerr;
extern char *sys_errlist[];
extern int  errno;

/*
 * MX4200 unit control structure.
 */
struct mx4200unit {
	u_int  pollcnt;			/* poll message counter */
	u_int  polled;			/* Hand in a time sample? */
#ifdef PPS
	u_int  lastserial;		/* last pps serial number */
	struct ppsclockev ppsev;	/* PPS control structure */
#endif /* PPS */
	double avg_lat;			/* average latitude */
	double avg_lon;			/* average longitude */
	double avg_alt;			/* average height */
	double filt_lat;		/* latitude filter length */
	double filt_lon;		/* longitude filter length */
	double filt_alt;		/* height filter length */
	double edop;			/* EDOP (east DOP) */
	double ndop;			/* NDOP (north DOP) */
	double vdop;			/* VDOP (vertical DOP) */
	u_int  moving;			/* are we moving? */
	u_int  known;			/* position known yet? */
	u_long clamp_time;		/* when to stop postion averaging */
	int    coderecv;		/* total received samples */
	int    nkeep;			/* number of samples to preserve */
	int    rshift;			/* number of rshifts for division */
	l_fp   filter[NSAMPLES];	/* offset filter */
	l_fp   lastref;			/* last reference timestamp */
};

static char pmvxg[] = "PMVXG";

/*
 * Function prototypes
 */
static	int	mx4200_start	P((int, struct peer *));
static	void	mx4200_shutdown	P((int, struct peer *));
static	void	mx4200_receive	P((struct recvbuf *));
static	void	mx4200_poll	P((int, struct peer *));

static	char *	mx4200_parse_t	P((struct peer *));
static	char *	mx4200_parse_p	P((struct peer *));
static	char *	mx4200_parse_d	P((struct peer *));
static	char *	mx4200_parse_s	P((struct peer *));
static	char *	mx4200_offset	P((struct peer *));
static	char *	mx4200_process	P((struct peer *));
#ifdef QSORT_USES_VOID_P
	int	mx4200_cmpl_fp	P((const void *, const void *));
#else
	int	mx4200_cmpl_fp	P((const l_fp *, const l_fp *));
#endif /* not QSORT_USES_VOID_P */
static	void	mx4200_config	P((struct peer *));
static	void	mx4200_ref	P((struct peer *));
static	void	mx4200_send	P((struct peer *, char *, ...));
static	u_char	mx4200_cksum	P((char *, u_int));
static	int	mx4200_jday	P((int, int, int));
static	void	mx4200_debug	P((struct peer *, char *, ...));
static	int	mx4200_pps	P((struct peer *));

/*
 * Transfer vector
 */
struct	refclock refclock_mx4200 = {
	mx4200_start,		/* start up driver */
	mx4200_shutdown,	/* shut down driver */
	mx4200_poll,		/* transmit poll message */
	noentry,		/* not used (old mx4200_control) */
	noentry,		/* initialize driver (not used) */
	noentry,		/* not used (old mx4200_buginfo) */
	NOFLAGS			/* not used */
};



/*
 * mx4200_start - open the devices and initialize data for processing
 */
static int
mx4200_start(unit, peer)
	int unit;
	struct peer *peer;
{
	register struct mx4200unit *up;
	struct refclockproc *pp;
	int fd;
	int i;
	char gpsdev[20];

	/*
	 * Open serial port
	 */
	(void)sprintf(gpsdev, DEVICE, unit);
#ifdef PPS
	if (!(fd = refclock_open(gpsdev, SPEED232, LDISC_PPS)))
#else
	if (!(fd = refclock_open(gpsdev, SPEED232, 0)))
#endif /* PPS */
		return (0);

	/*
	 * Allocate unit structure
	 */
	if (!(up = (struct mx4200unit *) emalloc(sizeof(struct mx4200unit)))) {
		(void) close(fd);
		return (0);
	}
	memset((char *)up, 0, sizeof(struct mx4200unit));
	pp = peer->procptr;
	pp->io.clock_recv = mx4200_receive;
	pp->io.srcclock = (caddr_t)peer;
	pp->io.datalen = 0;
	pp->io.fd = fd;
	if (!io_addclock(&pp->io)) {
		(void) close(fd);
		free(up);
		return (0);
	}
	pp->unitptr = (caddr_t)up;

	/*
	 * Initialize miscellaneous variables
	 */
	peer->precision = PRECISION;
	pp->clockdesc = DESCRIPTION;
	memcpy((char *)&pp->refid, REFID, 4);

	up->pollcnt  = 2;
	up->polled   = 0;
	up->moving   = 0;	/* not moving */
	up->known    = 0;	/* not moving */
	up->avg_lat  = 0.0;
	up->avg_lon  = 0.0;
	up->avg_alt  = 0.0;
	up->filt_lat = 0.0;
	up->filt_lon = 0.0;
	up->filt_alt = 0.0;
	up->edop     = USUAL_EDOP;
	up->ndop     = USUAL_NDOP;
	up->vdop     = USUAL_VDOP;
	up->clamp_time = current_time + (AVGING_TIME * 60 * 60);
	up->coderecv = 0;
	up->nkeep    = NKEEP;
	if (up->nkeep > NSAMPLES) up->nkeep = NSAMPLES;
	if (up->nkeep >=   1) up->rshift =  0;
	if (up->nkeep >=   2) up->rshift =  1;
	if (up->nkeep >=   4) up->rshift =  2;
	if (up->nkeep >=   8) up->rshift =  3;
	if (up->nkeep >=  16) up->rshift =  4;
	if (up->nkeep >=  32) up->rshift =  5;
	if (up->nkeep >=  64) up->rshift =  6;
	up->nkeep =1;
	i = up->rshift;
	while (i > 0) {
		up->nkeep *= 2;
		i--;
	}

	/* Ensure the receiver is properly configured */
	mx4200_config(peer);
	return (1);
}


/*
 * mx4200_shutdown - shut down the clock
 */
static void
mx4200_shutdown(unit, peer)
	int unit;
	struct peer *peer;
{
	register struct mx4200unit *up;
	struct refclockproc *pp;

	pp = peer->procptr;
	up = (struct mx4200unit *)pp->unitptr;
	io_closeclock(&pp->io);
	free(up);
}


/*
 * mx4200_config - Configure the receiver
 */
static void
mx4200_config(peer)
	struct peer *peer;
{
	register struct mx4200unit *up;
	struct refclockproc *pp;

	pp = peer->procptr;
	up = (struct mx4200unit *)pp->unitptr;

	/*
	 * "007" Control Port Configuration
	 * Zero the output list (do it twice to flush possible junk)
	 */
	mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg,
	    PMVXG_S_PORTCONF,
			/* control port output block Label */
	    1);		/* clear current output control list (1=yes) */
			/* add/delete sentences from list */
			/* must be null */
			/* sentence output rate (sec) */
			/* precision for position output */
			/* nmea version for cga & gll output */
			/* pass-through control */
	mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg,
	    PMVXG_S_PORTCONF, 1);

	/*
	 * Request software configuration so we can syslog the firmware version
	 */
	mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_SOFTCONF);

	/*
	 * "001" Initialization/Mode Control, Part A
	 * Where ARE we?
	 */
	mx4200_send(peer, "%s,%03d,,,,,,,,,,", pmvxg,
	    PMVXG_S_INITMODEA);
			/* day of month */
			/* month of year */
			/* year */
			/* gmt */
			/* latitude   DDMM.MMMM */
			/* north/south */
			/* longitude DDDMM.MMMM */
			/* east/west */
			/* height */
			/* Altitude Reference 1=MSL */

	/*
	 * "001" Initialization/Mode Control, Part B
	 * Start off in 2d/3d coast mode, holding altitude to last known
	 * value if only 3 satellites available.
	 */
	mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
	    pmvxg, PMVXG_S_INITMODEB,
	    3,		/* 2d/3d coast */
			/* reserved */
	    0.1,	/* hor accel fact as per Steve (m/s**2) */
	    0.1,	/* ver accel fact as per Steve (m/s**2) */
	    10,		/* vdop */
	    10,		/* hdop limit as per Steve */
	    5,		/* elevation limit as per Steve (deg) */
	    'U',	/* time output mode (UTC) */
	    0);		/* local time offset from gmt (HHHMM) */

	/*
	 * "023" Time Recovery Configuration
	 * Get UTC time from a stationary receiver.
	 * (Set field 1 'D' == dynamic if we are on a moving platform).
	 * (Set field 1 'S' == static  if we are not moving).
	 * (Set field 1 'K' == known position if we can initialize lat/lon/alt).
	 */
	mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg,
	    PMVXG_S_TRECOVCONF,
	    'S',	/* static: solve for pos, alt, time, while stationary */
	    'U',	/* synchronize to UTC */
	    'A',	/* always output a time pulse */
	    500,	/* max time error in ns */
	    0,		/* user bias in ns */
	    1);		/* output "830" sentences to control port */
			/* Multi-satellite mode */

	/*
	 * "007" Control Port Configuration
	 * Output "004" mode data
	 */
	mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg,
	    PMVXG_S_PORTCONF,
	    PMVXG_D_MODEDATA, /* control port output block Label */
	    0,		/* clear current output control list (0=no) */
	    1,		/* add/delete sentences from list (1=add) */
			/* must be null */
	    INTERVAL*10);/* sentence output rate (sec) */
	    		/* precision for position output */
			/* nmea version for cga & gll output */
			/* pass-through control */

	/*
	 * "007" Control Port Configuration
	 * Output "022" DOPs
	 */
	mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg,
	    PMVXG_S_PORTCONF,
	    PMVXG_D_DOPS, /* control port output block Label */
	    0,		/* clear current output control list (0=no) */
	    1,		/* add/delete sentences from list (1=add) */
			/* must be null */
	    INTERVAL);	/* sentence output rate (sec) */
	    		/* precision for position output */
			/* nmea version for cga & gll output */
			/* pass-through control */

	/*
	 * "007" Control Port Configuration
	 * Output "523" time recovery parameters currently in use
	 */
	mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg,
	    PMVXG_S_PORTCONF,
	    PMVXG_D_TRECOVUSEAGE, /* control port output block Label */
	    0,		/* clear current output control list (0=no) */
	    1,		/* add/delete sentences from list (1=add) */
			/* must be null */
	    INTERVAL*10); /* sentence output rate (sec) */
	    		/* precision for position output */
			/* nmea version for cga & gll output */
			/* pass-through control */

	/*
	 * "007" Control Port Configuration
	 * Output "021" position, height, velocity reports
	 */
	mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg,
	    PMVXG_S_PORTCONF,
	    PMVXG_D_PHV, /* control port output block Label */
	    0,		/* clear current output control list (0=no) */
	    1,		/* add/delete sentences from list (1=add) */
			/* must be null */
	    INTERVAL);	/* sentence output rate (sec) */
	    		/* precision for position output */
			/* nmea version for cga & gll output */
			/* pass-through control */


}

/*
 * mx4200_ref - Reconfigure unit as a reference station at a known position.
 */
static void
mx4200_ref(peer)
	struct peer *peer;
{
	register struct mx4200unit *up;
	struct refclockproc *pp;
	double dtemp, lat, lon, alt;
	char lats[32], lons[32];
	char nsc, ewc;

	pp = peer->procptr;
	up = (struct mx4200unit *)pp->unitptr;

	/*
	 * "001" Initialization/Mode Control, Part B
	 * Put receiver in fully-constrained 2d nav mode
	 */
	mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
	    pmvxg, PMVXG_S_INITMODEB,
	    2,		/* 2d nav */
			/* reserved */
	    0.1,	/* hor accel fact as per Steve (m/s**2) */
	    0.1,	/* ver accel fact as per Steve (m/s**2) */
	    10,		/* vdop */
	    10,		/* hdop limit as per Steve */
	    5,		/* elevation limit as per Steve (deg) */
	    'U',	/* time output mode (UTC) */
	    0);		/* local time offset from gmt (HHHMM) */

	/*
	 * "023" Time Recovery Configuration
	 * Get UTC time from a stationary receiver.  Solve for time only.
	 * This should improve the time resolution dramatically.
	 */
	mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg,
	    PMVXG_S_TRECOVCONF,
	    'K',	/* known position: solve for time only */
	    'U',	/* synchronize to UTC */
	    'A',	/* always output a time pulse */
	    500,	/* max time error in ns */
	    0,		/* user bias in ns */
	    1);		/* output "830" sentences to control port */
			/* Multi-satellite mode */

	/*
	 * "000" Initialization/Mode Control - Part A
	 * Fix to our averaged position.
	 */
	if (up->avg_lat >= 0.0) {
		lat = up->avg_lat;
		nsc = 'N';
	} else {
		lat = up->avg_lat * (-1.0);
		nsc = 'S';
	}
	if (up->avg_lon >= 0.0) {
		lon = up->avg_lon;
		ewc = 'E';
	} else {
		lon = up->avg_lon * (-1.0);
		ewc = 'W';
	}
	alt = up->avg_alt;
	dtemp = (lat - (double)(int)lat) * 600.0 / 10.0;
	sprintf(lats,"%02d%02.4f", (int)lat, dtemp);
	dtemp = (lon - (double)(int)lon) * 600.0 / 10.0;
	sprintf(lons,"%02d%02.4f", (int)lon, dtemp);

	mx4200_send(peer, "%s,%03d,,,,,%s,%c,%s,%c,%.2f,%d", pmvxg,
	    PMVXG_S_INITMODEA,
			/* day of month */
			/* month of year */
			/* year */
			/* gmt */
	    lats,	/* latitude   DDMM.MMMM */
	    nsc,	/* north/south */
	    lons,	/* longitude DDDMM.MMMM */
	    ewc,	/* east/west */
	    alt,	/* height */
	    1);		/* Altitude Reference 1=MSL */

	msyslog(LOG_DEBUG,
	    "mx4200_ref: reconfig to fixed location: %s %c, %s %c, %.2f m MSL",
	    lats, nsc, lons, ewc, alt );
}

/*
 * mx4200_poll - mx4200 watchdog routine
 */
static void
mx4200_poll(unit, peer)
	int unit;
	struct peer *peer;
{
	register struct mx4200unit *up;
	struct refclockproc *pp;

	pp = peer->procptr;
	up = (struct mx4200unit *)pp->unitptr;

	/*
	 * You don't need to poll this clock.  It puts out timecodes
	 * once per second.  If asked for a timestamp, take note.
	 * The next time a timecode comes in, it will be fed back.
	 */

	/*
	 * If we haven't had a response in a while, reset the receiver.
	 */
	if (up->pollcnt > 0) {
		up->pollcnt--;
	} else {
		refclock_report(peer, CEVNT_TIMEOUT);

		/*
		 * Request a "000" status message which should trigger a
		 * reconfig
		 */
		mx4200_send(peer, "%s,%03d",
		    "CDGPQ",		/* query from CDU to GPS */
		    PMVXG_D_STATUS);	/* label of desired sentence */
	}

	/*
	 * polled every 64 seconds. Ask mx4200_receive to hand in
	 * a timestamp.
	 */
	up->polled = 1;
	pp->polls++;
}

static char char2hex[] = "0123456789ABCDEF";

/*
 * mx4200_receive - receive gps data
 */
static void
mx4200_receive(rbufp)
	struct recvbuf *rbufp;
{
	register struct mx4200unit *up;
	struct refclockproc *pp;
	struct peer *peer;
	char *cp;
	int sentence_type;
	u_char ck;

	/*
	 * Initialize pointers and read the timecode and timestamp.
	 */
	peer = (struct peer *)rbufp->recv_srcclock;
	pp = peer->procptr;
	up = (struct mx4200unit *)pp->unitptr;

	/*
	 * Read clock output.  Automatically handles STREAMS, CLKLDISC.
	 */
	pp->lencode = refclock_gtlin(rbufp, pp->lastcode, BMAX, &pp->lastrec);

	/*
	 * There is a case where <cr><lf> generates 2 timestamps.
	 */
	if (pp->lencode == 0)
		return;

	up->pollcnt = 2;
	pp->lastcode[pp->lencode] = '\0';
	record_clock_stats(&peer->srcadr, pp->lastcode);
	mx4200_debug(peer, "mx4200_receive: %d %s\n",
		pp->lencode, pp->lastcode);

	/*
	 * The structure of the control port sentences is based on the
	 * NMEA-0183 Standard for interfacing Marine Electronics
	 * Navigation Devices (Version 1.5)
	 *
	 *	$PMVXG,XXX, ....................*CK<cr><lf>
	 *
	 *		$	Sentence Start Identifier (reserved char)
	 *			   (Start-of-Sentence Identifier)
	 *		P	Special ID (Proprietary)
	 *		MVX	Originator ID (Magnavox)
	 *		G	Interface ID (GPS)
	 *		,	Field Delimiters (reserved char)
	 *		XXX	Sentence Type
	 *		......	Data
	 *		*	Checksum Field Delimiter (reserved char)
	 *		CK	Checksum
	 *		<cr><lf> Carriage-Return/Line Feed (reserved chars)
	 *			   (End-of-Sentence Identifier)
	 *
	 * Reject if any important landmarks are missing.
	 */
	cp = pp->lastcode + pp->lencode - 3;
	if (cp < pp->lastcode || *pp->lastcode != '$' || cp[0] != '*' ) {
		mx4200_debug(peer, "mx4200_receive: bad format\n");
		refclock_report(peer, CEVNT_BADREPLY);
		return;
	}

	/*
	 * Check and discard the checksum
	 */
	ck = mx4200_cksum(&pp->lastcode[1], pp->lencode - 4);
	if (char2hex[ck >> 4] != cp[1] || char2hex[ck & 0xf] != cp[2]) {
		mx4200_debug(peer, "mx4200_receive: bad checksum\n");
		refclock_report(peer, CEVNT_BADREPLY);
		return;
	}
	*cp = '\0';

	/*
	 * Get the sentence type.
	 */
	sentence_type = 0;
	if ((cp = strchr(pp->lastcode, ',')) == NULL) {
		mx4200_debug(peer, "mx4200_receive: no sentence\n", cp);
		refclock_report(peer, CEVNT_BADREPLY);
		return;
	}
	cp++;
	sentence_type = strtol(cp, &cp, 10);

	/*
	 * "000" Status message
	 */

	if (sentence_type == PMVXG_D_STATUS) {
		/*
		 * XXX
		 * Since we configure the receiver to not give us status
		 * messages and since the receiver outputs status messages by
		 * default after being reset to factory defaults when sent the
		 * "$PMVXG,018,C\r\n" message, any status message we get
		 * indicates the reciever needs to be initialized; thus, it is
		 * not necessary to decode the status message.
		 */
		mx4200_debug(peer, "mx4200_receive: reset receiver\n", cp);
		mx4200_config(peer);
		return;
	}

	/*
	 * "021" Position, Height, Velocity message,
	 *  if we are still averaging our position
	 */
	if (sentence_type == PMVXG_D_PHV && !up->known) {
		/*
		 * Parse the message, calculating our averaged position.
		 */
		if ((cp = mx4200_parse_p(peer)) != NULL) {
			mx4200_debug(peer, "mx4200_receive: pos: %s\n", cp);
			return;
		}
		mx4200_debug(peer,
			"mx4200_receive: position avg %.9f %.9f %.4f\n",
			up->avg_lat, up->avg_lon, up->avg_alt);
		mx4200_debug(peer,
			"mx4200_receive: position len %.4f %.4f %.4f\n",
			up->filt_lat, up->filt_lon, up->filt_alt);
		mx4200_debug(peer,
			"mx4200_receive: position dop %.2f %.2f %.2f\n",
			up->ndop, up->edop, up->vdop);
		/*
		 * Reinitialize as a reference station
		 * if position is well known.
		 */
		if (current_time > up->clamp_time) {
			up->known++;
			mx4200_debug(peer, "mx4200_receive: reconfiguring!\n");
			mx4200_ref(peer);
		}
		return;
	}

	/*
	 * "022" DOPs, if we are still averaging our position
	 */
	if (sentence_type == PMVXG_D_DOPS && !up->known) {
		if ((cp = mx4200_parse_d(peer)) != NULL) {
			mx4200_debug(peer, "mx4200_receive: dop: %s\n", cp);
			return;
		}
		return;
	}

	/*
	 * "030" Software Configuration
	 */
	if (sentence_type == PMVXG_D_SOFTCONF && !up->known) {
		if ((cp = mx4200_parse_s(peer)) != NULL) {
			mx4200_debug(peer, "mx4200_receive: sw conf: %s\n", cp);
			return;
		}
		return;
	}

	/*
	 * "830" Time Recovery Results message
	 */
	if (sentence_type == PMVXG_D_TRECOVOUT) {

		/*
		 * Capture the last PPS signal.
		 * Precision timestamp is returned in pp->lastrec
		 */
		if (mx4200_pps(peer) != NULL) {
			mx4200_debug(peer, "mx4200_receive: pps failure\n");
			refclock_report(peer, CEVNT_FAULT);
			return;
		}

		/*
		 * Parse the time recovery message, and keep the info
		 * to print the pretty billboards.
		 */
		if ((cp = mx4200_parse_t(peer)) != NULL) {
			mx4200_debug(peer, "mx4200_receive: time: %s\n", cp);
			refclock_report(peer, CEVNT_BADREPLY);
			return;
		}

		/*
		 * Add the new sample to a median filter.
		 */
		if ((cp =mx4200_offset(peer)) != NULL) {
			mx4200_debug(peer,"mx4200_receive: offset: %s\n", cp);
			refclock_report(peer, CEVNT_BADTIME);
			return;
		}

		/*
		 * The clock will blurt a timecode every second but we only
		 * want one when polled.  If we havn't been polled, bail out.
		 */
		if (!up->polled)
			return;

		/*
		 * It's a live one!  Remember this time.
		 */
		pp->lasttime   = current_time;

		/*
		 * Determine the reference clock offset and dispersion.
		 * NKEEP of NSAMPLE offsets are passed through a median filter.
		 * Save the (filtered) offset and dispersion in
		 * pp->offset and pp->dispersion.
		 */
		if ((cp =mx4200_process(peer)) != NULL) {
			mx4200_debug(peer,"mx4200_receive: process: %s\n", cp);
			refclock_report(peer, CEVNT_BADTIME);
			return;
		}

		/*
		 * Return offset and dispersion to control module.  We use
		 * lastrec as both the reference time and receive time in
		 * order to avoid being cute, like setting the reference time
		 * later than the receive time, which may cause a paranoid
		 * protocol module to chuck out the data.
		 */
		mx4200_debug(peer, "mx4200_receive: process time: ");
		mx4200_debug(peer, "%4d-%03d %02d:%02d:%02d at %s, %s\n",
			pp->year, pp->day, pp->hour, pp->minute, pp->second,
			prettydate(pp->lastrec), lfptoa(&pp->offset, 6));

		refclock_receive(peer, &pp->offset, 0, pp->dispersion,
			&pp->lastrec, &pp->lastrec, pp->leap);

		/*
		 * We have succeeded in answering the poll.
		 * Turn off the flag and return
		 */
		up->polled = 0;
		return;
	}

	/*
	 * Ignore all other sentence types
	 */
	return;
}

/*
 * mx4200_offset - Calculate the offset, and add to the rolling filter.
 */
static char *
mx4200_offset(peer)
	struct peer *peer;
{
	register struct mx4200unit *up;
	struct refclockproc *pp;
	register int i;
	l_fp offset;

	pp = peer->procptr;
	up = (struct mx4200unit *)pp->unitptr;

	/*
	 * Calculate the offset
	 */
	if (!clocktime(pp->day, pp->hour, pp->minute, pp->second, GMT,
		pp->lastrec.l_ui, &pp->yearstart, &offset.l_ui)) {
		return ("mx4200_process: clocktime failed");
	}
	if (pp->usec) {
		TVUTOTSF(pp->usec, offset.l_uf);
	} else {
		MSUTOTSF(pp->msec, offset.l_uf);
	}
	L_ADD(&offset, &pp->fudgetime1);
	up->lastref = offset;   /* save last reference time */
	L_SUB(&offset, &pp->lastrec); /* form true offset */

	/*
	 * A rolling filter.  Initialize first time around.
	 */
	i = ((up->coderecv)) % NSAMPLES;

	up->filter[i] = offset;
	if (up->coderecv == 0)
		for (i = 1; (u_int) i < NSAMPLES; i++)
			up->filter[i] = up->filter[0];
	up->coderecv++;

	return (NULL);
}

/*
 * mx4200_process - process the sample from the clock,
 * passing it through a median filter and optionally averaging
 * the samples.  Returns offset and dispersion in "up" structure.
 */
static char *
mx4200_process(peer)
	struct peer *peer;
{
	register struct mx4200unit *up;
	struct refclockproc *pp;
	register int i, n;
	int j, k;
	l_fp offset, median, lftmp;
	u_fp disp;
	l_fp off[NSAMPLES];

	pp = peer->procptr;
	up = (struct mx4200unit *)pp->unitptr;

	/*
	 * Copy the raw offsets and sort into ascending order
	 */
	for (i = 0; i < NSAMPLES; i++)
		off[i] = up->filter[i];
	qsort((char *)off, NSAMPLES, sizeof(l_fp), mx4200_cmpl_fp);

	/*
	 * Reject the furthest from the median of NSAMPLES samples until
	 * NKEEP samples remain.
	 */
	i = 0;
	n = NSAMPLES;
	while ((n - i) > up->nkeep) {
		lftmp = off[n - 1];
		median = off[(n + i) / 2];
		L_SUB(&lftmp, &median);
		L_SUB(&median, &off[i]);
		if (L_ISHIS(&median, &lftmp)) {
			/* reject low end */
			i++;
		} else {
			/* reject high end */
			n--;
		}
	}

	/*
	 * Copy key values to the billboard to measure performance.
	 */
	pp->lastref = up->lastref;
	pp->coderecv = up->coderecv;
	pp->nstages = up->nkeep + 2;
	pp->filter[0] = off[0];			/* smallest offset */
	pp->filter[1] = off[NSAMPLES-1];	/* largest offset */
	for (j=2, k=i; k < n; j++, k++)
		pp->filter[j] = off[k];		/* offsets actually examined */

	/*
	 * Compute the dispersion based on the difference between the
	 * extremes of the remaining offsets. Add to this the time since
	 * the last clock update, which represents the dispersion
	 * increase with time. We know that NTP_MAXSKEW is 16. If the
	 * sum is greater than the allowed sample dispersion, bail out.
	 * If the loop is unlocked, return the most recent offset;
	 * otherwise, return the median offset.
	 */
	lftmp = off[n - 1];
	L_SUB(&lftmp, &off[i]);
	disp = LFPTOFP(&lftmp);
	if (disp > REFCLOCKMAXDISPERSE) {
		return("Maximum dispersion exceeded");
	}

	/*
	 * Now compute the offset estimate.  If the sloppy clock
	 * flag 1 is set, average the remainder, otherwise pick the
	 * median.
	 */
	if (pp->sloppyclockflag && CLK_FLAG1) {
		L_CLR(&lftmp);
		while (i < n) {
			L_ADD(&lftmp, &off[i]);
			i++;
		}
		i = up->rshift;
		while (i > 0) {
			L_RSHIFT(&lftmp);
			i--;
		}
		offset = lftmp;
	} else {
		i = (n + i) / 2;
		offset = off[i];
	}

	/*
	 * The payload: filtered offset and dispersion.
	 */

	pp->offset = offset;
	pp->dispersion = disp;

	return(NULL);

}

/* Compare two l_fp's, used with qsort() */
int
#ifdef QSORT_USES_VOID_P
mx4200_cmpl_fp(p1, p2)
	const void *p1, *p2;
{
	register const l_fp *fp1 = (const l_fp *)p1;
	register const l_fp *fp2 = (const l_fp *)p2;
#else
mx4200_cmpl_fp(fp1, fp2)
	register const l_fp *fp1;
	register const l_fp *fp2;
{
#endif /* not QSORT_USES_VOID_P */

	if (!L_ISGEQ(fp1, fp2))
		return (-1);
	if (L_ISEQU(fp1, fp2))
		return (0);
	return (1);
}


/*
 * Parse a mx4200 time recovery message. Returns a string if error.
 *
 * A typical message looks like this.  Checksum has already been stripped.
 *
 *    $PMVXG,830,T,YYYY,MM,DD,HH:MM:SS,U,S,FFFFFF,PPPPP,BBBBBB,LL
 *
 *	Field	Field Contents
 *	-----	--------------
 *		Block Label: $PMVXG
 *		Sentence Type: 830=Time Recovery Results
 *			This sentence is output approximately 1 second
 *			preceding the 1PPS output.  It indicates the
 *			exact time of the next pulse, whether or not the
 *			time mark will be valid (based on operator-specified
 *			error tolerance), the time to which the pulse is
 *			synchronized, the receiver operating mode,
 *			and the time error of the *last* 1PPS output.
 *	1	Time Mark Valid: T=Valid, F=Not Valid
 *	2	Year: 1993-
 *	3	Month of Year: 1-12
 *	4	Day of Month: 1-31
 *	5	Time of Day: HH:MM:SS
 *	6	Time Synchronization: U=UTC, G=GPS
 *	7	Time Recovery Mode: D=Dynamic, S=Static,
 *			K=Known Position, N=No Time Recovery
 *	8	Oscillator Offset: The filter's estimate of the oscillator
 *			frequency error, in parts per billion (ppb).
 *	9	Time Mark Error: The computed error of the *last* pulse
 *			output, in nanoseconds.
 *	10	User Time Bias: Operator specified bias, in nanoseconds
 *	11	Leap Second Flag: Indicates that a leap second will
 *			occur.  This value is usually zero, except during
 *			the week prior to the leap second occurence, when
 *			this value will be set to +1 or -1.  A value of
 *			+1 indicates that GPS time will be 1 second
 *			further ahead of UTC time.
 *
 */
static char *
mx4200_parse_t(peer)
	struct peer *peer;
{
	struct refclockproc *pp;
	struct mx4200unit *up;
	int sentence_type, valid;
	int year, yearday, month, monthday, hour, minute, second, leapsec;
	char *cp;

	pp = peer->procptr;
	up = (struct mx4200unit *)pp->unitptr;

	cp = pp->lastcode;

	if ((cp = strchr(cp, ',')) == NULL)
		return ("no rec-type");
	cp++;

	/* Sentence type */
	sentence_type = strtol(cp, &cp, 10);
	if (sentence_type != PMVXG_D_TRECOVOUT)
		return ("wrong rec-type");

	/* Pulse valid indicator */
	if (*cp++ != ',')
		return ("no pulse-valid");
	if (*cp == 'T')
		valid = 1;
	else if (*cp == 'F')
		valid = 0;
	else
		return ("bad pulse-valid");
	cp++;

	/* Year */
	if (*cp++ != ',')
		return ("no year");
	year = strtol(cp, &cp, 10);

	/* Month of year */
	if (*cp++ != ',')
		return ("no month");
	month = strtol(cp, &cp, 10);

	/* Day of month */
	if (*cp++ != ',')
		return ("no month day");
	monthday = strtol(cp, &cp, 10);

	/* Hour */
	if (*cp++ != ',')
		return ("no hour");
	hour = strtol(cp, &cp, 10);

	/* Minute */
	if (*cp++ != ':')
		return ("no minute");
	minute = strtol(cp, &cp, 10);

	/* Second */
	if (*cp++ != ':')
		return ("no second");
	second = strtol(cp, &cp, 10);

	/* Time indicator */
	if (*cp++ != ',')
		return ("no time indicator");
	if (*cp == 'G')
		return ("synchronized to GPS; should be UTC");
	else if (*cp != 'U')
		return ("not synchronized to UTC");
	cp++;

	/* Time recovery mode */
	if (*cp++ != ',' || *cp++ == '\0')
		return ("no time mode");

	/* Oscillator offset */
	if ((cp = strchr(cp, ',')) == NULL)
		return ("no osc off");
	cp++;

	/* Time mark error */
	/* (by request, always less than 0.5 usec (500 nsec), so ignore) */
	if ((cp = strchr(cp, ',')) == NULL)
		return ("no time mark err");
	cp++;

	/* User time bias */
	/* (by request, always zero, so ignore */
	if ((cp = strchr(cp, ',')) == NULL)
		return ("no user bias");
	cp++;

	/* Leap second flag */
	if ((cp = strchr(cp, ',')) == NULL)
		return ("no leap");
	cp++;
	leapsec = strtol(cp, &cp, 10);


	/*
	 * Check for insane time (allow for possible leap seconds)
	 */
	if (second > 60 || minute > 59 || hour > 23 ||
	    second <  0 || minute <  0 || hour <  0) {
		mx4200_debug(peer,
			"mx4200_parse_t: bad time %02d:%02d:%02d",
			hour, minute, second);
		if (leapsec != 0)
			mx4200_debug(peer, " (leap %+d\n)", leapsec);
		mx4200_debug(peer, "\n");
		refclock_report(peer, CEVNT_BADTIME);
		return ("bad time");
	}

	/*
	 * Check for insane date
	 */
	if (monthday > 31 || month > 12 ||
	    monthday <  1 || month <  1 || year < 1996) {
		mx4200_debug(peer,
			"mx4200_parse_t: bad date (%4d-%02d-%02d)\n",
			year, month, monthday);
		refclock_report(peer, CEVNT_BADDATE);
		return;
	}

	/*
	 * Silly Hack for MX4200:
	 * ASCII message is for *next* 1PPS signal, but we have the
	 * timestamp for the *last* 1PPS signal.  So we have to subtract
	 * a second.  Discard if we are on a month boundary to avoid
	 * possible leap seconds and leap days.
	 */
	second--;
	if (second < 0) {
		second = 59;
		minute--;
		if (minute < 0) {
			minute = 59;
			hour--;
			if (hour < 0) {
				hour = 23;
				monthday--;
				if (monthday < 1) {
					return ("sorry, month boundary");
				}
			}
		}
	}

	/*
	 * Calculate Julian date
	 */
	if (!(yearday = mx4200_jday(year, month, monthday))) {
		mx4200_debug(peer,
			"mx4200_parse_t: bad julian date %d (%4d-%02d-%02d)\n",
			yearday, year, month, monthday);
		refclock_report(peer, CEVNT_BADDATE);
		return("invalid julian date");
	}

	/*
	 * Setup leap second indicator
	 */
	if (leapsec == 0)
		pp->leap = LEAP_NOWARNING;
	else if (leapsec == 1)
		pp->leap = LEAP_ADDSECOND;
	else if (leapsec == -1)
		pp->leap = LEAP_DELSECOND;
	else
		pp->leap = LEAP_NOTINSYNC;	/* shouldn't happen */

	/*
	 * Copy time data for billboard monitoring.
	 */

	pp->year   = year;
	pp->day    = yearday;
	pp->hour   = hour;
	pp->minute = minute;
	pp->second = second;
	pp->msec   = 0;
	pp->usec   = 0;

	/*
	 * Toss if sentence is marked invalid
	 */
	if (!valid || pp->leap == LEAP_NOTINSYNC) {
		mx4200_debug(peer, "mx4200_parse_t: time mark not valid\n");
		refclock_report(peer, CEVNT_BADTIME);
		return ("pulse invalid");
	}

	return (NULL);
}

/*
 * Calculate the checksum
 */
static u_char
mx4200_cksum(cp, n)
	register char *cp;
	register u_int n;
{
	register u_char ck;

	for (ck = 0; n-- > 0; cp++)
		ck ^= *cp;
	return (ck);
}

/*
 * Tables to compute the day of year.  Viva la leap.
 */
static day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
static day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/*
 * Calculate the the Julian Day
 */
static int
mx4200_jday(year, month, monthday)
	int year;
	int month;
	int monthday;
{
	register int day, i;
	int leap_year;


	/*
	 * Is this a leap year ?
	 */
	if (year % 4) {
		leap_year = 0; /* FALSE */
	} else {
		if (year % 100) {
			leap_year = 1; /* TRUE */
		} else {
			if (year % 400) {
				leap_year = 0; /* FALSE */
			} else {
				leap_year = 1; /* TRUE */
			}
		}
	}

	/*
	 * Calculate the Julian Date
	 */
	day = monthday;

	if (leap_year) {
		/* a leap year */
		if (day > day2tab[month - 1]) {
			return (0);
		}
		for (i = 0; i < month - 1; i++)
			day += day2tab[i];
	} else {
		/* not a leap year */
		if (day > day1tab[month - 1]) {
			return (0);
		}
		for (i = 0; i < month - 1; i++)
			day += day1tab[i];
	}
	return (day);
}

/*
 * Parse a mx4200 position/height/velocity sentence.
 *
 * A typical message looks like this.  Checksum has already been stripped.
 *
 * $PMVXG,021,SSSSSS.SS,DDMM.MMMM,N,DDDMM.MMMM,E,HHHHH.H,GGGG.G,EEEE.E,WWWW.W,MM
 *
 *	Field	Field Contents
 *	-----	--------------
 *		Block Label: $PMVXG
 *		Sentence Type: 021=Position, Height Velocity Data
 *			This sentence gives the receiver position, height,
 *			navigation mode, and velocity north/east.
 *			*This sentence is intended for post-analysis
 *			applications.*
 *	1	UTC measurement time (seconds into week)
 *	2	WGS-84 Lattitude (degrees, minutes)
 *	3	N=North, S=South
 *	4	WGS-84 Longitude (degrees, minutes)
 *	5	E=East, W=West
 *	6	Altitude (meters above mean sea level)
 *	7	Geoidal height (meters)
 *	8	East velocity (m/sec)
 *	9	West Velocity (m/sec)
 *	10	Navigation Mode
 *		    Mode if navigating:
 *			1 = Position from remote device
 *			2 = 2-D position
 *			3 = 3-D position
 *			4 = 2-D differential position
 *			5 = 3-D differential position
 *			6 = Static
 *			8 = Position known -- reference station
 *			9 = Position known -- Navigator
 *		    Mode if not navigating:
 *			51 = Too few satellites
 *			52 = DOPs too large
 *			53 = Position STD too large
 *			54 = Velocity STD too large
 *			55 = Too many iterations for velocity
 *			56 = Too many iterations for position
 *			57 = 3 sat startup failed
 *			58 = Command abort
 */
static char *
mx4200_parse_p(peer)
	struct peer *peer;
{
	struct refclockproc *pp;
	struct mx4200unit *up;
	int sentence_type, mode;
	double dtemp, dtemp2, mtime, lat, lon, alt, geoid, vele, veln, weight;
	char *cp;

	pp = peer->procptr;
	up = (struct mx4200unit *)pp->unitptr;

	cp = pp->lastcode;

	if ((cp = strchr(cp, ',')) == NULL)
		return ("no rec-type");
	cp++;

	/* Sentence type */
	sentence_type = strtol(cp, &cp, 10);
	if (sentence_type != PMVXG_D_PHV)
		return ("wrong rec-type");

	/* Measurement Time */
	if (*cp++ != ',')
		return ("no measurement time");
	mtime = strtod(cp,&cp);

	/* Latitude (always +ve) */
	if (*cp++ != ',')
		return ("no latitude");
	dtemp = atof(cp);
	dtemp2 = strtod(cp,&cp);
	if (dtemp < 0.0)
		return ("negative latitude");
	lat = (double) ( (int)dtemp / 100);
	lat += (dtemp - (lat*100.0)) * 10.0 / 600.0;

	/* North/South */
	if (*cp++ != ',')
		return ("no north/south indicator");
	if (*cp == 'N')
		lat = lat;
	else if (*cp == 'S')
		lat *= -1.0;
	else
		return ("invalid north/south indicator");
	cp++;

	/* Longitude (always +ve) */
	if (*cp++ != ',')
		return ("no longitude");
	dtemp = atof(cp);
	dtemp2 = strtod(cp,&cp);
	if (dtemp < 0.0)
		return ("negative latitude");
	lon = (double) ( (int)dtemp / 100);
	lon += (dtemp - (lon*100.0)) * 10.0 / 600.0;

	/* East/West */
	if (*cp++ != ',')
		return ("no east/west indicator");
	if (*cp == 'E')
		lon = lon;
	else if (*cp == 'W')
		lon *= -1.0;
	else
		return ("invalid east/west indicator");
	cp++;

	/* Altitude */
	if (*cp++ != ',')
		return ("no altitude");
	alt = atof(cp);
	dtemp2 = strtod(cp,&cp);

	/* geoid height */
	if (*cp++ != ',')
		return ("no geoid height");
	geoid = strtod(cp,&cp);

	/* East velocity */
	if (*cp++ != ',')
		return ("no east velocity");
	vele = strtod(cp,&cp);

	/* north velocity */
	if (*cp++ != ',')
		return ("no north velocity");
	veln = strtod(cp,&cp);

	/* nav mode */
	if (*cp++ != ',')
		return ("no nav mode");
	mode = strtol(cp, &cp, 10);


	/*
	 * return if not navigating
	 */
	if (mode > 10)
		return ("not navigating");

	if (mode != 3 && mode != 5)
		return ("not navigating in 3D");

	/*
	 * Calculate running weighted averages
	 */

	weight = (USUAL_EDOP/up->edop);
	weight = weight * weight;
	up->avg_lon = up->filt_lon * up->avg_lon + weight * lon;
	up->filt_lon += weight;
	up->avg_lon = up->avg_lon / up->filt_lon;

	weight = (USUAL_NDOP/up->ndop);
	weight = weight * weight;
	up->avg_lat = up->filt_lat * up->avg_lat + weight * lat;
	up->filt_lat += weight;
	up->avg_lat = up->avg_lat / up->filt_lat;

	weight = (USUAL_VDOP/up->vdop);
	weight = weight * weight;
	up->avg_alt = up->filt_alt * up->avg_alt + weight * alt;
	up->filt_alt += weight;
	up->avg_alt = up->avg_alt / up->filt_alt;

	/*
	 * Are we moving?
	 */
	if ((vele*vele + veln*veln) > MAX_VEL_SQUARED)
		up->moving++;

	return (NULL);
}

/*
 * Parse a mx4200 DOP sentence.
 *
 * A typical message looks like this.  Checksum has already been stripped.
 *
 * $PMVXG,022,SSSSSS.SSEE.E,NN.N,VV.V,XX,XX,XX,XX,XX,XX
 *
 *	Field	Field Contents
 *	-----	--------------
 *		Block Label: $PMVXG
 *		Sentence Type: 022=DOPs.  The DOP values in this sentence
 *			correspond to the satellites listed.  The PRNs in
 *			the message are listed in receiver channel number order
 *	1	UTC measurement time (seconds into week)
 *	2	EDOP (east DOP)
 *	3	NDOP (north DOP)
 *	4	VDOP (vertical DOP)
 *	5	PRN on channel 1
 *	6	PRN on channel 2
 *	7	PRN on channel 3
 *	8	PRN on channel 4
 *	9	PRN on channel 5
 *	10	PRN on channel 6
 */
static char *
mx4200_parse_d(peer)
	struct peer *peer;
{
	struct refclockproc *pp;
	struct mx4200unit *up;
	int sentence_type;
	double mtime, dtemp2, edop, ndop, vdop;
	char *cp;

	pp = peer->procptr;
	up = (struct mx4200unit *)pp->unitptr;

	cp = pp->lastcode;

	if ((cp = strchr(cp, ',')) == NULL)
		return ("no rec-type");
	cp++;

	/* Sentence type */
	sentence_type = strtol(cp, &cp, 10);
	if (sentence_type != PMVXG_D_DOPS)
		return ("wrong rec-type");

	/* Measurement Time */
	if (*cp++ != ',')
		return ("no measurement time");
	mtime = strtod(cp,&cp);

	/* EDOP */
	if (*cp++ != ',')
		return ("no edop");
	edop = atof(cp);
	dtemp2 = strtod(cp,&cp);

	/* NDOP */
	if (*cp++ != ',')
		return ("no ndop");
	ndop = atof(cp);
	dtemp2 = strtod(cp,&cp);

	/* VDOP */
	if (*cp++ != ',')
		return ("no vdop");
	vdop = atof(cp);
	dtemp2 = strtod(cp,&cp);

	/* Ignore the PRNs... */


	/* Update values */
	if (ndop <= 0.0 || ndop<= 0.0 || vdop <= 0.0)
		return ("nonpositive dop");
	up->edop = edop;
	up->ndop = ndop;
	up->vdop = vdop;

	return (NULL);
}

/*
 * Parse a mx4200 Software Configuration sentence
 *
 * A typical message looks like this.  Checksum has already been stripped.
 *
 * $PMVXG,030,NNNN,FFF
 *
 *	Field	Field Contents
 *	-----	--------------
 *		Block Label: $PMVXG
 *		Sentence Type: 030=Software Configuration.
 *			This sentence contains the navigation processor
 *			and baseband firmware version numbers.
 *	1	Nav Processor Version Number
 *	2	Baseband Firmware Version Number
 */
static char *
mx4200_parse_s(peer)
	struct peer *peer;
{
	struct refclockproc *pp;
	struct mx4200unit *up;
	int sentence_type;
	char *cp;

	pp = peer->procptr;
	up = (struct mx4200unit *)pp->unitptr;

	cp = pp->lastcode;

	if ((cp = strchr(cp, ',')) == NULL)
		return ("no rec-type");
	cp++;

	/* Sentence type */
	sentence_type = strtol(cp, &cp, 10);
	if (sentence_type != PMVXG_D_SOFTCONF)
		return ("wrong rec-type");

	/* Log the software version */
	cp++;
	msyslog(LOG_DEBUG, "mx4200_parse_s: firmware configuration: %s", cp);

}

/*
 * Process a PPS signal, returning a timestamp.
 */
static int
mx4200_pps(peer)
	struct peer *peer;
{
#ifdef PPS
	struct refclockproc *pp;
	struct mx4200unit *up;

	int temp_serial;

	pp = peer->procptr;
	up = (struct mx4200unit *)pp->unitptr;


	/*
	 * Grab the timestamp of the PPS signal.
	 */
	temp_serial = up->ppsev.serial;
	if (ioctl(fdpps, CIOGETEV, (caddr_t)&up->ppsev) < 0) {
		/* XXX Actually, if this fails, we're pretty much screwed */
		mx4200_debug(peer, "mx4200_pps: CIOGETEV: ");
		if (errno < sys_nerr)
			mx4200_debug(peer, "%s", sys_errlist[errno]);
		mx4200_debug(peer, "\n");
		refclock_report(peer, CEVNT_FAULT);
		return(1);
	}
	if (temp_serial == up->ppsev.serial) {
		mx4200_debug(peer,
			"mx4200_pps: ppsev serial not incrementing: %d\n",
				up->ppsev.serial);
		refclock_report(peer, CEVNT_FAULT);
		return(1);
	}

	/*
	 * Check pps serial number against last one
	 */
	if (up->lastserial + 1 != up->ppsev.serial && up->lastserial != 0) {
		if (up->ppsev.serial == up->lastserial)
			mx4200_debug(peer, "mx4200_pps: no new pps event\n");
		else
			mx4200_debug(peer, "mx4200_pps: missed %d pps events\n",
				up->ppsev.serial - up->lastserial - 1);
		refclock_report(peer, CEVNT_FAULT);
	}
	up->lastserial = up->ppsev.serial;

	/*
	 * Return the timestamp in pp->lastrec
	 */
	up->ppsev.tv.tv_sec += (u_int32) JAN_1970;
	TVTOTS(&up->ppsev.tv,&pp->lastrec);

#endif /* PPS */

	return(0);
}

/*
 * mx4200_debug - print debug messages
 */
#if __STDC__
static void
mx4200_debug(struct peer *peer, char *fmt, ...)
#else
static void
mx4200_debug(peer, fmt, va_alist)
	struct peer *peer;
	char *fmt;
#endif
{
	va_list ap;
	struct refclockproc *pp;
	struct mx4200unit *up;

	if (debug) {

#if __STDC__
		va_start(ap, fmt);
#else
		va_start(ap);
#endif

		pp = peer->procptr;
		up = (struct mx4200unit *)pp->unitptr;


		/*
		 * Print debug message to stdout
		 * In the future, we may want to get get more creative...
		 */
		vprintf(fmt, ap);

		va_end(ap);
	}
}

/*
 * Send a character string to the receiver.  Checksum is appended here.
 */
static void
#if __STDC__
mx4200_send(struct peer *peer, char *fmt, ...)
#else
mx4200_send(peer, fmt, va_alist)
	struct peer *peer;
	char *fmt;
	va_dcl
#endif /* __STDC__ */
{
	struct refclockproc *pp;
	struct mx4200unit *up;

	register char *cp;
	register int n, m;
	va_list ap;
	char buf[1024];
	u_char ck;

#if __STDC__
	va_start(ap, fmt);
#else
	va_start(ap);
#endif /* __STDC__ */

	pp = peer->procptr;
	up = (struct mx4200unit *)pp->unitptr;

	cp = buf;
	*cp++ = '$';
#ifdef notdef
	/* BSD is rational */
	n = vsnprintf(cp, sizeof(buf) - 1, fmt, ap);
#else
	/* SunOS sucks */
	(void)vsprintf(cp, fmt, ap);
	n = strlen(cp);
#endif /* notdef */
	ck = mx4200_cksum(cp, n);
	cp += n;
	++n;
#ifdef notdef
	/* BSD is rational */
	n += snprintf(cp, sizeof(buf) - n - 5, "*%02X\r\n", ck);
#else
	/* SunOS sucks */
	sprintf(cp, "*%02X\r\n", ck);
	n += strlen(cp);
#endif /* notdef */

	m = write(pp->io.fd, buf, n);
	if (m < 0)
		msyslog(LOG_ERR, "mx4200_send: write: %m (%s)", buf);
	mx4200_debug(peer, "mx4200_send: %d %s\n", m, buf);
	va_end(ap);
}

#else /* not (REFCLOCK && MX4200 && PPS) */
int refclock_mx4200_bs;
#endif /* not (REFCLOCK && MX4200 && PPS) */

/*
 * History:
 *
 * refclock_mx4200.c
 *
 * Author:	Craig Leres?
 * Revised:	Marc Brett	1.10	1996-05-08
 * - rewrote to "new-style" xntpd driver standard.
 * - added position-averaging/fixed-location logic.
 * Revised:	Marc Brett	1.20	1997-02-11
 * - updated to xntp3-5.89.5 standards.
 *
 * Craig Leres	(leres@ee.lbl.gov)
 * Marc Brett	(Marc.Brett@waii.com)
 *
 */



