diff --git a/README.md b/README.md index 7535ab9..07ec11e 100644 --- a/README.md +++ b/README.md @@ -1 +1,47 @@ -# 英卓气象站 +# 气象站数据接收系统 + +UDP接收气象站数据,存PostgreSQL。 + +## 数据库字段转换 + +### 温度 +- `temp_f`, `dewpoint_f`, `windchill_f`, `indoor_temp_f`: 存储值=实际值×10 (°F) + +### 湿度 +- `humidity`, `indoor_humidity`: 直接存整数百分比 (%) + +### 风速 +- `wind_dir`: 直接存角度 (°) +- `wind_speed_mph`, `wind_gust_mph`: 存储值=实际值×100 (mph) + +### 降雨量 +- 所有rain字段: 存储值=实际值×1000 (英寸) + +### 气压 +- `abs_barometer_in`, `barometer_in`: 存储值=实际值×1000 (英寸汞柱) + +### 其他 +- `solar_radiation`: 存储值=实际值×100 (W/m²) +- `uv`: 直接存整数 +- `low_battery`: 布尔值 + +## 单位转换 +- 华氏→摄氏: (°F - 32) * 5/9 +- 英里→公里: mph * 1.60934 +- 英寸→毫米: in * 25.4 +- 英寸汞柱→百帕: inHg * 33.8639 + +## 查询示例 +```sql +SELECT + station_id, + timestamp AT TIME ZONE 'Asia/Shanghai' as local_time, + temp_f::float/10 as temp_f, + (temp_f::float/10 - 32) * 5/9 as temp_c, + humidity, + wind_speed_mph::float/100 as wind_speed_mph, + barometer_in::float/1000 * 33.8639 as barometer_hpa +FROM weather_data +ORDER BY timestamp DESC +LIMIT 10; +``` diff --git a/config.yaml b/config.yaml index 04d49fd..c56989c 100644 --- a/config.yaml +++ b/config.yaml @@ -1,2 +1,10 @@ server: - udp_port: 10006 \ No newline at end of file + udp_port: 10006 + +database: + host: "8.134.185.53" + port: 5432 + user: "weatheruser" + password: "yourpassword" + dbname: "weatherdb" + sslmode: "disable" \ No newline at end of file diff --git a/config/config.go b/config/config.go index da78786..c4b313a 100644 --- a/config/config.go +++ b/config/config.go @@ -12,8 +12,18 @@ type ServerConfig struct { UDPPort int `yaml:"udp_port"` } +type DatabaseConfig struct { + Host string `yaml:"host"` + Port int `yaml:"port"` + User string `yaml:"user"` + Password string `yaml:"password"` + DBName string `yaml:"dbname"` + SSLMode string `yaml:"sslmode"` +} + type Config struct { - Server ServerConfig `yaml:"server"` + Server ServerConfig `yaml:"server"` + Database DatabaseConfig `yaml:"database"` } var ( diff --git a/go.mod b/go.mod index 08291ab..fa33cee 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module weatherstation go 1.21 -require gopkg.in/yaml.v3 v3.0.1 // indirect +require ( + github.com/lib/pq v1.10.9 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index 4bc0337..da9ca8a 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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 index 9607c31..d5067a9 100644 --- a/main.go +++ b/main.go @@ -50,6 +50,7 @@ func setupLogger() { bufferedWriter := bufio.NewWriter(logFile) utf8Writer := NewUTF8Writer(bufferedWriter) + go func() { for { time.Sleep(1 * time.Second) @@ -67,6 +68,12 @@ func main() { cfg := config.GetConfig() + err := model.InitDB() + if err != nil { + log.Fatalf("初始化数据库失败: %v", err) + } + defer model.CloseDB() + addr := fmt.Sprintf(":%d", cfg.Server.UDPPort) conn, err := net.ListenPacket("udp", addr) if err != nil { @@ -95,6 +102,7 @@ func main() { hexDump := hexDump(rawData) log.Printf("原始码流(十六进制):\n%s", hexDump) + asciiDump := asciiDump(rawData) log.Printf("ASCII码:\n%s", asciiDump) @@ -103,6 +111,13 @@ func main() { log.Println("成功解析气象站数据:") log.Println(weatherData) + + err = model.SaveWeatherData(weatherData, data) + if err != nil { + log.Printf("保存数据到数据库失败: %v", err) + } else { + log.Printf("数据已成功保存到数据库") + } } } diff --git a/model/db.go b/model/db.go new file mode 100644 index 0000000..2ff127d --- /dev/null +++ b/model/db.go @@ -0,0 +1,103 @@ +package model + +import ( + "database/sql" + "fmt" + "time" + + "weatherstation/config" + + _ "github.com/lib/pq" +) + +var db *sql.DB + +func InitDB() error { + cfg := config.GetConfig() + + connStr := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", + cfg.Database.Host, cfg.Database.Port, cfg.Database.User, + cfg.Database.Password, cfg.Database.DBName, cfg.Database.SSLMode) + + var err error + db, err = sql.Open("postgres", connStr) + if err != nil { + return fmt.Errorf("无法连接到数据库: %v", err) + } + + err = db.Ping() + if err != nil { + return fmt.Errorf("数据库连接测试失败: %v", err) + } + + return nil +} + +func CloseDB() { + if db != nil { + db.Close() + } +} + +func ensureStationExists(stationID, password string) error { + if db == nil { + return fmt.Errorf("数据库未初始化") + } + + var count int + err := db.QueryRow("SELECT COUNT(*) FROM stations WHERE station_id = $1", stationID).Scan(&count) + if err != nil { + return fmt.Errorf("查询站点失败: %v", err) + } + + if count == 0 { + _, err = db.Exec("INSERT INTO stations (station_id, password) VALUES ($1, $2)", stationID, password) + if err != nil { + return fmt.Errorf("添加站点失败: %v", err) + } + } else { + _, err = db.Exec("UPDATE stations SET password = $1, last_update = $2 WHERE station_id = $3", + password, time.Now(), stationID) + if err != nil { + return fmt.Errorf("更新站点失败: %v", err) + } + } + + return nil +} + +func SaveWeatherData(data *WeatherData, rawData string) error { + if db == nil { + return fmt.Errorf("数据库未初始化") + } + + err := ensureStationExists(data.StationID, data.Password) + if err != nil { + return err + } + + cst := time.FixedZone("CST", 8*60*60) + timestamp := time.Now().In(cst) + + _, err = db.Exec(` + INSERT INTO weather_data ( + station_id, timestamp, temp_f, humidity, dewpoint_f, windchill_f, + wind_dir, wind_speed_mph, wind_gust_mph, rain_in, daily_rain_in, + weekly_rain_in, monthly_rain_in, yearly_rain_in, total_rain_in, + solar_radiation, uv, indoor_temp_f, indoor_humidity, + abs_barometer_in, barometer_in, low_battery, raw_data + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23)`, + data.StationID, timestamp, + int(data.TempF*10), data.Humidity, int(data.DewpointF*10), int(data.WindchillF*10), + data.WindDir, int(data.WindSpeedMph*100), int(data.WindGustMph*100), + int(data.RainIn*1000), int(data.DailyRainIn*1000), int(data.WeeklyRainIn*1000), + int(data.MonthlyRainIn*1000), int(data.YearlyRainIn*1000), int(data.TotalRainIn*1000), + int(data.SolarRadiation*100), data.UV, int(data.IndoorTempF*10), data.IndoorHumidity, + int(data.AbsBarometerIn*1000), int(data.BarometerIn*1000), data.LowBattery, rawData) + + if err != nil { + return fmt.Errorf("保存气象数据失败: %v", err) + } + + return nil +}