1 /* $NetBSD: refclock_mx4200.c,v 1.3 2006/06/11 19:34:12 kardel Exp $ */
4 * This software was developed by the Computer Systems Engineering group
5 * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66.
7 * Copyright (c) 1992 The Regents of the University of California.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the University of
21 * California, Lawrence Berkeley Laboratory.
22 * 4. The name of the University may not be used to endorse or promote
23 * products derived from this software without specific prior
26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 * Modified: Marc Brett <marc.brett@westgeo.com> Sept, 1999.
42 * 1. Added support for alternate PPS schemes, with code mostly
43 * copied from the Oncore driver (Thanks, Poul-Henning Kamp).
44 * This code runs on SunOS 4.1.3 with ppsclock-1.6a1 and Solaris 7.
52 #if defined(REFCLOCK) && defined(CLOCK_MX4200) && defined(HAVE_PPSAPI)
56 #include "ntp_refclock.h"
57 #include "ntp_unixtime.h"
58 #include "ntp_stdlib.h"
65 #ifdef HAVE_SYS_TERMIOS_H
66 # include <sys/termios.h>
68 #ifdef HAVE_SYS_PPSCLOCK_H
69 # include <sys/ppsclock.h>
72 #include "ntp_sprintf.h"
74 #ifndef HAVE_STRUCT_PPSCLOCKEV
76 # ifdef HAVE_STRUCT_TIMESPEC
83 #endif /* ! HAVE_STRUCT_PPSCLOCKEV */
86 # include "ppsapi_timepps.h"
87 #endif /* HAVE_PPSAPI */
90 * This driver supports the Magnavox Model MX 4200 GPS Receiver
91 * adapted to precision timing applications. It requires the
92 * ppsclock line discipline or streams module described in the
93 * Line Disciplines and Streams Drivers page. It also requires a
94 * gadget box and 1-PPS level converter, such as described in the
95 * Pulse-per-second (PPS) Signal Interfacing page.
97 * It's likely that other compatible Magnavox receivers such as the
98 * MX 4200D, MX 9212, MX 9012R, MX 9112 will be supported by this code.
102 * Check this every time you edit the code!
104 #define YEAR_LAST_MODIFIED 2000
109 #define DEVICE "/dev/gps%d" /* device name and unit */
110 #define SPEED232 B4800 /* baud */
113 * Radio interface parameters
115 #define PRECISION (-18) /* precision assumed (about 4 us) */
116 #define REFID "GPS\0" /* reference id */
117 #define DESCRIPTION "Magnavox MX4200 GPS Receiver" /* who we are */
118 #define DEFFUDGETIME 0 /* default fudge time (ms) */
120 #define SLEEPTIME 32 /* seconds to wait for reconfig to complete */
123 * Position Averaging.
125 #define INTERVAL 1 /* Interval between position measurements (s) */
126 #define AVGING_TIME 24 /* Number of hours to average */
127 #define NOT_INITIALIZED -9999. /* initial pivot longitude */
130 * MX4200 unit control structure.
133 u_int pollcnt
; /* poll message counter */
134 u_int polled
; /* Hand in a time sample? */
135 u_int lastserial
; /* last pps serial number */
136 struct ppsclockev ppsev
; /* PPS control structure */
137 double avg_lat
; /* average latitude */
138 double avg_lon
; /* average longitude */
139 double avg_alt
; /* average height */
140 double central_meridian
; /* central meridian */
141 double N_fixes
; /* Number of position measurements */
142 int last_leap
; /* leap second warning */
143 u_int moving
; /* mobile platform? */
144 u_long sloppyclockflag
; /* fudge flags */
145 u_int known
; /* position known yet? */
146 u_long clamp_time
; /* when to stop postion averaging */
147 u_long log_time
; /* when to print receiver status */
153 static char pmvxg
[] = "PMVXG";
155 /* XXX should be somewhere else */
157 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5)
158 #ifndef __attribute__
159 #define __attribute__(args)
160 #endif /* __attribute__ */
161 #endif /* __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) */
163 #ifndef __attribute__
164 #define __attribute__(args)
165 #endif /* __attribute__ */
166 #endif /* __GNUC__ */
170 * Function prototypes
172 static int mx4200_start
P((int, struct peer
*));
173 static void mx4200_shutdown
P((int, struct peer
*));
174 static void mx4200_receive
P((struct recvbuf
*));
175 static void mx4200_poll
P((int, struct peer
*));
177 static char * mx4200_parse_t
P((struct peer
*));
178 static char * mx4200_parse_p
P((struct peer
*));
179 static char * mx4200_parse_s
P((struct peer
*));
180 #ifdef QSORT_USES_VOID_P
181 int mx4200_cmpl_fp
P((const void *, const void *));
183 int mx4200_cmpl_fp
P((const l_fp
*, const l_fp
*));
184 #endif /* not QSORT_USES_VOID_P */
185 static int mx4200_config
P((struct peer
*));
186 static void mx4200_ref
P((struct peer
*));
187 static void mx4200_send
P((struct peer
*, char *, ...))
188 __attribute__ ((format (printf
, 2, 3)));
189 static u_char mx4200_cksum
P((char *, int));
190 static int mx4200_jday
P((int, int, int));
191 static void mx4200_debug
P((struct peer
*, char *, ...))
192 __attribute__ ((format (printf
, 2, 3)));
193 static int mx4200_pps
P((struct peer
*));
198 struct refclock refclock_mx4200
= {
199 mx4200_start
, /* start up driver */
200 mx4200_shutdown
, /* shut down driver */
201 mx4200_poll
, /* transmit poll message */
202 noentry
, /* not used (old mx4200_control) */
203 noentry
, /* initialize driver (not used) */
204 noentry
, /* not used (old mx4200_buginfo) */
205 NOFLAGS
/* not used */
211 * mx4200_start - open the devices and initialize data for processing
219 register struct mx4200unit
*up
;
220 struct refclockproc
*pp
;
227 (void)sprintf(gpsdev
, DEVICE
, unit
);
228 if (!(fd
= refclock_open(gpsdev
, SPEED232
, LDISC_PPS
))) {
233 * Allocate unit structure
235 if (!(up
= (struct mx4200unit
*) emalloc(sizeof(struct mx4200unit
)))) {
240 memset((char *)up
, 0, sizeof(struct mx4200unit
));
242 pp
->io
.clock_recv
= mx4200_receive
;
243 pp
->io
.srcclock
= (caddr_t
)peer
;
246 if (!io_addclock(&pp
->io
)) {
251 pp
->unitptr
= (caddr_t
)up
;
254 * Initialize miscellaneous variables
256 peer
->precision
= PRECISION
;
257 pp
->clockdesc
= DESCRIPTION
;
258 memcpy((char *)&pp
->refid
, REFID
, 4);
260 /* Ensure the receiver is properly configured */
261 return mx4200_config(peer
);
266 * mx4200_shutdown - shut down the clock
274 register struct mx4200unit
*up
;
275 struct refclockproc
*pp
;
278 up
= (struct mx4200unit
*)pp
->unitptr
;
279 io_closeclock(&pp
->io
);
285 * mx4200_config - Configure the receiver
294 register struct mx4200unit
*up
;
295 struct refclockproc
*pp
;
299 up
= (struct mx4200unit
*)pp
->unitptr
;
302 * Initialize the unit variables
304 * STRANGE BEHAVIOUR WARNING: The fudge flags are not available
305 * at the time mx4200_start is called. These are set later,
306 * and so the code must be prepared to handle changing flags.
308 up
->sloppyclockflag
= pp
->sloppyclockflag
;
309 if (pp
->sloppyclockflag
& CLK_FLAG2
) {
310 up
->moving
= 1; /* Receiver on mobile platform */
311 msyslog(LOG_DEBUG
, "mx4200_config: mobile platform");
313 up
->moving
= 0; /* Static Installation */
321 up
->central_meridian
= NOT_INITIALIZED
;
323 up
->last_leap
= 0; /* LEAP_NOWARNING */
324 up
->clamp_time
= current_time
+ (AVGING_TIME
* 60 * 60);
325 up
->log_time
= current_time
+ SLEEPTIME
;
327 if (time_pps_create(pp
->io
.fd
, &up
->pps_h
) < 0) {
328 perror("time_pps_create");
330 "mx4200_config: time_pps_create failed: %m");
333 if (time_pps_getcap(up
->pps_h
, &mode
) < 0) {
335 "mx4200_config: time_pps_getcap failed: %m");
339 if (time_pps_getparams(up
->pps_h
, &up
->pps_p
) < 0) {
341 "mx4200_config: time_pps_getparams failed: %m");
345 /* nb. only turn things on, if someone else has turned something
346 * on before we get here, leave it alone!
349 up
->pps_p
.mode
= PPS_CAPTUREASSERT
| PPS_TSFMT_TSPEC
;
350 up
->pps_p
.mode
&= mode
; /* only set what is legal */
352 if (time_pps_setparams(up
->pps_h
, &up
->pps_p
) < 0) {
353 perror("time_pps_setparams");
355 "mx4200_config: time_pps_setparams failed: %m");
359 if (time_pps_kcbind(up
->pps_h
, PPS_KC_HARDPPS
, PPS_CAPTUREASSERT
,
360 PPS_TSFMT_TSPEC
) < 0) {
361 perror("time_pps_kcbind");
363 "mx4200_config: time_pps_kcbind failed: %m");
369 * "007" Control Port Configuration
370 * Zero the output list (do it twice to flush possible junk)
372 mx4200_send(peer
, "%s,%03d,,%d,,,,,,", pmvxg
,
374 /* control port output block Label */
375 1); /* clear current output control list (1=yes) */
376 /* add/delete sentences from list */
378 /* sentence output rate (sec) */
379 /* precision for position output */
380 /* nmea version for cga & gll output */
381 /* pass-through control */
382 mx4200_send(peer
, "%s,%03d,,%d,,,,,,", pmvxg
,
383 PMVXG_S_PORTCONF
, 1);
386 * Request software configuration so we can syslog the firmware version
388 mx4200_send(peer
, "%s,%03d", "CDGPQ", PMVXG_D_SOFTCONF
);
391 * "001" Initialization/Mode Control, Part A
394 mx4200_send(peer
, "%s,%03d,,,,,,,,,,", pmvxg
,
400 /* latitude DDMM.MMMM */
402 /* longitude DDDMM.MMMM */
405 /* Altitude Reference 1=MSL */
408 * "001" Initialization/Mode Control, Part B
409 * Start off in 2d/3d coast mode, holding altitude to last known
410 * value if only 3 satellites available.
412 mx4200_send(peer
, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
413 pmvxg
, PMVXG_S_INITMODEB
,
416 0.1, /* hor accel fact as per Steve (m/s**2) */
417 0.1, /* ver accel fact as per Steve (m/s**2) */
419 10, /* hdop limit as per Steve */
420 5, /* elevation limit as per Steve (deg) */
421 'U', /* time output mode (UTC) */
422 0); /* local time offset from gmt (HHHMM) */
425 * "023" Time Recovery Configuration
426 * Get UTC time from a stationary receiver.
427 * (Set field 1 'D' == dynamic if we are on a moving platform).
428 * (Set field 1 'S' == static if we are not moving).
429 * (Set field 1 'K' == known position if we can initialize lat/lon/alt).
432 if (pp
->sloppyclockflag
& CLK_FLAG2
)
433 up
->moving
= 1; /* Receiver on mobile platform */
435 up
->moving
= 0; /* Static Installation */
439 /* dynamic: solve for pos, alt, time, while moving */
442 /* static: solve for pos, alt, time, while stationary */
445 mx4200_send(peer
, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg
,
447 tr_mode
, /* time recovery mode (see above ) */
448 'U', /* synchronize to UTC */
449 'A', /* always output a time pulse */
450 500, /* max time error in ns */
451 0, /* user bias in ns */
452 1); /* output "830" sentences to control port */
453 /* Multi-satellite mode */
456 * Output position information (to calculate fixed installation
457 * location) only if we are not moving
460 add_mode
= 2; /* delete from list */
462 add_mode
= 1; /* add to list */
467 * "007" Control Port Configuration
468 * Output "021" position, height, velocity reports
470 mx4200_send(peer
, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg
,
472 PMVXG_D_PHV
, /* control port output block Label */
473 0, /* clear current output control list (0=no) */
474 add_mode
, /* add/delete sentences from list (1=add, 2=del) */
476 INTERVAL
); /* sentence output rate (sec) */
477 /* precision for position output */
478 /* nmea version for cga & gll output */
479 /* pass-through control */
485 * mx4200_ref - Reconfigure unit as a reference station at a known position.
492 register struct mx4200unit
*up
;
493 struct refclockproc
*pp
;
494 double minute
, lat
, lon
, alt
;
495 char lats
[16], lons
[16];
499 up
= (struct mx4200unit
*)pp
->unitptr
;
501 /* Should never happen! */
502 if (up
->moving
) return;
505 * Set up to output status information in the near future
507 up
->log_time
= current_time
+ SLEEPTIME
;
510 * "007" Control Port Configuration
511 * Stop outputting "021" position, height, velocity reports
513 mx4200_send(peer
, "%s,%03d,%03d,%d,%d,,,,,", pmvxg
,
515 PMVXG_D_PHV
, /* control port output block Label */
516 0, /* clear current output control list (0=no) */
517 2); /* add/delete sentences from list (2=delete) */
519 /* sentence output rate (sec) */
520 /* precision for position output */
521 /* nmea version for cga & gll output */
522 /* pass-through control */
525 * "001" Initialization/Mode Control, Part B
526 * Put receiver in fully-constrained 2d nav mode
528 mx4200_send(peer
, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d",
529 pmvxg
, PMVXG_S_INITMODEB
,
532 0.1, /* hor accel fact as per Steve (m/s**2) */
533 0.1, /* ver accel fact as per Steve (m/s**2) */
535 10, /* hdop limit as per Steve */
536 5, /* elevation limit as per Steve (deg) */
537 'U', /* time output mode (UTC) */
538 0); /* local time offset from gmt (HHHMM) */
541 * "023" Time Recovery Configuration
542 * Get UTC time from a stationary receiver. Solve for time only.
543 * This should improve the time resolution dramatically.
545 mx4200_send(peer
, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg
,
547 'K', /* known position: solve for time only */
548 'U', /* synchronize to UTC */
549 'A', /* always output a time pulse */
550 500, /* max time error in ns */
551 0, /* user bias in ns */
552 1); /* output "830" sentences to control port */
553 /* Multi-satellite mode */
556 * "000" Initialization/Mode Control - Part A
557 * Fix to our averaged position.
559 if (up
->central_meridian
!= NOT_INITIALIZED
) {
560 up
->avg_lon
+= up
->central_meridian
;
561 if (up
->avg_lon
< -180.0) up
->avg_lon
+= 360.0;
562 if (up
->avg_lon
> 180.0) up
->avg_lon
-= 360.0;
565 if (up
->avg_lat
>= 0.0) {
569 lat
= up
->avg_lat
* (-1.0);
572 if (up
->avg_lon
>= 0.0) {
576 lon
= up
->avg_lon
* (-1.0);
580 minute
= (lat
- (double)(int)lat
) * 60.0;
581 sprintf(lats
,"%02d%02.4f", (int)lat
, minute
);
582 minute
= (lon
- (double)(int)lon
) * 60.0;
583 sprintf(lons
,"%03d%02.4f", (int)lon
, minute
);
585 mx4200_send(peer
, "%s,%03d,,,,,%s,%c,%s,%c,%.2f,%d", pmvxg
,
591 lats
, /* latitude DDMM.MMMM */
592 nsc
, /* north/south */
593 lons
, /* longitude DDDMM.MMMM */
596 1); /* Altitude Reference (0=WGS84 ellipsoid, 1=MSL geoid)*/
599 "mx4200: reconfig to fixed location: %s %c, %s %c, %.2f m",
600 lats
, nsc
, lons
, ewc
, alt
);
605 * mx4200_poll - mx4200 watchdog routine
613 register struct mx4200unit
*up
;
614 struct refclockproc
*pp
;
617 up
= (struct mx4200unit
*)pp
->unitptr
;
620 * You don't need to poll this clock. It puts out timecodes
621 * once per second. If asked for a timestamp, take note.
622 * The next time a timecode comes in, it will be fed back.
626 * If we haven't had a response in a while, reset the receiver.
628 if (up
->pollcnt
> 0) {
631 refclock_report(peer
, CEVNT_TIMEOUT
);
634 * Request a "000" status message which should trigger a
637 mx4200_send(peer
, "%s,%03d",
638 "CDGPQ", /* query from CDU to GPS */
639 PMVXG_D_STATUS
); /* label of desired sentence */
643 * polled every 64 seconds. Ask mx4200_receive to hand in
650 * Output receiver status information.
652 if ((up
->log_time
> 0) && (current_time
> up
->log_time
)) {
655 * Output the following messages once, for debugging.
657 * "523" Time Recovery Parameters
659 mx4200_send(peer
, "%s,%03d", "CDGPQ", PMVXG_D_MODEDATA
);
660 mx4200_send(peer
, "%s,%03d", "CDGPQ", PMVXG_D_TRECOVUSEAGE
);
664 static char char2hex
[] = "0123456789ABCDEF";
667 * mx4200_receive - receive gps data
671 struct recvbuf
*rbufp
674 register struct mx4200unit
*up
;
675 struct refclockproc
*pp
;
682 * Initialize pointers and read the timecode and timestamp.
684 peer
= (struct peer
*)rbufp
->recv_srcclock
;
686 up
= (struct mx4200unit
*)pp
->unitptr
;
689 * If operating mode has been changed, then reinitialize the receiver
690 * before doing anything else.
692 if ((pp
->sloppyclockflag
& CLK_FLAG2
) !=
693 (up
->sloppyclockflag
& CLK_FLAG2
)) {
694 up
->sloppyclockflag
= pp
->sloppyclockflag
;
696 "mx4200_receive: mode switch: reset receiver\n");
700 up
->sloppyclockflag
= pp
->sloppyclockflag
;
703 * Read clock output. Automatically handles STREAMS, CLKLDISC.
705 pp
->lencode
= refclock_gtlin(rbufp
, pp
->a_lastcode
, BMAX
, &pp
->lastrec
);
708 * There is a case where <cr><lf> generates 2 timestamps.
710 if (pp
->lencode
== 0)
714 pp
->a_lastcode
[pp
->lencode
] = '\0';
715 record_clock_stats(&peer
->srcadr
, pp
->a_lastcode
);
716 mx4200_debug(peer
, "mx4200_receive: %d %s\n",
717 pp
->lencode
, pp
->a_lastcode
);
720 * The structure of the control port sentences is based on the
721 * NMEA-0183 Standard for interfacing Marine Electronics
722 * Navigation Devices (Version 1.5)
724 * $PMVXG,XXX, ....................*CK<cr><lf>
726 * $ Sentence Start Identifier (reserved char)
727 * (Start-of-Sentence Identifier)
728 * P Special ID (Proprietary)
729 * MVX Originator ID (Magnavox)
730 * G Interface ID (GPS)
731 * , Field Delimiters (reserved char)
734 * * Checksum Field Delimiter (reserved char)
736 * <cr><lf> Carriage-Return/Line Feed (reserved chars)
737 * (End-of-Sentence Identifier)
739 * Reject if any important landmarks are missing.
741 cp
= pp
->a_lastcode
+ pp
->lencode
- 3;
742 if (cp
< pp
->a_lastcode
|| *pp
->a_lastcode
!= '$' || cp
[0] != '*' ) {
743 mx4200_debug(peer
, "mx4200_receive: bad format\n");
744 refclock_report(peer
, CEVNT_BADREPLY
);
749 * Check and discard the checksum
751 ck
= mx4200_cksum(&pp
->a_lastcode
[1], pp
->lencode
- 4);
752 if (char2hex
[ck
>> 4] != cp
[1] || char2hex
[ck
& 0xf] != cp
[2]) {
753 mx4200_debug(peer
, "mx4200_receive: bad checksum\n");
754 refclock_report(peer
, CEVNT_BADREPLY
);
760 * Get the sentence type.
763 if ((cp
= strchr(pp
->a_lastcode
, ',')) == NULL
) {
764 mx4200_debug(peer
, "mx4200_receive: no sentence\n");
765 refclock_report(peer
, CEVNT_BADREPLY
);
769 sentence_type
= strtol(cp
, &cp
, 10);
772 * Process the sentence according to its type.
774 switch (sentence_type
) {
777 * "000" Status message
782 * Since we configure the receiver to not give us status
783 * messages and since the receiver outputs status messages by
784 * default after being reset to factory defaults when sent the
785 * "$PMVXG,018,C\r\n" message, any status message we get
786 * indicates the reciever needs to be initialized; thus, it is
787 * not necessary to decode the status message.
789 if ((cp
= mx4200_parse_s(peer
)) != NULL
) {
791 "mx4200_receive: status: %s\n", cp
);
793 mx4200_debug(peer
, "mx4200_receive: reset receiver\n");
798 * "021" Position, Height, Velocity message,
799 * if we are still averaging our position
804 * Parse the message, calculating our averaged position.
806 if ((cp
= mx4200_parse_p(peer
)) != NULL
) {
807 mx4200_debug(peer
, "mx4200_receive: pos: %s\n", cp
);
811 "mx4200_receive: position avg %f %.9f %.9f %.4f\n",
812 up
->N_fixes
, up
->avg_lat
, up
->avg_lon
, up
->avg_alt
);
814 * Reinitialize as a reference station
815 * if position is well known.
817 if (current_time
> up
->clamp_time
) {
819 mx4200_debug(peer
, "mx4200_receive: reconfiguring!\n");
826 * Print to the syslog:
828 * "030" Software Configuration
829 * "523" Time Recovery Parameters Currently in Use
831 case PMVXG_D_MODEDATA
:
832 case PMVXG_D_SOFTCONF
:
833 case PMVXG_D_TRECOVUSEAGE
:
835 if ((cp
= mx4200_parse_s(peer
)) != NULL
) {
837 "mx4200_receive: multi-record: %s\n", cp
);
842 * "830" Time Recovery Results message
844 case PMVXG_D_TRECOVOUT
:
847 * Capture the last PPS signal.
848 * Precision timestamp is returned in pp->lastrec
850 if (mx4200_pps(peer
) != NULL
) {
851 mx4200_debug(peer
, "mx4200_receive: pps failure\n");
852 refclock_report(peer
, CEVNT_FAULT
);
858 * Parse the time recovery message, and keep the info
859 * to print the pretty billboards.
861 if ((cp
= mx4200_parse_t(peer
)) != NULL
) {
862 mx4200_debug(peer
, "mx4200_receive: time: %s\n", cp
);
863 refclock_report(peer
, CEVNT_BADREPLY
);
868 * Add the new sample to a median filter.
870 if (!refclock_process(pp
)) {
871 mx4200_debug(peer
,"mx4200_receive: offset: %.6f\n",
873 refclock_report(peer
, CEVNT_BADTIME
);
878 * The clock will blurt a timecode every second but we only
879 * want one when polled. If we havn't been polled, bail out.
885 * Return offset and dispersion to control module. We use
886 * lastrec as both the reference time and receive time in
887 * order to avoid being cute, like setting the reference time
888 * later than the receive time, which may cause a paranoid
889 * protocol module to chuck out the data.
891 mx4200_debug(peer
, "mx4200_receive: process time: ");
892 mx4200_debug(peer
, "%4d-%03d %02d:%02d:%02d at %s, %.6f\n",
893 pp
->year
, pp
->day
, pp
->hour
, pp
->minute
, pp
->second
,
894 prettydate(&pp
->lastrec
), pp
->offset
);
895 pp
->lastref
= pp
->lastrec
;
896 refclock_receive(peer
);
899 * We have succeeded in answering the poll.
900 * Turn off the flag and return
906 * Ignore all other sentence types
911 } /* switch (sentence_type) */
918 * Parse a mx4200 time recovery message. Returns a string if error.
920 * A typical message looks like this. Checksum has already been stripped.
922 * $PMVXG,830,T,YYYY,MM,DD,HH:MM:SS,U,S,FFFFFF,PPPPP,BBBBBB,LL
924 * Field Field Contents
925 * ----- --------------
926 * Block Label: $PMVXG
927 * Sentence Type: 830=Time Recovery Results
928 * This sentence is output approximately 1 second
929 * preceding the 1PPS output. It indicates the
930 * exact time of the next pulse, whether or not the
931 * time mark will be valid (based on operator-specified
932 * error tolerance), the time to which the pulse is
933 * synchronized, the receiver operating mode,
934 * and the time error of the *last* 1PPS output.
935 * 1 char Time Mark Valid: T=Valid, F=Not Valid
937 * 3 int Month of Year: 1-12
938 * 4 int Day of Month: 1-31
939 * 5 int Time of Day: HH:MM:SS
940 * 6 char Time Synchronization: U=UTC, G=GPS
941 * 7 char Time Recovery Mode: D=Dynamic, S=Static,
942 * K=Known Position, N=No Time Recovery
943 * 8 int Oscillator Offset: The filter's estimate of the oscillator
944 * frequency error, in parts per billion (ppb).
945 * 9 int Time Mark Error: The computed error of the *last* pulse
946 * output, in nanoseconds.
947 * 10 int User Time Bias: Operator specified bias, in nanoseconds
948 * 11 int Leap Second Flag: Indicates that a leap second will
949 * occur. This value is usually zero, except during
950 * the week prior to the leap second occurrence, when
951 * this value will be set to +1 or -1. A value of
952 * +1 indicates that GPS time will be 1 second
953 * further ahead of UTC time.
961 struct refclockproc
*pp
;
962 struct mx4200unit
*up
;
963 char time_mark_valid
, time_sync
, op_mode
;
964 int sentence_type
, valid
;
965 int year
, day_of_year
, month
, day_of_month
;
966 int hour
, minute
, second
, leapsec
;
967 int oscillator_offset
, time_mark_error
, time_bias
;
970 up
= (struct mx4200unit
*)pp
->unitptr
;
972 leapsec
= 0; /* Not all receivers output leap second warnings (!) */
973 sscanf(pp
->a_lastcode
,
974 "$PMVXG,%d,%c,%d,%d,%d,%d:%d:%d,%c,%c,%d,%d,%d,%d",
975 &sentence_type
, &time_mark_valid
, &year
, &month
, &day_of_month
,
976 &hour
, &minute
, &second
, &time_sync
, &op_mode
,
977 &oscillator_offset
, &time_mark_error
, &time_bias
, &leapsec
);
979 if (sentence_type
!= PMVXG_D_TRECOVOUT
)
980 return ("wrong rec-type");
982 switch (time_mark_valid
) {
990 return ("bad pulse-valid");
995 return ("synchronized to GPS; should be UTC");
997 break; /* UTC -> ok */
999 return ("not synchronized to UTC");
1003 * Check for insane time (allow for possible leap seconds)
1005 if (second
> 60 || minute
> 59 || hour
> 23 ||
1006 second
< 0 || minute
< 0 || hour
< 0) {
1008 "mx4200_parse_t: bad time %02d:%02d:%02d",
1009 hour
, minute
, second
);
1011 mx4200_debug(peer
, " (leap %+d\n)", leapsec
);
1012 mx4200_debug(peer
, "\n");
1013 refclock_report(peer
, CEVNT_BADTIME
);
1014 return ("bad time");
1016 if ( second
== 60 ) {
1018 "mx4200: leap second! %02d:%02d:%02d",
1019 hour
, minute
, second
);
1023 * Check for insane date
1024 * (Certainly can't be any year before this code was last altered!)
1026 if (day_of_month
> 31 || month
> 12 ||
1027 day_of_month
< 1 || month
< 1 || year
< YEAR_LAST_MODIFIED
) {
1029 "mx4200_parse_t: bad date (%4d-%02d-%02d)\n",
1030 year
, month
, day_of_month
);
1031 refclock_report(peer
, CEVNT_BADDATE
);
1032 return ("bad date");
1036 * Silly Hack for MX4200:
1037 * ASCII message is for *next* 1PPS signal, but we have the
1038 * timestamp for the *last* 1PPS signal. So we have to subtract
1039 * a second. Discard if we are on a month boundary to avoid
1040 * possible leap seconds and leap days.
1052 if (day_of_month
< 1) {
1053 return ("sorry, month boundary");
1060 * Calculate Julian date
1062 if (!(day_of_year
= mx4200_jday(year
, month
, day_of_month
))) {
1064 "mx4200_parse_t: bad julian date %d (%4d-%02d-%02d)\n",
1065 day_of_year
, year
, month
, day_of_month
);
1066 refclock_report(peer
, CEVNT_BADDATE
);
1067 return("invalid julian date");
1071 * Setup leap second indicator
1075 pp
->leap
= LEAP_NOWARNING
;
1078 pp
->leap
= LEAP_ADDSECOND
;
1081 pp
->leap
= LEAP_DELSECOND
;
1084 pp
->leap
= LEAP_NOTINSYNC
;
1088 * Any change to the leap second warning status?
1090 if (leapsec
!= up
->last_leap
) {
1092 "mx4200: leap second warning: %d to %d (%d)",
1093 up
->last_leap
, leapsec
, pp
->leap
);
1095 up
->last_leap
= leapsec
;
1098 * Copy time data for billboard monitoring.
1102 pp
->day
= day_of_year
;
1104 pp
->minute
= minute
;
1105 pp
->second
= second
;
1108 * Toss if sentence is marked invalid
1110 if (!valid
|| pp
->leap
== LEAP_NOTINSYNC
) {
1111 mx4200_debug(peer
, "mx4200_parse_t: time mark not valid\n");
1112 refclock_report(peer
, CEVNT_BADTIME
);
1113 return ("pulse invalid");
1120 * Calculate the checksum
1130 for (ck
= 0; n
-- > 0; cp
++)
1136 * Tables to compute the day of year. Viva la leap.
1138 static int day1tab
[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1139 static int day2tab
[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1142 * Calculate the the Julian Day
1151 register int day
, i
;
1155 * Is this a leap year ?
1158 leap_year
= 0; /* FALSE */
1161 leap_year
= 1; /* TRUE */
1164 leap_year
= 0; /* FALSE */
1166 leap_year
= 1; /* TRUE */
1172 * Calculate the Julian Date
1178 if (day
> day2tab
[month
- 1]) {
1181 for (i
= 0; i
< month
- 1; i
++)
1184 /* not a leap year */
1185 if (day
> day1tab
[month
- 1]) {
1188 for (i
= 0; i
< month
- 1; i
++)
1195 * Parse a mx4200 position/height/velocity sentence.
1197 * A typical message looks like this. Checksum has already been stripped.
1199 * $PMVXG,021,SSSSSS.SS,DDMM.MMMM,N,DDDMM.MMMM,E,HHHHH.H,GGGG.G,EEEE.E,WWWW.W,MM
1201 * Field Field Contents
1202 * ----- --------------
1203 * Block Label: $PMVXG
1204 * Sentence Type: 021=Position, Height Velocity Data
1205 * This sentence gives the receiver position, height,
1206 * navigation mode, and velocity north/east.
1207 * *This sentence is intended for post-analysis
1209 * 1 float UTC measurement time (seconds into week)
1210 * 2 float WGS-84 Lattitude (degrees, minutes)
1211 * 3 char N=North, S=South
1212 * 4 float WGS-84 Longitude (degrees, minutes)
1213 * 5 char E=East, W=West
1214 * 6 float Altitude (meters above mean sea level)
1215 * 7 float Geoidal height (meters)
1216 * 8 float East velocity (m/sec)
1217 * 9 float West Velocity (m/sec)
1218 * 10 int Navigation Mode
1219 * Mode if navigating:
1220 * 1 = Position from remote device
1223 * 4 = 2-D differential position
1224 * 5 = 3-D differential position
1226 * 8 = Position known -- reference station
1227 * 9 = Position known -- Navigator
1228 * Mode if not navigating:
1229 * 51 = Too few satellites
1230 * 52 = DOPs too large
1231 * 53 = Position STD too large
1232 * 54 = Velocity STD too large
1233 * 55 = Too many iterations for velocity
1234 * 56 = Too many iterations for position
1235 * 57 = 3 sat startup failed
1236 * 58 = Command abort
1243 struct refclockproc
*pp
;
1244 struct mx4200unit
*up
;
1245 int sentence_type
, mode
;
1246 double mtime
, lat
, lon
, alt
, geoid
, vele
, veln
;
1247 char north_south
, east_west
;
1250 up
= (struct mx4200unit
*)pp
->unitptr
;
1252 /* Should never happen! */
1253 if (up
->moving
) return ("mobile platform - no pos!");
1255 sscanf ( pp
->a_lastcode
,
1256 "$PMVXG,%d,%lf,%lf,%c,%lf,%c,%lf,%lf,%lf,%lf,%d",
1257 &sentence_type
, &mtime
, &lat
, &north_south
, &lon
, &east_west
,
1258 &alt
, &geoid
, &vele
, &veln
, &mode
);
1261 if (sentence_type
!= PMVXG_D_PHV
)
1262 return ("wrong rec-type");
1265 * return if not navigating
1268 return ("not navigating");
1269 if (mode
!= 3 && mode
!= 5)
1270 return ("not navigating in 3D");
1272 /* Latitude (always +ve) and convert DDMM.MMMM to decimal */
1273 if (lat
< 0.0) return ("negative latitude");
1274 if (lat
> 9000.0) lat
= 9000.0;
1276 lat
= ((int)lat
) + (((lat
- (int)lat
)) * 1.6666666666666666);
1279 switch (north_south
) {
1286 return ("invalid north/south indicator");
1289 /* Longitude (always +ve) and convert DDDMM.MMMM to decimal */
1290 if (lon
< 0.0) return ("negative longitude");
1291 if (lon
> 180.0) lon
= 180.0;
1293 lon
= ((int)lon
) + (((lon
- (int)lon
)) * 1.6666666666666666);
1296 switch (east_west
) {
1303 return ("invalid east/west indicator");
1307 * Normalize longitude to near 0 degrees.
1308 * Assume all data are clustered around first reading.
1310 if (up
->central_meridian
== NOT_INITIALIZED
) {
1311 up
->central_meridian
= lon
;
1313 "mx4200_receive: central meridian = %.9f \n",
1314 up
->central_meridian
);
1316 lon
-= up
->central_meridian
;
1317 if (lon
< -180.0) lon
+= 360.0;
1318 if (lon
> 180.0) lon
-= 360.0;
1321 * Calculate running averages
1324 up
->avg_lon
= (up
->N_fixes
* up
->avg_lon
) + lon
;
1325 up
->avg_lat
= (up
->N_fixes
* up
->avg_lat
) + lat
;
1326 up
->avg_alt
= (up
->N_fixes
* up
->avg_alt
) + alt
;
1330 up
->avg_lon
/= up
->N_fixes
;
1331 up
->avg_lat
/= up
->N_fixes
;
1332 up
->avg_alt
/= up
->N_fixes
;
1335 "mx4200_receive: position rdg %.0f: %.9f %.9f %.4f (CM=%.9f)\n",
1336 up
->N_fixes
, lat
, lon
, alt
, up
->central_meridian
);
1342 * Parse a mx4200 Status sentence
1343 * Parse a mx4200 Mode Data sentence
1344 * Parse a mx4200 Software Configuration sentence
1345 * Parse a mx4200 Time Recovery Parameters Currently in Use sentence
1346 * (used only for logging raw strings)
1348 * A typical message looks like this. Checksum has already been stripped.
1350 * $PMVXG,000,XXX,XX,X,HHMM,X
1352 * Field Field Contents
1353 * ----- --------------
1354 * Block Label: $PMVXG
1355 * Sentence Type: 000=Status.
1356 * Returns status of the receiver to the controller.
1357 * 1 Current Receiver Status:
1358 * ACQ = Satellite re-acquisition
1359 * ALT = Constellation selection
1360 * COR = Providing corrections (for reference stations only)
1361 * IAC = Initial acquisition
1362 * IDL = Idle, no satellites
1364 * STS = Search the Sky (no almanac available)
1366 * 2 Number of satellites that should be visible
1367 * 3 Number of satellites being tracked
1368 * 4 Time since last navigation status if not currently navigating
1370 * 5 Initialization status:
1371 * 0 = Waiting for initialization parameters
1372 * 1 = Initialization completed
1374 * A typical message looks like this. Checksum has already been stripped.
1376 * $PMVXG,004,C,R,D,H.HH,V.VV,TT,HHHH,VVVV,T
1378 * Field Field Contents
1379 * ----- --------------
1380 * Block Label: $PMVXG
1381 * Sentence Type: 004=Software Configuration.
1382 * Defines the navigation mode and criteria for
1383 * acceptable navigation for the receiver.
1384 * 1 Constrain Altitude Mode:
1385 * 0 = Auto. Constrain altitude (2-D solution) and use
1386 * manual altitude input when 3 sats avalable. Do
1387 * not constrain altitude (3-D solution) when 4 sats
1389 * 1 = Always constrain altitude (2-D solution).
1390 * 2 = Never constrain altitude (3-D solution).
1391 * 3 = Coast. Constrain altitude (2-D solution) and use
1392 * last GPS altitude calculation when 3 sats avalable.
1393 * Do not constrain altitude (3-D solution) when 4 sats
1395 * 2 Altitude Reference: (always 0 for MX4200)
1398 * 3 Differential Navigation Control:
1401 * 4 Horizontal Acceleration Constant (m/sec**2)
1402 * 5 Vertical Acceleration Constant (m/sec**2) (0 for MX4200)
1403 * 6 Tracking Elevation Limit (degrees)
1406 * 9 Time Output Mode:
1409 * 10 Local Time Offset (minutes) (absent on MX4200)
1411 * A typical message looks like this. Checksum has already been stripped.
1413 * $PMVXG,030,NNNN,FFF
1415 * Field Field Contents
1416 * ----- --------------
1417 * Block Label: $PMVXG
1418 * Sentence Type: 030=Software Configuration.
1419 * This sentence contains the navigation processor
1420 * and baseband firmware version numbers.
1421 * 1 Nav Processor Version Number
1422 * 2 Baseband Firmware Version Number
1424 * A typical message looks like this. Checksum has already been stripped.
1426 * $PMVXG,523,M,S,M,EEEE,BBBBBB,C,R
1428 * Field Field Contents
1429 * ----- --------------
1430 * Block Label: $PMVXG
1431 * Sentence Type: 523=Time Recovery Parameters Currently in Use.
1432 * This sentence contains the configuration of the
1433 * time recovery feature of the receiver.
1434 * 1 Time Recovery Mode:
1435 * D = Dynamic; solve for position and time while moving
1436 * S = Static; solve for position and time while stationary
1437 * K = Known position input, solve for time only
1438 * N = No time recovery
1439 * 2 Time Synchronization:
1443 * A = Always output a time pulse
1444 * V = Only output time pulse if time is valid (as determined
1445 * by Maximum Time Error)
1446 * 4 Maximum Time Error - the maximum error (in nanoseconds) for
1447 * which a time mark will be considered valid.
1448 * 5 User Time Bias - external bias in nanoseconds
1449 * 6 Time Message Control:
1450 * 0 = Do not output the time recovery message
1451 * 1 = Output the time recovery message (record 830) to
1453 * 2 = Output the time recovery message (record 830) to
1456 * 8 Position Known PRN (absent on MX 4200)
1464 struct refclockproc
*pp
;
1465 struct mx4200unit
*up
;
1469 up
= (struct mx4200unit
*)pp
->unitptr
;
1471 sscanf ( pp
->a_lastcode
, "$PMVXG,%d", &sentence_type
);
1474 switch (sentence_type
) {
1476 case PMVXG_D_STATUS
:
1478 "mx4200: status: %s", pp
->a_lastcode
);
1480 case PMVXG_D_MODEDATA
:
1482 "mx4200: mode data: %s", pp
->a_lastcode
);
1484 case PMVXG_D_SOFTCONF
:
1486 "mx4200: firmware configuration: %s", pp
->a_lastcode
);
1488 case PMVXG_D_TRECOVUSEAGE
:
1490 "mx4200: time recovery parms: %s", pp
->a_lastcode
);
1493 return ("wrong rec-type");
1500 * Process a PPS signal, placing a timestamp in pp->lastrec.
1508 struct refclockproc
*pp
;
1509 struct mx4200unit
*up
;
1511 struct timespec timeout
;
1514 up
= (struct mx4200unit
*)pp
->unitptr
;
1517 * Grab the timestamp of the PPS signal.
1519 temp_serial
= up
->pps_i
.assert_sequence
;
1521 timeout
.tv_nsec
= 0;
1522 if (time_pps_fetch(up
->pps_h
, PPS_TSFMT_TSPEC
, &(up
->pps_i
),
1525 "mx4200_pps: time_pps_fetch: serial=%ul, %s\n",
1526 (unsigned long)up
->pps_i
.assert_sequence
, strerror(errno
));
1527 refclock_report(peer
, CEVNT_FAULT
);
1530 if (temp_serial
== up
->pps_i
.assert_sequence
) {
1532 "mx4200_pps: assert_sequence serial not incrementing: %ul\n",
1533 (unsigned long)up
->pps_i
.assert_sequence
);
1534 refclock_report(peer
, CEVNT_FAULT
);
1538 * Check pps serial number against last one
1540 if (up
->lastserial
+ 1 != up
->pps_i
.assert_sequence
&&
1541 up
->lastserial
!= 0) {
1542 if (up
->pps_i
.assert_sequence
== up
->lastserial
) {
1543 mx4200_debug(peer
, "mx4200_pps: no new pps event\n");
1545 mx4200_debug(peer
, "mx4200_pps: missed %ul pps events\n",
1546 up
->pps_i
.assert_sequence
- up
->lastserial
- 1UL);
1548 refclock_report(peer
, CEVNT_FAULT
);
1550 up
->lastserial
= up
->pps_i
.assert_sequence
;
1553 * Return the timestamp in pp->lastrec
1556 pp
->lastrec
.l_ui
= up
->pps_i
.assert_timestamp
.tv_sec
+
1558 pp
->lastrec
.l_uf
= ((double)(up
->pps_i
.assert_timestamp
.tv_nsec
) *
1559 4.2949672960) + 0.5;
1565 * mx4200_debug - print debug messages
1567 #if defined(__STDC__)
1569 mx4200_debug(struct peer
*peer
, char *fmt
, ...)
1572 mx4200_debug(peer
, fmt
, va_alist
)
1575 #endif /* __STDC__ */
1579 struct refclockproc
*pp
;
1580 struct mx4200unit
*up
;
1584 #if defined(__STDC__)
1588 #endif /* __STDC__ */
1591 up
= (struct mx4200unit
*)pp
->unitptr
;
1595 * Print debug message to stdout
1596 * In the future, we may want to get get more creative...
1606 * Send a character string to the receiver. Checksum is appended here.
1608 #if defined(__STDC__)
1610 mx4200_send(struct peer
*peer
, char *fmt
, ...)
1613 mx4200_send(peer
, fmt
, va_alist
)
1617 #endif /* __STDC__ */
1619 struct refclockproc
*pp
;
1620 struct mx4200unit
*up
;
1628 #if defined(__STDC__)
1632 #endif /* __STDC__ */
1635 up
= (struct mx4200unit
*)pp
->unitptr
;
1639 n
= VSNPRINTF((cp
, sizeof(buf
) - 1, fmt
, ap
));
1640 ck
= mx4200_cksum(cp
, n
);
1643 n
+= SNPRINTF((cp
, sizeof(buf
) - n
- 5, "*%02X\r\n", ck
));
1645 m
= write(pp
->io
.fd
, buf
, (unsigned)n
);
1647 msyslog(LOG_ERR
, "mx4200_send: write: %m (%s)", buf
);
1648 mx4200_debug(peer
, "mx4200_send: %d %s\n", m
, buf
);
1653 int refclock_mx4200_bs
;
1654 #endif /* REFCLOCK */