From 1fa4319879f1b01ed1aca57fd110e2527a0c8579 Mon Sep 17 00:00:00 2001 From: yarnom Date: Mon, 21 Jul 2025 16:24:03 +0800 Subject: [PATCH] test: Listen on UDP port 10006 and parse weather station data --- config/config.go | 47 +++++++++ config/config.yaml | 2 + go.mod | 5 + go.sum | 3 + main.go | 46 +++++++++ model/weather_data.go | 216 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 319 insertions(+) create mode 100644 config/config.go create mode 100644 config/config.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 model/weather_data.go diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..b27c961 --- /dev/null +++ b/config/config.go @@ -0,0 +1,47 @@ +package config + +import ( + "fmt" + "os" + "sync" + + "gopkg.in/yaml.v3" +) + +type ServerConfig struct { + UDPPort int `yaml:"udp_port"` +} + +type Config struct { + Server ServerConfig `yaml:"server"` +} + +var ( + instance *Config + once sync.Once +) + +func GetConfig() *Config { + once.Do(func() { + instance = &Config{} + err := instance.loadConfig() + if err != nil { + panic(fmt.Sprintf("加载配置文件失败: %v", err)) + } + }) + return instance +} + +func (c *Config) loadConfig() error { + data, err := os.ReadFile("config/config.yaml") + if err != nil { + return fmt.Errorf("读取配置文件失败: %v", err) + } + + err = yaml.Unmarshal(data, c) + if err != nil { + return fmt.Errorf("解析配置文件失败: %v", err) + } + + return nil +} diff --git a/config/config.yaml b/config/config.yaml new file mode 100644 index 0000000..04d49fd --- /dev/null +++ b/config/config.yaml @@ -0,0 +1,2 @@ +server: + udp_port: 10006 \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..08291ab --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module weatherstation + +go 1.21 + +require gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4bc0337 --- /dev/null +++ b/go.sum @@ -0,0 +1,3 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..7e9c7e2 --- /dev/null +++ b/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + "log" + "net" + + "weatherstation/config" + "weatherstation/model" +) + +func main() { + cfg := config.GetConfig() + + addr := fmt.Sprintf(":%d", cfg.Server.UDPPort) + conn, err := net.ListenPacket("udp", addr) + if err != nil { + log.Fatalf("无法监听UDP端口 %d: %v", cfg.Server.UDPPort, err) + } + defer conn.Close() + + log.Printf("UDP服务器已启动,监听端口 %d...", cfg.Server.UDPPort) + + buffer := make([]byte, 2048) + + for { + n, addr, err := conn.ReadFrom(buffer) + if err != nil { + log.Printf("读取数据错误: %v", err) + continue + } + + data := string(buffer[:n]) + log.Printf("从 %s 接收到 %d 字节数据", addr.String(), n) + + weatherData, err := model.ParseWeatherData(data) + if err != nil { + log.Printf("解析数据失败: %v", err) + log.Printf("原始数据: %s", data) + continue + } + + fmt.Println("成功解析气象站数据:") + fmt.Println(weatherData) + } +} diff --git a/model/weather_data.go b/model/weather_data.go new file mode 100644 index 0000000..7f222c2 --- /dev/null +++ b/model/weather_data.go @@ -0,0 +1,216 @@ +package model + +import ( + "fmt" + "net/url" + "strconv" + "strings" +) + +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) +日降雨量: %.3f 英寸 (%.2f mm) +周降雨量: %.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, + ) +}