summaryrefslogtreecommitdiffstats
path: root/contrib/ntp/ntpd/refclock_hpgps.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/ntp/ntpd/refclock_hpgps.c')
-rw-r--r--contrib/ntp/ntpd/refclock_hpgps.c603
1 files changed, 603 insertions, 0 deletions
diff --git a/contrib/ntp/ntpd/refclock_hpgps.c b/contrib/ntp/ntpd/refclock_hpgps.c
new file mode 100644
index 0000000..30aa494
--- /dev/null
+++ b/contrib/ntp/ntpd/refclock_hpgps.c
@@ -0,0 +1,603 @@
+/*
+ * refclock_hpgps - clock driver for HP 58503A GPS receiver
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#if defined(REFCLOCK) && defined(CLOCK_HPGPS)
+
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/time.h>
+
+#include "ntpd.h"
+#include "ntp_io.h"
+#include "ntp_refclock.h"
+#include "ntp_stdlib.h"
+
+/* Version 0.1 April 1, 1995
+ * 0.2 April 25, 1995
+ * tolerant of missing timecode response prompt and sends
+ * clear status if prompt indicates error;
+ * can use either local time or UTC from receiver;
+ * can get receiver status screen via flag4
+ *
+ * WARNING!: This driver is UNDER CONSTRUCTION
+ * Everything in here should be treated with suspicion.
+ * If it looks wrong, it probably is.
+ *
+ * Comments and/or questions to: Dave Vitanye
+ * Hewlett Packard Company
+ * dave@scd.hp.com
+ * (408) 553-2856
+ *
+ * Thanks to the author of the PST driver, which was the starting point for
+ * this one.
+ *
+ * This driver supports the HP 58503A Time and Frequency Reference Receiver.
+ * This receiver uses HP SmartClock (TM) to implement an Enhanced GPS receiver.
+ * The receiver accuracy when locked to GPS in normal operation is better
+ * than 1 usec. The accuracy when operating in holdover is typically better
+ * than 10 usec. per day.
+ *
+ * The receiver should be operated with factory default settings.
+ * Initial driver operation: expects the receiver to be already locked
+ * to GPS, configured and able to output timecode format 2 messages.
+ *
+ * The driver uses the poll sequence :PTIME:TCODE? to get a response from
+ * the receiver. The receiver responds with a timecode string of ASCII
+ * printing characters, followed by a <cr><lf>, followed by a prompt string
+ * issued by the receiver, in the following format:
+ * T#yyyymmddhhmmssMFLRVcc<cr><lf>scpi >
+ *
+ * The driver processes the response at the <cr> and <lf>, so what the
+ * driver sees is the prompt from the previous poll, followed by this
+ * timecode. The prompt from the current poll is (usually) left unread until
+ * the next poll. So (except on the very first poll) the driver sees this:
+ *
+ * scpi > T#yyyymmddhhmmssMFLRVcc<cr><lf>
+ *
+ * The T is the on-time character, at 980 msec. before the next 1PPS edge.
+ * The # is the timecode format type. We look for format 2.
+ * Without any of the CLK or PPS stuff, then, the receiver buffer timestamp
+ * at the <cr> is 24 characters later, which is about 25 msec. at 9600 bps,
+ * so the first approximation for fudge time1 is nominally -0.955 seconds.
+ * This number probably needs adjusting for each machine / OS type, so far:
+ * -0.955000 on an HP 9000 Model 712/80 HP-UX 9.05
+ * -0.953175 on an HP 9000 Model 370 HP-UX 9.10
+ *
+ * This receiver also provides a 1PPS signal, but I haven't figured out
+ * how to deal with any of the CLK or PPS stuff yet. Stay tuned.
+ *
+ */
+
+/*
+ * Fudge Factors
+ *
+ * Fudge time1 is used to accomodate the timecode serial interface adjustment.
+ * Fudge flag4 can be set to request a receiver status screen summary, which
+ * is recorded in the clockstats file.
+ */
+
+/*
+ * Interface definitions
+ */
+#define DEVICE "/dev/hpgps%d" /* device name and unit */
+#define SPEED232 B9600 /* uart speed (9600 baud) */
+#define PRECISION (-10) /* precision assumed (about 1 ms) */
+#define REFID "GPS\0" /* reference ID */
+#define DESCRIPTION "HP 58503A GPS Time and Frequency Reference Receiver"
+
+#define SMAX 23*80+1 /* for :SYSTEM:PRINT? status screen response */
+
+#define MTZONE 2 /* number of fields in timezone reply */
+#define MTCODET2 12 /* number of fields in timecode format T2 */
+#define NTCODET2 21 /* number of chars to checksum in format T2 */
+
+/*
+ * Tables to compute the day of year from yyyymmdd timecode.
+ * 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};
+
+/*
+ * Unit control structure
+ */
+struct hpgpsunit {
+ int pollcnt; /* poll message counter */
+ int tzhour; /* timezone offset, hours */
+ int tzminute; /* timezone offset, minutes */
+ int linecnt; /* set for expected multiple line responses */
+ char *lastptr; /* pointer to receiver response data */
+ char statscrn[SMAX]; /* receiver status screen buffer */
+};
+
+/*
+ * Function prototypes
+ */
+static int hpgps_start P((int, struct peer *));
+static void hpgps_shutdown P((int, struct peer *));
+static void hpgps_receive P((struct recvbuf *));
+static void hpgps_poll P((int, struct peer *));
+
+/*
+ * Transfer vector
+ */
+struct refclock refclock_hpgps = {
+ hpgps_start, /* start up driver */
+ hpgps_shutdown, /* shut down driver */
+ hpgps_poll, /* transmit poll message */
+ noentry, /* not used (old hpgps_control) */
+ noentry, /* initialize driver */
+ noentry, /* not used (old hpgps_buginfo) */
+ NOFLAGS /* not used */
+};
+
+
+/*
+ * hpgps_start - open the devices and initialize data for processing
+ */
+static int
+hpgps_start(
+ int unit,
+ struct peer *peer
+ )
+{
+ register struct hpgpsunit *up;
+ struct refclockproc *pp;
+ int fd;
+ char device[20];
+
+ /*
+ * Open serial port. Use CLK line discipline, if available.
+ */
+ (void)sprintf(device, DEVICE, unit);
+ if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
+ return (0);
+
+ /*
+ * Allocate and initialize unit structure
+ */
+ if (!(up = (struct hpgpsunit *)
+ emalloc(sizeof(struct hpgpsunit)))) {
+ (void) close(fd);
+ return (0);
+ }
+ memset((char *)up, 0, sizeof(struct hpgpsunit));
+ pp = peer->procptr;
+ pp->io.clock_recv = hpgps_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);
+ up->tzhour = 0;
+ up->tzminute = 0;
+
+ *up->statscrn = '\0';
+ up->lastptr = up->statscrn;
+ up->pollcnt = 2;
+
+ /*
+ * Get the identifier string, which is logged but otherwise ignored,
+ * and get the local timezone information
+ */
+ up->linecnt = 1;
+ if (write(pp->io.fd, "*IDN?\r:PTIME:TZONE?\r", 20) != 20)
+ refclock_report(peer, CEVNT_FAULT);
+
+ return (1);
+}
+
+
+/*
+ * hpgps_shutdown - shut down the clock
+ */
+static void
+hpgps_shutdown(
+ int unit,
+ struct peer *peer
+ )
+{
+ register struct hpgpsunit *up;
+ struct refclockproc *pp;
+
+ pp = peer->procptr;
+ up = (struct hpgpsunit *)pp->unitptr;
+ io_closeclock(&pp->io);
+ free(up);
+}
+
+
+/*
+ * hpgps_receive - receive data from the serial interface
+ */
+static void
+hpgps_receive(
+ struct recvbuf *rbufp
+ )
+{
+ register struct hpgpsunit *up;
+ struct refclockproc *pp;
+ struct peer *peer;
+ l_fp trtmp;
+ char tcodechar1; /* identifies timecode format */
+ char tcodechar2; /* identifies timecode format */
+ char timequal; /* time figure of merit: 0-9 */
+ char freqqual; /* frequency figure of merit: 0-3 */
+ char leapchar; /* leapsecond: + or 0 or - */
+ char servchar; /* request for service: 0 = no, 1 = yes */
+ char syncchar; /* time info is invalid: 0 = no, 1 = yes */
+ short expectedsm; /* expected timecode byte checksum */
+ short tcodechksm; /* computed timecode byte checksum */
+ int i,m,n;
+ int month, day, lastday;
+ char *tcp; /* timecode pointer (skips over the prompt) */
+ char prompt[BMAX]; /* prompt in response from receiver */
+
+ /*
+ * Initialize pointers and read the receiver response
+ */
+ peer = (struct peer *)rbufp->recv_srcclock;
+ pp = peer->procptr;
+ up = (struct hpgpsunit *)pp->unitptr;
+ *pp->a_lastcode = '\0';
+ pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
+
+#ifdef DEBUG
+ if (debug)
+ printf("hpgps: lencode: %d timecode:%s\n",
+ pp->lencode, pp->a_lastcode);
+#endif
+
+ /*
+ * If there's no characters in the reply, we can quit now
+ */
+ if (pp->lencode == 0)
+ return;
+
+ /*
+ * If linecnt is greater than zero, we are getting information only,
+ * such as the receiver identification string or the receiver status
+ * screen, so put the receiver response at the end of the status
+ * screen buffer. When we have the last line, write the buffer to
+ * the clockstats file and return without further processing.
+ *
+ * If linecnt is zero, we are expecting either the timezone
+ * or a timecode. At this point, also write the response
+ * to the clockstats file, and go on to process the prompt (if any),
+ * timezone, or timecode and timestamp.
+ */
+
+
+ if (up->linecnt-- > 0) {
+ if ((int)(pp->lencode + 2) <= (SMAX - (up->lastptr - up->statscrn))) {
+ *up->lastptr++ = '\n';
+ (void)strcpy(up->lastptr, pp->a_lastcode);
+ up->lastptr += pp->lencode;
+ }
+ if (up->linecnt == 0)
+ record_clock_stats(&peer->srcadr, up->statscrn);
+
+ return;
+ }
+
+ record_clock_stats(&peer->srcadr, pp->a_lastcode);
+ pp->lastrec = trtmp;
+
+ up->lastptr = up->statscrn;
+ *up->lastptr = '\0';
+ up->pollcnt = 2;
+
+ /*
+ * We get down to business: get a prompt if one is there, issue
+ * a clear status command if it contains an error indication.
+ * Next, check for either the timezone reply or the timecode reply
+ * and decode it. If we don't recognize the reply, or don't get the
+ * proper number of decoded fields, or get an out of range timezone,
+ * or if the timecode checksum is bad, then we declare bad format
+ * and exit.
+ *
+ * Timezone format (including nominal prompt):
+ * scpi > -H,-M<cr><lf>
+ *
+ * Timecode format (including nominal prompt):
+ * scpi > T2yyyymmddhhmmssMFLRVcc<cr><lf>
+ *
+ */
+
+ (void)strcpy(prompt,pp->a_lastcode);
+ tcp = strrchr(pp->a_lastcode,'>');
+ if (tcp == NULL)
+ tcp = pp->a_lastcode;
+ else
+ tcp++;
+ prompt[tcp - pp->a_lastcode] = '\0';
+ while ((*tcp == ' ') || (*tcp == '\t')) tcp++;
+
+ /*
+ * deal with an error indication in the prompt here
+ */
+ if (strrchr(prompt,'E') > strrchr(prompt,'s')){
+#ifdef DEBUG
+ if (debug)
+ printf("hpgps: error indicated in prompt: %s\n", prompt);
+#endif
+ if (write(pp->io.fd, "*CLS\r\r", 6) != 6)
+ refclock_report(peer, CEVNT_FAULT);
+ }
+
+ /*
+ * make sure we got a timezone or timecode format and
+ * then process accordingly
+ */
+ m = sscanf(tcp,"%c%c", &tcodechar1, &tcodechar2);
+
+ if (m != 2){
+#ifdef DEBUG
+ if (debug)
+ printf("hpgps: no format indicator\n");
+#endif
+ refclock_report(peer, CEVNT_BADREPLY);
+ return;
+ }
+
+ switch (tcodechar1) {
+
+ case '+':
+ case '-':
+ m = sscanf(tcp,"%d,%d", &up->tzhour, &up->tzminute);
+ if (m != MTZONE) {
+#ifdef DEBUG
+ if (debug)
+ printf("hpgps: only %d fields recognized in timezone\n", m);
+#endif
+ refclock_report(peer, CEVNT_BADREPLY);
+ return;
+ }
+ if ((up->tzhour < -12) || (up->tzhour > 13) ||
+ (up->tzminute < -59) || (up->tzminute > 59)){
+#ifdef DEBUG
+ if (debug)
+ printf("hpgps: timezone %d, %d out of range\n",
+ up->tzhour, up->tzminute);
+#endif
+ refclock_report(peer, CEVNT_BADREPLY);
+ return;
+ }
+ return;
+
+ case 'T':
+ break;
+
+ default:
+#ifdef DEBUG
+ if (debug)
+ printf("hpgps: unrecognized reply format %c%c\n",
+ tcodechar1, tcodechar2);
+#endif
+ refclock_report(peer, CEVNT_BADREPLY);
+ return;
+ } /* end of tcodechar1 switch */
+
+
+ switch (tcodechar2) {
+
+ case '2':
+ m = sscanf(tcp,"%*c%*c%4d%2d%2d%2d%2d%2d%c%c%c%c%c%2hx",
+ &pp->year, &month, &day, &pp->hour, &pp->minute, &pp->second,
+ &timequal, &freqqual, &leapchar, &servchar, &syncchar,
+ &expectedsm);
+ n = NTCODET2;
+
+ if (m != MTCODET2){
+#ifdef DEBUG
+ if (debug)
+ printf("hpgps: only %d fields recognized in timecode\n", m);
+#endif
+ refclock_report(peer, CEVNT_BADREPLY);
+ return;
+ }
+ break;
+
+ default:
+#ifdef DEBUG
+ if (debug)
+ printf("hpgps: unrecognized timecode format %c%c\n",
+ tcodechar1, tcodechar2);
+#endif
+ refclock_report(peer, CEVNT_BADREPLY);
+ return;
+ } /* end of tcodechar2 format switch */
+
+ /*
+ * Compute and verify the checksum.
+ * Characters are summed starting at tcodechar1, ending at just
+ * before the expected checksum. Bail out if incorrect.
+ */
+ tcodechksm = 0;
+ while (n-- > 0) tcodechksm += *tcp++;
+ tcodechksm &= 0x00ff;
+
+ if (tcodechksm != expectedsm) {
+#ifdef DEBUG
+ if (debug)
+ printf("hpgps: checksum %2hX doesn't match %2hX expected\n",
+ tcodechksm, expectedsm);
+#endif
+ refclock_report(peer, CEVNT_BADREPLY);
+ return;
+ }
+
+ /*
+ * Compute the day of year from the yyyymmdd format.
+ */
+ if (month < 1 || month > 12 || day < 1) {
+ refclock_report(peer, CEVNT_BADTIME);
+ return;
+ }
+
+ if ( ! isleap_4(pp->year) ) { /* Y2KFixes */
+ /* not a leap year */
+ if (day > day1tab[month - 1]) {
+ refclock_report(peer, CEVNT_BADTIME);
+ return;
+ }
+ for (i = 0; i < month - 1; i++) day += day1tab[i];
+ lastday = 365;
+ } else {
+ /* a leap year */
+ if (day > day2tab[month - 1]) {
+ refclock_report(peer, CEVNT_BADTIME);
+ return;
+ }
+ for (i = 0; i < month - 1; i++) day += day2tab[i];
+ lastday = 366;
+ }
+
+ /*
+ * Deal with the timezone offset here. The receiver timecode is in
+ * local time = UTC + :PTIME:TZONE, so SUBTRACT the timezone values.
+ * For example, Pacific Standard Time is -8 hours , 0 minutes.
+ * Deal with the underflows and overflows.
+ */
+ pp->minute -= up->tzminute;
+ pp->hour -= up->tzhour;
+
+ if (pp->minute < 0) {
+ pp->minute += 60;
+ pp->hour--;
+ }
+ if (pp->minute > 59) {
+ pp->minute -= 60;
+ pp->hour++;
+ }
+ if (pp->hour < 0) {
+ pp->hour += 24;
+ day--;
+ if (day < 1) {
+ pp->year--;
+ if ( isleap_4(pp->year) ) /* Y2KFixes */
+ day = 366;
+ else
+ day = 365;
+ }
+ }
+
+ if (pp->hour > 23) {
+ pp->hour -= 24;
+ day++;
+ if (day > lastday) {
+ pp->year++;
+ day = 1;
+ }
+ }
+
+ pp->day = day;
+
+ /*
+ * Decode the MFLRV indicators.
+ * NEED TO FIGURE OUT how to deal with the request for service,
+ * time quality, and frequency quality indicators some day.
+ */
+ if (syncchar != '0') {
+ pp->leap = LEAP_NOTINSYNC;
+ }
+ else {
+ switch (leapchar) {
+
+ case '+':
+ pp->leap = LEAP_ADDSECOND;
+ break;
+
+ case '0':
+ pp->leap = LEAP_NOWARNING;
+ break;
+
+ case '-':
+ pp->leap = LEAP_DELSECOND;
+ break;
+
+ default:
+#ifdef DEBUG
+ if (debug)
+ printf("hpgps: unrecognized leap indicator: %c\n",
+ leapchar);
+#endif
+ refclock_report(peer, CEVNT_BADTIME);
+ return;
+ } /* end of leapchar switch */
+ }
+
+ /*
+ * Process the new sample in the median filter and determine the
+ * reference clock offset and dispersion. 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.
+ */
+ if (!refclock_process(pp)) {
+ refclock_report(peer, CEVNT_BADTIME);
+ return;
+ }
+ refclock_receive(peer);
+
+ /*
+ * If CLK_FLAG4 is set, ask for the status screen response.
+ */
+ if (pp->sloppyclockflag & CLK_FLAG4){
+ up->linecnt = 22;
+ if (write(pp->io.fd, ":SYSTEM:PRINT?\r", 15) != 15)
+ refclock_report(peer, CEVNT_FAULT);
+ }
+}
+
+
+/*
+ * hpgps_poll - called by the transmit procedure
+ */
+static void
+hpgps_poll(
+ int unit,
+ struct peer *peer
+ )
+{
+ register struct hpgpsunit *up;
+ struct refclockproc *pp;
+
+ /*
+ * Time to poll the clock. The HP 58503A responds to a
+ * ":PTIME:TCODE?" by returning a timecode in the format specified
+ * above. If nothing is heard from the clock for two polls,
+ * declare a timeout and keep going.
+ */
+ pp = peer->procptr;
+ up = (struct hpgpsunit *)pp->unitptr;
+ if (up->pollcnt == 0)
+ refclock_report(peer, CEVNT_TIMEOUT);
+ else
+ up->pollcnt--;
+ if (write(pp->io.fd, ":PTIME:TCODE?\r", 14) != 14) {
+ refclock_report(peer, CEVNT_FAULT);
+ }
+ else
+ pp->polls++;
+}
+
+#else
+int refclock_hpgps_bs;
+#endif /* REFCLOCK */
OpenPOWER on IntegriCloud