1 /* 2 * This software was developed by the Computer Systems Engineering group 3 * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66. 4 * 5 * Copyright (c) 1992 The Regents of the University of California. 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. All advertising materials mentioning features or use of this software 17 * must display the following acknowledgement: 18 * This product includes software developed by the University of 19 * California, Lawrence Berkeley Laboratory. 20 * 4. The name of the University may not be used to endorse or promote 21 * products derived from this software without specific prior 22 * written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 /* 38 * Modified: Marc Brett <marc.brett@westgeo.com> Sept, 1999. 39 * 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. 43 */ 44 45 46 #ifdef HAVE_CONFIG_H 47 # include <config.h> 48 #endif 49 50 #if defined(REFCLOCK) && defined(CLOCK_MX4200) && defined(PPS) 51 52 #include <stdio.h> 53 #include <ctype.h> 54 #include <sys/types.h> 55 56 #include "ntpd.h" 57 #include "ntp_io.h" 58 #include "ntp_refclock.h" 59 #include "ntp_unixtime.h" 60 #include "ntp_stdlib.h" 61 62 #include "mx4200.h" 63 64 #ifdef HAVE_SYS_TIME_H 65 # include <sys/time.h> 66 #endif 67 #ifdef HAVE_SYS_TERMIOS_H 68 # include <sys/termios.h> 69 #endif 70 #ifdef HAVE_SYS_PPSCLOCK_H 71 # include <sys/ppsclock.h> 72 #endif 73 74 #ifndef HAVE_STRUCT_PPSCLOCKEV 75 struct ppsclockev { 76 # ifdef HAVE_TIMESPEC 77 struct timespec tv; 78 # else 79 struct timeval tv; 80 # endif 81 u_int serial; 82 }; 83 #endif /* ! HAVE_STRUCT_PPSCLOCKEV */ 84 85 /* 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. 92 * 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. 95 */ 96 97 /* 98 * Check this every time you edit the code! 99 */ 100 #define YEAR_RIGHT_NOW 1998 101 102 /* 103 * GPS Definitions 104 */ 105 #define DEVICE "/dev/gps%d" /* device name and unit */ 106 #define SPEED232 B4800 /* baud */ 107 108 /* 109 * Radio interface parameters 110 */ 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) */ 115 116 #define SLEEPTIME 32 /* seconds to wait for reconfig to complete */ 117 118 /* 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. 127 */ 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 */ 131 132 /* 133 * MX4200 unit control structure. 134 */ 135 struct mx4200unit { 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 */ 156 }; 157 158 static char pmvxg[] = "PMVXG"; 159 160 /* XXX should be somewhere else */ 161 #ifdef __GNUC__ 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) */ 167 #else 168 #ifndef __attribute__ 169 #define __attribute__(args) 170 #endif /* __attribute__ */ 171 #endif /* __GNUC__ */ 172 /* XXX end */ 173 174 /* 175 * Function prototypes 176 */ 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 *)); 181 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 *)); 188 #else 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 *)); 200 201 /* 202 * Transfer vector 203 */ 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 */ 212 }; 213 214 215 216 /* 217 * mx4200_start - open the devices and initialize data for processing 218 */ 219 static int 220 mx4200_start( 221 int unit, 222 struct peer *peer 223 ) 224 { 225 register struct mx4200unit *up; 226 struct refclockproc *pp; 227 int fd; 228 char gpsdev[20]; 229 230 /* 231 * Open serial port 232 */ 233 (void)sprintf(gpsdev, DEVICE, unit); 234 if (!(fd = refclock_open(gpsdev, SPEED232, LDISC_PPS))) { 235 return (0); 236 } 237 238 /* 239 * Allocate unit structure 240 */ 241 if (!(up = (struct mx4200unit *) emalloc(sizeof(struct mx4200unit)))) { 242 (void) close(fd); 243 return (0); 244 } 245 memset((char *)up, 0, sizeof(struct mx4200unit)); 246 pp = peer->procptr; 247 pp->io.clock_recv = mx4200_receive; 248 pp->io.srcclock = (caddr_t)peer; 249 pp->io.datalen = 0; 250 pp->io.fd = fd; 251 if (!io_addclock(&pp->io)) { 252 (void) close(fd); 253 free(up); 254 return (0); 255 } 256 pp->unitptr = (caddr_t)up; 257 258 /* 259 * Initialize miscellaneous variables 260 */ 261 peer->precision = PRECISION; 262 pp->clockdesc = DESCRIPTION; 263 memcpy((char *)&pp->refid, REFID, 4); 264 265 /* Ensure the receiver is properly configured */ 266 mx4200_config(peer); 267 return (1); 268 } 269 270 271 /* 272 * mx4200_shutdown - shut down the clock 273 */ 274 static void 275 mx4200_shutdown( 276 int unit, 277 struct peer *peer 278 ) 279 { 280 register struct mx4200unit *up; 281 struct refclockproc *pp; 282 283 pp = peer->procptr; 284 up = (struct mx4200unit *)pp->unitptr; 285 io_closeclock(&pp->io); 286 free(up); 287 } 288 289 290 /* 291 * mx4200_config - Configure the receiver 292 */ 293 static void 294 mx4200_config( 295 struct peer *peer 296 ) 297 { 298 char tr_mode; 299 int add_mode; 300 register struct mx4200unit *up; 301 struct refclockproc *pp; 302 303 pp = peer->procptr; 304 up = (struct mx4200unit *)pp->unitptr; 305 306 /* 307 * Initialize the unit variables 308 * 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. 312 */ 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"); 317 } else { 318 up->moving = 0; /* Static Installation */ 319 } 320 up->pollcnt = 2; 321 up->polled = 0; 322 up->known = 0; 323 up->avg_lat = 0.0; 324 up->avg_lon = 0.0; 325 up->avg_alt = 0.0; 326 up->central_meridian = NOT_INITIALIZED; 327 up->filt_lat = 0.0; 328 up->filt_lon = 0.0; 329 up->filt_alt = 0.0; 330 up->edop = 1; 331 up->ndop = 1; 332 up->vdop = 1; 333 up->last_leap = 0; /* LEAP_NOWARNING */ 334 up->clamp_time = current_time + (AVGING_TIME * 60 * 60); 335 up->log_time = current_time + SLEEPTIME; 336 337 /* 338 * "007" Control Port Configuration 339 * Zero the output list (do it twice to flush possible junk) 340 */ 341 mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg, 342 PMVXG_S_PORTCONF, 343 /* control port output block Label */ 344 1); /* clear current output control list (1=yes) */ 345 /* add/delete sentences from list */ 346 /* must be null */ 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); 353 354 /* 355 * Request software configuration so we can syslog the firmware version 356 */ 357 mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_SOFTCONF); 358 359 /* 360 * "001" Initialization/Mode Control, Part A 361 * Where ARE we? 362 */ 363 mx4200_send(peer, "%s,%03d,,,,,,,,,,", pmvxg, 364 PMVXG_S_INITMODEA); 365 /* day of month */ 366 /* month of year */ 367 /* year */ 368 /* gmt */ 369 /* latitude DDMM.MMMM */ 370 /* north/south */ 371 /* longitude DDDMM.MMMM */ 372 /* east/west */ 373 /* height */ 374 /* Altitude Reference 1=MSL */ 375 376 /* 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. 380 */ 381 mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d", 382 pmvxg, PMVXG_S_INITMODEB, 383 3, /* 2d/3d coast */ 384 /* reserved */ 385 0.1, /* hor accel fact as per Steve (m/s**2) */ 386 0.1, /* ver accel fact as per Steve (m/s**2) */ 387 10, /* vdop */ 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) */ 392 393 /* 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). 399 */ 400 401 if (pp->sloppyclockflag & CLK_FLAG2) 402 up->moving = 1; /* Receiver on mobile platform */ 403 else 404 up->moving = 0; /* Static Installation */ 405 406 up->pollcnt = 2; 407 if (up->moving) { 408 /* dynamic: solve for pos, alt, time, while moving */ 409 tr_mode = 'D'; 410 } else { 411 /* static: solve for pos, alt, time, while stationary */ 412 tr_mode = 'S'; 413 } 414 mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg, 415 PMVXG_S_TRECOVCONF, 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 */ 423 424 /* 425 * Output position information (to calculate fixed installation 426 * location) only if we are not moving 427 */ 428 if (up->moving) { 429 add_mode = 2; /* delete from list */ 430 } else { 431 add_mode = 1; /* add to list */ 432 } 433 434 /* 435 * "007" Control Port Configuration 436 * Output "022" DOPs 437 */ 438 mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg, 439 PMVXG_S_PORTCONF, 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) */ 443 /* must be null */ 444 INTERVAL); /* sentence output rate (sec) */ 445 /* precision for position output */ 446 /* nmea version for cga & gll output */ 447 /* pass-through control */ 448 449 450 /* 451 * "007" Control Port Configuration 452 * Output "021" position, height, velocity reports 453 */ 454 mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg, 455 PMVXG_S_PORTCONF, 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) */ 459 /* must be null */ 460 INTERVAL); /* sentence output rate (sec) */ 461 /* precision for position output */ 462 /* nmea version for cga & gll output */ 463 /* pass-through control */ 464 } 465 466 /* 467 * mx4200_ref - Reconfigure unit as a reference station at a known position. 468 */ 469 static void 470 mx4200_ref( 471 struct peer *peer 472 ) 473 { 474 register struct mx4200unit *up; 475 struct refclockproc *pp; 476 double minute, lat, lon, alt; 477 char lats[16], lons[16]; 478 char nsc, ewc; 479 480 pp = peer->procptr; 481 up = (struct mx4200unit *)pp->unitptr; 482 483 /* Should never happen! */ 484 if (up->moving) return; 485 486 /* 487 * Set up to output status information in the near future 488 */ 489 up->log_time = current_time + SLEEPTIME; 490 491 /* 492 * "007" Control Port Configuration 493 * Stop outputting "022" DOPs 494 */ 495 mx4200_send(peer, "%s,%03d,%03d,%d,%d,,,,,", pmvxg, 496 PMVXG_S_PORTCONF, 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) */ 500 /* must be null */ 501 /* sentence output rate (sec) */ 502 /* precision for position output */ 503 /* nmea version for cga & gll output */ 504 /* pass-through control */ 505 506 /* 507 * "007" Control Port Configuration 508 * Stop outputting "021" position, height, velocity reports 509 */ 510 mx4200_send(peer, "%s,%03d,%03d,%d,%d,,,,,", pmvxg, 511 PMVXG_S_PORTCONF, 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) */ 515 /* must be null */ 516 /* sentence output rate (sec) */ 517 /* precision for position output */ 518 /* nmea version for cga & gll output */ 519 /* pass-through control */ 520 521 /* 522 * "001" Initialization/Mode Control, Part B 523 * Put receiver in fully-constrained 2d nav mode 524 */ 525 mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d", 526 pmvxg, PMVXG_S_INITMODEB, 527 2, /* 2d nav */ 528 /* reserved */ 529 0.1, /* hor accel fact as per Steve (m/s**2) */ 530 0.1, /* ver accel fact as per Steve (m/s**2) */ 531 10, /* vdop */ 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) */ 536 537 /* 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. 541 */ 542 mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg, 543 PMVXG_S_TRECOVCONF, 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 */ 551 552 /* 553 * "000" Initialization/Mode Control - Part A 554 * Fix to our averaged position. 555 */ 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; 560 } 561 562 if (up->avg_lat >= 0.0) { 563 lat = up->avg_lat; 564 nsc = 'N'; 565 } else { 566 lat = up->avg_lat * (-1.0); 567 nsc = 'S'; 568 } 569 if (up->avg_lon >= 0.0) { 570 lon = up->avg_lon; 571 ewc = 'E'; 572 } else { 573 lon = up->avg_lon * (-1.0); 574 ewc = 'W'; 575 } 576 alt = up->avg_alt; 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); 581 582 mx4200_send(peer, "%s,%03d,,,,,%s,%c,%s,%c,%.2f,%d", pmvxg, 583 PMVXG_S_INITMODEA, 584 /* day of month */ 585 /* month of year */ 586 /* year */ 587 /* gmt */ 588 lats, /* latitude DDMM.MMMM */ 589 nsc, /* north/south */ 590 lons, /* longitude DDDMM.MMMM */ 591 ewc, /* east/west */ 592 alt, /* Altitude */ 593 1); /* Altitude Reference (0=WGS84 ellipsoid, 1=MSL geoid) */ 594 595 msyslog(LOG_DEBUG, 596 "mx4200: reconfig to fixed location: %s %c, %s %c, %.2f m", 597 lats, nsc, lons, ewc, alt ); 598 599 } 600 601 /* 602 * mx4200_poll - mx4200 watchdog routine 603 */ 604 static void 605 mx4200_poll( 606 int unit, 607 struct peer *peer 608 ) 609 { 610 register struct mx4200unit *up; 611 struct refclockproc *pp; 612 613 pp = peer->procptr; 614 up = (struct mx4200unit *)pp->unitptr; 615 616 /* 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. 620 */ 621 622 /* 623 * If we haven't had a response in a while, reset the receiver. 624 */ 625 if (up->pollcnt > 0) { 626 up->pollcnt--; 627 } else { 628 refclock_report(peer, CEVNT_TIMEOUT); 629 630 /* 631 * Request a "000" status message which should trigger a 632 * reconfig 633 */ 634 mx4200_send(peer, "%s,%03d", 635 "CDGPQ", /* query from CDU to GPS */ 636 PMVXG_D_STATUS); /* label of desired sentence */ 637 } 638 639 /* 640 * polled every 64 seconds. Ask mx4200_receive to hand in 641 * a timestamp. 642 */ 643 up->polled = 1; 644 pp->polls++; 645 646 /* 647 * Output receiver status information. 648 */ 649 if ((up->log_time > 0) && (current_time > up->log_time)) { 650 up->log_time = 0; 651 /* 652 * Output the following messages once, for debugging. 653 * "004" Mode Data 654 * "523" Time Recovery Parameters 655 */ 656 mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_MODEDATA); 657 mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_TRECOVUSEAGE); 658 } 659 } 660 661 static char char2hex[] = "0123456789ABCDEF"; 662 663 /* 664 * mx4200_receive - receive gps data 665 */ 666 static void 667 mx4200_receive( 668 struct recvbuf *rbufp 669 ) 670 { 671 register struct mx4200unit *up; 672 struct refclockproc *pp; 673 struct peer *peer; 674 char *cp; 675 int sentence_type; 676 u_char ck; 677 678 /* 679 * Initialize pointers and read the timecode and timestamp. 680 */ 681 peer = (struct peer *)rbufp->recv_srcclock; 682 pp = peer->procptr; 683 up = (struct mx4200unit *)pp->unitptr; 684 685 /* 686 * If operating mode has been changed, then reinitialize the receiver 687 * before doing anything else. 688 */ 689 if ((pp->sloppyclockflag & CLK_FLAG2) != 690 (up->sloppyclockflag & CLK_FLAG2)) { 691 up->sloppyclockflag = pp->sloppyclockflag; 692 mx4200_debug(peer, 693 "mx4200_receive: mode switch: reset receiver\n"); 694 mx4200_config(peer); 695 return; 696 } 697 up->sloppyclockflag = pp->sloppyclockflag; 698 699 /* 700 * Read clock output. Automatically handles STREAMS, CLKLDISC. 701 */ 702 pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec); 703 704 /* 705 * There is a case where <cr><lf> generates 2 timestamps. 706 */ 707 if (pp->lencode == 0) 708 return; 709 710 up->pollcnt = 2; 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); 715 716 /* 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) 720 * 721 * $PMVXG,XXX, ....................*CK<cr><lf> 722 * 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) 729 * XXX Sentence Type 730 * ...... Data 731 * * Checksum Field Delimiter (reserved char) 732 * CK Checksum 733 * <cr><lf> Carriage-Return/Line Feed (reserved chars) 734 * (End-of-Sentence Identifier) 735 * 736 * Reject if any important landmarks are missing. 737 */ 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); 742 return; 743 } 744 745 /* 746 * Check and discard the checksum 747 */ 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); 752 return; 753 } 754 *cp = '\0'; 755 756 /* 757 * Get the sentence type. 758 */ 759 sentence_type = 0; 760 if ((cp = strchr(pp->a_lastcode, ',')) == NULL) { 761 mx4200_debug(peer, "mx4200_receive: no sentence\n"); 762 refclock_report(peer, CEVNT_BADREPLY); 763 return; 764 } 765 cp++; 766 sentence_type = strtol(cp, &cp, 10); 767 768 /* 769 * "000" Status message 770 */ 771 772 if (sentence_type == PMVXG_D_STATUS) { 773 /* 774 * XXX 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. 781 */ 782 if ((cp = mx4200_parse_s(peer)) != NULL) { 783 mx4200_debug(peer, 784 "mx4200_receive: status: %s\n", cp); 785 } 786 mx4200_debug(peer, "mx4200_receive: reset receiver\n"); 787 mx4200_config(peer); 788 return; 789 } 790 791 /* 792 * "021" Position, Height, Velocity message, 793 * if we are still averaging our position 794 */ 795 if (sentence_type == PMVXG_D_PHV && !up->known) { 796 /* 797 * Parse the message, calculating our averaged position. 798 */ 799 if ((cp = mx4200_parse_p(peer)) != NULL) { 800 mx4200_debug(peer, "mx4200_receive: pos: %s\n", cp); 801 return; 802 } 803 mx4200_debug(peer, 804 "mx4200_receive: position avg %.9f %.9f %.4f\n", 805 up->avg_lat, up->avg_lon, up->avg_alt); 806 mx4200_debug(peer, 807 "mx4200_receive: position len %.4f %.4f %.4f\n", 808 up->filt_lat, up->filt_lon, up->filt_alt); 809 mx4200_debug(peer, 810 "mx4200_receive: position dop %.1f %.1f %.1f\n", 811 up->ndop, up->edop, up->vdop); 812 /* 813 * Reinitialize as a reference station 814 * if position is well known. 815 */ 816 if (current_time > up->clamp_time) { 817 up->known++; 818 mx4200_debug(peer, "mx4200_receive: reconfiguring!\n"); 819 mx4200_ref(peer); 820 } 821 return; 822 } 823 824 /* 825 * "022" DOPs, if we are still averaging our position 826 */ 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); 830 return; 831 } 832 return; 833 } 834 835 /* 836 * Print to the syslog: 837 * "004" Mode Data 838 * "030" Software Configuration 839 * "523" Time Recovery Parameters Currently in Use 840 */ 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) { 845 mx4200_debug(peer, 846 "mx4200_receive: multi-record: %s\n", cp); 847 return; 848 } 849 return; 850 } 851 852 /* 853 * "830" Time Recovery Results message 854 */ 855 if (sentence_type == PMVXG_D_TRECOVOUT) { 856 857 /* 858 * Capture the last PPS signal. 859 * Precision timestamp is returned in pp->lastrec 860 */ 861 if (mx4200_pps(peer) != NULL) { 862 mx4200_debug(peer, "mx4200_receive: pps failure\n"); 863 refclock_report(peer, CEVNT_FAULT); 864 return; 865 } 866 867 868 /* 869 * Parse the time recovery message, and keep the info 870 * to print the pretty billboards. 871 */ 872 if ((cp = mx4200_parse_t(peer)) != NULL) { 873 mx4200_debug(peer, "mx4200_receive: time: %s\n", cp); 874 refclock_report(peer, CEVNT_BADREPLY); 875 return; 876 } 877 878 /* 879 * Add the new sample to a median filter. 880 */ 881 if (!refclock_process(pp)) { 882 mx4200_debug(peer,"mx4200_receive: offset: %.6f\n", 883 pp->offset); 884 refclock_report(peer, CEVNT_BADTIME); 885 return; 886 } 887 888 /* 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. 891 */ 892 if (!up->polled) 893 return; 894 895 /* 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. 901 */ 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); 906 907 refclock_receive(peer); 908 909 /* 910 * We have succeeded in answering the poll. 911 * Turn off the flag and return 912 */ 913 up->polled = 0; 914 return; 915 } 916 917 /* 918 * Ignore all other sentence types 919 */ 920 return; 921 } 922 923 924 /* 925 * Parse a mx4200 time recovery message. Returns a string if error. 926 * 927 * A typical message looks like this. Checksum has already been stripped. 928 * 929 * $PMVXG,830,T,YYYY,MM,DD,HH:MM:SS,U,S,FFFFFF,PPPPP,BBBBBB,LL 930 * 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 943 * 2 int Year: 1993- 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. 961 * 962 */ 963 static char * 964 mx4200_parse_t( 965 struct peer *peer 966 ) 967 { 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; 974 975 pp = peer->procptr; 976 up = (struct mx4200unit *)pp->unitptr; 977 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); 983 984 if (sentence_type != PMVXG_D_TRECOVOUT) 985 return ("wrong rec-type"); 986 987 switch (time_mark_valid) { 988 case 'T': 989 valid = 1; 990 break; 991 case 'F': 992 valid = 0; 993 break; 994 default: 995 return ("bad pulse-valid"); 996 } 997 998 switch (time_sync) { 999 case 'G': 1000 return ("synchronized to GPS; should be UTC"); 1001 case 'U': 1002 break; /* UTC -> ok */ 1003 default: 1004 return ("not synchronized to UTC"); 1005 } 1006 1007 /* 1008 * Check for insane time (allow for possible leap seconds) 1009 */ 1010 if (second > 60 || minute > 59 || hour > 23 || 1011 second < 0 || minute < 0 || hour < 0) { 1012 mx4200_debug(peer, 1013 "mx4200_parse_t: bad time %02d:%02d:%02d", 1014 hour, minute, second); 1015 if (leapsec != 0) 1016 mx4200_debug(peer, " (leap %+d\n)", leapsec); 1017 mx4200_debug(peer, "\n"); 1018 refclock_report(peer, CEVNT_BADTIME); 1019 return ("bad time"); 1020 } 1021 if ( second == 60 ) { 1022 msyslog(LOG_DEBUG, 1023 "mx4200: leap second! %02d:%02d:%02d", 1024 hour, minute, second); 1025 } 1026 1027 /* 1028 * Check for insane date 1029 * (Certainly can't be any year before this code was last altered!) 1030 */ 1031 if (day_of_month > 31 || month > 12 || 1032 day_of_month < 1 || month < 1 || year < YEAR_RIGHT_NOW) { 1033 mx4200_debug(peer, 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"); 1038 } 1039 1040 /* 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. 1046 */ 1047 second--; 1048 if (second < 0) { 1049 second = 59; 1050 minute--; 1051 if (minute < 0) { 1052 minute = 59; 1053 hour--; 1054 if (hour < 0) { 1055 hour = 23; 1056 day_of_month--; 1057 if (day_of_month < 1) { 1058 return ("sorry, month boundary"); 1059 } 1060 } 1061 } 1062 } 1063 1064 /* 1065 * Calculate Julian date 1066 */ 1067 if (!(day_of_year = mx4200_jday(year, month, day_of_month))) { 1068 mx4200_debug(peer, 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"); 1073 } 1074 1075 /* 1076 * Setup leap second indicator 1077 */ 1078 switch (leapsec) { 1079 case 0: 1080 pp->leap = LEAP_NOWARNING; 1081 break; 1082 case 1: 1083 pp->leap = LEAP_ADDSECOND; 1084 break; 1085 case -1: 1086 pp->leap = LEAP_DELSECOND; 1087 break; 1088 default: 1089 pp->leap = LEAP_NOTINSYNC; 1090 } 1091 1092 /* 1093 * Any change to the leap second warning status? 1094 */ 1095 if (leapsec != up->last_leap ) { 1096 msyslog(LOG_DEBUG, 1097 "mx4200: leap second warning: %d to %d (%d)", 1098 up->last_leap, leapsec, pp->leap); 1099 } 1100 up->last_leap = leapsec; 1101 1102 /* 1103 * Copy time data for billboard monitoring. 1104 */ 1105 1106 pp->year = year; 1107 pp->day = day_of_year; 1108 pp->hour = hour; 1109 pp->minute = minute; 1110 pp->second = second; 1111 pp->msec = 0; 1112 pp->usec = 0; 1113 1114 /* 1115 * Toss if sentence is marked invalid 1116 */ 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"); 1121 } 1122 1123 return (NULL); 1124 } 1125 1126 /* 1127 * Calculate the checksum 1128 */ 1129 static u_char 1130 mx4200_cksum( 1131 register char *cp, 1132 register int n 1133 ) 1134 { 1135 register u_char ck; 1136 1137 for (ck = 0; n-- > 0; cp++) 1138 ck ^= *cp; 1139 return (ck); 1140 } 1141 1142 /* 1143 * Tables to compute the day of year. Viva la leap. 1144 */ 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}; 1147 1148 /* 1149 * Calculate the the Julian Day 1150 */ 1151 static int 1152 mx4200_jday( 1153 int year, 1154 int month, 1155 int day_of_month 1156 ) 1157 { 1158 register int day, i; 1159 int leap_year; 1160 1161 /* 1162 * Is this a leap year ? 1163 */ 1164 if (year % 4) { 1165 leap_year = 0; /* FALSE */ 1166 } else { 1167 if (year % 100) { 1168 leap_year = 1; /* TRUE */ 1169 } else { 1170 if (year % 400) { 1171 leap_year = 0; /* FALSE */ 1172 } else { 1173 leap_year = 1; /* TRUE */ 1174 } 1175 } 1176 } 1177 1178 /* 1179 * Calculate the Julian Date 1180 */ 1181 day = day_of_month; 1182 1183 if (leap_year) { 1184 /* a leap year */ 1185 if (day > day2tab[month - 1]) { 1186 return (0); 1187 } 1188 for (i = 0; i < month - 1; i++) 1189 day += day2tab[i]; 1190 } else { 1191 /* not a leap year */ 1192 if (day > day1tab[month - 1]) { 1193 return (0); 1194 } 1195 for (i = 0; i < month - 1; i++) 1196 day += day1tab[i]; 1197 } 1198 return (day); 1199 } 1200 1201 /* 1202 * Parse a mx4200 position/height/velocity sentence. 1203 * 1204 * A typical message looks like this. Checksum has already been stripped. 1205 * 1206 * $PMVXG,021,SSSSSS.SS,DDMM.MMMM,N,DDDMM.MMMM,E,HHHHH.H,GGGG.G,EEEE.E,WWWW.W,MM 1207 * 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 1215 * applications.* 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 1228 * 2 = 2-D position 1229 * 3 = 3-D position 1230 * 4 = 2-D differential position 1231 * 5 = 3-D differential position 1232 * 6 = Static 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 1244 */ 1245 static char * 1246 mx4200_parse_p( 1247 struct peer *peer 1248 ) 1249 { 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; 1255 1256 pp = peer->procptr; 1257 up = (struct mx4200unit *)pp->unitptr; 1258 1259 /* Should never happen! */ 1260 if (up->moving) return ("mobile platform - no pos!"); 1261 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); 1265 1266 /* Sentence type */ 1267 if (sentence_type != PMVXG_D_PHV) 1268 return ("wrong rec-type"); 1269 1270 /* 1271 * return if not navigating 1272 */ 1273 if (mode > 10) 1274 return ("not navigating"); 1275 if (mode != 3 && mode != 5) 1276 return ("not navigating in 3D"); 1277 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; 1281 lat *= 0.01; 1282 lat = ((int)lat) + (((lat - (int)lat)) * 1.6666666666666666); 1283 1284 /* North/South */ 1285 switch (north_south) { 1286 case 'N': 1287 break; 1288 case 'S': 1289 lat *= -1.0; 1290 break; 1291 default: 1292 return ("invalid north/south indicator"); 1293 } 1294 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; 1298 lon *= 0.01; 1299 lon = ((int)lon) + (((lon - (int)lon)) * 1.6666666666666666); 1300 1301 /* East/West */ 1302 switch (east_west) { 1303 case 'E': 1304 break; 1305 case 'W': 1306 lon *= -1.0; 1307 break; 1308 default: 1309 return ("invalid east/west indicator"); 1310 } 1311 1312 /* 1313 * Normalize longitude to near 0 degrees. 1314 * Assume all data are clustered around first reading. 1315 */ 1316 if (up->central_meridian == NOT_INITIALIZED) { 1317 up->central_meridian = lon; 1318 mx4200_debug(peer, 1319 "mx4200_receive: central meridian = %.9f \n", 1320 up->central_meridian); 1321 } 1322 lon -= up->central_meridian; 1323 if (lon < -180.0) lon += 360.0; 1324 if (lon > 180.0) lon -= 360.0; 1325 1326 /* 1327 * Calculate running weighted averages 1328 */ 1329 weight = 1. / up->edop; 1330 weight *= weight; 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; 1334 1335 weight = 1. / up->ndop; 1336 weight *= weight; 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; 1340 1341 weight = 1. / up->vdop; 1342 weight *= weight; 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; 1346 1347 mx4200_debug(peer, 1348 "mx4200_receive: position rdg %.9f %.9f %.4f (CM=%.9f)\n", 1349 lat, lon, alt, up->central_meridian); 1350 1351 return (NULL); 1352 } 1353 1354 /* 1355 * Parse a mx4200 DOP sentence. 1356 * 1357 * A typical message looks like this. Checksum has already been stripped. 1358 * 1359 * $PMVXG,022,SSSSSS.SSEE.E,NN.N,VV.V,XX,XX,XX,XX,XX,XX 1360 * 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) 1368 * 2 EDOP (east DOP) 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) 1383 */ 1384 static char * 1385 mx4200_parse_d( 1386 struct peer *peer 1387 ) 1388 { 1389 struct refclockproc *pp; 1390 struct mx4200unit *up; 1391 int sentence_type; 1392 double mtime, edop, ndop, vdop; 1393 1394 pp = peer->procptr; 1395 up = (struct mx4200unit *)pp->unitptr; 1396 1397 /* Should never happen! */ 1398 if (up->moving) return ("mobile platform - no dop!"); 1399 1400 sscanf ( pp->a_lastcode, "$PMVXG,%d,%lf,%lf,%lf,%lf", 1401 &sentence_type, &mtime, &edop, &ndop, &vdop); 1402 1403 /* Sentence type */ 1404 if (sentence_type != PMVXG_D_DOPS) 1405 return ("wrong rec-type"); 1406 1407 /* Update values */ 1408 if (edop <= 0.0 || ndop <= 0.0 || vdop <= 0.0) 1409 return ("nonpositive dop"); 1410 up->edop = edop; 1411 up->ndop = ndop; 1412 up->vdop = vdop; 1413 1414 return (NULL); 1415 } 1416 1417 /* 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) 1423 * 1424 * A typical message looks like this. Checksum has already been stripped. 1425 * 1426 * $PMVXG,000,XXX,XX,X,HHMM,X 1427 * 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 1439 * NAV = Navigation 1440 * STS = Search the Sky (no almanac available) 1441 * TRK = Tracking 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 1445 * (hours, minutes) 1446 * 5 Initialization status: 1447 * 0 = Waiting for initialization parameters 1448 * 1 = Initialization completed 1449 * 1450 * A typical message looks like this. Checksum has already been stripped. 1451 * 1452 * $PMVXG,004,C,R,D,H.HH,V.VV,TT,HHHH,VVVV,T 1453 * 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 1464 * available. 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 1470 * available. 1471 * 2 Altitude Reference: (always 0 for MX4200) 1472 * 0 = Ellipsoid 1473 * 1 = Geoid (MSL) 1474 * 3 Differential Navigation Control: 1475 * 0 = Disabled 1476 * 1 = Enabled 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) 1480 * 7 HDOP Limit 1481 * 8 VDOP Limit 1482 * 9 Time Output Mode: 1483 * U = UTC 1484 * L = Local time 1485 * 10 Local Time Offset (minutes) (absent on MX4200) 1486 * 1487 * A typical message looks like this. Checksum has already been stripped. 1488 * 1489 * $PMVXG,030,NNNN,FFF 1490 * 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 1499 * 1500 * A typical message looks like this. Checksum has already been stripped. 1501 * 1502 * $PMVXG,523,M,S,M,EEEE,BBBBBB,C,R 1503 * 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: 1516 * U = UTC time 1517 * G = GPS time 1518 * 3 Time Mark Mode: 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 1528 * Control port 1529 * 2 = Output the time recovery message (record 830) to 1530 * Equipment port 1531 * 7 Reserved 1532 * 8 Position Known PRN (absent on MX 4200) 1533 * 1534 */ 1535 static char * 1536 mx4200_parse_s( 1537 struct peer *peer 1538 ) 1539 { 1540 struct refclockproc *pp; 1541 struct mx4200unit *up; 1542 int sentence_type; 1543 1544 pp = peer->procptr; 1545 up = (struct mx4200unit *)pp->unitptr; 1546 1547 sscanf ( pp->a_lastcode, "$PMVXG,%d", &sentence_type); 1548 1549 /* Sentence type */ 1550 switch (sentence_type) { 1551 1552 case PMVXG_D_STATUS: 1553 msyslog(LOG_DEBUG, 1554 "mx4200: status: %s", pp->a_lastcode); 1555 break; 1556 case PMVXG_D_MODEDATA: 1557 msyslog(LOG_DEBUG, 1558 "mx4200: mode data: %s", pp->a_lastcode); 1559 break; 1560 case PMVXG_D_SOFTCONF: 1561 msyslog(LOG_DEBUG, 1562 "mx4200: firmware configuration: %s", pp->a_lastcode); 1563 break; 1564 case PMVXG_D_TRECOVUSEAGE: 1565 msyslog(LOG_DEBUG, 1566 "mx4200: time recovery parms: %s", pp->a_lastcode); 1567 break; 1568 default: 1569 return ("wrong rec-type"); 1570 } 1571 1572 return (NULL); 1573 } 1574 1575 /* 1576 * Process a PPS signal, returning a timestamp. 1577 */ 1578 static int 1579 mx4200_pps( 1580 struct peer *peer 1581 ) 1582 { 1583 int temp_serial; 1584 struct refclockproc *pp; 1585 struct mx4200unit *up; 1586 1587 int request; 1588 #ifdef HAVE_CIOGETEV 1589 request = CIOGETEV; 1590 #endif 1591 #ifdef HAVE_TIOCGPPSEV 1592 request = TIOCGPPSEV; 1593 #endif 1594 1595 pp = peer->procptr; 1596 up = (struct mx4200unit *)pp->unitptr; 1597 1598 /* 1599 * Grab the timestamp of the PPS signal. 1600 */ 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 */ 1604 mx4200_debug(peer, 1605 "mx4200_pps: CIOGETEV/TIOCGPPSEV: serial=%d, fdpps=%d, %s\n", 1606 up->ppsev.serial, fdpps, strerror(errno)); 1607 refclock_report(peer, CEVNT_FAULT); 1608 return(1); 1609 } 1610 if (temp_serial == up->ppsev.serial) { 1611 mx4200_debug(peer, 1612 "mx4200_pps: ppsev serial not incrementing: %d\n", 1613 up->ppsev.serial); 1614 refclock_report(peer, CEVNT_FAULT); 1615 return(1); 1616 } 1617 1618 /* 1619 * Check pps serial number against last one 1620 */ 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"); 1624 else 1625 mx4200_debug(peer, "mx4200_pps: missed %d pps events\n", 1626 up->ppsev.serial - up->lastserial - 1); 1627 refclock_report(peer, CEVNT_FAULT); 1628 } 1629 up->lastserial = up->ppsev.serial; 1630 1631 /* 1632 * Return the timestamp in pp->lastrec 1633 */ 1634 up->ppsev.tv.tv_sec += (u_int32) JAN_1970; 1635 TVTOTS(&up->ppsev.tv,&pp->lastrec); 1636 1637 return(0); 1638 } 1639 1640 /* 1641 * mx4200_debug - print debug messages 1642 */ 1643 #if defined(__STDC__) 1644 static void 1645 mx4200_debug(struct peer *peer, char *fmt, ...) 1646 #else 1647 static void 1648 mx4200_debug(peer, fmt, va_alist) 1649 struct peer *peer; 1650 char *fmt; 1651 #endif /* __STDC__ */ 1652 { 1653 va_list ap; 1654 struct refclockproc *pp; 1655 struct mx4200unit *up; 1656 1657 if (debug) { 1658 1659 #if defined(__STDC__) 1660 va_start(ap, fmt); 1661 #else 1662 va_start(ap); 1663 #endif /* __STDC__ */ 1664 1665 pp = peer->procptr; 1666 up = (struct mx4200unit *)pp->unitptr; 1667 1668 1669 /* 1670 * Print debug message to stdout 1671 * In the future, we may want to get get more creative... 1672 */ 1673 vprintf(fmt, ap); 1674 1675 va_end(ap); 1676 } 1677 } 1678 1679 /* 1680 * Send a character string to the receiver. Checksum is appended here. 1681 */ 1682 #if defined(__STDC__) 1683 static void 1684 mx4200_send(struct peer *peer, char *fmt, ...) 1685 #else 1686 static void 1687 mx4200_send(peer, fmt, va_alist) 1688 struct peer *peer; 1689 char *fmt; 1690 va_dcl 1691 #endif /* __STDC__ */ 1692 { 1693 struct refclockproc *pp; 1694 struct mx4200unit *up; 1695 1696 register char *cp; 1697 register int n, m; 1698 va_list ap; 1699 char buf[1024]; 1700 u_char ck; 1701 1702 #if defined(__STDC__) 1703 va_start(ap, fmt); 1704 #else 1705 va_start(ap); 1706 #endif /* __STDC__ */ 1707 1708 pp = peer->procptr; 1709 up = (struct mx4200unit *)pp->unitptr; 1710 1711 cp = buf; 1712 *cp++ = '$'; 1713 #ifdef notdef 1714 /* BSD is rational */ 1715 n = vsnprintf(cp, sizeof(buf) - 1, fmt, ap); 1716 #else 1717 /* SunOS sucks */ 1718 (void)vsprintf(cp, fmt, ap); 1719 n = strlen(cp); 1720 #endif /* notdef */ 1721 ck = mx4200_cksum(cp, n); 1722 cp += n; 1723 ++n; 1724 #ifdef notdef 1725 /* BSD is rational */ 1726 n += snprintf(cp, sizeof(buf) - n - 5, "*%02X\r\n", ck); 1727 #else 1728 /* SunOS sucks */ 1729 sprintf(cp, "*%02X\r\n", ck); 1730 n += strlen(cp); 1731 #endif /* notdef */ 1732 1733 m = write(pp->io.fd, buf, (unsigned)n); 1734 if (m < 0) 1735 msyslog(LOG_ERR, "mx4200_send: write: %m (%s)", buf); 1736 mx4200_debug(peer, "mx4200_send: %d %s\n", m, buf); 1737 va_end(ap); 1738 } 1739 1740 #else 1741 int refclock_mx4200_bs; 1742 #endif /* REFCLOCK */ 1743