From b33e72f36e71803a81ec5cfb1674ecb218a09e8a Mon Sep 17 00:00:00 2001 From: root <13910913995@163.com> Date: Wed, 6 Aug 2025 09:52:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86imu=E7=AD=89?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=95=B0=E6=8D=AE=E6=8E=A8=E9=80=81=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app.py | 76 ++++++ backend/device_manager.py | 218 +++++++++++++++++- frontend/src/renderer/src/views/Detection.vue | 141 ++++++++++- 3 files changed, 425 insertions(+), 10 deletions(-) diff --git a/backend/app.py b/backend/app.py index 839fee93..a238bfa2 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1034,6 +1034,82 @@ def handle_stop_video(data=None): logger.error(f'停止视频流失败: {e}') emit('video_status', {'status': 'error', 'message': f'停止失败: {str(e)}'}) +@socketio.on('start_imu_streaming') +def handle_start_imu_streaming(data=None): + """启动IMU头部姿态数据推流""" + try: + if device_manager: + result = device_manager.start_imu_streaming() + if result: + emit('imu_status', {'status': 'success', 'message': 'IMU头部姿态数据推流已启动'}) + logger.info('IMU头部姿态数据推流已启动') + else: + emit('imu_status', {'status': 'error', 'message': 'IMU头部姿态数据推流启动失败'}) + logger.error('IMU头部姿态数据推流启动失败') + else: + emit('imu_status', {'status': 'error', 'message': '设备管理器未初始化'}) + logger.error('设备管理器未初始化') + except Exception as e: + logger.error(f'启动IMU数据推流失败: {e}') + emit('imu_status', {'status': 'error', 'message': f'启动失败: {str(e)}'}) + +@socketio.on('stop_imu_streaming') +def handle_stop_imu_streaming(data=None): + """停止IMU头部姿态数据推流""" + try: + if device_manager: + result = device_manager.stop_imu_streaming() + if result: + emit('imu_status', {'status': 'success', 'message': 'IMU头部姿态数据推流已停止'}) + logger.info('IMU头部姿态数据推流已停止') + else: + emit('imu_status', {'status': 'error', 'message': 'IMU头部姿态数据推流停止失败'}) + logger.error('IMU头部姿态数据推流停止失败') + else: + emit('imu_status', {'status': 'error', 'message': '设备管理器未初始化'}) + logger.error('设备管理器未初始化') + except Exception as e: + logger.error(f'停止IMU数据推流失败: {e}') + emit('imu_status', {'status': 'error', 'message': f'停止失败: {str(e)}'}) + +@socketio.on('start_pressure_streaming') +def handle_start_pressure_streaming(data=None): + """启动压力传感器足部压力数据推流""" + try: + if device_manager: + result = device_manager.start_pressure_streaming() + if result: + emit('pressure_status', {'status': 'success', 'message': '压力传感器足部压力数据推流已启动'}) + logger.info('压力传感器足部压力数据推流已启动') + else: + emit('pressure_status', {'status': 'error', 'message': '压力传感器足部压力数据推流启动失败'}) + logger.error('压力传感器足部压力数据推流启动失败') + else: + emit('pressure_status', {'status': 'error', 'message': '设备管理器未初始化'}) + logger.error('设备管理器未初始化') + except Exception as e: + logger.error(f'启动压力传感器数据推流失败: {e}') + emit('pressure_status', {'status': 'error', 'message': f'启动失败: {str(e)}'}) + +@socketio.on('stop_pressure_streaming') +def handle_stop_pressure_streaming(data=None): + """停止压力传感器足部压力数据推流""" + try: + if device_manager: + result = device_manager.stop_pressure_streaming() + if result: + emit('pressure_status', {'status': 'success', 'message': '压力传感器足部压力数据推流已停止'}) + logger.info('压力传感器足部压力数据推流已停止') + else: + emit('pressure_status', {'status': 'error', 'message': '压力传感器足部压力数据推流停止失败'}) + logger.error('压力传感器足部压力数据推流停止失败') + else: + emit('pressure_status', {'status': 'error', 'message': '设备管理器未初始化'}) + logger.error('设备管理器未初始化') + except Exception as e: + logger.error(f'停止压力传感器数据推流失败: {e}') + emit('pressure_status', {'status': 'error', 'message': f'停止失败: {str(e)}'}) + # 重复的事件处理器已删除,使用前面定义的版本 if __name__ == '__main__': diff --git a/backend/device_manager.py b/backend/device_manager.py index b26f08e6..4521aa75 100644 --- a/backend/device_manager.py +++ b/backend/device_manager.py @@ -778,22 +778,106 @@ class DeviceManager: logger.error(f'录制FemtoBolt帧失败: {e}') def set_socketio(self, socketio): - """设置WebSocket连接用于推流""" + """设置WebSocket连接""" self.socketio = socketio + + def start_imu_streaming(self): + """启动IMU头部姿态数据推流""" + try: + if self.imu_streaming: + logger.warning('IMU数据推流已在运行') + return True + + if not self.imu_device: + logger.error('IMU设备未初始化') + return False + + self.imu_streaming = True + self.imu_thread = threading.Thread(target=self._imu_streaming_thread, daemon=True) + self.imu_thread.start() + logger.info('IMU头部姿态数据推流已启动') + return True + + except Exception as e: + logger.error(f'启动IMU数据推流失败: {e}') + self.imu_streaming = False + return False + + def stop_imu_streaming(self): + """停止IMU头部姿态数据推流""" + try: + if not self.imu_streaming: + logger.warning('IMU数据推流未运行') + return True + + self.imu_streaming = False + if self.imu_thread and self.imu_thread.is_alive(): + self.imu_thread.join(timeout=2) + + logger.info('IMU头部姿态数据推流已停止') + return True + + except Exception as e: + logger.error(f'停止IMU数据推流失败: {e}') + return False + + def start_pressure_streaming(self): + """启动压力传感器足部压力数据推流""" + try: + if self.pressure_streaming: + logger.warning('压力传感器数据推流已在运行') + return True + + if not self.pressure_device: + logger.error('压力传感器设备未初始化') + return False + + self.pressure_streaming = True + self.pressure_thread = threading.Thread(target=self._pressure_streaming_thread, daemon=True) + self.pressure_thread.start() + logger.info('压力传感器足部压力数据推流已启动') + return True + + except Exception as e: + logger.error(f'启动压力传感器数据推流失败: {e}') + self.pressure_streaming = False + return False + + def stop_pressure_streaming(self): + """停止压力传感器足部压力数据推流""" + try: + if not self.pressure_streaming: + logger.warning('压力传感器数据推流未运行') + return True + + self.pressure_streaming = False + if self.pressure_thread and self.pressure_thread.is_alive(): + self.pressure_thread.join(timeout=2) + + logger.info('压力传感器足部压力数据推流已停止') + return True + + except Exception as e: + logger.error(f'停止压力传感器数据推流失败: {e}') + return False def start_streaming(self) -> Dict[str, bool]: - """启动视频推流 + """启动所有设备推流 Returns: Dict: 推流启动状态 { 'camera_streaming': bool, - 'femtobolt_streaming': bool + 'femtobolt_streaming': bool, + 'imu_streaming': bool, + 'pressure_streaming': bool } """ result = { 'camera_streaming': False, - 'femtobolt_streaming': False + 'femtobolt_streaming': False, + 'imu_streaming': False, + 'pressure_streaming': False } try: @@ -824,13 +908,23 @@ class DeviceManager: result['femtobolt_streaming'] = True logger.debug('FemtoBolt深度相机推流已启动') + # 启动IMU头部姿态数据推流 + if self.device_status['imu'] and not self.imu_streaming: + result['imu_streaming'] = self.start_imu_streaming() + logger.debug('IMU头部姿态数据推流已启动') + + # 启动压力传感器足部压力数据推流 + if self.device_status['pressure'] and not self.pressure_streaming: + result['pressure_streaming'] = self.start_pressure_streaming() + logger.debug('压力传感器足部压力数据推流已启动') + except Exception as e: logger.warning(f'启动推流失败: {e}') return result def stop_streaming(self) -> bool: - """停止所有视频推流 + """停止所有设备推流 Returns: bool: 停止操作是否成功 @@ -853,6 +947,16 @@ class DeviceManager: self.femtobolt_streaming_thread.join(timeout=2) logger.debug('FemtoBolt深度相机推流已停止') + # 停止IMU头部姿态数据推流 + if self.imu_streaming: + self.stop_imu_streaming() + logger.debug('IMU头部姿态数据推流已停止') + + # 停止压力传感器足部压力数据推流 + if self.pressure_streaming: + self.stop_pressure_streaming() + logger.debug('压力传感器足部压力数据推流已停止') + return True except Exception as e: @@ -1008,6 +1112,110 @@ class DeviceManager: logger.debug(f'FemtoBolt推流线程异常: {e}') finally: self.femtobolt_streaming = False + + def _imu_streaming_thread(self): + """IMU头部姿态数据推流线程""" + logger.info('IMU头部姿态数据推流线程已启动') + + try: + while self.imu_streaming and self.socketio: + try: + # 从IMU设备读取数据 + imu_data = self.imu_device.read_data() + + if imu_data: + # 计算头部姿态角度(欧拉角) + # 这里使用简化的计算方法,实际应用中可能需要更复杂的姿态解算 + 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 = { + 'roll': roll, + 'pitch': pitch, + 'yaw': yaw, + 'acceleration': accel, + 'gyroscope': gyro, + 'temperature': imu_data.get('temperature', 25), + 'timestamp': imu_data['timestamp'] + } + + # 通过WebSocket发送头部姿态数据 + self.socketio.emit('imu_data', { + 'head_pose': head_pose_data, + 'timestamp': datetime.now().isoformat() + }) + + # 控制数据发送频率(10Hz) + time.sleep(0.1) + + except Exception as e: + logger.error(f'IMU数据推流异常: {e}') + time.sleep(0.1) + + except Exception as e: + logger.error(f'IMU推流线程异常: {e}') + finally: + logger.info('IMU头部姿态数据推流线程已结束') + + def _pressure_streaming_thread(self): + """压力传感器足部压力数据推流线程""" + logger.info('压力传感器足部压力数据推流线程已启动') + + try: + while self.pressure_streaming and self.socketio: + try: + # 从压力传感器设备读取数据 + 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 + + # 计算平衡比例(左脚压力占总压力的比例) + balance_ratio = left_pressure / 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', + 'timestamp': pressure_data['timestamp'] + } + + # 通过WebSocket发送足部压力数据 + self.socketio.emit('pressure_data', { + 'foot_pressure': foot_pressure_data, + 'timestamp': datetime.now().isoformat() + }) + + # 控制数据发送频率(20Hz) + time.sleep(0.05) + + except Exception as e: + logger.error(f'压力传感器数据推流异常: {e}') + time.sleep(0.1) + + except Exception as e: + logger.error(f'压力传感器推流线程异常: {e}') + finally: + logger.info('压力传感器足部压力数据推流线程已结束') def start_recording(self, session_id: str, patient_id: str) -> Dict[str, Any]: """启动同步录制 diff --git a/frontend/src/renderer/src/views/Detection.vue b/frontend/src/renderer/src/views/Detection.vue index bb685e94..977e870f 100644 --- a/frontend/src/renderer/src/views/Detection.vue +++ b/frontend/src/renderer/src/views/Detection.vue @@ -542,6 +542,38 @@ function connectWebSocket() { console.error('❌ Socket错误:', error) }) + // 监听IMU状态事件 + socket.on('imu_status', (data) => { + console.log('📡 IMU状态:', data) + if (data.status === 'success') { + ElMessage.success(data.message) + } else { + ElMessage.error(data.message) + } + }) + + // 监听IMU头部姿态数据 + socket.on('imu_data', (data) => { + console.log('🎯 IMU头部姿态数据:', data) + handleIMUData(data) + }) + + // 监听压力传感器状态事件 + socket.on('pressure_status', (data) => { + console.log('📡 压力传感器状态:', data) + if (data.status === 'success') { + ElMessage.success(data.message) + } else { + ElMessage.error(data.message) + } + }) + + // 监听压力传感器足部压力数据 + socket.on('pressure_data', (data) => { + console.log('👣 压力传感器足部压力数据:', data) + handlePressureData(data) + }) + } catch (error) { console.error('💥 连接异常:', error.message) isConnected.value = false @@ -602,11 +634,6 @@ function stopRtsp() { } } - - - - - // 简单的帧显示函数 function displayFrame(base64Image) { if (base64Image && base64Image.length > 0) { @@ -631,6 +658,110 @@ function hideVideo() { depthCameraImgSrc.value = '' } +// 处理IMU头部姿态数据 +function handleIMUData(data) { + try { + if (data && data.euler_angles) { + // 更新头部姿态数据 + console.log('🎯 更新IMU头部姿态数据:', data.euler_angles) + + // 这里可以添加数据可视化逻辑 + // 例如更新图表或显示数值 + + // 如果有图表组件,可以在这里更新数据 + // updateHeadPoseChart(data.euler_angles) + } + } catch (error) { + console.error('❌ 处理IMU数据失败:', error) + } +} + +// 处理压力传感器足部压力数据 +function handlePressureData(data) { + try { + if (data && data.pressure_data) { + // 更新足部压力数据 + console.log('👣 更新压力传感器数据:', data.pressure_data) + + // 这里可以添加数据可视化逻辑 + // 例如更新图表或显示压力分布 + + // 如果有图表组件,可以在这里更新数据 + // updatePressureChart(data.pressure_data) + } + } catch (error) { + console.error('❌ 处理压力传感器数据失败:', error) + } +} + +// 启动IMU头部姿态数据推流 +function startIMUStreaming() { + if (socket && socket.connected) { + console.log('🚀 发送start_imu_streaming事件') + socket.emit('start_imu_streaming', {}, (ack) => { + if (ack) { + console.log('✅ start_imu_streaming事件已确认:', ack) + } else { + console.log('⚠️ start_imu_streaming事件无确认响应') + } + }) + } else { + console.error('❌ WebSocket未连接,无法启动IMU数据推流') + ElMessage.error('WebSocket未连接,无法启动IMU数据推流') + } +} + +// 停止IMU头部姿态数据推流 +function stopIMUStreaming() { + if (socket && socket.connected) { + console.log('🛑 发送stop_imu_streaming事件') + socket.emit('stop_imu_streaming', {}, (ack) => { + if (ack) { + console.log('✅ stop_imu_streaming事件已确认:', ack) + } else { + console.log('⚠️ stop_imu_streaming事件无确认响应') + } + }) + } else { + console.error('❌ WebSocket未连接,无法停止IMU数据推流') + ElMessage.error('WebSocket未连接,无法停止IMU数据推流') + } +} + +// 启动压力传感器足部压力数据推流 +function startPressureStreaming() { + if (socket && socket.connected) { + console.log('🚀 发送start_pressure_streaming事件') + socket.emit('start_pressure_streaming', {}, (ack) => { + if (ack) { + console.log('✅ start_pressure_streaming事件已确认:', ack) + } else { + console.log('⚠️ start_pressure_streaming事件无确认响应') + } + }) + } else { + console.error('❌ WebSocket未连接,无法启动压力传感器数据推流') + ElMessage.error('WebSocket未连接,无法启动压力传感器数据推流') + } +} + +// 停止压力传感器足部压力数据推流 +function stopPressureStreaming() { + if (socket && socket.connected) { + console.log('🛑 发送stop_pressure_streaming事件') + socket.emit('stop_pressure_streaming', {}, (ack) => { + if (ack) { + console.log('✅ stop_pressure_streaming事件已确认:', ack) + } else { + console.log('⚠️ stop_pressure_streaming事件无确认响应') + } + }) + } else { + console.error('❌ WebSocket未连接,无法停止压力传感器数据推流') + ElMessage.error('WebSocket未连接,无法停止压力传感器数据推流') + } +} + // 手动开始/停止录制功能 function handleStartStopRecording() { if (!isConnected.value) {