refclock_mx4200.c revision 1.1.1.7 1 /* $NetBSD: refclock_mx4200.c,v 1.1.1.7 2016/01/08 21:21:25 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 (at) 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