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