Compare commits
No commits in common. "a6fa18f5cc1a809344bd24233a456bfa84228509" and "ff2c0d691997013897703039ff706cad713117fb" have entirely different histories.
a6fa18f5cc
...
ff2c0d6919
17
main.go
17
main.go
@ -121,6 +121,7 @@ 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)
|
||||||
@ -128,14 +129,13 @@ func startUDP() {
|
|||||||
asciiDump := asciiDump(rawData)
|
asciiDump := asciiDump(rawData)
|
||||||
log.Printf("ASCII码:\n%s", asciiDump)
|
log.Printf("ASCII码:\n%s", asciiDump)
|
||||||
|
|
||||||
weatherData, err := model.ParseData(rawData)
|
weatherData, err := model.ParseWeatherData(data)
|
||||||
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, string(rawData))
|
err = model.SaveWeatherData(weatherData, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("保存数据到数据库失败: %v", err)
|
log.Printf("保存数据到数据库失败: %v", err)
|
||||||
} else {
|
} else {
|
||||||
@ -154,17 +154,6 @@ 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()
|
||||||
|
|||||||
69
model/db.go
69
model/db.go
@ -30,35 +30,9 @@ 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()
|
||||||
@ -105,19 +79,7 @@ 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,
|
||||||
@ -134,33 +96,8 @@ func saveWIFIWeatherData(data *WeatherData, rawData string, timestamp time.Time)
|
|||||||
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("保存WIFI气象数据失败: %v", err)
|
return fmt.Errorf("保存气象数据失败: %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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,814 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Protocol 定义协议结构
|
|
||||||
type Protocol struct {
|
|
||||||
RawData []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewProtocol 创建新的协议实例
|
|
||||||
func NewProtocol(data []byte) *Protocol {
|
|
||||||
return &Protocol{
|
|
||||||
RawData: data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IdentifyTxType 解析传输类型(第一个字节,bit 0-7)
|
|
||||||
func (p *Protocol) IdentifyTxType() (binary string, hexVal string, decVal uint8) {
|
|
||||||
if len(p.RawData) == 0 {
|
|
||||||
return "", "", 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取第一个字节
|
|
||||||
firstByte := p.RawData[0]
|
|
||||||
|
|
||||||
// 转换为二进制字符串(8位)
|
|
||||||
binary = fmt.Sprintf("%08b", firstByte)
|
|
||||||
|
|
||||||
// 转换为十六进制字符串
|
|
||||||
hexVal = fmt.Sprintf("%02X", firstByte)
|
|
||||||
|
|
||||||
// 十进制值
|
|
||||||
decVal = firstByte
|
|
||||||
|
|
||||||
return binary, hexVal, decVal
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDParts 存储ID的三个部分
|
|
||||||
type IDParts struct {
|
|
||||||
HSB struct {
|
|
||||||
Binary string
|
|
||||||
Hex string
|
|
||||||
Dec uint8
|
|
||||||
}
|
|
||||||
MSB struct {
|
|
||||||
Binary string
|
|
||||||
Hex string
|
|
||||||
Dec uint8
|
|
||||||
}
|
|
||||||
LSB struct {
|
|
||||||
Binary string
|
|
||||||
Hex string
|
|
||||||
Dec uint8
|
|
||||||
}
|
|
||||||
Complete struct {
|
|
||||||
Binary string
|
|
||||||
Hex string
|
|
||||||
Dec uint32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCompleteID 获取完整的24-bit ID code
|
|
||||||
// HSB: bit 168-175 (索引21)
|
|
||||||
// MSB: bit 176-183 (索引22)
|
|
||||||
// LSB: bit 8-15 (索引1)
|
|
||||||
func (p *Protocol) GetCompleteID() (*IDParts, error) {
|
|
||||||
if len(p.RawData) < 23 { // 确保有足够的数据
|
|
||||||
return nil, fmt.Errorf("insufficient data length")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &IDParts{}
|
|
||||||
|
|
||||||
// 处理 HSB (bit 168-175, 索引21)
|
|
||||||
hsbByte := p.RawData[21]
|
|
||||||
result.HSB.Binary = fmt.Sprintf("%08b", hsbByte)
|
|
||||||
result.HSB.Hex = fmt.Sprintf("%02X", hsbByte)
|
|
||||||
result.HSB.Dec = hsbByte
|
|
||||||
|
|
||||||
// 处理 MSB (bit 176-183, 索引22)
|
|
||||||
msbByte := p.RawData[22]
|
|
||||||
result.MSB.Binary = fmt.Sprintf("%08b", msbByte)
|
|
||||||
result.MSB.Hex = fmt.Sprintf("%02X", msbByte)
|
|
||||||
result.MSB.Dec = msbByte
|
|
||||||
|
|
||||||
// 处理 LSB (bit 8-15, 索引1)
|
|
||||||
lsbByte := p.RawData[1]
|
|
||||||
result.LSB.Binary = fmt.Sprintf("%08b", lsbByte)
|
|
||||||
result.LSB.Hex = fmt.Sprintf("%02X", lsbByte)
|
|
||||||
result.LSB.Dec = lsbByte
|
|
||||||
|
|
||||||
// 组合完整的24位ID
|
|
||||||
completeID := uint32(hsbByte)<<16 | uint32(msbByte)<<8 | uint32(lsbByte)
|
|
||||||
result.Complete.Binary = fmt.Sprintf("%024b", completeID)
|
|
||||||
result.Complete.Hex = fmt.Sprintf("%06X", completeID)
|
|
||||||
result.Complete.Dec = completeID
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindDirection 存储风向的三个部分
|
|
||||||
type WindDirection struct {
|
|
||||||
DirH struct {
|
|
||||||
Binary string // 4位二进制,格式为"000X",其中X是bit24
|
|
||||||
Value uint8 // 实际值
|
|
||||||
}
|
|
||||||
DirM struct {
|
|
||||||
Binary string // bit 16-19
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
DirL struct {
|
|
||||||
Binary string // bit 20-23
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
Complete struct {
|
|
||||||
Binary string // 完整的12位二进制
|
|
||||||
Value uint16 // 完整值 (Range: 0°- 359°, Invalid: 0x1FF)
|
|
||||||
Degree float64 // 角度值
|
|
||||||
IsValid bool // 是否有效
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetWindDirection 解析风向数据
|
|
||||||
// DIR_H: 4位 (000 + bit24)
|
|
||||||
// DIR_M: bit 16-19
|
|
||||||
// DIR_L: bit 20-23
|
|
||||||
// Value in hex (Range: 0°- 359°)
|
|
||||||
// If invalid fill with 0x1FF
|
|
||||||
func (p *Protocol) GetWindDirection() (*WindDirection, error) {
|
|
||||||
if len(p.RawData) < 4 { // 确保有足够的数据
|
|
||||||
return nil, fmt.Errorf("insufficient data length")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &WindDirection{}
|
|
||||||
|
|
||||||
// 获取包含bit24的字节(索引3)
|
|
||||||
byte3 := p.RawData[3]
|
|
||||||
// 获取bit24(字节的最低位)
|
|
||||||
bit24 := byte3 & 0x01
|
|
||||||
// 构造DIR_H: "000" + bit24
|
|
||||||
result.DirH.Binary = fmt.Sprintf("000%d", bit24)
|
|
||||||
result.DirH.Value = bit24
|
|
||||||
|
|
||||||
// 获取包含bit16-23的字节(索引2)
|
|
||||||
byte2 := p.RawData[2]
|
|
||||||
// 获取DIR_M (bit 16-19)
|
|
||||||
dirM := (byte2 >> 4) & 0x0F
|
|
||||||
result.DirM.Binary = fmt.Sprintf("%04b", dirM)
|
|
||||||
result.DirM.Value = dirM
|
|
||||||
|
|
||||||
// 获取DIR_L (bit 20-23)
|
|
||||||
dirL := byte2 & 0x0F
|
|
||||||
result.DirL.Binary = fmt.Sprintf("%04b", dirL)
|
|
||||||
result.DirL.Value = dirL
|
|
||||||
|
|
||||||
// 组合完整的风向值(12位)
|
|
||||||
completeDir := (uint16(bit24) << 8) | (uint16(dirM) << 4) | uint16(dirL)
|
|
||||||
result.Complete.Binary = fmt.Sprintf("%012b", completeDir)
|
|
||||||
result.Complete.Value = completeDir
|
|
||||||
|
|
||||||
// 检查值是否在有效范围内 (0-359)
|
|
||||||
if completeDir > 359 {
|
|
||||||
result.Complete.Value = 0x1FF
|
|
||||||
result.Complete.IsValid = false
|
|
||||||
result.Complete.Degree = 0
|
|
||||||
} else {
|
|
||||||
result.Complete.IsValid = true
|
|
||||||
result.Complete.Degree = float64(completeDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindSpeed 存储风速的各个部分
|
|
||||||
type WindSpeed struct {
|
|
||||||
WspFlag struct {
|
|
||||||
Binary string // bit 25
|
|
||||||
Value bool // true = 9bit模式, false = 10bit模式
|
|
||||||
}
|
|
||||||
Extend struct {
|
|
||||||
Binary string // 9bit模式: 000 + bit27
|
|
||||||
// 10bit模式: 00 + bit26 + bit27
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
WspH struct {
|
|
||||||
Binary string // bit 48-51
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
WspL struct {
|
|
||||||
Binary string // bit 52-55
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
Complete struct {
|
|
||||||
Binary string // 完整的9位或10位二进制
|
|
||||||
RawValue uint16 // 原始值
|
|
||||||
Value float64 // 实际风速值 (计算公式: RawValue/8*0.51)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetWindSpeed 解析风速数据
|
|
||||||
// WSP_FLAG: bit 25
|
|
||||||
// WIND_EXTEND:
|
|
||||||
// - 当WSP_FLAG=1时:000 + bit27(9bit模式)
|
|
||||||
// - 当WSP_FLAG=0时:0 + bit136 + bit26 + bit27(10bit模式)
|
|
||||||
//
|
|
||||||
// WIND_H: bit 48-51
|
|
||||||
// WIND_L: bit 52-55
|
|
||||||
// 实际风速计算公式: value/8*0.51
|
|
||||||
func (p *Protocol) GetWindSpeed() (*WindSpeed, error) {
|
|
||||||
if len(p.RawData) < 18 { // 确保有足够的数据(需要读取到bit136)
|
|
||||||
return nil, fmt.Errorf("insufficient data length")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &WindSpeed{}
|
|
||||||
|
|
||||||
// 解析 WSP_FLAG (bit 25)
|
|
||||||
byte3 := p.RawData[3]
|
|
||||||
wspFlag := (byte3 >> 1) & 0x01 // bit 25
|
|
||||||
result.WspFlag.Binary = fmt.Sprintf("%d", wspFlag)
|
|
||||||
result.WspFlag.Value = wspFlag == 1
|
|
||||||
|
|
||||||
// 获取bit26和bit27
|
|
||||||
bit26 := (byte3 >> 2) & 0x01
|
|
||||||
bit27 := (byte3 >> 3) & 0x01
|
|
||||||
|
|
||||||
// 获取bit136(在第17个字节的最高位)
|
|
||||||
byte17 := p.RawData[17]
|
|
||||||
bit136 := (byte17 >> 7) & 0x01
|
|
||||||
|
|
||||||
// 解析 WIND_H 和 WIND_L (byte6)
|
|
||||||
byte6 := p.RawData[6]
|
|
||||||
windH := (byte6 >> 4) & 0x0F
|
|
||||||
windL := byte6 & 0x0F
|
|
||||||
|
|
||||||
result.WspH.Binary = fmt.Sprintf("%04b", windH)
|
|
||||||
result.WspH.Value = windH
|
|
||||||
result.WspL.Binary = fmt.Sprintf("%04b", windL)
|
|
||||||
result.WspL.Value = windL
|
|
||||||
|
|
||||||
// 组合完整的风速值
|
|
||||||
var rawValue uint16
|
|
||||||
if result.WspFlag.Value {
|
|
||||||
// 9bit模式:000 + bit27 + WIND_H + WIND_L
|
|
||||||
rawValue = (uint16(bit27) << 8) | (uint16(windH) << 4) | uint16(windL)
|
|
||||||
result.Complete.Binary = fmt.Sprintf("%09b", rawValue)
|
|
||||||
} else {
|
|
||||||
// 10bit模式:0 + bit136 + bit26 + bit27 + WIND_H + WIND_L
|
|
||||||
extendBits := (uint16(0) << 3) | (uint16(bit136) << 2) | (uint16(bit26) << 1) | uint16(bit27)
|
|
||||||
rawValue = (uint16(extendBits) << 8) | (uint16(windH) << 4) | uint16(windL)
|
|
||||||
result.Complete.Binary = fmt.Sprintf("%010b", rawValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Complete.RawValue = rawValue
|
|
||||||
// 计算实际风速值:value/8*0.51
|
|
||||||
result.Complete.Value = float64(rawValue) / 8.0 * 0.51
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temperature 存储温度的三个部分
|
|
||||||
type Temperature struct {
|
|
||||||
TmpH struct {
|
|
||||||
Binary string // 3位二进制,格式为"0XXX",其中XXX是bit29-31
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
TmpM struct {
|
|
||||||
Binary string // bit 32-35
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
TmpL struct {
|
|
||||||
Binary string // bit 36-39
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
Complete struct {
|
|
||||||
Binary string // 完整的11位二进制
|
|
||||||
RawValue uint16 // 原始值(包含400的偏移)
|
|
||||||
Value float64 // 实际温度值 (Range: -40.0°C -> 60.0°C)
|
|
||||||
IsValid bool // 是否有效
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTemperature 解析温度数据
|
|
||||||
// TMP_H: 4位 (0 + bit29-31)
|
|
||||||
// TMP_M: bit 32-35
|
|
||||||
// TMP_L: bit 36-39
|
|
||||||
// 温度计算公式:(RawValue-400)/10
|
|
||||||
// 示例:
|
|
||||||
// 10.5°C = 0x1F9 (505-400)/10 = 10.5
|
|
||||||
// -10.5°C = 0x127 (295-400)/10 = -10.5
|
|
||||||
// 范围:-40.0°C -> 60.0°C
|
|
||||||
// 无效值:0x7FF
|
|
||||||
func (p *Protocol) GetTemperature() (*Temperature, error) {
|
|
||||||
if len(p.RawData) < 5 { // 确保有足够的数据
|
|
||||||
return nil, fmt.Errorf("insufficient data length")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &Temperature{}
|
|
||||||
|
|
||||||
// 获取包含bit29-31的字节(索引3)
|
|
||||||
byte3 := p.RawData[3]
|
|
||||||
// 直接获取bit29-31 (010)
|
|
||||||
tmpHBits := byte3 & 0x07
|
|
||||||
// TMP_H 是 "0" + bit29-31
|
|
||||||
result.TmpH.Binary = fmt.Sprintf("0%03b", tmpHBits)
|
|
||||||
result.TmpH.Value = tmpHBits
|
|
||||||
|
|
||||||
// 获取包含bit32-39的字节(索引4)
|
|
||||||
byte4 := p.RawData[4]
|
|
||||||
// 获取TMP_M (bit 32-35),在byte4的高4位
|
|
||||||
tmpM := (byte4 >> 4) & 0x0F
|
|
||||||
result.TmpM.Binary = fmt.Sprintf("%04b", tmpM)
|
|
||||||
result.TmpM.Value = tmpM
|
|
||||||
|
|
||||||
// 获取TMP_L (bit 36-39),在byte4的低4位
|
|
||||||
tmpL := byte4 & 0x0F
|
|
||||||
result.TmpL.Binary = fmt.Sprintf("%04b", tmpL)
|
|
||||||
result.TmpL.Value = tmpL
|
|
||||||
|
|
||||||
// 组合完整的温度值
|
|
||||||
// 1. TMP_H (0 + bit29-31) 放在最高位
|
|
||||||
// 2. TMP_M (bit 32-35) 放在中间
|
|
||||||
// 3. TMP_L (bit 36-39) 放在最低位
|
|
||||||
completeTemp := uint16(0)
|
|
||||||
completeTemp |= uint16(tmpHBits) << 8 // TMP_H 移到高8位
|
|
||||||
completeTemp |= uint16(tmpM) << 4 // TMP_M 移到中间4位
|
|
||||||
completeTemp |= uint16(tmpL) // TMP_L 在最低4位
|
|
||||||
|
|
||||||
result.Complete.Binary = fmt.Sprintf("%012b", completeTemp)
|
|
||||||
result.Complete.RawValue = completeTemp
|
|
||||||
|
|
||||||
// 检查温度是否在有效范围内
|
|
||||||
// 有效范围计算:
|
|
||||||
// -40°C = (-40 * 10 + 400) = 0
|
|
||||||
// 60°C = (60 * 10 + 400) = 1000
|
|
||||||
if completeTemp > 1000 { // 超出范围
|
|
||||||
result.Complete.RawValue = 0x7FF // 无效值
|
|
||||||
result.Complete.Value = 0
|
|
||||||
result.Complete.IsValid = false
|
|
||||||
} else {
|
|
||||||
// 温度计算:(RawValue-400)/10
|
|
||||||
result.Complete.Value = (float64(completeTemp) - 400) / 10
|
|
||||||
result.Complete.IsValid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Humidity 存储湿度的两个部分
|
|
||||||
type Humidity struct {
|
|
||||||
HmH struct {
|
|
||||||
Binary string // bit 40-43
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
HmL struct {
|
|
||||||
Binary string // bit 44-47
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
Complete struct {
|
|
||||||
Binary string // 完整的8位二进制
|
|
||||||
RawValue uint8 // 原始值(十六进制)
|
|
||||||
Value uint8 // 实际湿度值 (Range: 1% - 99%)
|
|
||||||
IsValid bool // 是否有效
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHumidity 解析湿度数据
|
|
||||||
// HM_H: bit 40-43
|
|
||||||
// HM_L: bit 44-47
|
|
||||||
// Range: 1% - 99%
|
|
||||||
// If invalid fill with 0xFF
|
|
||||||
// 示例:0x37 = 55% (3*16 + 7 = 55)
|
|
||||||
func (p *Protocol) GetHumidity() (*Humidity, error) {
|
|
||||||
if len(p.RawData) < 6 { // 确保有足够的数据(bit 47 在第6个字节内)
|
|
||||||
return nil, fmt.Errorf("insufficient data length")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &Humidity{}
|
|
||||||
|
|
||||||
// 获取包含bit40-47的字节(索引5)
|
|
||||||
byte5 := p.RawData[5]
|
|
||||||
|
|
||||||
// 获取HM_H (bit 40-43)
|
|
||||||
hmH := (byte5 >> 4) & 0x0F
|
|
||||||
result.HmH.Binary = fmt.Sprintf("%04b", hmH)
|
|
||||||
result.HmH.Value = hmH
|
|
||||||
|
|
||||||
// 获取HM_L (bit 44-47)
|
|
||||||
hmL := byte5 & 0x0F
|
|
||||||
result.HmL.Binary = fmt.Sprintf("%04b", hmL)
|
|
||||||
result.HmL.Value = hmL
|
|
||||||
|
|
||||||
// 原始十六进制值
|
|
||||||
result.Complete.Binary = fmt.Sprintf("%08b", byte5)
|
|
||||||
result.Complete.RawValue = byte5
|
|
||||||
|
|
||||||
// 直接使用十六进制值转换为十进制作为湿度值
|
|
||||||
decimalValue := byte5
|
|
||||||
|
|
||||||
// 检查湿度是否在有效范围内 (1-99)
|
|
||||||
if decimalValue < 1 || decimalValue > 99 {
|
|
||||||
result.Complete.RawValue = 0xFF // 无效值
|
|
||||||
result.Complete.Value = 0
|
|
||||||
result.Complete.IsValid = false
|
|
||||||
} else {
|
|
||||||
result.Complete.Value = decimalValue
|
|
||||||
result.Complete.IsValid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GustSpeed 存储阵风速度数据
|
|
||||||
type GustSpeed struct {
|
|
||||||
GustH struct {
|
|
||||||
Binary string // bit 56-59
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
GustL struct {
|
|
||||||
Binary string // bit 60-63
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
Complete struct {
|
|
||||||
Binary string // 完整的8位二进制
|
|
||||||
RawValue uint8 // 原始值
|
|
||||||
Value float64 // 实际阵风速度值 (计算公式: RawValue*0.51)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGustSpeed 解析阵风速度数据
|
|
||||||
// GUST_H: bit 56-59
|
|
||||||
// GUST_L: bit 60-63
|
|
||||||
// 实际阵风速度计算公式: value*0.51
|
|
||||||
func (p *Protocol) GetGustSpeed() (*GustSpeed, error) {
|
|
||||||
if len(p.RawData) < 8 { // 确保有足够的数据(bit 63 在第8个字节内)
|
|
||||||
return nil, fmt.Errorf("insufficient data length")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &GustSpeed{}
|
|
||||||
|
|
||||||
// 解析 GUST_H (bit 56-59) 和 GUST_L (bit 60-63)
|
|
||||||
byte7 := p.RawData[7]
|
|
||||||
gustH := (byte7 >> 4) & 0x0F
|
|
||||||
gustL := byte7 & 0x0F
|
|
||||||
|
|
||||||
result.GustH.Binary = fmt.Sprintf("%04b", gustH)
|
|
||||||
result.GustH.Value = gustH
|
|
||||||
result.GustL.Binary = fmt.Sprintf("%04b", gustL)
|
|
||||||
result.GustL.Value = gustL
|
|
||||||
|
|
||||||
// 组合完整的阵风速度值
|
|
||||||
rawValue := byte7
|
|
||||||
result.Complete.Binary = fmt.Sprintf("%08b", rawValue)
|
|
||||||
result.Complete.RawValue = rawValue
|
|
||||||
|
|
||||||
// 计算实际阵风速度值:value*0.51
|
|
||||||
result.Complete.Value = float64(rawValue) * 0.51
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rainfall 存储降雨量数据
|
|
||||||
type Rainfall struct {
|
|
||||||
RainHH struct {
|
|
||||||
Binary string // bit 64-67
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
RainHL struct {
|
|
||||||
Binary string // bit 68-71
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
RainLH struct {
|
|
||||||
Binary string // bit 72-75
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
RainLL struct {
|
|
||||||
Binary string // bit 76-79
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
Complete struct {
|
|
||||||
Binary string // 完整的16位二进制
|
|
||||||
RawValue uint16 // 原始值
|
|
||||||
Value float64 // 实际降雨量值 (计算公式: RawValue*0.254)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRainfall 解析降雨量数据
|
|
||||||
// RAIN_HH: bit 64-67
|
|
||||||
// RAIN_HL: bit 68-71
|
|
||||||
// RAIN_LH: bit 72-75
|
|
||||||
// RAIN_LL: bit 76-79
|
|
||||||
// 实际降雨量计算公式: value*0.254
|
|
||||||
func (p *Protocol) GetRainfall() (*Rainfall, error) {
|
|
||||||
if len(p.RawData) < 10 { // 确保有足够的数据(bit 79 在第10个字节内)
|
|
||||||
return nil, fmt.Errorf("insufficient data length")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &Rainfall{}
|
|
||||||
|
|
||||||
// 解析 RAIN_HH 和 RAIN_HL (byte8)
|
|
||||||
byte8 := p.RawData[8]
|
|
||||||
rainHH := (byte8 >> 4) & 0x0F
|
|
||||||
rainHL := byte8 & 0x0F
|
|
||||||
|
|
||||||
result.RainHH.Binary = fmt.Sprintf("%04b", rainHH)
|
|
||||||
result.RainHH.Value = rainHH
|
|
||||||
result.RainHL.Binary = fmt.Sprintf("%04b", rainHL)
|
|
||||||
result.RainHL.Value = rainHL
|
|
||||||
|
|
||||||
// 解析 RAIN_LH 和 RAIN_LL (byte9)
|
|
||||||
byte9 := p.RawData[9]
|
|
||||||
rainLH := (byte9 >> 4) & 0x0F
|
|
||||||
rainLL := byte9 & 0x0F
|
|
||||||
|
|
||||||
result.RainLH.Binary = fmt.Sprintf("%04b", rainLH)
|
|
||||||
result.RainLH.Value = rainLH
|
|
||||||
result.RainLL.Binary = fmt.Sprintf("%04b", rainLL)
|
|
||||||
result.RainLL.Value = rainLL
|
|
||||||
|
|
||||||
// 组合完整的降雨量值
|
|
||||||
rawValue := (uint16(rainHH) << 12) | (uint16(rainHL) << 8) | (uint16(rainLH) << 4) | uint16(rainLL)
|
|
||||||
result.Complete.Binary = fmt.Sprintf("%016b", rawValue)
|
|
||||||
result.Complete.RawValue = rawValue
|
|
||||||
|
|
||||||
// 计算实际降雨量值:value*0.254
|
|
||||||
result.Complete.Value = float64(rawValue) * 0.254
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UVIndex 存储紫外线指数数据
|
|
||||||
type UVIndex struct {
|
|
||||||
UviHH struct {
|
|
||||||
Binary string // bit 80-83
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
UviHL struct {
|
|
||||||
Binary string // bit 84-87
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
UviLH struct {
|
|
||||||
Binary string // bit 88-91
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
UviLL struct {
|
|
||||||
Binary string // bit 92-95
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
Complete struct {
|
|
||||||
Binary string // 完整的16位二进制
|
|
||||||
RawValue uint16 // 原始值
|
|
||||||
Value float64 // 实际紫外线值 (单位: uW/c㎡)
|
|
||||||
IsValid bool // 是否有效
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUVIndex 解析紫外线指数数据
|
|
||||||
// UVI_HH: bit 80-83
|
|
||||||
// UVI_HL: bit 84-87
|
|
||||||
// UVI_LH: bit 88-91
|
|
||||||
// UVI_LL: bit 92-95
|
|
||||||
// Range: 0 uW/c㎡ to 20000 uW/c㎡
|
|
||||||
// If invalid fill with 0xFFFF
|
|
||||||
func (p *Protocol) GetUVIndex() (*UVIndex, error) {
|
|
||||||
if len(p.RawData) < 12 { // 确保有足够的数据(bit 95 在第12个字节内)
|
|
||||||
return nil, fmt.Errorf("insufficient data length")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &UVIndex{}
|
|
||||||
|
|
||||||
// 解析 UVI_HH 和 UVI_HL (byte10)
|
|
||||||
byte10 := p.RawData[10]
|
|
||||||
uviHH := (byte10 >> 4) & 0x0F
|
|
||||||
uviHL := byte10 & 0x0F
|
|
||||||
|
|
||||||
result.UviHH.Binary = fmt.Sprintf("%04b", uviHH)
|
|
||||||
result.UviHH.Value = uviHH
|
|
||||||
result.UviHL.Binary = fmt.Sprintf("%04b", uviHL)
|
|
||||||
result.UviHL.Value = uviHL
|
|
||||||
|
|
||||||
// 解析 UVI_LH 和 UVI_LL (byte11)
|
|
||||||
byte11 := p.RawData[11]
|
|
||||||
uviLH := (byte11 >> 4) & 0x0F
|
|
||||||
uviLL := byte11 & 0x0F
|
|
||||||
|
|
||||||
result.UviLH.Binary = fmt.Sprintf("%04b", uviLH)
|
|
||||||
result.UviLH.Value = uviLH
|
|
||||||
result.UviLL.Binary = fmt.Sprintf("%04b", uviLL)
|
|
||||||
result.UviLL.Value = uviLL
|
|
||||||
|
|
||||||
// 组合完整的紫外线值
|
|
||||||
rawValue := (uint16(uviHH) << 12) | (uint16(uviHL) << 8) | (uint16(uviLH) << 4) | uint16(uviLL)
|
|
||||||
result.Complete.Binary = fmt.Sprintf("%016b", rawValue)
|
|
||||||
result.Complete.RawValue = rawValue
|
|
||||||
|
|
||||||
// 检查是否在有效范围内 (0-20000)
|
|
||||||
if rawValue > 20000 {
|
|
||||||
result.Complete.RawValue = 0xFFFF
|
|
||||||
result.Complete.Value = 0
|
|
||||||
result.Complete.IsValid = false
|
|
||||||
} else {
|
|
||||||
result.Complete.Value = float64(rawValue)
|
|
||||||
result.Complete.IsValid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Light 存储光照数据
|
|
||||||
type Light struct {
|
|
||||||
LightHH struct {
|
|
||||||
Binary string // bit 96-99
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
LightHL struct {
|
|
||||||
Binary string // bit 100-103
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
LightMH struct {
|
|
||||||
Binary string // bit 104-107
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
LightML struct {
|
|
||||||
Binary string // bit 108-111
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
LightLH struct {
|
|
||||||
Binary string // bit 112-115
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
LightLL struct {
|
|
||||||
Binary string // bit 116-119
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
Complete struct {
|
|
||||||
Binary string // 完整的24位二进制
|
|
||||||
RawValue uint32 // 原始值
|
|
||||||
Value float64 // 实际光照值 (计算公式: RawValue/10) (单位: lux)
|
|
||||||
IsValid bool // 是否有效
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLight 解析光照数据
|
|
||||||
// bit 96-119 (byte12-14: 00 04 9C)
|
|
||||||
// 实际光照计算公式: value/10
|
|
||||||
// Range: 0.0 lux -> 300,000.0 lux
|
|
||||||
// If invalid fill with 0xFFFFFF
|
|
||||||
func (p *Protocol) GetLight() (*Light, error) {
|
|
||||||
if len(p.RawData) < 15 { // 确保有足够的数据
|
|
||||||
return nil, fmt.Errorf("insufficient data length")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &Light{}
|
|
||||||
|
|
||||||
// 获取三个字节 (00 04 9C)
|
|
||||||
byte1 := p.RawData[12] // 00
|
|
||||||
byte2 := p.RawData[13] // 04
|
|
||||||
byte3 := p.RawData[14] // 9C
|
|
||||||
|
|
||||||
// 组合完整的光照值 (00 04 9C)
|
|
||||||
rawValue := (uint32(byte1) << 16) | (uint32(byte2) << 8) | uint32(byte3)
|
|
||||||
result.Complete.Binary = fmt.Sprintf("%024b", rawValue)
|
|
||||||
result.Complete.RawValue = rawValue
|
|
||||||
|
|
||||||
// 检查是否在有效范围内 (0-3000000, 因为实际值要除以10)
|
|
||||||
if rawValue > 3000000 {
|
|
||||||
result.Complete.RawValue = 0xFFFFFF
|
|
||||||
result.Complete.Value = 0
|
|
||||||
result.Complete.IsValid = false
|
|
||||||
} else {
|
|
||||||
result.Complete.Value = float64(rawValue) / 10.0
|
|
||||||
result.Complete.IsValid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pressure 存储大气压数据
|
|
||||||
type Pressure struct {
|
|
||||||
PressureH struct {
|
|
||||||
Binary string // bit 143-144 (补前导000)
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
PressureM struct {
|
|
||||||
Binary string // bit 145-151
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
PressureL struct {
|
|
||||||
Binary string // bit 152-159
|
|
||||||
Value uint8
|
|
||||||
}
|
|
||||||
Complete struct {
|
|
||||||
Binary string // 完整的17位二进制
|
|
||||||
RawValue uint32 // 原始值
|
|
||||||
Value float64 // 实际气压值 (计算公式: RawValue/100) (单位: hPa)
|
|
||||||
IsValid bool // 是否有效
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPressure 解析大气压数据
|
|
||||||
// 17位值组成:000 + bit143 + bit144-159
|
|
||||||
// 实际气压计算公式: value/100
|
|
||||||
// Range: 300.00 hPa -> 1200.00 hPa
|
|
||||||
// If invalid fill with 0x1FFFF
|
|
||||||
// Example: 0x018A9E = 1010.22 hPa
|
|
||||||
func (p *Protocol) GetPressure() (*Pressure, error) {
|
|
||||||
if len(p.RawData) < 20 { // 确保有足够的数据
|
|
||||||
return nil, fmt.Errorf("insufficient data length")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &Pressure{}
|
|
||||||
|
|
||||||
// 获取三个字节 (01 88 F5)
|
|
||||||
byte1 := p.RawData[17] // 01
|
|
||||||
byte2 := p.RawData[18] // 88
|
|
||||||
byte3 := p.RawData[19] // F5
|
|
||||||
|
|
||||||
// 组合完整的气压值
|
|
||||||
rawValue := (uint32(byte1) << 16) | (uint32(byte2) << 8) | uint32(byte3)
|
|
||||||
result.Complete.Binary = fmt.Sprintf("%024b", rawValue)
|
|
||||||
result.Complete.RawValue = rawValue
|
|
||||||
|
|
||||||
// 检查是否在有效范围内 (30000-120000,因为实际值要除以100)
|
|
||||||
if rawValue < 30000 || rawValue > 120000 {
|
|
||||||
result.Complete.RawValue = 0x1FFFF
|
|
||||||
result.Complete.Value = 0
|
|
||||||
result.Complete.IsValid = false
|
|
||||||
} else {
|
|
||||||
result.Complete.Value = float64(rawValue) / 100.0
|
|
||||||
result.Complete.IsValid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@ -1,688 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIdentifyTxType(t *testing.T) {
|
|
||||||
// 测试数据:24 36 F3 02 96 37 06 01 00 04 00 00 00 04 9C D3 9A 01 88 F5 7E 00 2A 9C F6
|
|
||||||
data := []byte{0x24, 0x36, 0xF3, 0x02, 0x96, 0x37, 0x06, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x9C, 0xD3, 0x9A, 0x01, 0x88, 0xF5, 0x7E, 0x00, 0x2A, 0x9C, 0xF6}
|
|
||||||
|
|
||||||
protocol := NewProtocol(data)
|
|
||||||
binary, hex, dec := protocol.IdentifyTxType()
|
|
||||||
|
|
||||||
// 预期结果
|
|
||||||
expectedBinary := "00100100" // 24 的二进制表示
|
|
||||||
expectedHex := "24" // 第一个字节的十六进制表示
|
|
||||||
expectedDec := uint8(36) // 24 的十进制表示
|
|
||||||
|
|
||||||
// 验证二进制结果
|
|
||||||
if binary != expectedBinary {
|
|
||||||
t.Errorf("Binary representation incorrect. Got %s, want %s", binary, expectedBinary)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证十六进制结果
|
|
||||||
if hex != expectedHex {
|
|
||||||
t.Errorf("Hex representation incorrect. Got %s, want %s", hex, expectedHex)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证十进制结果
|
|
||||||
if dec != expectedDec {
|
|
||||||
t.Errorf("Decimal representation incorrect. Got %d, want %d", dec, expectedDec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetCompleteID(t *testing.T) {
|
|
||||||
// 测试数据:24 36 F3 02 96 37 06 01 00 04 00 00 00 04 9C D3 9A 01 88 F5 7E 00 2A 9C F6
|
|
||||||
data := []byte{0x24, 0x36, 0xF3, 0x02, 0x96, 0x37, 0x06, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x9C, 0xD3, 0x9A, 0x01, 0x88, 0xF5, 0x7E, 0x00, 0x2A, 0x9C, 0xF6}
|
|
||||||
|
|
||||||
protocol := NewProtocol(data)
|
|
||||||
result, err := protocol.GetCompleteID()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试 HSB (应该是 00)
|
|
||||||
expectedHSBBinary := "00000000" // 00 的二进制
|
|
||||||
expectedHSBHex := "00" // 00 的十六进制
|
|
||||||
expectedHSBDec := uint8(0) // 00 的十进制
|
|
||||||
|
|
||||||
if result.HSB.Binary != expectedHSBBinary {
|
|
||||||
t.Errorf("HSB Binary incorrect. Got %s, want %s", result.HSB.Binary, expectedHSBBinary)
|
|
||||||
}
|
|
||||||
if result.HSB.Hex != expectedHSBHex {
|
|
||||||
t.Errorf("HSB Hex incorrect. Got %s, want %s", result.HSB.Hex, expectedHSBHex)
|
|
||||||
}
|
|
||||||
if result.HSB.Dec != expectedHSBDec {
|
|
||||||
t.Errorf("HSB Dec incorrect. Got %d, want %d", result.HSB.Dec, expectedHSBDec)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试 MSB (应该是 2A)
|
|
||||||
expectedMSBBinary := "00101010" // 2A 的二进制
|
|
||||||
expectedMSBHex := "2A" // 2A 的十六进制
|
|
||||||
expectedMSBDec := uint8(42) // 2A 的十进制
|
|
||||||
|
|
||||||
if result.MSB.Binary != expectedMSBBinary {
|
|
||||||
t.Errorf("MSB Binary incorrect. Got %s, want %s", result.MSB.Binary, expectedMSBBinary)
|
|
||||||
}
|
|
||||||
if result.MSB.Hex != expectedMSBHex {
|
|
||||||
t.Errorf("MSB Hex incorrect. Got %s, want %s", result.MSB.Hex, expectedMSBHex)
|
|
||||||
}
|
|
||||||
if result.MSB.Dec != expectedMSBDec {
|
|
||||||
t.Errorf("MSB Dec incorrect. Got %d, want %d", result.MSB.Dec, expectedMSBDec)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试 LSB (应该是 36)
|
|
||||||
expectedLSBBinary := "00110110" // 36 的二进制
|
|
||||||
expectedLSBHex := "36" // 36 的十六进制
|
|
||||||
expectedLSBDec := uint8(54) // 36 的十进制
|
|
||||||
|
|
||||||
if result.LSB.Binary != expectedLSBBinary {
|
|
||||||
t.Errorf("LSB Binary incorrect. Got %s, want %s", result.LSB.Binary, expectedLSBBinary)
|
|
||||||
}
|
|
||||||
if result.LSB.Hex != expectedLSBHex {
|
|
||||||
t.Errorf("LSB Hex incorrect. Got %s, want %s", result.LSB.Hex, expectedLSBHex)
|
|
||||||
}
|
|
||||||
if result.LSB.Dec != expectedLSBDec {
|
|
||||||
t.Errorf("LSB Dec incorrect. Got %d, want %d", result.LSB.Dec, expectedLSBDec)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试完整的24位ID (应该是 00 2A 36)
|
|
||||||
expectedCompleteBinary := "000000000010101000110110" // 完整24位二进制
|
|
||||||
expectedCompleteHex := "002A36" // 完整24位十六进制
|
|
||||||
expectedCompleteDec := uint32(0x002A36) // 完整24位十进制
|
|
||||||
|
|
||||||
if result.Complete.Binary != expectedCompleteBinary {
|
|
||||||
t.Errorf("Complete Binary incorrect. Got %s, want %s", result.Complete.Binary, expectedCompleteBinary)
|
|
||||||
}
|
|
||||||
if result.Complete.Hex != expectedCompleteHex {
|
|
||||||
t.Errorf("Complete Hex incorrect. Got %s, want %s", result.Complete.Hex, expectedCompleteHex)
|
|
||||||
}
|
|
||||||
if result.Complete.Dec != expectedCompleteDec {
|
|
||||||
t.Errorf("Complete Dec incorrect. Got %d, want %d", result.Complete.Dec, expectedCompleteDec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetWindDirection(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
data []byte
|
|
||||||
expectedValue uint16
|
|
||||||
expectedValid bool
|
|
||||||
expectedDegree float64
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Valid Direction",
|
|
||||||
data: []byte{0x24, 0x36, 0xF3, 0x02, 0x96}, // 原始数据,方向值为243
|
|
||||||
expectedValue: 0xF3,
|
|
||||||
expectedValid: true,
|
|
||||||
expectedDegree: 243,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid Direction",
|
|
||||||
data: []byte{0x24, 0x36, 0xFF, 0x03, 0x96}, // 设置一个大于359的值
|
|
||||||
expectedValue: 0x1FF,
|
|
||||||
expectedValid: false,
|
|
||||||
expectedDegree: 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
protocol := NewProtocol(tt.data)
|
|
||||||
result, err := protocol.GetWindDirection()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Complete.Value != tt.expectedValue {
|
|
||||||
t.Errorf("Value incorrect. Got %X, want %X", result.Complete.Value, tt.expectedValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Complete.IsValid != tt.expectedValid {
|
|
||||||
t.Errorf("IsValid incorrect. Got %v, want %v", result.Complete.IsValid, tt.expectedValid)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Complete.Degree != tt.expectedDegree {
|
|
||||||
t.Errorf("Degree incorrect. Got %v, want %v", result.Complete.Degree, tt.expectedDegree)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetTemperature(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
data []byte
|
|
||||||
expectedRaw uint16
|
|
||||||
expectedTemp float64
|
|
||||||
expectedValid bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Temperature 10.5°C",
|
|
||||||
// 0x1F9 = 505 -> (505-400)/10 = 10.5
|
|
||||||
// TMP_H = 1 (001)
|
|
||||||
// TMP_M = F (1111)
|
|
||||||
// TMP_L = 9 (1001)
|
|
||||||
data: []byte{0x24, 0x36, 0xF3, 0x20, 0xF9}, // 设置byte3的bit29-31为001,byte4为0xF9
|
|
||||||
expectedRaw: 0x1F9,
|
|
||||||
expectedTemp: 10.5,
|
|
||||||
expectedValid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Temperature -10.5°C",
|
|
||||||
// 0x127 = 295 -> (295-400)/10 = -10.5
|
|
||||||
// TMP_H = 1 (001)
|
|
||||||
// TMP_M = 2 (0010)
|
|
||||||
// TMP_L = 7 (0111)
|
|
||||||
data: []byte{0x24, 0x36, 0xF3, 0x20, 0x27}, // 设置byte3的bit29-31为001,byte4为0x27
|
|
||||||
expectedRaw: 0x127,
|
|
||||||
expectedTemp: -10.5,
|
|
||||||
expectedValid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid Temperature",
|
|
||||||
// 0x7FF = 2047 (超出范围)
|
|
||||||
// TMP_H = 7 (111)
|
|
||||||
// TMP_M = F (1111)
|
|
||||||
// TMP_L = F (1111)
|
|
||||||
data: []byte{0x24, 0x36, 0xF3, 0xE0, 0xFF}, // 设置byte3的bit29-31为111,byte4为0xFF
|
|
||||||
expectedRaw: 0x7FF,
|
|
||||||
expectedTemp: 0,
|
|
||||||
expectedValid: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
protocol := NewProtocol(tt.data)
|
|
||||||
result, err := protocol.GetTemperature()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Complete.RawValue != tt.expectedRaw {
|
|
||||||
t.Errorf("Raw value incorrect. Got %X, want %X", result.Complete.RawValue, tt.expectedRaw)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Complete.Value != tt.expectedTemp {
|
|
||||||
t.Errorf("Temperature value incorrect. Got %.1f, want %.1f", result.Complete.Value, tt.expectedTemp)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Complete.IsValid != tt.expectedValid {
|
|
||||||
t.Errorf("Validity incorrect. Got %v, want %v", result.Complete.IsValid, tt.expectedValid)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetHumidity(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
data []byte
|
|
||||||
expectedRaw uint8
|
|
||||||
expectedValue uint8
|
|
||||||
expectedValid bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Valid Humidity 55%",
|
|
||||||
// 0x37 = 3*16 + 7 = 55%
|
|
||||||
data: []byte{0x24, 0x36, 0xF3, 0x02, 0x96, 0x37},
|
|
||||||
expectedRaw: 0x37,
|
|
||||||
expectedValue: 55,
|
|
||||||
expectedValid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Valid Humidity 1%",
|
|
||||||
// 0x01 = 0*16 + 1 = 1%
|
|
||||||
data: []byte{0x24, 0x36, 0xF3, 0x02, 0x96, 0x01},
|
|
||||||
expectedRaw: 0x01,
|
|
||||||
expectedValue: 1,
|
|
||||||
expectedValid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Valid Humidity 99%",
|
|
||||||
// 0x63 = 6*16 + 3 = 99%
|
|
||||||
data: []byte{0x24, 0x36, 0xF3, 0x02, 0x96, 0x63},
|
|
||||||
expectedRaw: 0x63,
|
|
||||||
expectedValue: 99,
|
|
||||||
expectedValid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid Humidity (Too High)",
|
|
||||||
// 0x64 = 6*16 + 4 = 100% (无效)
|
|
||||||
data: []byte{0x24, 0x36, 0xF3, 0x02, 0x96, 0x64},
|
|
||||||
expectedRaw: 0xFF,
|
|
||||||
expectedValue: 0,
|
|
||||||
expectedValid: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid Humidity (Zero)",
|
|
||||||
// 0x00 = 0% (无效)
|
|
||||||
data: []byte{0x24, 0x36, 0xF3, 0x02, 0x96, 0x00},
|
|
||||||
expectedRaw: 0xFF,
|
|
||||||
expectedValue: 0,
|
|
||||||
expectedValid: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
protocol := NewProtocol(tt.data)
|
|
||||||
result, err := protocol.GetHumidity()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Complete.RawValue != tt.expectedRaw {
|
|
||||||
t.Errorf("Raw value incorrect. Got %X, want %X", result.Complete.RawValue, tt.expectedRaw)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Complete.Value != tt.expectedValue {
|
|
||||||
t.Errorf("Humidity value incorrect. Got %d%%, want %d%%", result.Complete.Value, tt.expectedValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Complete.IsValid != tt.expectedValid {
|
|
||||||
t.Errorf("Validity incorrect. Got %v, want %v", result.Complete.IsValid, tt.expectedValid)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetWindSpeed(t *testing.T) {
|
|
||||||
// 测试数据:24 36 F3 02 96 37 06 01 00 04 00 00 00 04 9C D3 9A 01 88 F5 7E 00 2A 9C F6
|
|
||||||
data := []byte{0x24, 0x36, 0xF3, 0x02, 0x96, 0x37, 0x06, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x9C, 0xD3, 0x9A, 0x01, 0x88, 0xF5, 0x7E, 0x00, 0x2A, 0x9C, 0xF6}
|
|
||||||
|
|
||||||
protocol := NewProtocol(data)
|
|
||||||
result, err := protocol.GetWindSpeed()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证 WSP_FLAG (bit 25)
|
|
||||||
if !result.WspFlag.Value {
|
|
||||||
t.Error("WSP_FLAG should be 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证 WIND_H 和 WIND_L
|
|
||||||
if result.WspH.Value != 0x0 {
|
|
||||||
t.Errorf("WIND_H incorrect. Got %X, want 0", result.WspH.Value)
|
|
||||||
}
|
|
||||||
if result.WspL.Value != 0x6 {
|
|
||||||
t.Errorf("WIND_L incorrect. Got %X, want 6", result.WspL.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证完整值(9bit模式:000 + bit27 + WIND_H + WIND_L)
|
|
||||||
expectedRaw := uint16(0x006)
|
|
||||||
if result.Complete.RawValue != expectedRaw {
|
|
||||||
t.Errorf("Raw value incorrect. Got %X, want %X", result.Complete.RawValue, expectedRaw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证实际风速值:6/8*0.51 = 0.38250 m/s
|
|
||||||
expectedValue := float64(0x006) / 8.0 * 0.51
|
|
||||||
if result.Complete.Value != expectedValue {
|
|
||||||
t.Errorf("Wind speed value incorrect. Got %.5f m/s, want %.5f m/s",
|
|
||||||
result.Complete.Value, expectedValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetGustSpeed(t *testing.T) {
|
|
||||||
// 测试数据:24 36 F3 02 96 37 06 01 00 04 00 00 00 04 9C D3 9A 01 88 F5 7E 00 2A 9C F6
|
|
||||||
data := []byte{0x24, 0x36, 0xF3, 0x02, 0x96, 0x37, 0x06, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x9C, 0xD3, 0x9A, 0x01, 0x88, 0xF5, 0x7E, 0x00, 0x2A, 0x9C, 0xF6}
|
|
||||||
|
|
||||||
protocol := NewProtocol(data)
|
|
||||||
result, err := protocol.GetGustSpeed()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证 GUST_H (0x0)
|
|
||||||
expectedGustHBinary := "0000"
|
|
||||||
if result.GustH.Binary != expectedGustHBinary {
|
|
||||||
t.Errorf("GUST_H Binary incorrect. Got %s, want %s", result.GustH.Binary, expectedGustHBinary)
|
|
||||||
}
|
|
||||||
if result.GustH.Value != 0x0 {
|
|
||||||
t.Errorf("GUST_H Value incorrect. Got %X, want 0", result.GustH.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证 GUST_L (0x1)
|
|
||||||
expectedGustLBinary := "0001"
|
|
||||||
if result.GustL.Binary != expectedGustLBinary {
|
|
||||||
t.Errorf("GUST_L Binary incorrect. Got %s, want %s", result.GustL.Binary, expectedGustLBinary)
|
|
||||||
}
|
|
||||||
if result.GustL.Value != 0x1 {
|
|
||||||
t.Errorf("GUST_L Value incorrect. Got %X, want 1", result.GustL.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证完整值
|
|
||||||
expectedBinary := "00000001" // 0x01
|
|
||||||
if result.Complete.Binary != expectedBinary {
|
|
||||||
t.Errorf("Complete Binary incorrect. Got %s, want %s", result.Complete.Binary, expectedBinary)
|
|
||||||
}
|
|
||||||
if result.Complete.RawValue != 0x01 {
|
|
||||||
t.Errorf("Raw value incorrect. Got %X, want 01", result.Complete.RawValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证实际阵风速度值:0x01 = 1, 1*0.51 = 0.51 m/s
|
|
||||||
expectedValue := float64(0x01) * 0.51
|
|
||||||
if result.Complete.Value != expectedValue {
|
|
||||||
t.Errorf("Gust speed value incorrect. Got %.5f m/s, want %.5f m/s", result.Complete.Value, expectedValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetRainfall(t *testing.T) {
|
|
||||||
// 测试数据:24 36 F3 02 96 37 06 01 00 04 00 00 00 04 9C D3 9A 01 88 F5 7E 00 2A 9C F6
|
|
||||||
data := []byte{0x24, 0x36, 0xF3, 0x02, 0x96, 0x37, 0x06, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x9C, 0xD3, 0x9A, 0x01, 0x88, 0xF5, 0x7E, 0x00, 0x2A, 0x9C, 0xF6}
|
|
||||||
|
|
||||||
protocol := NewProtocol(data)
|
|
||||||
result, err := protocol.GetRainfall()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证 RAIN_HH (0x0)
|
|
||||||
expectedRainHHBinary := "0000"
|
|
||||||
if result.RainHH.Binary != expectedRainHHBinary {
|
|
||||||
t.Errorf("RAIN_HH Binary incorrect. Got %s, want %s", result.RainHH.Binary, expectedRainHHBinary)
|
|
||||||
}
|
|
||||||
if result.RainHH.Value != 0x0 {
|
|
||||||
t.Errorf("RAIN_HH Value incorrect. Got %X, want 0", result.RainHH.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证 RAIN_HL (0x0)
|
|
||||||
expectedRainHLBinary := "0000"
|
|
||||||
if result.RainHL.Binary != expectedRainHLBinary {
|
|
||||||
t.Errorf("RAIN_HL Binary incorrect. Got %s, want %s", result.RainHL.Binary, expectedRainHLBinary)
|
|
||||||
}
|
|
||||||
if result.RainHL.Value != 0x0 {
|
|
||||||
t.Errorf("RAIN_HL Value incorrect. Got %X, want 0", result.RainHL.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证 RAIN_LH (0x0)
|
|
||||||
expectedRainLHBinary := "0000"
|
|
||||||
if result.RainLH.Binary != expectedRainLHBinary {
|
|
||||||
t.Errorf("RAIN_LH Binary incorrect. Got %s, want %s", result.RainLH.Binary, expectedRainLHBinary)
|
|
||||||
}
|
|
||||||
if result.RainLH.Value != 0x0 {
|
|
||||||
t.Errorf("RAIN_LH Value incorrect. Got %X, want 0", result.RainLH.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证 RAIN_LL (0x4)
|
|
||||||
expectedRainLLBinary := "0100"
|
|
||||||
if result.RainLL.Binary != expectedRainLLBinary {
|
|
||||||
t.Errorf("RAIN_LL Binary incorrect. Got %s, want %s", result.RainLL.Binary, expectedRainLLBinary)
|
|
||||||
}
|
|
||||||
if result.RainLL.Value != 0x4 {
|
|
||||||
t.Errorf("RAIN_LL Value incorrect. Got %X, want 4", result.RainLL.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证完整值
|
|
||||||
expectedBinary := "0000000000000100" // 0x0004
|
|
||||||
if result.Complete.Binary != expectedBinary {
|
|
||||||
t.Errorf("Complete Binary incorrect. Got %s, want %s", result.Complete.Binary, expectedBinary)
|
|
||||||
}
|
|
||||||
if result.Complete.RawValue != 0x0004 {
|
|
||||||
t.Errorf("Raw value incorrect. Got %X, want 0004", result.Complete.RawValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证实际降雨量值:0x0004 = 4, 4*0.254 = 1.016 mm
|
|
||||||
expectedValue := float64(0x0004) * 0.254
|
|
||||||
if result.Complete.Value != expectedValue {
|
|
||||||
t.Errorf("Rainfall value incorrect. Got %.3f mm, want %.3f mm", result.Complete.Value, expectedValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetUVIndex(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
data []byte
|
|
||||||
expectedRaw uint16
|
|
||||||
expectedValue float64
|
|
||||||
expectedValid bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Valid UV Index",
|
|
||||||
// 测试数据:24 36 F3 02 96 37 06 01 00 04 00 00 00 04 9C D3 9A 01 88 F5 7E 00 2A 9C F6
|
|
||||||
data: []byte{0x24, 0x36, 0xF3, 0x02, 0x96, 0x37, 0x06, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x9C, 0xD3, 0x9A, 0x01, 0x88, 0xF5, 0x7E, 0x00, 0x2A, 0x9C, 0xF6},
|
|
||||||
expectedRaw: 0x0000, // byte10=0x00, byte11=0x00
|
|
||||||
expectedValue: 0, // 0 uW/c㎡
|
|
||||||
expectedValid: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid UV Index (Too High)",
|
|
||||||
data: []byte{0x24, 0x36, 0xF3, 0x02, 0x96, 0x37, 0x06, 0x01, 0x00, 0x04, 0xFF, 0xFF}, // 设置一个超出范围的值
|
|
||||||
expectedRaw: 0xFFFF, // 无效值
|
|
||||||
expectedValue: 0, // 无效时返回0
|
|
||||||
expectedValid: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
protocol := NewProtocol(tt.data)
|
|
||||||
result, err := protocol.GetUVIndex()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Complete.RawValue != tt.expectedRaw {
|
|
||||||
t.Errorf("Raw value incorrect. Got %X, want %X", result.Complete.RawValue, tt.expectedRaw)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Complete.Value != tt.expectedValue {
|
|
||||||
t.Errorf("UV Index value incorrect. Got %.1f uW/c㎡, want %.1f uW/c㎡",
|
|
||||||
result.Complete.Value, tt.expectedValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Complete.IsValid != tt.expectedValid {
|
|
||||||
t.Errorf("Validity incorrect. Got %v, want %v", result.Complete.IsValid, tt.expectedValid)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetLight(t *testing.T) {
|
|
||||||
// 测试数据:24 36 F3 02 96 37 06 01 00 04 00 00 00 04 9C D3 9A 01 88 F5 7E 00 2A 9C F6
|
|
||||||
data := []byte{0x24, 0x36, 0xF3, 0x02, 0x96, 0x37, 0x06, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x9C, 0xD3, 0x9A, 0x01, 0x88, 0xF5, 0x7E, 0x00, 0x2A, 0x9C, 0xF6}
|
|
||||||
|
|
||||||
protocol := NewProtocol(data)
|
|
||||||
result, err := protocol.GetLight()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证原始值 (byte13-15: 00 04 9C)
|
|
||||||
expectedRaw := uint32(0x00049C)
|
|
||||||
if result.Complete.RawValue != expectedRaw {
|
|
||||||
t.Errorf("Raw value incorrect. Got %X, want %X", result.Complete.RawValue, expectedRaw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证实际光照值:0x00049C = 1180, 1180/10 = 118.0 lux
|
|
||||||
expectedValue := float64(0x00049C) / 10.0
|
|
||||||
if result.Complete.Value != expectedValue {
|
|
||||||
t.Errorf("Light value incorrect. Got %.1f lux, want %.1f lux",
|
|
||||||
result.Complete.Value, expectedValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result.Complete.IsValid {
|
|
||||||
t.Error("Light value should be valid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetPressure(t *testing.T) {
|
|
||||||
// 测试数据:24 36 F3 02 96 37 06 01 00 04 00 00 00 04 9C D3 9A 01 88 F5 7E 00 2A 9C F6
|
|
||||||
data := []byte{0x24, 0x36, 0xF3, 0x02, 0x96, 0x37, 0x06, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x9C, 0xD3, 0x9A, 0x01, 0x88, 0xF5, 0x7E, 0x00, 0x2A, 0x9C, 0xF6}
|
|
||||||
|
|
||||||
protocol := NewProtocol(data)
|
|
||||||
result, err := protocol.GetPressure()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证原始值 (01 88 F5)
|
|
||||||
expectedRaw := uint32(0x0188F5)
|
|
||||||
if result.Complete.RawValue != expectedRaw {
|
|
||||||
t.Errorf("Raw value incorrect. Got %X, want %X", result.Complete.RawValue, expectedRaw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证实际气压值:0x0188F5 = 100597, 100597/100 = 1005.97 hPa
|
|
||||||
expectedValue := float64(0x0188F5) / 100.0
|
|
||||||
if result.Complete.Value != expectedValue {
|
|
||||||
t.Errorf("Pressure value incorrect. Got %.2f hPa, want %.2f hPa",
|
|
||||||
result.Complete.Value, expectedValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result.Complete.IsValid {
|
|
||||||
t.Error("Pressure value should be valid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseNewData(t *testing.T) {
|
|
||||||
// 新的测试数据:24 F2 30 02 AF 51 03 01 00 08 00 00 00 00 00 6E C2 01 82 D8 5B 00 29 87 EA
|
|
||||||
data := []byte{0x24, 0xF2, 0x30, 0x02, 0xAF, 0x51, 0x03, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0xC2, 0x01, 0x82, 0xD8, 0x5B, 0x00, 0x29, 0x87, 0xEA}
|
|
||||||
|
|
||||||
protocol := NewProtocol(data)
|
|
||||||
|
|
||||||
// 1. 解析风速
|
|
||||||
windSpeed, err := protocol.GetWindSpeed()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to parse wind speed: %v", err)
|
|
||||||
}
|
|
||||||
t.Logf("Wind Speed: %.5f m/s (raw: 0x%X)", windSpeed.Complete.Value, windSpeed.Complete.RawValue)
|
|
||||||
|
|
||||||
// 2. 解析阵风速度
|
|
||||||
gustSpeed, err := protocol.GetGustSpeed()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to parse gust speed: %v", err)
|
|
||||||
}
|
|
||||||
t.Logf("Gust Speed: %.5f m/s (raw: 0x%X)", gustSpeed.Complete.Value, gustSpeed.Complete.RawValue)
|
|
||||||
|
|
||||||
// 3. 解析温度
|
|
||||||
temp, err := protocol.GetTemperature()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to parse temperature: %v", err)
|
|
||||||
}
|
|
||||||
t.Logf("Temperature: %.2f °C (raw: 0x%X)", temp.Complete.Value, temp.Complete.RawValue)
|
|
||||||
|
|
||||||
// 4. 解析湿度
|
|
||||||
humidity, err := protocol.GetHumidity()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to parse humidity: %v", err)
|
|
||||||
}
|
|
||||||
t.Logf("Humidity: %d%% (raw: 0x%X)", humidity.Complete.Value, humidity.Complete.RawValue)
|
|
||||||
|
|
||||||
// 5. 解析光照
|
|
||||||
light, err := protocol.GetLight()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to parse light: %v", err)
|
|
||||||
}
|
|
||||||
t.Logf("Light: %.1f lux (raw: 0x%X)", light.Complete.Value, light.Complete.RawValue)
|
|
||||||
|
|
||||||
// 6. 解析大气压
|
|
||||||
pressure, err := protocol.GetPressure()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to parse pressure: %v", err)
|
|
||||||
}
|
|
||||||
t.Logf("Pressure: %.2f hPa (raw: 0x%X)", pressure.Complete.Value, pressure.Complete.RawValue)
|
|
||||||
|
|
||||||
// 7. 解析UV指数
|
|
||||||
uv, err := protocol.GetUVIndex()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to parse UV index: %v", err)
|
|
||||||
}
|
|
||||||
t.Logf("UV Index: %.1f uW/c㎡ (raw: 0x%X)", uv.Complete.Value, uv.Complete.RawValue)
|
|
||||||
|
|
||||||
// 8. 解析降雨量
|
|
||||||
rainfall, err := protocol.GetRainfall()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to parse rainfall: %v", err)
|
|
||||||
}
|
|
||||||
t.Logf("Rainfall: %.3f mm (raw: 0x%X)", rainfall.Complete.Value, rainfall.Complete.RawValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseNewDataWithDetails(t *testing.T) {
|
|
||||||
// 新的测试数据:24 F2 09 02 BA 4F 13 03 00 6A 02 33 04 13 5C AC FE 01 83 93 17 00 29 35 88
|
|
||||||
data := []byte{0x24, 0xF2, 0x09, 0x02, 0xBA, 0x4F, 0x13, 0x03, 0x00, 0x6A, 0x02, 0x33, 0x04, 0x13, 0x5C, 0xAC, 0xFE, 0x01, 0x83, 0x93, 0x17, 0x00, 0x29, 0x35, 0x88}
|
|
||||||
|
|
||||||
protocol := NewProtocol(data)
|
|
||||||
|
|
||||||
// 1. 风速解析
|
|
||||||
t.Log("\n=== 风速解析 ===")
|
|
||||||
windSpeed, _ := protocol.GetWindSpeed()
|
|
||||||
t.Logf("风速: %.5f m/s (raw: 0x%X)", windSpeed.Complete.Value, windSpeed.Complete.RawValue)
|
|
||||||
t.Logf("WSP_H: %s (0x%X), WSP_L: %s (0x%X)",
|
|
||||||
windSpeed.WspH.Binary, windSpeed.WspH.Value,
|
|
||||||
windSpeed.WspL.Binary, windSpeed.WspL.Value)
|
|
||||||
if windSpeed.Extend.Value != 0 {
|
|
||||||
t.Logf("WIND_Extend: %s (0x%X)", windSpeed.Extend.Binary, windSpeed.Extend.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 温度解析
|
|
||||||
t.Log("\n=== 温度解析 ===")
|
|
||||||
temp, _ := protocol.GetTemperature()
|
|
||||||
t.Logf("温度: %.2f °C (raw: 0x%X)", temp.Complete.Value, temp.Complete.RawValue)
|
|
||||||
t.Logf("TMP_H: %s (0x%X), TMP_M: %s (0x%X), TMP_L: %s (0x%X)",
|
|
||||||
temp.TmpH.Binary, temp.TmpH.Value,
|
|
||||||
temp.TmpM.Binary, temp.TmpM.Value,
|
|
||||||
temp.TmpL.Binary, temp.TmpL.Value)
|
|
||||||
|
|
||||||
// 3. 湿度解析
|
|
||||||
t.Log("\n=== 湿度解析 ===")
|
|
||||||
humidity, _ := protocol.GetHumidity()
|
|
||||||
t.Logf("湿度: %d%% (raw: 0x%X)", humidity.Complete.Value, humidity.Complete.RawValue)
|
|
||||||
t.Logf("HM_H: %s (0x%X), HM_L: %s (0x%X)",
|
|
||||||
humidity.HmH.Binary, humidity.HmH.Value,
|
|
||||||
humidity.HmL.Binary, humidity.HmL.Value)
|
|
||||||
|
|
||||||
// 4. 光照解析
|
|
||||||
t.Log("\n=== 光照解析 ===")
|
|
||||||
light, _ := protocol.GetLight()
|
|
||||||
t.Logf("光照: %.1f lux (raw: 0x%X)", light.Complete.Value, light.Complete.RawValue)
|
|
||||||
t.Logf("原始字节: %02X %02X %02X", data[12], data[13], data[14])
|
|
||||||
|
|
||||||
// 5. 大气压解析
|
|
||||||
t.Log("\n=== 大气压解析 ===")
|
|
||||||
pressure, _ := protocol.GetPressure()
|
|
||||||
t.Logf("大气压: %.2f hPa (raw: 0x%X)", pressure.Complete.Value, pressure.Complete.RawValue)
|
|
||||||
t.Logf("原始字节: %02X %02X %02X", data[17], data[18], data[19])
|
|
||||||
|
|
||||||
// 6. UV指数解析
|
|
||||||
t.Log("\n=== UV指数解析 ===")
|
|
||||||
uv, _ := protocol.GetUVIndex()
|
|
||||||
t.Logf("UV指数: %.1f uW/c㎡ (raw: 0x%X)", uv.Complete.Value, uv.Complete.RawValue)
|
|
||||||
t.Logf("原始字节: %02X %02X", data[10], data[11])
|
|
||||||
|
|
||||||
// 7. 降雨量解析
|
|
||||||
t.Log("\n=== 降雨量解析 ===")
|
|
||||||
rainfall, _ := protocol.GetRainfall()
|
|
||||||
t.Logf("降雨量: %.3f mm (raw: 0x%X)", rainfall.Complete.Value, rainfall.Complete.RawValue)
|
|
||||||
t.Logf("原始字节: %02X %02X", data[8], data[9])
|
|
||||||
|
|
||||||
// 8. 阵风速度解析
|
|
||||||
t.Log("\n=== 阵风速度解析 ===")
|
|
||||||
gust, _ := protocol.GetGustSpeed()
|
|
||||||
t.Logf("阵风速度: %.2f m/s (raw: 0x%X)", gust.Complete.Value, gust.Complete.RawValue)
|
|
||||||
t.Logf("原始字节: %02X", data[7])
|
|
||||||
|
|
||||||
// 9. 风向解析
|
|
||||||
t.Log("\n=== 风向解析 ===")
|
|
||||||
windDir, _ := protocol.GetWindDirection()
|
|
||||||
t.Logf("风向: %.1f° (raw: 0x%X)", windDir.Complete.Degree, windDir.Complete.Value)
|
|
||||||
t.Logf("原始字节: %02X %02X", data[2], data[3])
|
|
||||||
|
|
||||||
// 10. 设备ID解析
|
|
||||||
t.Log("\n=== 设备ID解析 ===")
|
|
||||||
id, _ := protocol.GetCompleteID()
|
|
||||||
t.Logf("设备ID: %02X %02X %02X", id.HSB.Dec, id.MSB.Dec, id.LSB.Dec)
|
|
||||||
t.Logf("原始字节: HSB=%02X, MSB=%02X, LSB=%02X", data[21], data[22], data[1])
|
|
||||||
}
|
|
||||||
@ -5,19 +5,9 @@ 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
|
||||||
@ -49,37 +39,24 @@ type WeatherData struct {
|
|||||||
|
|
||||||
var urlRegex = regexp.MustCompile(`/weatherstation/updateweatherstation\.php\?([^&\s]+(&[^&\s]+)*)`)
|
var urlRegex = regexp.MustCompile(`/weatherstation/updateweatherstation\.php\?([^&\s]+(&[^&\s]+)*)`)
|
||||||
|
|
||||||
// ParseData 根据数据类型解析气象数据
|
func ParseWeatherData(data string) (*WeatherData, error) {
|
||||||
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")
|
||||||
|
|
||||||
@ -174,48 +151,6 @@ func ParseWIFIWeatherData(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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user