合并修改

This commit is contained in:
jingna 2025-08-06 14:28:39 +08:00
commit 75950d5acf
3 changed files with 482 additions and 72 deletions

View File

@ -561,6 +561,37 @@ def calibrate_devices():
logger.error(f'设备校准失败: {e}') logger.error(f'设备校准失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500 return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/devices/calibrate/imu', methods=['POST'])
def calibrate_imu():
"""校准IMU头部姿态传感器"""
try:
if not device_manager:
return jsonify({'success': False, 'error': '设备管理器未初始化'}), 500
if not device_manager.device_status.get('imu', False):
return jsonify({'success': False, 'error': 'IMU设备未连接'}), 400
# 执行IMU校准
result = device_manager._calibrate_imu()
if result.get('status') == 'success':
logger.info('IMU头部姿态校准成功')
return jsonify({
'success': True,
'message': 'IMU头部姿态校准成功正立状态已设为零位基准',
'data': result
})
else:
logger.error(f'IMU校准失败: {result.get("error", "未知错误")}')
return jsonify({
'success': False,
'error': result.get('error', 'IMU校准失败')
}), 500
except Exception as e:
logger.error(f'IMU校准异常: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
# ==================== 视频推流API ==================== # ==================== 视频推流API ====================
@app.route('/api/streaming/start', methods=['POST']) @app.route('/api/streaming/start', methods=['POST'])

View File

@ -429,13 +429,25 @@ class DeviceManager:
'z': np.mean([s['gyro']['z'] for s in samples]) 'z': np.mean([s['gyro']['z'] for s in samples])
} }
# 计算头部姿态零点偏移(正立状态为标准零位)
head_pose_offset = {
'rotation': np.mean([s['head_pose']['rotation'] for s in samples if 'head_pose' in s]),
'tilt': np.mean([s['head_pose']['tilt'] for s in samples if 'head_pose' in s]),
'pitch': np.mean([s['head_pose']['pitch'] for s in samples if 'head_pose' in s])
}
calibration = { calibration = {
'status': 'success', 'status': 'success',
'accel_offset': accel_offset, 'accel_offset': accel_offset,
'gyro_offset': gyro_offset, 'gyro_offset': gyro_offset,
'head_pose_offset': head_pose_offset, # 头部姿态零点偏移
'timestamp': datetime.now().isoformat() 'timestamp': datetime.now().isoformat()
} }
# 保存校准数据到设备实例
if hasattr(self.imu_device, 'set_calibration'):
self.imu_device.set_calibration(calibration)
return calibration return calibration
except Exception as e: except Exception as e:
@ -1123,27 +1135,16 @@ class DeviceManager:
# 从IMU设备读取数据 # 从IMU设备读取数据
imu_data = self.imu_device.read_data() imu_data = self.imu_device.read_data()
if imu_data: if imu_data and 'head_pose' in imu_data:
# 计算头部姿态角度(欧拉角) # 直接使用设备提供的头部姿态数据
# 这里使用简化的计算方法,实际应用中可能需要更复杂的姿态解算 head_pose = imu_data['head_pose']
accel = imu_data['accel']
gyro = imu_data['gyro']
# 计算俯仰角和横滚角(基于加速度计) # 构建完整的头部姿态数据
import math
pitch = math.atan2(accel['x'], math.sqrt(accel['y']**2 + accel['z']**2)) * 180 / math.pi
roll = math.atan2(accel['y'], math.sqrt(accel['x']**2 + accel['z']**2)) * 180 / math.pi
# 偏航角需要磁力计数据,这里使用陀螺仪积分(简化处理)
yaw = gyro['z'] * 0.1 # 简化的偏航角计算
# 构建头部姿态数据
head_pose_data = { head_pose_data = {
'roll': roll, 'rotation': head_pose['rotation'], # 旋转角:左旋(-), 右旋(+)
'pitch': pitch, 'tilt': head_pose['tilt'], # 倾斜角:左倾(-), 右倾(+)
'yaw': yaw, 'pitch': head_pose['pitch'], # 俯仰角:俯角(-), 仰角(+)
'acceleration': accel,
'gyroscope': gyro,
'temperature': imu_data.get('temperature', 25), 'temperature': imu_data.get('temperature', 25),
'timestamp': imu_data['timestamp'] 'timestamp': imu_data['timestamp']
} }
@ -1176,32 +1177,58 @@ class DeviceManager:
# 从压力传感器设备读取数据 # 从压力传感器设备读取数据
pressure_data = self.pressure_device.read_data() pressure_data = self.pressure_device.read_data()
if pressure_data: if pressure_data and 'foot_pressure' in pressure_data:
# 计算平衡相关指标 foot_pressure = pressure_data['foot_pressure']
left_pressure = pressure_data['left_foot']
right_pressure = pressure_data['right_foot'] # 获取各区域压力值
total_pressure = left_pressure + right_pressure left_front = foot_pressure['left_front']
left_rear = foot_pressure['left_rear']
right_front = foot_pressure['right_front']
right_rear = foot_pressure['right_rear']
left_total = foot_pressure['left_total']
right_total = foot_pressure['right_total']
# 计算总压力
total_pressure = left_total + right_total
# 计算平衡比例(左脚压力占总压力的比例) # 计算平衡比例(左脚压力占总压力的比例)
balance_ratio = left_pressure / total_pressure if total_pressure > 0 else 0.5 balance_ratio = left_total / total_pressure if total_pressure > 0 else 0.5
# 计算压力中心偏移 # 计算压力中心偏移
pressure_center_offset = (balance_ratio - 0.5) * 100 # 转换为百分比 pressure_center_offset = (balance_ratio - 0.5) * 100 # 转换为百分比
# 构建足部压力数据 # 计算前后足压力分布
foot_pressure_data = { left_front_ratio = left_front / left_total if left_total > 0 else 0.5
'left_foot_pressure': left_pressure, right_front_ratio = right_front / right_total if right_total > 0 else 0.5
'right_foot_pressure': right_pressure,
'total_pressure': total_pressure, # 构建完整的足部压力数据
'balance_ratio': balance_ratio, complete_pressure_data = {
'pressure_center_offset': pressure_center_offset, # 分区压力值
'pressure_zones': {
'left_front': left_front,
'left_rear': left_rear,
'right_front': right_front,
'right_rear': right_rear,
'left_total': left_total,
'right_total': right_total,
'total_pressure': total_pressure
},
# 平衡分析
'balance_analysis': {
'balance_ratio': round(balance_ratio, 3),
'pressure_center_offset': round(pressure_center_offset, 2),
'balance_status': 'balanced' if abs(pressure_center_offset) < 10 else 'unbalanced', 'balance_status': 'balanced' if abs(pressure_center_offset) < 10 else 'unbalanced',
'left_front_ratio': round(left_front_ratio, 3),
'right_front_ratio': round(right_front_ratio, 3)
},
# 压力图片
'pressure_image': pressure_data.get('pressure_image', ''),
'timestamp': pressure_data['timestamp'] 'timestamp': pressure_data['timestamp']
} }
# 通过WebSocket发送足部压力数据 # 通过WebSocket发送足部压力数据
self.socketio.emit('pressure_data', { self.socketio.emit('pressure_data', {
'foot_pressure': foot_pressure_data, 'foot_pressure': complete_pressure_data,
'timestamp': datetime.now().isoformat() 'timestamp': datetime.now().isoformat()
}) })
@ -1624,24 +1651,60 @@ class MockIMUDevice:
def __init__(self): def __init__(self):
self.noise_level = 0.1 self.noise_level = 0.1
self.calibration_data = None # 校准数据
self.head_pose_offset = {'rotation': 0, 'tilt': 0, 'pitch': 0} # 头部姿态零点偏移
def set_calibration(self, calibration: Dict[str, Any]):
"""设置校准数据"""
self.calibration_data = calibration
if 'head_pose_offset' in calibration:
self.head_pose_offset = calibration['head_pose_offset']
def apply_calibration(self, raw_data: Dict[str, Any]) -> Dict[str, Any]:
"""应用校准数据"""
if not self.calibration_data:
return raw_data
# 应用头部姿态零点校准
if 'head_pose' in raw_data:
raw_data['head_pose']['rotation'] -= self.head_pose_offset['rotation']
raw_data['head_pose']['tilt'] -= self.head_pose_offset['tilt']
raw_data['head_pose']['pitch'] -= self.head_pose_offset['pitch']
return raw_data
def read_data(self) -> Dict[str, Any]: def read_data(self) -> Dict[str, Any]:
"""读取IMU数据""" """读取IMU数据"""
return { # 生成头部姿态角度数据,角度范围(-90°, +90°)
'accel': { # 使用正弦波模拟自然的头部运动,添加随机噪声
'x': np.random.normal(0, self.noise_level), import time
'y': np.random.normal(0, self.noise_level), current_time = time.time()
'z': np.random.normal(9.8, self.noise_level) # 重力加速度
# 旋转角(左旋为负,右旋为正)
rotation_angle = 30 * np.sin(current_time * 0.5) + np.random.normal(0, self.noise_level * 5)
rotation_angle = np.clip(rotation_angle, -90, 90)
# 倾斜角(左倾为负,右倾为正)
tilt_angle = 20 * np.sin(current_time * 0.3 + np.pi/4) + np.random.normal(0, self.noise_level * 5)
tilt_angle = np.clip(tilt_angle, -90, 90)
# 俯仰角(俯角为负,仰角为正)
pitch_angle = 15 * np.sin(current_time * 0.7 + np.pi/2) + np.random.normal(0, self.noise_level * 5)
pitch_angle = np.clip(pitch_angle, -90, 90)
# 生成原始数据
raw_data = {
'head_pose': {
'rotation': rotation_angle, # 旋转角:左旋(-), 右旋(+)
'tilt': tilt_angle, # 倾斜角:左倾(-), 右倾(+)
'pitch': pitch_angle # 俯仰角:俯角(-), 仰角(+)
}, },
'gyro': {
'x': np.random.normal(0, self.noise_level),
'y': np.random.normal(0, self.noise_level),
'z': np.random.normal(0, self.noise_level)
},
'temperature': np.random.normal(25, 2),
'timestamp': datetime.now().isoformat() 'timestamp': datetime.now().isoformat()
} }
# 应用校准并返回
return self.apply_calibration(raw_data)
class MockPressureDevice: class MockPressureDevice:
"""模拟压力传感器设备""" """模拟压力传感器设备"""
@ -1652,16 +1715,94 @@ class MockPressureDevice:
def read_data(self) -> Dict[str, Any]: def read_data(self) -> Dict[str, Any]:
"""读取压力数据""" """读取压力数据"""
# 模拟轻微的左右脚压力差异 # 模拟各个足部区域的压力值
left_pressure = self.base_pressure + np.random.normal(0, self.noise_level) left_front = max(0, self.base_pressure * 0.6 + np.random.normal(0, self.noise_level))
right_pressure = self.base_pressure + np.random.normal(0, self.noise_level) left_rear = max(0, self.base_pressure * 0.4 + np.random.normal(0, self.noise_level))
right_front = max(0, self.base_pressure * 0.6 + np.random.normal(0, self.noise_level))
right_rear = max(0, self.base_pressure * 0.4 + np.random.normal(0, self.noise_level))
# 计算总压力
left_total = left_front + left_rear
right_total = right_front + right_rear
# 生成模拟的足部压力图片base64格式
pressure_image_base64 = self._generate_pressure_image(left_front, left_rear, right_front, right_rear)
return { return {
'left_foot': max(0, left_pressure), 'foot_pressure': {
'right_foot': max(0, right_pressure), 'left_front': round(left_front, 2), # 左前足压力
'left_rear': round(left_rear, 2), # 左后足压力
'right_front': round(right_front, 2), # 右前足压力
'right_rear': round(right_rear, 2), # 右后足压力
'left_total': round(left_total, 2), # 左足总压力
'right_total': round(right_total, 2) # 右足总压力
},
'pressure_image': pressure_image_base64, # 足部压力图片base64格式
'timestamp': datetime.now().isoformat() 'timestamp': datetime.now().isoformat()
} }
def _generate_pressure_image(self, left_front, left_rear, right_front, right_rear) -> str:
"""生成足部压力图片的base64数据"""
try:
import base64
from io import BytesIO
import matplotlib.pyplot as plt
import matplotlib.patches as patches
# 创建图形
fig, ax = plt.subplots(1, 1, figsize=(6, 8))
ax.set_xlim(0, 10)
ax.set_ylim(0, 12)
ax.set_aspect('equal')
ax.axis('off')
# 定义颜色映射(根据压力值)
max_pressure = max(left_front, left_rear, right_front, right_rear)
if max_pressure > 0:
left_front_color = plt.cm.Reds(left_front / max_pressure)
left_rear_color = plt.cm.Reds(left_rear / max_pressure)
right_front_color = plt.cm.Reds(right_front / max_pressure)
right_rear_color = plt.cm.Reds(right_rear / max_pressure)
else:
left_front_color = left_rear_color = right_front_color = right_rear_color = 'lightgray'
# 绘制左脚
left_front_rect = patches.Rectangle((1, 6), 2, 4, linewidth=1, edgecolor='black', facecolor=left_front_color)
left_rear_rect = patches.Rectangle((1, 2), 2, 4, linewidth=1, edgecolor='black', facecolor=left_rear_color)
# 绘制右脚
right_front_rect = patches.Rectangle((7, 6), 2, 4, linewidth=1, edgecolor='black', facecolor=right_front_color)
right_rear_rect = patches.Rectangle((7, 2), 2, 4, linewidth=1, edgecolor='black', facecolor=right_rear_color)
# 添加到图形
ax.add_patch(left_front_rect)
ax.add_patch(left_rear_rect)
ax.add_patch(right_front_rect)
ax.add_patch(right_rear_rect)
# 添加标签
ax.text(2, 8, f'{left_front:.1f}', ha='center', va='center', fontsize=10, weight='bold')
ax.text(2, 4, f'{left_rear:.1f}', ha='center', va='center', fontsize=10, weight='bold')
ax.text(8, 8, f'{right_front:.1f}', ha='center', va='center', fontsize=10, weight='bold')
ax.text(8, 4, f'{right_rear:.1f}', ha='center', va='center', fontsize=10, weight='bold')
ax.text(2, 0.5, '左足', ha='center', va='center', fontsize=12, weight='bold')
ax.text(8, 0.5, '右足', ha='center', va='center', fontsize=12, weight='bold')
# 保存为base64
buffer = BytesIO()
plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100, facecolor='white')
buffer.seek(0)
image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
plt.close(fig)
return f"data:image/png;base64,{image_base64}"
except Exception as e:
logger.warning(f"生成压力图片失败: {e}")
# 返回一个简单的占位符base64图片
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
class VideoStreamManager: class VideoStreamManager:
"""视频推流管理器""" """视频推流管理器"""

