PageRenderTime 57ms CodeModel.GetById 13ms app.highlight 38ms RepoModel.GetById 1ms app.codeStats 0ms

/contrib/ntp/ntpd/refclock_nmea.c

https://bitbucket.org/freebsd/freebsd-head/
C | 763 lines | 497 code | 84 blank | 182 comment | 99 complexity | 8ff99d453d85b0c7296b3355a9d68613 MD5 | raw file
  1/*
  2 * refclock_nmea.c - clock driver for an NMEA GPS CLOCK
  3 *		Michael Petry Jun 20, 1994
  4 *		 based on refclock_heathn.c
  5 */
  6#ifdef HAVE_CONFIG_H
  7#include <config.h>
  8#endif
  9
 10#if defined(REFCLOCK) && defined(CLOCK_NMEA)
 11
 12#include <stdio.h>
 13#include <ctype.h>
 14
 15#include "ntpd.h"
 16#include "ntp_io.h"
 17#include "ntp_unixtime.h"
 18#include "ntp_refclock.h"
 19#include "ntp_stdlib.h"
 20
 21#ifdef HAVE_PPSAPI
 22# include "ppsapi_timepps.h"
 23#endif /* HAVE_PPSAPI */
 24
 25#ifdef SYS_WINNT
 26extern int async_write(int, const void *, unsigned int);
 27#undef write
 28#define write(fd, data, octets)	async_write(fd, data, octets)
 29#endif
 30
 31/*
 32 * This driver supports the NMEA GPS Receiver with
 33 *
 34 * Protype was refclock_trak.c, Thanks a lot.
 35 *
 36 * The receiver used spits out the NMEA sentences for boat navigation.
 37 * And you thought it was an information superhighway.  Try a raging river
 38 * filled with rapids and whirlpools that rip away your data and warp time.
 39 *
 40 * If HAVE_PPSAPI is defined code to use the PPSAPI will be compiled in.
 41 * On startup if initialization of the PPSAPI fails, it will fall back
 42 * to the "normal" timestamps.
 43 *
 44 * The PPSAPI part of the driver understands fudge flag2 and flag3. If
 45 * flag2 is set, it will use the clear edge of the pulse. If flag3 is
 46 * set, kernel hardpps is enabled.
 47 *
 48 * GPS sentences other than RMC (the default) may be enabled by setting
 49 * the relevent bits of 'mode' in the server configuration line
 50 * server 127.127.20.x mode X
 51 * 
 52 * bit 0 - enables RMC (1)
 53 * bit 1 - enables GGA (2)
 54 * bit 2 - enables GLL (4)
 55 * multiple sentences may be selected
 56 */
 57
 58/*
 59 * Definitions
 60 */
 61#ifdef SYS_WINNT
 62# define DEVICE "COM%d:" 	/* COM 1 - 3 supported */
 63#else
 64# define DEVICE	"/dev/gps%d"	/* name of radio device */
 65#endif
 66#define	SPEED232	B4800	/* uart speed (4800 bps) */
 67#define	PRECISION	(-9)	/* precision assumed (about 2 ms) */
 68#define	PPS_PRECISION	(-20)	/* precision assumed (about 1 us) */
 69#define	REFID		"GPS\0"	/* reference id */
 70#define	DESCRIPTION	"NMEA GPS Clock" /* who we are */
 71#define NANOSECOND	1000000000 /* one second (ns) */
 72#define RANGEGATE	500000	/* range gate (ns) */
 73
 74#define LENNMEA		75	/* min timecode length */
 75
 76/*
 77 * Tables to compute the ddd of year form icky dd/mm timecode. Viva la
 78 * leap.
 79 */
 80static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 81static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 82
 83/*
 84 * Unit control structure
 85 */
 86struct nmeaunit {
 87	int	pollcnt;	/* poll message counter */
 88	int	polled;		/* Hand in a sample? */
 89	l_fp	tstamp;		/* timestamp of last poll */
 90#ifdef HAVE_PPSAPI
 91	struct timespec ts;	/* last timestamp */
 92	pps_params_t pps_params; /* pps parameters */
 93	pps_info_t pps_info;	/* last pps data */
 94	pps_handle_t handle;	/* pps handlebars */
 95#endif /* HAVE_PPSAPI */
 96};
 97
 98/*
 99 * Function prototypes
100 */
101static	int	nmea_start	P((int, struct peer *));
102static	void	nmea_shutdown	P((int, struct peer *));
103#ifdef HAVE_PPSAPI
104static	void	nmea_control	P((int, struct refclockstat *, struct
105				    refclockstat *, struct peer *));
106static	int	nmea_ppsapi	P((struct peer *, int, int));
107static	int	nmea_pps	P((struct nmeaunit *, l_fp *));
108#endif /* HAVE_PPSAPI */
109static	void	nmea_receive	P((struct recvbuf *));
110static	void	nmea_poll	P((int, struct peer *));
111static	void	gps_send	P((int, const char *, struct peer *));
112static	char	*field_parse	P((char *, int));
113
114/*
115 * Transfer vector
116 */
117struct	refclock refclock_nmea = {
118	nmea_start,		/* start up driver */
119	nmea_shutdown,	/* shut down driver */
120	nmea_poll,		/* transmit poll message */
121#ifdef HAVE_PPSAPI
122	nmea_control,		/* fudge control */
123#else
124	noentry,		/* fudge control */
125#endif /* HAVE_PPSAPI */
126	noentry,		/* initialize driver */
127	noentry,		/* buginfo */
128	NOFLAGS			/* not used */
129};
130
131/*
132 * nmea_start - open the GPS devices and initialize data for processing
133 */
134static int
135nmea_start(
136	int unit,
137	struct peer *peer
138	)
139{
140	register struct nmeaunit *up;
141	struct refclockproc *pp;
142	int fd;
143	char device[20];
144
145	/*
146	 * Open serial port. Use CLK line discipline, if available.
147	 */
148	(void)sprintf(device, DEVICE, unit);
149
150	fd = refclock_open(device, SPEED232, LDISC_CLK);
151	if (fd <= 0) {
152#ifdef HAVE_READLINK
153          /* nmead support added by Jon Miner (cp_n18@yahoo.com)
154           *
155           * See http://home.hiwaay.net/~taylorc/gps/nmea-server/
156           * for information about nmead
157           *
158           * To use this, you need to create a link from /dev/gpsX to
159           * the server:port where nmead is running.  Something like this:
160           *
161           * ln -s server:port /dev/gps1
162           */
163          char buffer[80];
164          char *nmea_host;
165          int   nmea_port;
166          int   len;
167          struct hostent *he;
168          struct protoent *p;
169          struct sockaddr_in so_addr;
170
171          if ((len = readlink(device,buffer,sizeof(buffer))) == -1)
172            return(0);
173          buffer[len] = 0;
174
175          if ((nmea_host = strtok(buffer,":")) == NULL)
176            return(0);
177         
178          nmea_port = atoi(strtok(NULL,":"));
179
180          if ((he = gethostbyname(nmea_host)) == NULL)
181            return(0);
182          if ((p = getprotobyname("ip")) == NULL)
183            return(0);
184          so_addr.sin_family = AF_INET;
185          so_addr.sin_port = htons(nmea_port);
186          so_addr.sin_addr = *((struct in_addr *) he->h_addr);
187
188          if ((fd = socket(PF_INET,SOCK_STREAM,p->p_proto)) == -1)
189            return(0);
190          if (connect(fd,(struct sockaddr *)&so_addr,SOCKLEN(&so_addr)) == -1) {
191            close(fd);
192            return (0);
193          }
194#else
195            return (0);
196#endif
197        }
198
199	/*
200	 * Allocate and initialize unit structure
201	 */
202	up = (struct nmeaunit *)emalloc(sizeof(struct nmeaunit));
203	if (up == NULL) {
204		(void) close(fd);
205		return (0);
206	}
207	memset((char *)up, 0, sizeof(struct nmeaunit));
208	pp = peer->procptr;
209	pp->io.clock_recv = nmea_receive;
210	pp->io.srcclock = (caddr_t)peer;
211	pp->io.datalen = 0;
212	pp->io.fd = fd;
213	if (!io_addclock(&pp->io)) {
214		(void) close(fd);
215		free(up);
216		return (0);
217	}
218	pp->unitptr = (caddr_t)up;
219
220	/*
221	 * Initialize miscellaneous variables
222	 */
223	peer->precision = PRECISION;
224	pp->clockdesc = DESCRIPTION;
225	memcpy((char *)&pp->refid, REFID, 4);
226	up->pollcnt = 2;
227	gps_send(pp->io.fd,"$PMOTG,RMC,0000*1D\r\n", peer);
228
229#ifdef HAVE_PPSAPI
230	/*
231	 * Start the PPSAPI interface if it is there. Default to use
232	 * the assert edge and do not enable the kernel hardpps.
233	 */
234	if (time_pps_create(fd, &up->handle) < 0) {
235		up->handle = 0;
236		msyslog(LOG_ERR,
237		    "refclock_nmea: time_pps_create failed: %m");
238		return (1);
239	}
240	return(nmea_ppsapi(peer, 0, 0));
241#else
242	return (1);
243#endif /* HAVE_PPSAPI */
244}
245
246/*
247 * nmea_shutdown - shut down a GPS clock
248 */
249static void
250nmea_shutdown(
251	int unit,
252	struct peer *peer
253	)
254{
255	register struct nmeaunit *up;
256	struct refclockproc *pp;
257
258	pp = peer->procptr;
259	up = (struct nmeaunit *)pp->unitptr;
260#ifdef HAVE_PPSAPI
261	if (up->handle != 0)
262		time_pps_destroy(up->handle);
263#endif /* HAVE_PPSAPI */
264	io_closeclock(&pp->io);
265	free(up);
266}
267
268#ifdef HAVE_PPSAPI
269/*
270 * nmea_control - fudge control
271 */
272static void
273nmea_control(
274	int unit,		/* unit (not used */
275	struct refclockstat *in, /* input parameters (not uded) */
276	struct refclockstat *out, /* output parameters (not used) */
277	struct peer *peer	/* peer structure pointer */
278	)
279{
280	struct refclockproc *pp;
281
282	pp = peer->procptr;
283	nmea_ppsapi(peer, pp->sloppyclockflag & CLK_FLAG2,
284	    pp->sloppyclockflag & CLK_FLAG3);
285}
286
287
288/*
289 * Initialize PPSAPI
290 */
291int
292nmea_ppsapi(
293	struct peer *peer,	/* peer structure pointer */
294	int enb_clear,		/* clear enable */
295	int enb_hardpps		/* hardpps enable */
296	)
297{
298	struct refclockproc *pp;
299	struct nmeaunit *up;
300	int capability;
301
302	pp = peer->procptr;
303	up = (struct nmeaunit *)pp->unitptr;
304	if (time_pps_getcap(up->handle, &capability) < 0) {
305		msyslog(LOG_ERR,
306		    "refclock_nmea: time_pps_getcap failed: %m");
307		return (0);
308	}
309	memset(&up->pps_params, 0, sizeof(pps_params_t));
310	if (enb_clear)
311		up->pps_params.mode = capability & PPS_CAPTURECLEAR;
312	else
313		up->pps_params.mode = capability & PPS_CAPTUREASSERT;
314	if (!up->pps_params.mode) {
315		msyslog(LOG_ERR,
316		    "refclock_nmea: invalid capture edge %d",
317		    !enb_clear);
318		return (0);
319	}
320	up->pps_params.mode |= PPS_TSFMT_TSPEC;
321	if (time_pps_setparams(up->handle, &up->pps_params) < 0) {
322		msyslog(LOG_ERR,
323		    "refclock_nmea: time_pps_setparams failed: %m");
324		return (0);
325	}
326	if (enb_hardpps) {
327		if (time_pps_kcbind(up->handle, PPS_KC_HARDPPS,
328				    up->pps_params.mode & ~PPS_TSFMT_TSPEC,
329				    PPS_TSFMT_TSPEC) < 0) {
330			msyslog(LOG_ERR,
331			    "refclock_nmea: time_pps_kcbind failed: %m");
332			return (0);
333		}
334		pps_enable = 1;
335	}
336	peer->precision = PPS_PRECISION;
337
338#if DEBUG
339	if (debug) {
340		time_pps_getparams(up->handle, &up->pps_params);
341		printf(
342		    "refclock_ppsapi: capability 0x%x version %d mode 0x%x kern %d\n",
343		    capability, up->pps_params.api_version,
344		    up->pps_params.mode, enb_hardpps);
345	}
346#endif
347
348	return (1);
349}
350
351/*
352 * Get PPSAPI timestamps.
353 *
354 * Return 0 on failure and 1 on success.
355 */
356static int
357nmea_pps(
358	struct nmeaunit *up,
359	l_fp *tsptr
360	)
361{
362	pps_info_t pps_info;
363	struct timespec timeout, ts;
364	double dtemp;
365	l_fp tstmp;
366
367	/*
368	 * Convert the timespec nanoseconds field to ntp l_fp units.
369	 */ 
370	if (up->handle == 0)
371		return (0);
372	timeout.tv_sec = 0;
373	timeout.tv_nsec = 0;
374	memcpy(&pps_info, &up->pps_info, sizeof(pps_info_t));
375	if (time_pps_fetch(up->handle, PPS_TSFMT_TSPEC, &up->pps_info,
376	    &timeout) < 0)
377		return (0);
378	if (up->pps_params.mode & PPS_CAPTUREASSERT) {
379		if (pps_info.assert_sequence ==
380		    up->pps_info.assert_sequence)
381			return (0);
382		ts = up->pps_info.assert_timestamp;
383	} else if (up->pps_params.mode & PPS_CAPTURECLEAR) {
384		if (pps_info.clear_sequence ==
385		    up->pps_info.clear_sequence)
386			return (0);
387		ts = up->pps_info.clear_timestamp;
388	} else {
389		return (0);
390	}
391	if ((up->ts.tv_sec == ts.tv_sec) && (up->ts.tv_nsec == ts.tv_nsec))
392		return (0);
393	up->ts = ts;
394
395	tstmp.l_ui = ts.tv_sec + JAN_1970;
396	dtemp = ts.tv_nsec * FRAC / 1e9;
397	tstmp.l_uf = (u_int32)dtemp;
398	*tsptr = tstmp;
399	return (1);
400}
401#endif /* HAVE_PPSAPI */
402
403/*
404 * nmea_receive - receive data from the serial interface
405 */
406static void
407nmea_receive(
408	struct recvbuf *rbufp
409	)
410{
411	register struct nmeaunit *up;
412	struct refclockproc *pp;
413	struct peer *peer;
414	int month, day;
415	int i;
416	char *cp, *dp;
417	int cmdtype;
418	/* Use these variables to hold data until we decide its worth keeping */
419	char	rd_lastcode[BMAX];
420	l_fp	rd_tmp;
421	u_short	rd_lencode;
422
423	/*
424	 * Initialize pointers and read the timecode and timestamp
425	 */
426	peer = (struct peer *)rbufp->recv_srcclock;
427	pp = peer->procptr;
428	up = (struct nmeaunit *)pp->unitptr;
429	rd_lencode = (u_short)refclock_gtlin(rbufp, rd_lastcode, BMAX, &rd_tmp);
430
431	/*
432	 * There is a case that a <CR><LF> gives back a "blank" line
433	 */
434	if (rd_lencode == 0)
435	    return;
436
437#ifdef DEBUG
438	if (debug)
439	    printf("nmea: gpsread %d %s\n", rd_lencode,
440		   rd_lastcode);
441#endif
442
443	/*
444	 * We check the timecode format and decode its contents. The
445	 * we only care about a few of them.  The most important being
446	 * the $GPRMC format
447	 * $GPRMC,hhmmss,a,fddmm.xx,n,dddmmm.xx,w,zz.z,yyy.,ddmmyy,dd,v*CC
448	 * For Magellan (ColorTrak) GLL probably datum (order of sentences)
449	 * also mode (0,1,2,3) select sentence ANY/ALL, RMC, GGA, GLL
450	 * $GPGLL,3513.8385,S,14900.7851,E,232420.594,A*21
451  	 * $GPGGA,232420.59,3513.8385,S,14900.7851,E,1,05,3.4,00519,M,,,,*3F
452	 * $GPRMB,...
453	 * $GPRMC,232418.19,A,3513.8386,S,14900.7853,E,00.0,000.0,121199,12.,E*77
454	 * $GPAPB,...
455	 * $GPGSA,...
456	 * $GPGSV,...
457	 * $GPGSV,...
458	 */
459#define GPXXX	0
460#define GPRMC	1
461#define GPGGA	2
462#define GPGLL	4
463	cp = rd_lastcode;
464	cmdtype=0;
465	if(strncmp(cp,"$GPRMC",6)==0) {
466		cmdtype=GPRMC;
467	}
468	else if(strncmp(cp,"$GPGGA",6)==0) {
469		cmdtype=GPGGA;
470	}
471	else if(strncmp(cp,"$GPGLL",6)==0) {
472		cmdtype=GPGLL;
473	}
474	else if(strncmp(cp,"$GPXXX",6)==0) {
475		cmdtype=GPXXX;
476	}
477	else
478	    return;
479
480
481	/* See if I want to process this message type */
482	if ( ((peer->ttl == 0) && (cmdtype != GPRMC))
483           || ((peer->ttl != 0) && !(cmdtype & peer->ttl)) )
484		return;
485
486	pp->lencode = rd_lencode;
487	strcpy(pp->a_lastcode,rd_lastcode);
488	cp = pp->a_lastcode;
489
490	pp->lastrec = up->tstamp = rd_tmp;
491	up->pollcnt = 2;
492
493#ifdef DEBUG
494	if (debug)
495	    printf("nmea: timecode %d %s\n", pp->lencode,
496		   pp->a_lastcode);
497#endif
498
499
500	/* Grab field depending on clock string type */
501	switch( cmdtype ) {
502	    case GPRMC:
503		/*
504		 * Test for synchronization.  Check for quality byte.
505		 */
506		dp = field_parse(cp,2);
507		if( dp[0] != 'A')
508			pp->leap = LEAP_NOTINSYNC;
509		else
510			pp->leap = LEAP_NOWARNING;
511
512		/* Now point at the time field */
513		dp = field_parse(cp,1);
514		break;
515
516
517	    case GPGGA:
518		/*
519		 * Test for synchronization.  Check for quality byte.
520		 */
521		dp = field_parse(cp,6);
522		if( dp[0] == '0')
523			pp->leap = LEAP_NOTINSYNC;
524		else
525			pp->leap = LEAP_NOWARNING;
526
527		/* Now point at the time field */
528		dp = field_parse(cp,1);
529		break;
530
531
532	    case GPGLL:
533		/*
534		 * Test for synchronization.  Check for quality byte.
535		 */
536		dp = field_parse(cp,6);
537		if( dp[0] != 'A')
538			pp->leap = LEAP_NOTINSYNC;
539		else
540			pp->leap = LEAP_NOWARNING;
541
542		/* Now point at the time field */
543		dp = field_parse(cp,5);
544		break;
545
546
547	    case GPXXX:
548		return;
549	    default:
550		return;
551
552	}
553
554		/*
555		 *	Check time code format of NMEA
556		 */
557
558		if( !isdigit((int)dp[0]) ||
559		    !isdigit((int)dp[1]) ||
560		    !isdigit((int)dp[2]) ||
561		    !isdigit((int)dp[3]) ||
562		    !isdigit((int)dp[4]) ||
563		    !isdigit((int)dp[5])	
564		    ) {
565			refclock_report(peer, CEVNT_BADREPLY);
566			return;
567		}
568
569
570	/*
571	 * Convert time and check values.
572	 */
573	pp->hour = ((dp[0] - '0') * 10) + dp[1] - '0';
574	pp->minute = ((dp[2] - '0') * 10) + dp[3] -  '0';
575	pp->second = ((dp[4] - '0') * 10) + dp[5] - '0';
576	/* Default to 0 milliseconds, if decimal convert milliseconds in
577	   one, two or three digits
578	*/
579	pp->nsec = 0; 
580	if (dp[6] == '.') {
581		if (isdigit((int)dp[7])) {
582			pp->nsec = (dp[7] - '0') * 100000000;
583			if (isdigit((int)dp[8])) {
584				pp->nsec += (dp[8] - '0') * 10000000;
585				if (isdigit((int)dp[9])) {
586					pp->nsec += (dp[9] - '0') * 1000000;
587				}
588			}
589		}
590	}
591
592	if (pp->hour > 23 || pp->minute > 59 || pp->second > 59
593	  || pp->nsec > 1000000000) {
594		refclock_report(peer, CEVNT_BADTIME);
595		return;
596	}
597
598
599	/*
600	 * Convert date and check values.
601	 */
602	if (cmdtype==GPRMC) {
603	    dp = field_parse(cp,9);
604	    day = dp[0] - '0';
605	    day = (day * 10) + dp[1] - '0';
606	    month = dp[2] - '0';
607	    month = (month * 10) + dp[3] - '0';
608	    pp->year = dp[4] - '0';
609	    pp->year = (pp->year * 10) + dp[5] - '0';
610	}
611	else {
612	/* only time */
613	    time_t tt = time(NULL);
614	    struct tm * t = gmtime(&tt);
615	    day = t->tm_mday;
616	    month = t->tm_mon + 1;
617	    pp->year= t->tm_year;
618	}
619
620	if (month < 1 || month > 12 || day < 1) {
621		refclock_report(peer, CEVNT_BADTIME);
622		return;
623	}
624
625        /* Hmmmm this will be a nono for 2100,2200,2300 but I don't think I'll be here */
626        /* good thing that 2000 is a leap year */
627	/* pp->year will be 00-99 if read from GPS, 00->  (years since 1900) from tm_year */
628	if (pp->year % 4) {
629		if (day > day1tab[month - 1]) {
630			refclock_report(peer, CEVNT_BADTIME);
631			return;
632		}
633		for (i = 0; i < month - 1; i++)
634		    day += day1tab[i];
635	} else {
636		if (day > day2tab[month - 1]) {
637			refclock_report(peer, CEVNT_BADTIME);
638			return;
639		}
640		for (i = 0; i < month - 1; i++)
641		    day += day2tab[i];
642	}
643	pp->day = day;
644
645
646#ifdef HAVE_PPSAPI
647	/*
648	 * If the PPSAPI is working, rather use its timestamps.
649	 * assume that the PPS occurs on the second so blow any msec
650	 */
651	if (nmea_pps(up, &rd_tmp) == 1) {
652		pp->lastrec = up->tstamp = rd_tmp;
653		pp->nsec = 0;
654	}
655#endif /* HAVE_PPSAPI */
656
657	/*
658	 * Process the new sample in the median filter and determine the
659	 * reference clock offset and dispersion. We use lastrec as both
660	 * the reference time and receive time, in order to avoid being
661	 * cute, like setting the reference time later than the receive
662	 * time, which may cause a paranoid protocol module to chuck out
663	 * the data.
664	 */
665
666	if (!refclock_process(pp)) {
667		refclock_report(peer, CEVNT_BADTIME);
668		return;
669	}
670
671
672
673	/*
674	 * Only go on if we had been polled.
675	 */
676	if (!up->polled)
677	    return;
678	up->polled = 0;
679	pp->lastref = pp->lastrec;
680	refclock_receive(peer);
681
682        /* If we get here - what we got from the clock is OK, so say so */
683         refclock_report(peer, CEVNT_NOMINAL);
684
685	record_clock_stats(&peer->srcadr, pp->a_lastcode);
686
687}
688
689/*
690 * nmea_poll - called by the transmit procedure
691 *
692 * We go to great pains to avoid changing state here, since there may be
693 * more than one eavesdropper receiving the same timecode.
694 */
695static void
696nmea_poll(
697	int unit,
698	struct peer *peer
699	)
700{
701	register struct nmeaunit *up;
702	struct refclockproc *pp;
703
704	pp = peer->procptr;
705	up = (struct nmeaunit *)pp->unitptr;
706	if (up->pollcnt == 0)
707	    refclock_report(peer, CEVNT_TIMEOUT);
708	else
709	    up->pollcnt--;
710	pp->polls++;
711	up->polled = 1;
712
713	/*
714	 * usually nmea_receive can get a timestamp every second
715	 */
716
717	gps_send(pp->io.fd,"$PMOTG,RMC,0000*1D\r\n", peer);
718}
719
720/*
721 *
722 *	gps_send(fd,cmd, peer)  Sends a command to the GPS receiver.
723 *	 as	gps_send(fd,"rqts,u\r", peer);
724 *
725 *	We don't currently send any data, but would like to send
726 *	RTCM SC104 messages for differential positioning. It should
727 *	also give us better time. Without a PPS output, we're
728 *	Just fooling ourselves because of the serial code paths
729 *
730 */
731static void
732gps_send(
733	int fd,
734	const char *cmd,
735	struct peer *peer
736	)
737{
738
739	if (write(fd, cmd, strlen(cmd)) == -1) {
740		refclock_report(peer, CEVNT_FAULT);
741	}
742}
743
744static char *
745field_parse(
746	char *cp,
747	int fn
748	)
749{
750	char *tp;
751	int i = fn;
752
753	for (tp = cp; *tp != '\0'; tp++) {
754		if (*tp == ',')
755		    i--;
756		if (i == 0)
757		    break;
758	}
759	return (++tp);
760}
761#else
762int refclock_nmea_bs;
763#endif /* REFCLOCK */