package tcp import ( "encoding/binary" "encoding/hex" "fmt" "net" "strings" "sync" "time" "go_rain_dtu/internal/dao" "go_rain_dtu/internal/model" "go_rain_dtu/pkg/logger" ) const ( queryCmd = "010301F400100408" // 读取寄存器命令 resetCmd = "0106600200005AB6" // 清除雨量统计命令 tcpPort = ":10004" // TCP服务器端口 ) type SensorComm struct { conn net.Conn dao *dao.SensorDAO address string mu sync.Mutex } // 创建新的传感器通信实例 func NewSensorComm(conn net.Conn, dao *dao.SensorDAO) *SensorComm { return &SensorComm{ conn: conn, dao: dao, address: conn.RemoteAddr().String(), } } // 发送查询命令 func (s *SensorComm) sendQuery() error { s.mu.Lock() defer s.mu.Unlock() cmd, err := hex.DecodeString(queryCmd) if err != nil { logger.Logger.Printf("解析命令失败: %v", err) return err } _, err = s.conn.Write(cmd) if err != nil { logger.Logger.Printf("发送查询命令失败: %v", err) return err } logger.Logger.Printf("发送查询命令: %X", cmd) return nil } // 处理连接 func handleConnection(sensor *SensorComm) { defer sensor.Close() logger.Logger.Printf("新连接建立: %s", sensor.address) // 发送首次查询 if err := sensor.sendQuery(); err != nil { return } // 设置定时器,每分钟查询一次 ticker := time.NewTicker(time.Minute * 5) defer ticker.Stop() // 读取数据的缓冲区 buffer := make([]byte, 1024) done := make(chan bool) // 处理接收数据的goroutine go func() { for { // 设置读取超时 sensor.conn.SetReadDeadline(time.Now().Add(time.Second * 30)) n, err := sensor.conn.Read(buffer) if err != nil { logger.Logger.Printf("读取数据失败: %v", err) done <- true return } if n > 0 { // 记录原始数据 logger.Logger.Printf("接收到原始数据 [%s]: % X", sensor.address, buffer[:n]) // 只处理符合预期长度的响应(37字节) if n == 37 { if sensorData := sensor.handleData(buffer[:n]); sensorData != nil { logger.Logger.Printf("处理数据成功: %+v", sensorData) } } else { logger.Logger.Printf("数据长度不符合要求: %d != 37", n) } } } }() // 主循环 for { select { case <-ticker.C: if err := sensor.sendQuery(); err != nil { return } case <-done: return } } } // 处理接收到的数据 func (s *SensorComm) handleData(data []byte) *model.SensorData { // 检查数据长度 if len(data) != 37 { logger.Logger.Printf("数据长度错误: %d != 37", len(data)) return nil } // 验证数据格式 // 1. 检查起始字节是否为 0x01 0x03 if data[0] != 0x01 || data[1] != 0x03 { logger.Logger.Printf("数据格式错误: 起始字节不是0x01 0x03") return nil } // 2. 检查数据长度字节(第3个字节)是否为0x20 if data[2] != 0x20 { logger.Logger.Printf("数据格式错误: 长度字节不是0x20") return nil } // 解析数据,从第4个字节开始是数据部分 sensorData := &model.SensorData{ Timestamp: time.Now(), WindSpeed: int(binary.BigEndian.Uint16(data[3:5])), WindForce: int(binary.BigEndian.Uint16(data[5:7])), WindDirection8: int(binary.BigEndian.Uint16(data[7:9])), WindDirection360: int(binary.BigEndian.Uint16(data[9:11])), Humidity: int(binary.BigEndian.Uint16(data[11:13])), Temperature: int(binary.BigEndian.Uint16(data[13:15])), AtmPressure: int(binary.BigEndian.Uint16(data[21:23])), SolarRadiation: int(binary.BigEndian.Uint16(data[33:35])), } // 保存数据到数据库 if err := s.dao.Insert(sensorData); err != nil { logger.Logger.Printf("保存数据失败: %v", err) return nil } return sensorData } // 关闭连接 func (s *SensorComm) Close() { s.mu.Lock() defer s.mu.Unlock() if s.conn != nil { s.conn.Close() s.conn = nil } } // 启动TCP服务器 func StartTCPServer(dao *dao.SensorDAO) error { listener, err := net.Listen("tcp", tcpPort) if err != nil { return fmt.Errorf("启动TCP服务器失败: %v", err) } defer listener.Close() logger.Logger.Printf("TCP服务器启动在端口%s", tcpPort) var currentConn *SensorComm var mu sync.Mutex for { conn, err := listener.Accept() if err != nil { logger.Logger.Printf("接受连接失败: %v", err) continue } mu.Lock() // 关闭旧连接 if currentConn != nil { currentConn.Close() } // 创建新连接 sensor := NewSensorComm(conn, dao) currentConn = sensor mu.Unlock() logger.Logger.Printf("新连接建立: %s", conn.RemoteAddr()) // 处理连接 go handleConnection(sensor) } } // 辅助函数:将十六进制字符串转换为字节数组 func hexStringToBytes(s string) []byte { // 移除空格 s = strings.ReplaceAll(s, " ", "") // 确保字符串长度是偶数 if len(s)%2 != 0 { return nil } bytes := make([]byte, len(s)/2) for i := 0; i < len(s); i += 2 { // 转换高位 high, ok := hexCharToByte(s[i]) if !ok { return nil } // 转换低位 low, ok := hexCharToByte(s[i+1]) if !ok { return nil } // 组合高位和低位 bytes[i/2] = high<<4 | low } return bytes } // 辅助函数:将十六进制字符转换为字节 func hexCharToByte(c byte) (byte, bool) { switch { case '0' <= c && c <= '9': return c - '0', true case 'a' <= c && c <= 'f': return c - 'a' + 10, true case 'A' <= c && c <= 'F': return c - 'A' + 10, true } return 0, false }