test: Listen on UDP port 10006 and parse weather station data
This commit is contained in:
parent
8312ff3008
commit
1fa4319879
47
config/config.go
Normal file
47
config/config.go
Normal file
@ -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
|
||||||
|
}
|
||||||
2
config/config.yaml
Normal file
2
config/config.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
server:
|
||||||
|
udp_port: 10006
|
||||||
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module weatherstation
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
3
go.sum
Normal file
3
go.sum
Normal file
@ -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=
|
||||||
46
main.go
Normal file
46
main.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
216
model/weather_data.go
Normal file
216
model/weather_data.go
Normal file
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user