最近在做gps相关工作,需要解析gps模组输出的nmea数据,获得经纬度等信息,整理了一下nmea各个字段的含义,供大家参考。

基本介绍

GNSS的全称是全球导航卫星系统(Global Navigation Satellite System),它是泛指所有的卫星导航系统,包括全球的、区域的和增强的,如美国的GPS、俄罗斯的Glonass、欧洲的Galileo、中国的北斗卫星导航系统,以及相关的增强系统,如美国的WAAS(广域增强系统)、欧洲的EGNOS(欧洲静地导航重叠系统)和日本的MSAS(多功能运输卫星增强系统)等,还涵盖在建和以后要建设的其他卫星导航系统。国际GNSS系统是个多系统、多层面、多模式的复杂组合系统,如下图所示。

NMEA的全称是美国国家海洋电子协会(National Marine Electronics Association),现在是GPS导航设备统一的RTCM(Radio Technical Commission for Maritime services)标准协议。
NMEA-0183协议定义的语句非常多,但是常用的或者说兼容性最广的语句只有GGA、GSA、GSV、RMC、VTG、GLL等。下面给出这些常用NMEA-0183语句的字段定义解释,此外还有GARMIN定义的扩展语句,本文不做阐述。

NMEA示例

下面是一包完整的nmea报文

$GPGSV,3,1,09,16,26,218,19,29,29,071,38,31,66,027,33,32,40,140,24,1*63
$GPGSV,3,2,09,03,28,291,,04,10,313,,22,29,265,,25,16,046,,1*63
$GPGSV,3,3,09,26,68,223,,1*54
$GPGSV,1,1,01,32,40,140,26,8*58
$GLGSV,3,1,09,66,25,169,30,77,60,003,28,76,18,047,25,87,03,115,23,1*72
$GLGSV,3,2,09,85,03,014,26,67,70,226,20,68,38,321,33,78,39,264,,1*7D
$GLGSV,3,3,09,86,12,057,,1*4E
$GAGSV,2,1,07,05,24,146,23,24,12,048,25,25,67,039,35,02,57,236,,7*75
$GAGSV,2,2,07,03,75,184,,08,43,310,,30,07,231,,7*41
$GAGSV,1,1,03,05,24,146,30,25,67,039,33,24,12,048,,1*40
$GQGSV,1,1,03,01,36,159,29,02,73,090,28,03,15,143,,1*50
$GQGSV,1,1,03,01,36,159,28,02,73,090,30,03,15,143,,8*51
$GBGSV,8,1,30,59,39,145,26,45,05,067,24,44,09,141,28,39,73,100,19,1*7D
$GBGSV,8,2,30,35,18,090,32,29,07,043,23,26,56,045,37,25,09,258,21,1*72
$GBGSV,8,3,30,21,06,166,21,16,79,067,30,09,71,329,31,06,81,011,26,1*7C
$GBGSV,8,4,30,05,15,248,25,04,26,124,29,01,36,140,35,02,33,226,,1*7E
$GBGSV,8,5,30,03,42,190,,07,47,199,,10,34,207,,14,55,242,,1*72
$GBGSV,8,6,30,24,62,286,,33,46,287,,40,55,184,,41,07,324,,1*74
$GBGSV,8,7,30,42,45,202,,56,50,233,,57,09,038,,58,46,088,,1*76
$GBGSV,8,8,30,60,28,228,,61,44,190,,1*7E
$GNGSA,A,3,16,29,31,32,,,,,,,,,1.2,1.0,0.8,1*34
$GNGSA,A,3,66,68,,,,,,,,,,,1.2,1.0,0.8,2*36
$GNGSA,A,3,05,25,,,,,,,,,,,1.2,1.0,0.8,3*3B
$GNGSA,A,3,16,26,35,39,,,,,,,,,1.2,1.0,0.8,4*31
$GNGSA,A,3,01,02,,,,,,,,,,,1.2,1.0,0.8,5*3C
$GNVTG,,T,,M,0.0,N,0.0,K,A*3D
$GNDTM,P90,,0000.000025,S,00000.000002,W,0.981,W84*49
$GNRMC,082923.00,A,3901.106815,N,11712.322006,E,0.0,,231121,5.8,W,A,V*61
$GNGNS,082923.00,3901.106815,N,11712.322006,E,AAAAA,14,1.0,60.6,-4.0,,,V*4D
$GNGGA,082923.00,3901.106815,N,11712.322006,E,1,12,1.0,60.6,M,-4.0,M,,*5A

NMEA解析

1、GP、GL、GA、GQ、GB、GN说明

GP (GPS)
GL (GLONASS)
GA (Galileo)
GQ (QZSS)
GB (Beidou)
GN (Any combination GNSS),表示使用了多个系统的卫星取得位置解算

2、GSV(可见卫星信息)

例:$GPGSV,3,1,09,16,26,218,19,29,29,071,38,31,66,027,33,32,40,140,24,163
字段0:$GPGSV,语句ID,表明该语句为GPS Satellites in View(GSV)可见卫星信息
字段1:本次GSV语句的总数目(1 - 3)
字段2:本条GSV语句是本次GSV语句的第几条(1 - 3)
字段3:当前可见卫星总数(00 - 12)(前导位数不足则补0)
字段4:PRN 码(伪随机噪声码)(01 - 32)(前导位数不足则补0)
字段5:卫星仰角(00 - 90)度(前导位数不足则补0)
字段6:卫星方位角(00 - 359)度(前导位数不足则补0)
字段7:信噪比(00-99)dbHz
字段8:PRN 码(伪随机噪声码)(01 - 32)(前导位数不足则补0)
字段9:卫星仰角(00 - 90)度(前导位数不足则补0)
字段10:卫星方位角(00 - 359)度(前导位数不足则补0)
字段11:信噪比(00-99)dbHz
字段12:PRN 码(伪随机噪声码)(01 - 32)(前导位数不足则补0)
字段13:卫星仰角(00 - 90)度(前导位数不足则补0)
字段14:卫星方位角(00 - 359)度(前导位数不足则补0)
字段15:信噪比(00-99)dbHz
字段16:校验值($与
之间的数异或后的值)

3、GSA( 当前卫星信息)

例:$GNGSA,A,3,16,29,31,32,1.2,1.0,0.8,134
字段0:$GPGSA,语句ID,表明该语句为GPS DOP and Active Satellites(GSA)当前卫星信息
字段1:定位模式(选择2D/3D),A=自动选择,M=手动选择
字段2:定位类型,1=未定位,2=2D定位,3=3D定位
字段3:PRN码(伪随机噪声码),第1信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
字段4:PRN码(伪随机噪声码),第2信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
字段5:PRN码(伪随机噪声码),第3信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
字段6:PRN码(伪随机噪声码),第4信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
字段7:PRN码(伪随机噪声码),第5信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
字段8:PRN码(伪随机噪声码),第6信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
字段9:PRN码(伪随机噪声码),第7信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
字段10:PRN码(伪随机噪声码),第8信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
字段11:PRN码(伪随机噪声码),第9信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
字段12:PRN码(伪随机噪声码),第10信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
字段13:PRN码(伪随机噪声码),第11信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
字段14:PRN码(伪随机噪声码),第12信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
字段15:PDOP综合位置精度因子(0.5 - 99.9)
字段16:HDOP水平精度因子(0.5 - 99.9)
字段17:VDOP垂直精度因子(0.5 - 99.9)
字段18:校验值($与
之间的数异或后的值)

4、RMC(推荐定位信息数据格式)

例:$GNRMC,082923.00,A,3901.106815,N,11712.322006,E,0.0,231121,5.8,W,A,V61
字段0:$GPRMC,语句ID,表明该语句为Recommended Minimum Specific GPS/TRANSIT Data(RMC)推荐最小定位信息
字段1:UTC时间,hhmmss.sss格式
字段2:状态,A=定位,V=未定位
字段3:纬度ddmm.mmmm,度分格式(前导位数不足则补0)
字段4:纬度N(北纬)或S(南纬)
字段5:经度dddmm.mmmm,度分格式(前导位数不足则补0)
字段6:经度E(东经)或W(西经)
字段7:速度,节,Knots
字段8:方位角,度
字段9:UTC日期,DDMMYY格式
字段10:磁偏角,(000 - 180)度(前导位数不足则补0)
字段11:磁偏角方向,E=东W=西
字段12: 模式指示(仅NMEA0183 3.00版本输出,A=自主定位,D=差分,E=估算,N=数据无效)
字段13:校验值($与
之间的数异或后的值)

5、GNS(定位信息)

例:$GNGNS,082923.00,3901.106815,N,11712.322006,E,AAAAA,14,1.0,60.6,-4.0,V4D
字段0:$GNGNS,语句ID,表明该语句为Global Positioning System Fix Data(GNS)GPS定位信息
字段1:UTC 时间,hhmmss.sss,时分秒格式
字段2:纬度ddmm.mmmm,度分格式(前导位数不足则补0)
字段3:纬度N(北纬)或S(南纬)
字段4:经度dddmm.mmmm,度分格式(前导位数不足则补0)
字段5:经度E(东经)或W(西经)
字段6:定位标识,NN - 未定位,AA - 定位Active
字段7:正在使用的卫星数量(00 - 12)(前导位数不足则补0)
字段8:HDOP水平精度因子(0.5 - 99.9)
字段9:海拔高度(-9999.9 - 99999.9)单位:M(米)
字段10:地球椭球面相对大地水准面的高度 WGS84水准面划分 单位:M(米)
字段11:差分时间(从接收到差分信号开始的秒数,如果不是差分定位将为空)
字段12:差分站ID号0000 - 1023(前导位数不足则补0,如果不是差分定位将为空)
字段13:校验值($与
之间的数异或后的值)

