feat: Add PostgreSQL to store weather data
This commit is contained in:
parent
a1b57655ee
commit
fb169d4d0c
48
README.md
48
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;
|
||||
```
|
||||
|
||||
@ -1,2 +1,10 @@
|
||||
server:
|
||||
udp_port: 10006
|
||||
|
||||
database:
|
||||
host: "8.134.185.53"
|
||||
port: 5432
|
||||
user: "weatheruser"
|
||||
password: "yourpassword"
|
||||
dbname: "weatherdb"
|
||||
sslmode: "disable"
|
||||
@ -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"`
|
||||
Database DatabaseConfig `yaml:"database"`
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
5
go.mod
5
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
|
||||
)
|
||||
|
||||
2
go.sum
2
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=
|
||||
|
||||
15
main.go
15
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("数据已成功保存到数据库")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
103
model/db.go
Normal file
103
model/db.go
Normal 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
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user