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