154359Sroberto/*
254359Sroberto * This software was developed by the Computer Systems Engineering group
354359Sroberto * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66.
454359Sroberto *
554359Sroberto * Copyright (c) 1992 The Regents of the University of California.
654359Sroberto * All rights reserved.
754359Sroberto *
854359Sroberto * Redistribution and use in source and binary forms, with or without
954359Sroberto * modification, are permitted provided that the following conditions
1054359Sroberto * are met:
1154359Sroberto * 1. Redistributions of source code must retain the above copyright
1254359Sroberto *    notice, this list of conditions and the following disclaimer.
1354359Sroberto * 2. Redistributions in binary form must reproduce the above copyright
1454359Sroberto *    notice, this list of conditions and the following disclaimer in the
1554359Sroberto *    documentation and/or other materials provided with the distribution.
1654359Sroberto * 3. All advertising materials mentioning features or use of this software
1754359Sroberto *    must display the following acknowledgement:
1854359Sroberto *	This product includes software developed by the University of
1954359Sroberto *	California, Lawrence Berkeley Laboratory.
2054359Sroberto * 4. The name of the University may not be used to endorse or promote
2154359Sroberto *    products derived from this software without specific prior
2254359Sroberto *    written permission.
2354359Sroberto *
2454359Sroberto * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2554359Sroberto * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2654359Sroberto * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2754359Sroberto * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2854359Sroberto * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2954359Sroberto * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
3054359Sroberto * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3154359Sroberto * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3254359Sroberto * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3354359Sroberto * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3454359Sroberto * SUCH DAMAGE.
3554359Sroberto */
3654359Sroberto
3754359Sroberto/*
3854359Sroberto * Modified: Marc Brett <marc.brett@westgeo.com>   Sept, 1999.
3954359Sroberto *
4054359Sroberto * 1. Added support for alternate PPS schemes, with code mostly
4154359Sroberto *    copied from the Oncore driver (Thanks, Poul-Henning Kamp).
4254359Sroberto *    This code runs on SunOS 4.1.3 with ppsclock-1.6a1 and Solaris 7.
4354359Sroberto */
4454359Sroberto
4554359Sroberto
4654359Sroberto#ifdef HAVE_CONFIG_H
4754359Sroberto# include <config.h>
4854359Sroberto#endif
4954359Sroberto
5082498Sroberto#if defined(REFCLOCK) && defined(CLOCK_MX4200) && defined(HAVE_PPSAPI)
5154359Sroberto
5254359Sroberto#include "ntpd.h"
5354359Sroberto#include "ntp_io.h"
5454359Sroberto#include "ntp_refclock.h"
5554359Sroberto#include "ntp_unixtime.h"
5654359Sroberto#include "ntp_stdlib.h"
5754359Sroberto
5882498Sroberto#include <stdio.h>
5982498Sroberto#include <ctype.h>
6082498Sroberto
6154359Sroberto#include "mx4200.h"
6254359Sroberto
6354359Sroberto#ifdef HAVE_SYS_TERMIOS_H
6454359Sroberto# include <sys/termios.h>
6554359Sroberto#endif
6654359Sroberto#ifdef HAVE_SYS_PPSCLOCK_H
6754359Sroberto# include <sys/ppsclock.h>
6854359Sroberto#endif
6954359Sroberto
70132451Sroberto#include "ntp_sprintf.h"
71132451Sroberto
7254359Sroberto#ifndef HAVE_STRUCT_PPSCLOCKEV
7354359Srobertostruct ppsclockev {
7482498Sroberto# ifdef HAVE_STRUCT_TIMESPEC
7554359Sroberto	struct timespec tv;
7654359Sroberto# else
7754359Sroberto	struct timeval tv;
7854359Sroberto# endif
7954359Sroberto	u_int serial;
8054359Sroberto};
8154359Sroberto#endif /* ! HAVE_STRUCT_PPSCLOCKEV */
8254359Sroberto
83182007Sroberto#ifdef HAVE_PPSAPI
84182007Sroberto# include "ppsapi_timepps.h"
85182007Sroberto#endif /* HAVE_PPSAPI */
8682498Sroberto
8754359Sroberto/*
8854359Sroberto * This driver supports the Magnavox Model MX 4200 GPS Receiver
8954359Sroberto * adapted to precision timing applications.  It requires the
9054359Sroberto * ppsclock line discipline or streams module described in the
9154359Sroberto * Line Disciplines and Streams Drivers page. It also requires a
9254359Sroberto * gadget box and 1-PPS level converter, such as described in the
9354359Sroberto * Pulse-per-second (PPS) Signal Interfacing page.
9454359Sroberto *
9554359Sroberto * It's likely that other compatible Magnavox receivers such as the
9654359Sroberto * MX 4200D, MX 9212, MX 9012R, MX 9112 will be supported by this code.
9754359Sroberto */
9854359Sroberto
9954359Sroberto/*
10054359Sroberto * Check this every time you edit the code!
10154359Sroberto */
10282498Sroberto#define YEAR_LAST_MODIFIED 2000
10354359Sroberto
10454359Sroberto/*
10554359Sroberto * GPS Definitions
10654359Sroberto */
10754359Sroberto#define	DEVICE		"/dev/gps%d"	/* device name and unit */
10854359Sroberto#define	SPEED232	B4800		/* baud */
10954359Sroberto
11054359Sroberto/*
11154359Sroberto * Radio interface parameters
11254359Sroberto */
11354359Sroberto#define	PRECISION	(-18)	/* precision assumed (about 4 us) */
11454359Sroberto#define	REFID	"GPS\0"		/* reference id */
11554359Sroberto#define	DESCRIPTION	"Magnavox MX4200 GPS Receiver" /* who we are */
11654359Sroberto#define	DEFFUDGETIME	0	/* default fudge time (ms) */
11754359Sroberto
11854359Sroberto#define	SLEEPTIME	32	/* seconds to wait for reconfig to complete */
11954359Sroberto
12054359Sroberto/*
12154359Sroberto * Position Averaging.
12254359Sroberto */
12354359Sroberto#define INTERVAL	1	/* Interval between position measurements (s) */
12454359Sroberto#define AVGING_TIME	24	/* Number of hours to average */
12554359Sroberto#define NOT_INITIALIZED	-9999.	/* initial pivot longitude */
12654359Sroberto
12754359Sroberto/*
12854359Sroberto * MX4200 unit control structure.
12954359Sroberto */
13054359Srobertostruct mx4200unit {
13154359Sroberto	u_int  pollcnt;			/* poll message counter */
13254359Sroberto	u_int  polled;			/* Hand in a time sample? */
13354359Sroberto	u_int  lastserial;		/* last pps serial number */
13454359Sroberto	struct ppsclockev ppsev;	/* PPS control structure */
13554359Sroberto	double avg_lat;			/* average latitude */
13654359Sroberto	double avg_lon;			/* average longitude */
13754359Sroberto	double avg_alt;			/* average height */
13854359Sroberto	double central_meridian;	/* central meridian */
13982498Sroberto	double N_fixes;			/* Number of position measurements */
14054359Sroberto	int    last_leap;		/* leap second warning */
14154359Sroberto	u_int  moving;			/* mobile platform? */
14254359Sroberto	u_long sloppyclockflag;		/* fudge flags */
14354359Sroberto	u_int  known;			/* position known yet? */
14454359Sroberto	u_long clamp_time;		/* when to stop postion averaging */
14554359Sroberto	u_long log_time;		/* when to print receiver status */
14682498Sroberto	pps_handle_t	pps_h;
14782498Sroberto	pps_params_t	pps_p;
14882498Sroberto	pps_info_t	pps_i;
14954359Sroberto};
15054359Sroberto
15154359Srobertostatic char pmvxg[] = "PMVXG";
15254359Sroberto
15354359Sroberto/* XXX should be somewhere else */
15454359Sroberto#ifdef __GNUC__
15554359Sroberto#if __GNUC__ < 2  || (__GNUC__ == 2 && __GNUC_MINOR__ < 5)
15654359Sroberto#ifndef __attribute__
15754359Sroberto#define __attribute__(args)
15856746Sroberto#endif /* __attribute__ */
15956746Sroberto#endif /* __GNUC__ < 2  || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) */
16054359Sroberto#else
16154359Sroberto#ifndef __attribute__
16254359Sroberto#define __attribute__(args)
16356746Sroberto#endif /* __attribute__ */
16456746Sroberto#endif /* __GNUC__ */
16554359Sroberto/* XXX end */
16654359Sroberto
16754359Sroberto/*
16854359Sroberto * Function prototypes
16954359Sroberto */
17054359Srobertostatic	int	mx4200_start	P((int, struct peer *));
17154359Srobertostatic	void	mx4200_shutdown	P((int, struct peer *));
17254359Srobertostatic	void	mx4200_receive	P((struct recvbuf *));
17354359Srobertostatic	void	mx4200_poll	P((int, struct peer *));
17454359Sroberto
17554359Srobertostatic	char *	mx4200_parse_t	P((struct peer *));
17654359Srobertostatic	char *	mx4200_parse_p	P((struct peer *));
17754359Srobertostatic	char *	mx4200_parse_s	P((struct peer *));
17854359Sroberto#ifdef QSORT_USES_VOID_P
17954359Srobertoint	mx4200_cmpl_fp	P((const void *, const void *));
18054359Sroberto#else
18154359Srobertoint	mx4200_cmpl_fp	P((const l_fp *, const l_fp *));
18254359Sroberto#endif /* not QSORT_USES_VOID_P */
18382498Srobertostatic	int	mx4200_config	P((struct peer *));
18454359Srobertostatic	void	mx4200_ref	P((struct peer *));
18554359Srobertostatic	void	mx4200_send	P((struct peer *, char *, ...))
18654359Sroberto    __attribute__ ((format (printf, 2, 3)));
18754359Srobertostatic	u_char	mx4200_cksum	P((char *, int));
18854359Srobertostatic	int	mx4200_jday	P((int, int, int));
18954359Srobertostatic	void	mx4200_debug	P((struct peer *, char *, ...))
19054359Sroberto    __attribute__ ((format (printf, 2, 3)));
19154359Srobertostatic	int	mx4200_pps	P((struct peer *));
19254359Sroberto
19354359Sroberto/*
19454359Sroberto * Transfer vector
19554359Sroberto */
19654359Srobertostruct	refclock refclock_mx4200 = {
19754359Sroberto	mx4200_start,		/* start up driver */
19854359Sroberto	mx4200_shutdown,	/* shut down driver */
19954359Sroberto	mx4200_poll,		/* transmit poll message */
20054359Sroberto	noentry,		/* not used (old mx4200_control) */
20154359Sroberto	noentry,		/* initialize driver (not used) */
20254359Sroberto	noentry,		/* not used (old mx4200_buginfo) */
20354359Sroberto	NOFLAGS			/* not used */
20454359Sroberto};
20554359Sroberto
20654359Sroberto
20754359Sroberto
20854359Sroberto/*
20954359Sroberto * mx4200_start - open the devices and initialize data for processing
21054359Sroberto */
21154359Srobertostatic int
21254359Srobertomx4200_start(
21354359Sroberto	int unit,
21454359Sroberto	struct peer *peer
21554359Sroberto	)
21654359Sroberto{
21754359Sroberto	register struct mx4200unit *up;
21854359Sroberto	struct refclockproc *pp;
21954359Sroberto	int fd;
22054359Sroberto	char gpsdev[20];
22154359Sroberto
22254359Sroberto	/*
22354359Sroberto	 * Open serial port
22454359Sroberto	 */
22554359Sroberto	(void)sprintf(gpsdev, DEVICE, unit);
22654359Sroberto	if (!(fd = refclock_open(gpsdev, SPEED232, LDISC_PPS))) {
22754359Sroberto	    return (0);
22854359Sroberto	}
22954359Sroberto
23054359Sroberto	/*
23154359Sroberto	 * Allocate unit structure
23254359Sroberto	 */
23354359Sroberto	if (!(up = (struct mx4200unit *) emalloc(sizeof(struct mx4200unit)))) {
23482498Sroberto		perror("emalloc");
23554359Sroberto		(void) close(fd);
23654359Sroberto		return (0);
23754359Sroberto	}
23854359Sroberto	memset((char *)up, 0, sizeof(struct mx4200unit));
23954359Sroberto	pp = peer->procptr;
24054359Sroberto	pp->io.clock_recv = mx4200_receive;
24154359Sroberto	pp->io.srcclock = (caddr_t)peer;
24254359Sroberto	pp->io.datalen = 0;
24354359Sroberto	pp->io.fd = fd;
24454359Sroberto	if (!io_addclock(&pp->io)) {
24554359Sroberto		(void) close(fd);
24654359Sroberto		free(up);
24754359Sroberto		return (0);
24854359Sroberto	}
24954359Sroberto	pp->unitptr = (caddr_t)up;
25054359Sroberto
25154359Sroberto	/*
25254359Sroberto	 * Initialize miscellaneous variables
25354359Sroberto	 */
25454359Sroberto	peer->precision = PRECISION;
25554359Sroberto	pp->clockdesc = DESCRIPTION;
25654359Sroberto	memcpy((char *)&pp->refid, REFID, 4);
25754359Sroberto
25854359Sroberto	/* Ensure the receiver is properly configured */
25982498Sroberto	return mx4200_config(peer);
26054359Sroberto}
26154359Sroberto
26254359Sroberto
26354359Sroberto/*
26454359Sroberto * mx4200_shutdown - shut down the clock
26554359Sroberto */
26654359Srobertostatic void
26754359Srobertomx4200_shutdown(
26854359Sroberto	int unit,
26954359Sroberto	struct peer *peer
27054359Sroberto	)
27154359Sroberto{
27254359Sroberto	register struct mx4200unit *up;
27354359Sroberto	struct refclockproc *pp;
27454359Sroberto
27554359Sroberto	pp = peer->procptr;
27654359Sroberto	up = (struct mx4200unit *)pp->unitptr;
27754359Sroberto	io_closeclock(&pp->io);
27854359Sroberto	free(up);
27954359Sroberto}
28054359Sroberto
28154359Sroberto
28254359Sroberto/*
28354359Sroberto * mx4200_config - Configure the receiver
28454359Sroberto */
28582498Srobertostatic int
28654359Srobertomx4200_config(
28754359Sroberto	struct peer *peer
28854359Sroberto	)
28954359Sroberto{
29054359Sroberto	char tr_mode;
29154359Sroberto	int add_mode;
29254359Sroberto	register struct mx4200unit *up;
29354359Sroberto	struct refclockproc *pp;
29482498Sroberto	int mode;
29554359Sroberto
29654359Sroberto	pp = peer->procptr;
29754359Sroberto	up = (struct mx4200unit *)pp->unitptr;
29854359Sroberto
29954359Sroberto	/*
30054359Sroberto	 * Initialize the unit variables
30154359Sroberto	 *
30254359Sroberto	 * STRANGE BEHAVIOUR WARNING: The fudge flags are not available
30354359Sroberto	 * at the time mx4200_start is called.  These are set later,
30454359Sroberto	 * and so the code must be prepared to handle changing flags.
30554359Sroberto	 */
30654359Sroberto	up->sloppyclockflag = pp->sloppyclockflag;
30754359Sroberto	if (pp->sloppyclockflag & CLK_FLAG2) {
30854359Sroberto		up->moving   = 1;	/* Receiver on mobile platform */
30954359Sroberto		msyslog(LOG_DEBUG, "mx4200_config: mobile platform");
31054359Sroberto	} else {
31154359Sroberto		up->moving   = 0;	/* Static Installation */
31254359Sroberto	}
31354359Sroberto	up->pollcnt     	= 2;
31454359Sroberto	up->polled      	= 0;
31554359Sroberto	up->known       	= 0;
31654359Sroberto	up->avg_lat     	= 0.0;
31754359Sroberto	up->avg_lon     	= 0.0;
31854359Sroberto	up->avg_alt     	= 0.0;
31954359Sroberto	up->central_meridian	= NOT_INITIALIZED;
32082498Sroberto	up->N_fixes    		= 0.0;
32154359Sroberto	up->last_leap   	= 0;	/* LEAP_NOWARNING */
32254359Sroberto	up->clamp_time  	= current_time + (AVGING_TIME * 60 * 60);
32354359Sroberto	up->log_time    	= current_time + SLEEPTIME;
32454359Sroberto
32582498Sroberto	if (time_pps_create(pp->io.fd, &up->pps_h) < 0) {
32682498Sroberto		perror("time_pps_create");
32782498Sroberto		msyslog(LOG_ERR,
32882498Sroberto			"mx4200_config: time_pps_create failed: %m");
32982498Sroberto		return (0);
33082498Sroberto	}
33182498Sroberto	if (time_pps_getcap(up->pps_h, &mode) < 0) {
33282498Sroberto		msyslog(LOG_ERR,
33382498Sroberto			"mx4200_config: time_pps_getcap failed: %m");
33482498Sroberto		return (0);
33582498Sroberto	}
33682498Sroberto
33782498Sroberto	if (time_pps_getparams(up->pps_h, &up->pps_p) < 0) {
33882498Sroberto		msyslog(LOG_ERR,
33982498Sroberto			"mx4200_config: time_pps_getparams failed: %m");
34082498Sroberto		return (0);
34182498Sroberto	}
34282498Sroberto
34382498Sroberto	/* nb. only turn things on, if someone else has turned something
34482498Sroberto	 *      on before we get here, leave it alone!
34582498Sroberto	 */
34682498Sroberto
34782498Sroberto	up->pps_p.mode = PPS_CAPTUREASSERT | PPS_TSFMT_TSPEC;
34882498Sroberto	up->pps_p.mode &= mode;		/* only set what is legal */
34982498Sroberto
35082498Sroberto	if (time_pps_setparams(up->pps_h, &up->pps_p) < 0) {
35182498Sroberto		perror("time_pps_setparams");
35282498Sroberto		msyslog(LOG_ERR,
35382498Sroberto			"mx4200_config: time_pps_setparams failed: %m");
35482498Sroberto		exit(1);
35582498Sroberto	}
35682498Sroberto
35782498Sroberto	if (time_pps_kcbind(up->pps_h, PPS_KC_HARDPPS, PPS_CAPTUREASSERT,
35882498Sroberto			PPS_TSFMT_TSPEC) < 0) {
35982498Sroberto		perror("time_pps_kcbind");
36082498Sroberto		msyslog(LOG_ERR,
36182498Sroberto			"mx4200_config: time_pps_kcbind failed: %m");
36282498Sroberto		exit(1);
36382498Sroberto	}
36482498Sroberto
36582498Sroberto
36654359Sroberto	/*
36754359Sroberto	 * "007" Control Port Configuration
36854359Sroberto	 * Zero the output list (do it twice to flush possible junk)
36954359Sroberto	 */
37054359Sroberto	mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg,
37154359Sroberto	    PMVXG_S_PORTCONF,
37254359Sroberto	    /* control port output block Label */
37354359Sroberto	    1);		/* clear current output control list (1=yes) */
37454359Sroberto	/* add/delete sentences from list */
37554359Sroberto	/* must be null */
37654359Sroberto	/* sentence output rate (sec) */
37754359Sroberto	/* precision for position output */
37854359Sroberto	/* nmea version for cga & gll output */
37954359Sroberto	/* pass-through control */
38054359Sroberto	mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg,
38154359Sroberto	    PMVXG_S_PORTCONF, 1);
38254359Sroberto
38354359Sroberto	/*
38454359Sroberto	 * Request software configuration so we can syslog the firmware version
38554359Sroberto	 */
38654359Sroberto	mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_SOFTCONF);
38754359Sroberto
38854359Sroberto	/*
38954359Sroberto	 * "001" Initialization/Mode Control, Part A
39054359Sroberto	 * Where ARE we?
39154359Sroberto	 */
39254359Sroberto	mx4200_send(peer, "%s,%03d,,,,,,,,,,", pmvxg,
39354359Sroberto	    PMVXG_S_INITMODEA);
39454359Sroberto	/* day of month */
39554359Sroberto	/* month of year */
39654359Sroberto	/* year */
39754359Sroberto	/* gmt */
39854359Sroberto	/* latitude   DDMM.MMMM */
39954359Sroberto	/* north/south */
40054359Sroberto	/* longitude DDDMM.MMMM */
40154359Sroberto	/* east/west */
40254359Sroberto	/* height */
40354359Sroberto	/* Altitude Reference 1=MSL */
40454359Sroberto
40554359Sroberto	/*
40654359Sroberto	 * "001" Initialization/Mode Control, Part B
40754359Sroberto	 * Start off in 2d/3d coast mode, holding altitude to last known
40854359Sroberto	 * value if only 3 satellites available.
40954359Sroberto	 */
41054359Sroberto	mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
41154359Sroberto	    pmvxg, PMVXG_S_INITMODEB,
41254359Sroberto	    3,		/* 2d/3d coast */
41354359Sroberto	    /* reserved */
41454359Sroberto	    0.1,	/* hor accel fact as per Steve (m/s**2) */
41554359Sroberto	    0.1,	/* ver accel fact as per Steve (m/s**2) */
41654359Sroberto	    10,		/* vdop */
41754359Sroberto	    10,		/* hdop limit as per Steve */
41854359Sroberto	    5,		/* elevation limit as per Steve (deg) */
41954359Sroberto	    'U',	/* time output mode (UTC) */
42054359Sroberto	    0);		/* local time offset from gmt (HHHMM) */
42154359Sroberto
42254359Sroberto	/*
42354359Sroberto	 * "023" Time Recovery Configuration
42454359Sroberto	 * Get UTC time from a stationary receiver.
42554359Sroberto	 * (Set field 1 'D' == dynamic if we are on a moving platform).
42654359Sroberto	 * (Set field 1 'S' == static  if we are not moving).
42754359Sroberto	 * (Set field 1 'K' == known position if we can initialize lat/lon/alt).
42854359Sroberto	 */
42954359Sroberto
43054359Sroberto	if (pp->sloppyclockflag & CLK_FLAG2)
43154359Sroberto		up->moving   = 1;	/* Receiver on mobile platform */
43254359Sroberto	else
43354359Sroberto		up->moving   = 0;	/* Static Installation */
43454359Sroberto
43554359Sroberto	up->pollcnt  = 2;
43654359Sroberto	if (up->moving) {
43754359Sroberto		/* dynamic: solve for pos, alt, time, while moving */
43854359Sroberto		tr_mode = 'D';
43954359Sroberto	} else {
44054359Sroberto		/* static: solve for pos, alt, time, while stationary */
44154359Sroberto		tr_mode = 'S';
44254359Sroberto	}
44354359Sroberto	mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg,
44454359Sroberto	    PMVXG_S_TRECOVCONF,
44554359Sroberto	    tr_mode,	/* time recovery mode (see above ) */
44654359Sroberto	    'U',	/* synchronize to UTC */
44754359Sroberto	    'A',	/* always output a time pulse */
44854359Sroberto	    500,	/* max time error in ns */
44954359Sroberto	    0,		/* user bias in ns */
45054359Sroberto	    1);		/* output "830" sentences to control port */
45182498Sroberto			/* Multi-satellite mode */
45254359Sroberto
45354359Sroberto	/*
45454359Sroberto	 * Output position information (to calculate fixed installation
45554359Sroberto	 * location) only if we are not moving
45654359Sroberto	 */
45754359Sroberto	if (up->moving) {
45854359Sroberto		add_mode = 2;	/* delete from list */
45954359Sroberto	} else {
46054359Sroberto		add_mode = 1;	/* add to list */
46154359Sroberto	}
46254359Sroberto
46354359Sroberto
46454359Sroberto	/*
46554359Sroberto	 * "007" Control Port Configuration
46654359Sroberto	 * Output "021" position, height, velocity reports
46754359Sroberto	 */
46854359Sroberto	mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg,
46954359Sroberto	    PMVXG_S_PORTCONF,
47054359Sroberto	    PMVXG_D_PHV, /* control port output block Label */
47154359Sroberto	    0,		/* clear current output control list (0=no) */
47254359Sroberto	    add_mode,	/* add/delete sentences from list (1=add, 2=del) */
47382498Sroberto	    		/* must be null */
47454359Sroberto	    INTERVAL);	/* sentence output rate (sec) */
47582498Sroberto			/* precision for position output */
47682498Sroberto			/* nmea version for cga & gll output */
47782498Sroberto			/* pass-through control */
47882498Sroberto
47982498Sroberto	return (1);
48054359Sroberto}
48154359Sroberto
48254359Sroberto/*
48354359Sroberto * mx4200_ref - Reconfigure unit as a reference station at a known position.
48454359Sroberto */
48554359Srobertostatic void
48654359Srobertomx4200_ref(
48754359Sroberto	struct peer *peer
48854359Sroberto	)
48954359Sroberto{
49054359Sroberto	register struct mx4200unit *up;
49154359Sroberto	struct refclockproc *pp;
49254359Sroberto	double minute, lat, lon, alt;
49354359Sroberto	char lats[16], lons[16];
49454359Sroberto	char nsc, ewc;
49554359Sroberto
49654359Sroberto	pp = peer->procptr;
49754359Sroberto	up = (struct mx4200unit *)pp->unitptr;
49854359Sroberto
49954359Sroberto	/* Should never happen! */
50054359Sroberto	if (up->moving) return;
50154359Sroberto
50254359Sroberto	/*
50354359Sroberto	 * Set up to output status information in the near future
50454359Sroberto	 */
50554359Sroberto	up->log_time    = current_time + SLEEPTIME;
50654359Sroberto
50754359Sroberto	/*
50854359Sroberto	 * "007" Control Port Configuration
50954359Sroberto	 * Stop outputting "021" position, height, velocity reports
51054359Sroberto	 */
51154359Sroberto	mx4200_send(peer, "%s,%03d,%03d,%d,%d,,,,,", pmvxg,
51254359Sroberto	    PMVXG_S_PORTCONF,
51354359Sroberto	    PMVXG_D_PHV, /* control port output block Label */
51454359Sroberto	    0,		/* clear current output control list (0=no) */
51554359Sroberto	    2);		/* add/delete sentences from list (2=delete) */
51654359Sroberto			/* must be null */
51754359Sroberto	    		/* sentence output rate (sec) */
51854359Sroberto			/* precision for position output */
51954359Sroberto			/* nmea version for cga & gll output */
52054359Sroberto			/* pass-through control */
52154359Sroberto
52254359Sroberto	/*
52354359Sroberto	 * "001" Initialization/Mode Control, Part B
52454359Sroberto	 * Put receiver in fully-constrained 2d nav mode
52554359Sroberto	 */
52654359Sroberto	mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
52754359Sroberto	    pmvxg, PMVXG_S_INITMODEB,
52854359Sroberto	    2,		/* 2d nav */
52954359Sroberto	    /* reserved */
53054359Sroberto	    0.1,	/* hor accel fact as per Steve (m/s**2) */
53154359Sroberto	    0.1,	/* ver accel fact as per Steve (m/s**2) */
53254359Sroberto	    10,		/* vdop */
53354359Sroberto	    10,		/* hdop limit as per Steve */
53454359Sroberto	    5,		/* elevation limit as per Steve (deg) */
53554359Sroberto	    'U',	/* time output mode (UTC) */
53654359Sroberto	    0);		/* local time offset from gmt (HHHMM) */
53754359Sroberto
53854359Sroberto	/*
53954359Sroberto	 * "023" Time Recovery Configuration
54054359Sroberto	 * Get UTC time from a stationary receiver.  Solve for time only.
54154359Sroberto	 * This should improve the time resolution dramatically.
54254359Sroberto	 */
54354359Sroberto	mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg,
54454359Sroberto	    PMVXG_S_TRECOVCONF,
54554359Sroberto	    'K',	/* known position: solve for time only */
54654359Sroberto	    'U',	/* synchronize to UTC */
54754359Sroberto	    'A',	/* always output a time pulse */
54854359Sroberto	    500,	/* max time error in ns */
54954359Sroberto	    0,		/* user bias in ns */
55054359Sroberto	    1);		/* output "830" sentences to control port */
55154359Sroberto	/* Multi-satellite mode */
55254359Sroberto
55354359Sroberto	/*
55454359Sroberto	 * "000" Initialization/Mode Control - Part A
55554359Sroberto	 * Fix to our averaged position.
55654359Sroberto	 */
55754359Sroberto	if (up->central_meridian != NOT_INITIALIZED) {
55854359Sroberto		up->avg_lon += up->central_meridian;
55954359Sroberto		if (up->avg_lon < -180.0) up->avg_lon += 360.0;
56054359Sroberto		if (up->avg_lon >  180.0) up->avg_lon -= 360.0;
56154359Sroberto	}
56254359Sroberto
56354359Sroberto	if (up->avg_lat >= 0.0) {
56454359Sroberto		lat = up->avg_lat;
56554359Sroberto		nsc = 'N';
56654359Sroberto	} else {
56754359Sroberto		lat = up->avg_lat * (-1.0);
56854359Sroberto		nsc = 'S';
56954359Sroberto	}
57054359Sroberto	if (up->avg_lon >= 0.0) {
57154359Sroberto		lon = up->avg_lon;
57254359Sroberto		ewc = 'E';
57354359Sroberto	} else {
57454359Sroberto		lon = up->avg_lon * (-1.0);
57554359Sroberto		ewc = 'W';
57654359Sroberto	}
57754359Sroberto	alt = up->avg_alt;
57854359Sroberto	minute = (lat - (double)(int)lat) * 60.0;
57954359Sroberto	sprintf(lats,"%02d%02.4f", (int)lat, minute);
58054359Sroberto	minute = (lon - (double)(int)lon) * 60.0;
58154359Sroberto	sprintf(lons,"%03d%02.4f", (int)lon, minute);
58254359Sroberto
58356746Sroberto	mx4200_send(peer, "%s,%03d,,,,,%s,%c,%s,%c,%.2f,%d", pmvxg,
58454359Sroberto	    PMVXG_S_INITMODEA,
58554359Sroberto	    /* day of month */
58654359Sroberto	    /* month of year */
58754359Sroberto	    /* year */
58854359Sroberto	    /* gmt */
58954359Sroberto	    lats,	/* latitude   DDMM.MMMM */
59054359Sroberto	    nsc,	/* north/south */
59154359Sroberto	    lons,	/* longitude DDDMM.MMMM */
59254359Sroberto	    ewc,	/* east/west */
59356746Sroberto	    alt,	/* Altitude */
59482498Sroberto	    1);		/* Altitude Reference (0=WGS84 ellipsoid, 1=MSL geoid)*/
59554359Sroberto
59654359Sroberto	msyslog(LOG_DEBUG,
59754359Sroberto	    "mx4200: reconfig to fixed location: %s %c, %s %c, %.2f m",
59854359Sroberto		lats, nsc, lons, ewc, alt );
59954359Sroberto
60054359Sroberto}
60154359Sroberto
60254359Sroberto/*
60354359Sroberto * mx4200_poll - mx4200 watchdog routine
60454359Sroberto */
60554359Srobertostatic void
60654359Srobertomx4200_poll(
60754359Sroberto	int unit,
60854359Sroberto	struct peer *peer
60954359Sroberto	)
61054359Sroberto{
61154359Sroberto	register struct mx4200unit *up;
61254359Sroberto	struct refclockproc *pp;
61354359Sroberto
61454359Sroberto	pp = peer->procptr;
61554359Sroberto	up = (struct mx4200unit *)pp->unitptr;
61654359Sroberto
61754359Sroberto	/*
61854359Sroberto	 * You don't need to poll this clock.  It puts out timecodes
61954359Sroberto	 * once per second.  If asked for a timestamp, take note.
62054359Sroberto	 * The next time a timecode comes in, it will be fed back.
62154359Sroberto	 */
62254359Sroberto
62354359Sroberto	/*
62454359Sroberto	 * If we haven't had a response in a while, reset the receiver.
62554359Sroberto	 */
62654359Sroberto	if (up->pollcnt > 0) {
62754359Sroberto		up->pollcnt--;
62854359Sroberto	} else {
62954359Sroberto		refclock_report(peer, CEVNT_TIMEOUT);
63054359Sroberto
63154359Sroberto		/*
63254359Sroberto		 * Request a "000" status message which should trigger a
63354359Sroberto		 * reconfig
63454359Sroberto		 */
63554359Sroberto		mx4200_send(peer, "%s,%03d",
63654359Sroberto		    "CDGPQ",		/* query from CDU to GPS */
63754359Sroberto		    PMVXG_D_STATUS);	/* label of desired sentence */
63854359Sroberto	}
63954359Sroberto
64054359Sroberto	/*
64154359Sroberto	 * polled every 64 seconds. Ask mx4200_receive to hand in
64254359Sroberto	 * a timestamp.
64354359Sroberto	 */
64454359Sroberto	up->polled = 1;
64554359Sroberto	pp->polls++;
64654359Sroberto
64754359Sroberto	/*
64854359Sroberto	 * Output receiver status information.
64954359Sroberto	 */
65054359Sroberto	if ((up->log_time > 0) && (current_time > up->log_time)) {
65154359Sroberto		up->log_time = 0;
65254359Sroberto		/*
65354359Sroberto		 * Output the following messages once, for debugging.
65454359Sroberto		 *    "004" Mode Data
65554359Sroberto		 *    "523" Time Recovery Parameters
65654359Sroberto		 */
65754359Sroberto		mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_MODEDATA);
65854359Sroberto		mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_TRECOVUSEAGE);
65954359Sroberto	}
66054359Sroberto}
66154359Sroberto
66254359Srobertostatic char char2hex[] = "0123456789ABCDEF";
66354359Sroberto
66454359Sroberto/*
66554359Sroberto * mx4200_receive - receive gps data
66654359Sroberto */
66754359Srobertostatic void
66854359Srobertomx4200_receive(
66954359Sroberto	struct recvbuf *rbufp
67054359Sroberto	)
67154359Sroberto{
67254359Sroberto	register struct mx4200unit *up;
67354359Sroberto	struct refclockproc *pp;
67454359Sroberto	struct peer *peer;
67554359Sroberto	char *cp;
67654359Sroberto	int sentence_type;
67754359Sroberto	u_char ck;
67854359Sroberto
67954359Sroberto	/*
68054359Sroberto	 * Initialize pointers and read the timecode and timestamp.
68154359Sroberto	 */
68254359Sroberto	peer = (struct peer *)rbufp->recv_srcclock;
68354359Sroberto	pp = peer->procptr;
68454359Sroberto	up = (struct mx4200unit *)pp->unitptr;
68554359Sroberto
68654359Sroberto	/*
68754359Sroberto	 * If operating mode has been changed, then reinitialize the receiver
68854359Sroberto	 * before doing anything else.
68954359Sroberto	 */
69054359Sroberto	if ((pp->sloppyclockflag & CLK_FLAG2) !=
69154359Sroberto	    (up->sloppyclockflag & CLK_FLAG2)) {
69254359Sroberto		up->sloppyclockflag = pp->sloppyclockflag;
69354359Sroberto		mx4200_debug(peer,
69454359Sroberto		    "mx4200_receive: mode switch: reset receiver\n");
69554359Sroberto		mx4200_config(peer);
69654359Sroberto		return;
69754359Sroberto	}
69854359Sroberto	up->sloppyclockflag = pp->sloppyclockflag;
69954359Sroberto
70054359Sroberto	/*
70154359Sroberto	 * Read clock output.  Automatically handles STREAMS, CLKLDISC.
70254359Sroberto	 */
70354359Sroberto	pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec);
70454359Sroberto
70554359Sroberto	/*
70654359Sroberto	 * There is a case where <cr><lf> generates 2 timestamps.
70754359Sroberto	 */
70854359Sroberto	if (pp->lencode == 0)
70954359Sroberto		return;
71054359Sroberto
71154359Sroberto	up->pollcnt = 2;
71254359Sroberto	pp->a_lastcode[pp->lencode] = '\0';
71354359Sroberto	record_clock_stats(&peer->srcadr, pp->a_lastcode);
71454359Sroberto	mx4200_debug(peer, "mx4200_receive: %d %s\n",
71554359Sroberto		     pp->lencode, pp->a_lastcode);
71654359Sroberto
71754359Sroberto	/*
71854359Sroberto	 * The structure of the control port sentences is based on the
71954359Sroberto	 * NMEA-0183 Standard for interfacing Marine Electronics
72054359Sroberto	 * Navigation Devices (Version 1.5)
72154359Sroberto	 *
72254359Sroberto	 *	$PMVXG,XXX, ....................*CK<cr><lf>
72354359Sroberto	 *
72454359Sroberto	 *		$	Sentence Start Identifier (reserved char)
72554359Sroberto	 *			   (Start-of-Sentence Identifier)
72654359Sroberto	 *		P	Special ID (Proprietary)
72754359Sroberto	 *		MVX	Originator ID (Magnavox)
72854359Sroberto	 *		G	Interface ID (GPS)
72954359Sroberto	 *		,	Field Delimiters (reserved char)
73054359Sroberto	 *		XXX	Sentence Type
73154359Sroberto	 *		......	Data
73254359Sroberto	 *		*	Checksum Field Delimiter (reserved char)
73354359Sroberto	 *		CK	Checksum
73454359Sroberto	 *		<cr><lf> Carriage-Return/Line Feed (reserved chars)
73554359Sroberto	 *			   (End-of-Sentence Identifier)
73654359Sroberto	 *
73754359Sroberto	 * Reject if any important landmarks are missing.
73854359Sroberto	 */
73954359Sroberto	cp = pp->a_lastcode + pp->lencode - 3;
74054359Sroberto	if (cp < pp->a_lastcode || *pp->a_lastcode != '$' || cp[0] != '*' ) {
74154359Sroberto		mx4200_debug(peer, "mx4200_receive: bad format\n");
74254359Sroberto		refclock_report(peer, CEVNT_BADREPLY);
74354359Sroberto		return;
74454359Sroberto	}
74554359Sroberto
74654359Sroberto	/*
74754359Sroberto	 * Check and discard the checksum
74854359Sroberto	 */
74954359Sroberto	ck = mx4200_cksum(&pp->a_lastcode[1], pp->lencode - 4);
75054359Sroberto	if (char2hex[ck >> 4] != cp[1] || char2hex[ck & 0xf] != cp[2]) {
75154359Sroberto		mx4200_debug(peer, "mx4200_receive: bad checksum\n");
75254359Sroberto		refclock_report(peer, CEVNT_BADREPLY);
75354359Sroberto		return;
75454359Sroberto	}
75554359Sroberto	*cp = '\0';
75654359Sroberto
75754359Sroberto	/*
75854359Sroberto	 * Get the sentence type.
75954359Sroberto	 */
76054359Sroberto	sentence_type = 0;
76154359Sroberto	if ((cp = strchr(pp->a_lastcode, ',')) == NULL) {
76254359Sroberto		mx4200_debug(peer, "mx4200_receive: no sentence\n");
76354359Sroberto		refclock_report(peer, CEVNT_BADREPLY);
76454359Sroberto		return;
76554359Sroberto	}
76654359Sroberto	cp++;
76754359Sroberto	sentence_type = strtol(cp, &cp, 10);
76854359Sroberto
76954359Sroberto	/*
77082498Sroberto	 * Process the sentence according to its type.
77182498Sroberto	 */
77282498Sroberto	switch (sentence_type) {
77382498Sroberto
77482498Sroberto	/*
77554359Sroberto	 * "000" Status message
77654359Sroberto	 */
77782498Sroberto	case PMVXG_D_STATUS:
77854359Sroberto		/*
77954359Sroberto		 * XXX
78054359Sroberto		 * Since we configure the receiver to not give us status
78154359Sroberto		 * messages and since the receiver outputs status messages by
78254359Sroberto		 * default after being reset to factory defaults when sent the
78354359Sroberto		 * "$PMVXG,018,C\r\n" message, any status message we get
78454359Sroberto		 * indicates the reciever needs to be initialized; thus, it is
78554359Sroberto		 * not necessary to decode the status message.
78654359Sroberto		 */
78754359Sroberto		if ((cp = mx4200_parse_s(peer)) != NULL) {
78854359Sroberto			mx4200_debug(peer,
78954359Sroberto				     "mx4200_receive: status: %s\n", cp);
79054359Sroberto		}
79154359Sroberto		mx4200_debug(peer, "mx4200_receive: reset receiver\n");
79254359Sroberto		mx4200_config(peer);
79382498Sroberto		break;
79454359Sroberto
79554359Sroberto	/*
79654359Sroberto	 * "021" Position, Height, Velocity message,
79754359Sroberto	 *  if we are still averaging our position
79854359Sroberto	 */
79982498Sroberto	case PMVXG_D_PHV:
80082498Sroberto		if (!up->known) {
80182498Sroberto			/*
80282498Sroberto			 * Parse the message, calculating our averaged position.
80382498Sroberto			 */
80482498Sroberto			if ((cp = mx4200_parse_p(peer)) != NULL) {
80582498Sroberto				mx4200_debug(peer, "mx4200_receive: pos: %s\n", cp);
80682498Sroberto				return;
80782498Sroberto			}
80882498Sroberto			mx4200_debug(peer,
80982498Sroberto			    "mx4200_receive: position avg %f %.9f %.9f %.4f\n",
81082498Sroberto			    up->N_fixes, up->avg_lat, up->avg_lon, up->avg_alt);
81182498Sroberto			/*
81282498Sroberto			 * Reinitialize as a reference station
81382498Sroberto			 * if position is well known.
81482498Sroberto			 */
81582498Sroberto			if (current_time > up->clamp_time) {
81682498Sroberto				up->known++;
81782498Sroberto				mx4200_debug(peer, "mx4200_receive: reconfiguring!\n");
81882498Sroberto				mx4200_ref(peer);
81982498Sroberto			}
82054359Sroberto		}
82182498Sroberto		break;
82254359Sroberto
82354359Sroberto	/*
82454359Sroberto	 * Print to the syslog:
82554359Sroberto	 * "004" Mode Data
82654359Sroberto	 * "030" Software Configuration
82754359Sroberto	 * "523" Time Recovery Parameters Currently in Use
82854359Sroberto	 */
82982498Sroberto	case PMVXG_D_MODEDATA:
83082498Sroberto	case PMVXG_D_SOFTCONF:
83182498Sroberto	case PMVXG_D_TRECOVUSEAGE:
83282498Sroberto
83354359Sroberto		if ((cp = mx4200_parse_s(peer)) != NULL) {
83454359Sroberto			mx4200_debug(peer,
83554359Sroberto				     "mx4200_receive: multi-record: %s\n", cp);
83654359Sroberto		}
83782498Sroberto		break;
83854359Sroberto
83954359Sroberto	/*
84054359Sroberto	 * "830" Time Recovery Results message
84154359Sroberto	 */
84282498Sroberto	case PMVXG_D_TRECOVOUT:
84354359Sroberto
84454359Sroberto		/*
84554359Sroberto		 * Capture the last PPS signal.
84654359Sroberto		 * Precision timestamp is returned in pp->lastrec
84754359Sroberto		 */
84854359Sroberto		if (mx4200_pps(peer) != NULL) {
84954359Sroberto			mx4200_debug(peer, "mx4200_receive: pps failure\n");
85054359Sroberto			refclock_report(peer, CEVNT_FAULT);
85154359Sroberto			return;
85254359Sroberto		}
85354359Sroberto
85454359Sroberto
85554359Sroberto		/*
85654359Sroberto		 * Parse the time recovery message, and keep the info
85754359Sroberto		 * to print the pretty billboards.
85854359Sroberto		 */
85954359Sroberto		if ((cp = mx4200_parse_t(peer)) != NULL) {
86054359Sroberto			mx4200_debug(peer, "mx4200_receive: time: %s\n", cp);
86154359Sroberto			refclock_report(peer, CEVNT_BADREPLY);
86254359Sroberto			return;
86354359Sroberto		}
86454359Sroberto
86554359Sroberto		/*
86654359Sroberto		 * Add the new sample to a median filter.
86754359Sroberto		 */
86854359Sroberto		if (!refclock_process(pp)) {
86954359Sroberto			mx4200_debug(peer,"mx4200_receive: offset: %.6f\n",
87054359Sroberto			    pp->offset);
87154359Sroberto			refclock_report(peer, CEVNT_BADTIME);
87254359Sroberto			return;
87354359Sroberto		}
87454359Sroberto
87554359Sroberto		/*
87654359Sroberto		 * The clock will blurt a timecode every second but we only
87754359Sroberto		 * want one when polled.  If we havn't been polled, bail out.
87854359Sroberto		 */
87954359Sroberto		if (!up->polled)
88054359Sroberto			return;
88154359Sroberto
88254359Sroberto		/*
88354359Sroberto		 * Return offset and dispersion to control module.  We use
88454359Sroberto		 * lastrec as both the reference time and receive time in
88554359Sroberto		 * order to avoid being cute, like setting the reference time
88654359Sroberto		 * later than the receive time, which may cause a paranoid
88754359Sroberto		 * protocol module to chuck out the data.
88854359Sroberto		 */
88954359Sroberto		mx4200_debug(peer, "mx4200_receive: process time: ");
89054359Sroberto		mx4200_debug(peer, "%4d-%03d %02d:%02d:%02d at %s, %.6f\n",
89154359Sroberto		    pp->year, pp->day, pp->hour, pp->minute, pp->second,
89254359Sroberto		    prettydate(&pp->lastrec), pp->offset);
893132451Sroberto		pp->lastref = pp->lastrec;
89454359Sroberto		refclock_receive(peer);
89554359Sroberto
89654359Sroberto		/*
89754359Sroberto		 * We have succeeded in answering the poll.
89854359Sroberto		 * Turn off the flag and return
89954359Sroberto		 */
90054359Sroberto		up->polled = 0;
90182498Sroberto		break;
90254359Sroberto
90354359Sroberto	/*
90454359Sroberto	 * Ignore all other sentence types
90554359Sroberto	 */
90682498Sroberto	default:
90782498Sroberto		break;
90882498Sroberto
90982498Sroberto	} /* switch (sentence_type) */
91082498Sroberto
91154359Sroberto	return;
91254359Sroberto}
91354359Sroberto
91454359Sroberto
91554359Sroberto/*
91654359Sroberto * Parse a mx4200 time recovery message. Returns a string if error.
91754359Sroberto *
91854359Sroberto * A typical message looks like this.  Checksum has already been stripped.
91954359Sroberto *
92054359Sroberto *    $PMVXG,830,T,YYYY,MM,DD,HH:MM:SS,U,S,FFFFFF,PPPPP,BBBBBB,LL
92154359Sroberto *
92254359Sroberto *	Field	Field Contents
92354359Sroberto *	-----	--------------
92454359Sroberto *		Block Label: $PMVXG
92554359Sroberto *		Sentence Type: 830=Time Recovery Results
92654359Sroberto *			This sentence is output approximately 1 second
92754359Sroberto *			preceding the 1PPS output.  It indicates the
92854359Sroberto *			exact time of the next pulse, whether or not the
92954359Sroberto *			time mark will be valid (based on operator-specified
93054359Sroberto *			error tolerance), the time to which the pulse is
93154359Sroberto *			synchronized, the receiver operating mode,
93254359Sroberto *			and the time error of the *last* 1PPS output.
93354359Sroberto *	1  char Time Mark Valid: T=Valid, F=Not Valid
93454359Sroberto *	2  int  Year: 1993-
93554359Sroberto *	3  int  Month of Year: 1-12
93654359Sroberto *	4  int  Day of Month: 1-31
93754359Sroberto *	5  int  Time of Day: HH:MM:SS
93854359Sroberto *	6  char Time Synchronization: U=UTC, G=GPS
93954359Sroberto *	7  char Time Recovery Mode: D=Dynamic, S=Static,
94054359Sroberto *			K=Known Position, N=No Time Recovery
94154359Sroberto *	8  int  Oscillator Offset: The filter's estimate of the oscillator
94254359Sroberto *			frequency error, in parts per billion (ppb).
94354359Sroberto *	9  int  Time Mark Error: The computed error of the *last* pulse
94454359Sroberto *			output, in nanoseconds.
94554359Sroberto *	10 int  User Time Bias: Operator specified bias, in nanoseconds
94654359Sroberto *	11 int  Leap Second Flag: Indicates that a leap second will
94754359Sroberto *			occur.  This value is usually zero, except during
948132451Sroberto *			the week prior to the leap second occurrence, when
94954359Sroberto *			this value will be set to +1 or -1.  A value of
95054359Sroberto *			+1 indicates that GPS time will be 1 second
95154359Sroberto *			further ahead of UTC time.
95254359Sroberto *
95354359Sroberto */
95454359Srobertostatic char *
95554359Srobertomx4200_parse_t(
95654359Sroberto	struct peer *peer
95754359Sroberto	)
95854359Sroberto{
95954359Sroberto	struct refclockproc *pp;
96054359Sroberto	struct mx4200unit *up;
96154359Sroberto	char   time_mark_valid, time_sync, op_mode;
96254359Sroberto	int    sentence_type, valid;
96382498Sroberto	int    year, day_of_year, month, day_of_month;
96482498Sroberto	int    hour, minute, second, leapsec;
96554359Sroberto	int    oscillator_offset, time_mark_error, time_bias;
96654359Sroberto
96754359Sroberto	pp = peer->procptr;
96854359Sroberto	up = (struct mx4200unit *)pp->unitptr;
96954359Sroberto
97054359Sroberto	leapsec = 0;  /* Not all receivers output leap second warnings (!) */
97182498Sroberto	sscanf(pp->a_lastcode,
97282498Sroberto		"$PMVXG,%d,%c,%d,%d,%d,%d:%d:%d,%c,%c,%d,%d,%d,%d",
97354359Sroberto		&sentence_type, &time_mark_valid, &year, &month, &day_of_month,
97482498Sroberto		&hour, &minute, &second, &time_sync, &op_mode,
97582498Sroberto		&oscillator_offset, &time_mark_error, &time_bias, &leapsec);
97654359Sroberto
97754359Sroberto	if (sentence_type != PMVXG_D_TRECOVOUT)
97854359Sroberto		return ("wrong rec-type");
97954359Sroberto
98054359Sroberto	switch (time_mark_valid) {
98154359Sroberto		case 'T':
98254359Sroberto			valid = 1;
98354359Sroberto			break;
98454359Sroberto		case 'F':
98554359Sroberto			valid = 0;
98654359Sroberto			break;
98754359Sroberto		default:
98854359Sroberto			return ("bad pulse-valid");
98954359Sroberto	}
99054359Sroberto
99154359Sroberto	switch (time_sync) {
99254359Sroberto		case 'G':
99354359Sroberto			return ("synchronized to GPS; should be UTC");
99454359Sroberto		case 'U':
99554359Sroberto			break; /* UTC -> ok */
99654359Sroberto		default:
99754359Sroberto			return ("not synchronized to UTC");
99854359Sroberto	}
99954359Sroberto
100054359Sroberto	/*
100154359Sroberto	 * Check for insane time (allow for possible leap seconds)
100254359Sroberto	 */
100354359Sroberto	if (second > 60 || minute > 59 || hour > 23 ||
100454359Sroberto	    second <  0 || minute <  0 || hour <  0) {
100554359Sroberto		mx4200_debug(peer,
100654359Sroberto		    "mx4200_parse_t: bad time %02d:%02d:%02d",
100754359Sroberto		    hour, minute, second);
100854359Sroberto		if (leapsec != 0)
100954359Sroberto			mx4200_debug(peer, " (leap %+d\n)", leapsec);
101054359Sroberto		mx4200_debug(peer, "\n");
101154359Sroberto		refclock_report(peer, CEVNT_BADTIME);
101254359Sroberto		return ("bad time");
101354359Sroberto	}
101454359Sroberto	if ( second == 60 ) {
101554359Sroberto		msyslog(LOG_DEBUG,
101654359Sroberto		    "mx4200: leap second! %02d:%02d:%02d",
101754359Sroberto		    hour, minute, second);
101854359Sroberto	}
101954359Sroberto
102054359Sroberto	/*
102154359Sroberto	 * Check for insane date
102254359Sroberto	 * (Certainly can't be any year before this code was last altered!)
102354359Sroberto	 */
102454359Sroberto	if (day_of_month > 31 || month > 12 ||
102582498Sroberto	    day_of_month <  1 || month <  1 || year < YEAR_LAST_MODIFIED) {
102654359Sroberto		mx4200_debug(peer,
102754359Sroberto		    "mx4200_parse_t: bad date (%4d-%02d-%02d)\n",
102854359Sroberto		    year, month, day_of_month);
102954359Sroberto		refclock_report(peer, CEVNT_BADDATE);
103054359Sroberto		return ("bad date");
103154359Sroberto	}
103254359Sroberto
103354359Sroberto	/*
103454359Sroberto	 * Silly Hack for MX4200:
103554359Sroberto	 * ASCII message is for *next* 1PPS signal, but we have the
103654359Sroberto	 * timestamp for the *last* 1PPS signal.  So we have to subtract
103754359Sroberto	 * a second.  Discard if we are on a month boundary to avoid
103854359Sroberto	 * possible leap seconds and leap days.
103954359Sroberto	 */
104054359Sroberto	second--;
104154359Sroberto	if (second < 0) {
104254359Sroberto		second = 59;
104354359Sroberto		minute--;
104454359Sroberto		if (minute < 0) {
104554359Sroberto			minute = 59;
104654359Sroberto			hour--;
104754359Sroberto			if (hour < 0) {
104854359Sroberto				hour = 23;
104954359Sroberto				day_of_month--;
105054359Sroberto				if (day_of_month < 1) {
105154359Sroberto					return ("sorry, month boundary");
105254359Sroberto				}
105354359Sroberto			}
105454359Sroberto		}
105554359Sroberto	}
105654359Sroberto
105754359Sroberto	/*
105854359Sroberto	 * Calculate Julian date
105954359Sroberto	 */
106054359Sroberto	if (!(day_of_year = mx4200_jday(year, month, day_of_month))) {
106154359Sroberto		mx4200_debug(peer,
106254359Sroberto		    "mx4200_parse_t: bad julian date %d (%4d-%02d-%02d)\n",
106354359Sroberto		    day_of_year, year, month, day_of_month);
106454359Sroberto		refclock_report(peer, CEVNT_BADDATE);
106554359Sroberto		return("invalid julian date");
106654359Sroberto	}
106754359Sroberto
106854359Sroberto	/*
106954359Sroberto	 * Setup leap second indicator
107054359Sroberto	 */
107154359Sroberto	switch (leapsec) {
107254359Sroberto		case 0:
107354359Sroberto			pp->leap = LEAP_NOWARNING;
107454359Sroberto			break;
107554359Sroberto		case 1:
107654359Sroberto			pp->leap = LEAP_ADDSECOND;
107754359Sroberto			break;
107854359Sroberto		case -1:
107954359Sroberto			pp->leap = LEAP_DELSECOND;
108054359Sroberto			break;
108154359Sroberto		default:
108254359Sroberto			pp->leap = LEAP_NOTINSYNC;
108354359Sroberto	}
108454359Sroberto
108554359Sroberto	/*
108654359Sroberto	 * Any change to the leap second warning status?
108754359Sroberto	 */
108854359Sroberto	if (leapsec != up->last_leap ) {
108954359Sroberto		msyslog(LOG_DEBUG,
109054359Sroberto		    "mx4200: leap second warning: %d to %d (%d)",
109154359Sroberto		    up->last_leap, leapsec, pp->leap);
109254359Sroberto	}
109354359Sroberto	up->last_leap = leapsec;
109454359Sroberto
109554359Sroberto	/*
109654359Sroberto	 * Copy time data for billboard monitoring.
109754359Sroberto	 */
109854359Sroberto
109954359Sroberto	pp->year   = year;
110054359Sroberto	pp->day    = day_of_year;
110154359Sroberto	pp->hour   = hour;
110254359Sroberto	pp->minute = minute;
110354359Sroberto	pp->second = second;
110454359Sroberto
110554359Sroberto	/*
110654359Sroberto	 * Toss if sentence is marked invalid
110754359Sroberto	 */
110854359Sroberto	if (!valid || pp->leap == LEAP_NOTINSYNC) {
110954359Sroberto		mx4200_debug(peer, "mx4200_parse_t: time mark not valid\n");
111054359Sroberto		refclock_report(peer, CEVNT_BADTIME);
111154359Sroberto		return ("pulse invalid");
111254359Sroberto	}
111354359Sroberto
111454359Sroberto	return (NULL);
111554359Sroberto}
111654359Sroberto
111754359Sroberto/*
111854359Sroberto * Calculate the checksum
111954359Sroberto */
112054359Srobertostatic u_char
112154359Srobertomx4200_cksum(
112254359Sroberto	register char *cp,
112354359Sroberto	register int n
112454359Sroberto	)
112554359Sroberto{
112654359Sroberto	register u_char ck;
112754359Sroberto
112854359Sroberto	for (ck = 0; n-- > 0; cp++)
112954359Sroberto		ck ^= *cp;
113054359Sroberto	return (ck);
113154359Sroberto}
113254359Sroberto
113354359Sroberto/*
113454359Sroberto * Tables to compute the day of year.  Viva la leap.
113554359Sroberto */
113654359Srobertostatic int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
113754359Srobertostatic int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
113854359Sroberto
113954359Sroberto/*
114054359Sroberto * Calculate the the Julian Day
114154359Sroberto */
114254359Srobertostatic int
114354359Srobertomx4200_jday(
114454359Sroberto	int year,
114554359Sroberto	int month,
114654359Sroberto	int day_of_month
114754359Sroberto	)
114854359Sroberto{
114954359Sroberto	register int day, i;
115054359Sroberto	int leap_year;
115154359Sroberto
115254359Sroberto	/*
115354359Sroberto	 * Is this a leap year ?
115454359Sroberto	 */
115554359Sroberto	if (year % 4) {
115654359Sroberto		leap_year = 0; /* FALSE */
115754359Sroberto	} else {
115854359Sroberto		if (year % 100) {
115954359Sroberto			leap_year = 1; /* TRUE */
116054359Sroberto		} else {
116154359Sroberto			if (year % 400) {
116254359Sroberto				leap_year = 0; /* FALSE */
116354359Sroberto			} else {
116454359Sroberto				leap_year = 1; /* TRUE */
116554359Sroberto			}
116654359Sroberto		}
116754359Sroberto	}
116854359Sroberto
116954359Sroberto	/*
117054359Sroberto	 * Calculate the Julian Date
117154359Sroberto	 */
117254359Sroberto	day = day_of_month;
117354359Sroberto
117454359Sroberto	if (leap_year) {
117554359Sroberto		/* a leap year */
117654359Sroberto		if (day > day2tab[month - 1]) {
117754359Sroberto			return (0);
117854359Sroberto		}
117954359Sroberto		for (i = 0; i < month - 1; i++)
118054359Sroberto		    day += day2tab[i];
118154359Sroberto	} else {
118254359Sroberto		/* not a leap year */
118354359Sroberto		if (day > day1tab[month - 1]) {
118454359Sroberto			return (0);
118554359Sroberto		}
118654359Sroberto		for (i = 0; i < month - 1; i++)
118754359Sroberto		    day += day1tab[i];
118854359Sroberto	}
118954359Sroberto	return (day);
119054359Sroberto}
119154359Sroberto
119254359Sroberto/*
119354359Sroberto * Parse a mx4200 position/height/velocity sentence.
119454359Sroberto *
119554359Sroberto * A typical message looks like this.  Checksum has already been stripped.
119654359Sroberto *
119754359Sroberto * $PMVXG,021,SSSSSS.SS,DDMM.MMMM,N,DDDMM.MMMM,E,HHHHH.H,GGGG.G,EEEE.E,WWWW.W,MM
119854359Sroberto *
119954359Sroberto *	Field	Field Contents
120054359Sroberto *	-----	--------------
120154359Sroberto *		Block Label: $PMVXG
120254359Sroberto *		Sentence Type: 021=Position, Height Velocity Data
120354359Sroberto *			This sentence gives the receiver position, height,
120454359Sroberto *			navigation mode, and velocity north/east.
120554359Sroberto *			*This sentence is intended for post-analysis
120654359Sroberto *			applications.*
120754359Sroberto *	1 float UTC measurement time (seconds into week)
120854359Sroberto *	2 float WGS-84 Lattitude (degrees, minutes)
120954359Sroberto *	3  char N=North, S=South
121054359Sroberto *	4 float WGS-84 Longitude (degrees, minutes)
121154359Sroberto *	5  char E=East, W=West
121254359Sroberto *	6 float Altitude (meters above mean sea level)
121354359Sroberto *	7 float Geoidal height (meters)
121454359Sroberto *	8 float East velocity (m/sec)
121554359Sroberto *	9 float West Velocity (m/sec)
121654359Sroberto *	10  int Navigation Mode
121754359Sroberto *		    Mode if navigating:
121854359Sroberto *			1 = Position from remote device
121954359Sroberto *			2 = 2-D position
122054359Sroberto *			3 = 3-D position
122154359Sroberto *			4 = 2-D differential position
122254359Sroberto *			5 = 3-D differential position
122354359Sroberto *			6 = Static
122454359Sroberto *			8 = Position known -- reference station
122554359Sroberto *			9 = Position known -- Navigator
122654359Sroberto *		    Mode if not navigating:
122754359Sroberto *			51 = Too few satellites
122854359Sroberto *			52 = DOPs too large
122954359Sroberto *			53 = Position STD too large
123054359Sroberto *			54 = Velocity STD too large
123154359Sroberto *			55 = Too many iterations for velocity
123254359Sroberto *			56 = Too many iterations for position
123354359Sroberto *			57 = 3 sat startup failed
123454359Sroberto *			58 = Command abort
123554359Sroberto */
123654359Srobertostatic char *
123754359Srobertomx4200_parse_p(
123854359Sroberto	struct peer *peer
123954359Sroberto	)
124054359Sroberto{
124154359Sroberto	struct refclockproc *pp;
124254359Sroberto	struct mx4200unit *up;
124354359Sroberto	int sentence_type, mode;
124482498Sroberto	double mtime, lat, lon, alt, geoid, vele, veln;
124554359Sroberto	char   north_south, east_west;
124654359Sroberto
124754359Sroberto	pp = peer->procptr;
124854359Sroberto	up = (struct mx4200unit *)pp->unitptr;
124954359Sroberto
125054359Sroberto	/* Should never happen! */
125154359Sroberto	if (up->moving) return ("mobile platform - no pos!");
125254359Sroberto
125382498Sroberto	sscanf ( pp->a_lastcode,
125482498Sroberto		"$PMVXG,%d,%lf,%lf,%c,%lf,%c,%lf,%lf,%lf,%lf,%d",
125582498Sroberto		&sentence_type, &mtime, &lat, &north_south, &lon, &east_west,
125682498Sroberto		&alt, &geoid, &vele, &veln, &mode);
125754359Sroberto
125854359Sroberto	/* Sentence type */
125954359Sroberto	if (sentence_type != PMVXG_D_PHV)
126054359Sroberto		return ("wrong rec-type");
126154359Sroberto
126254359Sroberto	/*
126354359Sroberto	 * return if not navigating
126454359Sroberto	 */
126554359Sroberto	if (mode > 10)
126654359Sroberto		return ("not navigating");
126754359Sroberto	if (mode != 3 && mode != 5)
126854359Sroberto		return ("not navigating in 3D");
126954359Sroberto
127054359Sroberto	/* Latitude (always +ve) and convert DDMM.MMMM to decimal */
127154359Sroberto	if (lat <  0.0) return ("negative latitude");
127254359Sroberto	if (lat > 9000.0) lat = 9000.0;
127354359Sroberto	lat *= 0.01;
127454359Sroberto	lat = ((int)lat) + (((lat - (int)lat)) * 1.6666666666666666);
127554359Sroberto
127654359Sroberto	/* North/South */
127754359Sroberto	switch (north_south) {
127854359Sroberto		case 'N':
127954359Sroberto			break;
128054359Sroberto		case 'S':
128154359Sroberto			lat *= -1.0;
128254359Sroberto			break;
128354359Sroberto		default:
128454359Sroberto			return ("invalid north/south indicator");
128554359Sroberto	}
128654359Sroberto
128754359Sroberto	/* Longitude (always +ve) and convert DDDMM.MMMM to decimal */
128854359Sroberto	if (lon <   0.0) return ("negative longitude");
128954359Sroberto	if (lon > 180.0) lon = 180.0;
129054359Sroberto	lon *= 0.01;
129154359Sroberto	lon = ((int)lon) + (((lon - (int)lon)) * 1.6666666666666666);
129254359Sroberto
129354359Sroberto	/* East/West */
129454359Sroberto	switch (east_west) {
129554359Sroberto		case 'E':
129654359Sroberto			break;
129754359Sroberto		case 'W':
129854359Sroberto			lon *= -1.0;
129954359Sroberto			break;
130054359Sroberto		default:
130154359Sroberto			return ("invalid east/west indicator");
130254359Sroberto	}
130354359Sroberto
130454359Sroberto	/*
130554359Sroberto	 * Normalize longitude to near 0 degrees.
130654359Sroberto	 * Assume all data are clustered around first reading.
130754359Sroberto	 */
130854359Sroberto	if (up->central_meridian == NOT_INITIALIZED) {
130954359Sroberto		up->central_meridian = lon;
131054359Sroberto		mx4200_debug(peer,
131154359Sroberto		    "mx4200_receive: central meridian =  %.9f \n",
131254359Sroberto		    up->central_meridian);
131354359Sroberto	}
131454359Sroberto	lon -= up->central_meridian;
131554359Sroberto	if (lon < -180.0) lon += 360.0;
131654359Sroberto	if (lon >  180.0) lon -= 360.0;
131754359Sroberto
131854359Sroberto	/*
131982498Sroberto	 * Calculate running averages
132054359Sroberto	 */
132154359Sroberto
132282498Sroberto	up->avg_lon = (up->N_fixes * up->avg_lon) + lon;
132382498Sroberto	up->avg_lat = (up->N_fixes * up->avg_lat) + lat;
132482498Sroberto	up->avg_alt = (up->N_fixes * up->avg_alt) + alt;
132554359Sroberto
132682498Sroberto	up->N_fixes += 1.0;
132754359Sroberto
132882498Sroberto	up->avg_lon /= up->N_fixes;
132982498Sroberto	up->avg_lat /= up->N_fixes;
133082498Sroberto	up->avg_alt /= up->N_fixes;
133182498Sroberto
133254359Sroberto	mx4200_debug(peer,
133382498Sroberto	    "mx4200_receive: position rdg %.0f: %.9f %.9f %.4f (CM=%.9f)\n",
133482498Sroberto	    up->N_fixes, lat, lon, alt, up->central_meridian);
133554359Sroberto
133654359Sroberto	return (NULL);
133754359Sroberto}
133854359Sroberto
133954359Sroberto/*
134054359Sroberto * Parse a mx4200 Status sentence
134154359Sroberto * Parse a mx4200 Mode Data sentence
134254359Sroberto * Parse a mx4200 Software Configuration sentence
134354359Sroberto * Parse a mx4200 Time Recovery Parameters Currently in Use sentence
134454359Sroberto * (used only for logging raw strings)
134554359Sroberto *
134654359Sroberto * A typical message looks like this.  Checksum has already been stripped.
134754359Sroberto *
134854359Sroberto * $PMVXG,000,XXX,XX,X,HHMM,X
134954359Sroberto *
135054359Sroberto *	Field	Field Contents
135154359Sroberto *	-----	--------------
135254359Sroberto *		Block Label: $PMVXG
135354359Sroberto *		Sentence Type: 000=Status.
135454359Sroberto *			Returns status of the receiver to the controller.
135554359Sroberto *	1	Current Receiver Status:
135654359Sroberto *		ACQ = Satellite re-acquisition
135754359Sroberto *		ALT = Constellation selection
135854359Sroberto *		COR = Providing corrections (for reference stations only)
135954359Sroberto *		IAC = Initial acquisition
136054359Sroberto *		IDL = Idle, no satellites
136154359Sroberto *		NAV = Navigation
136254359Sroberto *		STS = Search the Sky (no almanac available)
136354359Sroberto *		TRK = Tracking
136454359Sroberto *	2	Number of satellites that should be visible
136554359Sroberto *	3	Number of satellites being tracked
136654359Sroberto *	4	Time since last navigation status if not currently navigating
136754359Sroberto *		(hours, minutes)
136854359Sroberto *	5	Initialization status:
136954359Sroberto *		0 = Waiting for initialization parameters
137054359Sroberto *		1 = Initialization completed
137154359Sroberto *
137254359Sroberto * A typical message looks like this.  Checksum has already been stripped.
137354359Sroberto *
137454359Sroberto * $PMVXG,004,C,R,D,H.HH,V.VV,TT,HHHH,VVVV,T
137554359Sroberto *
137654359Sroberto *	Field	Field Contents
137754359Sroberto *	-----	--------------
137854359Sroberto *		Block Label: $PMVXG
137954359Sroberto *		Sentence Type: 004=Software Configuration.
138054359Sroberto *			Defines the navigation mode and criteria for
138154359Sroberto *			acceptable navigation for the receiver.
138254359Sroberto *	1	Constrain Altitude Mode:
138354359Sroberto *		0 = Auto.  Constrain altitude (2-D solution) and use
138454359Sroberto *		    manual altitude input when 3 sats avalable.  Do
138554359Sroberto *		    not constrain altitude (3-D solution) when 4 sats
138654359Sroberto *		    available.
138754359Sroberto *		1 = Always constrain altitude (2-D solution).
138854359Sroberto *		2 = Never constrain altitude (3-D solution).
138954359Sroberto *		3 = Coast.  Constrain altitude (2-D solution) and use
139054359Sroberto *		    last GPS altitude calculation when 3 sats avalable.
139154359Sroberto *		    Do not constrain altitude (3-D solution) when 4 sats
139254359Sroberto *		    available.
139354359Sroberto *	2	Altitude Reference: (always 0 for MX4200)
139454359Sroberto *		0 = Ellipsoid
139554359Sroberto *		1 = Geoid (MSL)
139654359Sroberto *	3	Differential Navigation Control:
139754359Sroberto *		0 = Disabled
139854359Sroberto *		1 = Enabled
139954359Sroberto *	4	Horizontal Acceleration Constant (m/sec**2)
140054359Sroberto *	5	Vertical Acceleration Constant (m/sec**2) (0 for MX4200)
140154359Sroberto *	6	Tracking Elevation Limit (degrees)
140254359Sroberto *	7	HDOP Limit
140354359Sroberto *	8	VDOP Limit
140454359Sroberto *	9	Time Output Mode:
140554359Sroberto *		U = UTC
140654359Sroberto *		L = Local time
140754359Sroberto *	10	Local Time Offset (minutes) (absent on MX4200)
140854359Sroberto *
140954359Sroberto * A typical message looks like this.  Checksum has already been stripped.
141054359Sroberto *
141154359Sroberto * $PMVXG,030,NNNN,FFF
141254359Sroberto *
141354359Sroberto *	Field	Field Contents
141454359Sroberto *	-----	--------------
141554359Sroberto *		Block Label: $PMVXG
141654359Sroberto *		Sentence Type: 030=Software Configuration.
141754359Sroberto *			This sentence contains the navigation processor
141854359Sroberto *			and baseband firmware version numbers.
141954359Sroberto *	1	Nav Processor Version Number
142054359Sroberto *	2	Baseband Firmware Version Number
142154359Sroberto *
142254359Sroberto * A typical message looks like this.  Checksum has already been stripped.
142354359Sroberto *
142454359Sroberto * $PMVXG,523,M,S,M,EEEE,BBBBBB,C,R
142554359Sroberto *
142654359Sroberto *	Field	Field Contents
142754359Sroberto *	-----	--------------
142854359Sroberto *		Block Label: $PMVXG
142954359Sroberto *		Sentence Type: 523=Time Recovery Parameters Currently in Use.
143054359Sroberto *			This sentence contains the configuration of the
143154359Sroberto *			time recovery feature of the receiver.
143254359Sroberto *	1	Time Recovery Mode:
143354359Sroberto *		D = Dynamic; solve for position and time while moving
143454359Sroberto *		S = Static; solve for position and time while stationary
143554359Sroberto *		K = Known position input, solve for time only
143654359Sroberto *		N = No time recovery
143754359Sroberto *	2	Time Synchronization:
143854359Sroberto *		U = UTC time
143954359Sroberto *		G = GPS time
144054359Sroberto *	3	Time Mark Mode:
144154359Sroberto *		A = Always output a time pulse
144254359Sroberto *		V = Only output time pulse if time is valid (as determined
144354359Sroberto *		    by Maximum Time Error)
144454359Sroberto *	4	Maximum Time Error - the maximum error (in nanoseconds) for
144554359Sroberto *		which a time mark will be considered valid.
144654359Sroberto *	5	User Time Bias - external bias in nanoseconds
144754359Sroberto *	6	Time Message Control:
144854359Sroberto *		0 = Do not output the time recovery message
144954359Sroberto *		1 = Output the time recovery message (record 830) to
145054359Sroberto *		    Control port
145154359Sroberto *		2 = Output the time recovery message (record 830) to
145254359Sroberto *		    Equipment port
145354359Sroberto *	7	Reserved
145454359Sroberto *	8	Position Known PRN (absent on MX 4200)
145554359Sroberto *
145654359Sroberto */
145754359Srobertostatic char *
145854359Srobertomx4200_parse_s(
145954359Sroberto	struct peer *peer
146054359Sroberto	)
146154359Sroberto{
146254359Sroberto	struct refclockproc *pp;
146354359Sroberto	struct mx4200unit *up;
146454359Sroberto	int sentence_type;
146554359Sroberto
146654359Sroberto	pp = peer->procptr;
146754359Sroberto	up = (struct mx4200unit *)pp->unitptr;
146854359Sroberto
146954359Sroberto        sscanf ( pp->a_lastcode, "$PMVXG,%d", &sentence_type);
147054359Sroberto
147154359Sroberto	/* Sentence type */
147254359Sroberto	switch (sentence_type) {
147354359Sroberto
147454359Sroberto		case PMVXG_D_STATUS:
147554359Sroberto			msyslog(LOG_DEBUG,
147682498Sroberto			  "mx4200: status: %s", pp->a_lastcode);
147754359Sroberto			break;
147854359Sroberto		case PMVXG_D_MODEDATA:
147954359Sroberto			msyslog(LOG_DEBUG,
148082498Sroberto			  "mx4200: mode data: %s", pp->a_lastcode);
148154359Sroberto			break;
148254359Sroberto		case PMVXG_D_SOFTCONF:
148354359Sroberto			msyslog(LOG_DEBUG,
148482498Sroberto			  "mx4200: firmware configuration: %s", pp->a_lastcode);
148554359Sroberto			break;
148654359Sroberto		case PMVXG_D_TRECOVUSEAGE:
148754359Sroberto			msyslog(LOG_DEBUG,
148882498Sroberto			  "mx4200: time recovery parms: %s", pp->a_lastcode);
148954359Sroberto			break;
149054359Sroberto		default:
149154359Sroberto			return ("wrong rec-type");
149254359Sroberto	}
149354359Sroberto
149454359Sroberto	return (NULL);
149554359Sroberto}
149654359Sroberto
149754359Sroberto/*
149882498Sroberto * Process a PPS signal, placing a timestamp in pp->lastrec.
149954359Sroberto */
150054359Srobertostatic int
150154359Srobertomx4200_pps(
150254359Sroberto	struct peer *peer
150354359Sroberto	)
150454359Sroberto{
150554359Sroberto	int temp_serial;
150654359Sroberto	struct refclockproc *pp;
150754359Sroberto	struct mx4200unit *up;
150854359Sroberto
150982498Sroberto	struct timespec timeout;
151054359Sroberto
151154359Sroberto	pp = peer->procptr;
151254359Sroberto	up = (struct mx4200unit *)pp->unitptr;
151354359Sroberto
151454359Sroberto	/*
151554359Sroberto	 * Grab the timestamp of the PPS signal.
151654359Sroberto	 */
151782498Sroberto	temp_serial = up->pps_i.assert_sequence;
151882498Sroberto	timeout.tv_sec  = 0;
151982498Sroberto	timeout.tv_nsec = 0;
152082498Sroberto	if (time_pps_fetch(up->pps_h, PPS_TSFMT_TSPEC, &(up->pps_i),
152182498Sroberto			&timeout) < 0) {
152254359Sroberto		mx4200_debug(peer,
1523182007Sroberto		  "mx4200_pps: time_pps_fetch: serial=%ul, %s\n",
1524182007Sroberto		     (unsigned long)up->pps_i.assert_sequence, strerror(errno));
152554359Sroberto		refclock_report(peer, CEVNT_FAULT);
152654359Sroberto		return(1);
152754359Sroberto	}
152882498Sroberto	if (temp_serial == up->pps_i.assert_sequence) {
152954359Sroberto		mx4200_debug(peer,
1530182007Sroberto		   "mx4200_pps: assert_sequence serial not incrementing: %ul\n",
1531182007Sroberto			(unsigned long)up->pps_i.assert_sequence);
153254359Sroberto		refclock_report(peer, CEVNT_FAULT);
153354359Sroberto		return(1);
153454359Sroberto	}
153554359Sroberto	/*
153654359Sroberto	 * Check pps serial number against last one
153754359Sroberto	 */
153882498Sroberto	if (up->lastserial + 1 != up->pps_i.assert_sequence &&
153982498Sroberto	    up->lastserial != 0) {
154082498Sroberto		if (up->pps_i.assert_sequence == up->lastserial) {
154154359Sroberto			mx4200_debug(peer, "mx4200_pps: no new pps event\n");
154282498Sroberto		} else {
1543182007Sroberto			mx4200_debug(peer, "mx4200_pps: missed %ul pps events\n",
1544182007Sroberto			    up->pps_i.assert_sequence - up->lastserial - 1UL);
154582498Sroberto		}
154654359Sroberto		refclock_report(peer, CEVNT_FAULT);
154754359Sroberto	}
154882498Sroberto	up->lastserial = up->pps_i.assert_sequence;
154954359Sroberto
155054359Sroberto	/*
155154359Sroberto	 * Return the timestamp in pp->lastrec
155254359Sroberto	 */
155354359Sroberto
155482498Sroberto	pp->lastrec.l_ui = up->pps_i.assert_timestamp.tv_sec +
155582498Sroberto			   (u_int32) JAN_1970;
155682498Sroberto	pp->lastrec.l_uf = ((double)(up->pps_i.assert_timestamp.tv_nsec) *
155782498Sroberto			   4.2949672960) + 0.5;
155882498Sroberto
155954359Sroberto	return(0);
156054359Sroberto}
156154359Sroberto
156254359Sroberto/*
156354359Sroberto * mx4200_debug - print debug messages
156454359Sroberto */
156556746Sroberto#if defined(__STDC__)
156654359Srobertostatic void
156754359Srobertomx4200_debug(struct peer *peer, char *fmt, ...)
156854359Sroberto#else
156956746Srobertostatic void
157054359Srobertomx4200_debug(peer, fmt, va_alist)
157154359Sroberto     struct peer *peer;
157254359Sroberto     char *fmt;
157356746Sroberto#endif /* __STDC__ */
157454359Sroberto{
1575182007Sroberto#ifdef DEBUG
157654359Sroberto	va_list ap;
157754359Sroberto	struct refclockproc *pp;
157854359Sroberto	struct mx4200unit *up;
157954359Sroberto
158054359Sroberto	if (debug) {
158154359Sroberto
158256746Sroberto#if defined(__STDC__)
158354359Sroberto		va_start(ap, fmt);
158454359Sroberto#else
158554359Sroberto		va_start(ap);
158656746Sroberto#endif /* __STDC__ */
158754359Sroberto
158854359Sroberto		pp = peer->procptr;
158954359Sroberto		up = (struct mx4200unit *)pp->unitptr;
159054359Sroberto
159154359Sroberto
159254359Sroberto		/*
159354359Sroberto		 * Print debug message to stdout
159454359Sroberto		 * In the future, we may want to get get more creative...
159554359Sroberto		 */
159654359Sroberto		vprintf(fmt, ap);
159754359Sroberto
159854359Sroberto		va_end(ap);
159954359Sroberto	}
1600182007Sroberto#endif
160154359Sroberto}
160254359Sroberto
160354359Sroberto/*
160454359Sroberto * Send a character string to the receiver.  Checksum is appended here.
160554359Sroberto */
160656746Sroberto#if defined(__STDC__)
160754359Srobertostatic void
160854359Srobertomx4200_send(struct peer *peer, char *fmt, ...)
160954359Sroberto#else
161056746Srobertostatic void
161156746Srobertomx4200_send(peer, fmt, va_alist)
161254359Sroberto     struct peer *peer;
161354359Sroberto     char *fmt;
161454359Sroberto     va_dcl
161554359Sroberto#endif /* __STDC__ */
161654359Sroberto{
161754359Sroberto	struct refclockproc *pp;
161854359Sroberto	struct mx4200unit *up;
161954359Sroberto
162054359Sroberto	register char *cp;
162154359Sroberto	register int n, m;
162254359Sroberto	va_list ap;
162354359Sroberto	char buf[1024];
162454359Sroberto	u_char ck;
162554359Sroberto
162656746Sroberto#if defined(__STDC__)
162754359Sroberto	va_start(ap, fmt);
162854359Sroberto#else
162954359Sroberto	va_start(ap);
163054359Sroberto#endif /* __STDC__ */
163154359Sroberto
163254359Sroberto	pp = peer->procptr;
163354359Sroberto	up = (struct mx4200unit *)pp->unitptr;
163454359Sroberto
163554359Sroberto	cp = buf;
163654359Sroberto	*cp++ = '$';
1637132451Sroberto	n = VSNPRINTF((cp, sizeof(buf) - 1, fmt, ap));
163854359Sroberto	ck = mx4200_cksum(cp, n);
163954359Sroberto	cp += n;
164054359Sroberto	++n;
1641132451Sroberto	n += SNPRINTF((cp, sizeof(buf) - n - 5, "*%02X\r\n", ck));
164254359Sroberto
164354359Sroberto	m = write(pp->io.fd, buf, (unsigned)n);
164454359Sroberto	if (m < 0)
164554359Sroberto		msyslog(LOG_ERR, "mx4200_send: write: %m (%s)", buf);
164654359Sroberto	mx4200_debug(peer, "mx4200_send: %d %s\n", m, buf);
164754359Sroberto	va_end(ap);
164854359Sroberto}
164954359Sroberto
165054359Sroberto#else
165154359Srobertoint refclock_mx4200_bs;
165254359Sroberto#endif /* REFCLOCK */
1653