1*eabc0478Schristos /* $NetBSD: refclock_mx4200.c,v 1.7 2024/08/18 20:47:18 christos Exp $ */ 2abb0f93cSkardel 3abb0f93cSkardel /* 4abb0f93cSkardel * This software was developed by the Computer Systems Engineering group 5abb0f93cSkardel * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66. 6abb0f93cSkardel * 7abb0f93cSkardel * Copyright (c) 1992 The Regents of the University of California. 8abb0f93cSkardel * All rights reserved. 9abb0f93cSkardel * 10abb0f93cSkardel * Redistribution and use in source and binary forms, with or without 11abb0f93cSkardel * modification, are permitted provided that the following conditions 12abb0f93cSkardel * are met: 13abb0f93cSkardel * 1. Redistributions of source code must retain the above copyright 14abb0f93cSkardel * notice, this list of conditions and the following disclaimer. 15abb0f93cSkardel * 2. Redistributions in binary form must reproduce the above copyright 16abb0f93cSkardel * notice, this list of conditions and the following disclaimer in the 17abb0f93cSkardel * documentation and/or other materials provided with the distribution. 18abb0f93cSkardel * 3. All advertising materials mentioning features or use of this software 19abb0f93cSkardel * must display the following acknowledgement: 20abb0f93cSkardel * This product includes software developed by the University of 21abb0f93cSkardel * California, Lawrence Berkeley Laboratory. 22abb0f93cSkardel * 4. The name of the University may not be used to endorse or promote 23abb0f93cSkardel * products derived from this software without specific prior 24abb0f93cSkardel * written permission. 25abb0f93cSkardel * 26abb0f93cSkardel * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 27abb0f93cSkardel * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28abb0f93cSkardel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29abb0f93cSkardel * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 30abb0f93cSkardel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31abb0f93cSkardel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 32abb0f93cSkardel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33abb0f93cSkardel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 34abb0f93cSkardel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 35abb0f93cSkardel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 36abb0f93cSkardel * SUCH DAMAGE. 37abb0f93cSkardel */ 38abb0f93cSkardel 39abb0f93cSkardel /* 40abb0f93cSkardel * Modified: Marc Brett <marc.brett@westgeo.com> Sept, 1999. 41abb0f93cSkardel * 42abb0f93cSkardel * 1. Added support for alternate PPS schemes, with code mostly 43abb0f93cSkardel * copied from the Oncore driver (Thanks, Poul-Henning Kamp). 44abb0f93cSkardel * This code runs on SunOS 4.1.3 with ppsclock-1.6a1 and Solaris 7. 45abb0f93cSkardel */ 46abb0f93cSkardel 47abb0f93cSkardel 48abb0f93cSkardel #ifdef HAVE_CONFIG_H 49abb0f93cSkardel # include <config.h> 50abb0f93cSkardel #endif 51abb0f93cSkardel 52abb0f93cSkardel #if defined(REFCLOCK) && defined(CLOCK_MX4200) && defined(HAVE_PPSAPI) 53abb0f93cSkardel 54abb0f93cSkardel #include "ntpd.h" 55abb0f93cSkardel #include "ntp_io.h" 56abb0f93cSkardel #include "ntp_refclock.h" 57abb0f93cSkardel #include "ntp_unixtime.h" 58abb0f93cSkardel #include "ntp_stdlib.h" 59abb0f93cSkardel 60abb0f93cSkardel #include <stdio.h> 61abb0f93cSkardel #include <ctype.h> 62abb0f93cSkardel 63abb0f93cSkardel #include "mx4200.h" 64abb0f93cSkardel 65abb0f93cSkardel #ifdef HAVE_SYS_TERMIOS_H 66abb0f93cSkardel # include <sys/termios.h> 67abb0f93cSkardel #endif 68abb0f93cSkardel #ifdef HAVE_SYS_PPSCLOCK_H 69abb0f93cSkardel # include <sys/ppsclock.h> 70abb0f93cSkardel #endif 71abb0f93cSkardel 72abb0f93cSkardel #ifndef HAVE_STRUCT_PPSCLOCKEV 73abb0f93cSkardel struct ppsclockev { 74abb0f93cSkardel # ifdef HAVE_STRUCT_TIMESPEC 75abb0f93cSkardel struct timespec tv; 76abb0f93cSkardel # else 77abb0f93cSkardel struct timeval tv; 78abb0f93cSkardel # endif 79abb0f93cSkardel u_int serial; 80abb0f93cSkardel }; 81abb0f93cSkardel #endif /* ! HAVE_STRUCT_PPSCLOCKEV */ 82abb0f93cSkardel 83abb0f93cSkardel #ifdef HAVE_PPSAPI 84abb0f93cSkardel # include "ppsapi_timepps.h" 85abb0f93cSkardel #endif /* HAVE_PPSAPI */ 86abb0f93cSkardel 87abb0f93cSkardel /* 88abb0f93cSkardel * This driver supports the Magnavox Model MX 4200 GPS Receiver 89abb0f93cSkardel * adapted to precision timing applications. It requires the 90abb0f93cSkardel * ppsclock line discipline or streams module described in the 91abb0f93cSkardel * Line Disciplines and Streams Drivers page. It also requires a 92abb0f93cSkardel * gadget box and 1-PPS level converter, such as described in the 93abb0f93cSkardel * Pulse-per-second (PPS) Signal Interfacing page. 94abb0f93cSkardel * 95abb0f93cSkardel * It's likely that other compatible Magnavox receivers such as the 96abb0f93cSkardel * MX 4200D, MX 9212, MX 9012R, MX 9112 will be supported by this code. 97abb0f93cSkardel */ 98abb0f93cSkardel 99abb0f93cSkardel /* 100abb0f93cSkardel * Check this every time you edit the code! 101abb0f93cSkardel */ 102abb0f93cSkardel #define YEAR_LAST_MODIFIED 2000 103abb0f93cSkardel 104abb0f93cSkardel /* 105abb0f93cSkardel * GPS Definitions 106abb0f93cSkardel */ 107abb0f93cSkardel #define DEVICE "/dev/gps%d" /* device name and unit */ 108abb0f93cSkardel #define SPEED232 B4800 /* baud */ 109abb0f93cSkardel 110abb0f93cSkardel /* 111abb0f93cSkardel * Radio interface parameters 112abb0f93cSkardel */ 113abb0f93cSkardel #define PRECISION (-18) /* precision assumed (about 4 us) */ 114abb0f93cSkardel #define REFID "GPS\0" /* reference id */ 115abb0f93cSkardel #define DESCRIPTION "Magnavox MX4200 GPS Receiver" /* who we are */ 116abb0f93cSkardel #define DEFFUDGETIME 0 /* default fudge time (ms) */ 117abb0f93cSkardel 118abb0f93cSkardel #define SLEEPTIME 32 /* seconds to wait for reconfig to complete */ 119abb0f93cSkardel 120abb0f93cSkardel /* 121abb0f93cSkardel * Position Averaging. 122abb0f93cSkardel */ 123abb0f93cSkardel #define INTERVAL 1 /* Interval between position measurements (s) */ 124abb0f93cSkardel #define AVGING_TIME 24 /* Number of hours to average */ 125abb0f93cSkardel #define NOT_INITIALIZED -9999. /* initial pivot longitude */ 126abb0f93cSkardel 127abb0f93cSkardel /* 128abb0f93cSkardel * MX4200 unit control structure. 129abb0f93cSkardel */ 130abb0f93cSkardel struct mx4200unit { 131abb0f93cSkardel u_int pollcnt; /* poll message counter */ 132abb0f93cSkardel u_int polled; /* Hand in a time sample? */ 133abb0f93cSkardel u_int lastserial; /* last pps serial number */ 134abb0f93cSkardel struct ppsclockev ppsev; /* PPS control structure */ 135abb0f93cSkardel double avg_lat; /* average latitude */ 136abb0f93cSkardel double avg_lon; /* average longitude */ 137abb0f93cSkardel double avg_alt; /* average height */ 138abb0f93cSkardel double central_meridian; /* central meridian */ 139abb0f93cSkardel double N_fixes; /* Number of position measurements */ 140abb0f93cSkardel int last_leap; /* leap second warning */ 141abb0f93cSkardel u_int moving; /* mobile platform? */ 142abb0f93cSkardel u_long sloppyclockflag; /* fudge flags */ 143abb0f93cSkardel u_int known; /* position known yet? */ 144abb0f93cSkardel u_long clamp_time; /* when to stop postion averaging */ 145abb0f93cSkardel u_long log_time; /* when to print receiver status */ 146abb0f93cSkardel pps_handle_t pps_h; 147abb0f93cSkardel pps_params_t pps_p; 148abb0f93cSkardel pps_info_t pps_i; 149abb0f93cSkardel }; 150abb0f93cSkardel 151abb0f93cSkardel static char pmvxg[] = "PMVXG"; 152abb0f93cSkardel 153abb0f93cSkardel /* XXX should be somewhere else */ 154abb0f93cSkardel #ifdef __GNUC__ 155abb0f93cSkardel #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) 156abb0f93cSkardel #ifndef __attribute__ 157abb0f93cSkardel #define __attribute__(args) 158abb0f93cSkardel #endif /* __attribute__ */ 159abb0f93cSkardel #endif /* __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) */ 160abb0f93cSkardel #else 161abb0f93cSkardel #ifndef __attribute__ 162abb0f93cSkardel #define __attribute__(args) 163abb0f93cSkardel #endif /* __attribute__ */ 164abb0f93cSkardel #endif /* __GNUC__ */ 165abb0f93cSkardel /* XXX end */ 166abb0f93cSkardel 167abb0f93cSkardel /* 168abb0f93cSkardel * Function prototypes 169abb0f93cSkardel */ 170abb0f93cSkardel static int mx4200_start (int, struct peer *); 171abb0f93cSkardel static void mx4200_shutdown (int, struct peer *); 172abb0f93cSkardel static void mx4200_receive (struct recvbuf *); 173abb0f93cSkardel static void mx4200_poll (int, struct peer *); 174abb0f93cSkardel 175abb0f93cSkardel static char * mx4200_parse_t (struct peer *); 176abb0f93cSkardel static char * mx4200_parse_p (struct peer *); 177abb0f93cSkardel static char * mx4200_parse_s (struct peer *); 178abb0f93cSkardel int mx4200_cmpl_fp (const void *, const void *); 179abb0f93cSkardel static int mx4200_config (struct peer *); 180abb0f93cSkardel static void mx4200_ref (struct peer *); 181abb0f93cSkardel static void mx4200_send (struct peer *, char *, ...) 182abb0f93cSkardel __attribute__ ((format (printf, 2, 3))); 183abb0f93cSkardel static u_char mx4200_cksum (char *, int); 184abb0f93cSkardel static int mx4200_jday (int, int, int); 185abb0f93cSkardel static void mx4200_debug (struct peer *, char *, ...) 186abb0f93cSkardel __attribute__ ((format (printf, 2, 3))); 187abb0f93cSkardel static int mx4200_pps (struct peer *); 188abb0f93cSkardel 189abb0f93cSkardel /* 190abb0f93cSkardel * Transfer vector 191abb0f93cSkardel */ 192abb0f93cSkardel struct refclock refclock_mx4200 = { 193abb0f93cSkardel mx4200_start, /* start up driver */ 194abb0f93cSkardel mx4200_shutdown, /* shut down driver */ 195abb0f93cSkardel mx4200_poll, /* transmit poll message */ 196abb0f93cSkardel noentry, /* not used (old mx4200_control) */ 197abb0f93cSkardel noentry, /* initialize driver (not used) */ 198abb0f93cSkardel noentry, /* not used (old mx4200_buginfo) */ 199abb0f93cSkardel NOFLAGS /* not used */ 200abb0f93cSkardel }; 201abb0f93cSkardel 202abb0f93cSkardel 203abb0f93cSkardel 204abb0f93cSkardel /* 205abb0f93cSkardel * mx4200_start - open the devices and initialize data for processing 206abb0f93cSkardel */ 207abb0f93cSkardel static int 208abb0f93cSkardel mx4200_start( 209abb0f93cSkardel int unit, 210abb0f93cSkardel struct peer *peer 211abb0f93cSkardel ) 212abb0f93cSkardel { 213abb0f93cSkardel register struct mx4200unit *up; 214abb0f93cSkardel struct refclockproc *pp; 215abb0f93cSkardel int fd; 216abb0f93cSkardel char gpsdev[20]; 217abb0f93cSkardel 218abb0f93cSkardel /* 219abb0f93cSkardel * Open serial port 220abb0f93cSkardel */ 221f003fb54Skardel snprintf(gpsdev, sizeof(gpsdev), DEVICE, unit); 222*eabc0478Schristos fd = refclock_open(&peer->srcadr, gpsdev, SPEED232, LDISC_PPS); 2238585484eSchristos if (fd <= 0) 2248585484eSchristos return 0; 225abb0f93cSkardel 226abb0f93cSkardel /* 227abb0f93cSkardel * Allocate unit structure 228abb0f93cSkardel */ 2298585484eSchristos up = emalloc_zero(sizeof(*up)); 230abb0f93cSkardel pp = peer->procptr; 231abb0f93cSkardel pp->io.clock_recv = mx4200_receive; 2328585484eSchristos pp->io.srcclock = peer; 233abb0f93cSkardel pp->io.datalen = 0; 234abb0f93cSkardel pp->io.fd = fd; 235abb0f93cSkardel if (!io_addclock(&pp->io)) { 236f003fb54Skardel close(fd); 237f003fb54Skardel pp->io.fd = -1; 238abb0f93cSkardel free(up); 239abb0f93cSkardel return (0); 240abb0f93cSkardel } 2418585484eSchristos pp->unitptr = up; 242abb0f93cSkardel 243abb0f93cSkardel /* 244abb0f93cSkardel * Initialize miscellaneous variables 245abb0f93cSkardel */ 246abb0f93cSkardel peer->precision = PRECISION; 247abb0f93cSkardel pp->clockdesc = DESCRIPTION; 248abb0f93cSkardel memcpy((char *)&pp->refid, REFID, 4); 249abb0f93cSkardel 250abb0f93cSkardel /* Ensure the receiver is properly configured */ 251abb0f93cSkardel return mx4200_config(peer); 252abb0f93cSkardel } 253abb0f93cSkardel 254abb0f93cSkardel 255abb0f93cSkardel /* 256abb0f93cSkardel * mx4200_shutdown - shut down the clock 257abb0f93cSkardel */ 258abb0f93cSkardel static void 259abb0f93cSkardel mx4200_shutdown( 260abb0f93cSkardel int unit, 261abb0f93cSkardel struct peer *peer 262abb0f93cSkardel ) 263abb0f93cSkardel { 264abb0f93cSkardel register struct mx4200unit *up; 265abb0f93cSkardel struct refclockproc *pp; 266abb0f93cSkardel 267abb0f93cSkardel pp = peer->procptr; 2688585484eSchristos up = pp->unitptr; 269f003fb54Skardel if (-1 != pp->io.fd) 270abb0f93cSkardel io_closeclock(&pp->io); 271f003fb54Skardel if (NULL != up) 272abb0f93cSkardel free(up); 273abb0f93cSkardel } 274abb0f93cSkardel 275abb0f93cSkardel 276abb0f93cSkardel /* 277abb0f93cSkardel * mx4200_config - Configure the receiver 278abb0f93cSkardel */ 279abb0f93cSkardel static int 280abb0f93cSkardel mx4200_config( 281abb0f93cSkardel struct peer *peer 282abb0f93cSkardel ) 283abb0f93cSkardel { 284abb0f93cSkardel char tr_mode; 285abb0f93cSkardel int add_mode; 286abb0f93cSkardel register struct mx4200unit *up; 287abb0f93cSkardel struct refclockproc *pp; 288abb0f93cSkardel int mode; 289abb0f93cSkardel 290abb0f93cSkardel pp = peer->procptr; 2918585484eSchristos up = pp->unitptr; 292abb0f93cSkardel 293abb0f93cSkardel /* 294abb0f93cSkardel * Initialize the unit variables 295abb0f93cSkardel * 296abb0f93cSkardel * STRANGE BEHAVIOUR WARNING: The fudge flags are not available 297abb0f93cSkardel * at the time mx4200_start is called. These are set later, 298abb0f93cSkardel * and so the code must be prepared to handle changing flags. 299abb0f93cSkardel */ 300abb0f93cSkardel up->sloppyclockflag = pp->sloppyclockflag; 301abb0f93cSkardel if (pp->sloppyclockflag & CLK_FLAG2) { 302abb0f93cSkardel up->moving = 1; /* Receiver on mobile platform */ 303abb0f93cSkardel msyslog(LOG_DEBUG, "mx4200_config: mobile platform"); 304abb0f93cSkardel } else { 305abb0f93cSkardel up->moving = 0; /* Static Installation */ 306abb0f93cSkardel } 307abb0f93cSkardel up->pollcnt = 2; 308abb0f93cSkardel up->polled = 0; 309abb0f93cSkardel up->known = 0; 310abb0f93cSkardel up->avg_lat = 0.0; 311abb0f93cSkardel up->avg_lon = 0.0; 312abb0f93cSkardel up->avg_alt = 0.0; 313abb0f93cSkardel up->central_meridian = NOT_INITIALIZED; 314abb0f93cSkardel up->N_fixes = 0.0; 315abb0f93cSkardel up->last_leap = 0; /* LEAP_NOWARNING */ 316abb0f93cSkardel up->clamp_time = current_time + (AVGING_TIME * 60 * 60); 317abb0f93cSkardel up->log_time = current_time + SLEEPTIME; 318abb0f93cSkardel 319abb0f93cSkardel if (time_pps_create(pp->io.fd, &up->pps_h) < 0) { 320abb0f93cSkardel perror("time_pps_create"); 321abb0f93cSkardel msyslog(LOG_ERR, 322abb0f93cSkardel "mx4200_config: time_pps_create failed: %m"); 323abb0f93cSkardel return (0); 324abb0f93cSkardel } 325abb0f93cSkardel if (time_pps_getcap(up->pps_h, &mode) < 0) { 326abb0f93cSkardel msyslog(LOG_ERR, 327abb0f93cSkardel "mx4200_config: time_pps_getcap failed: %m"); 328abb0f93cSkardel return (0); 329abb0f93cSkardel } 330abb0f93cSkardel 331abb0f93cSkardel if (time_pps_getparams(up->pps_h, &up->pps_p) < 0) { 332abb0f93cSkardel msyslog(LOG_ERR, 333abb0f93cSkardel "mx4200_config: time_pps_getparams failed: %m"); 334abb0f93cSkardel return (0); 335abb0f93cSkardel } 336abb0f93cSkardel 337abb0f93cSkardel /* nb. only turn things on, if someone else has turned something 338abb0f93cSkardel * on before we get here, leave it alone! 339abb0f93cSkardel */ 340abb0f93cSkardel 341abb0f93cSkardel up->pps_p.mode = PPS_CAPTUREASSERT | PPS_TSFMT_TSPEC; 342abb0f93cSkardel up->pps_p.mode &= mode; /* only set what is legal */ 343abb0f93cSkardel 344abb0f93cSkardel if (time_pps_setparams(up->pps_h, &up->pps_p) < 0) { 345abb0f93cSkardel perror("time_pps_setparams"); 346abb0f93cSkardel msyslog(LOG_ERR, 347abb0f93cSkardel "mx4200_config: time_pps_setparams failed: %m"); 348abb0f93cSkardel exit(1); 349abb0f93cSkardel } 350abb0f93cSkardel 351abb0f93cSkardel if (time_pps_kcbind(up->pps_h, PPS_KC_HARDPPS, PPS_CAPTUREASSERT, 352abb0f93cSkardel PPS_TSFMT_TSPEC) < 0) { 353abb0f93cSkardel perror("time_pps_kcbind"); 354abb0f93cSkardel msyslog(LOG_ERR, 355abb0f93cSkardel "mx4200_config: time_pps_kcbind failed: %m"); 356abb0f93cSkardel exit(1); 357abb0f93cSkardel } 358abb0f93cSkardel 359abb0f93cSkardel 360abb0f93cSkardel /* 361abb0f93cSkardel * "007" Control Port Configuration 362abb0f93cSkardel * Zero the output list (do it twice to flush possible junk) 363abb0f93cSkardel */ 364abb0f93cSkardel mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg, 365abb0f93cSkardel PMVXG_S_PORTCONF, 366abb0f93cSkardel /* control port output block Label */ 367abb0f93cSkardel 1); /* clear current output control list (1=yes) */ 368abb0f93cSkardel /* add/delete sentences from list */ 369abb0f93cSkardel /* must be null */ 370abb0f93cSkardel /* sentence output rate (sec) */ 371abb0f93cSkardel /* precision for position output */ 372abb0f93cSkardel /* nmea version for cga & gll output */ 373abb0f93cSkardel /* pass-through control */ 374abb0f93cSkardel mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg, 375abb0f93cSkardel PMVXG_S_PORTCONF, 1); 376abb0f93cSkardel 377abb0f93cSkardel /* 378abb0f93cSkardel * Request software configuration so we can syslog the firmware version 379abb0f93cSkardel */ 380abb0f93cSkardel mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_SOFTCONF); 381abb0f93cSkardel 382abb0f93cSkardel /* 383abb0f93cSkardel * "001" Initialization/Mode Control, Part A 384abb0f93cSkardel * Where ARE we? 385abb0f93cSkardel */ 386abb0f93cSkardel mx4200_send(peer, "%s,%03d,,,,,,,,,,", pmvxg, 387abb0f93cSkardel PMVXG_S_INITMODEA); 388abb0f93cSkardel /* day of month */ 389abb0f93cSkardel /* month of year */ 390abb0f93cSkardel /* year */ 391abb0f93cSkardel /* gmt */ 392abb0f93cSkardel /* latitude DDMM.MMMM */ 393abb0f93cSkardel /* north/south */ 394abb0f93cSkardel /* longitude DDDMM.MMMM */ 395abb0f93cSkardel /* east/west */ 396abb0f93cSkardel /* height */ 397abb0f93cSkardel /* Altitude Reference 1=MSL */ 398abb0f93cSkardel 399abb0f93cSkardel /* 400abb0f93cSkardel * "001" Initialization/Mode Control, Part B 401abb0f93cSkardel * Start off in 2d/3d coast mode, holding altitude to last known 402abb0f93cSkardel * value if only 3 satellites available. 403abb0f93cSkardel */ 404abb0f93cSkardel mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d", 405abb0f93cSkardel pmvxg, PMVXG_S_INITMODEB, 406abb0f93cSkardel 3, /* 2d/3d coast */ 407abb0f93cSkardel /* reserved */ 408abb0f93cSkardel 0.1, /* hor accel fact as per Steve (m/s**2) */ 409abb0f93cSkardel 0.1, /* ver accel fact as per Steve (m/s**2) */ 410abb0f93cSkardel 10, /* vdop */ 411abb0f93cSkardel 10, /* hdop limit as per Steve */ 412abb0f93cSkardel 5, /* elevation limit as per Steve (deg) */ 413abb0f93cSkardel 'U', /* time output mode (UTC) */ 414abb0f93cSkardel 0); /* local time offset from gmt (HHHMM) */ 415abb0f93cSkardel 416abb0f93cSkardel /* 417abb0f93cSkardel * "023" Time Recovery Configuration 418abb0f93cSkardel * Get UTC time from a stationary receiver. 419abb0f93cSkardel * (Set field 1 'D' == dynamic if we are on a moving platform). 420abb0f93cSkardel * (Set field 1 'S' == static if we are not moving). 421abb0f93cSkardel * (Set field 1 'K' == known position if we can initialize lat/lon/alt). 422abb0f93cSkardel */ 423abb0f93cSkardel 424abb0f93cSkardel if (pp->sloppyclockflag & CLK_FLAG2) 425abb0f93cSkardel up->moving = 1; /* Receiver on mobile platform */ 426abb0f93cSkardel else 427abb0f93cSkardel up->moving = 0; /* Static Installation */ 428abb0f93cSkardel 429abb0f93cSkardel up->pollcnt = 2; 430abb0f93cSkardel if (up->moving) { 431abb0f93cSkardel /* dynamic: solve for pos, alt, time, while moving */ 432abb0f93cSkardel tr_mode = 'D'; 433abb0f93cSkardel } else { 434abb0f93cSkardel /* static: solve for pos, alt, time, while stationary */ 435abb0f93cSkardel tr_mode = 'S'; 436abb0f93cSkardel } 437abb0f93cSkardel mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg, 438abb0f93cSkardel PMVXG_S_TRECOVCONF, 439abb0f93cSkardel tr_mode, /* time recovery mode (see above ) */ 440abb0f93cSkardel 'U', /* synchronize to UTC */ 441abb0f93cSkardel 'A', /* always output a time pulse */ 442abb0f93cSkardel 500, /* max time error in ns */ 443abb0f93cSkardel 0, /* user bias in ns */ 444abb0f93cSkardel 1); /* output "830" sentences to control port */ 445abb0f93cSkardel /* Multi-satellite mode */ 446abb0f93cSkardel 447abb0f93cSkardel /* 448abb0f93cSkardel * Output position information (to calculate fixed installation 449abb0f93cSkardel * location) only if we are not moving 450abb0f93cSkardel */ 451abb0f93cSkardel if (up->moving) { 452abb0f93cSkardel add_mode = 2; /* delete from list */ 453abb0f93cSkardel } else { 454abb0f93cSkardel add_mode = 1; /* add to list */ 455abb0f93cSkardel } 456abb0f93cSkardel 457abb0f93cSkardel 458abb0f93cSkardel /* 459abb0f93cSkardel * "007" Control Port Configuration 460abb0f93cSkardel * Output "021" position, height, velocity reports 461abb0f93cSkardel */ 462abb0f93cSkardel mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg, 463abb0f93cSkardel PMVXG_S_PORTCONF, 464abb0f93cSkardel PMVXG_D_PHV, /* control port output block Label */ 465abb0f93cSkardel 0, /* clear current output control list (0=no) */ 466abb0f93cSkardel add_mode, /* add/delete sentences from list (1=add, 2=del) */ 467abb0f93cSkardel /* must be null */ 468abb0f93cSkardel INTERVAL); /* sentence output rate (sec) */ 469abb0f93cSkardel /* precision for position output */ 470abb0f93cSkardel /* nmea version for cga & gll output */ 471abb0f93cSkardel /* pass-through control */ 472abb0f93cSkardel 473abb0f93cSkardel return (1); 474abb0f93cSkardel } 475abb0f93cSkardel 476abb0f93cSkardel /* 477abb0f93cSkardel * mx4200_ref - Reconfigure unit as a reference station at a known position. 478abb0f93cSkardel */ 479abb0f93cSkardel static void 480abb0f93cSkardel mx4200_ref( 481abb0f93cSkardel struct peer *peer 482abb0f93cSkardel ) 483abb0f93cSkardel { 484abb0f93cSkardel register struct mx4200unit *up; 485abb0f93cSkardel struct refclockproc *pp; 486abb0f93cSkardel double minute, lat, lon, alt; 487abb0f93cSkardel char lats[16], lons[16]; 488abb0f93cSkardel char nsc, ewc; 489abb0f93cSkardel 490abb0f93cSkardel pp = peer->procptr; 4918585484eSchristos up = pp->unitptr; 492abb0f93cSkardel 493abb0f93cSkardel /* Should never happen! */ 494abb0f93cSkardel if (up->moving) return; 495abb0f93cSkardel 496abb0f93cSkardel /* 497abb0f93cSkardel * Set up to output status information in the near future 498abb0f93cSkardel */ 499abb0f93cSkardel up->log_time = current_time + SLEEPTIME; 500abb0f93cSkardel 501abb0f93cSkardel /* 502abb0f93cSkardel * "007" Control Port Configuration 503abb0f93cSkardel * Stop outputting "021" position, height, velocity reports 504abb0f93cSkardel */ 505abb0f93cSkardel mx4200_send(peer, "%s,%03d,%03d,%d,%d,,,,,", pmvxg, 506abb0f93cSkardel PMVXG_S_PORTCONF, 507abb0f93cSkardel PMVXG_D_PHV, /* control port output block Label */ 508abb0f93cSkardel 0, /* clear current output control list (0=no) */ 509abb0f93cSkardel 2); /* add/delete sentences from list (2=delete) */ 510abb0f93cSkardel /* must be null */ 511abb0f93cSkardel /* sentence output rate (sec) */ 512abb0f93cSkardel /* precision for position output */ 513abb0f93cSkardel /* nmea version for cga & gll output */ 514abb0f93cSkardel /* pass-through control */ 515abb0f93cSkardel 516abb0f93cSkardel /* 517abb0f93cSkardel * "001" Initialization/Mode Control, Part B 518abb0f93cSkardel * Put receiver in fully-constrained 2d nav mode 519abb0f93cSkardel */ 520abb0f93cSkardel mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d", 521abb0f93cSkardel pmvxg, PMVXG_S_INITMODEB, 522abb0f93cSkardel 2, /* 2d nav */ 523abb0f93cSkardel /* reserved */ 524abb0f93cSkardel 0.1, /* hor accel fact as per Steve (m/s**2) */ 525abb0f93cSkardel 0.1, /* ver accel fact as per Steve (m/s**2) */ 526abb0f93cSkardel 10, /* vdop */ 527abb0f93cSkardel 10, /* hdop limit as per Steve */ 528abb0f93cSkardel 5, /* elevation limit as per Steve (deg) */ 529abb0f93cSkardel 'U', /* time output mode (UTC) */ 530abb0f93cSkardel 0); /* local time offset from gmt (HHHMM) */ 531abb0f93cSkardel 532abb0f93cSkardel /* 533abb0f93cSkardel * "023" Time Recovery Configuration 534abb0f93cSkardel * Get UTC time from a stationary receiver. Solve for time only. 535abb0f93cSkardel * This should improve the time resolution dramatically. 536abb0f93cSkardel */ 537abb0f93cSkardel mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg, 538abb0f93cSkardel PMVXG_S_TRECOVCONF, 539abb0f93cSkardel 'K', /* known position: solve for time only */ 540abb0f93cSkardel 'U', /* synchronize to UTC */ 541abb0f93cSkardel 'A', /* always output a time pulse */ 542abb0f93cSkardel 500, /* max time error in ns */ 543abb0f93cSkardel 0, /* user bias in ns */ 544abb0f93cSkardel 1); /* output "830" sentences to control port */ 545abb0f93cSkardel /* Multi-satellite mode */ 546abb0f93cSkardel 547abb0f93cSkardel /* 548abb0f93cSkardel * "000" Initialization/Mode Control - Part A 549abb0f93cSkardel * Fix to our averaged position. 550abb0f93cSkardel */ 551abb0f93cSkardel if (up->central_meridian != NOT_INITIALIZED) { 552abb0f93cSkardel up->avg_lon += up->central_meridian; 553abb0f93cSkardel if (up->avg_lon < -180.0) up->avg_lon += 360.0; 554abb0f93cSkardel if (up->avg_lon > 180.0) up->avg_lon -= 360.0; 555abb0f93cSkardel } 556abb0f93cSkardel 557abb0f93cSkardel if (up->avg_lat >= 0.0) { 558abb0f93cSkardel lat = up->avg_lat; 559abb0f93cSkardel nsc = 'N'; 560abb0f93cSkardel } else { 561abb0f93cSkardel lat = up->avg_lat * (-1.0); 562abb0f93cSkardel nsc = 'S'; 563abb0f93cSkardel } 564abb0f93cSkardel if (up->avg_lon >= 0.0) { 565abb0f93cSkardel lon = up->avg_lon; 566abb0f93cSkardel ewc = 'E'; 567abb0f93cSkardel } else { 568abb0f93cSkardel lon = up->avg_lon * (-1.0); 569abb0f93cSkardel ewc = 'W'; 570abb0f93cSkardel } 571abb0f93cSkardel alt = up->avg_alt; 572abb0f93cSkardel minute = (lat - (double)(int)lat) * 60.0; 573f003fb54Skardel snprintf(lats, sizeof(lats), "%02d%02.4f", (int)lat, minute); 574abb0f93cSkardel minute = (lon - (double)(int)lon) * 60.0; 575f003fb54Skardel snprintf(lons, sizeof(lons), "%03d%02.4f", (int)lon, minute); 576abb0f93cSkardel 577abb0f93cSkardel mx4200_send(peer, "%s,%03d,,,,,%s,%c,%s,%c,%.2f,%d", pmvxg, 578abb0f93cSkardel PMVXG_S_INITMODEA, 579abb0f93cSkardel /* day of month */ 580abb0f93cSkardel /* month of year */ 581abb0f93cSkardel /* year */ 582abb0f93cSkardel /* gmt */ 583abb0f93cSkardel lats, /* latitude DDMM.MMMM */ 584abb0f93cSkardel nsc, /* north/south */ 585abb0f93cSkardel lons, /* longitude DDDMM.MMMM */ 586abb0f93cSkardel ewc, /* east/west */ 587abb0f93cSkardel alt, /* Altitude */ 588abb0f93cSkardel 1); /* Altitude Reference (0=WGS84 ellipsoid, 1=MSL geoid)*/ 589abb0f93cSkardel 590abb0f93cSkardel msyslog(LOG_DEBUG, 591abb0f93cSkardel "mx4200: reconfig to fixed location: %s %c, %s %c, %.2f m", 592abb0f93cSkardel lats, nsc, lons, ewc, alt ); 593abb0f93cSkardel 594abb0f93cSkardel } 595abb0f93cSkardel 596abb0f93cSkardel /* 597abb0f93cSkardel * mx4200_poll - mx4200 watchdog routine 598abb0f93cSkardel */ 599abb0f93cSkardel static void 600abb0f93cSkardel mx4200_poll( 601abb0f93cSkardel int unit, 602abb0f93cSkardel struct peer *peer 603abb0f93cSkardel ) 604abb0f93cSkardel { 605abb0f93cSkardel register struct mx4200unit *up; 606abb0f93cSkardel struct refclockproc *pp; 607abb0f93cSkardel 608abb0f93cSkardel pp = peer->procptr; 6098585484eSchristos up = pp->unitptr; 610abb0f93cSkardel 611abb0f93cSkardel /* 612abb0f93cSkardel * You don't need to poll this clock. It puts out timecodes 613abb0f93cSkardel * once per second. If asked for a timestamp, take note. 614abb0f93cSkardel * The next time a timecode comes in, it will be fed back. 615abb0f93cSkardel */ 616abb0f93cSkardel 617abb0f93cSkardel /* 618abb0f93cSkardel * If we haven't had a response in a while, reset the receiver. 619abb0f93cSkardel */ 620abb0f93cSkardel if (up->pollcnt > 0) { 621abb0f93cSkardel up->pollcnt--; 622abb0f93cSkardel } else { 623abb0f93cSkardel refclock_report(peer, CEVNT_TIMEOUT); 624abb0f93cSkardel 625abb0f93cSkardel /* 626abb0f93cSkardel * Request a "000" status message which should trigger a 627abb0f93cSkardel * reconfig 628abb0f93cSkardel */ 629abb0f93cSkardel mx4200_send(peer, "%s,%03d", 630abb0f93cSkardel "CDGPQ", /* query from CDU to GPS */ 631abb0f93cSkardel PMVXG_D_STATUS); /* label of desired sentence */ 632abb0f93cSkardel } 633abb0f93cSkardel 634abb0f93cSkardel /* 635abb0f93cSkardel * polled every 64 seconds. Ask mx4200_receive to hand in 636abb0f93cSkardel * a timestamp. 637abb0f93cSkardel */ 638abb0f93cSkardel up->polled = 1; 639abb0f93cSkardel pp->polls++; 640abb0f93cSkardel 641abb0f93cSkardel /* 642abb0f93cSkardel * Output receiver status information. 643abb0f93cSkardel */ 644abb0f93cSkardel if ((up->log_time > 0) && (current_time > up->log_time)) { 645abb0f93cSkardel up->log_time = 0; 646abb0f93cSkardel /* 647abb0f93cSkardel * Output the following messages once, for debugging. 648abb0f93cSkardel * "004" Mode Data 649abb0f93cSkardel * "523" Time Recovery Parameters 650abb0f93cSkardel */ 651abb0f93cSkardel mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_MODEDATA); 652abb0f93cSkardel mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_TRECOVUSEAGE); 653abb0f93cSkardel } 654abb0f93cSkardel } 655abb0f93cSkardel 656abb0f93cSkardel static char char2hex[] = "0123456789ABCDEF"; 657abb0f93cSkardel 658abb0f93cSkardel /* 659abb0f93cSkardel * mx4200_receive - receive gps data 660abb0f93cSkardel */ 661abb0f93cSkardel static void 662abb0f93cSkardel mx4200_receive( 663abb0f93cSkardel struct recvbuf *rbufp 664abb0f93cSkardel ) 665abb0f93cSkardel { 666abb0f93cSkardel register struct mx4200unit *up; 667abb0f93cSkardel struct refclockproc *pp; 668abb0f93cSkardel struct peer *peer; 669abb0f93cSkardel char *cp; 670abb0f93cSkardel int sentence_type; 671abb0f93cSkardel u_char ck; 672abb0f93cSkardel 673abb0f93cSkardel /* 674abb0f93cSkardel * Initialize pointers and read the timecode and timestamp. 675abb0f93cSkardel */ 6768585484eSchristos peer = rbufp->recv_peer; 677abb0f93cSkardel pp = peer->procptr; 6788585484eSchristos up = pp->unitptr; 679abb0f93cSkardel 680abb0f93cSkardel /* 681abb0f93cSkardel * If operating mode has been changed, then reinitialize the receiver 682abb0f93cSkardel * before doing anything else. 683abb0f93cSkardel */ 684abb0f93cSkardel if ((pp->sloppyclockflag & CLK_FLAG2) != 685abb0f93cSkardel (up->sloppyclockflag & CLK_FLAG2)) { 686abb0f93cSkardel up->sloppyclockflag = pp->sloppyclockflag; 687abb0f93cSkardel mx4200_debug(peer, 688abb0f93cSkardel "mx4200_receive: mode switch: reset receiver\n"); 689abb0f93cSkardel mx4200_config(peer); 690abb0f93cSkardel return; 691abb0f93cSkardel } 692abb0f93cSkardel up->sloppyclockflag = pp->sloppyclockflag; 693abb0f93cSkardel 694abb0f93cSkardel /* 695abb0f93cSkardel * Read clock output. Automatically handles STREAMS, CLKLDISC. 696abb0f93cSkardel */ 697abb0f93cSkardel pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec); 698abb0f93cSkardel 699abb0f93cSkardel /* 700abb0f93cSkardel * There is a case where <cr><lf> generates 2 timestamps. 701abb0f93cSkardel */ 702abb0f93cSkardel if (pp->lencode == 0) 703abb0f93cSkardel return; 704abb0f93cSkardel 705abb0f93cSkardel up->pollcnt = 2; 706abb0f93cSkardel pp->a_lastcode[pp->lencode] = '\0'; 707abb0f93cSkardel record_clock_stats(&peer->srcadr, pp->a_lastcode); 708abb0f93cSkardel mx4200_debug(peer, "mx4200_receive: %d %s\n", 709abb0f93cSkardel pp->lencode, pp->a_lastcode); 710abb0f93cSkardel 711abb0f93cSkardel /* 712abb0f93cSkardel * The structure of the control port sentences is based on the 713abb0f93cSkardel * NMEA-0183 Standard for interfacing Marine Electronics 714abb0f93cSkardel * Navigation Devices (Version 1.5) 715abb0f93cSkardel * 716abb0f93cSkardel * $PMVXG,XXX, ....................*CK<cr><lf> 717abb0f93cSkardel * 718abb0f93cSkardel * $ Sentence Start Identifier (reserved char) 719abb0f93cSkardel * (Start-of-Sentence Identifier) 720abb0f93cSkardel * P Special ID (Proprietary) 721abb0f93cSkardel * MVX Originator ID (Magnavox) 722abb0f93cSkardel * G Interface ID (GPS) 723abb0f93cSkardel * , Field Delimiters (reserved char) 724abb0f93cSkardel * XXX Sentence Type 725abb0f93cSkardel * ...... Data 726abb0f93cSkardel * * Checksum Field Delimiter (reserved char) 727abb0f93cSkardel * CK Checksum 728abb0f93cSkardel * <cr><lf> Carriage-Return/Line Feed (reserved chars) 729abb0f93cSkardel * (End-of-Sentence Identifier) 730abb0f93cSkardel * 731abb0f93cSkardel * Reject if any important landmarks are missing. 732abb0f93cSkardel */ 733abb0f93cSkardel cp = pp->a_lastcode + pp->lencode - 3; 734abb0f93cSkardel if (cp < pp->a_lastcode || *pp->a_lastcode != '$' || cp[0] != '*' ) { 735abb0f93cSkardel mx4200_debug(peer, "mx4200_receive: bad format\n"); 736abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 737abb0f93cSkardel return; 738abb0f93cSkardel } 739abb0f93cSkardel 740abb0f93cSkardel /* 741abb0f93cSkardel * Check and discard the checksum 742abb0f93cSkardel */ 743abb0f93cSkardel ck = mx4200_cksum(&pp->a_lastcode[1], pp->lencode - 4); 744abb0f93cSkardel if (char2hex[ck >> 4] != cp[1] || char2hex[ck & 0xf] != cp[2]) { 745abb0f93cSkardel mx4200_debug(peer, "mx4200_receive: bad checksum\n"); 746abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 747abb0f93cSkardel return; 748abb0f93cSkardel } 749abb0f93cSkardel *cp = '\0'; 750abb0f93cSkardel 751abb0f93cSkardel /* 752abb0f93cSkardel * Get the sentence type. 753abb0f93cSkardel */ 754abb0f93cSkardel sentence_type = 0; 755abb0f93cSkardel if ((cp = strchr(pp->a_lastcode, ',')) == NULL) { 756abb0f93cSkardel mx4200_debug(peer, "mx4200_receive: no sentence\n"); 757abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 758abb0f93cSkardel return; 759abb0f93cSkardel } 760abb0f93cSkardel cp++; 761abb0f93cSkardel sentence_type = strtol(cp, &cp, 10); 762abb0f93cSkardel 763abb0f93cSkardel /* 764abb0f93cSkardel * Process the sentence according to its type. 765abb0f93cSkardel */ 766abb0f93cSkardel switch (sentence_type) { 767abb0f93cSkardel 768abb0f93cSkardel /* 769abb0f93cSkardel * "000" Status message 770abb0f93cSkardel */ 771abb0f93cSkardel case PMVXG_D_STATUS: 772abb0f93cSkardel /* 773abb0f93cSkardel * XXX 774abb0f93cSkardel * Since we configure the receiver to not give us status 775abb0f93cSkardel * messages and since the receiver outputs status messages by 776abb0f93cSkardel * default after being reset to factory defaults when sent the 777abb0f93cSkardel * "$PMVXG,018,C\r\n" message, any status message we get 778abb0f93cSkardel * indicates the reciever needs to be initialized; thus, it is 779abb0f93cSkardel * not necessary to decode the status message. 780abb0f93cSkardel */ 781abb0f93cSkardel if ((cp = mx4200_parse_s(peer)) != NULL) { 782abb0f93cSkardel mx4200_debug(peer, 783abb0f93cSkardel "mx4200_receive: status: %s\n", cp); 784abb0f93cSkardel } 785abb0f93cSkardel mx4200_debug(peer, "mx4200_receive: reset receiver\n"); 786abb0f93cSkardel mx4200_config(peer); 787abb0f93cSkardel break; 788abb0f93cSkardel 789abb0f93cSkardel /* 790abb0f93cSkardel * "021" Position, Height, Velocity message, 791abb0f93cSkardel * if we are still averaging our position 792abb0f93cSkardel */ 793abb0f93cSkardel case PMVXG_D_PHV: 794abb0f93cSkardel if (!up->known) { 795abb0f93cSkardel /* 796abb0f93cSkardel * Parse the message, calculating our averaged position. 797abb0f93cSkardel */ 798abb0f93cSkardel if ((cp = mx4200_parse_p(peer)) != NULL) { 799abb0f93cSkardel mx4200_debug(peer, "mx4200_receive: pos: %s\n", cp); 800abb0f93cSkardel return; 801abb0f93cSkardel } 802abb0f93cSkardel mx4200_debug(peer, 803abb0f93cSkardel "mx4200_receive: position avg %f %.9f %.9f %.4f\n", 804abb0f93cSkardel up->N_fixes, up->avg_lat, up->avg_lon, up->avg_alt); 805abb0f93cSkardel /* 806abb0f93cSkardel * Reinitialize as a reference station 807abb0f93cSkardel * if position is well known. 808abb0f93cSkardel */ 809abb0f93cSkardel if (current_time > up->clamp_time) { 810abb0f93cSkardel up->known++; 811abb0f93cSkardel mx4200_debug(peer, "mx4200_receive: reconfiguring!\n"); 812abb0f93cSkardel mx4200_ref(peer); 813abb0f93cSkardel } 814abb0f93cSkardel } 815abb0f93cSkardel break; 816abb0f93cSkardel 817abb0f93cSkardel /* 818abb0f93cSkardel * Print to the syslog: 819abb0f93cSkardel * "004" Mode Data 820abb0f93cSkardel * "030" Software Configuration 821abb0f93cSkardel * "523" Time Recovery Parameters Currently in Use 822abb0f93cSkardel */ 823abb0f93cSkardel case PMVXG_D_MODEDATA: 824abb0f93cSkardel case PMVXG_D_SOFTCONF: 825abb0f93cSkardel case PMVXG_D_TRECOVUSEAGE: 826abb0f93cSkardel 827abb0f93cSkardel if ((cp = mx4200_parse_s(peer)) != NULL) { 828abb0f93cSkardel mx4200_debug(peer, 829abb0f93cSkardel "mx4200_receive: multi-record: %s\n", cp); 830abb0f93cSkardel } 831abb0f93cSkardel break; 832abb0f93cSkardel 833abb0f93cSkardel /* 834abb0f93cSkardel * "830" Time Recovery Results message 835abb0f93cSkardel */ 836abb0f93cSkardel case PMVXG_D_TRECOVOUT: 837abb0f93cSkardel 838abb0f93cSkardel /* 839abb0f93cSkardel * Capture the last PPS signal. 840abb0f93cSkardel * Precision timestamp is returned in pp->lastrec 841abb0f93cSkardel */ 8428585484eSchristos if (0 != mx4200_pps(peer)) { 843abb0f93cSkardel mx4200_debug(peer, "mx4200_receive: pps failure\n"); 844abb0f93cSkardel refclock_report(peer, CEVNT_FAULT); 845abb0f93cSkardel return; 846abb0f93cSkardel } 847abb0f93cSkardel 848abb0f93cSkardel 849abb0f93cSkardel /* 850abb0f93cSkardel * Parse the time recovery message, and keep the info 851abb0f93cSkardel * to print the pretty billboards. 852abb0f93cSkardel */ 853abb0f93cSkardel if ((cp = mx4200_parse_t(peer)) != NULL) { 854abb0f93cSkardel mx4200_debug(peer, "mx4200_receive: time: %s\n", cp); 855abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 856abb0f93cSkardel return; 857abb0f93cSkardel } 858abb0f93cSkardel 859abb0f93cSkardel /* 860abb0f93cSkardel * Add the new sample to a median filter. 861abb0f93cSkardel */ 862abb0f93cSkardel if (!refclock_process(pp)) { 863abb0f93cSkardel mx4200_debug(peer,"mx4200_receive: offset: %.6f\n", 864abb0f93cSkardel pp->offset); 865abb0f93cSkardel refclock_report(peer, CEVNT_BADTIME); 866abb0f93cSkardel return; 867abb0f93cSkardel } 868abb0f93cSkardel 869abb0f93cSkardel /* 870abb0f93cSkardel * The clock will blurt a timecode every second but we only 871abb0f93cSkardel * want one when polled. If we havn't been polled, bail out. 872abb0f93cSkardel */ 873abb0f93cSkardel if (!up->polled) 874abb0f93cSkardel return; 875abb0f93cSkardel 876abb0f93cSkardel /* 877abb0f93cSkardel * Return offset and dispersion to control module. We use 878abb0f93cSkardel * lastrec as both the reference time and receive time in 879abb0f93cSkardel * order to avoid being cute, like setting the reference time 880abb0f93cSkardel * later than the receive time, which may cause a paranoid 881abb0f93cSkardel * protocol module to chuck out the data. 882abb0f93cSkardel */ 883abb0f93cSkardel mx4200_debug(peer, "mx4200_receive: process time: "); 884abb0f93cSkardel mx4200_debug(peer, "%4d-%03d %02d:%02d:%02d at %s, %.6f\n", 885abb0f93cSkardel pp->year, pp->day, pp->hour, pp->minute, pp->second, 886abb0f93cSkardel prettydate(&pp->lastrec), pp->offset); 887abb0f93cSkardel pp->lastref = pp->lastrec; 888abb0f93cSkardel refclock_receive(peer); 889abb0f93cSkardel 890abb0f93cSkardel /* 891abb0f93cSkardel * We have succeeded in answering the poll. 892abb0f93cSkardel * Turn off the flag and return 893abb0f93cSkardel */ 894abb0f93cSkardel up->polled = 0; 895abb0f93cSkardel break; 896abb0f93cSkardel 897abb0f93cSkardel /* 898abb0f93cSkardel * Ignore all other sentence types 899abb0f93cSkardel */ 900abb0f93cSkardel default: 901abb0f93cSkardel break; 902abb0f93cSkardel 903abb0f93cSkardel } /* switch (sentence_type) */ 904abb0f93cSkardel 905abb0f93cSkardel return; 906abb0f93cSkardel } 907abb0f93cSkardel 908abb0f93cSkardel 909abb0f93cSkardel /* 910abb0f93cSkardel * Parse a mx4200 time recovery message. Returns a string if error. 911abb0f93cSkardel * 912abb0f93cSkardel * A typical message looks like this. Checksum has already been stripped. 913abb0f93cSkardel * 914abb0f93cSkardel * $PMVXG,830,T,YYYY,MM,DD,HH:MM:SS,U,S,FFFFFF,PPPPP,BBBBBB,LL 915abb0f93cSkardel * 916abb0f93cSkardel * Field Field Contents 917abb0f93cSkardel * ----- -------------- 918abb0f93cSkardel * Block Label: $PMVXG 919abb0f93cSkardel * Sentence Type: 830=Time Recovery Results 920abb0f93cSkardel * This sentence is output approximately 1 second 921abb0f93cSkardel * preceding the 1PPS output. It indicates the 922abb0f93cSkardel * exact time of the next pulse, whether or not the 923abb0f93cSkardel * time mark will be valid (based on operator-specified 924abb0f93cSkardel * error tolerance), the time to which the pulse is 925abb0f93cSkardel * synchronized, the receiver operating mode, 926abb0f93cSkardel * and the time error of the *last* 1PPS output. 927abb0f93cSkardel * 1 char Time Mark Valid: T=Valid, F=Not Valid 928abb0f93cSkardel * 2 int Year: 1993- 929abb0f93cSkardel * 3 int Month of Year: 1-12 930abb0f93cSkardel * 4 int Day of Month: 1-31 931abb0f93cSkardel * 5 int Time of Day: HH:MM:SS 932abb0f93cSkardel * 6 char Time Synchronization: U=UTC, G=GPS 933abb0f93cSkardel * 7 char Time Recovery Mode: D=Dynamic, S=Static, 934abb0f93cSkardel * K=Known Position, N=No Time Recovery 935abb0f93cSkardel * 8 int Oscillator Offset: The filter's estimate of the oscillator 936abb0f93cSkardel * frequency error, in parts per billion (ppb). 937abb0f93cSkardel * 9 int Time Mark Error: The computed error of the *last* pulse 938abb0f93cSkardel * output, in nanoseconds. 939abb0f93cSkardel * 10 int User Time Bias: Operator specified bias, in nanoseconds 940abb0f93cSkardel * 11 int Leap Second Flag: Indicates that a leap second will 941abb0f93cSkardel * occur. This value is usually zero, except during 942abb0f93cSkardel * the week prior to the leap second occurrence, when 943abb0f93cSkardel * this value will be set to +1 or -1. A value of 944abb0f93cSkardel * +1 indicates that GPS time will be 1 second 945abb0f93cSkardel * further ahead of UTC time. 946abb0f93cSkardel * 947abb0f93cSkardel */ 948abb0f93cSkardel static char * 949abb0f93cSkardel mx4200_parse_t( 950abb0f93cSkardel struct peer *peer 951abb0f93cSkardel ) 952abb0f93cSkardel { 953abb0f93cSkardel struct refclockproc *pp; 954abb0f93cSkardel struct mx4200unit *up; 955abb0f93cSkardel char time_mark_valid, time_sync, op_mode; 956abb0f93cSkardel int sentence_type, valid; 957abb0f93cSkardel int year, day_of_year, month, day_of_month; 958abb0f93cSkardel int hour, minute, second, leapsec_warn; 959abb0f93cSkardel int oscillator_offset, time_mark_error, time_bias; 960abb0f93cSkardel 961abb0f93cSkardel pp = peer->procptr; 9628585484eSchristos up = pp->unitptr; 963abb0f93cSkardel 964abb0f93cSkardel leapsec_warn = 0; /* Not all receivers output leap second warnings (!) */ 965abb0f93cSkardel sscanf(pp->a_lastcode, 966abb0f93cSkardel "$PMVXG,%d,%c,%d,%d,%d,%d:%d:%d,%c,%c,%d,%d,%d,%d", 967abb0f93cSkardel &sentence_type, &time_mark_valid, &year, &month, &day_of_month, 968abb0f93cSkardel &hour, &minute, &second, &time_sync, &op_mode, 969abb0f93cSkardel &oscillator_offset, &time_mark_error, &time_bias, &leapsec_warn); 970abb0f93cSkardel 971abb0f93cSkardel if (sentence_type != PMVXG_D_TRECOVOUT) 972abb0f93cSkardel return ("wrong rec-type"); 973abb0f93cSkardel 974abb0f93cSkardel switch (time_mark_valid) { 975abb0f93cSkardel case 'T': 976abb0f93cSkardel valid = 1; 977abb0f93cSkardel break; 978abb0f93cSkardel case 'F': 979abb0f93cSkardel valid = 0; 980abb0f93cSkardel break; 981abb0f93cSkardel default: 982abb0f93cSkardel return ("bad pulse-valid"); 983abb0f93cSkardel } 984abb0f93cSkardel 985abb0f93cSkardel switch (time_sync) { 986abb0f93cSkardel case 'G': 987abb0f93cSkardel return ("synchronized to GPS; should be UTC"); 988abb0f93cSkardel case 'U': 989abb0f93cSkardel break; /* UTC -> ok */ 990abb0f93cSkardel default: 991abb0f93cSkardel return ("not synchronized to UTC"); 992abb0f93cSkardel } 993abb0f93cSkardel 994abb0f93cSkardel /* 995abb0f93cSkardel * Check for insane time (allow for possible leap seconds) 996abb0f93cSkardel */ 997abb0f93cSkardel if (second > 60 || minute > 59 || hour > 23 || 998abb0f93cSkardel second < 0 || minute < 0 || hour < 0) { 999abb0f93cSkardel mx4200_debug(peer, 1000abb0f93cSkardel "mx4200_parse_t: bad time %02d:%02d:%02d", 1001abb0f93cSkardel hour, minute, second); 1002abb0f93cSkardel if (leapsec_warn != 0) 1003abb0f93cSkardel mx4200_debug(peer, " (leap %+d\n)", leapsec_warn); 1004abb0f93cSkardel mx4200_debug(peer, "\n"); 1005abb0f93cSkardel refclock_report(peer, CEVNT_BADTIME); 1006abb0f93cSkardel return ("bad time"); 1007abb0f93cSkardel } 1008abb0f93cSkardel if ( second == 60 ) { 1009abb0f93cSkardel msyslog(LOG_DEBUG, 1010abb0f93cSkardel "mx4200: leap second! %02d:%02d:%02d", 1011abb0f93cSkardel hour, minute, second); 1012abb0f93cSkardel } 1013abb0f93cSkardel 1014abb0f93cSkardel /* 1015abb0f93cSkardel * Check for insane date 1016abb0f93cSkardel * (Certainly can't be any year before this code was last altered!) 1017abb0f93cSkardel */ 1018abb0f93cSkardel if (day_of_month > 31 || month > 12 || 1019abb0f93cSkardel day_of_month < 1 || month < 1 || year < YEAR_LAST_MODIFIED) { 1020abb0f93cSkardel mx4200_debug(peer, 1021abb0f93cSkardel "mx4200_parse_t: bad date (%4d-%02d-%02d)\n", 1022abb0f93cSkardel year, month, day_of_month); 1023abb0f93cSkardel refclock_report(peer, CEVNT_BADDATE); 1024abb0f93cSkardel return ("bad date"); 1025abb0f93cSkardel } 1026abb0f93cSkardel 1027abb0f93cSkardel /* 1028abb0f93cSkardel * Silly Hack for MX4200: 1029abb0f93cSkardel * ASCII message is for *next* 1PPS signal, but we have the 1030abb0f93cSkardel * timestamp for the *last* 1PPS signal. So we have to subtract 1031abb0f93cSkardel * a second. Discard if we are on a month boundary to avoid 1032abb0f93cSkardel * possible leap seconds and leap days. 1033abb0f93cSkardel */ 1034abb0f93cSkardel second--; 1035abb0f93cSkardel if (second < 0) { 1036abb0f93cSkardel second = 59; 1037abb0f93cSkardel minute--; 1038abb0f93cSkardel if (minute < 0) { 1039abb0f93cSkardel minute = 59; 1040abb0f93cSkardel hour--; 1041abb0f93cSkardel if (hour < 0) { 1042abb0f93cSkardel hour = 23; 1043abb0f93cSkardel day_of_month--; 1044abb0f93cSkardel if (day_of_month < 1) { 1045abb0f93cSkardel return ("sorry, month boundary"); 1046abb0f93cSkardel } 1047abb0f93cSkardel } 1048abb0f93cSkardel } 1049abb0f93cSkardel } 1050abb0f93cSkardel 1051abb0f93cSkardel /* 1052abb0f93cSkardel * Calculate Julian date 1053abb0f93cSkardel */ 1054abb0f93cSkardel if (!(day_of_year = mx4200_jday(year, month, day_of_month))) { 1055abb0f93cSkardel mx4200_debug(peer, 1056abb0f93cSkardel "mx4200_parse_t: bad julian date %d (%4d-%02d-%02d)\n", 1057abb0f93cSkardel day_of_year, year, month, day_of_month); 1058abb0f93cSkardel refclock_report(peer, CEVNT_BADDATE); 1059abb0f93cSkardel return("invalid julian date"); 1060abb0f93cSkardel } 1061abb0f93cSkardel 1062abb0f93cSkardel /* 1063abb0f93cSkardel * Setup leap second indicator 1064abb0f93cSkardel */ 1065abb0f93cSkardel switch (leapsec_warn) { 1066abb0f93cSkardel case 0: 1067abb0f93cSkardel pp->leap = LEAP_NOWARNING; 1068abb0f93cSkardel break; 1069abb0f93cSkardel case 1: 1070abb0f93cSkardel pp->leap = LEAP_ADDSECOND; 1071abb0f93cSkardel break; 1072abb0f93cSkardel case -1: 1073abb0f93cSkardel pp->leap = LEAP_DELSECOND; 1074abb0f93cSkardel break; 1075abb0f93cSkardel default: 1076abb0f93cSkardel pp->leap = LEAP_NOTINSYNC; 1077abb0f93cSkardel } 1078abb0f93cSkardel 1079abb0f93cSkardel /* 1080abb0f93cSkardel * Any change to the leap second warning status? 1081abb0f93cSkardel */ 1082abb0f93cSkardel if (leapsec_warn != up->last_leap ) { 1083abb0f93cSkardel msyslog(LOG_DEBUG, 1084abb0f93cSkardel "mx4200: leap second warning: %d to %d (%d)", 1085abb0f93cSkardel up->last_leap, leapsec_warn, pp->leap); 1086abb0f93cSkardel } 1087abb0f93cSkardel up->last_leap = leapsec_warn; 1088abb0f93cSkardel 1089abb0f93cSkardel /* 1090abb0f93cSkardel * Copy time data for billboard monitoring. 1091abb0f93cSkardel */ 1092abb0f93cSkardel 1093abb0f93cSkardel pp->year = year; 1094abb0f93cSkardel pp->day = day_of_year; 1095abb0f93cSkardel pp->hour = hour; 1096abb0f93cSkardel pp->minute = minute; 1097abb0f93cSkardel pp->second = second; 1098abb0f93cSkardel 1099abb0f93cSkardel /* 1100abb0f93cSkardel * Toss if sentence is marked invalid 1101abb0f93cSkardel */ 1102abb0f93cSkardel if (!valid || pp->leap == LEAP_NOTINSYNC) { 1103abb0f93cSkardel mx4200_debug(peer, "mx4200_parse_t: time mark not valid\n"); 1104abb0f93cSkardel refclock_report(peer, CEVNT_BADTIME); 1105abb0f93cSkardel return ("pulse invalid"); 1106abb0f93cSkardel } 1107abb0f93cSkardel 1108abb0f93cSkardel return (NULL); 1109abb0f93cSkardel } 1110abb0f93cSkardel 1111abb0f93cSkardel /* 1112abb0f93cSkardel * Calculate the checksum 1113abb0f93cSkardel */ 1114abb0f93cSkardel static u_char 1115abb0f93cSkardel mx4200_cksum( 1116abb0f93cSkardel register char *cp, 1117abb0f93cSkardel register int n 1118abb0f93cSkardel ) 1119abb0f93cSkardel { 1120abb0f93cSkardel register u_char ck; 1121abb0f93cSkardel 1122abb0f93cSkardel for (ck = 0; n-- > 0; cp++) 1123abb0f93cSkardel ck ^= *cp; 1124abb0f93cSkardel return (ck); 1125abb0f93cSkardel } 1126abb0f93cSkardel 1127abb0f93cSkardel /* 1128abb0f93cSkardel * Tables to compute the day of year. Viva la leap. 1129abb0f93cSkardel */ 1130abb0f93cSkardel static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1131abb0f93cSkardel static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1132abb0f93cSkardel 1133abb0f93cSkardel /* 1134abb0f93cSkardel * Calculate the the Julian Day 1135abb0f93cSkardel */ 1136abb0f93cSkardel static int 1137abb0f93cSkardel mx4200_jday( 1138abb0f93cSkardel int year, 1139abb0f93cSkardel int month, 1140abb0f93cSkardel int day_of_month 1141abb0f93cSkardel ) 1142abb0f93cSkardel { 1143abb0f93cSkardel register int day, i; 1144abb0f93cSkardel int leap_year; 1145abb0f93cSkardel 1146abb0f93cSkardel /* 1147abb0f93cSkardel * Is this a leap year ? 1148abb0f93cSkardel */ 1149abb0f93cSkardel if (year % 4) { 1150abb0f93cSkardel leap_year = 0; /* FALSE */ 1151abb0f93cSkardel } else { 1152abb0f93cSkardel if (year % 100) { 1153abb0f93cSkardel leap_year = 1; /* TRUE */ 1154abb0f93cSkardel } else { 1155abb0f93cSkardel if (year % 400) { 1156abb0f93cSkardel leap_year = 0; /* FALSE */ 1157abb0f93cSkardel } else { 1158abb0f93cSkardel leap_year = 1; /* TRUE */ 1159abb0f93cSkardel } 1160abb0f93cSkardel } 1161abb0f93cSkardel } 1162abb0f93cSkardel 1163abb0f93cSkardel /* 1164abb0f93cSkardel * Calculate the Julian Date 1165abb0f93cSkardel */ 1166abb0f93cSkardel day = day_of_month; 1167abb0f93cSkardel 1168abb0f93cSkardel if (leap_year) { 1169abb0f93cSkardel /* a leap year */ 1170abb0f93cSkardel if (day > day2tab[month - 1]) { 1171abb0f93cSkardel return (0); 1172abb0f93cSkardel } 1173abb0f93cSkardel for (i = 0; i < month - 1; i++) 1174abb0f93cSkardel day += day2tab[i]; 1175abb0f93cSkardel } else { 1176abb0f93cSkardel /* not a leap year */ 1177abb0f93cSkardel if (day > day1tab[month - 1]) { 1178abb0f93cSkardel return (0); 1179abb0f93cSkardel } 1180abb0f93cSkardel for (i = 0; i < month - 1; i++) 1181abb0f93cSkardel day += day1tab[i]; 1182abb0f93cSkardel } 1183abb0f93cSkardel return (day); 1184abb0f93cSkardel } 1185abb0f93cSkardel 1186abb0f93cSkardel /* 1187abb0f93cSkardel * Parse a mx4200 position/height/velocity sentence. 1188abb0f93cSkardel * 1189abb0f93cSkardel * A typical message looks like this. Checksum has already been stripped. 1190abb0f93cSkardel * 1191abb0f93cSkardel * $PMVXG,021,SSSSSS.SS,DDMM.MMMM,N,DDDMM.MMMM,E,HHHHH.H,GGGG.G,EEEE.E,WWWW.W,MM 1192abb0f93cSkardel * 1193abb0f93cSkardel * Field Field Contents 1194abb0f93cSkardel * ----- -------------- 1195abb0f93cSkardel * Block Label: $PMVXG 1196abb0f93cSkardel * Sentence Type: 021=Position, Height Velocity Data 1197abb0f93cSkardel * This sentence gives the receiver position, height, 1198abb0f93cSkardel * navigation mode, and velocity north/east. 1199abb0f93cSkardel * *This sentence is intended for post-analysis 1200abb0f93cSkardel * applications.* 1201abb0f93cSkardel * 1 float UTC measurement time (seconds into week) 1202abb0f93cSkardel * 2 float WGS-84 Lattitude (degrees, minutes) 1203abb0f93cSkardel * 3 char N=North, S=South 1204abb0f93cSkardel * 4 float WGS-84 Longitude (degrees, minutes) 1205abb0f93cSkardel * 5 char E=East, W=West 1206abb0f93cSkardel * 6 float Altitude (meters above mean sea level) 1207abb0f93cSkardel * 7 float Geoidal height (meters) 1208abb0f93cSkardel * 8 float East velocity (m/sec) 1209abb0f93cSkardel * 9 float West Velocity (m/sec) 1210abb0f93cSkardel * 10 int Navigation Mode 1211abb0f93cSkardel * Mode if navigating: 1212abb0f93cSkardel * 1 = Position from remote device 1213abb0f93cSkardel * 2 = 2-D position 1214abb0f93cSkardel * 3 = 3-D position 1215abb0f93cSkardel * 4 = 2-D differential position 1216abb0f93cSkardel * 5 = 3-D differential position 1217abb0f93cSkardel * 6 = Static 1218abb0f93cSkardel * 8 = Position known -- reference station 1219abb0f93cSkardel * 9 = Position known -- Navigator 1220abb0f93cSkardel * Mode if not navigating: 1221abb0f93cSkardel * 51 = Too few satellites 1222abb0f93cSkardel * 52 = DOPs too large 1223abb0f93cSkardel * 53 = Position STD too large 1224abb0f93cSkardel * 54 = Velocity STD too large 1225abb0f93cSkardel * 55 = Too many iterations for velocity 1226abb0f93cSkardel * 56 = Too many iterations for position 1227abb0f93cSkardel * 57 = 3 sat startup failed 1228abb0f93cSkardel * 58 = Command abort 1229abb0f93cSkardel */ 1230abb0f93cSkardel static char * 1231abb0f93cSkardel mx4200_parse_p( 1232abb0f93cSkardel struct peer *peer 1233abb0f93cSkardel ) 1234abb0f93cSkardel { 1235abb0f93cSkardel struct refclockproc *pp; 1236abb0f93cSkardel struct mx4200unit *up; 1237abb0f93cSkardel int sentence_type, mode; 1238abb0f93cSkardel double mtime, lat, lon, alt, geoid, vele, veln; 1239abb0f93cSkardel char north_south, east_west; 1240abb0f93cSkardel 1241abb0f93cSkardel pp = peer->procptr; 12428585484eSchristos up = pp->unitptr; 1243abb0f93cSkardel 1244abb0f93cSkardel /* Should never happen! */ 1245abb0f93cSkardel if (up->moving) return ("mobile platform - no pos!"); 1246abb0f93cSkardel 1247abb0f93cSkardel sscanf ( pp->a_lastcode, 1248abb0f93cSkardel "$PMVXG,%d,%lf,%lf,%c,%lf,%c,%lf,%lf,%lf,%lf,%d", 1249abb0f93cSkardel &sentence_type, &mtime, &lat, &north_south, &lon, &east_west, 1250abb0f93cSkardel &alt, &geoid, &vele, &veln, &mode); 1251abb0f93cSkardel 1252abb0f93cSkardel /* Sentence type */ 1253abb0f93cSkardel if (sentence_type != PMVXG_D_PHV) 1254abb0f93cSkardel return ("wrong rec-type"); 1255abb0f93cSkardel 1256abb0f93cSkardel /* 1257abb0f93cSkardel * return if not navigating 1258abb0f93cSkardel */ 1259abb0f93cSkardel if (mode > 10) 1260abb0f93cSkardel return ("not navigating"); 1261abb0f93cSkardel if (mode != 3 && mode != 5) 1262abb0f93cSkardel return ("not navigating in 3D"); 1263abb0f93cSkardel 1264abb0f93cSkardel /* Latitude (always +ve) and convert DDMM.MMMM to decimal */ 1265abb0f93cSkardel if (lat < 0.0) return ("negative latitude"); 1266abb0f93cSkardel if (lat > 9000.0) lat = 9000.0; 1267abb0f93cSkardel lat *= 0.01; 1268abb0f93cSkardel lat = ((int)lat) + (((lat - (int)lat)) * 1.6666666666666666); 1269abb0f93cSkardel 1270abb0f93cSkardel /* North/South */ 1271abb0f93cSkardel switch (north_south) { 1272abb0f93cSkardel case 'N': 1273abb0f93cSkardel break; 1274abb0f93cSkardel case 'S': 1275abb0f93cSkardel lat *= -1.0; 1276abb0f93cSkardel break; 1277abb0f93cSkardel default: 1278abb0f93cSkardel return ("invalid north/south indicator"); 1279abb0f93cSkardel } 1280abb0f93cSkardel 1281abb0f93cSkardel /* Longitude (always +ve) and convert DDDMM.MMMM to decimal */ 1282abb0f93cSkardel if (lon < 0.0) return ("negative longitude"); 1283abb0f93cSkardel if (lon > 180.0) lon = 180.0; 1284abb0f93cSkardel lon *= 0.01; 1285abb0f93cSkardel lon = ((int)lon) + (((lon - (int)lon)) * 1.6666666666666666); 1286abb0f93cSkardel 1287abb0f93cSkardel /* East/West */ 1288abb0f93cSkardel switch (east_west) { 1289abb0f93cSkardel case 'E': 1290abb0f93cSkardel break; 1291abb0f93cSkardel case 'W': 1292abb0f93cSkardel lon *= -1.0; 1293abb0f93cSkardel break; 1294abb0f93cSkardel default: 1295abb0f93cSkardel return ("invalid east/west indicator"); 1296abb0f93cSkardel } 1297abb0f93cSkardel 1298abb0f93cSkardel /* 1299abb0f93cSkardel * Normalize longitude to near 0 degrees. 1300abb0f93cSkardel * Assume all data are clustered around first reading. 1301abb0f93cSkardel */ 1302abb0f93cSkardel if (up->central_meridian == NOT_INITIALIZED) { 1303abb0f93cSkardel up->central_meridian = lon; 1304abb0f93cSkardel mx4200_debug(peer, 1305abb0f93cSkardel "mx4200_receive: central meridian = %.9f \n", 1306abb0f93cSkardel up->central_meridian); 1307abb0f93cSkardel } 1308abb0f93cSkardel lon -= up->central_meridian; 1309abb0f93cSkardel if (lon < -180.0) lon += 360.0; 1310abb0f93cSkardel if (lon > 180.0) lon -= 360.0; 1311abb0f93cSkardel 1312abb0f93cSkardel /* 1313abb0f93cSkardel * Calculate running averages 1314abb0f93cSkardel */ 1315abb0f93cSkardel 1316abb0f93cSkardel up->avg_lon = (up->N_fixes * up->avg_lon) + lon; 1317abb0f93cSkardel up->avg_lat = (up->N_fixes * up->avg_lat) + lat; 1318abb0f93cSkardel up->avg_alt = (up->N_fixes * up->avg_alt) + alt; 1319abb0f93cSkardel 1320abb0f93cSkardel up->N_fixes += 1.0; 1321abb0f93cSkardel 1322abb0f93cSkardel up->avg_lon /= up->N_fixes; 1323abb0f93cSkardel up->avg_lat /= up->N_fixes; 1324abb0f93cSkardel up->avg_alt /= up->N_fixes; 1325abb0f93cSkardel 1326abb0f93cSkardel mx4200_debug(peer, 1327abb0f93cSkardel "mx4200_receive: position rdg %.0f: %.9f %.9f %.4f (CM=%.9f)\n", 1328abb0f93cSkardel up->N_fixes, lat, lon, alt, up->central_meridian); 1329abb0f93cSkardel 1330abb0f93cSkardel return (NULL); 1331abb0f93cSkardel } 1332abb0f93cSkardel 1333abb0f93cSkardel /* 1334abb0f93cSkardel * Parse a mx4200 Status sentence 1335abb0f93cSkardel * Parse a mx4200 Mode Data sentence 1336abb0f93cSkardel * Parse a mx4200 Software Configuration sentence 1337abb0f93cSkardel * Parse a mx4200 Time Recovery Parameters Currently in Use sentence 1338abb0f93cSkardel * (used only for logging raw strings) 1339abb0f93cSkardel * 1340abb0f93cSkardel * A typical message looks like this. Checksum has already been stripped. 1341abb0f93cSkardel * 1342abb0f93cSkardel * $PMVXG,000,XXX,XX,X,HHMM,X 1343abb0f93cSkardel * 1344abb0f93cSkardel * Field Field Contents 1345abb0f93cSkardel * ----- -------------- 1346abb0f93cSkardel * Block Label: $PMVXG 1347abb0f93cSkardel * Sentence Type: 000=Status. 1348abb0f93cSkardel * Returns status of the receiver to the controller. 1349abb0f93cSkardel * 1 Current Receiver Status: 1350abb0f93cSkardel * ACQ = Satellite re-acquisition 1351abb0f93cSkardel * ALT = Constellation selection 1352abb0f93cSkardel * COR = Providing corrections (for reference stations only) 1353abb0f93cSkardel * IAC = Initial acquisition 1354abb0f93cSkardel * IDL = Idle, no satellites 1355abb0f93cSkardel * NAV = Navigation 1356abb0f93cSkardel * STS = Search the Sky (no almanac available) 1357abb0f93cSkardel * TRK = Tracking 1358abb0f93cSkardel * 2 Number of satellites that should be visible 1359abb0f93cSkardel * 3 Number of satellites being tracked 1360abb0f93cSkardel * 4 Time since last navigation status if not currently navigating 1361abb0f93cSkardel * (hours, minutes) 1362abb0f93cSkardel * 5 Initialization status: 1363abb0f93cSkardel * 0 = Waiting for initialization parameters 1364abb0f93cSkardel * 1 = Initialization completed 1365abb0f93cSkardel * 1366abb0f93cSkardel * A typical message looks like this. Checksum has already been stripped. 1367abb0f93cSkardel * 1368abb0f93cSkardel * $PMVXG,004,C,R,D,H.HH,V.VV,TT,HHHH,VVVV,T 1369abb0f93cSkardel * 1370abb0f93cSkardel * Field Field Contents 1371abb0f93cSkardel * ----- -------------- 1372abb0f93cSkardel * Block Label: $PMVXG 1373abb0f93cSkardel * Sentence Type: 004=Software Configuration. 1374abb0f93cSkardel * Defines the navigation mode and criteria for 1375abb0f93cSkardel * acceptable navigation for the receiver. 1376abb0f93cSkardel * 1 Constrain Altitude Mode: 1377abb0f93cSkardel * 0 = Auto. Constrain altitude (2-D solution) and use 1378abb0f93cSkardel * manual altitude input when 3 sats avalable. Do 1379abb0f93cSkardel * not constrain altitude (3-D solution) when 4 sats 1380abb0f93cSkardel * available. 1381abb0f93cSkardel * 1 = Always constrain altitude (2-D solution). 1382abb0f93cSkardel * 2 = Never constrain altitude (3-D solution). 1383abb0f93cSkardel * 3 = Coast. Constrain altitude (2-D solution) and use 1384abb0f93cSkardel * last GPS altitude calculation when 3 sats avalable. 1385abb0f93cSkardel * Do not constrain altitude (3-D solution) when 4 sats 1386abb0f93cSkardel * available. 1387abb0f93cSkardel * 2 Altitude Reference: (always 0 for MX4200) 1388abb0f93cSkardel * 0 = Ellipsoid 1389abb0f93cSkardel * 1 = Geoid (MSL) 1390abb0f93cSkardel * 3 Differential Navigation Control: 1391abb0f93cSkardel * 0 = Disabled 1392abb0f93cSkardel * 1 = Enabled 1393abb0f93cSkardel * 4 Horizontal Acceleration Constant (m/sec**2) 1394abb0f93cSkardel * 5 Vertical Acceleration Constant (m/sec**2) (0 for MX4200) 1395abb0f93cSkardel * 6 Tracking Elevation Limit (degrees) 1396abb0f93cSkardel * 7 HDOP Limit 1397abb0f93cSkardel * 8 VDOP Limit 1398abb0f93cSkardel * 9 Time Output Mode: 1399abb0f93cSkardel * U = UTC 1400abb0f93cSkardel * L = Local time 1401abb0f93cSkardel * 10 Local Time Offset (minutes) (absent on MX4200) 1402abb0f93cSkardel * 1403abb0f93cSkardel * A typical message looks like this. Checksum has already been stripped. 1404abb0f93cSkardel * 1405abb0f93cSkardel * $PMVXG,030,NNNN,FFF 1406abb0f93cSkardel * 1407abb0f93cSkardel * Field Field Contents 1408abb0f93cSkardel * ----- -------------- 1409abb0f93cSkardel * Block Label: $PMVXG 1410abb0f93cSkardel * Sentence Type: 030=Software Configuration. 1411abb0f93cSkardel * This sentence contains the navigation processor 1412abb0f93cSkardel * and baseband firmware version numbers. 1413abb0f93cSkardel * 1 Nav Processor Version Number 1414abb0f93cSkardel * 2 Baseband Firmware Version Number 1415abb0f93cSkardel * 1416abb0f93cSkardel * A typical message looks like this. Checksum has already been stripped. 1417abb0f93cSkardel * 1418abb0f93cSkardel * $PMVXG,523,M,S,M,EEEE,BBBBBB,C,R 1419abb0f93cSkardel * 1420abb0f93cSkardel * Field Field Contents 1421abb0f93cSkardel * ----- -------------- 1422abb0f93cSkardel * Block Label: $PMVXG 1423abb0f93cSkardel * Sentence Type: 523=Time Recovery Parameters Currently in Use. 1424abb0f93cSkardel * This sentence contains the configuration of the 1425abb0f93cSkardel * time recovery feature of the receiver. 1426abb0f93cSkardel * 1 Time Recovery Mode: 1427abb0f93cSkardel * D = Dynamic; solve for position and time while moving 1428abb0f93cSkardel * S = Static; solve for position and time while stationary 1429abb0f93cSkardel * K = Known position input, solve for time only 1430abb0f93cSkardel * N = No time recovery 1431abb0f93cSkardel * 2 Time Synchronization: 1432abb0f93cSkardel * U = UTC time 1433abb0f93cSkardel * G = GPS time 1434abb0f93cSkardel * 3 Time Mark Mode: 1435abb0f93cSkardel * A = Always output a time pulse 1436abb0f93cSkardel * V = Only output time pulse if time is valid (as determined 1437abb0f93cSkardel * by Maximum Time Error) 1438abb0f93cSkardel * 4 Maximum Time Error - the maximum error (in nanoseconds) for 1439abb0f93cSkardel * which a time mark will be considered valid. 1440abb0f93cSkardel * 5 User Time Bias - external bias in nanoseconds 1441abb0f93cSkardel * 6 Time Message Control: 1442abb0f93cSkardel * 0 = Do not output the time recovery message 1443abb0f93cSkardel * 1 = Output the time recovery message (record 830) to 1444abb0f93cSkardel * Control port 1445abb0f93cSkardel * 2 = Output the time recovery message (record 830) to 1446abb0f93cSkardel * Equipment port 1447abb0f93cSkardel * 7 Reserved 1448abb0f93cSkardel * 8 Position Known PRN (absent on MX 4200) 1449abb0f93cSkardel * 1450abb0f93cSkardel */ 1451abb0f93cSkardel static char * 1452abb0f93cSkardel mx4200_parse_s( 1453abb0f93cSkardel struct peer *peer 1454abb0f93cSkardel ) 1455abb0f93cSkardel { 1456abb0f93cSkardel struct refclockproc *pp; 1457abb0f93cSkardel struct mx4200unit *up; 1458abb0f93cSkardel int sentence_type; 1459abb0f93cSkardel 1460abb0f93cSkardel pp = peer->procptr; 14618585484eSchristos up = pp->unitptr; 1462abb0f93cSkardel 1463abb0f93cSkardel sscanf ( pp->a_lastcode, "$PMVXG,%d", &sentence_type); 1464abb0f93cSkardel 1465abb0f93cSkardel /* Sentence type */ 1466abb0f93cSkardel switch (sentence_type) { 1467abb0f93cSkardel 1468abb0f93cSkardel case PMVXG_D_STATUS: 1469abb0f93cSkardel msyslog(LOG_DEBUG, 1470abb0f93cSkardel "mx4200: status: %s", pp->a_lastcode); 1471abb0f93cSkardel break; 1472abb0f93cSkardel case PMVXG_D_MODEDATA: 1473abb0f93cSkardel msyslog(LOG_DEBUG, 1474abb0f93cSkardel "mx4200: mode data: %s", pp->a_lastcode); 1475abb0f93cSkardel break; 1476abb0f93cSkardel case PMVXG_D_SOFTCONF: 1477abb0f93cSkardel msyslog(LOG_DEBUG, 1478abb0f93cSkardel "mx4200: firmware configuration: %s", pp->a_lastcode); 1479abb0f93cSkardel break; 1480abb0f93cSkardel case PMVXG_D_TRECOVUSEAGE: 1481abb0f93cSkardel msyslog(LOG_DEBUG, 1482abb0f93cSkardel "mx4200: time recovery parms: %s", pp->a_lastcode); 1483abb0f93cSkardel break; 1484abb0f93cSkardel default: 1485abb0f93cSkardel return ("wrong rec-type"); 1486abb0f93cSkardel } 1487abb0f93cSkardel 1488abb0f93cSkardel return (NULL); 1489abb0f93cSkardel } 1490abb0f93cSkardel 1491abb0f93cSkardel /* 1492abb0f93cSkardel * Process a PPS signal, placing a timestamp in pp->lastrec. 1493abb0f93cSkardel */ 1494abb0f93cSkardel static int 1495abb0f93cSkardel mx4200_pps( 1496abb0f93cSkardel struct peer *peer 1497abb0f93cSkardel ) 1498abb0f93cSkardel { 1499abb0f93cSkardel int temp_serial; 1500abb0f93cSkardel struct refclockproc *pp; 1501abb0f93cSkardel struct mx4200unit *up; 1502abb0f93cSkardel 1503abb0f93cSkardel struct timespec timeout; 1504abb0f93cSkardel 1505abb0f93cSkardel pp = peer->procptr; 15068585484eSchristos up = pp->unitptr; 1507abb0f93cSkardel 1508abb0f93cSkardel /* 1509abb0f93cSkardel * Grab the timestamp of the PPS signal. 1510abb0f93cSkardel */ 1511abb0f93cSkardel temp_serial = up->pps_i.assert_sequence; 1512abb0f93cSkardel timeout.tv_sec = 0; 1513abb0f93cSkardel timeout.tv_nsec = 0; 1514abb0f93cSkardel if (time_pps_fetch(up->pps_h, PPS_TSFMT_TSPEC, &(up->pps_i), 1515abb0f93cSkardel &timeout) < 0) { 1516abb0f93cSkardel mx4200_debug(peer, 15178585484eSchristos "mx4200_pps: time_pps_fetch: serial=%lu, %m\n", 15188585484eSchristos (unsigned long)up->pps_i.assert_sequence); 1519abb0f93cSkardel refclock_report(peer, CEVNT_FAULT); 1520abb0f93cSkardel return(1); 1521abb0f93cSkardel } 1522abb0f93cSkardel if (temp_serial == up->pps_i.assert_sequence) { 1523abb0f93cSkardel mx4200_debug(peer, 1524abb0f93cSkardel "mx4200_pps: assert_sequence serial not incrementing: %lu\n", 1525abb0f93cSkardel (unsigned long)up->pps_i.assert_sequence); 1526abb0f93cSkardel refclock_report(peer, CEVNT_FAULT); 1527abb0f93cSkardel return(1); 1528abb0f93cSkardel } 1529abb0f93cSkardel /* 1530abb0f93cSkardel * Check pps serial number against last one 1531abb0f93cSkardel */ 1532abb0f93cSkardel if (up->lastserial + 1 != up->pps_i.assert_sequence && 1533abb0f93cSkardel up->lastserial != 0) { 1534abb0f93cSkardel if (up->pps_i.assert_sequence == up->lastserial) { 1535abb0f93cSkardel mx4200_debug(peer, "mx4200_pps: no new pps event\n"); 1536abb0f93cSkardel } else { 1537abb0f93cSkardel mx4200_debug(peer, "mx4200_pps: missed %lu pps events\n", 1538abb0f93cSkardel up->pps_i.assert_sequence - up->lastserial - 1UL); 1539abb0f93cSkardel } 1540abb0f93cSkardel refclock_report(peer, CEVNT_FAULT); 1541abb0f93cSkardel } 1542abb0f93cSkardel up->lastserial = up->pps_i.assert_sequence; 1543abb0f93cSkardel 1544abb0f93cSkardel /* 1545abb0f93cSkardel * Return the timestamp in pp->lastrec 1546abb0f93cSkardel */ 1547abb0f93cSkardel 1548abb0f93cSkardel pp->lastrec.l_ui = up->pps_i.assert_timestamp.tv_sec + 1549abb0f93cSkardel (u_int32) JAN_1970; 1550abb0f93cSkardel pp->lastrec.l_uf = ((double)(up->pps_i.assert_timestamp.tv_nsec) * 1551abb0f93cSkardel 4.2949672960) + 0.5; 1552abb0f93cSkardel 1553abb0f93cSkardel return(0); 1554abb0f93cSkardel } 1555abb0f93cSkardel 1556abb0f93cSkardel /* 1557abb0f93cSkardel * mx4200_debug - print debug messages 1558abb0f93cSkardel */ 1559abb0f93cSkardel static void 1560abb0f93cSkardel mx4200_debug(struct peer *peer, char *fmt, ...) 1561abb0f93cSkardel { 1562abb0f93cSkardel #ifdef DEBUG 1563abb0f93cSkardel va_list ap; 1564abb0f93cSkardel struct refclockproc *pp; 1565abb0f93cSkardel struct mx4200unit *up; 1566abb0f93cSkardel 1567abb0f93cSkardel if (debug) { 1568abb0f93cSkardel va_start(ap, fmt); 1569abb0f93cSkardel 1570abb0f93cSkardel pp = peer->procptr; 15718585484eSchristos up = pp->unitptr; 1572abb0f93cSkardel 1573abb0f93cSkardel /* 1574abb0f93cSkardel * Print debug message to stdout 1575abb0f93cSkardel * In the future, we may want to get get more creative... 1576abb0f93cSkardel */ 15778585484eSchristos mvprintf(fmt, ap); 1578abb0f93cSkardel 1579abb0f93cSkardel va_end(ap); 1580abb0f93cSkardel } 1581abb0f93cSkardel #endif 1582abb0f93cSkardel } 1583abb0f93cSkardel 1584abb0f93cSkardel /* 1585abb0f93cSkardel * Send a character string to the receiver. Checksum is appended here. 1586abb0f93cSkardel */ 1587abb0f93cSkardel #if defined(__STDC__) 1588abb0f93cSkardel static void 1589abb0f93cSkardel mx4200_send(struct peer *peer, char *fmt, ...) 1590abb0f93cSkardel #else 1591abb0f93cSkardel static void 1592abb0f93cSkardel mx4200_send(peer, fmt, va_alist) 1593abb0f93cSkardel struct peer *peer; 1594abb0f93cSkardel char *fmt; 1595abb0f93cSkardel va_dcl 1596abb0f93cSkardel #endif /* __STDC__ */ 1597abb0f93cSkardel { 1598abb0f93cSkardel struct refclockproc *pp; 1599abb0f93cSkardel struct mx4200unit *up; 1600abb0f93cSkardel 1601ccc794f0Schristos register char *cp, *ep; 1602abb0f93cSkardel register int n, m; 1603abb0f93cSkardel va_list ap; 1604abb0f93cSkardel char buf[1024]; 1605abb0f93cSkardel u_char ck; 1606abb0f93cSkardel 1607ccc794f0Schristos pp = peer->procptr; 1608ccc794f0Schristos up = pp->unitptr; 1609ccc794f0Schristos 1610ccc794f0Schristos cp = buf; 1611ccc794f0Schristos ep = cp + sizeof(buf); 1612ccc794f0Schristos *cp++ = '$'; 1613ccc794f0Schristos 1614abb0f93cSkardel #if defined(__STDC__) 1615abb0f93cSkardel va_start(ap, fmt); 1616abb0f93cSkardel #else 1617abb0f93cSkardel va_start(ap); 1618abb0f93cSkardel #endif /* __STDC__ */ 1619ccc794f0Schristos n = VSNPRINTF((cp, (size_t)(ep - cp), fmt, ap)); 1620ccc794f0Schristos va_end(ap); 1621ccc794f0Schristos if (n < 0 || (size_t)n >= (size_t)(ep - cp)) 1622ccc794f0Schristos goto overflow; 1623abb0f93cSkardel 1624abb0f93cSkardel ck = mx4200_cksum(cp, n); 1625abb0f93cSkardel cp += n; 1626ccc794f0Schristos n = SNPRINTF((cp, (size_t)(ep - cp), "*%02X\r\n", ck)); 1627ccc794f0Schristos if (n < 0 || (size_t)n >= (size_t)(ep - cp)) 1628ccc794f0Schristos goto overflow; 1629ccc794f0Schristos cp += n; 1630ccc794f0Schristos m = write(pp->io.fd, buf, (unsigned)(cp - buf)); 1631abb0f93cSkardel if (m < 0) 1632abb0f93cSkardel msyslog(LOG_ERR, "mx4200_send: write: %m (%s)", buf); 1633abb0f93cSkardel mx4200_debug(peer, "mx4200_send: %d %s\n", m, buf); 1634ccc794f0Schristos 1635ccc794f0Schristos overflow: 1636ccc794f0Schristos msyslog(LOG_ERR, "mx4200_send: %s", "data exceeds buffer size"); 1637abb0f93cSkardel } 1638abb0f93cSkardel 1639abb0f93cSkardel #else 1640*eabc0478Schristos NONEMPTY_TRANSLATION_UNIT 1641abb0f93cSkardel #endif /* REFCLOCK */ 1642