refclock_oncore.c   [plain text]


/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <phk@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
 * ----------------------------------------------------------------------------
 *
 * refclock_oncore.c
 *
 * Driver for some of the various the Motorola Oncore GPS receivers.
 *
 * Tested with:
 *
 *		(UT)				   (VP)
 *   COPYRIGHT 1991-1997 MOTOROLA INC.	COPYRIGHT 1991-1996 MOTOROLA INC.
 *   SFTW P/N #     98-P36848P		SFTW P/N # 98-P36830P
 *   SOFTWARE VER # 2			SOFTWARE VER # 8
 *   SOFTWARE REV # 2			SOFTWARE REV # 8
 *   SOFTWARE DATE  APR 24 1998 	SOFTWARE DATE  06 Aug 1996
 *   MODEL #	R1121N1114		MODEL #    B4121P1155
 *   HWDR P/N # 1			HDWR P/N # _
 *   SERIAL #	R0010A			SERIAL #   SSG0226478
 *   MANUFACTUR DATE 6H07		MANUFACTUR DATE 7E02
 *					OPTIONS LIST	IB
 *
 * --------------------------------------------------------------------------
 * This code uses the two devices
 *      /dev/oncore.serial.n
 *      /dev/oncore.pps.n
 * which may be linked to the same device.
 * and can read initialization data from the file
 *      /etc/ntp.oncoreN (where n and N are the unit number, viz 127.127.30.N)
 *  or	/etc/ntp.oncore
 * --------------------------------------------------------------------------
 * Reg.Clemens <reg@dwf.com> Sep98.
 *  Original code written for FreeBSD.
 *  With these mods it works on SunOS, Solaris (untested) and Linux
 *    (RedHat 5.1 2.0.35 + PPSKit, 2.1.126 + changes).
 *
 *  Lat,Long,Ht, cable-delay, offset, and the ReceiverID (along with the
 *  state machine state) are printed to CLOCKSTATS if that file is enabled
 *  in /etc/ntp.conf.
 *
 * --------------------------------------------------------------------------
 */

/*
 * According to the ONCORE manual (TRM0003, Rev 3.2, June 1998, page 3.13)
 * doing an average of 10000 valid 2D and 3D fixes is what the automatic
 * site survey mode does.  Looking at the output from the receiver
 * it seems like it is only using 3D fixes.
 * When we do it ourselves, take 10000 3D fixes.
 */

#define POS_HOLD_AVERAGE	10000	/* nb, 10000s ~= 2h45m */

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

#if defined(REFCLOCK) && defined(CLOCK_ONCORE)

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>

#ifdef HAVE_PPSAPI
#  ifdef HAVE_TIMEPPS_H
#    include <timepps.h> /* Once FreeBSD 3.1 dies, <timepps.h> should be dead */
# else
#  ifdef HAVE_SYS_TIMEPPS_H
#    include <sys/timepps.h>
#  endif
# endif
#endif

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

#ifdef HAVE_SYS_TERMIOS_H
#include <sys/termios.h>
#endif

#ifdef HAVE_SYS_PPSCLOCK_H
# include <sys/ppsclock.h>
#endif

#ifndef HAVE_STRUCT_PPSCLOCKEV
struct ppsclockev {
# ifdef HAVE_TIMESPEC
	struct timespec tv;
# else
	struct timeval tv;
# endif
	u_int serial;
};
#endif /* not HAVE_STRUCT_PPSCLOCKEV */

enum receive_state {
	ONCORE_NO_IDEA,
	ONCORE_RESET_SENT,
	ONCORE_TEST_SENT,
	ONCORE_ID_SENT,
	ONCORE_ALMANAC,
	ONCORE_RUN
};

enum site_survey_state {
	ONCORE_SS_UNKNOWN,
	ONCORE_SS_HW,
	ONCORE_SS_SW,
	ONCORE_SS_DONE
};

struct instance {
	int	unit;		/* 127.127.30.unit */
	int	ttyfd;		/* TTY file descriptor */
	int	ppsfd;		/* PPS file descriptor */
#ifdef HAVE_PPSAPI
	pps_handle_t pps_h;
	pps_params_t pps_p;
#endif
	enum receive_state state;		/* Receive state */

	enum site_survey_state site_survey;	/* Site Survey state */

	struct	refclockproc *pp;
	struct	peer *peer;

	int	Bj_day;
	int	assert;

	long	delay;		/* ns */
	long	offset; 	/* ns */

	double	ss_lat;
	double	ss_long;
	double	ss_ht;
	int	ss_count;
	u_char	ss_ht_type;
	int	posn_set;

	int	printed;
	int	pollcnt;
	int	polled;
	u_int	ev_serial;
	int	Rcvptr;
	u_char	Rcvbuf[500];
	u_char	Ea[77];
	u_char	En[70];
	u_char	Cj[300];
	u_char	As;
	u_char	Ay;
	u_char	Az;
	u_char	init_type;
	s_char	saw_tooth;
};

#define rcvbuf	instance->Rcvbuf
#define rcvptr	instance->Rcvptr

static	void	oncore_consume	  P((struct instance *));
static	void	oncore_poll	  P((int, struct peer *));
static	void	oncore_read_config	P((struct instance *));
static	void	oncore_receive	  P((struct recvbuf *));
static	void	oncore_sendmsg	  P((int fd, u_char *, u_int));
static	void	oncore_shutdown   P((int, struct peer *));
static	int	oncore_start	  P((int, struct peer *));
static	void	oncore_stats	  P((struct instance *));

static	void	oncore_msg_any	P((struct instance *, u_char *, u_int, int));
static	void	oncore_msg_As	P((struct instance *, u_char *, u_int));
static	void	oncore_msg_At	P((struct instance *, u_char *, u_int));
static	void	oncore_msg_Ay	P((struct instance *, u_char *, u_int));
static	void	oncore_msg_Az	P((struct instance *, u_char *, u_int));
static	void	oncore_msg_Bj	P((struct instance *, u_char *, u_int));
static	void	oncore_msg_Cf	P((struct instance *, u_char *, u_int));
static	void	oncore_msg_Cj	P((struct instance *, u_char *, u_int));
static	void	oncore_msg_Ea	P((struct instance *, u_char *, u_int));
static	void	oncore_msg_En	P((struct instance *, u_char *, u_int));
static	void	oncore_msg_Fa	P((struct instance *, u_char *, u_int));

struct	refclock refclock_oncore = {
	oncore_start,		/* start up driver */
	oncore_shutdown,	/* shut down driver */
	oncore_poll,		/* transmit poll message */
	noentry,		/* not used */
	noentry,		/* not used */
	noentry,		/* not used */
	NOFLAGS 		/* not used */
};

/*
 * Understanding the next bit here is not easy unless you have a manual
 * for the the UT or VP Oncore.
 */

