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
mx4200_start(int unit,struct peer * peer)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(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
mx4200_shutdown(int unit,struct peer * peer)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
mx4200_config(struct peer * peer)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
mx4200_ref(struct peer * peer)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
mx4200_poll(int unit,struct peer * peer)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
mx4200_receive(struct recvbuf * rbufp)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 *
mx4200_parse_t(struct peer * peer)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
mx4200_cksum(register char * cp,register int n)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
mx4200_jday(int year,int month,int day_of_month)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 *
mx4200_parse_p(struct peer * peer)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 *
mx4200_parse_s(struct peer * peer)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
mx4200_pps(struct peer * peer)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
mx4200_debug(struct peer * peer,char * fmt,...)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 vprintf(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
mx4200_send(struct peer * peer,char * fmt,...)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