377 lines
9.9 KiB
Go
377 lines
9.9 KiB
Go
|
package driver
|
|||
|
|
|||
|
import (
|
|||
|
"errors"
|
|||
|
"fmt"
|
|||
|
"io"
|
|||
|
"sync"
|
|||
|
"time"
|
|||
|
|
|||
|
"github.com/tarm/serial"
|
|||
|
)
|
|||
|
|
|||
|
const BufSize = 2048
|
|||
|
|
|||
|
type LCX6XZ struct {
|
|||
|
NMEA_RMC *NMEA_RMC
|
|||
|
NMEA_GGA *NMEA_GGA
|
|||
|
NMEA_GLL *NMEA_GLL
|
|||
|
NMEA_VTG *NMEA_VTG
|
|||
|
NMEA_GSA *NMEA_GSA
|
|||
|
NMEA_GSV *NMEA_GSV
|
|||
|
OutputRates map[NMEA_SUB_ID]uint8 // 存储查询到的输出速率
|
|||
|
ResData []byte
|
|||
|
mutex sync.Mutex
|
|||
|
uartAckCh chan struct{}
|
|||
|
uartFd io.ReadWriteCloser
|
|||
|
}
|
|||
|
|
|||
|
func UartRX_Task(lcx6xz *LCX6XZ) {
|
|||
|
var dataBuffer []byte // 累积数据缓冲区
|
|||
|
readBuffer := make([]byte, 1024) // 单次读取缓冲区
|
|||
|
|
|||
|
for {
|
|||
|
// 读取串口数据
|
|||
|
n, err := lcx6xz.uartFd.Read(readBuffer)
|
|||
|
|
|||
|
// 处理读取错误
|
|||
|
if err != nil {
|
|||
|
if err.Error() == "timeout" {
|
|||
|
// 超时是正常的,继续读取
|
|||
|
continue
|
|||
|
}
|
|||
|
fmt.Printf("串口读取错误: %v\n", err)
|
|||
|
time.Sleep(100 * time.Millisecond) // 避免死循环
|
|||
|
continue
|
|||
|
}
|
|||
|
|
|||
|
// 没有读取到数据
|
|||
|
if n == 0 {
|
|||
|
time.Sleep(10 * time.Millisecond) // 避免CPU占用过高
|
|||
|
continue
|
|||
|
}
|
|||
|
|
|||
|
// 将新数据追加到累积缓冲区
|
|||
|
dataBuffer = append(dataBuffer, readBuffer[:n]...)
|
|||
|
|
|||
|
// 处理累积缓冲区中的完整NMEA语句
|
|||
|
dataBuffer = processNMEAData(dataBuffer, lcx6xz)
|
|||
|
|
|||
|
// 防止缓冲区无限增长
|
|||
|
if len(dataBuffer) > BufSize {
|
|||
|
fmt.Println("WARNING: 数据缓冲区过大,清空缓冲区")
|
|||
|
dataBuffer = dataBuffer[:0] // 清空缓冲区
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// processNMEAData 处理NMEA数据并返回剩余的未处理数据
|
|||
|
func processNMEAData(data []byte, lcx6xz *LCX6XZ) []byte {
|
|||
|
processed := 0
|
|||
|
|
|||
|
for i := 0; i < len(data); {
|
|||
|
// 查找NMEA语句开始标记 '$'
|
|||
|
if data[i] == 0x24 {
|
|||
|
// 查找完整的NMEA语句(以\r\n结尾)
|
|||
|
endPos := findNMEAEnd(data[i:])
|
|||
|
if endPos == -1 {
|
|||
|
// 没有找到完整语句,保留剩余数据
|
|||
|
break
|
|||
|
}
|
|||
|
|
|||
|
// 解析NMEA语句
|
|||
|
sentence := data[i : i+endPos]
|
|||
|
err := parseNMEASentence(sentence, lcx6xz)
|
|||
|
if err != nil {
|
|||
|
fmt.Printf("NMEA解析错误: %v\n", err)
|
|||
|
}
|
|||
|
|
|||
|
// 移动到下一个位置
|
|||
|
i += endPos
|
|||
|
processed = i
|
|||
|
|
|||
|
} else if i < len(data)-1 && data[i] == 0xF1 && data[i+1] == 0xD9 {
|
|||
|
// 处理二进制协议(如果需要)
|
|||
|
skip, err := ParsBM(data[i:], lcx6xz)
|
|||
|
if err != nil {
|
|||
|
fmt.Printf("二进制协议解析错误: %v\n", err)
|
|||
|
i++ // 跳过当前字节
|
|||
|
} else {
|
|||
|
i += skip
|
|||
|
processed = i
|
|||
|
}
|
|||
|
} else {
|
|||
|
// 跳过无效字节
|
|||
|
i++
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 返回未处理的数据
|
|||
|
if processed > 0 {
|
|||
|
return data[processed:]
|
|||
|
}
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
// findNMEAEnd 查找NMEA语句的结束位置
|
|||
|
func findNMEAEnd(data []byte) int {
|
|||
|
for i := 0; i < len(data)-1; i++ {
|
|||
|
if data[i] == '\r' && data[i+1] == '\n' {
|
|||
|
return i + 2 // 包含\r\n
|
|||
|
}
|
|||
|
}
|
|||
|
return -1 // 没有找到结束标记
|
|||
|
}
|
|||
|
|
|||
|
// parseNMEASentence 解析单个NMEA语句
|
|||
|
func parseNMEASentence(sentence []byte, lcx6xz *LCX6XZ) error {
|
|||
|
// 移除\r\n
|
|||
|
sentenceStr := string(sentence)
|
|||
|
if len(sentenceStr) >= 2 && sentenceStr[len(sentenceStr)-2:] == "\r\n" {
|
|||
|
sentenceStr = sentenceStr[:len(sentenceStr)-2]
|
|||
|
}
|
|||
|
|
|||
|
// 验证最小长度
|
|||
|
if len(sentenceStr) < 6 {
|
|||
|
return fmt.Errorf("NMEA语句太短: %s", sentenceStr)
|
|||
|
}
|
|||
|
|
|||
|
// 解析NMEA语句类型
|
|||
|
nmeaType := ParsNMEAType(sentenceStr, len(sentenceStr))
|
|||
|
|
|||
|
// 加锁保护共享数据
|
|||
|
lcx6xz.mutex.Lock()
|
|||
|
defer lcx6xz.mutex.Unlock()
|
|||
|
|
|||
|
switch nmeaType {
|
|||
|
case NMEA_RMC_TYPE:
|
|||
|
rmc := ParsNMEARMC(sentenceStr, len(sentenceStr))
|
|||
|
if rmc != nil {
|
|||
|
lcx6xz.NMEA_RMC = rmc
|
|||
|
fmt.Printf("✅ RMC: 时间=%s, 纬度=%s%s, 经度=%s%s, 状态=%s\n",
|
|||
|
trimNullBytes(rmc.UTC[:]), trimNullBytes(rmc.Lat[:]), trimNullBytes(rmc.N_S[:]),
|
|||
|
trimNullBytes(rmc.Lon[:]), trimNullBytes(rmc.E_W[:]), trimNullBytes(rmc.Status[:]))
|
|||
|
}
|
|||
|
case NMEA_GGA_TYPE:
|
|||
|
gga := ParsNMEAGGA(sentenceStr, len(sentenceStr))
|
|||
|
if gga != nil {
|
|||
|
lcx6xz.NMEA_GGA = gga // 存储GGA数据
|
|||
|
fmt.Printf("✅ GGA: 时间=%s, 纬度=%s%s, 经度=%s%s, 质量=%s, 卫星数=%s\n",
|
|||
|
trimNullBytes(gga.UTC[:]), trimNullBytes(gga.Lat[:]), trimNullBytes(gga.N_S[:]),
|
|||
|
trimNullBytes(gga.Lon[:]), trimNullBytes(gga.E_W[:]),
|
|||
|
trimNullBytes(gga.Quality[:]), trimNullBytes(gga.NumSatUsed[:]))
|
|||
|
}
|
|||
|
case NMEA_GLL_TYPE:
|
|||
|
gll := ParsNMEAGLL(sentenceStr, len(sentenceStr))
|
|||
|
if gll != nil {
|
|||
|
lcx6xz.NMEA_GLL = gll // 存储GLL数据
|
|||
|
fmt.Printf("✅ GLL: 纬度=%s%s, 经度=%s%s, 时间=%s, 状态=%s\n",
|
|||
|
trimNullBytes(gll.Lat[:]), trimNullBytes(gll.N_S[:]),
|
|||
|
trimNullBytes(gll.Lon[:]), trimNullBytes(gll.E_W[:]),
|
|||
|
trimNullBytes(gll.UTC[:]), trimNullBytes(gll.Status[:]))
|
|||
|
}
|
|||
|
case NMEA_GSA_TYPE:
|
|||
|
gsa := ParsNMEAGSA(sentenceStr, len(sentenceStr))
|
|||
|
if gsa != nil {
|
|||
|
lcx6xz.NMEA_GSA = gsa // 存储GSA数据
|
|||
|
fmt.Printf("✅ GSA: 模式=%s, 定位模式=%s, PDOP=%s, HDOP=%s, VDOP=%s\n",
|
|||
|
trimNullBytes(gsa.Mode[:]), trimNullBytes(gsa.FixMode[:]),
|
|||
|
trimNullBytes(gsa.PDOP[:]), trimNullBytes(gsa.HDOP[:]), trimNullBytes(gsa.VDOP[:]))
|
|||
|
}
|
|||
|
case NMEA_GSV_TYPE:
|
|||
|
gsv := ParsNMEAGSV(sentenceStr, len(sentenceStr))
|
|||
|
if gsv != nil {
|
|||
|
lcx6xz.NMEA_GSV = gsv // 存储GSV数据
|
|||
|
fmt.Printf("✅ GSV: 总语句数=%s, 语句号=%s, 可视卫星数=%s\n",
|
|||
|
trimNullBytes(gsv.TotalNumSen[:]), trimNullBytes(gsv.SenNum[:]),
|
|||
|
trimNullBytes(gsv.TotalNumSat[:]))
|
|||
|
}
|
|||
|
case NMEA_VTG_TYPE:
|
|||
|
vtg := ParsNMEAVTG(sentenceStr, len(sentenceStr))
|
|||
|
if vtg != nil {
|
|||
|
lcx6xz.NMEA_VTG = vtg // 存储VTG数据
|
|||
|
fmt.Printf("✅ VTG: 航向=%s, 速度(节)=%s, 速度(km/h)=%s\n",
|
|||
|
trimNullBytes(vtg.COGT[:]), trimNullBytes(vtg.SOGN[:]), trimNullBytes(vtg.SOGK[:]))
|
|||
|
}
|
|||
|
default:
|
|||
|
if len(sentenceStr) >= 6 {
|
|||
|
fmt.Printf("⚠️ 未知NMEA语句类型: %s\n", sentenceStr[:6])
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// trimNullBytes 移除字节数组中的空字节
|
|||
|
func trimNullBytes(data []byte) string {
|
|||
|
for i, b := range data {
|
|||
|
if b == 0 {
|
|||
|
return string(data[:i])
|
|||
|
}
|
|||
|
}
|
|||
|
return string(data)
|
|||
|
}
|
|||
|
|
|||
|
// ParsNMEA 解析NMEA协议语句(保持向后兼容)
|
|||
|
func ParsNMEA(buffer []byte, lcx6xz *LCX6XZ) (int, error) {
|
|||
|
// 使用新的解析函数
|
|||
|
err := parseNMEASentence(buffer, lcx6xz)
|
|||
|
if err != nil {
|
|||
|
return 0, err
|
|||
|
}
|
|||
|
|
|||
|
// 查找结束位置
|
|||
|
endPos := findNMEAEnd(buffer)
|
|||
|
if endPos == -1 {
|
|||
|
return 0, errors.New("incomplete NMEA sentence")
|
|||
|
}
|
|||
|
|
|||
|
return endPos, nil
|
|||
|
}
|
|||
|
|
|||
|
// ParsBM 解析二进制协议语句
|
|||
|
func ParsBM(buffer []byte, lcx6xz *LCX6XZ) (int, error) {
|
|||
|
if len(buffer) < 8 {
|
|||
|
return 0, errors.New("二进制消息太短")
|
|||
|
}
|
|||
|
|
|||
|
// 检查帧头 0xF1 0xD9
|
|||
|
if buffer[0] != 0xF1 || buffer[1] != 0xD9 {
|
|||
|
return 0, errors.New("无效的二进制消息头")
|
|||
|
}
|
|||
|
|
|||
|
groupID := buffer[2]
|
|||
|
subID := buffer[3]
|
|||
|
length := uint16(buffer[4]) | (uint16(buffer[5]) << 8)
|
|||
|
|
|||
|
// 检查消息长度
|
|||
|
totalLen := int(6 + length + 2) // 头部6字节 + 载荷 + 校验和2字节
|
|||
|
if len(buffer) < totalLen {
|
|||
|
return 0, errors.New("二进制消息数据不完整")
|
|||
|
}
|
|||
|
|
|||
|
// 验证校验和
|
|||
|
checksum := QlCheckQuectel(buffer[:totalLen-2])
|
|||
|
receivedChecksum := uint16(buffer[totalLen-2]) | (uint16(buffer[totalLen-1]) << 8)
|
|||
|
|
|||
|
if checksum != receivedChecksum {
|
|||
|
return 0, fmt.Errorf("校验和错误: 期望 %04X, 实际 %04X", checksum, receivedChecksum)
|
|||
|
}
|
|||
|
|
|||
|
// 处理不同类型的二进制消息
|
|||
|
switch groupID {
|
|||
|
case 0x05: // BIN_RES_GID - 响应消息
|
|||
|
switch subID {
|
|||
|
case 0x01: // ACK
|
|||
|
fmt.Printf("✅ 收到ACK确认: GroupID=0x%02X, SubID=0x%02X\n",
|
|||
|
buffer[6], buffer[7])
|
|||
|
case 0x00: // NAK
|
|||
|
fmt.Printf("❌ 收到NAK否认: GroupID=0x%02X, SubID=0x%02X\n",
|
|||
|
buffer[6], buffer[7])
|
|||
|
}
|
|||
|
case 0x06: // BIN_CFG_GID - 配置消息响应
|
|||
|
switch subID {
|
|||
|
case 0x01: // MSG配置响应
|
|||
|
if length >= 3 {
|
|||
|
targetGroupID := buffer[6]
|
|||
|
targetSubID := buffer[7]
|
|||
|
outputRate := buffer[8]
|
|||
|
fmt.Printf("📊 输出速率响应: NMEA类型=0x%02X%02X, 速率=%d\n",
|
|||
|
targetGroupID, targetSubID, outputRate)
|
|||
|
|
|||
|
// 存储查询结果到设备结构中
|
|||
|
if lcx6xz != nil {
|
|||
|
lcx6xz.mutex.Lock()
|
|||
|
if lcx6xz.OutputRates == nil {
|
|||
|
lcx6xz.OutputRates = make(map[NMEA_SUB_ID]uint8)
|
|||
|
}
|
|||
|
lcx6xz.OutputRates[NMEA_SUB_ID(targetSubID)] = outputRate
|
|||
|
lcx6xz.mutex.Unlock()
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return totalLen, nil
|
|||
|
}
|
|||
|
|
|||
|
// SendBinaryCommand 发送二进制命令到GPS设备
|
|||
|
func SendBinaryCommand(lcx6xz *LCX6XZ, data []byte) error {
|
|||
|
if lcx6xz == nil || lcx6xz.uartFd == nil {
|
|||
|
return errors.New("GPS设备未连接")
|
|||
|
}
|
|||
|
|
|||
|
fmt.Printf("📤 发送二进制命令: %X\n", data)
|
|||
|
|
|||
|
_, err := lcx6xz.uartFd.Write(data)
|
|||
|
if err != nil {
|
|||
|
return fmt.Errorf("发送二进制命令失败: %v", err)
|
|||
|
}
|
|||
|
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// SetNMEAOutputRate 设置NMEA消息输出速率
|
|||
|
func SetNMEAOutputRate(lcx6xz *LCX6XZ, nmeaType NMEA_SUB_ID, rate uint8) error {
|
|||
|
// 创建设置输出速率的配置消息
|
|||
|
cfgMsg := CfgMsgSetOutRate(NMEA_GID, nmeaType, rate)
|
|||
|
if cfgMsg == nil {
|
|||
|
return errors.New("创建配置消息失败")
|
|||
|
}
|
|||
|
|
|||
|
// 发送配置消息
|
|||
|
msgBytes := cfgMsg.ToBytes()
|
|||
|
if msgBytes == nil {
|
|||
|
return errors.New("转换配置消息失败")
|
|||
|
}
|
|||
|
|
|||
|
return SendBinaryCommand(lcx6xz, msgBytes)
|
|||
|
}
|
|||
|
|
|||
|
// GetNMEAOutputRate 查询NMEA消息输出速率
|
|||
|
func GetNMEAOutputRate(lcx6xz *LCX6XZ, nmeaType NMEA_SUB_ID) error {
|
|||
|
// 创建查询输出速率的配置消息
|
|||
|
cfgMsg := CfgMsgQueOutRate(NMEA_GID, nmeaType)
|
|||
|
if cfgMsg == nil {
|
|||
|
return errors.New("创建查询消息失败")
|
|||
|
}
|
|||
|
|
|||
|
// 发送查询消息
|
|||
|
msgBytes := cfgMsg.ToBytes()
|
|||
|
if msgBytes == nil {
|
|||
|
return errors.New("转换查询消息失败")
|
|||
|
}
|
|||
|
|
|||
|
return SendBinaryCommand(lcx6xz, msgBytes)
|
|||
|
}
|
|||
|
|
|||
|
// 初始化LCX6XZ
|
|||
|
func InitLCX6XZ(Name string, Baud int, ReadTimeout int) (*LCX6XZ, error) {
|
|||
|
lcx6xz := &LCX6XZ{
|
|||
|
OutputRates: make(map[NMEA_SUB_ID]uint8),
|
|||
|
ResData: make([]byte, 1024),
|
|||
|
uartAckCh: make(chan struct{}),
|
|||
|
}
|
|||
|
|
|||
|
// 配置串口
|
|||
|
config := &serial.Config{
|
|||
|
Name: Name,
|
|||
|
Baud: Baud,
|
|||
|
Parity: serial.ParityNone,
|
|||
|
StopBits: serial.Stop1,
|
|||
|
ReadTimeout: time.Duration(ReadTimeout) * time.Millisecond,
|
|||
|
}
|
|||
|
|
|||
|
port, err := serial.OpenPort(config)
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("open uart device: %w", err)
|
|||
|
}
|
|||
|
|
|||
|
lcx6xz.uartFd = port
|
|||
|
|
|||
|
// 启动接收任务
|
|||
|
go UartRX_Task(lcx6xz)
|
|||
|
|
|||
|
return lcx6xz, nil
|
|||
|
}
|