《最佳实践之》GPS NMEA-0183 协议, 解析 $GPRMC 协议

2024-04-08 19:28

本文主要是介绍《最佳实践之》GPS NMEA-0183 协议, 解析 $GPRMC 协议,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一,了解 GPS NMEA-0183 协议

需要基础物联网对接知识,需要对解析协议有一定认识。
如果不知道怎么连接硬件,请看我的另一篇博客:https://blog.csdn.net/Crazy_Cw/article/details/126613967
这篇文章只说明,如何解析协议。

在这里插入图片描述

NMEA 是 National Marine Electronics Association 的缩写,是美国国家海洋电子协会的简称,现在是 GPS 导航设备统一的 RTCM 标准协议。NMEA-0183 协议是目前 GPS 接收机上使用最广泛的协议,大多数常见的 GPS 接收机、GPS 数据处理软件、导航软件都遵守或者至少兼容这个协议。

如果你使用过 GPS 传感器,那么可能对从串口中冒出了大量以 GPGGA、GPGSA、GPRMC 等开头的数据有印象,它们就是 NMEA-0183 协议数据。
在这里插入图片描述


[14:31:01.842]收←◆$GPGGA,063100.00,3104.39321639,N,12125.30910133,E,5,41,0.5,22.8980,M,11.6136,M,10,9959*55
$GNGLL,3104.39321639,N,12125.30910133,E,063100.00,A,D*79
$GPZDA,063100.00,08,04,2024,,*6A
$GPRMC,063100.00,A,3104.39321639,N,12125.30910133,E,0.005,340.1,080424,6.3,W,D*25
$GPVTG,340.106,T,346.435,M,0.00461,N,0.00853,K,D*28
$GPHDT,337.0082,T*08
$GPGGA,063101.00,3104.39321864,N,12125.30910076,E,5,41,0.5,22.8981,M,11.6136,M,11,9959*52
$GNGLL,3104.39321864,N,12125.30910076,E,063101.00,A,D*7E
$GPZDA,063101.00,08,04,2024,,*6B
$GPRMC,063101.00,A,3104.39321864,N,12125.30910076,E,0.005,309.9,080424,6.3,W,D*27
$GPVTG,309.931,T,316.260,M,0.00521,N,0.00966,K,D*28
$GPHDT,337.0855,T*0A

在这些数据中,包含了位置、速度、时间等信息,通过解析这数据,就可以实时获取物体的位置信息,或者实现时间同步。

二、协议格式

NMEA 0183 通讯协议是以 ASCII 码为基础的,一般格式如下:

$aaaaa,df1,df2,…[CR][LF]

格式说明:
$ 为起始标志;
, 为域分隔符;

  • 为校验和识别符,其后两位数为校验和,代表了 $和 * 之间所有字符的按位异或值(不包括这两个字符);
    \r\n 为终止符(不可见),所有的语句必须以来结束,也就是 ASCII 字符的“回车”(十六进制的 0D)和“换行”(十六进制的 0A)。
    NMEA-0183 协议定义的语句非常多,但是常用的或者说兼容性最广的语句只有 GPGGA、GPGSA、GPGSV、GPRMC、GPVTG、GPGLL 等。下面给出这些常用 NMEA 0183 语句的字段定义解释。

2.1 GPGSA

GPS DOP and Active Satellites(GSA)当前卫星信息

$GPGSA,<1>,<2>,<3>,<3>,<3>,<3>,<3>,<4>,<5>,<6>,<7>

各字段描述如下:

  • 模式 :M = 手动, A = 自动。
  • 定位型式 1 = 未定位, 2 = 二维定位, 3 = 三维定位。
  • PRN 数字:01 至 32 表天空使用中的卫星编号,最多可接收 12 颗卫星信息。
  • PDOP 位置精度因子(0.5~99.9)
  • HDOP 水平精度因子(0.5~99.9)
  • VDOP 垂直精度因子(0.5~99.9)
  • Checksum(检查位)

2.2 GPGSV

GPS Satellites in View(GSV)可见卫星信息

$GPGSV, <1>,<2>,<3>,<4>,<5>,<6>,<7>,?<4>,<5>,<6>,<7>,<8>

