xref: /freebsd/contrib/ntp/ntpd/refclock_mx4200.c (revision c66ec88fed842fbaad62c30d510644ceb7bd2d71)
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
71 struct 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  */
128 struct 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 
149 static 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  */
168 static	int	mx4200_start	(int, struct peer *);
169 static	void	mx4200_shutdown	(int, struct peer *);
170 static	void	mx4200_receive	(struct recvbuf *);
171 static	void	mx4200_poll	(int, struct peer *);
172 
173 static	char *	mx4200_parse_t	(struct peer *);
174 static	char *	mx4200_parse_p	(struct peer *);
175 static	char *	mx4200_parse_s	(struct peer *);
176 int	mx4200_cmpl_fp	(const void *, const void *);
177 static	int	mx4200_config	(struct peer *);
178 static	void	mx4200_ref	(struct peer *);
179 static	void	mx4200_send	(struct peer *, char *, ...)
180     __attribute__ ((format (printf, 2, 3)));
181 static	u_char	mx4200_cksum	(char *, int);
182 static	int	mx4200_jday	(int, int, int);
183 static	void	mx4200_debug	(struct peer *, char *, ...)
184     __attribute__ ((format (printf, 2, 3)));
185 static	int	mx4200_pps	(struct peer *);
186 
187 /*
188  * Transfer vector
189  */
190 struct	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  */
205 static int
206 mx4200_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  */
256 static void
257 mx4200_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  */
277 static int
278 mx4200_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  */
477 static void
478 mx4200_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  */
597 static void
598 mx4200_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 
654 static char char2hex[] = "0123456789ABCDEF";
655 
656 /*
657  * mx4200_receive - receive gps data
658  */
659 static void
660 mx4200_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  */
946 static char *
947 mx4200_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  */
1112 static u_char
1113 mx4200_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  */
1128 static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1129 static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1130 
1131 /*
1132  * Calculate the the Julian Day
1133  */
1134 static int
1135 mx4200_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  */
1228 static char *
1229 mx4200_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  */
1449 static char *
1450 mx4200_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  */
1492 static int
1493 mx4200_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  */
1557 static void
1558 mx4200_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__)
1586 static void
1587 mx4200_send(struct peer *peer, char *fmt, ...)
1588 #else
1589 static void
1590 mx4200_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, *ep;
1600 	register int n, m;
1601 	va_list ap;
1602 	char buf[1024];
1603 	u_char ck;
1604 
1605 	pp = peer->procptr;
1606 	up = pp->unitptr;
1607 
1608 	cp = buf;
1609 	ep = cp + sizeof(buf);
1610 	*cp++ = '$';
1611 
1612 #if defined(__STDC__)
1613 	va_start(ap, fmt);
1614 #else
1615 	va_start(ap);
1616 #endif /* __STDC__ */
1617 	n = VSNPRINTF((cp, (size_t)(ep - cp), fmt, ap));
1618 	va_end(ap);
1619 	if (n < 0 || (size_t)n >= (size_t)(ep - cp))
1620 		goto overflow;
1621 
1622 	ck = mx4200_cksum(cp, n);
1623 	cp += n;
1624 	n = SNPRINTF((cp, (size_t)(ep - cp), "*%02X\r\n", ck));
1625 	if (n < 0 || (size_t)n >= (size_t)(ep - cp))
1626 		goto overflow;
1627 	cp += n;
1628 	m = write(pp->io.fd, buf, (unsigned)(cp - buf));
1629 	if (m < 0)
1630 		msyslog(LOG_ERR, "mx4200_send: write: %m (%s)", buf);
1631 	mx4200_debug(peer, "mx4200_send: %d %s\n", m, buf);
1632 
1633   overflow:
1634 	msyslog(LOG_ERR, "mx4200_send: %s", "data exceeds buffer size");
1635 }
1636 
1637 #else
1638 int refclock_mx4200_bs;
1639 #endif /* REFCLOCK */
1640