refclock_hpgps.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* Copyright (c) 1996 by Sun Microsystems, Inc.
* All Rights Reserved.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* refclock_hpgps - clock driver for HP 58503A GPS receiver
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <ctype.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.
*
* 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 DESCRIPTION "HP 58503A GPS Time and Frequency Reference Receiver"
/*
* Imported from ntp_timer module
*/
/*
* Imported from ntpd module
*/
extern int debug; /* global debug flag */
/*
* Tables to compute the day of year from yyyymmdd timecode.
* Viva la leap.
*/
/*
* 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 */
};
/*
* 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
int unit;
{
struct refclockproc *pp;
int fd;
char device[20];
/*
* Open serial port. Use CLK line discipline, if available.
*/
#ifdef TTYCLK
#else
#endif /* TTYCLK */
return (0);
/*
* Allocate and initialize unit structure
*/
return (0);
}
return (0);
}
/*
* Initialize miscellaneous variables
*/
/*
* Get the identifier string, which is logged but otherwise ignored,
* and get the local timezone information
*/
return (1);
}
/*
* hpgps_shutdown - shut down the clock
*/
static void
int unit;
{
struct refclockproc *pp;
}
/*
* hpgps_receive - receive data from the serial interface
*/
static void
{
struct refclockproc *pp;
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;
char *tcp; /* timecode pointer (skips over the prompt) */
/*
* Initialize pointers and read the receiver response
*/
#ifdef DEBUG
if (debug)
printf("hpgps: lencode: %d timecode:%s\n",
#endif
/*
* If there's no characters in the reply, we can quit now
*/
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.
*/
}
return;
}
/*
* 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>
*
*/
else
tcp++;
/*
* deal with an error indication in the prompt here
*/
#ifdef DEBUG
if (debug)
#endif
}
/*
* make sure we got a timezone or timecode format and
* then process accordingly
*/
if (m != 2){
#ifdef DEBUG
if (debug)
printf("hpgps: no format indicator\n");
#endif
return;
}
switch (tcodechar1) {
case '+':
case '-':
if (m != MTZONE) {
#ifdef DEBUG
if (debug)
printf("hpgps: only %d fields recognized in timezone\n", m);
#endif
return;
}
#ifdef DEBUG
if (debug)
printf("hpgps: timezone %d, %d out of range\n",
#endif
return;
}
return;
case 'T':
break;
default:
#ifdef DEBUG
if (debug)
printf("hpgps: unrecognized reply format %c%c\n",
#endif
return;
} /* end of tcodechar1 switch */
switch (tcodechar2) {
case '2':
&expectedsm);
n = NTCODET2;
if (m != MTCODET2){
#ifdef DEBUG
if (debug)
printf("hpgps: only %d fields recognized in timecode\n", m);
#endif
return;
}
break;
default:
#ifdef DEBUG
if (debug)
printf("hpgps: unrecognized timecode format %c%c\n",
#endif
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",
#endif
return;
}
/*
* Compute the day of year from the yyyymmdd format.
* Exception noted for year 2000.
*/
return;
}
/* not a leap year */
return;
}
lastday = 365;
} else {
/* a leap year */
return;
}
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.
*/
}
}
day--;
if (day < 1) {
day = 365;
else
day = 366;
}
}
day++;
day = 1;
}
}
/*
* 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') {
}
else {
switch (leapchar) {
case '+':
break;
case '0':
break;
case '-':
break;
default:
#ifdef DEBUG
if (debug)
printf("hpgps: unrecognized leap indicator: %c\n",
leapchar);
#endif
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.
*/
return;
}
#ifdef DEBUG
if (debug)
printf("hpgps: refclock_rcv: off: %s, disp: %s, tref: %s, trec: %s, lp: %hd\n",
#endif
/*
* If CLK_FLAG4 is set, ask for the status screen response.
*/
}
}
/*
* hpgps_poll - called by the transmit procedure
*/
static void
int unit;
{
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.
*/
else
}
else
}
#else /* not (REFCLOCK && HPGPS) */
int refclock_hpgps_bs;
#endif /* not (REFCLOCK && HPGPS) */