Annotation of embedaddon/ntp/ntpd/refclock_hpgps.c, revision 1.1
1.1 ! misho 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
! 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: char device[20];
! 160:
! 161: /*
! 162: * Open serial port. Use CLK line discipline, if available.
! 163: * Default is HP 58503A, mode arg selects HP Z3801A
! 164: */
! 165: snprintf(device, sizeof(device), DEVICE, unit);
! 166: /* mode parameter to server config line shares ttl slot */
! 167: if ((peer->ttl == 1)) {
! 168: if (!(fd = refclock_open(device, SPEED232Z,
! 169: LDISC_CLK | LDISC_7O1)))
! 170: return (0);
! 171: } else {
! 172: if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
! 173: return (0);
! 174: }
! 175: /*
! 176: * Allocate and initialize unit structure
! 177: */
! 178: up = emalloc(sizeof(*up));
! 179: memset(up, 0, sizeof(*up));
! 180: pp = peer->procptr;
! 181: pp->io.clock_recv = hpgps_receive;
! 182: pp->io.srcclock = (caddr_t)peer;
! 183: pp->io.datalen = 0;
! 184: pp->io.fd = fd;
! 185: if (!io_addclock(&pp->io)) {
! 186: close(fd);
! 187: pp->io.fd = -1;
! 188: free(up);
! 189: return (0);
! 190: }
! 191: pp->unitptr = (caddr_t)up;
! 192:
! 193: /*
! 194: * Initialize miscellaneous variables
! 195: */
! 196: peer->precision = PRECISION;
! 197: pp->clockdesc = DESCRIPTION;
! 198: memcpy((char *)&pp->refid, REFID, 4);
! 199: up->tzhour = 0;
! 200: up->tzminute = 0;
! 201:
! 202: *up->statscrn = '\0';
! 203: up->lastptr = up->statscrn;
! 204: up->pollcnt = 2;
! 205:
! 206: /*
! 207: * Get the identifier string, which is logged but otherwise ignored,
! 208: * and get the local timezone information
! 209: */
! 210: up->linecnt = 1;
! 211: if (write(pp->io.fd, "*IDN?\r:PTIME:TZONE?\r", 20) != 20)
! 212: refclock_report(peer, CEVNT_FAULT);
! 213:
! 214: return (1);
! 215: }
! 216:
! 217:
! 218: /*
! 219: * hpgps_shutdown - shut down the clock
! 220: */
! 221: static void
! 222: hpgps_shutdown(
! 223: int unit,
! 224: struct peer *peer
! 225: )
! 226: {
! 227: register struct hpgpsunit *up;
! 228: struct refclockproc *pp;
! 229:
! 230: pp = peer->procptr;
! 231: up = (struct hpgpsunit *)pp->unitptr;
! 232: if (-1 != pp->io.fd)
! 233: io_closeclock(&pp->io);
! 234: if (NULL != up)
! 235: free(up);
! 236: }
! 237:
! 238:
! 239: /*
! 240: * hpgps_receive - receive data from the serial interface
! 241: */
! 242: static void
! 243: hpgps_receive(
! 244: struct recvbuf *rbufp
! 245: )
! 246: {
! 247: register struct hpgpsunit *up;
! 248: struct refclockproc *pp;
! 249: struct peer *peer;
! 250: l_fp trtmp;
! 251: char tcodechar1; /* identifies timecode format */
! 252: char tcodechar2; /* identifies timecode format */
! 253: char timequal; /* time figure of merit: 0-9 */
! 254: char freqqual; /* frequency figure of merit: 0-3 */
! 255: char leapchar; /* leapsecond: + or 0 or - */
! 256: char servchar; /* request for service: 0 = no, 1 = yes */
! 257: char syncchar; /* time info is invalid: 0 = no, 1 = yes */
! 258: short expectedsm; /* expected timecode byte checksum */
! 259: short tcodechksm; /* computed timecode byte checksum */
! 260: int i,m,n;
! 261: int month, day, lastday;
! 262: char *tcp; /* timecode pointer (skips over the prompt) */
! 263: char prompt[BMAX]; /* prompt in response from receiver */
! 264:
! 265: /*
! 266: * Initialize pointers and read the receiver response
! 267: */
! 268: peer = (struct peer *)rbufp->recv_srcclock;
! 269: pp = peer->procptr;
! 270: up = (struct hpgpsunit *)pp->unitptr;
! 271: *pp->a_lastcode = '\0';
! 272: pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
! 273:
! 274: #ifdef DEBUG
! 275: if (debug)
! 276: printf("hpgps: lencode: %d timecode:%s\n",
! 277: pp->lencode, pp->a_lastcode);
! 278: #endif
! 279:
! 280: /*
! 281: * If there's no characters in the reply, we can quit now
! 282: */
! 283: if (pp->lencode == 0)
! 284: return;
! 285:
! 286: /*
! 287: * If linecnt is greater than zero, we are getting information only,
! 288: * such as the receiver identification string or the receiver status
! 289: * screen, so put the receiver response at the end of the status
! 290: * screen buffer. When we have the last line, write the buffer to
! 291: * the clockstats file and return without further processing.
! 292: *
! 293: * If linecnt is zero, we are expecting either the timezone
! 294: * or a timecode. At this point, also write the response
! 295: * to the clockstats file, and go on to process the prompt (if any),
! 296: * timezone, or timecode and timestamp.
! 297: */
! 298:
! 299:
! 300: if (up->linecnt-- > 0) {
! 301: if ((int)(pp->lencode + 2) <= (SMAX - (up->lastptr - up->statscrn))) {
! 302: *up->lastptr++ = '\n';
! 303: (void)strcpy(up->lastptr, pp->a_lastcode);
! 304: up->lastptr += pp->lencode;
! 305: }
! 306: if (up->linecnt == 0)
! 307: record_clock_stats(&peer->srcadr, up->statscrn);
! 308:
! 309: return;
! 310: }
! 311:
! 312: record_clock_stats(&peer->srcadr, pp->a_lastcode);
! 313: pp->lastrec = trtmp;
! 314:
! 315: up->lastptr = up->statscrn;
! 316: *up->lastptr = '\0';
! 317: up->pollcnt = 2;
! 318:
! 319: /*
! 320: * We get down to business: get a prompt if one is there, issue
! 321: * a clear status command if it contains an error indication.
! 322: * Next, check for either the timezone reply or the timecode reply
! 323: * and decode it. If we don't recognize the reply, or don't get the
! 324: * proper number of decoded fields, or get an out of range timezone,
! 325: * or if the timecode checksum is bad, then we declare bad format
! 326: * and exit.
! 327: *
! 328: * Timezone format (including nominal prompt):
! 329: * scpi > -H,-M<cr><lf>
! 330: *
! 331: * Timecode format (including nominal prompt):
! 332: * scpi > T2yyyymmddhhmmssMFLRVcc<cr><lf>
! 333: *
! 334: */
! 335:
! 336: (void)strcpy(prompt,pp->a_lastcode);
! 337: tcp = strrchr(pp->a_lastcode,'>');
! 338: if (tcp == NULL)
! 339: tcp = pp->a_lastcode;
! 340: else
! 341: tcp++;
! 342: prompt[tcp - pp->a_lastcode] = '\0';
! 343: while ((*tcp == ' ') || (*tcp == '\t')) tcp++;
! 344:
! 345: /*
! 346: * deal with an error indication in the prompt here
! 347: */
! 348: if (strrchr(prompt,'E') > strrchr(prompt,'s')){
! 349: #ifdef DEBUG
! 350: if (debug)
! 351: printf("hpgps: error indicated in prompt: %s\n", prompt);
! 352: #endif
! 353: if (write(pp->io.fd, "*CLS\r\r", 6) != 6)
! 354: refclock_report(peer, CEVNT_FAULT);
! 355: }
! 356:
! 357: /*
! 358: * make sure we got a timezone or timecode format and
! 359: * then process accordingly
! 360: */
! 361: m = sscanf(tcp,"%c%c", &tcodechar1, &tcodechar2);
! 362:
! 363: if (m != 2){
! 364: #ifdef DEBUG
! 365: if (debug)
! 366: printf("hpgps: no format indicator\n");
! 367: #endif
! 368: refclock_report(peer, CEVNT_BADREPLY);
! 369: return;
! 370: }
! 371:
! 372: switch (tcodechar1) {
! 373:
! 374: case '+':
! 375: case '-':
! 376: m = sscanf(tcp,"%d,%d", &up->tzhour, &up->tzminute);
! 377: if (m != MTZONE) {
! 378: #ifdef DEBUG
! 379: if (debug)
! 380: printf("hpgps: only %d fields recognized in timezone\n", m);
! 381: #endif
! 382: refclock_report(peer, CEVNT_BADREPLY);
! 383: return;
! 384: }
! 385: if ((up->tzhour < -12) || (up->tzhour > 13) ||
! 386: (up->tzminute < -59) || (up->tzminute > 59)){
! 387: #ifdef DEBUG
! 388: if (debug)
! 389: printf("hpgps: timezone %d, %d out of range\n",
! 390: up->tzhour, up->tzminute);
! 391: #endif
! 392: refclock_report(peer, CEVNT_BADREPLY);
! 393: return;
! 394: }
! 395: return;
! 396:
! 397: case 'T':
! 398: break;
! 399:
! 400: default:
! 401: #ifdef DEBUG
! 402: if (debug)
! 403: printf("hpgps: unrecognized reply format %c%c\n",
! 404: tcodechar1, tcodechar2);
! 405: #endif
! 406: refclock_report(peer, CEVNT_BADREPLY);
! 407: return;
! 408: } /* end of tcodechar1 switch */
! 409:
! 410:
! 411: switch (tcodechar2) {
! 412:
! 413: case '2':
! 414: m = sscanf(tcp,"%*c%*c%4d%2d%2d%2d%2d%2d%c%c%c%c%c%2hx",
! 415: &pp->year, &month, &day, &pp->hour, &pp->minute, &pp->second,
! 416: &timequal, &freqqual, &leapchar, &servchar, &syncchar,
! 417: &expectedsm);
! 418: n = NTCODET2;
! 419:
! 420: if (m != MTCODET2){
! 421: #ifdef DEBUG
! 422: if (debug)
! 423: printf("hpgps: only %d fields recognized in timecode\n", m);
! 424: #endif
! 425: refclock_report(peer, CEVNT_BADREPLY);
! 426: return;
! 427: }
! 428: break;
! 429:
! 430: default:
! 431: #ifdef DEBUG
! 432: if (debug)
! 433: printf("hpgps: unrecognized timecode format %c%c\n",
! 434: tcodechar1, tcodechar2);
! 435: #endif
! 436: refclock_report(peer, CEVNT_BADREPLY);
! 437: return;
! 438: } /* end of tcodechar2 format switch */
! 439:
! 440: /*
! 441: * Compute and verify the checksum.
! 442: * Characters are summed starting at tcodechar1, ending at just
! 443: * before the expected checksum. Bail out if incorrect.
! 444: */
! 445: tcodechksm = 0;
! 446: while (n-- > 0) tcodechksm += *tcp++;
! 447: tcodechksm &= 0x00ff;
! 448:
! 449: if (tcodechksm != expectedsm) {
! 450: #ifdef DEBUG
! 451: if (debug)
! 452: printf("hpgps: checksum %2hX doesn't match %2hX expected\n",
! 453: tcodechksm, expectedsm);
! 454: #endif
! 455: refclock_report(peer, CEVNT_BADREPLY);
! 456: return;
! 457: }
! 458:
! 459: /*
! 460: * Compute the day of year from the yyyymmdd format.
! 461: */
! 462: if (month < 1 || month > 12 || day < 1) {
! 463: refclock_report(peer, CEVNT_BADTIME);
! 464: return;
! 465: }
! 466:
! 467: if ( ! isleap_4(pp->year) ) { /* Y2KFixes */
! 468: /* not a leap year */
! 469: if (day > day1tab[month - 1]) {
! 470: refclock_report(peer, CEVNT_BADTIME);
! 471: return;
! 472: }
! 473: for (i = 0; i < month - 1; i++) day += day1tab[i];
! 474: lastday = 365;
! 475: } else {
! 476: /* a leap year */
! 477: if (day > day2tab[month - 1]) {
! 478: refclock_report(peer, CEVNT_BADTIME);
! 479: return;
! 480: }
! 481: for (i = 0; i < month - 1; i++) day += day2tab[i];
! 482: lastday = 366;
! 483: }
! 484:
! 485: /*
! 486: * Deal with the timezone offset here. The receiver timecode is in
! 487: * local time = UTC + :PTIME:TZONE, so SUBTRACT the timezone values.
! 488: * For example, Pacific Standard Time is -8 hours , 0 minutes.
! 489: * Deal with the underflows and overflows.
! 490: */
! 491: pp->minute -= up->tzminute;
! 492: pp->hour -= up->tzhour;
! 493:
! 494: if (pp->minute < 0) {
! 495: pp->minute += 60;
! 496: pp->hour--;
! 497: }
! 498: if (pp->minute > 59) {
! 499: pp->minute -= 60;
! 500: pp->hour++;
! 501: }
! 502: if (pp->hour < 0) {
! 503: pp->hour += 24;
! 504: day--;
! 505: if (day < 1) {
! 506: pp->year--;
! 507: if ( isleap_4(pp->year) ) /* Y2KFixes */
! 508: day = 366;
! 509: else
! 510: day = 365;
! 511: }
! 512: }
! 513:
! 514: if (pp->hour > 23) {
! 515: pp->hour -= 24;
! 516: day++;
! 517: if (day > lastday) {
! 518: pp->year++;
! 519: day = 1;
! 520: }
! 521: }
! 522:
! 523: pp->day = day;
! 524:
! 525: /*
! 526: * Decode the MFLRV indicators.
! 527: * NEED TO FIGURE OUT how to deal with the request for service,
! 528: * time quality, and frequency quality indicators some day.
! 529: */
! 530: if (syncchar != '0') {
! 531: pp->leap = LEAP_NOTINSYNC;
! 532: }
! 533: else {
! 534: pp->leap = LEAP_NOWARNING;
! 535: switch (leapchar) {
! 536:
! 537: case '0':
! 538: break;
! 539:
! 540: /* See http://bugs.ntp.org/1090
! 541: * Ignore leap announcements unless June or December.
! 542: * Better would be to use :GPSTime? to find the month,
! 543: * but that seems too likely to introduce other bugs.
! 544: */
! 545: case '+':
! 546: if ((month==6) || (month==12))
! 547: pp->leap = LEAP_ADDSECOND;
! 548: break;
! 549:
! 550: case '-':
! 551: if ((month==6) || (month==12))
! 552: pp->leap = LEAP_DELSECOND;
! 553: break;
! 554:
! 555: default:
! 556: #ifdef DEBUG
! 557: if (debug)
! 558: printf("hpgps: unrecognized leap indicator: %c\n",
! 559: leapchar);
! 560: #endif
! 561: refclock_report(peer, CEVNT_BADTIME);
! 562: return;
! 563: } /* end of leapchar switch */
! 564: }
! 565:
! 566: /*
! 567: * Process the new sample in the median filter and determine the
! 568: * reference clock offset and dispersion. We use lastrec as both
! 569: * the reference time and receive time in order to avoid being
! 570: * cute, like setting the reference time later than the receive
! 571: * time, which may cause a paranoid protocol module to chuck out
! 572: * the data.
! 573: */
! 574: if (!refclock_process(pp)) {
! 575: refclock_report(peer, CEVNT_BADTIME);
! 576: return;
! 577: }
! 578: pp->lastref = pp->lastrec;
! 579: refclock_receive(peer);
! 580:
! 581: /*
! 582: * If CLK_FLAG4 is set, ask for the status screen response.
! 583: */
! 584: if (pp->sloppyclockflag & CLK_FLAG4){
! 585: up->linecnt = 22;
! 586: if (write(pp->io.fd, ":SYSTEM:PRINT?\r", 15) != 15)
! 587: refclock_report(peer, CEVNT_FAULT);
! 588: }
! 589: }
! 590:
! 591:
! 592: /*
! 593: * hpgps_poll - called by the transmit procedure
! 594: */
! 595: static void
! 596: hpgps_poll(
! 597: int unit,
! 598: struct peer *peer
! 599: )
! 600: {
! 601: register struct hpgpsunit *up;
! 602: struct refclockproc *pp;
! 603:
! 604: /*
! 605: * Time to poll the clock. The HP 58503A responds to a
! 606: * ":PTIME:TCODE?" by returning a timecode in the format specified
! 607: * above. If nothing is heard from the clock for two polls,
! 608: * declare a timeout and keep going.
! 609: */
! 610: pp = peer->procptr;
! 611: up = (struct hpgpsunit *)pp->unitptr;
! 612: if (up->pollcnt == 0)
! 613: refclock_report(peer, CEVNT_TIMEOUT);
! 614: else
! 615: up->pollcnt--;
! 616: if (write(pp->io.fd, ":PTIME:TCODE?\r", 14) != 14) {
! 617: refclock_report(peer, CEVNT_FAULT);
! 618: }
! 619: else
! 620: pp->polls++;
! 621: }
! 622:
! 623: #else
! 624: int refclock_hpgps_bs;
! 625: #endif /* REFCLOCK */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>