fix:修改一些 BUG

This commit is contained in:
fengyarnom 2025-05-15 18:17:20 +08:00
parent 4a6c6e58ea
commit 7fe577eedd
5 changed files with 152 additions and 22 deletions

View File

@ -67,9 +67,9 @@ func (dao *SensorDAO) GetAggregatedData(start, end time.Time, interval string) (
ROUND(AVG(temperature)/10, 1) AS avg_temp, ROUND(AVG(temperature)/10, 1) AS avg_temp,
MAX(rainfall) - MIN(rainfall) AS rainfall, MAX(rainfall) - MIN(rainfall) AS rainfall,
ROUND(AVG(humidity)/10, 1) AS avg_humidity, ROUND(AVG(humidity)/10, 1) AS avg_humidity,
ROUND(AVG(wind_speed)/10, 1) AS avg_wind_speed, ROUND(AVG(wind_speed)/100, 1) AS avg_wind_speed,
ROUND(AVG(atm_pressure)/10, 1) AS avg_atm_pressure, ROUND(AVG(atm_pressure)/10, 1) AS avg_atm_pressure,
ROUND(AVG(solar_radiation)/10, 1) AS avg_solar_radiation ROUND(AVG(solar_radiation), 1) AS avg_solar_radiation
FROM sensor_data FROM sensor_data
WHERE timestamp BETWEEN ? AND ? WHERE timestamp BETWEEN ? AND ?
GROUP BY bucket_time GROUP BY bucket_time

View File

@ -6,6 +6,7 @@ import (
"time" "time"
"go_rain_dtu/internal/dao" "go_rain_dtu/internal/dao"
"go_rain_dtu/internal/tcp"
"go_rain_dtu/pkg/logger" "go_rain_dtu/pkg/logger"
) )
@ -63,3 +64,24 @@ func (h *SensorHandler) ServeStatic(w http.ResponseWriter, r *http.Request) {
} }
http.FileServer(http.Dir("static")).ServeHTTP(w, r) http.FileServer(http.Dir("static")).ServeHTTP(w, r)
} }
// GetConnectionStatus 返回当前TCP连接状态
func (h *SensorHandler) GetConnectionStatus(w http.ResponseWriter, r *http.Request) {
connected, ip, port := tcp.GetConnectionStatus()
status := struct {
Connected bool `json:"connected"`
IP string `json:"ip"`
Port string `json:"port"`
}{
Connected: connected,
IP: ip,
Port: port,
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(status); err != nil {
logger.Logger.Printf("JSON编码失败: %v", err)
http.Error(w, "服务器内部错误", http.StatusInternalServerError)
}
}

View File

@ -20,6 +20,51 @@ const (
tcpPort = ":10004" // TCP服务器端口 tcpPort = ":10004" // TCP服务器端口
) )
// ConnectionStatus 保存TCP连接状态
var (
ConnectionStatus = struct {
Connected bool
IP string
Port string
LastSeen time.Time
mu sync.RWMutex
}{
Connected: false,
IP: "",
Port: "",
}
)
// GetConnectionStatus 返回当前连接状态
func GetConnectionStatus() (bool, string, string) {
ConnectionStatus.mu.RLock()
defer ConnectionStatus.mu.RUnlock()
// 如果最后一次通信时间超过1分钟认为连接已断开
if ConnectionStatus.Connected && time.Since(ConnectionStatus.LastSeen) > time.Minute {
return false, ConnectionStatus.IP, ConnectionStatus.Port
}
return ConnectionStatus.Connected, ConnectionStatus.IP, ConnectionStatus.Port
}
// updateConnectionStatus 更新连接状态
func updateConnectionStatus(connected bool, addr string) {
ConnectionStatus.mu.Lock()
defer ConnectionStatus.mu.Unlock()
ConnectionStatus.Connected = connected
if connected && addr != "" {
host, port, _ := net.SplitHostPort(addr)
ConnectionStatus.IP = host
ConnectionStatus.Port = port
ConnectionStatus.LastSeen = time.Now()
} else if !connected {
// 如果断开连接只更新状态保留最后的IP和端口信息
ConnectionStatus.Connected = false
}
}
type SensorComm struct { type SensorComm struct {
conn net.Conn conn net.Conn
dao *dao.SensorDAO dao *dao.SensorDAO
@ -62,9 +107,11 @@ func handleConnection(sensor *SensorComm) {
defer sensor.Close() defer sensor.Close()
logger.Logger.Printf("新连接建立: %s", sensor.address) logger.Logger.Printf("新连接建立: %s", sensor.address)
updateConnectionStatus(true, sensor.address)
// 发送首次查询 // 发送首次查询
if err := sensor.sendQuery(); err != nil { if err := sensor.sendQuery(); err != nil {
updateConnectionStatus(false, "")
return return
} }
@ -99,11 +146,15 @@ func handleConnection(sensor *SensorComm) {
// 其他错误才认为连接断开 // 其他错误才认为连接断开
logger.Logger.Printf("读取数据失败: %v", err) logger.Logger.Printf("读取数据失败: %v", err)
updateConnectionStatus(false, "")
done <- true done <- true
return return
} }
if n > 0 { if n > 0 {
// 更新最后通信时间
updateConnectionStatus(true, sensor.address)
// 记录原始数据 // 记录原始数据
logger.Logger.Printf("接收到原始数据 [%s]: % X", sensor.address, buffer[:n]) logger.Logger.Printf("接收到原始数据 [%s]: % X", sensor.address, buffer[:n])
@ -125,9 +176,11 @@ func handleConnection(sensor *SensorComm) {
case <-ticker.C: case <-ticker.C:
logger.Logger.Printf("定时查询触发 [%s]", sensor.address) logger.Logger.Printf("定时查询触发 [%s]", sensor.address)
if err := sensor.sendQuery(); err != nil { if err := sensor.sendQuery(); err != nil {
updateConnectionStatus(false, "")
return return
} }
case <-done: case <-done:
updateConnectionStatus(false, "")
return return
} }
} }
@ -155,18 +208,30 @@ func (s *SensorComm) handleData(data []byte) *model.SensorData {
} }
// 解析数据从第4个字节开始是数据部分 // 解析数据从第4个字节开始是数据部分
// 根据原始TCP服务器代码中的单位转换
windSpeed := int(binary.BigEndian.Uint16(data[3:5])) // 风速值*100
sensorData := &model.SensorData{ sensorData := &model.SensorData{
Timestamp: time.Now(), Timestamp: time.Now(),
WindSpeed: int(binary.BigEndian.Uint16(data[3:5])), WindSpeed: windSpeed, // 原始值已经是*100的
WindForce: int(binary.BigEndian.Uint16(data[5:7])), WindForce: int(binary.BigEndian.Uint16(data[5:7])),
WindDirection8: int(binary.BigEndian.Uint16(data[7:9])), WindDirection8: int(binary.BigEndian.Uint16(data[7:9])),
WindDirection360: int(binary.BigEndian.Uint16(data[9:11])), WindDirection360: int(binary.BigEndian.Uint16(data[9:11])),
Humidity: int(binary.BigEndian.Uint16(data[11:13])), Humidity: int(binary.BigEndian.Uint16(data[11:13])), // 湿度*10
Temperature: int(binary.BigEndian.Uint16(data[13:15])), Temperature: int(binary.BigEndian.Uint16(data[13:15])), // 温度*10
AtmPressure: int(binary.BigEndian.Uint16(data[21:23])), AtmPressure: int(binary.BigEndian.Uint16(data[21:23])), // 大气压*10
SolarRadiation: int(binary.BigEndian.Uint16(data[33:35])), SolarRadiation: int(binary.BigEndian.Uint16(data[33:35])), // 太阳辐射原始值
} }
// 记录解析后的传感器数据,展示实际物理量
logger.Logger.Printf("传感器数据: 风速=%.2f m/s, 风力=%d级, 风向=%d°, 湿度=%.1f%%, 温度=%.1f℃, 大气压=%.1f kPa, 太阳辐射=%d W/m²",
float64(sensorData.WindSpeed)/100.0,
sensorData.WindForce,
sensorData.WindDirection360,
float64(sensorData.Humidity)/10.0,
float64(sensorData.Temperature)/10.0,
float64(sensorData.AtmPressure)/10.0,
sensorData.SolarRadiation)
// 保存数据到数据库 // 保存数据到数据库
if err := s.dao.Insert(sensorData); err != nil { if err := s.dao.Insert(sensorData); err != nil {
logger.Logger.Printf("保存数据失败: %v", err) logger.Logger.Printf("保存数据失败: %v", err)
@ -184,6 +249,7 @@ func (s *SensorComm) Close() {
if s.conn != nil { if s.conn != nil {
s.conn.Close() s.conn.Close()
s.conn = nil s.conn = nil
updateConnectionStatus(false, "")
} }
} }

View File

@ -52,6 +52,7 @@ func main() {
// 设置路由 // 设置路由
http.HandleFunc("/api/data", sensorHandler.GetAggregatedData) http.HandleFunc("/api/data", sensorHandler.GetAggregatedData)
http.HandleFunc("/api/status", sensorHandler.GetConnectionStatus)
http.HandleFunc("/", sensorHandler.ServeStatic) http.HandleFunc("/", sensorHandler.ServeStatic)
// 启动TCP服务器 // 启动TCP服务器

View File

@ -118,6 +118,9 @@
<body> <body>
<div class="header"> <div class="header">
<h1>雨量计数据</h1> <h1>雨量计数据</h1>
<div id="connectionStatus" style="display: inline-block; padding: 5px 10px; border-radius: 4px; margin-left: 10px; background-color: red; color: white;">
未连接
</div>
</div> </div>
<div class="container"> <div class="container">
@ -141,10 +144,6 @@
<input type="datetime-local" id="endDate"> <input type="datetime-local" id="endDate">
</div> </div>
<div class="control-group">
<label for="adaptiveScale">自适应范围:</label>
<input type="checkbox" id="adaptiveScale">
</div>
<div class="control-group"> <div class="control-group">
<button onclick="queryData()">查询</button> <button onclick="queryData()">查询</button>
@ -162,10 +161,10 @@
<tr> <tr>
<th>时间</th> <th>时间</th>
<th>降雨量(mm)</th> <th>降雨量(mm)</th>
<th>平均温度(℃)</th> <th>温度(℃)</th>
<th>平均湿度(%)</th> <th>湿度(%)</th>
<th>平均风速(m/s)</th> <th>风速(m/s)</th>
<th>大气压(hPa)</th> <th>大气压(kPa)</th>
<th>太阳辐射(W/m²)</th> <th>太阳辐射(W/m²)</th>
</tr> </tr>
</thead> </thead>
@ -176,6 +175,29 @@
<script> <script>
let mainChart = null; let mainChart = null;
let connectionCheckTimer = null;
// 检查连接状态
function checkConnectionStatus() {
fetch('/api/status')
.then(response => response.json())
.then(data => {
const statusElem = document.getElementById('connectionStatus');
if (data.connected) {
statusElem.style.backgroundColor = 'green';
statusElem.textContent = `已连接: ${data.ip}:${data.port}`;
} else {
statusElem.style.backgroundColor = 'red';
statusElem.textContent = '未连接';
}
})
.catch(error => {
console.error('获取连接状态失败:', error);
const statusElem = document.getElementById('connectionStatus');
statusElem.style.backgroundColor = 'red';
statusElem.textContent = '状态未知';
});
}
// 初始化日期选择器 // 初始化日期选择器
function initDatePickers() { function initDatePickers() {
@ -248,7 +270,7 @@
}); });
// 是否使用自适应范围 // 是否使用自适应范围
const useAdaptiveScale = document.getElementById('adaptiveScale').checked; const useAdaptiveScale = 1;
// 计算降雨量和温度的范围 // 计算降雨量和温度的范围
let rainfallMin = 0; let rainfallMin = 0;
@ -267,6 +289,8 @@
rainfallMax = Math.ceil(Math.max(...rainfalls) * 1.2) || 10; // 如果最大值是0则默认为10 rainfallMax = Math.ceil(Math.max(...rainfalls) * 1.2) || 10; // 如果最大值是0则默认为10
} }
// 数据已经在DAO层正确转换了单位
// 不需要再做额外的单位转换
mainChart = new Chart(ctx, { mainChart = new Chart(ctx, {
data: { data: {
labels: labels, labels: labels,
@ -336,9 +360,10 @@
const tbody = document.getElementById('tableBody'); const tbody = document.getElementById('tableBody');
tbody.innerHTML = ''; tbody.innerHTML = '';
// 数据在updateChart中已经排序这里不需要重复排序 // 按时间倒序排列数据(从晚到早),这样最新的数据在表格顶部
const sortedData = [...data].sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
data.forEach(item => { sortedData.forEach(item => {
const row = document.createElement('tr'); const row = document.createElement('tr');
// 解析时间字符串为本地时间 // 解析时间字符串为本地时间
const date = new Date(item.timestamp); const date = new Date(item.timestamp);
@ -351,14 +376,19 @@
date.getMinutes().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0') + ':' +
date.getSeconds().toString().padStart(2, '0'); date.getSeconds().toString().padStart(2, '0');
// 根据原始TCP服务器中的单位换算
// 原始的风速是实际值的100倍
// 温度和湿度是实际值的10倍
// 大气压是实际值的10倍
// 太阳辐射是原始值
row.innerHTML = ` row.innerHTML = `
<td>${formattedDate}</td> <td>${formattedDate}</td>
<td>${item.rainfall.toFixed(1)}</td> <td>${item.rainfall.toFixed(1)}</td>
<td>${item.avg_temperature.toFixed(1)}</td> <td>${item.avg_temperature.toFixed(1)}</td>
<td>${item.avg_humidity.toFixed(1)}</td> <td>${item.avg_humidity.toFixed(1)}</td>
<td>${item.avg_wind_speed.toFixed(1)}</td> <td>${item.avg_wind_speed.toFixed(2)}</td>
<td>${item.atm_pressure ? (item.atm_pressure/10).toFixed(1) : 'N/A'}</td> <td>${item.atm_pressure ? item.atm_pressure.toFixed(1) : 'N/A'}</td>
<td>${item.solar_radiation ? (item.solar_radiation/10).toFixed(1) : 'N/A'}</td> <td>${item.solar_radiation ? item.solar_radiation.toFixed(0) : 'N/A'}</td>
`; `;
tbody.appendChild(row); tbody.appendChild(row);
}); });
@ -368,7 +398,7 @@
function exportData() { function exportData() {
// 从表格中获取完整数据 // 从表格中获取完整数据
const tableRows = document.querySelectorAll('#tableBody tr'); const tableRows = document.querySelectorAll('#tableBody tr');
let csv = '时间,降雨量(mm),温度(℃),湿度(%),风速(m/s),大气压(hPa),太阳辐射(W/m²)\n'; let csv = '时间,降雨量(mm),温度(℃),湿度(%),风速(m/s),大气压(kPa),太阳辐射(W/m²)\n';
tableRows.forEach(row => { tableRows.forEach(row => {
const cells = row.querySelectorAll('td'); const cells = row.querySelectorAll('td');
@ -395,6 +425,17 @@
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
initDatePickers(); initDatePickers();
queryData(); queryData();
// 每30秒检查一次连接状态
checkConnectionStatus();
connectionCheckTimer = setInterval(checkConnectionStatus, 30000);
});
// 页面卸载时清除定时器
window.addEventListener('beforeunload', function() {
if (connectionCheckTimer) {
clearInterval(connectionCheckTimer);
}
}); });
</script> </script>
</body> </body>