weather-station/model/weather_data.go

500 lines
12 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 // 风速标志位
}
// 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 := uint32(data[1]) // bits 8-15
idMSB := uint32(data[22]) | uint32(data[21])<<8 // bits 168-183
wd.StationID = fmt.Sprintf("%06X", (idMSB<<8)|idLSB)
// 3. 风向 (bits 16-24)
// 取第3字节的8位 + 第4字节的第1位
windDirLow := uint16(data[2]) // bits 16-23
windDirHigh := uint16(data[3] & 0x01) // bit 24
windDir := windDirLow | (windDirHigh << 8)
wd.WindDirection = int(windDir) % 360
// 4. WSP_FLAG (bit 25)
wd.WSPFlag = (data[3]>>1)&0x01 == 1
// 5. WSP_9 和 WSP_8 (bits 26-27)
wspHigh := uint16((data[3] >> 2) & 0x03)
// 6. 低电量标志 (bit 28)
wd.LowBattery = (data[3]>>4)&0x01 == 1
// 7. 温度 (bits 29-39) - 11位数据
// 协议说明温度数据实际上是从第4字节的低4位开始 + 第5字节的低3位
// 但根据示例数据分析应该是第4字节完整8位 + 第5字节低3位
tempLow := uint16(data[4]) // 第4字节的8位
tempHigh := uint16(data[5] & 0x07) // 第5字节的低3位
tempRaw := tempLow | (tempHigh << 8)
wd.Temperature = float64(tempRaw-400) / 10.0
// 8. 湿度 (bits 40-47)
// 湿度应该是第5字节的高5位
wd.Humidity = int(data[5] >> 3)
// 9. 风速 (bits 48-55 + WSP_9,WSP_8)
windSpeedLow := uint16(data[6]) // bits 48-55
if wd.WSPFlag {
// 9位风速
wd.WindSpeed = float64(windSpeedLow) / 8.0 * 0.51
} else {
// 10位风速加上bits 26-27的WSP_9和WSP_8
windSpeedRaw := windSpeedLow | (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) - 从第18-20字节 (索引17-19)
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
}
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,
}
}