feat: 新增 485 的解析

This commit is contained in:
yarnom 2025-08-03 11:12:37 +08:00
parent 8cbde597fd
commit a6fa18f5cc
4 changed files with 229 additions and 10 deletions

17
main.go
View File

@ -121,7 +121,6 @@ func startUDP() {
continue continue
} }
rawData := buffer[:n] rawData := buffer[:n]
data := string(rawData)
log.Printf("从 %s 接收到 %d 字节数据", addr.String(), n) log.Printf("从 %s 接收到 %d 字节数据", addr.String(), n)
hexDump := hexDump(rawData) hexDump := hexDump(rawData)
@ -129,13 +128,14 @@ func startUDP() {
asciiDump := asciiDump(rawData) asciiDump := asciiDump(rawData)
log.Printf("ASCII码:\n%s", asciiDump) log.Printf("ASCII码:\n%s", asciiDump)
weatherData, err := model.ParseWeatherData(data) weatherData, err := model.ParseData(rawData)
if err != nil { if err != nil {
log.Printf("解析数据失败: %v", err) log.Printf("解析数据失败: %v", err)
continue continue
} }
log.Println("成功解析气象站数据:") log.Println("成功解析气象站数据:")
log.Printf("设备类型: %s", getDeviceTypeString(weatherData.DeviceType))
log.Println(weatherData) log.Println(weatherData)
if weatherData.StationID != "" { if weatherData.StationID != "" {
@ -145,7 +145,7 @@ func startUDP() {
log.Printf("警告: 收到的数据没有站点ID") log.Printf("警告: 收到的数据没有站点ID")
} }
err = model.SaveWeatherData(weatherData, data) err = model.SaveWeatherData(weatherData, string(rawData))
if err != nil { if err != nil {
log.Printf("保存数据到数据库失败: %v", err) log.Printf("保存数据到数据库失败: %v", err)
} else { } else {
@ -154,6 +154,17 @@ func startUDP() {
} }
} }
func getDeviceTypeString(deviceType model.DeviceType) string {
switch deviceType {
case model.DeviceTypeWIFI:
return "WIFI"
case model.DeviceTypeRS485:
return "RS485"
default:
return "未知"
}
}
func main() { func main() {
setupLogger() setupLogger()
startUDP() startUDP()

View File

@ -30,9 +30,35 @@ func InitDB() error {
return fmt.Errorf("数据库连接测试失败: %v", err) return fmt.Errorf("数据库连接测试失败: %v", err)
} }
// 创建RS485数据表
err = createRS485Table()
if err != nil {
return fmt.Errorf("创建RS485数据表失败: %v", err)
}
return nil return nil
} }
func createRS485Table() error {
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS rs485_weather_data (
id SERIAL PRIMARY KEY,
station_id VARCHAR(50) NOT NULL,
timestamp TIMESTAMP NOT NULL,
temperature DECIMAL(5,2), -- 温度摄氏度
humidity DECIMAL(5,2), -- 湿度%
wind_speed DECIMAL(5,2), -- 风速m/s
wind_direction DECIMAL(5,2), -- 风向
rainfall DECIMAL(5,2), -- 降雨量mm
light DECIMAL(10,2), -- 光照lux
uv DECIMAL(5,2), -- 紫外线
pressure DECIMAL(7,2), -- 气压hPa
raw_data TEXT, -- 原始数据
FOREIGN KEY (station_id) REFERENCES stations(station_id)
)`)
return err
}
func CloseDB() { func CloseDB() {
if db != nil { if db != nil {
db.Close() db.Close()
@ -79,7 +105,19 @@ func SaveWeatherData(data *WeatherData, rawData string) error {
cst := time.FixedZone("CST", 8*60*60) cst := time.FixedZone("CST", 8*60*60)
timestamp := time.Now().In(cst) timestamp := time.Now().In(cst)
_, err = db.Exec(` // 根据设备类型选择不同的保存方法
switch data.DeviceType {
case DeviceTypeWIFI:
return saveWIFIWeatherData(data, rawData, timestamp)
case DeviceTypeRS485:
return saveRS485WeatherData(data, rawData, timestamp)
default:
return fmt.Errorf("未知的设备类型")
}
}
func saveWIFIWeatherData(data *WeatherData, rawData string, timestamp time.Time) error {
_, err := db.Exec(`
INSERT INTO weather_data ( INSERT INTO weather_data (
station_id, timestamp, temp_f, humidity, dewpoint_f, windchill_f, station_id, timestamp, temp_f, humidity, dewpoint_f, windchill_f,
wind_dir, wind_speed_mph, wind_gust_mph, rain_in, daily_rain_in, wind_dir, wind_speed_mph, wind_gust_mph, rain_in, daily_rain_in,
@ -96,8 +134,33 @@ func SaveWeatherData(data *WeatherData, rawData string) error {
int(data.AbsBarometerIn*1000), int(data.BarometerIn*1000), data.LowBattery, rawData) int(data.AbsBarometerIn*1000), int(data.BarometerIn*1000), data.LowBattery, rawData)
if err != nil { if err != nil {
return fmt.Errorf("保存气象数据失败: %v", err) return fmt.Errorf("保存WIFI气象数据失败: %v", err)
}
return nil
}
func saveRS485WeatherData(data *WeatherData, rawData string, timestamp time.Time) error {
// 将华氏度转换回摄氏度
tempC := (data.TempF - 32) * 5 / 9
// 将mph转换回m/s
windSpeedMS := data.WindSpeedMph / 2.23694
// 将inch转换回mm
rainfallMM := data.RainIn * 25.4
// 将inHg转换回hPa
pressureHPa := data.BarometerIn * 33.8639
_, err := db.Exec(`
INSERT INTO rs485_weather_data (
station_id, timestamp, temperature, humidity, wind_speed,
wind_direction, rainfall, light, uv, pressure, raw_data
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
data.StationID, timestamp,
tempC, data.Humidity, windSpeedMS,
data.WindDir, rainfallMM, data.SolarRadiation,
data.UV, pressureHPa, rawData)
if err != nil {
return fmt.Errorf("保存RS485气象数据失败: %v", err)
} }
return nil return nil
} }

