source: trunk/third/xntp/xntpd/refclock_mx4200.c @ 10832

Revision 10832, 51.0 KB checked in by brlewis, 27 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r10831, which included commits to RCS files with non-trunk default branches.
Line 
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#ifdef HAVE_CONFIG_H
38# include <config.h>
39#endif
40
41#if defined(REFCLOCK) && defined(MX4200) && defined(PPS)
42
43#include <stdio.h>
44#include <ctype.h>
45#include <sys/time.h>
46#include <errno.h>
47
48#include "ntpd.h"
49#include "ntp_io.h"
50#include "ntp_refclock.h"
51#include "ntp_unixtime.h"
52#include "ntp_stdlib.h"
53
54#include "mx4200.h"
55
56#if __STDC__
57#include <stdarg.h>
58#else
59#include <varargs.h>
60#endif /* __STDC__ */
61
62#ifdef PPS
63#include <sys/ppsclock.h>
64#endif /* PPS */
65
66/*
67 * This driver supports the Magnavox Model MX 4200 GPS Receiver
68 * adapted to precision timing applications.  It requires the
69 * ppsclock line discipline or streams module described in the
70 * Line Disciplines and Streams Drivers page. It also requires a
71 * gadget box and 1-PPS level converter, such as described in the
72 * Pulse-per-second (PPS) Signal Interfacing page.
73 *
74 * It's likely that other compatible Magnavox receivers such as the
75 * MX 4200D, MX 9212, MX 9012R, MX 9112 will be supported by this code.
76 */
77
78/*
79 * GPS Definitions
80 */
81#define DEVICE          "/dev/gps%d"    /* device name and unit */
82#define SPEED232        B4800           /* baud */
83
84/*
85 * The number of raw samples which we acquire to derive a single estimate.
86 * NSAMPLES ideally should not exceed the default poll interval 64.
87 * NKEEP must be a power of 2 to simplify the averaging process.
88 */
89#define NSAMPLES        64
90#define NKEEP           8
91#define REFCLOCKMAXDISPERSE (FP_SECOND/4) /* max sample dispersion */
92
93/*
94 * Radio interface parameters
95 */
96#define PRECISION       (-18)   /* precision assumed (about 4 us) */
97#define REFID   "GPS\0"         /* reference id */
98#define DESCRIPTION     "Magnavox MX4200 GPS Receiver" /* who we are */
99#define DEFFUDGETIME    0       /* default fudge time (ms) */
100
101#define SLEEPTIME       32      /* seconds to wait for reconfig to complete */
102
103/*
104 * Position Averaging.
105 * Reference: Dr. Thomas A. Clark's Totally Accurate Clock (TAC) files at
106 * ftp://aleph.gsfc.nasa.gov/GPS/totally.accurate.clock/
107 */
108#define INTERVAL        1       /* Interval between position measurements (s) */
109#define AVGING_TIME     24      /* Number of hours to average */
110#define USUAL_EDOP      0.75    /* used for normalizing EDOP */
111#define USUAL_NDOP      0.75    /* used for normalizing NDOP */
112#define USUAL_VDOP      1.70    /* used for normalizing VDOP */
113
114/*
115 * Imported from the ntp_timer module
116 */
117extern u_long current_time;     /* current time (s) */
118
119/*
120 * Imported from ntpd module
121 */
122extern int debug;               /* global debug flag */
123
124#ifdef PPS
125/*
126 * Imported from loop_filter module
127 */
128extern int fdpps;               /* ppsclock file descriptor */
129#endif /* PPS */
130
131/*
132 * Imported from perror(3)
133 */
134extern int  sys_nerr;
135extern char *sys_errlist[];
136extern int  errno;
137
138/*
139 * MX4200 unit control structure.
140 */
141struct mx4200unit {
142        u_int  pollcnt;                 /* poll message counter */
143        u_int  polled;                  /* Hand in a time sample? */
144#ifdef PPS
145        u_int  lastserial;              /* last pps serial number */
146        struct ppsclockev ppsev;        /* PPS control structure */
147#endif /* PPS */
148        double avg_lat;                 /* average latitude */
149        double avg_lon;                 /* average longitude */
150        double avg_alt;                 /* average height */
151        double filt_lat;                /* latitude filter length */
152        double filt_lon;                /* longitude filter length */
153        double filt_alt;                /* height filter length */
154        double edop;                    /* EDOP (east DOP) */
155        double ndop;                    /* NDOP (north DOP) */
156        double vdop;                    /* VDOP (vertical DOP) */
157        int    last_leap;               /* leap second warning */
158        u_int  moving;                  /* mobile platform? */
159        u_long sloppyclockflag;         /* fudge flags */
160        u_int  known;                   /* position known yet? */
161        u_long clamp_time;              /* when to stop postion averaging */
162        u_long log_time;                /* when to print receiver status */
163        int    coderecv;                /* total received samples */
164        int    nkeep;                   /* number of samples to preserve */
165        int    rshift;                  /* number of rshifts for division */
166        l_fp   filter[NSAMPLES];        /* offset filter */
167        l_fp   lastref;                 /* last reference timestamp */
168};
169
170static char pmvxg[] = "PMVXG";
171
172/*
173 * Function prototypes
174 */
175static  int     mx4200_start    P((int, struct peer *));
176static  void    mx4200_shutdown P((int, struct peer *));
177static  void    mx4200_receive  P((struct recvbuf *));
178static  void    mx4200_poll     P((int, struct peer *));
179
180static  char *  mx4200_parse_t  P((struct peer *));
181static  char *  mx4200_parse_p  P((struct peer *));
182static  char *  mx4200_parse_d  P((struct peer *));
183static  char *  mx4200_parse_s  P((struct peer *));
184static  char *  mx4200_offset   P((struct peer *));
185static  char *  mx4200_process  P((struct peer *));
186#ifdef QSORT_USES_VOID_P
187        int     mx4200_cmpl_fp  P((const void *, const void *));
188#else
189        int     mx4200_cmpl_fp  P((const l_fp *, const l_fp *));
190#endif /* not QSORT_USES_VOID_P */
191static  void    mx4200_config   P((struct peer *));
192static  void    mx4200_ref      P((struct peer *));
193static  void    mx4200_send     P((struct peer *, char *, ...));
194static  u_char  mx4200_cksum    P((char *, u_int));
195static  int     mx4200_jday     P((int, int, int));
196static  void    mx4200_debug    P((struct peer *, char *, ...));
197static  int     mx4200_pps      P((struct peer *));
198
199/*
200 * Transfer vector
201 */
202struct  refclock refclock_mx4200 = {
203        mx4200_start,           /* start up driver */
204        mx4200_shutdown,        /* shut down driver */
205        mx4200_poll,            /* transmit poll message */
206        noentry,                /* not used (old mx4200_control) */
207        noentry,                /* initialize driver (not used) */
208        noentry,                /* not used (old mx4200_buginfo) */
209        NOFLAGS                 /* not used */
210};
211
212
213
214/*
215 * mx4200_start - open the devices and initialize data for processing
216 */
217static int
218mx4200_start(unit, peer)
219        int unit;
220        struct peer *peer;
221{
222        register struct mx4200unit *up;
223        struct refclockproc *pp;
224        int fd;
225        char gpsdev[20];
226
227        /*
228         * Open serial port
229         */
230        (void)sprintf(gpsdev, DEVICE, unit);
231        if (!(fd = refclock_open(gpsdev, SPEED232,
232#ifdef PPS
233                                 LDISC_PPS
234#else  /* not PPS */
235                                 0
236#endif /* not PPS */
237                                 )))
238                return (0);
239
240        /*
241         * Allocate unit structure
242         */
243        if (!(up = (struct mx4200unit *) emalloc(sizeof(struct mx4200unit)))) {
244                (void) close(fd);
245                return (0);
246        }
247        memset((char *)up, 0, sizeof(struct mx4200unit));
248        pp = peer->procptr;
249        pp->io.clock_recv = mx4200_receive;
250        pp->io.srcclock = (caddr_t)peer;
251        pp->io.datalen = 0;
252        pp->io.fd = fd;
253        if (!io_addclock(&pp->io)) {
254                (void) close(fd);
255                free(up);
256                return (0);
257        }
258        pp->unitptr = (caddr_t)up;
259
260        /*
261         * Initialize miscellaneous variables
262         */
263        peer->precision = PRECISION;
264        pp->clockdesc = DESCRIPTION;
265        memcpy((char *)&pp->refid, REFID, 4);
266
267
268        /* Ensure the receiver is properly configured */
269        mx4200_config(peer);
270        return (1);
271}
272
273
274/*
275 * mx4200_shutdown - shut down the clock
276 */
277static void
278mx4200_shutdown(unit, peer)
279        int unit;
280        struct peer *peer;
281{
282        register struct mx4200unit *up;
283        struct refclockproc *pp;
284
285        pp = peer->procptr;
286        up = (struct mx4200unit *)pp->unitptr;
287        io_closeclock(&pp->io);
288        free(up);
289}
290
291
292/*
293 * mx4200_config - Configure the receiver
294 */
295static void
296mx4200_config(peer)
297        struct peer *peer;
298{
299        char tr_mode;
300        char add_mode;
301        int i;
302        register struct mx4200unit *up;
303        struct refclockproc *pp;
304
305        pp = peer->procptr;
306        up = (struct mx4200unit *)pp->unitptr;
307
308        /*
309         * Initialize the unit variables
310         *
311         * STRANGE BEHAVIOUR WARNING: The fudge flags are not available
312         * at the time mx4200_start is called.  These are set later,
313         * and so the code must be prepared to handle changing flags.
314         */
315        up->sloppyclockflag = pp->sloppyclockflag;
316        if (pp->sloppyclockflag & CLK_FLAG2) {
317                up->moving   = 1;       /* Receiver on mobile platform */
318                msyslog(LOG_DEBUG, "mx4200_config: mobile platform");
319        } else {
320                up->moving   = 0;       /* Static Installation */
321        }
322        up->pollcnt     = 2;
323        up->polled      = 0;
324        up->known       = 0;
325        up->avg_lat     = 0.0;
326        up->avg_lon     = 0.0;
327        up->avg_alt     = 0.0;
328        up->filt_lat    = 0.0;
329        up->filt_lon    = 0.0;
330        up->filt_alt    = 0.0;
331        up->edop        = USUAL_EDOP;
332        up->ndop        = USUAL_NDOP;
333        up->vdop        = USUAL_VDOP;
334        up->last_leap   = 0;    /* LEAP_NOWARNING */
335        up->clamp_time  = current_time + (AVGING_TIME * 60 * 60);
336        up->log_time    = current_time + SLEEPTIME;
337        up->coderecv    = 0;
338        up->nkeep       = NKEEP;
339        if (up->nkeep > NSAMPLES) up->nkeep = NSAMPLES;
340        if (up->nkeep >=   1) up->rshift = 0;
341        if (up->nkeep >=   2) up->rshift = 1;
342        if (up->nkeep >=   4) up->rshift = 2;
343        if (up->nkeep >=   8) up->rshift = 3;
344        if (up->nkeep >=  16) up->rshift = 4;
345        if (up->nkeep >=  32) up->rshift = 5;
346        if (up->nkeep >=  64) up->rshift = 6;
347        up->nkeep =1;
348        i = up->rshift;
349        while (i > 0) {
350                up->nkeep *= 2;
351                i--;
352        }
353
354        /*
355         * "007" Control Port Configuration
356         * Zero the output list (do it twice to flush possible junk)
357         */
358        mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg,
359            PMVXG_S_PORTCONF,
360                        /* control port output block Label */
361            1);         /* clear current output control list (1=yes) */
362                        /* add/delete sentences from list */
363                        /* must be null */
364                        /* sentence output rate (sec) */
365                        /* precision for position output */
366                        /* nmea version for cga & gll output */
367                        /* pass-through control */
368        mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg,
369            PMVXG_S_PORTCONF, 1);
370
371        /*
372         * Request software configuration so we can syslog the firmware version
373         */
374        mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_SOFTCONF);
375
376        /*
377         * "001" Initialization/Mode Control, Part A
378         * Where ARE we?
379         */
380        mx4200_send(peer, "%s,%03d,,,,,,,,,,", pmvxg,
381            PMVXG_S_INITMODEA);
382                        /* day of month */
383                        /* month of year */
384                        /* year */
385                        /* gmt */
386                        /* latitude   DDMM.MMMM */
387                        /* north/south */
388                        /* longitude DDDMM.MMMM */
389                        /* east/west */
390                        /* height */
391                        /* Altitude Reference 1=MSL */
392
393        /*
394         * "001" Initialization/Mode Control, Part B
395         * Start off in 2d/3d coast mode, holding altitude to last known
396         * value if only 3 satellites available.
397         */
398        mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
399            pmvxg, PMVXG_S_INITMODEB,
400            3,          /* 2d/3d coast */
401                        /* reserved */
402            0.1,        /* hor accel fact as per Steve (m/s**2) */
403            0.1,        /* ver accel fact as per Steve (m/s**2) */
404            10,         /* vdop */
405            10,         /* hdop limit as per Steve */
406            5,          /* elevation limit as per Steve (deg) */
407            'U',        /* time output mode (UTC) */
408            0);         /* local time offset from gmt (HHHMM) */
409
410        /*
411         * "023" Time Recovery Configuration
412         * Get UTC time from a stationary receiver.
413         * (Set field 1 'D' == dynamic if we are on a moving platform).
414         * (Set field 1 'S' == static  if we are not moving).
415         * (Set field 1 'K' == known position if we can initialize lat/lon/alt).
416         */
417
418        if (pp->sloppyclockflag & CLK_FLAG2)
419                up->moving   = 1;       /* Receiver on mobile platform */
420        else
421                up->moving   = 0;       /* Static Installation */
422
423        up->pollcnt  = 2;
424        if (up->moving) {
425                /* dynamic: solve for pos, alt, time, while moving */
426                tr_mode = 'D';
427                add_mode = 0;
428        } else {
429                /* static: solve for pos, alt, time, while stationary */
430                tr_mode = 'S';
431                add_mode = 1;
432        }
433        mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg,
434            PMVXG_S_TRECOVCONF,
435            tr_mode,    /* time recovery mode (see above ) */
436            'U',        /* synchronize to UTC */
437            'A',        /* always output a time pulse */
438            500,        /* max time error in ns */
439            0,          /* user bias in ns */
440            1);         /* output "830" sentences to control port */
441                        /* Multi-satellite mode */
442
443        /*
444         * Output position information (to calculate fixed installation
445         * location) only if we are not moving
446         */
447        if (up->moving) {
448                add_mode = 0;   /* delete from list */
449        } else {
450                add_mode = 1;   /* add to list */
451        }
452
453        /*
454         * "007" Control Port Configuration
455         * Output "022" DOPs
456         */
457        mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg,
458            PMVXG_S_PORTCONF,
459            PMVXG_D_DOPS, /* control port output block Label */
460            0,          /* clear current output control list (0=no) */
461            add_mode,   /* add/delete sentences from list (1=add) */
462                        /* must be null */
463            INTERVAL);  /* sentence output rate (sec) */
464                        /* precision for position output */
465                        /* nmea version for cga & gll output */
466                        /* pass-through control */
467
468
469        /*
470         * "007" Control Port Configuration
471         * Output "021" position, height, velocity reports
472         */
473        mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg,
474            PMVXG_S_PORTCONF,
475            PMVXG_D_PHV, /* control port output block Label */
476            0,          /* clear current output control list (0=no) */
477            add_mode,   /* add/delete sentences from list (1=add) */
478                        /* must be null */
479            INTERVAL);  /* sentence output rate (sec) */
480                        /* precision for position output */
481                        /* nmea version for cga & gll output */
482                        /* pass-through control */
483}
484
485/*
486 * mx4200_ref - Reconfigure unit as a reference station at a known position.
487 */
488static void
489mx4200_ref(peer)
490        struct peer *peer;
491{
492        register struct mx4200unit *up;
493        struct refclockproc *pp;
494        double minute, lat, lon, alt;
495        char lats[16], lons[16];
496        char nsc, ewc;
497
498        pp = peer->procptr;
499        up = (struct mx4200unit *)pp->unitptr;
500
501        /* Should never happen! */
502        if (up->moving) return;
503
504        /*
505         * Set up to output status information in the near future
506         */
507        up->log_time    = current_time + SLEEPTIME;
508
509        /*
510         * "001" Initialization/Mode Control, Part B
511         * Put receiver in fully-constrained 2d nav mode
512         */
513        mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
514            pmvxg, PMVXG_S_INITMODEB,
515            2,          /* 2d nav */
516                        /* reserved */
517            0.1,        /* hor accel fact as per Steve (m/s**2) */
518            0.1,        /* ver accel fact as per Steve (m/s**2) */
519            10,         /* vdop */
520            10,         /* hdop limit as per Steve */
521            5,          /* elevation limit as per Steve (deg) */
522            'U',        /* time output mode (UTC) */
523            0);         /* local time offset from gmt (HHHMM) */
524
525        /*
526         * "023" Time Recovery Configuration
527         * Get UTC time from a stationary receiver.  Solve for time only.
528         * This should improve the time resolution dramatically.
529         */
530        mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg,
531            PMVXG_S_TRECOVCONF,
532            'K',        /* known position: solve for time only */
533            'U',        /* synchronize to UTC */
534            'A',        /* always output a time pulse */
535            500,        /* max time error in ns */
536            0,          /* user bias in ns */
537            1);         /* output "830" sentences to control port */
538                        /* Multi-satellite mode */
539
540        /*
541         * "000" Initialization/Mode Control - Part A
542         * Fix to our averaged position.
543         */
544        if (up->avg_lat >= 0.0) {
545                lat = up->avg_lat;
546                nsc = 'N';
547        } else {
548                lat = up->avg_lat * (-1.0);
549                nsc = 'S';
550        }
551        if (up->avg_lon >= 0.0) {
552                lon = up->avg_lon;
553                ewc = 'E';
554        } else {
555                lon = up->avg_lon * (-1.0);
556                ewc = 'W';
557        }
558        alt = up->avg_alt;
559        minute = (lat - (double)(int)lat) * 600.0 / 10.0;
560        sprintf(lats,"%02d%02.4f", (int)lat, minute);
561        minute = (lon - (double)(int)lon) * 600.0 / 10.0;
562        sprintf(lons,"%03d%02.4f", (int)lon, minute);
563
564        mx4200_send(peer, "%s,%03d,,,,,%s,%c,%s,%c,%.2f,%d", pmvxg,
565            PMVXG_S_INITMODEA,
566                        /* day of month */
567                        /* month of year */
568                        /* year */
569                        /* gmt */
570            lats,       /* latitude   DDMM.MMMM */
571            nsc,        /* north/south */
572            lons,       /* longitude DDDMM.MMMM */
573            ewc,        /* east/west */
574            alt,        /* height */
575            1);         /* Altitude Reference 1=MSL */
576
577
578        /*
579         * "007" Control Port Configuration
580         * Stop outputting "022" DOPs
581         */
582        mx4200_send(peer, "%s,%03d,%03d,%d,%d,,,,,", pmvxg,
583            PMVXG_S_PORTCONF,
584            PMVXG_D_DOPS, /* control port output block Label */
585            0,          /* clear current output control list (0=no) */
586            0);         /* add/delete sentences from list (0=delete) */
587                        /* must be null */
588                        /* sentence output rate (sec) */
589                        /* precision for position output */
590                        /* nmea version for cga & gll output */
591                        /* pass-through control */
592
593        /*
594         * "007" Control Port Configuration
595         * Stop outputting "021" position, height, velocity reports
596         */
597        mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg,
598            PMVXG_S_PORTCONF,
599            PMVXG_D_PHV, /* control port output block Label */
600            0,          /* clear current output control list (0=no) */
601            0);         /* add/delete sentences from list (0=delete) */
602                        /* must be null */
603                        /* sentence output rate (sec) */
604                        /* precision for position output */
605                        /* nmea version for cga & gll output */
606                        /* pass-through control */
607
608        msyslog(LOG_DEBUG,
609            "mx4200_ref: reconfig to fixed location: %s %c, %s %c, %.2f m MSL",
610            lats, nsc, lons, ewc, alt );
611
612}
613
614/*
615 * mx4200_poll - mx4200 watchdog routine
616 */
617static void
618mx4200_poll(unit, peer)
619        int unit;
620        struct peer *peer;
621{
622        register struct mx4200unit *up;
623        struct refclockproc *pp;
624
625        pp = peer->procptr;
626        up = (struct mx4200unit *)pp->unitptr;
627
628        /*
629         * You don't need to poll this clock.  It puts out timecodes
630         * once per second.  If asked for a timestamp, take note.
631         * The next time a timecode comes in, it will be fed back.
632         */
633
634        /*
635         * If we haven't had a response in a while, reset the receiver.
636         */
637        if (up->pollcnt > 0) {
638                up->pollcnt--;
639        } else {
640                refclock_report(peer, CEVNT_TIMEOUT);
641
642                /*
643                 * Request a "000" status message which should trigger a
644                 * reconfig
645                 */
646                mx4200_send(peer, "%s,%03d",
647                    "CDGPQ",            /* query from CDU to GPS */
648                    PMVXG_D_STATUS);    /* label of desired sentence */
649        }
650
651        /*
652         * polled every 64 seconds. Ask mx4200_receive to hand in
653         * a timestamp.
654         */
655        up->polled = 1;
656        pp->polls++;
657
658        /*
659         * Output receiver status information.
660         */
661        if ((up->log_time > 0) && (current_time > up->log_time)) {
662                up->log_time = 0;
663                /*
664                 * Output the following messages once, for debugging.
665                 *    "004" Mode Data
666                 *    "523" Time Recovery Parameters
667                 */
668                mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_MODEDATA);
669                mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_TRECOVUSEAGE);
670        }
671}
672
673static char char2hex[] = "0123456789ABCDEF";
674
675/*
676 * mx4200_receive - receive gps data
677 */
678static void
679mx4200_receive(rbufp)
680        struct recvbuf *rbufp;
681{
682        register struct mx4200unit *up;
683        struct refclockproc *pp;
684        struct peer *peer;
685        char *cp;
686        int sentence_type;
687        u_char ck;
688
689        /*
690         * Initialize pointers and read the timecode and timestamp.
691         */
692        peer = (struct peer *)rbufp->recv_srcclock;
693        pp = peer->procptr;
694        up = (struct mx4200unit *)pp->unitptr;
695
696        /*
697         * If operating mode has been changed, then reinitialize the receiver
698         * before doing anything else.
699         */
700        if ((pp->sloppyclockflag & CLK_FLAG2) !=
701            (up->sloppyclockflag & CLK_FLAG2)) {
702                up->sloppyclockflag = pp->sloppyclockflag;
703                mx4200_debug(peer, "mx4200_receive: mode switch: reset receiver\n", cp);
704                mx4200_config(peer);
705                return;
706        }
707        up->sloppyclockflag = pp->sloppyclockflag;
708
709        /*
710         * Read clock output.  Automatically handles STREAMS, CLKLDISC.
711         */
712        pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec);
713
714        /*
715         * There is a case where <cr><lf> generates 2 timestamps.
716         */
717        if (pp->lencode == 0)
718                return;
719
720        up->pollcnt = 2;
721        pp->a_lastcode[pp->lencode] = '\0';
722        record_clock_stats(&peer->srcadr, pp->a_lastcode);
723        mx4200_debug(peer, "mx4200_receive: %d %s\n",
724                pp->lencode, pp->a_lastcode);
725
726        /*
727         * The structure of the control port sentences is based on the
728         * NMEA-0183 Standard for interfacing Marine Electronics
729         * Navigation Devices (Version 1.5)
730         *
731         *      $PMVXG,XXX, ....................*CK<cr><lf>
732         *
733         *              $       Sentence Start Identifier (reserved char)
734         *                         (Start-of-Sentence Identifier)
735         *              P       Special ID (Proprietary)
736         *              MVX     Originator ID (Magnavox)
737         *              G       Interface ID (GPS)
738         *              ,       Field Delimiters (reserved char)
739         *              XXX     Sentence Type
740         *              ......  Data
741         *              *       Checksum Field Delimiter (reserved char)
742         *              CK      Checksum
743         *              <cr><lf> Carriage-Return/Line Feed (reserved chars)
744         *                         (End-of-Sentence Identifier)
745         *
746         * Reject if any important landmarks are missing.
747         */
748        cp = pp->a_lastcode + pp->lencode - 3;
749        if (cp < pp->a_lastcode || *pp->a_lastcode != '$' || cp[0] != '*' ) {
750                mx4200_debug(peer, "mx4200_receive: bad format\n");
751                refclock_report(peer, CEVNT_BADREPLY);
752                return;
753        }
754
755        /*
756         * Check and discard the checksum
757         */
758        ck = mx4200_cksum(&pp->a_lastcode[1], pp->lencode - 4);
759        if (char2hex[ck >> 4] != cp[1] || char2hex[ck & 0xf] != cp[2]) {
760                mx4200_debug(peer, "mx4200_receive: bad checksum\n");
761                refclock_report(peer, CEVNT_BADREPLY);
762                return;
763        }
764        *cp = '\0';
765
766        /*
767         * Get the sentence type.
768         */
769        sentence_type = 0;
770        if ((cp = strchr(pp->a_lastcode, ',')) == NULL) {
771                mx4200_debug(peer, "mx4200_receive: no sentence\n", cp);
772                refclock_report(peer, CEVNT_BADREPLY);
773                return;
774        }
775        cp++;
776        sentence_type = strtol(cp, &cp, 10);
777
778        /*
779         * "000" Status message
780         */
781
782        if (sentence_type == PMVXG_D_STATUS) {
783                /*
784                 * XXX
785                 * Since we configure the receiver to not give us status
786                 * messages and since the receiver outputs status messages by
787                 * default after being reset to factory defaults when sent the
788                 * "$PMVXG,018,C\r\n" message, any status message we get
789                 * indicates the reciever needs to be initialized; thus, it is
790                 * not necessary to decode the status message.
791                 */
792                if ((cp = mx4200_parse_s(peer)) != NULL) {
793                        mx4200_debug(peer,
794                                "mx4200_receive: status: %s\n", cp);
795                }
796                mx4200_debug(peer, "mx4200_receive: reset receiver\n", cp);
797                mx4200_config(peer);
798                return;
799        }
800
801        /*
802         * "021" Position, Height, Velocity message,
803         *  if we are still averaging our position
804         */
805        if (sentence_type == PMVXG_D_PHV && !up->known) {
806                /*
807                 * Parse the message, calculating our averaged position.
808                 */
809                if ((cp = mx4200_parse_p(peer)) != NULL) {
810                        mx4200_debug(peer, "mx4200_receive: pos: %s\n", cp);
811                        return;
812                }
813                mx4200_debug(peer,
814                        "mx4200_receive: position avg %.9f %.9f %.4f\n",
815                        up->avg_lat, up->avg_lon, up->avg_alt);
816                mx4200_debug(peer,
817                        "mx4200_receive: position len %.4f %.4f %.4f\n",
818                        up->filt_lat, up->filt_lon, up->filt_alt);
819                mx4200_debug(peer,
820                        "mx4200_receive: position dop %.2f %.2f %.2f\n",
821                        up->ndop, up->edop, up->vdop);
822                /*
823                 * Reinitialize as a reference station
824                 * if position is well known.
825                 */
826                if (current_time > up->clamp_time) {
827                        up->known++;
828                        mx4200_debug(peer, "mx4200_receive: reconfiguring!\n");
829                        mx4200_ref(peer);
830                }
831                return;
832        }
833
834        /*
835         * "022" DOPs, if we are still averaging our position
836         */
837        if (sentence_type == PMVXG_D_DOPS && !up->known) {
838                if ((cp = mx4200_parse_d(peer)) != NULL) {
839                        mx4200_debug(peer, "mx4200_receive: dop: %s\n", cp);
840                        return;
841                }
842                return;
843        }
844
845        /*
846         * Print to the syslog:
847         * "004" Mode Data
848         * "030" Software Configuration
849         * "523" Time Recovery Parameters Currently in Use
850         */
851        if (sentence_type == PMVXG_D_MODEDATA ||
852            sentence_type == PMVXG_D_SOFTCONF ||
853            sentence_type == PMVXG_D_TRECOVUSEAGE ) {
854                if ((cp = mx4200_parse_s(peer)) != NULL) {
855                        mx4200_debug(peer,
856                                "mx4200_receive: multi-record: %s\n", cp);
857                        return;
858                }
859                return;
860        }
861
862        /*
863         * "830" Time Recovery Results message
864         */
865        if (sentence_type == PMVXG_D_TRECOVOUT) {
866
867                /*
868                 * Capture the last PPS signal.
869                 * Precision timestamp is returned in pp->lastrec
870                 */
871                if (mx4200_pps(peer) != NULL) {
872                        mx4200_debug(peer, "mx4200_receive: pps failure\n");
873                        refclock_report(peer, CEVNT_FAULT);
874                        return;
875                }
876
877                /*
878                 * Parse the time recovery message, and keep the info
879                 * to print the pretty billboards.
880                 */
881                if ((cp = mx4200_parse_t(peer)) != NULL) {
882                        mx4200_debug(peer, "mx4200_receive: time: %s\n", cp);
883                        refclock_report(peer, CEVNT_BADREPLY);
884                        return;
885                }
886
887                /*
888                 * Add the new sample to a median filter.
889                 */
890                if ((cp =mx4200_offset(peer)) != NULL) {
891                        mx4200_debug(peer,"mx4200_receive: offset: %s\n", cp);
892                        refclock_report(peer, CEVNT_BADTIME);
893                        return;
894                }
895
896                /*
897                 * The clock will blurt a timecode every second but we only
898                 * want one when polled.  If we havn't been polled, bail out.
899                 */
900                if (!up->polled)
901                        return;
902
903                /*
904                 * It's a live one!  Remember this time.
905                 */
906                pp->lasttime   = current_time;
907
908                /*
909                 * Determine the reference clock offset and dispersion.
910                 * NKEEP of NSAMPLE offsets are passed through a median filter.
911                 * Save the (filtered) offset and dispersion in
912                 * pp->offset and pp->dispersion.
913                 */
914                if ((cp =mx4200_process(peer)) != NULL) {
915                        mx4200_debug(peer,"mx4200_receive: process: %s\n", cp);
916                        refclock_report(peer, CEVNT_BADTIME);
917                        return;
918                }
919
920                /*
921                 * Return offset and dispersion to control module.  We use
922                 * lastrec as both the reference time and receive time in
923                 * order to avoid being cute, like setting the reference time
924                 * later than the receive time, which may cause a paranoid
925                 * protocol module to chuck out the data.
926                 */
927                mx4200_debug(peer, "mx4200_receive: process time: ");
928                mx4200_debug(peer, "%4d-%03d %02d:%02d:%02d at %s, %s\n",
929                        pp->year, pp->day, pp->hour, pp->minute, pp->second,
930                        prettydate(&pp->lastrec), lfptoa(&pp->offset, 6));
931
932                refclock_receive(peer, &pp->offset, 0, pp->dispersion,
933                        &pp->lastrec, &pp->lastrec, pp->leap);
934
935                /*
936                 * We have succeeded in answering the poll.
937                 * Turn off the flag and return
938                 */
939                up->polled = 0;
940                return;
941        }
942
943        /*
944         * Ignore all other sentence types
945         */
946        return;
947}
948
949/*
950 * mx4200_offset - Calculate the offset, and add to the rolling filter.
951 */
952static char *
953mx4200_offset(peer)
954        struct peer *peer;
955{
956        register struct mx4200unit *up;
957        struct refclockproc *pp;
958        register int i;
959        l_fp offset;
960
961        pp = peer->procptr;
962        up = (struct mx4200unit *)pp->unitptr;
963
964        /*
965         * Calculate the offset
966         */
967        if (!clocktime(pp->day, pp->hour, pp->minute, pp->second, GMT,
968                pp->lastrec.l_ui, &pp->yearstart, &offset.l_ui)) {
969                return ("mx4200_process: clocktime failed");
970        }
971        if (pp->usec) {
972                TVUTOTSF(pp->usec, offset.l_uf);
973        } else {
974                MSUTOTSF(pp->msec, offset.l_uf);
975        }
976        L_ADD(&offset, &pp->fudgetime1);
977        up->lastref = offset;   /* save last reference time */
978        L_SUB(&offset, &pp->lastrec); /* form true offset */
979
980        /*
981         * A rolling filter.  Initialize first time around.
982         */
983        i = ((up->coderecv)) % NSAMPLES;
984
985        up->filter[i] = offset;
986        if (up->coderecv == 0)
987                for (i = 1; (u_int) i < NSAMPLES; i++)
988                        up->filter[i] = up->filter[0];
989        up->coderecv++;
990
991        return (NULL);
992}
993
994/*
995 * mx4200_process - process the sample from the clock,
996 * passing it through a median filter and optionally averaging
997 * the samples.  Returns offset and dispersion in "up" structure.
998 */
999static char *
1000mx4200_process(peer)
1001        struct peer *peer;
1002{
1003        register struct mx4200unit *up;
1004        struct refclockproc *pp;
1005        register int i, n;
1006        int j, k;
1007        l_fp offset, median, lftmp;
1008        u_fp disp;
1009        l_fp off[NSAMPLES];
1010
1011        pp = peer->procptr;
1012        up = (struct mx4200unit *)pp->unitptr;
1013
1014        /*
1015         * Copy the raw offsets and sort into ascending order
1016         */
1017        for (i = 0; i < NSAMPLES; i++)
1018                off[i] = up->filter[i];
1019        qsort((char *)off, NSAMPLES, sizeof(l_fp), mx4200_cmpl_fp);
1020
1021        /*
1022         * Reject the furthest from the median of NSAMPLES samples until
1023         * NKEEP samples remain.
1024         */
1025        i = 0;
1026        n = NSAMPLES;
1027        while ((n - i) > up->nkeep) {
1028                lftmp = off[n - 1];
1029                median = off[(n + i) / 2];
1030                L_SUB(&lftmp, &median);
1031                L_SUB(&median, &off[i]);
1032                if (L_ISHIS(&median, &lftmp)) {
1033                        /* reject low end */
1034                        i++;
1035                } else {
1036                        /* reject high end */
1037                        n--;
1038                }
1039        }
1040
1041        /*
1042         * Copy key values to the billboard to measure performance.
1043         */
1044        pp->lastref = up->lastref;
1045        pp->coderecv = up->coderecv;
1046        pp->nstages = up->nkeep + 2;
1047        pp->filter[0] = off[0];                 /* smallest offset */
1048        pp->filter[1] = off[NSAMPLES-1];        /* largest offset */
1049        for (j=2, k=i; k < n; j++, k++)
1050                pp->filter[j] = off[k];         /* offsets actually examined */
1051
1052        /*
1053         * Compute the dispersion based on the difference between the
1054         * extremes of the remaining offsets. Add to this the time since
1055         * the last clock update, which represents the dispersion
1056         * increase with time. We know that NTP_MAXSKEW is 16. If the
1057         * sum is greater than the allowed sample dispersion, bail out.
1058         * If the loop is unlocked, return the most recent offset;
1059         * otherwise, return the median offset.
1060         */
1061        lftmp = off[n - 1];
1062        L_SUB(&lftmp, &off[i]);
1063        disp = LFPTOFP(&lftmp);
1064        if (disp > REFCLOCKMAXDISPERSE) {
1065                return("Maximum dispersion exceeded");
1066        }
1067
1068        /*
1069         * Now compute the offset estimate.  If fudge flag 1
1070         * is set, average the remainder, otherwise pick the
1071         * median.
1072         */
1073        if (pp->sloppyclockflag & CLK_FLAG1) {
1074                L_CLR(&lftmp);
1075                while (i < n) {
1076                        L_ADD(&lftmp, &off[i]);
1077                        i++;
1078                }
1079                i = up->rshift;
1080                while (i > 0) {
1081                        L_RSHIFT(&lftmp);
1082                        i--;
1083                }
1084                offset = lftmp;
1085        } else {
1086                i = (n + i) / 2;
1087                offset = off[i];
1088        }
1089
1090        /*
1091         * The payload: filtered offset and dispersion.
1092         */
1093
1094        pp->offset = offset;
1095        pp->dispersion = disp;
1096
1097        return(NULL);
1098
1099}
1100
1101/* Compare two l_fp's, used with qsort() */
1102int
1103#ifdef QSORT_USES_VOID_P
1104mx4200_cmpl_fp(p1, p2)
1105        const void *p1, *p2;
1106{
1107        register const l_fp *fp1 = (const l_fp *)p1;
1108        register const l_fp *fp2 = (const l_fp *)p2;
1109#else
1110mx4200_cmpl_fp(fp1, fp2)
1111        register const l_fp *fp1;
1112        register const l_fp *fp2;
1113{
1114#endif /* not QSORT_USES_VOID_P */
1115
1116        if (!L_ISGEQ(fp1, fp2))
1117                return (-1);
1118        if (L_ISEQU(fp1, fp2))
1119                return (0);
1120        return (1);
1121}
1122
1123
1124/*
1125 * Parse a mx4200 time recovery message. Returns a string if error.
1126 *
1127 * A typical message looks like this.  Checksum has already been stripped.
1128 *
1129 *    $PMVXG,830,T,YYYY,MM,DD,HH:MM:SS,U,S,FFFFFF,PPPPP,BBBBBB,LL
1130 *
1131 *      Field   Field Contents
1132 *      -----   --------------
1133 *              Block Label: $PMVXG
1134 *              Sentence Type: 830=Time Recovery Results
1135 *                      This sentence is output approximately 1 second
1136 *                      preceding the 1PPS output.  It indicates the
1137 *                      exact time of the next pulse, whether or not the
1138 *                      time mark will be valid (based on operator-specified
1139 *                      error tolerance), the time to which the pulse is
1140 *                      synchronized, the receiver operating mode,
1141 *                      and the time error of the *last* 1PPS output.
1142 *      1       Time Mark Valid: T=Valid, F=Not Valid
1143 *      2       Year: 1993-
1144 *      3       Month of Year: 1-12
1145 *      4       Day of Month: 1-31
1146 *      5       Time of Day: HH:MM:SS
1147 *      6       Time Synchronization: U=UTC, G=GPS
1148 *      7       Time Recovery Mode: D=Dynamic, S=Static,
1149 *                      K=Known Position, N=No Time Recovery
1150 *      8       Oscillator Offset: The filter's estimate of the oscillator
1151 *                      frequency error, in parts per billion (ppb).
1152 *      9       Time Mark Error: The computed error of the *last* pulse
1153 *                      output, in nanoseconds.
1154 *      10      User Time Bias: Operator specified bias, in nanoseconds
1155 *      11      Leap Second Flag: Indicates that a leap second will
1156 *                      occur.  This value is usually zero, except during
1157 *                      the week prior to the leap second occurence, when
1158 *                      this value will be set to +1 or -1.  A value of
1159 *                      +1 indicates that GPS time will be 1 second
1160 *                      further ahead of UTC time.
1161 *
1162 */
1163static char *
1164mx4200_parse_t(peer)
1165        struct peer *peer;
1166{
1167        struct refclockproc *pp;
1168        struct mx4200unit *up;
1169        int sentence_type, valid;
1170        int year, yearday, month, monthday, hour, minute, second, leapsec;
1171        char *cp;
1172
1173        pp = peer->procptr;
1174        up = (struct mx4200unit *)pp->unitptr;
1175
1176        cp = pp->a_lastcode;
1177
1178        if ((cp = strchr(cp, ',')) == NULL)
1179                return ("no rec-type");
1180        cp++;
1181
1182        /* Sentence type */
1183        sentence_type = strtol(cp, &cp, 10);
1184        if (sentence_type != PMVXG_D_TRECOVOUT)
1185                return ("wrong rec-type");
1186
1187        /* Pulse valid indicator */
1188        if (*cp++ != ',')
1189                return ("no pulse-valid");
1190        if (*cp == 'T')
1191                valid = 1;
1192        else if (*cp == 'F')
1193                valid = 0;
1194        else
1195                return ("bad pulse-valid");
1196        cp++;
1197
1198        /* Year */
1199        if (*cp++ != ',')
1200                return ("no year");
1201        year = strtol(cp, &cp, 10);
1202
1203        /* Month of year */
1204        if (*cp++ != ',')
1205                return ("no month");
1206        month = strtol(cp, &cp, 10);
1207
1208        /* Day of month */
1209        if (*cp++ != ',')
1210                return ("no month day");
1211        monthday = strtol(cp, &cp, 10);
1212
1213        /* Hour */
1214        if (*cp++ != ',')
1215                return ("no hour");
1216        hour = strtol(cp, &cp, 10);
1217
1218        /* Minute */
1219        if (*cp++ != ':')
1220                return ("no minute");
1221        minute = strtol(cp, &cp, 10);
1222
1223        /* Second */
1224        if (*cp++ != ':')
1225                return ("no second");
1226        second = strtol(cp, &cp, 10);
1227
1228        /* Time indicator */
1229        if (*cp++ != ',')
1230                return ("no time indicator");
1231        if (*cp == 'G')
1232                return ("synchronized to GPS; should be UTC");
1233        else if (*cp != 'U')
1234                return ("not synchronized to UTC");
1235        cp++;
1236
1237        /* Time recovery mode */
1238        if (*cp++ != ',' || *cp++ == '\0')
1239                return ("no time mode");
1240
1241        /* Oscillator offset */
1242        if ((cp = strchr(cp, ',')) == NULL)
1243                return ("no osc off");
1244        cp++;
1245
1246        /* Time mark error */
1247        /* (by request, always less than 0.5 usec (500 nsec), so ignore) */
1248        if ((cp = strchr(cp, ',')) == NULL)
1249                return ("no time mark err");
1250        cp++;
1251
1252        /* User time bias */
1253        /* (by request, always zero, so ignore */
1254        if ((cp = strchr(cp, ',')) == NULL)
1255                return ("no user bias");
1256        cp++;
1257
1258        /* Leap second flag */
1259        if ((cp = strchr(cp, ',')) == NULL)
1260                return ("no leap");
1261        cp++;
1262        leapsec = strtol(cp, &cp, 10);
1263
1264
1265        /*
1266         * Check for insane time (allow for possible leap seconds)
1267         */
1268        if (second > 60 || minute > 59 || hour > 23 ||
1269            second <  0 || minute <  0 || hour <  0) {
1270                mx4200_debug(peer,
1271                        "mx4200_parse_t: bad time %02d:%02d:%02d",
1272                        hour, minute, second);
1273                if (leapsec != 0)
1274                        mx4200_debug(peer, " (leap %+d\n)", leapsec);
1275                mx4200_debug(peer, "\n");
1276                refclock_report(peer, CEVNT_BADTIME);
1277                return ("bad time");
1278        }
1279        if ( second == 60 ) {
1280                msyslog(LOG_DEBUG, "mx4200_parse_t: leap second! %02d:%02d:%02d",
1281                         hour, minute, second);
1282        }
1283
1284        /*
1285         * Check for insane date
1286         * (Certainly can't be a year before this code was last altered!)
1287         */
1288        if (monthday > 31 || month > 12 ||
1289            monthday <  1 || month <  1 || year < 1997) {
1290                mx4200_debug(peer,
1291                        "mx4200_parse_t: bad date (%4d-%02d-%02d)\n",
1292                        year, month, monthday);
1293                refclock_report(peer, CEVNT_BADDATE);
1294                return ("bad date");
1295        }
1296
1297        /*
1298         * Silly Hack for MX4200:
1299         * ASCII message is for *next* 1PPS signal, but we have the
1300         * timestamp for the *last* 1PPS signal.  So we have to subtract
1301         * a second.  Discard if we are on a month boundary to avoid
1302         * possible leap seconds and leap days.
1303         */
1304        second--;
1305        if (second < 0) {
1306                second = 59;
1307                minute--;
1308                if (minute < 0) {
1309                        minute = 59;
1310                        hour--;
1311                        if (hour < 0) {
1312                                hour = 23;
1313                                monthday--;
1314                                if (monthday < 1) {
1315                                        return ("sorry, month boundary");
1316                                }
1317                        }
1318                }
1319        }
1320
1321        /*
1322         * Calculate Julian date
1323         */
1324        if (!(yearday = mx4200_jday(year, month, monthday))) {
1325                mx4200_debug(peer,
1326                        "mx4200_parse_t: bad julian date %d (%4d-%02d-%02d)\n",
1327                        yearday, year, month, monthday);
1328                refclock_report(peer, CEVNT_BADDATE);
1329                return("invalid julian date");
1330        }
1331
1332        /*
1333         * Setup leap second indicator
1334         */
1335        if (leapsec == 0)
1336                pp->leap = LEAP_NOWARNING;
1337        else if (leapsec == 1)
1338                pp->leap = LEAP_ADDSECOND;
1339        else if (leapsec == -1)
1340                pp->leap = LEAP_DELSECOND;
1341        else
1342                pp->leap = LEAP_NOTINSYNC;      /* shouldn't happen */
1343
1344        /*
1345         * Any change to the leap second warning status?
1346         */
1347        if (leapsec != up->last_leap ) {
1348                msyslog(LOG_DEBUG,
1349                        "mx4200_parse_t: leap second warning: %d to %d (%d)",
1350                        up->last_leap, leapsec, pp->leap);
1351        }
1352        up->last_leap = leapsec;
1353
1354        /*
1355         * Copy time data for billboard monitoring.
1356         */
1357
1358        pp->year   = year;
1359        pp->day    = yearday;
1360        pp->hour   = hour;
1361        pp->minute = minute;
1362        pp->second = second;
1363        pp->msec   = 0;
1364        pp->usec   = 0;
1365
1366        /*
1367         * Toss if sentence is marked invalid
1368         */
1369        if (!valid || pp->leap == LEAP_NOTINSYNC) {
1370                mx4200_debug(peer, "mx4200_parse_t: time mark not valid\n");
1371                refclock_report(peer, CEVNT_BADTIME);
1372                return ("pulse invalid");
1373        }
1374
1375        return (NULL);
1376}
1377
1378/*
1379 * Calculate the checksum
1380 */
1381static u_char
1382mx4200_cksum(cp, n)
1383        register char *cp;
1384        register u_int n;
1385{
1386        register u_char ck;
1387
1388        for (ck = 0; n-- > 0; cp++)
1389                ck ^= *cp;
1390        return (ck);
1391}
1392
1393/*
1394 * Tables to compute the day of year.  Viva la leap.
1395 */
1396static day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1397static day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1398
1399/*
1400 * Calculate the the Julian Day
1401 */
1402static int
1403mx4200_jday(year, month, monthday)
1404        int year;
1405        int month;
1406        int monthday;
1407{
1408        register int day, i;
1409        int leap_year;
1410
1411
1412        /*
1413         * Is this a leap year ?
1414         */
1415        if (year % 4) {
1416                leap_year = 0; /* FALSE */
1417        } else {
1418                if (year % 100) {
1419                        leap_year = 1; /* TRUE */
1420                } else {
1421                        if (year % 400) {
1422                                leap_year = 0; /* FALSE */
1423                        } else {
1424                                leap_year = 1; /* TRUE */
1425                        }
1426                }
1427        }
1428
1429        /*
1430         * Calculate the Julian Date
1431         */
1432        day = monthday;
1433
1434        if (leap_year) {
1435                /* a leap year */
1436                if (day > day2tab[month - 1]) {
1437                        return (0);
1438                }
1439                for (i = 0; i < month - 1; i++)
1440                        day += day2tab[i];
1441        } else {
1442                /* not a leap year */
1443                if (day > day1tab[month - 1]) {
1444                        return (0);
1445                }
1446                for (i = 0; i < month - 1; i++)
1447                        day += day1tab[i];
1448        }
1449        return (day);
1450}
1451
1452/*
1453 * Parse a mx4200 position/height/velocity sentence.
1454 *
1455 * A typical message looks like this.  Checksum has already been stripped.
1456 *
1457 * $PMVXG,021,SSSSSS.SS,DDMM.MMMM,N,DDDMM.MMMM,E,HHHHH.H,GGGG.G,EEEE.E,WWWW.W,MM
1458 *
1459 *      Field   Field Contents
1460 *      -----   --------------
1461 *              Block Label: $PMVXG
1462 *              Sentence Type: 021=Position, Height Velocity Data
1463 *                      This sentence gives the receiver position, height,
1464 *                      navigation mode, and velocity north/east.
1465 *                      *This sentence is intended for post-analysis
1466 *                      applications.*
1467 *      1       UTC measurement time (seconds into week)
1468 *      2       WGS-84 Lattitude (degrees, minutes)
1469 *      3       N=North, S=South
1470 *      4       WGS-84 Longitude (degrees, minutes)
1471 *      5       E=East, W=West
1472 *      6       Altitude (meters above mean sea level)
1473 *      7       Geoidal height (meters)
1474 *      8       East velocity (m/sec)
1475 *      9       West Velocity (m/sec)
1476 *      10      Navigation Mode
1477 *                  Mode if navigating:
1478 *                      1 = Position from remote device
1479 *                      2 = 2-D position
1480 *                      3 = 3-D position
1481 *                      4 = 2-D differential position
1482 *                      5 = 3-D differential position
1483 *                      6 = Static
1484 *                      8 = Position known -- reference station
1485 *                      9 = Position known -- Navigator
1486 *                  Mode if not navigating:
1487 *                      51 = Too few satellites
1488 *                      52 = DOPs too large
1489 *                      53 = Position STD too large
1490 *                      54 = Velocity STD too large
1491 *                      55 = Too many iterations for velocity
1492 *                      56 = Too many iterations for position
1493 *                      57 = 3 sat startup failed
1494 *                      58 = Command abort
1495 */
1496static char *
1497mx4200_parse_p(peer)
1498        struct peer *peer;
1499{
1500        struct refclockproc *pp;
1501        struct mx4200unit *up;
1502        int sentence_type, mode;
1503        double dtemp, dtemp2, mtime, lat, lon, alt, geoid, vele, veln, weight;
1504        char *cp;
1505
1506        pp = peer->procptr;
1507        up = (struct mx4200unit *)pp->unitptr;
1508
1509        /* Should never happen! */
1510        if (up->moving) return ("mobile platform - no pos!");
1511
1512        cp = pp->a_lastcode;
1513
1514        if ((cp = strchr(cp, ',')) == NULL)
1515                return ("no rec-type");
1516        cp++;
1517
1518        /* Sentence type */
1519        sentence_type = strtol(cp, &cp, 10);
1520        if (sentence_type != PMVXG_D_PHV)
1521                return ("wrong rec-type");
1522
1523        /* Measurement Time */
1524        if (*cp++ != ',')
1525                return ("no measurement time");
1526        mtime = strtod(cp,&cp);
1527
1528        /* Latitude (always +ve) */
1529        if (*cp++ != ',')
1530                return ("no latitude");
1531        dtemp = atof(cp);
1532        dtemp2 = strtod(cp,&cp);
1533        if (dtemp < 0.0)
1534                return ("negative latitude");
1535        lat = (double) ( (int)dtemp / 100);
1536        lat += (dtemp - (lat*100.0)) * 10.0 / 600.0;
1537
1538        /* North/South */
1539        if (*cp++ != ',')
1540                return ("no north/south indicator");
1541        if (*cp == 'N')
1542                lat = lat;
1543        else if (*cp == 'S')
1544                lat *= -1.0;
1545        else
1546                return ("invalid north/south indicator");
1547        cp++;
1548
1549        /* Longitude (always +ve) */
1550        if (*cp++ != ',')
1551                return ("no longitude");
1552        dtemp = atof(cp);
1553        dtemp2 = strtod(cp,&cp);
1554        if (dtemp < 0.0)
1555                return ("negative latitude");
1556        lon = (double) ( (int)dtemp / 100);
1557        lon += (dtemp - (lon*100.0)) * 10.0 / 600.0;
1558
1559        /* East/West */
1560        if (*cp++ != ',')
1561                return ("no east/west indicator");
1562        if (*cp == 'E')
1563                lon = lon;
1564        else if (*cp == 'W')
1565                lon *= -1.0;
1566        else
1567                return ("invalid east/west indicator");
1568        cp++;
1569
1570        /* Altitude */
1571        if (*cp++ != ',')
1572                return ("no altitude");
1573        alt = atof(cp);
1574        dtemp2 = strtod(cp,&cp);
1575
1576        /* geoid height */
1577        if (*cp++ != ',')
1578                return ("no geoid height");
1579        geoid = strtod(cp,&cp);
1580
1581        /* East velocity */
1582        if (*cp++ != ',')
1583                return ("no east velocity");
1584        vele = strtod(cp,&cp);
1585
1586        /* north velocity */
1587        if (*cp++ != ',')
1588                return ("no north velocity");
1589        veln = strtod(cp,&cp);
1590
1591        /* nav mode */
1592        if (*cp++ != ',')
1593                return ("no nav mode");
1594        mode = strtol(cp, &cp, 10);
1595
1596
1597        /*
1598         * return if not navigating
1599         */
1600        if (mode > 10)
1601                return ("not navigating");
1602
1603        if (mode != 3 && mode != 5)
1604                return ("not navigating in 3D");
1605
1606        /*
1607         * Calculate running weighted averages
1608         */
1609
1610        weight = (USUAL_EDOP/up->edop);
1611        weight = weight * weight;
1612        up->avg_lon = up->filt_lon * up->avg_lon + weight * lon;
1613        up->filt_lon += weight;
1614        up->avg_lon = up->avg_lon / up->filt_lon;
1615
1616        weight = (USUAL_NDOP/up->ndop);
1617        weight = weight * weight;
1618        up->avg_lat = up->filt_lat * up->avg_lat + weight * lat;
1619        up->filt_lat += weight;
1620        up->avg_lat = up->avg_lat / up->filt_lat;
1621
1622        weight = (USUAL_VDOP/up->vdop);
1623        weight = weight * weight;
1624        up->avg_alt = up->filt_alt * up->avg_alt + weight * alt;
1625        up->filt_alt += weight;
1626        up->avg_alt = up->avg_alt / up->filt_alt;
1627
1628        return (NULL);
1629}
1630
1631/*
1632 * Parse a mx4200 DOP sentence.
1633 *
1634 * A typical message looks like this.  Checksum has already been stripped.
1635 *
1636 * $PMVXG,022,SSSSSS.SSEE.E,NN.N,VV.V,XX,XX,XX,XX,XX,XX
1637 *
1638 *      Field   Field Contents
1639 *      -----   --------------
1640 *              Block Label: $PMVXG
1641 *              Sentence Type: 022=DOPs.  The DOP values in this sentence
1642 *                      correspond to the satellites listed.  The PRNs in
1643 *                      the message are listed in receiver channel number order
1644 *      1       UTC measurement time (seconds into week)
1645 *      2       EDOP (east DOP)
1646 *      3       NDOP (north DOP)
1647 *      4       VDOP (vertical DOP)
1648 *      5       PRN on channel 1
1649 *      6       PRN on channel 2
1650 *      7       PRN on channel 3
1651 *      8       PRN on channel 4
1652 *      9       PRN on channel 5
1653 *      10      PRN on channel 6
1654 */
1655static char *
1656mx4200_parse_d(peer)
1657        struct peer *peer;
1658{
1659        struct refclockproc *pp;
1660        struct mx4200unit *up;
1661        int sentence_type;
1662        double mtime, dtemp2, edop, ndop, vdop;
1663        char *cp;
1664
1665        pp = peer->procptr;
1666        up = (struct mx4200unit *)pp->unitptr;
1667
1668        /* Should never happen! */
1669        if (up->moving) return ("mobile platform - no dop!");
1670
1671        cp = pp->a_lastcode;
1672
1673        if ((cp = strchr(cp, ',')) == NULL)
1674                return ("no rec-type");
1675        cp++;
1676
1677        /* Sentence type */
1678        sentence_type = strtol(cp, &cp, 10);
1679        if (sentence_type != PMVXG_D_DOPS)
1680                return ("wrong rec-type");
1681
1682        /* Measurement Time */
1683        if (*cp++ != ',')
1684                return ("no measurement time");
1685        mtime = strtod(cp,&cp);
1686
1687        /* EDOP */
1688        if (*cp++ != ',')
1689                return ("no edop");
1690        edop = atof(cp);
1691        dtemp2 = strtod(cp,&cp);
1692
1693        /* NDOP */
1694        if (*cp++ != ',')
1695                return ("no ndop");
1696        ndop = atof(cp);
1697        dtemp2 = strtod(cp,&cp);
1698
1699        /* VDOP */
1700        if (*cp++ != ',')
1701                return ("no vdop");
1702        vdop = atof(cp);
1703        dtemp2 = strtod(cp,&cp);
1704
1705        /* Ignore the PRNs... */
1706
1707
1708        /* Update values */
1709        if (ndop <= 0.0 || ndop<= 0.0 || vdop <= 0.0)
1710                return ("nonpositive dop");
1711        up->edop = edop;
1712        up->ndop = ndop;
1713        up->vdop = vdop;
1714
1715        return (NULL);
1716}
1717
1718/*
1719 * Parse a mx4200 Status sentence
1720 * Parse a mx4200 Mode Data sentence
1721 * Parse a mx4200 Software Configuration sentence
1722 * Parse a mx4200 Time Recovery Parameters Currently in Use sentence
1723 * (used only for logging raw strings)
1724 *
1725 * A typical message looks like this.  Checksum has already been stripped.
1726 *
1727 * $PMVXG,000,XXX,XX,X,HHMM,X
1728 *
1729 *      Field   Field Contents
1730 *      -----   --------------
1731 *              Block Label: $PMVXG
1732 *              Sentence Type: 000=Status.
1733 *                      Returns status of the receiver to the controller.
1734 *      1       Current Receiver Status:
1735 *              ACQ = Satellite re-acquisition
1736 *              ALT = Constellation selection
1737 *              COR = Providing corrections (for reference stations only)
1738 *              IAC = Initial acquisition
1739 *              IDL = Idle, no satellites
1740 *              NAV = Navigation
1741 *              STS = Search the Sky (no almanac available)
1742 *              TRK = Tracking
1743 *      2       Number of satellites that should be visible
1744 *      3       Number of satellites being tracked
1745 *      4       Time since last navigation status if not currently navigating
1746 *              (hours, minutes)
1747 *      5       Initialization status:
1748 *              0 = Waiting for initialization parameters
1749 *              1 = Initialization completed
1750 *
1751 * A typical message looks like this.  Checksum has already been stripped.
1752 *
1753 * $PMVXG,004,C,R,D,H.HH,V.VV,TT,HHHH,VVVV,T
1754 *
1755 *      Field   Field Contents
1756 *      -----   --------------
1757 *              Block Label: $PMVXG
1758 *              Sentence Type: 004=Software Configuration.
1759 *                      Defines the navigation mode and criteria for
1760 *                      acceptable navigation for the receiver.
1761 *      1       Constrain Altitude Mode:
1762 *              0 = Auto.  Constrain altitude (2-D solution) and use
1763 *                  manual altitude input when 3 sats avalable.  Do
1764 *                  not constrain altitude (3-D solution) when 4 sats
1765 *                  available.
1766 *              1 = Always constrain altitude (2-D solution).
1767 *              2 = Never constrain altitude (3-D solution).
1768 *              3 = Coast.  Constrain altitude (2-D solution) and use
1769 *                  last GPS altitude calculation when 3 sats avalable.
1770 *                  Do not constrain altitude (3-D solution) when 4 sats
1771 *                  available.
1772 *      2       Altitude Reference: (always 0 for MX4200)
1773 *              0 = Ellipsoid
1774 *              1 = Geoid (MSL)
1775 *      3       Differential Navigation Control:
1776 *              0 = Disabled
1777 *              1 = Enabled
1778 *      4       Horizontal Acceleration Constant (m/sec**2)
1779 *      5       Vertical Acceleration Constant (m/sec**2) (0 for MX4200)
1780 *      6       Tracking Elevation Limit (degrees)
1781 *      7       HDOP Limit
1782 *      8       VDOP Limit
1783 *      9       Time Output Mode:
1784 *              U = UTC
1785 *              L = Local time
1786 *      10      Local Time Offset (minutes) (absent on MX4200)
1787 *
1788 * A typical message looks like this.  Checksum has already been stripped.
1789 *
1790 * $PMVXG,030,NNNN,FFF
1791 *
1792 *      Field   Field Contents
1793 *      -----   --------------
1794 *              Block Label: $PMVXG
1795 *              Sentence Type: 030=Software Configuration.
1796 *                      This sentence contains the navigation processor
1797 *                      and baseband firmware version numbers.
1798 *      1       Nav Processor Version Number
1799 *      2       Baseband Firmware Version Number
1800 *
1801 * A typical message looks like this.  Checksum has already been stripped.
1802 *
1803 * $PMVXG,523,M,S,M,EEEE,BBBBBB,C,R
1804 *
1805 *      Field   Field Contents
1806 *      -----   --------------
1807 *              Block Label: $PMVXG
1808 *              Sentence Type: 523=Time Recovery Parameters Currently in Use.
1809 *                      This sentence contains the configuration of the
1810 *                      time recovery feature of the receiver.
1811 *      1       Time Recovery Mode:
1812 *              D = Dynamic; solve for position and time while moving
1813 *              S = Static; solve for position and time while stationary
1814 *              K = Known position input, solve for time only
1815 *              N = No time recovery
1816 *      2       Time Synchronization:
1817 *              U = UTC time
1818 *              G = GPS time
1819 *      3       Time Mark Mode:
1820 *              A = Always output a time pulse
1821 *              V = Only output time pulse if time is valid (as determined
1822 *                  by Maximum Time Error)
1823 *      4       Maximum Time Error - the maximum error (in nanoseconds) for
1824 *              which a time mark will be considered valid.
1825 *      5       User Time Bias - external bias in nanoseconds
1826 *      6       Time Message Control:
1827 *              0 = Do not output the time recovery message
1828 *              1 = Output the time recovery message (record 830) to
1829 *                  Control port
1830 *              2 = Output the time recovery message (record 830) to
1831 *                  Equipment port
1832 *      7       Reserved
1833 *      8       Position Known PRN (absent on MX 4200)
1834 *
1835 */
1836static char *
1837mx4200_parse_s(peer)
1838        struct peer *peer;
1839{
1840        struct refclockproc *pp;
1841        struct mx4200unit *up;
1842        int sentence_type;
1843        char *cp;
1844
1845        pp = peer->procptr;
1846        up = (struct mx4200unit *)pp->unitptr;
1847
1848        cp = pp->a_lastcode;
1849
1850        if ((cp = strchr(cp, ',')) == NULL)
1851                return ("no rec-type");
1852        cp++;
1853
1854        /* Sentence type */
1855        sentence_type = strtol(cp, &cp, 10);
1856        if (sentence_type != PMVXG_D_STATUS &&
1857            sentence_type != PMVXG_D_MODEDATA &&
1858            sentence_type != PMVXG_D_SOFTCONF &&
1859            sentence_type != PMVXG_D_TRECOVUSEAGE )
1860                return ("wrong rec-type");
1861        cp++;
1862
1863        /* Log the Status */
1864        if (sentence_type == PMVXG_D_STATUS) {
1865                msyslog(LOG_DEBUG,
1866                "mx4200_parse_s: status: %s", cp);
1867        }
1868
1869        /* Log the Mode Data */
1870        if (sentence_type == PMVXG_D_MODEDATA) {
1871                msyslog(LOG_DEBUG,
1872                "mx4200_parse_s: mode data: %s", cp);
1873        }
1874
1875        /* Log the Software Version */
1876        if (sentence_type == PMVXG_D_SOFTCONF) {
1877                msyslog(LOG_DEBUG,
1878                "mx4200_parse_s: firmware configuration: %s", cp);
1879        }
1880
1881        /* Log the Time Recovery Parameters */
1882        if (sentence_type == PMVXG_D_TRECOVUSEAGE) {
1883                msyslog(LOG_DEBUG,
1884                "mx4200_parse_s: time recovery parms: %s", cp);
1885        }
1886
1887        return (NULL);
1888}
1889
1890/*
1891 * Process a PPS signal, returning a timestamp.
1892 */
1893static int
1894mx4200_pps(peer)
1895        struct peer *peer;
1896{
1897#ifdef PPS
1898        struct refclockproc *pp;
1899        struct mx4200unit *up;
1900
1901        int temp_serial;
1902
1903        pp = peer->procptr;
1904        up = (struct mx4200unit *)pp->unitptr;
1905
1906
1907        /*
1908         * Grab the timestamp of the PPS signal.
1909         */
1910        temp_serial = up->ppsev.serial;
1911        if (ioctl(fdpps, CIOGETEV, (caddr_t)&up->ppsev) < 0) {
1912                /* XXX Actually, if this fails, we're pretty much screwed */
1913                mx4200_debug(peer, "mx4200_pps: CIOGETEV: ");
1914                if (errno < sys_nerr)
1915                        mx4200_debug(peer, "%s", sys_errlist[errno]);
1916                mx4200_debug(peer, "\n");
1917                refclock_report(peer, CEVNT_FAULT);
1918                return(1);
1919        }
1920        if (temp_serial == up->ppsev.serial) {
1921                mx4200_debug(peer,
1922                        "mx4200_pps: ppsev serial not incrementing: %d\n",
1923                                up->ppsev.serial);
1924                refclock_report(peer, CEVNT_FAULT);
1925                return(1);
1926        }
1927
1928        /*
1929         * Check pps serial number against last one
1930         */
1931        if (up->lastserial + 1 != up->ppsev.serial && up->lastserial != 0) {
1932                if (up->ppsev.serial == up->lastserial)
1933                        mx4200_debug(peer, "mx4200_pps: no new pps event\n");
1934                else
1935                        mx4200_debug(peer, "mx4200_pps: missed %d pps events\n",
1936                                up->ppsev.serial - up->lastserial - 1);
1937                refclock_report(peer, CEVNT_FAULT);
1938        }
1939        up->lastserial = up->ppsev.serial;
1940
1941        /*
1942         * Return the timestamp in pp->lastrec
1943         */
1944        up->ppsev.tv.tv_sec += (u_int32) JAN_1970;
1945        TVTOTS(&up->ppsev.tv,&pp->lastrec);
1946
1947#endif /* PPS */
1948
1949        return(0);
1950}
1951
1952/*
1953 * mx4200_debug - print debug messages
1954 */
1955#if __STDC__
1956static void
1957mx4200_debug(struct peer *peer, char *fmt, ...)
1958#else
1959static void
1960mx4200_debug(peer, fmt, va_alist)
1961        struct peer *peer;
1962        char *fmt;
1963#endif
1964{
1965        va_list ap;
1966        struct refclockproc *pp;
1967        struct mx4200unit *up;
1968
1969        if (debug) {
1970
1971#if __STDC__
1972                va_start(ap, fmt);
1973#else
1974                va_start(ap);
1975#endif
1976
1977                pp = peer->procptr;
1978                up = (struct mx4200unit *)pp->unitptr;
1979
1980
1981                /*
1982                 * Print debug message to stdout
1983                 * In the future, we may want to get get more creative...
1984                 */
1985                vprintf(fmt, ap);
1986
1987                va_end(ap);
1988        }
1989}
1990
1991/*
1992 * Send a character string to the receiver.  Checksum is appended here.
1993 */
1994static void
1995#if __STDC__
1996mx4200_send(struct peer *peer, char *fmt, ...)
1997#else
1998mx4200_send(peer, fmt, va_alist)
1999        struct peer *peer;
2000        char *fmt;
2001        va_dcl
2002#endif /* __STDC__ */
2003{
2004        struct refclockproc *pp;
2005        struct mx4200unit *up;
2006
2007        register char *cp;
2008        register int n, m;
2009        va_list ap;
2010        char buf[1024];
2011        u_char ck;
2012
2013#if __STDC__
2014        va_start(ap, fmt);
2015#else
2016        va_start(ap);
2017#endif /* __STDC__ */
2018
2019        pp = peer->procptr;
2020        up = (struct mx4200unit *)pp->unitptr;
2021
2022        cp = buf;
2023        *cp++ = '$';
2024#ifdef notdef
2025        /* BSD is rational */
2026        n = vsnprintf(cp, sizeof(buf) - 1, fmt, ap);
2027#else
2028        /* SunOS sucks */
2029        (void)vsprintf(cp, fmt, ap);
2030        n = strlen(cp);
2031#endif /* notdef */
2032        ck = mx4200_cksum(cp, n);
2033        cp += n;
2034        ++n;
2035#ifdef notdef
2036        /* BSD is rational */
2037        n += snprintf(cp, sizeof(buf) - n - 5, "*%02X\r\n", ck);
2038#else
2039        /* SunOS sucks */
2040        sprintf(cp, "*%02X\r\n", ck);
2041        n += strlen(cp);
2042#endif /* notdef */
2043
2044        m = write(pp->io.fd, buf, n);
2045        if (m < 0)
2046                msyslog(LOG_ERR, "mx4200_send: write: %m (%s)", buf);
2047        mx4200_debug(peer, "mx4200_send: %d %s\n", m, buf);
2048        va_end(ap);
2049}
2050
2051#else /* not (REFCLOCK && MX4200 && PPS) */
2052int refclock_mx4200_bs;
2053#endif /* not (REFCLOCK && MX4200 && PPS) */
Note: See TracBrowser for help on using the repository browser.