1 /*
2 * refclock_hpgps - clock driver for HP 58503A GPS receiver
3 */
4
5 #ifdef HAVE_CONFIG_H
6 # include <config.h>
7 #endif
8
9 #if defined(REFCLOCK) && defined(CLOCK_HPGPS)
10
11 #include "ntpd.h"
12 #include "ntp_io.h"
13 #include "ntp_refclock.h"
14 #include "ntp_stdlib.h"
15
16 #include <stdio.h>
17 #include <ctype.h>
18
19 /* Version 0.1 April 1, 1995
20 * 0.2 April 25, 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
25 *
26 * WARNING!: This driver is UNDER CONSTRUCTION
27 * Everything in here should be treated with suspicion.
28 * If it looks wrong, it probably is.
29 *
30 * Comments and/or questions to: Dave Vitanye
31 * Hewlett Packard Company
32 * dave@scd.hp.com
33 * (408) 553-2856
34 *
35 * Thanks to the author of the PST driver, which was the starting point for
36 * this one.
37 *
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.
43 *
44 * The same driver also handles the HP Z3801A which is available surplus
45 * from the cell phone industry. It's popular with hams.
46 * It needs a different line setup: 19200 baud, 7 data bits, odd parity
47 * That is selected by adding "mode 1" to the server line in ntp.conf
48 * HP Z3801A code from Jeff Mock added by Hal Murray, Sep 2005
49 *
50 *
51 * The receiver should be operated with factory default settings.
52 * Initial driver operation: expects the receiver to be already locked
53 * to GPS, configured and able to output timecode format 2 messages.
54 *
55 * The driver uses the poll sequence :PTIME:TCODE? to get a response from
56 * the receiver. The receiver responds with a timecode string of ASCII
57 * printing characters, followed by a <cr><lf>, followed by a prompt string
58 * issued by the receiver, in the following format:
59 * T#yyyymmddhhmmssMFLRVcc<cr><lf>scpi >
60 *
61 * The driver processes the response at the <cr> and <lf>, so what the
62 * driver sees is the prompt from the previous poll, followed by this
63 * timecode. The prompt from the current poll is (usually) left unread until
64 * the next poll. So (except on the very first poll) the driver sees this:
65 *
66 * scpi > T#yyyymmddhhmmssMFLRVcc<cr><lf>
67 *
68 * The T is the on-time character, at 980 msec. before the next 1PPS edge.
69 * The # is the timecode format type. We look for format 2.
70 * Without any of the CLK or PPS stuff, then, the receiver buffer timestamp
71 * at the <cr> is 24 characters later, which is about 25 msec. at 9600 bps,
72 * so the first approximation for fudge time1 is nominally -0.955 seconds.
73 * This number probably needs adjusting for each machine / OS type, so far:
74 * -0.955000 on an HP 9000 Model 712/80 HP-UX 9.05
75 * -0.953175 on an HP 9000 Model 370 HP-UX 9.10
76 *
77 * This receiver also provides a 1PPS signal, but I haven't figured out
78 * how to deal with any of the CLK or PPS stuff yet. Stay tuned.
79 *
80 */
81
82 /*
83 * Fudge Factors
84 *
85 * Fudge time1 is used to accomodate the timecode serial interface adjustment.
86 * Fudge flag4 can be set to request a receiver status screen summary, which
87 * is recorded in the clockstats file.
88 */
89
90 /*
91 * Interface definitions
92 */
93 #define DEVICE "/dev/hpgps%d" /* device name and unit */
94 #define SPEED232 B9600 /* uart speed (9600 baud) */
95 #define SPEED232Z B19200 /* uart speed (19200 baud) */
96 #define PRECISION (-10) /* precision assumed (about 1 ms) */
97 #define REFID "GPS\0" /* reference ID */
98 #define DESCRIPTION "HP 58503A GPS Time and Frequency Reference Receiver"
99
100 #define SMAX 23*80+1 /* for :SYSTEM:PRINT? status screen response */
101
102 #define MTZONE 2 /* number of fields in timezone reply */
103 #define MTCODET2 12 /* number of fields in timecode format T2 */
104 #define NTCODET2 21 /* number of chars to checksum in format T2 */
105
106 /*
107 * Tables to compute the day of year from yyyymmdd timecode.
108 * Viva la leap.
109 */
110 static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
111 static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
112
113 /*
114 * Unit control structure
115 */
116 struct hpgpsunit {
117 int pollcnt; /* poll message counter */
118 int tzhour; /* timezone offset, hours */
119 int tzminute; /* timezone offset, minutes */
120 int linecnt; /* set for expected multiple line responses */
121 char *lastptr; /* pointer to receiver response data */
122 char statscrn[SMAX]; /* receiver status screen buffer */
123 };
124
125 /*
126 * Function prototypes
127 */
128 static int hpgps_start (int, struct peer *);
129 static void hpgps_shutdown (int, struct peer *);
130 static void hpgps_receive (struct recvbuf *);
131 static void hpgps_poll (int, struct peer *);
132
133 /*
134 * Transfer vector
135 */
136 struct refclock refclock_hpgps = {
137 hpgps_start, /* start up driver */
138 hpgps_shutdown, /* shut down driver */
139 hpgps_poll, /* transmit poll message */
140 noentry, /* not used (old hpgps_control) */
141 noentry, /* initialize driver */
142 noentry, /* not used (old hpgps_buginfo) */
143 NOFLAGS /* not used */
144 };
145
146
147 /*
148 * hpgps_start - open the devices and initialize data for processing
149 */
150 static int
hpgps_start(int unit,struct peer * peer)151 hpgps_start(
152 int unit,
153 struct peer *peer
154 )
155 {
156 register struct hpgpsunit *up;
157 struct refclockproc *pp;
158 int fd;
159 int speed, ldisc;
160 char device[20];
161
162 /*
163 * Open serial port. Use CLK line discipline, if available.
164 * Default is HP 58503A, mode arg selects HP Z3801A
165 */
166 snprintf(device, sizeof(device), DEVICE, unit);
167 ldisc = LDISC_CLK;
168 speed = SPEED232;
169 /* mode parameter to server config line shares ttl slot */
170 if (1 == peer->ttl) {
171 ldisc |= LDISC_7O1;
172 speed = SPEED232Z;
173 }
174 fd = refclock_open(&peer->srcadr, device, speed, ldisc);
175 if (fd <= 0)
176 return (0);
177 /*
178 * Allocate and initialize unit structure
179 */
180 up = emalloc_zero(sizeof(*up));
181 pp = peer->procptr;
182 pp->io.clock_recv = hpgps_receive;
183 pp->io.srcclock = peer;
184 pp->io.datalen = 0;
185 pp->io.fd = fd;
186 if (!io_addclock(&pp->io)) {
187 close(fd);
188 pp->io.fd = -1;
189 free(up);
190 return (0);
191 }
192 pp->unitptr = up;
193
194 /*
195 * Initialize miscellaneous variables
196 */
197 peer->precision = PRECISION;
198 pp->clockdesc = DESCRIPTION;
199 memcpy((char *)&pp->refid, REFID, 4);
200 up->tzhour = 0;
201 up->tzminute = 0;
202
203 *up->statscrn = '\0';
204 up->lastptr = up->statscrn;
205 up->pollcnt = 2;
206
207 /*
208 * Get the identifier string, which is logged but otherwise ignored,
209 * and get the local timezone information
210 */
211 up->linecnt = 1;
212 if (refclock_write(peer, "*IDN?\r:PTIME:TZONE?\r", 20, NULL) != 20)
213 refclock_report(peer, CEVNT_FAULT);
214
215 return (1);
216 }
217
218
219 /*
220 * hpgps_shutdown - shut down the clock
221 */
222 static void
hpgps_shutdown(int unit,struct peer * peer)223 hpgps_shutdown(
224 int unit,
225 struct peer *peer
226 )
227 {
228 register struct hpgpsunit *up;
229 struct refclockproc *pp;
230
231 pp = peer->procptr;
232 up = pp->unitptr;
233 if (-1 != pp->io.fd)
234 io_closeclock(&pp->io);
235 if (NULL != up)
236 free(up);
237 }
238
239
240 /*
241 * hpgps_receive - receive data from the serial interface
242 */
243 static void
hpgps_receive(struct recvbuf * rbufp)244 hpgps_receive(
245 struct recvbuf *rbufp
246 )
247 {
248 register struct hpgpsunit *up;
249 struct refclockproc *pp;
250 struct peer *peer;
251 l_fp trtmp;
252 char tcodechar1; /* identifies timecode format */
253 char tcodechar2; /* identifies timecode format */
254 char timequal; /* time figure of merit: 0-9 */
255 char freqqual; /* frequency figure of merit: 0-3 */
256 char leapchar; /* leapsecond: + or 0 or - */
257 char servchar; /* request for service: 0 = no, 1 = yes */
258 char syncchar; /* time info is invalid: 0 = no, 1 = yes */
259 short expectedsm; /* expected timecode byte checksum */
260 short tcodechksm; /* computed timecode byte checksum */
261 int i,m,n;
262 int month, day, lastday;
263 char *tcp; /* timecode pointer (skips over the prompt) */
264 char prompt[BMAX]; /* prompt in response from receiver */
265
266 /*
267 * Initialize pointers and read the receiver response
268 */
269 peer = rbufp->recv_peer;
270 pp = peer->procptr;
271 up = pp->unitptr;
272 *pp->a_lastcode = '\0';
273 pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
274
275 #ifdef DEBUG
276 if (debug)
277 printf("hpgps: lencode: %d timecode:%s\n",
278 pp->lencode, pp->a_lastcode);
279 #endif
280
281 /*
282 * If there's no characters in the reply, we can quit now
283 */
284 if (pp->lencode == 0)
285 return;
286
287 /*
288 * If linecnt is greater than zero, we are getting information only,
289 * such as the receiver identification string or the receiver status
290 * screen, so put the receiver response at the end of the status
291 * screen buffer. When we have the last line, write the buffer to
292 * the clockstats file and return without further processing.
293 *
294 * If linecnt is zero, we are expecting either the timezone
295 * or a timecode. At this point, also write the response
296 * to the clockstats file, and go on to process the prompt (if any),
297 * timezone, or timecode and timestamp.
298 */
299
300
301 if (up->linecnt-- > 0) {
302 if ((int)(pp->lencode + 2) <= (SMAX - (up->lastptr - up->statscrn))) {
303 *up->lastptr++ = '\n';
304 memcpy(up->lastptr, pp->a_lastcode, pp->lencode);
305 up->lastptr += pp->lencode;
306 }
307 if (up->linecnt == 0)
308 record_clock_stats(&peer->srcadr, up->statscrn);
309
310 return;
311 }
312
313 record_clock_stats(&peer->srcadr, pp->a_lastcode);
314 pp->lastrec = trtmp;
315
316 up->lastptr = up->statscrn;
317 *up->lastptr = '\0';
318 up->pollcnt = 2;
319
320 /*
321 * We get down to business: get a prompt if one is there, issue
322 * a clear status command if it contains an error indication.
323 * Next, check for either the timezone reply or the timecode reply
324 * and decode it. If we don't recognize the reply, or don't get the
325 * proper number of decoded fields, or get an out of range timezone,
326 * or if the timecode checksum is bad, then we declare bad format
327 * and exit.
328 *
329 * Timezone format (including nominal prompt):
330 * scpi > -H,-M<cr><lf>
331 *
332 * Timecode format (including nominal prompt):
333 * scpi > T2yyyymmddhhmmssMFLRVcc<cr><lf>
334 *
335 */
336
337 strlcpy(prompt, pp->a_lastcode, sizeof(prompt));
338 tcp = strrchr(pp->a_lastcode,'>');
339 if (tcp == NULL)
340 tcp = pp->a_lastcode;
341 else
342 tcp++;
343 prompt[tcp - pp->a_lastcode] = '\0';
344 while ((*tcp == ' ') || (*tcp == '\t')) tcp++;
345
346 /*
347 * deal with an error indication in the prompt here
348 */
349 if (strrchr(prompt,'E') > strrchr(prompt,'s')){
350 #ifdef DEBUG
351 if (debug)
352 printf("hpgps: error indicated in prompt: %s\n", prompt);
353 #endif
354 if (refclock_write(peer, "*CLS\r\r", 6, NULL) != 6)
355 refclock_report(peer, CEVNT_FAULT);
356 }
357
358 /*
359 * make sure we got a timezone or timecode format and
360 * then process accordingly
361 */
362 m = sscanf(tcp,"%c%c", &tcodechar1, &tcodechar2);
363
364 if (m != 2){
365 #ifdef DEBUG
366 if (debug)
367 printf("hpgps: no format indicator\n");
368 #endif
369 refclock_report(peer, CEVNT_BADREPLY);
370 return;
371 }
372
373 switch (tcodechar1) {
374
375 case '+':
376 case '-':
377 m = sscanf(tcp,"%d,%d", &up->tzhour, &up->tzminute);
378 if (m != MTZONE) {
379 #ifdef DEBUG
380 if (debug)
381 printf("hpgps: only %d fields recognized in timezone\n", m);
382 #endif
383 refclock_report(peer, CEVNT_BADREPLY);
384 return;
385 }
386 if ((up->tzhour < -12) || (up->tzhour > 13) ||
387 (up->tzminute < -59) || (up->tzminute > 59)){
388 #ifdef DEBUG
389 if (debug)
390 printf("hpgps: timezone %d, %d out of range\n",
391 up->tzhour, up->tzminute);
392 #endif
393 refclock_report(peer, CEVNT_BADREPLY);
394 return;
395 }
396 return;
397
398 case 'T':
399 break;
400
401 default:
402 #ifdef DEBUG
403 if (debug)
404 printf("hpgps: unrecognized reply format %c%c\n",
405 tcodechar1, tcodechar2);
406 #endif
407 refclock_report(peer, CEVNT_BADREPLY);
408 return;
409 } /* end of tcodechar1 switch */
410
411
412 switch (tcodechar2) {
413
414 case '2':
415 m = sscanf(tcp,"%*c%*c%4d%2d%2d%2d%2d%2d%c%c%c%c%c%2hx",
416 &pp->year, &month, &day, &pp->hour, &pp->minute, &pp->second,
417 &timequal, &freqqual, &leapchar, &servchar, &syncchar,
418 &expectedsm);
419 n = NTCODET2;
420
421 if (m != MTCODET2){
422 #ifdef DEBUG
423 if (debug)
424 printf("hpgps: only %d fields recognized in timecode\n", m);
425 #endif
426 refclock_report(peer, CEVNT_BADREPLY);
427 return;
428 }
429 break;
430
431 default:
432 #ifdef DEBUG
433 if (debug)
434 printf("hpgps: unrecognized timecode format %c%c\n",
435 tcodechar1, tcodechar2);
436 #endif
437 refclock_report(peer, CEVNT_BADREPLY);
438 return;
439 } /* end of tcodechar2 format switch */
440
441 /*
442 * Compute and verify the checksum.
443 * Characters are summed starting at tcodechar1, ending at just
444 * before the expected checksum. Bail out if incorrect.
445 */
446 tcodechksm = 0;
447 while (n-- > 0) tcodechksm += *tcp++;
448 tcodechksm &= 0x00ff;
449
450 if (tcodechksm != expectedsm) {
451 #ifdef DEBUG
452 if (debug)
453 printf("hpgps: checksum %2hX doesn't match %2hX expected\n",
454 tcodechksm, expectedsm);
455 #endif
456 refclock_report(peer, CEVNT_BADREPLY);
457 return;
458 }
459
460 /*
461 * Compute the day of year from the yyyymmdd format.
462 */
463 if (month < 1 || month > 12 || day < 1) {
464 refclock_report(peer, CEVNT_BADTIME);
465 return;
466 }
467
468 if ( ! isleap_4(pp->year) ) { /* Y2KFixes */
469 /* not a leap year */
470 if (day > day1tab[month - 1]) {
471 refclock_report(peer, CEVNT_BADTIME);
472 return;
473 }
474 for (i = 0; i < month - 1; i++) day += day1tab[i];
475 lastday = 365;
476 } else {
477 /* a leap year */
478 if (day > day2tab[month - 1]) {
479 refclock_report(peer, CEVNT_BADTIME);
480 return;
481 }
482 for (i = 0; i < month - 1; i++) day += day2tab[i];
483 lastday = 366;
484 }
485
486 /*
487 * Deal with the timezone offset here. The receiver timecode is in
488 * local time = UTC + :PTIME:TZONE, so SUBTRACT the timezone values.
489 * For example, Pacific Standard Time is -8 hours , 0 minutes.
490 * Deal with the underflows and overflows.
491 */
492 pp->minute -= up->tzminute;
493 pp->hour -= up->tzhour;
494
495 if (pp->minute < 0) {
496 pp->minute += 60;
497 pp->hour--;
498 }
499 if (pp->minute > 59) {
500 pp->minute -= 60;
501 pp->hour++;
502 }
503 if (pp->hour < 0) {
504 pp->hour += 24;
505 day--;
506 if (day < 1) {
507 pp->year--;
508 if ( isleap_4(pp->year) ) /* Y2KFixes */
509 day = 366;
510 else
511 day = 365;
512 }
513 }
514
515 if (pp->hour > 23) {
516 pp->hour -= 24;
517 day++;
518 if (day > lastday) {
519 pp->year++;
520 day = 1;
521 }
522 }
523
524 pp->day = day;
525
526 /*
527 * Decode the MFLRV indicators.
528 * NEED TO FIGURE OUT how to deal with the request for service,
529 * time quality, and frequency quality indicators some day.
530 */
531 if (syncchar != '0') {
532 pp->leap = LEAP_NOTINSYNC;
533 }
534 else {
535 pp->leap = LEAP_NOWARNING;
536 switch (leapchar) {
537
538 case '0':
539 break;
540
541 /* See http://bugs.ntp.org/1090
542 * Ignore leap announcements unless June or December.
543 * Better would be to use :GPSTime? to find the month,
544 * but that seems too likely to introduce other bugs.
545 */
546 case '+':
547 if ((month==6) || (month==12))
548 pp->leap = LEAP_ADDSECOND;
549 break;
550
551 case '-':
552 if ((month==6) || (month==12))
553 pp->leap = LEAP_DELSECOND;
554 break;
555
556 default:
557 #ifdef DEBUG
558 if (debug)
559 printf("hpgps: unrecognized leap indicator: %c\n",
560 leapchar);
561 #endif
562 refclock_report(peer, CEVNT_BADTIME);
563 return;
564 } /* end of leapchar switch */
565 }
566
567 /*
568 * Process the new sample in the median filter and determine the
569 * reference clock offset and dispersion. We use lastrec as both
570 * the reference time and receive time in order to avoid being
571 * cute, like setting the reference time later than the receive
572 * time, which may cause a paranoid protocol module to chuck out
573 * the data.
574 */
575 if (!refclock_process(pp)) {
576 refclock_report(peer, CEVNT_BADTIME);
577 return;
578 }
579 pp->lastref = pp->lastrec;
580 refclock_receive(peer);
581
582 /*
583 * If CLK_FLAG4 is set, ask for the status screen response.
584 */
585 if (pp->sloppyclockflag & CLK_FLAG4){
586 up->linecnt = 22;
587 if (refclock_write(peer, ":SYSTEM:PRINT?\r", 15, NULL) != 15)
588 refclock_report(peer, CEVNT_FAULT);
589 }
590 }
591
592
593 /*
594 * hpgps_poll - called by the transmit procedure
595 */
596 static void
hpgps_poll(int unit,struct peer * peer)597 hpgps_poll(
598 int unit,
599 struct peer *peer
600 )
601 {
602 register struct hpgpsunit *up;
603 struct refclockproc *pp;
604
605 /*
606 * Time to poll the clock. The HP 58503A responds to a
607 * ":PTIME:TCODE?" by returning a timecode in the format specified
608 * above. If nothing is heard from the clock for two polls,
609 * declare a timeout and keep going.
610 */
611 pp = peer->procptr;
612 up = pp->unitptr;
613 if (up->pollcnt == 0)
614 refclock_report(peer, CEVNT_TIMEOUT);
615 else
616 up->pollcnt--;
617 if (refclock_write(peer, ":PTIME:TCODE?\r", 14, NULL) != 14) {
618 refclock_report(peer, CEVNT_FAULT);
619 }
620 else
621 pp->polls++;
622 }
623
624 #else
625 NONEMPTY_TRANSLATION_UNIT
626 #endif /* REFCLOCK */
627