2 * refclock_hpgps - clock driver for HP 58503A GPS receiver
9 #if defined(REFCLOCK) && defined(CLOCK_HPGPS)
13 #include "ntp_refclock.h"
14 #include "ntp_stdlib.h"
19 /* Version 0.1 April 1, 1995
21 * tolerant of missing timecode response prompt and sends
22 * clear status if prompt indicates error;
23 * can use either local time or UTC from receiver;
24 * can get receiver status screen via flag4
26 * WARNING!: This driver is UNDER CONSTRUCTION
27 * Everything in here should be treated with suspicion.
28 * If it looks wrong, it probably is.
30 * Comments and/or questions to: Dave Vitanye
31 * Hewlett Packard Company
35 * Thanks to the author of the PST driver, which was the starting point for
38 * This driver supports the HP 58503A Time and Frequency Reference Receiver.
39 * This receiver uses HP SmartClock (TM) to implement an Enhanced GPS receiver.
40 * The receiver accuracy when locked to GPS in normal operation is better
41 * than 1 usec. The accuracy when operating in holdover is typically better
42 * than 10 usec. per day.
44 * The receiver should be operated with factory default settings.
45 * Initial driver operation: expects the receiver to be already locked
46 * to GPS, configured and able to output timecode format 2 messages.
48 * The driver uses the poll sequence :PTIME:TCODE? to get a response from
49 * the receiver. The receiver responds with a timecode string of ASCII
50 * printing characters, followed by a <cr><lf>, followed by a prompt string
51 * issued by the receiver, in the following format:
52 * T#yyyymmddhhmmssMFLRVcc<cr><lf>scpi >
54 * The driver processes the response at the <cr> and <lf>, so what the
55 * driver sees is the prompt from the previous poll, followed by this
56 * timecode. The prompt from the current poll is (usually) left unread until
57 * the next poll. So (except on the very first poll) the driver sees this:
59 * scpi > T#yyyymmddhhmmssMFLRVcc<cr><lf>
61 * The T is the on-time character, at 980 msec. before the next 1PPS edge.
62 * The # is the timecode format type. We look for format 2.
63 * Without any of the CLK or PPS stuff, then, the receiver buffer timestamp
64 * at the <cr> is 24 characters later, which is about 25 msec. at 9600 bps,
65 * so the first approximation for fudge time1 is nominally -0.955 seconds.
66 * This number probably needs adjusting for each machine / OS type, so far:
67 * -0.955000 on an HP 9000 Model 712/80 HP-UX 9.05
68 * -0.953175 on an HP 9000 Model 370 HP-UX 9.10
70 * This receiver also provides a 1PPS signal, but I haven't figured out
71 * how to deal with any of the CLK or PPS stuff yet. Stay tuned.
78 * Fudge time1 is used to accomodate the timecode serial interface adjustment.
79 * Fudge flag4 can be set to request a receiver status screen summary, which
80 * is recorded in the clockstats file.
84 * Interface definitions
86 #define DEVICE "/dev/hpgps%d" /* device name and unit */
87 #define SPEED232 B9600 /* uart speed (9600 baud) */
88 #define PRECISION (-10) /* precision assumed (about 1 ms) */
89 #define REFID "GPS\0" /* reference ID */
90 #define DESCRIPTION "HP 58503A GPS Time and Frequency Reference Receiver"
92 #define SMAX 23*80+1 /* for :SYSTEM:PRINT? status screen response */
94 #define MTZONE 2 /* number of fields in timezone reply */
95 #define MTCODET2 12 /* number of fields in timecode format T2 */
96 #define NTCODET2 21 /* number of chars to checksum in format T2 */
99 * Tables to compute the day of year from yyyymmdd timecode.
102 static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
103 static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
106 * Unit control structure
109 int pollcnt; /* poll message counter */
110 int tzhour; /* timezone offset, hours */
111 int tzminute; /* timezone offset, minutes */
112 int linecnt; /* set for expected multiple line responses */
113 char *lastptr; /* pointer to receiver response data */
114 char statscrn[SMAX]; /* receiver status screen buffer */
118 * Function prototypes
120 static int hpgps_start P((int, struct peer *));
121 static void hpgps_shutdown P((int, struct peer *));
122 static void hpgps_receive P((struct recvbuf *));
123 static void hpgps_poll P((int, struct peer *));
128 struct refclock refclock_hpgps = {
129 hpgps_start, /* start up driver */
130 hpgps_shutdown, /* shut down driver */
131 hpgps_poll, /* transmit poll message */
132 noentry, /* not used (old hpgps_control) */
133 noentry, /* initialize driver */
134 noentry, /* not used (old hpgps_buginfo) */
135 NOFLAGS /* not used */
140 * hpgps_start - open the devices and initialize data for processing
148 register struct hpgpsunit *up;
149 struct refclockproc *pp;
154 * Open serial port. Use CLK line discipline, if available.
156 (void)sprintf(device, DEVICE, unit);
157 if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
161 * Allocate and initialize unit structure
163 if (!(up = (struct hpgpsunit *)
164 emalloc(sizeof(struct hpgpsunit)))) {
168 memset((char *)up, 0, sizeof(struct hpgpsunit));
170 pp->io.clock_recv = hpgps_receive;
171 pp->io.srcclock = (caddr_t)peer;
174 if (!io_addclock(&pp->io)) {
179 pp->unitptr = (caddr_t)up;
182 * Initialize miscellaneous variables
184 peer->precision = PRECISION;
185 pp->clockdesc = DESCRIPTION;
186 memcpy((char *)&pp->refid, REFID, 4);
190 *up->statscrn = '\0';
191 up->lastptr = up->statscrn;
195 * Get the identifier string, which is logged but otherwise ignored,
196 * and get the local timezone information
199 if (write(pp->io.fd, "*IDN?\r:PTIME:TZONE?\r", 20) != 20)
200 refclock_report(peer, CEVNT_FAULT);
207 * hpgps_shutdown - shut down the clock
215 register struct hpgpsunit *up;
216 struct refclockproc *pp;
219 up = (struct hpgpsunit *)pp->unitptr;
220 io_closeclock(&pp->io);
226 * hpgps_receive - receive data from the serial interface
230 struct recvbuf *rbufp
233 register struct hpgpsunit *up;
234 struct refclockproc *pp;
237 char tcodechar1; /* identifies timecode format */
238 char tcodechar2; /* identifies timecode format */
239 char timequal; /* time figure of merit: 0-9 */
240 char freqqual; /* frequency figure of merit: 0-3 */
241 char leapchar; /* leapsecond: + or 0 or - */
242 char servchar; /* request for service: 0 = no, 1 = yes */
243 char syncchar; /* time info is invalid: 0 = no, 1 = yes */
244 short expectedsm; /* expected timecode byte checksum */
245 short tcodechksm; /* computed timecode byte checksum */
247 int month, day, lastday;
248 char *tcp; /* timecode pointer (skips over the prompt) */
249 char prompt[BMAX]; /* prompt in response from receiver */
252 * Initialize pointers and read the receiver response
254 peer = (struct peer *)rbufp->recv_srcclock;
256 up = (struct hpgpsunit *)pp->unitptr;
257 *pp->a_lastcode = '\0';
258 pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
262 printf("hpgps: lencode: %d timecode:%s\n",
263 pp->lencode, pp->a_lastcode);
267 * If there's no characters in the reply, we can quit now
269 if (pp->lencode == 0)
273 * If linecnt is greater than zero, we are getting information only,
274 * such as the receiver identification string or the receiver status
275 * screen, so put the receiver response at the end of the status
276 * screen buffer. When we have the last line, write the buffer to
277 * the clockstats file and return without further processing.
279 * If linecnt is zero, we are expecting either the timezone
280 * or a timecode. At this point, also write the response
281 * to the clockstats file, and go on to process the prompt (if any),
282 * timezone, or timecode and timestamp.
286 if (up->linecnt-- > 0) {
287 if ((int)(pp->lencode + 2) <= (SMAX - (up->lastptr - up->statscrn))) {
288 *up->lastptr++ = '\n';
289 (void)strcpy(up->lastptr, pp->a_lastcode);
290 up->lastptr += pp->lencode;
292 if (up->linecnt == 0)
293 record_clock_stats(&peer->srcadr, up->statscrn);
298 record_clock_stats(&peer->srcadr, pp->a_lastcode);
301 up->lastptr = up->statscrn;
306 * We get down to business: get a prompt if one is there, issue
307 * a clear status command if it contains an error indication.
308 * Next, check for either the timezone reply or the timecode reply
309 * and decode it. If we don't recognize the reply, or don't get the
310 * proper number of decoded fields, or get an out of range timezone,
311 * or if the timecode checksum is bad, then we declare bad format
314 * Timezone format (including nominal prompt):
315 * scpi > -H,-M<cr><lf>
317 * Timecode format (including nominal prompt):
318 * scpi > T2yyyymmddhhmmssMFLRVcc<cr><lf>
322 (void)strcpy(prompt,pp->a_lastcode);
323 tcp = strrchr(pp->a_lastcode,'>');
325 tcp = pp->a_lastcode;
328 prompt[tcp - pp->a_lastcode] = '\0';
329 while ((*tcp == ' ') || (*tcp == '\t')) tcp++;
332 * deal with an error indication in the prompt here
334 if (strrchr(prompt,'E') > strrchr(prompt,'s')){
337 printf("hpgps: error indicated in prompt: %s\n", prompt);
339 if (write(pp->io.fd, "*CLS\r\r", 6) != 6)
340 refclock_report(peer, CEVNT_FAULT);
344 * make sure we got a timezone or timecode format and
345 * then process accordingly
347 m = sscanf(tcp,"%c%c", &tcodechar1, &tcodechar2);
352 printf("hpgps: no format indicator\n");
354 refclock_report(peer, CEVNT_BADREPLY);
358 switch (tcodechar1) {
362 m = sscanf(tcp,"%d,%d", &up->tzhour, &up->tzminute);
366 printf("hpgps: only %d fields recognized in timezone\n", m);
368 refclock_report(peer, CEVNT_BADREPLY);
371 if ((up->tzhour < -12) || (up->tzhour > 13) ||
372 (up->tzminute < -59) || (up->tzminute > 59)){
375 printf("hpgps: timezone %d, %d out of range\n",
376 up->tzhour, up->tzminute);
378 refclock_report(peer, CEVNT_BADREPLY);
389 printf("hpgps: unrecognized reply format %c%c\n",
390 tcodechar1, tcodechar2);
392 refclock_report(peer, CEVNT_BADREPLY);
394 } /* end of tcodechar1 switch */
397 switch (tcodechar2) {
400 m = sscanf(tcp,"%*c%*c%4d%2d%2d%2d%2d%2d%c%c%c%c%c%2hx",
401 &pp->year, &month, &day, &pp->hour, &pp->minute, &pp->second,
402 &timequal, &freqqual, &leapchar, &servchar, &syncchar,
409 printf("hpgps: only %d fields recognized in timecode\n", m);
411 refclock_report(peer, CEVNT_BADREPLY);
419 printf("hpgps: unrecognized timecode format %c%c\n",
420 tcodechar1, tcodechar2);
422 refclock_report(peer, CEVNT_BADREPLY);
424 } /* end of tcodechar2 format switch */
427 * Compute and verify the checksum.
428 * Characters are summed starting at tcodechar1, ending at just
429 * before the expected checksum. Bail out if incorrect.
432 while (n-- > 0) tcodechksm += *tcp++;
433 tcodechksm &= 0x00ff;
435 if (tcodechksm != expectedsm) {
438 printf("hpgps: checksum %2hX doesn't match %2hX expected\n",
439 tcodechksm, expectedsm);
441 refclock_report(peer, CEVNT_BADREPLY);
446 * Compute the day of year from the yyyymmdd format.
448 if (month < 1 || month > 12 || day < 1) {
449 refclock_report(peer, CEVNT_BADTIME);
453 if ( ! isleap_4(pp->year) ) { /* Y2KFixes */
454 /* not a leap year */
455 if (day > day1tab[month - 1]) {
456 refclock_report(peer, CEVNT_BADTIME);
459 for (i = 0; i < month - 1; i++) day += day1tab[i];
463 if (day > day2tab[month - 1]) {
464 refclock_report(peer, CEVNT_BADTIME);
467 for (i = 0; i < month - 1; i++) day += day2tab[i];
472 * Deal with the timezone offset here. The receiver timecode is in
473 * local time = UTC + :PTIME:TZONE, so SUBTRACT the timezone values.
474 * For example, Pacific Standard Time is -8 hours , 0 minutes.
475 * Deal with the underflows and overflows.
477 pp->minute -= up->tzminute;
478 pp->hour -= up->tzhour;
480 if (pp->minute < 0) {
484 if (pp->minute > 59) {
493 if ( isleap_4(pp->year) ) /* Y2KFixes */
512 * Decode the MFLRV indicators.
513 * NEED TO FIGURE OUT how to deal with the request for service,
514 * time quality, and frequency quality indicators some day.
516 if (syncchar != '0') {
517 pp->leap = LEAP_NOTINSYNC;
523 pp->leap = LEAP_ADDSECOND;
527 pp->leap = LEAP_NOWARNING;
531 pp->leap = LEAP_DELSECOND;
537 printf("hpgps: unrecognized leap indicator: %c\n",
540 refclock_report(peer, CEVNT_BADTIME);
542 } /* end of leapchar switch */
546 * Process the new sample in the median filter and determine the
547 * reference clock offset and dispersion. We use lastrec as both
548 * the reference time and receive time in order to avoid being
549 * cute, like setting the reference time later than the receive
550 * time, which may cause a paranoid protocol module to chuck out
553 if (!refclock_process(pp)) {
554 refclock_report(peer, CEVNT_BADTIME);
557 refclock_receive(peer);
560 * If CLK_FLAG4 is set, ask for the status screen response.
562 if (pp->sloppyclockflag & CLK_FLAG4){
564 if (write(pp->io.fd, ":SYSTEM:PRINT?\r", 15) != 15)
565 refclock_report(peer, CEVNT_FAULT);
571 * hpgps_poll - called by the transmit procedure
579 register struct hpgpsunit *up;
580 struct refclockproc *pp;
583 * Time to poll the clock. The HP 58503A responds to a
584 * ":PTIME:TCODE?" by returning a timecode in the format specified
585 * above. If nothing is heard from the clock for two polls,
586 * declare a timeout and keep going.
589 up = (struct hpgpsunit *)pp->unitptr;
590 if (up->pollcnt == 0)
591 refclock_report(peer, CEVNT_TIMEOUT);
594 if (write(pp->io.fd, ":PTIME:TCODE?\r", 14) != 14) {
595 refclock_report(peer, CEVNT_FAULT);
602 int refclock_hpgps_bs;
603 #endif /* REFCLOCK */