From a6fa18f5cc1a809344bd24233a456bfa84228509 Mon Sep 17 00:00:00 2001 From: yarnom Date: Sun, 3 Aug 2025 11:12:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20485=20=E7=9A=84?= =?UTF-8?q?=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 17 +++++++-- model/db.go | 69 +++++++++++++++++++++++++++++++++++-- model/protocol.go | 80 +++++++++++++++++++++++++++++++++++++++++++ model/weather_data.go | 73 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 229 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index 58dc2b2..984fb29 100644 --- a/main.go +++ b/main.go @@ -121,7 +121,6 @@ func startUDP() { continue } rawData := buffer[:n] - data := string(rawData) log.Printf("从 %s 接收到 %d 字节数据", addr.String(), n) hexDump := hexDump(rawData) @@ -129,13 +128,14 @@ func startUDP() { asciiDump := asciiDump(rawData) log.Printf("ASCII码:\n%s", asciiDump) - weatherData, err := model.ParseWeatherData(data) + weatherData, err := model.ParseData(rawData) if err != nil { log.Printf("解析数据失败: %v", err) continue } log.Println("成功解析气象站数据:") + log.Printf("设备类型: %s", getDeviceTypeString(weatherData.DeviceType)) log.Println(weatherData) if weatherData.StationID != "" { @@ -145,7 +145,7 @@ func startUDP() { log.Printf("警告: 收到的数据没有站点ID") } - err = model.SaveWeatherData(weatherData, data) + err = model.SaveWeatherData(weatherData, string(rawData)) if err != nil { log.Printf("保存数据到数据库失败: %v", err) } 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() { setupLogger() startUDP() diff --git a/model/db.go b/model/db.go index 2ff127d..d970d82 100644 --- a/model/db.go +++ b/model/db.go @@ -30,9 +30,35 @@ func InitDB() error { return fmt.Errorf("数据库连接测试失败: %v", err) } + // 创建RS485数据表 + err = createRS485Table() + if err != nil { + return fmt.Errorf("创建RS485数据表失败: %v", err) + } + 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() { if db != nil { db.Close() @@ -79,7 +105,19 @@ func SaveWeatherData(data *WeatherData, rawData string) error { cst := time.FixedZone("CST", 8*60*60) 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 ( station_id, timestamp, temp_f, humidity, dewpoint_f, windchill_f, 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) 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 } diff --git a/model/protocol.go b/model/protocol.go index fe14d32..23e3804 100644 --- a/model/protocol.go +++ b/model/protocol.go @@ -732,3 +732,83 @@ func (p *Protocol) GetPressure() (*Pressure, error) { 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 +} diff --git a/model/weather_data.go b/model/weather_data.go index 0d8c580..30ef3c7 100644 --- a/model/weather_data.go +++ b/model/weather_data.go @@ -5,9 +5,19 @@ import ( "net/url" "regexp" "strconv" + "time" +) + +// DeviceType 定义设备类型 +type DeviceType int + +const ( + DeviceTypeWIFI DeviceType = iota + DeviceTypeRS485 ) type WeatherData struct { + DeviceType DeviceType StationID string Password string TempF float64 @@ -39,24 +49,37 @@ type WeatherData struct { 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) if len(matches) < 2 { return nil, fmt.Errorf("无法找到有效的气象站数据URL") } queryString := matches[1] - values, err := url.ParseQuery(queryString) if err != nil { 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.Password = values.Get("PASSWORD") - wd.DateUTC = values.Get("dateutc") wd.SoftwareType = values.Get("softwaretype") wd.Action = values.Get("action") @@ -151,6 +174,48 @@ func ParseWeatherData(data string) (*WeatherData, error) { 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 { return fmt.Sprintf(` 站点ID: %s