weather-station/main.go

307 lines
7.3 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 {
// 解析RS485数据
hexStr := strings.ReplaceAll(hexDump, "\n", " ")
parseHexData(hexStr)
// 注册设备
protocol := model.NewProtocol(rawData)
idParts, err := protocol.GetCompleteID()
if err == nil {
stationID := fmt.Sprintf("RS485-%s", idParts.Complete.Hex)
model.RegisterDevice(stationID, addr)
log.Printf("设备 %s 已注册IP: %s", stationID, addr.String())
}
} 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")
}
}
}
}
// 暂时不保存到数据库
// err = model.SaveWeatherData(data, string(rawData))
// if err != nil {
// log.Printf("保存数据到数据库失败: %v", err)
// } else {
// log.Printf("数据已成功保存到数据库")
// }
}
}
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
}
// 检查数据有效性
if !model.ValidateRS485Data(data) {
log.Printf("无效的RS485数据格式: 长度=%d, 起始字节=%02X", len(data), data[0])
return
}
// 创建协议解析器
protocol := model.NewProtocol(data)
rs485Protocol := model.NewRS485Protocol(data)
// 获取设备ID
idParts, err := protocol.GetCompleteID()
if err != nil {
log.Printf("获取设备ID失败: %v", err)
return
}
// 解析RS485数据
rs485Data, err := rs485Protocol.ParseRS485Data()
if err != nil {
log.Printf("解析RS485数据失败: %v", err)
return
}
// 添加设备ID和时间戳
rs485Data.DeviceID = idParts.Complete.Hex
rs485Data.ReceivedAt = time.Now()
rs485Data.RawDataHex = fmt.Sprintf("%X", data)
// 打印解析结果
log.Println("=== RS485数据解析结果 ===")
log.Printf("原始数据: %s", hexStr)
log.Printf("设备ID: %s (HSB=%s, MSB=%s, LSB=%s)",
idParts.Complete.Hex,
idParts.HSB.Hex,
idParts.MSB.Hex,
idParts.LSB.Hex)
log.Printf("温度: %.2f°C", rs485Data.Temperature)
log.Printf("湿度: %.1f%%", rs485Data.Humidity)
log.Printf("风向: %.1f°", rs485Data.WindDirection)
log.Printf("风速: %.2f m/s", rs485Data.WindSpeed)
log.Printf("降雨量: %.2f mm", rs485Data.Rainfall)
log.Printf("光照: %.2f lux", rs485Data.Light)
log.Printf("紫外线: %.2f", rs485Data.UV)
log.Printf("气压: %.2f hPa", rs485Data.Pressure)
}