487 lines
12 KiB
Go
487 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 // 风速标志位
|
||
}
|
||
|
||
// 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字节)
|
||
id := (uint32(data[22])<<8|uint32(data[21]))<<8 | uint32(data[1])
|
||
wd.StationID = fmt.Sprintf("%06X", id)
|
||
|
||
// 3. 风向 (bits 16-24)
|
||
windDir := uint16(data[2]) | ((uint16(data[3]) & 0x01) << 8)
|
||
wd.WindDirection = int(windDir) % 360
|
||
|
||
// 4. WSP_FLAG (bit 25)
|
||
wd.WSPFlag = (data[3]>>1)&0x01 == 1
|
||
|
||
// 5. 风速高2位 (bits 26-27)
|
||
wspHigh := (data[3] >> 2) & 0x03
|
||
|
||
// 6. 低电量标志 (bit 28)
|
||
wd.LowBattery = (data[3]>>4)&0x01 == 1
|
||
|
||
// 7. 温度 (bits 29-39) - 11位数据
|
||
tempRaw := (uint16((data[3]>>5)&0x07) << 8) | uint16(data[4])
|
||
wd.Temperature = float64(tempRaw-400) / 10.0
|
||
|
||
// 8. 湿度 (bits 40-47)
|
||
wd.Humidity = int(data[5])
|
||
|
||
// 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
|
||
|
||
// 10. 阵风 (bits 56-63)
|
||
wd.WindGust = float64(data[7]) * 0.51
|
||
|
||
// 11. 降雨量 (bits 64-79)
|
||
rainRaw := uint16(data[8]) | uint16(data[9])<<8
|
||
wd.Rain = float64(rainRaw) * 0.254
|
||
|
||
// 12. 紫外线 (bits 80-95)
|
||
uvRaw := uint16(data[10]) | uint16(data[11])<<8
|
||
wd.UV = getUVIndex(uvRaw)
|
||
|
||
// 13. 光照 (bits 96-119)
|
||
lightRaw := uint32(data[12]) | uint32(data[13])<<8 | uint32(data[14])<<16
|
||
wd.Light = float64(lightRaw) / 10.0
|
||
|
||
// 14. 气压 (bits 136-159)
|
||
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
|
||
}
|
||
|
||
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,
|
||
}
|
||
}
|