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