View File

@ -732,3 +732,83 @@ func (p *Protocol) GetPressure() (*Pressure, error) {
return result, nil return result, nil
} }
// RS485Protocol 定义RS485协议结构
type RS485Protocol struct {
RawData []byte
}
// NewRS485Protocol 创建新的RS485协议实例
func NewRS485Protocol(data []byte) *RS485Protocol {
return &RS485Protocol{
RawData: data,
}
}
// ValidateRS485Data 验证RS485数据是否有效
func ValidateRS485Data(data []byte) bool {
// 检查数据长度是否为25字节
if len(data) != 25 {
return false
}
// 检查起始字节是否为0x24
if data[0] != 0x24 {
return false
}
return true
}
// RS485WeatherData 存储RS485气象数据
type RS485WeatherData struct {
Temperature float64 // 温度
Humidity float64 // 湿度
WindSpeed float64 // 风速
WindDirection float64 // 风向
Rainfall float64 // 雨量
Light float64 // 光照
UV float64 // 紫外线
Pressure float64 // 气压
}
// ParseRS485Data 解析RS485数据
func (p *RS485Protocol) ParseRS485Data() (*RS485WeatherData, error) {
if !ValidateRS485Data(p.RawData) {
return nil, fmt.Errorf("无效的RS485数据格式")
}
data := &RS485WeatherData{}
// 解析温度 (索引5-6)
tempRaw := int16(p.RawData[5])<<8 | int16(p.RawData[6])
data.Temperature = float64(tempRaw) / 10.0
// 解析湿度 (索引7-8)
humRaw := int16(p.RawData[7])<<8 | int16(p.RawData[8])
data.Humidity = float64(humRaw) / 10.0
// 解析风速 (索引9-10)
windSpeedRaw := int16(p.RawData[9])<<8 | int16(p.RawData[10])
data.WindSpeed = float64(windSpeedRaw) / 10.0
// 解析风向 (索引11-12)
windDirRaw := int16(p.RawData[11])<<8 | int16(p.RawData[12])
data.WindDirection = float64(windDirRaw)
// 解析雨量 (索引13-14)
rainRaw := int16(p.RawData[13])<<8 | int16(p.RawData[14])
data.Rainfall = float64(rainRaw) / 10.0
// 解析光照 (索引15-16)
lightRaw := int16(p.RawData[15])<<8 | int16(p.RawData[16])
data.Light = float64(lightRaw)
// 解析紫外线 (索引17-18)
uvRaw := int16(p.RawData[17])<<8 | int16(p.RawData[18])
data.UV = float64(uvRaw) / 10.0
// 解析气压 (索引19-20)
pressureRaw := int16(p.RawData[19])<<8 | int16(p.RawData[20])
data.Pressure = float64(pressureRaw) / 10.0
return data, nil
}

