1 /* $NetBSD: refclock_hpgps.c,v 1.2 2003/12/04 16:23:37 drochner Exp $ */
4 * refclock_hpgps - clock driver for HP 58503A GPS receiver
11 #if defined(REFCLOCK) && defined(CLOCK_HPGPS)
15 #include "ntp_refclock.h"
16 #include "ntp_stdlib.h"
21 /* Version 0.1 April 1, 1995
23 * tolerant of missing timecode response prompt and sends
24 * clear status if prompt indicates error;
25 * can use either local time or UTC from receiver;
26 * can get receiver status screen via flag4
28 * WARNING!: This driver is UNDER CONSTRUCTION
29 * Everything in here should be treated with suspicion.
30 * If it looks wrong, it probably is.
32 * Comments and/or questions to: Dave Vitanye
33 * Hewlett Packard Company
37 * Thanks to the author of the PST driver, which was the starting point for
40 * This driver supports the HP 58503A Time and Frequency Reference Receiver.
41 * This receiver uses HP SmartClock (TM) to implement an Enhanced GPS receiver.
42 * The receiver accuracy when locked to GPS in normal operation is better
43 * than 1 usec. The accuracy when operating in holdover is typically better
44 * than 10 usec. per day.
46 * The same driver also handles the HP Z3801A which is available surplus
47 * from the cell phone industry. It's popular with hams.
48 * It needs a different line setup: 19200 baud, 7 data bits, odd parity
49 * That is selected by adding "mode 1" to the server line in ntp.conf
50 * HP Z3801A code from Jeff Mock added by Hal Murray, Sep 2005
53 * The receiver should be operated with factory default settings.
54 * Initial driver operation: expects the receiver to be already locked
55 * to GPS, configured and able to output timecode format 2 messages.
57 * The driver uses the poll sequence :PTIME:TCODE? to get a response from
58 * the receiver. The receiver responds with a timecode string of ASCII
59 * printing characters, followed by a <cr><lf>, followed by a prompt string
60 * issued by the receiver, in the following format:
61 * T#yyyymmddhhmmssMFLRVcc<cr><lf>scpi >
63 * The driver processes the response at the <cr> and <lf>, so what the
64 * driver sees is the prompt from the previous poll, followed by this
65 * timecode. The prompt from the current poll is (usually) left unread until
66 * the next poll. So (except on the very first poll) the driver sees this:
68 * scpi > T#yyyymmddhhmmssMFLRVcc<cr><lf>
70 * The T is the on-time character, at 980 msec. before the next 1PPS edge.
71 * The # is the timecode format type. We look for format 2.
72 * Without any of the CLK or PPS stuff, then, the receiver buffer timestamp
73 * at the <cr> is 24 characters later, which is about 25 msec. at 9600 bps,
74 * so the first approximation for fudge time1 is nominally -0.955 seconds.
75 * This number probably needs adjusting for each machine / OS type, so far:
76 * -0.955000 on an HP 9000 Model 712/80 HP-UX 9.05
77 * -0.953175 on an HP 9000 Model 370 HP-UX 9.10
79 * This receiver also provides a 1PPS signal, but I haven't figured out
80 * how to deal with any of the CLK or PPS stuff yet. Stay tuned.
87 * Fudge time1 is used to accomodate the timecode serial interface adjustment.
88 * Fudge flag4 can be set to request a receiver status screen summary, which
89 * is recorded in the clockstats file.
93 * Interface definitions
95 #define DEVICE "/dev/hpgps%d" /* device name and unit */
96 #define SPEED232 B9600 /* uart speed (9600 baud) */
97 #define SPEED232Z B19200 /* uart speed (19200 baud) */
98 #define PRECISION (-10) /* precision assumed (about 1 ms) */
99 #define REFID "GPS\0" /* reference ID */
100 #define DESCRIPTION "HP 58503A GPS Time and Frequency Reference Receiver"
102 #define SMAX 23*80+1 /* for :SYSTEM:PRINT? status screen response */
104 #define MTZONE 2 /* number of fields in timezone reply */
105 #define MTCODET2 12 /* number of fields in timecode format T2 */
106 #define NTCODET2 21 /* number of chars to checksum in format T2 */
109 * Tables to compute the day of year from yyyymmdd timecode.
112 static int day1tab
[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
113 static int day2tab
[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
116 * Unit control structure
119 int pollcnt
; /* poll message counter */
120 int tzhour
; /* timezone offset, hours */
121 int tzminute
; /* timezone offset, minutes */
122 int linecnt
; /* set for expected multiple line responses */
123 char *lastptr
; /* pointer to receiver response data */
124 char statscrn
[SMAX
]; /* receiver status screen buffer */
128 * Function prototypes
130 static int hpgps_start
P((int, struct peer
*));
131 static void hpgps_shutdown
P((int, struct peer
*));
132 static void hpgps_receive
P((struct recvbuf
*));
133 static void hpgps_poll
P((int, struct peer
*));
138 struct refclock refclock_hpgps
= {
139 hpgps_start
, /* start up driver */
140 hpgps_shutdown
, /* shut down driver */
141 hpgps_poll
, /* transmit poll message */
142 noentry
, /* not used (old hpgps_control) */
143 noentry
, /* initialize driver */
144 noentry
, /* not used (old hpgps_buginfo) */
145 NOFLAGS
/* not used */
150 * hpgps_start - open the devices and initialize data for processing
158 register struct hpgpsunit
*up
;
159 struct refclockproc
*pp
;
164 * Open serial port. Use CLK line discipline, if available.
165 * Default is HP 58503A, mode arg selects HP Z3801A
167 (void)sprintf(device
, DEVICE
, unit
);
168 /* mode parameter to server config line shares ttl slot */
169 if ((peer
->ttl
== 1)) {
170 if (!(fd
= refclock_open(device
, SPEED232Z
,
171 LDISC_CLK
| LDISC_7O1
)))
174 if (!(fd
= refclock_open(device
, SPEED232
, LDISC_CLK
)))
178 * Allocate and initialize unit structure
180 if (!(up
= (struct hpgpsunit
*)
181 emalloc(sizeof(struct hpgpsunit
)))) {
185 memset((char *)up
, 0, sizeof(struct hpgpsunit
));
187 pp
->io
.clock_recv
= hpgps_receive
;
188 pp
->io
.srcclock
= (caddr_t
)peer
;
191 if (!io_addclock(&pp
->io
)) {
196 pp
->unitptr
= (caddr_t
)up
;
199 * Initialize miscellaneous variables
201 peer
->precision
= PRECISION
;
202 pp
->clockdesc
= DESCRIPTION
;
203 memcpy((char *)&pp
->refid
, REFID
, 4);
207 *up
->statscrn
= '\0';
208 up
->lastptr
= up
->statscrn
;
212 * Get the identifier string, which is logged but otherwise ignored,
213 * and get the local timezone information
216 if (write(pp
->io
.fd
, "*IDN?\r:PTIME:TZONE?\r", 20) != 20)
217 refclock_report(peer
, CEVNT_FAULT
);
224 * hpgps_shutdown - shut down the clock
232 register struct hpgpsunit
*up
;
233 struct refclockproc
*pp
;
236 up
= (struct hpgpsunit
*)pp
->unitptr
;
237 io_closeclock(&pp
->io
);
243 * hpgps_receive - receive data from the serial interface
247 struct recvbuf
*rbufp
250 register struct hpgpsunit
*up
;
251 struct refclockproc
*pp
;
254 char tcodechar1
; /* identifies timecode format */
255 char tcodechar2
; /* identifies timecode format */
256 char timequal
; /* time figure of merit: 0-9 */
257 char freqqual
; /* frequency figure of merit: 0-3 */
258 char leapchar
; /* leapsecond: + or 0 or - */
259 char servchar
; /* request for service: 0 = no, 1 = yes */
260 char syncchar
; /* time info is invalid: 0 = no, 1 = yes */
261 short expectedsm
; /* expected timecode byte checksum */
262 short tcodechksm
; /* computed timecode byte checksum */
264 int month
, day
, lastday
;
265 char *tcp
; /* timecode pointer (skips over the prompt) */
266 char prompt
[BMAX
]; /* prompt in response from receiver */
269 * Initialize pointers and read the receiver response
271 peer
= (struct peer
*)rbufp
->recv_srcclock
;
273 up
= (struct hpgpsunit
*)pp
->unitptr
;
274 *pp
->a_lastcode
= '\0';
275 pp
->lencode
= refclock_gtlin(rbufp
, pp
->a_lastcode
, BMAX
, &trtmp
);
279 printf("hpgps: lencode: %d timecode:%s\n",
280 pp
->lencode
, pp
->a_lastcode
);
284 * If there's no characters in the reply, we can quit now
286 if (pp
->lencode
== 0)
290 * If linecnt is greater than zero, we are getting information only,
291 * such as the receiver identification string or the receiver status
292 * screen, so put the receiver response at the end of the status
293 * screen buffer. When we have the last line, write the buffer to
294 * the clockstats file and return without further processing.
296 * If linecnt is zero, we are expecting either the timezone
297 * or a timecode. At this point, also write the response
298 * to the clockstats file, and go on to process the prompt (if any),
299 * timezone, or timecode and timestamp.
303 if (up
->linecnt
-- > 0) {
304 if ((int)(pp
->lencode
+ 2) <= (SMAX
- (up
->lastptr
- up
->statscrn
))) {
305 *up
->lastptr
++ = '\n';
306 (void)strcpy(up
->lastptr
, pp
->a_lastcode
);
307 up
->lastptr
+= pp
->lencode
;
309 if (up
->linecnt
== 0)
310 record_clock_stats(&peer
->srcadr
, up
->statscrn
);
315 record_clock_stats(&peer
->srcadr
, pp
->a_lastcode
);
318 up
->lastptr
= up
->statscrn
;
323 * We get down to business: get a prompt if one is there, issue
324 * a clear status command if it contains an error indication.
325 * Next, check for either the timezone reply or the timecode reply
326 * and decode it. If we don't recognize the reply, or don't get the
327 * proper number of decoded fields, or get an out of range timezone,
328 * or if the timecode checksum is bad, then we declare bad format
331 * Timezone format (including nominal prompt):
332 * scpi > -H,-M<cr><lf>
334 * Timecode format (including nominal prompt):
335 * scpi > T2yyyymmddhhmmssMFLRVcc<cr><lf>
339 (void)strcpy(prompt
,pp
->a_lastcode
);
340 tcp
= strrchr(pp
->a_lastcode
,'>');
342 tcp
= pp
->a_lastcode
;
345 prompt
[tcp
- pp
->a_lastcode
] = '\0';
346 while ((*tcp
== ' ') || (*tcp
== '\t')) tcp
++;
349 * deal with an error indication in the prompt here
351 if (strrchr(prompt
,'E') > strrchr(prompt
,'s')){
354 printf("hpgps: error indicated in prompt: %s\n", prompt
);
356 if (write(pp
->io
.fd
, "*CLS\r\r", 6) != 6)
357 refclock_report(peer
, CEVNT_FAULT
);
361 * make sure we got a timezone or timecode format and
362 * then process accordingly
364 m
= sscanf(tcp
,"%c%c", &tcodechar1
, &tcodechar2
);
369 printf("hpgps: no format indicator\n");
371 refclock_report(peer
, CEVNT_BADREPLY
);
375 switch (tcodechar1
) {
379 m
= sscanf(tcp
,"%d,%d", &up
->tzhour
, &up
->tzminute
);
383 printf("hpgps: only %d fields recognized in timezone\n", m
);
385 refclock_report(peer
, CEVNT_BADREPLY
);
388 if ((up
->tzhour
< -12) || (up
->tzhour
> 13) ||
389 (up
->tzminute
< -59) || (up
->tzminute
> 59)){
392 printf("hpgps: timezone %d, %d out of range\n",
393 up
->tzhour
, up
->tzminute
);
395 refclock_report(peer
, CEVNT_BADREPLY
);
406 printf("hpgps: unrecognized reply format %c%c\n",
407 tcodechar1
, tcodechar2
);
409 refclock_report(peer
, CEVNT_BADREPLY
);
411 } /* end of tcodechar1 switch */
414 switch (tcodechar2
) {
417 m
= sscanf(tcp
,"%*c%*c%4d%2d%2d%2d%2d%2d%c%c%c%c%c%2hx",
418 &pp
->year
, &month
, &day
, &pp
->hour
, &pp
->minute
, &pp
->second
,
419 &timequal
, &freqqual
, &leapchar
, &servchar
, &syncchar
,
426 printf("hpgps: only %d fields recognized in timecode\n", m
);
428 refclock_report(peer
, CEVNT_BADREPLY
);
436 printf("hpgps: unrecognized timecode format %c%c\n",
437 tcodechar1
, tcodechar2
);
439 refclock_report(peer
, CEVNT_BADREPLY
);
441 } /* end of tcodechar2 format switch */
444 * Compute and verify the checksum.
445 * Characters are summed starting at tcodechar1, ending at just
446 * before the expected checksum. Bail out if incorrect.
449 while (n
-- > 0) tcodechksm
+= *tcp
++;
450 tcodechksm
&= 0x00ff;
452 if (tcodechksm
!= expectedsm
) {
455 printf("hpgps: checksum %2hX doesn't match %2hX expected\n",
456 tcodechksm
, expectedsm
);
458 refclock_report(peer
, CEVNT_BADREPLY
);
463 * Compute the day of year from the yyyymmdd format.
465 if (month
< 1 || month
> 12 || day
< 1) {
466 refclock_report(peer
, CEVNT_BADTIME
);
470 if ( ! isleap_4(pp
->year
) ) { /* Y2KFixes */
471 /* not a leap year */
472 if (day
> day1tab
[month
- 1]) {
473 refclock_report(peer
, CEVNT_BADTIME
);
476 for (i
= 0; i
< month
- 1; i
++) day
+= day1tab
[i
];
480 if (day
> day2tab
[month
- 1]) {
481 refclock_report(peer
, CEVNT_BADTIME
);
484 for (i
= 0; i
< month
- 1; i
++) day
+= day2tab
[i
];
489 * Deal with the timezone offset here. The receiver timecode is in
490 * local time = UTC + :PTIME:TZONE, so SUBTRACT the timezone values.
491 * For example, Pacific Standard Time is -8 hours , 0 minutes.
492 * Deal with the underflows and overflows.
494 pp
->minute
-= up
->tzminute
;
495 pp
->hour
-= up
->tzhour
;
497 if (pp
->minute
< 0) {
501 if (pp
->minute
> 59) {
510 if ( isleap_4(pp
->year
) ) /* Y2KFixes */
529 * Decode the MFLRV indicators.
530 * NEED TO FIGURE OUT how to deal with the request for service,
531 * time quality, and frequency quality indicators some day.
533 if (syncchar
!= '0') {
534 pp
->leap
= LEAP_NOTINSYNC
;
540 pp
->leap
= LEAP_ADDSECOND
;
544 pp
->leap
= LEAP_NOWARNING
;
548 pp
->leap
= LEAP_DELSECOND
;
554 printf("hpgps: unrecognized leap indicator: %c\n",
557 refclock_report(peer
, CEVNT_BADTIME
);
559 } /* end of leapchar switch */
563 * Process the new sample in the median filter and determine the
564 * reference clock offset and dispersion. We use lastrec as both
565 * the reference time and receive time in order to avoid being
566 * cute, like setting the reference time later than the receive
567 * time, which may cause a paranoid protocol module to chuck out
570 if (!refclock_process(pp
)) {
571 refclock_report(peer
, CEVNT_BADTIME
);
574 pp
->lastref
= pp
->lastrec
;
575 refclock_receive(peer
);
578 * If CLK_FLAG4 is set, ask for the status screen response.
580 if (pp
->sloppyclockflag
& CLK_FLAG4
){
582 if (write(pp
->io
.fd
, ":SYSTEM:PRINT?\r", 15) != 15)
583 refclock_report(peer
, CEVNT_FAULT
);
589 * hpgps_poll - called by the transmit procedure
597 register struct hpgpsunit
*up
;
598 struct refclockproc
*pp
;
601 * Time to poll the clock. The HP 58503A responds to a
602 * ":PTIME:TCODE?" by returning a timecode in the format specified
603 * above. If nothing is heard from the clock for two polls,
604 * declare a timeout and keep going.
607 up
= (struct hpgpsunit
*)pp
->unitptr
;
608 if (up
->pollcnt
== 0)
609 refclock_report(peer
, CEVNT_TIMEOUT
);
612 if (write(pp
->io
.fd
, ":PTIME:TCODE?\r", 14) != 14) {
613 refclock_report(peer
, CEVNT_FAULT
);
620 int refclock_hpgps_bs
;
621 #endif /* REFCLOCK */