static struct {
	const char	flag[3];
	const int	len;
	void		(*handler) P((struct instance *, u_char *, u_int));
	const char	*fmt;
} oncore_messages[] = {
	/* Ea and En first since they're most common */
	{ "Ea",  76,    oncore_msg_Ea,  "mdyyhmsffffaaaaoooohhhhmmmmvvhhddtntimsdimsdimsdimsdimsdimsdimsdimsdsC" },
	{ "En",  69,    oncore_msg_En,  "otaapxxxxxxxxxxpysreensffffsffffsffffsffffsffffsffffsffffsffffC" },
	{ "Ab",  10,    0,              "" },
	{ "Ac",  11,    0,              "" },
	{ "Ad",  11,    0,              "" },
	{ "Ae",  11,    0,              "" },
	{ "Af",  15,    0,              "" },
	{ "As",  20,    oncore_msg_As,  "" },
	{ "At",   8,    oncore_msg_At,  "" },
	{ "Aw",   8,    0,              "" },
	{ "Ay",  11,    oncore_msg_Ay,  "" },
	{ "Az",  11,    oncore_msg_Az,  "" },
	{ "AB",   8,    0,              "" },
	{ "Bb",  92,    0,              "" },
	{ "Bj",   8,    oncore_msg_Bj,  "" },
	{ "Cb",  33,    0,              "" },
	{ "Cf",   7,    oncore_msg_Cf,  "" },
	{ "Cg",   8,    0,              "" },
	{ "Ch",   9,    0,              "" },
	{ "Cj", 294,    oncore_msg_Cj,  "" },
	{ "Ek",  71,    0,              "" },
	{ "Fa",   9,    oncore_msg_Fa,  "" },
	{ "Sz",   8,    0,              "" },
	{ {0},	  7,	0, ""}
};


/*
 * Position Set.
 */
u_char oncore_cmd_Ad[] = { 'A', 'd', 0,0,0,0 };
u_char oncore_cmd_Ae[] = { 'A', 'e', 0,0,0,0 };
u_char oncore_cmd_Af[] = { 'A', 'f', 0,0,0,0, 0 };

/*
 * Position-Hold Mode
 *    Start automatic site survey
 */
static u_char oncore_cmd_At[] = { 'A', 't', 2 };

/*
 * Position-Hold Position
 */
u_char oncore_cmd_As[] = { 'A', 's', 0,0,0,0, 0,0,0,0, 0,0,0,0, 0 };
u_char oncore_cmd_Asx[]= { 'A', 's', 0x7f, 0xff, 0xff, 0xff,
				     0x7f, 0xff, 0xff, 0xff,
				     0x7f, 0xff, 0xff, 0xff, 0xff };

/*
 * Set to UTC time (not GPS).
 */
u_char oncore_cmd_Aw[] = { 'A', 'w', 1 };

/*
 * Read back PPS Offset for Output
 */
u_char oncore_cmd_Ay[]	= { 'A', 'y', 0, 0, 0, 0 };
u_char oncore_cmd_Ayx[] = { 'A', 'y', 0xff, 0xff, 0xff, 0xff };

/*
 * Read back Cable Delay for Output
 */
u_char oncore_cmd_Az[]	= { 'A', 'z', 0, 0, 0, 0 };
u_char oncore_cmd_Azx[] = { 'A', 'z', 0xff, 0xff, 0xff, 0xff };

/*
 * Application type = static.
 */
u_char oncore_cmd_AB[] = { 'A', 'B', 4 };

/*
 * Visible Satellite Status Msg.
 */
u_char oncore_cmd_Bb[] = { 'B', 'b', 0 };      /* just turn off */

/*
 * Leap Second Pending Message
 *    Request message once
 */
u_char oncore_cmd_Bj[] = { 'B', 'j', 0 };

/*
 * Set to Defaults
 */
static u_char oncore_cmd_Cf[] = { 'C', 'f' };

/*
 * Set to Position Fix mode (only needed on VP).
 */
u_char oncore_cmd_Cg[] = { 'C', 'g', 1 };

/*
 * Receiver Id
 */
static u_char oncore_cmd_Cj[] = { 'C', 'j' };

/*
 * Position/Status/Data message
 *    Send once per second
 */
static u_char oncore_cmd_Ea[] = { 'E', 'a', 1 };

/*
 * Position/Status Extension Msg
 */
u_char oncore_cmd_Ek[] = { 'E', 'k', 0 };	/* just turn off */

/*
 * Time Raim Setup & Status Message
 *    Send once per second
 *    Time-RAIM on
 *    Alarm limit 1us
 *    PPS on when we have the first sat
 */
static u_char oncore_cmd_En[] = { 'E', 'n', 1, 1, 0,10, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

/*
 * Self-test
 */
static u_char oncore_cmd_Fa[] = { 'F', 'a' };

#define DEVICE1 	"/dev/oncore.serial.%d"   /* name of serial device */
#define DEVICE2 	"/dev/oncore.pps.%d"   /* name of pps device */
#define INIT_FILE	"/etc/ntp.oncore" /* optional init file */

#define SPEED		B9600		/* Oncore Binary speed (9600 bps) */

/*
 * Assemble and disassemble 32bit signed quantities from a buffer.
 *
 */

	/* to buffer, int w, u_char *buf */
#define w32_buf(buf,w)	{ unsigned int i_tmp;		   \
			  i_tmp = (w<0) ? (~(-w)+1) : (w); \
			  (buf)[0] = (i_tmp >> 24) & 0xff; \
			  (buf)[1] = (i_tmp >> 16) & 0xff; \
			  (buf)[2] = (i_tmp >>	8) & 0xff; \
			  (buf)[3] = (i_tmp	 ) & 0xff; \
			}

#define w32(buf)      (((buf)[0]&0xff) << 24 | \
		       ((buf)[1]&0xff) << 16 | \
		       ((buf)[2]&0xff) <<  8 | \
		       ((buf)[3]&0xff) )

	/* from buffer, char *buf, result to an int */
#define buf_w32(buf) (((buf)[0]&0200) ? (-(~w32(buf)+1)) : w32(buf))

/*
 * oncore_start - initialize data for processing
 */
