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