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> |