static int
oncore_start(
	int unit,
	struct peer *peer
	)
{
	register struct instance *instance;
	struct refclockproc *pp;
	int fd1, fd2, mode;
	char device1[30], device2[30];
	const char *cp;
	struct stat stat1, stat2;

	/* OPEN DEVICES */
	/* opening different devices for fd1 and fd2 presents no problems */
	/* opening the SAME device twice, seems to be OS dependent.
		(a) on Linux (no streams) no problem
		(b) on SunOS (and possibly Solaris, untested), (streams)
			never see the line discipline.
	   Since things ALWAYS work if we only open the device once, we check
	     to see if the two devices are in fact the same, then proceed to
	     do one open or two.
	*/

	(void)sprintf(device1, DEVICE1, unit);
	(void)sprintf(device2, DEVICE2, unit);

	if (stat(device1, &stat1)) {
		perror("ONCORE: stat fd1");
		exit(1);
	}

	if (stat(device2, &stat2)) {
		perror("ONCORE: stat fd2");
		exit(1);
	}

	if ((stat1.st_dev == stat2.st_dev) && (stat1.st_ino == stat2.st_ino)) {
		/* same device here */
		if (!(fd1 = refclock_open(device1, SPEED, LDISC_RAW
#ifdef HAVE_PPSAPI
#else
		      | LDISC_PPS
#endif
		   ))) {
			perror("ONCORE: fd1");
			exit(1);
		}
		fd2 = fd1;
	} else { /* different devices here */
		if (!(fd1=refclock_open(device1, SPEED, LDISC_RAW))) {
			perror("ONCORE: fd1");
			exit(1);
		}
		if ((fd2=open(device2, O_RDWR)) < 0) {
			perror("ONCORE: fd2");
			exit(1);
		}
	}

	/* Devices now open, initialize instance structure */

	if (!(instance = (struct instance *)emalloc(sizeof *instance))) {
		perror("malloc");
		close(fd1);
		return (0);
	}
	memset((char *) instance, 0, sizeof *instance);
	pp = peer->procptr;
	pp->nstages = 16;
	pp->nskeep = pp->nstages * 3 / 5;

	pp->unitptr = (caddr_t)instance;
	instance->unit	= unit;
	instance->ttyfd = fd1;
	instance->ppsfd = fd2;

	instance->Bj_day = -1;
	instance->assert = 1;

	/* go read any input data in /etc/ntp.oncoreX */

	oncore_read_config(instance);

#ifdef HAVE_PPSAPI
	if (time_pps_create(fd2, &instance->pps_h) < 0) {
		perror("time_pps_create");
		exit(1);
	}

	if (instance->assert) {
		instance->pps_p.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT | PPS_HARDPPSONASSERT;
		instance->pps_p.assert_offset.tv_sec = 0;
		instance->pps_p.assert_offset.tv_nsec = 0;
	} else {
		instance->pps_p.mode = PPS_CAPTURECLEAR  | PPS_OFFSETCLEAR  | PPS_HARDPPSONCLEAR;
		instance->pps_p.clear_offset.tv_sec = 0;
		instance->pps_p.clear_offset.tv_nsec = 0;
	}
	if (time_pps_setparams(instance->pps_h, &instance->pps_p) < 0) {
		perror("time_pps_setparams");
		exit(1);
	}
#endif
	instance->pp = pp;
	instance->peer = peer;
	instance->state = ONCORE_NO_IDEA;
	cp = "state = ONCORE_NO_IDEA";
	record_clock_stats(&(instance->peer->srcadr), cp);

	/*
	 * Initialize miscellaneous variables
	 */

	peer->precision = -26;
	peer->minpoll = 4;
	peer->maxpoll = 4;
	pp->clockdesc = "Motorola UT/VP Oncore GPS Receiver";
	memcpy((char *)&pp->refid, "GPS\0", 4);

	pp->io.clock_recv = oncore_receive;
	pp->io.srcclock = (caddr_t)peer;
	pp->io.datalen = 0;
	pp->io.fd = fd1;
	if (!io_addclock(&pp->io)) {
		perror("io_addclock");
		(void) close(fd1);
		free(instance);
		return (0);
	}
	pp->unitptr = (caddr_t)instance;

	/*
	 * This will start the Oncore receiver.
	 * We send info from config to Oncore later.
	 */

	mode = instance->init_type;
	if (mode == 3 || mode == 4) {
		oncore_sendmsg(instance->ttyfd, oncore_cmd_Cf, sizeof oncore_cmd_Cf);
		instance->state = ONCORE_RESET_SENT;
		cp = "state = ONCORE_RESET_SENT";
		record_clock_stats(&(instance->peer->srcadr), cp);
	} else {
		oncore_sendmsg(instance->ttyfd, oncore_cmd_Fa, sizeof oncore_cmd_Fa);
		instance->state = ONCORE_TEST_SENT;
		cp = "state = ONCORE_TEST_SENT";
		record_clock_stats(&(instance->peer->srcadr), cp);
	}

	instance->pollcnt = 2;

	return (1);
}

/*
 * Read Input file if it exists.
 */
