#!/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不可用,将使用简化的压力图像生成") class RealPressureDevice: """真实SMiTSense压力传感器设备""" 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路径 - 使用正确的DLL文件名 if dll_path is None: # 尝试多个可能的DLL文件名 dll_candidates = [ os.path.join(os.path.dirname(__file__), '..', 'dll', 'smitsense', 'SMiTSenseUsbWrapper.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.WinDLL(self.dll_path) logger.info(f"成功加载DLL: {self.dll_path}") # 设置函数签名(基于testsmit.py的工作代码) self.dll.SMiTSenseUsb_Init.argtypes = [ctypes.c_int] self.dll.SMiTSenseUsb_Init.restype = ctypes.c_int self.dll.SMiTSenseUsb_ScanDevices.argtypes = [ctypes.POINTER(ctypes.c_int)] self.dll.SMiTSenseUsb_ScanDevices.restype = ctypes.c_int self.dll.SMiTSenseUsb_OpenAndStart.argtypes = [ ctypes.c_int, ctypes.POINTER(ctypes.c_uint16), ctypes.POINTER(ctypes.c_uint16) ] self.dll.SMiTSenseUsb_OpenAndStart.restype = ctypes.c_int self.dll.SMiTSenseUsb_GetLatestFrame.argtypes = [ ctypes.POINTER(ctypes.c_uint16), ctypes.c_int ] self.dll.SMiTSenseUsb_GetLatestFrame.restype = ctypes.c_int self.dll.SMiTSenseUsb_StopAndClose.argtypes = [] self.dll.SMiTSenseUsb_StopAndClose.restype = ctypes.c_int logger.info("DLL函数签名设置完成") except Exception as e: logger.error(f"加载DLL失败: {e}") raise def _initialize_device(self): """初始化设备连接""" try: # 初始化USB连接 ret = self.dll.SMiTSenseUsb_Init(0) if ret != 0: raise RuntimeError(f"USB初始化失败: {ret}") # 扫描设备 count = ctypes.c_int() ret = self.dll.SMiTSenseUsb_ScanDevices(ctypes.byref(count)) if ret != 0 or count.value == 0: raise RuntimeError(f"设备扫描失败或未找到设备: {ret}, count: {count.value}") logger.info(f"发现 {count.value} 个SMiTSense设备") # 打开并启动第一个设备 rows = ctypes.c_uint16() cols = ctypes.c_uint16() ret = self.dll.SMiTSenseUsb_OpenAndStart(0, ctypes.byref(rows), ctypes.byref(cols)) if ret != 0: raise RuntimeError(f"设备启动失败: {ret}") self.rows = rows.value self.cols = cols.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: logger.error("设备未连接") return self._get_empty_data() # 读取原始压力数据 ret = self.dll.SMiTSenseUsb_GetLatestFrame(self.buf, self.frame_size) if ret != 0: logger.warning(f"读取数据帧失败: {ret}") 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 / left_total_abs * 100) if left_total_abs > 0 else 0) left_rear_pct = float((left_rear / left_total_abs * 100) if left_total_abs > 0 else 0) right_front_pct = float((right_front / right_total_abs * 100) if right_total_abs > 0 else 0) right_rear_pct = float((right_rear / right_total_abs * 100) if right_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实现,固定范围映射,效果与matplotlib一致)""" try: import cv2 import numpy as np import base64 from io import BytesIO from PIL import Image # 固定映射范围(与 matplotlib vmin/vmax 一致) vmin, vmax = 0, 1000 norm_data = np.clip((raw_data - vmin) / (vmax - vmin) * 255, 0, 255).astype(np.uint8) # 应用 jet 颜色映射 heatmap = cv2.applyColorMap(norm_data, cv2.COLORMAP_JET) # 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') # 保存为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: self.dll.SMiTSenseUsb_StopAndClose() self.is_connected = False logger.info('SMiTSense压力传感器连接已关闭') except Exception as e: logger.error(f'关闭压力传感器连接异常: {e}') def __del__(self): """析构函数,确保资源清理""" self.close() class MockPressureDevice: """模拟压力传感器设备,模拟真实SMiTSense设备的行为""" def __init__(self): self.base_pressure = 500 # 基础压力值 self.noise_level = 10 self.rows = 4 # 模拟传感器矩阵行数 self.cols = 4 # 模拟传感器矩阵列数 self.time_offset = np.random.random() * 10 # 随机时间偏移,让每个实例的波形不同 def read_data(self) -> Dict[str, Any]: """读取压力数据,模拟基于矩阵数据的真实设备行为""" try: # 生成模拟的传感器矩阵数据 raw_data = self._generate_simulated_matrix_data() # 使用与真实设备相同的计算逻辑 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 _generate_simulated_matrix_data(self): """生成模拟的传感器矩阵数据,模拟真实的足部压力分布""" import time current_time = time.time() + self.time_offset # 创建4x4的传感器矩阵 matrix_data = np.zeros((self.rows, self.cols)) # 模拟动态的压力分布,使用正弦波叠加噪声 for i in range(self.rows): for j in range(self.cols): # 基础压力值,根据传感器位置不同 base_value = self.base_pressure * (0.3 + 0.7 * np.random.random()) # 添加时间变化(模拟人体重心变化) time_variation = np.sin(current_time * 0.5 + i * 0.5 + j * 0.3) * 0.3 # 添加噪声 noise = np.random.normal(0, self.noise_level) # 确保压力值非负 matrix_data[i, j] = max(0, base_value * (1 + time_variation) + noise) return matrix_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 / left_total_abs * 100) if left_total_abs > 0 else 0) left_rear_pct = float((left_rear / left_total_abs * 100) if left_total_abs > 0 else 0) right_front_pct = float((right_front / right_total_abs * 100) if right_total_abs > 0 else 0) right_rear_pct = float((right_rear / right_total_abs * 100) if right_total_abs > 0 else 0) return { 'left_front': left_front_pct, 'left_rear': left_rear_pct, 'right_front': right_front_pct, 'right_rear': right_rear_pct, 'left_total': left_total_pct, 'right_total': right_total_pct, 'total_pressure': float(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: """生成基于原始数据的热力图""" try: import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt from io import BytesIO # 参考 tests/testsmit.py 的渲染方式:使用 jet 色图、nearest 插值、固定范围并关闭坐标轴 fig, ax = plt.subplots() im = ax.imshow(raw_data, cmap='jet', interpolation='nearest', vmin=0, vmax=1000) ax.axis('off') # 紧凑布局并导出为 base64 from io import BytesIO buffer = BytesIO() plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100, pad_inches=0) 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 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') # 设置非交互式后端,避免Tkinter错误 import matplotlib.pyplot as plt import matplotlib.patches as patches from io import BytesIO # 临时禁用PIL的调试日志 pil_logger = logging.getLogger('PIL') original_level = pil_logger.level pil_logger.setLevel(logging.WARNING) # 创建图形 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') # 保存为base64 buffer = BytesIO() plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100, facecolor='white') buffer.seek(0) image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') plt.close(fig) # 恢复PIL的日志级别 pil_logger.setLevel(original_level) return f"data:image/png;base64,{image_base64}" except Exception as e: # 确保在异常情况下也恢复PIL的日志级别 try: pil_logger.setLevel(original_level) except: pass logger.warning(f"生成压力图片失败: {e}") # 返回一个简单的占位符base64图片 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() } 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 self.logger.info(f"压力板管理器初始化完成 - 设备类型: {self.device_type}") def initialize(self) -> bool: """ 初始化压力板设备 Returns: bool: 初始化是否成功 """ try: self.logger.info(f"正在初始化压力板设备 - 类型: {self.device_type}") # 根据设备类型创建设备实例 if self.device_type == 'real': self.device = RealPressureDevice() else: self.device = MockPressureDevice() self.is_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}") self.is_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 and self.is_connected: try: # 从设备读取数据 if self.device: pressure_data = self.device.read_data() if pressure_data: # 更新统计信息 self.packet_count += 1 self.last_data_time = time.time() # 发送数据到前端 if self._socketio: self._socketio.emit('pressure_data', pressure_data, namespace='/pressure') 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 self.is_connected = False self.logger.info("压力板设备连接已断开") return True except Exception as e: self.logger.error(f"断开压力板设备连接失败: {e}") return False def cleanup(self) -> None: """清理资源""" try: self.stop_streaming() self.disconnect() self.logger.info("压力板设备资源清理完成") except Exception as e: self.logger.error(f"压力板设备资源清理失败: {e}")