View File

@ -82,10 +82,26 @@
</div> </div>
<div style="margin-left: 10px;font-size: 14px;">{{ imuStatus }}</div> <div style="margin-left: 10px;font-size: 14px;">{{ imuStatus }}</div>
</div> </div>
<el-button type="primary" class="start-btn" style="background-image: linear-gradient(to right, rgb(236, 50, 166), rgb(160, 5, 216)); <div style="display: flex; gap: 10px;">
--el-button-border-color: transparent !important;border-radius: 20px;border:none;width: 150px;"> <el-button
清零 type="primary"
class="start-btn"
@click="clearAndStartTracking"
:disabled="isRecording"
style="background-image: linear-gradient(to right, rgb(236, 50, 166), rgb(160, 5, 216));
--el-button-border-color: transparent !important;border-radius: 20px;border:none;width: 80px;">
{{ isTrackingMaxValues ? '跟踪中' : '清零' }}
</el-button> </el-button>
<el-button
type="success"
class="start-btn"
@click="saveMaxValuesToHistory"
:disabled="!isTrackingMaxValues || isRecording"
style="background-image: linear-gradient(to right, rgb(64, 158, 255), rgb(64, 158, 255));
--el-button-border-color: transparent !important;border-radius: 20px;border:none;width: 80px;">
保存
</el-button>
</div>
</div> </div>
</div> </div>
@ -95,25 +111,28 @@
<div id="headChart1" style="width: 100%;height: 140px;"></div> <div id="headChart1" style="width: 100%;height: 140px;"></div>
<!-- <img src="@/assets/test1.png" alt="" style="width: 100%;height: 140px;"> --> <!-- <img src="@/assets/test1.png" alt="" style="width: 100%;height: 140px;"> -->
<div class="gauge-group-box"> <div class="gauge-group-box">
<div class="gauge-group-box-text1"><span class="gauge-group-box-text2">-55.2°</span></div> <div class="gauge-group-box-text1">旋转</div>
<div class="gauge-group-box-text1"><span class="gauge-group-box-text2">{{ headPoseMaxValues.rotationLeftMax.toFixed(1) }}°</span></div>
<div class="gauge-group-box-text1" style="margin-left: 20px;"><span <div class="gauge-group-box-text1" style="margin-left: 20px;"><span
class="gauge-group-box-text2">-55.2°</span></div> class="gauge-group-box-text2">{{ headPoseMaxValues.rotationRightMax.toFixed(1) }}°</span></div>
</div> </div>
</div> </div>
<div style="width: 33%;"> <div style="width: 33%;">
<img src="@/assets/test1.png" alt="" style="width: 100%;height: 140px;"> <img src="@/assets/test1.png" alt="" style="width: 100%;height: 140px;">
<div class="gauge-group-box"> <div class="gauge-group-box">
<div class="gauge-group-box-text1"><span class="gauge-group-box-text2">-7.7°</span></div> <div class="gauge-group-box-text1">倾斜</div>
<div class="gauge-group-box-text1"><span class="gauge-group-box-text2">{{ headPoseMaxValues.tiltLeftMax.toFixed(1) }}°</span></div>
<div class="gauge-group-box-text1" style="margin-left: 20px;"><span <div class="gauge-group-box-text1" style="margin-left: 20px;"><span
class="gauge-group-box-text2">8.7°</span></div> class="gauge-group-box-text2">{{ headPoseMaxValues.tiltRightMax.toFixed(1) }}°</span></div>
</div> </div>
</div> </div>
<div style="width: 33%;"> <div style="width: 33%;">
<img src="@/assets/test1.png" alt="" style="width: 100%;height: 140px;"> <img src="@/assets/test1.png" alt="" style="width: 100%;height: 140px;">
<div class="gauge-group-box"> <div class="gauge-group-box">
<div class="gauge-group-box-text1"><span class="gauge-group-box-text2">-10.5°</span></div> <div class="gauge-group-box-text1">俯仰</div>
<div class="gauge-group-box-text1" style="margin-left: 20px;"><span <div class="gauge-group-box-text1"><span class="gauge-group-box-text2">{{ headPoseMaxValues.pitchDownMax.toFixed(1) }}°</span></div>
class="gauge-group-box-text2">11.5°</span></div> <div class="gauge-group-box-text1" style="margin-left: 20px;"><span
class="gauge-group-box-text2">{{ headPoseMaxValues.pitchUpMax.toFixed(1) }}°</span></div>
</div> </div>
</div> </div>
</div> </div>
@ -672,36 +691,255 @@ function displayDepthCameraFrame(base64Image) {
} }
// 姿
const headPoseMaxValues = ref({
rotationLeftMax: 0, // -
rotationRightMax: 0, // -
tiltLeftMax: 0, // -
tiltRightMax: 0, // -
pitchUpMax: 0, // -
pitchDownMax: 0 // -
})
// 姿
const headPoseHistory = ref([])
//
const isTrackingMaxValues = ref(false)
// IMU姿 // IMU姿
function handleIMUData(data) { function handleIMUData(data) {
try { try {
if (data && data.euler_angles) { if (data && data.head_pose) {
const headPose = data.head_pose
// 姿 // 姿
console.log('🎯 更新IMU头部姿态数据:', data.euler_angles) console.log('🎯 更新IMU头部姿态数据:', {
rotation: headPose.rotation, // (-), (+)
tilt: headPose.tilt, // (-), (+)
pitch: headPose.pitch // (-), (+)
})
//
console.log(`📐 头部姿态角度 - 旋转: ${headPose.rotation.toFixed(1)}°, 倾斜: ${headPose.tilt.toFixed(1)}°, 俯仰: ${headPose.pitch.toFixed(1)}°`)
//
if (isTrackingMaxValues.value) {
updateHeadPoseMaxValues(headPose)
}
// //
// //
// //
// updateHeadPoseChart(data.euler_angles) // updateHeadPoseChart({
// rotation: headPose.rotation,
// tilt: headPose.tilt,
// pitch: headPose.pitch
// })
} }
} catch (error) { } catch (error) {
console.error('❌ 处理IMU数据失败:', error) console.error('❌ 处理IMU数据失败:', error)
} }
} }
// 姿
function updateHeadPoseMaxValues(headPose) {
try {
//
if (headPose.rotation < 0) {
//
headPoseMaxValues.value.rotationLeftMax = Math.min(
headPoseMaxValues.value.rotationLeftMax,
Math.abs(headPose.rotation)
)
} else if (headPose.rotation > 0) {
//
headPoseMaxValues.value.rotationRightMax = Math.max(
headPoseMaxValues.value.rotationRightMax,
headPose.rotation
)
}
//
if (headPose.tilt < 0) {
//
headPoseMaxValues.value.tiltLeftMax = Math.min(
headPoseMaxValues.value.tiltLeftMax,
Math.abs(headPose.tilt)
)
} else if (headPose.tilt > 0) {
//
headPoseMaxValues.value.tiltRightMax = Math.max(
headPoseMaxValues.value.tiltRightMax,
headPose.tilt
)
}
//
if (headPose.pitch < 0) {
//
headPoseMaxValues.value.pitchDownMax = Math.min(
headPoseMaxValues.value.pitchDownMax,
Math.abs(headPose.pitch)
)
} else if (headPose.pitch > 0) {
//
headPoseMaxValues.value.pitchUpMax = Math.max(
headPoseMaxValues.value.pitchUpMax,
headPose.pitch
)
}
//
console.log('📊 当前头部姿态最值:', {
rotationLeft: headPoseMaxValues.value.rotationLeftMax.toFixed(1),
rotationRight: headPoseMaxValues.value.rotationRightMax.toFixed(1),
tiltLeft: headPoseMaxValues.value.tiltLeftMax.toFixed(1),
tiltRight: headPoseMaxValues.value.tiltRightMax.toFixed(1),
pitchUp: headPoseMaxValues.value.pitchUpMax.toFixed(1),
pitchDown: headPoseMaxValues.value.pitchDownMax.toFixed(1)
})
} catch (error) {
console.error('❌ 更新头部姿态最值失败:', error)
}
}
//
function clearAndStartTracking() {
try {
saveMaxValuesToHistory()
// 0
headPoseMaxValues.value = {
rotationLeftMax: 0,
rotationRightMax: 0,
tiltLeftMax: 0,
tiltRightMax: 0,
pitchUpMax: 0,
pitchDownMax: 0
}
//
isTrackingMaxValues.value = true
console.log('🔄 头部姿态最值已清零,开始跟踪')
ElMessage.success('头部姿态最值已清零,开始跟踪')
} catch (error) {
console.error('❌ 清零最值失败:', error)
ElMessage.error('清零最值失败')
}
}
//
function saveMaxValuesToHistory() {
try {
if (!isTrackingMaxValues.value) {
ElMessage.warning('请先点击清零开始跟踪')
return
}
//
const currentMaxValues = {
id: headPoseHistory.value.length + 1,
rotationLeftMax: Number(headPoseMaxValues.value.rotationLeftMax.toFixed(1)),
rotationRightMax: Number(headPoseMaxValues.value.rotationRightMax.toFixed(1)),
tiltLeftMax: Number(headPoseMaxValues.value.tiltLeftMax.toFixed(1)),
tiltRightMax: Number(headPoseMaxValues.value.tiltRightMax.toFixed(1)),
pitchUpMax: Number(headPoseMaxValues.value.pitchUpMax.toFixed(1)),
pitchDownMax: Number(headPoseMaxValues.value.pitchDownMax.toFixed(1)),
timestamp: new Date().toLocaleString()
}
//
headPoseHistory.value.push(currentMaxValues)
//
isTrackingMaxValues.value = false
console.log('💾 头部姿态最值已保存:', currentMaxValues)
ElMessage.success(`头部姿态最值已保存(第${currentMaxValues.id}组)`)
//
updateHistoryTable()
} catch (error) {
console.error('❌ 保存最值失败:', error)
ElMessage.error('保存最值失败')
}
}
//
function updateHistoryTable() {
try {
// 姿
if (headPoseHistory.value.length > 0) {
const latestData = headPoseHistory.value[headPoseHistory.value.length - 1]
// historyData姿
const newHistoryItem = {
id: latestData.id,
rotLeft: latestData.rotationLeftMax,
rotRight: latestData.rotationRightMax,
tiltLeft: latestData.tiltLeftMax,
tiltRight: latestData.tiltRightMax,
pitchDown: latestData.pitchDownMax,
pitchUp: latestData.pitchUpMax,
timestamp: latestData.timestamp
}
// historyData
if (historyData.value) {
historyData.value.push(newHistoryItem)
}
console.log('📋 历史数据表格已更新')
}
} catch (error) {
console.error('❌ 更新历史数据表格失败:', error)
}
}
// //
function handlePressureData(data) { function handlePressureData(data) {
try { try {
if (data && data.pressure_data) { if (data && data.foot_pressure) {
const pressureData = data.foot_pressure
// //
console.log('👣 更新压力传感器数据:', data.pressure_data) console.log('👣 接收到压力传感器数据:')
//
if (pressureData.pressure_zones) {
const zones = pressureData.pressure_zones
console.log(' 分区压力值:')
console.log(` 左前足: ${zones.left_front} N`)
console.log(` 左后足: ${zones.left_rear} N`)
console.log(` 右前足: ${zones.right_front} N`)
console.log(` 右后足: ${zones.right_rear} N`)
console.log(` 左足总压力: ${zones.left_total} N`)
console.log(` 右足总压力: ${zones.right_total} N`)
console.log(` 总压力: ${zones.total_pressure} N`)
}
//
if (pressureData.balance_analysis) {
const balance = pressureData.balance_analysis
console.log(' 平衡分析:')
console.log(` 平衡比例: ${(balance.balance_ratio * 100).toFixed(1)}%`)
console.log(` 压力中心偏移: ${balance.pressure_center_offset}%`)
console.log(` 平衡状态: ${balance.balance_status}`)
console.log(` 左足前后比: ${(balance.left_front_ratio * 100).toFixed(1)}%`)
console.log(` 右足前后比: ${(balance.right_front_ratio * 100).toFixed(1)}%`)
}
//
if (pressureData.pressure_image) {
console.log(' 📊 接收到压力分布图片 (base64格式)')
//
updatePressureImage(pressureData.pressure_image)
}
//
//
//
// updatePressureChart(data.pressure_data)
} }
} catch (error) { } catch (error) {
console.error('❌ 处理压力传感器数据失败:', error) console.error('❌ 处理压力传感器数据失败:', error)