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