weather-station/model/weather_data.go

527 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 // 风速标志位
}
// 辅助函数将byte转换为二进制字符串
func byteToBinary(b byte) string {
return fmt.Sprintf("%08b", b)
}
// ParseWH65LPData 解析WH65LP设备的25字节数据
func ParseWH65LPData(data []byte) (*WH65LPData, error) {
if len(data) != 25 {
return nil, fmt.Errorf("数据长度错误期望25字节实际%d字节", len(data))
}
fmt.Printf("\n=== 数据包解析详情 ===\n")
fmt.Printf("原始数据(25字节)\n")
for i, b := range data {
fmt.Printf("字节[%2d]: 0x%02X (二进制: %s)\n", i, b, byteToBinary(b))
}
fmt.Printf("\n")
wd := &WH65LPData{
Timestamp: time.Now(),
}
// 1. 家族码 (第1字节bits 0-7)
wd.FamilyCode = data[0]
fmt.Printf("1. 家族码: 0x%02X\n", wd.FamilyCode)
// 2. 设备ID (第2字节 + 第22-23字节)
idLSB := uint32(data[1])
idMSB := uint32(data[22])<<8 | uint32(data[21])
id := (idMSB << 8) | idLSB
wd.StationID = fmt.Sprintf("%06X", id)
fmt.Printf("2. 设备ID: LSB=0x%02X, MSB=0x%04X, 完整ID=0x%06X\n", idLSB, idMSB, id)
// 3. 风向 (bits 16-24)
windDirLow := uint16(data[2])
windDirHigh := uint16(data[3] & 0x01)
windDir := windDirLow | (windDirHigh << 8)
wd.WindDirection = int(windDir) % 360
fmt.Printf("3. 风向: Low=0x%02X (%s), High=%d, 角度=%d°\n",
data[2], byteToBinary(data[2]), windDirHigh, wd.WindDirection)
// 4. WSP_FLAG (bit 25)
wd.WSPFlag = (data[3]>>1)&0x01 == 1
fmt.Printf("4. WSP_FLAG: %v (字节3=%s)\n", wd.WSPFlag, byteToBinary(data[3]))
// 5. 风速高2位 (bits 26-27)
wspHigh := (data[3] >> 2) & 0x03
fmt.Printf("5. 风速高2位: 0x%X (字节3位6-7=%s)\n", wspHigh, byteToBinary(data[3])[2:4])
// 6. 低电量标志 (bit 28)
wd.LowBattery = (data[3]>>4)&0x01 == 1
fmt.Printf("6. 低电量: %v (字节3位4=%d)\n", wd.LowBattery, (data[3]>>4)&0x01)
// 7. 温度 (bits 29-39)
// 从字节3的高3位和字节4组成11位温度数据
tempHigh := uint16((data[3] >> 5)) & 0x07 // 取字节3的高3位
tempLow := uint16(data[4]) // 取字节4的8位
tempRaw := (tempHigh << 8) | tempLow // 组合成11位温度数据
wd.Temperature = float64(tempRaw-400) / 10.0
fmt.Printf("7. 温度: High=0x%X (%s), Low=0x%02X (%s), Raw=0x%X, 温度=%.1f°C\n",
tempHigh, byteToBinary(data[3])[0:3], tempLow, byteToBinary(data[4]), tempRaw, wd.Temperature)
// 8. 湿度 (bits 40-47)
// 第5字节完整8位就是湿度值
wd.Humidity = int(data[5])
fmt.Printf("8. 湿度: 原始值=0x%02X (%s), 湿度=%d%%\n",
data[5], byteToBinary(data[5]), wd.Humidity)
// 9. 风速 (bits 48-55 + WSP_9,WSP_8)
windSpeedRaw := uint16(data[6])
if !wd.WSPFlag {
windSpeedRaw |= uint16(wspHigh) << 8
}
wd.WindSpeed = float64(windSpeedRaw) / 8.0 * 0.51
fmt.Printf("9. 风速: Raw=0x%X, 风速=%.2f m/s\n", windSpeedRaw, wd.WindSpeed)
// 10. 阵风 (bits 56-63)
wd.WindGust = float64(data[7]) * 0.51
fmt.Printf("10. 阵风: Raw=0x%02X, 阵风=%.2f m/s\n", data[7], wd.WindGust)
// 11. 降雨量 (bits 64-79)
// 注意:这里要用小端序,低字节在前
rainRaw := uint16(data[9])<<8 | uint16(data[8])
wd.Rain = float64(rainRaw) * 0.254
fmt.Printf("11. 降雨量: Low=0x%02X, High=0x%02X, Raw=0x%04X, 降雨量=%.3f mm\n",
data[8], data[9], rainRaw, wd.Rain)
// 12. 紫外线 (bits 80-95)
uvRaw := uint16(data[10]) | uint16(data[11])<<8
wd.UV = getUVIndex(uvRaw)
fmt.Printf("12. 紫外线: Raw=0x%04X, UV指数=%d\n", uvRaw, wd.UV)
// 13. 光照 (bits 96-119)
lightRaw := uint32(data[12]) | uint32(data[13])<<8 | uint32(data[14])<<16
wd.Light = float64(lightRaw) / 10.0
fmt.Printf("13. 光照: Raw=0x%06X, 光照=%.1f lux\n", lightRaw, wd.Light)
// 14. 气压 (bits 136-159)
// 使用字节17-19组成气压值注意17位限制
pressureRaw := uint32(data[17]) | uint32(data[18])<<8 | uint32(data[19])<<16
pressureRaw &= 0x1FFFF // 确保只取17位
if pressureRaw == 0x1FFFF {
wd.Pressure = 0 // 无效值
} else {
wd.Pressure = float64(pressureRaw) / 100.0
}
fmt.Printf("14. 气压: Bytes=[0x%02X,0x%02X,0x%02X], Raw=0x%05X, 气压=%.2f hPa\n",
data[17], data[18], data[19], pressureRaw, wd.Pressure)
fmt.Printf("\n=== 解析结果 ===\n%s\n", wd.String())
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,
}
}