2 * This software was developed by the Computer Systems Engineering group
3 * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66.
5 * Copyright (c) 1992 The Regents of the University of California.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
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
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
38 * Modified: Marc Brett <marc.brett@westgeo.com> Sept, 1999.
40 * 1. Added support for alternate PPS schemes, with code mostly
41 * copied from the Oncore driver (Thanks, Poul-Henning Kamp).
42 * This code runs on SunOS 4.1.3 with ppsclock-1.6a1 and Solaris 7.
50 #if defined(REFCLOCK) && defined(CLOCK_MX4200) && defined(PPS)
54 #include <sys/types.h>
58 #include "ntp_refclock.h"
59 #include "ntp_unixtime.h"
60 #include "ntp_stdlib.h"
64 #ifdef HAVE_SYS_TIME_H
65 # include <sys/time.h>
67 #ifdef HAVE_SYS_TERMIOS_H
68 # include <sys/termios.h>
70 #ifdef HAVE_SYS_PPSCLOCK_H
71 # include <sys/ppsclock.h>
74 #ifndef HAVE_STRUCT_PPSCLOCKEV
83 #endif /* ! HAVE_STRUCT_PPSCLOCKEV */
86 * This driver supports the Magnavox Model MX 4200 GPS Receiver
87 * adapted to precision timing applications. It requires the
88 * ppsclock line discipline or streams module described in the
89 * Line Disciplines and Streams Drivers page. It also requires a
90 * gadget box and 1-PPS level converter, such as described in the
91 * Pulse-per-second (PPS) Signal Interfacing page.
93 * It's likely that other compatible Magnavox receivers such as the
94 * MX 4200D, MX 9212, MX 9012R, MX 9112 will be supported by this code.
98 * Check this every time you edit the code!
100 #define YEAR_RIGHT_NOW 1998
105 #define DEVICE "/dev/gps%d" /* device name and unit */
106 #define SPEED232 B4800 /* baud */
109 * Radio interface parameters
111 #define PRECISION (-18) /* precision assumed (about 4 us) */
112 #define REFID "GPS\0" /* reference id */
113 #define DESCRIPTION "Magnavox MX4200 GPS Receiver" /* who we are */
114 #define DEFFUDGETIME 0 /* default fudge time (ms) */
116 #define SLEEPTIME 32 /* seconds to wait for reconfig to complete */
119 * Position Averaging.
120 * Reference: Dr. Thomas A. Clark's Totally Accurate Clock (TAC) files at
121 * ftp://aleph.gsfc.nasa.gov/GPS/totally.accurate.clock/
122 * For a 6-channel Motorola Oncore, he indicates that good nominal
123 * HDOP and VDOP are 1.50 and 2.00 respectively. Given the relationship
124 * HDOP^2 = NDOP^2 + EDOP^2 and assuming EDOP and NDOP are equal, we
125 * have a nominal NDOP = EDOP = sqrt((HDOP*HDOP)/2). An 8-channel
126 * Oncore does well with HDOP=1.20 and VDOP=1.70.
128 #define INTERVAL 1 /* Interval between position measurements (s) */
129 #define AVGING_TIME 24 /* Number of hours to average */
130 #define NOT_INITIALIZED -9999. /* initial pivot longitude */
133 * MX4200 unit control structure.
136 u_int pollcnt; /* poll message counter */
137 u_int polled; /* Hand in a time sample? */
138 u_int lastserial; /* last pps serial number */
139 struct ppsclockev ppsev; /* PPS control structure */
140 double avg_lat; /* average latitude */
141 double avg_lon; /* average longitude */
142 double avg_alt; /* average height */
143 double central_meridian; /* central meridian */
144 double filt_lat; /* latitude filter length */
145 double filt_lon; /* longitude filter length */
146 double filt_alt; /* height filter length */
147 double edop; /* EDOP (east DOP) */
148 double ndop; /* NDOP (north DOP) */
149 double vdop; /* VDOP (vertical DOP) */
150 int last_leap; /* leap second warning */
151 u_int moving; /* mobile platform? */
152 u_long sloppyclockflag; /* fudge flags */
153 u_int known; /* position known yet? */
154 u_long clamp_time; /* when to stop postion averaging */
155 u_long log_time; /* when to print receiver status */
158 static char pmvxg[] = "PMVXG";
160 /* XXX should be somewhere else */
162 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5)
163 #ifndef __attribute__
164 #define __attribute__(args)
165 #endif /* __attribute__ */
166 #endif /* __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) */
168 #ifndef __attribute__
169 #define __attribute__(args)
170 #endif /* __attribute__ */
171 #endif /* __GNUC__ */
175 * Function prototypes
177 static int mx4200_start P((int, struct peer *));
178 static void mx4200_shutdown P((int, struct peer *));
179 static void mx4200_receive P((struct recvbuf *));
180 static void mx4200_poll P((int, struct peer *));
182 static char * mx4200_parse_t P((struct peer *));
183 static char * mx4200_parse_p P((struct peer *));
184 static char * mx4200_parse_d P((struct peer *));
185 static char * mx4200_parse_s P((struct peer *));
186 #ifdef QSORT_USES_VOID_P
187 int mx4200_cmpl_fp P((const void *, const void *));
189 int mx4200_cmpl_fp P((const l_fp *, const l_fp *));
190 #endif /* not QSORT_USES_VOID_P */
191 static void mx4200_config P((struct peer *));
192 static void mx4200_ref P((struct peer *));
193 static void mx4200_send P((struct peer *, char *, ...))
194 __attribute__ ((format (printf, 2, 3)));
195 static u_char mx4200_cksum P((char *, int));
196 static int mx4200_jday P((int, int, int));
197 static void mx4200_debug P((struct peer *, char *, ...))
198 __attribute__ ((format (printf, 2, 3)));
199 static int mx4200_pps P((struct peer *));
204 struct refclock refclock_mx4200 = {
205 mx4200_start, /* start up driver */
206 mx4200_shutdown, /* shut down driver */
207 mx4200_poll, /* transmit poll message */
208 noentry, /* not used (old mx4200_control) */
209 noentry, /* initialize driver (not used) */
210 noentry, /* not used (old mx4200_buginfo) */
211 NOFLAGS /* not used */
217 * mx4200_start - open the devices and initialize data for processing
225 register struct mx4200unit *up;
226 struct refclockproc *pp;
233 (void)sprintf(gpsdev, DEVICE, unit);
234 if (!(fd = refclock_open(gpsdev, SPEED232, LDISC_PPS))) {
239 * Allocate unit structure
241 if (!(up = (struct mx4200unit *) emalloc(sizeof(struct mx4200unit)))) {
245 memset((char *)up, 0, sizeof(struct mx4200unit));
247 pp->io.clock_recv = mx4200_receive;
248 pp->io.srcclock = (caddr_t)peer;
251 if (!io_addclock(&pp->io)) {
256 pp->unitptr = (caddr_t)up;
259 * Initialize miscellaneous variables
261 peer->precision = PRECISION;
262 pp->clockdesc = DESCRIPTION;
263 memcpy((char *)&pp->refid, REFID, 4);
265 /* Ensure the receiver is properly configured */
272 * mx4200_shutdown - shut down the clock
280 register struct mx4200unit *up;
281 struct refclockproc *pp;
284 up = (struct mx4200unit *)pp->unitptr;
285 io_closeclock(&pp->io);
291 * mx4200_config - Configure the receiver
300 register struct mx4200unit *up;
301 struct refclockproc *pp;
304 up = (struct mx4200unit *)pp->unitptr;
307 * Initialize the unit variables
309 * STRANGE BEHAVIOUR WARNING: The fudge flags are not available
310 * at the time mx4200_start is called. These are set later,
311 * and so the code must be prepared to handle changing flags.
313 up->sloppyclockflag = pp->sloppyclockflag;
314 if (pp->sloppyclockflag & CLK_FLAG2) {
315 up->moving = 1; /* Receiver on mobile platform */
316 msyslog(LOG_DEBUG, "mx4200_config: mobile platform");
318 up->moving = 0; /* Static Installation */
326 up->central_meridian = NOT_INITIALIZED;
333 up->last_leap = 0; /* LEAP_NOWARNING */
334 up->clamp_time = current_time + (AVGING_TIME * 60 * 60);
335 up->log_time = current_time + SLEEPTIME;
338 * "007" Control Port Configuration
339 * Zero the output list (do it twice to flush possible junk)
341 mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg,
343 /* control port output block Label */
344 1); /* clear current output control list (1=yes) */
345 /* add/delete sentences from list */
347 /* sentence output rate (sec) */
348 /* precision for position output */
349 /* nmea version for cga & gll output */
350 /* pass-through control */
351 mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg,
352 PMVXG_S_PORTCONF, 1);
355 * Request software configuration so we can syslog the firmware version
357 mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_SOFTCONF);
360 * "001" Initialization/Mode Control, Part A
363 mx4200_send(peer, "%s,%03d,,,,,,,,,,", pmvxg,
369 /* latitude DDMM.MMMM */
371 /* longitude DDDMM.MMMM */
374 /* Altitude Reference 1=MSL */
377 * "001" Initialization/Mode Control, Part B
378 * Start off in 2d/3d coast mode, holding altitude to last known
379 * value if only 3 satellites available.
381 mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
382 pmvxg, PMVXG_S_INITMODEB,
385 0.1, /* hor accel fact as per Steve (m/s**2) */
386 0.1, /* ver accel fact as per Steve (m/s**2) */
388 10, /* hdop limit as per Steve */
389 5, /* elevation limit as per Steve (deg) */
390 'U', /* time output mode (UTC) */
391 0); /* local time offset from gmt (HHHMM) */
394 * "023" Time Recovery Configuration
395 * Get UTC time from a stationary receiver.
396 * (Set field 1 'D' == dynamic if we are on a moving platform).
397 * (Set field 1 'S' == static if we are not moving).
398 * (Set field 1 'K' == known position if we can initialize lat/lon/alt).
401 if (pp->sloppyclockflag & CLK_FLAG2)
402 up->moving = 1; /* Receiver on mobile platform */
404 up->moving = 0; /* Static Installation */
408 /* dynamic: solve for pos, alt, time, while moving */
411 /* static: solve for pos, alt, time, while stationary */
414 mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg,
416 tr_mode, /* time recovery mode (see above ) */
417 'U', /* synchronize to UTC */
418 'A', /* always output a time pulse */
419 500, /* max time error in ns */
420 0, /* user bias in ns */
421 1); /* output "830" sentences to control port */
422 /* Multi-satellite mode */
425 * Output position information (to calculate fixed installation
426 * location) only if we are not moving
429 add_mode = 2; /* delete from list */
431 add_mode = 1; /* add to list */
435 * "007" Control Port Configuration
438 mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg,
440 PMVXG_D_DOPS, /* control port output block Label */
441 0, /* clear current output control list (0=no) */
442 add_mode, /* add/delete sentences from list (1=add, 2=del) */
444 INTERVAL); /* sentence output rate (sec) */
445 /* precision for position output */
446 /* nmea version for cga & gll output */
447 /* pass-through control */
451 * "007" Control Port Configuration
452 * Output "021" position, height, velocity reports
454 mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg,
456 PMVXG_D_PHV, /* control port output block Label */
457 0, /* clear current output control list (0=no) */
458 add_mode, /* add/delete sentences from list (1=add, 2=del) */
460 INTERVAL); /* sentence output rate (sec) */
461 /* precision for position output */
462 /* nmea version for cga & gll output */
463 /* pass-through control */
467 * mx4200_ref - Reconfigure unit as a reference station at a known position.
474 register struct mx4200unit *up;
475 struct refclockproc *pp;
476 double minute, lat, lon, alt;
477 char lats[16], lons[16];
481 up = (struct mx4200unit *)pp->unitptr;
483 /* Should never happen! */
484 if (up->moving) return;
487 * Set up to output status information in the near future
489 up->log_time = current_time + SLEEPTIME;
492 * "007" Control Port Configuration
493 * Stop outputting "022" DOPs
495 mx4200_send(peer, "%s,%03d,%03d,%d,%d,,,,,", pmvxg,
497 PMVXG_D_DOPS, /* control port output block Label */
498 0, /* clear current output control list (0=no) */
499 2); /* add/delete sentences from list (2=delete) */
501 /* sentence output rate (sec) */
502 /* precision for position output */
503 /* nmea version for cga & gll output */
504 /* pass-through control */
507 * "007" Control Port Configuration
508 * Stop outputting "021" position, height, velocity reports
510 mx4200_send(peer, "%s,%03d,%03d,%d,%d,,,,,", pmvxg,
512 PMVXG_D_PHV, /* control port output block Label */
513 0, /* clear current output control list (0=no) */
514 2); /* add/delete sentences from list (2=delete) */
516 /* sentence output rate (sec) */
517 /* precision for position output */
518 /* nmea version for cga & gll output */
519 /* pass-through control */
522 * "001" Initialization/Mode Control, Part B
523 * Put receiver in fully-constrained 2d nav mode
525 mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
526 pmvxg, PMVXG_S_INITMODEB,
529 0.1, /* hor accel fact as per Steve (m/s**2) */
530 0.1, /* ver accel fact as per Steve (m/s**2) */
532 10, /* hdop limit as per Steve */
533 5, /* elevation limit as per Steve (deg) */
534 'U', /* time output mode (UTC) */
535 0); /* local time offset from gmt (HHHMM) */
538 * "023" Time Recovery Configuration
539 * Get UTC time from a stationary receiver. Solve for time only.
540 * This should improve the time resolution dramatically.
542 mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg,
544 'K', /* known position: solve for time only */
545 'U', /* synchronize to UTC */
546 'A', /* always output a time pulse */
547 500, /* max time error in ns */
548 0, /* user bias in ns */
549 1); /* output "830" sentences to control port */
550 /* Multi-satellite mode */
553 * "000" Initialization/Mode Control - Part A
554 * Fix to our averaged position.
556 if (up->central_meridian != NOT_INITIALIZED) {
557 up->avg_lon += up->central_meridian;
558 if (up->avg_lon < -180.0) up->avg_lon += 360.0;
559 if (up->avg_lon > 180.0) up->avg_lon -= 360.0;
562 if (up->avg_lat >= 0.0) {
566 lat = up->avg_lat * (-1.0);
569 if (up->avg_lon >= 0.0) {
573 lon = up->avg_lon * (-1.0);
577 minute = (lat - (double)(int)lat) * 60.0;
578 sprintf(lats,"%02d%02.4f", (int)lat, minute);
579 minute = (lon - (double)(int)lon) * 60.0;
580 sprintf(lons,"%03d%02.4f", (int)lon, minute);
582 mx4200_send(peer, "%s,%03d,,,,,%s,%c,%s,%c,%.2f,%d", pmvxg,
588 lats, /* latitude DDMM.MMMM */
589 nsc, /* north/south */
590 lons, /* longitude DDDMM.MMMM */
593 1); /* Altitude Reference (0=WGS84 ellipsoid, 1=MSL geoid) */
596 "mx4200: reconfig to fixed location: %s %c, %s %c, %.2f m",
597 lats, nsc, lons, ewc, alt );
602 * mx4200_poll - mx4200 watchdog routine
610 register struct mx4200unit *up;
611 struct refclockproc *pp;
614 up = (struct mx4200unit *)pp->unitptr;
617 * You don't need to poll this clock. It puts out timecodes
618 * once per second. If asked for a timestamp, take note.
619 * The next time a timecode comes in, it will be fed back.
623 * If we haven't had a response in a while, reset the receiver.
625 if (up->pollcnt > 0) {
628 refclock_report(peer, CEVNT_TIMEOUT);
631 * Request a "000" status message which should trigger a
634 mx4200_send(peer, "%s,%03d",
635 "CDGPQ", /* query from CDU to GPS */
636 PMVXG_D_STATUS); /* label of desired sentence */
640 * polled every 64 seconds. Ask mx4200_receive to hand in
647 * Output receiver status information.
649 if ((up->log_time > 0) && (current_time > up->log_time)) {
652 * Output the following messages once, for debugging.
654 * "523" Time Recovery Parameters
656 mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_MODEDATA);
657 mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_TRECOVUSEAGE);
661 static char char2hex[] = "0123456789ABCDEF";
664 * mx4200_receive - receive gps data
668 struct recvbuf *rbufp
671 register struct mx4200unit *up;
672 struct refclockproc *pp;
679 * Initialize pointers and read the timecode and timestamp.
681 peer = (struct peer *)rbufp->recv_srcclock;
683 up = (struct mx4200unit *)pp->unitptr;
686 * If operating mode has been changed, then reinitialize the receiver
687 * before doing anything else.
689 if ((pp->sloppyclockflag & CLK_FLAG2) !=
690 (up->sloppyclockflag & CLK_FLAG2)) {
691 up->sloppyclockflag = pp->sloppyclockflag;
693 "mx4200_receive: mode switch: reset receiver\n");
697 up->sloppyclockflag = pp->sloppyclockflag;
700 * Read clock output. Automatically handles STREAMS, CLKLDISC.
702 pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec);
705 * There is a case where <cr><lf> generates 2 timestamps.
707 if (pp->lencode == 0)
711 pp->a_lastcode[pp->lencode] = '\0';
712 record_clock_stats(&peer->srcadr, pp->a_lastcode);
713 mx4200_debug(peer, "mx4200_receive: %d %s\n",
714 pp->lencode, pp->a_lastcode);
717 * The structure of the control port sentences is based on the
718 * NMEA-0183 Standard for interfacing Marine Electronics
719 * Navigation Devices (Version 1.5)
721 * $PMVXG,XXX, ....................*CK<cr><lf>
723 * $ Sentence Start Identifier (reserved char)
724 * (Start-of-Sentence Identifier)
725 * P Special ID (Proprietary)
726 * MVX Originator ID (Magnavox)
727 * G Interface ID (GPS)
728 * , Field Delimiters (reserved char)
731 * * Checksum Field Delimiter (reserved char)
733 * <cr><lf> Carriage-Return/Line Feed (reserved chars)
734 * (End-of-Sentence Identifier)
736 * Reject if any important landmarks are missing.
738 cp = pp->a_lastcode + pp->lencode - 3;
739 if (cp < pp->a_lastcode || *pp->a_lastcode != '$' || cp[0] != '*' ) {
740 mx4200_debug(peer, "mx4200_receive: bad format\n");
741 refclock_report(peer, CEVNT_BADREPLY);
746 * Check and discard the checksum
748 ck = mx4200_cksum(&pp->a_lastcode[1], pp->lencode - 4);
749 if (char2hex[ck >> 4] != cp[1] || char2hex[ck & 0xf] != cp[2]) {
750 mx4200_debug(peer, "mx4200_receive: bad checksum\n");
751 refclock_report(peer, CEVNT_BADREPLY);
757 * Get the sentence type.
760 if ((cp = strchr(pp->a_lastcode, ',')) == NULL) {
761 mx4200_debug(peer, "mx4200_receive: no sentence\n");
762 refclock_report(peer, CEVNT_BADREPLY);
766 sentence_type = strtol(cp, &cp, 10);
769 * "000" Status message
772 if (sentence_type == PMVXG_D_STATUS) {
775 * Since we configure the receiver to not give us status
776 * messages and since the receiver outputs status messages by
777 * default after being reset to factory defaults when sent the
778 * "$PMVXG,018,C\r\n" message, any status message we get
779 * indicates the reciever needs to be initialized; thus, it is
780 * not necessary to decode the status message.
782 if ((cp = mx4200_parse_s(peer)) != NULL) {
784 "mx4200_receive: status: %s\n", cp);
786 mx4200_debug(peer, "mx4200_receive: reset receiver\n");
792 * "021" Position, Height, Velocity message,
793 * if we are still averaging our position
795 if (sentence_type == PMVXG_D_PHV && !up->known) {
797 * Parse the message, calculating our averaged position.
799 if ((cp = mx4200_parse_p(peer)) != NULL) {
800 mx4200_debug(peer, "mx4200_receive: pos: %s\n", cp);
804 "mx4200_receive: position avg %.9f %.9f %.4f\n",
805 up->avg_lat, up->avg_lon, up->avg_alt);
807 "mx4200_receive: position len %.4f %.4f %.4f\n",
808 up->filt_lat, up->filt_lon, up->filt_alt);
810 "mx4200_receive: position dop %.1f %.1f %.1f\n",
811 up->ndop, up->edop, up->vdop);
813 * Reinitialize as a reference station
814 * if position is well known.
816 if (current_time > up->clamp_time) {
818 mx4200_debug(peer, "mx4200_receive: reconfiguring!\n");
825 * "022" DOPs, if we are still averaging our position
827 if (sentence_type == PMVXG_D_DOPS && !up->known) {
828 if ((cp = mx4200_parse_d(peer)) != NULL) {
829 mx4200_debug(peer, "mx4200_receive: dop: %s\n", cp);
836 * Print to the syslog:
838 * "030" Software Configuration
839 * "523" Time Recovery Parameters Currently in Use
841 if (sentence_type == PMVXG_D_MODEDATA ||
842 sentence_type == PMVXG_D_SOFTCONF ||
843 sentence_type == PMVXG_D_TRECOVUSEAGE ) {
844 if ((cp = mx4200_parse_s(peer)) != NULL) {
846 "mx4200_receive: multi-record: %s\n", cp);
853 * "830" Time Recovery Results message
855 if (sentence_type == PMVXG_D_TRECOVOUT) {
858 * Capture the last PPS signal.
859 * Precision timestamp is returned in pp->lastrec
861 if (mx4200_pps(peer) != NULL) {
862 mx4200_debug(peer, "mx4200_receive: pps failure\n");
863 refclock_report(peer, CEVNT_FAULT);
869 * Parse the time recovery message, and keep the info
870 * to print the pretty billboards.
872 if ((cp = mx4200_parse_t(peer)) != NULL) {
873 mx4200_debug(peer, "mx4200_receive: time: %s\n", cp);
874 refclock_report(peer, CEVNT_BADREPLY);
879 * Add the new sample to a median filter.
881 if (!refclock_process(pp)) {
882 mx4200_debug(peer,"mx4200_receive: offset: %.6f\n",
884 refclock_report(peer, CEVNT_BADTIME);
889 * The clock will blurt a timecode every second but we only
890 * want one when polled. If we havn't been polled, bail out.
896 * Return offset and dispersion to control module. We use
897 * lastrec as both the reference time and receive time in
898 * order to avoid being cute, like setting the reference time
899 * later than the receive time, which may cause a paranoid
900 * protocol module to chuck out the data.
902 mx4200_debug(peer, "mx4200_receive: process time: ");
903 mx4200_debug(peer, "%4d-%03d %02d:%02d:%02d at %s, %.6f\n",
904 pp->year, pp->day, pp->hour, pp->minute, pp->second,
905 prettydate(&pp->lastrec), pp->offset);
907 refclock_receive(peer);
910 * We have succeeded in answering the poll.
911 * Turn off the flag and return
918 * Ignore all other sentence types
925 * Parse a mx4200 time recovery message. Returns a string if error.
927 * A typical message looks like this. Checksum has already been stripped.
929 * $PMVXG,830,T,YYYY,MM,DD,HH:MM:SS,U,S,FFFFFF,PPPPP,BBBBBB,LL
931 * Field Field Contents
932 * ----- --------------
933 * Block Label: $PMVXG
934 * Sentence Type: 830=Time Recovery Results
935 * This sentence is output approximately 1 second
936 * preceding the 1PPS output. It indicates the
937 * exact time of the next pulse, whether or not the
938 * time mark will be valid (based on operator-specified
939 * error tolerance), the time to which the pulse is
940 * synchronized, the receiver operating mode,
941 * and the time error of the *last* 1PPS output.
942 * 1 char Time Mark Valid: T=Valid, F=Not Valid
944 * 3 int Month of Year: 1-12
945 * 4 int Day of Month: 1-31
946 * 5 int Time of Day: HH:MM:SS
947 * 6 char Time Synchronization: U=UTC, G=GPS
948 * 7 char Time Recovery Mode: D=Dynamic, S=Static,
949 * K=Known Position, N=No Time Recovery
950 * 8 int Oscillator Offset: The filter's estimate of the oscillator
951 * frequency error, in parts per billion (ppb).
952 * 9 int Time Mark Error: The computed error of the *last* pulse
953 * output, in nanoseconds.
954 * 10 int User Time Bias: Operator specified bias, in nanoseconds
955 * 11 int Leap Second Flag: Indicates that a leap second will
956 * occur. This value is usually zero, except during
957 * the week prior to the leap second occurence, when
958 * this value will be set to +1 or -1. A value of
959 * +1 indicates that GPS time will be 1 second
960 * further ahead of UTC time.
968 struct refclockproc *pp;
969 struct mx4200unit *up;
970 char time_mark_valid, time_sync, op_mode;
971 int sentence_type, valid;
972 int year, day_of_year, month, day_of_month, hour, minute, second, leapsec;
973 int oscillator_offset, time_mark_error, time_bias;
976 up = (struct mx4200unit *)pp->unitptr;
978 leapsec = 0; /* Not all receivers output leap second warnings (!) */
979 sscanf(pp->a_lastcode, "$PMVXG,%d,%c,%d,%d,%d,%d:%d:%d,%c,%c,%d,%d,%d,%d",
980 &sentence_type, &time_mark_valid, &year, &month, &day_of_month,
981 &hour, &minute, &second, &time_sync, &op_mode, &oscillator_offset,
982 &time_mark_error, &time_bias, &leapsec);
984 if (sentence_type != PMVXG_D_TRECOVOUT)
985 return ("wrong rec-type");
987 switch (time_mark_valid) {
995 return ("bad pulse-valid");
1000 return ("synchronized to GPS; should be UTC");
1002 break; /* UTC -> ok */
1004 return ("not synchronized to UTC");
1008 * Check for insane time (allow for possible leap seconds)
1010 if (second > 60 || minute > 59 || hour > 23 ||
1011 second < 0 || minute < 0 || hour < 0) {
1013 "mx4200_parse_t: bad time %02d:%02d:%02d",
1014 hour, minute, second);
1016 mx4200_debug(peer, " (leap %+d\n)", leapsec);
1017 mx4200_debug(peer, "\n");
1018 refclock_report(peer, CEVNT_BADTIME);
1019 return ("bad time");
1021 if ( second == 60 ) {
1023 "mx4200: leap second! %02d:%02d:%02d",
1024 hour, minute, second);
1028 * Check for insane date
1029 * (Certainly can't be any year before this code was last altered!)
1031 if (day_of_month > 31 || month > 12 ||
1032 day_of_month < 1 || month < 1 || year < YEAR_RIGHT_NOW) {
1034 "mx4200_parse_t: bad date (%4d-%02d-%02d)\n",
1035 year, month, day_of_month);
1036 refclock_report(peer, CEVNT_BADDATE);
1037 return ("bad date");
1041 * Silly Hack for MX4200:
1042 * ASCII message is for *next* 1PPS signal, but we have the
1043 * timestamp for the *last* 1PPS signal. So we have to subtract
1044 * a second. Discard if we are on a month boundary to avoid
1045 * possible leap seconds and leap days.
1057 if (day_of_month < 1) {
1058 return ("sorry, month boundary");
1065 * Calculate Julian date
1067 if (!(day_of_year = mx4200_jday(year, month, day_of_month))) {
1069 "mx4200_parse_t: bad julian date %d (%4d-%02d-%02d)\n",
1070 day_of_year, year, month, day_of_month);
1071 refclock_report(peer, CEVNT_BADDATE);
1072 return("invalid julian date");
1076 * Setup leap second indicator
1080 pp->leap = LEAP_NOWARNING;
1083 pp->leap = LEAP_ADDSECOND;
1086 pp->leap = LEAP_DELSECOND;
1089 pp->leap = LEAP_NOTINSYNC;
1093 * Any change to the leap second warning status?
1095 if (leapsec != up->last_leap ) {
1097 "mx4200: leap second warning: %d to %d (%d)",
1098 up->last_leap, leapsec, pp->leap);
1100 up->last_leap = leapsec;
1103 * Copy time data for billboard monitoring.
1107 pp->day = day_of_year;
1109 pp->minute = minute;
1110 pp->second = second;
1115 * Toss if sentence is marked invalid
1117 if (!valid || pp->leap == LEAP_NOTINSYNC) {
1118 mx4200_debug(peer, "mx4200_parse_t: time mark not valid\n");
1119 refclock_report(peer, CEVNT_BADTIME);
1120 return ("pulse invalid");
1127 * Calculate the checksum
1137 for (ck = 0; n-- > 0; cp++)
1143 * Tables to compute the day of year. Viva la leap.
1145 static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1146 static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1149 * Calculate the the Julian Day
1158 register int day, i;
1162 * Is this a leap year ?
1165 leap_year = 0; /* FALSE */
1168 leap_year = 1; /* TRUE */
1171 leap_year = 0; /* FALSE */
1173 leap_year = 1; /* TRUE */
1179 * Calculate the Julian Date
1185 if (day > day2tab[month - 1]) {
1188 for (i = 0; i < month - 1; i++)
1191 /* not a leap year */
1192 if (day > day1tab[month - 1]) {
1195 for (i = 0; i < month - 1; i++)
1202 * Parse a mx4200 position/height/velocity sentence.
1204 * A typical message looks like this. Checksum has already been stripped.
1206 * $PMVXG,021,SSSSSS.SS,DDMM.MMMM,N,DDDMM.MMMM,E,HHHHH.H,GGGG.G,EEEE.E,WWWW.W,MM
1208 * Field Field Contents
1209 * ----- --------------
1210 * Block Label: $PMVXG
1211 * Sentence Type: 021=Position, Height Velocity Data
1212 * This sentence gives the receiver position, height,
1213 * navigation mode, and velocity north/east.
1214 * *This sentence is intended for post-analysis
1216 * 1 float UTC measurement time (seconds into week)
1217 * 2 float WGS-84 Lattitude (degrees, minutes)
1218 * 3 char N=North, S=South
1219 * 4 float WGS-84 Longitude (degrees, minutes)
1220 * 5 char E=East, W=West
1221 * 6 float Altitude (meters above mean sea level)
1222 * 7 float Geoidal height (meters)
1223 * 8 float East velocity (m/sec)
1224 * 9 float West Velocity (m/sec)
1225 * 10 int Navigation Mode
1226 * Mode if navigating:
1227 * 1 = Position from remote device
1230 * 4 = 2-D differential position
1231 * 5 = 3-D differential position
1233 * 8 = Position known -- reference station
1234 * 9 = Position known -- Navigator
1235 * Mode if not navigating:
1236 * 51 = Too few satellites
1237 * 52 = DOPs too large
1238 * 53 = Position STD too large
1239 * 54 = Velocity STD too large
1240 * 55 = Too many iterations for velocity
1241 * 56 = Too many iterations for position
1242 * 57 = 3 sat startup failed
1243 * 58 = Command abort
1250 struct refclockproc *pp;
1251 struct mx4200unit *up;
1252 int sentence_type, mode;
1253 double mtime, lat, lon, alt, geoid, vele, veln, weight;
1254 char north_south, east_west;
1257 up = (struct mx4200unit *)pp->unitptr;
1259 /* Should never happen! */
1260 if (up->moving) return ("mobile platform - no pos!");
1262 sscanf ( pp->a_lastcode, "$PMVXG,%d,%lf,%lf,%c,%lf,%c,%lf,%lf,%lf,%lf,%d",
1263 &sentence_type, &mtime, &lat, &north_south, &lon, &east_west, &alt,
1264 &geoid, &vele, &veln, &mode);
1267 if (sentence_type != PMVXG_D_PHV)
1268 return ("wrong rec-type");
1271 * return if not navigating
1274 return ("not navigating");
1275 if (mode != 3 && mode != 5)
1276 return ("not navigating in 3D");
1278 /* Latitude (always +ve) and convert DDMM.MMMM to decimal */
1279 if (lat < 0.0) return ("negative latitude");
1280 if (lat > 9000.0) lat = 9000.0;
1282 lat = ((int)lat) + (((lat - (int)lat)) * 1.6666666666666666);
1285 switch (north_south) {
1292 return ("invalid north/south indicator");
1295 /* Longitude (always +ve) and convert DDDMM.MMMM to decimal */
1296 if (lon < 0.0) return ("negative longitude");
1297 if (lon > 180.0) lon = 180.0;
1299 lon = ((int)lon) + (((lon - (int)lon)) * 1.6666666666666666);
1302 switch (east_west) {
1309 return ("invalid east/west indicator");
1313 * Normalize longitude to near 0 degrees.
1314 * Assume all data are clustered around first reading.
1316 if (up->central_meridian == NOT_INITIALIZED) {
1317 up->central_meridian = lon;
1319 "mx4200_receive: central meridian = %.9f \n",
1320 up->central_meridian);
1322 lon -= up->central_meridian;
1323 if (lon < -180.0) lon += 360.0;
1324 if (lon > 180.0) lon -= 360.0;
1327 * Calculate running weighted averages
1329 weight = 1. / up->edop;
1331 up->avg_lon = (up->filt_lon * up->avg_lon) + (weight * lon);
1332 up->filt_lon += weight;
1333 up->avg_lon = up->avg_lon / up->filt_lon;
1335 weight = 1. / up->ndop;
1337 up->avg_lat = (up->filt_lat * up->avg_lat) + (weight * lat);
1338 up->filt_lat += weight;
1339 up->avg_lat = up->avg_lat / up->filt_lat;
1341 weight = 1. / up->vdop;
1343 up->avg_alt = (up->filt_alt * up->avg_alt) + (weight * alt);
1344 up->filt_alt += weight;
1345 up->avg_alt = up->avg_alt / up->filt_alt;
1348 "mx4200_receive: position rdg %.9f %.9f %.4f (CM=%.9f)\n",
1349 lat, lon, alt, up->central_meridian);
1355 * Parse a mx4200 DOP sentence.
1357 * A typical message looks like this. Checksum has already been stripped.
1359 * $PMVXG,022,SSSSSS.SSEE.E,NN.N,VV.V,XX,XX,XX,XX,XX,XX
1361 * Field Field Contents
1362 * ----- --------------
1363 * Block Label: $PMVXG
1364 * Sentence Type: 022=DOPs. The DOP values in this sentence
1365 * correspond to the satellites listed. The PRNs in
1366 * the message are listed in receiver channel number order
1367 * 1 UTC measurement time (seconds into week)
1369 * 3 NDOP (north DOP)
1370 * 4 VDOP (vertical DOP)
1371 * 5 PRN on channel 1
1372 * 6 PRN on channel 2
1373 * 7 PRN on channel 3
1374 * 8 PRN on channel 4
1375 * 9 PRN on channel 5
1376 * 10 PRN on channel 6
1377 * 11 PRN on channel 7 (12-channel receivers only)
1378 * 12 PRN on channel 8 (12-channel receivers only)
1379 * 13 PRN on channel 9 (12-channel receivers only)
1380 * 14 PRN on channel 10 (12-channel receivers only)
1381 * 15 PRN on channel 11 (12-channel receivers only)
1382 * 16 PRN on channel 12 (12-channel receivers only)
1389 struct refclockproc *pp;
1390 struct mx4200unit *up;
1392 double mtime, edop, ndop, vdop;
1395 up = (struct mx4200unit *)pp->unitptr;
1397 /* Should never happen! */
1398 if (up->moving) return ("mobile platform - no dop!");
1400 sscanf ( pp->a_lastcode, "$PMVXG,%d,%lf,%lf,%lf,%lf",
1401 &sentence_type, &mtime, &edop, &ndop, &vdop);
1404 if (sentence_type != PMVXG_D_DOPS)
1405 return ("wrong rec-type");
1408 if (edop <= 0.0 || ndop <= 0.0 || vdop <= 0.0)
1409 return ("nonpositive dop");
1418 * Parse a mx4200 Status sentence
1419 * Parse a mx4200 Mode Data sentence
1420 * Parse a mx4200 Software Configuration sentence
1421 * Parse a mx4200 Time Recovery Parameters Currently in Use sentence
1422 * (used only for logging raw strings)
1424 * A typical message looks like this. Checksum has already been stripped.
1426 * $PMVXG,000,XXX,XX,X,HHMM,X
1428 * Field Field Contents
1429 * ----- --------------
1430 * Block Label: $PMVXG
1431 * Sentence Type: 000=Status.
1432 * Returns status of the receiver to the controller.
1433 * 1 Current Receiver Status:
1434 * ACQ = Satellite re-acquisition
1435 * ALT = Constellation selection
1436 * COR = Providing corrections (for reference stations only)
1437 * IAC = Initial acquisition
1438 * IDL = Idle, no satellites
1440 * STS = Search the Sky (no almanac available)
1442 * 2 Number of satellites that should be visible
1443 * 3 Number of satellites being tracked
1444 * 4 Time since last navigation status if not currently navigating
1446 * 5 Initialization status:
1447 * 0 = Waiting for initialization parameters
1448 * 1 = Initialization completed
1450 * A typical message looks like this. Checksum has already been stripped.
1452 * $PMVXG,004,C,R,D,H.HH,V.VV,TT,HHHH,VVVV,T
1454 * Field Field Contents
1455 * ----- --------------
1456 * Block Label: $PMVXG
1457 * Sentence Type: 004=Software Configuration.
1458 * Defines the navigation mode and criteria for
1459 * acceptable navigation for the receiver.
1460 * 1 Constrain Altitude Mode:
1461 * 0 = Auto. Constrain altitude (2-D solution) and use
1462 * manual altitude input when 3 sats avalable. Do
1463 * not constrain altitude (3-D solution) when 4 sats
1465 * 1 = Always constrain altitude (2-D solution).
1466 * 2 = Never constrain altitude (3-D solution).
1467 * 3 = Coast. Constrain altitude (2-D solution) and use
1468 * last GPS altitude calculation when 3 sats avalable.
1469 * Do not constrain altitude (3-D solution) when 4 sats
1471 * 2 Altitude Reference: (always 0 for MX4200)
1474 * 3 Differential Navigation Control:
1477 * 4 Horizontal Acceleration Constant (m/sec**2)
1478 * 5 Vertical Acceleration Constant (m/sec**2) (0 for MX4200)
1479 * 6 Tracking Elevation Limit (degrees)
1482 * 9 Time Output Mode:
1485 * 10 Local Time Offset (minutes) (absent on MX4200)
1487 * A typical message looks like this. Checksum has already been stripped.
1489 * $PMVXG,030,NNNN,FFF
1491 * Field Field Contents
1492 * ----- --------------
1493 * Block Label: $PMVXG
1494 * Sentence Type: 030=Software Configuration.
1495 * This sentence contains the navigation processor
1496 * and baseband firmware version numbers.
1497 * 1 Nav Processor Version Number
1498 * 2 Baseband Firmware Version Number
1500 * A typical message looks like this. Checksum has already been stripped.
1502 * $PMVXG,523,M,S,M,EEEE,BBBBBB,C,R
1504 * Field Field Contents
1505 * ----- --------------
1506 * Block Label: $PMVXG
1507 * Sentence Type: 523=Time Recovery Parameters Currently in Use.
1508 * This sentence contains the configuration of the
1509 * time recovery feature of the receiver.
1510 * 1 Time Recovery Mode:
1511 * D = Dynamic; solve for position and time while moving
1512 * S = Static; solve for position and time while stationary
1513 * K = Known position input, solve for time only
1514 * N = No time recovery
1515 * 2 Time Synchronization:
1519 * A = Always output a time pulse
1520 * V = Only output time pulse if time is valid (as determined
1521 * by Maximum Time Error)
1522 * 4 Maximum Time Error - the maximum error (in nanoseconds) for
1523 * which a time mark will be considered valid.
1524 * 5 User Time Bias - external bias in nanoseconds
1525 * 6 Time Message Control:
1526 * 0 = Do not output the time recovery message
1527 * 1 = Output the time recovery message (record 830) to
1529 * 2 = Output the time recovery message (record 830) to
1532 * 8 Position Known PRN (absent on MX 4200)
1540 struct refclockproc *pp;
1541 struct mx4200unit *up;
1545 up = (struct mx4200unit *)pp->unitptr;
1547 sscanf ( pp->a_lastcode, "$PMVXG,%d", &sentence_type);
1550 switch (sentence_type) {
1552 case PMVXG_D_STATUS:
1554 "mx4200: status: %s", pp->a_lastcode);
1556 case PMVXG_D_MODEDATA:
1558 "mx4200: mode data: %s", pp->a_lastcode);
1560 case PMVXG_D_SOFTCONF:
1562 "mx4200: firmware configuration: %s", pp->a_lastcode);
1564 case PMVXG_D_TRECOVUSEAGE:
1566 "mx4200: time recovery parms: %s", pp->a_lastcode);
1569 return ("wrong rec-type");
1576 * Process a PPS signal, returning a timestamp.
1584 struct refclockproc *pp;
1585 struct mx4200unit *up;
1588 #ifdef HAVE_CIOGETEV
1591 #ifdef HAVE_TIOCGPPSEV
1592 request = TIOCGPPSEV;
1596 up = (struct mx4200unit *)pp->unitptr;
1599 * Grab the timestamp of the PPS signal.
1601 temp_serial = up->ppsev.serial;
1602 if (ioctl(fdpps, request, (caddr_t)&up->ppsev) < 0) {
1603 /* XXX Actually, if this fails, we're pretty much screwed */
1605 "mx4200_pps: CIOGETEV/TIOCGPPSEV: serial=%d, fdpps=%d, %s\n",
1606 up->ppsev.serial, fdpps, strerror(errno));
1607 refclock_report(peer, CEVNT_FAULT);
1610 if (temp_serial == up->ppsev.serial) {
1612 "mx4200_pps: ppsev serial not incrementing: %d\n",
1614 refclock_report(peer, CEVNT_FAULT);
1619 * Check pps serial number against last one
1621 if (up->lastserial + 1 != up->ppsev.serial && up->lastserial != 0) {
1622 if (up->ppsev.serial == up->lastserial)
1623 mx4200_debug(peer, "mx4200_pps: no new pps event\n");
1625 mx4200_debug(peer, "mx4200_pps: missed %d pps events\n",
1626 up->ppsev.serial - up->lastserial - 1);
1627 refclock_report(peer, CEVNT_FAULT);
1629 up->lastserial = up->ppsev.serial;
1632 * Return the timestamp in pp->lastrec
1634 up->ppsev.tv.tv_sec += (u_int32) JAN_1970;
1635 TVTOTS(&up->ppsev.tv,&pp->lastrec);
1641 * mx4200_debug - print debug messages
1643 #if defined(__STDC__)
1645 mx4200_debug(struct peer *peer, char *fmt, ...)
1648 mx4200_debug(peer, fmt, va_alist)
1651 #endif /* __STDC__ */
1654 struct refclockproc *pp;
1655 struct mx4200unit *up;
1659 #if defined(__STDC__)
1663 #endif /* __STDC__ */
1666 up = (struct mx4200unit *)pp->unitptr;
1670 * Print debug message to stdout
1671 * In the future, we may want to get get more creative...
1680 * Send a character string to the receiver. Checksum is appended here.
1682 #if defined(__STDC__)
1684 mx4200_send(struct peer *peer, char *fmt, ...)
1687 mx4200_send(peer, fmt, va_alist)
1691 #endif /* __STDC__ */
1693 struct refclockproc *pp;
1694 struct mx4200unit *up;
1702 #if defined(__STDC__)
1706 #endif /* __STDC__ */
1709 up = (struct mx4200unit *)pp->unitptr;
1714 /* BSD is rational */
1715 n = vsnprintf(cp, sizeof(buf) - 1, fmt, ap);
1718 (void)vsprintf(cp, fmt, ap);
1721 ck = mx4200_cksum(cp, n);
1725 /* BSD is rational */
1726 n += snprintf(cp, sizeof(buf) - n - 5, "*%02X\r\n", ck);
1729 sprintf(cp, "*%02X\r\n", ck);
1733 m = write(pp->io.fd, buf, (unsigned)n);
1735 msyslog(LOG_ERR, "mx4200_send: write: %m (%s)", buf);
1736 mx4200_debug(peer, "mx4200_send: %d %s\n", m, buf);
1741 int refclock_mx4200_bs;
1742 #endif /* REFCLOCK */