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