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