package modbus import ( "encoding/hex" "fmt" "log" "net" "rain_monitor/db" "rain_monitor/models" "sync" "time" ) const ( DeviceWeatherStation = 1 // 气象站 DeviceRainGauge = 2 // 雨量计 ) const ( WeatherStationAddr = 0x01 RainGaugeAddr = 0x02 ) const ( FuncReadHoldingRegisters = 0x03 ) var ( WeatherStationCmd = []byte{0x01, 0x03, 0x01, 0xf4, 0x00, 0x10, 0x04, 0x08} // 气象站查询命令 RainGaugeCmd = []byte{0x02, 0x03, 0x00, 0x00, 0x00, 0x0a, 0xc5, 0xfe} // 雨量计查询命令 ) var ( connectedClients map[string]net.Conn clientsMutex sync.RWMutex latestWeatherData *models.WeatherData latestRainData *models.RainGaugeData dataMutex sync.RWMutex ) func init() { connectedClients = make(map[string]net.Conn) } // StartTCPServer 启动TCP服务器 func StartTCPServer() { listener, err := net.Listen("tcp", ":10004") if err != nil { log.Fatalf("无法启动TCP服务器: %v", err) } defer listener.Close() log.Println("TCP服务器已启动,监听端口 10004") for { conn, err := listener.Accept() if err != nil { log.Printf("接受连接失败: %v", err) continue } clientAddr := conn.RemoteAddr().String() log.Printf("新客户端连接: %s", clientAddr) clientsMutex.Lock() connectedClients[clientAddr] = conn clientsMutex.Unlock() go handleConnection(conn) } } // HandleConnection 处理客户端连接 func handleConnection(conn net.Conn) { defer func() { conn.Close() clientAddr := conn.RemoteAddr().String() clientsMutex.Lock() delete(connectedClients, clientAddr) clientsMutex.Unlock() log.Printf("客户端断开连接: %s", clientAddr) }() buffer := make([]byte, 1024) for { n, err := conn.Read(buffer) if err != nil { log.Printf("读取数据失败: %v", err) break } if n > 0 { data := buffer[:n] log.Printf("收到数据: %s", hex.EncodeToString(data)) processModbusData(data) } } } // ProcessModbusData 解析ModBus数据 func processModbusData(data []byte) { if len(data) < 3 { log.Println("数据长度不足") return } deviceAddr := data[0] functionCode := data[1] if functionCode != FuncReadHoldingRegisters { log.Printf("不支持的功能码: %02X", functionCode) return } switch deviceAddr { case WeatherStationAddr: processWeatherStationData(data) case RainGaugeAddr: processRainGaugeData(data) default: log.Printf("未知设备地址: %02X", deviceAddr) } } // ProcessWeatherStationData 处理气象站数据 func processWeatherStationData(data []byte) { if len(data) < 35 { log.Println("气象站数据长度不足") return } byteCount := int(data[2]) if len(data) < 3+byteCount+2 { log.Println("气象站数据长度与字节数不匹配") return } dataSection := data[3 : 3+byteCount] weather := &models.WeatherData{ Timestamp: time.Now(), } if len(dataSection) >= 32 { weather.WindSpeed = float64(uint16(dataSection[0])<<8|uint16(dataSection[1])) / 100.0 weather.WindForce = int(uint16(dataSection[2])<<8 | uint16(dataSection[3])) weather.WindDirection8 = int(uint16(dataSection[4])<<8 | uint16(dataSection[5])) weather.WindDirection360 = int(uint16(dataSection[6])<<8 | uint16(dataSection[7])) weather.Humidity = float64(uint16(dataSection[8])<<8|uint16(dataSection[9])) / 10.0 weather.Temperature = float64(uint16(dataSection[10])<<8|uint16(dataSection[11])) / 10.0 weather.Noise = float64(uint16(dataSection[12])<<8|uint16(dataSection[13])) / 10.0 weather.PM25 = int(uint16(dataSection[14])<<8 | uint16(dataSection[15])) weather.PM10 = int(uint16(dataSection[16])<<8 | uint16(dataSection[17])) weather.AtmPressure = float64(uint16(dataSection[18])<<8|uint16(dataSection[19])) / 10.0 weather.LuxHigh = int(uint16(dataSection[20])<<8 | uint16(dataSection[21])) weather.LuxLow = int(uint16(dataSection[22])<<8 | uint16(dataSection[23])) weather.LightIntensity = int(uint16(dataSection[24])<<8 | uint16(dataSection[25])) weather.Rainfall = float64(uint16(dataSection[26])<<8|uint16(dataSection[27])) / 10.0 weather.CompassAngle = float64(uint16(dataSection[28])<<8|uint16(dataSection[29])) / 100.0 weather.SolarRadiation = int(uint16(dataSection[30])<<8 | uint16(dataSection[31])) dataMutex.Lock() latestWeatherData = weather dataMutex.Unlock() log.Printf("气象站数据更新: 温度=%.1f℃, 湿度=%.1f%%, 风速=%.2fm/s, 风向=%d°, 大气压力=%.1fhPa, PM2.5=%dμg/m³, PM10=%dμg/m³, 降雨量=%.1fmm, 光照强度=%dlux", weather.Temperature, weather.Humidity, weather.WindSpeed, weather.WindDirection360, weather.AtmPressure, weather.PM25, weather.PM10, weather.Rainfall, weather.LightIntensity) // 保存到数据库 _, err := db.SaveWeatherData(weather) if err != nil { log.Printf("保存气象站数据失败: %v", err) } else { log.Println("气象站数据已保存到数据库") } } } // ProcessRainGaugeData 处理雨量计数据 func processRainGaugeData(data []byte) { if len(data) < 25 { log.Println("雨量计数据长度不足") return } byteCount := int(data[2]) if len(data) < 3+byteCount+2 { log.Println("雨量计数据长度与字节数不匹配") return } dataSection := data[3 : 3+byteCount] rainData := &models.RainGaugeData{ Timestamp: time.Now(), } if len(dataSection) >= 20 { rainData.DailyRainfall = float64(uint16(dataSection[0])<<8|uint16(dataSection[1])) / 10.0 rainData.InstantRainfall = float64(uint16(dataSection[2])<<8|uint16(dataSection[3])) / 10.0 rainData.YesterdayRainfall = float64(uint16(dataSection[4])<<8|uint16(dataSection[5])) / 10.0 rainData.TotalRainfall = float64(uint16(dataSection[6])<<8|uint16(dataSection[7])) / 10.0 rainData.HourlyRainfall = float64(uint16(dataSection[8])<<8|uint16(dataSection[9])) / 10.0 rainData.LastHourRainfall = float64(uint16(dataSection[10])<<8|uint16(dataSection[11])) / 10.0 rainData.Max24hRainfall = float64(uint16(dataSection[12])<<8|uint16(dataSection[13])) / 10.0 rainData.Max24hRainfallPeriod = int(uint16(dataSection[14])<<8 | uint16(dataSection[15])) rainData.Min24hRainfall = float64(uint16(dataSection[16])<<8|uint16(dataSection[17])) / 10.0 rainData.Min24hRainfallPeriod = int(uint16(dataSection[18])<<8 | uint16(dataSection[19])) dataMutex.Lock() latestRainData = rainData dataMutex.Unlock() log.Printf("雨量计数据更新: 当天降雨量=%.1fmm, 瞬时降雨量=%.1fmm, 总降雨量=%.1fmm, 昨日降雨量=%.1fmm, 小时降雨量=%.1fmm, 上一小时降雨量=%.1fmm", rainData.DailyRainfall, rainData.InstantRainfall, rainData.TotalRainfall, rainData.YesterdayRainfall, rainData.HourlyRainfall, rainData.LastHourRainfall) // 保存到数据库 _, err := db.SaveRainGaugeData(rainData) if err != nil { log.Printf("保存雨量计数据失败: %v", err) } else { log.Println("雨量计数据已保存到数据库") } } } // QueryDevice 向设备发送查询命令 func QueryDevice(deviceType int) error { var cmd []byte switch deviceType { case DeviceWeatherStation: cmd = WeatherStationCmd case DeviceRainGauge: cmd = RainGaugeCmd default: return fmt.Errorf("未知设备类型: %d", deviceType) } clientsMutex.RLock() defer clientsMutex.RUnlock() if len(connectedClients) == 0 { return fmt.Errorf("没有连接的客户端") } for addr, conn := range connectedClients { _, err := conn.Write(cmd) if err != nil { log.Printf("向客户端 %s 发送命令失败: %v", addr, err) continue } log.Printf("向客户端 %s 发送命令: %s", addr, hex.EncodeToString(cmd)) } return nil } // GetConnectionStatus 获取连接状态 func GetConnectionStatus() models.ConnectionStatus { clientsMutex.RLock() defer clientsMutex.RUnlock() status := models.ConnectionStatus{ Connected: len(connectedClients) > 0, Count: len(connectedClients), } if len(connectedClients) > 0 { for addr := range connectedClients { host, _, _ := net.SplitHostPort(addr) status.IP = host status.Port = 10004 break } } return status } // GetLatestWeatherData 获取最新气象站数据 func GetLatestWeatherData() *models.WeatherData { dataMutex.RLock() defer dataMutex.RUnlock() if latestWeatherData == nil { return nil } // 返回一个副本 data := *latestWeatherData return &data } // GetLatestRainData 获取最新雨量计数据 func GetLatestRainData() *models.RainGaugeData { dataMutex.RLock() defer dataMutex.RUnlock() if latestRainData == nil { return nil } // 返回一个副本 data := *latestRainData return &data }