feat: Add PostgreSQL to store weather data

This commit is contained in:
yarnom 2025-07-21 17:51:04 +08:00
parent a1b57655ee
commit fb169d4d0c
7 changed files with 191 additions and 4 deletions

View File

@ -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;
```

View File

@ -1,2 +1,10 @@
server: server:
udp_port: 10006 udp_port: 10006
database:
host: "8.134.185.53"
port: 5432
user: "weatheruser"
password: "yourpassword"
dbname: "weatherdb"
sslmode: "disable"

View File

@ -12,8 +12,18 @@ type ServerConfig struct {
UDPPort int `yaml:"udp_port"` 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 { type Config struct {
Server ServerConfig `yaml:"server"` Server ServerConfig `yaml:"server"`
Database DatabaseConfig `yaml:"database"`
} }
var ( var (

5
go.mod
View File

@ -2,4 +2,7 @@ module weatherstation
go 1.21 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
)

2
go.sum
View File

@ -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/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

15
main.go
View File

@ -50,6 +50,7 @@ func setupLogger() {
bufferedWriter := bufio.NewWriter(logFile) bufferedWriter := bufio.NewWriter(logFile)
utf8Writer := NewUTF8Writer(bufferedWriter) utf8Writer := NewUTF8Writer(bufferedWriter)
go func() { go func() {
for { for {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
@ -67,6 +68,12 @@ func main() {
cfg := config.GetConfig() cfg := config.GetConfig()
err := model.InitDB()
if err != nil {
log.Fatalf("初始化数据库失败: %v", err)
}
defer model.CloseDB()
addr := fmt.Sprintf(":%d", cfg.Server.UDPPort) addr := fmt.Sprintf(":%d", cfg.Server.UDPPort)
conn, err := net.ListenPacket("udp", addr) conn, err := net.ListenPacket("udp", addr)
if err != nil { if err != nil {
@ -95,6 +102,7 @@ func main() {
hexDump := hexDump(rawData) hexDump := hexDump(rawData)
log.Printf("原始码流(十六进制):\n%s", hexDump) log.Printf("原始码流(十六进制):\n%s", hexDump)
asciiDump := asciiDump(rawData) asciiDump := asciiDump(rawData)
log.Printf("ASCII码:\n%s", asciiDump) log.Printf("ASCII码:\n%s", asciiDump)
@ -103,6 +111,13 @@ func main() {
log.Println("成功解析气象站数据:") log.Println("成功解析气象站数据:")
log.Println(weatherData) log.Println(weatherData)
err = model.SaveWeatherData(weatherData, data)
if err != nil {
log.Printf("保存数据到数据库失败: %v", err)
} else {
log.Printf("数据已成功保存到数据库")
}
} }
} }

103
model/db.go Normal file
View File

@ -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
}