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