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