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 P((int, struct peer *)); 129 static void hpgps_shutdown P((int, struct peer *)); 130 static void hpgps_receive P((struct recvbuf *)); 131 static void hpgps_poll P((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 (void)sprintf(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 if (!(up = (struct hpgpsunit *) 179 emalloc(sizeof(struct hpgpsunit)))) { 180 (void) close(fd); 181 return (0); 182 } 183 memset((char *)up, 0, sizeof(struct hpgpsunit)); 184 pp = peer->procptr; 185 pp->io.clock_recv = hpgps_receive; 186 pp->io.srcclock = (caddr_t)peer; 187 pp->io.datalen = 0; 188 pp->io.fd = fd; 189 if (!io_addclock(&pp->io)) { 190 (void) close(fd); 191 free(up); 192 return (0); 193 } 194 pp->unitptr = (caddr_t)up; 195 196 /* 197 * Initialize miscellaneous variables 198 */ 199 peer->precision = PRECISION; 200 pp->clockdesc = DESCRIPTION; 201 memcpy((char *)&pp->refid, REFID, 4); 202 up->tzhour = 0; 203 up->tzminute = 0; 204 205 *up->statscrn = '\0'; 206 up->lastptr = up->statscrn; 207 up->pollcnt = 2; 208 209 /* 210 * Get the identifier string, which is logged but otherwise ignored, 211 * and get the local timezone information 212 */ 213 up->linecnt = 1; 214 if (write(pp->io.fd, "*IDN?\r:PTIME:TZONE?\r", 20) != 20) 215 refclock_report(peer, CEVNT_FAULT); 216 217 return (1); 218 } 219 220 221 /* 222 * hpgps_shutdown - shut down the clock 223 */ 224 static void 225 hpgps_shutdown( 226 int unit, 227 struct peer *peer 228 ) 229 { 230 register struct hpgpsunit *up; 231 struct refclockproc *pp; 232 233 pp = peer->procptr; 234 up = (struct hpgpsunit *)pp->unitptr; 235 io_closeclock(&pp->io); 236 free(up); 237 } 238 239 240 /* 241 * hpgps_receive - receive data from the serial interface 242 */ 243 static void 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 = (struct peer *)rbufp->recv_srcclock; 270 pp = peer->procptr; 271 up = (struct hpgpsunit *)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 (void)strcpy(up->lastptr, pp->a_lastcode); 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 (void)strcpy(prompt,pp->a_lastcode); 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 (write(pp->io.fd, "*CLS\r\r", 6) != 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 switch (leapchar) { 536 537 case '+': 538 pp->leap = LEAP_ADDSECOND; 539 break; 540 541 case '0': 542 pp->leap = LEAP_NOWARNING; 543 break; 544 545 case '-': 546 pp->leap = LEAP_DELSECOND; 547 break; 548 549 default: 550 #ifdef DEBUG 551 if (debug) 552 printf("hpgps: unrecognized leap indicator: %c\n", 553 leapchar); 554 #endif 555 refclock_report(peer, CEVNT_BADTIME); 556 return; 557 } /* end of leapchar switch */ 558 } 559 560 /* 561 * Process the new sample in the median filter and determine the 562 * reference clock offset and dispersion. We use lastrec as both 563 * the reference time and receive time in order to avoid being 564 * cute, like setting the reference time later than the receive 565 * time, which may cause a paranoid protocol module to chuck out 566 * the data. 567 */ 568 if (!refclock_process(pp)) { 569 refclock_report(peer, CEVNT_BADTIME); 570 return; 571 } 572 pp->lastref = pp->lastrec; 573 refclock_receive(peer); 574 575 /* 576 * If CLK_FLAG4 is set, ask for the status screen response. 577 */ 578 if (pp->sloppyclockflag & CLK_FLAG4){ 579 up->linecnt = 22; 580 if (write(pp->io.fd, ":SYSTEM:PRINT?\r", 15) != 15) 581 refclock_report(peer, CEVNT_FAULT); 582 } 583 } 584 585 586 /* 587 * hpgps_poll - called by the transmit procedure 588 */ 589 static void 590 hpgps_poll( 591 int unit, 592 struct peer *peer 593 ) 594 { 595 register struct hpgpsunit *up; 596 struct refclockproc *pp; 597 598 /* 599 * Time to poll the clock. The HP 58503A responds to a 600 * ":PTIME:TCODE?" by returning a timecode in the format specified 601 * above. If nothing is heard from the clock for two polls, 602 * declare a timeout and keep going. 603 */ 604 pp = peer->procptr; 605 up = (struct hpgpsunit *)pp->unitptr; 606 if (up->pollcnt == 0) 607 refclock_report(peer, CEVNT_TIMEOUT); 608 else 609 up->pollcnt--; 610 if (write(pp->io.fd, ":PTIME:TCODE?\r", 14) != 14) { 611 refclock_report(peer, CEVNT_FAULT); 612 } 613 else 614 pp->polls++; 615 } 616 617 #else 618 int refclock_hpgps_bs; 619 #endif /* REFCLOCK */ 620