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