BodyBalanceEvaluation/backend/devices/pressure_manager.py

902 lines
36 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
压力板管理器
负责压力传感器的连接、校准和足部压力数据采集
"""
import os
import ctypes
import threading
import time
import json
import numpy as np
from typing import Optional, Dict, Any, List, Tuple
import logging
from collections import deque
import cv2
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from io import BytesIO
import base64
from datetime import datetime
try:
from .base_device import BaseDevice
from .utils.socket_manager import SocketManager
from .utils.config_manager import ConfigManager
except ImportError:
from base_device import BaseDevice
from utils.socket_manager import SocketManager
from utils.config_manager import ConfigManager
# 设置日志
logger = logging.getLogger(__name__)
# 检查matplotlib可用性
try:
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.patches as patches
MATPLOTLIB_AVAILABLE = True
except ImportError:
MATPLOTLIB_AVAILABLE = False
logger.warning("matplotlib不可用将使用简化的压力图像生成")
# 定义 C 结构体
class FPMS_DEVICE_INFO(ctypes.Structure):
_fields_ = [
("mn", ctypes.c_uint16),
("sn", ctypes.c_char * 64),
("fwVersion", ctypes.c_uint16),
("protoVer", ctypes.c_uint8),
("pid", ctypes.c_uint16),
("vid", ctypes.c_uint16),
("rows", ctypes.c_uint16),
("cols", ctypes.c_uint16),
]
class RealPressureDevice:
"""真实SMiTSense压力传感器设备"""
# 类级别的USB初始化状态跟踪
_usb_initialized = False
_usb_init_lock = threading.Lock()
def __init__(self, dll_path=None):
"""
初始化SMiTSense压力传感器
Args:
dll_path: DLL文件路径如果为None则使用默认路径
"""
self.dll = None
self.device_handle = None
self.is_connected = False
self.rows = 0
self.cols = 0
self.frame_size = 0
self.buf = None
# 设置DLL路径 - 使用Wrapper.dll
if dll_path is None:
# 尝试多个可能的DLL文件名
dll_candidates = [
os.path.join(os.path.dirname(__file__), '..', 'dll', 'smitsense', 'Wrapper.dll'),
os.path.join(os.path.dirname(__file__), '..', 'dll', 'smitsense', 'SMiTSenseUsb-F3.0.dll')
]
dll_path = None
for candidate in dll_candidates:
if os.path.exists(candidate):
dll_path = candidate
break
if dll_path is None:
raise FileNotFoundError(f"未找到SMiTSense DLL文件检查路径: {dll_candidates}")
self.dll_path = dll_path
logger.info(f'初始化真实压力传感器设备DLL路径: {dll_path}')
try:
self._load_dll()
self._initialize_device()
except Exception as e:
logger.error(f'压力传感器初始化失败: {e}')
# 如果真实设备初始化失败,可以选择降级为模拟设备
raise
def _load_dll(self):
"""加载SMiTSense DLL并设置函数签名"""
try:
if not os.path.exists(self.dll_path):
raise FileNotFoundError(f"DLL文件未找到: {self.dll_path}")
# 加载DLL
self.dll = ctypes.CDLL(self.dll_path)
logger.info(f"成功加载DLL: {self.dll_path}")
# 设置函数签名基于test22new.py的工作代码
self.dll.fpms_usb_init_wrap.argtypes = [ctypes.c_int]
self.dll.fpms_usb_init_wrap.restype = ctypes.c_int
self.dll.fpms_usb_get_device_list_wrap.argtypes = [ctypes.POINTER(FPMS_DEVICE_INFO), ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
self.dll.fpms_usb_get_device_list_wrap.restype = ctypes.c_int
self.dll.fpms_usb_open_wrap.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_uint64)]
self.dll.fpms_usb_open_wrap.restype = ctypes.c_int
self.dll.fpms_usb_read_frame_wrap.argtypes = [ctypes.c_uint64, ctypes.POINTER(ctypes.c_uint16), ctypes.c_size_t]
self.dll.fpms_usb_read_frame_wrap.restype = ctypes.c_int
self.dll.fpms_usb_close_wrap.argtypes = [ctypes.c_uint64]
self.dll.fpms_usb_close_wrap.restype = ctypes.c_int
logger.info("DLL函数签名设置完成")
except Exception as e:
logger.error(f"加载DLL失败: {e}")
raise
def _initialize_device(self):
"""初始化设备连接"""
try:
# 使用类级别锁确保USB子系统只初始化一次
with RealPressureDevice._usb_init_lock:
if not RealPressureDevice._usb_initialized:
# 初始化USB连接
if self.dll.fpms_usb_init_wrap(0) != 0:
raise RuntimeError("USB子系统初始化失败")
RealPressureDevice._usb_initialized = True
logger.info("USB子系统初始化成功")
else:
logger.info("USB子系统已初始化跳过重复初始化")
# 获取设备列表
count = ctypes.c_int()
devs = (FPMS_DEVICE_INFO * 10)()
r = self.dll.fpms_usb_get_device_list_wrap(devs, 10, ctypes.byref(count))
if r != 0 or count.value == 0:
raise RuntimeError(f"未检测到设备: {r}, count: {count.value}")
logger.info(f"检测到设备数量: {count.value}")
dev = devs[0]
self.rows, self.cols = dev.rows, dev.cols
logger.info(f"使用设备 SN={dev.sn.decode(errors='ignore')} {self.rows}x{self.cols}")
# 打开设备
self.device_handle = ctypes.c_uint64()
r = self.dll.fpms_usb_open_wrap(0, ctypes.byref(self.device_handle))
if r != 0:
raise RuntimeError("设备打开失败")
logger.info(f"设备已打开, 句柄 = {self.device_handle.value}")
# 准备数据缓冲区
self.frame_size = self.rows * self.cols
self.buf_type = ctypes.c_uint16 * self.frame_size
self.buf = self.buf_type()
# 设置连接状态
self.is_connected = True
logger.info(f"SMiTSense压力传感器初始化成功: {self.rows}行 x {self.cols}")
except Exception as e:
logger.error(f"设备初始化失败: {e}")
raise
def read_data(self) -> Dict[str, Any]:
"""读取压力数据并转换为与MockPressureDevice兼容的格式"""
try:
if not self.is_connected or not self.dll:
return self._get_empty_data()
# 检查device_handle是否有效
if not self.device_handle:
return self._get_empty_data()
# 读取原始压力数据
r = self.dll.fpms_usb_read_frame_wrap(self.device_handle.value, self.buf, self.frame_size)
if r != 0:
logger.warning(f"读取帧失败, code= {r}")
# 如果返回负数,多半表示物理断开或严重错误,标记断连并关闭句柄,触发上层重连
if r < 0:
try:
if self.device_handle:
try:
self.dll.fpms_usb_close_wrap(self.device_handle.value)
except Exception:
pass
finally:
self.device_handle = None
except Exception:
pass
return self._get_empty_data()
# 转换为numpy数组
raw_data = np.frombuffer(self.buf, dtype=np.uint16).reshape((self.rows, self.cols))
# 计算足部区域压力 (基于传感器的实际布局)
foot_zones = self._calculate_foot_pressure_zones(raw_data)
# 生成压力图像
pressure_image_base64 = self._generate_pressure_image(
foot_zones['left_front'],
foot_zones['left_rear'],
foot_zones['right_front'],
foot_zones['right_rear'],
raw_data
)
return {
'foot_pressure': {
'left_front': round(foot_zones['left_front'], 2),
'left_rear': round(foot_zones['left_rear'], 2),
'right_front': round(foot_zones['right_front'], 2),
'right_rear': round(foot_zones['right_rear'], 2),
'left_total': round(foot_zones['left_total'], 2),
'right_total': round(foot_zones['right_total'], 2)
},
'pressure_image': pressure_image_base64,
'timestamp': datetime.now().isoformat()
}
except Exception as e:
logger.error(f"读取压力数据异常: {e}")
return self._get_empty_data()
def _calculate_foot_pressure_zones(self, raw_data):
"""计算足部区域压力,返回百分比:
- 左足、右足:相对于双足总压的百分比
- 左前、左后:相对于左足总压的百分比
- 右前、右后:相对于右足总压的百分比
基于原始矩阵按行列各等分为四象限(上半部为前、下半部为后,左半部为左、右半部为右)。
"""
try:
# 防护:空数据
if raw_data is None:
raise ValueError("raw_data is None")
# 转为浮点以避免 uint16 溢出
rd = np.asarray(raw_data, dtype=np.float64)
rows, cols = rd.shape if rd.ndim == 2 else (0, 0)
if rows == 0 or cols == 0:
raise ValueError("raw_data has invalid shape")
# 行列对半分(上=前,下=后;左=左,右=右)
mid_r = rows // 2
mid_c = cols // 2
# 四象限求和
left_front = float(np.sum(rd[:mid_r, :mid_c], dtype=np.float64))
left_rear = float(np.sum(rd[mid_r:, :mid_c], dtype=np.float64))
right_front = float(np.sum(rd[:mid_r, mid_c:], dtype=np.float64))
right_rear = float(np.sum(rd[mid_r:, mid_c:], dtype=np.float64))
# 绝对总压
left_total_abs = left_front + left_rear
right_total_abs = right_front + right_rear
total_abs = left_total_abs + right_total_abs
# 左右足占比(相对于双足总压)
left_total_pct = float((left_total_abs / total_abs * 100) if total_abs > 0 else 0)
right_total_pct = float((right_total_abs / total_abs * 100) if total_abs > 0 else 0)
# 前后占比(相对于各自单足总压)
left_front_pct = float((left_front / total_abs * 100) if total_abs > 0 else 0)
left_rear_pct = float((left_rear / total_abs * 100) if total_abs > 0 else 0)
right_front_pct = float((right_front / total_abs * 100) if total_abs > 0 else 0)
right_rear_pct = float((right_rear / total_abs * 100) if total_abs > 0 else 0)
return {
'left_front': round(left_front_pct),
'left_rear': round(left_rear_pct),
'right_front': round(right_front_pct),
'right_rear': round(right_rear_pct),
'left_total': round(left_total_pct),
'right_total': round(right_total_pct),
'total_pressure': round(total_abs)
}
except Exception as e:
logger.error(f"计算足部区域压力异常: {e}")
return {
'left_front': 0, 'left_rear': 0, 'right_front': 0, 'right_rear': 0,
'left_total': 0, 'right_total': 0, 'total_pressure': 0
}
def _generate_pressure_image(self, left_front, left_rear, right_front, right_rear, raw_data=None) -> str:
"""生成足部压力图片的base64数据"""
try:
if MATPLOTLIB_AVAILABLE and raw_data is not None:
# 使用原始数据生成更详细的热力图
return self._generate_heatmap_image(raw_data)
else:
# 降级到简单的区域显示图
return self._generate_simple_pressure_image(left_front, left_rear, right_front, right_rear)
except Exception as e:
logger.warning(f"生成压力图片失败: {e}")
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
def _generate_heatmap_image(self, raw_data) -> str:
"""生成基于原始数据的热力图OpenCV实现自适应归一化黑色背景"""
try:
import cv2
import numpy as np
import base64
from io import BytesIO
from PIL import Image
# 自适应归一化基于test22new.py的方法2
vmin = 10 # 最小阈值,低于此值显示为黑色
dmin, dmax = np.min(raw_data), np.max(raw_data)
norm_data = np.clip((raw_data - dmin) / max(dmax - dmin, 1) * 255, 0, 255).astype(np.uint8)
# 应用 jet 颜色映射
heatmap = cv2.applyColorMap(norm_data, cv2.COLORMAP_JET)
# 将低于阈值的区域设置为黑色
heatmap[raw_data <= vmin] = (0, 0, 0)
# 放大图像以便更好地显示细节
rows, cols = raw_data.shape
heatmap = cv2.resize(heatmap, (cols*4, rows*4), interpolation=cv2.INTER_NEAREST)
# OpenCV 生成的是 BGR转成 RGB
heatmap_rgb = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB)
# 转成 Pillow Image
img = Image.fromarray(heatmap_rgb)
# 输出为 Base64 PNG
buffer = BytesIO()
img.save(buffer, format="PNG")
buffer.seek(0)
image_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
return f"data:image/png;base64,{image_base64}"
except Exception as e:
logger.warning(f"生成热力图失败: {e}")
return self._generate_simple_pressure_image(0, 0, 0, 0)
def _generate_simple_pressure_image(self, left_front, left_rear, right_front, right_rear) -> str:
"""生成简单的足部压力区域图"""
try:
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from io import BytesIO
# 创建图形
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')
# 设置图形背景为黑色
fig.patch.set_facecolor('black')
ax.set_facecolor('black')
# 保存为base64
buffer = BytesIO()
plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100, facecolor='black')
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}")
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
def _get_empty_data(self):
"""返回空的压力数据"""
return {
'foot_pressure': {
'left_front': 0.0,
'left_rear': 0.0,
'right_front': 0.0,
'right_rear': 0.0,
'left_total': 0.0,
'right_total': 0.0
},
'pressure_image': "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==",
'timestamp': datetime.now().isoformat()
}
def close(self):
"""显式关闭压力传感器连接"""
try:
if self.is_connected and self.dll and self.device_handle:
self.dll.fpms_usb_close_wrap(self.device_handle.value)
# 重置设备句柄
self.device_handle = None
# 设置连接状态为断开
self.is_connected = False
logger.info('SMiTSense压力传感器连接已关闭')
except Exception as e:
logger.error(f'关闭压力传感器连接异常: {e}')
@classmethod
def reset_usb_state(cls):
"""重置USB初始化状态用于设备完全断开后的重新初始化"""
with cls._usb_init_lock:
cls._usb_initialized = False
logger.info("USB子系统状态已重置")
def __del__(self):
"""析构函数,确保资源清理"""
self.close()
class PressureManager(BaseDevice):
"""压力板管理器"""
def __init__(self, socketio, config_manager: Optional[ConfigManager] = None):
"""
初始化压力板管理器
Args:
socketio: SocketIO实例
config_manager: 配置管理器实例
"""
# 配置管理
self.config_manager = config_manager or ConfigManager()
self.config = self.config_manager.get_device_config('pressure')
super().__init__("pressure", self.config)
# 保存socketio实例
self._socketio = socketio
# 设备实例
self.device = None
self.device_type = self.config.get('device_type', 'mock') # 'real' 或 'mock'
# 数据流相关
self.streaming_thread = None
self.is_streaming = False
self.stream_interval = self.config.get('stream_interval', 0.1) # 100ms间隔
# 校准相关
self.is_calibrated = False
self.calibration_data = None
# 性能统计
self.packet_count = 0
self.error_count = 0
self.last_data_time = None
# 重连相关配置与camera_manager保持一致的键名和默认值
self.max_reconnect_attempts = int(self.config.get('max_reconnect_attempts', -1)) # -1 表示无限重连
self.reconnect_delay = float(self.config.get('reconnect_delay', 2.0))
self.read_fail_threshold = int(self.config.get('read_fail_threshold', 30))
self._last_connected_state = None # 去抖动状态广播
self.logger.info(f"压力板管理器初始化完成 - 设备类型: {self.device_type}")
def initialize(self) -> bool:
"""
初始化压力板设备
Returns:
bool: 初始化是否成功
"""
try:
self.logger.info(f"正在初始化压力板设备...")
# 使用构造函数中已加载的配置,避免并发读取配置文件
self.logger.info(f"使用已加载配置: device_type={self.device_type}, stream_interval={self.stream_interval}")
# 根据设备类型创建设备实例
if self.device_type == 'real':
self.device = RealPressureDevice()
else:
self.device = MockPressureDevice()
# 使用set_connected方法启动连接监控线程
self.set_connected(True)
self._device_info.update({
'device_type': self.device_type,
'matrix_size': '4x4' if hasattr(self.device, 'rows') else 'unknown'
})
self.logger.info(f"压力板初始化成功 - 设备类型: {self.device_type}")
return True
except Exception as e:
self.logger.error(f"压力板初始化失败: {e}")
# 使用set_connected方法停止连接监控线程
self.set_connected(False)
self.device = None
return False
def start_streaming(self) -> bool:
"""
开始压力数据流
Args:
socketio: SocketIO实例
Returns:
bool: 启动是否成功
"""
try:
if not self.is_connected or not self.device:
self.logger.error("设备未连接,无法启动数据流")
return False
if self.is_streaming:
self.logger.warning("压力数据流已在运行")
return True
self.is_streaming = True
self.streaming_thread = threading.Thread(target=self._pressure_streaming_thread, daemon=True)
self.streaming_thread.start()
self.logger.info("压力数据流启动成功")
return True
except Exception as e:
self.logger.error(f"启动压力数据流失败: {e}")
self.is_streaming = False
return False
def stop_streaming(self) -> bool:
"""
停止压力数据流
Returns:
bool: 停止是否成功
"""
try:
if not self.is_streaming:
return True
self.is_streaming = False
if self.streaming_thread and self.streaming_thread.is_alive():
self.streaming_thread.join(timeout=2.0)
self.logger.info("压力数据流已停止")
return True
except Exception as e:
self.logger.error(f"停止压力数据流失败: {e}")
return False
def _pressure_streaming_thread(self):
"""
压力数据流处理线程
"""
self.logger.info("压力数据流线程启动")
try:
while self.is_streaming:
try:
# 从设备读取数据
pressure_data = None
if self.device:
pressure_data = self.device.read_data()
# 如果底层设备在读取时标记了断开,则在此处进入下一轮以触发重连
if hasattr(self.device, 'is_connected') and not self.device.is_connected:
self.is_connected = False
time.sleep(self.reconnect_delay)
continue
# 读数成功,立即更新心跳和连接状态
self.is_connected = True
self.update_heartbeat()
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_total / total_pressure if total_pressure > 0 else 0.5
# 计算压力中心偏移
pressure_center_offset = (balance_ratio - 0.5) * 100 # 转换为百分比
# 计算前后足压力分布
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']
}
# 更新统计信息
self.packet_count += 1
self.last_data_time = time.time()
# 发送数据到前端
if self._socketio:
self._socketio.emit('pressure_data', {
'foot_pressure': complete_pressure_data,
'timestamp': datetime.now().isoformat()
}, namespace='/devices')
else:
self.logger.warning("SocketIO实例为空无法发送压力数据")
time.sleep(self.stream_interval)
except Exception as e:
self.error_count += 1
# self.logger.error(f"压力数据流处理异常: {e}")
time.sleep(0.1)
except Exception as e:
self.logger.error(f"压力数据流线程异常: {e}")
finally:
self.logger.info("压力数据流线程结束")
def get_status(self) -> Dict[str, Any]:
"""
获取设备状态
Returns:
Dict[str, Any]: 设备状态信息
"""
return {
'device_type': self.device_type,
'is_connected': self.is_connected,
'is_streaming': self.is_streaming,
'is_calibrated': self.is_calibrated,
'packet_count': self.packet_count,
'error_count': self.error_count,
'last_data_time': self.last_data_time,
'device_info': self.get_device_info()
}
def calibrate(self) -> bool:
"""
校准压力传感器
Returns:
bool: 校准是否成功
"""
try:
self.logger.info("开始压力传感器校准...")
# 这里可以添加具体的校准逻辑
# 目前简单设置为已校准状态
self.is_calibrated = True
self.calibration_data = {
'timestamp': datetime.now().isoformat(),
'baseline': 'calibrated'
}
self.logger.info("压力传感器校准完成")
return True
except Exception as e:
self.logger.error(f"压力传感器校准失败: {e}")
return False
def disconnect(self) -> bool:
"""
断开设备连接
Returns:
bool: 断开是否成功
"""
try:
# 停止数据流
self.stop_streaming()
# 关闭设备连接
if self.device and hasattr(self.device, 'close'):
self.device.close()
self.device = None
# 使用set_connected方法停止连接监控线程
self.set_connected(False)
self.logger.info("压力板设备连接已断开")
return True
except Exception as e:
self.logger.error(f"断开压力板设备连接失败: {e}")
return False
def reload_config(self) -> bool:
"""
重新加载压力板配置
Returns:
bool: 配置重新加载是否成功
"""
try:
self.logger.info("正在重新加载压力板配置...")
# 重新获取配置
new_config = self.config_manager.get_device_config('pressure')
# 更新配置属性
self.config = new_config
self.device_type = new_config.get('device_type', 'mock')
self.stream_interval = new_config.get('stream_interval', 0.1)
# 动态更新重连参数
self.max_reconnect_attempts = int(new_config.get('max_reconnect_attempts', self.max_reconnect_attempts))
self.reconnect_delay = float(new_config.get('reconnect_delay', self.reconnect_delay))
self.read_fail_threshold = int(new_config.get('read_fail_threshold', self.read_fail_threshold))
self.logger.info(f"压力板配置重新加载成功 - 设备类型: {self.device_type}, 流间隔: {self.stream_interval}")
return True
except Exception as e:
self.logger.error(f"重新加载压力板配置失败: {e}")
return False
def check_hardware_connection(self) -> bool:
"""
检查压力板硬件连接状态
Returns:
bool: 硬件连接是否正常
"""
try:
if not self.device:
# 如果设备实例不存在返回False表示硬件未连接
return self._attempt_device_reconnection()
# 对于真实设备检查DLL和设备句柄状态
if hasattr(self.device, 'dll') and hasattr(self.device, 'device_handle'):
if not self.device.dll or not self.device.device_handle:
# DLL或句柄无效返回False表示硬件未连接
return self._attempt_device_reconnection()
# 直接检查设备句柄的有效性
try:
# 检查设备句柄是否有效
if not self.device.device_handle or not hasattr(self.device.device_handle, 'value'):
return self._attempt_device_reconnection()
# 检查句柄值是否为0无效句柄
if self.device.device_handle.value == 0:
return self._attempt_device_reconnection()
# 尝试实际的设备通信来验证硬件连接
# 使用DLL函数检查设备列表验证设备是否真实存在
count = ctypes.c_int()
devs = (FPMS_DEVICE_INFO * 10)()
r = self.device.dll.fpms_usb_get_device_list_wrap(devs, 10, ctypes.byref(count))
# 如果获取设备列表失败或设备数量为0说明硬件已断开
if r != 0 or count.value == 0:
self.logger.debug(f"设备列表检查失败: r={r}, count={count.value}")
return self._attempt_device_reconnection()
# 设备列表正常,硬件连接正常
return True
except Exception as e:
self.logger.debug(f"硬件连接检查异常: {e}")
return self._attempt_device_reconnection()
# 对于Mock设备直接返回True
return True
except Exception as e:
self.logger.debug(f"检查压力板硬件连接时出错: {e}")
return False
def _attempt_device_reconnection(self) -> bool:
"""
尝试重新连接压力板设备
Returns:
bool: 重连是否成功
"""
try:
self.logger.info("检测到压力板设备断开,尝试重新连接...")
# 清理旧的设备实例
if self.device and hasattr(self.device, 'close'):
try:
self.device.close()
except Exception as e:
self.logger.debug(f"清理旧设备实例时出错: {e}")
self.device = None
# 重置USB状态为重新插入的设备做准备
RealPressureDevice.reset_usb_state()
# 根据设备类型重新创建设备实例
self.device = RealPressureDevice()
# 检查新设备是否连接成功
if hasattr(self.device, 'is_connected') and self.device.is_connected:
self._notify_status_change(True)
# 重连成功后,确保数据流正在运行
self.logger.info("重连成功,启动压力数据流")
self.start_streaming()
return True
else:
self.logger.warning("压力板设备重连失败")
return False
except Exception as e:
self.logger.error(f"压力板设备重连过程中出错: {e}")
self.device = None
return False
def cleanup(self) -> None:
"""清理资源"""
try:
# 停止连接监控
self._cleanup_monitoring()
self.stop_streaming()
self.disconnect()
self.logger.info("压力板设备资源清理完成")
except Exception as e:
self.logger.error(f"压力板设备资源清理失败: {e}")