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:
|
server:
|
||||||
udp_port: 10006
|
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"`
|
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
5
go.mod
@ -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
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/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
15
main.go
@ -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
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