6、GGA(定位信息)

例:$GNGGA,082923.00,3901.106815,N,11712.322006,E,1,12,1.0,60.6,M,-4.0,M,5A
字段0:$GPGGA,语句ID,表明该语句为Global Positioning System Fix Data(GGA)GPS定位信息
字段1:UTC 时间,hhmmss.sss,时分秒格式
字段2:纬度ddmm.mmmm,度分格式(前导位数不足则补0)
字段3:纬度N(北纬)或S(南纬)
字段4:经度dddmm.mmmm,度分格式(前导位数不足则补0)
字段5:经度E(东经)或W(西经)
字段6:GPS状态,0=不可用(FIX NOT valid),1=单点定位(GPS FIX),2=差分定位(DGPS),3=无效PPS,4=实时差分定位(RTK FIX),5=RTK FLOAT,6=正在估算
字段7:正在使用的卫星数量(00 - 12)(前导位数不足则补0)
字段8:HDOP水平精度因子(0.5 - 99.9)
字段9:海拔高度(-9999.9 - 99999.9)
字段10:海拔高度 单位:M(米)
字段11:地球椭球面相对大地水准面的高度 WGS84水准面划分
字段12:WGS84水准面划分 单位:M(米)
字段13:差分时间(从接收到差分信号开始的秒数,如果不是差分定位将为空)
字段14:差分站ID号0000 - 1023(前导位数不足则补0,如果不是差分定位将为空)
字段15:校验值($与
之间的数异或后的值)

7、GLL(地理定位信息)

例:$GNGLL,4250.5589,S,14718.5084,E,092204.999,A2D
字段0:$GNGLL,语句ID,表明该语句为Geographic Position(GLL)地理定位信息
字段1:纬度ddmm.mmmm,度分格式(前导位数不足则补0)
字段2:纬度N(北纬)或S(南纬)
字段3:经度dddmm.mmmm,度分格式(前导位数不足则补0)
字段4:经度E(东经)或W(西经)
字段5:UTC时间,hhmmss.sss格式
字段6:状态,A=定位,V=未定位
字段7:校验值($与
之间的数异或后的值)

8、VTG(地面速度信息)

例:$GNVTG,T,M,0.0,N,0.0,K,A3D
字段0:$GPVTG,语句ID,表明该语句为Track Made Good and Ground Speed(VTG)地面速度信息
字段1:运动角度,000 - 359,(前导位数不足则补0)
字段2:T=真北参照系
字段3:运动角度,000 - 359,(前导位数不足则补0)
字段4:M=磁北参照系
字段5:水平运动速度(0.00)(前导位数不足则补0)
字段6:N=节,Knots
字段7:水平运动速度(0.00)(前导位数不足则补0)
字段8:K=公里/时,km/h
字段9:校验值($与
之间的数异或后的值)

9、ZDA(时间和日期信息)

例:$GNZDA,160012.71,11,03,2004,-1,007D
字段0:$GNZDA,语句ID,表明该语句为Data and time(ZDA)时间和日期信息
字段1:UTC时间,hhmmss(时分秒)格式
字段2:UTC日期,日
字段3:UTC日期,月
字段4:UTC日期,年
字段5:时区
字段6:校验值($与
之间的数异或后的值)

10、DTM(大地坐标系信息)

例:$GNDTM,P90,0000.000025,S,00000.000002,W,0.981,W8449
字段0:$GNDTM,语句ID,表明该语句为Datum(DTM)大地坐标系信息
字段1:本地坐标系代码 W84
字段2:坐标系子代码 空
字段3:纬度偏移量
字段4:纬度半球N(北半球)或S(南半球)
字段5:经度偏移量
字段6:经度半球E(东经)或W(西经)
字段7:高度偏移量
字段8:坐标系代码 W84
字段9:校验值($与
之间的数异或后的值)

NMEA解析C程序

引用自开源程序代码gpsd-3.16,仅供参考。
driver_nmea0183.c

