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