496 lines
12 KiB
Go
496 lines
12 KiB
Go
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))
|
||
}
|
||
|
||
wd := &WH65LPData{
|
||
Timestamp: time.Now(),
|
||
}
|
||
|
||
// 1. 家族码 (第1字节)
|
||
wd.FamilyCode = data[0]
|
||
fmt.Printf("\n=== 字段解析详情 ===\n")
|
||
fmt.Printf("1. 家族码: 0x%02X\n", wd.FamilyCode)
|
||
|
||
// 2. 设备ID (字节22,23,2)
|
||
// ID = ID_HSB(22) + ID_MSB(23) + ID_LSB(2)
|
||
// 按照文档顺序:00 2A 36
|
||
id := uint32(0)<<16 | uint32(data[22])<<8 | uint32(data[1])
|
||
wd.StationID = fmt.Sprintf("%06X", id)
|
||
fmt.Printf("2. 设备ID: 0x%s (字节22=0x%02X, 字节21=0x%02X, 字节1=0x%02X)\n",
|
||
wd.StationID, data[22], data[21], data[1])
|
||
|
||
// 3. 风向 (字节3,4)
|
||
// 9位数据,直接转换为十进制即为角度
|
||
windDir := uint16(data[2])
|
||
wd.WindDirection = int(windDir)
|
||
fmt.Printf("3. 风向: 0x%02X = %d°\n", windDir, wd.WindDirection)
|
||
|
||
// 4. 温度 (字节4,5)
|
||
// 温度原始值 = 0x296 = 662
|
||
// 计算公式:(662-400)/10 = 26.2
|
||
tempRaw := uint16(data[3])<<8 | uint16(data[4])
|
||
wd.Temperature = float64(tempRaw-400) / 10.0
|
||
fmt.Printf("4. 温度: 0x%03X = %.1f°C\n", tempRaw, wd.Temperature)
|
||
|
||
// 5. 湿度 (字节6)
|
||
// 直接转换为十进制
|
||
wd.Humidity = int(data[5])
|
||
fmt.Printf("5. 湿度: 0x%02X = %d%%\n", data[5], wd.Humidity)
|
||
|
||
// 6. 风速 (字节7)
|
||
// 计算公式:原始值/8*0.51
|
||
windSpeedRaw := uint16(data[6])
|
||
wd.WindSpeed = float64(windSpeedRaw) / 8.0 * 0.51
|
||
fmt.Printf("6. 风速: 0x%02X = %.2f m/s\n", windSpeedRaw, wd.WindSpeed)
|
||
|
||
// 7. 阵风 (字节8)
|
||
// 计算公式:原始值*0.51
|
||
wd.WindGust = float64(data[7]) * 0.51
|
||
fmt.Printf("7. 阵风: 0x%02X = %.2f m/s\n", data[7], wd.WindGust)
|
||
|
||
// 8. 降雨量 (字节9,10)
|
||
// 计算公式:原始值*0.254
|
||
rainRaw := uint16(data[8])<<8 | uint16(data[9])
|
||
wd.Rain = float64(rainRaw) * 0.254
|
||
fmt.Printf("8. 降雨量: 0x%04X = %.3f mm\n", rainRaw, wd.Rain)
|
||
|
||
// 9. 紫外线 (字节11,12)
|
||
uvRaw := uint16(data[10])<<8 | uint16(data[11])
|
||
wd.UV = getUVIndex(uvRaw)
|
||
fmt.Printf("9. 紫外线: 0x%04X = %d\n", uvRaw, wd.UV)
|
||
|
||
// 10. 光照 (字节13-15)
|
||
// 计算公式:原始值/10
|
||
lightRaw := uint32(data[12])<<16 | uint32(data[13])<<8 | uint32(data[14])
|
||
wd.Light = float64(lightRaw) / 10.0
|
||
fmt.Printf("10. 光照: 0x%06X = %.1f lux\n", lightRaw, wd.Light)
|
||
|
||
// 11. 气压 (字节18-20)
|
||
// 计算公式:原始值/100
|
||
pressureRaw := uint32(data[17])<<16 | uint32(data[18])<<8 | uint32(data[19])
|
||
wd.Pressure = float64(pressureRaw) / 100.0
|
||
fmt.Printf("11. 气压: 0x%06X = %.2f hPa\n", pressureRaw, wd.Pressure)
|
||
|
||
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,
|
||
}
|
||
}
|