1743 lines
45 KiB
C
1743 lines
45 KiB
C
/*
|
|
* This software was developed by the Computer Systems Engineering group
|
|
* at Lawrence Berkeley Laboratory under DARPA contract BG 91-66.
|
|
*
|
|
* Copyright (c) 1992 The Regents of the University of California.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the University of
|
|
* California, Lawrence Berkeley Laboratory.
|
|
* 4. The name of the University may not be used to endorse or promote
|
|
* products derived from this software without specific prior
|
|
* written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* Modified: Marc Brett <marc.brett@westgeo.com> Sept, 1999.
|
|
*
|
|
* 1. Added support for alternate PPS schemes, with code mostly
|
|
* copied from the Oncore driver (Thanks, Poul-Henning Kamp).
|
|
* This code runs on SunOS 4.1.3 with ppsclock-1.6a1 and Solaris 7.
|
|
*/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#if defined(REFCLOCK) && defined(CLOCK_MX4200) && defined(PPS)
|
|
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "ntpd.h"
|
|
#include "ntp_io.h"
|
|
#include "ntp_refclock.h"
|
|
#include "ntp_unixtime.h"
|
|
#include "ntp_stdlib.h"
|
|
|
|
#include "mx4200.h"
|
|
|
|
#ifdef HAVE_SYS_TIME_H
|
|
# include <sys/time.h>
|
|
#endif
|
|
#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 /* ! HAVE_STRUCT_PPSCLOCKEV */
|
|
|
|
/*
|
|
* This driver supports the Magnavox Model MX 4200 GPS Receiver
|
|
* adapted to precision timing applications. It requires the
|
|
* ppsclock line discipline or streams module described in the
|
|
* Line Disciplines and Streams Drivers page. It also requires a
|
|
* gadget box and 1-PPS level converter, such as described in the
|
|
* Pulse-per-second (PPS) Signal Interfacing page.
|
|
*
|
|
* It's likely that other compatible Magnavox receivers such as the
|
|
* MX 4200D, MX 9212, MX 9012R, MX 9112 will be supported by this code.
|
|
*/
|
|
|
|
/*
|
|
* Check this every time you edit the code!
|
|
*/
|
|
#define YEAR_RIGHT_NOW 1998
|
|
|
|
/*
|
|
* GPS Definitions
|
|
*/
|
|
#define DEVICE "/dev/gps%d" /* device name and unit */
|
|
#define SPEED232 B4800 /* baud */
|
|
|
|
/*
|
|
* Radio interface parameters
|
|
*/
|
|
#define PRECISION (-18) /* precision assumed (about 4 us) */
|
|
#define REFID "GPS\0" /* reference id */
|
|
#define DESCRIPTION "Magnavox MX4200 GPS Receiver" /* who we are */
|
|
#define DEFFUDGETIME 0 /* default fudge time (ms) */
|
|
|
|
#define SLEEPTIME 32 /* seconds to wait for reconfig to complete */
|
|
|
|
/*
|
|
* Position Averaging.
|
|
* Reference: Dr. Thomas A. Clark's Totally Accurate Clock (TAC) files at
|
|
* ftp://aleph.gsfc.nasa.gov/GPS/totally.accurate.clock/
|
|
* For a 6-channel Motorola Oncore, he indicates that good nominal
|
|
* HDOP and VDOP are 1.50 and 2.00 respectively. Given the relationship
|
|
* HDOP^2 = NDOP^2 + EDOP^2 and assuming EDOP and NDOP are equal, we
|
|
* have a nominal NDOP = EDOP = sqrt((HDOP*HDOP)/2). An 8-channel
|
|
* Oncore does well with HDOP=1.20 and VDOP=1.70.
|
|
*/
|
|
#define INTERVAL 1 /* Interval between position measurements (s) */
|
|
#define AVGING_TIME 24 /* Number of hours to average */
|
|
#define NOT_INITIALIZED -9999. /* initial pivot longitude */
|
|
|
|
/*
|
|
* MX4200 unit control structure.
|
|
*/
|
|
struct mx4200unit {
|
|
u_int pollcnt; /* poll message counter */
|
|
u_int polled; /* Hand in a time sample? */
|
|
u_int lastserial; /* last pps serial number */
|
|
struct ppsclockev ppsev; /* PPS control structure */
|
|
double avg_lat; /* average latitude */
|
|
double avg_lon; /* average longitude */
|
|
double avg_alt; /* average height */
|
|
double central_meridian; /* central meridian */
|
|
double filt_lat; /* latitude filter length */
|
|
double filt_lon; /* longitude filter length */
|
|
double filt_alt; /* height filter length */
|
|
double edop; /* EDOP (east DOP) */
|
|
double ndop; /* NDOP (north DOP) */
|
|
double vdop; /* VDOP (vertical DOP) */
|
|
int last_leap; /* leap second warning */
|
|
u_int moving; /* mobile platform? */
|
|
u_long sloppyclockflag; /* fudge flags */
|
|
u_int known; /* position known yet? */
|
|
u_long clamp_time; /* when to stop postion averaging */
|
|
u_long log_time; /* when to print receiver status */
|
|
};
|
|
|
|
static char pmvxg[] = "PMVXG";
|
|
|
|
/* XXX should be somewhere else */
|
|
#ifdef __GNUC__
|
|
#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5)
|
|
#ifndef __attribute__
|
|
#define __attribute__(args)
|
|
#endif /* __attribute__ */
|
|
#endif /* __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) */
|
|
#else
|
|
#ifndef __attribute__
|
|
#define __attribute__(args)
|
|
#endif /* __attribute__ */
|
|
#endif /* __GNUC__ */
|
|
/* XXX end */
|
|
|
|
/*
|
|
* Function prototypes
|
|
*/
|
|
static int mx4200_start P((int, struct peer *));
|
|
static void mx4200_shutdown P((int, struct peer *));
|
|
static void mx4200_receive P((struct recvbuf *));
|
|
static void mx4200_poll P((int, struct peer *));
|
|
|
|
static char * mx4200_parse_t P((struct peer *));
|
|
static char * mx4200_parse_p P((struct peer *));
|
|
static char * mx4200_parse_d P((struct peer *));
|
|
static char * mx4200_parse_s P((struct peer *));
|
|
#ifdef QSORT_USES_VOID_P
|
|
int mx4200_cmpl_fp P((const void *, const void *));
|
|
#else
|
|
int mx4200_cmpl_fp P((const l_fp *, const l_fp *));
|
|
#endif /* not QSORT_USES_VOID_P */
|
|
static void mx4200_config P((struct peer *));
|
|
static void mx4200_ref P((struct peer *));
|
|
static void mx4200_send P((struct peer *, char *, ...))
|
|
__attribute__ ((format (printf, 2, 3)));
|
|
static u_char mx4200_cksum P((char *, int));
|
|
static int mx4200_jday P((int, int, int));
|
|
static void mx4200_debug P((struct peer *, char *, ...))
|
|
__attribute__ ((format (printf, 2, 3)));
|
|
static int mx4200_pps P((struct peer *));
|
|
|
|
/*
|
|
* Transfer vector
|
|
*/
|
|
struct refclock refclock_mx4200 = {
|
|
mx4200_start, /* start up driver */
|
|
mx4200_shutdown, /* shut down driver */
|
|
mx4200_poll, /* transmit poll message */
|
|
noentry, /* not used (old mx4200_control) */
|
|
noentry, /* initialize driver (not used) */
|
|
noentry, /* not used (old mx4200_buginfo) */
|
|
NOFLAGS /* not used */
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
* mx4200_start - open the devices and initialize data for processing
|
|
*/
|
|
static int
|
|
mx4200_start(
|
|
int unit,
|
|
struct peer *peer
|
|
)
|
|
{
|
|
register struct mx4200unit *up;
|
|
struct refclockproc *pp;
|
|
int fd;
|
|
char gpsdev[20];
|
|
|
|
/*
|
|
* Open serial port
|
|
*/
|
|
(void)sprintf(gpsdev, DEVICE, unit);
|
|
if (!(fd = refclock_open(gpsdev, SPEED232, LDISC_PPS))) {
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Allocate unit structure
|
|
*/
|
|
if (!(up = (struct mx4200unit *) emalloc(sizeof(struct mx4200unit)))) {
|
|
(void) close(fd);
|
|
return (0);
|
|
}
|
|
memset((char *)up, 0, sizeof(struct mx4200unit));
|
|
pp = peer->procptr;
|
|
pp->io.clock_recv = mx4200_receive;
|
|
pp->io.srcclock = (caddr_t)peer;
|
|
pp->io.datalen = 0;
|
|
pp->io.fd = fd;
|
|
if (!io_addclock(&pp->io)) {
|
|
(void) close(fd);
|
|
free(up);
|
|
return (0);
|
|
}
|
|
pp->unitptr = (caddr_t)up;
|
|
|
|
/*
|
|
* Initialize miscellaneous variables
|
|
*/
|
|
peer->precision = PRECISION;
|
|
pp->clockdesc = DESCRIPTION;
|
|
memcpy((char *)&pp->refid, REFID, 4);
|
|
|
|
/* Ensure the receiver is properly configured */
|
|
mx4200_config(peer);
|
|
return (1);
|
|
}
|
|
|
|
|
|
/*
|
|
* mx4200_shutdown - shut down the clock
|
|
*/
|
|
static void
|
|
mx4200_shutdown(
|
|
int unit,
|
|
struct peer *peer
|
|
)
|
|
{
|
|
register struct mx4200unit *up;
|
|
struct refclockproc *pp;
|
|
|
|
pp = peer->procptr;
|
|
up = (struct mx4200unit *)pp->unitptr;
|
|
io_closeclock(&pp->io);
|
|
free(up);
|
|
}
|
|
|
|
|
|
/*
|
|
* mx4200_config - Configure the receiver
|
|
*/
|
|
static void
|
|
mx4200_config(
|
|
struct peer *peer
|
|
)
|
|
{
|
|
char tr_mode;
|
|
int add_mode;
|
|
register struct mx4200unit *up;
|
|
struct refclockproc *pp;
|
|
|
|
pp = peer->procptr;
|
|
up = (struct mx4200unit *)pp->unitptr;
|
|
|
|
/*
|
|
* Initialize the unit variables
|
|
*
|
|
* STRANGE BEHAVIOUR WARNING: The fudge flags are not available
|
|
* at the time mx4200_start is called. These are set later,
|
|
* and so the code must be prepared to handle changing flags.
|
|
*/
|
|
up->sloppyclockflag = pp->sloppyclockflag;
|
|
if (pp->sloppyclockflag & CLK_FLAG2) {
|
|
up->moving = 1; /* Receiver on mobile platform */
|
|
msyslog(LOG_DEBUG, "mx4200_config: mobile platform");
|
|
} else {
|
|
up->moving = 0; /* Static Installation */
|
|
}
|
|
up->pollcnt = 2;
|
|
up->polled = 0;
|
|
up->known = 0;
|
|
up->avg_lat = 0.0;
|
|
up->avg_lon = 0.0;
|
|
up->avg_alt = 0.0;
|
|
up->central_meridian = NOT_INITIALIZED;
|
|
up->filt_lat = 0.0;
|
|
up->filt_lon = 0.0;
|
|
up->filt_alt = 0.0;
|
|
up->edop = 1;
|
|
up->ndop = 1;
|
|
up->vdop = 1;
|
|
up->last_leap = 0; /* LEAP_NOWARNING */
|
|
up->clamp_time = current_time + (AVGING_TIME * 60 * 60);
|
|
up->log_time = current_time + SLEEPTIME;
|
|
|
|
/*
|
|
* "007" Control Port Configuration
|
|
* Zero the output list (do it twice to flush possible junk)
|
|
*/
|
|
mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg,
|
|
PMVXG_S_PORTCONF,
|
|
/* control port output block Label */
|
|
1); /* clear current output control list (1=yes) */
|
|
/* add/delete sentences from list */
|
|
/* must be null */
|
|
/* sentence output rate (sec) */
|
|
/* precision for position output */
|
|
/* nmea version for cga & gll output */
|
|
/* pass-through control */
|
|
mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg,
|
|
PMVXG_S_PORTCONF, 1);
|
|
|
|
/*
|
|
* Request software configuration so we can syslog the firmware version
|
|
*/
|
|
mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_SOFTCONF);
|
|
|
|
/*
|
|
* "001" Initialization/Mode Control, Part A
|
|
* Where ARE we?
|
|
*/
|
|
mx4200_send(peer, "%s,%03d,,,,,,,,,,", pmvxg,
|
|
PMVXG_S_INITMODEA);
|
|
/* day of month */
|
|
/* month of year */
|
|
/* year */
|
|
/* gmt */
|
|
/* latitude DDMM.MMMM */
|
|
/* north/south */
|
|
/* longitude DDDMM.MMMM */
|
|
/* east/west */
|
|
/* height */
|
|
/* Altitude Reference 1=MSL */
|
|
|
|
/*
|
|
* "001" Initialization/Mode Control, Part B
|
|
* Start off in 2d/3d coast mode, holding altitude to last known
|
|
* value if only 3 satellites available.
|
|
*/
|
|
mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
|
|
pmvxg, PMVXG_S_INITMODEB,
|
|
3, /* 2d/3d coast */
|
|
/* reserved */
|
|
0.1, /* hor accel fact as per Steve (m/s**2) */
|
|
0.1, /* ver accel fact as per Steve (m/s**2) */
|
|
10, /* vdop */
|
|
10, /* hdop limit as per Steve */
|
|
5, /* elevation limit as per Steve (deg) */
|
|
'U', /* time output mode (UTC) */
|
|
0); /* local time offset from gmt (HHHMM) */
|
|
|
|
/*
|
|
* "023" Time Recovery Configuration
|
|
* Get UTC time from a stationary receiver.
|
|
* (Set field 1 'D' == dynamic if we are on a moving platform).
|
|
* (Set field 1 'S' == static if we are not moving).
|
|
* (Set field 1 'K' == known position if we can initialize lat/lon/alt).
|
|
*/
|
|
|
|
if (pp->sloppyclockflag & CLK_FLAG2)
|
|
up->moving = 1; /* Receiver on mobile platform */
|
|
else
|
|
up->moving = 0; /* Static Installation */
|
|
|
|
up->pollcnt = 2;
|
|
if (up->moving) {
|
|
/* dynamic: solve for pos, alt, time, while moving */
|
|
tr_mode = 'D';
|
|
} else {
|
|
/* static: solve for pos, alt, time, while stationary */
|
|
tr_mode = 'S';
|
|
}
|
|
mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg,
|
|
PMVXG_S_TRECOVCONF,
|
|
tr_mode, /* time recovery mode (see above ) */
|
|
'U', /* synchronize to UTC */
|
|
'A', /* always output a time pulse */
|
|
500, /* max time error in ns */
|
|
0, /* user bias in ns */
|
|
1); /* output "830" sentences to control port */
|
|
/* Multi-satellite mode */
|
|
|
|
/*
|
|
* Output position information (to calculate fixed installation
|
|
* location) only if we are not moving
|
|
*/
|
|
if (up->moving) {
|
|
add_mode = 2; /* delete from list */
|
|
} else {
|
|
add_mode = 1; /* add to list */
|
|
}
|
|
|
|
/*
|
|
* "007" Control Port Configuration
|
|
* Output "022" DOPs
|
|
*/
|
|
mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg,
|
|
PMVXG_S_PORTCONF,
|
|
PMVXG_D_DOPS, /* control port output block Label */
|
|
0, /* clear current output control list (0=no) */
|
|
add_mode, /* add/delete sentences from list (1=add, 2=del) */
|
|
/* must be null */
|
|
INTERVAL); /* sentence output rate (sec) */
|
|
/* precision for position output */
|
|
/* nmea version for cga & gll output */
|
|
/* pass-through control */
|
|
|
|
|
|
/*
|
|
* "007" Control Port Configuration
|
|
* Output "021" position, height, velocity reports
|
|
*/
|
|
mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg,
|
|
PMVXG_S_PORTCONF,
|
|
PMVXG_D_PHV, /* control port output block Label */
|
|
0, /* clear current output control list (0=no) */
|
|
add_mode, /* add/delete sentences from list (1=add, 2=del) */
|
|
/* must be null */
|
|
INTERVAL); /* sentence output rate (sec) */
|
|
/* precision for position output */
|
|
/* nmea version for cga & gll output */
|
|
/* pass-through control */
|
|
}
|
|
|
|
/*
|
|
* mx4200_ref - Reconfigure unit as a reference station at a known position.
|
|
*/
|
|
static void
|
|
mx4200_ref(
|
|
struct peer *peer
|
|
)
|
|
{
|
|
register struct mx4200unit *up;
|
|
struct refclockproc *pp;
|
|
double minute, lat, lon, alt;
|
|
char lats[16], lons[16];
|
|
char nsc, ewc;
|
|
|
|
pp = peer->procptr;
|
|
up = (struct mx4200unit *)pp->unitptr;
|
|
|
|
/* Should never happen! */
|
|
if (up->moving) return;
|
|
|
|
/*
|
|
* Set up to output status information in the near future
|
|
*/
|
|
up->log_time = current_time + SLEEPTIME;
|
|
|
|
/*
|
|
* "007" Control Port Configuration
|
|
* Stop outputting "022" DOPs
|
|
*/
|
|
mx4200_send(peer, "%s,%03d,%03d,%d,%d,,,,,", pmvxg,
|
|
PMVXG_S_PORTCONF,
|
|
PMVXG_D_DOPS, /* control port output block Label */
|
|
0, /* clear current output control list (0=no) */
|
|
2); /* add/delete sentences from list (2=delete) */
|
|
/* must be null */
|
|
/* sentence output rate (sec) */
|
|
/* precision for position output */
|
|
/* nmea version for cga & gll output */
|
|
/* pass-through control */
|
|
|
|
/*
|
|
* "007" Control Port Configuration
|
|
* Stop outputting "021" position, height, velocity reports
|
|
*/
|
|
mx4200_send(peer, "%s,%03d,%03d,%d,%d,,,,,", pmvxg,
|
|
PMVXG_S_PORTCONF,
|
|
PMVXG_D_PHV, /* control port output block Label */
|
|
0, /* clear current output control list (0=no) */
|
|
2); /* add/delete sentences from list (2=delete) */
|
|
/* must be null */
|
|
/* sentence output rate (sec) */
|
|
/* precision for position output */
|
|
/* nmea version for cga & gll output */
|
|
/* pass-through control */
|
|
|
|
/*
|
|
* "001" Initialization/Mode Control, Part B
|
|
* Put receiver in fully-constrained 2d nav mode
|
|
*/
|
|
mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
|
|
pmvxg, PMVXG_S_INITMODEB,
|
|
2, /* 2d nav */
|
|
/* reserved */
|
|
0.1, /* hor accel fact as per Steve (m/s**2) */
|
|
0.1, /* ver accel fact as per Steve (m/s**2) */
|
|
10, /* vdop */
|
|
10, /* hdop limit as per Steve */
|
|
5, /* elevation limit as per Steve (deg) */
|
|
'U', /* time output mode (UTC) */
|
|
0); /* local time offset from gmt (HHHMM) */
|
|
|
|
/*
|
|
* "023" Time Recovery Configuration
|
|
* Get UTC time from a stationary receiver. Solve for time only.
|
|
* This should improve the time resolution dramatically.
|
|
*/
|
|
mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg,
|
|
PMVXG_S_TRECOVCONF,
|
|
'K', /* known position: solve for time only */
|
|
'U', /* synchronize to UTC */
|
|
'A', /* always output a time pulse */
|
|
500, /* max time error in ns */
|
|
0, /* user bias in ns */
|
|
1); /* output "830" sentences to control port */
|
|
/* Multi-satellite mode */
|
|
|
|
/*
|
|
* "000" Initialization/Mode Control - Part A
|
|
* Fix to our averaged position.
|
|
*/
|
|
if (up->central_meridian != NOT_INITIALIZED) {
|
|
up->avg_lon += up->central_meridian;
|
|
if (up->avg_lon < -180.0) up->avg_lon += 360.0;
|
|
if (up->avg_lon > 180.0) up->avg_lon -= 360.0;
|
|
}
|
|
|
|
if (up->avg_lat >= 0.0) {
|
|
lat = up->avg_lat;
|
|
nsc = 'N';
|
|
} else {
|
|
lat = up->avg_lat * (-1.0);
|
|
nsc = 'S';
|
|
}
|
|
if (up->avg_lon >= 0.0) {
|
|
lon = up->avg_lon;
|
|
ewc = 'E';
|
|
} else {
|
|
lon = up->avg_lon * (-1.0);
|
|
ewc = 'W';
|
|
}
|
|
alt = up->avg_alt;
|
|
minute = (lat - (double)(int)lat) * 60.0;
|
|
sprintf(lats,"%02d%02.4f", (int)lat, minute);
|
|
minute = (lon - (double)(int)lon) * 60.0;
|
|
sprintf(lons,"%03d%02.4f", (int)lon, minute);
|
|
|
|
mx4200_send(peer, "%s,%03d,,,,,%s,%c,%s,%c,%.2f,%d", pmvxg,
|
|
PMVXG_S_INITMODEA,
|
|
/* day of month */
|
|
/* month of year */
|
|
/* year */
|
|
/* gmt */
|
|
lats, /* latitude DDMM.MMMM */
|
|
nsc, /* north/south */
|
|
lons, /* longitude DDDMM.MMMM */
|
|
ewc, /* east/west */
|
|
alt, /* Altitude */
|
|
1); /* Altitude Reference (0=WGS84 ellipsoid, 1=MSL geoid) */
|
|
|
|
msyslog(LOG_DEBUG,
|
|
"mx4200: reconfig to fixed location: %s %c, %s %c, %.2f m",
|
|
lats, nsc, lons, ewc, alt );
|
|
|
|
}
|
|
|
|
/*
|
|
* mx4200_poll - mx4200 watchdog routine
|
|
*/
|
|
static void
|
|
mx4200_poll(
|
|
int unit,
|
|
struct peer *peer
|
|
)
|
|
{
|
|
register struct mx4200unit *up;
|
|
struct refclockproc *pp;
|
|
|
|
pp = peer->procptr;
|
|
up = (struct mx4200unit *)pp->unitptr;
|
|
|
|
/*
|
|
* You don't need to poll this clock. It puts out timecodes
|
|
* once per second. If asked for a timestamp, take note.
|
|
* The next time a timecode comes in, it will be fed back.
|
|
*/
|
|
|
|
/*
|
|
* If we haven't had a response in a while, reset the receiver.
|
|
*/
|
|
if (up->pollcnt > 0) {
|
|
up->pollcnt--;
|
|
} else {
|
|
refclock_report(peer, CEVNT_TIMEOUT);
|
|
|
|
/*
|
|
* Request a "000" status message which should trigger a
|
|
* reconfig
|
|
*/
|
|
mx4200_send(peer, "%s,%03d",
|
|
"CDGPQ", /* query from CDU to GPS */
|
|
PMVXG_D_STATUS); /* label of desired sentence */
|
|
}
|
|
|
|
/*
|
|
* polled every 64 seconds. Ask mx4200_receive to hand in
|
|
* a timestamp.
|
|
*/
|
|
up->polled = 1;
|
|
pp->polls++;
|
|
|
|
/*
|
|
* Output receiver status information.
|
|
*/
|
|
if ((up->log_time > 0) && (current_time > up->log_time)) {
|
|
up->log_time = 0;
|
|
/*
|
|
* Output the following messages once, for debugging.
|
|
* "004" Mode Data
|
|
* "523" Time Recovery Parameters
|
|
*/
|
|
mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_MODEDATA);
|
|
mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_TRECOVUSEAGE);
|
|
}
|
|
}
|
|
|
|
static char char2hex[] = "0123456789ABCDEF";
|
|
|
|
/*
|
|
* mx4200_receive - receive gps data
|
|
*/
|
|
static void
|
|
mx4200_receive(
|
|
struct recvbuf *rbufp
|
|
)
|
|
{
|
|
register struct mx4200unit *up;
|
|
struct refclockproc *pp;
|
|
struct peer *peer;
|
|
char *cp;
|
|
int sentence_type;
|
|
u_char ck;
|
|
|
|
/*
|
|
* Initialize pointers and read the timecode and timestamp.
|
|
*/
|
|
peer = (struct peer *)rbufp->recv_srcclock;
|
|
pp = peer->procptr;
|
|
up = (struct mx4200unit *)pp->unitptr;
|
|
|
|
/*
|
|
* If operating mode has been changed, then reinitialize the receiver
|
|
* before doing anything else.
|
|
*/
|
|
if ((pp->sloppyclockflag & CLK_FLAG2) !=
|
|
(up->sloppyclockflag & CLK_FLAG2)) {
|
|
up->sloppyclockflag = pp->sloppyclockflag;
|
|
mx4200_debug(peer,
|
|
"mx4200_receive: mode switch: reset receiver\n");
|
|
mx4200_config(peer);
|
|
return;
|
|
}
|
|
up->sloppyclockflag = pp->sloppyclockflag;
|
|
|
|
/*
|
|
* Read clock output. Automatically handles STREAMS, CLKLDISC.
|
|
*/
|
|
pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec);
|
|
|
|
/*
|
|
* There is a case where <cr><lf> generates 2 timestamps.
|
|
*/
|
|
if (pp->lencode == 0)
|
|
return;
|
|
|
|
up->pollcnt = 2;
|
|
pp->a_lastcode[pp->lencode] = '\0';
|
|
record_clock_stats(&peer->srcadr, pp->a_lastcode);
|
|
mx4200_debug(peer, "mx4200_receive: %d %s\n",
|
|
pp->lencode, pp->a_lastcode);
|
|
|
|
/*
|
|
* The structure of the control port sentences is based on the
|
|
* NMEA-0183 Standard for interfacing Marine Electronics
|
|
* Navigation Devices (Version 1.5)
|
|
*
|
|
* $PMVXG,XXX, ....................*CK<cr><lf>
|
|
*
|
|
* $ Sentence Start Identifier (reserved char)
|
|
* (Start-of-Sentence Identifier)
|
|
* P Special ID (Proprietary)
|
|
* MVX Originator ID (Magnavox)
|
|
* G Interface ID (GPS)
|
|
* , Field Delimiters (reserved char)
|
|
* XXX Sentence Type
|
|
* ...... Data
|
|
* * Checksum Field Delimiter (reserved char)
|
|
* CK Checksum
|
|
* <cr><lf> Carriage-Return/Line Feed (reserved chars)
|
|
* (End-of-Sentence Identifier)
|
|
*
|
|
* Reject if any important landmarks are missing.
|
|
*/
|
|
cp = pp->a_lastcode + pp->lencode - 3;
|
|
if (cp < pp->a_lastcode || *pp->a_lastcode != '$' || cp[0] != '*' ) {
|
|
mx4200_debug(peer, "mx4200_receive: bad format\n");
|
|
refclock_report(peer, CEVNT_BADREPLY);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check and discard the checksum
|
|
*/
|
|
ck = mx4200_cksum(&pp->a_lastcode[1], pp->lencode - 4);
|
|
if (char2hex[ck >> 4] != cp[1] || char2hex[ck & 0xf] != cp[2]) {
|
|
mx4200_debug(peer, "mx4200_receive: bad checksum\n");
|
|
refclock_report(peer, CEVNT_BADREPLY);
|
|
return;
|
|
}
|
|
*cp = '\0';
|
|
|
|
/*
|
|
* Get the sentence type.
|
|
*/
|
|
sentence_type = 0;
|
|
if ((cp = strchr(pp->a_lastcode, ',')) == NULL) {
|
|
mx4200_debug(peer, "mx4200_receive: no sentence\n");
|
|
refclock_report(peer, CEVNT_BADREPLY);
|
|
return;
|
|
}
|
|
cp++;
|
|
sentence_type = strtol(cp, &cp, 10);
|
|
|
|
/*
|
|
* "000" Status message
|
|
*/
|
|
|
|
if (sentence_type == PMVXG_D_STATUS) {
|
|
/*
|
|
* XXX
|
|
* Since we configure the receiver to not give us status
|
|
* messages and since the receiver outputs status messages by
|
|
* default after being reset to factory defaults when sent the
|
|
* "$PMVXG,018,C\r\n" message, any status message we get
|
|
* indicates the reciever needs to be initialized; thus, it is
|
|
* not necessary to decode the status message.
|
|
*/
|
|
if ((cp = mx4200_parse_s(peer)) != NULL) {
|
|
mx4200_debug(peer,
|
|
"mx4200_receive: status: %s\n", cp);
|
|
}
|
|
mx4200_debug(peer, "mx4200_receive: reset receiver\n");
|
|
mx4200_config(peer);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* "021" Position, Height, Velocity message,
|
|
* if we are still averaging our position
|
|
*/
|
|
if (sentence_type == PMVXG_D_PHV && !up->known) {
|
|
/*
|
|
* Parse the message, calculating our averaged position.
|
|
*/
|
|
if ((cp = mx4200_parse_p(peer)) != NULL) {
|
|
mx4200_debug(peer, "mx4200_receive: pos: %s\n", cp);
|
|
return;
|
|
}
|
|
mx4200_debug(peer,
|
|
"mx4200_receive: position avg %.9f %.9f %.4f\n",
|
|
up->avg_lat, up->avg_lon, up->avg_alt);
|
|
mx4200_debug(peer,
|
|
"mx4200_receive: position len %.4f %.4f %.4f\n",
|
|
up->filt_lat, up->filt_lon, up->filt_alt);
|
|
mx4200_debug(peer,
|
|
"mx4200_receive: position dop %.1f %.1f %.1f\n",
|
|
up->ndop, up->edop, up->vdop);
|
|
/*
|
|
* Reinitialize as a reference station
|
|
* if position is well known.
|
|
*/
|
|
if (current_time > up->clamp_time) {
|
|
up->known++;
|
|
mx4200_debug(peer, "mx4200_receive: reconfiguring!\n");
|
|
mx4200_ref(peer);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* "022" DOPs, if we are still averaging our position
|
|
*/
|
|
if (sentence_type == PMVXG_D_DOPS && !up->known) {
|
|
if ((cp = mx4200_parse_d(peer)) != NULL) {
|
|
mx4200_debug(peer, "mx4200_receive: dop: %s\n", cp);
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Print to the syslog:
|
|
* "004" Mode Data
|
|
* "030" Software Configuration
|
|
* "523" Time Recovery Parameters Currently in Use
|
|
*/
|
|
if (sentence_type == PMVXG_D_MODEDATA ||
|
|
sentence_type == PMVXG_D_SOFTCONF ||
|
|
sentence_type == PMVXG_D_TRECOVUSEAGE ) {
|
|
if ((cp = mx4200_parse_s(peer)) != NULL) {
|
|
mx4200_debug(peer,
|
|
"mx4200_receive: multi-record: %s\n", cp);
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* "830" Time Recovery Results message
|
|
*/
|
|
if (sentence_type == PMVXG_D_TRECOVOUT) {
|
|
|
|
/*
|
|
* Capture the last PPS signal.
|
|
* Precision timestamp is returned in pp->lastrec
|
|
*/
|
|
if (mx4200_pps(peer) != NULL) {
|
|
mx4200_debug(peer, "mx4200_receive: pps failure\n");
|
|
refclock_report(peer, CEVNT_FAULT);
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* Parse the time recovery message, and keep the info
|
|
* to print the pretty billboards.
|
|
*/
|
|
if ((cp = mx4200_parse_t(peer)) != NULL) {
|
|
mx4200_debug(peer, "mx4200_receive: time: %s\n", cp);
|
|
refclock_report(peer, CEVNT_BADREPLY);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Add the new sample to a median filter.
|
|
*/
|
|
if (!refclock_process(pp)) {
|
|
mx4200_debug(peer,"mx4200_receive: offset: %.6f\n",
|
|
pp->offset);
|
|
refclock_report(peer, CEVNT_BADTIME);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* The clock will blurt a timecode every second but we only
|
|
* want one when polled. If we havn't been polled, bail out.
|
|
*/
|
|
if (!up->polled)
|
|
return;
|
|
|
|
/*
|
|
* Return offset and dispersion to control module. We use
|
|
* lastrec as both the reference time and receive time in
|
|
* order to avoid being cute, like setting the reference time
|
|
* later than the receive time, which may cause a paranoid
|
|
* protocol module to chuck out the data.
|
|
*/
|
|
mx4200_debug(peer, "mx4200_receive: process time: ");
|
|
mx4200_debug(peer, "%4d-%03d %02d:%02d:%02d at %s, %.6f\n",
|
|
pp->year, pp->day, pp->hour, pp->minute, pp->second,
|
|
prettydate(&pp->lastrec), pp->offset);
|
|
|
|
refclock_receive(peer);
|
|
|
|
/*
|
|
* We have succeeded in answering the poll.
|
|
* Turn off the flag and return
|
|
*/
|
|
up->polled = 0;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Ignore all other sentence types
|
|
*/
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* Parse a mx4200 time recovery message. Returns a string if error.
|
|
*
|
|
* A typical message looks like this. Checksum has already been stripped.
|
|
*
|
|
* $PMVXG,830,T,YYYY,MM,DD,HH:MM:SS,U,S,FFFFFF,PPPPP,BBBBBB,LL
|
|
*
|
|
* Field Field Contents
|
|
* ----- --------------
|
|
* Block Label: $PMVXG
|
|
* Sentence Type: 830=Time Recovery Results
|
|
* This sentence is output approximately 1 second
|
|
* preceding the 1PPS output. It indicates the
|
|
* exact time of the next pulse, whether or not the
|
|
* time mark will be valid (based on operator-specified
|
|
* error tolerance), the time to which the pulse is
|
|
* synchronized, the receiver operating mode,
|
|
* and the time error of the *last* 1PPS output.
|
|
* 1 char Time Mark Valid: T=Valid, F=Not Valid
|
|
* 2 int Year: 1993-
|
|
* 3 int Month of Year: 1-12
|
|
* 4 int Day of Month: 1-31
|
|
* 5 int Time of Day: HH:MM:SS
|
|
* 6 char Time Synchronization: U=UTC, G=GPS
|
|
* 7 char Time Recovery Mode: D=Dynamic, S=Static,
|
|
* K=Known Position, N=No Time Recovery
|
|
* 8 int Oscillator Offset: The filter's estimate of the oscillator
|
|
* frequency error, in parts per billion (ppb).
|
|
* 9 int Time Mark Error: The computed error of the *last* pulse
|
|
* output, in nanoseconds.
|
|
* 10 int User Time Bias: Operator specified bias, in nanoseconds
|
|
* 11 int Leap Second Flag: Indicates that a leap second will
|
|
* occur. This value is usually zero, except during
|
|
* the week prior to the leap second occurence, when
|
|
* this value will be set to +1 or -1. A value of
|
|
* +1 indicates that GPS time will be 1 second
|
|
* further ahead of UTC time.
|
|
*
|
|
*/
|
|
static char *
|
|
mx4200_parse_t(
|
|
struct peer *peer
|
|
)
|
|
{
|
|
struct refclockproc *pp;
|
|
struct mx4200unit *up;
|
|
char time_mark_valid, time_sync, op_mode;
|
|
int sentence_type, valid;
|
|
int year, day_of_year, month, day_of_month, hour, minute, second, leapsec;
|
|
int oscillator_offset, time_mark_error, time_bias;
|
|
|
|
pp = peer->procptr;
|
|
up = (struct mx4200unit *)pp->unitptr;
|
|
|
|
leapsec = 0; /* Not all receivers output leap second warnings (!) */
|
|
sscanf(pp->a_lastcode, "$PMVXG,%d,%c,%d,%d,%d,%d:%d:%d,%c,%c,%d,%d,%d,%d",
|
|
&sentence_type, &time_mark_valid, &year, &month, &day_of_month,
|
|
&hour, &minute, &second, &time_sync, &op_mode, &oscillator_offset,
|
|
&time_mark_error, &time_bias, &leapsec);
|
|
|
|
if (sentence_type != PMVXG_D_TRECOVOUT)
|
|
return ("wrong rec-type");
|
|
|
|
switch (time_mark_valid) {
|
|
case 'T':
|
|
valid = 1;
|
|
break;
|
|
case 'F':
|
|
valid = 0;
|
|
break;
|
|
default:
|
|
return ("bad pulse-valid");
|
|
}
|
|
|
|
switch (time_sync) {
|
|
case 'G':
|
|
return ("synchronized to GPS; should be UTC");
|
|
case 'U':
|
|
break; /* UTC -> ok */
|
|
default:
|
|
return ("not synchronized to UTC");
|
|
}
|
|
|
|
/*
|
|
* Check for insane time (allow for possible leap seconds)
|
|
*/
|
|
if (second > 60 || minute > 59 || hour > 23 ||
|
|
second < 0 || minute < 0 || hour < 0) {
|
|
mx4200_debug(peer,
|
|
"mx4200_parse_t: bad time %02d:%02d:%02d",
|
|
hour, minute, second);
|
|
if (leapsec != 0)
|
|
mx4200_debug(peer, " (leap %+d\n)", leapsec);
|
|
mx4200_debug(peer, "\n");
|
|
refclock_report(peer, CEVNT_BADTIME);
|
|
return ("bad time");
|
|
}
|
|
if ( second == 60 ) {
|
|
msyslog(LOG_DEBUG,
|
|
"mx4200: leap second! %02d:%02d:%02d",
|
|
hour, minute, second);
|
|
}
|
|
|
|
/*
|
|
* Check for insane date
|
|
* (Certainly can't be any year before this code was last altered!)
|
|
*/
|
|
if (day_of_month > 31 || month > 12 ||
|
|
day_of_month < 1 || month < 1 || year < YEAR_RIGHT_NOW) {
|
|
mx4200_debug(peer,
|
|
"mx4200_parse_t: bad date (%4d-%02d-%02d)\n",
|
|
year, month, day_of_month);
|
|
refclock_report(peer, CEVNT_BADDATE);
|
|
return ("bad date");
|
|
}
|
|
|
|
/*
|
|
* Silly Hack for MX4200:
|
|
* ASCII message is for *next* 1PPS signal, but we have the
|
|
* timestamp for the *last* 1PPS signal. So we have to subtract
|
|
* a second. Discard if we are on a month boundary to avoid
|
|
* possible leap seconds and leap days.
|
|
*/
|
|
second--;
|
|
if (second < 0) {
|
|
second = 59;
|
|
minute--;
|
|
if (minute < 0) {
|
|
minute = 59;
|
|
hour--;
|
|
if (hour < 0) {
|
|
hour = 23;
|
|
day_of_month--;
|
|
if (day_of_month < 1) {
|
|
return ("sorry, month boundary");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Calculate Julian date
|
|
*/
|
|
if (!(day_of_year = mx4200_jday(year, month, day_of_month))) {
|
|
mx4200_debug(peer,
|
|
"mx4200_parse_t: bad julian date %d (%4d-%02d-%02d)\n",
|
|
day_of_year, year, month, day_of_month);
|
|
refclock_report(peer, CEVNT_BADDATE);
|
|
return("invalid julian date");
|
|
}
|
|
|
|
/*
|
|
* Setup leap second indicator
|
|
*/
|
|
switch (leapsec) {
|
|
case 0:
|
|
pp->leap = LEAP_NOWARNING;
|
|
break;
|
|
case 1:
|
|
pp->leap = LEAP_ADDSECOND;
|
|
break;
|
|
case -1:
|
|
pp->leap = LEAP_DELSECOND;
|
|
break;
|
|
default:
|
|
pp->leap = LEAP_NOTINSYNC;
|
|
}
|
|
|
|
/*
|
|
* Any change to the leap second warning status?
|
|
*/
|
|
if (leapsec != up->last_leap ) {
|
|
msyslog(LOG_DEBUG,
|
|
"mx4200: leap second warning: %d to %d (%d)",
|
|
up->last_leap, leapsec, pp->leap);
|
|
}
|
|
up->last_leap = leapsec;
|
|
|
|
/*
|
|
* Copy time data for billboard monitoring.
|
|
*/
|
|
|
|
pp->year = year;
|
|
pp->day = day_of_year;
|
|
pp->hour = hour;
|
|
pp->minute = minute;
|
|
pp->second = second;
|
|
pp->msec = 0;
|
|
pp->usec = 0;
|
|
|
|
/*
|
|
* Toss if sentence is marked invalid
|
|
*/
|
|
if (!valid || pp->leap == LEAP_NOTINSYNC) {
|
|
mx4200_debug(peer, "mx4200_parse_t: time mark not valid\n");
|
|
refclock_report(peer, CEVNT_BADTIME);
|
|
return ("pulse invalid");
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Calculate the checksum
|
|
*/
|
|
static u_char
|
|
mx4200_cksum(
|
|
register char *cp,
|
|
register int n
|
|
)
|
|
{
|
|
register u_char ck;
|
|
|
|
for (ck = 0; n-- > 0; cp++)
|
|
ck ^= *cp;
|
|
return (ck);
|
|
}
|
|
|
|
/*
|
|
* Tables to compute the day of year. Viva la leap.
|
|
*/
|
|
static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
|
static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
|
|
|
/*
|
|
* Calculate the the Julian Day
|
|
*/
|
|
static int
|
|
mx4200_jday(
|
|
int year,
|
|
int month,
|
|
int day_of_month
|
|
)
|
|
{
|
|
register int day, i;
|
|
int leap_year;
|
|
|
|
/*
|
|
* Is this a leap year ?
|
|
*/
|
|
if (year % 4) {
|
|
leap_year = 0; /* FALSE */
|
|
} else {
|
|
if (year % 100) {
|
|
leap_year = 1; /* TRUE */
|
|
} else {
|
|
if (year % 400) {
|
|
leap_year = 0; /* FALSE */
|
|
} else {
|
|
leap_year = 1; /* TRUE */
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Calculate the Julian Date
|
|
*/
|
|
day = day_of_month;
|
|
|
|
if (leap_year) {
|
|
/* a leap year */
|
|
if (day > day2tab[month - 1]) {
|
|
return (0);
|
|
}
|
|
for (i = 0; i < month - 1; i++)
|
|
day += day2tab[i];
|
|
} else {
|
|
/* not a leap year */
|
|
if (day > day1tab[month - 1]) {
|
|
return (0);
|
|
}
|
|
for (i = 0; i < month - 1; i++)
|
|
day += day1tab[i];
|
|
}
|
|
return (day);
|
|
}
|
|
|
|
/*
|
|
* Parse a mx4200 position/height/velocity sentence.
|
|
*
|
|
* A typical message looks like this. Checksum has already been stripped.
|
|
*
|
|
* $PMVXG,021,SSSSSS.SS,DDMM.MMMM,N,DDDMM.MMMM,E,HHHHH.H,GGGG.G,EEEE.E,WWWW.W,MM
|
|
*
|
|
* Field Field Contents
|
|
* ----- --------------
|
|
* Block Label: $PMVXG
|
|
* Sentence Type: 021=Position, Height Velocity Data
|
|
* This sentence gives the receiver position, height,
|
|
* navigation mode, and velocity north/east.
|
|
* *This sentence is intended for post-analysis
|
|
* applications.*
|
|
* 1 float UTC measurement time (seconds into week)
|
|
* 2 float WGS-84 Lattitude (degrees, minutes)
|
|
* 3 char N=North, S=South
|
|
* 4 float WGS-84 Longitude (degrees, minutes)
|
|
* 5 char E=East, W=West
|
|
* 6 float Altitude (meters above mean sea level)
|
|
* 7 float Geoidal height (meters)
|
|
* 8 float East velocity (m/sec)
|
|
* 9 float West Velocity (m/sec)
|
|
* 10 int Navigation Mode
|
|
* Mode if navigating:
|
|
* 1 = Position from remote device
|
|
* 2 = 2-D position
|
|
* 3 = 3-D position
|
|
* 4 = 2-D differential position
|
|
* 5 = 3-D differential position
|
|
* 6 = Static
|
|
* 8 = Position known -- reference station
|
|
* 9 = Position known -- Navigator
|
|
* Mode if not navigating:
|
|
* 51 = Too few satellites
|
|
* 52 = DOPs too large
|
|
* 53 = Position STD too large
|
|
* 54 = Velocity STD too large
|
|
* 55 = Too many iterations for velocity
|
|
* 56 = Too many iterations for position
|
|
* 57 = 3 sat startup failed
|
|
* 58 = Command abort
|
|
*/
|
|
static char *
|
|
mx4200_parse_p(
|
|
struct peer *peer
|
|
)
|
|
{
|
|
struct refclockproc *pp;
|
|
struct mx4200unit *up;
|
|
int sentence_type, mode;
|
|
double mtime, lat, lon, alt, geoid, vele, veln, weight;
|
|
char north_south, east_west;
|
|
|
|
pp = peer->procptr;
|
|
up = (struct mx4200unit *)pp->unitptr;
|
|
|
|
/* Should never happen! */
|
|
if (up->moving) return ("mobile platform - no pos!");
|
|
|
|
sscanf ( pp->a_lastcode, "$PMVXG,%d,%lf,%lf,%c,%lf,%c,%lf,%lf,%lf,%lf,%d",
|
|
&sentence_type, &mtime, &lat, &north_south, &lon, &east_west, &alt,
|
|
&geoid, &vele, &veln, &mode);
|
|
|
|
/* Sentence type */
|
|
if (sentence_type != PMVXG_D_PHV)
|
|
return ("wrong rec-type");
|
|
|
|
/*
|
|
* return if not navigating
|
|
*/
|
|
if (mode > 10)
|
|
return ("not navigating");
|
|
if (mode != 3 && mode != 5)
|
|
return ("not navigating in 3D");
|
|
|
|
/* Latitude (always +ve) and convert DDMM.MMMM to decimal */
|
|
if (lat < 0.0) return ("negative latitude");
|
|
if (lat > 9000.0) lat = 9000.0;
|
|
lat *= 0.01;
|
|
lat = ((int)lat) + (((lat - (int)lat)) * 1.6666666666666666);
|
|
|
|
/* North/South */
|
|
switch (north_south) {
|
|
case 'N':
|
|
break;
|
|
case 'S':
|
|
lat *= -1.0;
|
|
break;
|
|
default:
|
|
return ("invalid north/south indicator");
|
|
}
|
|
|
|
/* Longitude (always +ve) and convert DDDMM.MMMM to decimal */
|
|
if (lon < 0.0) return ("negative longitude");
|
|
if (lon > 180.0) lon = 180.0;
|
|
lon *= 0.01;
|
|
lon = ((int)lon) + (((lon - (int)lon)) * 1.6666666666666666);
|
|
|
|
/* East/West */
|
|
switch (east_west) {
|
|
case 'E':
|
|
break;
|
|
case 'W':
|
|
lon *= -1.0;
|
|
break;
|
|
default:
|
|
return ("invalid east/west indicator");
|
|
}
|
|
|
|
/*
|
|
* Normalize longitude to near 0 degrees.
|
|
* Assume all data are clustered around first reading.
|
|
*/
|
|
if (up->central_meridian == NOT_INITIALIZED) {
|
|
up->central_meridian = lon;
|
|
mx4200_debug(peer,
|
|
"mx4200_receive: central meridian = %.9f \n",
|
|
up->central_meridian);
|
|
}
|
|
lon -= up->central_meridian;
|
|
if (lon < -180.0) lon += 360.0;
|
|
if (lon > 180.0) lon -= 360.0;
|
|
|
|
/*
|
|
* Calculate running weighted averages
|
|
*/
|
|
weight = 1. / up->edop;
|
|
weight *= weight;
|
|
up->avg_lon = (up->filt_lon * up->avg_lon) + (weight * lon);
|
|
up->filt_lon += weight;
|
|
up->avg_lon = up->avg_lon / up->filt_lon;
|
|
|
|
weight = 1. / up->ndop;
|
|
weight *= weight;
|
|
up->avg_lat = (up->filt_lat * up->avg_lat) + (weight * lat);
|
|
up->filt_lat += weight;
|
|
up->avg_lat = up->avg_lat / up->filt_lat;
|
|
|
|
weight = 1. / up->vdop;
|
|
weight *= weight;
|
|
up->avg_alt = (up->filt_alt * up->avg_alt) + (weight * alt);
|
|
up->filt_alt += weight;
|
|
up->avg_alt = up->avg_alt / up->filt_alt;
|
|
|
|
mx4200_debug(peer,
|
|
"mx4200_receive: position rdg %.9f %.9f %.4f (CM=%.9f)\n",
|
|
lat, lon, alt, up->central_meridian);
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Parse a mx4200 DOP sentence.
|
|
*
|
|
* A typical message looks like this. Checksum has already been stripped.
|
|
*
|
|
* $PMVXG,022,SSSSSS.SSEE.E,NN.N,VV.V,XX,XX,XX,XX,XX,XX
|
|
*
|
|
* Field Field Contents
|
|
* ----- --------------
|
|
* Block Label: $PMVXG
|
|
* Sentence Type: 022=DOPs. The DOP values in this sentence
|
|
* correspond to the satellites listed. The PRNs in
|
|
* the message are listed in receiver channel number order
|
|
* 1 UTC measurement time (seconds into week)
|
|
* 2 EDOP (east DOP)
|
|
* 3 NDOP (north DOP)
|
|
* 4 VDOP (vertical DOP)
|
|
* 5 PRN on channel 1
|
|
* 6 PRN on channel 2
|
|
* 7 PRN on channel 3
|
|
* 8 PRN on channel 4
|
|
* 9 PRN on channel 5
|
|
* 10 PRN on channel 6
|
|
* 11 PRN on channel 7 (12-channel receivers only)
|
|
* 12 PRN on channel 8 (12-channel receivers only)
|
|
* 13 PRN on channel 9 (12-channel receivers only)
|
|
* 14 PRN on channel 10 (12-channel receivers only)
|
|
* 15 PRN on channel 11 (12-channel receivers only)
|
|
* 16 PRN on channel 12 (12-channel receivers only)
|
|
*/
|
|
static char *
|
|
mx4200_parse_d(
|
|
struct peer *peer
|
|
)
|
|
{
|
|
struct refclockproc *pp;
|
|
struct mx4200unit *up;
|
|
int sentence_type;
|
|
double mtime, edop, ndop, vdop;
|
|
|
|
pp = peer->procptr;
|
|
up = (struct mx4200unit *)pp->unitptr;
|
|
|
|
/* Should never happen! */
|
|
if (up->moving) return ("mobile platform - no dop!");
|
|
|
|
sscanf ( pp->a_lastcode, "$PMVXG,%d,%lf,%lf,%lf,%lf",
|
|
&sentence_type, &mtime, &edop, &ndop, &vdop);
|
|
|
|
/* Sentence type */
|
|
if (sentence_type != PMVXG_D_DOPS)
|
|
return ("wrong rec-type");
|
|
|
|
/* Update values */
|
|
if (edop <= 0.0 || ndop <= 0.0 || vdop <= 0.0)
|
|
return ("nonpositive dop");
|
|
up->edop = edop;
|
|
up->ndop = ndop;
|
|
up->vdop = vdop;
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Parse a mx4200 Status sentence
|
|
* Parse a mx4200 Mode Data sentence
|
|
* Parse a mx4200 Software Configuration sentence
|
|
* Parse a mx4200 Time Recovery Parameters Currently in Use sentence
|
|
* (used only for logging raw strings)
|
|
*
|
|
* A typical message looks like this. Checksum has already been stripped.
|
|
*
|
|
* $PMVXG,000,XXX,XX,X,HHMM,X
|
|
*
|
|
* Field Field Contents
|
|
* ----- --------------
|
|
* Block Label: $PMVXG
|
|
* Sentence Type: 000=Status.
|
|
* Returns status of the receiver to the controller.
|
|
* 1 Current Receiver Status:
|
|
* ACQ = Satellite re-acquisition
|
|
* ALT = Constellation selection
|
|
* COR = Providing corrections (for reference stations only)
|
|
* IAC = Initial acquisition
|
|
* IDL = Idle, no satellites
|
|
* NAV = Navigation
|
|
* STS = Search the Sky (no almanac available)
|
|
* TRK = Tracking
|
|
* 2 Number of satellites that should be visible
|
|
* 3 Number of satellites being tracked
|
|
* 4 Time since last navigation status if not currently navigating
|
|
* (hours, minutes)
|
|
* 5 Initialization status:
|
|
* 0 = Waiting for initialization parameters
|
|
* 1 = Initialization completed
|
|
*
|
|
* A typical message looks like this. Checksum has already been stripped.
|
|
*
|
|
* $PMVXG,004,C,R,D,H.HH,V.VV,TT,HHHH,VVVV,T
|
|
*
|
|
* Field Field Contents
|
|
* ----- --------------
|
|
* Block Label: $PMVXG
|
|
* Sentence Type: 004=Software Configuration.
|
|
* Defines the navigation mode and criteria for
|
|
* acceptable navigation for the receiver.
|
|
* 1 Constrain Altitude Mode:
|
|
* 0 = Auto. Constrain altitude (2-D solution) and use
|
|
* manual altitude input when 3 sats avalable. Do
|
|
* not constrain altitude (3-D solution) when 4 sats
|
|
* available.
|
|
* 1 = Always constrain altitude (2-D solution).
|
|
* 2 = Never constrain altitude (3-D solution).
|
|
* 3 = Coast. Constrain altitude (2-D solution) and use
|
|
* last GPS altitude calculation when 3 sats avalable.
|
|
* Do not constrain altitude (3-D solution) when 4 sats
|
|
* available.
|
|
* 2 Altitude Reference: (always 0 for MX4200)
|
|
* 0 = Ellipsoid
|
|
* 1 = Geoid (MSL)
|
|
* 3 Differential Navigation Control:
|
|
* 0 = Disabled
|
|
* 1 = Enabled
|
|
* 4 Horizontal Acceleration Constant (m/sec**2)
|
|
* 5 Vertical Acceleration Constant (m/sec**2) (0 for MX4200)
|
|
* 6 Tracking Elevation Limit (degrees)
|
|
* 7 HDOP Limit
|
|
* 8 VDOP Limit
|
|
* 9 Time Output Mode:
|
|
* U = UTC
|
|
* L = Local time
|
|
* 10 Local Time Offset (minutes) (absent on MX4200)
|
|
*
|
|
* A typical message looks like this. Checksum has already been stripped.
|
|
*
|
|
* $PMVXG,030,NNNN,FFF
|
|
*
|
|
* Field Field Contents
|
|
* ----- --------------
|
|
* Block Label: $PMVXG
|
|
* Sentence Type: 030=Software Configuration.
|
|
* This sentence contains the navigation processor
|
|
* and baseband firmware version numbers.
|
|
* 1 Nav Processor Version Number
|
|
* 2 Baseband Firmware Version Number
|
|
*
|
|
* A typical message looks like this. Checksum has already been stripped.
|
|
*
|
|
* $PMVXG,523,M,S,M,EEEE,BBBBBB,C,R
|
|
*
|
|
* Field Field Contents
|
|
* ----- --------------
|
|
* Block Label: $PMVXG
|
|
* Sentence Type: 523=Time Recovery Parameters Currently in Use.
|
|
* This sentence contains the configuration of the
|
|
* time recovery feature of the receiver.
|
|
* 1 Time Recovery Mode:
|
|
* D = Dynamic; solve for position and time while moving
|
|
* S = Static; solve for position and time while stationary
|
|
* K = Known position input, solve for time only
|
|
* N = No time recovery
|
|
* 2 Time Synchronization:
|
|
* U = UTC time
|
|
* G = GPS time
|
|
* 3 Time Mark Mode:
|
|
* A = Always output a time pulse
|
|
* V = Only output time pulse if time is valid (as determined
|
|
* by Maximum Time Error)
|
|
* 4 Maximum Time Error - the maximum error (in nanoseconds) for
|
|
* which a time mark will be considered valid.
|
|
* 5 User Time Bias - external bias in nanoseconds
|
|
* 6 Time Message Control:
|
|
* 0 = Do not output the time recovery message
|
|
* 1 = Output the time recovery message (record 830) to
|
|
* Control port
|
|
* 2 = Output the time recovery message (record 830) to
|
|
* Equipment port
|
|
* 7 Reserved
|
|
* 8 Position Known PRN (absent on MX 4200)
|
|
*
|
|
*/
|
|
static char *
|
|
mx4200_parse_s(
|
|
struct peer *peer
|
|
)
|
|
{
|
|
struct refclockproc *pp;
|
|
struct mx4200unit *up;
|
|
int sentence_type;
|
|
|
|
pp = peer->procptr;
|
|
up = (struct mx4200unit *)pp->unitptr;
|
|
|
|
sscanf ( pp->a_lastcode, "$PMVXG,%d", &sentence_type);
|
|
|
|
/* Sentence type */
|
|
switch (sentence_type) {
|
|
|
|
case PMVXG_D_STATUS:
|
|
msyslog(LOG_DEBUG,
|
|
"mx4200: status: %s", pp->a_lastcode);
|
|
break;
|
|
case PMVXG_D_MODEDATA:
|
|
msyslog(LOG_DEBUG,
|
|
"mx4200: mode data: %s", pp->a_lastcode);
|
|
break;
|
|
case PMVXG_D_SOFTCONF:
|
|
msyslog(LOG_DEBUG,
|
|
"mx4200: firmware configuration: %s", pp->a_lastcode);
|
|
break;
|
|
case PMVXG_D_TRECOVUSEAGE:
|
|
msyslog(LOG_DEBUG,
|
|
"mx4200: time recovery parms: %s", pp->a_lastcode);
|
|
break;
|
|
default:
|
|
return ("wrong rec-type");
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Process a PPS signal, returning a timestamp.
|
|
*/
|
|
static int
|
|
mx4200_pps(
|
|
struct peer *peer
|
|
)
|
|
{
|
|
int temp_serial;
|
|
struct refclockproc *pp;
|
|
struct mx4200unit *up;
|
|
|
|
int request;
|
|
#ifdef HAVE_CIOGETEV
|
|
request = CIOGETEV;
|
|
#endif
|
|
#ifdef HAVE_TIOCGPPSEV
|
|
request = TIOCGPPSEV;
|
|
#endif
|
|
|
|
pp = peer->procptr;
|
|
up = (struct mx4200unit *)pp->unitptr;
|
|
|
|
/*
|
|
* Grab the timestamp of the PPS signal.
|
|
*/
|
|
temp_serial = up->ppsev.serial;
|
|
if (ioctl(fdpps, request, (caddr_t)&up->ppsev) < 0) {
|
|
/* XXX Actually, if this fails, we're pretty much screwed */
|
|
mx4200_debug(peer,
|
|
"mx4200_pps: CIOGETEV/TIOCGPPSEV: serial=%d, fdpps=%d, %s\n",
|
|
up->ppsev.serial, fdpps, strerror(errno));
|
|
refclock_report(peer, CEVNT_FAULT);
|
|
return(1);
|
|
}
|
|
if (temp_serial == up->ppsev.serial) {
|
|
mx4200_debug(peer,
|
|
"mx4200_pps: ppsev serial not incrementing: %d\n",
|
|
up->ppsev.serial);
|
|
refclock_report(peer, CEVNT_FAULT);
|
|
return(1);
|
|
}
|
|
|
|
/*
|
|
* Check pps serial number against last one
|
|
*/
|
|
if (up->lastserial + 1 != up->ppsev.serial && up->lastserial != 0) {
|
|
if (up->ppsev.serial == up->lastserial)
|
|
mx4200_debug(peer, "mx4200_pps: no new pps event\n");
|
|
else
|
|
mx4200_debug(peer, "mx4200_pps: missed %d pps events\n",
|
|
up->ppsev.serial - up->lastserial - 1);
|
|
refclock_report(peer, CEVNT_FAULT);
|
|
}
|
|
up->lastserial = up->ppsev.serial;
|
|
|
|
/*
|
|
* Return the timestamp in pp->lastrec
|
|
*/
|
|
up->ppsev.tv.tv_sec += (u_int32) JAN_1970;
|
|
TVTOTS(&up->ppsev.tv,&pp->lastrec);
|
|
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* mx4200_debug - print debug messages
|
|
*/
|
|
#if defined(__STDC__)
|
|
static void
|
|
mx4200_debug(struct peer *peer, char *fmt, ...)
|
|
#else
|
|
static void
|
|
mx4200_debug(peer, fmt, va_alist)
|
|
struct peer *peer;
|
|
char *fmt;
|
|
#endif /* __STDC__ */
|
|
{
|
|
va_list ap;
|
|
struct refclockproc *pp;
|
|
struct mx4200unit *up;
|
|
|
|
if (debug) {
|
|
|
|
#if defined(__STDC__)
|
|
va_start(ap, fmt);
|
|
#else
|
|
va_start(ap);
|
|
#endif /* __STDC__ */
|
|
|
|
pp = peer->procptr;
|
|
up = (struct mx4200unit *)pp->unitptr;
|
|
|
|
|
|
/*
|
|
* Print debug message to stdout
|
|
* In the future, we may want to get get more creative...
|
|
*/
|
|
vprintf(fmt, ap);
|
|
|
|
va_end(ap);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Send a character string to the receiver. Checksum is appended here.
|
|
*/
|
|
#if defined(__STDC__)
|
|
static void
|
|
mx4200_send(struct peer *peer, char *fmt, ...)
|
|
#else
|
|
static void
|
|
mx4200_send(peer, fmt, va_alist)
|
|
struct peer *peer;
|
|
char *fmt;
|
|
va_dcl
|
|
#endif /* __STDC__ */
|
|
{
|
|
struct refclockproc *pp;
|
|
struct mx4200unit *up;
|
|
|
|
register char *cp;
|
|
register int n, m;
|
|
va_list ap;
|
|
char buf[1024];
|
|
u_char ck;
|
|
|
|
#if defined(__STDC__)
|
|
va_start(ap, fmt);
|
|
#else
|
|
va_start(ap);
|
|
#endif /* __STDC__ */
|
|
|
|
pp = peer->procptr;
|
|
up = (struct mx4200unit *)pp->unitptr;
|
|
|
|
cp = buf;
|
|
*cp++ = '$';
|
|
#ifdef notdef
|
|
/* BSD is rational */
|
|
n = vsnprintf(cp, sizeof(buf) - 1, fmt, ap);
|
|
#else
|
|
/* SunOS sucks */
|
|
(void)vsprintf(cp, fmt, ap);
|
|
n = strlen(cp);
|
|
#endif /* notdef */
|
|
ck = mx4200_cksum(cp, n);
|
|
cp += n;
|
|
++n;
|
|
#ifdef notdef
|
|
/* BSD is rational */
|
|
n += snprintf(cp, sizeof(buf) - n - 5, "*%02X\r\n", ck);
|
|
#else
|
|
/* SunOS sucks */
|
|
sprintf(cp, "*%02X\r\n", ck);
|
|
n += strlen(cp);
|
|
#endif /* notdef */
|
|
|
|
m = write(pp->io.fd, buf, (unsigned)n);
|
|
if (m < 0)
|
|
msyslog(LOG_ERR, "mx4200_send: write: %m (%s)", buf);
|
|
mx4200_debug(peer, "mx4200_send: %d %s\n", m, buf);
|
|
va_end(ap);
|
|
}
|
|
|
|
#else
|
|
int refclock_mx4200_bs;
|
|
#endif /* REFCLOCK */
|