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(HAVE_PPSAPI) 51 52 #include "ntpd.h" 53 #include "ntp_io.h" 54 #include "ntp_refclock.h" 55 #include "ntp_unixtime.h" 56 #include "ntp_stdlib.h" 57 58 #include <stdio.h> 59 #include <ctype.h> 60 61 #include "mx4200.h" 62 63 #ifdef HAVE_SYS_TERMIOS_H 64 # include <sys/termios.h> 65 #endif 66 #ifdef HAVE_SYS_PPSCLOCK_H 67 # include <sys/ppsclock.h> 68 #endif 69 70 #ifndef HAVE_STRUCT_PPSCLOCKEV 71 struct ppsclockev { 72 # ifdef HAVE_STRUCT_TIMESPEC 73 struct timespec tv; 74 # else 75 struct timeval tv; 76 # endif 77 u_int serial; 78 }; 79 #endif /* ! HAVE_STRUCT_PPSCLOCKEV */ 80 81 #ifdef HAVE_TIMEPPS_H 82 # include <timepps.h> 83 #else 84 # ifdef HAVE_SYS_TIMEPPS_H 85 # include <sys/timepps.h> 86 # endif 87 #endif 88 89 /* 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. 96 * 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. 99 */ 100 101 /* 102 * Check this every time you edit the code! 103 */ 104 #define YEAR_LAST_MODIFIED 2000 105 106 /* 107 * GPS Definitions 108 */ 109 #define DEVICE "/dev/gps%d" /* device name and unit */ 110 #define SPEED232 B4800 /* baud */ 111 112 /* 113 * Radio interface parameters 114 */ 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) */ 119 120 #define SLEEPTIME 32 /* seconds to wait for reconfig to complete */ 121 122 /* 123 * Position Averaging. 124 */ 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 */ 128 129 /* 130 * MX4200 unit control structure. 131 */ 132 struct mx4200unit { 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 */ 148 pps_handle_t pps_h; 149 pps_params_t pps_p; 150 pps_info_t pps_i; 151 }; 152 153 static char pmvxg[] = "PMVXG"; 154 155 /* XXX should be somewhere else */ 156 #ifdef __GNUC__ 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) */ 162 #else 163 #ifndef __attribute__ 164 #define __attribute__(args) 165 #endif /* __attribute__ */ 166 #endif /* __GNUC__ */ 167 /* XXX end */ 168 169 /* 170 * Function prototypes 171 */ 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 *)); 176 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 *)); 182 #else 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 *)); 194 195 /* 196 * Transfer vector 197 */ 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 */ 206 }; 207 208 209 210 /* 211 * mx4200_start - open the devices and initialize data for processing 212 */ 213 static int 214 mx4200_start( 215 int unit, 216 struct peer *peer 217 ) 218 { 219 register struct mx4200unit *up; 220 struct refclockproc *pp; 221 int fd; 222 char gpsdev[20]; 223 224 /* 225 * Open serial port 226 */ 227 (void)sprintf(gpsdev, DEVICE, unit); 228 if (!(fd = refclock_open(gpsdev, SPEED232, LDISC_PPS))) { 229 return (0); 230 } 231 232 /* 233 * Allocate unit structure 234 */ 235 if (!(up = (struct mx4200unit *) emalloc(sizeof(struct mx4200unit)))) { 236 perror("emalloc"); 237 (void) close(fd); 238 return (0); 239 } 240 memset((char *)up, 0, sizeof(struct mx4200unit)); 241 pp = peer->procptr; 242 pp->io.clock_recv = mx4200_receive; 243 pp->io.srcclock = (caddr_t)peer; 244 pp->io.datalen = 0; 245 pp->io.fd = fd; 246 if (!io_addclock(&pp->io)) { 247 (void) close(fd); 248 free(up); 249 return (0); 250 } 251 pp->unitptr = (caddr_t)up; 252 253 /* 254 * Initialize miscellaneous variables 255 */ 256 peer->precision = PRECISION; 257 pp->clockdesc = DESCRIPTION; 258 memcpy((char *)&pp->refid, REFID, 4); 259 260 /* Ensure the receiver is properly configured */ 261 return mx4200_config(peer); 262 } 263 264 265 /* 266 * mx4200_shutdown - shut down the clock 267 */ 268 static void 269 mx4200_shutdown( 270 int unit, 271 struct peer *peer 272 ) 273 { 274 register struct mx4200unit *up; 275 struct refclockproc *pp; 276 277 pp = peer->procptr; 278 up = (struct mx4200unit *)pp->unitptr; 279 io_closeclock(&pp->io); 280 free(up); 281 } 282 283 284 /* 285 * mx4200_config - Configure the receiver 286 */ 287 static int 288 mx4200_config( 289 struct peer *peer 290 ) 291 { 292 char tr_mode; 293 int add_mode; 294 register struct mx4200unit *up; 295 struct refclockproc *pp; 296 int mode; 297 298 pp = peer->procptr; 299 up = (struct mx4200unit *)pp->unitptr; 300 301 /* 302 * Initialize the unit variables 303 * 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. 307 */ 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"); 312 } else { 313 up->moving = 0; /* Static Installation */ 314 } 315 up->pollcnt = 2; 316 up->polled = 0; 317 up->known = 0; 318 up->avg_lat = 0.0; 319 up->avg_lon = 0.0; 320 up->avg_alt = 0.0; 321 up->central_meridian = NOT_INITIALIZED; 322 up->N_fixes = 0.0; 323 up->last_leap = 0; /* LEAP_NOWARNING */ 324 up->clamp_time = current_time + (AVGING_TIME * 60 * 60); 325 up->log_time = current_time + SLEEPTIME; 326 327 if (time_pps_create(pp->io.fd, &up->pps_h) < 0) { 328 perror("time_pps_create"); 329 msyslog(LOG_ERR, 330 "mx4200_config: time_pps_create failed: %m"); 331 return (0); 332 } 333 if (time_pps_getcap(up->pps_h, &mode) < 0) { 334 msyslog(LOG_ERR, 335 "mx4200_config: time_pps_getcap failed: %m"); 336 return (0); 337 } 338 339 if (time_pps_getparams(up->pps_h, &up->pps_p) < 0) { 340 msyslog(LOG_ERR, 341 "mx4200_config: time_pps_getparams failed: %m"); 342 return (0); 343 } 344 345 /* nb. only turn things on, if someone else has turned something 346 * on before we get here, leave it alone! 347 */ 348 349 up->pps_p.mode = PPS_CAPTUREASSERT | PPS_TSFMT_TSPEC; 350 up->pps_p.mode &= mode; /* only set what is legal */ 351 352 if (time_pps_setparams(up->pps_h, &up->pps_p) < 0) { 353 perror("time_pps_setparams"); 354 msyslog(LOG_ERR, 355 "mx4200_config: time_pps_setparams failed: %m"); 356 exit(1); 357 } 358 359 if (time_pps_kcbind(up->pps_h, PPS_KC_HARDPPS, PPS_CAPTUREASSERT, 360 PPS_TSFMT_TSPEC) < 0) { 361 perror("time_pps_kcbind"); 362 msyslog(LOG_ERR, 363 "mx4200_config: time_pps_kcbind failed: %m"); 364 exit(1); 365 } 366 367 368 /* 369 * "007" Control Port Configuration 370 * Zero the output list (do it twice to flush possible junk) 371 */ 372 mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg, 373 PMVXG_S_PORTCONF, 374 /* control port output block Label */ 375 1); /* clear current output control list (1=yes) */ 376 /* add/delete sentences from list */ 377 /* must be null */ 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); 384 385 /* 386 * Request software configuration so we can syslog the firmware version 387 */ 388 mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_SOFTCONF); 389 390 /* 391 * "001" Initialization/Mode Control, Part A 392 * Where ARE we? 393 */ 394 mx4200_send(peer, "%s,%03d,,,,,,,,,,", pmvxg, 395 PMVXG_S_INITMODEA); 396 /* day of month */ 397 /* month of year */ 398 /* year */ 399 /* gmt */ 400 /* latitude DDMM.MMMM */ 401 /* north/south */ 402 /* longitude DDDMM.MMMM */ 403 /* east/west */ 404 /* height */ 405 /* Altitude Reference 1=MSL */ 406 407 /* 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. 411 */ 412 mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d", 413 pmvxg, PMVXG_S_INITMODEB, 414 3, /* 2d/3d coast */ 415 /* reserved */ 416 0.1, /* hor accel fact as per Steve (m/s**2) */ 417 0.1, /* ver accel fact as per Steve (m/s**2) */ 418 10, /* vdop */ 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) */ 423 424 /* 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). 430 */ 431 432 if (pp->sloppyclockflag & CLK_FLAG2) 433 up->moving = 1; /* Receiver on mobile platform */ 434 else 435 up->moving = 0; /* Static Installation */ 436 437 up->pollcnt = 2; 438 if (up->moving) { 439 /* dynamic: solve for pos, alt, time, while moving */ 440 tr_mode = 'D'; 441 } else { 442 /* static: solve for pos, alt, time, while stationary */ 443 tr_mode = 'S'; 444 } 445 mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg, 446 PMVXG_S_TRECOVCONF, 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 */ 454 455 /* 456 * Output position information (to calculate fixed installation 457 * location) only if we are not moving 458 */ 459 if (up->moving) { 460 add_mode = 2; /* delete from list */ 461 } else { 462 add_mode = 1; /* add to list */ 463 } 464 465 466 /* 467 * "007" Control Port Configuration 468 * Output "021" position, height, velocity reports 469 */ 470 mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg, 471 PMVXG_S_PORTCONF, 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) */ 475 /* must be null */ 476 INTERVAL); /* sentence output rate (sec) */ 477 /* precision for position output */ 478 /* nmea version for cga & gll output */ 479 /* pass-through control */ 480 481 return (1); 482 } 483 484 /* 485 * mx4200_ref - Reconfigure unit as a reference station at a known position. 486 */ 487 static void 488 mx4200_ref( 489 struct peer *peer 490 ) 491 { 492 register struct mx4200unit *up; 493 struct refclockproc *pp; 494 double minute, lat, lon, alt; 495 char lats[16], lons[16]; 496 char nsc, ewc; 497 498 pp = peer->procptr; 499 up = (struct mx4200unit *)pp->unitptr; 500 501 /* Should never happen! */ 502 if (up->moving) return; 503 504 /* 505 * Set up to output status information in the near future 506 */ 507 up->log_time = current_time + SLEEPTIME; 508 509 /* 510 * "007" Control Port Configuration 511 * Stop outputting "021" position, height, velocity reports 512 */ 513 mx4200_send(peer, "%s,%03d,%03d,%d,%d,,,,,", pmvxg, 514 PMVXG_S_PORTCONF, 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) */ 518 /* must be null */ 519 /* sentence output rate (sec) */ 520 /* precision for position output */ 521 /* nmea version for cga & gll output */ 522 /* pass-through control */ 523 524 /* 525 * "001" Initialization/Mode Control, Part B 526 * Put receiver in fully-constrained 2d nav mode 527 */ 528 mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d", 529 pmvxg, PMVXG_S_INITMODEB, 530 2, /* 2d nav */ 531 /* reserved */ 532 0.1, /* hor accel fact as per Steve (m/s**2) */ 533 0.1, /* ver accel fact as per Steve (m/s**2) */ 534 10, /* vdop */ 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) */ 539 540 /* 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. 544 */ 545 mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg, 546 PMVXG_S_TRECOVCONF, 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 */ 554 555 /* 556 * "000" Initialization/Mode Control - Part A 557 * Fix to our averaged position. 558 */ 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; 563 } 564 565 if (up->avg_lat >= 0.0) { 566 lat = up->avg_lat; 567 nsc = 'N'; 568 } else { 569 lat = up->avg_lat * (-1.0); 570 nsc = 'S'; 571 } 572 if (up->avg_lon >= 0.0) { 573 lon = up->avg_lon; 574 ewc = 'E'; 575 } else { 576 lon = up->avg_lon * (-1.0); 577 ewc = 'W'; 578 } 579 alt = up->avg_alt; 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); 584 585 mx4200_send(peer, "%s,%03d,,,,,%s,%c,%s,%c,%.2f,%d", pmvxg, 586 PMVXG_S_INITMODEA, 587 /* day of month */ 588 /* month of year */ 589 /* year */ 590 /* gmt */ 591 lats, /* latitude DDMM.MMMM */ 592 nsc, /* north/south */ 593 lons, /* longitude DDDMM.MMMM */ 594 ewc, /* east/west */ 595 alt, /* Altitude */ 596 1); /* Altitude Reference (0=WGS84 ellipsoid, 1=MSL geoid)*/ 597 598 msyslog(LOG_DEBUG, 599 "mx4200: reconfig to fixed location: %s %c, %s %c, %.2f m", 600 lats, nsc, lons, ewc, alt ); 601 602 } 603 604 /* 605 * mx4200_poll - mx4200 watchdog routine 606 */ 607 static void 608 mx4200_poll( 609 int unit, 610 struct peer *peer 611 ) 612 { 613 register struct mx4200unit *up; 614 struct refclockproc *pp; 615 616 pp = peer->procptr; 617 up = (struct mx4200unit *)pp->unitptr; 618 619 /* 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. 623 */ 624 625 /* 626 * If we haven't had a response in a while, reset the receiver. 627 */ 628 if (up->pollcnt > 0) { 629 up->pollcnt--; 630 } else { 631 refclock_report(peer, CEVNT_TIMEOUT); 632 633 /* 634 * Request a "000" status message which should trigger a 635 * reconfig 636 */ 637 mx4200_send(peer, "%s,%03d", 638 "CDGPQ", /* query from CDU to GPS */ 639 PMVXG_D_STATUS); /* label of desired sentence */ 640 } 641 642 /* 643 * polled every 64 seconds. Ask mx4200_receive to hand in 644 * a timestamp. 645 */ 646 up->polled = 1; 647 pp->polls++; 648 649 /* 650 * Output receiver status information. 651 */ 652 if ((up->log_time > 0) && (current_time > up->log_time)) { 653 up->log_time = 0; 654 /* 655 * Output the following messages once, for debugging. 656 * "004" Mode Data 657 * "523" Time Recovery Parameters 658 */ 659 mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_MODEDATA); 660 mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_TRECOVUSEAGE); 661 } 662 } 663 664 static char char2hex[] = "0123456789ABCDEF"; 665 666 /* 667 * mx4200_receive - receive gps data 668 */ 669 static void 670 mx4200_receive( 671 struct recvbuf *rbufp 672 ) 673 { 674 register struct mx4200unit *up; 675 struct refclockproc *pp; 676 struct peer *peer; 677 char *cp; 678 int sentence_type; 679 u_char ck; 680 681 /* 682 * Initialize pointers and read the timecode and timestamp. 683 */ 684 peer = (struct peer *)rbufp->recv_srcclock; 685 pp = peer->procptr; 686 up = (struct mx4200unit *)pp->unitptr; 687 688 /* 689 * If operating mode has been changed, then reinitialize the receiver 690 * before doing anything else. 691 */ 692 if ((pp->sloppyclockflag & CLK_FLAG2) != 693 (up->sloppyclockflag & CLK_FLAG2)) { 694 up->sloppyclockflag = pp->sloppyclockflag; 695 mx4200_debug(peer, 696 "mx4200_receive: mode switch: reset receiver\n"); 697 mx4200_config(peer); 698 return; 699 } 700 up->sloppyclockflag = pp->sloppyclockflag; 701 702 /* 703 * Read clock output. Automatically handles STREAMS, CLKLDISC. 704 */ 705 pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec); 706 707 /* 708 * There is a case where <cr><lf> generates 2 timestamps. 709 */ 710 if (pp->lencode == 0) 711 return; 712 713 up->pollcnt = 2; 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); 718 719 /* 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) 723 * 724 * $PMVXG,XXX, ....................*CK<cr><lf> 725 * 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) 732 * XXX Sentence Type 733 * ...... Data 734 * * Checksum Field Delimiter (reserved char) 735 * CK Checksum 736 * <cr><lf> Carriage-Return/Line Feed (reserved chars) 737 * (End-of-Sentence Identifier) 738 * 739 * Reject if any important landmarks are missing. 740 */ 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); 745 return; 746 } 747 748 /* 749 * Check and discard the checksum 750 */ 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); 755 return; 756 } 757 *cp = '\0'; 758 759 /* 760 * Get the sentence type. 761 */ 762 sentence_type = 0; 763 if ((cp = strchr(pp->a_lastcode, ',')) == NULL) { 764 mx4200_debug(peer, "mx4200_receive: no sentence\n"); 765 refclock_report(peer, CEVNT_BADREPLY); 766 return; 767 } 768 cp++; 769 sentence_type = strtol(cp, &cp, 10); 770 771 /* 772 * Process the sentence according to its type. 773 */ 774 switch (sentence_type) { 775 776 /* 777 * "000" Status message 778 */ 779 case PMVXG_D_STATUS: 780 /* 781 * XXX 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. 788 */ 789 if ((cp = mx4200_parse_s(peer)) != NULL) { 790 mx4200_debug(peer, 791 "mx4200_receive: status: %s\n", cp); 792 } 793 mx4200_debug(peer, "mx4200_receive: reset receiver\n"); 794 mx4200_config(peer); 795 break; 796 797 /* 798 * "021" Position, Height, Velocity message, 799 * if we are still averaging our position 800 */ 801 case PMVXG_D_PHV: 802 if (!up->known) { 803 /* 804 * Parse the message, calculating our averaged position. 805 */ 806 if ((cp = mx4200_parse_p(peer)) != NULL) { 807 mx4200_debug(peer, "mx4200_receive: pos: %s\n", cp); 808 return; 809 } 810 mx4200_debug(peer, 811 "mx4200_receive: position avg %f %.9f %.9f %.4f\n", 812 up->N_fixes, up->avg_lat, up->avg_lon, up->avg_alt); 813 /* 814 * Reinitialize as a reference station 815 * if position is well known. 816 */ 817 if (current_time > up->clamp_time) { 818 up->known++; 819 mx4200_debug(peer, "mx4200_receive: reconfiguring!\n"); 820 mx4200_ref(peer); 821 } 822 } 823 break; 824 825 /* 826 * Print to the syslog: 827 * "004" Mode Data 828 * "030" Software Configuration 829 * "523" Time Recovery Parameters Currently in Use 830 */ 831 case PMVXG_D_MODEDATA: 832 case PMVXG_D_SOFTCONF: 833 case PMVXG_D_TRECOVUSEAGE: 834 835 if ((cp = mx4200_parse_s(peer)) != NULL) { 836 mx4200_debug(peer, 837 "mx4200_receive: multi-record: %s\n", cp); 838 } 839 break; 840 841 /* 842 * "830" Time Recovery Results message 843 */ 844 case PMVXG_D_TRECOVOUT: 845 846 /* 847 * Capture the last PPS signal. 848 * Precision timestamp is returned in pp->lastrec 849 */ 850 if (mx4200_pps(peer) != NULL) { 851 mx4200_debug(peer, "mx4200_receive: pps failure\n"); 852 refclock_report(peer, CEVNT_FAULT); 853 return; 854 } 855 856 857 /* 858 * Parse the time recovery message, and keep the info 859 * to print the pretty billboards. 860 */ 861 if ((cp = mx4200_parse_t(peer)) != NULL) { 862 mx4200_debug(peer, "mx4200_receive: time: %s\n", cp); 863 refclock_report(peer, CEVNT_BADREPLY); 864 return; 865 } 866 867 /* 868 * Add the new sample to a median filter. 869 */ 870 if (!refclock_process(pp)) { 871 mx4200_debug(peer,"mx4200_receive: offset: %.6f\n", 872 pp->offset); 873 refclock_report(peer, CEVNT_BADTIME); 874 return; 875 } 876 877 /* 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. 880 */ 881 if (!up->polled) 882 return; 883 884 /* 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. 890 */ 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 896 refclock_receive(peer); 897 898 /* 899 * We have succeeded in answering the poll. 900 * Turn off the flag and return 901 */ 902 up->polled = 0; 903 break; 904 905 /* 906 * Ignore all other sentence types 907 */ 908 default: 909 break; 910 911 } /* switch (sentence_type) */ 912 913 return; 914 } 915 916 917 /* 918 * Parse a mx4200 time recovery message. Returns a string if error. 919 * 920 * A typical message looks like this. Checksum has already been stripped. 921 * 922 * $PMVXG,830,T,YYYY,MM,DD,HH:MM:SS,U,S,FFFFFF,PPPPP,BBBBBB,LL 923 * 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 936 * 2 int Year: 1993- 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 occurence, 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. 954 * 955 */ 956 static char * 957 mx4200_parse_t( 958 struct peer *peer 959 ) 960 { 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; 968 969 pp = peer->procptr; 970 up = (struct mx4200unit *)pp->unitptr; 971 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); 978 979 if (sentence_type != PMVXG_D_TRECOVOUT) 980 return ("wrong rec-type"); 981 982 switch (time_mark_valid) { 983 case 'T': 984 valid = 1; 985 break; 986 case 'F': 987 valid = 0; 988 break; 989 default: 990 return ("bad pulse-valid"); 991 } 992 993 switch (time_sync) { 994 case 'G': 995 return ("synchronized to GPS; should be UTC"); 996 case 'U': 997 break; /* UTC -> ok */ 998 default: 999 return ("not synchronized to UTC"); 1000 } 1001 1002 /* 1003 * Check for insane time (allow for possible leap seconds) 1004 */ 1005 if (second > 60 || minute > 59 || hour > 23 || 1006 second < 0 || minute < 0 || hour < 0) { 1007 mx4200_debug(peer, 1008 "mx4200_parse_t: bad time %02d:%02d:%02d", 1009 hour, minute, second); 1010 if (leapsec != 0) 1011 mx4200_debug(peer, " (leap %+d\n)", leapsec); 1012 mx4200_debug(peer, "\n"); 1013 refclock_report(peer, CEVNT_BADTIME); 1014 return ("bad time"); 1015 } 1016 if ( second == 60 ) { 1017 msyslog(LOG_DEBUG, 1018 "mx4200: leap second! %02d:%02d:%02d", 1019 hour, minute, second); 1020 } 1021 1022 /* 1023 * Check for insane date 1024 * (Certainly can't be any year before this code was last altered!) 1025 */ 1026 if (day_of_month > 31 || month > 12 || 1027 day_of_month < 1 || month < 1 || year < YEAR_LAST_MODIFIED) { 1028 mx4200_debug(peer, 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"); 1033 } 1034 1035 /* 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. 1041 */ 1042 second--; 1043 if (second < 0) { 1044 second = 59; 1045 minute--; 1046 if (minute < 0) { 1047 minute = 59; 1048 hour--; 1049 if (hour < 0) { 1050 hour = 23; 1051 day_of_month--; 1052 if (day_of_month < 1) { 1053 return ("sorry, month boundary"); 1054 } 1055 } 1056 } 1057 } 1058 1059 /* 1060 * Calculate Julian date 1061 */ 1062 if (!(day_of_year = mx4200_jday(year, month, day_of_month))) { 1063 mx4200_debug(peer, 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"); 1068 } 1069 1070 /* 1071 * Setup leap second indicator 1072 */ 1073 switch (leapsec) { 1074 case 0: 1075 pp->leap = LEAP_NOWARNING; 1076 break; 1077 case 1: 1078 pp->leap = LEAP_ADDSECOND; 1079 break; 1080 case -1: 1081 pp->leap = LEAP_DELSECOND; 1082 break; 1083 default: 1084 pp->leap = LEAP_NOTINSYNC; 1085 } 1086 1087 /* 1088 * Any change to the leap second warning status? 1089 */ 1090 if (leapsec != up->last_leap ) { 1091 msyslog(LOG_DEBUG, 1092 "mx4200: leap second warning: %d to %d (%d)", 1093 up->last_leap, leapsec, pp->leap); 1094 } 1095 up->last_leap = leapsec; 1096 1097 /* 1098 * Copy time data for billboard monitoring. 1099 */ 1100 1101 pp->year = year; 1102 pp->day = day_of_year; 1103 pp->hour = hour; 1104 pp->minute = minute; 1105 pp->second = second; 1106 pp->msec = 0; 1107 pp->usec = 0; 1108 1109 /* 1110 * Toss if sentence is marked invalid 1111 */ 1112 if (!valid || pp->leap == LEAP_NOTINSYNC) { 1113 mx4200_debug(peer, "mx4200_parse_t: time mark not valid\n"); 1114 refclock_report(peer, CEVNT_BADTIME); 1115 return ("pulse invalid"); 1116 } 1117 1118 return (NULL); 1119 } 1120 1121 /* 1122 * Calculate the checksum 1123 */ 1124 static u_char 1125 mx4200_cksum( 1126 register char *cp, 1127 register int n 1128 ) 1129 { 1130 register u_char ck; 1131 1132 for (ck = 0; n-- > 0; cp++) 1133 ck ^= *cp; 1134 return (ck); 1135 } 1136 1137 /* 1138 * Tables to compute the day of year. Viva la leap. 1139 */ 1140 static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1141 static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1142 1143 /* 1144 * Calculate the the Julian Day 1145 */ 1146 static int 1147 mx4200_jday( 1148 int year, 1149 int month, 1150 int day_of_month 1151 ) 1152 { 1153 register int day, i; 1154 int leap_year; 1155 1156 /* 1157 * Is this a leap year ? 1158 */ 1159 if (year % 4) { 1160 leap_year = 0; /* FALSE */ 1161 } else { 1162 if (year % 100) { 1163 leap_year = 1; /* TRUE */ 1164 } else { 1165 if (year % 400) { 1166 leap_year = 0; /* FALSE */ 1167 } else { 1168 leap_year = 1; /* TRUE */ 1169 } 1170 } 1171 } 1172 1173 /* 1174 * Calculate the Julian Date 1175 */ 1176 day = day_of_month; 1177 1178 if (leap_year) { 1179 /* a leap year */ 1180 if (day > day2tab[month - 1]) { 1181 return (0); 1182 } 1183 for (i = 0; i < month - 1; i++) 1184 day += day2tab[i]; 1185 } else { 1186 /* not a leap year */ 1187 if (day > day1tab[month - 1]) { 1188 return (0); 1189 } 1190 for (i = 0; i < month - 1; i++) 1191 day += day1tab[i]; 1192 } 1193 return (day); 1194 } 1195 1196 /* 1197 * Parse a mx4200 position/height/velocity sentence. 1198 * 1199 * A typical message looks like this. Checksum has already been stripped. 1200 * 1201 * $PMVXG,021,SSSSSS.SS,DDMM.MMMM,N,DDDMM.MMMM,E,HHHHH.H,GGGG.G,EEEE.E,WWWW.W,MM 1202 * 1203 * Field Field Contents 1204 * ----- -------------- 1205 * Block Label: $PMVXG 1206 * Sentence Type: 021=Position, Height Velocity Data 1207 * This sentence gives the receiver position, height, 1208 * navigation mode, and velocity north/east. 1209 * *This sentence is intended for post-analysis 1210 * applications.* 1211 * 1 float UTC measurement time (seconds into week) 1212 * 2 float WGS-84 Lattitude (degrees, minutes) 1213 * 3 char N=North, S=South 1214 * 4 float WGS-84 Longitude (degrees, minutes) 1215 * 5 char E=East, W=West 1216 * 6 float Altitude (meters above mean sea level) 1217 * 7 float Geoidal height (meters) 1218 * 8 float East velocity (m/sec) 1219 * 9 float West Velocity (m/sec) 1220 * 10 int Navigation Mode 1221 * Mode if navigating: 1222 * 1 = Position from remote device 1223 * 2 = 2-D position 1224 * 3 = 3-D position 1225 * 4 = 2-D differential position 1226 * 5 = 3-D differential position 1227 * 6 = Static 1228 * 8 = Position known -- reference station 1229 * 9 = Position known -- Navigator 1230 * Mode if not navigating: 1231 * 51 = Too few satellites 1232 * 52 = DOPs too large 1233 * 53 = Position STD too large 1234 * 54 = Velocity STD too large 1235 * 55 = Too many iterations for velocity 1236 * 56 = Too many iterations for position 1237 * 57 = 3 sat startup failed 1238 * 58 = Command abort 1239 */ 1240 static char * 1241 mx4200_parse_p( 1242 struct peer *peer 1243 ) 1244 { 1245 struct refclockproc *pp; 1246 struct mx4200unit *up; 1247 int sentence_type, mode; 1248 double mtime, lat, lon, alt, geoid, vele, veln; 1249 char north_south, east_west; 1250 1251 pp = peer->procptr; 1252 up = (struct mx4200unit *)pp->unitptr; 1253 1254 /* Should never happen! */ 1255 if (up->moving) return ("mobile platform - no pos!"); 1256 1257 sscanf ( pp->a_lastcode, 1258 "$PMVXG,%d,%lf,%lf,%c,%lf,%c,%lf,%lf,%lf,%lf,%d", 1259 &sentence_type, &mtime, &lat, &north_south, &lon, &east_west, 1260 &alt, &geoid, &vele, &veln, &mode); 1261 1262 /* Sentence type */ 1263 if (sentence_type != PMVXG_D_PHV) 1264 return ("wrong rec-type"); 1265 1266 /* 1267 * return if not navigating 1268 */ 1269 if (mode > 10) 1270 return ("not navigating"); 1271 if (mode != 3 && mode != 5) 1272 return ("not navigating in 3D"); 1273 1274 /* Latitude (always +ve) and convert DDMM.MMMM to decimal */ 1275 if (lat < 0.0) return ("negative latitude"); 1276 if (lat > 9000.0) lat = 9000.0; 1277 lat *= 0.01; 1278 lat = ((int)lat) + (((lat - (int)lat)) * 1.6666666666666666); 1279 1280 /* North/South */ 1281 switch (north_south) { 1282 case 'N': 1283 break; 1284 case 'S': 1285 lat *= -1.0; 1286 break; 1287 default: 1288 return ("invalid north/south indicator"); 1289 } 1290 1291 /* Longitude (always +ve) and convert DDDMM.MMMM to decimal */ 1292 if (lon < 0.0) return ("negative longitude"); 1293 if (lon > 180.0) lon = 180.0; 1294 lon *= 0.01; 1295 lon = ((int)lon) + (((lon - (int)lon)) * 1.6666666666666666); 1296 1297 /* East/West */ 1298 switch (east_west) { 1299 case 'E': 1300 break; 1301 case 'W': 1302 lon *= -1.0; 1303 break; 1304 default: 1305 return ("invalid east/west indicator"); 1306 } 1307 1308 /* 1309 * Normalize longitude to near 0 degrees. 1310 * Assume all data are clustered around first reading. 1311 */ 1312 if (up->central_meridian == NOT_INITIALIZED) { 1313 up->central_meridian = lon; 1314 mx4200_debug(peer, 1315 "mx4200_receive: central meridian = %.9f \n", 1316 up->central_meridian); 1317 } 1318 lon -= up->central_meridian; 1319 if (lon < -180.0) lon += 360.0; 1320 if (lon > 180.0) lon -= 360.0; 1321 1322 /* 1323 * Calculate running averages 1324 */ 1325 1326 up->avg_lon = (up->N_fixes * up->avg_lon) + lon; 1327 up->avg_lat = (up->N_fixes * up->avg_lat) + lat; 1328 up->avg_alt = (up->N_fixes * up->avg_alt) + alt; 1329 1330 up->N_fixes += 1.0; 1331 1332 up->avg_lon /= up->N_fixes; 1333 up->avg_lat /= up->N_fixes; 1334 up->avg_alt /= up->N_fixes; 1335 1336 mx4200_debug(peer, 1337 "mx4200_receive: position rdg %.0f: %.9f %.9f %.4f (CM=%.9f)\n", 1338 up->N_fixes, lat, lon, alt, up->central_meridian); 1339 1340 return (NULL); 1341 } 1342 1343 /* 1344 * Parse a mx4200 Status sentence 1345 * Parse a mx4200 Mode Data sentence 1346 * Parse a mx4200 Software Configuration sentence 1347 * Parse a mx4200 Time Recovery Parameters Currently in Use sentence 1348 * (used only for logging raw strings) 1349 * 1350 * A typical message looks like this. Checksum has already been stripped. 1351 * 1352 * $PMVXG,000,XXX,XX,X,HHMM,X 1353 * 1354 * Field Field Contents 1355 * ----- -------------- 1356 * Block Label: $PMVXG 1357 * Sentence Type: 000=Status. 1358 * Returns status of the receiver to the controller. 1359 * 1 Current Receiver Status: 1360 * ACQ = Satellite re-acquisition 1361 * ALT = Constellation selection 1362 * COR = Providing corrections (for reference stations only) 1363 * IAC = Initial acquisition 1364 * IDL = Idle, no satellites 1365 * NAV = Navigation 1366 * STS = Search the Sky (no almanac available) 1367 * TRK = Tracking 1368 * 2 Number of satellites that should be visible 1369 * 3 Number of satellites being tracked 1370 * 4 Time since last navigation status if not currently navigating 1371 * (hours, minutes) 1372 * 5 Initialization status: 1373 * 0 = Waiting for initialization parameters 1374 * 1 = Initialization completed 1375 * 1376 * A typical message looks like this. Checksum has already been stripped. 1377 * 1378 * $PMVXG,004,C,R,D,H.HH,V.VV,TT,HHHH,VVVV,T 1379 * 1380 * Field Field Contents 1381 * ----- -------------- 1382 * Block Label: $PMVXG 1383 * Sentence Type: 004=Software Configuration. 1384 * Defines the navigation mode and criteria for 1385 * acceptable navigation for the receiver. 1386 * 1 Constrain Altitude Mode: 1387 * 0 = Auto. Constrain altitude (2-D solution) and use 1388 * manual altitude input when 3 sats avalable. Do 1389 * not constrain altitude (3-D solution) when 4 sats 1390 * available. 1391 * 1 = Always constrain altitude (2-D solution). 1392 * 2 = Never constrain altitude (3-D solution). 1393 * 3 = Coast. Constrain altitude (2-D solution) and use 1394 * last GPS altitude calculation when 3 sats avalable. 1395 * Do not constrain altitude (3-D solution) when 4 sats 1396 * available. 1397 * 2 Altitude Reference: (always 0 for MX4200) 1398 * 0 = Ellipsoid 1399 * 1 = Geoid (MSL) 1400 * 3 Differential Navigation Control: 1401 * 0 = Disabled 1402 * 1 = Enabled 1403 * 4 Horizontal Acceleration Constant (m/sec**2) 1404 * 5 Vertical Acceleration Constant (m/sec**2) (0 for MX4200) 1405 * 6 Tracking Elevation Limit (degrees) 1406 * 7 HDOP Limit 1407 * 8 VDOP Limit 1408 * 9 Time Output Mode: 1409 * U = UTC 1410 * L = Local time 1411 * 10 Local Time Offset (minutes) (absent on MX4200) 1412 * 1413 * A typical message looks like this. Checksum has already been stripped. 1414 * 1415 * $PMVXG,030,NNNN,FFF 1416 * 1417 * Field Field Contents 1418 * ----- -------------- 1419 * Block Label: $PMVXG 1420 * Sentence Type: 030=Software Configuration. 1421 * This sentence contains the navigation processor 1422 * and baseband firmware version numbers. 1423 * 1 Nav Processor Version Number 1424 * 2 Baseband Firmware Version Number 1425 * 1426 * A typical message looks like this. Checksum has already been stripped. 1427 * 1428 * $PMVXG,523,M,S,M,EEEE,BBBBBB,C,R 1429 * 1430 * Field Field Contents 1431 * ----- -------------- 1432 * Block Label: $PMVXG 1433 * Sentence Type: 523=Time Recovery Parameters Currently in Use. 1434 * This sentence contains the configuration of the 1435 * time recovery feature of the receiver. 1436 * 1 Time Recovery Mode: 1437 * D = Dynamic; solve for position and time while moving 1438 * S = Static; solve for position and time while stationary 1439 * K = Known position input, solve for time only 1440 * N = No time recovery 1441 * 2 Time Synchronization: 1442 * U = UTC time 1443 * G = GPS time 1444 * 3 Time Mark Mode: 1445 * A = Always output a time pulse 1446 * V = Only output time pulse if time is valid (as determined 1447 * by Maximum Time Error) 1448 * 4 Maximum Time Error - the maximum error (in nanoseconds) for 1449 * which a time mark will be considered valid. 1450 * 5 User Time Bias - external bias in nanoseconds 1451 * 6 Time Message Control: 1452 * 0 = Do not output the time recovery message 1453 * 1 = Output the time recovery message (record 830) to 1454 * Control port 1455 * 2 = Output the time recovery message (record 830) to 1456 * Equipment port 1457 * 7 Reserved 1458 * 8 Position Known PRN (absent on MX 4200) 1459 * 1460 */ 1461 static char * 1462 mx4200_parse_s( 1463 struct peer *peer 1464 ) 1465 { 1466 struct refclockproc *pp; 1467 struct mx4200unit *up; 1468 int sentence_type; 1469 1470 pp = peer->procptr; 1471 up = (struct mx4200unit *)pp->unitptr; 1472 1473 sscanf ( pp->a_lastcode, "$PMVXG,%d", &sentence_type); 1474 1475 /* Sentence type */ 1476 switch (sentence_type) { 1477 1478 case PMVXG_D_STATUS: 1479 msyslog(LOG_DEBUG, 1480 "mx4200: status: %s", pp->a_lastcode); 1481 break; 1482 case PMVXG_D_MODEDATA: 1483 msyslog(LOG_DEBUG, 1484 "mx4200: mode data: %s", pp->a_lastcode); 1485 break; 1486 case PMVXG_D_SOFTCONF: 1487 msyslog(LOG_DEBUG, 1488 "mx4200: firmware configuration: %s", pp->a_lastcode); 1489 break; 1490 case PMVXG_D_TRECOVUSEAGE: 1491 msyslog(LOG_DEBUG, 1492 "mx4200: time recovery parms: %s", pp->a_lastcode); 1493 break; 1494 default: 1495 return ("wrong rec-type"); 1496 } 1497 1498 return (NULL); 1499 } 1500 1501 /* 1502 * Process a PPS signal, placing a timestamp in pp->lastrec. 1503 */ 1504 static int 1505 mx4200_pps( 1506 struct peer *peer 1507 ) 1508 { 1509 int temp_serial; 1510 struct refclockproc *pp; 1511 struct mx4200unit *up; 1512 1513 struct timespec timeout; 1514 1515 pp = peer->procptr; 1516 up = (struct mx4200unit *)pp->unitptr; 1517 1518 /* 1519 * Grab the timestamp of the PPS signal. 1520 */ 1521 temp_serial = up->pps_i.assert_sequence; 1522 timeout.tv_sec = 0; 1523 timeout.tv_nsec = 0; 1524 if (time_pps_fetch(up->pps_h, PPS_TSFMT_TSPEC, &(up->pps_i), 1525 &timeout) < 0) { 1526 mx4200_debug(peer, 1527 "mx4200_pps: time_pps_fetch: serial=%d, %s\n", 1528 up->pps_i.assert_sequence, strerror(errno)); 1529 refclock_report(peer, CEVNT_FAULT); 1530 return(1); 1531 } 1532 if (temp_serial == up->pps_i.assert_sequence) { 1533 mx4200_debug(peer, 1534 "mx4200_pps: assert_sequence serial not incrementing: %d\n", 1535 up->pps_i.assert_sequence); 1536 refclock_report(peer, CEVNT_FAULT); 1537 return(1); 1538 } 1539 /* 1540 * Check pps serial number against last one 1541 */ 1542 if (up->lastserial + 1 != up->pps_i.assert_sequence && 1543 up->lastserial != 0) { 1544 if (up->pps_i.assert_sequence == up->lastserial) { 1545 mx4200_debug(peer, "mx4200_pps: no new pps event\n"); 1546 } else { 1547 mx4200_debug(peer, "mx4200_pps: missed %d pps events\n", 1548 up->pps_i.assert_sequence - up->lastserial - 1); 1549 } 1550 refclock_report(peer, CEVNT_FAULT); 1551 } 1552 up->lastserial = up->pps_i.assert_sequence; 1553 1554 /* 1555 * Return the timestamp in pp->lastrec 1556 */ 1557 1558 pp->lastrec.l_ui = up->pps_i.assert_timestamp.tv_sec + 1559 (u_int32) JAN_1970; 1560 pp->lastrec.l_uf = ((double)(up->pps_i.assert_timestamp.tv_nsec) * 1561 4.2949672960) + 0.5; 1562 1563 return(0); 1564 } 1565 1566 /* 1567 * mx4200_debug - print debug messages 1568 */ 1569 #if defined(__STDC__) 1570 static void 1571 mx4200_debug(struct peer *peer, char *fmt, ...) 1572 #else 1573 static void 1574 mx4200_debug(peer, fmt, va_alist) 1575 struct peer *peer; 1576 char *fmt; 1577 #endif /* __STDC__ */ 1578 { 1579 va_list ap; 1580 struct refclockproc *pp; 1581 struct mx4200unit *up; 1582 1583 if (debug) { 1584 1585 #if defined(__STDC__) 1586 va_start(ap, fmt); 1587 #else 1588 va_start(ap); 1589 #endif /* __STDC__ */ 1590 1591 pp = peer->procptr; 1592 up = (struct mx4200unit *)pp->unitptr; 1593 1594 1595 /* 1596 * Print debug message to stdout 1597 * In the future, we may want to get get more creative... 1598 */ 1599 vprintf(fmt, ap); 1600 1601 va_end(ap); 1602 } 1603 } 1604 1605 /* 1606 * Send a character string to the receiver. Checksum is appended here. 1607 */ 1608 #if defined(__STDC__) 1609 static void 1610 mx4200_send(struct peer *peer, char *fmt, ...) 1611 #else 1612 static void 1613 mx4200_send(peer, fmt, va_alist) 1614 struct peer *peer; 1615 char *fmt; 1616 va_dcl 1617 #endif /* __STDC__ */ 1618 { 1619 struct refclockproc *pp; 1620 struct mx4200unit *up; 1621 1622 register char *cp; 1623 register int n, m; 1624 va_list ap; 1625 char buf[1024]; 1626 u_char ck; 1627 1628 #if defined(__STDC__) 1629 va_start(ap, fmt); 1630 #else 1631 va_start(ap); 1632 #endif /* __STDC__ */ 1633 1634 pp = peer->procptr; 1635 up = (struct mx4200unit *)pp->unitptr; 1636 1637 cp = buf; 1638 *cp++ = '$'; 1639 #ifdef notdef 1640 /* BSD is rational */ 1641 n = vsnprintf(cp, sizeof(buf) - 1, fmt, ap); 1642 #else 1643 /* SunOS sucks */ 1644 (void)vsprintf(cp, fmt, ap); 1645 n = strlen(cp); 1646 #endif /* notdef */ 1647 ck = mx4200_cksum(cp, n); 1648 cp += n; 1649 ++n; 1650 #ifdef notdef 1651 /* BSD is rational */ 1652 n += snprintf(cp, sizeof(buf) - n - 5, "*%02X\r\n", ck); 1653 #else 1654 /* SunOS sucks */ 1655 sprintf(cp, "*%02X\r\n", ck); 1656 n += strlen(cp); 1657 #endif /* notdef */ 1658 1659 m = write(pp->io.fd, buf, (unsigned)n); 1660 if (m < 0) 1661 msyslog(LOG_ERR, "mx4200_send: write: %m (%s)", buf); 1662 mx4200_debug(peer, "mx4200_send: %d %s\n", m, buf); 1663 va_end(ap); 1664 } 1665 1666 #else 1667 int refclock_mx4200_bs; 1668 #endif /* REFCLOCK */ 1669