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