weather-station/main.go

444 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"bufio"
"encoding/hex"
"fmt"
"io"
"log"
"net"
"os"
"path/filepath"
"strings"
"sync"
"time"
"unicode/utf8"
"weatherstation/config"
"weatherstation/model"
)
type UTF8Writer struct {
w io.Writer
}
func NewUTF8Writer(w io.Writer) *UTF8Writer {
return &UTF8Writer{w: w}
}
func (w *UTF8Writer) Write(p []byte) (n int, err error) {
if utf8.Valid(p) {
return w.w.Write(p)
}
s := string(p)
s = strings.ToValidUTF8(s, "")
return w.w.Write([]byte(s))
}
var (
logFile *os.File
logFileMutex sync.Mutex
currentLogDay int
)
func getLogFileName() string {
currentTime := time.Now()
return filepath.Join("log", fmt.Sprintf("%s.log", currentTime.Format("2006-01-02")))
}
func openLogFile() (*os.File, error) {
logDir := "log"
if _, err := os.Stat(logDir); os.IsNotExist(err) {
os.MkdirAll(logDir, 0755)
}
logFileName := getLogFileName()
return os.OpenFile(logFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
}
func setupLogger() {
var err error
logFile, err = openLogFile()
if err != nil {
log.Fatalf("无法创建日志文件: %v", err)
}
currentLogDay = time.Now().Day()
bufferedWriter := bufio.NewWriter(logFile)
utf8Writer := NewUTF8Writer(bufferedWriter)
go func() {
for {
time.Sleep(1 * time.Second)
logFileMutex.Lock()
bufferedWriter.Flush()
now := time.Now()
if now.Day() != currentLogDay {
oldLogFile := logFile
logFile, err = openLogFile()
if err != nil {
log.Printf("无法创建新日志文件: %v", err)
} else {
oldLogFile.Close()
currentLogDay = now.Day()
bufferedWriter = bufio.NewWriter(logFile)
utf8Writer = NewUTF8Writer(bufferedWriter)
log.SetOutput(io.MultiWriter(os.Stdout, utf8Writer))
log.Println("日志文件已轮转")
}
}
logFileMutex.Unlock()
}
}()
multiWriter := io.MultiWriter(os.Stdout, utf8Writer)
log.SetOutput(multiWriter)
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
}
func startUDP() {
cfg := config.GetConfig()
err := model.InitDB()
if err != nil {
log.Fatalf("初始化数据库失败: %v", err)
}
defer model.CloseDB()
addr := fmt.Sprintf(":%d", cfg.Server.UDPPort)
conn, err := net.ListenPacket("udp", addr)
if err != nil {
log.Fatalf("无法监听UDP端口 %d: %v", cfg.Server.UDPPort, err)
}
defer conn.Close()
log.Printf("UDP服务器已启动监听端口 %d...", cfg.Server.UDPPort)
buffer := make([]byte, 2048)
for {
n, addr, err := conn.ReadFrom(buffer)
if err != nil {
log.Printf("读取数据错误: %v", err)
continue
}
rawData := buffer[:n]
log.Printf("从 %s 接收到 %d 字节数据", addr.String(), n)
hexDump := hexDump(rawData)
log.Printf("原始码流(十六进制):\n%s", hexDump)
asciiDump := asciiDump(rawData)
log.Printf("ASCII码:\n%s", asciiDump)
// 检查数据是否为RS485格式
if len(rawData) == 25 && rawData[0] == 0x24 {
log.Println("=== 检测到RS485设备数据 ===")
// 生成源码字符串(用于日志记录)
sourceHex := strings.ReplaceAll(strings.TrimSpace(hexDump), "\n", " ")
log.Printf("源码: %s", sourceHex)
// 解析RS485数据
protocol := model.NewProtocol(rawData)
rs485Protocol := model.NewRS485Protocol(rawData)
// 获取设备ID
idParts, err := protocol.GetCompleteID()
if err != nil {
log.Printf("获取设备ID失败: %v", err)
continue
}
// 解析RS485数据
rs485Data, err := rs485Protocol.ParseRS485Data()
if err != nil {
log.Printf("解析RS485数据失败: %v", err)
continue
}
// 添加设备ID和时间戳
rs485Data.DeviceID = idParts.Complete.Hex
rs485Data.ReceivedAt = time.Now()
rs485Data.RawDataHex = sourceHex
// 打印解析结果到日志
log.Println("=== RS485解析结果 ===")
log.Printf("设备ID: RS485-%s", rs485Data.DeviceID)
log.Printf("温度: %.2f°C", rs485Data.Temperature)
log.Printf("湿度: %.1f%%", rs485Data.Humidity)
log.Printf("风速: %.5f m/s", rs485Data.WindSpeed)
log.Printf("风向: %.1f°", rs485Data.WindDirection)
log.Printf("降雨量: %.3f mm", rs485Data.Rainfall)
log.Printf("光照: %.1f lux", rs485Data.Light)
log.Printf("紫外线: %.1f", rs485Data.UV)
log.Printf("气压: %.2f hPa", rs485Data.Pressure)
log.Printf("接收时间: %s", rs485Data.ReceivedAt.Format("2006-01-02 15:04:05"))
// 注册设备
stationID := fmt.Sprintf("RS485-%s", rs485Data.DeviceID)
model.RegisterDevice(stationID, addr)
log.Printf("设备 %s 已注册IP: %s", stationID, addr.String())
// 可选:保存到数据库
// err = model.SaveWeatherData(rs485Data, string(rawData))
// if err != nil {
// log.Printf("保存数据到数据库失败: %v", err)
// } else {
// log.Printf("数据已成功保存到数据库")
// }
} else {
// 尝试解析WIFI数据
data, deviceType, err := model.ParseData(rawData)
if err != nil {
log.Printf("解析数据失败: %v", err)
continue
}
log.Println("成功解析气象站数据:")
log.Printf("设备类型: %s", getDeviceTypeString(deviceType))
log.Println(data)
if deviceType == model.DeviceTypeWIFI {
if wifiData, ok := data.(*model.WeatherData); ok {
stationID := wifiData.StationID
if stationID != "" {
model.RegisterDevice(stationID, addr)
log.Printf("设备 %s 已注册IP: %s", stationID, addr.String())
} else {
log.Printf("警告: 收到的数据没有站点ID")
}
}
}
}
}
}
func getDeviceTypeString(deviceType model.DeviceType) string {
switch deviceType {
case model.DeviceTypeWIFI:
return "WIFI"
case model.DeviceTypeRS485:
return "RS485"
default:
return "未知"
}
}
func main() {
// 检查是否有命令行参数
if len(os.Args) > 1 && os.Args[1] == "parse" {
if len(os.Args) > 2 {
// 解析指定的十六进制数据
hexData := os.Args[2]
parseHexData(hexData)
} else {
fmt.Println("用法: ./weatherstation parse <十六进制数据>")
fmt.Println("示例: ./weatherstation parse \"24 F2 10 02 C7 48 10 03 00 6A 03 E8 05 F5 96 10 3F 01 83 2D B1 00 29 9B A4\"")
}
} else {
// 正常启动服务器
setupLogger()
startUDP()
}
}
func hexDump(data []byte) string {
var result strings.Builder
for i := 0; i < len(data); i += 16 {
end := i + 16
if end > len(data) {
end = len(data)
}
chunk := data[i:end]
hexStr := hex.EncodeToString(chunk)
for j := 0; j < len(hexStr); j += 2 {
if j+2 <= len(hexStr) {
result.WriteString(strings.ToUpper(hexStr[j : j+2]))
result.WriteString(" ")
}
}
result.WriteString("\n")
}
return result.String()
}
func asciiDump(data []byte) string {
var result strings.Builder
for i := 0; i < len(data); i += 64 {
end := i + 64
if end > len(data) {
end = len(data)
}
chunk := data[i:end]
for _, b := range chunk {
if b >= 32 && b <= 126 {
result.WriteByte(b)
} else {
result.WriteString(".")
}
}
result.WriteString("\n")
}
return result.String()
}
// parseHexData 解析十六进制字符串数据
func parseHexData(hexStr string) {
// 移除所有空格
hexStr = strings.ReplaceAll(hexStr, " ", "")
// 将十六进制字符串转换为字节数组
data, err := hex.DecodeString(hexStr)
if err != nil {
log.Printf("解析十六进制字符串失败: %v", err)
return
}
// 打印原始数据
log.Println("=== 原始数据分析 ===")
log.Printf("输入的十六进制字符串: %s", hexStr)
log.Printf("解析后的字节数组长度: %d", len(data))
log.Printf("字节数组内容: %v", data)
// 按索引打印每个字节
for i, b := range data {
log.Printf("索引[%2d]: 0x%02X (%d)", i, b, b)
}
// 检查数据有效性
if !model.ValidateRS485Data(data) {
log.Printf("无效的RS485数据格式: 长度=%d, 起始字节=%02X", len(data), data[0])
return
}
log.Println("\n=== 使用Protocol.go标准解析 ===")
// 创建协议解析器
protocol := model.NewProtocol(data)
rs485Protocol := model.NewRS485Protocol(data)
// 1. 解析设备ID
log.Println("--- 设备ID解析 ---")
idParts, err := protocol.GetCompleteID()
if err != nil {
log.Printf("获取设备ID失败: %v", err)
return
}
log.Printf("HSB (索引21): 0x%02X = %d", data[21], data[21])
log.Printf("MSB (索引22): 0x%02X = %d", data[22], data[22])
log.Printf("LSB (索引1): 0x%02X = %d", data[1], data[1])
log.Printf("完整设备ID: %s", idParts.Complete.Hex)
// 2. 解析温度
log.Println("--- 温度解析 ---")
temp, err := protocol.GetTemperature()
if err != nil {
log.Printf("获取温度失败: %v", err)
} else {
log.Printf("TMP_H (bit29-31): %s (0x%X)", temp.TmpH.Binary, temp.TmpH.Value)
log.Printf("TMP_M (bit32-35): %s (0x%X)", temp.TmpM.Binary, temp.TmpM.Value)
log.Printf("TMP_L (bit36-39): %s (0x%X)", temp.TmpL.Binary, temp.TmpL.Value)
log.Printf("原始值: 0x%X = %d", temp.Complete.RawValue, temp.Complete.RawValue)
log.Printf("温度: %.2f°C", temp.Complete.Value)
}
// 3. 解析湿度
log.Println("--- 湿度解析 ---")
humidity, err := protocol.GetHumidity()
if err != nil {
log.Printf("获取湿度失败: %v", err)
} else {
log.Printf("HM_H (bit40-43): %s (0x%X)", humidity.HmH.Binary, humidity.HmH.Value)
log.Printf("HM_L (bit44-47): %s (0x%X)", humidity.HmL.Binary, humidity.HmL.Value)
log.Printf("原始值: 0x%02X = %d", humidity.Complete.RawValue, humidity.Complete.RawValue)
log.Printf("湿度: %d%%", humidity.Complete.Value)
}
// 4. 解析风速
log.Println("--- 风速解析 ---")
windSpeed, err := protocol.GetWindSpeed()
if err != nil {
log.Printf("获取风速失败: %v", err)
} else {
log.Printf("WSP_FLAG: %v", windSpeed.WspFlag.Value)
log.Printf("WSP_H (bit48-51): %s (0x%X)", windSpeed.WspH.Binary, windSpeed.WspH.Value)
log.Printf("WSP_L (bit52-55): %s (0x%X)", windSpeed.WspL.Binary, windSpeed.WspL.Value)
log.Printf("原始值: 0x%X = %d", windSpeed.Complete.RawValue, windSpeed.Complete.RawValue)
log.Printf("风速: %.5f m/s", windSpeed.Complete.Value)
}
// 5. 解析风向
log.Println("--- 风向解析 ---")
windDir, err := protocol.GetWindDirection()
if err != nil {
log.Printf("获取风向失败: %v", err)
} else {
log.Printf("DIR_H: %s (0x%X)", windDir.DirH.Binary, windDir.DirH.Value)
log.Printf("DIR_M: %s (0x%X)", windDir.DirM.Binary, windDir.DirM.Value)
log.Printf("DIR_L: %s (0x%X)", windDir.DirL.Binary, windDir.DirL.Value)
log.Printf("原始值: 0x%X = %d", windDir.Complete.Value, windDir.Complete.Value)
log.Printf("风向: %.1f°", windDir.Complete.Degree)
}
// 6. 解析降雨量
log.Println("--- 降雨量解析 ---")
rainfall, err := protocol.GetRainfall()
if err != nil {
log.Printf("获取降雨量失败: %v", err)
} else {
log.Printf("原始值: 0x%X = %d", rainfall.Complete.RawValue, rainfall.Complete.RawValue)
log.Printf("降雨量: %.3f mm", rainfall.Complete.Value)
}
// 7. 解析光照
log.Println("--- 光照解析 ---")
light, err := protocol.GetLight()
if err != nil {
log.Printf("获取光照失败: %v", err)
} else {
log.Printf("原始值: 0x%X = %d", light.Complete.RawValue, light.Complete.RawValue)
log.Printf("光照: %.1f lux", light.Complete.Value)
}
// 8. 解析UV指数
log.Println("--- UV指数解析 ---")
uv, err := protocol.GetUVIndex()
if err != nil {
log.Printf("获取UV指数失败: %v", err)
} else {
log.Printf("原始值: 0x%X = %d", uv.Complete.RawValue, uv.Complete.RawValue)
log.Printf("UV指数: %.1f uW/c㎡", uv.Complete.Value)
}
// 9. 解析气压
log.Println("--- 气压解析 ---")
pressure, err := protocol.GetPressure()
if err != nil {
log.Printf("获取气压失败: %v", err)
} else {
log.Printf("原始值: 0x%X = %d", pressure.Complete.RawValue, pressure.Complete.RawValue)
log.Printf("气压: %.2f hPa", pressure.Complete.Value)
}
log.Println("\n=== RS485协议统一解析结果 ===")
// 使用修正后的RS485解析
rs485Data, err := rs485Protocol.ParseRS485Data()
if err != nil {
log.Printf("解析RS485数据失败: %v", err)
return
} else {
rs485Data.DeviceID = idParts.Complete.Hex
rs485Data.ReceivedAt = time.Now()
rs485Data.RawDataHex = fmt.Sprintf("%X", data)
log.Printf("设备ID: RS485-%s", rs485Data.DeviceID)
log.Printf("温度: %.2f°C", rs485Data.Temperature)
log.Printf("湿度: %.1f%%", rs485Data.Humidity)
log.Printf("风速: %.5f m/s", rs485Data.WindSpeed)
log.Printf("风向: %.1f°", rs485Data.WindDirection)
log.Printf("降雨量: %.3f mm", rs485Data.Rainfall)
log.Printf("光照: %.1f lux", rs485Data.Light)
log.Printf("紫外线: %.1f", rs485Data.UV)
log.Printf("气压: %.2f hPa", rs485Data.Pressure)
}
}