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