提交修正方法
This commit is contained in:
parent
38fc3abcf2
commit
8b3c1b9ec5
59
.vscode/launch.json
vendored
59
.vscode/launch.json
vendored
@ -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
|
||||
}
|
||||
|
||||
]
|
||||
}
|
@ -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
|
||||
|
@ -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}")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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()
|
||||
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()
|
@ -16,7 +16,7 @@ max_backups = 7
|
||||
|
||||
[CAMERA]
|
||||
enabled = True
|
||||
device_index = 3
|
||||
device_index = 0
|
||||
width = 1280
|
||||
height = 720
|
||||
fps = 30
|
||||
|
112
backend/main.py
112
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:
|
||||
|
@ -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)],
|
||||
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user