912 lines
33 KiB
HTML
912 lines
33 KiB
HTML
|
<!DOCTYPE html>
|
|||
|
<html lang="zh-CN">
|
|||
|
<head>
|
|||
|
<meta charset="UTF-8">
|
|||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
<title>设备测试页面</title>
|
|||
|
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
|
|||
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
|||
|
<style>
|
|||
|
* {
|
|||
|
margin: 0;
|
|||
|
padding: 0;
|
|||
|
box-sizing: border-box;
|
|||
|
}
|
|||
|
|
|||
|
body {
|
|||
|
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
|||
|
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
|
|||
|
color: #ffffff;
|
|||
|
min-height: 100vh;
|
|||
|
padding: 20px;
|
|||
|
}
|
|||
|
|
|||
|
.container {
|
|||
|
max-width: 1400px;
|
|||
|
margin: 0 auto;
|
|||
|
}
|
|||
|
|
|||
|
.header {
|
|||
|
text-align: center;
|
|||
|
margin-bottom: 30px;
|
|||
|
}
|
|||
|
|
|||
|
.header h1 {
|
|||
|
font-size: 2.5rem;
|
|||
|
margin-bottom: 10px;
|
|||
|
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
|
|||
|
-webkit-background-clip: text;
|
|||
|
-webkit-text-fill-color: transparent;
|
|||
|
background-clip: text;
|
|||
|
}
|
|||
|
|
|||
|
.control-panel {
|
|||
|
display: flex;
|
|||
|
justify-content: center;
|
|||
|
gap: 20px;
|
|||
|
margin-bottom: 30px;
|
|||
|
}
|
|||
|
|
|||
|
.btn {
|
|||
|
padding: 12px 24px;
|
|||
|
border: none;
|
|||
|
border-radius: 8px;
|
|||
|
font-size: 16px;
|
|||
|
font-weight: bold;
|
|||
|
cursor: pointer;
|
|||
|
transition: all 0.3s ease;
|
|||
|
text-transform: uppercase;
|
|||
|
}
|
|||
|
|
|||
|
.btn-start {
|
|||
|
background: linear-gradient(45deg, #4CAF50, #45a049);
|
|||
|
color: white;
|
|||
|
}
|
|||
|
|
|||
|
.btn-start:hover {
|
|||
|
transform: translateY(-2px);
|
|||
|
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4);
|
|||
|
}
|
|||
|
|
|||
|
.btn-stop {
|
|||
|
background: linear-gradient(45deg, #f44336, #d32f2f);
|
|||
|
color: white;
|
|||
|
}
|
|||
|
|
|||
|
.btn-stop:hover {
|
|||
|
transform: translateY(-2px);
|
|||
|
box-shadow: 0 4px 12px rgba(244, 67, 54, 0.4);
|
|||
|
}
|
|||
|
|
|||
|
.btn:disabled {
|
|||
|
opacity: 0.6;
|
|||
|
cursor: not-allowed;
|
|||
|
transform: none;
|
|||
|
}
|
|||
|
|
|||
|
.status-bar {
|
|||
|
display: flex;
|
|||
|
justify-content: center;
|
|||
|
gap: 30px;
|
|||
|
margin-bottom: 30px;
|
|||
|
padding: 15px;
|
|||
|
background: rgba(255, 255, 255, 0.1);
|
|||
|
border-radius: 10px;
|
|||
|
backdrop-filter: blur(10px);
|
|||
|
}
|
|||
|
|
|||
|
.status-item {
|
|||
|
display: flex;
|
|||
|
align-items: center;
|
|||
|
gap: 10px;
|
|||
|
}
|
|||
|
|
|||
|
.status-indicator {
|
|||
|
width: 12px;
|
|||
|
height: 12px;
|
|||
|
border-radius: 50%;
|
|||
|
background: #ff4444;
|
|||
|
transition: background-color 0.3s ease;
|
|||
|
}
|
|||
|
|
|||
|
.status-indicator.connected {
|
|||
|
background: #44ff44;
|
|||
|
box-shadow: 0 0 10px rgba(68, 255, 68, 0.5);
|
|||
|
}
|
|||
|
|
|||
|
.devices-grid {
|
|||
|
display: grid;
|
|||
|
grid-template-columns: 1fr 1fr;
|
|||
|
grid-template-rows: 1fr 1fr;
|
|||
|
gap: 20px;
|
|||
|
height: 80vh;
|
|||
|
}
|
|||
|
|
|||
|
.device-card {
|
|||
|
background: rgba(255, 255, 255, 0.1);
|
|||
|
border-radius: 15px;
|
|||
|
padding: 20px;
|
|||
|
backdrop-filter: blur(10px);
|
|||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|||
|
transition: transform 0.3s ease;
|
|||
|
}
|
|||
|
|
|||
|
.device-card:hover {
|
|||
|
transform: translateY(-5px);
|
|||
|
}
|
|||
|
|
|||
|
.device-header {
|
|||
|
display: flex;
|
|||
|
justify-content: space-between;
|
|||
|
align-items: center;
|
|||
|
margin-bottom: 15px;
|
|||
|
padding-bottom: 10px;
|
|||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
|||
|
}
|
|||
|
|
|||
|
.device-title {
|
|||
|
font-size: 1.4rem;
|
|||
|
font-weight: bold;
|
|||
|
}
|
|||
|
|
|||
|
.device-status {
|
|||
|
font-size: 0.9rem;
|
|||
|
padding: 4px 12px;
|
|||
|
border-radius: 20px;
|
|||
|
background: rgba(255, 68, 68, 0.2);
|
|||
|
color: #ff4444;
|
|||
|
}
|
|||
|
|
|||
|
.device-status.connected {
|
|||
|
background: rgba(68, 255, 68, 0.2);
|
|||
|
color: #44ff44;
|
|||
|
}
|
|||
|
|
|||
|
.device-content {
|
|||
|
height: calc(100% - 60px);
|
|||
|
display: flex;
|
|||
|
flex-direction: column;
|
|||
|
}
|
|||
|
|
|||
|
.video-container {
|
|||
|
flex: 1;
|
|||
|
display: flex;
|
|||
|
justify-content: center;
|
|||
|
align-items: center;
|
|||
|
background: #000;
|
|||
|
border-radius: 10px;
|
|||
|
overflow: hidden;
|
|||
|
position: relative;
|
|||
|
}
|
|||
|
|
|||
|
.video-container img {
|
|||
|
max-width: 100%;
|
|||
|
max-height: 100%;
|
|||
|
object-fit: contain;
|
|||
|
}
|
|||
|
|
|||
|
.no-signal {
|
|||
|
color: #666;
|
|||
|
font-size: 1.2rem;
|
|||
|
text-align: center;
|
|||
|
}
|
|||
|
|
|||
|
.data-display {
|
|||
|
margin-top: 15px;
|
|||
|
padding: 15px;
|
|||
|
background: rgba(0, 0, 0, 0.3);
|
|||
|
border-radius: 10px;
|
|||
|
}
|
|||
|
|
|||
|
.data-row {
|
|||
|
display: flex;
|
|||
|
justify-content: space-between;
|
|||
|
margin-bottom: 8px;
|
|||
|
}
|
|||
|
|
|||
|
.data-label {
|
|||
|
color: #ccc;
|
|||
|
}
|
|||
|
|
|||
|
.data-value {
|
|||
|
color: #fff;
|
|||
|
font-weight: bold;
|
|||
|
}
|
|||
|
|
|||
|
.imu-gauges {
|
|||
|
display: flex;
|
|||
|
justify-content: space-around;
|
|||
|
margin-top: 10px;
|
|||
|
}
|
|||
|
|
|||
|
.gauge-container {
|
|||
|
text-align: center;
|
|||
|
}
|
|||
|
|
|||
|
.gauge {
|
|||
|
width: 80px;
|
|||
|
height: 80px;
|
|||
|
}
|
|||
|
|
|||
|
.gauge-label {
|
|||
|
font-size: 0.8rem;
|
|||
|
margin-top: 5px;
|
|||
|
color: #ccc;
|
|||
|
}
|
|||
|
|
|||
|
.pressure-visualization {
|
|||
|
display: flex;
|
|||
|
justify-content: center;
|
|||
|
align-items: center;
|
|||
|
margin-top: 10px;
|
|||
|
}
|
|||
|
|
|||
|
.foot-diagram {
|
|||
|
position: relative;
|
|||
|
width: 200px;
|
|||
|
height: 200px;
|
|||
|
}
|
|||
|
|
|||
|
.foot-diagram img {
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
object-fit: contain;
|
|||
|
}
|
|||
|
|
|||
|
.frame-info {
|
|||
|
position: absolute;
|
|||
|
top: 10px;
|
|||
|
left: 10px;
|
|||
|
background: rgba(0, 0, 0, 0.7);
|
|||
|
padding: 5px 10px;
|
|||
|
border-radius: 5px;
|
|||
|
font-size: 0.8rem;
|
|||
|
}
|
|||
|
|
|||
|
.fps-counter {
|
|||
|
position: absolute;
|
|||
|
top: 10px;
|
|||
|
right: 10px;
|
|||
|
background: rgba(0, 0, 0, 0.7);
|
|||
|
padding: 5px 10px;
|
|||
|
border-radius: 5px;
|
|||
|
font-size: 0.8rem;
|
|||
|
}
|
|||
|
|
|||
|
@keyframes pulse {
|
|||
|
0% { opacity: 1; }
|
|||
|
50% { opacity: 0.5; }
|
|||
|
100% { opacity: 1; }
|
|||
|
}
|
|||
|
|
|||
|
.recording {
|
|||
|
animation: pulse 1s infinite;
|
|||
|
}
|
|||
|
|
|||
|
.log-panel {
|
|||
|
position: fixed;
|
|||
|
bottom: 20px;
|
|||
|
right: 20px;
|
|||
|
width: 300px;
|
|||
|
max-height: 200px;
|
|||
|
background: rgba(0, 0, 0, 0.8);
|
|||
|
border-radius: 10px;
|
|||
|
padding: 15px;
|
|||
|
overflow-y: auto;
|
|||
|
font-size: 0.8rem;
|
|||
|
z-index: 1000;
|
|||
|
}
|
|||
|
|
|||
|
.log-entry {
|
|||
|
margin-bottom: 5px;
|
|||
|
padding: 2px 0;
|
|||
|
}
|
|||
|
|
|||
|
.log-timestamp {
|
|||
|
color: #888;
|
|||
|
}
|
|||
|
|
|||
|
.log-message {
|
|||
|
color: #fff;
|
|||
|
}
|
|||
|
|
|||
|
.log-error {
|
|||
|
color: #ff4444;
|
|||
|
}
|
|||
|
|
|||
|
.log-success {
|
|||
|
color: #44ff44;
|
|||
|
}
|
|||
|
</style>
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<div class="container">
|
|||
|
<div class="header">
|
|||
|
<h1>设备测试控制台</h1>
|
|||
|
<p>实时监控四种设备的数据流:深度相机、普通相机、压力板、IMU传感器</p>
|
|||
|
</div>
|
|||
|
|
|||
|
<div class="control-panel">
|
|||
|
<button id="startBtn" class="btn btn-start">开始测试</button>
|
|||
|
<button id="stopBtn" class="btn btn-stop" disabled>停止测试</button>
|
|||
|
</div>
|
|||
|
|
|||
|
<div class="status-bar">
|
|||
|
<div class="status-item">
|
|||
|
<div id="serverStatus" class="status-indicator"></div>
|
|||
|
<span>服务器连接</span>
|
|||
|
</div>
|
|||
|
<div class="status-item">
|
|||
|
<div id="cameraStatus" class="status-indicator"></div>
|
|||
|
<span>普通相机</span>
|
|||
|
</div>
|
|||
|
<div class="status-item">
|
|||
|
<div id="femtoboltStatus" class="status-indicator"></div>
|
|||
|
<span>深度相机</span>
|
|||
|
</div>
|
|||
|
<div class="status-item">
|
|||
|
<div id="imuStatus" class="status-indicator"></div>
|
|||
|
<span>IMU传感器</span>
|
|||
|
</div>
|
|||
|
<div class="status-item">
|
|||
|
<div id="pressureStatus" class="status-indicator"></div>
|
|||
|
<span>压力板</span>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<div class="devices-grid">
|
|||
|
<!-- 普通相机 -->
|
|||
|
<div class="device-card">
|
|||
|
<div class="device-header">
|
|||
|
<div class="device-title">📹 普通相机</div>
|
|||
|
<div id="cameraDeviceStatus" class="device-status">未连接</div>
|
|||
|
</div>
|
|||
|
<div class="device-content">
|
|||
|
<div class="video-container">
|
|||
|
<img id="cameraImage" src="" alt="相机画面" style="display: none;">
|
|||
|
<div id="cameraNoSignal" class="no-signal">等待相机信号...</div>
|
|||
|
<div id="cameraFrameInfo" class="frame-info" style="display: none;">帧数: 0</div>
|
|||
|
<div id="cameraFps" class="fps-counter" style="display: none;">FPS: 0</div>
|
|||
|
</div>
|
|||
|
<div class="data-display">
|
|||
|
<div class="data-row">
|
|||
|
<span class="data-label">分辨率:</span>
|
|||
|
<span id="cameraResolution" class="data-value">-</span>
|
|||
|
</div>
|
|||
|
<div class="data-row">
|
|||
|
<span class="data-label">设备ID:</span>
|
|||
|
<span id="cameraDeviceId" class="data-value">-</span>
|
|||
|
</div>
|
|||
|
<div class="data-row">
|
|||
|
<span class="data-label">最后更新:</span>
|
|||
|
<span id="cameraLastUpdate" class="data-value">-</span>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<!-- 深度相机 -->
|
|||
|
<div class="device-card">
|
|||
|
<div class="device-header">
|
|||
|
<div class="device-title">🔍 深度相机</div>
|
|||
|
<div id="femtoboltDeviceStatus" class="device-status">未连接</div>
|
|||
|
</div>
|
|||
|
<div class="device-content">
|
|||
|
<div class="video-container">
|
|||
|
<img id="femtoboltImage" src="" alt="深度画面" style="display: none;">
|
|||
|
<div id="femtoboltNoSignal" class="no-signal">等待深度相机信号...</div>
|
|||
|
<div id="femtoboltFrameInfo" class="frame-info" style="display: none;">帧数: 0</div>
|
|||
|
<div id="femtoboltFps" class="fps-counter" style="display: none;">FPS: 0</div>
|
|||
|
</div>
|
|||
|
<div class="data-display">
|
|||
|
<div class="data-row">
|
|||
|
<span class="data-label">深度范围:</span>
|
|||
|
<span id="femtoboltDepthRange" class="data-value">-</span>
|
|||
|
</div>
|
|||
|
<div class="data-row">
|
|||
|
<span class="data-label">设备ID:</span>
|
|||
|
<span id="femtoboltDeviceId" class="data-value">-</span>
|
|||
|
</div>
|
|||
|
<div class="data-row">
|
|||
|
<span class="data-label">最后更新:</span>
|
|||
|
<span id="femtoboltLastUpdate" class="data-value">-</span>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<!-- IMU传感器 -->
|
|||
|
<div class="device-card">
|
|||
|
<div class="device-header">
|
|||
|
<div class="device-title">🧭 IMU传感器</div>
|
|||
|
<div id="imuDeviceStatus" class="device-status">未连接</div>
|
|||
|
</div>
|
|||
|
<div class="device-content">
|
|||
|
<div class="imu-gauges">
|
|||
|
<div class="gauge-container">
|
|||
|
<div id="rotationGauge" class="gauge"></div>
|
|||
|
<div class="gauge-label">旋转角</div>
|
|||
|
<div id="rotationValue" class="data-value">0°</div>
|
|||
|
</div>
|
|||
|
<div class="gauge-container">
|
|||
|
<div id="tiltGauge" class="gauge"></div>
|
|||
|
<div class="gauge-label">倾斜角</div>
|
|||
|
<div id="tiltValue" class="data-value">0°</div>
|
|||
|
</div>
|
|||
|
<div class="gauge-container">
|
|||
|
<div id="pitchGauge" class="gauge"></div>
|
|||
|
<div class="gauge-label">俯仰角</div>
|
|||
|
<div id="pitchValue" class="data-value">0°</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<div class="data-display">
|
|||
|
<div class="data-row">
|
|||
|
<span class="data-label">加速度 X:</span>
|
|||
|
<span id="accelX" class="data-value">0</span>
|
|||
|
</div>
|
|||
|
<div class="data-row">
|
|||
|
<span class="data-label">加速度 Y:</span>
|
|||
|
<span id="accelY" class="data-value">0</span>
|
|||
|
</div>
|
|||
|
<div class="data-row">
|
|||
|
<span class="data-label">加速度 Z:</span>
|
|||
|
<span id="accelZ" class="data-value">0</span>
|
|||
|
</div>
|
|||
|
<div class="data-row">
|
|||
|
<span class="data-label">温度:</span>
|
|||
|
<span id="temperature" class="data-value">0°C</span>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<!-- 压力板 -->
|
|||
|
<div class="device-card">
|
|||
|
<div class="device-header">
|
|||
|
<div class="device-title">⚖️ 压力板</div>
|
|||
|
<div id="pressureDeviceStatus" class="device-status">未连接</div>
|
|||
|
</div>
|
|||
|
<div class="device-content">
|
|||
|
<div class="pressure-visualization">
|
|||
|
<div class="foot-diagram">
|
|||
|
<img id="pressureImage" src="" alt="压力分布" style="display: none;">
|
|||
|
<div id="pressureNoSignal" class="no-signal">等待压力数据...</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<div class="data-display">
|
|||
|
<div class="data-row">
|
|||
|
<span class="data-label">左足总压力:</span>
|
|||
|
<span id="leftTotal" class="data-value">0</span>
|
|||
|
</div>
|
|||
|
<div class="data-row">
|
|||
|
<span class="data-label">右足总压力:</span>
|
|||
|
<span id="rightTotal" class="data-value">0</span>
|
|||
|
</div>
|
|||
|
<div class="data-row">
|
|||
|
<span class="data-label">总压力:</span>
|
|||
|
<span id="totalPressure" class="data-value">0</span>
|
|||
|
</div>
|
|||
|
<div class="data-row">
|
|||
|
<span class="data-label">平衡比例:</span>
|
|||
|
<span id="balanceRatio" class="data-value">50%</span>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<!-- 日志面板 -->
|
|||
|
<div class="log-panel">
|
|||
|
<div style="font-weight: bold; margin-bottom: 10px; border-bottom: 1px solid #333; padding-bottom: 5px;">系统日志</div>
|
|||
|
<div id="logContainer"></div>
|
|||
|
</div>
|
|||
|
|
|||
|
<script>
|
|||
|
// 全局变量
|
|||
|
let socket = null;
|
|||
|
let isConnected = false;
|
|||
|
let isTesting = false;
|
|||
|
|
|||
|
// FPS计算
|
|||
|
const fpsCounters = {
|
|||
|
camera: { frames: 0, lastTime: Date.now() },
|
|||
|
femtobolt: { frames: 0, lastTime: Date.now() }
|
|||
|
};
|
|||
|
|
|||
|
// ECharts图表实例
|
|||
|
let rotationChart, tiltChart, pitchChart;
|
|||
|
|
|||
|
// DOM元素
|
|||
|
const elements = {
|
|||
|
startBtn: document.getElementById('startBtn'),
|
|||
|
stopBtn: document.getElementById('stopBtn'),
|
|||
|
serverStatus: document.getElementById('serverStatus'),
|
|||
|
cameraStatus: document.getElementById('cameraStatus'),
|
|||
|
femtoboltStatus: document.getElementById('femtoboltStatus'),
|
|||
|
imuStatus: document.getElementById('imuStatus'),
|
|||
|
pressureStatus: document.getElementById('pressureStatus'),
|
|||
|
logContainer: document.getElementById('logContainer')
|
|||
|
};
|
|||
|
|
|||
|
// 初始化
|
|||
|
document.addEventListener('DOMContentLoaded', function() {
|
|||
|
initializeCharts();
|
|||
|
setupEventListeners();
|
|||
|
connectToServer();
|
|||
|
addLog('系统初始化完成', 'success');
|
|||
|
});
|
|||
|
|
|||
|
// 设置事件监听器
|
|||
|
function setupEventListeners() {
|
|||
|
elements.startBtn.addEventListener('click', startTest);
|
|||
|
elements.stopBtn.addEventListener('click', stopTest);
|
|||
|
}
|
|||
|
|
|||
|
// 初始化ECharts图表
|
|||
|
function initializeCharts() {
|
|||
|
const gaugeOption = {
|
|||
|
backgroundColor: 'transparent',
|
|||
|
series: [{
|
|||
|
type: 'gauge',
|
|||
|
radius: '100%',
|
|||
|
min: -90,
|
|||
|
max: 90,
|
|||
|
splitNumber: 6,
|
|||
|
axisLine: {
|
|||
|
lineStyle: {
|
|||
|
width: 6,
|
|||
|
color: [[0.3, '#67e0e3'], [0.7, '#37a2da'], [1, '#fd666d']]
|
|||
|
}
|
|||
|
},
|
|||
|
pointer: {
|
|||
|
itemStyle: {
|
|||
|
color: 'auto'
|
|||
|
}
|
|||
|
},
|
|||
|
axisTick: {
|
|||
|
distance: -30,
|
|||
|
length: 8,
|
|||
|
lineStyle: {
|
|||
|
color: '#fff',
|
|||
|
width: 2
|
|||
|
}
|
|||
|
},
|
|||
|
splitLine: {
|
|||
|
distance: -30,
|
|||
|
length: 30,
|
|||
|
lineStyle: {
|
|||
|
color: '#fff',
|
|||
|
width: 4
|
|||
|
}
|
|||
|
},
|
|||
|
axisLabel: {
|
|||
|
color: 'auto',
|
|||
|
distance: 40,
|
|||
|
fontSize: 10
|
|||
|
},
|
|||
|
detail: {
|
|||
|
valueAnimation: true,
|
|||
|
formatter: '{value}°',
|
|||
|
color: 'auto',
|
|||
|
fontSize: 12
|
|||
|
},
|
|||
|
data: [{
|
|||
|
value: 0
|
|||
|
}]
|
|||
|
}]
|
|||
|
};
|
|||
|
|
|||
|
rotationChart = echarts.init(document.getElementById('rotationGauge'));
|
|||
|
tiltChart = echarts.init(document.getElementById('tiltGauge'));
|
|||
|
pitchChart = echarts.init(document.getElementById('pitchGauge'));
|
|||
|
|
|||
|
rotationChart.setOption(gaugeOption);
|
|||
|
tiltChart.setOption(gaugeOption);
|
|||
|
pitchChart.setOption(gaugeOption);
|
|||
|
}
|
|||
|
|
|||
|
// 连接到服务器
|
|||
|
function connectToServer() {
|
|||
|
addLog('正在连接到测试服务器...');
|
|||
|
|
|||
|
// 创建主连接
|
|||
|
socket = io('http://localhost:5001', {
|
|||
|
transports: ['websocket', 'polling'],
|
|||
|
timeout: 10000,
|
|||
|
forceNew: true
|
|||
|
});
|
|||
|
|
|||
|
// 创建各设备命名空间连接
|
|||
|
const cameraSocket = io('http://localhost:5001/camera');
|
|||
|
const femtoboltSocket = io('http://localhost:5001/femtobolt');
|
|||
|
const imuSocket = io('http://localhost:5001/imu');
|
|||
|
const pressureSocket = io('http://localhost:5001/pressure');
|
|||
|
|
|||
|
// 主连接事件
|
|||
|
socket.on('connect', () => {
|
|||
|
isConnected = true;
|
|||
|
elements.serverStatus.classList.add('connected');
|
|||
|
addLog('服务器连接成功', 'success');
|
|||
|
elements.startBtn.disabled = false;
|
|||
|
});
|
|||
|
|
|||
|
socket.on('disconnect', () => {
|
|||
|
isConnected = false;
|
|||
|
elements.serverStatus.classList.remove('connected');
|
|||
|
addLog('服务器连接断开', 'error');
|
|||
|
if (isTesting) {
|
|||
|
stopTest();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
socket.on('connect_error', (error) => {
|
|||
|
addLog(`连接错误: ${error.message}`, 'error');
|
|||
|
});
|
|||
|
|
|||
|
// 测试状态事件
|
|||
|
socket.on('test_status', (data) => {
|
|||
|
addLog(`测试状态: ${data.message}`, data.status === 'error' ? 'error' : 'success');
|
|||
|
});
|
|||
|
|
|||
|
// 设备命名空间数据事件监听
|
|||
|
cameraSocket.on('camera_frame', handleCameraData);
|
|||
|
femtoboltSocket.on('femtobolt_frame', handleFemtoBoltData);
|
|||
|
imuSocket.on('imu_data', handleIMUData);
|
|||
|
pressureSocket.on('pressure_data', handlePressureData);
|
|||
|
|
|||
|
// 设备连接状态监听
|
|||
|
cameraSocket.on('connect', () => {
|
|||
|
elements.cameraStatus.classList.add('connected');
|
|||
|
addLog('相机命名空间已连接', 'success');
|
|||
|
});
|
|||
|
|
|||
|
femtoboltSocket.on('connect', () => {
|
|||
|
elements.femtoboltStatus.classList.add('connected');
|
|||
|
addLog('深度相机命名空间已连接', 'success');
|
|||
|
});
|
|||
|
|
|||
|
imuSocket.on('connect', () => {
|
|||
|
elements.imuStatus.classList.add('connected');
|
|||
|
addLog('IMU命名空间已连接', 'success');
|
|||
|
});
|
|||
|
|
|||
|
pressureSocket.on('connect', () => {
|
|||
|
elements.pressureStatus.classList.add('connected');
|
|||
|
addLog('压力板命名空间已连接', 'success');
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
// 设置设备事件监听器(已移至connectToServer函数中)
|
|||
|
function setupDeviceEventListeners() {
|
|||
|
// 此函数已废弃,事件监听器现在在connectToServer中设置
|
|||
|
}
|
|||
|
|
|||
|
// 开始测试
|
|||
|
function startTest() {
|
|||
|
if (!isConnected) {
|
|||
|
addLog('服务器未连接,无法开始测试', 'error');
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
isTesting = true;
|
|||
|
elements.startBtn.disabled = true;
|
|||
|
elements.stopBtn.disabled = false;
|
|||
|
|
|||
|
socket.emit('start_test');
|
|||
|
addLog('开始设备测试', 'success');
|
|||
|
}
|
|||
|
|
|||
|
// 停止测试
|
|||
|
function stopTest() {
|
|||
|
isTesting = false;
|
|||
|
elements.startBtn.disabled = false;
|
|||
|
elements.stopBtn.disabled = true;
|
|||
|
|
|||
|
// 重置所有状态指示器
|
|||
|
resetDeviceStatus();
|
|||
|
|
|||
|
if (socket && isConnected) {
|
|||
|
socket.emit('stop_test');
|
|||
|
}
|
|||
|
addLog('停止设备测试', 'success');
|
|||
|
}
|
|||
|
|
|||
|
// 重置设备状态
|
|||
|
function resetDeviceStatus() {
|
|||
|
elements.cameraStatus.classList.remove('connected');
|
|||
|
elements.femtoboltStatus.classList.remove('connected');
|
|||
|
elements.imuStatus.classList.remove('connected');
|
|||
|
elements.pressureStatus.classList.remove('connected');
|
|||
|
|
|||
|
document.getElementById('cameraDeviceStatus').textContent = '未连接';
|
|||
|
document.getElementById('cameraDeviceStatus').classList.remove('connected');
|
|||
|
|
|||
|
document.getElementById('femtoboltDeviceStatus').textContent = '未连接';
|
|||
|
document.getElementById('femtoboltDeviceStatus').classList.remove('connected');
|
|||
|
|
|||
|
document.getElementById('imuDeviceStatus').textContent = '未连接';
|
|||
|
document.getElementById('imuDeviceStatus').classList.remove('connected');
|
|||
|
|
|||
|
document.getElementById('pressureDeviceStatus').textContent = '未连接';
|
|||
|
document.getElementById('pressureDeviceStatus').classList.remove('connected');
|
|||
|
|
|||
|
// 隐藏图像和数据
|
|||
|
document.getElementById('cameraImage').style.display = 'none';
|
|||
|
document.getElementById('cameraNoSignal').style.display = 'block';
|
|||
|
document.getElementById('femtoboltImage').style.display = 'none';
|
|||
|
document.getElementById('femtoboltNoSignal').style.display = 'block';
|
|||
|
document.getElementById('pressureImage').style.display = 'none';
|
|||
|
document.getElementById('pressureNoSignal').style.display = 'block';
|
|||
|
}
|
|||
|
|
|||
|
// 处理普通相机数据
|
|||
|
function handleCameraData(data) {
|
|||
|
if (!isTesting) return;
|
|||
|
|
|||
|
elements.cameraStatus.classList.add('connected');
|
|||
|
document.getElementById('cameraDeviceStatus').textContent = '已连接';
|
|||
|
document.getElementById('cameraDeviceStatus').classList.add('connected');
|
|||
|
|
|||
|
if (data.image) {
|
|||
|
const img = document.getElementById('cameraImage');
|
|||
|
img.src = 'data:image/jpeg;base64,' + data.image;
|
|||
|
img.style.display = 'block';
|
|||
|
document.getElementById('cameraNoSignal').style.display = 'none';
|
|||
|
|
|||
|
// 更新帧信息
|
|||
|
document.getElementById('cameraFrameInfo').textContent = `帧数: ${data.frame_count || 0}`;
|
|||
|
document.getElementById('cameraFrameInfo').style.display = 'block';
|
|||
|
|
|||
|
// 计算FPS
|
|||
|
updateFPS('camera', data.fps || 30);
|
|||
|
}
|
|||
|
|
|||
|
// 更新设备信息
|
|||
|
if (data.resolution) {
|
|||
|
document.getElementById('cameraResolution').textContent = `${data.resolution.width}x${data.resolution.height}`;
|
|||
|
}
|
|||
|
document.getElementById('cameraDeviceId').textContent = data.device_id || 'mock_camera';
|
|||
|
document.getElementById('cameraLastUpdate').textContent = new Date().toLocaleTimeString();
|
|||
|
}
|
|||
|
|
|||
|
// 处理深度相机数据
|
|||
|
function handleFemtoBoltData(data) {
|
|||
|
if (!isTesting) return;
|
|||
|
|
|||
|
elements.femtoboltStatus.classList.add('connected');
|
|||
|
document.getElementById('femtoboltDeviceStatus').textContent = '已连接';
|
|||
|
document.getElementById('femtoboltDeviceStatus').classList.add('connected');
|
|||
|
|
|||
|
if (data.depth_image) {
|
|||
|
const img = document.getElementById('femtoboltImage');
|
|||
|
img.src = 'data:image/jpeg;base64,' + data.depth_image;
|
|||
|
img.style.display = 'block';
|
|||
|
document.getElementById('femtoboltNoSignal').style.display = 'none';
|
|||
|
|
|||
|
// 更新帧信息
|
|||
|
document.getElementById('femtoboltFrameInfo').textContent = `帧数: ${data.frame_count || 0}`;
|
|||
|
document.getElementById('femtoboltFrameInfo').style.display = 'block';
|
|||
|
|
|||
|
// 计算FPS
|
|||
|
updateFPS('femtobolt', data.fps || 15);
|
|||
|
}
|
|||
|
|
|||
|
// 更新设备信息
|
|||
|
if (data.depth_range) {
|
|||
|
document.getElementById('femtoboltDepthRange').textContent = `${data.depth_range.min}-${data.depth_range.max}mm`;
|
|||
|
}
|
|||
|
document.getElementById('femtoboltDeviceId').textContent = data.device_id || 'mock_femtobolt';
|
|||
|
document.getElementById('femtoboltLastUpdate').textContent = new Date().toLocaleTimeString();
|
|||
|
}
|
|||
|
|
|||
|
// 处理IMU数据
|
|||
|
function handleIMUData(data) {
|
|||
|
if (!isTesting) return;
|
|||
|
|
|||
|
elements.imuStatus.classList.add('connected');
|
|||
|
document.getElementById('imuDeviceStatus').textContent = '已连接';
|
|||
|
document.getElementById('imuDeviceStatus').classList.add('connected');
|
|||
|
|
|||
|
if (data.head_pose) {
|
|||
|
const { rotation, tilt, pitch } = data.head_pose;
|
|||
|
|
|||
|
// 更新仪表盘
|
|||
|
rotationChart.setOption({
|
|||
|
series: [{ data: [{ value: rotation }] }]
|
|||
|
});
|
|||
|
tiltChart.setOption({
|
|||
|
series: [{ data: [{ value: tilt }] }]
|
|||
|
});
|
|||
|
pitchChart.setOption({
|
|||
|
series: [{ data: [{ value: pitch }] }]
|
|||
|
});
|
|||
|
|
|||
|
// 更新数值显示
|
|||
|
document.getElementById('rotationValue').textContent = `${rotation}°`;
|
|||
|
document.getElementById('tiltValue').textContent = `${tilt}°`;
|
|||
|
document.getElementById('pitchValue').textContent = `${pitch}°`;
|
|||
|
}
|
|||
|
|
|||
|
// 更新加速度和温度数据
|
|||
|
if (data.accelerometer) {
|
|||
|
document.getElementById('accelX').textContent = data.accelerometer.x;
|
|||
|
document.getElementById('accelY').textContent = data.accelerometer.y;
|
|||
|
document.getElementById('accelZ').textContent = data.accelerometer.z;
|
|||
|
}
|
|||
|
|
|||
|
if (data.temperature) {
|
|||
|
document.getElementById('temperature').textContent = `${data.temperature}°C`;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 处理压力板数据
|
|||
|
function handlePressureData(data) {
|
|||
|
if (!isTesting) return;
|
|||
|
|
|||
|
elements.pressureStatus.classList.add('connected');
|
|||
|
document.getElementById('pressureDeviceStatus').textContent = '已连接';
|
|||
|
document.getElementById('pressureDeviceStatus').classList.add('connected');
|
|||
|
|
|||
|
if (data.pressure_image) {
|
|||
|
const img = document.getElementById('pressureImage');
|
|||
|
img.src = 'data:image/jpeg;base64,' + data.pressure_image;
|
|||
|
img.style.display = 'block';
|
|||
|
document.getElementById('pressureNoSignal').style.display = 'none';
|
|||
|
}
|
|||
|
|
|||
|
// 更新压力数据
|
|||
|
if (data.pressure_data) {
|
|||
|
const pd = data.pressure_data;
|
|||
|
document.getElementById('leftTotal').textContent = pd.left_total;
|
|||
|
document.getElementById('rightTotal').textContent = pd.right_total;
|
|||
|
document.getElementById('totalPressure').textContent = pd.total_pressure;
|
|||
|
document.getElementById('balanceRatio').textContent = `${pd.balance_ratio}%`;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 更新FPS显示
|
|||
|
function updateFPS(device, targetFps) {
|
|||
|
const counter = fpsCounters[device];
|
|||
|
counter.frames++;
|
|||
|
|
|||
|
const now = Date.now();
|
|||
|
if (now - counter.lastTime >= 1000) {
|
|||
|
const fps = Math.round(counter.frames * 1000 / (now - counter.lastTime));
|
|||
|
document.getElementById(`${device}Fps`).textContent = `FPS: ${fps}`;
|
|||
|
document.getElementById(`${device}Fps`).style.display = 'block';
|
|||
|
|
|||
|
counter.frames = 0;
|
|||
|
counter.lastTime = now;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 添加日志
|
|||
|
function addLog(message, type = 'info') {
|
|||
|
const logEntry = document.createElement('div');
|
|||
|
logEntry.className = 'log-entry';
|
|||
|
|
|||
|
const timestamp = new Date().toLocaleTimeString();
|
|||
|
logEntry.innerHTML = `
|
|||
|
<span class="log-timestamp">[${timestamp}]</span>
|
|||
|
<span class="log-message log-${type}">${message}</span>
|
|||
|
`;
|
|||
|
|
|||
|
elements.logContainer.appendChild(logEntry);
|
|||
|
elements.logContainer.scrollTop = elements.logContainer.scrollHeight;
|
|||
|
|
|||
|
// 限制日志条数
|
|||
|
while (elements.logContainer.children.length > 50) {
|
|||
|
elements.logContainer.removeChild(elements.logContainer.firstChild);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 页面卸载时清理资源
|
|||
|
window.addEventListener('beforeunload', () => {
|
|||
|
if (socket) {
|
|||
|
socket.disconnect();
|
|||
|
}
|
|||
|
});
|
|||
|
</script>
|
|||
|
</body>
|
|||
|
</html>
|