static void
oncore_read_config(
	struct instance *instance
	)
{
/*
 * First we try to open the configuration file /etc/ntp.oncoreN, where
 * N is the unit number viz 127.127.30.N.
 * If we don't find it, then we try the file /etc/ntp.oncore.
 *
 * If we find NEITHER then we don't have the cable delay or PPS offset
 * and we choose MODE (4) below.
 *
 * Five Choices for MODE
 *    (0) ONCORE is preinitialized, don't do anything to change it.
 *	    nb, DON'T set 0D mode, DON'T set Delay, position...
 *    (1) NO RESET, Read Position, delays from data file, lock it in, go to 0D mode.
 *    (2) NO RESET, Read Delays from data file, do SITE SURVEY to get position,
 *		    lock this in, go to 0D mode.
 *    (3) HARD RESET, Read Position, delays from data file, lock it in, go to 0D mode.
 *    (4) HARD RESET, Read Delays from data file, do SITE SURVEY to get position,
 *		    lock this in, go to 0D mode.
 *     NB. If a POSITION is specified in the config file with mode=(2,4) [SITE SURVEY]
 *	   then this position is set as the INITIAL position of the ONCORE.
 *	   This can reduce the time to first fix.
 * -------------------------------------------------------------------------------
 * Note that an Oncore UT without a battery backup retains NO information if it is
 *   power cycled, with a Battery Backup it remembers the almanac, etc.
 * For an Oncore VP, there is an eeprom that will contain this data, along with the
 *   option of Battery Backup.
 * So a UT without Battery Backup is equivalent to doing a HARD RESET on each
 *   power cycle, since there is nowhere to store the data.
 * -------------------------------------------------------------------------------
 *
 * If we open one or the other of the files, we read it looking for
 *   MODE, LAT, LON, (HT, HTGPS, HTMSL), DELAY, OFFSET
 * then initialize using method MODE.  For Mode = (1,3) all of (LAT, LON, HT) must
 *   be present or mode reverts to (2,4).
 *
 * Read input file.
 *
 *	# is comment to end of line
 *	= allowed between 1st and 2nd fields.
 *
 *	Expect to see one line with 'MODE' as first field, followed by an integer
 *	   in the range 0-4 (default = 4).
 *
 *	Expect to see two lines with 'LONG', 'LAT' followed by 1-3 fields.
 *	All numbers are floating point.
 *		DDD.ddd
 *		DDD  MMM.mmm
 *		DDD  MMM  SSS.sss
 *
 *	Expect to see one line with 'HT' (or 'HTMSL' or 'HTGPS') as first field.
 *	   followed by 1-2 fields.  First is a number, the second is 'FT' or 'M'.
 *	   for feet or meters.	HT is the same as HTGPS.
 *	     HTMSL = HT above mean_sea_level,
 *	     HTGPS = HT above GPS ellipse.
 *
 *	There are two optional lines, starting with DELAY and OFFSET, followed
 *	   by 1 or two fields.	The first is a number (a time) the second is
 *	   'MS', 'US' or 'NS' for miliseconds, microseconds or nanoseconds.
 *	     DELAY  is cable delay, typically a few tens of ns.
 *	     OFFSET is the offset of the PPS pulse from 0. (only fully implemented
 *		with the PPSAPI, we need to be able to tell the Kernel about this
 *		offset if the Kernel PLL is in use, but can only do this presently
 *		when using the PPSAPI interface.  If not using the Kernel PLL,
 *		then there is no problem.
 *
 *	There is another optional line, with either ASSERT or CLEAR on it, which
 *	   determine which transition of the PPS signal is used for timing by the
 *	   PPSAPI.  If neither is present, then ASSERT is assumed.
 *
 * So acceptable input would be
 *	# these are my coordinates (RWC)
 *	LON  -106 34.610
 *	LAT    35 08.999
 *	HT	1589	# could equally well say HT 5215 FT
 *	DELAY  60 ns
 */

	FILE	*fd;
	char	*cp, line[100], units[2], device[20];
	int	i, j, sign, lat_flg, long_flg, ht_flg, mode;
	double	f1, f2, f3;


	sprintf(device, "%s%d", INIT_FILE, instance->unit);
	if ((fd=fopen(device, "r")) == NULL)
		if ((fd=fopen(INIT_FILE, "r")) == NULL) {
			instance->init_type = 4;
			return;
		}

	mode = 0;
	lat_flg = long_flg = ht_flg = 0;
	while (fgets(line, 100, fd)) {
		if ((cp=strchr(line, '#')))
			*cp = '\0';
		i = strlen(line);
		for (j=0; j<i; j++)	/* just in case lower case */
			if (islower(line[j]))
				line[j] = toupper(line[j]);
		for (j=0; j<i; j++)	/* let them use `=' between terms */
			if (line[j] == '=')
				line[j] = ' ';
		for (j=0; j<i; j++)
			if (line[j] != ' ')
				break;
		if (!strncmp(&line[j], "LAT", 3)) {
			j += 3;
			f1 = f2 = f3 = 0;
			sscanf(&line[j], "%lf %lf %lf", &f1, &f2, &f3);
			sign = 1;
			if (f1 < 0) {
				f1 = -f1;
				sign = -1;
			}
			instance->ss_lat = sign*1000*(fabs(f3) + 60*(fabs(f2) + 60*f1)); /*miliseconds*/
			lat_flg++;
		} else if (!strncmp(&line[j], "LON", 3)) {
			j += 3;
			f1 = f2 = f3 = 0;
			sscanf(&line[j], "%lf %lf %lf", &f1, &f2, &f3);
			sign = 1;
			if (f1 < 0) {
				f1 = -f1;
				sign = -1;
			}
			instance->ss_long = sign*1000*(fabs(f3) + 60*(fabs(f2) + 60*f1)); /*miliseconds*/
			long_flg++;
		} else if (!strncmp(&line[j], "HT", 2)) {
			instance->ss_ht_type = 0;
			if (!strncmp(&line[j], "HTGPS", 5)) {
				instance->ss_ht_type = 0;
				j +=3;
			}
			if (!strncmp(&line[j], "HTMSL", 5)) {
				instance->ss_ht_type = 1;
				j +=3;
			}
			j += 2;
			f1 = 0;
			units[0] = '\0';
			sscanf(&line[j], "%lf %1s", &f1, units);
			if (units[0] == 'F')
				f1 = 0.3048 * f1;
			instance->ss_ht = 100 * f1;    /* cm */
			ht_flg++;
		} else if (!strncmp(&line[j], "DELAY", 5)) {
			j += 5;
			f1 = 0;
			units[0] = '\0';
			sscanf(&line[j], "%lf %1s", &f1, units);
			if (units[0] == 'N')
				;
			else if (units[0] == 'U')
				f1 = 1000 * f1;
			else if (units[0] == 'M')
				f1 = 1000000 * f1;
			else
				f1 = 1000000000 * f1;
			if (f1 < 0 || f1 > 1.e9)
				f1 = 0;
			instance->delay = f1;		/* delay in ns */
		} else if (!strncmp(&line[j], "OFFSET", 6)) {
			j += 6;
			f1 = 0;
			units[0] = '\0';
			sscanf(&line[j], "%lf %1s", &f1, units);
			if (units[0] == 'N')
				;
			else if (units[0] == 'U')
				f1 = 1000 * f1;
			else if (units[0] == 'M')
				f1 = 1000000 * f1;
			else
				f1 = 1000000000 * f1;
			if (f1 < 0 || f1 > 1.e9)
				f1 = 0;
			instance->offset = f1;		/* offset in ns */
		} else if (!strncmp(&line[j], "MODE", 4)) {
			j += 4;
			sscanf(&line[j], "%d", &mode);
			if (mode < 0 || mode > 4)
				mode = 4;
			instance->init_type = mode;
		} else if (!strncmp(&line[j], "ASSERT", 6)) {
			instance->assert = 1;
		} else if (!strncmp(&line[j], "CLEAR", 5)) {
			instance->assert = 0;
		}
	}
	fclose(fd);

	/*
	 *    OK, have read all of data file, and extracted the good stuff.
	 *    If lat/long/ht specified they ALL must be specified for mode = (1,3).
	 */

	instance->posn_set = 1;
	if ((lat_flg || long_flg || ht_flg) && !(lat_flg * long_flg * ht_flg)) {
		printf("ONCORE: incomplete data on %s\n", INIT_FILE);
		instance->posn_set = 0;
		if (mode == 1 || mode == 3)
			instance->init_type++;
	}
}



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

	pp = peer->procptr;
	instance = (struct instance *) pp->unitptr;
	free(instance);
}



/*
 * oncore_poll - called by the transmit procedure
 */
static void
oncore_poll(
	int unit,
	struct peer *peer
	)
{
	struct instance *instance;

	instance = (struct instance *) peer->procptr->unitptr;
	if (!instance->pollcnt)
		refclock_report(peer, CEVNT_TIMEOUT);
	else
		instance->pollcnt--;
	peer->procptr->polls++;
	instance->polled = 1;
}



/*
 * move dta from NTP to buffer (toss in unlikely case it wont fit)
 */
static void
oncore_receive(
	struct recvbuf *rbufp
	)
{
	u_int	i;
	u_char *p;
	struct peer *peer;
	struct instance *instance;

	peer = (struct peer *)rbufp->recv_srcclock;
	instance = (struct instance *) peer->procptr->unitptr;
	p = (u_char *) &rbufp->recv_space;

#if 0
	if (debug > 4) {
		int i;
		printf("ONCORE: >>>");
		for(i=0; i<rbufp->recv_length; i++)
			printf("%02x ", p[i]);
		printf("\n");
		printf("ONCORE: >>>");
		for(i=0; i<rbufp->recv_length; i++)
			printf("%03o ", p[i]);
		printf("\n");
	}
#endif

	i = rbufp->recv_length;
	if (rcvbuf+rcvptr+i > &rcvbuf[sizeof rcvbuf])
		i = sizeof(rcvbuf) - rcvptr;	/* and some char will be lost */
	memcpy(rcvbuf+rcvptr, p, i);
	rcvptr += i;
	oncore_consume(instance);
}



