Compare commits

...

13 Commits

9 changed files with 573 additions and 113 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -7,12 +7,4 @@ database:
user: "weatheruser" user: "weatheruser"
password: "yourpassword" password: "yourpassword"
dbname: "weatherdb" dbname: "weatherdb"
sslmode: "disable" sslmode: "disable"
heartbeat:
interval: 5
message: "Hello"
device_check:
interval: 5
message: "Hello"

View File

@ -21,21 +21,9 @@ type DatabaseConfig struct {
SSLMode string `yaml:"sslmode"` SSLMode string `yaml:"sslmode"`
} }
type HeartbeatConfig struct {
Interval int `yaml:"interval"`
Message string `yaml:"message"`
}
type DeviceCheckConfig struct {
Interval int `yaml:"interval"`
Message string `yaml:"message"`
}
type Config struct { type Config struct {
Server ServerConfig `yaml:"server"` Server ServerConfig `yaml:"server"`
Database DatabaseConfig `yaml:"database"` Database DatabaseConfig `yaml:"database"`
Heartbeat HeartbeatConfig `yaml:"heartbeat"`
DeviceCheck DeviceCheckConfig `yaml:"device_check"`
} }
var ( var (

100
main.go
View File

@ -16,8 +16,6 @@ import (
"weatherstation/config" "weatherstation/config"
"weatherstation/model" "weatherstation/model"
"github.com/gin-gonic/gin"
) )
type UTF8Writer struct { type UTF8Writer struct {
@ -123,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)
@ -131,17 +128,54 @@ func startUDP() {
asciiDump := asciiDump(rawData) asciiDump := asciiDump(rawData)
log.Printf("ASCII码:\n%s", asciiDump) log.Printf("ASCII码:\n%s", asciiDump)
// 首先尝试解析为WH65LP数据
if len(rawData) == 25 && rawData[0] == 0x24 {
wh65lpData, err := model.ParseWH65LPData(rawData)
if err != nil {
log.Printf("解析WH65LP数据失败: %v", err)
} else {
log.Println("成功解析WH65LP气象站数据:")
log.Println(wh65lpData)
// 更新内存中的设备信息
model.UpdateDeviceInMemory(wh65lpData.StationID, addr, model.DeviceTypeWH65LP)
// 注册设备到数据库
err = model.RegisterDeviceInDB(wh65lpData.StationID, addr)
if err != nil {
log.Printf("注册设备失败: %v", err)
}
log.Printf("设备 %s 已注册IP: %s", wh65lpData.StationID, addr.String())
// 保存数据
err = model.SaveWH65LPData(wh65lpData, rawData)
if err != nil {
log.Printf("保存数据到数据库失败: %v", err)
} else {
log.Printf("数据已成功保存到数据库")
}
continue
}
}
// 如果不是WH65LP数据尝试解析为ECOWITT数据
data := string(rawData)
weatherData, err := model.ParseWeatherData(data) weatherData, err := model.ParseWeatherData(data)
if err != nil { if err != nil {
log.Printf("解析数据失败: %v", err) log.Printf("解析ECOWITT数据失败: %v", err)
continue continue
} }
log.Println("成功解析气象站数据:") log.Println("成功解析ECOWITT气象站数据:")
log.Println(weatherData) log.Println(weatherData)
if weatherData.StationID != "" { if weatherData.StationID != "" {
model.RegisterDevice(weatherData.StationID, addr) // 更新内存中的设备信息
model.UpdateDeviceInMemory(weatherData.StationID, addr, model.DeviceTypeEcowitt)
// 注册设备到数据库
err = model.RegisterDeviceInDB(weatherData.StationID, addr)
if err != nil {
log.Printf("注册设备失败: %v", err)
}
log.Printf("设备 %s 已注册IP: %s", weatherData.StationID, addr.String()) log.Printf("设备 %s 已注册IP: %s", weatherData.StationID, addr.String())
} else { } else {
log.Printf("警告: 收到的数据没有站点ID") log.Printf("警告: 收到的数据没有站点ID")
@ -156,61 +190,9 @@ func startUDP() {
} }
} }
func startDeviceCheck() {
cfg := config.GetConfig()
ticker := time.NewTicker(time.Duration(cfg.DeviceCheck.Interval) * time.Minute)
defer ticker.Stop()
for range ticker.C {
devices := model.GetOnlineDevices()
log.Printf("当前在线设备数: %d", len(devices))
for _, device := range devices {
sendUDPMessage(device.IP, cfg.DeviceCheck.Message)
}
}
}
func sendUDPMessage(ip string, message string) {
cfg := config.GetConfig()
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ip, cfg.Server.UDPPort))
if err != nil {
log.Printf("解析UDP地址失败: %v", err)
return
}
log.Printf("尝试向 %s 发送消息...", addr.String())
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
log.Printf("连接UDP失败: %v", err)
return
}
defer conn.Close()
conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
n, err := conn.Write([]byte(message))
if err != nil {
log.Printf("发送UDP消息失败: %v", err)
return
}
log.Printf("成功向 %s 发送 %d 字节消息: %s", ip, n, message)
}
func main() { func main() {
setupLogger() setupLogger()
go startUDP() startUDP() // 直接运行UDP服务器不再使用goroutine
go startDeviceCheck()
r := gin.Default()
r.LoadHTMLGlob("templates/*")
r.Static("/static", "static")
r.GET("/", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{})
})
r.Run(":10007")
} }
func hexDump(data []byte) string { func hexDump(data []byte) string {

View File

@ -3,6 +3,7 @@ package model
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"net"
"time" "time"
"weatherstation/config" "weatherstation/config"
@ -12,6 +13,14 @@ import (
var db *sql.DB var db *sql.DB
// DBDevice 数据库中的设备信息
type DBDevice struct {
StationID string
IP string
LastUpdate time.Time
DeviceType DeviceType
}
func InitDB() error { func InitDB() error {
cfg := config.GetConfig() cfg := config.GetConfig()
@ -39,7 +48,7 @@ func CloseDB() {
} }
} }
func ensureStationExists(stationID, password string) error { func ensureStationExists(stationID, password string, deviceType DeviceType) error {
if db == nil { if db == nil {
return fmt.Errorf("数据库未初始化") return fmt.Errorf("数据库未初始化")
} }
@ -51,13 +60,14 @@ func ensureStationExists(stationID, password string) error {
} }
if count == 0 { if count == 0 {
_, err = db.Exec("INSERT INTO stations (station_id, password) VALUES ($1, $2)", stationID, password) _, err = db.Exec("INSERT INTO stations (station_id, password, device_type) VALUES ($1, $2, $3)",
stationID, password, deviceType)
if err != nil { if err != nil {
return fmt.Errorf("添加站点失败: %v", err) return fmt.Errorf("添加站点失败: %v", err)
} }
} else { } else {
_, err = db.Exec("UPDATE stations SET password = $1, last_update = $2 WHERE station_id = $3", _, err = db.Exec("UPDATE stations SET password = $1, last_update = $2, device_type = $3 WHERE station_id = $4",
password, time.Now(), stationID) password, time.Now(), deviceType, stationID)
if err != nil { if err != nil {
return fmt.Errorf("更新站点失败: %v", err) return fmt.Errorf("更新站点失败: %v", err)
} }
@ -66,12 +76,74 @@ func ensureStationExists(stationID, password string) error {
return nil return nil
} }
// RegisterDeviceInDB 在数据库中注册设备
func RegisterDeviceInDB(stationID string, addr net.Addr) error {
if db == nil {
return fmt.Errorf("数据库未初始化")
}
ipStr := addr.String()
if ipStr == "" {
return fmt.Errorf("无效的IP地址")
}
ip, _, err := net.SplitHostPort(ipStr)
if err != nil {
return fmt.Errorf("解析IP地址失败: %v", err)
}
_, err = db.Exec(`
INSERT INTO device_ips (station_id, ip_address, last_update)
VALUES ($1, $2, $3)
ON CONFLICT (station_id)
DO UPDATE SET ip_address = $2, last_update = $3
`, stationID, ip, time.Now())
if err != nil {
return fmt.Errorf("注册设备IP失败: %v", err)
}
return nil
}
// GetOnlineDevicesFromDB 从数据库获取在线设备
func GetOnlineDevicesFromDB() []DBDevice {
if db == nil {
return nil
}
rows, err := db.Query(`
SELECT d.station_id, d.ip_address, d.last_update, s.device_type
FROM device_ips d
JOIN stations s ON d.station_id = s.station_id
WHERE d.last_update > $1
ORDER BY d.last_update DESC
`, time.Now().Add(-24*time.Hour))
if err != nil {
return nil
}
defer rows.Close()
var devices []DBDevice
for rows.Next() {
var d DBDevice
err := rows.Scan(&d.StationID, &d.IP, &d.LastUpdate, &d.DeviceType)
if err != nil {
continue
}
devices = append(devices, d)
}
return devices
}
func SaveWeatherData(data *WeatherData, rawData string) error { func SaveWeatherData(data *WeatherData, rawData string) error {
if db == nil { if db == nil {
return fmt.Errorf("数据库未初始化") return fmt.Errorf("数据库未初始化")
} }
err := ensureStationExists(data.StationID, data.Password) err := ensureStationExists(data.StationID, data.Password, DeviceTypeEcowitt)
if err != nil { if err != nil {
return err return err
} }
@ -101,3 +173,42 @@ func SaveWeatherData(data *WeatherData, rawData string) error {
return nil return nil
} }
func SaveWH65LPData(data *WH65LPData, rawData []byte) error {
if db == nil {
return fmt.Errorf("数据库未初始化")
}
// 确保设备存在WH65LP设备没有密码
err := ensureStationExists(data.StationID, "", DeviceTypeWH65LP)
if err != nil {
return err
}
// 插入数据
_, err = db.Exec(`
INSERT INTO wh65lp_data (
station_id, timestamp, temperature, humidity, wind_direction,
wind_speed, wind_gust, rain, uv_index, light, pressure,
low_battery, wsp_flag, raw_data
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)`,
data.StationID, data.Timestamp,
int(data.Temperature*10), // 温度保存为整数精确到0.1
data.Humidity,
data.WindDirection,
int(data.WindSpeed*100), // 风速保存为整数精确到0.01
int(data.WindGust*100), // 阵风保存为整数精确到0.01
int(data.Rain*1000), // 降雨量保存为整数精确到0.001
data.UV,
int(data.Light*10), // 光照保存为整数精确到0.1
int(data.Pressure*100), // 气压保存为整数精确到0.01
data.LowBattery,
data.WSPFlag,
rawData)
if err != nil {
return fmt.Errorf("保存WH65LP数据失败: %v", err)
}
return nil
}

View File

@ -6,35 +6,49 @@ import (
"time" "time"
) )
type Device struct { // DeviceType 设备类型枚举
IP string type DeviceType string
LastSeen time.Time
StationID string const (
DeviceTypeEcowitt DeviceType = "ECOWITT"
DeviceTypeWH65LP DeviceType = "WH65LP"
DeviceTypeUnknown DeviceType = "UNKNOWN"
)
// MemoryDevice 内存中的设备信息
type MemoryDevice struct {
IP string
LastSeen time.Time
StationID string
DeviceType DeviceType
} }
var ( var (
devices = make(map[string]*Device) devices = make(map[string]*MemoryDevice)
deviceMutex sync.RWMutex deviceMutex sync.RWMutex
) )
func RegisterDevice(stationID string, addr net.Addr) { // UpdateDeviceInMemory 更新内存中的设备信息
func UpdateDeviceInMemory(stationID string, addr net.Addr, deviceType DeviceType) {
ip, _, _ := net.SplitHostPort(addr.String()) ip, _, _ := net.SplitHostPort(addr.String())
deviceMutex.Lock() deviceMutex.Lock()
defer deviceMutex.Unlock() defer deviceMutex.Unlock()
devices[stationID] = &Device{ devices[stationID] = &MemoryDevice{
IP: ip, IP: ip,
LastSeen: time.Now(), LastSeen: time.Now(),
StationID: stationID, StationID: stationID,
DeviceType: deviceType,
} }
} }
func GetOnlineDevices() []*Device { // GetOnlineDevicesFromMemory 获取内存中的在线设备
func GetOnlineDevicesFromMemory() []*MemoryDevice {
deviceMutex.RLock() deviceMutex.RLock()
defer deviceMutex.RUnlock() defer deviceMutex.RUnlock()
result := make([]*Device, 0, len(devices)) result := make([]*MemoryDevice, 0, len(devices))
for _, device := range devices { for _, device := range devices {
if time.Since(device.LastSeen) < 10*time.Minute { if time.Since(device.LastSeen) < 10*time.Minute {
result = append(result, device) result = append(result, device)
@ -42,3 +56,14 @@ func GetOnlineDevices() []*Device {
} }
return result return result
} }
// GetDeviceTypeFromMemory 从内存中获取设备类型
func GetDeviceTypeFromMemory(stationID string) DeviceType {
deviceMutex.RLock()
defer deviceMutex.RUnlock()
if device, ok := devices[stationID]; ok {
return device.DeviceType
}
return DeviceTypeUnknown
}

View File

@ -3,8 +3,9 @@ package model
import ( import (
"fmt" "fmt"
"net/url" "net/url"
"regexp"
"strconv" "strconv"
"strings"
"time"
) )
type WeatherData struct { type WeatherData struct {
@ -37,16 +38,28 @@ type WeatherData struct {
RTFreq int RTFreq int
} }
var urlRegex = regexp.MustCompile(`/weatherstation/updateweatherstation\.php\?([^&\s]+(&[^&\s]+)*)`)
func ParseWeatherData(data string) (*WeatherData, error) { func ParseWeatherData(data string) (*WeatherData, error) {
matches := urlRegex.FindStringSubmatch(data) if !strings.Contains(data, "GET /weatherstation/updateweatherstation.php") {
if len(matches) < 2 { return nil, fmt.Errorf("不是气象站数据")
return nil, fmt.Errorf("无法找到有效的气象站数据URL")
} }
queryString := matches[1] urlStart := strings.Index(data, "GET ")
if urlStart == -1 {
return nil, fmt.Errorf("无法找到URL开始位置")
}
httpVersionEnd := strings.Index(data, " HTTP")
if httpVersionEnd == -1 {
return nil, fmt.Errorf("无法找到URL结束位置")
}
urlString := data[urlStart+4 : httpVersionEnd]
queryStart := strings.Index(urlString, "?")
if queryStart == -1 {
return nil, fmt.Errorf("无法找到查询参数")
}
queryString := urlString[queryStart+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)
@ -161,12 +174,12 @@ func (w *WeatherData) String() string {
风向: %d° 风向: %d°
风速: %.2f mph (%.2f km/h) 风速: %.2f mph (%.2f km/h)
阵风: %.2f mph (%.2f km/h) 阵风: %.2f mph (%.2f km/h)
降雨量: %.3f 英寸 (%.2f mm) 降雨量: %.3f 英寸/小时 (%.2f mm/h) - 当前降雨率
日降雨量: %.3f 英寸 (%.2f mm) 日降雨量: %.3f 英寸 (%.2f mm) - 过去24小时累计
周降雨量: %.3f 英寸 (%.2f mm) 周降雨量: %.3f 英寸 (%.2f mm) - 本周累计
月降雨量: %.3f 英寸 (%.2f mm) 月降雨量: %.3f 英寸 (%.2f mm) - 本月累计
年降雨量: %.3f 英寸 (%.2f mm) 年降雨量: %.3f 英寸 (%.2f mm) - 本年累计
总降雨量: %.3f 英寸 (%.2f mm) 总降雨量: %.3f 英寸 (%.2f mm) - 自设备安装以来累计
太阳辐射: %.2f W/m² 太阳辐射: %.2f W/m²
紫外线指数: %d 紫外线指数: %d
室内温度: %.1f°F (%.1f°C) 室内温度: %.1f°F (%.1f°C)
@ -175,7 +188,8 @@ func (w *WeatherData) String() string {
相对气压: %.3f 英寸汞柱 (%.2f hPa) 相对气压: %.3f 英寸汞柱 (%.2f hPa)
低电量: %v 低电量: %v
软件类型: %s 软件类型: %s
日期UTC: %s`, 日期UTC: %s
`,
w.StationID, w.StationID,
w.TempF, (w.TempF-32)*5/9, w.TempF, (w.TempF-32)*5/9,
w.Humidity, w.Humidity,
@ -201,3 +215,281 @@ func (w *WeatherData) String() string {
w.DateUTC, w.DateUTC,
) )
} }
type WH65LPData struct {
StationID string // 设备ID (24位)
Timestamp time.Time // 数据时间戳
FamilyCode byte // 家族码 (0x24)
Temperature float64 // 温度 (°C)
Humidity int // 湿度 (%)
WindDirection int // 风向 (0-359°)
WindSpeed float64 // 风速 (m/s)
WindGust float64 // 阵风 (m/s)
Rain float64 // 降雨量 (mm)
UV int // 紫外线指数 (0-13)
Light float64 // 光照 (lux)
Pressure float64 // 气压 (hPa)
LowBattery bool // 低电量标志
WSPFlag bool // 风速标志位
}
// 辅助函数将byte转换为二进制字符串
func byteToBinary(b byte) string {
return fmt.Sprintf("%08b", b)
}
// ParseWH65LPData 解析WH65LP设备的25字节数据
func ParseWH65LPData(data []byte) (*WH65LPData, error) {
if len(data) != 25 {
return nil, fmt.Errorf("数据长度错误期望25字节实际%d字节", len(data))
}
wd := &WH65LPData{
Timestamp: time.Now(),
}
// 1. 家族码 (第1字节)
wd.FamilyCode = data[0]
fmt.Printf("\n=== 字段解析详情 ===\n")
fmt.Printf("1. 家族码: 0x%02X\n", wd.FamilyCode)
// 2. 设备ID (字节22,23,2)
// ID = ID_HSB(22) + ID_MSB(23) + ID_LSB(2)
// 按照文档顺序00 2A 36
id := uint32(0)<<16 | uint32(data[22])<<8 | uint32(data[1])
wd.StationID = fmt.Sprintf("%06X", id)
fmt.Printf("2. 设备ID: 0x%s (字节22=0x%02X, 字节21=0x%02X, 字节1=0x%02X)\n",
wd.StationID, data[22], data[21], data[1])
// 3. 风向 (字节3,4)
// 9位数据直接转换为十进制即为角度
windDir := uint16(data[2])
wd.WindDirection = int(windDir)
fmt.Printf("3. 风向: 0x%02X = %d°\n", windDir, wd.WindDirection)
// 4. 温度 (字节4,5)
// 温度原始值 = 0x296 = 662
// 计算公式:(662-400)/10 = 26.2
tempRaw := uint16(data[3])<<8 | uint16(data[4])
wd.Temperature = float64(tempRaw-400) / 10.0
fmt.Printf("4. 温度: 0x%03X = %.1f°C\n", tempRaw, wd.Temperature)
// 5. 湿度 (字节6)
// 直接转换为十进制
wd.Humidity = int(data[5])
fmt.Printf("5. 湿度: 0x%02X = %d%%\n", data[5], wd.Humidity)
// 6. 风速 (字节7)
// 计算公式:原始值/8*0.51
windSpeedRaw := uint16(data[6])
wd.WindSpeed = float64(windSpeedRaw) / 8.0 * 0.51
fmt.Printf("6. 风速: 0x%02X = %.2f m/s\n", windSpeedRaw, wd.WindSpeed)
// 7. 阵风 (字节8)
// 计算公式:原始值*0.51
wd.WindGust = float64(data[7]) * 0.51
fmt.Printf("7. 阵风: 0x%02X = %.2f m/s\n", data[7], wd.WindGust)
// 8. 降雨量 (字节9,10)
// 计算公式:原始值*0.254
rainRaw := uint16(data[8])<<8 | uint16(data[9])
wd.Rain = float64(rainRaw) * 0.254
fmt.Printf("8. 降雨量: 0x%04X = %.3f mm\n", rainRaw, wd.Rain)
// 9. 紫外线 (字节11,12)
uvRaw := uint16(data[10])<<8 | uint16(data[11])
wd.UV = getUVIndex(uvRaw)
fmt.Printf("9. 紫外线: 0x%04X = %d\n", uvRaw, wd.UV)
// 10. 光照 (字节13-15)
// 计算公式:原始值/10
lightRaw := uint32(data[12])<<16 | uint32(data[13])<<8 | uint32(data[14])
wd.Light = float64(lightRaw) / 10.0
fmt.Printf("10. 光照: 0x%06X = %.1f lux\n", lightRaw, wd.Light)
// 11. 气压 (字节18-20)
// 计算公式:原始值/100
pressureRaw := uint32(data[17])<<16 | uint32(data[18])<<8 | uint32(data[19])
wd.Pressure = float64(pressureRaw) / 100.0
fmt.Printf("11. 气压: 0x%06X = %.2f hPa\n", pressureRaw, wd.Pressure)
return wd, nil
}
// 调试辅助函数
func getBits(data []byte, startBit, length int) uint32 {
var result uint32
startByte := startBit / 8
startBitInByte := startBit % 8
// 从起始字节开始读取
result = uint32(data[startByte]) >> startBitInByte
bitsGot := 8 - startBitInByte
// 如果需要更多位,继续读取后续字节
for bitsGot < length {
startByte++
result |= uint32(data[startByte]) << bitsGot
bitsGot += 8
}
// 只保留需要的位数
result &= (1 << length) - 1
return result
}
// getUVIndex 根据UV原始值获取UV指数
func getUVIndex(uvRaw uint16) int {
switch {
case uvRaw <= 432:
return 0
case uvRaw <= 851:
return 1
case uvRaw <= 1210:
return 2
case uvRaw <= 1570:
return 3
case uvRaw <= 2017:
return 4
case uvRaw <= 2450:
return 5
case uvRaw <= 2761:
return 6
case uvRaw <= 3100:
return 7
case uvRaw <= 3512:
return 8
case uvRaw <= 3918:
return 9
case uvRaw <= 4277:
return 10
case uvRaw <= 4650:
return 11
case uvRaw <= 5029:
return 12
default:
return 13
}
}
// String 格式化输出WH65LP数据
func (w *WH65LPData) String() string {
return fmt.Sprintf(`
设备ID: %s
温度: %.1f°C
湿度: %d%%
风向: %d°
风速: %.2f m/s
阵风: %.2f m/s
降雨量: %.3f mm
UV指数: %d
光照: %.1f lux
气压: %.2f hPa
电池状态: %v
`,
w.StationID,
w.Temperature,
w.Humidity,
w.WindDirection,
w.WindSpeed,
w.WindGust,
w.Rain,
w.UV,
w.Light,
w.Pressure,
w.LowBattery,
)
}
// IsWH65LPData 检查数据是否是WH65LP的数据
func IsWH65LPData(data []byte) bool {
// 1. 检查数据长度
if len(data) != 25 {
return false
}
// 2. 检查家族码
if data[0] != 0x24 {
return false
}
// 3. 验证CRC (第16字节前15字节的CRC8)
if !ValidateCRC8(data[:15], data[15]) {
return false
}
// 4. 验证气压校验和 (第21字节)
pressureSum := uint8(data[17] + data[18] + data[19])
if pressureSum != data[20] {
return false
}
// 5. 验证CRC2 (第24字节前23字节的CRC8)
if !ValidateCRC8(data[:23], data[23]) {
return false
}
// 6. 验证最终校验和 (第25字节)
var sum uint8
for i := 0; i < 24; i++ {
sum += data[i]
}
if sum != data[24] {
return false
}
return true
}
// ValidateCRC8 验证CRC8校验
// 多项式0x31初始值0x00
func ValidateCRC8(data []byte, crc uint8) bool {
return crc == CalculateCRC8(data)
}
// CalculateCRC8 计算CRC8
func CalculateCRC8(data []byte) uint8 {
var crc uint8
for _, b := range data {
crc ^= b
for i := 0; i < 8; i++ {
if crc&0x80 != 0 {
crc = (crc << 1) ^ 0x31
} else {
crc <<= 1
}
}
}
return crc
}
// ParseUDP10006Data 解析UDP 10006端口的数据
func ParseUDP10006Data(data []byte) (*WH65LPData, error) {
// 1. 验证数据格式
if !IsWH65LPData(data) {
return nil, fmt.Errorf("无效的WH65LP数据格式")
}
// 2. 解析数据
return ParseWH65LPData(data)
}
// ConvertToMetric 将英制单位转换为公制单位
func ConvertToMetric(data *WeatherData) *WH65LPData {
// 用于将旧格式数据转换为WH65LP格式
return &WH65LPData{
StationID: data.StationID,
Timestamp: time.Now(),
Temperature: (data.TempF - 32) * 5 / 9,
Humidity: data.Humidity,
WindDirection: data.WindDir,
WindSpeed: data.WindSpeedMph * 0.44704, // mph转m/s
WindGust: data.WindGustMph * 0.44704, // mph转m/s
Rain: data.RainIn * 25.4, // inch转mm
UV: data.UV,
Light: data.SolarRadiation * 126.7, // W/m²转lux (近似转换)
Pressure: data.BarometerIn * 33.8639, // inHg转hPa
LowBattery: data.LowBattery,
}
}

61
server/udp_server.go Normal file
View File

@ -0,0 +1,61 @@
package server
import (
"log"
"net"
"weatherstation/model"
)
func StartUDPServer(port string) error {
addr, err := net.ResolveUDPAddr("udp", ":"+port)
if err != nil {
return err
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
return err
}
defer conn.Close()
log.Printf("UDP服务器已启动监听端口 %s", port)
buffer := make([]byte, 1024)
for {
n, remoteAddr, err := conn.ReadFromUDP(buffer)
if err != nil {
log.Printf("读取UDP数据失败: %v", err)
continue
}
// 处理接收到的数据
go handleUDPData(buffer[:n], remoteAddr)
}
}
func handleUDPData(data []byte, addr *net.UDPAddr) {
// 尝试解析为WH65LP数据
weatherData, err := model.ParseUDP10006Data(data)
if err != nil {
log.Printf("解析数据失败: %v", err)
return
}
// 更新内存中的设备信息
model.UpdateDeviceInMemory(weatherData.StationID, addr, model.DeviceTypeWH65LP)
// 注册设备到数据库
err = model.RegisterDeviceInDB(weatherData.StationID, addr)
if err != nil {
log.Printf("注册设备失败: %v", err)
}
// 保存数据
err = model.SaveWH65LPData(weatherData, data)
if err != nil {
log.Printf("保存数据失败: %v", err)
return
}
log.Printf("成功接收并保存来自 %s 的WH65LP数据:\n%s", addr.String(), weatherData.String())
}

1
参考页面.html Normal file
View File

@ -0,0 +1 @@
Hello