/** This file is Copyright (c) 2010 by the GPSD project* BSD terms apply: see the file COPYING in the distribution root for details.*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>#include "gpsd.h"
#include "strfuncs.h"#ifdef NMEA0183_ENABLE
/**************************************************************************** Parser helpers begin here***************************************************************************/static void do_lat_lon(char *field[], struct gps_fix_t *out)
/* process a pair of latitude/longitude fields starting at field index BEGIN */
{double d, m;char str[20], *p;if (*(p = field[0]) != '\0') {double lat;(void)strlcpy(str, p, sizeof(str));lat = atof(str);m = 100.0 * modf(lat / 100.0, &d);lat = d + m / 60.0;p = field[1];if (*p == 'S')lat = -lat;out->latitude = lat;}if (*(p = field[2]) != '\0') {double lon;(void)strlcpy(str, p, sizeof(str));lon = atof(str);m = 100.0 * modf(lon / 100.0, &d);lon = d + m / 60.0;p = field[3];if (*p == 'W')lon = -lon;out->longitude = lon;}
}/**************************************************************************** Scary timestamp fudging begins here** Four sentences, GGA and GLL and RMC and ZDA, contain timestamps.* GGA/GLL/RMC timestamps look like hhmmss.ss, with the trailing .ss* part optional.  RMC has a date field, in the format ddmmyy.  ZDA* has separate fields for day/month/year, with a 4-digit year.  This* means that for RMC we must supply a century and for GGA and GLL we* must supply a century, year, and day.  We get the missing data from* a previous RMC or ZDA; century in RMC is supplied from the daemon's* context (initialized at startup time) if there has been no previous* ZDA.***************************************************************************/#define DD(s)    ((int)((s)[0]-'0')*10+(int)((s)[1]-'0'))static void merge_ddmmyy(char *ddmmyy, struct gps_device_t *session)
/* sentence supplied ddmmyy, but no century part */
{int yy = DD(ddmmyy + 4);int mon = DD(ddmmyy + 2);int mday = DD(ddmmyy);int year;/* check for century wrap */if (session->nmea.date.tm_year % 100 == 99 && yy == 0)gpsd_century_update(session, session->context->century + 100);year = (session->context->century + yy);if ( (1 > mon ) || (12 < mon ) ) {gpsd_log(&session->context->errout, LOG_WARN,"merge_ddmmyy(%s), malformed month\n",  ddmmyy);} else if ( (1 > mday ) || (31 < mday ) ) {gpsd_log(&session->context->errout, LOG_WARN,"merge_ddmmyy(%s), malformed day\n",  ddmmyy);} else {gpsd_log(&session->context->errout, LOG_DATA,"merge_ddmmyy(%s) sets year %d\n",ddmmyy, year);session->nmea.date.tm_year = year - 1900;session->nmea.date.tm_mon = mon - 1;session->nmea.date.tm_mday = mday;}
}static void merge_hhmmss(char *hhmmss, struct gps_device_t *session)
/* update from a UTC time */
{int old_hour = session->nmea.date.tm_hour;session->nmea.date.tm_hour = DD(hhmmss);if (session->nmea.date.tm_hour < old_hour)  /* midnight wrap */session->nmea.date.tm_mday++;session->nmea.date.tm_min = DD(hhmmss + 2);session->nmea.date.tm_sec = DD(hhmmss + 4);session->nmea.subseconds =safe_atof(hhmmss + 4) - session->nmea.date.tm_sec;
}static void register_fractional_time(const char *tag, const char *fld,struct gps_device_t *session)
{if (fld[0] != '\0') {session->nmea.last_frac_time =session->nmea.this_frac_time;session->nmea.this_frac_time = safe_atof(fld);session->nmea.latch_frac_time = true;gpsd_log(&session->context->errout, LOG_DATA,"%s: registers fractional time %.2f\n",tag, session->nmea.this_frac_time);}
}/**************************************************************************** Compare GPS timestamps for equality.  Depends on the fact that the* timestamp granularity of GPS is 1/100th of a second.  Use this to avoid* naive float comparisons.***************************************************************************/#define GPS_TIME_EQUAL(a, b) (fabs((a) - (b)) < 0.01)/**************************************************************************** NMEA sentence handling begins here***************************************************************************/static gps_mask_t processRMC(int count, char *field[],struct gps_device_t *session)
/* Recommend Minimum Course Specific GPS/TRANSIT Data */
{/** RMC,225446.33,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E,A*68* 1     225446.33    Time of fix 22:54:46 UTC* 2     A          Status of Fix: A = Autonomous, valid;* D = Differential, valid; V = invalid* 3,4   4916.45,N    Latitude 49 deg. 16.45 min North* 5,6   12311.12,W   Longitude 123 deg. 11.12 min West* 7     000.5      Speed over ground, Knots* 8     054.7      Course Made Good, True north* 9     181194       Date of fix  18 November 1994* 10,11 020.3,E      Magnetic variation 20.3 deg East* 12    A      FAA mode indicator (NMEA 2.3 and later)* A=autonomous, D=differential, E=Estimated,* N=not valid, S=Simulator, M=Manual input mode* *68        mandatory nmea_checksum** * SiRF chipsets don't return either Mode Indicator or magnetic variation.*/gps_mask_t mask = 0;if (strcmp(field[2], "V") == 0) {/* copes with Magellan EC-10X, see below */if (session->gpsdata.status != STATUS_NO_FIX) {session->gpsdata.status = STATUS_NO_FIX;mask |= STATUS_SET;}if (session->newdata.mode >= MODE_2D) {session->newdata.mode = MODE_NO_FIX;mask |= MODE_SET;}/* set something nz, so it won't look like an unknown sentence */mask |= ONLINE_SET;} else if (strcmp(field[2], "A") == 0) {/** The MTK3301, Royaltek RGM-3800, and possibly other* devices deliver bogus time values when the navigation* warning bit is set.*/if (count > 9 && field[1][0] != '\0' && field[9][0] != '\0') {merge_hhmmss(field[1], session);merge_ddmmyy(field[9], session);mask |= TIME_SET;register_fractional_time(field[0], field[1], session);}do_lat_lon(&field[3], &session->newdata);mask |= LATLON_SET;session->newdata.speed = safe_atof(field[7]) * KNOTS_TO_MPS;session->newdata.track = safe_atof(field[8]);mask |= (TRACK_SET | SPEED_SET);/** This copes with GPSes like the Magellan EC-10X that *only* emit* GPRMC. In this case we set mode and status here so the client* code that relies on them won't mistakenly believe it has never* received a fix.*/if (session->gpsdata.status == STATUS_NO_FIX) {session->gpsdata.status = STATUS_FIX;  /* could be DGPS_FIX, we can't tell */mask |= STATUS_SET;}if (session->newdata.mode < MODE_2D) {session->newdata.mode = MODE_2D;mask |= MODE_SET;}}gpsd_log(&session->context->errout, LOG_DATA,"RMC: ddmmyy=%s hhmmss=%s lat=%.2f lon=%.2f ""speed=%.2f track=%.2f mode=%d status=%d\n",field[9], field[1],session->newdata.latitude,session->newdata.longitude,session->newdata.speed,session->newdata.track,session->newdata.mode,session->gpsdata.status);return mask;
}static gps_mask_t processGLL(int count, char *field[],struct gps_device_t *session)
/* Geographic position - Latitude, Longitude */
{/* Introduced in NMEA 3.0.** $GPGLL,4916.45,N,12311.12,W,225444,A,A*5C** 1,2: 4916.46,N    Latitude 49 deg. 16.45 min. North* 3,4: 12311.12,W   Longitude 123 deg. 11.12 min. West* 5:   225444       Fix taken at 22:54:44 UTC* 6:   A            Data valid* 7:   A            Autonomous mode* 8:   *5C          Mandatory NMEA checksum** 1,2 Latitude, N (North) or S (South)* 3,4 Longitude, E (East) or W (West)* 5 UTC of position* 6 A=Active, V=Void* 7 Mode Indicator* A = Autonomous mode* D = Differential Mode* E = Estimated (dead-reckoning) mode* M = Manual Input Mode* S = Simulated Mode* N = Data Not Valid** I found a note at <http://www.secoh.ru/windows/gps/nmfqexep.txt>* indicating that the Garmin 65 does not return time and status.* SiRF chipsets don't return the Mode Indicator.* This code copes gracefully with both quirks.** Unless you care about the FAA indicator, this sentence supplies nothing* that GPRMC doesn't already.  But at least one Garmin GPS -- the 48* actually ships updates in GLL that aren't redundant.*/char *status = field[7];gps_mask_t mask = 0;if (field[5][0] != '\0') {merge_hhmmss(field[5], session);register_fractional_time(field[0], field[5], session);if (session->nmea.date.tm_year == 0)gpsd_log(&session->context->errout, LOG_WARN,"can't use GLL time until after ZDA or RMC has supplied a year.\n");else {mask = TIME_SET;}}if (strcmp(field[6], "A") == 0 && (count < 8 || *status != 'N')) {int newstatus;do_lat_lon(&field[1], &session->newdata);mask |= LATLON_SET;if (count >= 8 && *status == 'D')newstatus = STATUS_DGPS_FIX; /* differential */elsenewstatus = STATUS_FIX;/** This is a bit dodgy.  Technically we shouldn't set the mode* bit until we see GSA.  But it may be later in the cycle,* some devices like the FV-18 don't send it by default, and* elsewhere in the code we want to be able to test for the* presence of a valid fix with mode > MODE_NO_FIX.*/if (session->newdata.mode < MODE_2D) {session->newdata.mode = MODE_2D;mask |= MODE_SET;}session->gpsdata.status = newstatus;mask |= STATUS_SET;}gpsd_log(&session->context->errout, LOG_DATA,"GLL: hhmmss=%s lat=%.2f lon=%.2f mode=%d status=%d\n",field[5],session->newdata.latitude,session->newdata.longitude,session->newdata.mode,session->gpsdata.status);return mask;
}static gps_mask_t processGGA(int c UNUSED, char *field[],struct gps_device_t *session)
/* Global Positioning System Fix Data */
{/** GGA,123519,4807.038,N,01131.324,E,1,08,0.9,545.4,M,46.9,M, , *42* 1     123519       Fix taken at 12:35:19 UTC* 2,3   4807.038,N   Latitude 48 deg 07.038' N* 4,5   01131.324,E  Longitude 11 deg 31.324' E* 6         1            Fix quality: 0 = invalid, 1 = GPS, 2 = DGPS,* 3=PPS (Precise Position Service),* 4=RTK (Real Time Kinematic) with fixed integers,* 5=Float RTK, 6=Estimated, 7=Manual, 8=Simulator* 7     08       Number of satellites being tracked* 8     0.9              Horizontal dilution of position* 9,10  545.4,M      Altitude, Metres above mean sea level* 11,12 46.9,M       Height of geoid (mean sea level) above WGS84* ellipsoid, in Meters* (empty field) time in seconds since last DGPS update* (empty field) DGPS station ID number (0000-1023)*/gps_mask_t mask;session->gpsdata.status = atoi(field[6]);mask = STATUS_SET;/** There are some receivers (the Trimble Placer 450 is an example) that* don't ship a GSA with mode 1 when they lose satellite lock. Instead* they just keep reporting GGA and GSA on subsequent cycles with the* timestamp not advancing and a bogus mode.  On the assumption that GGA* is only issued once per cycle we can detect this here (it would be* nicer to do it on GSA but GSA has no timestamp).*/session->nmea.latch_mode = strncmp(field[1],session->nmea.last_gga_timestamp,sizeof(session->nmea.last_gga_timestamp))==0;if (session->nmea.latch_mode) {session->gpsdata.status = STATUS_NO_FIX;session->newdata.mode = MODE_NO_FIX;} else(void)strlcpy(session->nmea.last_gga_timestamp,field[1],sizeof(session->nmea.last_gga_timestamp));/* if we have a fix and the mode latch is off, go... */if (session->gpsdata.status > STATUS_NO_FIX) {char *altitude;merge_hhmmss(field[1], session);register_fractional_time(field[0], field[1], session);if (session->nmea.date.tm_year == 0)gpsd_log(&session->context->errout, LOG_WARN,"can't use GGA time until after ZDA or RMC has supplied a year.\n");else {mask |= TIME_SET;}do_lat_lon(&field[2], &session->newdata);mask |= LATLON_SET;session->gpsdata.satellites_used = atoi(field[7]);altitude = field[9];/** SiRF chipsets up to version 2.2 report a null altitude field.* See <http://www.sirf.com/Downloads/Technical/apnt0033.pdf>.* If we see this, force mode to 2D at most.*/if (altitude[0] == '\0') {if (session->newdata.mode > MODE_2D) {session->newdata.mode = MODE_2D;mask |= MODE_SET;}} else {session->newdata.altitude = safe_atof(altitude);mask |= ALTITUDE_SET;/** This is a bit dodgy.  Technically we shouldn't set the mode* bit until we see GSA.  But it may be later in the cycle,* some devices like the FV-18 don't send it by default, and* elsewhere in the code we want to be able to test for the* presence of a valid fix with mode > MODE_NO_FIX.*/if (session->newdata.mode < MODE_3D) {session->newdata.mode = MODE_3D;mask |= MODE_SET;}}if (strlen(field[11]) > 0) {session->gpsdata.separation = safe_atof(field[11]);} else {session->gpsdata.separation =wgs84_separation(session->newdata.latitude,session->newdata.longitude);}}gpsd_log(&session->context->errout, LOG_DATA,"GGA: hhmmss=%s lat=%.2f lon=%.2f alt=%.2f mode=%d status=%d\n",field[1],session->newdata.latitude,session->newdata.longitude,session->newdata.altitude,session->newdata.mode,session->gpsdata.status);return mask;
}static gps_mask_t processGST(int count, char *field[], struct gps_device_t *session)
/* GST - GPS Pseudorange Noise Statistics */
{/** GST,hhmmss.ss,x,x,x,x,x,x,x,*hh* 1 TC time of associated GGA fix* 2 Total RMS standard deviation of ranges inputs to the navigation solution* 3 Standard deviation (meters) of semi-major axis of error ellipse* 4 Standard deviation (meters) of semi-minor axis of error ellipse* 5 Orientation of semi-major axis of error ellipse (true north degrees)* 6 Standard deviation (meters) of latitude error* 7 Standard deviation (meters) of longitude error* 8 Standard deviation (meters) of altitude error* 9 Checksum
*/if (count < 8) {return 0;}#define PARSE_FIELD(n) (*field[n]!='\0' ? safe_atof(field[n]) : NAN)session->gpsdata.gst.utctime             = PARSE_FIELD(1);session->gpsdata.gst.rms_deviation       = PARSE_FIELD(2);session->gpsdata.gst.smajor_deviation    = PARSE_FIELD(3);session->gpsdata.gst.sminor_deviation    = PARSE_FIELD(4);session->gpsdata.gst.smajor_orientation  = PARSE_FIELD(5);session->gpsdata.gst.lat_err_deviation   = PARSE_FIELD(6);session->gpsdata.gst.lon_err_deviation   = PARSE_FIELD(7);session->gpsdata.gst.alt_err_deviation   = PARSE_FIELD(8);
#undef PARSE_FIELDregister_fractional_time(field[0], field[1], session);gpsd_log(&session->context->errout, LOG_DATA,"GST: utc = %.2f, rms = %.2f, maj = %.2f, min = %.2f, ori = %.2f, lat = %.2f, lon = %.2f, alt = %.2f\n",session->gpsdata.gst.utctime,session->gpsdata.gst.rms_deviation,session->gpsdata.gst.smajor_deviation,session->gpsdata.gst.sminor_deviation,session->gpsdata.gst.smajor_orientation,session->gpsdata.gst.lat_err_deviation,session->gpsdata.gst.lon_err_deviation,session->gpsdata.gst.alt_err_deviation);return GST_SET | ONLINE_SET;
}static int nmeaid_to_prn(char *talker, int satnum)
/* deal with range-mapping attempts to to use IDs 1-32 by Beidou, etc. */
{/** According to https://github.com/mvglasow/satstat/wiki/NMEA-IDs* NMEA IDs can be roughly divided into the following ranges:**   1..32:  GPS*   33..54: Various SBAS systems (EGNOS, WAAS, SDCM, GAGAN, MSAS)*           ... some IDs still unused*   55..64: not used (might be assigned to further SBAS systems)*   65..88: GLONASS*   89..96: GLONASS (future extensions?)*   97..192: not used (SBAS PRNs 120-151 fall in here)*   193..195: QZSS*   196..200: QZSS (future extensions?)*   201..235: Beidou** The issue is what to do when GPSes from these different systems* fight for IDs in the  1-32 range, as in this pair of Beidou sentences** $BDGSV,2,1,07,01,00,000,45,02,13,089,35,03,00,000,37,04,00,000,42*6E* $BDGSV,2,2,07,05,27,090,,13,19,016,,11,07,147,*5E** Because the PRNs are only used for generating a satellite* chart, mistakes here aren't dangerous.  The code will record* and use multiple sats with the same ID in one skyview; in* effect, they're recorded by the order in which they occur* rather than by PRN.*/// NMEA-ID (33..64) to SBAS PRN 120-151.if (satnum >= 33 && satnum <= 64)satnum += 87;if (satnum < 32) {/* map Beidou IDs */if (talker[0] == 'B' && talker[1] == 'D')satnum += 200;else if (talker[0] == 'G' && talker[1] == 'B')satnum += 200;/* GLONASS GL doesn't seem to do this, but better safe than sorry */if (talker[0] == 'G' && (talker[1] == 'L' || talker[1] == 'N'))satnum += 37;/* QZSS */if (talker[0] == 'Q' && talker[1] == 'Z')satnum += 193;}return satnum;
}static gps_mask_t processGSA(int count, char *field[],struct gps_device_t *session)
/* GPS DOP and Active Satellites */
{/** eg1. $GPGSA,A,3,,,,,,16,18,,22,24,,,3.6,2.1,2.2*3C* eg2. $GPGSA,A,3,19,28,14,18,27,22,31,39,,,,,1.7,1.0,1.3*35* 1    = Mode:* M=Manual, forced to operate in 2D or 3D* A=Automatic, 3D/2D* 2    = Mode: 1=Fix not available, 2=2D, 3=3D* 3-14 = PRNs of satellites used in position fix (null for unused fields)* 15   = PDOP* 16   = HDOP* 17   = VDOP*/gps_mask_t mask;/** One chipset called the i.Trek M3 issues GPGSA lines that look like* this: "$GPGSA,A,1,,,,*32" when it has no fix.  This is broken* in at least two ways: it's got the wrong number of fields, and* it claims to be a valid sentence (A flag) when it isn't.* Alarmingly, it's possible this error may be generic to SiRFstarIII.*/if (count < 17) {gpsd_log(&session->context->errout, LOG_DATA,"GPGSA: malformed, setting ONLINE_SET only.\n");mask = ONLINE_SET;} else if (session->nmea.latch_mode) {/* last GGA had a non-advancing timestamp; don't trust this GSA */mask = ONLINE_SET;} else {int i;session->newdata.mode = atoi(field[2]);/** The first arm of this conditional ignores dead-reckoning* fixes from an Antaris chipset. which returns E in field 2* for a dead-reckoning estimate.  Fix by Andreas Stricker.*/if (session->newdata.mode == 0 && field[2][0] == 'E')mask = 0;elsemask = MODE_SET;gpsd_log(&session->context->errout, LOG_PROG,"GPGSA sets mode %d\n", session->newdata.mode);if (field[15][0] != '\0')session->gpsdata.dop.pdop = safe_atof(field[15]);if (field[16][0] != '\0')session->gpsdata.dop.hdop = safe_atof(field[16]);if (field[17][0] != '\0')session->gpsdata.dop.vdop = safe_atof(field[17]);session->gpsdata.satellites_used = 0;memset(session->nmea.sats_used, 0, sizeof(session->nmea.sats_used));/* the magic 6 here counts the tag, two mode fields, and the DOP fields */for (i = 0; i < count - 6; i++) {int prn = nmeaid_to_prn(field[0], atoi(field[i + 3]));if (prn > 0)session->nmea.sats_used[session->gpsdata.satellites_used++] =(unsigned short)prn;}mask |= DOP_SET | USED_IS;gpsd_log(&session->context->errout, LOG_DATA,"GPGSA: mode=%d used=%d pdop=%.2f hdop=%.2f vdop=%.2f\n",session->newdata.mode,session->gpsdata.satellites_used,session->gpsdata.dop.pdop,session->gpsdata.dop.hdop,session->gpsdata.dop.vdop);}return mask;
}static gps_mask_t processGSV(int count, char *field[],struct gps_device_t *session)
/* GPS Satellites in View */
{
#define GSV_TALKER  field[0][1]/** GSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75* 2           Number of sentences for full data* 1           Sentence 1 of 2* 08          Total number of satellites in view* 01          Satellite PRN number* 40          Elevation, degrees* 083         Azimuth, degrees* 46          Signal-to-noise ratio in decibels* <repeat for up to 4 satellites per sentence>* There my be up to three GSV sentences in a data packet** Can occur with talker IDs:*   BD (Beidou),*   GA (Galileo),*   GB (Beidou),*   GL (GLONASS),*   GN (GLONASS, any combination GNSS),*   GP (GPS, SBAS, QZSS),*   QZ (QZSS).** GL may be (incorrectly) used when GSVs are mixed containing* GLONASS, GN may be (incorrectly) used when GSVs contain GLONASS* only.  Usage is inconsistent.** In the GLONASS version sat IDs run from 65-96 (NMEA0183 standardizes* this). At least two GPS, the BU-353 GLONASS and the u-blox NEO-M8N,* emit a GPGSV set followed by a GLGSV set.  We have also seen a* SiRF-IV variant that emits GPGSV followed by BDGSV. We need to* combine these.** NMEA 4.1 adds a signal-ID field just before the checksum. First* seen in May 2015 on a u-blox M8,*/int n, fldnum;if (count <= 3) {gpsd_log(&session->context->errout, LOG_WARN,"malformed GPGSV - fieldcount %d <= 3\n",count);gpsd_zero_satellites(&session->gpsdata);session->gpsdata.satellites_visible = 0;return ONLINE_SET;}/** This check used to be !=0, but we have loosen it a little to let by* NMEA 4.1 GSVs with an extra signal-ID field at the end.  */if (count % 4 > 1) {gpsd_log(&session->context->errout, LOG_WARN,"malformed GPGSV - fieldcount %d %% 4 != 0\n",count);gpsd_zero_satellites(&session->gpsdata);session->gpsdata.satellites_visible = 0;return ONLINE_SET;}session->nmea.await = atoi(field[1]);if ((session->nmea.part = atoi(field[2])) < 1) {gpsd_log(&session->context->errout, LOG_WARN,"malformed GPGSV - bad part\n");gpsd_zero_satellites(&session->gpsdata);return ONLINE_SET;} else if (session->nmea.part == 1) {/** might have gone from GPGSV to GLGSV/BDGSV/QZGSV,* in which case accumulate*/if (session->nmea.last_gsv_talker == '\0' || GSV_TALKER == session->nmea.last_gsv_talker) {gpsd_zero_satellites(&session->gpsdata);}session->nmea.last_gsv_talker = GSV_TALKER;if (session->nmea.last_gsv_talker == 'L')session->nmea.seen_glgsv = true;if (session->nmea.last_gsv_talker == 'D')session->nmea.seen_bdgsv = true;if (session->nmea.last_gsv_talker == 'Z')session->nmea.seen_qzss = true;}for (fldnum = 4; fldnum < count;) {struct satellite_t *sp;if (session->gpsdata.satellites_visible >= MAXCHANNELS) {gpsd_log(&session->context->errout, LOG_ERROR,"internal error - too many satellites [%d]!\n",session->gpsdata.satellites_visible);gpsd_zero_satellites(&session->gpsdata);break;}sp = &session->gpsdata.skyview[session->gpsdata.satellites_visible];sp->PRN = (short)nmeaid_to_prn(field[0], atoi(field[fldnum++]));sp->elevation = (short)atoi(field[fldnum++]);sp->azimuth = (short)atoi(field[fldnum++]);sp->ss = (float)atoi(field[fldnum++]);sp->used = false;if (sp->PRN > 0)for (n = 0; n < MAXCHANNELS; n++)if (session->nmea.sats_used[n] == (unsigned short)sp->PRN) {sp->used = true;break;}/** 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 (sp->PRN != 0)session->gpsdata.satellites_visible++;}/** Alas, we can't sanity check field counts when there are multiple sat * pictures, because the visible member counts *all* satellites - you * get a bad result on the second and later SV spans.  Note, this code* assumes that if any of the special sat pics occur they come right* after a stock GPGSV one.*/if (session->nmea.seen_glgsv || session->nmea.seen_bdgsv || session->nmea.seen_qzss)if (session->nmea.part == session->nmea.await&& atoi(field[3]) != session->gpsdata.satellites_visible)gpsd_log(&session->context->errout, LOG_WARN,"GPGSV field 3 value of %d != actual count %d\n",atoi(field[3]), session->gpsdata.satellites_visible);/* not valid data until we've seen a complete set of parts */if (session->nmea.part < session->nmea.await) {gpsd_log(&session->context->errout, LOG_PROG,"Partial satellite data (%d of %d).\n",session->nmea.part, session->nmea.await);return ONLINE_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_visible; n++)if (session->gpsdata.skyview[n].azimuth != 0)goto sane;gpsd_log(&session->context->errout, LOG_WARN,"Satellite data no good (%d of %d).\n",session->nmea.part, session->nmea.await);gpsd_zero_satellites(&session->gpsdata);return ONLINE_SET;sane:session->gpsdata.skyview_time = NAN;gpsd_log(&session->context->errout, LOG_DATA,"GSV: Satellite data OK (%d of %d).\n",session->nmea.part, session->nmea.await);/* assumes GLGSV or BDGSV group, if present, is emitted after the GPGSV */if ((session->nmea.seen_glgsv || session->nmea.seen_bdgsv || session->nmea.seen_qzss) && GSV_TALKER == 'P')return ONLINE_SET;return SATELLITE_SET;
#undef GSV_TALKER
}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, and* * in libgpsd_core.c where we generate $PGRME.*/gps_mask_t mask;if ((strcmp(field[2], "M") != 0) ||(strcmp(field[4], "M") != 0) || (strcmp(field[6], "M") != 0)) {session->newdata.epx =session->newdata.epy =session->newdata.epv = session->gpsdata.epe = 100;mask = 0;} else {session->newdata.epx = session->newdata.epy =safe_atof(field[1]) * (1 / sqrt(2)) * (GPSD_CONFIDENCE / CEP50_SIGMA);session->newdata.epv =safe_atof(field[3]) * (GPSD_CONFIDENCE / CEP50_SIGMA);session->gpsdata.epe =safe_atof(field[5]) * (GPSD_CONFIDENCE / CEP50_SIGMA);mask = HERR_SET | VERR_SET | PERR_IS;}gpsd_log(&session->context->errout, LOG_DATA,"PGRME: epx=%.2f epy=%.2f epv=%.2f\n",session->newdata.epx,session->newdata.epy,session->newdata.epv);return mask;
}static gps_mask_t processGBS(int c UNUSED, char *field[],struct gps_device_t *session)
/* NMEA 3.0 Estimated Position Error */
{/** $GPGBS,082941.00,2.4,1.5,3.9,25,,-43.7,27.5*65* 1) UTC time of the fix associated with this sentence (hhmmss.ss)* 2) Expected error in latitude (meters)* 3) Expected error in longitude (meters)* 4) Expected error in altitude (meters)* 5) PRN of most likely failed satellite* 6) Probability of missed detection for most likely failed satellite* 7) Estimate of bias in meters on most likely failed satellite* 8) Standard deviation of bias estimate* 9) Checksum*//* register fractional time for end-of-cycle detection */register_fractional_time(field[0], field[1], session);/* check that we're associated with the current fix */if (session->nmea.date.tm_hour == DD(field[1])&& session->nmea.date.tm_min == DD(field[1] + 2)&& session->nmea.date.tm_sec == DD(field[1] + 4)) {session->newdata.epy = safe_atof(field[2]);session->newdata.epx = safe_atof(field[3]);session->newdata.epv = safe_atof(field[4]);gpsd_log(&session->context->errout, LOG_DATA,"GBS: epx=%.2f epy=%.2f epv=%.2f\n",session->newdata.epx,session->newdata.epy,session->newdata.epv);return HERR_SET | VERR_SET;} else {gpsd_log(&session->context->errout, LOG_PROG,"second in $GPGBS error estimates doesn't match.\n");return 0;}
}static gps_mask_t processZDA(int c UNUSED, char *field[],struct gps_device_t *session)
/* Time & Date */
{/** $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** Note: some devices, like the u-blox ANTARIS 4h, are known to ship ZDAs* with some fields blank under poorly-understood circumstances (probably* when they don't have satellite lock yet).*/gps_mask_t mask = 0;if (field[1][0] == '\0' || field[2][0] == '\0' || field[3][0] == '\0'|| field[4][0] == '\0') {gpsd_log(&session->context->errout, LOG_WARN, "ZDA fields are empty\n");} else {int year, mon, mday, century;merge_hhmmss(field[1], session);/** We don't register fractional time here because want to leave* ZDA out of end-of-cycle detection. Some devices sensibly emit it only* when they have a fix, so watching for it can make them look* like they have a variable fix reporting cycle.*/year = atoi(field[4]);mon = atoi(field[3]);mday = atoi(field[2]);century = year - year % 100;if ( (1900 > year ) || (2200 < year ) ) {gpsd_log(&session->context->errout, LOG_WARN,"malformed ZDA year: %s\n",  field[4]);} else if ( (1 > mon ) || (12 < mon ) ) {gpsd_log(&session->context->errout, LOG_WARN,"malformed ZDA month: %s\n",  field[3]);} else if ( (1 > mday ) || (31 < mday ) ) {gpsd_log(&session->context->errout, LOG_WARN,"malformed ZDA day: %s\n",  field[2]);} else {gpsd_century_update(session, century);session->nmea.date.tm_year = year - 1900;session->nmea.date.tm_mon = mon - 1;session->nmea.date.tm_mday = mday;mask = TIME_SET;}};return mask;
}static gps_mask_t processHDT(int c UNUSED, char *field[],struct gps_device_t *session)
{/** $HEHDT,341.8,T*21** HDT,x.x*hh<cr><lf>** The only data field is true heading in degrees.* The following field is required to be 'T' indicating a true heading.* It is followed by a mandatory nmea_checksum.*/gps_mask_t mask;mask = ONLINE_SET;session->gpsdata.attitude.heading = safe_atof(field[1]);session->gpsdata.attitude.mag_st = '\0';session->gpsdata.attitude.pitch = NAN;session->gpsdata.attitude.pitch_st = '\0';session->gpsdata.attitude.roll = NAN;session->gpsdata.attitude.roll_st = '\0';session->gpsdata.attitude.yaw = NAN;session->gpsdata.attitude.yaw_st = '\0';session->gpsdata.attitude.dip = NAN;session->gpsdata.attitude.mag_len = NAN;session->gpsdata.attitude.mag_x = NAN;session->gpsdata.attitude.mag_y = NAN;session->gpsdata.attitude.mag_z = NAN;session->gpsdata.attitude.acc_len = NAN;session->gpsdata.attitude.acc_x = NAN;session->gpsdata.attitude.acc_y = NAN;session->gpsdata.attitude.acc_z = NAN;session->gpsdata.attitude.gyro_x = NAN;session->gpsdata.attitude.gyro_y = NAN;session->gpsdata.attitude.temp = NAN;session->gpsdata.attitude.depth = NAN;mask |= (ATTITUDE_SET);gpsd_log(&session->context->errout, LOG_RAW,"time %.3f, heading %lf.\n",session->newdata.time,session->gpsdata.attitude.heading);return mask;
}static gps_mask_t processDBT(int c UNUSED, char *field[],struct gps_device_t *session)
{/** $SDDBT,7.7,f,2.3,M,1.3,F*05* 1) Depth below sounder in feet* 2) Fixed value 'f' indicating feet* 3) Depth below sounder in meters* 4) Fixed value 'M' indicating meters* 5) Depth below sounder in fathoms* 6) Fixed value 'F' indicating fathoms* 7) Checksum.** In real-world sensors, sometimes not all three conversions are reported.*/gps_mask_t mask;mask = ONLINE_SET;if (field[3][0] != '\0') {session->newdata.altitude = -safe_atof(field[3]);mask |= (ALTITUDE_SET);} else if (field[1][0] != '\0') {session->newdata.altitude = -safe_atof(field[1]) / METERS_TO_FEET;mask |= (ALTITUDE_SET);} else if (field[5][0] != '\0') {session->newdata.altitude = -safe_atof(field[5]) / METERS_TO_FATHOMS;mask |= (ALTITUDE_SET);}if ((mask & ALTITUDE_SET) != 0) {if (session->newdata.mode < MODE_3D) {session->newdata.mode = MODE_3D;mask |= MODE_SET;}}/** Hack: We report depth below keep as negative altitude because there's* no better place to put it.  Should work in practice as nobody is* likely to be operating a depth sounder at varying altitudes.*/gpsd_log(&session->context->errout, LOG_RAW,"mode %d, depth %lf.\n",session->newdata.mode,session->newdata.altitude);return mask;
}static gps_mask_t processTXT(int count, char *field[],struct gps_device_t *session)
/* GPS Text message */
{/** $GNTXT,01,01,01,PGRM inv format*2A* 1                   Number of sentences for full data* 1                   Sentence 1 of 1* 01                  Message type*       00 - error*       01 - warning*       02 - notice*       07 - user* PGRM inv format     ASCII text** Can occur with talker IDs:*   BD (Beidou),*   GA (Galileo),*   GB (Beidou),*   GL (GLONASS),*   GN (GLONASS, any combination GNSS),*   GP (GPS, SBAS, QZSS),*   QZ (QZSS).*/gps_mask_t mask = 0;int msgType = 0;char *msgType_txt = "Unknown";if ( 5 != count) {return 0;}/* set something, so it won't look like an unknown sentence */mask |= ONLINE_SET;msgType = atoi(field[3]);switch ( msgType ) {case 0:msgType_txt = "Error";break;case 1:msgType_txt = "Warning";break;case 2:msgType_txt = "Notice";break;case 7:msgType_txt = "User";break;}/* maximum text lenght unknown, guess 80 */gpsd_log(&session->context->errout, LOG_WARN,"TXT: %.10s: %.80s\n",msgType_txt, field[4]);return mask;
}#ifdef TNT_ENABLE
static 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.$PTNTHTM,14223,N,169,N,-43,N,13641,2454*15HTM,x.x,a,x.x,a,x.x,a,x.x,x.x*hh<cr><lf>Fields in order:1. True heading (compass measurement + deviation + variation)2. magnetometer status character:C = magnetometer calibration alarmL = low alarmM = low warningN = normalO = high warningP = high alarmV = magnetometer voltage level alarm3. pitch angle4. pitch status character - see field 2 above5. roll angle6. roll status character - see field 2 above7. dip angle8. relative magnitude horizontal component of earth's magnetic field*hh          mandatory nmea_checksumBy default, angles are reported as 26-bit integers: weirdly, thetechnical manual says either 0 to 65535 or -32768 to 32767 canoccur as a range.*/gps_mask_t mask;mask = ONLINE_SET;session->gpsdata.attitude.heading = safe_atof(field[1]);session->gpsdata.attitude.mag_st = *field[2];session->gpsdata.attitude.pitch = safe_atof(field[3]);session->gpsdata.attitude.pitch_st = *field[4];session->gpsdata.attitude.roll = safe_atof(field[5]);session->gpsdata.attitude.roll_st = *field[6];session->gpsdata.attitude.yaw = NAN;session->gpsdata.attitude.yaw_st = '\0';session->gpsdata.attitude.dip = safe_atof(field[7]);session->gpsdata.attitude.mag_len = NAN;session->gpsdata.attitude.mag_x = safe_atof(field[8]);session->gpsdata.attitude.mag_y = NAN;session->gpsdata.attitude.mag_z = NAN;session->gpsdata.attitude.acc_len = NAN;session->gpsdata.attitude.acc_x = NAN;session->gpsdata.attitude.acc_y = NAN;session->gpsdata.attitude.acc_z = NAN;session->gpsdata.attitude.gyro_x = NAN;session->gpsdata.attitude.gyro_y = NAN;session->gpsdata.attitude.temp = NAN;session->gpsdata.attitude.depth = NAN;mask |= (ATTITUDE_SET);gpsd_log(&session->context->errout, LOG_RAW,"time %.3f, heading %lf (%c).\n",session->newdata.time,session->gpsdata.attitude.heading,session->gpsdata.attitude.mag_st);return mask;
}
#endif /* TNT_ENABLE */#ifdef OCEANSERVER_ENABLE
static gps_mask_t processOHPR(int c UNUSED, char *field[],struct gps_device_t *session)
{/** Proprietary sentence for OceanServer Magnetic Compass.OHPR,x.x,x.x,x.x,x.x,x.x,x.x,x.x,x.x,x.x,x.x,x.x,x.x,x.x,x.x,x.x,x.x,x.x,x.x*hh<cr><lf>Fields in order:1. Azimuth2. Pitch Angle3. Roll Angle4. Sensor temp, degrees centigrade5. Depth (feet)6. Magnetic Vector Length7-9. 3 axis Magnetic Field readings x,y,z10. Acceleration Vector Length11-13. 3 axis Acceleration Readings x,y,z14. Reserved15-16. 2 axis Gyro Output, X,y17. Reserved18. Reserved*hh          mandatory nmea_checksum*/gps_mask_t mask;mask = ONLINE_SET;session->gpsdata.attitude.heading = safe_atof(field[1]);session->gpsdata.attitude.mag_st = '\0';session->gpsdata.attitude.pitch = safe_atof(field[2]);session->gpsdata.attitude.pitch_st = '\0';session->gpsdata.attitude.roll = safe_atof(field[3]);session->gpsdata.attitude.roll_st = '\0';session->gpsdata.attitude.yaw = NAN;session->gpsdata.attitude.yaw_st = '\0';session->gpsdata.attitude.dip = NAN;session->gpsdata.attitude.temp = safe_atof(field[4]);session->gpsdata.attitude.depth = safe_atof(field[5]) / METERS_TO_FEET;session->gpsdata.attitude.mag_len = safe_atof(field[6]);session->gpsdata.attitude.mag_x = safe_atof(field[7]);session->gpsdata.attitude.mag_y = safe_atof(field[8]);session->gpsdata.attitude.mag_z = safe_atof(field[9]);session->gpsdata.attitude.acc_len = safe_atof(field[10]);session->gpsdata.attitude.acc_x = safe_atof(field[11]);session->gpsdata.attitude.acc_y = safe_atof(field[12]);session->gpsdata.attitude.acc_z = safe_atof(field[13]);session->gpsdata.attitude.gyro_x = safe_atof(field[15]);session->gpsdata.attitude.gyro_y = safe_atof(field[16]);mask |= (ATTITUDE_SET);gpsd_log(&session->context->errout, LOG_RAW,"Heading %lf.\n", session->gpsdata.attitude.heading);return mask;
}
#endif /* OCEANSERVER_ENABLE */#ifdef ASHTECH_ENABLE
/* Ashtech sentences take this format:* $PASHDR,type[,val[,val]]*CS* type is an alphabetic subsentence type** Oxford Technical Solutions (OXTS) also uses the $PASHR sentence,* but with a very different sentence contents:* $PASHR,HHMMSS.SSS,HHH.HH,T,RRR.RR,PPP.PP,aaa.aa,r.rrr,p.ppp,h.hhh,Q1,Q2*CS** so field 1 in ASHTECH is always alphabetic and numeric in OXTS* FIXME: decode OXTS $PASHDR**/
static gps_mask_t processPASHR(int c UNUSED, char *field[],struct gps_device_t *session)
{gps_mask_t mask;mask = 0;if (0 == strcmp("RID", field[1])) { /* Receiver ID */(void)snprintf(session->subtype, sizeof(session->subtype) - 1,"%s ver %s", field[2], field[3]);gpsd_log(&session->context->errout, LOG_DATA,"PASHR,RID: subtype=%s mask={}\n",session->subtype);return mask;} else if (0 == strcmp("POS", field[1])) {   /* 3D Position */mask |= MODE_SET | STATUS_SET | CLEAR_IS;if (0 == strlen(field[2])) {/* empty first field means no 3D fix is available */session->gpsdata.status = STATUS_NO_FIX;session->newdata.mode = MODE_NO_FIX;} else {/* if we make it this far, we at least have a 3D fix */session->newdata.mode = MODE_3D;if (1 == atoi(field[2]))session->gpsdata.status = STATUS_DGPS_FIX;elsesession->gpsdata.status = STATUS_FIX;session->gpsdata.satellites_used = atoi(field[3]);merge_hhmmss(field[4], session);register_fractional_time(field[0], field[4], session);do_lat_lon(&field[5], &session->newdata);session->newdata.altitude = safe_atof(field[9]);session->newdata.track = safe_atof(field[11]);session->newdata.speed = safe_atof(field[12]) / MPS_TO_KPH;session->newdata.climb = safe_atof(field[13]);session->gpsdata.dop.pdop = safe_atof(field[14]);session->gpsdata.dop.hdop = safe_atof(field[15]);session->gpsdata.dop.vdop = safe_atof(field[16]);session->gpsdata.dop.tdop = safe_atof(field[17]);mask |= (TIME_SET | LATLON_SET | ALTITUDE_SET);mask |= (SPEED_SET | TRACK_SET | CLIMB_SET);mask |= DOP_SET;gpsd_log(&session->context->errout, LOG_DATA,"PASHR,POS: hhmmss=%s lat=%.2f lon=%.2f alt=%.f speed=%.2f track=%.2f climb=%.2f mode=%d status=%d pdop=%.2f hdop=%.2f vdop=%.2f tdop=%.2f\n",field[4], session->newdata.latitude,session->newdata.longitude, session->newdata.altitude,session->newdata.speed, session->newdata.track,session->newdata.climb, session->newdata.mode,session->gpsdata.status, session->gpsdata.dop.pdop,session->gpsdata.dop.hdop, session->gpsdata.dop.vdop,session->gpsdata.dop.tdop);}} else if (0 == strcmp("SAT", field[1])) { /* Satellite Status */struct satellite_t *sp;int i, n = session->gpsdata.satellites_visible = atoi(field[2]);session->gpsdata.satellites_used = 0;for (i = 0, sp = session->gpsdata.skyview; sp < session->gpsdata.skyview + n; sp++, i++) {sp->PRN = (short)atoi(field[3 + i * 5 + 0]);sp->azimuth = (short)atoi(field[3 + i * 5 + 1]);sp->elevation = (short)atoi(field[3 + i * 5 + 2]);sp->ss = safe_atof(field[3 + i * 5 + 3]);sp->used = false;if (field[3 + i * 5 + 4][0] == 'U') {sp->used = true;session->gpsdata.satellites_used++;}}gpsd_log(&session->context->errout, LOG_DATA,"PASHR,SAT: used=%d\n",session->gpsdata.satellites_used);session->gpsdata.skyview_time = NAN;mask |= SATELLITE_SET | USED_IS;}return mask;
}
#endif /* ASHTECH_ENABLE */#ifdef MTK3301_ENABLE
static gps_mask_t processMTK3301(int c UNUSED, char *field[],struct gps_device_t *session)
{int msg, reason;msg = atoi(&(session->nmea.field[0])[4]);switch (msg) {case 001:         /* ACK / NACK */reason = atoi(field[2]);if (atoi(field[1]) == -1)gpsd_log(&session->context->errout, LOG_WARN,"MTK NACK: unknown sentence\n");else if (reason < 3) {const char *mtk_reasons[] = {"Invalid","Unsupported","Valid but Failed","Valid success"};gpsd_log(&session->context->errout, LOG_WARN,"MTK NACK: %s, reason: %s\n",field[1], mtk_reasons[reason]);}elsegpsd_log(&session->context->errout, LOG_DATA,"MTK ACK: %s\n", field[1]);return ONLINE_SET;case 424:          /* PPS pulse width response *//** Response will look something like: $PMTK424,0,0,1,0,69*12* The pulse width is in field 5 (69 in this example).  This* sentence is poorly documented at:* http://www.trimble.com/embeddedsystems/condor-gps-module.aspx?dtID=documentation** Packet Type: 324 PMTK_API_SET_OUTPUT_CTL* Packet meaning* Write the TSIP / antenna / PPS configuration data to the Flash memory.* DataField [Data0]:TSIP Packet[on/off]* 0 - Disable TSIP output (Default).* 1 - Enable TSIP output.* [Data1]:Antenna Detect[on/off]* 0 - Disable antenna detect function (Default).* 1 - Enable antenna detect function.* [Data2]:PPS on/off* 0 - Disable PPS function.* 1 - Enable PPS function (Default).* [Data3]:PPS output timing* 0 - Always output PPS (Default).* 1 - Only output PPS when GPS position is fixed.* [Data4]:PPS pulse width* 1~16367999: 61 ns~(61x 16367999) ns (Default = 69)** The documentation does not give the units of the data field.* Andy Walls <andy@silverblocksystems.net> says:** "The best I can figure using an oscilloscope, is that it is* in units of 16.368000 MHz clock cycles.  It may be* different for any other unit other than the Trimble* Condor. 69 cycles / 16368000 cycles/sec = 4.216 microseconds* [which is the pulse width I have observed]"** Support for this theory comes from the fact that crystal* TXCOs with a 16.368MHZ period are commonly available from* multiple vendors. Furthermore, 61*69 = 4209, which is* close to the observed cycle time and suggests that the* documentation is trying to indicate 61ns units.** He continues:** "I chose [127875] because to divides 16368000 nicely and the* pulse width is close to 1/100th of a second.  Any number* the user wants to use would be fine.  127875 cycles /* 16368000 cycles/second = 1/128 seconds = 7.8125* milliseconds"*//* too short?  Make it longer */if (atoi(field[5]) < 127875)(void)nmea_send(session, "$PMTK324,0,0,1,0,127875");return ONLINE_SET;case 705:          /* return device subtype */(void)strlcat(session->subtype, field[1], sizeof(session->subtype));(void)strlcat(session->subtype, "-", sizeof(session->subtype));(void)strlcat(session->subtype, field[2], sizeof(session->subtype));return ONLINE_SET;default:gpsd_log(&session->context->errout, LOG_PROG,"MTK: unknown msg: %d\n", msg);return ONLINE_SET;       /* ignore */}
}
#endif /* MTK3301_ENABLE *//**************************************************************************** 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 */bool cycle_continue;  /* cycle continuer? */nmea_decoder decoder;} nmea_phrase[] = {{"PGRMC", 0, false, NULL},    /* ignore Garmin Sensor Config */{"PGRME", 7, false, processPGRME},{"PGRMI", 0, false, NULL},    /* ignore Garmin Sensor Init */{"PGRMO", 0, false, NULL},  /* ignore Garmin Sentence Enable *//** Basic sentences must come after the PG* ones, otherwise* Garmins can get stuck in a loop that looks like this:** 1. A Garmin GPS in NMEA mode is detected.** 2. PGRMC is sent to reconfigure to Garmin binary mode.*    If successful, the GPS echoes the phrase.** 3. nmea_parse() sees the echo as RMC because the talker*    ID is ignored, and fails to recognize the echo as*    PGRMC and ignore it.** 4. The mode is changed back to NMEA, resulting in an*    infinite loop.*/{"DBT", 7,  true,  processDBT},{"GBS", 7,  false, processGBS},{"GGA", 13, false, processGGA},{"GLL", 7,  false, processGLL},{"GSA", 17, false, processGSA},{"GST", 8,  false, processGST},{"GSV", 0,  false, processGSV},{"HDT", 1,  false, processHDT},
#ifdef OCEANSERVER_ENABLE{"OHPR", 18, false, processOHPR},
#endif /* OCEANSERVER_ENABLE */
#ifdef ASHTECH_ENABLE{"PASHR", 3, false, processPASHR},    /* general handler for Ashtech */
#endif /* ASHTECH_ENABLE */
#ifdef MTK3301_ENABLE{"PMTK", 3,  false, processMTK3301},/* for some reason thhe parser no longer triggering on leading chars */{"PMTK001", 3,  false, processMTK3301},{"PMTK424", 3,  false, processMTK3301},{"PMTK705", 3,  false, processMTK3301},
#endif /* MTK3301_ENABLE */
#ifdef TNT_ENABLE{"PTNTHTM", 9, false, processTNTHTM},
#endif /* TNT_ENABLE */{"RMC", 8,  false, processRMC},{"TXT", 5,  false, processTXT},{"ZDA", 4,  false, processZDA},{"VTG", 0,  false, NULL},   /* ignore Velocity Track made Good */};int count;gps_mask_t retval = 0;unsigned int i, thistag;char *p, *s, *e;volatile char *t;/** 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_log(&session->context->errout, LOG_WARN,"Overlong packet of %zd chars rejected.\n",strlen(sentence));return ONLINE_SET;}/* make an editable copy of the sentence */(void)strlcpy((char *)session->nmea.fieldcopy, sentence, sizeof(session->nmea.fieldcopy) - 1);/* discard the checksum part */for (p = (char *)session->nmea.fieldcopy;(*p != '*') && (*p >= ' ');)++p;if (*p == '*')*p++ = ',';       /* otherwise we drop the last field */*p = '\0';e = p;/* split sentence copy on commas, filling the field array */count = 0;t = p;            /* end of sentence */p = (char *)session->nmea.fieldcopy + 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)) {session->nmea.field[count] = p;    /* we have a field. record it */if ((p = strchr(p, ',')) != NULL) { /* search for the next delimiter */*p = '\0';       /* replace it with a NUL */count++;       /* bump the counters and continue */p++;}}/* point remaining fields at empty string, just in case */for (i = (unsigned int)count;i <(unsigned)(sizeof(session->nmea.field) /sizeof(session->nmea.field[0])); i++)session->nmea.field[i] = e;/* sentences handlers will tell us when they have fractional time */session->nmea.latch_frac_time = false;/* dispatch on field zero, the sentence tag */for (thistag = i = 0;i < (unsigned)(sizeof(nmea_phrase) / sizeof(nmea_phrase[0])); ++i) {s = session->nmea.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,session->nmea.field,session);if (nmea_phrase[i].cycle_continue)session->nmea.cycle_continue = true;/** Must force this to be nz, as we're going to rely on a zero* value to mean "no previous tag" later.*/thistag = i + 1;} elseretval = ONLINE_SET;   /* unknown sentence */break;}}/* prevent overaccumulation of sat reports */if (!str_starts_with(session->nmea.field[0] + 2, "GSV"))session->nmea.last_gsv_talker = '\0';/* timestamp recording for fixes happens here */if ((retval & TIME_SET) != 0) {session->newdata.time = gpsd_utc_resolve(session);/** WARNING: This assumes time is always field 0, and that field 0* is a timestamp whenever TIME_SET is set.*/gpsd_log(&session->context->errout, LOG_DATA,"%s time is %2f = %d-%02d-%02dT%02d:%02d:%02.2fZ\n",session->nmea.field[0], session->newdata.time,1900 + session->nmea.date.tm_year,session->nmea.date.tm_mon + 1,session->nmea.date.tm_mday,session->nmea.date.tm_hour,session->nmea.date.tm_min,session->nmea.date.tm_sec + session->nmea.subseconds);/** If we have time and PPS is available, assume we have good time.* Because this is a generic driver we don't really have enough* information for a sharper test, so we'll leave it up to the* PPS code to do its own sanity filtering.*/retval |= PPSTIME_IS;}/** The end-of-cycle detector.  This code depends on just one* assumption: if a sentence with a timestamp occurs just before* start of cycle, then it is always good to trigger a report on* that sentence in the future.  For devices with a fixed cycle* this should work perfectly, locking in detection after one* cycle.  Most split-cycle devices (Garmin 48, for example) will* work fine.  Problems will only arise if a a sentence that* occurs just befiore timestamp increments also occurs in* mid-cycle, as in the Garmin eXplorist 210; those might jitter.*/if (session->nmea.latch_frac_time) {gpsd_log(&session->context->errout, LOG_PROG,"%s sentence timestamped %.2f.\n",session->nmea.field[0],session->nmea.this_frac_time);if (!GPS_TIME_EQUAL(session->nmea.this_frac_time,session->nmea.last_frac_time)) {uint lasttag = session->nmea.lasttag;retval |= CLEAR_IS;gpsd_log(&session->context->errout, LOG_PROG,"%s starts a reporting cycle.\n",session->nmea.field[0]);/** Have we seen a previously timestamped NMEA tag?* If so, designate as end-of-cycle marker.* But not if there are continuation sentences;* those get sorted after the last timestamped sentence*/if (lasttag > 0&& (session->nmea.cycle_enders & (1 << lasttag)) == 0&& !session->nmea.cycle_continue) {session->nmea.cycle_enders |= (1 << lasttag);gpsd_log(&session->context->errout, LOG_PROG,"tagged %s as a cycle ender.\n",nmea_phrase[lasttag - 1].name);}}} else {/* extend the cycle to an un-timestamped sentence? */if ((session->nmea.lasttag & session->nmea.cycle_enders) != 0)gpsd_log(&session->context->errout, LOG_PROG,"%s is just after a cycle ender.\n",session->nmea.field[0]);if (session->nmea.cycle_continue) {gpsd_log(&session->context->errout, LOG_PROG,"%s extends the reporting cycle.\n",session->nmea.field[0]);session->nmea.cycle_enders &=~ (1 << session->nmea.lasttag);session->nmea.cycle_enders |= (1 << thistag);}}/* here's where we check for end-of-cycle */if ((session->nmea.latch_frac_time || session->nmea.cycle_continue)&& (session->nmea.cycle_enders & (1 << thistag))!=0) {gpsd_log(&session->context->errout, LOG_PROG,"%s ends a reporting cycle.\n",session->nmea.field[0]);retval |= REPORT_IS;}if (session->nmea.latch_frac_time)session->nmea.lasttag = thistag;/* we might have a reliable end-of-cycle */if (session->nmea.cycle_enders != 0)session->cycle_end_reliable = true;return retval;
}#endif /* NMEA0183_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 == '!') {p++;}while (((c = *p) != '*') && (c != '\0')) {sum ^= c;p++;}*p++ = '*';(void)snprintf(p, 5, "%02X\r\n", (unsigned)sum);
}ssize_t nmea_write(struct gps_device_t *session, char *buf, size_t len UNUSED)
/* ship a command to the GPS, adding * and correct checksum */
{(void)strlcpy(session->msgbuf, buf, sizeof(session->msgbuf));if (session->msgbuf[0] == '$') {(void)strlcat(session->msgbuf, "*", sizeof(session->msgbuf));nmea_add_checksum(session->msgbuf);} else(void)strlcat(session->msgbuf, "\r\n", sizeof(session->msgbuf));session->msgbuflen = strlen(session->msgbuf);return gpsd_write(session, session->msgbuf, session->msgbuflen);
}ssize_t nmea_send(struct gps_device_t * session, const char *fmt, ...)
{char buf[BUFSIZ];va_list ap;va_start(ap, fmt);(void)vsnprintf(buf, sizeof(buf) - 5, fmt, ap);va_end(ap);return nmea_write(session, buf, strlen(buf));
}

