nmea_parse.c

来自「gpsd, a popular GPS daemon.」· C语言 代码 · 共 842 行 · 第 1/2 页

C
842
字号
	session->gpsdata.satellites = 0;	return ERROR_SET;    }    if (count % 4 != 3){	gpsd_report(LOG_WARN, "malformed GPGSV - fieldcount %d %% 4 != 3\n", count);	gpsd_zero_satellites(&session->gpsdata);	session->gpsdata.satellites = 0;	return ERROR_SET;    }    session->driver.nmea.await = atoi(field[1]);    if (sscanf(field[2], "%d", &session->driver.nmea.part) < 1) {	gpsd_zero_satellites(&session->gpsdata);	return ERROR_SET;    } else if (session->driver.nmea.part == 1)	gpsd_zero_satellites(&session->gpsdata);    for (fldnum = 4; fldnum < count; ) {	if (session->gpsdata.satellites >= MAXCHANNELS) {	    gpsd_report(LOG_ERROR, "internal error - too many satellites!\n");	    gpsd_zero_satellites(&session->gpsdata);	    break;	}	session->gpsdata.PRN[session->gpsdata.satellites]       = atoi(field[fldnum++]);	session->gpsdata.elevation[session->gpsdata.satellites] = atoi(field[fldnum++]);	session->gpsdata.azimuth[session->gpsdata.satellites]   = atoi(field[fldnum++]);	session->gpsdata.ss[session->gpsdata.satellites]	= atoi(field[fldnum++]);	/*	 * Incrementing this unconditionally falls afoul of chipsets like	 * the Motorola Oncore GT+ that emit empty fields at the end of the	 * last sentence in a GPGSV set if the number of satellites is not	 * a multiple of 4.	 */	if (session->gpsdata.PRN[session->gpsdata.satellites] != 0)	    session->gpsdata.satellites++;    }    if (session->driver.nmea.part == session->driver.nmea.await && atoi(field[3]) != session->gpsdata.satellites)	gpsd_report(LOG_WARN, "GPGSV field 3 value of %d != actual count %d\n",		    atoi(field[3]), session->gpsdata.satellites);    /* not valid data until we've seen a complete set of parts */    if (session->driver.nmea.part < session->driver.nmea.await) {	gpsd_report(LOG_PROG, "Partial satellite data (%d of %d).\n", session->driver.nmea.part, session->driver.nmea.await);	return ERROR_SET;    }    /*     * This sanity check catches an odd behavior of SiRFstarII receivers.     * When they can't see any satellites at all (like, inside a     * building) they sometimes cough up a hairball in the form of a     * GSV packet with all the azimuth entries 0 (but nonzero     * elevations).  This behavior was observed under SiRF firmware     * revision 231.000.000_A2.     */    for (n = 0; n < session->gpsdata.satellites; n++)	if (session->gpsdata.azimuth[n] != 0)	    goto sane;    gpsd_report(LOG_WARN, "Satellite data no good (%d of %d).\n", session->driver.nmea.part, session->driver.nmea.await);    gpsd_zero_satellites(&session->gpsdata);    return ERROR_SET;  sane:    gpsd_report(LOG_PROG, "Satellite data OK (%d of %d).\n", session->driver.nmea.part, session->driver.nmea.await);    return SATELLITE_SET;    }static gps_mask_t processPGRME(int c UNUSED, char *field[], struct gps_device_t *session)/* Garmin Estimated Position Error */{    /*       $PGRME,15.0,M,45.0,M,25.0,M*22	1    = horizontal error estimate	2    = units	3    = vertical error estimate	4    = units	5    = spherical error estimate	6    = units     *     * Garmin won't say, but the general belief is that these are 50% CEP.     * We follow the advice at <http://gpsinformation.net/main/errors.htm>.     * If this assumption changes here, it should also change in garmin.c     * where we scale error estimates from Garmin binary packets.     */    if ((strcmp(field[2], "M")!=0) ||	(strcmp(field[4], "M")!=0) ||	(strcmp(field[6], "M")!=0)){	    session->gpsdata.fix.eph =	    session->gpsdata.fix.epv =	    session->gpsdata.epe = 100;	    return ERROR_SET;    }    session->gpsdata.fix.eph = atof(field[1]) * (GPSD_CONFIDENCE/CEP50_SIGMA);    session->gpsdata.fix.epv = atof(field[3]) * (GPSD_CONFIDENCE/CEP50_SIGMA);    session->gpsdata.epe = atof(field[5]) * (GPSD_CONFIDENCE/CEP50_SIGMA);    return HERR_SET | VERR_SET | PERR_SET;}static gps_mask_t processGPZDA(int c UNUSED, char *field[], struct gps_device_t *session)/* Time & Date */{    gps_mask_t mask = TIME_SET;    /*      $GPZDA,160012.71,11,03,2004,-1,00*7D      1) UTC time (hours, minutes, seconds, may have fractional subsecond)      2) Day, 01 to 31      3) Month, 01 to 12      4) Year (4 digits)      5) Local zone description, 00 to +- 13 hours      6) Local zone minutes description, apply same sign as local hours      7) Checksum     */    merge_hhmmss(field[1], session);    session->driver.nmea.date.tm_year = atoi(field[4]) - 1900;    session->driver.nmea.date.tm_mon = atoi(field[3])-1;    session->driver.nmea.date.tm_mday = atoi(field[2]);    session->gpsdata.fix.time = (double)mkgmtime(&session->driver.nmea.date)+session->driver.nmea.subseconds;    if (!GPS_TIME_EQUAL(session->gpsdata.sentence_time, session->gpsdata.fix.time)) {	mask |= CYCLE_START_SET;	gpsd_report(LOG_PROG, "GPZDA starts a reporting cycle.\n");    }    session->gpsdata.sentence_time = session->gpsdata. fix.time;    return mask;}#ifdef TNT_ENABLEstatic gps_mask_t processTNTHTM(int c UNUSED, char *field[], struct gps_device_t *session){    /*     * Proprietary sentence for True North Technologies Magnetic Compass.     * This may also apply to some Honeywell units since they may have been     * designed by True North.	HTM,x.x,a,x.x,a,x.x,a,x.x,x.x*hh<cr><lf>	Fields in order:	1. True heading in degrees	2. magnetometer status character:		C = magnetometer calibration alarm		L = low alarm		M = low warning		N = normal		O = high warning		P = high alarm		V = magnetometer voltage level alarm	3. pitch angle	4. pitch status character - see field 2 above	5. roll angle	6. roll status character - see field 2 above	7. dip angle	8. relative magnitude horizontal component of earth's magnetic field	*hh	  mandatory nmea_checksum     */    gps_mask_t mask;    mask = ONLINE_SET;    //gpsd_zero_satellites(&session->gpsdata);    /*     * Heading maps to track.     * Pitch maps to climb.     * Roll maps to speed.     * Dip maps to altitude.     */    session->gpsdata.fix.time = timestamp();    session->gpsdata.fix.track = atof(field[1]);    session->gpsdata.headingStatus = *field[2];    session->gpsdata.fix.climb = atof(field[3]);    session->gpsdata.pitchStatus = *field[4];    session->gpsdata.fix.speed = atof(field[5]);    session->gpsdata.rollStatus = *field[6];    session->gpsdata.fix.altitude = atof(field[7]);    session->gpsdata.horzField = atof(field[8]);    session->gpsdata.fix.mode = MODE_3D;    mask |= (STATUS_SET | MODE_SET | TRACK_SET | SPEED_SET | CLIMB_SET | ALTITUDE_SET);    session->gpsdata.status = STATUS_FIX;	/* could be DGPS_FIX */    gpsd_report(LOG_RAW, "Heading %lf  %c.\n", session->gpsdata.fix.track, session->gpsdata.headingStatus);    return mask;}#endif /* TNT_ENABLE */#ifdef ASHTECH_ENABLEstatic gps_mask_t processPASHR(int c UNUSED, char *field[], struct gps_device_t *session){	gps_mask_t mask;	mask = ONLINE_SET;	if (0 == strcmp("RID", field[1])){ /* Receiver ID */		(void)snprintf(session->subtype, sizeof(session->subtype)-1,			       "%s ver %s", field[2], field[3]);		return 0;	} else if (0 == strcmp("POS", field[1])){ /* 3D Position */		mask |= MODE_SET | STATUS_SET | CYCLE_START_SET;		if (0 == strlen(field[2])){			/* empty first field means no 3D fix is available */			session->gpsdata.status = STATUS_NO_FIX;			session->gpsdata.fix.mode = MODE_NO_FIX;			return mask;		}		/* if we make it this far, we at least have a 3D fix */		session->gpsdata.fix.mode = MODE_3D;		if (1 == atoi(field[2]))			session->gpsdata.status = STATUS_DGPS_FIX;		else			session->gpsdata.status = STATUS_FIX;		session->gpsdata.satellites_used = atoi(field[3]);		merge_hhmmss(field[4], session);		do_lat_lon(&field[5], &session->gpsdata);		session->gpsdata.fix.altitude = atof(field[9]);		session->gpsdata.fix.track = atof(field[11]);		session->gpsdata.fix.speed = atof(field[12]) / MPS_TO_KPH;		session->gpsdata.fix.climb = atof(field[13]);		session->gpsdata.pdop = atof(field[14]);		session->gpsdata.hdop = atof(field[15]);		session->gpsdata.vdop = atof(field[16]);		session->gpsdata.tdop = atof(field[17]);		mask |= (TIME_SET | LATLON_SET | ALTITUDE_SET);		mask |= (SPEED_SET | TRACK_SET | CLIMB_SET);		mask |= (PDOP_SET | HDOP_SET | VDOP_SET | TDOP_SET);	} else if (0 == strcmp("SAT", field[1])){ /* Satellite Status */		int i, n, p, u;		n = session->gpsdata.satellites = atoi(field[2]);		u = 0;		for (i = 0; i < n; i++){			session->gpsdata.PRN[i] = p = atoi(field[3+i*5+0]);			session->gpsdata.azimuth[i] = atoi(field[3+i*5+1]);			session->gpsdata.elevation[i] = atoi(field[3+i*5+2]);			session->gpsdata.ss[i] = atoi(field[3+i*5+3]);			if (field[3+i*5+4][0] == 'U')				session->gpsdata.used[u++] = p;		}		session->gpsdata.satellites_used = u;		mask |= SATELLITE_SET | USED_SET;	}	return mask;}#endif /* ASHTECH_ENABLE */#ifdef __UNUSED__static short nmea_checksum(char *sentence, unsigned char *correct_sum)/* is the checksum on the specified sentence good? */{    unsigned char sum = '\0';    char c, *p = sentence, csum[3];    while ((c = *p++) != '*' && c != '\0')	sum ^= c;    if (correct_sum)	*correct_sum = sum;    (void)snprintf(csum, sizeof(csum), "%02X", sum);    return(csum[0]==toupper(p[0])) && (csum[1]==toupper(p[1]));}#endif /* __ UNUSED__ *//************************************************************************** * * Entry points begin here * **************************************************************************/gps_mask_t nmea_parse(char *sentence, struct gps_device_t *session)/* parse an NMEA sentence, unpack it into a session structure */{    typedef gps_mask_t (*nmea_decoder)(int count, char *f[], struct gps_device_t *session);    static struct {	char *name;	int nf;		/* minimum number of fields required to parse */	nmea_decoder decoder;    } nmea_phrase[] = {	{"RMC", 8,	processGPRMC},	{"GGA", 13,	processGPGGA},	{"GLL", 7, 	processGPGLL},	{"GSA", 17,	processGPGSA},	{"GSV", 0,	processGPGSV},	{"VTG", 0, 	NULL},		/* ignore Velocity Track made Good */	{"ZDA", 7, 	processGPZDA},	{"PGRMC", 0,	NULL},		/* ignore Garmin Sensor Config */	{"PGRME", 7,	processPGRME},	{"PGRMI", 0,	NULL},		/* ignore Garmin Sensor Init */	{"PGRMO", 0,	NULL},		/* ignore Garmin Sentence Enable */#ifdef TNT_ENABLE	{"PTNTHTM", 9,	processTNTHTM},#endif /* TNT_ENABLE */#ifdef ASHTECH_ENABLE	{"PASHR", 3,	processPASHR},	/* general handler for Ashtech */#endif /* TNT_ENABLE */    };    volatile unsigned char buf[NMEA_MAX+1];    int count;    gps_mask_t retval = 0;    unsigned int i;    char *p, *field[NMEA_MAX], *s;#ifndef USE_OLD_SPLIT    volatile char *t;#endif#ifdef __UNUSED__    unsigned char sum;    if (!nmea_checksum(sentence+1, &sum)) {	gpsd_report(LOG_ERROR, "Bad NMEA checksum: '%s' should be %02X\n",		   sentence, sum);	return 0;    }#endif /* __ UNUSED__ */    /*     * We've had reports that on the Garmin GPS-10 the device sometimes     * (1:1000 or so) sends garbage packets that have a valid checksum     * but are like 2 successive NMEA packets merged together in one     * with some fields lost.  Usually these are much longer than the     * legal limit for NMEA, so we can cope by just tossing out overlong     * packets.  This may be a generic bug of all Garmin chipsets.     */    if (strlen(sentence) > NMEA_MAX) {	gpsd_report(LOG_WARN, "Overlong packet rejected.\n");	return ONLINE_SET;    }#ifdef BREAK_REGRESSIONS    /* trim trailing CR/LF */    for (i = 0; i < strlen(sentence); i++)	if ((sentence[i] == '\r') || (sentence[i] == '\n')){	    sentence[i] = '\0';	    break;	}#endif    /*@ -usedef @*//* splint 3.1.1 seems to have a bug here */    /* make an editable copy of the sentence */    strncpy((char *)buf, sentence, NMEA_MAX);    /* discard the checksum part */    for (p = (char *)buf; (*p != '*') && (*p >= ' '); ) ++p;    *p = '\0';    /* split sentence copy on commas, filling the field array */#ifdef USE_OLD_SPLIT    for (count = 0, p = (char *)buf; p != NULL && *p != '\0'; ++count, p = strchr(p, ',')) {	*p = '\0';	field[count] = ++p;    }#else    count = 0;    t = p;  /* end of sentence */    p = (char *)buf + 1; /* beginning of tag, 'G' not '$' */    /* while there is a search string and we haven't run off the buffer... */    while((p != NULL) && (p <= t)){	field[count] = p; /* we have a field. record it */	/*@ -compdef @*/	if ((p = strchr(p, ',')) != NULL){ /* search for the next delimiter */	    *p = '\0'; /* replace it with a NUL */	    count++; /* bump the counters and continue */	    p++;	}	/*@ +compdef @*/    }#endif    /* dispatch on field zero, the sentence tag */    for (i = 0; i < (unsigned)(sizeof(nmea_phrase)/sizeof(nmea_phrase[0])); ++i) {	s = field[0];	if (strlen(nmea_phrase[i].name) == 3)	    s += 2;	/* skip talker ID */	if (strcmp(nmea_phrase[i].name, s) == 0) {	    if (nmea_phrase[i].decoder!=NULL && (count >= nmea_phrase[i].nf)) {		retval = (nmea_phrase[i].decoder)(count, field, session);		strncpy(session->gpsdata.tag, nmea_phrase[i].name, MAXTAGLEN);		session->gpsdata.sentence_length = strlen(sentence);	    } else		retval = ONLINE_SET;		/* unknown sentence */	    break;	}    }    /*@ +usedef @*/    return retval;}#endif /* NMEA_ENABLE */void nmea_add_checksum(char *sentence)/* add NMEA checksum to a possibly  *-terminated sentence */{    unsigned char sum = '\0';    char c, *p = sentence;    if (*p == '$') {	p++;    } else {	gpsd_report(LOG_ERROR, "Bad NMEA sentence: '%s'\n", sentence);    }    while ( ((c = *p) != '*') && (c != '\0')) {	sum ^= c;	p++;    }    *p++ = '*';    (void)snprintf(p, 5, "%02X\r\n", (unsigned)sum);}int nmea_send(int fd, const char *fmt, ... )/* ship a command to the GPS, adding * and correct checksum */{    int status;    char buf[BUFSIZ];    va_list ap;    va_start(ap, fmt) ;    (void)vsnprintf(buf, sizeof(buf)-5, fmt, ap);    va_end(ap);    if (fmt[0] == '$') {	(void)strlcat(buf, "*", BUFSIZ);	nmea_add_checksum(buf);    } else	(void)strlcat(buf, "\r\n", BUFSIZ);    status = (int)write(fd, buf, strlen(buf));    (void)tcdrain(fd);    if (status == (int)strlen(buf)) {	gpsd_report(LOG_IO, "=> GPS: %s\n", buf);	return status;    } else {	gpsd_report(LOG_WARN, "=> GPS: %s FAILED\n", buf);	return -1;    }}

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?