各字段描述如下:

  • GSV语句的总数
  • 本句GSV的编号
  • 可见卫星的总数,00 至 12。
  • 卫星编号, 01 至 32。
  • 卫星仰角, 00 至 90 度。
  • 卫星方位角, 000 至 359 度。实际值。
  • 讯号噪声比(C/No), 00 至 99 dB;无表未接收到讯号。
  • Checksum(检查位)
    注意:第 <4>,<5>,<6>,<7> 项个别卫星会重复出现,每行最多有四颗卫星。其余卫星信息会于次一行出现,若未使用,这些字段会空白。

2.3 GPGGA

Global Positioning System Fix Data(GGA)GPS定位信息

$GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>*hh

各字段描述如下:

  • UTC 时间,hhmmss(时分秒)格式
  • 纬度 ddmm.mmmm(度分)格式(前面的 0 也将被传输)
  • 纬度半球 N(北纬)或 S(南纬)
  • 经度 dddmm.mmmm(度分)格式(前面的 0 也将被传输)
  • 经度半球 E(东经)或 W(西经)
  • GPS 状态:0=未定位,1=非差分定位,2=差分定位,6=正在估算
  • 正在使用解算位置的卫星数量(00~12)(前面的 0 也将被传输)
  • HDOP 水平精度因子(0.5~99.9)
  • 海拔高度(-9999.9~99999.9)
  • 地球椭球面相对大地水准面的高度
  • 差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空)
  • 差分站 ID 号 0000~1023(前面的 0 也将被传输,如果不是差分定位将为空)

2.4 GPRMC

Recommended Minimum Specific GPS/TRANSIT Data(RMC)推荐定位信息

$GPRMC,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,<10>,<11>,<12>*hh

各字段描述如下:

  • UTC 时间,格式 hhmmss.ssss,代表时分秒.毫秒
  • 定位状态,A=有效定位,V=无效定位
  • 纬度 ddmm.mmmm(度分)格式(前面的 0 也将被传输)
  • 纬度半球 N(北纬)或 S(南纬)
  • 经度 dddmm.mmmm(度分)格式(前面的 0 也将被传输)
  • 经度半球 E(东经)或 W(西经)
  • 地面速率(000.0~999.9 节,前面的 0 也将被传输)
  • 地面航向(方位角),等效于二维罗盘(000.0~359.9 度,以真北为参考基准,前面的 0 也将被传输)
  • UTC 日期,DDMMYY(日月年)格式
  • 磁偏角(000.0~180.0 度,前面的 0 也将被传输)
  • 磁偏角方向,E(东)或 W(西)
  • 模式指示(仅 NMEA0183 3.0 版本输出,A=自主定位,D=差分,E=估算,N=数据无效)
    最后两个字节是校验和
    注意:

如果字段 4 的值等于 N,则字段 3 的值等于 ddmm.mmmmmm
如果字段 4 的值等于 S,则字段 3 的值等于 -ddmm.mmmmmm
如果字段 6 的值等于 E,则字段 5 的值等于 ddmm.mmmmmm
如果字段 6 的值等于 W,则字段 5 的值等于 -ddmm.mmmmmm
十进制北纬度数 = dd + mm.mmmmmm/60
十进制南纬度数 = -(dd + mm.mmmmmm/60)
十进制东经度数 = ddd + mm.mmmmmm/60
十进制西经度数 = -(ddd + mm.mmmmmm/60)

2.5 GPVTG

Track Made Good and Ground Speed(VTG)地面速度信息

$GPVTG,<1>,T,<2>,M,<3>,N,<4>,K,<5>*hh

各字段描述如下:

  • 以真北为参考基准的地面航向(000~359 度,前面的 0 也将被传输)
  • 以磁北为参考基准的地面航向(000~359 度,前面的 0 也将被传输)
  • 地面速率(000.0~999.9 节,前面的 0 也将被传输)
  • 地面速率(0000.0~1851.8 公里/小时,前面的0也将被传输)
  • 模式指示(仅 NMEA 0183 3.0 版本输出,A=自主定位,D=差分,E=估算,N=数据无效)

三、实践解析DEMO

解析GPRMC,实践

3.1 创建一个接收对象

