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 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. 47 * 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 > 53 * 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: 58 * 59 * scpi > T#yyyymmddhhmmssMFLRVcc<cr><lf> 60 * 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 69 * 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. 72 * 73 */ 74 75 /* 76 * Fudge Factors 77 * 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. 81 */ 82 83 /* 84 * Interface definitions 85 */ 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" 91 92 #define SMAX 23*80+1 /* for :SYSTEM:PRINT? status screen response */ 93 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 */ 97 98 /* 99 * Tables to compute the day of year from yyyymmdd timecode. 100 * Viva la leap. 101 */ 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}; 104 105 /* 106 * Unit control structure 107 */ 108 struct hpgpsunit { 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 */ 115 }; 116 117 /* 118 * Function prototypes 119 */ 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 *)); 124 125 /* 126 * Transfer vector 127 */ 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 */ 136 }; 137 138 139 /* 140 * hpgps_start - open the devices and initialize data for processing 141 */ 142 static int 143 hpgps_start( 144 int unit, 145 struct peer *peer 146 ) 147 { 148 register struct hpgpsunit *up; 149 struct refclockproc *pp; 150 int fd; 151 char device[20]; 152 153 /* 154 * Open serial port. Use CLK line discipline, if available. 155 */ 156 (void)sprintf(device, DEVICE, unit); 157 if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) 158 return (0); 159 160 /* 161 * Allocate and initialize unit structure 162 */ 163 if (!(up = (struct hpgpsunit *) 164 emalloc(sizeof(struct hpgpsunit)))) { 165 (void) close(fd); 166 return (0); 167 } 168 memset((char *)up, 0, sizeof(struct hpgpsunit)); 169 pp = peer->procptr; 170 pp->io.clock_recv = hpgps_receive; 171 pp->io.srcclock = (caddr_t)peer; 172 pp->io.datalen = 0; 173 pp->io.fd = fd; 174 if (!io_addclock(&pp->io)) { 175 (void) close(fd); 176 free(up); 177 return (0); 178 } 179 pp->unitptr = (caddr_t)up; 180 181 /* 182 * Initialize miscellaneous variables 183 */ 184 peer->precision = PRECISION; 185 pp->clockdesc = DESCRIPTION; 186 memcpy((char *)&pp->refid, REFID, 4); 187 up->tzhour = 0; 188 up->tzminute = 0; 189 190 *up->statscrn = '\0'; 191 up->lastptr = up->statscrn; 192 up->pollcnt = 2; 193 194 /* 195 * Get the identifier string, which is logged but otherwise ignored, 196 * and get the local timezone information 197 */ 198 up->linecnt = 1; 199 if (write(pp->io.fd, "*IDN?\r:PTIME:TZONE?\r", 20) != 20) 200 refclock_report(peer, CEVNT_FAULT); 201 202 return (1); 203 } 204 205 206 /* 207 * hpgps_shutdown - shut down the clock 208 */ 209 static void 210 hpgps_shutdown( 211 int unit, 212 struct peer *peer 213 ) 214 { 215 register struct hpgpsunit *up; 216 struct refclockproc *pp; 217 218 pp = peer->procptr; 219 up = (struct hpgpsunit *)pp->unitptr; 220 io_closeclock(&pp->io); 221 free(up); 222 } 223 224 225 /* 226 * hpgps_receive - receive data from the serial interface 227 */ 228 static void 229 hpgps_receive( 230 struct recvbuf *rbufp 231 ) 232 { 233 register struct hpgpsunit *up; 234 struct refclockproc *pp; 235 struct peer *peer; 236 l_fp trtmp; 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 */ 246 int i,m,n; 247 int month, day, lastday; 248 char *tcp; /* timecode pointer (skips over the prompt) */ 249 char prompt[BMAX]; /* prompt in response from receiver */ 250 251 /* 252 * Initialize pointers and read the receiver response 253 */ 254 peer = (struct peer *)rbufp->recv_srcclock; 255 pp = peer->procptr; 256 up = (struct hpgpsunit *)pp->unitptr; 257 *pp->a_lastcode = '\0'; 258 pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); 259 260 #ifdef DEBUG 261 if (debug) 262 printf("hpgps: lencode: %d timecode:%s\n", 263 pp->lencode, pp->a_lastcode); 264 #endif 265 266 /* 267 * If there's no characters in the reply, we can quit now 268 */ 269 if (pp->lencode == 0) 270 return; 271 272 /* 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. 278 * 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. 283 */ 284 285 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; 291 } 292 if (up->linecnt == 0) 293 record_clock_stats(&peer->srcadr, up->statscrn); 294 295 return; 296 } 297 298 record_clock_stats(&peer->srcadr, pp->a_lastcode); 299 pp->lastrec = trtmp; 300 301 up->lastptr = up->statscrn; 302 *up->lastptr = '\0'; 303 up->pollcnt = 2; 304 305 /* 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 312 * and exit. 313 * 314 * Timezone format (including nominal prompt): 315 * scpi > -H,-M<cr><lf> 316 * 317 * Timecode format (including nominal prompt): 318 * scpi > T2yyyymmddhhmmssMFLRVcc<cr><lf> 319 * 320 */ 321 322 (void)strcpy(prompt,pp->a_lastcode); 323 tcp = strrchr(pp->a_lastcode,'>'); 324 if (tcp == NULL) 325 tcp = pp->a_lastcode; 326 else 327 tcp++; 328 prompt[tcp - pp->a_lastcode] = '\0'; 329 while ((*tcp == ' ') || (*tcp == '\t')) tcp++; 330 331 /* 332 * deal with an error indication in the prompt here 333 */ 334 if (strrchr(prompt,'E') > strrchr(prompt,'s')){ 335 #ifdef DEBUG 336 if (debug) 337 printf("hpgps: error indicated in prompt: %s\n", prompt); 338 #endif 339 if (write(pp->io.fd, "*CLS\r\r", 6) != 6) 340 refclock_report(peer, CEVNT_FAULT); 341 } 342 343 /* 344 * make sure we got a timezone or timecode format and 345 * then process accordingly 346 */ 347 m = sscanf(tcp,"%c%c", &tcodechar1, &tcodechar2); 348 349 if (m != 2){ 350 #ifdef DEBUG 351 if (debug) 352 printf("hpgps: no format indicator\n"); 353 #endif 354 refclock_report(peer, CEVNT_BADREPLY); 355 return; 356 } 357 358 switch (tcodechar1) { 359 360 case '+': 361 case '-': 362 m = sscanf(tcp,"%d,%d", &up->tzhour, &up->tzminute); 363 if (m != MTZONE) { 364 #ifdef DEBUG 365 if (debug) 366 printf("hpgps: only %d fields recognized in timezone\n", m); 367 #endif 368 refclock_report(peer, CEVNT_BADREPLY); 369 return; 370 } 371 if ((up->tzhour < -12) || (up->tzhour > 13) || 372 (up->tzminute < -59) || (up->tzminute > 59)){ 373 #ifdef DEBUG 374 if (debug) 375 printf("hpgps: timezone %d, %d out of range\n", 376 up->tzhour, up->tzminute); 377 #endif 378 refclock_report(peer, CEVNT_BADREPLY); 379 return; 380 } 381 return; 382 383 case 'T': 384 break; 385 386 default: 387 #ifdef DEBUG 388 if (debug) 389 printf("hpgps: unrecognized reply format %c%c\n", 390 tcodechar1, tcodechar2); 391 #endif 392 refclock_report(peer, CEVNT_BADREPLY); 393 return; 394 } /* end of tcodechar1 switch */ 395 396 397 switch (tcodechar2) { 398 399 case '2': 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, 403 &expectedsm); 404 n = NTCODET2; 405 406 if (m != MTCODET2){ 407 #ifdef DEBUG 408 if (debug) 409 printf("hpgps: only %d fields recognized in timecode\n", m); 410 #endif 411 refclock_report(peer, CEVNT_BADREPLY); 412 return; 413 } 414 break; 415 416 default: 417 #ifdef DEBUG 418 if (debug) 419 printf("hpgps: unrecognized timecode format %c%c\n", 420 tcodechar1, tcodechar2); 421 #endif 422 refclock_report(peer, CEVNT_BADREPLY); 423 return; 424 } /* end of tcodechar2 format switch */ 425 426 /* 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. 430 */ 431 tcodechksm = 0; 432 while (n-- > 0) tcodechksm += *tcp++; 433 tcodechksm &= 0x00ff; 434 435 if (tcodechksm != expectedsm) { 436 #ifdef DEBUG 437 if (debug) 438 printf("hpgps: checksum %2hX doesn't match %2hX expected\n", 439 tcodechksm, expectedsm); 440 #endif 441 refclock_report(peer, CEVNT_BADREPLY); 442 return; 443 } 444 445 /* 446 * Compute the day of year from the yyyymmdd format. 447 */ 448 if (month < 1 || month > 12 || day < 1) { 449 refclock_report(peer, CEVNT_BADTIME); 450 return; 451 } 452 453 if ( ! isleap_4(pp->year) ) { /* Y2KFixes */ 454 /* not a leap year */ 455 if (day > day1tab[month - 1]) { 456 refclock_report(peer, CEVNT_BADTIME); 457 return; 458 } 459 for (i = 0; i < month - 1; i++) day += day1tab[i]; 460 lastday = 365; 461 } else { 462 /* a leap year */ 463 if (day > day2tab[month - 1]) { 464 refclock_report(peer, CEVNT_BADTIME); 465 return; 466 } 467 for (i = 0; i < month - 1; i++) day += day2tab[i]; 468 lastday = 366; 469 } 470 471 /* 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. 476 */ 477 pp->minute -= up->tzminute; 478 pp->hour -= up->tzhour; 479 480 if (pp->minute < 0) { 481 pp->minute += 60; 482 pp->hour--; 483 } 484 if (pp->minute > 59) { 485 pp->minute -= 60; 486 pp->hour++; 487 } 488 if (pp->hour < 0) { 489 pp->hour += 24; 490 day--; 491 if (day < 1) { 492 pp->year--; 493 if ( isleap_4(pp->year) ) /* Y2KFixes */ 494 day = 366; 495 else 496 day = 365; 497 } 498 } 499 500 if (pp->hour > 23) { 501 pp->hour -= 24; 502 day++; 503 if (day > lastday) { 504 pp->year++; 505 day = 1; 506 } 507 } 508 509 pp->day = day; 510 511 /* 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. 515 */ 516 if (syncchar != '0') { 517 pp->leap = LEAP_NOTINSYNC; 518 } 519 else { 520 switch (leapchar) { 521 522 case '+': 523 pp->leap = LEAP_ADDSECOND; 524 break; 525 526 case '0': 527 pp->leap = LEAP_NOWARNING; 528 break; 529 530 case '-': 531 pp->leap = LEAP_DELSECOND; 532 break; 533 534 default: 535 #ifdef DEBUG 536 if (debug) 537 printf("hpgps: unrecognized leap indicator: %c\n", 538 leapchar); 539 #endif 540 refclock_report(peer, CEVNT_BADTIME); 541 return; 542 } /* end of leapchar switch */ 543 } 544 545 /* 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 551 * the data. 552 */ 553 if (!refclock_process(pp)) { 554 refclock_report(peer, CEVNT_BADTIME); 555 return; 556 } 557 pp->lastref = pp->lastrec; 558 refclock_receive(peer); 559 560 /* 561 * If CLK_FLAG4 is set, ask for the status screen response. 562 */ 563 if (pp->sloppyclockflag & CLK_FLAG4){ 564 up->linecnt = 22; 565 if (write(pp->io.fd, ":SYSTEM:PRINT?\r", 15) != 15) 566 refclock_report(peer, CEVNT_FAULT); 567 } 568 } 569 570 571 /* 572 * hpgps_poll - called by the transmit procedure 573 */ 574 static void 575 hpgps_poll( 576 int unit, 577 struct peer *peer 578 ) 579 { 580 register struct hpgpsunit *up; 581 struct refclockproc *pp; 582 583 /* 584 * Time to poll the clock. The HP 58503A responds to a 585 * ":PTIME:TCODE?" by returning a timecode in the format specified 586 * above. If nothing is heard from the clock for two polls, 587 * declare a timeout and keep going. 588 */ 589 pp = peer->procptr; 590 up = (struct hpgpsunit *)pp->unitptr; 591 if (up->pollcnt == 0) 592 refclock_report(peer, CEVNT_TIMEOUT); 593 else 594 up->pollcnt--; 595 if (write(pp->io.fd, ":PTIME:TCODE?\r", 14) != 14) { 596 refclock_report(peer, CEVNT_FAULT); 597 } 598 else 599 pp->polls++; 600 } 601 602 #else 603 int refclock_hpgps_bs; 604 #endif /* REFCLOCK */ 605