diff --git a/.vscode/launch.json b/.vscode/launch.json index f389751e..fccb5d4d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,42 +1,6 @@ { "version": "0.2.0", - "configurations": [ - { - "name": "Debug Backend Server", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/debug_server.py", - "console": "integratedTerminal", - "cwd": "${workspaceFolder}", - "env": { - "FLASK_ENV": "development", - "FLASK_DEBUG": "1", - "PYTHONPATH": "${workspaceFolder}" - }, - "args": [], - "justMyCode": false, - "stopOnEntry": false, - "showReturnValue": true, - "redirectOutput": true - }, - { - "name": "Debug Backend App.py", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/backend/app.py", - "console": "integratedTerminal", - "cwd": "${workspaceFolder}", - "env": { - "FLASK_ENV": "development", - "FLASK_DEBUG": "1", - "PYTHONPATH": "${workspaceFolder}" - }, - "args": [], - "justMyCode": false, - "stopOnEntry": false, - "showReturnValue": true, - "redirectOutput": true - }, + "configurations": [ { "name": "Debug Main.py", "type": "python", @@ -48,29 +12,14 @@ "PYTHONPATH": "${workspaceFolder}" }, "args": [ - "--mode", "development", - "--log-level", "DEBUG" + "--host", "0.0.0.0", + "--port", "5000" ], "justMyCode": false, "stopOnEntry": false, "showReturnValue": true, "redirectOutput": true }, - { - "name": "Debug WebSocket Test", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/test_websocket.py", - "console": "integratedTerminal", - "cwd": "${workspaceFolder}", - "env": { - "PYTHONPATH": "${workspaceFolder}" - }, - "args": [], - "justMyCode": false, - "stopOnEntry": false, - "showReturnValue": true, - "redirectOutput": true - } + ] } \ No newline at end of file diff --git a/backend/config.ini b/backend/config.ini index e7de00df..7545327a 100644 --- a/backend/config.ini +++ b/backend/config.ini @@ -15,24 +15,33 @@ backup_interval = 24 max_backups = 7 [CAMERA] -device_index = 1 +enabled = True +device_index = 3 width = 1280 height = 720 fps = 30 +buffer_size = 1 +fourcc = MJPG +backend = directshow [FEMTOBOLT] -algorithm_type = plt +enabled = True +algorithm_type = opencv color_resolution = 1080P depth_mode = NFOV_2X2BINNED -camera_fps = 15 -depth_range_min = 1200 -depth_range_max = 1500 +camera_fps = 20 +depth_range_min = 1000 +depth_range_max = 1400 +fps = 15 +synchronized_images_only = False [DEVICES] -imu_device_type = real -imu_port = COM14 +imu_enabled = True +imu_device_type = ble +imu_port = COM9 imu_mac_address = ef:3c:1a:0a:fe:02 imu_baudrate = 9600 +pressure_enabled = True pressure_device_type = real pressure_use_mock = False pressure_port = COM5 diff --git a/backend/devices/camera_manager.py b/backend/devices/camera_manager.py index 0ca6dcfb..4b15e365 100644 --- a/backend/devices/camera_manager.py +++ b/backend/devices/camera_manager.py @@ -898,167 +898,23 @@ class CameraManager(BaseDevice): bool: 相机是否物理连接 """ try: - if not self.cap: - # 如果相机实例不存在,尝试重新创建 - self.logger.info("相机实例不存在,尝试重新创建-----------------") - return self._attempt_device_reconnection() + if not self.is_connected: + self.logger.info("相机未连接,检查连接状态") + return False - if not self.cap.isOpened(): - # 相机未打开,尝试重连 - self.logger.info("相机未打开,尝试重新连接-----------------") - return self._attempt_device_reconnection() + # 尝试读取一帧 + ret, _ = self.cap.read() + if not ret: + self.logger.error("相机连接已断开,读取失败") + return False - # 多层次验证相机连接状态 - try: - # 第一步:使用grab()方法快速清除所有缓存帧 - self.logger.debug("快速清除相机缓存帧...") - try: - # grab()方法只获取帧但不解码,速度更快 - # 连续grab多次以清空内部缓冲区 - for _ in range(15): # 增加清除次数,确保缓存完全清空 - if not self.cap.grab(): - break # 如果grab失败,说明没有更多缓存帧 - except Exception as e: - self.logger.debug(f"清除缓存帧时出现异常: {e}") - - # 第二步:严格的连续帧检测 - failed_count = 0 - total_frames = 15 # 增加检测帧数 - consecutive_failures = 0 # 连续失败计数 - - for i in range(total_frames): - try: - ret, frame = self.cap.read() - if ret and frame is not None: - # 验证帧数据的有效性 - if self._validate_frame_data(frame): - del frame - consecutive_failures = 0 # 重置连续失败计数 - else: - failed_count += 1 - consecutive_failures += 1 - del frame - else: - failed_count += 1 - consecutive_failures += 1 - - # 如果连续3帧失败,立即判定为断开 - if consecutive_failures >= 3: - self.logger.warning(f"相机连接检测失败:连续{consecutive_failures}帧失败") - return False - - # 如果总失败帧数超过30%,判定为断开 - if failed_count > total_frames * 0.3: - self.logger.warning(f"相机连接检测失败:{failed_count}/{i+1}帧读取失败,超过30%阈值") - return False - - except Exception as e: - failed_count += 1 - consecutive_failures += 1 - self.logger.debug(f"读取第{i+1}帧时异常: {e}") - - # 连续异常也判定为断开 - if consecutive_failures >= 3: - self.logger.warning(f"相机连接检测异常:连续{consecutive_failures}帧异常") - return False - - # 短暂延时,避免过快读取 - time.sleep(0.005) # 减少延时提高检测速度 - - # 第三步:最终判断 - success_rate = (total_frames - failed_count) / total_frames - if success_rate >= 0.7: # 成功率需要达到70% - self.logger.info(f"相机连接检测成功:{total_frames-failed_count}/{total_frames}帧读取成功,成功率{success_rate:.1%}") - return True - else: - self.logger.warning(f"相机连接检测失败:成功率{success_rate:.1%}低于70%阈值") - return False - - except Exception as e: - self.logger.warning(f"相机连接检测过程中发生异常: {e}") - return False - except Exception as e: - self.logger.debug(f"检查相机硬件连接时发生异常: {e}") - return False - - def _validate_frame_data(self, frame) -> bool: - """ - 验证帧数据的有效性 - - Args: - frame: 要验证的帧数据 - - Returns: - bool: 帧数据是否有效 - """ - try: - if frame is None: - return False - - # 检查帧尺寸 - if frame.shape[0] < 10 or frame.shape[1] < 10: - return False - - # 检查帧数据是否全为零(可能是无效帧) - if np.all(frame == 0): - return False - - # 检查帧数据的方差(全黑或全白帧可能是无效的) - if np.var(frame) < 1.0: - return False - + self.logger.info("相机硬件连接正常") return True - except Exception: - return False - - def _attempt_device_reconnection(self) -> bool: - """ - 尝试重新连接相机设备 - - Returns: - bool: 重连是否成功 - """ - try: - self.logger.info("检测到相机设备断开,尝试重新连接...") - - # 清理旧的相机实例 - if self.cap: - try: - self.cap.release() - except Exception as e: - self.logger.debug(f"清理旧相机实例时出错: {e}") - - self.cap = None - - # 等待设备释放 - time.sleep(0.5) - - # 重新初始化相机 - if self.initialize(): - self._notify_status_change(True) - # 重连成功后,确保数据流正在运行 - if not self.is_streaming: - self.logger.info("重连成功,启动相机数据流") - self.start_streaming() - - # 更新设备信息 - self._device_info.update({ - 'device_index': self.device_index, - 'resolution': f"{self.width}x{self.height}", - 'fps': self.fps, - 'backend': self.cap.getBackendName() if hasattr(self.cap, 'getBackendName') else 'Unknown' - }) - - return True - else: - self.logger.warning("相机设备重连失败") - return False - except Exception as e: - self.logger.error(f"相机设备重连过程中出错: {e}") - self.cap = None + self.logger.error(f"检查相机硬件连接时发生错误: {e}") return False + def cleanup(self): """ @@ -1099,6 +955,6 @@ class CameraManager(BaseDevice): except Exception as e: self.logger.error(f"清理相机资源时发生错误: {e}") + - - + \ No newline at end of file diff --git a/backend/devices/device_coordinator.py b/backend/devices/device_coordinator.py index a40b232f..6c9a2a02 100644 --- a/backend/devices/device_coordinator.py +++ b/backend/devices/device_coordinator.py @@ -491,8 +491,7 @@ class DeviceCoordinator: def restart_device(self, device_name: str) -> bool: """ - 重启指定设备 - + 彻底重启指定设备:停止推流,断开连接,销毁设备实例,重新创建实例,初始化,恢复推流 Args: device_name: 设备名称 @@ -503,30 +502,155 @@ class DeviceCoordinator: self.logger.error(f"设备 {device_name} 不存在") return False + restart_start = time.time() + device = self.devices[device_name] + was_streaming = False + try: - self.logger.info(f"重启设备: {device_name}") + self.logger.info(f"开始彻底重启设备: {device_name}") - device = self.devices[device_name] - - # 停止数据流 - if hasattr(device, 'stop_streaming'): - device.stop_streaming() + # 第一步:检查并停止数据流 + stop_start = time.time() + if hasattr(device, 'is_streaming'): + was_streaming = device.is_streaming - # 清理资源 + if hasattr(device, 'stop_streaming') and was_streaming: + self.logger.info(f"正在停止 {device_name} 设备推流...") + try: + if not device.stop_streaming(): + self.logger.warning(f"停止 {device_name} 推流失败,继续重启流程") + else: + self.logger.info(f"{device_name} 设备推流已停止") + except Exception as e: + self.logger.warning(f"停止 {device_name} 推流异常: {e},继续重启流程") + + stop_time = (time.time() - stop_start) * 1000 + + # 第二步:断开连接并彻底清理资源 + cleanup_start = time.time() + self.logger.info(f"正在彻底清理 {device_name} 设备...") + + # 断开连接 + if hasattr(device, 'disconnect'): + try: + device.disconnect() + self.logger.info(f"{device_name} 设备连接已断开") + except Exception as e: + self.logger.warning(f"断开 {device_name} 连接异常: {e}") + + # 彻底清理资源 if hasattr(device, 'cleanup'): - device.cleanup() + try: + device.cleanup() + self.logger.info(f"{device_name} 设备资源已彻底清理") + except Exception as e: + self.logger.warning(f"清理 {device_name} 资源异常: {e}") + + cleanup_time = (time.time() - cleanup_start) * 1000 + + # 第三步:彻底销毁设备实例 + destroy_start = time.time() + self.logger.info(f"正在销毁 {device_name} 设备实例...") + + # 从设备字典中移除 + old_device = self.devices.pop(device_name, None) + if old_device: + # 强制删除引用,帮助垃圾回收 + del old_device + self.logger.info(f"{device_name} 设备实例已销毁") + + # 短暂等待,确保资源完全释放 + time.sleep(0.2) + destroy_time = (time.time() - destroy_start) * 1000 + + # 第四步:重新创建设备实例 + create_start = time.time() + self.logger.info(f"正在重新创建 {device_name} 设备实例...") + + new_device = None + try: + # 根据设备类型重新创建实例 + if device_name == 'camera': + try: + from .camera_manager import CameraManager + except ImportError: + from camera_manager import CameraManager + new_device = CameraManager(self.socketio, self.config_manager) + elif device_name == 'imu': + try: + from .imu_manager import IMUManager + except ImportError: + from imu_manager import IMUManager + new_device = IMUManager(self.socketio, self.config_manager) + elif device_name == 'pressure': + try: + from .pressure_manager import PressureManager + except ImportError: + from pressure_manager import PressureManager + new_device = PressureManager(self.socketio, self.config_manager) + elif device_name == 'femtobolt': + try: + from .femtobolt_manager import FemtoBoltManager + except ImportError: + from femtobolt_manager import FemtoBoltManager + new_device = FemtoBoltManager(self.socketio, self.config_manager) + else: + raise ValueError(f"未知的设备类型: {device_name}") - # 重新初始化 - if device.initialize(): - self.logger.info(f"设备 {device_name} 重启成功") - self._emit_event('device_restarted', {'device': device_name}) - return True - else: - self.logger.error(f"设备 {device_name} 重启失败") + if new_device is None: + raise Exception("设备实例创建失败") + + # 将新设备实例添加到设备字典 + self.devices[device_name] = new_device + create_time = (time.time() - create_start) * 1000 + self.logger.info(f"{device_name} 设备实例重新创建成功 (耗时: {create_time:.1f}ms)") + + except Exception as e: + create_time = (time.time() - create_start) * 1000 + self.logger.error(f"重新创建 {device_name} 设备实例失败: {e} (耗时: {create_time:.1f}ms)") return False + + # 第五步:初始化新设备实例 + init_start = time.time() + self.logger.info(f"正在初始化新的 {device_name} 设备实例...") + + if not new_device.initialize(): + init_time = (time.time() - init_start) * 1000 + self.logger.error(f"{device_name} 设备初始化失败 (耗时: {init_time:.1f}ms)") + # 初始化失败,从设备字典中移除 + self.devices.pop(device_name, None) + return False + + init_time = (time.time() - init_start) * 1000 + self.logger.info(f"{device_name} 设备初始化成功 (耗时: {init_time:.1f}ms)") + + # 第六步:如果之前在推流,则启动推流 + stream_time = 0 + if was_streaming and hasattr(new_device, 'start_streaming'): + stream_start = time.time() + self.logger.info(f"正在启动 {device_name} 设备推流...") + try: + if not new_device.start_streaming(): + stream_time = (time.time() - stream_start) * 1000 + self.logger.error(f"启动 {device_name} 设备推流失败 (耗时: {stream_time:.1f}ms)") + return False + + stream_time = (time.time() - stream_start) * 1000 + self.logger.info(f"{device_name} 设备推流已启动 (耗时: {stream_time:.1f}ms)") + except Exception as e: + stream_time = (time.time() - stream_start) * 1000 + self.logger.error(f"启动 {device_name} 推流异常: {e} (耗时: {stream_time:.1f}ms)") + return False + + # 计算总耗时并记录 + total_time = (time.time() - restart_start) * 1000 + self.logger.info(f"{device_name} 设备彻底重启完成 - 停止推流: {stop_time:.1f}ms, 清理资源: {cleanup_time:.1f}ms, 销毁实例: {destroy_time:.1f}ms, 创建实例: {create_time:.1f}ms, 初始化: {init_time:.1f}ms, 启动推流: {stream_time:.1f}ms, 总耗时: {total_time:.1f}ms") + return True except Exception as e: - self.logger.error(f"重启设备 {device_name} 异常: {e}") + total_time = (time.time() - restart_start) * 1000 + error_msg = f"彻底重启设备 {device_name} 异常: {e} (耗时: {total_time:.1f}ms)" + self.logger.error(error_msg) return False def _start_monitor(self): @@ -664,4 +788,184 @@ class DeviceCoordinator: def __exit__(self, exc_type, exc_val, exc_tb): """上下文管理器出口""" - self.shutdown() \ No newline at end of file + self.shutdown() + + +def test_restart_device(device_name=None): + """ + 测试设备重启功能 + + 该测试方法演示如何使用restart_device方法进行设备的彻底重启, + 包括模拟设备初始化、推流、重启和状态验证的完整流程。 + + Args: + device_name (str, optional): 指定要测试的设备名称。如果为None,则自动选择第一个可用设备。 + 可选值: 'camera', 'imu', 'pressure', 'femtobolt' + """ + import time + import threading + from unittest.mock import Mock + + print("=" * 60) + print("设备协调器重启功能测试") + print("=" * 60) + + # 创建模拟的SocketIO和配置管理器 + mock_socketio = Mock() + mock_config_manager = Mock() + + # 模拟配置数据 + mock_config_manager.get_device_config.return_value = { + 'camera': {'enabled': True, 'device_id': 0, 'fps': 30}, + 'imu': {'enabled': True, 'device_type': 'mock'}, + 'pressure': {'enabled': True, 'device_type': 'mock'}, + 'femtobolt': {'enabled': False} + } + + try: + # 创建设备协调器实例 + print("1. 创建设备协调器...") + coordinator = DeviceCoordinator(mock_socketio, mock_config_manager) + + # 初始化设备协调器 + print("2. 初始化设备协调器...") + if not coordinator.initialize(): + print("❌ 设备协调器初始化失败") + return False + + print("✅ 设备协调器初始化成功") + print(f" 已初始化设备: {list(coordinator.devices.keys())}") + + # 等待设备稳定 + time.sleep(1) + + # 选择要测试的设备 + available_devices = list(coordinator.devices.keys()) + if not available_devices: + print("❌ 没有可用的设备进行测试") + return False + + # 根据参数选择测试设备 + if device_name: + if device_name in available_devices: + test_device = device_name + print(f"3. 使用指定的测试设备: {test_device}") + else: + print(f"❌ 指定的设备 '{device_name}' 不存在") + print(f" 可用设备: {available_devices}") + return False + else: + test_device = available_devices[0] # 选择第一个设备进行测试 + print(f"3. 自动选择测试设备: {test_device}") + print(f" 可用设备: {available_devices}") + + # 获取设备初始状态 + device = coordinator.devices[test_device] + initial_streaming = getattr(device, 'is_streaming', False) + initial_connected = getattr(device, 'is_connected', False) + + print(f" 设备初始状态 - 连接: {initial_connected}, 推流: {initial_streaming}") + + # 如果设备未推流,先启动推流 + if hasattr(device, 'start_streaming') and not initial_streaming: + print("4. 启动设备推流...") + if device.start_streaming(): + print("✅ 设备推流启动成功") + time.sleep(0.5) # 等待推流稳定 + else: + print("⚠️ 设备推流启动失败,继续测试") + + # 记录重启前的设备实例ID + old_device_id = id(device) + print(f" 重启前设备实例ID: {old_device_id}") + + # 执行设备重启 + print("5. 执行设备重启...") + restart_start = time.time() + + restart_success = coordinator.restart_device(test_device) + + restart_time = (time.time() - restart_start) * 1000 + + if restart_success: + print(f"✅ 设备重启成功 (总耗时: {restart_time:.1f}ms)") + + # 验证设备实例是否已更换 + new_device = coordinator.devices.get(test_device) + if new_device: + new_device_id = id(new_device) + print(f" 重启后设备实例ID: {new_device_id}") + + if new_device_id != old_device_id: + print("✅ 设备实例已成功更换") + else: + print("❌ 设备实例未更换,可能重启不彻底") + + # 检查设备状态 + new_connected = getattr(new_device, 'is_connected', False) + new_streaming = getattr(new_device, 'is_streaming', False) + + print(f" 重启后设备状态 - 连接: {new_connected}, 推流: {new_streaming}") + + # 验证推流状态恢复 + if initial_streaming and new_streaming: + print("✅ 推流状态已正确恢复") + elif not initial_streaming and not new_streaming: + print("✅ 推流状态保持一致") + else: + print("⚠️ 推流状态与预期不符") + + else: + print("❌ 重启后设备实例丢失") + + else: + print(f"❌ 设备重启失败 (耗时: {restart_time:.1f}ms)") + print("8. 清理资源...") + coordinator.shutdown() + print("✅ 资源清理完成") + + print("=" * 60) + print("测试完成") + print("=" * 60) + + return restart_success + + except Exception as e: + print(f"❌ 测试过程中发生异常: {e}") + import traceback + traceback.print_exc() + return False + + +if __name__ == "__main__": + """ + 直接运行此文件时执行设备重启测试 + """ + print("启动设备协调器重启功能测试...") + + # 检查命令行参数 + device_name = None + + try: + # 设置日志级别 + import logging + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + + # 执行测试 + # 可选值: 'camera', 'imu', 'pressure', 'femtobolt' + success = test_restart_device('pressure') + + if success: + print("\n🎉 所有测试通过!") + else: + print("\n❌ 测试失败,请检查日志") + + except KeyboardInterrupt: + print("\n⚠️ 测试被用户中断") + except Exception as e: + print(f"\n❌ 测试启动失败: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/backend/devices/utils/config.ini b/backend/devices/utils/config.ini index 7545327a..18167216 100644 --- a/backend/devices/utils/config.ini +++ b/backend/devices/utils/config.ini @@ -16,7 +16,7 @@ max_backups = 7 [CAMERA] enabled = True -device_index = 3 +device_index = 0 width = 1280 height = 720 fps = 30 diff --git a/backend/main.py b/backend/main.py index bb82bb4a..82525250 100644 --- a/backend/main.py +++ b/backend/main.py @@ -863,61 +863,8 @@ class AppServer: 'message': f'不支持的设备类型: {device_name},支持的设备类型: {", ".join(supported_devices)}' }), 400 - result = self.config_manager.set_all_device_configs(data) + result = self.config_manager.set_all_device_configs(data) - # 如果配置设置成功,异步重启设备数据推送 - # if result['success']: - # def restart_devices_async(): - # """异步重启设备数据推送""" - # try: - # self.logger.info("设备配置更新成功,异步重启设备数据推送...") - # # 先停止当前的数据推送 - # if self.is_pushing_data: - # self.stop_device_push_data() - # time.sleep(1) # 等待停止完成 - - # # 为每个设备管理器重新加载配置 - # self.logger.info("重新加载设备配置...") - # reload_results = [] - # for device_name, manager in self.device_managers.items(): - # if manager is not None and hasattr(manager, 'reload_config'): - # try: - # success = manager.reload_config() - # reload_results.append(f"{device_name}: {'成功' if success else '失败'}") - # self.logger.info(f"{device_name}设备配置重新加载{'成功' if success else '失败'}") - # except Exception as e: - # reload_results.append(f"{device_name}: 异常 - {str(e)}") - # self.logger.error(f"{device_name}设备配置重新加载异常: {e}") - # else: - # reload_results.append(f"{device_name}: 跳过(管理器未初始化或不支持reload_config)") - - # self.logger.info(f"配置重新加载结果: {'; '.join(reload_results)}") - - # # 重新启动设备数据推送 - # self.start_device_push_data() - # self.logger.info("设备配置更新并重启数据推送完成") - - # # 通过SocketIO通知前端重启完成 - # self.socketio.emit('device_restart_complete', { - # 'status': 'success', - # 'message': '设备重启完成', - # 'reload_results': reload_results - # }, namespace='/devices') - - # except Exception as restart_error: - # self.logger.error(f"重启设备数据推送失败: {restart_error}") - # # 通过SocketIO通知前端重启失败 - # self.socketio.emit('device_restart_complete', { - # 'status': 'error', - # 'message': f'设备重启失败: {str(restart_error)}' - # }, namespace='/devices') - - # # 启动异步线程执行重启操作 - # restart_thread = threading.Thread(target=restart_devices_async) - # restart_thread.daemon = True - # restart_thread.start() - - # result['message'] = result.get('message', '') + ' 设备正在后台重启中,请稍候...' status_code = 200 if result['success'] else 400 return jsonify(result), status_code @@ -1510,6 +1457,63 @@ class AppServer: except Exception as e: emit('test_status', {'status': 'error', 'message': str(e)}, namespace='/devices') + @self.socketio.on('restart_device', namespace='/devices') + def handle_restart_device(data): + """重启特定设备""" + device_type = data.get('device_type') + self.logger.info(f'客户端请求重启{device_type}设备') + self.restart_device(device_type) + + def restart_device(self, device_type: str): + """重启指定类型的设备 + + Args: + device_type (str): 设备类型 (camera, imu, pressure, femtobolt) + """ + if not self.device_coordinator: + self.logger.error('设备协调器未初始化,无法重启设备') + return False + + try: + self.logger.info(f'开始重启 {device_type} 设备...') + + # 调用设备协调器的重启方法 + success = self.device_coordinator.restart_device(device_type) + + if success: + self.logger.info(f'{device_type} 设备重启成功') + + # 发送重启成功事件到前端 + self.socketio.emit('device_restart_message', { + 'device_type': device_type, + 'message': f'{device_type} 设备重启成功!', + 'timestamp': time.time() + }) + return True + else: + self.logger.error(f'{device_type} 设备重启失败!') + # 发送重启失败事件到前端 + self.socketio.emit('device_restart_message', { + 'device_type': device_type, + 'message': f'{device_type} 设备重启失败!', + 'timestamp': time.time() + }) + return False + + except Exception as e: + self.logger.error(f'重启 {device_type} 设备时发生异常: {str(e)}') + + # 发送重启异常事件到前端 + self.socketio.emit('device_restart_error', { + 'device_type': device_type, + 'error': str(e), + 'message': f'重启 {device_type} 设备时发生异常', + 'timestamp': time.time() + }) + return False + + + def start_device_push_data(self): """开始设备数据推送""" if self.is_pushing_data: diff --git a/frontend/src/renderer/src/views/Detection.vue b/frontend/src/renderer/src/views/Detection.vue index bfaa74b5..861d23fd 100644 --- a/frontend/src/renderer/src/views/Detection.vue +++ b/frontend/src/renderer/src/views/Detection.vue @@ -1984,9 +1984,9 @@ const startRecord = async () => { // 开始录屏 patient_id: patientInfo.value.sessionId, // 可以添加其他录屏参数 creator_id: creatorId.value, - screen_location:[Math.round(screen_location.x), Math.round(screen_location.y) + titile_height, Math.round(screen_location.width), Math.round(screen_location.height-titile_height)], - camera_location:[Math.round(camera_location.x), Math.round(camera_location.y)+ titile_height, Math.round(camera_location.width), Math.round(camera_location.height-titile_height)], - femtobolt_location:[Math.round(femtobolt_location.x), Math.round(femtobolt_location.y) + titile_height, Math.round(femtobolt_location.width), Math.round(femtobolt_location.height-titile_height)], + screen_location:[Math.round(screen_location.x), Math.round(screen_location.y) + titile_height, Math.round(screen_location.width), Math.round(screen_location.height)], + camera_location:[Math.round(camera_location.x), Math.round(camera_location.y)+ titile_height, Math.round(camera_location.width), Math.round(camera_location.height)], + femtobolt_location:[Math.round(femtobolt_location.x), Math.round(femtobolt_location.y) + titile_height, Math.round(femtobolt_location.width), Math.round(femtobolt_location.height)], }) })