xref: /freebsd/contrib/ntp/ntpd/refclock_nmea.c (revision 23f282aa31e9b6fceacd449020e936e98d6f2298)
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 #include <sys/time.h>
15 #include <time.h>
16 
17 #include "ntpd.h"
18 #include "ntp_io.h"
19 #include "ntp_refclock.h"
20 #include "ntp_stdlib.h"
21 
22 /*
23  * This driver supports the NMEA GPS Receiver with
24  *
25  * Protype was refclock_trak.c, Thanks a lot.
26  *
27  * The receiver used spits out the NMEA sentences for boat navigation.
28  * And you thought it was an information superhighway.  Try a raging river
29  * filled with rapids and whirlpools that rip away your data and warp time.
30  */
31 
32 /*
33  * Definitions
34  */
35 #ifdef SYS_WINNT
36 # define DEVICE "COM%d:" 	/* COM 1 - 3 supported */
37 #else
38 # define DEVICE	"/dev/gps%d"	/* name of radio device */
39 #endif
40 #define	SPEED232	B4800	/* uart speed (4800 bps) */
41 #define	PRECISION	(-9)	/* precision assumed (about 2 ms) */
42 #define	DCD_PRECISION	(-20)	/* precision assumed (about 1 us) */
43 #define	REFID		"GPS\0"	/* reference id */
44 #define	DESCRIPTION	"NMEA GPS Clock" /* who we are */
45 
46 #define LENNMEA		75	/* min timecode length */
47 
48 /*
49  * Tables to compute the ddd of year form icky dd/mm timecode. Viva la
50  * leap.
51  */
52 static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
53 static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
54 
55 /*
56  * Unit control structure
57  */
58 struct nmeaunit {
59 	int	pollcnt;	/* poll message counter */
60 	int	polled;		/* Hand in a sample? */
61 	l_fp	tstamp;		/* timestamp of last poll */
62 };
63 
64 /*
65  * Function prototypes
66  */
67 static	int	nmea_start	P((int, struct peer *));
68 static	void	nmea_shutdown	P((int, struct peer *));
69 static	void	nmea_receive	P((struct recvbuf *));
70 static	void	nmea_poll	P((int, struct peer *));
71 static	void	gps_send	P((int, const char *, struct peer *));
72 static	char	*field_parse	P((char *, int));
73 
74 /*
75  * Transfer vector
76  */
77 struct	refclock refclock_nmea = {
78 	nmea_start,		/* start up driver */
79 	nmea_shutdown,	/* shut down driver */
80 	nmea_poll,		/* transmit poll message */
81 	noentry,		/* handle control */
82 	noentry,		/* initialize driver */
83 	noentry,		/* buginfo */
84 	NOFLAGS			/* not used */
85 };
86 
87 /*
88  * nmea_start - open the GPS devices and initialize data for processing
89  */
90 static int
91 nmea_start(
92 	int unit,
93 	struct peer *peer
94 	)
95 {
96 	register struct nmeaunit *up;
97 	struct refclockproc *pp;
98 	int fd;
99 	char device[20];
100 
101 	/*
102 	 * Open serial port. Use CLK line discipline, if available.
103 	 */
104 	(void)sprintf(device, DEVICE, unit);
105 
106 	if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
107 	    return (0);
108 
109 	/*
110 	 * Allocate and initialize unit structure
111 	 */
112 	if (!(up = (struct nmeaunit *)
113 	      emalloc(sizeof(struct nmeaunit)))) {
114 		(void) close(fd);
115 		return (0);
116 	}
117 	memset((char *)up, 0, sizeof(struct nmeaunit));
118 	pp = peer->procptr;
119 	pp->io.clock_recv = nmea_receive;
120 	pp->io.srcclock = (caddr_t)peer;
121 	pp->io.datalen = 0;
122 	pp->io.fd = fd;
123 	if (!io_addclock(&pp->io)) {
124 		(void) close(fd);
125 		free(up);
126 		return (0);
127 	}
128 	pp->unitptr = (caddr_t)up;
129 
130 	/*
131 	 * Initialize miscellaneous variables
132 	 */
133 	peer->precision = DCD_PRECISION;
134 	pp->clockdesc = DESCRIPTION;
135 	memcpy((char *)&pp->refid, REFID, 4);
136 	up->pollcnt = 2;
137 	gps_send(pp->io.fd,"$PMOTG,RMC,0000*1D\r\n", peer);
138 
139 	return (1);
140 }
141 
142 /*
143  * nmea_shutdown - shut down a GPS clock
144  */
145 static void
146 nmea_shutdown(
147 	int unit,
148 	struct peer *peer
149 	)
150 {
151 	register struct nmeaunit *up;
152 	struct refclockproc *pp;
153 
154 	pp = peer->procptr;
155 	up = (struct nmeaunit *)pp->unitptr;
156 	io_closeclock(&pp->io);
157 	free(up);
158 }
159 
160 /*
161  * nmea_receive - receive data from the serial interface
162  */
163 static void
164 nmea_receive(
165 	struct recvbuf *rbufp
166 	)
167 {
168 	register struct nmeaunit *up;
169 	struct refclockproc *pp;
170 	struct peer *peer;
171 	l_fp trtmp;
172 	int month, day;
173 	int i;
174 	char *cp, *dp;
175 	int cmdtype;
176 
177 	/*
178 	 * Initialize pointers and read the timecode and timestamp
179 	 */
180 	peer = (struct peer *)rbufp->recv_srcclock;
181 	pp = peer->procptr;
182 	up = (struct nmeaunit *)pp->unitptr;
183 	pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
184 
185 	/*
186 	 * There is a case that a <CR><LF> gives back a "blank" line
187 	 */
188 	if (pp->lencode == 0)
189 	    return;
190 
191 	/*
192 	 * We get a buffer and timestamp for each <cr>.
193 	 */
194 	pp->lastrec = up->tstamp = trtmp;
195 	up->pollcnt = 2;
196 #ifdef DEBUG
197 	if (debug)
198 	    printf("nmea: timecode %d %s\n", pp->lencode,
199 		   pp->a_lastcode);
200 #endif
201 
202 	/*
203 	 * We check the timecode format and decode its contents. The
204 	 * we only care about a few of them.  The most important being
205 	 * the $GPRMC format
206 	 * $GPRMC,hhmmss,a,fddmm.xx,n,dddmmm.xx,w,zz.z,yyy.,ddmmyy,dd,v*CC
207   	 * $GPGGA,162617.0,4548.339,N,00837.719,E,1,07,0.97,00262,M,048,M,,*5D
208 	 */
209 #define GPRMC	0
210 #define GPXXX	1
211 #define GPGCA	2
212 	cp = pp->a_lastcode;
213 	cmdtype=0;
214 	if(strncmp(cp,"$GPRMC",6)==0) {
215 		cmdtype=GPRMC;
216 	}
217 	else if(strncmp(cp,"$GPGGA",6)==0) {
218 		cmdtype=GPGCA;
219 	}
220 	else if(strncmp(cp,"$GPXXX",6)==0) {
221 		cmdtype=GPXXX;
222 	}
223 	else
224 	    return;
225 
226 	switch( cmdtype ) {
227 	    case GPRMC:
228 	    case GPGCA:
229 		/*
230 		 *	Check time code format of NMEA
231 		 */
232 
233 		dp = field_parse(cp,1);
234 		if( !isdigit((int)dp[0]) ||
235 		    !isdigit((int)dp[1]) ||
236 		    !isdigit((int)dp[2]) ||
237 		    !isdigit((int)dp[3]) ||
238 		    !isdigit((int)dp[4]) ||
239 		    !isdigit((int)dp[5])
240 		    ) {
241 			refclock_report(peer, CEVNT_BADREPLY);
242 			return;
243 		}
244 
245 		/*
246 		 * Test for synchronization.  Check for quality byte.
247 		 */
248 		dp = field_parse(cp,2);
249 		if( dp[0] != 'A')  {
250 			refclock_report(peer, CEVNT_BADREPLY);
251 			return;
252 		}
253 		break;
254 	    case GPXXX:
255 		return;
256 	    default:
257 		return;
258 
259 	}
260 
261 	if (cmdtype ==GPGCA) {
262 		/* only time */
263 		time_t tt = time(NULL);
264 		struct tm * t = gmtime(&tt);
265 		day = t->tm_mday;
266 		month = t->tm_mon + 1;
267 		pp->year= t->tm_year;
268 	} else {
269 	dp = field_parse(cp,9);
270 	/*
271 	 * Convert date and check values.
272 	 */
273 	day = dp[0] - '0';
274 	day = (day * 10) + dp[1] - '0';
275 	month = dp[2] - '0';
276 	month = (month * 10) + dp[3] - '0';
277 	pp->year = dp[4] - '0';
278 	pp->year = (pp->year * 10) + dp[5] - '0';
279 	}
280 
281 	if (month < 1 || month > 12 || day < 1) {
282 		refclock_report(peer, CEVNT_BADTIME);
283 		return;
284 	}
285 
286 	if (pp->year % 4) {
287 		if (day > day1tab[month - 1]) {
288 			refclock_report(peer, CEVNT_BADTIME);
289 			return;
290 		}
291 		for (i = 0; i < month - 1; i++)
292 		    day += day1tab[i];
293 	} else {
294 		if (day > day2tab[month - 1]) {
295 			refclock_report(peer, CEVNT_BADTIME);
296 			return;
297 		}
298 		for (i = 0; i < month - 1; i++)
299 		    day += day2tab[i];
300 	}
301 	pp->day = day;
302 
303 	dp = field_parse(cp,1);
304 	/*
305 	 * Convert time and check values.
306 	 */
307 	pp->hour = ((dp[0] - '0') * 10) + dp[1] - '0';
308 	pp->minute = ((dp[2] - '0') * 10) + dp[3] -  '0';
309 	pp->second = ((dp[4] - '0') * 10) + dp[5] - '0';
310 	pp->msec = 0;
311 
312 	if (pp->hour > 23 || pp->minute > 59 || pp->second > 59) {
313 		refclock_report(peer, CEVNT_BADTIME);
314 		return;
315 	}
316 
317 	/*
318 	 * Process the new sample in the median filter and determine the
319 	 * reference clock offset and dispersion. We use lastrec as both
320 	 * the reference time and receive time, in order to avoid being
321 	 * cute, like setting the reference time later than the receive
322 	 * time, which may cause a paranoid protocol module to chuck out
323 	 * the data.
324 	 */
325 	if (!refclock_process(pp)) {
326 		refclock_report(peer, CEVNT_BADTIME);
327 		return;
328 	}
329 
330 	/*
331 	 * Only go on if we had been polled.
332 	 */
333 	if (!up->polled)
334 	    return;
335 	up->polled = 0;
336 
337 	refclock_receive(peer);
338 
339 	record_clock_stats(&peer->srcadr, pp->a_lastcode);
340 }
341 
342 /*
343  * nmea_poll - called by the transmit procedure
344  *
345  * We go to great pains to avoid changing state here, since there may be
346  * more than one eavesdropper receiving the same timecode.
347  */
348 static void
349 nmea_poll(
350 	int unit,
351 	struct peer *peer
352 	)
353 {
354 	register struct nmeaunit *up;
355 	struct refclockproc *pp;
356 
357 	pp = peer->procptr;
358 	up = (struct nmeaunit *)pp->unitptr;
359 	if (up->pollcnt == 0)
360 	    refclock_report(peer, CEVNT_TIMEOUT);
361 	else
362 	    up->pollcnt--;
363 	pp->polls++;
364 	up->polled = 1;
365 
366 	/*
367 	 * usually nmea_receive can get a timestamp every second
368 	 */
369 
370 	gps_send(pp->io.fd,"$PMOTG,RMC,0000*1D\r\n", peer);
371 }
372 
373 /*
374  *
375  *	gps_send(fd,cmd, peer)  Sends a command to the GPS receiver.
376  *	 as	gps_send(fd,"rqts,u\r", peer);
377  *
378  *	We don't currently send any data, but would like to send
379  *	RTCM SC104 messages for differential positioning. It should
380  *	also give us better time. Without a PPS output, we're
381  *	Just fooling ourselves because of the serial code paths
382  *
383  */
384 static void
385 gps_send(
386 	int fd,
387 	const char *cmd,
388 	struct peer *peer
389 	)
390 {
391 
392 	if (write(fd, cmd, strlen(cmd)) == -1) {
393 		refclock_report(peer, CEVNT_FAULT);
394 	}
395 }
396 
397 static char *
398 field_parse(
399 	char *cp,
400 	int fn
401 	)
402 {
403 	char *tp;
404 	int i = fn;
405 
406 	for (tp = cp; *tp != '\0'; tp++) {
407 		if (*tp == ',')
408 		    i--;
409 		if (i == 0)
410 		    break;
411 	}
412 	return (++tp);
413 }
414 #else
415 int refclock_nmea_bs;
416 #endif /* REFCLOCK */
417