GNSS NMEA-0183协议解析
最近在做gps相关工作,需要解析gps模组输出的nmea数据,获得经纬度等信息,整理了一下nmea各个字段的含义,供大家参考。基本介绍GNSS的全称是全球导航卫星系统(Global Navigation Satellite System),它是泛指所有的卫星导航系统,包括全球的、区域的和增强的,如美国的GPS、俄罗斯的Glonass、欧洲的Galileo、中国的北斗卫星导航系统,以及相关的增强系统
最近在做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 */
else
newstatus = 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_FIELD
register_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;
else
mask = 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*15
HTM,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 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
By default, angles are reported as 26-bit integers: weirdly, the
technical manual says either 0 to 65535 or -32768 to 32767 can
occur 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. Azimuth
2. Pitch Angle
3. Roll Angle
4. Sensor temp, degrees centigrade
5. Depth (feet)
6. Magnetic Vector Length
7-9. 3 axis Magnetic Field readings x,y,z
10. Acceleration Vector Length
11-13. 3 axis Acceleration Readings x,y,z
14. Reserved
15-16. 2 axis Gyro Output, X,y
17. Reserved
18. 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;
else
session->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]);
}
else
gpsd_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;
} else
retval = 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));
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)