GNSS NMEA-0183协议解析相关推荐

  1. minmea——GPS NMEA 0183 协议解析库

    今天给大家推荐一个纯C语言编写,轻量级的GPS NMEA 0183协议解析库:minmea github地址:https://github.com/kosma/minmea 一.特点 1.C99标准编 ...

  2. NMEA码数据解析(C语言)

    #include "NMEA.h" #include "stdio.h" #include "stdarg.h" #include &quo ...

  3. 嵌入式GPS模块编程 NMEA协议 0183协议

    嵌入式GPS模块编程 NMEA协议 0183协议 学前小知识: NMEA协议是为了在不同的GPS导航设备中建立统一的RTCM(海事无线电技术委员会)标准,它最初是由美国国家海洋电子协会(NMEA-Th ...

  4. GPS NMEA 0183 4.10协议/GPS Linux串口驱动

      NMEA 0183是美国国家海洋电子协会(National Marine Electronics Association)为海用电子设备制定的标准格式.现在已经成为GPS导航设备统一的RTCM(R ...

  5. GPS NMEA协议解析之通用语句

    GPS NMEA协议解析(NMEA通用语句) 文章目录 前言 一.NMEA协议简介 二.NMEA数据格式 1.GGA(全球定位系统定位数据) 2.GSA(GNSS 精度因子与有效卫星) 3.GSV(可 ...

  6. GPS卫星定位接收器的NMEA协议解析

    原文地址:GPS卫星定位接收器的NMEA协议解析作者:蟹蟹 GPS接收机只要处于工作状态就会源源不断地把接收并计算出的GPS导航定位信息通过串口传送到计算机中.前面的代码只负责从串口接收数据并将其放置 ...

  7. GPS NMEA0183协议解析(转载)

    这几天忙里偷闲集中把GPS NMEA0183协议好好研究了一下,不仅整理了一份相对较完整的协议文本,并且编写了一个相对较完善的GPS协议解析程序. 上图是我所说的测试程序,已经可以获得定位数据及相关卫 ...

  8. c语言gga字符串校验和代码,NMEA-0183协议解析(示例代码)

    NMEA-0183 NMEA 0183是美国国家海洋电子协会(National Marine Electronics Association )为海用电子设备制定的标准格式.目前业已成了GPS导航设备 ...

  9. GPS NMEA数据包解析

    GPS NMEA数据包解析 NMEA-0183是美国国家海洋电子协会为海用电子设备制定的标准格式.它包含了定位时间,纬度,经度,高度,定位所用的卫星数,DOP值,差分状态和校正时段等很多信息 一 通用 ...

  10. X-Analyser 总线分析软件:CANopen、1939解析、UDS诊断、NMEA2000 协议解析、DBC文件解析、仿真工具、CAN报文分析、仿CANoe曲线显示 CAN仪表模拟器

    X-Analyser 总线分析软件主要用于:CANopen协议解析.J1939解析 J1939地址ISO15765(UDS诊断) .NMEA2000 协议解析.DBC文件解析 DBC仿真工具.CANo ...

最新文章

  1. Scrum Mastery:产品开发中如何优化产品价值?
  2. SQL CROSS JOIN
  3. maven-约定优于配置
  4. Xshell6连Linux
  5. python装饰器详细剖析
  6. java设置断点,在Java中设置断点
  7. 算法分析:Oracle 11g 中基于哈希算法对唯一值数(NDV)的估算
  8. C++11多线程のfuture,promise,package_task
  9. 【转】QT中使用MYSQL中文乱码解决方法
  10. 通过EmbeddedServletContainerCustomizer接口调优Tomcat
  11. 什么软件能打开Android,哪位晓得apk文件用什么软件打开
  12. 在小程序可以完成任务的情况下,为什么程序员非要编写大程序呢?
  13. 如何让linux自动调整时间同步,如何让Linux时间与internet时间同步(CentOS)?
  14. iOS app签名机制
  15. TIOBE 8 月编程语言排行榜:数据挖掘和人工智能语言强势崛起!
  16. 网页版百度网盘倍速方法
  17. Log BERT 日志异常检测
  18. 华为2013年存储市场战略分析
  19. 【LOJ573】「LibreOJ NOI Round #2」单枪匹马
  20. matlab21世纪论坛,compressive sensing 压缩感知(转) 21世纪最火的研究方向

热门文章

  1. 在线编程JavaScript
  2. chromedriver与chrome各版本及下载地址
  3. 【AD20学习笔记】PCB封装库的创建
  4. 【PDF直接下载】6G总体愿景与潜在关键技术白皮书
  5. 【正点原子STM32连载】第七章 认识HAL库 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
  6. 账户系统,余额与体现
  7. Cradle CFD—专业热流场分析工具
  8. 深信服虚拟桌面部署及性能优化关键点配置(图文顺序全解)
  9. MySQL配置root远程连接mysql授权远程
  10. 【转载】SI 9000 及阻抗匹配学习笔记(一)