From ba8f13b3ea5043232761bd839b7df868fad66e6a Mon Sep 17 00:00:00 2001 From: root <13910913995@163.com> Date: Wed, 6 Aug 2025 14:01:04 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E5=A4=B4?= =?UTF-8?q?=E9=83=A8=E5=A7=BF=E6=80=81=E5=92=8C=E8=B6=B3=E9=83=A8=E5=8E=8B?= =?UTF-8?q?=E5=8A=9B=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app.py | 31 +++ backend/device_manager.py | 241 ++++++++++++++---- frontend/src/renderer/src/services/api.js | 5 + frontend/src/renderer/src/views/Detection.vue | 62 ++++- 4 files changed, 279 insertions(+), 60 deletions(-) diff --git a/backend/app.py b/backend/app.py index 25ee14e1..4bfd7b99 100644 --- a/backend/app.py +++ b/backend/app.py @@ -561,6 +561,37 @@ def calibrate_devices(): logger.error(f'设备校准失败: {e}') 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 ==================== @app.route('/api/streaming/start', methods=['POST']) diff --git a/backend/device_manager.py b/backend/device_manager.py index 4521aa75..bdf58e0e 100644 --- a/backend/device_manager.py +++ b/backend/device_manager.py @@ -429,13 +429,25 @@ class DeviceManager: '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 = { 'status': 'success', 'accel_offset': accel_offset, 'gyro_offset': gyro_offset, + 'head_pose_offset': head_pose_offset, # 头部姿态零点偏移 'timestamp': datetime.now().isoformat() } + # 保存校准数据到设备实例 + if hasattr(self.imu_device, 'set_calibration'): + self.imu_device.set_calibration(calibration) + return calibration except Exception as e: @@ -1123,27 +1135,16 @@ class DeviceManager: # 从IMU设备读取数据 imu_data = self.imu_device.read_data() - if imu_data: - # 计算头部姿态角度(欧拉角) - # 这里使用简化的计算方法,实际应用中可能需要更复杂的姿态解算 - accel = imu_data['accel'] - gyro = imu_data['gyro'] + if imu_data and 'head_pose' in imu_data: + # 直接使用设备提供的头部姿态数据 + head_pose = imu_data['head_pose'] - # 计算俯仰角和横滚角(基于加速度计) - 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 = { - 'roll': roll, - 'pitch': pitch, - 'yaw': yaw, - 'acceleration': accel, - 'gyroscope': gyro, + 'rotation': head_pose['rotation'], # 旋转角:左旋(-), 右旋(+) + 'tilt': head_pose['tilt'], # 倾斜角:左倾(-), 右倾(+) + 'pitch': head_pose['pitch'], # 俯仰角:俯角(-), 仰角(+) + 'temperature': imu_data.get('temperature', 25), 'timestamp': imu_data['timestamp'] } @@ -1176,32 +1177,58 @@ class DeviceManager: # 从压力传感器设备读取数据 pressure_data = self.pressure_device.read_data() - if pressure_data: - # 计算平衡相关指标 - left_pressure = pressure_data['left_foot'] - right_pressure = pressure_data['right_foot'] - total_pressure = left_pressure + right_pressure + if pressure_data and 'foot_pressure' in pressure_data: + foot_pressure = pressure_data['foot_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 # 转换为百分比 - # 构建足部压力数据 - foot_pressure_data = { - 'left_foot_pressure': left_pressure, - 'right_foot_pressure': right_pressure, - 'total_pressure': total_pressure, - 'balance_ratio': balance_ratio, - 'pressure_center_offset': pressure_center_offset, - 'balance_status': 'balanced' if abs(pressure_center_offset) < 10 else 'unbalanced', + # 计算前后足压力分布 + left_front_ratio = left_front / left_total if left_total > 0 else 0.5 + right_front_ratio = right_front / right_total if right_total > 0 else 0.5 + + # 构建完整的足部压力数据 + complete_pressure_data = { + # 分区压力值 + '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', + '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'] } # 通过WebSocket发送足部压力数据 self.socketio.emit('pressure_data', { - 'foot_pressure': foot_pressure_data, + 'foot_pressure': complete_pressure_data, 'timestamp': datetime.now().isoformat() }) @@ -1624,23 +1651,59 @@ class MockIMUDevice: def __init__(self): 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]: """读取IMU数据""" - return { - 'accel': { - 'x': np.random.normal(0, self.noise_level), - 'y': np.random.normal(0, self.noise_level), - 'z': np.random.normal(9.8, self.noise_level) # 重力加速度 + # 生成头部姿态角度数据,角度范围(-90°, +90°) + # 使用正弦波模拟自然的头部运动,添加随机噪声 + import time + current_time = time.time() + + # 旋转角(左旋为负,右旋为正) + 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() } + + # 应用校准并返回 + return self.apply_calibration(raw_data) class MockPressureDevice: @@ -1652,15 +1715,93 @@ class MockPressureDevice: def read_data(self) -> Dict[str, Any]: """读取压力数据""" - # 模拟轻微的左右脚压力差异 - left_pressure = self.base_pressure + np.random.normal(0, self.noise_level) - right_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)) + 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 { - 'left_foot': max(0, left_pressure), - 'right_foot': max(0, right_pressure), + 'foot_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() } + + 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: diff --git a/frontend/src/renderer/src/services/api.js b/frontend/src/renderer/src/services/api.js index 8d2c1896..22c23e23 100644 --- a/frontend/src/renderer/src/services/api.js +++ b/frontend/src/renderer/src/services/api.js @@ -112,6 +112,11 @@ export const deviceAPI = { return api.post('/api/devices/calibrate') }, + // 校准IMU头部姿态传感器 + calibrateIMU() { + return api.post('/api/devices/calibrate/imu') + }, + // 测试设备 testDevice() { return api.post('/api/devices/test') diff --git a/frontend/src/renderer/src/views/Detection.vue b/frontend/src/renderer/src/views/Detection.vue index d0bccf91..41f0b015 100644 --- a/frontend/src/renderer/src/views/Detection.vue +++ b/frontend/src/renderer/src/views/Detection.vue @@ -30,7 +30,7 @@ --el-button-border-color: #409EFF; --el-button-border-color: transparent "> 保存数据 - +
@@ -645,15 +645,28 @@ function displayDepthCameraFrame(base64Image) { // 处理IMU头部姿态数据 function handleIMUData(data) { 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)}°`) // 这里可以添加数据可视化逻辑 // 例如更新图表或显示数值 // 如果有图表组件,可以在这里更新数据 - // updateHeadPoseChart(data.euler_angles) + // updateHeadPoseChart({ + // rotation: headPose.rotation, + // tilt: headPose.tilt, + // pitch: headPose.pitch + // }) } } catch (error) { console.error('❌ 处理IMU数据失败:', error) @@ -663,15 +676,44 @@ function handleIMUData(data) { // 处理压力传感器足部压力数据 function handlePressureData(data) { 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`) + } - // 如果有图表组件,可以在这里更新数据 - // updatePressureChart(data.pressure_data) + // 显示平衡分析 + 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) + } + + } } catch (error) { console.error('❌ 处理压力传感器数据失败:', error) From c7f96636f35f8e5fa3c45bdae54de6a3f3317353 Mon Sep 17 00:00:00 2001 From: root <13910913995@163.com> Date: Wed, 6 Aug 2025 14:24:27 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=A4=B4?= =?UTF-8?q?=E9=83=A8=E5=A7=BF=E6=80=81=E6=95=B0=E6=8D=AE=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/renderer/src/views/Detection.vue | 218 +++++++++++++++++- 1 file changed, 207 insertions(+), 11 deletions(-) diff --git a/frontend/src/renderer/src/views/Detection.vue b/frontend/src/renderer/src/views/Detection.vue index 41f0b015..47b4a805 100644 --- a/frontend/src/renderer/src/views/Detection.vue +++ b/frontend/src/renderer/src/views/Detection.vue @@ -78,10 +78,26 @@ 头部姿态
- - 清零 - +
+ + {{ isTrackingMaxValues ? '跟踪中' : '清零' }} + + + 保存 + +
@@ -91,25 +107,28 @@
-
左:-55.2°
+
旋转
+
左:{{ headPoseMaxValues.rotationLeftMax.toFixed(1) }}°
右:-55.2°
+ class="gauge-group-box-text2">{{ headPoseMaxValues.rotationRightMax.toFixed(1) }}°
-
左:-7.7°
+
倾斜
+
左:{{ headPoseMaxValues.tiltLeftMax.toFixed(1) }}°
右:8.7°
+ class="gauge-group-box-text2">{{ headPoseMaxValues.tiltRightMax.toFixed(1) }}°
-
左:-10.5°
-
右:11.5°
+
俯仰
+
下:{{ headPoseMaxValues.pitchDownMax.toFixed(1) }}°
+
上:{{ headPoseMaxValues.pitchUpMax.toFixed(1) }}°
@@ -642,6 +661,22 @@ 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头部姿态数据 function handleIMUData(data) { try { @@ -658,6 +693,11 @@ function handleIMUData(data) { // 显示角度值(保留一位小数) console.log(`📐 头部姿态角度 - 旋转: ${headPose.rotation.toFixed(1)}°, 倾斜: ${headPose.tilt.toFixed(1)}°, 俯仰: ${headPose.pitch.toFixed(1)}°`) + // 如果正在跟踪最值,则更新最值数据 + if (isTrackingMaxValues.value) { + updateHeadPoseMaxValues(headPose) + } + // 这里可以添加数据可视化逻辑 // 例如更新图表或显示数值 @@ -673,6 +713,162 @@ function handleIMUData(data) { } } +// 更新头部姿态最值 +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) { try {