修改了界面保存功能
This commit is contained in:
parent
54e81ac0ea
commit
03c3f0a6c9
@ -641,10 +641,10 @@ class DatabaseManager:
|
||||
creator_id,
|
||||
settings.get('duration', 60),
|
||||
json.dumps(settings),
|
||||
'running',
|
||||
settings.get('diagnosis_info', ''),
|
||||
settings.get('treatment_info', ''),
|
||||
settings.get('suggestion_info', ''),
|
||||
'checking',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
china_time,
|
||||
china_time
|
||||
))
|
||||
|
@ -44,6 +44,12 @@ class BaseDevice(ABC):
|
||||
# 状态变化回调
|
||||
self._status_change_callbacks = []
|
||||
|
||||
# 设备连接监控
|
||||
self._connection_monitor_thread = None
|
||||
self._monitor_stop_event = threading.Event()
|
||||
self._connection_check_interval = config.get('connection_check_interval', 5.0) # 默认5秒检查一次
|
||||
self._connection_timeout = config.get('connection_timeout', 30.0) # 默认30秒超时
|
||||
|
||||
# 设备状态信息
|
||||
self._device_info = {
|
||||
'name': device_name,
|
||||
@ -138,6 +144,16 @@ class BaseDevice(ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def check_hardware_connection(self) -> bool:
|
||||
"""
|
||||
检查设备硬件连接状态
|
||||
|
||||
Returns:
|
||||
bool: 设备是否物理连接
|
||||
"""
|
||||
pass
|
||||
|
||||
def set_socketio(self, socketio):
|
||||
"""
|
||||
设置SocketIO实例
|
||||
@ -194,6 +210,12 @@ class BaseDevice(ABC):
|
||||
if old_status != is_connected:
|
||||
self._notify_status_change(is_connected)
|
||||
|
||||
# 启动或停止连接监控
|
||||
if is_connected and not self._connection_monitor_thread:
|
||||
self._start_connection_monitor()
|
||||
elif not is_connected and self._connection_monitor_thread:
|
||||
self._stop_connection_monitor()
|
||||
|
||||
def emit_data(self, event: str, data: Any, namespace: Optional[str] = None):
|
||||
"""
|
||||
发送数据到前端
|
||||
@ -306,6 +328,64 @@ class BaseDevice(ABC):
|
||||
with self._lock:
|
||||
self._stats['start_time'] = None
|
||||
|
||||
def _start_connection_monitor(self):
|
||||
"""
|
||||
启动连接监控线程
|
||||
"""
|
||||
if self._connection_monitor_thread and self._connection_monitor_thread.is_alive():
|
||||
return
|
||||
|
||||
self._monitor_stop_event.clear()
|
||||
self._connection_monitor_thread = threading.Thread(
|
||||
target=self._connection_monitor_worker,
|
||||
name=f"{self.device_name}_connection_monitor",
|
||||
daemon=True
|
||||
)
|
||||
self._connection_monitor_thread.start()
|
||||
self.logger.info(f"设备 {self.device_name} 连接监控线程已启动")
|
||||
|
||||
def _stop_connection_monitor(self):
|
||||
"""
|
||||
停止连接监控线程
|
||||
"""
|
||||
if self._connection_monitor_thread:
|
||||
self._monitor_stop_event.set()
|
||||
if self._connection_monitor_thread.is_alive():
|
||||
self._connection_monitor_thread.join(timeout=2.0)
|
||||
self._connection_monitor_thread = None
|
||||
self.logger.info(f"设备 {self.device_name} 连接监控线程已停止")
|
||||
|
||||
def _connection_monitor_worker(self):
|
||||
"""
|
||||
连接监控工作线程
|
||||
"""
|
||||
self.logger.info(f"设备 {self.device_name} 连接监控开始")
|
||||
|
||||
while not self._monitor_stop_event.is_set():
|
||||
try:
|
||||
# 检查硬件连接状态
|
||||
hardware_connected = self.check_hardware_connection()
|
||||
|
||||
# 如果硬件断开但软件状态仍为连接,则更新状态
|
||||
if not hardware_connected and self.is_connected:
|
||||
self.logger.warning(f"检测到设备 {self.device_name} 硬件连接断开")
|
||||
self.set_connected(False)
|
||||
break # 硬件断开后停止监控
|
||||
|
||||
# 检查心跳超时
|
||||
if self.is_connected and time.time() - self._last_heartbeat > self._connection_timeout:
|
||||
self.logger.warning(f"设备 {self.device_name} 心跳超时,判定为断开连接")
|
||||
self.set_connected(False)
|
||||
break # 超时后停止监控
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"设备 {self.device_name} 连接监控异常: {e}")
|
||||
|
||||
# 等待下次检查
|
||||
self._monitor_stop_event.wait(self._connection_check_interval)
|
||||
|
||||
self.logger.info(f"设备 {self.device_name} 连接监控结束")
|
||||
|
||||
def __enter__(self):
|
||||
"""
|
||||
上下文管理器入口
|
||||
@ -318,5 +398,11 @@ class BaseDevice(ABC):
|
||||
"""
|
||||
self.cleanup()
|
||||
|
||||
def _cleanup_monitoring(self):
|
||||
"""
|
||||
清理监控线程
|
||||
"""
|
||||
self._stop_connection_monitor()
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__}(name='{self.device_name}', connected={self.is_connected}, streaming={self.is_streaming})>"
|
@ -612,19 +612,39 @@ class CameraManager(BaseDevice):
|
||||
self.logger.error(f"重新加载相机配置失败: {e}")
|
||||
return False
|
||||
|
||||
def check_hardware_connection(self) -> bool:
|
||||
"""
|
||||
检查相机硬件连接状态
|
||||
|
||||
Returns:
|
||||
bool: 相机是否物理连接
|
||||
"""
|
||||
try:
|
||||
if self.cap and self.cap.isOpened():
|
||||
# 尝试读取一帧来验证连接
|
||||
ret, _ = self.cap.read()
|
||||
return ret
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.debug(f"检查相机硬件连接时发生异常: {e}")
|
||||
return False
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
清理资源
|
||||
"""
|
||||
try:
|
||||
self.logger.info("开始清理相机资源")
|
||||
|
||||
# 清理监控线程
|
||||
self._cleanup_monitoring()
|
||||
|
||||
# 停止流
|
||||
if self.is_streaming:
|
||||
self.stop_streaming()
|
||||
|
||||
if self.cap:
|
||||
try:
|
||||
self.cap.release()
|
||||
except Exception:
|
||||
pass
|
||||
self.cap = None
|
||||
# 断开连接
|
||||
self.disconnect()
|
||||
|
||||
# 清理帧缓存
|
||||
while not self.frame_cache.empty():
|
||||
@ -632,20 +652,21 @@ class CameraManager(BaseDevice):
|
||||
self.frame_cache.get_nowait()
|
||||
except queue.Empty:
|
||||
break
|
||||
self.last_frame = None
|
||||
|
||||
# 清理帧队列
|
||||
# 清理全局帧队列
|
||||
while not self.frame_queue.empty():
|
||||
try:
|
||||
self.frame_queue.get_nowait()
|
||||
except queue.Empty:
|
||||
break
|
||||
|
||||
self.last_frame = None
|
||||
|
||||
super().cleanup()
|
||||
self.logger.info("相机资源清理完成")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"清理相机资源失败: {e}")
|
||||
self.logger.error(f"清理相机资源时发生错误: {e}")
|
||||
|
||||
def _save_frame_to_cache(self, frame, frame_type='camera'):
|
||||
"""保存帧到全局缓存"""
|
||||
|
@ -851,11 +851,40 @@ class FemtoBoltManager(BaseDevice):
|
||||
self.logger.error(f"重新加载FemtoBolt配置失败: {e}")
|
||||
return False
|
||||
|
||||
def check_hardware_connection(self) -> bool:
|
||||
"""
|
||||
检查FemtoBolt设备硬件连接状态
|
||||
|
||||
Returns:
|
||||
bool: 设备是否物理连接
|
||||
"""
|
||||
try:
|
||||
if not self.sdk_initialized or not self.device_handle:
|
||||
return False
|
||||
|
||||
# 尝试获取设备状态来验证连接
|
||||
if hasattr(self.femtobolt, 'device_get_capture'):
|
||||
try:
|
||||
# 尝试获取一帧数据来验证设备连接
|
||||
capture = self.femtobolt.device_get_capture(self.device_handle, timeout_in_ms=1000)
|
||||
if capture:
|
||||
self.femtobolt.capture_release(capture)
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.debug(f"检查FemtoBolt硬件连接时发生异常: {e}")
|
||||
return False
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
清理资源
|
||||
"""
|
||||
try:
|
||||
# 清理监控线程
|
||||
self._cleanup_monitoring()
|
||||
|
||||
self.stop_streaming()
|
||||
self._cleanup_device()
|
||||
|
||||
|
@ -628,11 +628,51 @@ class IMUManager(BaseDevice):
|
||||
self.logger.error(f"重新加载IMU配置失败: {e}")
|
||||
return False
|
||||
|
||||
def check_hardware_connection(self) -> bool:
|
||||
"""
|
||||
检查IMU硬件连接状态
|
||||
"""
|
||||
try:
|
||||
if not self.imu_device:
|
||||
return False
|
||||
|
||||
# 对于真实设备,检查串口连接状态
|
||||
if hasattr(self.imu_device, 'ser') and self.imu_device.ser:
|
||||
# 检查串口是否仍然打开
|
||||
if not self.imu_device.ser.is_open:
|
||||
return False
|
||||
|
||||
# 尝试读取数据来验证连接
|
||||
try:
|
||||
# 保存当前超时设置
|
||||
original_timeout = self.imu_device.ser.timeout
|
||||
self.imu_device.ser.timeout = 0.1 # 设置短超时
|
||||
|
||||
# 尝试读取少量数据
|
||||
test_data = self.imu_device.ser.read(1)
|
||||
|
||||
# 恢复原始超时设置
|
||||
self.imu_device.ser.timeout = original_timeout
|
||||
|
||||
return True # 如果没有异常,认为连接正常
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# 对于模拟设备,总是返回True
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.debug(f"检查IMU硬件连接时出错: {e}")
|
||||
return False
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
清理资源
|
||||
"""
|
||||
try:
|
||||
# 停止连接监控
|
||||
self._cleanup_monitoring()
|
||||
|
||||
self.disconnect()
|
||||
|
||||
# 清理缓冲区
|
||||
|
@ -998,9 +998,46 @@ class PressureManager(BaseDevice):
|
||||
self.logger.error(f"重新加载压力板配置失败: {e}")
|
||||
return False
|
||||
|
||||
def check_hardware_connection(self) -> bool:
|
||||
"""
|
||||
检查压力板硬件连接状态
|
||||
|
||||
Returns:
|
||||
bool: 硬件连接是否正常
|
||||
"""
|
||||
try:
|
||||
if not self.device:
|
||||
return False
|
||||
|
||||
# 对于真实设备,检查DLL和设备句柄状态
|
||||
if hasattr(self.device, 'dll') and hasattr(self.device, 'device_handle'):
|
||||
if not self.device.dll or not self.device.device_handle:
|
||||
return False
|
||||
|
||||
# 检查设备连接状态
|
||||
if not self.device.is_connected:
|
||||
return False
|
||||
|
||||
# 尝试读取一次数据来验证连接
|
||||
try:
|
||||
test_data = self.device.read_data()
|
||||
return test_data is not None and 'foot_pressure' in test_data
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# 对于模拟设备,总是返回True
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.debug(f"检查压力板硬件连接时出错: {e}")
|
||||
return False
|
||||
|
||||
def cleanup(self) -> None:
|
||||
"""清理资源"""
|
||||
try:
|
||||
# 停止连接监控
|
||||
self._cleanup_monitoring()
|
||||
|
||||
self.stop_streaming()
|
||||
self.disconnect()
|
||||
self.logger.info("压力板设备资源清理完成")
|
||||
|
@ -547,7 +547,7 @@ class RecordingManager:
|
||||
if self.current_session_id:
|
||||
result['database_updates'] = {
|
||||
'session_id': self.current_session_id,
|
||||
'status': 'checked'
|
||||
'status': 'recorded'
|
||||
}
|
||||
self.logger.info(f'数据库更新信息已准备 - 会话ID: {self.current_session_id}')
|
||||
|
||||
@ -648,11 +648,6 @@ class RecordingManager:
|
||||
if frame is not None:
|
||||
video_writer.write(frame)
|
||||
frame_count += 1
|
||||
|
||||
# # 每100帧记录一次进度
|
||||
# if frame_count % 100 == 0:
|
||||
# elapsed_recording_time = current_time - recording_start_time
|
||||
# self.logger.debug(f'{recording_type}录制进度: {frame_count}帧, 已录制{elapsed_recording_time:.1f}秒, 目标帧率{target_fps}fps')
|
||||
else:
|
||||
self.logger.warning(f'{recording_type}获取帧失败,跳过此帧')
|
||||
|
||||
@ -665,16 +660,7 @@ class RecordingManager:
|
||||
# 计算录制统计信息
|
||||
if self.global_recording_start_time:
|
||||
total_recording_time = time.time() - self.global_recording_start_time
|
||||
actual_fps = frame_count / total_recording_time if total_recording_time > 0 else 0
|
||||
expected_frames = int(total_recording_time * target_fps)
|
||||
|
||||
self.logger.info(f'{recording_type}录制线程结束统计:')
|
||||
self.logger.info(f' 实际录制帧数: {frame_count}帧')
|
||||
self.logger.info(f' 预期录制帧数: {expected_frames}帧')
|
||||
self.logger.info(f' 目标帧率: {target_fps}fps')
|
||||
self.logger.info(f' 实际平均帧率: {actual_fps:.2f}fps')
|
||||
self.logger.info(f' 录制时长: {total_recording_time:.3f}秒')
|
||||
|
||||
if abs(frame_count - expected_frames) > target_fps * 0.1: # 如果帧数差异超过0.1秒的帧数
|
||||
self.logger.warning(f'{recording_type}帧数异常: 实际{frame_count}帧 vs 预期{expected_frames}帧,差异{frame_count - expected_frames}帧')
|
||||
else:
|
||||
|
121
backend/main.py
121
backend/main.py
@ -978,9 +978,6 @@ class AppServer:
|
||||
data = flask_request.get_json()
|
||||
patient_id = data.get('patient_id')
|
||||
creator_id = data.get('creator_id')
|
||||
screen_location = data.get('screen_location') # [0,0,1920,1080]
|
||||
camera_location = data.get('camera_location') # [0,0,640,480]
|
||||
femtobolt_location = data.get('femtobolt_location') # [0,0,640,480]
|
||||
|
||||
|
||||
if not patient_id or not creator_id:
|
||||
@ -988,36 +985,7 @@ class AppServer:
|
||||
|
||||
# 调用create_detection_session方法,settings传空字典
|
||||
session_id = self.db_manager.create_detection_session(patient_id, settings={}, creator_id=creator_id)
|
||||
|
||||
# 开始同步录制
|
||||
recording_response = None
|
||||
try:
|
||||
recording_response = self.recording_manager.start_recording(session_id, patient_id,screen_location,camera_location,femtobolt_location)
|
||||
|
||||
# 处理录制管理器返回的数据库更新信息
|
||||
if recording_response and recording_response.get('success') and 'database_updates' in recording_response:
|
||||
db_updates = recording_response['database_updates']
|
||||
try:
|
||||
# 更新会话状态
|
||||
if not self.db_manager.update_session_status(db_updates['session_id'], db_updates['status']):
|
||||
self.logger.error(f'更新会话状态失败 - 会话ID: {db_updates["session_id"]}, 状态: {db_updates["status"]}')
|
||||
|
||||
# 更新视频文件路径
|
||||
video_paths = db_updates['video_paths']
|
||||
self.db_manager.update_session_normal_video_path(db_updates['session_id'], video_paths['normal_video_path'])
|
||||
self.db_manager.update_session_screen_video_path(db_updates['session_id'], video_paths['screen_video_path'])
|
||||
self.db_manager.update_session_femtobolt_video_path(db_updates['session_id'], video_paths['femtobolt_video_path'])
|
||||
|
||||
self.logger.info(f'数据库更新成功 - 会话ID: {db_updates["session_id"]}')
|
||||
except Exception as db_error:
|
||||
self.logger.error(f'处理数据库更新失败: {db_error}')
|
||||
|
||||
except Exception as rec_e:
|
||||
self.logger.error(f'开始同步录制失败: {rec_e}')
|
||||
|
||||
start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
return jsonify({'success': True, 'session_id': session_id, 'detectionStartTime': start_time, 'recording': recording_response})
|
||||
return jsonify({'success': True, 'session_id': session_id, 'detectionStartTime': start_time})
|
||||
except Exception as e:
|
||||
self.logger.error(f'开始检测失败: {e}')
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
@ -1049,6 +1017,89 @@ class AppServer:
|
||||
except Exception as duration_error:
|
||||
self.logger.error(f'更新会话持续时间失败: {duration_error}')
|
||||
|
||||
success = self.db_manager.update_session_status(session_id, 'completed')
|
||||
if success:
|
||||
self.logger.info(f'检测会话已停止 - 会话ID: {session_id}')
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '检测已停止'
|
||||
})
|
||||
else:
|
||||
self.logger.error('停止检测失败,更新会话状态失败')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': '停止检测失败'
|
||||
}), 500
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f'停止检测失败: {e}', exc_info=True)
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@self.app.route('/api/detection/<session_id>/start_record', methods=['POST'])
|
||||
def start_record(session_id):
|
||||
"""开始视频录制"""
|
||||
try:
|
||||
if not self.db_manager or not self.device_coordinator:
|
||||
return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500
|
||||
|
||||
data = flask_request.get_json()
|
||||
patient_id = data.get('patient_id')
|
||||
screen_location = data.get('screen_location') # [0,0,1920,1080]
|
||||
camera_location = data.get('camera_location') # [0,0,640,480]
|
||||
femtobolt_location = data.get('femtobolt_location') # [0,0,640,480]
|
||||
|
||||
|
||||
if not patient_id:
|
||||
return jsonify({'success': False, 'error': '缺少患者ID'}), 400
|
||||
|
||||
# 开始视频录制
|
||||
recording_response = None
|
||||
try:
|
||||
recording_response = self.recording_manager.start_recording(session_id, patient_id,screen_location,camera_location,femtobolt_location)
|
||||
|
||||
# 处理录制管理器返回的数据库更新信息
|
||||
if recording_response and recording_response.get('success') and 'database_updates' in recording_response:
|
||||
db_updates = recording_response['database_updates']
|
||||
try:
|
||||
# 更新会话状态
|
||||
if not self.db_manager.update_session_status(db_updates['session_id'], db_updates['status']):
|
||||
self.logger.error(f'更新会话状态失败 - 会话ID: {db_updates["session_id"]}, 状态: {db_updates["status"]}')
|
||||
|
||||
# 更新视频文件路径
|
||||
video_paths = db_updates['video_paths']
|
||||
self.db_manager.update_session_normal_video_path(db_updates['session_id'], video_paths['normal_video_path'])
|
||||
self.db_manager.update_session_screen_video_path(db_updates['session_id'], video_paths['screen_video_path'])
|
||||
self.db_manager.update_session_femtobolt_video_path(db_updates['session_id'], video_paths['femtobolt_video_path'])
|
||||
|
||||
self.logger.info(f'数据库更新成功 - 会话ID: {db_updates["session_id"]}')
|
||||
except Exception as db_error:
|
||||
self.logger.error(f'处理数据库更新失败: {db_error}')
|
||||
|
||||
except Exception as rec_e:
|
||||
self.logger.error(f'开始同步录制失败: {rec_e}')
|
||||
|
||||
start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
return jsonify({'success': True, 'session_id': session_id, 'detectionStartTime': start_time, 'recording': recording_response})
|
||||
except Exception as e:
|
||||
self.logger.error(f'开始录制失败: {e}')
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@self.app.route('/api/detection/<session_id>/stop_record', methods=['POST'])
|
||||
def stop_record(session_id):
|
||||
"""停止视频录制"""
|
||||
try:
|
||||
if not self.db_manager or not self.device_coordinator:
|
||||
self.logger.error('数据库管理器或设备管理器未初始化')
|
||||
return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500
|
||||
|
||||
if not session_id:
|
||||
self.logger.error('缺少会话ID')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': '缺少会话ID'
|
||||
}), 400
|
||||
|
||||
# 停止同步录制,传递视频数据
|
||||
try:
|
||||
restrt = self.recording_manager.stop_recording(session_id)
|
||||
@ -1066,12 +1117,12 @@ class AppServer:
|
||||
success = False
|
||||
else:
|
||||
# 如果录制管理器没有返回数据库更新信息,则手动更新
|
||||
success = self.db_manager.update_session_status(session_id, 'completed')
|
||||
success = self.db_manager.update_session_status(session_id, 'recorded')
|
||||
|
||||
except Exception as rec_e:
|
||||
self.logger.error(f'停止同步录制失败: {rec_e}', exc_info=True)
|
||||
# 即使录制停止失败,也尝试更新数据库状态
|
||||
success = self.db_manager.update_session_status(session_id, 'completed')
|
||||
success = self.db_manager.update_session_status(session_id, 'recorded')
|
||||
raise
|
||||
|
||||
if success:
|
||||
|
Loading…
Reference in New Issue
Block a user