EdgexAgent/device-gps-go/run/driver/gpsController.go

377 lines
9.9 KiB
Go
Raw Normal View History

2025-07-10 20:30:06 +08:00
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
}