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