package com.joyaiot.vehiclemonitor.netty.handler.carprotocol.entiy;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.sql.Time;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;/**
* 功能描述: TODO 方法描述
*
* @Author keLe
* @Date 2024/4/8
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GPRMCData {/**UTC时间*/private Time utcTime;/*** GPS状态* A=有效,V=无效*/private  String status;/*** 纬度,DDDMM.MMMMM*/private  double latitude;/*** 纬度,DDDMM.MMMMM*/private  double longitude;/*** 速度,Knots*/private  double speedKnots;/*** 地面速度,节(nautical miles per hour)*/private  double courseDegrees;/**日期*/private  Date date;/*** 磁偏角*/private  double magneticVariation;/*** 磁变方向,W=西变,E=东变*/private  String magneticVariationDir;/*** 模式,A=自主定位,D=差分,E=估算,N=数据无效* */private  String modeIndicator;}

3.2 解析协议工具类

package com.joyaiot.vehiclemonitor.netty.util;import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.fast.api.base.util.StringUtil;
import com.joyaiot.vehiclemonitor.netty.handler.carprotocol.entiy.GPRMCData;
import com.joyaiot.vehiclemonitor.utils.DecimalUtils;
import lombok.extern.slf4j.Slf4j;import java.sql.Time;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** 功能描述: GPRMC协议解析* 协议格式:$GPRMC,075704.00,A,3104.39326987,N,12125.30904175,E,0.007,286.4,070424,6.3,W,D*29* @see  <p>https://blog.csdn.net/Crazy_Cw/article/details/137515699</p>** @Author keLe* @Date 2024/4/8*/
@Slf4j
public class GPRMCParser {/*** 正则表达式*  验证协议 $GPRMC,075704.00,A,3104.39326987,N,12125.30904175,E,0.007,286.4,070424,6.3,W,D*29*/private static final Pattern GPRMC_PATTERN = Pattern.compile("\\$GPRMC,(\\d+\\.?\\d*),([AV]),(\\d+\\.\\d+),(N|S),(\\d+\\.\\d+),(E|W)," +"(\\d+\\.\\d+),(\\d+\\.\\d+)?,(\\d+)?,(\\d+\\.\\d+)?,(E|W)?,(A|D|E|N)\\*\\w+");/*** 1节等于1.852公里/小时(km/h) (单位换算)*/private static final double KNOTS_TO_KILOMETERS_PER_HOUR = 1.852;/*** 1km = 1000m (单位换算)*/private static final double KILOMETERS_TO_METERS = 1000;/*** 1小时等于3600秒 (单位换算)*/private static final double HOURS_TO_SECONDS = 3600.0;/*** 功能描述: 协议解析* @Author keLe* @Date 2024/4/8* @param  gprmcString 协议字符串* @return com.joyaiot.vehiclemonitor.netty.handler.carprotocol.entiy.GPRMCData 协议解析结果*/public static GPRMCData parse(String gprmcString) {GPRMCData data = new GPRMCData();Matcher matcher = isValidGPRMCFormat(gprmcString);// 校验GPRMC格式if (null == matcher) {log.error("Invalid GPRMC format");return null;}try {// 解析UTC时间String utcTime = parseUTCTime(matcher,gprmcString);data.setUtcTime(parseUTCTimeToDate(utcTime));// 解析状态data.setStatus(parseStatus(matcher,gprmcString));// 解析纬度double latitude = parseLatitude(matcher,gprmcString);data.setLatitude(latitude);// 解析经度double longitude = parseLongitude(matcher,gprmcString);data.setLongitude(longitude);// 解析速度double speedKnots = parseSpeed(matcher,gprmcString);data.setSpeedKnots(convertKnotsToMetersPerSecond(speedKnots));// 解析航向data.setCourseDegrees(parseCourse(matcher,gprmcString));// 解析日期data.setDate(parseDate(matcher,gprmcString, utcTime));// 解析磁偏角data.setMagneticVariation(parseMagneticVariation(matcher,gprmcString));// 解析磁偏角方向data.setMagneticVariationDir(parseMagneticVariationDir(matcher,gprmcString));// 解析模式指示data.setModeIndicator(parseModeIndicator(matcher,gprmcString));}catch (Exception e){log.error("GPRMC parse error:{}", e.getMessage());return null;}return data;}/*** 校验GPRMC格式是否有效* @Author keLe* @Date 2024/4/8* @param gprmcString GPRMC字符串* @return Matcher 匹配器*/private static Matcher isValidGPRMCFormat(String gprmcString) {Matcher matcher = GPRMC_PATTERN.matcher(gprmcString);return matcher.matches() ? matcher : null;}/*** 解析UTC时间* @Author keLe* @Date 2024/4/8* @param gprmcString GPRMC字符串* @return String UTC时间*/private static String parseUTCTime(Matcher matcher,String gprmcString) {String utcTimeStr = matcher.group(1);int hours = Integer.parseInt(utcTimeStr.substring(0, 2));int minutes = Integer.parseInt(utcTimeStr.substring(2, 4));String seconds = utcTimeStr.substring(4);String[] split = seconds.split("\\.");return hours + ":" + minutes + ":" + split[0] + "." + split[1];}/*** 将UTC时间字符串转换为Date对象* @Author keLe* @Date 2024/4/8* @param utcTimeStr UTC时间字符串* @return Date UTC时间对应的Date对象*/private static Time parseUTCTimeToDate(String utcTimeStr) {try {DateTime parse = DateUtil.parse(utcTimeStr, "HH:mm:ss.ssss");return new Time(parse.getTime());} catch (Exception e) {log.error("Failed to parse UTC time:{}, 报错原因:{}", utcTimeStr, e.toString());return null;}}/*** 解析状态* @Author keLe* @Date 2024/4/8* @param gprmcString GPRMC字符串* @return String 状态*/private static String parseStatus(Matcher matcher,String gprmcString) {return matcher.group(4).equals("A") ? "有效" : "无效";}/*** 解析纬度* @Author keLe* @Date 2024/4/8* @param gprmcString GPRMC字符串* @return double 纬度*/private static double parseLatitude(Matcher matcher,String gprmcString) {return parseCoordinate(matcher.group(3), matcher.group(4), 2);}/*** 解析经度* @Author keLe* @Date 2024/4/8* @param gprmcString GPRMC字符串* @return double 经度*/private static double parseLongitude(Matcher matcher,String gprmcString) {return parseCoordinate(matcher.group(5), matcher.group(6), 3);}/*** 功能描述: 解析速度* @Author keLe* @Date 2024/4/8* @param  matcher  匹配器* @param  gprmcString  GPRMC字符串* @return java.lang.String*/private static double parseSpeed(Matcher matcher,String gprmcString) {return parseOptionalDouble(matcher.group(7));}/*** 功能描述: 解析时间* @Author keLe* @Date 2024/4/8* @param  matcher  匹配器* @param  gprmcString  GPRMC字符串* @return java.lang.String*/private static double parseCourse(Matcher matcher,String gprmcString) {return parseOptionalDouble(matcher.group(8));}/*** 功能描述: 解析时间* @Author keLe* @Date 2024/4/8* @param  matcher  匹配器* @param  gprmcString  GPRMC字符串* @return java.lang.String*/private static Date parseDate(Matcher matcher,String gprmcString, String utcTime) {String yyyy = 2000 + Integer.parseInt(matcher.group(9).substring(4)) + "";String MM = matcher.group(9).substring(2, 4);String dd = matcher.group(9).substring(0, 2);try {return DateUtil.parse(yyyy + "-" + MM + "-" + dd + " " + utcTime, DatePattern.NORM_DATETIME_PATTERN);} catch (Exception e) {log.error("Failed to parse  YYMMDD :{}, 报错原因:{}", matcher.group(9), e.toString());return null;}}/*** 功能描述: 解析磁偏角* @Author keLe* @Date 2024/4/8* @param  matcher  匹配器* @param  gprmcString  GPRMC字符串* @return java.lang.String*/private static double parseMagneticVariation(Matcher matcher,String gprmcString) {return parseOptionalDouble(matcher.group(10));}/*** 功能描述: 解析磁偏角方向* @Author keLe* @Date 2024/4/8* @param  matcher  匹配器* @param  gprmcString  GPRMC字符串* @return java.lang.String*/private static String parseMagneticVariationDir(Matcher matcher,String gprmcString) {return parseDir(matcher.group(11));}/*** 功能描述: parse Mode* @Author keLe* @Date 2024/4/8* @param  matcher  匹配器* @param  gprmcString  GPRMC字符串* @return java.lang.String*/private static String parseModeIndicator(Matcher matcher,String gprmcString) {return parseModel(matcher.group(12));}/*** 功能描述: parse Coordinate* @Author keLe* @Date 2024/4/8* @param  degrees 经纬度* @param  direction 方向* @param  index 截取下标位置* @return java.lang.String*/private static double parseCoordinate(String degrees, String direction, int index) {double coord = Double.parseDouble(degrees.substring(0, index)) + Double.parseDouble(degrees.substring(index)) / 60.0;if (direction.startsWith("S") || direction.startsWith("W")) {coord *= -1;}return coord;}/*** 功能描述: parse Double* @Author keLe* @Date 2024/4/8* @param  value 字符串*/private static double parseOptionalDouble(String value) {if (value == null || value.isEmpty()) {return Double.NaN;}return Double.parseDouble(value);}/*** Converts speed from knots to meters per second.** @param speedInKnots The speed in knots.* @return The speed in meters per second.*/public static double convertKnotsToMetersPerSecond(double speedInKnots) {if (speedInKnots == 0) {return speedInKnots;}double speedInKilometersPerHour = speedInKnots * KNOTS_TO_KILOMETERS_PER_HOUR * KILOMETERS_TO_METERS;return DecimalUtils.preserveDecimal(speedInKilometersPerHour / HOURS_TO_SECONDS, 3);}/*** 功能描述: Mode indication (NMEA0183 version 3.0 output only, A=autonomous positioning,*   D=differential, E=estimate, N=invalid data)* @Author keLe* @Date 2024/4/8* @param  charStr 模式指示* @return java.lang.String*/public static String parseModel(String charStr) {String str = "数据无效";if (StringUtil.isBlank(charStr)) {return str;}switch (charStr) {case "A":str = "自主定位";break;case "D":str = "差分";break;case "E":str = "估算";break;default:str = "数据无效";break;}return str;}/*** 功能描述: Magnetic declination direction, E (east) or W (west)* @Author keLe* @Date 2024/4/8* @param  group 磁偏角方向* @return java.lang.String*/private static String parseDir(String group) {if (StringUtil.isBlank(group)) {return "数据无效";}return group.equals("E") ? "东" : "西";}public static void main(String[] args) {String gprmcString = "$GPRMC,075704.00,A,3104.39326987,N,12125.30904175,E,0.007,286.4,070424,6.3,W,D*29";System.out.println("原始报文:" + gprmcString);System.out.println("解析报文结果:" + parse(gprmcString));}
}

这篇关于《最佳实践之》GPS NMEA-0183 协议, 解析 $GPRMC 协议的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/886317

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

【Linux】应用层http协议

一、HTTP协议 1.1 简要介绍一下HTTP        我们在网络的应用层中可以自己定义协议,但是,已经有大佬定义了一些现成的,非常好用的应用层协议,供我们直接使用,HTTP(超文本传输协议)就是其中之一。        在互联网世界中,HTTP(超文本传输协议)是一个至关重要的协议,他定义了客户端(如浏览器)与服务器之间如何进行通信,以交换或者传输超文本(比如HTML文档)。

OWASP十大安全漏洞解析

OWASP(开放式Web应用程序安全项目)发布的“十大安全漏洞”列表是Web应用程序安全领域的权威指南,它总结了Web应用程序中最常见、最危险的安全隐患。以下是对OWASP十大安全漏洞的详细解析: 1. 注入漏洞(Injection) 描述:攻击者通过在应用程序的输入数据中插入恶意代码,从而控制应用程序的行为。常见的注入类型包括SQL注入、OS命令注入、LDAP注入等。 影响:可能导致数据泄

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [

多线程解析报表

假如有这样一个需求,当我们需要解析一个Excel里多个sheet的数据时,可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。 Way1 join import java.time.LocalTime;public class Main {public static void main(String[] args) thro