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