From a2d0bc8226bb1ca45e4f50521eac0c6dc30a152f Mon Sep 17 00:00:00 2001 From: fengyarnom Date: Sat, 24 May 2025 13:13:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E7=9A=84=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db.go | 40 ++-- tcp_server.go | 528 ++++++++++++++++++++++++------------------- templates/index.html | 30 +-- udp_server.go | 8 +- 4 files changed, 335 insertions(+), 271 deletions(-) diff --git a/db.go b/db.go index 0652e0a..f1e8f15 100644 --- a/db.go +++ b/db.go @@ -48,20 +48,22 @@ func CloseDB() { } } -// 保存传感器数据 - 将浮点值转换为整数存储 -func SaveSensorData(sensorID int, x, y, z float64) error { +// 保存传感器数据 - 将浮点值转换为整数存储,添加温度支持 +func SaveSensorData(sensorID int, x, y, z, temperature float64) error { xInt := int(x * SCALING_FACTOR) yInt := int(y * SCALING_FACTOR) zInt := int(z * SCALING_FACTOR) + tempInt := int(temperature * SCALING_FACTOR) - query := `INSERT INTO sensor_data (sensor_id, x_value, y_value, z_value) VALUES (?, ?, ?, ?)` - _, err := db.Exec(query, sensorID, xInt, yInt, zInt) + query := `INSERT INTO sensor_data (sensor_id, x_value, y_value, z_value, temperature) VALUES (?, ?, ?, ?, ?)` + _, err := db.Exec(query, sensorID, xInt, yInt, zInt, tempInt) return err } -// 获取传感器数据 - 添加时间范围 +// 获取传感器数据 - 添加时间范围,包含温度字段 func GetSensorData(sensorID int, limit int, startDate time.Time, endDate time.Time) ([]SensorData, error) { query := `SELECT id, sensor_id, x_value, y_value, z_value, + COALESCE(temperature, 0) as temperature, timestamp as timestamp FROM sensor_data WHERE sensor_id = ?` @@ -97,9 +99,9 @@ func GetSensorData(sensorID int, limit int, startDate time.Time, endDate time.Ti for rows.Next() { var data SensorData - var xInt, yInt, zInt int + var xInt, yInt, zInt, tempInt int - err := rows.Scan(&data.ID, &data.SensorID, &xInt, &yInt, &zInt, &data.Timestamp) + err := rows.Scan(&data.ID, &data.SensorID, &xInt, &yInt, &zInt, &tempInt, &data.Timestamp) if err != nil { return nil, err } @@ -107,6 +109,7 @@ func GetSensorData(sensorID int, limit int, startDate time.Time, endDate time.Ti data.X = float64(xInt) / SCALING_FACTOR data.Y = float64(yInt) / SCALING_FACTOR data.Z = float64(zInt) / SCALING_FACTOR + data.Temperature = float64(tempInt) / SCALING_FACTOR result = append(result, data) } @@ -114,9 +117,10 @@ func GetSensorData(sensorID int, limit int, startDate time.Time, endDate time.Ti return result, nil } -// 获取所有传感器数据 +// 获取所有传感器数据,包含温度字段 func GetAllSensorData(limit int, startDate time.Time, endDate time.Time) ([]SensorData, error) { query := `SELECT id, sensor_id, x_value, y_value, z_value, + COALESCE(temperature, 0) as temperature, timestamp as timestamp FROM sensor_data WHERE 1=1` @@ -151,9 +155,9 @@ func GetAllSensorData(limit int, startDate time.Time, endDate time.Time) ([]Sens for rows.Next() { var data SensorData - var xInt, yInt, zInt int + var xInt, yInt, zInt, tempInt int - err := rows.Scan(&data.ID, &data.SensorID, &xInt, &yInt, &zInt, &data.Timestamp) + err := rows.Scan(&data.ID, &data.SensorID, &xInt, &yInt, &zInt, &tempInt, &data.Timestamp) if err != nil { return nil, err } @@ -161,6 +165,7 @@ func GetAllSensorData(limit int, startDate time.Time, endDate time.Time) ([]Sens data.X = float64(xInt) / SCALING_FACTOR data.Y = float64(yInt) / SCALING_FACTOR data.Z = float64(zInt) / SCALING_FACTOR + data.Temperature = float64(tempInt) / SCALING_FACTOR result = append(result, data) } @@ -190,12 +195,13 @@ func GetAllSensorIDs() ([]int, error) { return ids, nil } -// SensorData 结构用于存储传感器数据 +// SensorData 结构用于存储传感器数据,添加温度字段 type SensorData struct { - ID int `json:"id"` - SensorID int `json:"sensor_id"` - X float64 `json:"x"` - Y float64 `json:"y"` - Z float64 `json:"z"` - Timestamp time.Time `json:"timestamp"` + ID int `json:"id"` + SensorID int `json:"sensor_id"` + X float64 `json:"x"` + Y float64 `json:"y"` + Z float64 `json:"z"` + Temperature float64 `json:"temperature"` + Timestamp time.Time `json:"timestamp"` } diff --git a/tcp_server.go b/tcp_server.go index 5a9aaa4..f7437d4 100644 --- a/tcp_server.go +++ b/tcp_server.go @@ -1,236 +1,292 @@ -package main - -import ( - "fmt" - "io" - "net" - "regexp" - "strconv" - "strings" - "sync" - "time" -) - -// 客户端信息结构 -type ClientInfo struct { - IP string // IP地址 - Port string // 端口 - LastSeen time.Time // 最后活跃时间 -} - -// 客户端列表(使用互斥锁保护的映射) -var ( - clientsMutex sync.Mutex - clients = make(map[string]*ClientInfo) -) - -// StartTCPServer 启动TCP服务器 -func StartTCPServer(address string) error { - listener, err := net.Listen("tcp", address) - if err != nil { - return err - } - - startClientCleanup() - - Logger.Printf("TCP服务器已启动,正在监听 %s\n", address) - - for { - conn, err := listener.Accept() - if err != nil { - Logger.Printf("接受连接失败: %v", err) - continue - } - - go handleConnection(conn) - } -} - -// handleConnection 处理客户端连接 -func handleConnection(conn net.Conn) { - defer conn.Close() - - remoteAddr := conn.RemoteAddr().String() - Logger.Printf("新的客户端连接: %s", remoteAddr) - - addClient(remoteAddr) - - buffer := make([]byte, 1024) - - for { - n, err := conn.Read(buffer) - if err != nil { - if err != io.EOF { - Logger.Printf("从客户端读取失败 %s: %v", remoteAddr, err) - } else { - Logger.Printf("客户端断开连接 %s", remoteAddr) - } - removeClient(remoteAddr) - break - } - - rawData := string(buffer[:n]) - TCPDataLogger.Printf("从客户端 %s 接收到原始数据: %s", remoteAddr, rawData) - - sensorID, x, y, z, err := parseData(rawData) - - if err == nil { - TCPDataLogger.Printf("解析成功 - 客户端: %s, 传感器ID: %d, 值: X=%.3f, Y=%.3f, Z=%.3f", - remoteAddr, sensorID, x, y, z) - - if err := SaveSensorData(sensorID, x, y, z); err != nil { - Logger.Printf("保存传感器数据失败: %v", err) - } - } else { - TCPDataLogger.Printf("无法解析从客户端 %s 接收到的数据: %s, 错误: %v", remoteAddr, rawData, err) - } - - resp := "OK\n" - if _, err := conn.Write([]byte(resp)); err != nil { - Logger.Printf("发送响应到客户端 %s 失败: %v", remoteAddr, err) - removeClient(remoteAddr) - break - } - - updateClientLastSeen(remoteAddr) - } -} - -// parseData 使用正则表达式解析传感器数据 -func parseData(data string) (int, float64, float64, float64, error) { - pattern := regexp.MustCompile(`(\d+):([-]?\d+\.\d+),\s*([-]?\d+\.\d+),\s*([-]?\d+\.\d+)`) - matches := pattern.FindStringSubmatch(data) - - if len(matches) != 5 { - return 0, 0, 0, 0, fmt.Errorf("数据格式不正确: %s", data) - } - - sensorID, err := strconv.Atoi(matches[1]) - if err != nil { - return 0, 0, 0, 0, fmt.Errorf("解析传感器ID失败: %v", err) - } - - x, err := strconv.ParseFloat(strings.TrimSpace(matches[2]), 64) - if err != nil { - return 0, 0, 0, 0, fmt.Errorf("解析X值失败: %v", err) - } - - y, err := strconv.ParseFloat(strings.TrimSpace(matches[3]), 64) - if err != nil { - return 0, 0, 0, 0, fmt.Errorf("解析Y值失败: %v", err) - } - - z, err := strconv.ParseFloat(strings.TrimSpace(matches[4]), 64) - if err != nil { - return 0, 0, 0, 0, fmt.Errorf("解析Z值失败: %v", err) - } - - return sensorID, x, y, z, nil -} - -// addClient 添加客户端 -func addClient(addr string) { - clientsMutex.Lock() - defer clientsMutex.Unlock() - - host, port, err := net.SplitHostPort(addr) - if err != nil { - Logger.Printf("解析客户端地址失败 %s: %v", addr, err) - host = addr - port = "unknown" - } - - clients[addr] = &ClientInfo{ - IP: host, - Port: port, - LastSeen: time.Now(), - } - - Logger.Printf("添加新客户端: %s", addr) -} - -// updateClientLastSeen 更新客户端最后活跃时间 -func updateClientLastSeen(addr string) { - clientsMutex.Lock() - defer clientsMutex.Unlock() - - if client, exists := clients[addr]; exists { - client.LastSeen = time.Now() - } -} - -// removeClient 移除客户端 -func removeClient(addr string) { - clientsMutex.Lock() - defer clientsMutex.Unlock() - - if client, exists := clients[addr]; exists { - client.LastSeen = time.Now() - Logger.Printf("客户端标记为断开连接: %s", addr) - } -} - -// getAllClients 获取所有客户端信息 -func getAllClients() []map[string]interface{} { - clientsMutex.Lock() - defer clientsMutex.Unlock() - - now := time.Now() - result := make([]map[string]interface{}, 0, len(clients)) - - for addr, client := range clients { - lastSeenDuration := now.Sub(client.LastSeen) - - if lastSeenDuration > 24*time.Hour { - delete(clients, addr) - continue - } - - isOnline := lastSeenDuration < 10*time.Minute - - result = append(result, map[string]interface{}{ - "address": addr, - "ip": client.IP, - "port": client.Port, - "lastSeen": client.LastSeen, - "isOnline": isOnline, - "lastSeenFormatted": formatDuration(lastSeenDuration), - }) - } - - return result -} - -// formatDuration 格式化持续时间为友好的字符串 -func formatDuration(d time.Duration) string { - if d < time.Minute { - return "刚刚" - } else if d < time.Hour { - return fmt.Sprintf("%d分钟前", int(d.Minutes())) - } else if d < 24*time.Hour { - return fmt.Sprintf("%d小时前", int(d.Hours())) - } else { - return fmt.Sprintf("%d天前", int(d.Hours()/24)) - } -} - -// startClientCleanup 启动清理过期客户端的goroutine -func startClientCleanup() { - go func() { - for { - time.Sleep(1 * time.Hour) // 每小时检查一次 - - clientsMutex.Lock() - now := time.Now() - - for addr, client := range clients { - if now.Sub(client.LastSeen) > 24*time.Hour { - delete(clients, addr) - Logger.Printf("移除过期客户端: %s", addr) - } - } - - clientsMutex.Unlock() - } - }() -} +package main + +import ( + "fmt" + "io" + "net" + "regexp" + "strconv" + "strings" + "sync" + "time" +) + +// 客户端信息结构 +type ClientInfo struct { + IP string // IP地址 + Port string // 端口 + LastSeen time.Time // 最后活跃时间 +} + +// 客户端列表(使用互斥锁保护的映射) +var ( + clientsMutex sync.Mutex + clients = make(map[string]*ClientInfo) +) + +// StartTCPServer 启动TCP服务器 +func StartTCPServer(address string) error { + listener, err := net.Listen("tcp", address) + if err != nil { + return err + } + + startClientCleanup() + + Logger.Printf("TCP服务器已启动,正在监听 %s\n", address) + + for { + conn, err := listener.Accept() + if err != nil { + Logger.Printf("接受连接失败: %v", err) + continue + } + + go handleConnection(conn) + } +} + +// handleConnection 处理客户端连接 +func handleConnection(conn net.Conn) { + defer conn.Close() + + remoteAddr := conn.RemoteAddr().String() + Logger.Printf("新的客户端连接: %s", remoteAddr) + + addClient(remoteAddr) + + // 启动定时发送指令的goroutine + go sendPeriodicCommand(conn, remoteAddr) + + buffer := make([]byte, 1024) + + for { + n, err := conn.Read(buffer) + if err != nil { + if err != io.EOF { + Logger.Printf("从客户端读取失败 %s: %v", remoteAddr, err) + } else { + Logger.Printf("客户端断开连接 %s", remoteAddr) + } + removeClient(remoteAddr) + break + } + + rawData := string(buffer[:n]) + TCPDataLogger.Printf("从客户端 %s 接收到原始数据: %s", remoteAddr, rawData) + + sensorID, x, y, z, temperature, err := parseData(rawData) + + if err == nil { + TCPDataLogger.Printf("解析成功 - 客户端: %s, 传感器ID: %d, 值: X=%.3f, Y=%.3f, Z=%.3f, 温度=%.1f°C", + remoteAddr, sensorID, x, y, z, temperature) + + if err := SaveSensorData(sensorID, x, y, z, temperature); err != nil { + Logger.Printf("保存传感器数据失败: %v", err) + } + } else { + TCPDataLogger.Printf("无法解析从客户端 %s 接收到的数据: %s, 错误: %v", remoteAddr, rawData, err) + } + + resp := "OK\n" + if _, err := conn.Write([]byte(resp)); err != nil { + Logger.Printf("发送响应到客户端 %s 失败: %v", remoteAddr, err) + removeClient(remoteAddr) + break + } + + updateClientLastSeen(remoteAddr) + } +} + +// sendPeriodicCommand 每10秒发送一次查询指令 +func sendPeriodicCommand(conn net.Conn, remoteAddr string) { + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + command := "@1602301014A*0!\n" + if _, err := conn.Write([]byte(command)); err != nil { + Logger.Printf("发送定时指令到客户端 %s 失败: %v", remoteAddr, err) + return // 连接断开,退出goroutine + } + TCPDataLogger.Printf("发送定时指令到客户端 %s: %s", remoteAddr, strings.TrimSpace(command)) + } + } +} + +// parseData 使用正则表达式解析传感器数据,支持新格式 #{1602301014-01,1,1,28.4,-6.884,1.540}! +func parseData(data string) (int, float64, float64, float64, float64, error) { + // 尝试解析新格式: #{1602301014-01,1,1,28.4,-6.884,1.540}! + newPattern := regexp.MustCompile(`#\{[^,]+-(\d+),\d+,(\d+),([-]?\d+\.\d+),([-]?\d+\.\d+),([-]?\d+\.\d+)\}!`) + matches := newPattern.FindStringSubmatch(data) + + if len(matches) == 6 { + // 新格式解析 + sensorID, err := strconv.Atoi(matches[2]) // 使用传感器地址编号 + if err != nil { + return 0, 0, 0, 0, 0, fmt.Errorf("解析传感器ID失败: %v", err) + } + + temperature, err := strconv.ParseFloat(strings.TrimSpace(matches[3]), 64) + if err != nil { + return 0, 0, 0, 0, 0, fmt.Errorf("解析温度值失败: %v", err) + } + + x, err := strconv.ParseFloat(strings.TrimSpace(matches[4]), 64) + if err != nil { + return 0, 0, 0, 0, 0, fmt.Errorf("解析X值失败: %v", err) + } + + y, err := strconv.ParseFloat(strings.TrimSpace(matches[5]), 64) + if err != nil { + return 0, 0, 0, 0, 0, fmt.Errorf("解析Y值失败: %v", err) + } + + z := 0.0 // 新格式没有Z值,设为0 + + return sensorID, x, y, z, temperature, nil + } + + // 尝试解析旧格式: 1:1.000, 2.000, 3.000 + oldPattern := regexp.MustCompile(`(\d+):([-]?\d+\.\d+),\s*([-]?\d+\.\d+),\s*([-]?\d+\.\d+)`) + matches = oldPattern.FindStringSubmatch(data) + + if len(matches) == 5 { + // 旧格式解析 + sensorID, err := strconv.Atoi(matches[1]) + if err != nil { + return 0, 0, 0, 0, 0, fmt.Errorf("解析传感器ID失败: %v", err) + } + + x, err := strconv.ParseFloat(strings.TrimSpace(matches[2]), 64) + if err != nil { + return 0, 0, 0, 0, 0, fmt.Errorf("解析X值失败: %v", err) + } + + y, err := strconv.ParseFloat(strings.TrimSpace(matches[3]), 64) + if err != nil { + return 0, 0, 0, 0, 0, fmt.Errorf("解析Y值失败: %v", err) + } + + z, err := strconv.ParseFloat(strings.TrimSpace(matches[4]), 64) + if err != nil { + return 0, 0, 0, 0, 0, fmt.Errorf("解析Z值失败: %v", err) + } + + temperature := 0.0 // 旧格式没有温度值,设为0 + + return sensorID, x, y, z, temperature, nil + } + + return 0, 0, 0, 0, 0, fmt.Errorf("数据格式不正确: %s", data) +} + +// addClient 添加客户端 +func addClient(addr string) { + clientsMutex.Lock() + defer clientsMutex.Unlock() + + host, port, err := net.SplitHostPort(addr) + if err != nil { + Logger.Printf("解析客户端地址失败 %s: %v", addr, err) + host = addr + port = "unknown" + } + + clients[addr] = &ClientInfo{ + IP: host, + Port: port, + LastSeen: time.Now(), + } + + Logger.Printf("添加新客户端: %s", addr) +} + +// updateClientLastSeen 更新客户端最后活跃时间 +func updateClientLastSeen(addr string) { + clientsMutex.Lock() + defer clientsMutex.Unlock() + + if client, exists := clients[addr]; exists { + client.LastSeen = time.Now() + } +} + +// removeClient 移除客户端 +func removeClient(addr string) { + clientsMutex.Lock() + defer clientsMutex.Unlock() + + if client, exists := clients[addr]; exists { + client.LastSeen = time.Now() + Logger.Printf("客户端标记为断开连接: %s", addr) + } +} + +// getAllClients 获取所有客户端信息 +func getAllClients() []map[string]interface{} { + clientsMutex.Lock() + defer clientsMutex.Unlock() + + now := time.Now() + result := make([]map[string]interface{}, 0, len(clients)) + + for addr, client := range clients { + lastSeenDuration := now.Sub(client.LastSeen) + + if lastSeenDuration > 24*time.Hour { + delete(clients, addr) + continue + } + + isOnline := lastSeenDuration < 10*time.Minute + + result = append(result, map[string]interface{}{ + "address": addr, + "ip": client.IP, + "port": client.Port, + "lastSeen": client.LastSeen, + "isOnline": isOnline, + "lastSeenFormatted": formatDuration(lastSeenDuration), + }) + } + + return result +} + +// formatDuration 格式化持续时间为友好的字符串 +func formatDuration(d time.Duration) string { + if d < time.Minute { + return "刚刚" + } else if d < time.Hour { + return fmt.Sprintf("%d分钟前", int(d.Minutes())) + } else if d < 24*time.Hour { + return fmt.Sprintf("%d小时前", int(d.Hours())) + } else { + return fmt.Sprintf("%d天前", int(d.Hours()/24)) + } +} + +// startClientCleanup 启动清理过期客户端的goroutine +func startClientCleanup() { + go func() { + for { + time.Sleep(1 * time.Hour) // 每小时检查一次 + + clientsMutex.Lock() + now := time.Now() + + for addr, client := range clients { + if now.Sub(client.LastSeen) > 24*time.Hour { + delete(clients, addr) + Logger.Printf("移除过期客户端: %s", addr) + } + } + + clientsMutex.Unlock() + } + }() +} diff --git a/templates/index.html b/templates/index.html index c2186fa..3a9287f 100644 --- a/templates/index.html +++ b/templates/index.html @@ -201,11 +201,12 @@ 数据编号 - 探头地址 + 探头地址 时间 X Y Z + 温度(°C) @@ -439,7 +440,7 @@ if (data.length === 0) { const row = document.createElement('tr'); - row.innerHTML = '没有数据'; + row.innerHTML = '没有数据'; tableBody.appendChild(row); return; } @@ -467,7 +468,8 @@ '' + formattedDate + '' + '' + item.x.toFixed(3) + '' + '' + item.y.toFixed(3) + '' + - '' + item.z.toFixed(3) + '' ; + '' + item.z.toFixed(3) + '' + + '' + item.temperature.toFixed(1) + ''; tableBody.appendChild(row); }); @@ -579,11 +581,12 @@ 'rgb(255, 159, 64)' ]; - // 为X, Y, Z创建不同的数据集 + // 为X, Y, Z, 温度创建不同的数据集 const dataTypes = [ { key: 'x', label: 'X' }, { key: 'y', label: 'Y' }, - { key: 'z', label: 'Z' } + { key: 'z', label: 'Z' }, + { key: 'temperature', label: '温度(°C)' } ]; sensorIDs.forEach((sensorID, sensorIndex) => { @@ -616,7 +619,7 @@ } // 准备CSV内容 - let csvContent = "数据编号,探头地址编号,X,Y,Z,时间\n"; + let csvContent = "数据编号,探头地址编号,X,Y,Z,温度,时间\n"; currentSensorData.forEach(item => { // 解析时间并调整为中国时间 @@ -632,14 +635,13 @@ date.getMinutes().toString().padStart(2, '0') + ':' + date.getSeconds().toString().padStart(2, '0'); - // 添加一行数据 - csvContent += - item.id + "," + - item.sensor_id + "," + - item.x.toFixed(3) + "," + - item.y.toFixed(3) + "," + - item.z.toFixed(3) + "," + - formattedDate + "\n"; + csvContent += item.id + "," + + item.sensor_id + "," + + item.x.toFixed(3) + "," + + item.y.toFixed(3) + "," + + item.z.toFixed(3) + "," + + item.temperature.toFixed(1) + "," + + formattedDate + "\n"; }); // 创建Blob对象 diff --git a/udp_server.go b/udp_server.go index da53dcd..ed650d5 100644 --- a/udp_server.go +++ b/udp_server.go @@ -44,13 +44,13 @@ func handleUDPPacket(conn *net.UDPConn, addr *net.UDPAddr, data []byte) { rawData := string(data) TCPDataLogger.Printf("从UDP客户端 %s 接收到原始数据: %s", remoteAddr, rawData) - sensorID, x, y, z, err := parseData(rawData) + sensorID, x, y, z, temperature, err := parseData(rawData) if err == nil { - TCPDataLogger.Printf("解析成功 - UDP客户端: %s, 传感器ID: %d, 值: X=%.3f, Y=%.3f, Z=%.3f", - remoteAddr, sensorID, x, y, z) + TCPDataLogger.Printf("解析成功 - UDP客户端: %s, 传感器ID: %d, 值: X=%.3f, Y=%.3f, Z=%.3f, 温度=%.1f°C", + remoteAddr, sensorID, x, y, z, temperature) - if err := SaveSensorData(sensorID, x, y, z); err != nil { + if err := SaveSensorData(sensorID, x, y, z, temperature); err != nil { Logger.Printf("保存传感器数据失败: %v", err) } } else {