Annotation of embedaddon/ntp/ntpd/refclock_hpgps.c, revision 1.1.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>