fix:修改一些 BUG
This commit is contained in:
parent
4a6c6e58ea
commit
7fe577eedd
@ -67,9 +67,9 @@ func (dao *SensorDAO) GetAggregatedData(start, end time.Time, interval string) (
|
||||
ROUND(AVG(temperature)/10, 1) AS avg_temp,
|
||||
MAX(rainfall) - MIN(rainfall) AS rainfall,
|
||||
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(solar_radiation)/10, 1) AS avg_solar_radiation
|
||||
ROUND(AVG(solar_radiation), 1) AS avg_solar_radiation
|
||||
FROM sensor_data
|
||||
WHERE timestamp BETWEEN ? AND ?
|
||||
GROUP BY bucket_time
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"go_rain_dtu/internal/dao"
|
||||
"go_rain_dtu/internal/tcp"
|
||||
"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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,6 +20,51 @@ const (
|
||||
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 {
|
||||
conn net.Conn
|
||||
dao *dao.SensorDAO
|
||||
@ -62,9 +107,11 @@ func handleConnection(sensor *SensorComm) {
|
||||
defer sensor.Close()
|
||||
|
||||
logger.Logger.Printf("新连接建立: %s", sensor.address)
|
||||
updateConnectionStatus(true, sensor.address)
|
||||
|
||||
// 发送首次查询
|
||||
if err := sensor.sendQuery(); err != nil {
|
||||
updateConnectionStatus(false, "")
|
||||
return
|
||||
}
|
||||
|
||||
@ -99,11 +146,15 @@ func handleConnection(sensor *SensorComm) {
|
||||
|
||||
// 其他错误才认为连接断开
|
||||
logger.Logger.Printf("读取数据失败: %v", err)
|
||||
updateConnectionStatus(false, "")
|
||||
done <- true
|
||||
return
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
// 更新最后通信时间
|
||||
updateConnectionStatus(true, sensor.address)
|
||||
|
||||
// 记录原始数据
|
||||
logger.Logger.Printf("接收到原始数据 [%s]: % X", sensor.address, buffer[:n])
|
||||
|
||||
@ -125,9 +176,11 @@ func handleConnection(sensor *SensorComm) {
|
||||
case <-ticker.C:
|
||||
logger.Logger.Printf("定时查询触发 [%s]", sensor.address)
|
||||
if err := sensor.sendQuery(); err != nil {
|
||||
updateConnectionStatus(false, "")
|
||||
return
|
||||
}
|
||||
case <-done:
|
||||
updateConnectionStatus(false, "")
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -155,18 +208,30 @@ func (s *SensorComm) handleData(data []byte) *model.SensorData {
|
||||
}
|
||||
|
||||
// 解析数据,从第4个字节开始是数据部分
|
||||
// 根据原始TCP服务器代码中的单位转换
|
||||
windSpeed := int(binary.BigEndian.Uint16(data[3:5])) // 风速值*100
|
||||
sensorData := &model.SensorData{
|
||||
Timestamp: time.Now(),
|
||||
WindSpeed: int(binary.BigEndian.Uint16(data[3:5])),
|
||||
WindSpeed: windSpeed, // 原始值已经是*100的
|
||||
WindForce: int(binary.BigEndian.Uint16(data[5:7])),
|
||||
WindDirection8: int(binary.BigEndian.Uint16(data[7:9])),
|
||||
WindDirection360: int(binary.BigEndian.Uint16(data[9:11])),
|
||||
Humidity: int(binary.BigEndian.Uint16(data[11:13])),
|
||||
Temperature: int(binary.BigEndian.Uint16(data[13:15])),
|
||||
AtmPressure: int(binary.BigEndian.Uint16(data[21:23])),
|
||||
SolarRadiation: int(binary.BigEndian.Uint16(data[33:35])),
|
||||
Humidity: int(binary.BigEndian.Uint16(data[11:13])), // 湿度*10
|
||||
Temperature: int(binary.BigEndian.Uint16(data[13:15])), // 温度*10
|
||||
AtmPressure: int(binary.BigEndian.Uint16(data[21:23])), // 大气压*10
|
||||
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 {
|
||||
logger.Logger.Printf("保存数据失败: %v", err)
|
||||
@ -184,6 +249,7 @@ func (s *SensorComm) Close() {
|
||||
if s.conn != nil {
|
||||
s.conn.Close()
|
||||
s.conn = nil
|
||||
updateConnectionStatus(false, "")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
main.go
1
main.go
@ -52,6 +52,7 @@ func main() {
|
||||
|
||||
// 设置路由
|
||||
http.HandleFunc("/api/data", sensorHandler.GetAggregatedData)
|
||||
http.HandleFunc("/api/status", sensorHandler.GetConnectionStatus)
|
||||
http.HandleFunc("/", sensorHandler.ServeStatic)
|
||||
|
||||
// 启动TCP服务器
|
||||
|
||||
@ -118,6 +118,9 @@
|
||||
<body>
|
||||
<div class="header">
|
||||
<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 class="container">
|
||||
@ -141,10 +144,6 @@
|
||||
<input type="datetime-local" id="endDate">
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="adaptiveScale">自适应范围:</label>
|
||||
<input type="checkbox" id="adaptiveScale">
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<button onclick="queryData()">查询</button>
|
||||
@ -162,10 +161,10 @@
|
||||
<tr>
|
||||
<th>时间</th>
|
||||
<th>降雨量(mm)</th>
|
||||
<th>平均温度(℃)</th>
|
||||
<th>平均湿度(%)</th>
|
||||
<th>平均风速(m/s)</th>
|
||||
<th>大气压(hPa)</th>
|
||||
<th>温度(℃)</th>
|
||||
<th>湿度(%)</th>
|
||||
<th>风速(m/s)</th>
|
||||
<th>大气压(kPa)</th>
|
||||
<th>太阳辐射(W/m²)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -176,6 +175,29 @@
|
||||
|
||||
<script>
|
||||
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() {
|
||||
@ -248,7 +270,7 @@
|
||||
});
|
||||
|
||||
// 是否使用自适应范围
|
||||
const useAdaptiveScale = document.getElementById('adaptiveScale').checked;
|
||||
const useAdaptiveScale = 1;
|
||||
|
||||
// 计算降雨量和温度的范围
|
||||
let rainfallMin = 0;
|
||||
@ -267,6 +289,8 @@
|
||||
rainfallMax = Math.ceil(Math.max(...rainfalls) * 1.2) || 10; // 如果最大值是0,则默认为10
|
||||
}
|
||||
|
||||
// 数据已经在DAO层正确转换了单位
|
||||
// 不需要再做额外的单位转换
|
||||
mainChart = new Chart(ctx, {
|
||||
data: {
|
||||
labels: labels,
|
||||
@ -336,9 +360,10 @@
|
||||
const tbody = document.getElementById('tableBody');
|
||||
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 date = new Date(item.timestamp);
|
||||
@ -351,14 +376,19 @@
|
||||
date.getMinutes().toString().padStart(2, '0') + ':' +
|
||||
date.getSeconds().toString().padStart(2, '0');
|
||||
|
||||
// 根据原始TCP服务器中的单位换算
|
||||
// 原始的风速是实际值的100倍
|
||||
// 温度和湿度是实际值的10倍
|
||||
// 大气压是实际值的10倍
|
||||
// 太阳辐射是原始值
|
||||
row.innerHTML = `
|
||||
<td>${formattedDate}</td>
|
||||
<td>${item.rainfall.toFixed(1)}</td>
|
||||
<td>${item.avg_temperature.toFixed(1)}</td>
|
||||
<td>${item.avg_humidity.toFixed(1)}</td>
|
||||
<td>${item.avg_wind_speed.toFixed(1)}</td>
|
||||
<td>${item.atm_pressure ? (item.atm_pressure/10).toFixed(1) : 'N/A'}</td>
|
||||
<td>${item.solar_radiation ? (item.solar_radiation/10).toFixed(1) : 'N/A'}</td>
|
||||
<td>${item.avg_wind_speed.toFixed(2)}</td>
|
||||
<td>${item.atm_pressure ? item.atm_pressure.toFixed(1) : 'N/A'}</td>
|
||||
<td>${item.solar_radiation ? item.solar_radiation.toFixed(0) : 'N/A'}</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
@ -368,7 +398,7 @@
|
||||
function exportData() {
|
||||
// 从表格中获取完整数据
|
||||
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 => {
|
||||
const cells = row.querySelectorAll('td');
|
||||
@ -395,6 +425,17 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initDatePickers();
|
||||
queryData();
|
||||
|
||||
// 每30秒检查一次连接状态
|
||||
checkConnectionStatus();
|
||||
connectionCheckTimer = setInterval(checkConnectionStatus, 30000);
|
||||
});
|
||||
|
||||
// 页面卸载时清除定时器
|
||||
window.addEventListener('beforeunload', function() {
|
||||
if (connectionCheckTimer) {
|
||||
clearInterval(connectionCheckTimer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user