diff options
Diffstat (limited to 'ntpd/refclock_mx4200.c')
-rw-r--r-- | ntpd/refclock_mx4200.c | 1654 |
1 files changed, 1654 insertions, 0 deletions
diff --git a/ntpd/refclock_mx4200.c b/ntpd/refclock_mx4200.c new file mode 100644 index 0000000..bc694ad --- /dev/null +++ b/ntpd/refclock_mx4200.c @@ -0,0 +1,1654 @@ +/* + * This software was developed by the Computer Systems Engineering group + * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66. + * + * Copyright (c) 1992 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Lawrence Berkeley Laboratory. + * 4. The name of the University may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Modified: Marc Brett <marc.brett@westgeo.com> Sept, 1999. + * + * 1. Added support for alternate PPS schemes, with code mostly + * copied from the Oncore driver (Thanks, Poul-Henning Kamp). + * This code runs on SunOS 4.1.3 with ppsclock-1.6a1 and Solaris 7. + */ + + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_MX4200) && defined(HAVE_PPSAPI) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +#include "mx4200.h" + +#ifdef HAVE_SYS_TERMIOS_H +# include <sys/termios.h> +#endif +#ifdef HAVE_SYS_PPSCLOCK_H +# include <sys/ppsclock.h> +#endif + +#include "ntp_sprintf.h" + +#ifndef HAVE_STRUCT_PPSCLOCKEV +struct ppsclockev { +# ifdef HAVE_STRUCT_TIMESPEC + struct timespec tv; +# else + struct timeval tv; +# endif + u_int serial; +}; +#endif /* ! HAVE_STRUCT_PPSCLOCKEV */ + +#ifdef HAVE_TIMEPPS_H +# include <timepps.h> +#else +# ifdef HAVE_SYS_TIMEPPS_H +# include <sys/timepps.h> +# endif +#endif + +/* + * This driver supports the Magnavox Model MX 4200 GPS Receiver + * adapted to precision timing applications. It requires the + * ppsclock line discipline or streams module described in the + * Line Disciplines and Streams Drivers page. It also requires a + * gadget box and 1-PPS level converter, such as described in the + * Pulse-per-second (PPS) Signal Interfacing page. + * + * It's likely that other compatible Magnavox receivers such as the + * MX 4200D, MX 9212, MX 9012R, MX 9112 will be supported by this code. + */ + +/* + * Check this every time you edit the code! + */ +#define YEAR_LAST_MODIFIED 2000 + +/* + * GPS Definitions + */ +#define DEVICE "/dev/gps%d" /* device name and unit */ +#define SPEED232 B4800 /* baud */ + +/* + * Radio interface parameters + */ +#define PRECISION (-18) /* precision assumed (about 4 us) */ +#define REFID "GPS\0" /* reference id */ +#define DESCRIPTION "Magnavox MX4200 GPS Receiver" /* who we are */ +#define DEFFUDGETIME 0 /* default fudge time (ms) */ + +#define SLEEPTIME 32 /* seconds to wait for reconfig to complete */ + +/* + * Position Averaging. + */ +#define INTERVAL 1 /* Interval between position measurements (s) */ +#define AVGING_TIME 24 /* Number of hours to average */ +#define NOT_INITIALIZED -9999. /* initial pivot longitude */ + +/* + * MX4200 unit control structure. + */ +struct mx4200unit { + u_int pollcnt; /* poll message counter */ + u_int polled; /* Hand in a time sample? */ + u_int lastserial; /* last pps serial number */ + struct ppsclockev ppsev; /* PPS control structure */ + double avg_lat; /* average latitude */ + double avg_lon; /* average longitude */ + double avg_alt; /* average height */ + double central_meridian; /* central meridian */ + double N_fixes; /* Number of position measurements */ + int last_leap; /* leap second warning */ + u_int moving; /* mobile platform? */ + u_long sloppyclockflag; /* fudge flags */ + u_int known; /* position known yet? */ + u_long clamp_time; /* when to stop postion averaging */ + u_long log_time; /* when to print receiver status */ + pps_handle_t pps_h; + pps_params_t pps_p; + pps_info_t pps_i; +}; + +static char pmvxg[] = "PMVXG"; + +/* XXX should be somewhere else */ +#ifdef __GNUC__ +#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) +#ifndef __attribute__ +#define __attribute__(args) +#endif /* __attribute__ */ +#endif /* __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) */ +#else +#ifndef __attribute__ +#define __attribute__(args) +#endif /* __attribute__ */ +#endif /* __GNUC__ */ +/* XXX end */ + +/* + * Function prototypes + */ +static int mx4200_start P((int, struct peer *)); +static void mx4200_shutdown P((int, struct peer *)); +static void mx4200_receive P((struct recvbuf *)); +static void mx4200_poll P((int, struct peer *)); + +static char * mx4200_parse_t P((struct peer *)); +static char * mx4200_parse_p P((struct peer *)); +static char * mx4200_parse_s P((struct peer *)); +#ifdef QSORT_USES_VOID_P +int mx4200_cmpl_fp P((const void *, const void *)); +#else +int mx4200_cmpl_fp P((const l_fp *, const l_fp *)); +#endif /* not QSORT_USES_VOID_P */ +static int mx4200_config P((struct peer *)); +static void mx4200_ref P((struct peer *)); +static void mx4200_send P((struct peer *, char *, ...)) + __attribute__ ((format (printf, 2, 3))); +static u_char mx4200_cksum P((char *, int)); +static int mx4200_jday P((int, int, int)); +static void mx4200_debug P((struct peer *, char *, ...)) + __attribute__ ((format (printf, 2, 3))); +static int mx4200_pps P((struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_mx4200 = { + mx4200_start, /* start up driver */ + mx4200_shutdown, /* shut down driver */ + mx4200_poll, /* transmit poll message */ + noentry, /* not used (old mx4200_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old mx4200_buginfo) */ + NOFLAGS /* not used */ +}; + + + +/* + * mx4200_start - open the devices and initialize data for processing + */ +static int +mx4200_start( + int unit, + struct peer *peer + ) +{ + register struct mx4200unit *up; + struct refclockproc *pp; + int fd; + char gpsdev[20]; + + /* + * Open serial port + */ + (void)sprintf(gpsdev, DEVICE, unit); + if (!(fd = refclock_open(gpsdev, SPEED232, LDISC_PPS))) { + return (0); + } + + /* + * Allocate unit structure + */ + if (!(up = (struct mx4200unit *) emalloc(sizeof(struct mx4200unit)))) { + perror("emalloc"); + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct mx4200unit)); + pp = peer->procptr; + pp->io.clock_recv = mx4200_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + + /* Ensure the receiver is properly configured */ + return mx4200_config(peer); +} + + +/* + * mx4200_shutdown - shut down the clock + */ +static void +mx4200_shutdown( + int unit, + struct peer *peer + ) +{ + register struct mx4200unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * mx4200_config - Configure the receiver + */ +static int +mx4200_config( + struct peer *peer + ) +{ + char tr_mode; + int add_mode; + register struct mx4200unit *up; + struct refclockproc *pp; + int mode; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + /* + * Initialize the unit variables + * + * STRANGE BEHAVIOUR WARNING: The fudge flags are not available + * at the time mx4200_start is called. These are set later, + * and so the code must be prepared to handle changing flags. + */ + up->sloppyclockflag = pp->sloppyclockflag; + if (pp->sloppyclockflag & CLK_FLAG2) { + up->moving = 1; /* Receiver on mobile platform */ + msyslog(LOG_DEBUG, "mx4200_config: mobile platform"); + } else { + up->moving = 0; /* Static Installation */ + } + up->pollcnt = 2; + up->polled = 0; + up->known = 0; + up->avg_lat = 0.0; + up->avg_lon = 0.0; + up->avg_alt = 0.0; + up->central_meridian = NOT_INITIALIZED; + up->N_fixes = 0.0; + up->last_leap = 0; /* LEAP_NOWARNING */ + up->clamp_time = current_time + (AVGING_TIME * 60 * 60); + up->log_time = current_time + SLEEPTIME; + + if (time_pps_create(pp->io.fd, &up->pps_h) < 0) { + perror("time_pps_create"); + msyslog(LOG_ERR, + "mx4200_config: time_pps_create failed: %m"); + return (0); + } + if (time_pps_getcap(up->pps_h, &mode) < 0) { + msyslog(LOG_ERR, + "mx4200_config: time_pps_getcap failed: %m"); + return (0); + } + + if (time_pps_getparams(up->pps_h, &up->pps_p) < 0) { + msyslog(LOG_ERR, + "mx4200_config: time_pps_getparams failed: %m"); + return (0); + } + + /* nb. only turn things on, if someone else has turned something + * on before we get here, leave it alone! + */ + + up->pps_p.mode = PPS_CAPTUREASSERT | PPS_TSFMT_TSPEC; + up->pps_p.mode &= mode; /* only set what is legal */ + + if (time_pps_setparams(up->pps_h, &up->pps_p) < 0) { + perror("time_pps_setparams"); + msyslog(LOG_ERR, + "mx4200_config: time_pps_setparams failed: %m"); + exit(1); + } + + if (time_pps_kcbind(up->pps_h, PPS_KC_HARDPPS, PPS_CAPTUREASSERT, + PPS_TSFMT_TSPEC) < 0) { + perror("time_pps_kcbind"); + msyslog(LOG_ERR, + "mx4200_config: time_pps_kcbind failed: %m"); + exit(1); + } + + + /* + * "007" Control Port Configuration + * Zero the output list (do it twice to flush possible junk) + */ + mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg, + PMVXG_S_PORTCONF, + /* control port output block Label */ + 1); /* clear current output control list (1=yes) */ + /* add/delete sentences from list */ + /* must be null */ + /* sentence output rate (sec) */ + /* precision for position output */ + /* nmea version for cga & gll output */ + /* pass-through control */ + mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg, + PMVXG_S_PORTCONF, 1); + + /* + * Request software configuration so we can syslog the firmware version + */ + mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_SOFTCONF); + + /* + * "001" Initialization/Mode Control, Part A + * Where ARE we? + */ + mx4200_send(peer, "%s,%03d,,,,,,,,,,", pmvxg, + PMVXG_S_INITMODEA); + /* day of month */ + /* month of year */ + /* year */ + /* gmt */ + /* latitude DDMM.MMMM */ + /* north/south */ + /* longitude DDDMM.MMMM */ + /* east/west */ + /* height */ + /* Altitude Reference 1=MSL */ + + /* + * "001" Initialization/Mode Control, Part B + * Start off in 2d/3d coast mode, holding altitude to last known + * value if only 3 satellites available. + */ + mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d", + pmvxg, PMVXG_S_INITMODEB, + 3, /* 2d/3d coast */ + /* reserved */ + 0.1, /* hor accel fact as per Steve (m/s**2) */ + 0.1, /* ver accel fact as per Steve (m/s**2) */ + 10, /* vdop */ + 10, /* hdop limit as per Steve */ + 5, /* elevation limit as per Steve (deg) */ + 'U', /* time output mode (UTC) */ + 0); /* local time offset from gmt (HHHMM) */ + + /* + * "023" Time Recovery Configuration + * Get UTC time from a stationary receiver. + * (Set field 1 'D' == dynamic if we are on a moving platform). + * (Set field 1 'S' == static if we are not moving). + * (Set field 1 'K' == known position if we can initialize lat/lon/alt). + */ + + if (pp->sloppyclockflag & CLK_FLAG2) + up->moving = 1; /* Receiver on mobile platform */ + else + up->moving = 0; /* Static Installation */ + + up->pollcnt = 2; + if (up->moving) { + /* dynamic: solve for pos, alt, time, while moving */ + tr_mode = 'D'; + } else { + /* static: solve for pos, alt, time, while stationary */ + tr_mode = 'S'; + } + mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg, + PMVXG_S_TRECOVCONF, + tr_mode, /* time recovery mode (see above ) */ + 'U', /* synchronize to UTC */ + 'A', /* always output a time pulse */ + 500, /* max time error in ns */ + 0, /* user bias in ns */ + 1); /* output "830" sentences to control port */ + /* Multi-satellite mode */ + + /* + * Output position information (to calculate fixed installation + * location) only if we are not moving + */ + if (up->moving) { + add_mode = 2; /* delete from list */ + } else { + add_mode = 1; /* add to list */ + } + + + /* + * "007" Control Port Configuration + * Output "021" position, height, velocity reports + */ + mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg, + PMVXG_S_PORTCONF, + PMVXG_D_PHV, /* control port output block Label */ + 0, /* clear current output control list (0=no) */ + add_mode, /* add/delete sentences from list (1=add, 2=del) */ + /* must be null */ + INTERVAL); /* sentence output rate (sec) */ + /* precision for position output */ + /* nmea version for cga & gll output */ + /* pass-through control */ + + return (1); +} + +/* + * mx4200_ref - Reconfigure unit as a reference station at a known position. + */ +static void +mx4200_ref( + struct peer *peer + ) +{ + register struct mx4200unit *up; + struct refclockproc *pp; + double minute, lat, lon, alt; + char lats[16], lons[16]; + char nsc, ewc; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + /* Should never happen! */ + if (up->moving) return; + + /* + * Set up to output status information in the near future + */ + up->log_time = current_time + SLEEPTIME; + + /* + * "007" Control Port Configuration + * Stop outputting "021" position, height, velocity reports + */ + mx4200_send(peer, "%s,%03d,%03d,%d,%d,,,,,", pmvxg, + PMVXG_S_PORTCONF, + PMVXG_D_PHV, /* control port output block Label */ + 0, /* clear current output control list (0=no) */ + 2); /* add/delete sentences from list (2=delete) */ + /* must be null */ + /* sentence output rate (sec) */ + /* precision for position output */ + /* nmea version for cga & gll output */ + /* pass-through control */ + + /* + * "001" Initialization/Mode Control, Part B + * Put receiver in fully-constrained 2d nav mode + */ + mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d", + pmvxg, PMVXG_S_INITMODEB, + 2, /* 2d nav */ + /* reserved */ + 0.1, /* hor accel fact as per Steve (m/s**2) */ + 0.1, /* ver accel fact as per Steve (m/s**2) */ + 10, /* vdop */ + 10, /* hdop limit as per Steve */ + 5, /* elevation limit as per Steve (deg) */ + 'U', /* time output mode (UTC) */ + 0); /* local time offset from gmt (HHHMM) */ + + /* + * "023" Time Recovery Configuration + * Get UTC time from a stationary receiver. Solve for time only. + * This should improve the time resolution dramatically. + */ + mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg, + PMVXG_S_TRECOVCONF, + 'K', /* known position: solve for time only */ + 'U', /* synchronize to UTC */ + 'A', /* always output a time pulse */ + 500, /* max time error in ns */ + 0, /* user bias in ns */ + 1); /* output "830" sentences to control port */ + /* Multi-satellite mode */ + + /* + * "000" Initialization/Mode Control - Part A + * Fix to our averaged position. + */ + if (up->central_meridian != NOT_INITIALIZED) { + up->avg_lon += up->central_meridian; + if (up->avg_lon < -180.0) up->avg_lon += 360.0; + if (up->avg_lon > 180.0) up->avg_lon -= 360.0; + } + + if (up->avg_lat >= 0.0) { + lat = up->avg_lat; + nsc = 'N'; + } else { + lat = up->avg_lat * (-1.0); + nsc = 'S'; + } + if (up->avg_lon >= 0.0) { + lon = up->avg_lon; + ewc = 'E'; + } else { + lon = up->avg_lon * (-1.0); + ewc = 'W'; + } + alt = up->avg_alt; + minute = (lat - (double)(int)lat) * 60.0; + sprintf(lats,"%02d%02.4f", (int)lat, minute); + minute = (lon - (double)(int)lon) * 60.0; + sprintf(lons,"%03d%02.4f", (int)lon, minute); + + mx4200_send(peer, "%s,%03d,,,,,%s,%c,%s,%c,%.2f,%d", pmvxg, + PMVXG_S_INITMODEA, + /* day of month */ + /* month of year */ + /* year */ + /* gmt */ + lats, /* latitude DDMM.MMMM */ + nsc, /* north/south */ + lons, /* longitude DDDMM.MMMM */ + ewc, /* east/west */ + alt, /* Altitude */ + 1); /* Altitude Reference (0=WGS84 ellipsoid, 1=MSL geoid)*/ + + msyslog(LOG_DEBUG, + "mx4200: reconfig to fixed location: %s %c, %s %c, %.2f m", + lats, nsc, lons, ewc, alt ); + +} + +/* + * mx4200_poll - mx4200 watchdog routine + */ +static void +mx4200_poll( + int unit, + struct peer *peer + ) +{ + register struct mx4200unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + /* + * You don't need to poll this clock. It puts out timecodes + * once per second. If asked for a timestamp, take note. + * The next time a timecode comes in, it will be fed back. + */ + + /* + * If we haven't had a response in a while, reset the receiver. + */ + if (up->pollcnt > 0) { + up->pollcnt--; + } else { + refclock_report(peer, CEVNT_TIMEOUT); + + /* + * Request a "000" status message which should trigger a + * reconfig + */ + mx4200_send(peer, "%s,%03d", + "CDGPQ", /* query from CDU to GPS */ + PMVXG_D_STATUS); /* label of desired sentence */ + } + + /* + * polled every 64 seconds. Ask mx4200_receive to hand in + * a timestamp. + */ + up->polled = 1; + pp->polls++; + + /* + * Output receiver status information. + */ + if ((up->log_time > 0) && (current_time > up->log_time)) { + up->log_time = 0; + /* + * Output the following messages once, for debugging. + * "004" Mode Data + * "523" Time Recovery Parameters + */ + mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_MODEDATA); + mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_TRECOVUSEAGE); + } +} + +static char char2hex[] = "0123456789ABCDEF"; + +/* + * mx4200_receive - receive gps data + */ +static void +mx4200_receive( + struct recvbuf *rbufp + ) +{ + register struct mx4200unit *up; + struct refclockproc *pp; + struct peer *peer; + char *cp; + int sentence_type; + u_char ck; + + /* + * Initialize pointers and read the timecode and timestamp. + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + /* + * If operating mode has been changed, then reinitialize the receiver + * before doing anything else. + */ + if ((pp->sloppyclockflag & CLK_FLAG2) != + (up->sloppyclockflag & CLK_FLAG2)) { + up->sloppyclockflag = pp->sloppyclockflag; + mx4200_debug(peer, + "mx4200_receive: mode switch: reset receiver\n"); + mx4200_config(peer); + return; + } + up->sloppyclockflag = pp->sloppyclockflag; + + /* + * Read clock output. Automatically handles STREAMS, CLKLDISC. + */ + pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec); + + /* + * There is a case where <cr><lf> generates 2 timestamps. + */ + if (pp->lencode == 0) + return; + + up->pollcnt = 2; + pp->a_lastcode[pp->lencode] = '\0'; + record_clock_stats(&peer->srcadr, pp->a_lastcode); + mx4200_debug(peer, "mx4200_receive: %d %s\n", + pp->lencode, pp->a_lastcode); + + /* + * The structure of the control port sentences is based on the + * NMEA-0183 Standard for interfacing Marine Electronics + * Navigation Devices (Version 1.5) + * + * $PMVXG,XXX, ....................*CK<cr><lf> + * + * $ Sentence Start Identifier (reserved char) + * (Start-of-Sentence Identifier) + * P Special ID (Proprietary) + * MVX Originator ID (Magnavox) + * G Interface ID (GPS) + * , Field Delimiters (reserved char) + * XXX Sentence Type + * ...... Data + * * Checksum Field Delimiter (reserved char) + * CK Checksum + * <cr><lf> Carriage-Return/Line Feed (reserved chars) + * (End-of-Sentence Identifier) + * + * Reject if any important landmarks are missing. + */ + cp = pp->a_lastcode + pp->lencode - 3; + if (cp < pp->a_lastcode || *pp->a_lastcode != '$' || cp[0] != '*' ) { + mx4200_debug(peer, "mx4200_receive: bad format\n"); + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + /* + * Check and discard the checksum + */ + ck = mx4200_cksum(&pp->a_lastcode[1], pp->lencode - 4); + if (char2hex[ck >> 4] != cp[1] || char2hex[ck & 0xf] != cp[2]) { + mx4200_debug(peer, "mx4200_receive: bad checksum\n"); + refclock_report(peer, CEVNT_BADREPLY); + return; + } + *cp = '\0'; + + /* + * Get the sentence type. + */ + sentence_type = 0; + if ((cp = strchr(pp->a_lastcode, ',')) == NULL) { + mx4200_debug(peer, "mx4200_receive: no sentence\n"); + refclock_report(peer, CEVNT_BADREPLY); + return; + } + cp++; + sentence_type = strtol(cp, &cp, 10); + + /* + * Process the sentence according to its type. + */ + switch (sentence_type) { + + /* + * "000" Status message + */ + case PMVXG_D_STATUS: + /* + * XXX + * Since we configure the receiver to not give us status + * messages and since the receiver outputs status messages by + * default after being reset to factory defaults when sent the + * "$PMVXG,018,C\r\n" message, any status message we get + * indicates the reciever needs to be initialized; thus, it is + * not necessary to decode the status message. + */ + if ((cp = mx4200_parse_s(peer)) != NULL) { + mx4200_debug(peer, + "mx4200_receive: status: %s\n", cp); + } + mx4200_debug(peer, "mx4200_receive: reset receiver\n"); + mx4200_config(peer); + break; + + /* + * "021" Position, Height, Velocity message, + * if we are still averaging our position + */ + case PMVXG_D_PHV: + if (!up->known) { + /* + * Parse the message, calculating our averaged position. + */ + if ((cp = mx4200_parse_p(peer)) != NULL) { + mx4200_debug(peer, "mx4200_receive: pos: %s\n", cp); + return; + } + mx4200_debug(peer, + "mx4200_receive: position avg %f %.9f %.9f %.4f\n", + up->N_fixes, up->avg_lat, up->avg_lon, up->avg_alt); + /* + * Reinitialize as a reference station + * if position is well known. + */ + if (current_time > up->clamp_time) { + up->known++; + mx4200_debug(peer, "mx4200_receive: reconfiguring!\n"); + mx4200_ref(peer); + } + } + break; + + /* + * Print to the syslog: + * "004" Mode Data + * "030" Software Configuration + * "523" Time Recovery Parameters Currently in Use + */ + case PMVXG_D_MODEDATA: + case PMVXG_D_SOFTCONF: + case PMVXG_D_TRECOVUSEAGE: + + if ((cp = mx4200_parse_s(peer)) != NULL) { + mx4200_debug(peer, + "mx4200_receive: multi-record: %s\n", cp); + } + break; + + /* + * "830" Time Recovery Results message + */ + case PMVXG_D_TRECOVOUT: + + /* + * Capture the last PPS signal. + * Precision timestamp is returned in pp->lastrec + */ + if (mx4200_pps(peer) != NULL) { + mx4200_debug(peer, "mx4200_receive: pps failure\n"); + refclock_report(peer, CEVNT_FAULT); + return; + } + + + /* + * Parse the time recovery message, and keep the info + * to print the pretty billboards. + */ + if ((cp = mx4200_parse_t(peer)) != NULL) { + mx4200_debug(peer, "mx4200_receive: time: %s\n", cp); + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + /* + * Add the new sample to a median filter. + */ + if (!refclock_process(pp)) { + mx4200_debug(peer,"mx4200_receive: offset: %.6f\n", + pp->offset); + refclock_report(peer, CEVNT_BADTIME); + return; + } + + /* + * The clock will blurt a timecode every second but we only + * want one when polled. If we havn't been polled, bail out. + */ + if (!up->polled) + return; + + /* + * Return offset and dispersion to control module. We use + * lastrec as both the reference time and receive time in + * order to avoid being cute, like setting the reference time + * later than the receive time, which may cause a paranoid + * protocol module to chuck out the data. + */ + mx4200_debug(peer, "mx4200_receive: process time: "); + mx4200_debug(peer, "%4d-%03d %02d:%02d:%02d at %s, %.6f\n", + pp->year, pp->day, pp->hour, pp->minute, pp->second, + prettydate(&pp->lastrec), pp->offset); + pp->lastref = pp->lastrec; + refclock_receive(peer); + + /* + * We have succeeded in answering the poll. + * Turn off the flag and return + */ + up->polled = 0; + break; + + /* + * Ignore all other sentence types + */ + default: + break; + + } /* switch (sentence_type) */ + + return; +} + + +/* + * Parse a mx4200 time recovery message. Returns a string if error. + * + * A typical message looks like this. Checksum has already been stripped. + * + * $PMVXG,830,T,YYYY,MM,DD,HH:MM:SS,U,S,FFFFFF,PPPPP,BBBBBB,LL + * + * Field Field Contents + * ----- -------------- + * Block Label: $PMVXG + * Sentence Type: 830=Time Recovery Results + * This sentence is output approximately 1 second + * preceding the 1PPS output. It indicates the + * exact time of the next pulse, whether or not the + * time mark will be valid (based on operator-specified + * error tolerance), the time to which the pulse is + * synchronized, the receiver operating mode, + * and the time error of the *last* 1PPS output. + * 1 char Time Mark Valid: T=Valid, F=Not Valid + * 2 int Year: 1993- + * 3 int Month of Year: 1-12 + * 4 int Day of Month: 1-31 + * 5 int Time of Day: HH:MM:SS + * 6 char Time Synchronization: U=UTC, G=GPS + * 7 char Time Recovery Mode: D=Dynamic, S=Static, + * K=Known Position, N=No Time Recovery + * 8 int Oscillator Offset: The filter's estimate of the oscillator + * frequency error, in parts per billion (ppb). + * 9 int Time Mark Error: The computed error of the *last* pulse + * output, in nanoseconds. + * 10 int User Time Bias: Operator specified bias, in nanoseconds + * 11 int Leap Second Flag: Indicates that a leap second will + * occur. This value is usually zero, except during + * the week prior to the leap second occurrence, when + * this value will be set to +1 or -1. A value of + * +1 indicates that GPS time will be 1 second + * further ahead of UTC time. + * + */ +static char * +mx4200_parse_t( + struct peer *peer + ) +{ + struct refclockproc *pp; + struct mx4200unit *up; + char time_mark_valid, time_sync, op_mode; + int sentence_type, valid; + int year, day_of_year, month, day_of_month; + int hour, minute, second, leapsec; + int oscillator_offset, time_mark_error, time_bias; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + leapsec = 0; /* Not all receivers output leap second warnings (!) */ + sscanf(pp->a_lastcode, + "$PMVXG,%d,%c,%d,%d,%d,%d:%d:%d,%c,%c,%d,%d,%d,%d", + &sentence_type, &time_mark_valid, &year, &month, &day_of_month, + &hour, &minute, &second, &time_sync, &op_mode, + &oscillator_offset, &time_mark_error, &time_bias, &leapsec); + + if (sentence_type != PMVXG_D_TRECOVOUT) + return ("wrong rec-type"); + + switch (time_mark_valid) { + case 'T': + valid = 1; + break; + case 'F': + valid = 0; + break; + default: + return ("bad pulse-valid"); + } + + switch (time_sync) { + case 'G': + return ("synchronized to GPS; should be UTC"); + case 'U': + break; /* UTC -> ok */ + default: + return ("not synchronized to UTC"); + } + + /* + * Check for insane time (allow for possible leap seconds) + */ + if (second > 60 || minute > 59 || hour > 23 || + second < 0 || minute < 0 || hour < 0) { + mx4200_debug(peer, + "mx4200_parse_t: bad time %02d:%02d:%02d", + hour, minute, second); + if (leapsec != 0) + mx4200_debug(peer, " (leap %+d\n)", leapsec); + mx4200_debug(peer, "\n"); + refclock_report(peer, CEVNT_BADTIME); + return ("bad time"); + } + if ( second == 60 ) { + msyslog(LOG_DEBUG, + "mx4200: leap second! %02d:%02d:%02d", + hour, minute, second); + } + + /* + * Check for insane date + * (Certainly can't be any year before this code was last altered!) + */ + if (day_of_month > 31 || month > 12 || + day_of_month < 1 || month < 1 || year < YEAR_LAST_MODIFIED) { + mx4200_debug(peer, + "mx4200_parse_t: bad date (%4d-%02d-%02d)\n", + year, month, day_of_month); + refclock_report(peer, CEVNT_BADDATE); + return ("bad date"); + } + + /* + * Silly Hack for MX4200: + * ASCII message is for *next* 1PPS signal, but we have the + * timestamp for the *last* 1PPS signal. So we have to subtract + * a second. Discard if we are on a month boundary to avoid + * possible leap seconds and leap days. + */ + second--; + if (second < 0) { + second = 59; + minute--; + if (minute < 0) { + minute = 59; + hour--; + if (hour < 0) { + hour = 23; + day_of_month--; + if (day_of_month < 1) { + return ("sorry, month boundary"); + } + } + } + } + + /* + * Calculate Julian date + */ + if (!(day_of_year = mx4200_jday(year, month, day_of_month))) { + mx4200_debug(peer, + "mx4200_parse_t: bad julian date %d (%4d-%02d-%02d)\n", + day_of_year, year, month, day_of_month); + refclock_report(peer, CEVNT_BADDATE); + return("invalid julian date"); + } + + /* + * Setup leap second indicator + */ + switch (leapsec) { + case 0: + pp->leap = LEAP_NOWARNING; + break; + case 1: + pp->leap = LEAP_ADDSECOND; + break; + case -1: + pp->leap = LEAP_DELSECOND; + break; + default: + pp->leap = LEAP_NOTINSYNC; + } + + /* + * Any change to the leap second warning status? + */ + if (leapsec != up->last_leap ) { + msyslog(LOG_DEBUG, + "mx4200: leap second warning: %d to %d (%d)", + up->last_leap, leapsec, pp->leap); + } + up->last_leap = leapsec; + + /* + * Copy time data for billboard monitoring. + */ + + pp->year = year; + pp->day = day_of_year; + pp->hour = hour; + pp->minute = minute; + pp->second = second; + + /* + * Toss if sentence is marked invalid + */ + if (!valid || pp->leap == LEAP_NOTINSYNC) { + mx4200_debug(peer, "mx4200_parse_t: time mark not valid\n"); + refclock_report(peer, CEVNT_BADTIME); + return ("pulse invalid"); + } + + return (NULL); +} + +/* + * Calculate the checksum + */ +static u_char +mx4200_cksum( + register char *cp, + register int n + ) +{ + register u_char ck; + + for (ck = 0; n-- > 0; cp++) + ck ^= *cp; + return (ck); +} + +/* + * Tables to compute the day of year. Viva la leap. + */ +static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +/* + * Calculate the the Julian Day + */ +static int +mx4200_jday( + int year, + int month, + int day_of_month + ) +{ + register int day, i; + int leap_year; + + /* + * Is this a leap year ? + */ + if (year % 4) { + leap_year = 0; /* FALSE */ + } else { + if (year % 100) { + leap_year = 1; /* TRUE */ + } else { + if (year % 400) { + leap_year = 0; /* FALSE */ + } else { + leap_year = 1; /* TRUE */ + } + } + } + + /* + * Calculate the Julian Date + */ + day = day_of_month; + + if (leap_year) { + /* a leap year */ + if (day > day2tab[month - 1]) { + return (0); + } + for (i = 0; i < month - 1; i++) + day += day2tab[i]; + } else { + /* not a leap year */ + if (day > day1tab[month - 1]) { + return (0); + } + for (i = 0; i < month - 1; i++) + day += day1tab[i]; + } + return (day); +} + +/* + * Parse a mx4200 position/height/velocity sentence. + * + * A typical message looks like this. Checksum has already been stripped. + * + * $PMVXG,021,SSSSSS.SS,DDMM.MMMM,N,DDDMM.MMMM,E,HHHHH.H,GGGG.G,EEEE.E,WWWW.W,MM + * + * Field Field Contents + * ----- -------------- + * Block Label: $PMVXG + * Sentence Type: 021=Position, Height Velocity Data + * This sentence gives the receiver position, height, + * navigation mode, and velocity north/east. + * *This sentence is intended for post-analysis + * applications.* + * 1 float UTC measurement time (seconds into week) + * 2 float WGS-84 Lattitude (degrees, minutes) + * 3 char N=North, S=South + * 4 float WGS-84 Longitude (degrees, minutes) + * 5 char E=East, W=West + * 6 float Altitude (meters above mean sea level) + * 7 float Geoidal height (meters) + * 8 float East velocity (m/sec) + * 9 float West Velocity (m/sec) + * 10 int Navigation Mode + * Mode if navigating: + * 1 = Position from remote device + * 2 = 2-D position + * 3 = 3-D position + * 4 = 2-D differential position + * 5 = 3-D differential position + * 6 = Static + * 8 = Position known -- reference station + * 9 = Position known -- Navigator + * Mode if not navigating: + * 51 = Too few satellites + * 52 = DOPs too large + * 53 = Position STD too large + * 54 = Velocity STD too large + * 55 = Too many iterations for velocity + * 56 = Too many iterations for position + * 57 = 3 sat startup failed + * 58 = Command abort + */ +static char * +mx4200_parse_p( + struct peer *peer + ) +{ + struct refclockproc *pp; + struct mx4200unit *up; + int sentence_type, mode; + double mtime, lat, lon, alt, geoid, vele, veln; + char north_south, east_west; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + /* Should never happen! */ + if (up->moving) return ("mobile platform - no pos!"); + + sscanf ( pp->a_lastcode, + "$PMVXG,%d,%lf,%lf,%c,%lf,%c,%lf,%lf,%lf,%lf,%d", + &sentence_type, &mtime, &lat, &north_south, &lon, &east_west, + &alt, &geoid, &vele, &veln, &mode); + + /* Sentence type */ + if (sentence_type != PMVXG_D_PHV) + return ("wrong rec-type"); + + /* + * return if not navigating + */ + if (mode > 10) + return ("not navigating"); + if (mode != 3 && mode != 5) + return ("not navigating in 3D"); + + /* Latitude (always +ve) and convert DDMM.MMMM to decimal */ + if (lat < 0.0) return ("negative latitude"); + if (lat > 9000.0) lat = 9000.0; + lat *= 0.01; + lat = ((int)lat) + (((lat - (int)lat)) * 1.6666666666666666); + + /* North/South */ + switch (north_south) { + case 'N': + break; + case 'S': + lat *= -1.0; + break; + default: + return ("invalid north/south indicator"); + } + + /* Longitude (always +ve) and convert DDDMM.MMMM to decimal */ + if (lon < 0.0) return ("negative longitude"); + if (lon > 180.0) lon = 180.0; + lon *= 0.01; + lon = ((int)lon) + (((lon - (int)lon)) * 1.6666666666666666); + + /* East/West */ + switch (east_west) { + case 'E': + break; + case 'W': + lon *= -1.0; + break; + default: + return ("invalid east/west indicator"); + } + + /* + * Normalize longitude to near 0 degrees. + * Assume all data are clustered around first reading. + */ + if (up->central_meridian == NOT_INITIALIZED) { + up->central_meridian = lon; + mx4200_debug(peer, + "mx4200_receive: central meridian = %.9f \n", + up->central_meridian); + } + lon -= up->central_meridian; + if (lon < -180.0) lon += 360.0; + if (lon > 180.0) lon -= 360.0; + + /* + * Calculate running averages + */ + + up->avg_lon = (up->N_fixes * up->avg_lon) + lon; + up->avg_lat = (up->N_fixes * up->avg_lat) + lat; + up->avg_alt = (up->N_fixes * up->avg_alt) + alt; + + up->N_fixes += 1.0; + + up->avg_lon /= up->N_fixes; + up->avg_lat /= up->N_fixes; + up->avg_alt /= up->N_fixes; + + mx4200_debug(peer, + "mx4200_receive: position rdg %.0f: %.9f %.9f %.4f (CM=%.9f)\n", + up->N_fixes, lat, lon, alt, up->central_meridian); + + return (NULL); +} + +/* + * Parse a mx4200 Status sentence + * Parse a mx4200 Mode Data sentence + * Parse a mx4200 Software Configuration sentence + * Parse a mx4200 Time Recovery Parameters Currently in Use sentence + * (used only for logging raw strings) + * + * A typical message looks like this. Checksum has already been stripped. + * + * $PMVXG,000,XXX,XX,X,HHMM,X + * + * Field Field Contents + * ----- -------------- + * Block Label: $PMVXG + * Sentence Type: 000=Status. + * Returns status of the receiver to the controller. + * 1 Current Receiver Status: + * ACQ = Satellite re-acquisition + * ALT = Constellation selection + * COR = Providing corrections (for reference stations only) + * IAC = Initial acquisition + * IDL = Idle, no satellites + * NAV = Navigation + * STS = Search the Sky (no almanac available) + * TRK = Tracking + * 2 Number of satellites that should be visible + * 3 Number of satellites being tracked + * 4 Time since last navigation status if not currently navigating + * (hours, minutes) + * 5 Initialization status: + * 0 = Waiting for initialization parameters + * 1 = Initialization completed + * + * A typical message looks like this. Checksum has already been stripped. + * + * $PMVXG,004,C,R,D,H.HH,V.VV,TT,HHHH,VVVV,T + * + * Field Field Contents + * ----- -------------- + * Block Label: $PMVXG + * Sentence Type: 004=Software Configuration. + * Defines the navigation mode and criteria for + * acceptable navigation for the receiver. + * 1 Constrain Altitude Mode: + * 0 = Auto. Constrain altitude (2-D solution) and use + * manual altitude input when 3 sats avalable. Do + * not constrain altitude (3-D solution) when 4 sats + * available. + * 1 = Always constrain altitude (2-D solution). + * 2 = Never constrain altitude (3-D solution). + * 3 = Coast. Constrain altitude (2-D solution) and use + * last GPS altitude calculation when 3 sats avalable. + * Do not constrain altitude (3-D solution) when 4 sats + * available. + * 2 Altitude Reference: (always 0 for MX4200) + * 0 = Ellipsoid + * 1 = Geoid (MSL) + * 3 Differential Navigation Control: + * 0 = Disabled + * 1 = Enabled + * 4 Horizontal Acceleration Constant (m/sec**2) + * 5 Vertical Acceleration Constant (m/sec**2) (0 for MX4200) + * 6 Tracking Elevation Limit (degrees) + * 7 HDOP Limit + * 8 VDOP Limit + * 9 Time Output Mode: + * U = UTC + * L = Local time + * 10 Local Time Offset (minutes) (absent on MX4200) + * + * A typical message looks like this. Checksum has already been stripped. + * + * $PMVXG,030,NNNN,FFF + * + * Field Field Contents + * ----- -------------- + * Block Label: $PMVXG + * Sentence Type: 030=Software Configuration. + * This sentence contains the navigation processor + * and baseband firmware version numbers. + * 1 Nav Processor Version Number + * 2 Baseband Firmware Version Number + * + * A typical message looks like this. Checksum has already been stripped. + * + * $PMVXG,523,M,S,M,EEEE,BBBBBB,C,R + * + * Field Field Contents + * ----- -------------- + * Block Label: $PMVXG + * Sentence Type: 523=Time Recovery Parameters Currently in Use. + * This sentence contains the configuration of the + * time recovery feature of the receiver. + * 1 Time Recovery Mode: + * D = Dynamic; solve for position and time while moving + * S = Static; solve for position and time while stationary + * K = Known position input, solve for time only + * N = No time recovery + * 2 Time Synchronization: + * U = UTC time + * G = GPS time + * 3 Time Mark Mode: + * A = Always output a time pulse + * V = Only output time pulse if time is valid (as determined + * by Maximum Time Error) + * 4 Maximum Time Error - the maximum error (in nanoseconds) for + * which a time mark will be considered valid. + * 5 User Time Bias - external bias in nanoseconds + * 6 Time Message Control: + * 0 = Do not output the time recovery message + * 1 = Output the time recovery message (record 830) to + * Control port + * 2 = Output the time recovery message (record 830) to + * Equipment port + * 7 Reserved + * 8 Position Known PRN (absent on MX 4200) + * + */ +static char * +mx4200_parse_s( + struct peer *peer + ) +{ + struct refclockproc *pp; + struct mx4200unit *up; + int sentence_type; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + sscanf ( pp->a_lastcode, "$PMVXG,%d", &sentence_type); + + /* Sentence type */ + switch (sentence_type) { + + case PMVXG_D_STATUS: + msyslog(LOG_DEBUG, + "mx4200: status: %s", pp->a_lastcode); + break; + case PMVXG_D_MODEDATA: + msyslog(LOG_DEBUG, + "mx4200: mode data: %s", pp->a_lastcode); + break; + case PMVXG_D_SOFTCONF: + msyslog(LOG_DEBUG, + "mx4200: firmware configuration: %s", pp->a_lastcode); + break; + case PMVXG_D_TRECOVUSEAGE: + msyslog(LOG_DEBUG, + "mx4200: time recovery parms: %s", pp->a_lastcode); + break; + default: + return ("wrong rec-type"); + } + + return (NULL); +} + +/* + * Process a PPS signal, placing a timestamp in pp->lastrec. + */ +static int +mx4200_pps( + struct peer *peer + ) +{ + int temp_serial; + struct refclockproc *pp; + struct mx4200unit *up; + + struct timespec timeout; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + /* + * Grab the timestamp of the PPS signal. + */ + temp_serial = up->pps_i.assert_sequence; + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + if (time_pps_fetch(up->pps_h, PPS_TSFMT_TSPEC, &(up->pps_i), + &timeout) < 0) { + mx4200_debug(peer, + "mx4200_pps: time_pps_fetch: serial=%d, %s\n", + up->pps_i.assert_sequence, strerror(errno)); + refclock_report(peer, CEVNT_FAULT); + return(1); + } + if (temp_serial == up->pps_i.assert_sequence) { + mx4200_debug(peer, + "mx4200_pps: assert_sequence serial not incrementing: %d\n", + up->pps_i.assert_sequence); + refclock_report(peer, CEVNT_FAULT); + return(1); + } + /* + * Check pps serial number against last one + */ + if (up->lastserial + 1 != up->pps_i.assert_sequence && + up->lastserial != 0) { + if (up->pps_i.assert_sequence == up->lastserial) { + mx4200_debug(peer, "mx4200_pps: no new pps event\n"); + } else { + mx4200_debug(peer, "mx4200_pps: missed %d pps events\n", + up->pps_i.assert_sequence - up->lastserial - 1); + } + refclock_report(peer, CEVNT_FAULT); + } + up->lastserial = up->pps_i.assert_sequence; + + /* + * Return the timestamp in pp->lastrec + */ + + pp->lastrec.l_ui = up->pps_i.assert_timestamp.tv_sec + + (u_int32) JAN_1970; + pp->lastrec.l_uf = ((double)(up->pps_i.assert_timestamp.tv_nsec) * + 4.2949672960) + 0.5; + + return(0); +} + +/* + * mx4200_debug - print debug messages + */ +#if defined(__STDC__) +static void +mx4200_debug(struct peer *peer, char *fmt, ...) +#else +static void +mx4200_debug(peer, fmt, va_alist) + struct peer *peer; + char *fmt; +#endif /* __STDC__ */ +{ + va_list ap; + struct refclockproc *pp; + struct mx4200unit *up; + + if (debug) { + +#if defined(__STDC__) + va_start(ap, fmt); +#else + va_start(ap); +#endif /* __STDC__ */ + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + + /* + * Print debug message to stdout + * In the future, we may want to get get more creative... + */ + vprintf(fmt, ap); + + va_end(ap); + } +} + +/* + * Send a character string to the receiver. Checksum is appended here. + */ +#if defined(__STDC__) +static void +mx4200_send(struct peer *peer, char *fmt, ...) +#else +static void +mx4200_send(peer, fmt, va_alist) + struct peer *peer; + char *fmt; + va_dcl +#endif /* __STDC__ */ +{ + struct refclockproc *pp; + struct mx4200unit *up; + + register char *cp; + register int n, m; + va_list ap; + char buf[1024]; + u_char ck; + +#if defined(__STDC__) + va_start(ap, fmt); +#else + va_start(ap); +#endif /* __STDC__ */ + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + cp = buf; + *cp++ = '$'; + n = VSNPRINTF((cp, sizeof(buf) - 1, fmt, ap)); + ck = mx4200_cksum(cp, n); + cp += n; + ++n; + n += SNPRINTF((cp, sizeof(buf) - n - 5, "*%02X\r\n", ck)); + + m = write(pp->io.fd, buf, (unsigned)n); + if (m < 0) + msyslog(LOG_ERR, "mx4200_send: write: %m (%s)", buf); + mx4200_debug(peer, "mx4200_send: %d %s\n", m, buf); + va_end(ap); +} + +#else +int refclock_mx4200_bs; +#endif /* REFCLOCK */ |