source: trunk/third/xntp/xntpd/refclock_hpgps.c @ 10832

Revision 10832, 18.0 KB checked in by brlewis, 27 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r10831, which included commits to RCS files with non-trunk default branches.
Line 
1/*
2 * refclock_hpgps - clock driver for HP 58503A GPS receiver
3 */
4#ifdef HAVE_CONFIG_H
5#include <config.h>
6#endif
7
8#if defined(REFCLOCK) && defined(HPGPS)
9
10#include <stdio.h>
11#include <ctype.h>
12#include <sys/time.h>
13
14#include "ntpd.h"
15#include "ntp_io.h"
16#include "ntp_refclock.h"
17#include "ntp_stdlib.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 NSAMPLES        3       /* stages of median filter */
93#define SMAX            23*80+1 /* for :SYSTEM:PRINT? status screen response */
94
95#define MTZONE          2       /* number of fields in timezone reply */
96#define MTCODET2        12      /* number of fields in timecode format T2 */
97#define NTCODET2        21      /* number of chars to checksum in format T2 */
98
99/*
100 * Imported from ntp_timer module
101 */
102extern u_long current_time;     /* current time (s) */
103
104/*
105 * Imported from ntpd module
106 */
107extern int debug;               /* global debug flag */
108
109/*
110 * Tables to compute the day of year from yyyymmdd timecode.
111 * Viva la leap.
112 */
113static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
114static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
115
116/*
117 * Unit control structure
118 */
119struct hpgpsunit {
120        int     pollcnt;        /* poll message counter */
121        int     tzhour;         /* timezone offset, hours */
122        int     tzminute;       /* timezone offset, minutes */
123        int     linecnt;        /* set for expected multiple line responses */
124        char    *lastptr;       /* pointer to receiver response data */
125        char    statscrn[SMAX]; /* receiver status screen buffer */
126};
127
128/*
129 * Function prototypes
130 */
131static  int     hpgps_start     P((int, struct peer *));
132static  void    hpgps_shutdown  P((int, struct peer *));
133static  void    hpgps_receive   P((struct recvbuf *));
134static  void    hpgps_poll      P((int, struct peer *));
135
136/*
137 * Transfer vector
138 */
139struct  refclock refclock_hpgps = {
140        hpgps_start,            /* start up driver */
141        hpgps_shutdown,         /* shut down driver */
142        hpgps_poll,             /* transmit poll message */
143        noentry,                /* not used (old hpgps_control) */
144        noentry,                /* initialize driver */
145        noentry,                /* not used (old hpgps_buginfo) */
146        NOFLAGS                 /* not used */
147};
148
149
150/*
151 * hpgps_start - open the devices and initialize data for processing
152 */
153static int
154hpgps_start(unit, peer)
155        int unit;
156        struct peer *peer;
157{
158        register struct hpgpsunit *up;
159        struct refclockproc *pp;
160        int fd;
161        char device[20];
162
163        /*
164         * Open serial port. Use CLK line discipline, if available.
165         */
166        (void)sprintf(device, DEVICE, unit);
167#ifdef TTYCLK
168        if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
169#else
170        if (!(fd = refclock_open(device, SPEED232, 0)))
171#endif /* TTYCLK */
172                return (0);
173
174        /*
175         * Allocate and initialize unit structure
176         */
177        if (!(up = (struct hpgpsunit *)
178            emalloc(sizeof(struct hpgpsunit)))) {
179                (void) close(fd);
180                return (0);
181        }
182        memset((char *)up, 0, sizeof(struct hpgpsunit));
183        pp = peer->procptr;
184        pp->io.clock_recv = hpgps_receive;
185        pp->io.srcclock = (caddr_t)peer;
186        pp->io.datalen = 0;
187        pp->io.fd = fd;
188        if (!io_addclock(&pp->io)) {
189                (void) close(fd);
190                free(up);
191                return (0);
192        }
193        pp->unitptr = (caddr_t)up;
194
195        /*
196         * Initialize miscellaneous variables
197         */
198        peer->precision = PRECISION;
199        pp->clockdesc = DESCRIPTION;
200        memcpy((char *)&pp->refid, REFID, 4);
201        up->tzhour = 0;
202        up->tzminute = 0;
203
204        *up->statscrn = '\0';
205        up->lastptr = up->statscrn;
206        up->pollcnt = 2;
207
208        /*
209         * Get the identifier string, which is logged but otherwise ignored,
210         * and get the local timezone information
211         */
212        up->linecnt = 1;
213        if (write(pp->io.fd, "*IDN?\r:PTIME:TZONE?\r", 20) != 20)
214            refclock_report(peer, CEVNT_FAULT);
215
216        return (1);
217}
218
219
220/*
221 * hpgps_shutdown - shut down the clock
222 */
223static void
224hpgps_shutdown(unit, peer)
225        int unit;
226        struct peer *peer;
227{
228        register struct hpgpsunit *up;
229        struct refclockproc *pp;
230
231        pp = peer->procptr;
232        up = (struct hpgpsunit *)pp->unitptr;
233        io_closeclock(&pp->io);
234        free(up);
235}
236
237
238/*
239 * hpgps_receive - receive data from the serial interface
240 */
241static void
242hpgps_receive(rbufp)
243        struct recvbuf *rbufp;
244{
245        register struct hpgpsunit *up;
246        struct refclockproc *pp;
247        struct peer *peer;
248        l_fp trtmp;
249        char tcodechar1;        /* identifies timecode format */
250        char tcodechar2;        /* identifies timecode format */
251        char timequal;          /* time figure of merit: 0-9 */
252        char freqqual;          /* frequency figure of merit: 0-3 */
253        char leapchar;          /* leapsecond: + or 0 or - */
254        char servchar;          /* request for service: 0 = no, 1 = yes */
255        char syncchar;          /* time info is invalid: 0 = no, 1 = yes */
256        short expectedsm;       /* expected timecode byte checksum */
257        short tcodechksm;       /* computed timecode byte checksum */
258        int i,m,n;
259        int month, day, lastday;
260        char *tcp;              /* timecode pointer (skips over the prompt) */
261        char prompt[BMAX];      /* prompt in response from receiver */
262
263        /*
264         * Initialize pointers and read the receiver response
265         */
266        peer = (struct peer *)rbufp->recv_srcclock;
267        pp = peer->procptr;
268        up = (struct hpgpsunit *)pp->unitptr;
269        *pp->a_lastcode = '\0';
270        pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
271
272#ifdef DEBUG
273        if (debug)
274            printf("hpgps: lencode: %d timecode:%s\n",
275                   pp->lencode, pp->a_lastcode);
276#endif
277
278        /*
279         * If there's no characters in the reply, we can quit now
280         */
281        if (pp->lencode == 0)
282                return;
283
284        /*
285         * If linecnt is greater than zero, we are getting information only,
286         * such as the receiver identification string or the receiver status
287         * screen, so put the receiver response at the end of the status
288         * screen buffer. When we have the last line, write the buffer to
289         * the clockstats file and return without further processing.
290         *
291         * If linecnt is zero, we are expecting either the timezone
292         * or a timecode. At this point, also write the response
293         * to the clockstats file, and go on to process the prompt (if any),
294         * timezone, or timecode and timestamp.
295         */
296
297
298        if (up->linecnt-- > 0) {
299            if ((int)(pp->lencode + 2) <= (SMAX - (up->lastptr - up->statscrn))) {
300                *up->lastptr++ = '\n';
301                (void)strcpy(up->lastptr, pp->a_lastcode);
302                up->lastptr += pp->lencode;
303            }
304            if (up->linecnt == 0)
305                record_clock_stats(&peer->srcadr, up->statscrn);
306               
307            return;
308        }
309
310        record_clock_stats(&peer->srcadr, pp->a_lastcode);
311        pp->lastrec = trtmp;
312           
313        up->lastptr = up->statscrn;
314        *up->lastptr = '\0';
315        up->pollcnt = 2;
316
317        /*
318         * We get down to business: get a prompt if one is there, issue
319         * a clear status command if it contains an error indication.
320         * Next, check for either the timezone reply or the timecode reply
321         * and decode it.  If we don't recognize the reply, or don't get the
322         * proper number of decoded fields, or get an out of range timezone,
323         * or if the timecode checksum is bad, then we declare bad format
324         * and exit.
325         *
326         * Timezone format (including nominal prompt):
327         * scpi > -H,-M<cr><lf>
328         *
329         * Timecode format (including nominal prompt):
330         * scpi > T2yyyymmddhhmmssMFLRVcc<cr><lf>
331         *
332         */
333
334        (void)strcpy(prompt,pp->a_lastcode);
335        tcp = strrchr(pp->a_lastcode,'>');
336        if (tcp == NULL)
337            tcp = pp->a_lastcode;
338        else
339            tcp++;
340        prompt[tcp - pp->a_lastcode] = '\0';
341        while ((*tcp == ' ') || (*tcp == '\t')) tcp++;
342
343        /*
344         * deal with an error indication in the prompt here
345         */
346        if (strrchr(prompt,'E') > strrchr(prompt,'s')){
347#ifdef DEBUG
348            if (debug)
349                printf("hpgps: error indicated in prompt: %s\n", prompt);
350#endif
351            if (write(pp->io.fd, "*CLS\r\r", 6) != 6)
352                refclock_report(peer, CEVNT_FAULT);
353            }
354
355        /*
356         * make sure we got a timezone or timecode format and
357         * then process accordingly
358         */
359        m = sscanf(tcp,"%c%c", &tcodechar1, &tcodechar2);
360
361        if (m != 2){
362#ifdef DEBUG
363            if (debug)
364                printf("hpgps: no format indicator\n");
365#endif
366            refclock_report(peer, CEVNT_BADREPLY);
367            return;
368            }
369
370        switch (tcodechar1) {
371
372        case '+':
373        case '-':
374        m = sscanf(tcp,"%d,%d", &up->tzhour, &up->tzminute);
375        if (m != MTZONE) {
376#ifdef DEBUG
377            if (debug)
378                printf("hpgps: only %d fields recognized in timezone\n", m);
379#endif
380            refclock_report(peer, CEVNT_BADREPLY);
381            return;
382            }
383        if ((up->tzhour < -12) || (up->tzhour > 13) ||
384            (up->tzminute < -59) || (up->tzminute > 59)){
385#ifdef DEBUG
386            if (debug)
387                printf("hpgps: timezone %d, %d out of range\n",
388                       up->tzhour, up->tzminute);
389#endif
390            refclock_report(peer, CEVNT_BADREPLY);
391            return;
392            }
393        return;
394
395        case 'T':
396        break;
397
398        default:
399#ifdef DEBUG
400        if (debug)
401            printf("hpgps: unrecognized reply format %c%c\n",
402                   tcodechar1, tcodechar2);
403#endif
404        refclock_report(peer, CEVNT_BADREPLY);
405        return;
406        } /* end of tcodechar1 switch */
407
408
409        switch (tcodechar2) {
410
411        case '2':
412        m = sscanf(tcp,"%*c%*c%4d%2d%2d%2d%2d%2d%c%c%c%c%c%2hx",
413            &pp->year, &month, &day, &pp->hour, &pp->minute, &pp->second,
414            &timequal, &freqqual, &leapchar, &servchar, &syncchar,
415            &expectedsm);
416        n = NTCODET2;
417
418        if (m != MTCODET2){
419#ifdef DEBUG
420            if (debug)
421                printf("hpgps: only %d fields recognized in timecode\n", m);
422#endif
423            refclock_report(peer, CEVNT_BADREPLY);
424            return;
425            }
426        break;
427
428        default:
429#ifdef DEBUG
430        if (debug)
431            printf("hpgps: unrecognized timecode format %c%c\n",
432                   tcodechar1, tcodechar2);
433#endif
434        refclock_report(peer, CEVNT_BADREPLY);
435        return;
436        } /* end of tcodechar2 format switch */
437           
438        /*
439         * Compute and verify the checksum.
440         * Characters are summed starting at tcodechar1, ending at just
441         * before the expected checksum.  Bail out if incorrect.
442         */
443        tcodechksm = 0;
444        while (n-- > 0) tcodechksm += *tcp++;
445        tcodechksm &= 0x00ff;
446
447        if (tcodechksm != expectedsm) {
448#ifdef DEBUG
449            if (debug)
450                printf("hpgps: checksum %2hX doesn't match %2hX expected\n",
451                       tcodechksm, expectedsm);
452#endif
453            refclock_report(peer, CEVNT_BADREPLY);
454            return;
455        }
456
457        /*
458         * Compute the day of year from the yyyymmdd format.
459         * Exception noted for year 2000.
460         */
461        if (month < 1 || month > 12 || day < 1) {
462                refclock_report(peer, CEVNT_BADTIME);
463                return;
464        }
465        if ((pp->year % 4) || (pp->year == 2000)) {
466                /* not a leap year */
467                if (day > day1tab[month - 1]) {
468                        refclock_report(peer, CEVNT_BADTIME);
469                        return;
470                }
471                for (i = 0; i < month - 1; i++) day += day1tab[i];
472                lastday = 365;
473        } else {
474                /* a leap year */
475                if (day > day2tab[month - 1]) {
476                        refclock_report(peer, CEVNT_BADTIME);
477                        return;
478                }
479                for (i = 0; i < month - 1; i++) day += day2tab[i];
480                lastday = 366;
481        }
482
483        /*
484         * Deal with the timezone offset here. The receiver timecode is in
485         * local time = UTC + :PTIME:TZONE, so SUBTRACT the timezone values.
486         * For example, Pacific Standard Time is -8 hours , 0 minutes.
487         * Deal with the underflows and overflows.
488         */
489        pp->minute -= up->tzminute;
490        pp->hour -= up->tzhour;
491
492        if (pp->minute < 0) {
493                pp->minute += 60;
494                pp->hour--;
495        }
496        if (pp->minute > 59) {
497                pp->minute -= 60;
498                pp->hour++;
499        }
500        if (pp->hour < 0)  {
501            pp->hour += 24;
502            day--;
503            if (day < 1) {
504                pp->year--;
505                if ((pp->year % 4) || (pp->year % 400))
506                        day = 365;
507                else
508                        day = 366;
509                }
510            }
511
512        if (pp->hour > 23) {
513            pp->hour -= 24;
514            day++;
515            if (day > lastday) {
516                pp->year++;
517                day = 1;
518                }
519            }
520
521        pp->day = day;
522
523        /*
524         * Decode the MFLRV indicators.
525         * NEED TO FIGURE OUT how to deal with the request for service,
526         * time quality, and frequency quality indicators some day.
527         */
528        if (syncchar != '0') {
529            pp->leap = LEAP_NOTINSYNC;
530            }
531        else {
532            switch (leapchar) {
533
534                    case '+':
535                    pp->leap = LEAP_ADDSECOND;
536                    break;
537                     
538                    case '0':
539                    pp->leap = LEAP_NOWARNING;
540                    break;
541                     
542                    case '-':
543                    pp->leap = LEAP_DELSECOND;
544                    break;
545                     
546                    default:
547#ifdef DEBUG
548                    if (debug)
549                        printf("hpgps: unrecognized leap indicator: %c\n",
550                               leapchar);
551#endif
552                     refclock_report(peer, CEVNT_BADTIME);
553                     return;
554            } /* end of leapchar switch */
555
556            pp->lasttime = current_time;
557        }
558
559        /*
560         * Process the new sample in the median filter and determine the
561         * reference clock offset and dispersion. We use lastrec as both
562         * the reference time and receive time in order to avoid being
563         * cute, like setting the reference time later than the receive
564         * time, which may cause a paranoid protocol module to chuck out
565         * the data.
566         */
567        if (!refclock_process(pp, NSAMPLES, NSAMPLES)) {
568                refclock_report(peer, CEVNT_BADTIME);
569                return;
570        }
571        trtmp = pp->lastrec;
572
573#ifdef DEBUG
574if (debug)
575printf("hpgps: refclock_rcv: off: %s, disp: %s, tref: %s, trec: %s, lp: %hd\n",
576       lfptoa(&pp->offset,6),
577       ufptoa(pp->dispersion,6),
578       ulfptoa(&trtmp,6),
579       ulfptoa(&pp->lastrec,6),
580       pp->leap);
581#endif
582
583        refclock_receive(peer, &pp->offset, 0, pp->dispersion, &trtmp,
584                         &pp->lastrec, pp->leap);
585
586       /*
587        * If CLK_FLAG4 is set, ask for the status screen response.
588        */
589        if (pp->sloppyclockflag & CLK_FLAG4){
590            up->linecnt = 22;
591            if (write(pp->io.fd, ":SYSTEM:PRINT?\r", 15) != 15)
592                refclock_report(peer, CEVNT_FAULT);
593            }
594}
595
596
597/*
598 * hpgps_poll - called by the transmit procedure
599 */
600static void
601hpgps_poll(unit, peer)
602        int unit;
603        struct peer *peer;
604{
605        register struct hpgpsunit *up;
606        struct refclockproc *pp;
607
608        /*
609         * Time to poll the clock. The HP 58503A responds to a
610         * ":PTIME:TCODE?" by returning a timecode in the format specified
611         * above. If nothing is heard from the clock for two polls,
612         * declare a timeout and keep going.
613         */
614        pp = peer->procptr;
615        up = (struct hpgpsunit *)pp->unitptr;
616        if (up->pollcnt == 0)
617                refclock_report(peer, CEVNT_TIMEOUT);
618        else
619                up->pollcnt--;
620        if (write(pp->io.fd, ":PTIME:TCODE?\r", 14) != 14) {
621            refclock_report(peer, CEVNT_FAULT);
622            }
623        else
624            pp->polls++;
625}
626
627#endif
Note: See TracBrowser for help on using the repository browser.