/*
 * Deal with any complete messages
 */
static void
oncore_consume(
	struct instance *instance
	)
{
	int i, l, j, m;

	while (rcvptr >= 7) {
		if (rcvbuf[0] != '@' || rcvbuf[1] != '@') {
			/* We're not in sync, lets try to get there */
			for (i=1; i < rcvptr-1; i++)
				if (rcvbuf[i] == '@' && rcvbuf[i+1] == '@')
					break;
			if (debug > 4)
				printf("ONCORE: >>> skipping %d chars\n", i);
			if (i != rcvptr)
				memcpy(rcvbuf, rcvbuf+i, (unsigned)(rcvptr-i));
			rcvptr -= i;
		}

		/* Ok, we have a header now */
		l = sizeof(oncore_messages)/sizeof(oncore_messages[0]) -1;
		for(m=0; m<l; m++)
			if (!strncmp(oncore_messages[m].flag, (char *)(rcvbuf+2), 2))
				break;
		l = oncore_messages[m].len;
#if 0
		if (debug > 3)
			printf("ONCORE: GOT: %c%c  %d of %d entry %d\n", rcvbuf[2], rcvbuf[3], rcvptr, l, m);
#endif
		/* Got the entire message ? */

		if (rcvptr < l)
			return;

		/* Check the checksum */

		j = 0;
		for (i = 2; i < l-3; i++)
			j ^= rcvbuf[i];
		if (j == rcvbuf[l-3]) {
			oncore_msg_any(instance, rcvbuf, (unsigned) (l-3), m);
			if (oncore_messages[m].handler)
				oncore_messages[m].handler(instance, rcvbuf, (unsigned) (l-3));
		} else if (debug) {
			printf("ONCORE: Checksum mismatch! calc %o is %o\n", j, rcvbuf[l-3]);
			printf("ONCORE: @@%c%c ", rcvbuf[2], rcvbuf[3]);
			for (i=4; i<l; i++)
				printf("%03o ", rcvbuf[i]);
			printf("\n");
		}

		if (l != rcvptr)
			memcpy(rcvbuf, rcvbuf+l, (unsigned) (rcvptr-l));
		rcvptr -= l;
	}
}



/*
 * write message to Oncore.
 */
static void
oncore_sendmsg(
	int	fd,
	u_char *ptr,
	u_int len
	)
{
	u_char cs = 0;

	printf("ONCORE: Send @@%c%c %d\n", ptr[0], ptr[1], len);
	write(fd, "@@", 2);
	write(fd, ptr, len);
	while (len--)
		cs ^= *ptr++;
	write(fd, &cs, 1);
	write(fd, "\r\n", 2);
}



static void
oncore_msg_any(
	struct instance *instance,
	u_char *buf,
	u_int len,
	int idx
	)
{
	int i;
	const char *fmt = oncore_messages[idx].fmt;
	const char *p;
	struct timeval tv;

	if (debug > 3) {
		GETTIMEOFDAY(&tv, 0);
		printf("ONCORE: %ld.%06ld\n", (long) tv.tv_sec, (long) tv.tv_usec);

		if (!*fmt) {
			printf(">>@@%c%c ", buf[2], buf[3]);
			for(i=2; i < len && i < 2400 ; i++)
				printf("%02x", buf[i]);
			printf("\n");
			return;
		} else {
			printf("##");
			for (p = fmt; *p; p++) {
				putchar(*p);
				putchar('_');
			}
			printf("\n%c%c", buf[2], buf[3]);
			i = 4;
			for (p = fmt; *p; p++) {
				printf("%02x", buf[i++]);
			}
			printf("\n");
		}
	}
}



/*
 * Set to Factory Defaults (Reasonable for UT w/ no Battery Backup
 *	not so for VP (eeprom) or UT with battery
 */
static void
oncore_msg_Cf(
	struct instance *instance,
	u_char *buf,
	u_int len
	)
{
	const char *cp;

	if (instance->state == ONCORE_RESET_SENT) {
		oncore_sendmsg(instance->ttyfd, oncore_cmd_Fa, sizeof oncore_cmd_Fa);
		instance->state = ONCORE_TEST_SENT;
		cp = "state = ONCORE_TEST_SENT";
		record_clock_stats(&(instance->peer->srcadr), cp);
	}
}



static void
oncore_msg_Fa(
	struct instance *instance,
	u_char *buf,
	u_int len
	)
{
	const char *cp;

	if (instance->state == ONCORE_TEST_SENT) {
		if (debug > 2)
			printf("ONCORE: >>@@Fa %x %x\n", buf[4], buf[5]);
		if (buf[4] || buf[5]) {
			printf("ONCORE: SELF TEST FAILED\n");
			exit(1);
		}

		/* sometimes the @@Cj request does not produce any output
		   PERHAPS the ONCORE is still busy from the selftest???
		   try a 2 second sleep here to see if it makes any difference
		 */

		sleep(2);
		oncore_sendmsg(instance->ttyfd, oncore_cmd_Cj, sizeof oncore_cmd_Cj);
		instance->state = ONCORE_ID_SENT;
		cp = "state = ONCORE_ID_SENT";
		record_clock_stats(&(instance->peer->srcadr), cp);
	}
}



/*
 * preliminaries out of the way, this is the REAL start of initialization
 */
