golang解析NMEA协议数据的插件库go-nmea的使用
golang解析NMEA协议数据的插件库go-nmea的使用
go-nmea简介
这是一个用于Go编程语言(Golang)的NMEA库。
主要特性
- 解析单个NMEA 0183语句
- 支持带有NMEA 4.10 "TAG Blocks"的语句
- 为不支持的语句类型注册自定义解析器
- 用户友好的MIT许可证
安装
使用go get
安装go-nmea:
go get github.com/adrianmo/go-nmea
这将使github.com/adrianmo/go-nmea
包可用。
更新到最新版本
使用go get -u github.com/adrianmo/go-nmea
更新go-nmea到最新版本。
支持的语句
该库支持多种NMEA语句类型,包括但不限于:
- GGA - 全球定位系统(GPS)定位数据
- GLL - 地理位置,纬度/经度
- GSA - GNSS DOP和活动卫星
- GSV - GNSS可视卫星
- RMC - 推荐最小GNSS数据
- VTG - 地面航向和地面速度
- 等等…
使用示例
内置消息解析
package main
import (
"fmt"
"log"
"github.com/adrianmo/go-nmea"
)
func main() {
sentence := "$GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70"
s, err := nmea.Parse(sentence)
if err != nil {
log.Fatal(err)
}
if s.DataType() == nmea.TypeRMC {
m := s.(nmea.RMC)
fmt.Printf("Raw sentence: %v\n", m)
fmt.Printf("Time: %s\n", m.Time)
fmt.Printf("Validity: %s\n", m.Validity)
fmt.Printf("Latitude GPS: %s\n", nmea.FormatGPS(m.Latitude))
fmt.Printf("Latitude DMS: %s\n", nmea.FormatDMS(m.Latitude))
fmt.Printf("Longitude GPS: %s\n", nmea.FormatGPS(m.Longitude))
fmt.Printf("Longitude DMS: %s\n", nmea.FormatDMS(m.Longitude))
fmt.Printf("Speed: %f\n", m.Speed)
fmt.Printf("Course: %f\n", m.Course)
fmt.Printf("Date: %s\n", m.Date)
fmt.Printf("Variation: %f\n", m.Variation)
}
}
输出:
Raw sentence: $GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70
Time: 22:05:16.0000
Validity: A
Latitude GPS: 5133.8200
Latitude DMS: 51° 33' 49.200000"
Longitude GPS: 042.2400
Longitude DMS: 0° 42' 14.400000"
Speed: 173.800000
Course: 231.800000
Date: 13/06/94
Variation: -4.200000
自定义语句解析器
解析逻辑可以通过创建nmea.SentenceParser
实例并提供回调实现来自定义:
p := nmea.SentenceParser{
CustomParsers: nil,
ParsePrefix: nil,
CheckCRC: nil,
OnTagBlock: nil,
}
s, err := p.Parse("$GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70")
TAG Blocks
NMEA 4.10 TAG Block值可以通过消息的TagBlock
结构访问:
package main
import (
"fmt"
"log"
"time"
"github.com/adrianmo/go-nmea"
)
func main() {
sentence := "\\s:Satelite_1,c:1553390539*62\\!AIVDM,1,1,,A,13M@ah0025QdPDTCOl`K6`nV00Sv,0*52"
s, err := nmea.Parse(sentence)
if err != nil {
log.Fatal(err)
}
parsed := s.(nmea.VDMVDO)
fmt.Printf("TAG Block timestamp: %v\n", time.Unix(parsed.TagBlock.Time, 0))
fmt.Printf("TAG Block source: %v\n", parsed.TagBlock.Source)
}
输出:
TAG Block timestamp: 2019-03-24 14:22:19 +1300 NZDT
TAG Block source: Satelite_1
自定义消息解析
如果需要解析库不支持的消息,可以实现自己的消息解析。以下示例实现了一个假设的XYZ NMEA语句类型的解析器:
package main
import (
"fmt"
"github.com/adrianmo/go-nmea"
)
// 保存解析记录的类型
type XYZType struct {
nmea.BaseSentence
Time nmea.Time
Counter int64
Label string
Value float64
}
func main() {
// 只需执行一次,如果多次注册相同类型会报错
err := nmea.RegisterParser("XYZ", func(s nmea.BaseSentence) (nmea.Sentence, error) {
// 此示例使用包内置的解析助手
// 你也可以实现自己的解析逻辑
p := nmea.NewParser(s)
return XYZType{
BaseSentence: s,
Time: p.Time(0, "time"),
Label: p.String(1, "label"),
Counter: p.Int64(2, "counter"),
Value: p.Float64(3, "value"),
}, p.Err()
})
if err != nil {
panic(err)
}
sentence := "$00XYZ,220516,A,23,5133.82,W*42"
s, err := nmea.Parse(sentence)
if err != nil {
panic(err)
}
switch m := s.(type) {
case XYZType:
fmt.Printf("Raw sentence: %v\n", m)
fmt.Printf("Time: %s\n", m.Time)
fmt.Printf("Label: %s\n", m.Label)
fmt.Printf("Counter: %d\n", m.Counter)
fmt.Printf("Value: %f\n", m.Value)
default:
panic("Could not parse XYZ sentence")
}
}
输出:
Raw sentence: $AAXYZ,220516,A,23,5133.82,W*42
Time: 22:05:16.0000
Label: A
Counter: 23
Value: 5133.820000
带有可选值的消息解析
有些消息有可选字段。默认情况下,省略的数字值设置为0。在需要区分未定义值和实际0的情况下,可以使用nmea.Int64
和nmea.Float64
代替int64
和float64
。
package main
import (
"fmt"
"github.com/adrianmo/go-nmea"
)
// VTG表示航迹和速度数据
type VTG struct {
nmea.BaseSentence
TrueTrack nmea.Float64
MagneticTrack nmea.Float64
GroundSpeedKnots nmea.Float64
GroundSpeedKPH nmea.Float64
}
func main() {
nmea.MustRegisterParser("VTG", func(s nmea.BaseSentence) (nmea.Sentence, error) {
p := nmea.NewParser(s)
return VTG{
BaseSentence: s,
TrueTrack: p.NullFloat64(0, "true track"),
MagneticTrack: p.NullFloat64(2, "magnetic track"),
GroundSpeedKnots: p.NullFloat64(4, "ground speed (knots)"),
GroundSpeedKPH: p.NullFloat64(6, "ground speed (km/h)"),
}, p.Err()
})
sentence := "$GPVTG,140.88,T,,M,8.04,N,14.89,K,D*05"
s, err := nmea.Parse(sentence)
if err != nil {
panic(err)
}
m, ok := s.(VTG)
if !ok {
panic("Could not parse VTG sentence")
}
fmt.Printf("Raw sentence: %v\n", m)
fmt.Printf("TrueTrack: %v\n", m.TrueTrack)
fmt.Printf("MagneticTrack: %v\n", m.MagneticTrack)
fmt.Printf("GroundSpeedKnots: %v\n", m.GroundSpeedKnots)
fmt.Printf("GroundSpeedKPH: %v\n", m.GroundSpeedKPH)
}
输出:
Raw sentence: $GPVTG,140.88,T,,M,8.04,N,14.89,K,D*05
TrueTrack: {140.88 true}
MagneticTrack: {0 false}
GroundSpeedKnots: {8.04 true}
GroundSpeedKPH: {14.89 true}
贡献
欢迎提交问题或fork仓库并发送pull请求来更新库、修复bug、实现对新的语句类型的支持、重构代码等。
许可证
MIT许可证。
更多关于golang解析NMEA协议数据的插件库go-nmea的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang解析NMEA协议数据的插件库go-nmea的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
go-nmea库使用指南:解析NMEA协议数据
NMEA (National Marine Electronics Association) 协议是GPS设备常用的数据格式标准。go-nmea是一个用于解析NMEA协议数据的Golang库,下面详细介绍其使用方法。
安装
go get github.com/adrianmo/go-nmea
基本用法
1. 解析NMEA语句
package main
import (
"fmt"
"github.com/adrianmo/go-nmea"
)
func main() {
// 示例NMEA语句
sentence := "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A"
// 解析语句
s, err := nmea.Parse(sentence)
if err != nil {
fmt.Println("解析错误:", err)
return
}
// 类型断言获取具体消息
if m, ok := s.(nmea.RMC); ok {
fmt.Printf("时间: %v\n", m.Time)
fmt.Printf("状态: %v\n", m.Validity)
fmt.Printf("纬度: %f %s\n", m.Latitude, m.LatitudeHemisphere)
fmt.Printf("经度: %f %s\n", m.Longitude, m.LongitudeHemisphere)
fmt.Printf("速度: %f 节\n", m.Speed)
fmt.Printf("航向: %f 度\n", m.Course)
fmt.Printf("日期: %v\n", m.Date)
fmt.Printf("磁偏角: %f %s\n", m.Variation, m.VariationDirection)
}
}
2. 支持的NMEA消息类型
go-nmea支持多种NMEA消息类型:
- GGA - Global Positioning System Fix Data
- GLL - Geographic Position - Latitude/Longitude
- GSA - GPS DOP and active satellites
- GSV - GPS Satellites in view
- RMC - Recommended Minimum Specific GPS/Transit data
- VTG - Track Made Good and Ground Speed
- ZDA - Time & Date
高级用法
1. 从串口读取NMEA数据
package main
import (
"fmt"
"log"
"go.bug.st/serial"
"github.com/adrianmo/go-nmea"
)
func main() {
// 打开串口
mode := &serial.Mode{
BaudRate: 4800,
}
port, err := serial.Open("/dev/ttyUSB0", mode)
if err != nil {
log.Fatal(err)
}
defer port.Close()
buf := make([]byte, 1024)
for {
n, err := port.Read(buf)
if err != nil {
log.Fatal(err)
}
// 解析每条NMEA语句
sentences := string(buf[:n])
for _, sentence := range strings.Split(sentences, "\n") {
if strings.HasPrefix(sentence, "$") {
s, err := nmea.Parse(sentence)
if err != nil {
log.Println("解析错误:", err)
continue
}
// 处理不同类型的消息
switch m := s.(type) {
case nmea.GGA:
fmt.Printf("GGA - 定位质量: %d, 卫星数: %d, 海拔: %f\n",
m.Quality, m.NumSatellites, m.Altitude)
case nmea.RMC:
fmt.Printf("RMC - 位置: %f,%f\n", m.Latitude, m.Longitude)
// 其他类型处理...
}
}
}
}
}
2. 自定义解析器
// 自定义消息类型
type MyCustomMessage struct {
nmea.BaseSentence
Field1 string
Field2 float64
}
// 自定义解析函数
func parseMyCustomMessage(s nmea.BaseSentence) (nmea.Sentence, error) {
p := nmea.NewParser(s)
m := MyCustomMessage{
BaseSentence: s,
Field1: p.String(0, "field1"),
Field2: p.Float64(1, "field2"),
}
return m, p.Err()
}
func main() {
// 注册自定义解析器
nmea.MustRegisterParser("MYMSG", parseMyCustomMessage)
// 解析自定义消息
sentence := "$MYMSG,value1,123.45*12"
s, err := nmea.Parse(sentence)
if err != nil {
fmt.Println("解析错误:", err)
return
}
if m, ok := s.(MyCustomMessage); ok {
fmt.Printf("Field1: %s, Field2: %f\n", m.Field1, m.Field2)
}
}
性能考虑
go-nmea库设计时考虑了性能因素:
- 避免不必要的内存分配
- 使用快速字符串转换方法
- 最小化锁竞争
对于高频率的NMEA数据流(如10Hz GPS),建议:
- 使用缓冲通道处理解析后的数据
- 在单独的goroutine中进行解析
- 批量处理数据而不是逐条处理
错误处理
go-nmea提供了详细的错误信息:
sentence := "GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A"
_, err := nmea.Parse(sentence)
if err != nil {
if e, ok := err.(*nmea.ParseError); ok {
fmt.Printf("错误类型: %v, 位置: %d, 详情: %v\n",
e.Type, e.Pos, e.Message)
} else {
fmt.Println("其他错误:", err)
}
}
总结
go-nmea是一个功能完善且易于使用的NMEA协议解析库,适用于各种GPS数据处理场景。通过其灵活的API,开发者可以轻松解析标准NMEA消息,也可以扩展支持自定义消息格式。