PageRenderTime 40ms CodeModel.GetById 12ms app.highlight 22ms RepoModel.GetById 1ms app.codeStats 0ms

/contrib/ntp/ntpd/refclock_hpgps.c

https://bitbucket.org/freebsd/freebsd-head/
C | 619 lines | 358 code | 67 blank | 194 comment | 78 complexity | 8470fef4c27f1d2d8329376550af9cef MD5 | raw file
  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 */
110static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
111static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
112
113/*
114 * Unit control structure
115 */
116struct 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 */
128static	int	hpgps_start	P((int, struct peer *));
129static	void	hpgps_shutdown	P((int, struct peer *));
130static	void	hpgps_receive	P((struct recvbuf *));
131static	void	hpgps_poll	P((int, struct peer *));
132
133/*
134 * Transfer vector
135 */
136struct	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 */
150static int
151hpgps_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 */
224static void
225hpgps_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 */
243static void
244hpgps_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 */
589static void
590hpgps_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
618int refclock_hpgps_bs;
619#endif /* REFCLOCK */