EdgexAgent/device-gps-go/run/driver/gpsController.go
2025-07-10 20:30:06 +08:00

377 lines
9.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}