fix:调整是数据显示与数据获取
This commit is contained in:
parent
16615a5029
commit
4a6c6e58ea
@ -67,7 +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)/10, 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
|
||||||
FROM sensor_data
|
FROM sensor_data
|
||||||
WHERE timestamp BETWEEN ? AND ?
|
WHERE timestamp BETWEEN ? AND ?
|
||||||
GROUP BY bucket_time
|
GROUP BY bucket_time
|
||||||
@ -92,7 +94,7 @@ func (dao *SensorDAO) GetAggregatedData(start, end time.Time, interval string) (
|
|||||||
var data model.AggregatedData
|
var data model.AggregatedData
|
||||||
var tsStr string
|
var tsStr string
|
||||||
err := rows.Scan(&tsStr, &data.AvgTemperature, &data.Rainfall,
|
err := rows.Scan(&tsStr, &data.AvgTemperature, &data.Rainfall,
|
||||||
&data.AvgHumidity, &data.AvgWindSpeed)
|
&data.AvgHumidity, &data.AvgWindSpeed, &data.AvgAtmPressure, &data.AvgSolarRadiation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logger.Printf("扫描数据行失败: %v", err)
|
logger.Logger.Printf("扫描数据行失败: %v", err)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@ -19,9 +19,11 @@ type SensorData struct {
|
|||||||
|
|
||||||
// AggregatedData 聚合数据结构
|
// AggregatedData 聚合数据结构
|
||||||
type AggregatedData struct {
|
type AggregatedData struct {
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
AvgTemperature float64 `json:"avg_temperature"`
|
AvgTemperature float64 `json:"avg_temperature"`
|
||||||
Rainfall float64 `json:"rainfall"`
|
Rainfall float64 `json:"rainfall"`
|
||||||
AvgHumidity float64 `json:"avg_humidity"`
|
AvgHumidity float64 `json:"avg_humidity"`
|
||||||
AvgWindSpeed float64 `json:"avg_wind_speed"`
|
AvgWindSpeed float64 `json:"avg_wind_speed"`
|
||||||
|
AvgAtmPressure float64 `json:"atm_pressure"` // 平均大气压
|
||||||
|
AvgSolarRadiation float64 `json:"solar_radiation"` // 平均太阳辐射
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,7 +68,7 @@ func handleConnection(sensor *SensorComm) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置定时器,每分钟查询一次
|
// 设置定时器,每5分钟查询一次
|
||||||
ticker := time.NewTicker(time.Minute * 5)
|
ticker := time.NewTicker(time.Minute * 5)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@ -76,14 +76,28 @@ func handleConnection(sensor *SensorComm) {
|
|||||||
buffer := make([]byte, 1024)
|
buffer := make([]byte, 1024)
|
||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
|
|
||||||
|
// 设置tcp保持活动状态,防止连接断开
|
||||||
|
tcpConn, ok := sensor.conn.(*net.TCPConn)
|
||||||
|
if ok {
|
||||||
|
tcpConn.SetKeepAlive(true)
|
||||||
|
tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
// 处理接收数据的goroutine
|
// 处理接收数据的goroutine
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
// 设置读取超时
|
// 设置读取超时 - 增加超时时间到10分钟,大于查询间隔
|
||||||
sensor.conn.SetReadDeadline(time.Now().Add(time.Second * 30))
|
sensor.conn.SetReadDeadline(time.Now().Add(10 * time.Minute))
|
||||||
|
|
||||||
n, err := sensor.conn.Read(buffer)
|
n, err := sensor.conn.Read(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||||
|
// 超时错误不一定意味着连接断开,继续等待
|
||||||
|
logger.Logger.Printf("读取超时: %v, 等待下一次查询", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他错误才认为连接断开
|
||||||
logger.Logger.Printf("读取数据失败: %v", err)
|
logger.Logger.Printf("读取数据失败: %v", err)
|
||||||
done <- true
|
done <- true
|
||||||
return
|
return
|
||||||
@ -109,6 +123,7 @@ func handleConnection(sensor *SensorComm) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
logger.Logger.Printf("定时查询触发 [%s]", sensor.address)
|
||||||
if err := sensor.sendQuery(); err != nil {
|
if err := sensor.sendQuery(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -174,7 +189,13 @@ func (s *SensorComm) Close() {
|
|||||||
|
|
||||||
// 启动TCP服务器
|
// 启动TCP服务器
|
||||||
func StartTCPServer(dao *dao.SensorDAO) error {
|
func StartTCPServer(dao *dao.SensorDAO) error {
|
||||||
listener, err := net.Listen("tcp", tcpPort)
|
// 创建TCP监听器并设置TCP选项
|
||||||
|
addr, err := net.ResolveTCPAddr("tcp", tcpPort)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("解析TCP地址失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.ListenTCP("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("启动TCP服务器失败: %v", err)
|
return fmt.Errorf("启动TCP服务器失败: %v", err)
|
||||||
}
|
}
|
||||||
@ -186,12 +207,17 @@ func StartTCPServer(dao *dao.SensorDAO) error {
|
|||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
|
|
||||||
for {
|
for {
|
||||||
conn, err := listener.Accept()
|
conn, err := listener.AcceptTCP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logger.Printf("接受连接失败: %v", err)
|
logger.Logger.Printf("接受连接失败: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置TCP连接选项
|
||||||
|
conn.SetKeepAlive(true)
|
||||||
|
conn.SetKeepAlivePeriod(30 * time.Second)
|
||||||
|
conn.SetLinger(0) // 立即关闭
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
// 关闭旧连接
|
// 关闭旧连接
|
||||||
if currentConn != nil {
|
if currentConn != nil {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>气象站数据监控</title>
|
<title>雨量计数据</title>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
@ -16,7 +16,6 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid #ddd;
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@ -118,7 +117,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>气象站数据监控</h1>
|
<h1>雨量计数据</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -142,6 +141,11 @@
|
|||||||
<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>
|
||||||
<button onclick="exportData()">导出数据</button>
|
<button onclick="exportData()">导出数据</button>
|
||||||
@ -161,6 +165,8 @@
|
|||||||
<th>平均温度(℃)</th>
|
<th>平均温度(℃)</th>
|
||||||
<th>平均湿度(%)</th>
|
<th>平均湿度(%)</th>
|
||||||
<th>平均风速(m/s)</th>
|
<th>平均风速(m/s)</th>
|
||||||
|
<th>大气压(hPa)</th>
|
||||||
|
<th>太阳辐射(W/m²)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="tableBody"></tbody>
|
<tbody id="tableBody"></tbody>
|
||||||
@ -241,6 +247,26 @@
|
|||||||
date.getMinutes().toString().padStart(2, '0');
|
date.getMinutes().toString().padStart(2, '0');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 是否使用自适应范围
|
||||||
|
const useAdaptiveScale = document.getElementById('adaptiveScale').checked;
|
||||||
|
|
||||||
|
// 计算降雨量和温度的范围
|
||||||
|
let rainfallMin = 0;
|
||||||
|
let rainfallMax = 50;
|
||||||
|
let tempMin = -10;
|
||||||
|
let tempMax = 40;
|
||||||
|
|
||||||
|
if (useAdaptiveScale && data.length > 0) {
|
||||||
|
// 找出温度的最小值和最大值,并添加一些边距
|
||||||
|
const temps = data.map(item => item.avg_temperature);
|
||||||
|
tempMin = Math.floor(Math.min(...temps) - 5);
|
||||||
|
tempMax = Math.ceil(Math.max(...temps) + 5);
|
||||||
|
|
||||||
|
// 找出降雨量的最大值,并添加一些边距
|
||||||
|
const rainfalls = data.map(item => item.rainfall);
|
||||||
|
rainfallMax = Math.ceil(Math.max(...rainfalls) * 1.2) || 10; // 如果最大值是0,则默认为10
|
||||||
|
}
|
||||||
|
|
||||||
mainChart = new Chart(ctx, {
|
mainChart = new Chart(ctx, {
|
||||||
data: {
|
data: {
|
||||||
labels: labels,
|
labels: labels,
|
||||||
@ -282,10 +308,10 @@
|
|||||||
grid: {
|
grid: {
|
||||||
drawOnChartArea: false
|
drawOnChartArea: false
|
||||||
},
|
},
|
||||||
// 设置降雨量的合理范围
|
// 设置降雨量的范围
|
||||||
min: 0,
|
min: rainfallMin,
|
||||||
max: 50, // 根据实际情况可调整
|
max: rainfallMax,
|
||||||
suggestedMax: 10
|
suggestedMax: Math.min(10, rainfallMax)
|
||||||
},
|
},
|
||||||
'y-temp': {
|
'y-temp': {
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
@ -294,11 +320,11 @@
|
|||||||
display: true,
|
display: true,
|
||||||
text: '温度(℃)'
|
text: '温度(℃)'
|
||||||
},
|
},
|
||||||
// 设置温度的合理范围
|
// 设置温度的范围
|
||||||
min: -10,
|
min: tempMin,
|
||||||
max: 40,
|
max: tempMax,
|
||||||
suggestedMin: 0,
|
suggestedMin: Math.max(0, tempMin),
|
||||||
suggestedMax: 30
|
suggestedMax: Math.min(30, tempMax)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -331,6 +357,8 @@
|
|||||||
<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(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>
|
||||||
`;
|
`;
|
||||||
tbody.appendChild(row);
|
tbody.appendChild(row);
|
||||||
});
|
});
|
||||||
@ -340,7 +368,7 @@
|
|||||||
function exportData() {
|
function exportData() {
|
||||||
// 从表格中获取完整数据
|
// 从表格中获取完整数据
|
||||||
const tableRows = document.querySelectorAll('#tableBody tr');
|
const tableRows = document.querySelectorAll('#tableBody tr');
|
||||||
let csv = '时间,降雨量(mm),温度(℃),湿度(%),风速(m/s)\n';
|
let csv = '时间,降雨量(mm),温度(℃),湿度(%),风速(m/s),大气压(hPa),太阳辐射(W/m²)\n';
|
||||||
|
|
||||||
tableRows.forEach(row => {
|
tableRows.forEach(row => {
|
||||||
const cells = row.querySelectorAll('td');
|
const cells = row.querySelectorAll('td');
|
||||||
@ -349,7 +377,9 @@
|
|||||||
cells[1].textContent, // 降雨量
|
cells[1].textContent, // 降雨量
|
||||||
cells[2].textContent, // 温度
|
cells[2].textContent, // 温度
|
||||||
cells[3].textContent, // 湿度
|
cells[3].textContent, // 湿度
|
||||||
cells[4].textContent // 风速
|
cells[4].textContent, // 风速
|
||||||
|
cells[5].textContent, // 大气压
|
||||||
|
cells[6].textContent // 太阳辐射
|
||||||
];
|
];
|
||||||
csv += rowData.join(',') + '\n';
|
csv += rowData.join(',') + '\n';
|
||||||
});
|
});
|
||||||
@ -357,7 +387,7 @@
|
|||||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = URL.createObjectURL(blob);
|
link.href = URL.createObjectURL(blob);
|
||||||
link.download = '气象站数据.csv';
|
link.download = '雨量计数据.csv';
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user