diff options
Diffstat (limited to 'ntpd/refclock_hpgps.c')
-rw-r--r-- | ntpd/refclock_hpgps.c | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/ntpd/refclock_hpgps.c b/ntpd/refclock_hpgps.c new file mode 100644 index 0000000..7c801bb --- /dev/null +++ b/ntpd/refclock_hpgps.c @@ -0,0 +1,604 @@ +/* + * refclock_hpgps - clock driver for HP 58503A GPS receiver + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_HPGPS) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.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; + } + pp->lastref = pp->lastrec; + 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 */ |