static void
oncore_msg_Cj(
	struct instance *instance,
	u_char *buf,
	u_int len
	)
{
	const char *cp;
	int	mode;

	if (instance->state != ONCORE_ID_SENT)
		return;

	memcpy(instance->Cj, buf, len);

	oncore_sendmsg(instance->ttyfd, oncore_cmd_Cg, sizeof oncore_cmd_Cg); /* Set Posn Fix mode (not Idle (VP)) */
	oncore_sendmsg(instance->ttyfd, oncore_cmd_Bb, sizeof oncore_cmd_Bb); /* turn off */
	oncore_sendmsg(instance->ttyfd, oncore_cmd_Ek, sizeof oncore_cmd_Ek); /* turn off */
	oncore_sendmsg(instance->ttyfd, oncore_cmd_Aw, sizeof oncore_cmd_Aw); /* UTC time */
	oncore_sendmsg(instance->ttyfd, oncore_cmd_AB, sizeof oncore_cmd_AB); /* Appl type static */

	mode = instance->init_type;
	if (debug)
		printf("ONCORE: INIT mode = %d\n", mode);

	/* If there is Position input in the Config file
	 * and mode = (1,3) set it as posn hold posn, goto 0D mode.
	 *  or mode = (2,4) set it as INITIAL position, and Site Survey.
	 */

	switch (mode) {
	case 0: /* NO initialization, don't change anything */
		instance->site_survey = ONCORE_SS_DONE;
		break;

	case 1:
	case 3:
		w32_buf(&oncore_cmd_As[2],  (int) instance->ss_lat);
		w32_buf(&oncore_cmd_As[6],  (int) instance->ss_long);
		w32_buf(&oncore_cmd_As[10], (int) instance->ss_ht);
		oncore_cmd_As[14] = instance->ss_ht_type;
		oncore_sendmsg(instance->ttyfd, oncore_cmd_As,	sizeof oncore_cmd_As);

		instance->site_survey = ONCORE_SS_DONE;
		oncore_cmd_At[2] = 1;
		oncore_sendmsg(instance->ttyfd, oncore_cmd_At,	sizeof oncore_cmd_At);
		record_clock_stats(&(instance->peer->srcadr), "Now in 0D mode");
		break;

	case 2:
	case 4:
		if (instance->posn_set) {
			w32_buf(&oncore_cmd_Ad[2], (int) instance->ss_lat);
			w32_buf(&oncore_cmd_Ae[2], (int) instance->ss_long);
			w32_buf(&oncore_cmd_Af[2], (int) instance->ss_ht);
			oncore_cmd_Af[6] = instance->ss_ht_type;
			oncore_sendmsg(instance->ttyfd, oncore_cmd_Ad,	sizeof oncore_cmd_Ad);
			oncore_sendmsg(instance->ttyfd, oncore_cmd_Ae,	sizeof oncore_cmd_Ae);
			oncore_sendmsg(instance->ttyfd, oncore_cmd_Af,	sizeof oncore_cmd_Af);
		}
		instance->site_survey = ONCORE_SS_UNKNOWN;
		oncore_cmd_At[2] = 2;
		oncore_sendmsg(instance->ttyfd, oncore_cmd_At,	sizeof oncore_cmd_At);
		break;
	}

	if (mode != 0) {
			/* cable delay in ns */
		w32_buf(&oncore_cmd_Az[2], instance->delay);
		oncore_sendmsg(instance->ttyfd, oncore_cmd_Az,	sizeof oncore_cmd_Az);

			/* PPS offset in ns */
		w32_buf(&oncore_cmd_Ay[2], instance->offset);
		oncore_sendmsg(instance->ttyfd, oncore_cmd_Ay,	sizeof oncore_cmd_Ay);
	}

	/* 8chan - Position/Status/Data Output Message, 1/s */

	oncore_sendmsg(instance->ttyfd, oncore_cmd_Ea,	sizeof oncore_cmd_Ea);

	instance->state = ONCORE_ALMANAC;
	cp = "state = ONCORE_ALMANAC";
	record_clock_stats(&(instance->peer->srcadr), cp);
}



static void
oncore_msg_Ea(
	struct instance *instance,
	u_char *buf,
	u_int len
	)
{
	const char	*cp;

	if (instance->state != ONCORE_ALMANAC && instance->state != ONCORE_RUN)
		return;

	memcpy(instance->Ea, buf, len);

	/* When we have an almanac, start the En messages */

	if (instance->state == ONCORE_ALMANAC) {
		if ((instance->Ea[72] & 1)) {
			if (debug)
				printf("ONCORE: waiting for almanac\n");
			return;
		} else {
			oncore_sendmsg(instance->ttyfd, oncore_cmd_En, sizeof oncore_cmd_En);
			instance->state = ONCORE_RUN;
			cp = "state = ONCORE_RUN";
			record_clock_stats(&(instance->peer->srcadr), cp);
		}
	}

	/* must be ONCORE_RUN if we are here */
	/* First check if Hardware SiteSurvey has Finished */

	if ((instance->site_survey == ONCORE_SS_HW) && !(instance->Ea[37] & 0x20)) {
		instance->site_survey = ONCORE_SS_DONE;
		record_clock_stats(&(instance->peer->srcadr), "Now in 0D mode");
	}

	if (!instance->printed && instance->site_survey == ONCORE_SS_DONE) {	/* will print to clockstat when all */
		instance->printed = 1;						/* three messages respond */
			/* Read back Position Hold Params */
		oncore_sendmsg(instance->ttyfd, oncore_cmd_Asx,  sizeof oncore_cmd_Asx);
			/* Read back PPS Offset for Output */
		oncore_sendmsg(instance->ttyfd, oncore_cmd_Ayx,  sizeof oncore_cmd_Ayx);
			/* Read back Cable Delay for Output */
		oncore_sendmsg(instance->ttyfd, oncore_cmd_Azx,  sizeof oncore_cmd_Azx);
	}

	/* Check the leap second status once per day */

	/*
	 * The following additional check, checking for June/December, is a
	 * workaround for incorrect ONCORE firmware.  The oncore starts
	 * reporting the leap second when the GPS satellite data message
	 * (page 18, subframe 4) is updated to a date in the future, which
	 * which can be several months before the leap second.	WWV and other
	 * services seem to wait until the month of the event to turn
	 * on their indicators (which are usually a single bit).
	 */

	if ((buf[4] == 6) || (buf[4] == 12)) {
		if (instance->Bj_day != buf[5]) {     /* do this 1/day */
			instance->Bj_day = buf[5];
			oncore_sendmsg(instance->ttyfd, oncore_cmd_Bj, sizeof oncore_cmd_Bj);
		}
	}
	instance->pp->year = buf[6]*256+buf[7];
	instance->pp->day = ymd2yd(buf[6]*256+buf[7], buf[4], buf[5]);
	instance->pp->hour = buf[8];
	instance->pp->minute = buf[9];
	instance->pp->second = buf[10];

	if (instance->site_survey != ONCORE_SS_SW)
		return;

	/*
	 * We have to average our own position for the Position Hold Mode
	 */

	/* We only take PDOP/3D fixes */

	if (instance->Ea[37] & 1)
		return;

	/* Not if poor geometry or less than 3 sats */

	if (instance->Ea[72] & 0x52)
		return;

	/* Only 3D fix */

	if (!(instance->Ea[72] & 0x20))
		return;

	instance->ss_lat  += buf_w32(&instance->Ea[15]);
	instance->ss_long += buf_w32(&instance->Ea[19]);
	instance->ss_ht   += buf_w32(&instance->Ea[23]);  /* GPS ellipse */
	instance->ss_count++;

	if (debug)
		printf("ONCORE: AVG %d %d %d %d\n",
			instance->ss_count,
			(unsigned) (instance->ss_lat  / instance->ss_count),
			(unsigned) (instance->ss_long / instance->ss_count),
			(unsigned) (instance->ss_ht   / instance->ss_count)
			);

	if (instance->ss_count != POS_HOLD_AVERAGE)
		return;

	instance->ss_lat  /= POS_HOLD_AVERAGE;
	instance->ss_long /= POS_HOLD_AVERAGE;
	instance->ss_ht   /= POS_HOLD_AVERAGE;

	w32_buf(&oncore_cmd_As[2],  (int) instance->ss_lat);
	w32_buf(&oncore_cmd_As[6],  (int) instance->ss_long);
	w32_buf(&oncore_cmd_As[10], (int) instance->ss_ht);
	oncore_cmd_As[14] = 0;
	oncore_sendmsg(instance->ttyfd, oncore_cmd_As, sizeof oncore_cmd_As);

	oncore_cmd_At[2] = 1;
	oncore_sendmsg(instance->ttyfd, oncore_cmd_At, sizeof oncore_cmd_At);
	record_clock_stats(&(instance->peer->srcadr), "Now in 0D mode");
	instance->site_survey = ONCORE_SS_DONE;
}



