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.Int64nmea.Float64代替int64float64

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

1 回复

更多关于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库设计时考虑了性能因素:

  1. 避免不必要的内存分配
  2. 使用快速字符串转换方法
  3. 最小化锁竞争

对于高频率的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消息,也可以扩展支持自定义消息格式。

回到顶部