package model import ( "fmt" "net/url" "strconv" "strings" "time" ) type WeatherData struct { StationID string Password string TempF float64 Humidity int DewpointF float64 WindchillF float64 WindDir int WindSpeedMph float64 WindGustMph float64 RainIn float64 DailyRainIn float64 WeeklyRainIn float64 MonthlyRainIn float64 YearlyRainIn float64 TotalRainIn float64 SolarRadiation float64 UV int IndoorTempF float64 IndoorHumidity int AbsBarometerIn float64 BarometerIn float64 LowBattery bool SoftwareType string DateUTC string Action string RealTime int RTFreq int } func ParseWeatherData(data string) (*WeatherData, error) { if !strings.Contains(data, "GET /weatherstation/updateweatherstation.php") { return nil, fmt.Errorf("不是气象站数据") } urlStart := strings.Index(data, "GET ") if urlStart == -1 { return nil, fmt.Errorf("无法找到URL开始位置") } httpVersionEnd := strings.Index(data, " HTTP") if httpVersionEnd == -1 { return nil, fmt.Errorf("无法找到URL结束位置") } urlString := data[urlStart+4 : httpVersionEnd] queryStart := strings.Index(urlString, "?") if queryStart == -1 { return nil, fmt.Errorf("无法找到查询参数") } queryString := urlString[queryStart+1:] values, err := url.ParseQuery(queryString) if err != nil { return nil, fmt.Errorf("解析查询参数失败: %v", err) } wd := &WeatherData{} wd.StationID = values.Get("ID") wd.Password = values.Get("PASSWORD") wd.DateUTC = values.Get("dateutc") wd.SoftwareType = values.Get("softwaretype") wd.Action = values.Get("action") if tempF, err := strconv.ParseFloat(values.Get("tempf"), 64); err == nil { wd.TempF = tempF } if humidity, err := strconv.Atoi(values.Get("humidity")); err == nil { wd.Humidity = humidity } if dewpointF, err := strconv.ParseFloat(values.Get("dewptf"), 64); err == nil { wd.DewpointF = dewpointF } if windchillF, err := strconv.ParseFloat(values.Get("windchillf"), 64); err == nil { wd.WindchillF = windchillF } if windDir, err := strconv.Atoi(values.Get("winddir")); err == nil { wd.WindDir = windDir } if windSpeedMph, err := strconv.ParseFloat(values.Get("windspeedmph"), 64); err == nil { wd.WindSpeedMph = windSpeedMph } if windGustMph, err := strconv.ParseFloat(values.Get("windgustmph"), 64); err == nil { wd.WindGustMph = windGustMph } if rainIn, err := strconv.ParseFloat(values.Get("rainin"), 64); err == nil { wd.RainIn = rainIn } if dailyRainIn, err := strconv.ParseFloat(values.Get("dailyrainin"), 64); err == nil { wd.DailyRainIn = dailyRainIn } if weeklyRainIn, err := strconv.ParseFloat(values.Get("weeklyrainin"), 64); err == nil { wd.WeeklyRainIn = weeklyRainIn } if monthlyRainIn, err := strconv.ParseFloat(values.Get("monthlyrainin"), 64); err == nil { wd.MonthlyRainIn = monthlyRainIn } if yearlyRainIn, err := strconv.ParseFloat(values.Get("yearlyrainin"), 64); err == nil { wd.YearlyRainIn = yearlyRainIn } if totalRainIn, err := strconv.ParseFloat(values.Get("totalrainin"), 64); err == nil { wd.TotalRainIn = totalRainIn } if solarRadiation, err := strconv.ParseFloat(values.Get("solarradiation"), 64); err == nil { wd.SolarRadiation = solarRadiation } if uv, err := strconv.Atoi(values.Get("UV")); err == nil { wd.UV = uv } if indoorTempF, err := strconv.ParseFloat(values.Get("indoortempf"), 64); err == nil { wd.IndoorTempF = indoorTempF } if indoorHumidity, err := strconv.Atoi(values.Get("indoorhumidity")); err == nil { wd.IndoorHumidity = indoorHumidity } if absBarometerIn, err := strconv.ParseFloat(values.Get("absbaromin"), 64); err == nil { wd.AbsBarometerIn = absBarometerIn } if barometerIn, err := strconv.ParseFloat(values.Get("baromin"), 64); err == nil { wd.BarometerIn = barometerIn } if lowBatt, err := strconv.Atoi(values.Get("lowbatt")); err == nil { wd.LowBattery = lowBatt != 0 } if realTime, err := strconv.Atoi(values.Get("realtime")); err == nil { wd.RealTime = realTime } if rtFreq, err := strconv.Atoi(values.Get("rtfreq")); err == nil { wd.RTFreq = rtFreq } return wd, nil } func (w *WeatherData) String() string { return fmt.Sprintf(` 站点ID: %s 温度: %.1f°F (%.1f°C) 湿度: %d%% 露点: %.1f°F (%.1f°C) 风寒指数: %.1f°F (%.1f°C) 风向: %d° 风速: %.2f mph (%.2f km/h) 阵风: %.2f mph (%.2f km/h) 降雨量: %.3f 英寸/小时 (%.2f mm/h) - 当前降雨率 日降雨量: %.3f 英寸 (%.2f mm) - 过去24小时累计 周降雨量: %.3f 英寸 (%.2f mm) - 本周累计 月降雨量: %.3f 英寸 (%.2f mm) - 本月累计 年降雨量: %.3f 英寸 (%.2f mm) - 本年累计 总降雨量: %.3f 英寸 (%.2f mm) - 自设备安装以来累计 太阳辐射: %.2f W/m² 紫外线指数: %d 室内温度: %.1f°F (%.1f°C) 室内湿度: %d%% 绝对气压: %.3f 英寸汞柱 (%.2f hPa) 相对气压: %.3f 英寸汞柱 (%.2f hPa) 低电量: %v 软件类型: %s 日期UTC: %s `, w.StationID, w.TempF, (w.TempF-32)*5/9, w.Humidity, w.DewpointF, (w.DewpointF-32)*5/9, w.WindchillF, (w.WindchillF-32)*5/9, w.WindDir, w.WindSpeedMph, w.WindSpeedMph*1.60934, w.WindGustMph, w.WindGustMph*1.60934, w.RainIn, w.RainIn*25.4, w.DailyRainIn, w.DailyRainIn*25.4, w.WeeklyRainIn, w.WeeklyRainIn*25.4, w.MonthlyRainIn, w.MonthlyRainIn*25.4, w.YearlyRainIn, w.YearlyRainIn*25.4, w.TotalRainIn, w.TotalRainIn*25.4, w.SolarRadiation, w.UV, w.IndoorTempF, (w.IndoorTempF-32)*5/9, w.IndoorHumidity, w.AbsBarometerIn, w.AbsBarometerIn*33.8639, w.BarometerIn, w.BarometerIn*33.8639, w.LowBattery, w.SoftwareType, w.DateUTC, ) } type WH65LPData struct { StationID string // 设备ID (24位) Timestamp time.Time // 数据时间戳 FamilyCode byte // 家族码 (0x24) Temperature float64 // 温度 (°C) Humidity int // 湿度 (%) WindDirection int // 风向 (0-359°) WindSpeed float64 // 风速 (m/s) WindGust float64 // 阵风 (m/s) Rain float64 // 降雨量 (mm) UV int // 紫外线指数 (0-13) Light float64 // 光照 (lux) Pressure float64 // 气压 (hPa) LowBattery bool // 低电量标志 WSPFlag bool // 风速标志位 } // ParseWH65LPData 解析WH65LP设备的25字节数据 func ParseWH65LPData(data []byte) (*WH65LPData, error) { if len(data) != 25 { return nil, fmt.Errorf("数据长度错误:期望25字节,实际%d字节", len(data)) } wd := &WH65LPData{ Timestamp: time.Now(), } // 1. 解析家族码 (第1字节,bits 0-7) wd.FamilyCode = data[0] if wd.FamilyCode != 0x24 { return nil, fmt.Errorf("无效的家族码:0x%02X", wd.FamilyCode) } // 2. 解析设备ID (第2字节 + 第22-23字节) idLSB := data[1] idMSB := uint32(data[22]) | uint32(data[21])<<8 wd.StationID = fmt.Sprintf("%06X", (idMSB<<8)|uint32(idLSB)) // 3. 解析风向 (bits 16-24) // 第3字节完整8位 + 第4字节的最低位 windDir := uint16(data[2]) | ((uint16(data[3]) & 0x01) << 8) wd.WindDirection = int(windDir) % 360 // 4. 风速标志和温度 // WSP_FLAG 在第4字节的第2位 (bit 25) wd.WSPFlag = (data[3]>>1)&0x01 == 1 // 低电量标志在第4字节的第4位 (bit 28) wd.LowBattery = (data[3]>>3)&0x01 == 1 // 5. 温度 (bits 28-39) // 从第4字节的高4位和第5字节的低3位组合出11位温度数据 tempRaw := ((uint16(data[3]) >> 4) & 0x07) | (uint16(data[4]) << 3) wd.Temperature = float64(tempRaw-400) / 10.0 // 6. 湿度 (bits 40-47) // 第5字节的高5位 wd.Humidity = int(data[4] >> 3) // 7. 风速 (bits 48-55 + WSP_9,WSP_8) windSpeedRaw := uint16(data[6]) if !wd.WSPFlag { // 10位风速,从第4字节的bits 6-7获取高2位 windSpeedRaw |= uint16((data[3]>>6)&0x03) << 8 } wd.WindSpeed = float64(windSpeedRaw) / 8.0 * 0.51 // 8. 阵风 (bits 56-63) wd.WindGust = float64(data[7]) * 0.51 // 9. 降雨量 (bits 64-79) rainRaw := uint16(data[8]) | uint16(data[9])<<8 wd.Rain = float64(rainRaw) * 0.254 // 10. 紫外线 (bits 80-95) uvRaw := uint16(data[10]) | uint16(data[11])<<8 wd.UV = getUVIndex(uvRaw) // 11. 光照 (bits 96-119) lightRaw := uint32(data[12]) | uint32(data[13])<<8 | uint32(data[14])<<16 wd.Light = float64(lightRaw) / 10.0 // 12. 气压 (bits 136-159) // 从第18-20字节提取17位气压数据 pressureRaw := uint32(data[17]) | uint32(data[18])<<8 | uint32(data[19])<<16 pressureRaw &= 0x1FFFF if pressureRaw == 0x1FFFF { wd.Pressure = 0 // 无效值 } else { wd.Pressure = float64(pressureRaw) / 100.0 } // 13. 验证校验和 if !IsWH65LPData(data) { return nil, fmt.Errorf("数据校验失败") } return wd, nil } // 调试辅助函数 func getBits(data []byte, startBit, length int) uint32 { var result uint32 startByte := startBit / 8 startBitInByte := startBit % 8 // 从起始字节开始读取 result = uint32(data[startByte]) >> startBitInByte bitsGot := 8 - startBitInByte // 如果需要更多位,继续读取后续字节 for bitsGot < length { startByte++ result |= uint32(data[startByte]) << bitsGot bitsGot += 8 } // 只保留需要的位数 result &= (1 << length) - 1 return result } // getUVIndex 根据UV原始值获取UV指数 func getUVIndex(uvRaw uint16) int { switch { case uvRaw <= 432: return 0 case uvRaw <= 851: return 1 case uvRaw <= 1210: return 2 case uvRaw <= 1570: return 3 case uvRaw <= 2017: return 4 case uvRaw <= 2450: return 5 case uvRaw <= 2761: return 6 case uvRaw <= 3100: return 7 case uvRaw <= 3512: return 8 case uvRaw <= 3918: return 9 case uvRaw <= 4277: return 10 case uvRaw <= 4650: return 11 case uvRaw <= 5029: return 12 default: return 13 } } // String 格式化输出WH65LP数据 func (w *WH65LPData) String() string { return fmt.Sprintf(` 设备ID: %s 温度: %.1f°C 湿度: %d%% 风向: %d° 风速: %.2f m/s 阵风: %.2f m/s 降雨量: %.3f mm UV指数: %d 光照: %.1f lux 气压: %.2f hPa 电池状态: %v `, w.StationID, w.Temperature, w.Humidity, w.WindDirection, w.WindSpeed, w.WindGust, w.Rain, w.UV, w.Light, w.Pressure, w.LowBattery, ) } // IsWH65LPData 检查数据是否是WH65LP的数据 func IsWH65LPData(data []byte) bool { // 1. 检查数据长度 if len(data) != 25 { return false } // 2. 检查家族码 if data[0] != 0x24 { return false } // 3. 验证CRC (第16字节,前15字节的CRC8) if !ValidateCRC8(data[:15], data[15]) { return false } // 4. 验证气压校验和 (第21字节) pressureSum := uint8(data[17] + data[18] + data[19]) if pressureSum != data[20] { return false } // 5. 验证CRC2 (第24字节,前23字节的CRC8) if !ValidateCRC8(data[:23], data[23]) { return false } // 6. 验证最终校验和 (第25字节) var sum uint8 for i := 0; i < 24; i++ { sum += data[i] } if sum != data[24] { return false } return true } // ValidateCRC8 验证CRC8校验 // 多项式:0x31,初始值:0x00 func ValidateCRC8(data []byte, crc uint8) bool { return crc == CalculateCRC8(data) } // CalculateCRC8 计算CRC8 func CalculateCRC8(data []byte) uint8 { var crc uint8 for _, b := range data { crc ^= b for i := 0; i < 8; i++ { if crc&0x80 != 0 { crc = (crc << 1) ^ 0x31 } else { crc <<= 1 } } } return crc } // ParseUDP10006Data 解析UDP 10006端口的数据 func ParseUDP10006Data(data []byte) (*WH65LPData, error) { // 1. 验证数据格式 if !IsWH65LPData(data) { return nil, fmt.Errorf("无效的WH65LP数据格式") } // 2. 解析数据 return ParseWH65LPData(data) } // ConvertToMetric 将英制单位转换为公制单位 func ConvertToMetric(data *WeatherData) *WH65LPData { // 用于将旧格式数据转换为WH65LP格式 return &WH65LPData{ StationID: data.StationID, Timestamp: time.Now(), Temperature: (data.TempF - 32) * 5 / 9, Humidity: data.Humidity, WindDirection: data.WindDir, WindSpeed: data.WindSpeedMph * 0.44704, // mph转m/s WindGust: data.WindGustMph * 0.44704, // mph转m/s Rain: data.RainIn * 25.4, // inch转mm UV: data.UV, Light: data.SolarRadiation * 126.7, // W/m²转lux (近似转换) Pressure: data.BarometerIn * 33.8639, // inHg转hPa LowBattery: data.LowBattery, } }