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(HAVE_PPSAPI)
54 #include "ntp_refclock.h"
55 #include "ntp_unixtime.h"
56 #include "ntp_stdlib.h"
63 #ifdef HAVE_SYS_TERMIOS_H
64 # include <sys/termios.h>
66 #ifdef HAVE_SYS_PPSCLOCK_H
67 # include <sys/ppsclock.h>
70 #include "ntp_sprintf.h"
72 #ifndef HAVE_STRUCT_PPSCLOCKEV
74 # ifdef HAVE_STRUCT_TIMESPEC
81 #endif /* ! HAVE_STRUCT_PPSCLOCKEV */
84 # include "ppsapi_timepps.h"
85 #endif /* HAVE_PPSAPI */
88 * This driver supports the Magnavox Model MX 4200 GPS Receiver
89 * adapted to precision timing applications. It requires the
90 * ppsclock line discipline or streams module described in the
91 * Line Disciplines and Streams Drivers page. It also requires a
92 * gadget box and 1-PPS level converter, such as described in the
93 * Pulse-per-second (PPS) Signal Interfacing page.
95 * It's likely that other compatible Magnavox receivers such as the
96 * MX 4200D, MX 9212, MX 9012R, MX 9112 will be supported by this code.
100 * Check this every time you edit the code!
102 #define YEAR_LAST_MODIFIED 2000
107 #define DEVICE "/dev/gps%d" /* device name and unit */
108 #define SPEED232 B4800 /* baud */
111 * Radio interface parameters
113 #define PRECISION (-18) /* precision assumed (about 4 us) */
114 #define REFID "GPS\0" /* reference id */
115 #define DESCRIPTION "Magnavox MX4200 GPS Receiver" /* who we are */
116 #define DEFFUDGETIME 0 /* default fudge time (ms) */
118 #define SLEEPTIME 32 /* seconds to wait for reconfig to complete */
121 * Position Averaging.
123 #define INTERVAL 1 /* Interval between position measurements (s) */
124 #define AVGING_TIME 24 /* Number of hours to average */
125 #define NOT_INITIALIZED -9999. /* initial pivot longitude */
128 * MX4200 unit control structure.
131 u_int pollcnt
; /* poll message counter */
132 u_int polled
; /* Hand in a time sample? */
133 u_int lastserial
; /* last pps serial number */
134 struct ppsclockev ppsev
; /* PPS control structure */
135 double avg_lat
; /* average latitude */
136 double avg_lon
; /* average longitude */
137 double avg_alt
; /* average height */
138 double central_meridian
; /* central meridian */
139 double N_fixes
; /* Number of position measurements */
140 int last_leap
; /* leap second warning */
141 u_int moving
; /* mobile platform? */
142 u_long sloppyclockflag
; /* fudge flags */
143 u_int known
; /* position known yet? */
144 u_long clamp_time
; /* when to stop postion averaging */
145 u_long log_time
; /* when to print receiver status */
151 static char pmvxg
[] = "PMVXG";
153 /* XXX should be somewhere else */
155 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5)
156 #ifndef __attribute__
157 #define __attribute__(args)
158 #endif /* __attribute__ */
159 #endif /* __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) */
161 #ifndef __attribute__
162 #define __attribute__(args)
163 #endif /* __attribute__ */
164 #endif /* __GNUC__ */
168 * Function prototypes
170 static int mx4200_start
P((int, struct peer
*));
171 static void mx4200_shutdown
P((int, struct peer
*));
172 static void mx4200_receive
P((struct recvbuf
*));
173 static void mx4200_poll
P((int, struct peer
*));
175 static char * mx4200_parse_t
P((struct peer
*));
176 static char * mx4200_parse_p
P((struct peer
*));
177 static char * mx4200_parse_s
P((struct peer
*));
178 #ifdef QSORT_USES_VOID_P
179 int mx4200_cmpl_fp
P((const void *, const void *));
181 int mx4200_cmpl_fp
P((const l_fp
*, const l_fp
*));
182 #endif /* not QSORT_USES_VOID_P */
183 static int mx4200_config
P((struct peer
*));
184 static void mx4200_ref
P((struct peer
*));
185 static void mx4200_send
P((struct peer
*, char *, ...))
186 __attribute__ ((format (printf
, 2, 3)));
187 static u_char mx4200_cksum
P((char *, int));
188 static int mx4200_jday
P((int, int, int));
189 static void mx4200_debug
P((struct peer
*, char *, ...))
190 __attribute__ ((format (printf
, 2, 3)));
191 static int mx4200_pps
P((struct peer
*));
196 struct refclock refclock_mx4200
= {
197 mx4200_start
, /* start up driver */
198 mx4200_shutdown
, /* shut down driver */
199 mx4200_poll
, /* transmit poll message */
200 noentry
, /* not used (old mx4200_control) */
201 noentry
, /* initialize driver (not used) */
202 noentry
, /* not used (old mx4200_buginfo) */
203 NOFLAGS
/* not used */
209 * mx4200_start - open the devices and initialize data for processing
217 register struct mx4200unit
*up
;
218 struct refclockproc
*pp
;
225 (void)sprintf(gpsdev
, DEVICE
, unit
);
226 if (!(fd
= refclock_open(gpsdev
, SPEED232
, LDISC_PPS
))) {
231 * Allocate unit structure
233 if (!(up
= (struct mx4200unit
*) emalloc(sizeof(struct mx4200unit
)))) {
238 memset((char *)up
, 0, sizeof(struct mx4200unit
));
240 pp
->io
.clock_recv
= mx4200_receive
;
241 pp
->io
.srcclock
= (caddr_t
)peer
;
244 if (!io_addclock(&pp
->io
)) {
249 pp
->unitptr
= (caddr_t
)up
;
252 * Initialize miscellaneous variables
254 peer
->precision
= PRECISION
;
255 pp
->clockdesc
= DESCRIPTION
;
256 memcpy((char *)&pp
->refid
, REFID
, 4);
258 /* Ensure the receiver is properly configured */
259 return mx4200_config(peer
);
264 * mx4200_shutdown - shut down the clock
272 register struct mx4200unit
*up
;
273 struct refclockproc
*pp
;
276 up
= (struct mx4200unit
*)pp
->unitptr
;
277 io_closeclock(&pp
->io
);
283 * mx4200_config - Configure the receiver
292 register struct mx4200unit
*up
;
293 struct refclockproc
*pp
;
297 up
= (struct mx4200unit
*)pp
->unitptr
;
300 * Initialize the unit variables
302 * STRANGE BEHAVIOUR WARNING: The fudge flags are not available
303 * at the time mx4200_start is called. These are set later,
304 * and so the code must be prepared to handle changing flags.
306 up
->sloppyclockflag
= pp
->sloppyclockflag
;
307 if (pp
->sloppyclockflag
& CLK_FLAG2
) {
308 up
->moving
= 1; /* Receiver on mobile platform */
309 msyslog(LOG_DEBUG
, "mx4200_config: mobile platform");
311 up
->moving
= 0; /* Static Installation */
319 up
->central_meridian
= NOT_INITIALIZED
;
321 up
->last_leap
= 0; /* LEAP_NOWARNING */
322 up
->clamp_time
= current_time
+ (AVGING_TIME
* 60 * 60);
323 up
->log_time
= current_time
+ SLEEPTIME
;
325 if (time_pps_create(pp
->io
.fd
, &up
->pps_h
) < 0) {
326 perror("time_pps_create");
328 "mx4200_config: time_pps_create failed: %m");
331 if (time_pps_getcap(up
->pps_h
, &mode
) < 0) {
333 "mx4200_config: time_pps_getcap failed: %m");
337 if (time_pps_getparams(up
->pps_h
, &up
->pps_p
) < 0) {
339 "mx4200_config: time_pps_getparams failed: %m");
343 /* nb. only turn things on, if someone else has turned something
344 * on before we get here, leave it alone!
347 up
->pps_p
.mode
= PPS_CAPTUREASSERT
| PPS_TSFMT_TSPEC
;
348 up
->pps_p
.mode
&= mode
; /* only set what is legal */
350 if (time_pps_setparams(up
->pps_h
, &up
->pps_p
) < 0) {
351 perror("time_pps_setparams");
353 "mx4200_config: time_pps_setparams failed: %m");
357 if (time_pps_kcbind(up
->pps_h
, PPS_KC_HARDPPS
, PPS_CAPTUREASSERT
,
358 PPS_TSFMT_TSPEC
) < 0) {
359 perror("time_pps_kcbind");
361 "mx4200_config: time_pps_kcbind failed: %m");
367 * "007" Control Port Configuration
368 * Zero the output list (do it twice to flush possible junk)
370 mx4200_send(peer
, "%s,%03d,,%d,,,,,,", pmvxg
,
372 /* control port output block Label */
373 1); /* clear current output control list (1=yes) */
374 /* add/delete sentences from list */
376 /* sentence output rate (sec) */
377 /* precision for position output */
378 /* nmea version for cga & gll output */
379 /* pass-through control */
380 mx4200_send(peer
, "%s,%03d,,%d,,,,,,", pmvxg
,
381 PMVXG_S_PORTCONF
, 1);
384 * Request software configuration so we can syslog the firmware version
386 mx4200_send(peer
, "%s,%03d", "CDGPQ", PMVXG_D_SOFTCONF
);
389 * "001" Initialization/Mode Control, Part A
392 mx4200_send(peer
, "%s,%03d,,,,,,,,,,", pmvxg
,
398 /* latitude DDMM.MMMM */
400 /* longitude DDDMM.MMMM */
403 /* Altitude Reference 1=MSL */
406 * "001" Initialization/Mode Control, Part B
407 * Start off in 2d/3d coast mode, holding altitude to last known
408 * value if only 3 satellites available.
410 mx4200_send(peer
, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
411 pmvxg
, PMVXG_S_INITMODEB
,
414 0.1, /* hor accel fact as per Steve (m/s**2) */
415 0.1, /* ver accel fact as per Steve (m/s**2) */
417 10, /* hdop limit as per Steve */
418 5, /* elevation limit as per Steve (deg) */
419 'U', /* time output mode (UTC) */
420 0); /* local time offset from gmt (HHHMM) */
423 * "023" Time Recovery Configuration
424 * Get UTC time from a stationary receiver.
425 * (Set field 1 'D' == dynamic if we are on a moving platform).
426 * (Set field 1 'S' == static if we are not moving).
427 * (Set field 1 'K' == known position if we can initialize lat/lon/alt).
430 if (pp
->sloppyclockflag
& CLK_FLAG2
)
431 up
->moving
= 1; /* Receiver on mobile platform */
433 up
->moving
= 0; /* Static Installation */
437 /* dynamic: solve for pos, alt, time, while moving */
440 /* static: solve for pos, alt, time, while stationary */
443 mx4200_send(peer
, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg
,
445 tr_mode
, /* time recovery mode (see above ) */
446 'U', /* synchronize to UTC */
447 'A', /* always output a time pulse */
448 500, /* max time error in ns */
449 0, /* user bias in ns */
450 1); /* output "830" sentences to control port */
451 /* Multi-satellite mode */
454 * Output position information (to calculate fixed installation
455 * location) only if we are not moving
458 add_mode
= 2; /* delete from list */
460 add_mode
= 1; /* add to list */
465 * "007" Control Port Configuration
466 * Output "021" position, height, velocity reports
468 mx4200_send(peer
, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg
,
470 PMVXG_D_PHV
, /* control port output block Label */
471 0, /* clear current output control list (0=no) */
472 add_mode
, /* add/delete sentences from list (1=add, 2=del) */
474 INTERVAL
); /* sentence output rate (sec) */
475 /* precision for position output */
476 /* nmea version for cga & gll output */
477 /* pass-through control */
483 * mx4200_ref - Reconfigure unit as a reference station at a known position.
490 register struct mx4200unit
*up
;
491 struct refclockproc
*pp
;
492 double minute
, lat
, lon
, alt
;
493 char lats
[16], lons
[16];
497 up
= (struct mx4200unit
*)pp
->unitptr
;
499 /* Should never happen! */
500 if (up
->moving
) return;
503 * Set up to output status information in the near future
505 up
->log_time
= current_time
+ SLEEPTIME
;
508 * "007" Control Port Configuration
509 * Stop outputting "021" position, height, velocity reports
511 mx4200_send(peer
, "%s,%03d,%03d,%d,%d,,,,,", pmvxg
,
513 PMVXG_D_PHV
, /* control port output block Label */
514 0, /* clear current output control list (0=no) */
515 2); /* add/delete sentences from list (2=delete) */
517 /* sentence output rate (sec) */
518 /* precision for position output */
519 /* nmea version for cga & gll output */
520 /* pass-through control */
523 * "001" Initialization/Mode Control, Part B
524 * Put receiver in fully-constrained 2d nav mode
526 mx4200_send(peer
, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
527 pmvxg
, PMVXG_S_INITMODEB
,
530 0.1, /* hor accel fact as per Steve (m/s**2) */
531 0.1, /* ver accel fact as per Steve (m/s**2) */
533 10, /* hdop limit as per Steve */
534 5, /* elevation limit as per Steve (deg) */
535 'U', /* time output mode (UTC) */
536 0); /* local time offset from gmt (HHHMM) */
539 * "023" Time Recovery Configuration
540 * Get UTC time from a stationary receiver. Solve for time only.
541 * This should improve the time resolution dramatically.
543 mx4200_send(peer
, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg
,
545 'K', /* known position: solve for time only */
546 'U', /* synchronize to UTC */
547 'A', /* always output a time pulse */
548 500, /* max time error in ns */
549 0, /* user bias in ns */
550 1); /* output "830" sentences to control port */
551 /* Multi-satellite mode */
554 * "000" Initialization/Mode Control - Part A
555 * Fix to our averaged position.
557 if (up
->central_meridian
!= NOT_INITIALIZED
) {
558 up
->avg_lon
+= up
->central_meridian
;
559 if (up
->avg_lon
< -180.0) up
->avg_lon
+= 360.0;
560 if (up
->avg_lon
> 180.0) up
->avg_lon
-= 360.0;
563 if (up
->avg_lat
>= 0.0) {
567 lat
= up
->avg_lat
* (-1.0);
570 if (up
->avg_lon
>= 0.0) {
574 lon
= up
->avg_lon
* (-1.0);
578 minute
= (lat
- (double)(int)lat
) * 60.0;
579 sprintf(lats
,"%02d%02.4f", (int)lat
, minute
);
580 minute
= (lon
- (double)(int)lon
) * 60.0;
581 sprintf(lons
,"%03d%02.4f", (int)lon
, minute
);
583 mx4200_send(peer
, "%s,%03d,,,,,%s,%c,%s,%c,%.2f,%d", pmvxg
,
589 lats
, /* latitude DDMM.MMMM */
590 nsc
, /* north/south */
591 lons
, /* longitude DDDMM.MMMM */
594 1); /* Altitude Reference (0=WGS84 ellipsoid, 1=MSL geoid)*/
597 "mx4200: reconfig to fixed location: %s %c, %s %c, %.2f m",
598 lats
, nsc
, lons
, ewc
, alt
);
603 * mx4200_poll - mx4200 watchdog routine
611 register struct mx4200unit
*up
;
612 struct refclockproc
*pp
;
615 up
= (struct mx4200unit
*)pp
->unitptr
;
618 * You don't need to poll this clock. It puts out timecodes
619 * once per second. If asked for a timestamp, take note.
620 * The next time a timecode comes in, it will be fed back.
624 * If we haven't had a response in a while, reset the receiver.
626 if (up
->pollcnt
> 0) {
629 refclock_report(peer
, CEVNT_TIMEOUT
);
632 * Request a "000" status message which should trigger a
635 mx4200_send(peer
, "%s,%03d",
636 "CDGPQ", /* query from CDU to GPS */
637 PMVXG_D_STATUS
); /* label of desired sentence */
641 * polled every 64 seconds. Ask mx4200_receive to hand in
648 * Output receiver status information.
650 if ((up
->log_time
> 0) && (current_time
> up
->log_time
)) {
653 * Output the following messages once, for debugging.
655 * "523" Time Recovery Parameters
657 mx4200_send(peer
, "%s,%03d", "CDGPQ", PMVXG_D_MODEDATA
);
658 mx4200_send(peer
, "%s,%03d", "CDGPQ", PMVXG_D_TRECOVUSEAGE
);
662 static char char2hex
[] = "0123456789ABCDEF";
665 * mx4200_receive - receive gps data
669 struct recvbuf
*rbufp
672 register struct mx4200unit
*up
;
673 struct refclockproc
*pp
;
680 * Initialize pointers and read the timecode and timestamp.
682 peer
= (struct peer
*)rbufp
->recv_srcclock
;
684 up
= (struct mx4200unit
*)pp
->unitptr
;
687 * If operating mode has been changed, then reinitialize the receiver
688 * before doing anything else.
690 if ((pp
->sloppyclockflag
& CLK_FLAG2
) !=
691 (up
->sloppyclockflag
& CLK_FLAG2
)) {
692 up
->sloppyclockflag
= pp
->sloppyclockflag
;
694 "mx4200_receive: mode switch: reset receiver\n");
698 up
->sloppyclockflag
= pp
->sloppyclockflag
;
701 * Read clock output. Automatically handles STREAMS, CLKLDISC.
703 pp
->lencode
= refclock_gtlin(rbufp
, pp
->a_lastcode
, BMAX
, &pp
->lastrec
);
706 * There is a case where <cr><lf> generates 2 timestamps.
708 if (pp
->lencode
== 0)
712 pp
->a_lastcode
[pp
->lencode
] = '\0';
713 record_clock_stats(&peer
->srcadr
, pp
->a_lastcode
);
714 mx4200_debug(peer
, "mx4200_receive: %d %s\n",
715 pp
->lencode
, pp
->a_lastcode
);
718 * The structure of the control port sentences is based on the
719 * NMEA-0183 Standard for interfacing Marine Electronics
720 * Navigation Devices (Version 1.5)
722 * $PMVXG,XXX, ....................*CK<cr><lf>
724 * $ Sentence Start Identifier (reserved char)
725 * (Start-of-Sentence Identifier)
726 * P Special ID (Proprietary)
727 * MVX Originator ID (Magnavox)
728 * G Interface ID (GPS)
729 * , Field Delimiters (reserved char)
732 * * Checksum Field Delimiter (reserved char)
734 * <cr><lf> Carriage-Return/Line Feed (reserved chars)
735 * (End-of-Sentence Identifier)
737 * Reject if any important landmarks are missing.
739 cp
= pp
->a_lastcode
+ pp
->lencode
- 3;
740 if (cp
< pp
->a_lastcode
|| *pp
->a_lastcode
!= '$' || cp
[0] != '*' ) {
741 mx4200_debug(peer
, "mx4200_receive: bad format\n");
742 refclock_report(peer
, CEVNT_BADREPLY
);
747 * Check and discard the checksum
749 ck
= mx4200_cksum(&pp
->a_lastcode
[1], pp
->lencode
- 4);
750 if (char2hex
[ck
>> 4] != cp
[1] || char2hex
[ck
& 0xf] != cp
[2]) {
751 mx4200_debug(peer
, "mx4200_receive: bad checksum\n");
752 refclock_report(peer
, CEVNT_BADREPLY
);
758 * Get the sentence type.
761 if ((cp
= strchr(pp
->a_lastcode
, ',')) == NULL
) {
762 mx4200_debug(peer
, "mx4200_receive: no sentence\n");
763 refclock_report(peer
, CEVNT_BADREPLY
);
767 sentence_type
= strtol(cp
, &cp
, 10);
770 * Process the sentence according to its type.
772 switch (sentence_type
) {
775 * "000" Status message
780 * Since we configure the receiver to not give us status
781 * messages and since the receiver outputs status messages by
782 * default after being reset to factory defaults when sent the
783 * "$PMVXG,018,C\r\n" message, any status message we get
784 * indicates the reciever needs to be initialized; thus, it is
785 * not necessary to decode the status message.
787 if ((cp
= mx4200_parse_s(peer
)) != NULL
) {
789 "mx4200_receive: status: %s\n", cp
);
791 mx4200_debug(peer
, "mx4200_receive: reset receiver\n");
796 * "021" Position, Height, Velocity message,
797 * if we are still averaging our position
802 * Parse the message, calculating our averaged position.
804 if ((cp
= mx4200_parse_p(peer
)) != NULL
) {
805 mx4200_debug(peer
, "mx4200_receive: pos: %s\n", cp
);
809 "mx4200_receive: position avg %f %.9f %.9f %.4f\n",
810 up
->N_fixes
, up
->avg_lat
, up
->avg_lon
, up
->avg_alt
);
812 * Reinitialize as a reference station
813 * if position is well known.
815 if (current_time
> up
->clamp_time
) {
817 mx4200_debug(peer
, "mx4200_receive: reconfiguring!\n");
824 * Print to the syslog:
826 * "030" Software Configuration
827 * "523" Time Recovery Parameters Currently in Use
829 case PMVXG_D_MODEDATA
:
830 case PMVXG_D_SOFTCONF
:
831 case PMVXG_D_TRECOVUSEAGE
:
833 if ((cp
= mx4200_parse_s(peer
)) != NULL
) {
835 "mx4200_receive: multi-record: %s\n", cp
);
840 * "830" Time Recovery Results message
842 case PMVXG_D_TRECOVOUT
:
845 * Capture the last PPS signal.
846 * Precision timestamp is returned in pp->lastrec
848 if (mx4200_pps(peer
) != NULL
) {
849 mx4200_debug(peer
, "mx4200_receive: pps failure\n");
850 refclock_report(peer
, CEVNT_FAULT
);
856 * Parse the time recovery message, and keep the info
857 * to print the pretty billboards.
859 if ((cp
= mx4200_parse_t(peer
)) != NULL
) {
860 mx4200_debug(peer
, "mx4200_receive: time: %s\n", cp
);
861 refclock_report(peer
, CEVNT_BADREPLY
);
866 * Add the new sample to a median filter.
868 if (!refclock_process(pp
)) {
869 mx4200_debug(peer
,"mx4200_receive: offset: %.6f\n",
871 refclock_report(peer
, CEVNT_BADTIME
);
876 * The clock will blurt a timecode every second but we only
877 * want one when polled. If we havn't been polled, bail out.
883 * Return offset and dispersion to control module. We use
884 * lastrec as both the reference time and receive time in
885 * order to avoid being cute, like setting the reference time
886 * later than the receive time, which may cause a paranoid
887 * protocol module to chuck out the data.
889 mx4200_debug(peer
, "mx4200_receive: process time: ");
890 mx4200_debug(peer
, "%4d-%03d %02d:%02d:%02d at %s, %.6f\n",
891 pp
->year
, pp
->day
, pp
->hour
, pp
->minute
, pp
->second
,
892 prettydate(&pp
->lastrec
), pp
->offset
);
893 pp
->lastref
= pp
->lastrec
;
894 refclock_receive(peer
);
897 * We have succeeded in answering the poll.
898 * Turn off the flag and return
904 * Ignore all other sentence types
909 } /* switch (sentence_type) */
916 * Parse a mx4200 time recovery message. Returns a string if error.
918 * A typical message looks like this. Checksum has already been stripped.
920 * $PMVXG,830,T,YYYY,MM,DD,HH:MM:SS,U,S,FFFFFF,PPPPP,BBBBBB,LL
922 * Field Field Contents
923 * ----- --------------
924 * Block Label: $PMVXG
925 * Sentence Type: 830=Time Recovery Results
926 * This sentence is output approximately 1 second
927 * preceding the 1PPS output. It indicates the
928 * exact time of the next pulse, whether or not the
929 * time mark will be valid (based on operator-specified
930 * error tolerance), the time to which the pulse is
931 * synchronized, the receiver operating mode,
932 * and the time error of the *last* 1PPS output.
933 * 1 char Time Mark Valid: T=Valid, F=Not Valid
935 * 3 int Month of Year: 1-12
936 * 4 int Day of Month: 1-31
937 * 5 int Time of Day: HH:MM:SS
938 * 6 char Time Synchronization: U=UTC, G=GPS
939 * 7 char Time Recovery Mode: D=Dynamic, S=Static,
940 * K=Known Position, N=No Time Recovery
941 * 8 int Oscillator Offset: The filter's estimate of the oscillator
942 * frequency error, in parts per billion (ppb).
943 * 9 int Time Mark Error: The computed error of the *last* pulse
944 * output, in nanoseconds.
945 * 10 int User Time Bias: Operator specified bias, in nanoseconds
946 * 11 int Leap Second Flag: Indicates that a leap second will
947 * occur. This value is usually zero, except during
948 * the week prior to the leap second occurrence, when
949 * this value will be set to +1 or -1. A value of
950 * +1 indicates that GPS time will be 1 second
951 * further ahead of UTC time.
959 struct refclockproc
*pp
;
960 struct mx4200unit
*up
;
961 char time_mark_valid
, time_sync
, op_mode
;
962 int sentence_type
, valid
;
963 int year
, day_of_year
, month
, day_of_month
;
964 int hour
, minute
, second
, leapsec
;
965 int oscillator_offset
, time_mark_error
, time_bias
;
968 up
= (struct mx4200unit
*)pp
->unitptr
;
970 leapsec
= 0; /* Not all receivers output leap second warnings (!) */
971 sscanf(pp
->a_lastcode
,
972 "$PMVXG,%d,%c,%d,%d,%d,%d:%d:%d,%c,%c,%d,%d,%d,%d",
973 &sentence_type
, &time_mark_valid
, &year
, &month
, &day_of_month
,
974 &hour
, &minute
, &second
, &time_sync
, &op_mode
,
975 &oscillator_offset
, &time_mark_error
, &time_bias
, &leapsec
);
977 if (sentence_type
!= PMVXG_D_TRECOVOUT
)
978 return ("wrong rec-type");
980 switch (time_mark_valid
) {
988 return ("bad pulse-valid");
993 return ("synchronized to GPS; should be UTC");
995 break; /* UTC -> ok */
997 return ("not synchronized to UTC");
1001 * Check for insane time (allow for possible leap seconds)
1003 if (second
> 60 || minute
> 59 || hour
> 23 ||
1004 second
< 0 || minute
< 0 || hour
< 0) {
1006 "mx4200_parse_t: bad time %02d:%02d:%02d",
1007 hour
, minute
, second
);
1009 mx4200_debug(peer
, " (leap %+d\n)", leapsec
);
1010 mx4200_debug(peer
, "\n");
1011 refclock_report(peer
, CEVNT_BADTIME
);
1012 return ("bad time");
1014 if ( second
== 60 ) {
1016 "mx4200: leap second! %02d:%02d:%02d",
1017 hour
, minute
, second
);
1021 * Check for insane date
1022 * (Certainly can't be any year before this code was last altered!)
1024 if (day_of_month
> 31 || month
> 12 ||
1025 day_of_month
< 1 || month
< 1 || year
< YEAR_LAST_MODIFIED
) {
1027 "mx4200_parse_t: bad date (%4d-%02d-%02d)\n",
1028 year
, month
, day_of_month
);
1029 refclock_report(peer
, CEVNT_BADDATE
);
1030 return ("bad date");
1034 * Silly Hack for MX4200:
1035 * ASCII message is for *next* 1PPS signal, but we have the
1036 * timestamp for the *last* 1PPS signal. So we have to subtract
1037 * a second. Discard if we are on a month boundary to avoid
1038 * possible leap seconds and leap days.
1050 if (day_of_month
< 1) {
1051 return ("sorry, month boundary");
1058 * Calculate Julian date
1060 if (!(day_of_year
= mx4200_jday(year
, month
, day_of_month
))) {
1062 "mx4200_parse_t: bad julian date %d (%4d-%02d-%02d)\n",
1063 day_of_year
, year
, month
, day_of_month
);
1064 refclock_report(peer
, CEVNT_BADDATE
);
1065 return("invalid julian date");
1069 * Setup leap second indicator
1073 pp
->leap
= LEAP_NOWARNING
;
1076 pp
->leap
= LEAP_ADDSECOND
;
1079 pp
->leap
= LEAP_DELSECOND
;
1082 pp
->leap
= LEAP_NOTINSYNC
;
1086 * Any change to the leap second warning status?
1088 if (leapsec
!= up
->last_leap
) {
1090 "mx4200: leap second warning: %d to %d (%d)",
1091 up
->last_leap
, leapsec
, pp
->leap
);
1093 up
->last_leap
= leapsec
;
1096 * Copy time data for billboard monitoring.
1100 pp
->day
= day_of_year
;
1102 pp
->minute
= minute
;
1103 pp
->second
= second
;
1106 * Toss if sentence is marked invalid
1108 if (!valid
|| pp
->leap
== LEAP_NOTINSYNC
) {
1109 mx4200_debug(peer
, "mx4200_parse_t: time mark not valid\n");
1110 refclock_report(peer
, CEVNT_BADTIME
);
1111 return ("pulse invalid");
1118 * Calculate the checksum
1128 for (ck
= 0; n
-- > 0; cp
++)
1134 * Tables to compute the day of year. Viva la leap.
1136 static int day1tab
[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1137 static int day2tab
[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1140 * Calculate the the Julian Day
1149 register int day
, i
;
1153 * Is this a leap year ?
1156 leap_year
= 0; /* FALSE */
1159 leap_year
= 1; /* TRUE */
1162 leap_year
= 0; /* FALSE */
1164 leap_year
= 1; /* TRUE */
1170 * Calculate the Julian Date
1176 if (day
> day2tab
[month
- 1]) {
1179 for (i
= 0; i
< month
- 1; i
++)
1182 /* not a leap year */
1183 if (day
> day1tab
[month
- 1]) {
1186 for (i
= 0; i
< month
- 1; i
++)
1193 * Parse a mx4200 position/height/velocity sentence.
1195 * A typical message looks like this. Checksum has already been stripped.
1197 * $PMVXG,021,SSSSSS.SS,DDMM.MMMM,N,DDDMM.MMMM,E,HHHHH.H,GGGG.G,EEEE.E,WWWW.W,MM
1199 * Field Field Contents
1200 * ----- --------------
1201 * Block Label: $PMVXG
1202 * Sentence Type: 021=Position, Height Velocity Data
1203 * This sentence gives the receiver position, height,
1204 * navigation mode, and velocity north/east.
1205 * *This sentence is intended for post-analysis
1207 * 1 float UTC measurement time (seconds into week)
1208 * 2 float WGS-84 Lattitude (degrees, minutes)
1209 * 3 char N=North, S=South
1210 * 4 float WGS-84 Longitude (degrees, minutes)
1211 * 5 char E=East, W=West
1212 * 6 float Altitude (meters above mean sea level)
1213 * 7 float Geoidal height (meters)
1214 * 8 float East velocity (m/sec)
1215 * 9 float West Velocity (m/sec)
1216 * 10 int Navigation Mode
1217 * Mode if navigating:
1218 * 1 = Position from remote device
1221 * 4 = 2-D differential position
1222 * 5 = 3-D differential position
1224 * 8 = Position known -- reference station
1225 * 9 = Position known -- Navigator
1226 * Mode if not navigating:
1227 * 51 = Too few satellites
1228 * 52 = DOPs too large
1229 * 53 = Position STD too large
1230 * 54 = Velocity STD too large
1231 * 55 = Too many iterations for velocity
1232 * 56 = Too many iterations for position
1233 * 57 = 3 sat startup failed
1234 * 58 = Command abort
1241 struct refclockproc
*pp
;
1242 struct mx4200unit
*up
;
1243 int sentence_type
, mode
;
1244 double mtime
, lat
, lon
, alt
, geoid
, vele
, veln
;
1245 char north_south
, east_west
;
1248 up
= (struct mx4200unit
*)pp
->unitptr
;
1250 /* Should never happen! */
1251 if (up
->moving
) return ("mobile platform - no pos!");
1253 sscanf ( pp
->a_lastcode
,
1254 "$PMVXG,%d,%lf,%lf,%c,%lf,%c,%lf,%lf,%lf,%lf,%d",
1255 &sentence_type
, &mtime
, &lat
, &north_south
, &lon
, &east_west
,
1256 &alt
, &geoid
, &vele
, &veln
, &mode
);
1259 if (sentence_type
!= PMVXG_D_PHV
)
1260 return ("wrong rec-type");
1263 * return if not navigating
1266 return ("not navigating");
1267 if (mode
!= 3 && mode
!= 5)
1268 return ("not navigating in 3D");
1270 /* Latitude (always +ve) and convert DDMM.MMMM to decimal */
1271 if (lat
< 0.0) return ("negative latitude");
1272 if (lat
> 9000.0) lat
= 9000.0;
1274 lat
= ((int)lat
) + (((lat
- (int)lat
)) * 1.6666666666666666);
1277 switch (north_south
) {
1284 return ("invalid north/south indicator");
1287 /* Longitude (always +ve) and convert DDDMM.MMMM to decimal */
1288 if (lon
< 0.0) return ("negative longitude");
1289 if (lon
> 180.0) lon
= 180.0;
1291 lon
= ((int)lon
) + (((lon
- (int)lon
)) * 1.6666666666666666);
1294 switch (east_west
) {
1301 return ("invalid east/west indicator");
1305 * Normalize longitude to near 0 degrees.
1306 * Assume all data are clustered around first reading.
1308 if (up
->central_meridian
== NOT_INITIALIZED
) {
1309 up
->central_meridian
= lon
;
1311 "mx4200_receive: central meridian = %.9f \n",
1312 up
->central_meridian
);
1314 lon
-= up
->central_meridian
;
1315 if (lon
< -180.0) lon
+= 360.0;
1316 if (lon
> 180.0) lon
-= 360.0;
1319 * Calculate running averages
1322 up
->avg_lon
= (up
->N_fixes
* up
->avg_lon
) + lon
;
1323 up
->avg_lat
= (up
->N_fixes
* up
->avg_lat
) + lat
;
1324 up
->avg_alt
= (up
->N_fixes
* up
->avg_alt
) + alt
;
1328 up
->avg_lon
/= up
->N_fixes
;
1329 up
->avg_lat
/= up
->N_fixes
;
1330 up
->avg_alt
/= up
->N_fixes
;
1333 "mx4200_receive: position rdg %.0f: %.9f %.9f %.4f (CM=%.9f)\n",
1334 up
->N_fixes
, lat
, lon
, alt
, up
->central_meridian
);
1340 * Parse a mx4200 Status sentence
1341 * Parse a mx4200 Mode Data sentence
1342 * Parse a mx4200 Software Configuration sentence
1343 * Parse a mx4200 Time Recovery Parameters Currently in Use sentence
1344 * (used only for logging raw strings)
1346 * A typical message looks like this. Checksum has already been stripped.
1348 * $PMVXG,000,XXX,XX,X,HHMM,X
1350 * Field Field Contents
1351 * ----- --------------
1352 * Block Label: $PMVXG
1353 * Sentence Type: 000=Status.
1354 * Returns status of the receiver to the controller.
1355 * 1 Current Receiver Status:
1356 * ACQ = Satellite re-acquisition
1357 * ALT = Constellation selection
1358 * COR = Providing corrections (for reference stations only)
1359 * IAC = Initial acquisition
1360 * IDL = Idle, no satellites
1362 * STS = Search the Sky (no almanac available)
1364 * 2 Number of satellites that should be visible
1365 * 3 Number of satellites being tracked
1366 * 4 Time since last navigation status if not currently navigating
1368 * 5 Initialization status:
1369 * 0 = Waiting for initialization parameters
1370 * 1 = Initialization completed
1372 * A typical message looks like this. Checksum has already been stripped.
1374 * $PMVXG,004,C,R,D,H.HH,V.VV,TT,HHHH,VVVV,T
1376 * Field Field Contents
1377 * ----- --------------
1378 * Block Label: $PMVXG
1379 * Sentence Type: 004=Software Configuration.
1380 * Defines the navigation mode and criteria for
1381 * acceptable navigation for the receiver.
1382 * 1 Constrain Altitude Mode:
1383 * 0 = Auto. Constrain altitude (2-D solution) and use
1384 * manual altitude input when 3 sats avalable. Do
1385 * not constrain altitude (3-D solution) when 4 sats
1387 * 1 = Always constrain altitude (2-D solution).
1388 * 2 = Never constrain altitude (3-D solution).
1389 * 3 = Coast. Constrain altitude (2-D solution) and use
1390 * last GPS altitude calculation when 3 sats avalable.
1391 * Do not constrain altitude (3-D solution) when 4 sats
1393 * 2 Altitude Reference: (always 0 for MX4200)
1396 * 3 Differential Navigation Control:
1399 * 4 Horizontal Acceleration Constant (m/sec**2)
1400 * 5 Vertical Acceleration Constant (m/sec**2) (0 for MX4200)
1401 * 6 Tracking Elevation Limit (degrees)
1404 * 9 Time Output Mode:
1407 * 10 Local Time Offset (minutes) (absent on MX4200)
1409 * A typical message looks like this. Checksum has already been stripped.
1411 * $PMVXG,030,NNNN,FFF
1413 * Field Field Contents
1414 * ----- --------------
1415 * Block Label: $PMVXG
1416 * Sentence Type: 030=Software Configuration.
1417 * This sentence contains the navigation processor
1418 * and baseband firmware version numbers.
1419 * 1 Nav Processor Version Number
1420 * 2 Baseband Firmware Version Number
1422 * A typical message looks like this. Checksum has already been stripped.
1424 * $PMVXG,523,M,S,M,EEEE,BBBBBB,C,R
1426 * Field Field Contents
1427 * ----- --------------
1428 * Block Label: $PMVXG
1429 * Sentence Type: 523=Time Recovery Parameters Currently in Use.
1430 * This sentence contains the configuration of the
1431 * time recovery feature of the receiver.
1432 * 1 Time Recovery Mode:
1433 * D = Dynamic; solve for position and time while moving
1434 * S = Static; solve for position and time while stationary
1435 * K = Known position input, solve for time only
1436 * N = No time recovery
1437 * 2 Time Synchronization:
1441 * A = Always output a time pulse
1442 * V = Only output time pulse if time is valid (as determined
1443 * by Maximum Time Error)
1444 * 4 Maximum Time Error - the maximum error (in nanoseconds) for
1445 * which a time mark will be considered valid.
1446 * 5 User Time Bias - external bias in nanoseconds
1447 * 6 Time Message Control:
1448 * 0 = Do not output the time recovery message
1449 * 1 = Output the time recovery message (record 830) to
1451 * 2 = Output the time recovery message (record 830) to
1454 * 8 Position Known PRN (absent on MX 4200)
1462 struct refclockproc
*pp
;
1463 struct mx4200unit
*up
;
1467 up
= (struct mx4200unit
*)pp
->unitptr
;
1469 sscanf ( pp
->a_lastcode
, "$PMVXG,%d", &sentence_type
);
1472 switch (sentence_type
) {
1474 case PMVXG_D_STATUS
:
1476 "mx4200: status: %s", pp
->a_lastcode
);
1478 case PMVXG_D_MODEDATA
:
1480 "mx4200: mode data: %s", pp
->a_lastcode
);
1482 case PMVXG_D_SOFTCONF
:
1484 "mx4200: firmware configuration: %s", pp
->a_lastcode
);
1486 case PMVXG_D_TRECOVUSEAGE
:
1488 "mx4200: time recovery parms: %s", pp
->a_lastcode
);
1491 return ("wrong rec-type");
1498 * Process a PPS signal, placing a timestamp in pp->lastrec.
1506 struct refclockproc
*pp
;
1507 struct mx4200unit
*up
;
1509 struct timespec timeout
;
1512 up
= (struct mx4200unit
*)pp
->unitptr
;
1515 * Grab the timestamp of the PPS signal.
1517 temp_serial
= up
->pps_i
.assert_sequence
;
1519 timeout
.tv_nsec
= 0;
1520 if (time_pps_fetch(up
->pps_h
, PPS_TSFMT_TSPEC
, &(up
->pps_i
),
1523 "mx4200_pps: time_pps_fetch: serial=%ul, %s\n",
1524 (unsigned long)up
->pps_i
.assert_sequence
, strerror(errno
));
1525 refclock_report(peer
, CEVNT_FAULT
);
1528 if (temp_serial
== up
->pps_i
.assert_sequence
) {
1530 "mx4200_pps: assert_sequence serial not incrementing: %ul\n",
1531 (unsigned long)up
->pps_i
.assert_sequence
);
1532 refclock_report(peer
, CEVNT_FAULT
);
1536 * Check pps serial number against last one
1538 if (up
->lastserial
+ 1 != up
->pps_i
.assert_sequence
&&
1539 up
->lastserial
!= 0) {
1540 if (up
->pps_i
.assert_sequence
== up
->lastserial
) {
1541 mx4200_debug(peer
, "mx4200_pps: no new pps event\n");
1543 mx4200_debug(peer
, "mx4200_pps: missed %ul pps events\n",
1544 up
->pps_i
.assert_sequence
- up
->lastserial
- 1UL);
1546 refclock_report(peer
, CEVNT_FAULT
);
1548 up
->lastserial
= up
->pps_i
.assert_sequence
;
1551 * Return the timestamp in pp->lastrec
1554 pp
->lastrec
.l_ui
= up
->pps_i
.assert_timestamp
.tv_sec
+
1556 pp
->lastrec
.l_uf
= ((double)(up
->pps_i
.assert_timestamp
.tv_nsec
) *
1557 4.2949672960) + 0.5;
1563 * mx4200_debug - print debug messages
1565 #if defined(__STDC__)
1567 mx4200_debug(struct peer
*peer
, char *fmt
, ...)
1570 mx4200_debug(peer
, fmt
, va_alist
)
1573 #endif /* __STDC__ */
1577 struct refclockproc
*pp
;
1578 struct mx4200unit
*up
;
1582 #if defined(__STDC__)
1586 #endif /* __STDC__ */
1589 up
= (struct mx4200unit
*)pp
->unitptr
;
1593 * Print debug message to stdout
1594 * In the future, we may want to get get more creative...
1604 * Send a character string to the receiver. Checksum is appended here.
1606 #if defined(__STDC__)
1608 mx4200_send(struct peer
*peer
, char *fmt
, ...)
1611 mx4200_send(peer
, fmt
, va_alist
)
1615 #endif /* __STDC__ */
1617 struct refclockproc
*pp
;
1618 struct mx4200unit
*up
;
1626 #if defined(__STDC__)
1630 #endif /* __STDC__ */
1633 up
= (struct mx4200unit
*)pp
->unitptr
;
1637 n
= VSNPRINTF((cp
, sizeof(buf
) - 1, fmt
, ap
));
1638 ck
= mx4200_cksum(cp
, n
);
1641 n
+= SNPRINTF((cp
, sizeof(buf
) - n
- 5, "*%02X\r\n", ck
));
1643 m
= write(pp
->io
.fd
, buf
, (unsigned)n
);
1645 msyslog(LOG_ERR
, "mx4200_send: write: %m (%s)", buf
);
1646 mx4200_debug(peer
, "mx4200_send: %d %s\n", m
, buf
);
1651 int refclock_mx4200_bs
;
1652 #endif /* REFCLOCK */