static void
oncore_msg_En(
	struct instance *instance,
	u_char *buf,
	u_int len
	)
{
	int	j;
	l_fp ts, ts_tmp;
	double dmy;
#ifdef HAVE_TIMESPEC
	struct timespec *tsp = 0;
#else
	struct timeval	*tsp = 0;
#endif
#ifdef HAVE_PPSAPI
	pps_info_t pps_i;
#else  /* ! HAVE_PPSAPI */
#ifdef HAVE_CIOGETEV
	struct ppsclockev ev;
	u_long r = CIOGETEV;
#endif
#ifdef HAVE_TIOCGPPSEV
	struct ppsclockev ev;
	u_long r = TIOCGPPSEV;
#endif
#endif	/* ! HAVE_PPS_API */

	if (instance->state != ONCORE_RUN)
		return;

	memcpy(instance->En, buf, len);

	/* Don't do anything without an almanac to define the GPS->UTC delta */

	if (instance->Ea[72] & 1)
		return;

	/* If Time RAIM doesn't like it, don't trust it */

	if (instance->En[21])
		return;

#ifdef HAVE_PPSAPI
	j = instance->ev_serial;
	if (time_pps_fetch(instance->pps_h, &pps_i) < 0) {
		printf("ONCORE: time_pps_fetch failed\n");
		return;
	}

	if (instance->assert) {
		tsp = &pps_i.assert_timestamp;

		if (debug > 2)
			printf("ONCORE: serial/j (%d, %d) %ld.%09ld\n",
			    pps_i.assert_sequence, j, tsp->tv_sec, tsp->tv_nsec);

		if (pps_i.assert_sequence == j) {
			printf("ONCORE: oncore_msg_En, error serial pps\n");
			return;
		}
		instance->ev_serial = pps_i.assert_sequence;
	} else {
		tsp = &pps_i.clear_timestamp;

		if (debug > 2)
			printf("ONCORE: serial/j (%d, %d) %ld.%09ld\n",
			    pps_i.clear_sequence, j, tsp->tv_sec, tsp->tv_nsec);

		if (pps_i.clear_sequence == j) {
			printf("ONCORE: oncore_msg_En, error serial pps\n");
			return;
		}
		instance->ev_serial = pps_i.clear_sequence;
	}

	/* convert timespec -> ntp l_fp */

	dmy = tsp->tv_nsec;
	dmy /= 1e9;
	ts.l_uf =  dmy * 4294967296.0;
	ts.l_ui = tsp->tv_sec;
#if 0
     alternate code for previous 4 lines is
	dmy = 1.0e-9*tsp->tv_nsec;	/* fractional part */
	DTOLFP(dmy, &ts);
	dmy = tsp->tv_sec;		/* integer part */
	DTOLFP(dmy, &ts_tmp);
	L_ADD(&ts, &ts_tmp);
     or more simply
	dmy = 1.0e-9*tsp->tv_nsec;	/* fractional part */
	DTOLFP(dmy, &ts);
	ts.l_ui = tsp->tv_sec;
#endif	/* 0 */
#else	/* ! HAVE_PPSAPI */
	j = instance->ev_serial;
	if (ioctl(instance->ppsfd, r, (caddr_t) &ev) < 0) {
		perror("ONCORE: IOCTL:");
		return;
	}

	tsp = &ev.tv;

	if (debug > 2)
		printf("ONCORE: serial/j (%d, %d) %ld.%06ld\n",
			ev.serial, j, tsp->tv_sec, tsp->tv_usec);

	if (ev.serial == j) {
		printf("ONCORE: oncore_msg_En, error serial pps\n");
		return;
	}
	instance->ev_serial = ev.serial;

	/* convert timeval -> ntp l_fp */

	TVTOTS(tsp, &ts);
#endif
	/* now have timestamp in ts */
	/* add in saw_tooth and offset */

	/* saw_tooth not really necessary if using TIMEVAL */
	/* since its only precise to us, but do it anyway. */

	/* offset in ns, and is positive (late), we subtract */
	/* to put the PPS time transition back where it belongs */

	j  = instance->saw_tooth + instance->offset;
	instance->saw_tooth = (s_char) buf[25]; /* update for next time */
#ifdef HAVE_PPSAPI
	/* must hand this offset off to the Kernel to do the addition */
	/* so that the Kernel PLL sees the offset too */

	if (instance->assert) {
		instance->pps_p.assert_offset.tv_nsec =
			 -(instance->saw_tooth + instance->offset);
	} else {
		instance->pps_p.clear_offset.tv_nsec =
			 -(instance->saw_tooth + instance->offset);
	}

	if (time_pps_setparams(instance->pps_h, &instance->pps_p))
		perror("time_pps_setparams");
#else
	/* if not PPSAPI, no way to inform kernel of OFFSET, just do it */

	dmy = -1.0e-9*j;
	DTOLFP(dmy, &ts_tmp);
	L_ADD(&ts, &ts_tmp);
#endif
	/* have time from UNIX origin, convert to NTP origin. */

	ts.l_ui += JAN_1970;
	instance->pp->lastrec = ts;
	instance->pp->msec = 0;

	ts_tmp = ts;
	ts_tmp.l_ui = 0;        /* zero integer part */
	LFPTOD(&ts_tmp, dmy);   /* convert fractional part to a double */
	j = 1.0e9*dmy;          /* then to integer ns */
	sprintf(instance->pp->a_lastcode,
	    "%u.%09u %d %d %2d %2d %2d %2ld rstat %02x dop %d nsat %2d,%d raim %d sigma %d neg-sawtooth %3d sat %d%d%d%d%d%d%d%d",
	    ts.l_ui, j,
	    instance->pp->year, instance->pp->day,
	    instance->pp->hour, instance->pp->minute, instance->pp->second,
	    (long) tsp->tv_sec % 60,

	    instance->Ea[72], instance->Ea[37], instance->Ea[38], instance->Ea[39], instance->En[21],
	    /*rstat           dop               nsat visible,     nsat tracked,     raim */
	    instance->En[23]*256+instance->En[24], (s_char) buf[25],
	    /* sigma				   neg-sawtooth */
  /*sat*/   instance->Ea[41], instance->Ea[45], instance->Ea[49], instance->Ea[53],
	    instance->Ea[57], instance->Ea[61], instance->Ea[65], instance->Ea[69]
	    );

	if (debug > 2) {
		int i;
		i = strlen(instance->pp->a_lastcode);
		printf("ONCORE: len = %d %s\n", i, instance->pp->a_lastcode);
	}

	if (!refclock_process(instance->pp)) {
		refclock_report(instance->peer, CEVNT_BADTIME);
		return;
	}

	record_clock_stats(&(instance->peer->srcadr), instance->pp->a_lastcode);
	instance->pollcnt = 2;

	if (instance->polled) {
		instance->polled = 0;
/*
		instance->pp->dispersion = instance->pp->skew = 0;
*/
		refclock_receive(instance->peer);
	}
}



