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