View File

@ -5,9 +5,19 @@ import (
"net/url" "net/url"
"regexp" "regexp"
"strconv" "strconv"
"time"
)
// DeviceType 定义设备类型
type DeviceType int
const (
DeviceTypeWIFI DeviceType = iota
DeviceTypeRS485
) )
type WeatherData struct { type WeatherData struct {
DeviceType DeviceType
StationID string StationID string
Password string Password string
TempF float64 TempF float64
@ -39,24 +49,37 @@ type WeatherData struct {
var urlRegex = regexp.MustCompile(`/weatherstation/updateweatherstation\.php\?([^&\s]+(&[^&\s]+)*)`) var urlRegex = regexp.MustCompile(`/weatherstation/updateweatherstation\.php\?([^&\s]+(&[^&\s]+)*)`)
func ParseWeatherData(data string) (*WeatherData, error) { // ParseData 根据数据类型解析气象数据
func ParseData(data []byte) (*WeatherData, error) {
// 检查是否为RS485数据
if len(data) == 25 && data[0] == 0x24 {
return ParseRS485WeatherData(data)
}
// 尝试解析为WIFI数据
return ParseWIFIWeatherData(string(data))
}
// ParseWIFIWeatherData 解析WIFI设备数据
func ParseWIFIWeatherData(data string) (*WeatherData, error) {
matches := urlRegex.FindStringSubmatch(data) matches := urlRegex.FindStringSubmatch(data)
if len(matches) < 2 { if len(matches) < 2 {
return nil, fmt.Errorf("无法找到有效的气象站数据URL") return nil, fmt.Errorf("无法找到有效的气象站数据URL")
} }
queryString := matches[1] queryString := matches[1]
values, err := url.ParseQuery(queryString) values, err := url.ParseQuery(queryString)
if err != nil { if err != nil {
return nil, fmt.Errorf("解析查询参数失败: %v", err) return nil, fmt.Errorf("解析查询参数失败: %v", err)
} }
wd := &WeatherData{} wd := &WeatherData{
DeviceType: DeviceTypeWIFI,
DateUTC: time.Now().UTC().Format("2006-01-02 15:04:05"),
}
wd.StationID = values.Get("ID") wd.StationID = values.Get("ID")
wd.Password = values.Get("PASSWORD") wd.Password = values.Get("PASSWORD")
wd.DateUTC = values.Get("dateutc")
wd.SoftwareType = values.Get("softwaretype") wd.SoftwareType = values.Get("softwaretype")
wd.Action = values.Get("action") wd.Action = values.Get("action")
@ -151,6 +174,48 @@ func ParseWeatherData(data string) (*WeatherData, error) {
return wd, nil return wd, nil
} }
// ParseRS485WeatherData 解析RS485设备数据
func ParseRS485WeatherData(data []byte) (*WeatherData, error) {
protocol := NewRS485Protocol(data)
rs485Data, err := protocol.ParseRS485Data()
if err != nil {
return nil, err
}
wd := &WeatherData{
DeviceType: DeviceTypeRS485,
DateUTC: time.Now().UTC().Format("2006-01-02 15:04:05"),
StationID: fmt.Sprintf("RS485-%02X%02X", data[1], data[2]), // 使用前两个字节作为设备ID
}
// 转换温度(摄氏度到华氏度)
wd.TempF = rs485Data.Temperature*9/5 + 32
// 转换湿度(直接使用)
wd.Humidity = int(rs485Data.Humidity)
// 转换风向(直接使用)
wd.WindDir = int(rs485Data.WindDirection)
// 转换风速m/s到mph
wd.WindSpeedMph = rs485Data.WindSpeed * 2.23694
// 转换降雨量mm到inch
wd.RainIn = rs485Data.Rainfall / 25.4
// 转换光照(直接使用)
wd.SolarRadiation = rs485Data.Light
// 转换UV直接使用
wd.UV = int(rs485Data.UV)
// 转换气压hPa到inHg
wd.BarometerIn = rs485Data.Pressure / 33.8639
wd.AbsBarometerIn = wd.BarometerIn
return wd, nil
}
func (w *WeatherData) String() string { func (w *WeatherData) String() string {
return fmt.Sprintf(` return fmt.Sprintf(`
站点ID: %s 站点ID: %s