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
7054359Sroberto#ifndef HAVE_STRUCT_PPSCLOCKEV
7154359Srobertostruct ppsclockev {
7282498Sroberto# ifdef HAVE_STRUCT_TIMESPEC
7354359Sroberto	struct timespec tv;
7454359Sroberto# else
7554359Sroberto	struct timeval tv;
7654359Sroberto# endif
7754359Sroberto	u_int serial;
7854359Sroberto};
7954359Sroberto#endif /* ! HAVE_STRUCT_PPSCLOCKEV */
8054359Sroberto
81182007Sroberto#ifdef HAVE_PPSAPI
82182007Sroberto# include "ppsapi_timepps.h"
83182007Sroberto#endif /* HAVE_PPSAPI */
8482498Sroberto
8554359Sroberto/*
8654359Sroberto * This driver supports the Magnavox Model MX 4200 GPS Receiver
8754359Sroberto * adapted to precision timing applications.  It requires the
8854359Sroberto * ppsclock line discipline or streams module described in the
8954359Sroberto * Line Disciplines and Streams Drivers page. It also requires a
9054359Sroberto * gadget box and 1-PPS level converter, such as described in the
9154359Sroberto * Pulse-per-second (PPS) Signal Interfacing page.
9254359Sroberto *
9354359Sroberto * It's likely that other compatible Magnavox receivers such as the
9454359Sroberto * MX 4200D, MX 9212, MX 9012R, MX 9112 will be supported by this code.
9554359Sroberto */
9654359Sroberto
9754359Sroberto/*
9854359Sroberto * Check this every time you edit the code!
9954359Sroberto */
10082498Sroberto#define YEAR_LAST_MODIFIED 2000
10154359Sroberto
10254359Sroberto/*
10354359Sroberto * GPS Definitions
10454359Sroberto */
10554359Sroberto#define	DEVICE		"/dev/gps%d"	/* device name and unit */
10654359Sroberto#define	SPEED232	B4800		/* baud */
10754359Sroberto
10854359Sroberto/*
10954359Sroberto * Radio interface parameters
11054359Sroberto */
11154359Sroberto#define	PRECISION	(-18)	/* precision assumed (about 4 us) */
11254359Sroberto#define	REFID	"GPS\0"		/* reference id */
11354359Sroberto#define	DESCRIPTION	"Magnavox MX4200 GPS Receiver" /* who we are */
11454359Sroberto#define	DEFFUDGETIME	0	/* default fudge time (ms) */
11554359Sroberto
11654359Sroberto#define	SLEEPTIME	32	/* seconds to wait for reconfig to complete */
11754359Sroberto
11854359Sroberto/*
11954359Sroberto * Position Averaging.
12054359Sroberto */
12154359Sroberto#define INTERVAL	1	/* Interval between position measurements (s) */
12254359Sroberto#define AVGING_TIME	24	/* Number of hours to average */
12354359Sroberto#define NOT_INITIALIZED	-9999.	/* initial pivot longitude */
12454359Sroberto
12554359Sroberto/*
12654359Sroberto * MX4200 unit control structure.
12754359Sroberto */
12854359Srobertostruct mx4200unit {
12954359Sroberto	u_int  pollcnt;			/* poll message counter */
13054359Sroberto	u_int  polled;			/* Hand in a time sample? */
13154359Sroberto	u_int  lastserial;		/* last pps serial number */
13254359Sroberto	struct ppsclockev ppsev;	/* PPS control structure */
13354359Sroberto	double avg_lat;			/* average latitude */
13454359Sroberto	double avg_lon;			/* average longitude */
13554359Sroberto	double avg_alt;			/* average height */
13654359Sroberto	double central_meridian;	/* central meridian */
13782498Sroberto	double N_fixes;			/* Number of position measurements */
13854359Sroberto	int    last_leap;		/* leap second warning */
13954359Sroberto	u_int  moving;			/* mobile platform? */
14054359Sroberto	u_long sloppyclockflag;		/* fudge flags */
14154359Sroberto	u_int  known;			/* position known yet? */
14254359Sroberto	u_long clamp_time;		/* when to stop postion averaging */
14354359Sroberto	u_long log_time;		/* when to print receiver status */
14482498Sroberto	pps_handle_t	pps_h;
14582498Sroberto	pps_params_t	pps_p;
14682498Sroberto	pps_info_t	pps_i;
14754359Sroberto};
14854359Sroberto
14954359Srobertostatic char pmvxg[] = "PMVXG";
15054359Sroberto
15154359Sroberto/* XXX should be somewhere else */
15254359Sroberto#ifdef __GNUC__
15354359Sroberto#if __GNUC__ < 2  || (__GNUC__ == 2 && __GNUC_MINOR__ < 5)
15454359Sroberto#ifndef __attribute__
15554359Sroberto#define __attribute__(args)
15656746Sroberto#endif /* __attribute__ */
15756746Sroberto#endif /* __GNUC__ < 2  || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) */
15854359Sroberto#else
15954359Sroberto#ifndef __attribute__
16054359Sroberto#define __attribute__(args)
16156746Sroberto#endif /* __attribute__ */
16256746Sroberto#endif /* __GNUC__ */
16354359Sroberto/* XXX end */
16454359Sroberto
16554359Sroberto/*
16654359Sroberto * Function prototypes
16754359Sroberto */
168290001Sglebiusstatic	int	mx4200_start	(int, struct peer *);
169290001Sglebiusstatic	void	mx4200_shutdown	(int, struct peer *);
170290001Sglebiusstatic	void	mx4200_receive	(struct recvbuf *);
171290001Sglebiusstatic	void	mx4200_poll	(int, struct peer *);
17254359Sroberto
173290001Sglebiusstatic	char *	mx4200_parse_t	(struct peer *);
174290001Sglebiusstatic	char *	mx4200_parse_p	(struct peer *);
175290001Sglebiusstatic	char *	mx4200_parse_s	(struct peer *);
176290001Sglebiusint	mx4200_cmpl_fp	(const void *, const void *);
177290001Sglebiusstatic	int	mx4200_config	(struct peer *);
178290001Sglebiusstatic	void	mx4200_ref	(struct peer *);
179290001Sglebiusstatic	void	mx4200_send	(struct peer *, char *, ...)
18054359Sroberto    __attribute__ ((format (printf, 2, 3)));
181290001Sglebiusstatic	u_char	mx4200_cksum	(char *, int);
182290001Sglebiusstatic	int	mx4200_jday	(int, int, int);
183290001Sglebiusstatic	void	mx4200_debug	(struct peer *, char *, ...)
18454359Sroberto    __attribute__ ((format (printf, 2, 3)));
185290001Sglebiusstatic	int	mx4200_pps	(struct peer *);
18654359Sroberto
18754359Sroberto/*
18854359Sroberto * Transfer vector
18954359Sroberto */
19054359Srobertostruct	refclock refclock_mx4200 = {
19154359Sroberto	mx4200_start,		/* start up driver */
19254359Sroberto	mx4200_shutdown,	/* shut down driver */
19354359Sroberto	mx4200_poll,		/* transmit poll message */
19454359Sroberto	noentry,		/* not used (old mx4200_control) */
19554359Sroberto	noentry,		/* initialize driver (not used) */
19654359Sroberto	noentry,		/* not used (old mx4200_buginfo) */
19754359Sroberto	NOFLAGS			/* not used */
19854359Sroberto};
19954359Sroberto
20054359Sroberto
20154359Sroberto
20254359Sroberto/*
20354359Sroberto * mx4200_start - open the devices and initialize data for processing
20454359Sroberto */
20554359Srobertostatic int
20654359Srobertomx4200_start(
20754359Sroberto	int unit,
20854359Sroberto	struct peer *peer
20954359Sroberto	)
21054359Sroberto{
21154359Sroberto	register struct mx4200unit *up;
21254359Sroberto	struct refclockproc *pp;
21354359Sroberto	int fd;
21454359Sroberto	char gpsdev[20];
21554359Sroberto
21654359Sroberto	/*
21754359Sroberto	 * Open serial port
21854359Sroberto	 */
219290001Sglebius	snprintf(gpsdev, sizeof(gpsdev), DEVICE, unit);
220290001Sglebius	fd = refclock_open(gpsdev, SPEED232, LDISC_PPS);
221290001Sglebius	if (fd <= 0)
222290001Sglebius		return 0;
22354359Sroberto
22454359Sroberto	/*
22554359Sroberto	 * Allocate unit structure
22654359Sroberto	 */
227290001Sglebius	up = emalloc_zero(sizeof(*up));
22854359Sroberto	pp = peer->procptr;
22954359Sroberto	pp->io.clock_recv = mx4200_receive;
230290001Sglebius	pp->io.srcclock = peer;
23154359Sroberto	pp->io.datalen = 0;
23254359Sroberto	pp->io.fd = fd;
23354359Sroberto	if (!io_addclock(&pp->io)) {
234290001Sglebius		close(fd);
235290001Sglebius		pp->io.fd = -1;
23654359Sroberto		free(up);
23754359Sroberto		return (0);
23854359Sroberto	}
239290001Sglebius	pp->unitptr = up;
24054359Sroberto
24154359Sroberto	/*
24254359Sroberto	 * Initialize miscellaneous variables
24354359Sroberto	 */
24454359Sroberto	peer->precision = PRECISION;
24554359Sroberto	pp->clockdesc = DESCRIPTION;
24654359Sroberto	memcpy((char *)&pp->refid, REFID, 4);
24754359Sroberto
24854359Sroberto	/* Ensure the receiver is properly configured */
24982498Sroberto	return mx4200_config(peer);
25054359Sroberto}
25154359Sroberto
25254359Sroberto
25354359Sroberto/*
25454359Sroberto * mx4200_shutdown - shut down the clock
25554359Sroberto */
25654359Srobertostatic void
25754359Srobertomx4200_shutdown(
25854359Sroberto	int unit,
25954359Sroberto	struct peer *peer
26054359Sroberto	)
26154359Sroberto{
26254359Sroberto	register struct mx4200unit *up;
26354359Sroberto	struct refclockproc *pp;
26454359Sroberto
26554359Sroberto	pp = peer->procptr;
266290001Sglebius	up = pp->unitptr;
267290001Sglebius	if (-1 != pp->io.fd)
268290001Sglebius		io_closeclock(&pp->io);
269290001Sglebius	if (NULL != up)
270290001Sglebius		free(up);
27154359Sroberto}
27254359Sroberto
27354359Sroberto
27454359Sroberto/*
27554359Sroberto * mx4200_config - Configure the receiver
27654359Sroberto */
27782498Srobertostatic int
27854359Srobertomx4200_config(
27954359Sroberto	struct peer *peer
28054359Sroberto	)
28154359Sroberto{
28254359Sroberto	char tr_mode;
28354359Sroberto	int add_mode;
28454359Sroberto	register struct mx4200unit *up;
28554359Sroberto	struct refclockproc *pp;
28682498Sroberto	int mode;
28754359Sroberto
28854359Sroberto	pp = peer->procptr;
289290001Sglebius	up = pp->unitptr;
29054359Sroberto
29154359Sroberto	/*
29254359Sroberto	 * Initialize the unit variables
29354359Sroberto	 *
29454359Sroberto	 * STRANGE BEHAVIOUR WARNING: The fudge flags are not available
29554359Sroberto	 * at the time mx4200_start is called.  These are set later,
29654359Sroberto	 * and so the code must be prepared to handle changing flags.
29754359Sroberto	 */
29854359Sroberto	up->sloppyclockflag = pp->sloppyclockflag;
29954359Sroberto	if (pp->sloppyclockflag & CLK_FLAG2) {
30054359Sroberto		up->moving   = 1;	/* Receiver on mobile platform */
30154359Sroberto		msyslog(LOG_DEBUG, "mx4200_config: mobile platform");
30254359Sroberto	} else {
30354359Sroberto		up->moving   = 0;	/* Static Installation */
30454359Sroberto	}
30554359Sroberto	up->pollcnt     	= 2;
30654359Sroberto	up->polled      	= 0;
30754359Sroberto	up->known       	= 0;
30854359Sroberto	up->avg_lat     	= 0.0;
30954359Sroberto	up->avg_lon     	= 0.0;
31054359Sroberto	up->avg_alt     	= 0.0;
31154359Sroberto	up->central_meridian	= NOT_INITIALIZED;
31282498Sroberto	up->N_fixes    		= 0.0;
31354359Sroberto	up->last_leap   	= 0;	/* LEAP_NOWARNING */
31454359Sroberto	up->clamp_time  	= current_time + (AVGING_TIME * 60 * 60);
31554359Sroberto	up->log_time    	= current_time + SLEEPTIME;
31654359Sroberto
31782498Sroberto	if (time_pps_create(pp->io.fd, &up->pps_h) < 0) {
31882498Sroberto		perror("time_pps_create");
31982498Sroberto		msyslog(LOG_ERR,
32082498Sroberto			"mx4200_config: time_pps_create failed: %m");
32182498Sroberto		return (0);
32282498Sroberto	}
32382498Sroberto	if (time_pps_getcap(up->pps_h, &mode) < 0) {
32482498Sroberto		msyslog(LOG_ERR,
32582498Sroberto			"mx4200_config: time_pps_getcap failed: %m");
32682498Sroberto		return (0);
32782498Sroberto	}
32882498Sroberto
32982498Sroberto	if (time_pps_getparams(up->pps_h, &up->pps_p) < 0) {
33082498Sroberto		msyslog(LOG_ERR,
33182498Sroberto			"mx4200_config: time_pps_getparams failed: %m");
33282498Sroberto		return (0);
33382498Sroberto	}
33482498Sroberto
33582498Sroberto	/* nb. only turn things on, if someone else has turned something
33682498Sroberto	 *      on before we get here, leave it alone!
33782498Sroberto	 */
33882498Sroberto
33982498Sroberto	up->pps_p.mode = PPS_CAPTUREASSERT | PPS_TSFMT_TSPEC;
34082498Sroberto	up->pps_p.mode &= mode;		/* only set what is legal */
34182498Sroberto
34282498Sroberto	if (time_pps_setparams(up->pps_h, &up->pps_p) < 0) {
34382498Sroberto		perror("time_pps_setparams");
34482498Sroberto		msyslog(LOG_ERR,
34582498Sroberto			"mx4200_config: time_pps_setparams failed: %m");
34682498Sroberto		exit(1);
34782498Sroberto	}
34882498Sroberto
34982498Sroberto	if (time_pps_kcbind(up->pps_h, PPS_KC_HARDPPS, PPS_CAPTUREASSERT,
35082498Sroberto			PPS_TSFMT_TSPEC) < 0) {
35182498Sroberto		perror("time_pps_kcbind");
35282498Sroberto		msyslog(LOG_ERR,
35382498Sroberto			"mx4200_config: time_pps_kcbind failed: %m");
35482498Sroberto		exit(1);
35582498Sroberto	}
35682498Sroberto
35782498Sroberto
35854359Sroberto	/*
35954359Sroberto	 * "007" Control Port Configuration
36054359Sroberto	 * Zero the output list (do it twice to flush possible junk)
36154359Sroberto	 */
36254359Sroberto	mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg,
36354359Sroberto	    PMVXG_S_PORTCONF,
36454359Sroberto	    /* control port output block Label */
36554359Sroberto	    1);		/* clear current output control list (1=yes) */
36654359Sroberto	/* add/delete sentences from list */
36754359Sroberto	/* must be null */
36854359Sroberto	/* sentence output rate (sec) */
36954359Sroberto	/* precision for position output */
37054359Sroberto	/* nmea version for cga & gll output */
37154359Sroberto	/* pass-through control */
37254359Sroberto	mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg,
37354359Sroberto	    PMVXG_S_PORTCONF, 1);
37454359Sroberto
37554359Sroberto	/*
37654359Sroberto	 * Request software configuration so we can syslog the firmware version
37754359Sroberto	 */
37854359Sroberto	mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_SOFTCONF);
37954359Sroberto
38054359Sroberto	/*
38154359Sroberto	 * "001" Initialization/Mode Control, Part A
38254359Sroberto	 * Where ARE we?
38354359Sroberto	 */
38454359Sroberto	mx4200_send(peer, "%s,%03d,,,,,,,,,,", pmvxg,
38554359Sroberto	    PMVXG_S_INITMODEA);
38654359Sroberto	/* day of month */
38754359Sroberto	/* month of year */
38854359Sroberto	/* year */
38954359Sroberto	/* gmt */
39054359Sroberto	/* latitude   DDMM.MMMM */
39154359Sroberto	/* north/south */
39254359Sroberto	/* longitude DDDMM.MMMM */
39354359Sroberto	/* east/west */
39454359Sroberto	/* height */
39554359Sroberto	/* Altitude Reference 1=MSL */
39654359Sroberto
39754359Sroberto	/*
39854359Sroberto	 * "001" Initialization/Mode Control, Part B
39954359Sroberto	 * Start off in 2d/3d coast mode, holding altitude to last known
40054359Sroberto	 * value if only 3 satellites available.
40154359Sroberto	 */
40254359Sroberto	mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
40354359Sroberto	    pmvxg, PMVXG_S_INITMODEB,
40454359Sroberto	    3,		/* 2d/3d coast */
40554359Sroberto	    /* reserved */
40654359Sroberto	    0.1,	/* hor accel fact as per Steve (m/s**2) */
40754359Sroberto	    0.1,	/* ver accel fact as per Steve (m/s**2) */
40854359Sroberto	    10,		/* vdop */
40954359Sroberto	    10,		/* hdop limit as per Steve */
41054359Sroberto	    5,		/* elevation limit as per Steve (deg) */
41154359Sroberto	    'U',	/* time output mode (UTC) */
41254359Sroberto	    0);		/* local time offset from gmt (HHHMM) */
41354359Sroberto
41454359Sroberto	/*
41554359Sroberto	 * "023" Time Recovery Configuration
41654359Sroberto	 * Get UTC time from a stationary receiver.
41754359Sroberto	 * (Set field 1 'D' == dynamic if we are on a moving platform).
41854359Sroberto	 * (Set field 1 'S' == static  if we are not moving).
41954359Sroberto	 * (Set field 1 'K' == known position if we can initialize lat/lon/alt).
42054359Sroberto	 */
42154359Sroberto
42254359Sroberto	if (pp->sloppyclockflag & CLK_FLAG2)
42354359Sroberto		up->moving   = 1;	/* Receiver on mobile platform */
42454359Sroberto	else
42554359Sroberto		up->moving   = 0;	/* Static Installation */
42654359Sroberto
42754359Sroberto	up->pollcnt  = 2;
42854359Sroberto	if (up->moving) {
42954359Sroberto		/* dynamic: solve for pos, alt, time, while moving */
43054359Sroberto		tr_mode = 'D';
43154359Sroberto	} else {
43254359Sroberto		/* static: solve for pos, alt, time, while stationary */
43354359Sroberto		tr_mode = 'S';
43454359Sroberto	}
43554359Sroberto	mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg,
43654359Sroberto	    PMVXG_S_TRECOVCONF,
43754359Sroberto	    tr_mode,	/* time recovery mode (see above ) */
43854359Sroberto	    'U',	/* synchronize to UTC */
43954359Sroberto	    'A',	/* always output a time pulse */
44054359Sroberto	    500,	/* max time error in ns */
44154359Sroberto	    0,		/* user bias in ns */
44254359Sroberto	    1);		/* output "830" sentences to control port */
44382498Sroberto			/* Multi-satellite mode */
44454359Sroberto
44554359Sroberto	/*
44654359Sroberto	 * Output position information (to calculate fixed installation
44754359Sroberto	 * location) only if we are not moving
44854359Sroberto	 */
44954359Sroberto	if (up->moving) {
45054359Sroberto		add_mode = 2;	/* delete from list */
45154359Sroberto	} else {
45254359Sroberto		add_mode = 1;	/* add to list */
45354359Sroberto	}
45454359Sroberto
45554359Sroberto
45654359Sroberto	/*
45754359Sroberto	 * "007" Control Port Configuration
45854359Sroberto	 * Output "021" position, height, velocity reports
45954359Sroberto	 */
46054359Sroberto	mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg,
46154359Sroberto	    PMVXG_S_PORTCONF,
46254359Sroberto	    PMVXG_D_PHV, /* control port output block Label */
46354359Sroberto	    0,		/* clear current output control list (0=no) */
46454359Sroberto	    add_mode,	/* add/delete sentences from list (1=add, 2=del) */
46582498Sroberto	    		/* must be null */
46654359Sroberto	    INTERVAL);	/* sentence output rate (sec) */
46782498Sroberto			/* precision for position output */
46882498Sroberto			/* nmea version for cga & gll output */
46982498Sroberto			/* pass-through control */
47082498Sroberto
47182498Sroberto	return (1);
47254359Sroberto}
47354359Sroberto
47454359Sroberto/*
47554359Sroberto * mx4200_ref - Reconfigure unit as a reference station at a known position.
47654359Sroberto */
47754359Srobertostatic void
47854359Srobertomx4200_ref(
47954359Sroberto	struct peer *peer
48054359Sroberto	)
48154359Sroberto{
48254359Sroberto	register struct mx4200unit *up;
48354359Sroberto	struct refclockproc *pp;
48454359Sroberto	double minute, lat, lon, alt;
48554359Sroberto	char lats[16], lons[16];
48654359Sroberto	char nsc, ewc;
48754359Sroberto
48854359Sroberto	pp = peer->procptr;
489290001Sglebius	up = pp->unitptr;
49054359Sroberto
49154359Sroberto	/* Should never happen! */
49254359Sroberto	if (up->moving) return;
49354359Sroberto
49454359Sroberto	/*
49554359Sroberto	 * Set up to output status information in the near future
49654359Sroberto	 */
49754359Sroberto	up->log_time    = current_time + SLEEPTIME;
49854359Sroberto
49954359Sroberto	/*
50054359Sroberto	 * "007" Control Port Configuration
50154359Sroberto	 * Stop outputting "021" position, height, velocity reports
50254359Sroberto	 */
50354359Sroberto	mx4200_send(peer, "%s,%03d,%03d,%d,%d,,,,,", pmvxg,
50454359Sroberto	    PMVXG_S_PORTCONF,
50554359Sroberto	    PMVXG_D_PHV, /* control port output block Label */
50654359Sroberto	    0,		/* clear current output control list (0=no) */
50754359Sroberto	    2);		/* add/delete sentences from list (2=delete) */
50854359Sroberto			/* must be null */
50954359Sroberto	    		/* sentence output rate (sec) */
51054359Sroberto			/* precision for position output */
51154359Sroberto			/* nmea version for cga & gll output */
51254359Sroberto			/* pass-through control */
51354359Sroberto
51454359Sroberto	/*
51554359Sroberto	 * "001" Initialization/Mode Control, Part B
51654359Sroberto	 * Put receiver in fully-constrained 2d nav mode
51754359Sroberto	 */
51854359Sroberto	mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
51954359Sroberto	    pmvxg, PMVXG_S_INITMODEB,
52054359Sroberto	    2,		/* 2d nav */
52154359Sroberto	    /* reserved */
52254359Sroberto	    0.1,	/* hor accel fact as per Steve (m/s**2) */
52354359Sroberto	    0.1,	/* ver accel fact as per Steve (m/s**2) */
52454359Sroberto	    10,		/* vdop */
52554359Sroberto	    10,		/* hdop limit as per Steve */
52654359Sroberto	    5,		/* elevation limit as per Steve (deg) */
52754359Sroberto	    'U',	/* time output mode (UTC) */
52854359Sroberto	    0);		/* local time offset from gmt (HHHMM) */
52954359Sroberto
53054359Sroberto	/*
53154359Sroberto	 * "023" Time Recovery Configuration
53254359Sroberto	 * Get UTC time from a stationary receiver.  Solve for time only.
53354359Sroberto	 * This should improve the time resolution dramatically.
53454359Sroberto	 */
53554359Sroberto	mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg,
53654359Sroberto	    PMVXG_S_TRECOVCONF,
53754359Sroberto	    'K',	/* known position: solve for time only */
53854359Sroberto	    'U',	/* synchronize to UTC */
53954359Sroberto	    'A',	/* always output a time pulse */
54054359Sroberto	    500,	/* max time error in ns */
54154359Sroberto	    0,		/* user bias in ns */
54254359Sroberto	    1);		/* output "830" sentences to control port */
54354359Sroberto	/* Multi-satellite mode */
54454359Sroberto
54554359Sroberto	/*
54654359Sroberto	 * "000" Initialization/Mode Control - Part A
54754359Sroberto	 * Fix to our averaged position.
54854359Sroberto	 */
54954359Sroberto	if (up->central_meridian != NOT_INITIALIZED) {
55054359Sroberto		up->avg_lon += up->central_meridian;
55154359Sroberto		if (up->avg_lon < -180.0) up->avg_lon += 360.0;
55254359Sroberto		if (up->avg_lon >  180.0) up->avg_lon -= 360.0;
55354359Sroberto	}
55454359Sroberto
55554359Sroberto	if (up->avg_lat >= 0.0) {
55654359Sroberto		lat = up->avg_lat;
55754359Sroberto		nsc = 'N';
55854359Sroberto	} else {
55954359Sroberto		lat = up->avg_lat * (-1.0);
56054359Sroberto		nsc = 'S';
56154359Sroberto	}
56254359Sroberto	if (up->avg_lon >= 0.0) {
56354359Sroberto		lon = up->avg_lon;
56454359Sroberto		ewc = 'E';
56554359Sroberto	} else {
56654359Sroberto		lon = up->avg_lon * (-1.0);
56754359Sroberto		ewc = 'W';
56854359Sroberto	}
56954359Sroberto	alt = up->avg_alt;
57054359Sroberto	minute = (lat - (double)(int)lat) * 60.0;
571290001Sglebius	snprintf(lats, sizeof(lats), "%02d%02.4f", (int)lat, minute);
57254359Sroberto	minute = (lon - (double)(int)lon) * 60.0;
573290001Sglebius	snprintf(lons, sizeof(lons), "%03d%02.4f", (int)lon, minute);
57454359Sroberto
57556746Sroberto	mx4200_send(peer, "%s,%03d,,,,,%s,%c,%s,%c,%.2f,%d", pmvxg,
57654359Sroberto	    PMVXG_S_INITMODEA,
57754359Sroberto	    /* day of month */
57854359Sroberto	    /* month of year */
57954359Sroberto	    /* year */
58054359Sroberto	    /* gmt */
58154359Sroberto	    lats,	/* latitude   DDMM.MMMM */
58254359Sroberto	    nsc,	/* north/south */
58354359Sroberto	    lons,	/* longitude DDDMM.MMMM */
58454359Sroberto	    ewc,	/* east/west */
58556746Sroberto	    alt,	/* Altitude */
58682498Sroberto	    1);		/* Altitude Reference (0=WGS84 ellipsoid, 1=MSL geoid)*/
58754359Sroberto
58854359Sroberto	msyslog(LOG_DEBUG,
58954359Sroberto	    "mx4200: reconfig to fixed location: %s %c, %s %c, %.2f m",
59054359Sroberto		lats, nsc, lons, ewc, alt );
59154359Sroberto
59254359Sroberto}
59354359Sroberto
59454359Sroberto/*
59554359Sroberto * mx4200_poll - mx4200 watchdog routine
59654359Sroberto */
59754359Srobertostatic void
59854359Srobertomx4200_poll(
59954359Sroberto	int unit,
60054359Sroberto	struct peer *peer
60154359Sroberto	)
60254359Sroberto{
60354359Sroberto	register struct mx4200unit *up;
60454359Sroberto	struct refclockproc *pp;
60554359Sroberto
60654359Sroberto	pp = peer->procptr;
607290001Sglebius	up = pp->unitptr;
60854359Sroberto
60954359Sroberto	/*
61054359Sroberto	 * You don't need to poll this clock.  It puts out timecodes
61154359Sroberto	 * once per second.  If asked for a timestamp, take note.
61254359Sroberto	 * The next time a timecode comes in, it will be fed back.
61354359Sroberto	 */
61454359Sroberto
61554359Sroberto	/*
61654359Sroberto	 * If we haven't had a response in a while, reset the receiver.
61754359Sroberto	 */
61854359Sroberto	if (up->pollcnt > 0) {
61954359Sroberto		up->pollcnt--;
62054359Sroberto	} else {
62154359Sroberto		refclock_report(peer, CEVNT_TIMEOUT);
62254359Sroberto
62354359Sroberto		/*
62454359Sroberto		 * Request a "000" status message which should trigger a
62554359Sroberto		 * reconfig
62654359Sroberto		 */
62754359Sroberto		mx4200_send(peer, "%s,%03d",
62854359Sroberto		    "CDGPQ",		/* query from CDU to GPS */
62954359Sroberto		    PMVXG_D_STATUS);	/* label of desired sentence */
63054359Sroberto	}
63154359Sroberto
63254359Sroberto	/*
63354359Sroberto	 * polled every 64 seconds. Ask mx4200_receive to hand in
63454359Sroberto	 * a timestamp.
63554359Sroberto	 */
63654359Sroberto	up->polled = 1;
63754359Sroberto	pp->polls++;
63854359Sroberto
63954359Sroberto	/*
64054359Sroberto	 * Output receiver status information.
64154359Sroberto	 */
64254359Sroberto	if ((up->log_time > 0) && (current_time > up->log_time)) {
64354359Sroberto		up->log_time = 0;
64454359Sroberto		/*
64554359Sroberto		 * Output the following messages once, for debugging.
64654359Sroberto		 *    "004" Mode Data
64754359Sroberto		 *    "523" Time Recovery Parameters
64854359Sroberto		 */
64954359Sroberto		mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_MODEDATA);
65054359Sroberto		mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_TRECOVUSEAGE);
65154359Sroberto	}
65254359Sroberto}
65354359Sroberto
65454359Srobertostatic char char2hex[] = "0123456789ABCDEF";
65554359Sroberto
65654359Sroberto/*
65754359Sroberto * mx4200_receive - receive gps data
65854359Sroberto */
65954359Srobertostatic void
66054359Srobertomx4200_receive(
66154359Sroberto	struct recvbuf *rbufp
66254359Sroberto	)
66354359Sroberto{
66454359Sroberto	register struct mx4200unit *up;
66554359Sroberto	struct refclockproc *pp;
66654359Sroberto	struct peer *peer;
66754359Sroberto	char *cp;
66854359Sroberto	int sentence_type;
66954359Sroberto	u_char ck;
67054359Sroberto
67154359Sroberto	/*
67254359Sroberto	 * Initialize pointers and read the timecode and timestamp.
67354359Sroberto	 */
674290001Sglebius	peer = rbufp->recv_peer;
67554359Sroberto	pp = peer->procptr;
676290001Sglebius	up = pp->unitptr;
67754359Sroberto
67854359Sroberto	/*
67954359Sroberto	 * If operating mode has been changed, then reinitialize the receiver
68054359Sroberto	 * before doing anything else.
68154359Sroberto	 */
68254359Sroberto	if ((pp->sloppyclockflag & CLK_FLAG2) !=
68354359Sroberto	    (up->sloppyclockflag & CLK_FLAG2)) {
68454359Sroberto		up->sloppyclockflag = pp->sloppyclockflag;
68554359Sroberto		mx4200_debug(peer,
68654359Sroberto		    "mx4200_receive: mode switch: reset receiver\n");
68754359Sroberto		mx4200_config(peer);
68854359Sroberto		return;
68954359Sroberto	}
69054359Sroberto	up->sloppyclockflag = pp->sloppyclockflag;
69154359Sroberto
69254359Sroberto	/*
69354359Sroberto	 * Read clock output.  Automatically handles STREAMS, CLKLDISC.
69454359Sroberto	 */
69554359Sroberto	pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec);
69654359Sroberto
69754359Sroberto	/*
69854359Sroberto	 * There is a case where <cr><lf> generates 2 timestamps.
69954359Sroberto	 */
70054359Sroberto	if (pp->lencode == 0)
70154359Sroberto		return;
70254359Sroberto
70354359Sroberto	up->pollcnt = 2;
70454359Sroberto	pp->a_lastcode[pp->lencode] = '\0';
70554359Sroberto	record_clock_stats(&peer->srcadr, pp->a_lastcode);
70654359Sroberto	mx4200_debug(peer, "mx4200_receive: %d %s\n",
70754359Sroberto		     pp->lencode, pp->a_lastcode);
70854359Sroberto
70954359Sroberto	/*
71054359Sroberto	 * The structure of the control port sentences is based on the
71154359Sroberto	 * NMEA-0183 Standard for interfacing Marine Electronics
71254359Sroberto	 * Navigation Devices (Version 1.5)
71354359Sroberto	 *
71454359Sroberto	 *	$PMVXG,XXX, ....................*CK<cr><lf>
71554359Sroberto	 *
71654359Sroberto	 *		$	Sentence Start Identifier (reserved char)
71754359Sroberto	 *			   (Start-of-Sentence Identifier)
71854359Sroberto	 *		P	Special ID (Proprietary)
71954359Sroberto	 *		MVX	Originator ID (Magnavox)
72054359Sroberto	 *		G	Interface ID (GPS)
72154359Sroberto	 *		,	Field Delimiters (reserved char)
72254359Sroberto	 *		XXX	Sentence Type
72354359Sroberto	 *		......	Data
72454359Sroberto	 *		*	Checksum Field Delimiter (reserved char)
72554359Sroberto	 *		CK	Checksum
72654359Sroberto	 *		<cr><lf> Carriage-Return/Line Feed (reserved chars)
72754359Sroberto	 *			   (End-of-Sentence Identifier)
72854359Sroberto	 *
72954359Sroberto	 * Reject if any important landmarks are missing.
73054359Sroberto	 */
73154359Sroberto	cp = pp->a_lastcode + pp->lencode - 3;
73254359Sroberto	if (cp < pp->a_lastcode || *pp->a_lastcode != '$' || cp[0] != '*' ) {
73354359Sroberto		mx4200_debug(peer, "mx4200_receive: bad format\n");
73454359Sroberto		refclock_report(peer, CEVNT_BADREPLY);
73554359Sroberto		return;
73654359Sroberto	}
73754359Sroberto
73854359Sroberto	/*
73954359Sroberto	 * Check and discard the checksum
74054359Sroberto	 */
74154359Sroberto	ck = mx4200_cksum(&pp->a_lastcode[1], pp->lencode - 4);
74254359Sroberto	if (char2hex[ck >> 4] != cp[1] || char2hex[ck & 0xf] != cp[2]) {
74354359Sroberto		mx4200_debug(peer, "mx4200_receive: bad checksum\n");
74454359Sroberto		refclock_report(peer, CEVNT_BADREPLY);
74554359Sroberto		return;
74654359Sroberto	}
74754359Sroberto	*cp = '\0';
74854359Sroberto
74954359Sroberto	/*
75054359Sroberto	 * Get the sentence type.
75154359Sroberto	 */
75254359Sroberto	sentence_type = 0;
75354359Sroberto	if ((cp = strchr(pp->a_lastcode, ',')) == NULL) {
75454359Sroberto		mx4200_debug(peer, "mx4200_receive: no sentence\n");
75554359Sroberto		refclock_report(peer, CEVNT_BADREPLY);
75654359Sroberto		return;
75754359Sroberto	}
75854359Sroberto	cp++;
75954359Sroberto	sentence_type = strtol(cp, &cp, 10);
76054359Sroberto
76154359Sroberto	/*
76282498Sroberto	 * Process the sentence according to its type.
76382498Sroberto	 */
76482498Sroberto	switch (sentence_type) {
76582498Sroberto
76682498Sroberto	/*
76754359Sroberto	 * "000" Status message
76854359Sroberto	 */
76982498Sroberto	case PMVXG_D_STATUS:
77054359Sroberto		/*
77154359Sroberto		 * XXX
77254359Sroberto		 * Since we configure the receiver to not give us status
77354359Sroberto		 * messages and since the receiver outputs status messages by
77454359Sroberto		 * default after being reset to factory defaults when sent the
77554359Sroberto		 * "$PMVXG,018,C\r\n" message, any status message we get
77654359Sroberto		 * indicates the reciever needs to be initialized; thus, it is
77754359Sroberto		 * not necessary to decode the status message.
77854359Sroberto		 */
77954359Sroberto		if ((cp = mx4200_parse_s(peer)) != NULL) {
78054359Sroberto			mx4200_debug(peer,
78154359Sroberto				     "mx4200_receive: status: %s\n", cp);
78254359Sroberto		}
78354359Sroberto		mx4200_debug(peer, "mx4200_receive: reset receiver\n");
78454359Sroberto		mx4200_config(peer);
78582498Sroberto		break;
78654359Sroberto
78754359Sroberto	/*
78854359Sroberto	 * "021" Position, Height, Velocity message,
78954359Sroberto	 *  if we are still averaging our position
79054359Sroberto	 */
79182498Sroberto	case PMVXG_D_PHV:
79282498Sroberto		if (!up->known) {
79382498Sroberto			/*
79482498Sroberto			 * Parse the message, calculating our averaged position.
79582498Sroberto			 */
79682498Sroberto			if ((cp = mx4200_parse_p(peer)) != NULL) {
79782498Sroberto				mx4200_debug(peer, "mx4200_receive: pos: %s\n", cp);
79882498Sroberto				return;
79982498Sroberto			}
80082498Sroberto			mx4200_debug(peer,
80182498Sroberto			    "mx4200_receive: position avg %f %.9f %.9f %.4f\n",
80282498Sroberto			    up->N_fixes, up->avg_lat, up->avg_lon, up->avg_alt);
80382498Sroberto			/*
80482498Sroberto			 * Reinitialize as a reference station
80582498Sroberto			 * if position is well known.
80682498Sroberto			 */
80782498Sroberto			if (current_time > up->clamp_time) {
80882498Sroberto				up->known++;
80982498Sroberto				mx4200_debug(peer, "mx4200_receive: reconfiguring!\n");
81082498Sroberto				mx4200_ref(peer);
81182498Sroberto			}
81254359Sroberto		}
81382498Sroberto		break;
81454359Sroberto
81554359Sroberto	/*
81654359Sroberto	 * Print to the syslog:
81754359Sroberto	 * "004" Mode Data
81854359Sroberto	 * "030" Software Configuration
81954359Sroberto	 * "523" Time Recovery Parameters Currently in Use
82054359Sroberto	 */
82182498Sroberto	case PMVXG_D_MODEDATA:
82282498Sroberto	case PMVXG_D_SOFTCONF:
82382498Sroberto	case PMVXG_D_TRECOVUSEAGE:
82482498Sroberto
82554359Sroberto		if ((cp = mx4200_parse_s(peer)) != NULL) {
82654359Sroberto			mx4200_debug(peer,
82754359Sroberto				     "mx4200_receive: multi-record: %s\n", cp);
82854359Sroberto		}
82982498Sroberto		break;
83054359Sroberto
83154359Sroberto	/*
83254359Sroberto	 * "830" Time Recovery Results message
83354359Sroberto	 */
83482498Sroberto	case PMVXG_D_TRECOVOUT:
83554359Sroberto
83654359Sroberto		/*
83754359Sroberto		 * Capture the last PPS signal.
83854359Sroberto		 * Precision timestamp is returned in pp->lastrec
83954359Sroberto		 */
840290001Sglebius		if (0 != mx4200_pps(peer)) {
84154359Sroberto			mx4200_debug(peer, "mx4200_receive: pps failure\n");
84254359Sroberto			refclock_report(peer, CEVNT_FAULT);
84354359Sroberto			return;
84454359Sroberto		}
84554359Sroberto
84654359Sroberto
84754359Sroberto		/*
84854359Sroberto		 * Parse the time recovery message, and keep the info
84954359Sroberto		 * to print the pretty billboards.
85054359Sroberto		 */
85154359Sroberto		if ((cp = mx4200_parse_t(peer)) != NULL) {
85254359Sroberto			mx4200_debug(peer, "mx4200_receive: time: %s\n", cp);
85354359Sroberto			refclock_report(peer, CEVNT_BADREPLY);
85454359Sroberto			return;
85554359Sroberto		}
85654359Sroberto
85754359Sroberto		/*
85854359Sroberto		 * Add the new sample to a median filter.
85954359Sroberto		 */
86054359Sroberto		if (!refclock_process(pp)) {
86154359Sroberto			mx4200_debug(peer,"mx4200_receive: offset: %.6f\n",
86254359Sroberto			    pp->offset);
86354359Sroberto			refclock_report(peer, CEVNT_BADTIME);
86454359Sroberto			return;
86554359Sroberto		}
86654359Sroberto
86754359Sroberto		/*
86854359Sroberto		 * The clock will blurt a timecode every second but we only
86954359Sroberto		 * want one when polled.  If we havn't been polled, bail out.
87054359Sroberto		 */
87154359Sroberto		if (!up->polled)
87254359Sroberto			return;
87354359Sroberto
87454359Sroberto		/*
87554359Sroberto		 * Return offset and dispersion to control module.  We use
87654359Sroberto		 * lastrec as both the reference time and receive time in
87754359Sroberto		 * order to avoid being cute, like setting the reference time
87854359Sroberto		 * later than the receive time, which may cause a paranoid
87954359Sroberto		 * protocol module to chuck out the data.
88054359Sroberto		 */
88154359Sroberto		mx4200_debug(peer, "mx4200_receive: process time: ");
88254359Sroberto		mx4200_debug(peer, "%4d-%03d %02d:%02d:%02d at %s, %.6f\n",
88354359Sroberto		    pp->year, pp->day, pp->hour, pp->minute, pp->second,
88454359Sroberto		    prettydate(&pp->lastrec), pp->offset);
885132451Sroberto		pp->lastref = pp->lastrec;
88654359Sroberto		refclock_receive(peer);
88754359Sroberto
88854359Sroberto		/*
88954359Sroberto		 * We have succeeded in answering the poll.
89054359Sroberto		 * Turn off the flag and return
89154359Sroberto		 */
89254359Sroberto		up->polled = 0;
89382498Sroberto		break;
89454359Sroberto
89554359Sroberto	/*
89654359Sroberto	 * Ignore all other sentence types
89754359Sroberto	 */
89882498Sroberto	default:
89982498Sroberto		break;
90082498Sroberto
90182498Sroberto	} /* switch (sentence_type) */
90282498Sroberto
90354359Sroberto	return;
90454359Sroberto}
90554359Sroberto
90654359Sroberto
90754359Sroberto/*
90854359Sroberto * Parse a mx4200 time recovery message. Returns a string if error.
90954359Sroberto *
91054359Sroberto * A typical message looks like this.  Checksum has already been stripped.
91154359Sroberto *
91254359Sroberto *    $PMVXG,830,T,YYYY,MM,DD,HH:MM:SS,U,S,FFFFFF,PPPPP,BBBBBB,LL
91354359Sroberto *
91454359Sroberto *	Field	Field Contents
91554359Sroberto *	-----	--------------
91654359Sroberto *		Block Label: $PMVXG
91754359Sroberto *		Sentence Type: 830=Time Recovery Results
91854359Sroberto *			This sentence is output approximately 1 second
91954359Sroberto *			preceding the 1PPS output.  It indicates the
92054359Sroberto *			exact time of the next pulse, whether or not the
92154359Sroberto *			time mark will be valid (based on operator-specified
92254359Sroberto *			error tolerance), the time to which the pulse is
92354359Sroberto *			synchronized, the receiver operating mode,
92454359Sroberto *			and the time error of the *last* 1PPS output.
92554359Sroberto *	1  char Time Mark Valid: T=Valid, F=Not Valid
92654359Sroberto *	2  int  Year: 1993-
92754359Sroberto *	3  int  Month of Year: 1-12
92854359Sroberto *	4  int  Day of Month: 1-31
92954359Sroberto *	5  int  Time of Day: HH:MM:SS
93054359Sroberto *	6  char Time Synchronization: U=UTC, G=GPS
93154359Sroberto *	7  char Time Recovery Mode: D=Dynamic, S=Static,
93254359Sroberto *			K=Known Position, N=No Time Recovery
93354359Sroberto *	8  int  Oscillator Offset: The filter's estimate of the oscillator
93454359Sroberto *			frequency error, in parts per billion (ppb).
93554359Sroberto *	9  int  Time Mark Error: The computed error of the *last* pulse
93654359Sroberto *			output, in nanoseconds.
93754359Sroberto *	10 int  User Time Bias: Operator specified bias, in nanoseconds
93854359Sroberto *	11 int  Leap Second Flag: Indicates that a leap second will
93954359Sroberto *			occur.  This value is usually zero, except during
940132451Sroberto *			the week prior to the leap second occurrence, when
94154359Sroberto *			this value will be set to +1 or -1.  A value of
94254359Sroberto *			+1 indicates that GPS time will be 1 second
94354359Sroberto *			further ahead of UTC time.
94454359Sroberto *
94554359Sroberto */
94654359Srobertostatic char *
94754359Srobertomx4200_parse_t(
94854359Sroberto	struct peer *peer
94954359Sroberto	)
95054359Sroberto{
95154359Sroberto	struct refclockproc *pp;
95254359Sroberto	struct mx4200unit *up;
95354359Sroberto	char   time_mark_valid, time_sync, op_mode;
95454359Sroberto	int    sentence_type, valid;
95582498Sroberto	int    year, day_of_year, month, day_of_month;
956290001Sglebius	int    hour, minute, second, leapsec_warn;
95754359Sroberto	int    oscillator_offset, time_mark_error, time_bias;
95854359Sroberto
95954359Sroberto	pp = peer->procptr;
960290001Sglebius	up = pp->unitptr;
96154359Sroberto
962290001Sglebius	leapsec_warn = 0;  /* Not all receivers output leap second warnings (!) */
96382498Sroberto	sscanf(pp->a_lastcode,
96482498Sroberto		"$PMVXG,%d,%c,%d,%d,%d,%d:%d:%d,%c,%c,%d,%d,%d,%d",
96554359Sroberto		&sentence_type, &time_mark_valid, &year, &month, &day_of_month,
96682498Sroberto		&hour, &minute, &second, &time_sync, &op_mode,
967290001Sglebius		&oscillator_offset, &time_mark_error, &time_bias, &leapsec_warn);
96854359Sroberto
96954359Sroberto	if (sentence_type != PMVXG_D_TRECOVOUT)
97054359Sroberto		return ("wrong rec-type");
97154359Sroberto
97254359Sroberto	switch (time_mark_valid) {
97354359Sroberto		case 'T':
97454359Sroberto			valid = 1;
97554359Sroberto			break;
97654359Sroberto		case 'F':
97754359Sroberto			valid = 0;
97854359Sroberto			break;
97954359Sroberto		default:
98054359Sroberto			return ("bad pulse-valid");
98154359Sroberto	}
98254359Sroberto
98354359Sroberto	switch (time_sync) {
98454359Sroberto		case 'G':
98554359Sroberto			return ("synchronized to GPS; should be UTC");
98654359Sroberto		case 'U':
98754359Sroberto			break; /* UTC -> ok */
98854359Sroberto		default:
98954359Sroberto			return ("not synchronized to UTC");
99054359Sroberto	}
99154359Sroberto
99254359Sroberto	/*
99354359Sroberto	 * Check for insane time (allow for possible leap seconds)
99454359Sroberto	 */
99554359Sroberto	if (second > 60 || minute > 59 || hour > 23 ||
99654359Sroberto	    second <  0 || minute <  0 || hour <  0) {
99754359Sroberto		mx4200_debug(peer,
99854359Sroberto		    "mx4200_parse_t: bad time %02d:%02d:%02d",
99954359Sroberto		    hour, minute, second);
1000290001Sglebius		if (leapsec_warn != 0)
1001290001Sglebius			mx4200_debug(peer, " (leap %+d\n)", leapsec_warn);
100254359Sroberto		mx4200_debug(peer, "\n");
100354359Sroberto		refclock_report(peer, CEVNT_BADTIME);
100454359Sroberto		return ("bad time");
100554359Sroberto	}
100654359Sroberto	if ( second == 60 ) {
100754359Sroberto		msyslog(LOG_DEBUG,
100854359Sroberto		    "mx4200: leap second! %02d:%02d:%02d",
100954359Sroberto		    hour, minute, second);
101054359Sroberto	}
101154359Sroberto
101254359Sroberto	/*
101354359Sroberto	 * Check for insane date
101454359Sroberto	 * (Certainly can't be any year before this code was last altered!)
101554359Sroberto	 */
101654359Sroberto	if (day_of_month > 31 || month > 12 ||
101782498Sroberto	    day_of_month <  1 || month <  1 || year < YEAR_LAST_MODIFIED) {
101854359Sroberto		mx4200_debug(peer,
101954359Sroberto		    "mx4200_parse_t: bad date (%4d-%02d-%02d)\n",
102054359Sroberto		    year, month, day_of_month);
102154359Sroberto		refclock_report(peer, CEVNT_BADDATE);
102254359Sroberto		return ("bad date");
102354359Sroberto	}
102454359Sroberto
102554359Sroberto	/*
102654359Sroberto	 * Silly Hack for MX4200:
102754359Sroberto	 * ASCII message is for *next* 1PPS signal, but we have the
102854359Sroberto	 * timestamp for the *last* 1PPS signal.  So we have to subtract
102954359Sroberto	 * a second.  Discard if we are on a month boundary to avoid
103054359Sroberto	 * possible leap seconds and leap days.
103154359Sroberto	 */
103254359Sroberto	second--;
103354359Sroberto	if (second < 0) {
103454359Sroberto		second = 59;
103554359Sroberto		minute--;
103654359Sroberto		if (minute < 0) {
103754359Sroberto			minute = 59;
103854359Sroberto			hour--;
103954359Sroberto			if (hour < 0) {
104054359Sroberto				hour = 23;
104154359Sroberto				day_of_month--;
104254359Sroberto				if (day_of_month < 1) {
104354359Sroberto					return ("sorry, month boundary");
104454359Sroberto				}
104554359Sroberto			}
104654359Sroberto		}
104754359Sroberto	}
104854359Sroberto
104954359Sroberto	/*
105054359Sroberto	 * Calculate Julian date
105154359Sroberto	 */
105254359Sroberto	if (!(day_of_year = mx4200_jday(year, month, day_of_month))) {
105354359Sroberto		mx4200_debug(peer,
105454359Sroberto		    "mx4200_parse_t: bad julian date %d (%4d-%02d-%02d)\n",
105554359Sroberto		    day_of_year, year, month, day_of_month);
105654359Sroberto		refclock_report(peer, CEVNT_BADDATE);
105754359Sroberto		return("invalid julian date");
105854359Sroberto	}
105954359Sroberto
106054359Sroberto	/*
106154359Sroberto	 * Setup leap second indicator
106254359Sroberto	 */
1063290001Sglebius	switch (leapsec_warn) {
106454359Sroberto		case 0:
106554359Sroberto			pp->leap = LEAP_NOWARNING;
106654359Sroberto			break;
106754359Sroberto		case 1:
106854359Sroberto			pp->leap = LEAP_ADDSECOND;
106954359Sroberto			break;
107054359Sroberto		case -1:
107154359Sroberto			pp->leap = LEAP_DELSECOND;
107254359Sroberto			break;
107354359Sroberto		default:
107454359Sroberto			pp->leap = LEAP_NOTINSYNC;
107554359Sroberto	}
107654359Sroberto
107754359Sroberto	/*
107854359Sroberto	 * Any change to the leap second warning status?
107954359Sroberto	 */
1080290001Sglebius	if (leapsec_warn != up->last_leap ) {
108154359Sroberto		msyslog(LOG_DEBUG,
108254359Sroberto		    "mx4200: leap second warning: %d to %d (%d)",
1083290001Sglebius		    up->last_leap, leapsec_warn, pp->leap);
108454359Sroberto	}
1085290001Sglebius	up->last_leap = leapsec_warn;
108654359Sroberto
108754359Sroberto	/*
108854359Sroberto	 * Copy time data for billboard monitoring.
108954359Sroberto	 */
109054359Sroberto
109154359Sroberto	pp->year   = year;
109254359Sroberto	pp->day    = day_of_year;
109354359Sroberto	pp->hour   = hour;
109454359Sroberto	pp->minute = minute;
109554359Sroberto	pp->second = second;
109654359Sroberto
109754359Sroberto	/*
109854359Sroberto	 * Toss if sentence is marked invalid
109954359Sroberto	 */
110054359Sroberto	if (!valid || pp->leap == LEAP_NOTINSYNC) {
110154359Sroberto		mx4200_debug(peer, "mx4200_parse_t: time mark not valid\n");
110254359Sroberto		refclock_report(peer, CEVNT_BADTIME);
110354359Sroberto		return ("pulse invalid");
110454359Sroberto	}
110554359Sroberto
110654359Sroberto	return (NULL);
110754359Sroberto}
110854359Sroberto
110954359Sroberto/*
111054359Sroberto * Calculate the checksum
111154359Sroberto */
111254359Srobertostatic u_char
111354359Srobertomx4200_cksum(
111454359Sroberto	register char *cp,
111554359Sroberto	register int n
111654359Sroberto	)
111754359Sroberto{
111854359Sroberto	register u_char ck;
111954359Sroberto
112054359Sroberto	for (ck = 0; n-- > 0; cp++)
112154359Sroberto		ck ^= *cp;
112254359Sroberto	return (ck);
112354359Sroberto}
112454359Sroberto
112554359Sroberto/*
112654359Sroberto * Tables to compute the day of year.  Viva la leap.
112754359Sroberto */
112854359Srobertostatic int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
112954359Srobertostatic int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
113054359Sroberto
113154359Sroberto/*
113254359Sroberto * Calculate the the Julian Day
113354359Sroberto */
113454359Srobertostatic int
113554359Srobertomx4200_jday(
113654359Sroberto	int year,
113754359Sroberto	int month,
113854359Sroberto	int day_of_month
113954359Sroberto	)
114054359Sroberto{
114154359Sroberto	register int day, i;
114254359Sroberto	int leap_year;
114354359Sroberto
114454359Sroberto	/*
114554359Sroberto	 * Is this a leap year ?
114654359Sroberto	 */
114754359Sroberto	if (year % 4) {
114854359Sroberto		leap_year = 0; /* FALSE */
114954359Sroberto	} else {
115054359Sroberto		if (year % 100) {
115154359Sroberto			leap_year = 1; /* TRUE */
115254359Sroberto		} else {
115354359Sroberto			if (year % 400) {
115454359Sroberto				leap_year = 0; /* FALSE */
115554359Sroberto			} else {
115654359Sroberto				leap_year = 1; /* TRUE */
115754359Sroberto			}
115854359Sroberto		}
115954359Sroberto	}
116054359Sroberto
116154359Sroberto	/*
116254359Sroberto	 * Calculate the Julian Date
116354359Sroberto	 */
116454359Sroberto	day = day_of_month;
116554359Sroberto
116654359Sroberto	if (leap_year) {
116754359Sroberto		/* a leap year */
116854359Sroberto		if (day > day2tab[month - 1]) {
116954359Sroberto			return (0);
117054359Sroberto		}
117154359Sroberto		for (i = 0; i < month - 1; i++)
117254359Sroberto		    day += day2tab[i];
117354359Sroberto	} else {
117454359Sroberto		/* not a leap year */
117554359Sroberto		if (day > day1tab[month - 1]) {
117654359Sroberto			return (0);
117754359Sroberto		}
117854359Sroberto		for (i = 0; i < month - 1; i++)
117954359Sroberto		    day += day1tab[i];
118054359Sroberto	}
118154359Sroberto	return (day);
118254359Sroberto}
118354359Sroberto
118454359Sroberto/*
118554359Sroberto * Parse a mx4200 position/height/velocity sentence.
118654359Sroberto *
118754359Sroberto * A typical message looks like this.  Checksum has already been stripped.
118854359Sroberto *
118954359Sroberto * $PMVXG,021,SSSSSS.SS,DDMM.MMMM,N,DDDMM.MMMM,E,HHHHH.H,GGGG.G,EEEE.E,WWWW.W,MM
119054359Sroberto *
119154359Sroberto *	Field	Field Contents
119254359Sroberto *	-----	--------------
119354359Sroberto *		Block Label: $PMVXG
119454359Sroberto *		Sentence Type: 021=Position, Height Velocity Data
119554359Sroberto *			This sentence gives the receiver position, height,
119654359Sroberto *			navigation mode, and velocity north/east.
119754359Sroberto *			*This sentence is intended for post-analysis
119854359Sroberto *			applications.*
119954359Sroberto *	1 float UTC measurement time (seconds into week)
120054359Sroberto *	2 float WGS-84 Lattitude (degrees, minutes)
120154359Sroberto *	3  char N=North, S=South
120254359Sroberto *	4 float WGS-84 Longitude (degrees, minutes)
120354359Sroberto *	5  char E=East, W=West
120454359Sroberto *	6 float Altitude (meters above mean sea level)
120554359Sroberto *	7 float Geoidal height (meters)
120654359Sroberto *	8 float East velocity (m/sec)
120754359Sroberto *	9 float West Velocity (m/sec)
120854359Sroberto *	10  int Navigation Mode
120954359Sroberto *		    Mode if navigating:
121054359Sroberto *			1 = Position from remote device
121154359Sroberto *			2 = 2-D position
121254359Sroberto *			3 = 3-D position
121354359Sroberto *			4 = 2-D differential position
121454359Sroberto *			5 = 3-D differential position
121554359Sroberto *			6 = Static
121654359Sroberto *			8 = Position known -- reference station
121754359Sroberto *			9 = Position known -- Navigator
121854359Sroberto *		    Mode if not navigating:
121954359Sroberto *			51 = Too few satellites
122054359Sroberto *			52 = DOPs too large
122154359Sroberto *			53 = Position STD too large
122254359Sroberto *			54 = Velocity STD too large
122354359Sroberto *			55 = Too many iterations for velocity
122454359Sroberto *			56 = Too many iterations for position
122554359Sroberto *			57 = 3 sat startup failed
122654359Sroberto *			58 = Command abort
122754359Sroberto */
122854359Srobertostatic char *
122954359Srobertomx4200_parse_p(
123054359Sroberto	struct peer *peer
123154359Sroberto	)
123254359Sroberto{
123354359Sroberto	struct refclockproc *pp;
123454359Sroberto	struct mx4200unit *up;
123554359Sroberto	int sentence_type, mode;
123682498Sroberto	double mtime, lat, lon, alt, geoid, vele, veln;
123754359Sroberto	char   north_south, east_west;
123854359Sroberto
123954359Sroberto	pp = peer->procptr;
1240290001Sglebius	up = pp->unitptr;
124154359Sroberto
124254359Sroberto	/* Should never happen! */
124354359Sroberto	if (up->moving) return ("mobile platform - no pos!");
124454359Sroberto
124582498Sroberto	sscanf ( pp->a_lastcode,
124682498Sroberto		"$PMVXG,%d,%lf,%lf,%c,%lf,%c,%lf,%lf,%lf,%lf,%d",
124782498Sroberto		&sentence_type, &mtime, &lat, &north_south, &lon, &east_west,
124882498Sroberto		&alt, &geoid, &vele, &veln, &mode);
124954359Sroberto
125054359Sroberto	/* Sentence type */
125154359Sroberto	if (sentence_type != PMVXG_D_PHV)
125254359Sroberto		return ("wrong rec-type");
125354359Sroberto
125454359Sroberto	/*
125554359Sroberto	 * return if not navigating
125654359Sroberto	 */
125754359Sroberto	if (mode > 10)
125854359Sroberto		return ("not navigating");
125954359Sroberto	if (mode != 3 && mode != 5)
126054359Sroberto		return ("not navigating in 3D");
126154359Sroberto
126254359Sroberto	/* Latitude (always +ve) and convert DDMM.MMMM to decimal */
126354359Sroberto	if (lat <  0.0) return ("negative latitude");
126454359Sroberto	if (lat > 9000.0) lat = 9000.0;
126554359Sroberto	lat *= 0.01;
126654359Sroberto	lat = ((int)lat) + (((lat - (int)lat)) * 1.6666666666666666);
126754359Sroberto
126854359Sroberto	/* North/South */
126954359Sroberto	switch (north_south) {
127054359Sroberto		case 'N':
127154359Sroberto			break;
127254359Sroberto		case 'S':
127354359Sroberto			lat *= -1.0;
127454359Sroberto			break;
127554359Sroberto		default:
127654359Sroberto			return ("invalid north/south indicator");
127754359Sroberto	}
127854359Sroberto
127954359Sroberto	/* Longitude (always +ve) and convert DDDMM.MMMM to decimal */
128054359Sroberto	if (lon <   0.0) return ("negative longitude");
128154359Sroberto	if (lon > 180.0) lon = 180.0;
128254359Sroberto	lon *= 0.01;
128354359Sroberto	lon = ((int)lon) + (((lon - (int)lon)) * 1.6666666666666666);
128454359Sroberto
128554359Sroberto	/* East/West */
128654359Sroberto	switch (east_west) {
128754359Sroberto		case 'E':
128854359Sroberto			break;
128954359Sroberto		case 'W':
129054359Sroberto			lon *= -1.0;
129154359Sroberto			break;
129254359Sroberto		default:
129354359Sroberto			return ("invalid east/west indicator");
129454359Sroberto	}
129554359Sroberto
129654359Sroberto	/*
129754359Sroberto	 * Normalize longitude to near 0 degrees.
129854359Sroberto	 * Assume all data are clustered around first reading.
129954359Sroberto	 */
130054359Sroberto	if (up->central_meridian == NOT_INITIALIZED) {
130154359Sroberto		up->central_meridian = lon;
130254359Sroberto		mx4200_debug(peer,
130354359Sroberto		    "mx4200_receive: central meridian =  %.9f \n",
130454359Sroberto		    up->central_meridian);
130554359Sroberto	}
130654359Sroberto	lon -= up->central_meridian;
130754359Sroberto	if (lon < -180.0) lon += 360.0;
130854359Sroberto	if (lon >  180.0) lon -= 360.0;
130954359Sroberto
131054359Sroberto	/*
131182498Sroberto	 * Calculate running averages
131254359Sroberto	 */
131354359Sroberto
131482498Sroberto	up->avg_lon = (up->N_fixes * up->avg_lon) + lon;
131582498Sroberto	up->avg_lat = (up->N_fixes * up->avg_lat) + lat;
131682498Sroberto	up->avg_alt = (up->N_fixes * up->avg_alt) + alt;
131754359Sroberto
131882498Sroberto	up->N_fixes += 1.0;
131954359Sroberto
132082498Sroberto	up->avg_lon /= up->N_fixes;
132182498Sroberto	up->avg_lat /= up->N_fixes;
132282498Sroberto	up->avg_alt /= up->N_fixes;
132382498Sroberto
132454359Sroberto	mx4200_debug(peer,
132582498Sroberto	    "mx4200_receive: position rdg %.0f: %.9f %.9f %.4f (CM=%.9f)\n",
132682498Sroberto	    up->N_fixes, lat, lon, alt, up->central_meridian);
132754359Sroberto
132854359Sroberto	return (NULL);
132954359Sroberto}
133054359Sroberto
133154359Sroberto/*
133254359Sroberto * Parse a mx4200 Status sentence
133354359Sroberto * Parse a mx4200 Mode Data sentence
133454359Sroberto * Parse a mx4200 Software Configuration sentence
133554359Sroberto * Parse a mx4200 Time Recovery Parameters Currently in Use sentence
133654359Sroberto * (used only for logging raw strings)
133754359Sroberto *
133854359Sroberto * A typical message looks like this.  Checksum has already been stripped.
133954359Sroberto *
134054359Sroberto * $PMVXG,000,XXX,XX,X,HHMM,X
134154359Sroberto *
134254359Sroberto *	Field	Field Contents
134354359Sroberto *	-----	--------------
134454359Sroberto *		Block Label: $PMVXG
134554359Sroberto *		Sentence Type: 000=Status.
134654359Sroberto *			Returns status of the receiver to the controller.
134754359Sroberto *	1	Current Receiver Status:
134854359Sroberto *		ACQ = Satellite re-acquisition
134954359Sroberto *		ALT = Constellation selection
135054359Sroberto *		COR = Providing corrections (for reference stations only)
135154359Sroberto *		IAC = Initial acquisition
135254359Sroberto *		IDL = Idle, no satellites
135354359Sroberto *		NAV = Navigation
135454359Sroberto *		STS = Search the Sky (no almanac available)
135554359Sroberto *		TRK = Tracking
135654359Sroberto *	2	Number of satellites that should be visible
135754359Sroberto *	3	Number of satellites being tracked
135854359Sroberto *	4	Time since last navigation status if not currently navigating
135954359Sroberto *		(hours, minutes)
136054359Sroberto *	5	Initialization status:
136154359Sroberto *		0 = Waiting for initialization parameters
136254359Sroberto *		1 = Initialization completed
136354359Sroberto *
136454359Sroberto * A typical message looks like this.  Checksum has already been stripped.
136554359Sroberto *
136654359Sroberto * $PMVXG,004,C,R,D,H.HH,V.VV,TT,HHHH,VVVV,T
136754359Sroberto *
136854359Sroberto *	Field	Field Contents
136954359Sroberto *	-----	--------------
137054359Sroberto *		Block Label: $PMVXG
137154359Sroberto *		Sentence Type: 004=Software Configuration.
137254359Sroberto *			Defines the navigation mode and criteria for
137354359Sroberto *			acceptable navigation for the receiver.
137454359Sroberto *	1	Constrain Altitude Mode:
137554359Sroberto *		0 = Auto.  Constrain altitude (2-D solution) and use
137654359Sroberto *		    manual altitude input when 3 sats avalable.  Do
137754359Sroberto *		    not constrain altitude (3-D solution) when 4 sats
137854359Sroberto *		    available.
137954359Sroberto *		1 = Always constrain altitude (2-D solution).
138054359Sroberto *		2 = Never constrain altitude (3-D solution).
138154359Sroberto *		3 = Coast.  Constrain altitude (2-D solution) and use
138254359Sroberto *		    last GPS altitude calculation when 3 sats avalable.
138354359Sroberto *		    Do not constrain altitude (3-D solution) when 4 sats
138454359Sroberto *		    available.
138554359Sroberto *	2	Altitude Reference: (always 0 for MX4200)
138654359Sroberto *		0 = Ellipsoid
138754359Sroberto *		1 = Geoid (MSL)
138854359Sroberto *	3	Differential Navigation Control:
138954359Sroberto *		0 = Disabled
139054359Sroberto *		1 = Enabled
139154359Sroberto *	4	Horizontal Acceleration Constant (m/sec**2)
139254359Sroberto *	5	Vertical Acceleration Constant (m/sec**2) (0 for MX4200)
139354359Sroberto *	6	Tracking Elevation Limit (degrees)
139454359Sroberto *	7	HDOP Limit
139554359Sroberto *	8	VDOP Limit
139654359Sroberto *	9	Time Output Mode:
139754359Sroberto *		U = UTC
139854359Sroberto *		L = Local time
139954359Sroberto *	10	Local Time Offset (minutes) (absent on MX4200)
140054359Sroberto *
140154359Sroberto * A typical message looks like this.  Checksum has already been stripped.
140254359Sroberto *
140354359Sroberto * $PMVXG,030,NNNN,FFF
140454359Sroberto *
140554359Sroberto *	Field	Field Contents
140654359Sroberto *	-----	--------------
140754359Sroberto *		Block Label: $PMVXG
140854359Sroberto *		Sentence Type: 030=Software Configuration.
140954359Sroberto *			This sentence contains the navigation processor
141054359Sroberto *			and baseband firmware version numbers.
141154359Sroberto *	1	Nav Processor Version Number
141254359Sroberto *	2	Baseband Firmware Version Number
141354359Sroberto *
141454359Sroberto * A typical message looks like this.  Checksum has already been stripped.
141554359Sroberto *
141654359Sroberto * $PMVXG,523,M,S,M,EEEE,BBBBBB,C,R
141754359Sroberto *
141854359Sroberto *	Field	Field Contents
141954359Sroberto *	-----	--------------
142054359Sroberto *		Block Label: $PMVXG
142154359Sroberto *		Sentence Type: 523=Time Recovery Parameters Currently in Use.
142254359Sroberto *			This sentence contains the configuration of the
142354359Sroberto *			time recovery feature of the receiver.
142454359Sroberto *	1	Time Recovery Mode:
142554359Sroberto *		D = Dynamic; solve for position and time while moving
142654359Sroberto *		S = Static; solve for position and time while stationary
142754359Sroberto *		K = Known position input, solve for time only
142854359Sroberto *		N = No time recovery
142954359Sroberto *	2	Time Synchronization:
143054359Sroberto *		U = UTC time
143154359Sroberto *		G = GPS time
143254359Sroberto *	3	Time Mark Mode:
143354359Sroberto *		A = Always output a time pulse
143454359Sroberto *		V = Only output time pulse if time is valid (as determined
143554359Sroberto *		    by Maximum Time Error)
143654359Sroberto *	4	Maximum Time Error - the maximum error (in nanoseconds) for
143754359Sroberto *		which a time mark will be considered valid.
143854359Sroberto *	5	User Time Bias - external bias in nanoseconds
143954359Sroberto *	6	Time Message Control:
144054359Sroberto *		0 = Do not output the time recovery message
144154359Sroberto *		1 = Output the time recovery message (record 830) to
144254359Sroberto *		    Control port
144354359Sroberto *		2 = Output the time recovery message (record 830) to
144454359Sroberto *		    Equipment port
144554359Sroberto *	7	Reserved
144654359Sroberto *	8	Position Known PRN (absent on MX 4200)
144754359Sroberto *
144854359Sroberto */
144954359Srobertostatic char *
145054359Srobertomx4200_parse_s(
145154359Sroberto	struct peer *peer
145254359Sroberto	)
145354359Sroberto{
145454359Sroberto	struct refclockproc *pp;
145554359Sroberto	struct mx4200unit *up;
145654359Sroberto	int sentence_type;
145754359Sroberto
145854359Sroberto	pp = peer->procptr;
1459290001Sglebius	up = pp->unitptr;
146054359Sroberto
146154359Sroberto        sscanf ( pp->a_lastcode, "$PMVXG,%d", &sentence_type);
146254359Sroberto
146354359Sroberto	/* Sentence type */
146454359Sroberto	switch (sentence_type) {
146554359Sroberto
146654359Sroberto		case PMVXG_D_STATUS:
146754359Sroberto			msyslog(LOG_DEBUG,
146882498Sroberto			  "mx4200: status: %s", pp->a_lastcode);
146954359Sroberto			break;
147054359Sroberto		case PMVXG_D_MODEDATA:
147154359Sroberto			msyslog(LOG_DEBUG,
147282498Sroberto			  "mx4200: mode data: %s", pp->a_lastcode);
147354359Sroberto			break;
147454359Sroberto		case PMVXG_D_SOFTCONF:
147554359Sroberto			msyslog(LOG_DEBUG,
147682498Sroberto			  "mx4200: firmware configuration: %s", pp->a_lastcode);
147754359Sroberto			break;
147854359Sroberto		case PMVXG_D_TRECOVUSEAGE:
147954359Sroberto			msyslog(LOG_DEBUG,
148082498Sroberto			  "mx4200: time recovery parms: %s", pp->a_lastcode);
148154359Sroberto			break;
148254359Sroberto		default:
148354359Sroberto			return ("wrong rec-type");
148454359Sroberto	}
148554359Sroberto
148654359Sroberto	return (NULL);
148754359Sroberto}
148854359Sroberto
148954359Sroberto/*
149082498Sroberto * Process a PPS signal, placing a timestamp in pp->lastrec.
149154359Sroberto */
149254359Srobertostatic int
149354359Srobertomx4200_pps(
149454359Sroberto	struct peer *peer
149554359Sroberto	)
149654359Sroberto{
149754359Sroberto	int temp_serial;
149854359Sroberto	struct refclockproc *pp;
149954359Sroberto	struct mx4200unit *up;
150054359Sroberto
150182498Sroberto	struct timespec timeout;
150254359Sroberto
150354359Sroberto	pp = peer->procptr;
1504290001Sglebius	up = pp->unitptr;
150554359Sroberto
150654359Sroberto	/*
150754359Sroberto	 * Grab the timestamp of the PPS signal.
150854359Sroberto	 */
150982498Sroberto	temp_serial = up->pps_i.assert_sequence;
151082498Sroberto	timeout.tv_sec  = 0;
151182498Sroberto	timeout.tv_nsec = 0;
151282498Sroberto	if (time_pps_fetch(up->pps_h, PPS_TSFMT_TSPEC, &(up->pps_i),
151382498Sroberto			&timeout) < 0) {
151454359Sroberto		mx4200_debug(peer,
1515290001Sglebius		  "mx4200_pps: time_pps_fetch: serial=%lu, %m\n",
1516290001Sglebius		     (unsigned long)up->pps_i.assert_sequence);
151754359Sroberto		refclock_report(peer, CEVNT_FAULT);
151854359Sroberto		return(1);
151954359Sroberto	}
152082498Sroberto	if (temp_serial == up->pps_i.assert_sequence) {
152154359Sroberto		mx4200_debug(peer,
1522290001Sglebius		   "mx4200_pps: assert_sequence serial not incrementing: %lu\n",
1523182007Sroberto			(unsigned long)up->pps_i.assert_sequence);
152454359Sroberto		refclock_report(peer, CEVNT_FAULT);
152554359Sroberto		return(1);
152654359Sroberto	}
152754359Sroberto	/*
152854359Sroberto	 * Check pps serial number against last one
152954359Sroberto	 */
153082498Sroberto	if (up->lastserial + 1 != up->pps_i.assert_sequence &&
153182498Sroberto	    up->lastserial != 0) {
153282498Sroberto		if (up->pps_i.assert_sequence == up->lastserial) {
153354359Sroberto			mx4200_debug(peer, "mx4200_pps: no new pps event\n");
153482498Sroberto		} else {
1535290001Sglebius			mx4200_debug(peer, "mx4200_pps: missed %lu pps events\n",
1536182007Sroberto			    up->pps_i.assert_sequence - up->lastserial - 1UL);
153782498Sroberto		}
153854359Sroberto		refclock_report(peer, CEVNT_FAULT);
153954359Sroberto	}
154082498Sroberto	up->lastserial = up->pps_i.assert_sequence;
154154359Sroberto
154254359Sroberto	/*
154354359Sroberto	 * Return the timestamp in pp->lastrec
154454359Sroberto	 */
154554359Sroberto
154682498Sroberto	pp->lastrec.l_ui = up->pps_i.assert_timestamp.tv_sec +
154782498Sroberto			   (u_int32) JAN_1970;
154882498Sroberto	pp->lastrec.l_uf = ((double)(up->pps_i.assert_timestamp.tv_nsec) *
154982498Sroberto			   4.2949672960) + 0.5;
155082498Sroberto
155154359Sroberto	return(0);
155254359Sroberto}
155354359Sroberto
155454359Sroberto/*
155554359Sroberto * mx4200_debug - print debug messages
155654359Sroberto */
155754359Srobertostatic void
155854359Srobertomx4200_debug(struct peer *peer, char *fmt, ...)
155954359Sroberto{
1560182007Sroberto#ifdef DEBUG
156154359Sroberto	va_list ap;
156254359Sroberto	struct refclockproc *pp;
156354359Sroberto	struct mx4200unit *up;
156454359Sroberto
156554359Sroberto	if (debug) {
156654359Sroberto		va_start(ap, fmt);
156754359Sroberto
156854359Sroberto		pp = peer->procptr;
1569290001Sglebius		up = pp->unitptr;
157054359Sroberto
157154359Sroberto		/*
157254359Sroberto		 * Print debug message to stdout
157354359Sroberto		 * In the future, we may want to get get more creative...
157454359Sroberto		 */
1575290001Sglebius		mvprintf(fmt, ap);
157654359Sroberto
157754359Sroberto		va_end(ap);
157854359Sroberto	}
1579182007Sroberto#endif
158054359Sroberto}
158154359Sroberto
158254359Sroberto/*
158354359Sroberto * Send a character string to the receiver.  Checksum is appended here.
158454359Sroberto */
158556746Sroberto#if defined(__STDC__)
158654359Srobertostatic void
158754359Srobertomx4200_send(struct peer *peer, char *fmt, ...)
158854359Sroberto#else
158956746Srobertostatic void
159056746Srobertomx4200_send(peer, fmt, va_alist)
159154359Sroberto     struct peer *peer;
159254359Sroberto     char *fmt;
159354359Sroberto     va_dcl
159454359Sroberto#endif /* __STDC__ */
159554359Sroberto{
159654359Sroberto	struct refclockproc *pp;
159754359Sroberto	struct mx4200unit *up;
159854359Sroberto
159954359Sroberto	register char *cp;
160054359Sroberto	register int n, m;
160154359Sroberto	va_list ap;
160254359Sroberto	char buf[1024];
160354359Sroberto	u_char ck;
160454359Sroberto
160556746Sroberto#if defined(__STDC__)
160654359Sroberto	va_start(ap, fmt);
160754359Sroberto#else
160854359Sroberto	va_start(ap);
160954359Sroberto#endif /* __STDC__ */
161054359Sroberto
161154359Sroberto	pp = peer->procptr;
1612290001Sglebius	up = pp->unitptr;
161354359Sroberto
161454359Sroberto	cp = buf;
161554359Sroberto	*cp++ = '$';
1616132451Sroberto	n = VSNPRINTF((cp, sizeof(buf) - 1, fmt, ap));
161754359Sroberto	ck = mx4200_cksum(cp, n);
161854359Sroberto	cp += n;
161954359Sroberto	++n;
1620132451Sroberto	n += SNPRINTF((cp, sizeof(buf) - n - 5, "*%02X\r\n", ck));
162154359Sroberto
162254359Sroberto	m = write(pp->io.fd, buf, (unsigned)n);
162354359Sroberto	if (m < 0)
162454359Sroberto		msyslog(LOG_ERR, "mx4200_send: write: %m (%s)", buf);
162554359Sroberto	mx4200_debug(peer, "mx4200_send: %d %s\n", m, buf);
162654359Sroberto	va_end(ap);
162754359Sroberto}
162854359Sroberto
162954359Sroberto#else
163054359Srobertoint refclock_mx4200_bs;
163154359Sroberto#endif /* REFCLOCK */
1632