/*
 * Try to use Oncore UT+ Auto Survey Feature
 *	If its not there (VP), set flag to do it ourselves.
 */
static void
oncore_msg_At(
	struct instance *instance,
	u_char *buf,
	u_int len
	)
{

	if (instance->site_survey != ONCORE_SS_UNKNOWN)
		return;

	if (buf[4] == 2)
		instance->site_survey = ONCORE_SS_HW;
	else {
		instance->site_survey = ONCORE_SS_SW;

		/*
		 * Probably a VP or an older UT which can't do site-survey.
		 * We will have to do it ourselves
		 */
		oncore_cmd_At[2] = 0;
		instance->ss_lat = instance->ss_long = instance->ss_ht = 0;
		oncore_sendmsg(instance->ttyfd, oncore_cmd_At, sizeof oncore_cmd_At);
	}
}



/* get leap-second warning message */

/*
 * @@Bj does NOT behave as documented in current Oncore firmware.
 * It turns on the LEAP indicator when the data is set, and does not,
 * as documented, wait until the beginning of the month when the
 * leap second will occur.
 * Until this firmware bug is fixed, @@Bj is only called in June/December.
 */

static void
oncore_msg_Bj(
	struct instance *instance,
	u_char *buf,
	u_int len
	)
{
	const char	*cp;

	switch(buf[4]) {
	case 1:
		instance->peer->leap = LEAP_ADDSECOND;
		cp = "Set peer.leap to LEAP_ADDSECOND";
		break;
	case 2:
		instance->peer->leap = LEAP_DELSECOND;
		cp = "Set peer.leap to LEAP_DELSECOND";
		break;
	case 0:
	default:
		instance->peer->leap = LEAP_NOWARNING;
		cp = "Set peer.leap to LEAP_NOWARNING";
		break;
	}
	record_clock_stats(&(instance->peer->srcadr), cp);
}



/*
 * get Position hold position
 */
static void
oncore_msg_As(
	struct instance *instance,
	u_char *buf,
	u_int len
	)
{
	int lat, lon, ht;

	if (!instance->printed || instance->As)
		return;

	instance->As = 1;

	lat = buf_w32(&buf[4]);
	instance->ss_lat = lat;

	lon = buf_w32(&buf[8]);
	instance->ss_long = lon;

	ht = buf_w32(&buf[12]);
	instance->ss_ht = ht;

	instance->ss_ht_type = buf[16];

	if (instance->Ay && instance->Az)
		oncore_stats(instance);
}



/*
 * get PPS Offset
 */
static void
oncore_msg_Ay(
	struct instance *instance,
	u_char *buf,
	u_int len
	)
{
	if (!instance->printed || instance->Ay)
		return;

	instance->Ay = 1;

	instance->offset = buf_w32(&buf[4]);

	if (instance->As && instance->Az)
		oncore_stats(instance);
}



/*
 * get Cable Delay
 */
static void
oncore_msg_Az(
	struct instance *instance,
	u_char *buf,
	u_int len
	)
{
	if (!instance->printed || instance->Az)
		return;

	instance->Az = 1;

	instance->delay = buf_w32(&buf[4]);

	if (instance->As && instance->Ay)
		oncore_stats(instance);
}



/*
 * print init data in ONCORE to clockstats file
 */
static void
oncore_stats(
	struct instance *instance
	)
{
	char Msg[120], ew, ns, *cp, *cp1;
	const char *Ht;
	double xd, xm, xs, yd, ym, ys, hm, hft;
	int idx, idy, is, imx, imy;
	long lat, lon;

	/* First, Receiver ID */

	instance->Cj[294] = '\0';
	for (cp=(char *)instance->Cj; cp< (char *) &instance->Cj[294]; ) {
		cp1 = strchr(cp, '\r');
		if (!cp1)
			cp1 = (char *)&instance->Cj[294];
		*cp1 = '\0';
		record_clock_stats(&(instance->peer->srcadr), cp);
		*cp1 = '\r';
		cp = cp1+2;
	}

	/* Next Position */

	record_clock_stats(&(instance->peer->srcadr), "Posn:");
	ew = 'E';
	lon = instance->ss_long;
	if (lon < 0) {
		ew = 'W';
		lon = -lon;
	}

	ns = 'N';
	lat = instance->ss_lat;
	if (lat < 0) {
		ns = 'S';
		lat = -lat;
	}

	hm = instance->ss_ht/100.;
	hft= hm/0.3048;
	Ht = instance->ss_ht_type ? "MSL" : "GPS";

	xd = lat/3600000.;	/* lat, lon in int msec arc, ht in cm. */
	yd = lon/3600000.;
	sprintf(Msg, "Lat = %c %11.7fdeg,    Long = %c %11.7fdeg,    Alt = %5.2fm (%5.2fft) %s", ns, xd, ew, yd, hm, hft, Ht);
	record_clock_stats(&(instance->peer->srcadr), Msg);

	idx = xd;
	idy = yd;
	imx = lat%3600000;
	imy = lon%3600000;
	xm = imx/60000.;
	ym = imy/60000.;
	sprintf(Msg, "Lat = %c %3ddeg %7.4fm,   Long = %c %3ddeg %8.5fm,  Alt = %5.2fm (%5.2fft) %s", ns, idx, xm, ew, idy, ym, hm, hft, Ht);
	record_clock_stats(&(instance->peer->srcadr), Msg);

	imx = xm;
	imy = ym;
	is  = lat%60000;
	xs  = is/1000.;
	is  = lon%60000;
	ys  = is/1000.;
	sprintf(Msg, "Lat = %c %3ddeg %2dm %5.2fs, Long = %c %3ddeg %2dm %5.2fs, Alt = %5.2fm (%5.2fft) %s", ns, idx, imx, xs, ew, idy, imy, ys, hm, hft, Ht);
	record_clock_stats(&(instance->peer->srcadr), Msg);

	/* finally, cable delay  and PPS offset */

	sprintf(Msg, "Cable delay is set to %ld ns", instance->delay);
	record_clock_stats(&(instance->peer->srcadr), Msg);

	sprintf(Msg, "PPS Offset  is set to %ld ns", instance->offset);
	record_clock_stats(&(instance->peer->srcadr), Msg);

#ifdef HAVE_PPSAPI
	if (instance->assert)
		cp = "Timing on Assert.";
	else
		cp = "Timing on Clear.";
	record_clock_stats(&(instance->peer->srcadr), cp);
#endif
}

#else
int refclock_oncore_bs;
#endif /* REFCLOCK */