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