| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- package test
- import (
- "context"
- "encoding/binary"
- "encoding/hex"
- "fmt"
- "internal/service/uhf"
- "sync"
- "testing"
- "time"
- )
- // ===================== 复用原有CRC和解析函数(无需修改)=====================
- func uiCrc16Cal(pucY []byte, ucX uint8) uint16 {
- const PRESET_VALUE = 0xFFFF
- const POLYNOMIAL = 0x8408
- var uiCrcValue uint16 = PRESET_VALUE
- for ucI := uint8(0); ucI < ucX; ucI++ {
- uiCrcValue = uiCrcValue ^ uint16(pucY[ucI])
- for ucJ := uint8(0); ucJ < 8; ucJ++ {
- if uiCrcValue&0x0001 != 0 {
- uiCrcValue = (uiCrcValue >> 1) ^ POLYNOMIAL
- } else {
- uiCrcValue = uiCrcValue >> 1
- }
- }
- }
- return (uiCrcValue << 8) | (uiCrcValue >> 8)
- }
- func parseResp(resp []byte) (bool, error) {
- if len(resp) < 7 {
- return false, fmt.Errorf("返回报文长度不足,实际长度: %d", len(resp))
- }
- if resp[0] != 0xCF {
- return false, fmt.Errorf("帧头错误,期望0xCF,实际0x%02X", resp[0])
- }
- dataWithoutCRC := resp[:len(resp)-2]
- recvCRC := binary.LittleEndian.Uint16(resp[len(resp)-2:])
- calcCRC := uiCrc16Cal(dataWithoutCRC, uint8(len(dataWithoutCRC)))
- if recvCRC != calcCRC {
- return false, fmt.Errorf("CRC校验失败 | 接收:0x%04X | 计算:0x%04X", recvCRC, calcCRC)
- }
- fmt.Println("✅ 设备上报原始Hex:", hex.EncodeToString(resp))
- fmt.Println("=====================================")
- cmd := binary.BigEndian.Uint16(resp[2:4])
- switch cmd {
- case 0x0072:
- fmt.Println("📦 解析【设备版本信息】")
- if len(resp) >= 8 {
- fmt.Printf("硬件版本: %d.%d\n", resp[6], resp[7])
- if len(resp) >= 9 {
- fmt.Printf("软件版本: %d.%d\n", resp[8], resp[9])
- }
- }
- case 0x0064:
- fmt.Println("📦 解析【设备远程网口信息】")
- if len(resp) >= 16 {
- status := resp[6]
- option := resp[7]
- enable := resp[8]
- ip := fmt.Sprintf("%d.%d.%d.%d", resp[9], resp[10], resp[11], resp[12])
- port := binary.BigEndian.Uint16([]byte{resp[13], resp[14]})
- heartTime := resp[15]
- fmt.Printf("执行状态: 0x%02X (%s)\n", status, map[byte]string{0x00: "成功", 0xFF: "失败"}[status])
- fmt.Printf("操作类型: 0x%02X (0x02=读取)\n", option)
- fmt.Printf("上报使能: %d (1=开启, 0=关闭)\n", enable)
- fmt.Printf("远程IP: %s\n", ip)
- fmt.Printf("远程端口: %d\n", port)
- fmt.Printf("心跳时间: %d*5S = %dS\n", heartTime, heartTime*5)
- }
- default:
- fmt.Println("📦 解析【设备主动上报数据】")
- // 这里根据设备上报的自定义指令格式扩展解析逻辑
- }
- fmt.Println("=====================================")
- return true, nil
- }
- // ===================== 串口持续读取核心逻辑 =====================
- // SerialReaderWrapper 封装串口读取器,增加协程控制
- type SerialReaderWrapper struct {
- reader *uhf.SerialReader
- ctx context.Context
- cancel context.CancelFunc
- dataChan chan []byte // 上报数据通道
- errChan chan error // 错误通道
- wg sync.WaitGroup // 等待组,确保协程优雅退出
- isRunning bool // 读取协程运行状态
- comPort string // 串口号
- baudRate int // 波特率
- reconnect bool // 是否自动重连
- }
- // NewSerialReaderWrapper 创建串口读取包装器
- func NewSerialReaderWrapper(comPort string, baudRate int, reconnect bool) *SerialReaderWrapper {
- ctx, cancel := context.WithCancel(context.Background())
- return &SerialReaderWrapper{
- reader: uhf.NewSerialReader(comPort, baudRate),
- ctx: ctx,
- cancel: cancel,
- dataChan: make(chan []byte, 100), // 缓冲通道,避免阻塞(根据上报频率调整大小)
- errChan: make(chan error, 10),
- comPort: comPort,
- baudRate: baudRate,
- reconnect: reconnect,
- }
- }
- // StartReadLoop 启动串口持续读取协程
- func (w *SerialReaderWrapper) StartReadLoop() error {
- if w.isRunning {
- return fmt.Errorf("读取协程已在运行")
- }
- // 先连接串口
- if err := w.reader.Connect(); err != nil {
- return fmt.Errorf("串口初始连接失败: %v", err)
- }
- w.isRunning = true
- w.wg.Add(1)
- // 启动读取协程
- go func() {
- defer func() {
- // 协程退出时清理
- w.isRunning = false
- w.reader.Disconnect()
- w.wg.Done()
- close(w.dataChan)
- close(w.errChan)
- fmt.Println("🔌 串口读取协程已退出")
- }()
- fmt.Println("🔄 串口持续读取协程已启动,等待设备上报数据...")
- for {
- select {
- // 检测退出信号
- case <-w.ctx.Done():
- fmt.Println("📢 收到退出信号,停止读取串口")
- return
- default:
- // 循环读取串口数据
- buf, err := w.reader.ReadData()
- if err != nil {
- w.errChan <- fmt.Errorf("读取串口失败: %v", err)
- // 自动重连逻辑
- if w.reconnect {
- fmt.Println("🔁 尝试重新连接串口...")
- w.reader.Disconnect()
- // 重连间隔2秒,避免频繁重试
- time.Sleep(2 * time.Second)
- if reconnectErr := w.reader.Connect(); reconnectErr != nil {
- w.errChan <- fmt.Errorf("串口重连失败: %v", reconnectErr)
- } else {
- w.errChan <- fmt.Errorf("串口重连成功")
- }
- } else {
- // 不重连则退出协程
- return
- }
- continue
- }
- // 读取到有效数据,发送到数据通道
- if len(buf) > 0 {
- select {
- case w.dataChan <- buf:
- // 数据成功写入通道
- default:
- // 通道满时丢弃(或根据业务调整,比如阻塞/报错)
- w.errChan <- fmt.Errorf("数据通道已满,丢弃上报数据: %x", buf)
- }
- }
- }
- }
- }()
- return nil
- }
- // StopReadLoop 停止读取协程(优雅退出)
- func (w *SerialReaderWrapper) StopReadLoop() {
- if !w.isRunning {
- return
- }
- // 发送退出信号
- w.cancel()
- // 等待协程退出
- w.wg.Wait()
- fmt.Println("✅ 串口读取协程已优雅停止")
- }
- // ProcessData 处理上报数据(可在主线程/另一个协程中运行)
- func (w *SerialReaderWrapper) ProcessData() {
- for {
- select {
- case <-w.ctx.Done():
- return
- // 处理上报数据
- case data, ok := <-w.dataChan:
- if !ok {
- return
- }
- fmt.Println("\n📥 收到设备上报数据:")
- // 解析数据(复用你已有的parseResp函数)
- if _, err := parseResp(data); err != nil {
- fmt.Printf("❌ 解析上报数据失败: %v | 原始数据: %x\n", err, data)
- }
- // 处理错误
- case err, ok := <-w.errChan:
- if !ok {
- return
- }
- fmt.Printf("⚠️ 串口读取错误: %v\n", err)
- }
- }
- }
- // ===================== 测试主函数 =====================
- func TestContinuousSerialRead(t *testing.T) {
- // 1. 创建串口包装器(COM9,115200波特率,开启自动重连)
- serialWrapper := NewSerialReaderWrapper("COM9", 115200, true)
- defer serialWrapper.StopReadLoop() // 测试结束时停止协程
- // 2. 启动持续读取协程
- if err := serialWrapper.StartReadLoop(); err != nil {
- t.Fatalf("❌ 启动串口读取协程失败: %v", err)
- }
- // 3. 启动数据处理协程(主线程也可以直接调用ProcessData,这里用协程避免阻塞)
- go serialWrapper.ProcessData()
- // 4. 主线程保持运行(模拟业务逻辑,比如运行30秒后退出)
- fmt.Println("🚀 主线程运行中,按Ctrl+C退出...")
- time.Sleep(30 * time.Second) // 运行30秒,可替换为无限循环(for {})
- // 5. 主动停止(可选,defer也会自动停止)
- serialWrapper.StopReadLoop()
- }
|