BodyBalanceEvaluation/document/多厂家足底压力板适配开发方案.md

351 lines
16 KiB
Markdown
Raw Permalink Normal View History

# 多厂家足底压力板适配开发方案
## 1. 需求背景与目标
当前系统使用足底压力板采集足底压力数据。项目经历了一次设备更换:
| 设备 | 程序文件 | 状态 |
| --- | --- | --- |
| 原厂家小一号压力板SMiTSense USB | `backend/devices/pressure_manager_small.py` | 已停用 |
| 新厂家大一号压力板X2 共享内存) | `backend/devices/pressure_manager.py` | 当前使用 |
**需求目标**:程序同时支持两个厂家的设备,通过**配置文件参数**切换使用哪个厂家,无需修改代码、重新打包即可在两种设备间切换。
具体拆解为三点:
1. 新增配置文件参数,用 `small` 代表原厂家设备(对应 `pressure_manager_small.py`),用 `large` 代表新厂家设备(对应 `pressure_manager.py`
2. 修改配置与初始化程序,根据配置文件**动态初始化**对应厂家的设备管理器;
3. 检测时,根据配置文件**加载对应厂家**的设备进行数据采集。
---
## 2. 现状分析
### 2.1 两个压力板管理器的异同
两个文件均定义了 `class PressureManager(BaseDevice)`,对外接口**完全一致**
- 生命周期:`initialize()` / `start_streaming()` / `stop_streaming()` / `disconnect()` / `cleanup()` / `restart` 相关
- 配置:`reload_config()` / `get_status()` / `calibrate()`
- 数据上报:均通过 Socket.IO 以 `pressure_data` 事件推送到 `/devices` 命名空间,数据结构相同(`foot_pressure` + `pressure_image` + `timestamp`
因此**前端无需任何改动**,只需要保证后端"按配置加载正确的管理器类"即可。
底层设备实现差异:
| 项目 | pressure_manager_small.py原厂家 | pressure_manager.py新厂家 |
| --- | --- | --- |
| 真实设备 | `RealPressureDevice`SMiTSense `Wrapper.dll` USB 读取) | `SharedMemoryPressureDevice`X2 共享内存)为主,同时保留 `RealPressureDevice` |
| 模拟设备 | `MockPressureDevice` | `MockPressureDevice` |
| 数据源切换方式 | 仅 `use_mock` | `use_mock` + `pressure_source``smitsense` / `shared_memory` |
| 与硬件耦合 | DLL 文件 `dll/smitsense/Wrapper.dll` | 共享内存名 `x2_pressure` 等一批 `pressure_shared_memory_*` 参数 |
### 2.2 当前硬编码点(需要改造的位置)
现有代码在多处**直接 import 并实例化 `pressure_manager.PressureManager`**,这是需要改造的核心:
| 文件 | 行号 | 现状 |
| --- | --- | --- |
| `backend/devices/device_coordinator.py` | L19 / L26 | 模块顶部 `from .pressure_manager import PressureManager` |
| `backend/devices/device_coordinator.py` | L421-436 | `_init_pressure()` 直接 `PressureManager(self.socketio, self.config_manager)` |
| `backend/devices/device_coordinator.py` | L837-842 | `restart_device('pressure')` 分支再次直接 import + 实例化 |
| `backend/main.py` | L33 | 模块顶部 `from devices.pressure_manager import PressureManager` |
| `backend/devices/__init__.py` | L11 / L19 | `from .pressure_manager import PressureManager` 并导出 |
### 2.3 配置现状
`[DEVICES]` 段已有压力板相关参数(`backend/config.ini` 与打包产物 `backend/dist/BodyBalanceBackend/config.ini` 内容一致):
```ini
[DEVICES]
; pressure_enable = True
; pressure_use_mock = True
pressure_enable = False
pressure_use_mock = True
pressure_source = shared_memory
pressure_shared_memory_name = x2_pressure
pressure_shared_memory_header_bytes = 72
pressure_shared_memory_rows = 288
pressure_shared_memory_cols = 64
pressure_shared_memory_dtype = float32
pressure_shared_memory_crop_rows = 0
pressure_shared_memory_low_percentile = 5
pressure_shared_memory_high_percentile = 98
pressure_shared_memory_gamma = 1.0
pressure_shared_memory_ema_alpha = 0.1
```
`ConfigManager._get_pressure_config()` 目前只返回 `enable / use_mock / port / baudrate` 四个字段;`pressure_source` 及 `pressure_shared_memory_*``pressure_manager.py` 内部通过 `get_config_value('DEVICES', ...)` 直接读取。
---
## 3. 设计方案
### 3.1 新增配置参数:`pressure_vendor`
`[DEVICES]` 段新增参数,作为**厂家选择的总开关**
```ini
[DEVICES]
pressure_enable = True
pressure_vendor = large ; small = 原厂家小压力板pressure_manager_small.py
; large = 新厂家大压力板pressure_manager.py
pressure_use_mock = False
```
**取值约定**(严格按需求):
- `small` → 加载 `pressure_manager_small.py` 中的 `PressureManager`
- `large` → 加载 `pressure_manager.py` 中的 `PressureManager`
- 未配置或非法值 → **默认回退为 `large`**(与当前线上部署行为一致,保证向后兼容)
**厂商专用参数共存策略**`small` 与 `large` 各自的参数(`pressure_source`、`pressure_shared_memory_*`、`pressure_dll_path` 等)全部保留在 `[DEVICES]` 段中,互不冲突。加载哪个管理器,就由哪个管理器读取自己关心的参数,其余参数被忽略,无需拆分配置段。
### 3.2 新增设备工厂(适配器):`pressure_device_factory.py`
新建 `backend/devices/pressure_device_factory.py`,作为**唯一的压力板管理器入口**
```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""压力板设备工厂
根据配置 [DEVICES] pressure_vendor 动态选择加载对应厂家的压力板管理器。
small = 原厂家pressure_manager_small.pylarge = 新厂家pressure_manager.py
"""
import logging
logger = logging.getLogger(__name__)
# 静态导入两个厂家的管理器(静态导入可保证 PyInstaller 打包时两个模块都被收集)
try:
from .pressure_manager_small import PressureManager as SmallPressureManager
from .pressure_manager import PressureManager as LargePressureManager
except ImportError:
from pressure_manager_small import PressureManager as SmallPressureManager
from pressure_manager import PressureManager as LargePressureManager
# 厂家注册表:配置值 -> 管理器类
VENDOR_MAP = {
'small': SmallPressureManager, # 原厂家(小压力板)
'large': LargePressureManager, # 新厂家(大压力板)
}
DEFAULT_VENDOR = 'large' # 向后兼容:默认新厂家
def resolve_pressure_vendor(config_manager) -> str:
"""读取配置中的厂家值,非法值回退为默认厂家"""
try:
vendor = str(
config_manager.get_config_value('DEVICES', 'pressure_vendor', fallback=DEFAULT_VENDOR)
).strip().lower()
except Exception as e:
logger.warning(f'读取 pressure_vendor 失败,使用默认厂家 {DEFAULT_VENDOR}: {e}')
vendor = DEFAULT_VENDOR
if vendor not in VENDOR_MAP:
logger.warning(f'未知 pressure_vendor: {vendor},回退为 {DEFAULT_VENDOR}')
vendor = DEFAULT_VENDOR
return vendor
def get_pressure_manager_class(config_manager):
"""根据配置返回对应的压力板管理器类"""
vendor = resolve_pressure_vendor(config_manager)
cls = VENDOR_MAP[vendor]
logger.info(f'压力板厂家选择: {vendor} -> {cls.__module__}.{cls.__name__}')
return cls
def create_pressure_manager(socketio, config_manager):
"""创建压力板管理器实例(对外统一入口)"""
cls = get_pressure_manager_class(config_manager)
return cls(socketio, config_manager)
```
**要点**
- 采用**静态 import 两个模块**而非 `importlib` 动态导入——因为 PyInstaller 的静态分析能识别 `from X import Y`,确保打包时两个厂家模块都被打进 exe避免打包后切厂家时报"模块不存在"。
- 对外只暴露 `create_pressure_manager(socketio, config_manager)` 一个入口,调用方不关心具体厂家。
### 3.3 初始化流程改造(动态初始化)
#### (1) `backend/devices/device_coordinator.py`
- 顶部 importL19/L26不再直接导入 `PressureManager`,改为导入工厂:
```python
try:
from .pressure_device_factory import create_pressure_manager
except ImportError:
from pressure_device_factory import create_pressure_manager
```
- `_init_pressure()`L421-436改为
```python
def _init_pressure(self) -> bool:
try:
pressure = create_pressure_manager(self.socketio, self.config_manager)
self.devices['pressure'] = pressure
if pressure.initialize():
return True
return False
except Exception as e:
self.logger.error(f"初始化压力传感器失败: {e}")
return False
```
- `restart_device()``pressure` 分支L837-842同步改为
```python
elif device_name == 'pressure':
new_device = create_pressure_manager(self.socketio, self.config_manager)
```
这样**在运行中修改 `pressure_vendor` 后调用"设备重启"接口即可热切换厂家**,无需重启整个后端。
#### (2) `backend/main.py`
- L33 `from devices.pressure_manager import PressureManager`:该导入在 main.py 中并未被实际调用(压力板实例统一由协调器持有),建议**删除该行**,避免与工厂逻辑冲突;若确需引用,改为从工厂获取类。
#### (3) `backend/devices/__init__.py`
- L11 / L19`PressureManager` 导出改为通过工厂间接暴露,建议调整为:
```python
from .pressure_device_factory import create_pressure_manager, get_pressure_manager_class
```
对外不再直接导出某个厂家的 `PressureManager` 类,统一走工厂。
### 3.4 检测时加载对应设备(需求点 3
检测流程(`main.py` 的检测启动、数据采集)**均通过 `DeviceCoordinator` 获取压力板实例**`self.device_coordinator.get_device('pressure')` / `device_managers`),而协调器在初始化时已经按 `pressure_vendor` 创建了正确厂家的实例,因此检测时天然加载的就是配置指定厂家的设备,**无需改动检测主流程**。
补充两个健壮性措施(建议纳入实现):
1. **检测启动前校验**:在检测开始接口中读取 `pressure_vendor`,与当前压力板实例的 `__class__.__module__` 对比,若不一致则记录告警日志(提示"配置已切换但设备未重启"),引导用户通过设备重启接口热切换。
2. **配置变更热切换**:前端"设备设置"保存 `pressure_vendor` 后,后端提示"重启软件系统生效"(沿用现有 `set_all_device_configs` 的提示语);同时支持在设备管理页调用现有 `restart_device('pressure')` 立即切换。
### 3.5 配置管理 / API 支持
`backend/devices/utils/config_manager.py`
- `_get_pressure_config()`L243-255新增返回 `vendor` 字段:
```python
return {
'enable': self.config.getboolean('DEVICES', 'pressure_enable', fallback=False),
'use_mock': self.config.getboolean('DEVICES', 'pressure_use_mock', fallback=False),
'vendor': self.config.get('DEVICES', 'pressure_vendor', fallback='large'),
'port': self.config.get('DEVICES', 'pressure_port', fallback='COM8'),
'baudrate': self.config.getint('DEVICES', 'pressure_baudrate', fallback=115200),
}
```
- `_batch_update_device_configs()``pressure` 分支L453-475新增
```python
if 'vendor' in config_data:
self.set_config_value('DEVICES', 'pressure_vendor', str(config_data['vendor']))
```
这样前端设备设置页(如果暴露压力板配置)可读写 `pressure_vendor`,走既有的 `set_all_device_configs` 接口即可。
### 3.6 打包注意事项PyInstaller
- 工厂采用**静态导入**两个厂家模块PyInstaller 会自动收集 `pressure_manager_small.py``pressure_manager.py`,一般无需额外配置。
- 稳妥起见,在 `backend/build_app.py` 生成的 `app.spec``hiddenimports` 中追加:
```python
hiddenimports=['devices.pressure_manager_small', 'devices.pressure_manager', 'devices.pressure_device_factory'],
```
- 若某厂家依赖额外 DLL`dll/smitsense/Wrapper.dll`),需确认打包时该 DLL 已被收集(`build_app.py` 后处理逻辑中已包含 dll 目录拷贝,需核对 smitsense 子目录是否在内)。
---
## 4. 配置文件示例
### 4.1 使用新厂家(大压力板,当前部署)
```ini
[DEVICES]
pressure_enable = True
pressure_vendor = large
pressure_use_mock = False
pressure_source = shared_memory
pressure_shared_memory_name = x2_pressure
pressure_shared_memory_header_bytes = 72
pressure_shared_memory_rows = 288
pressure_shared_memory_cols = 64
pressure_shared_memory_dtype = float32
; ... 其余 pressure_shared_memory_* 参数保持不变 ...
```
### 4.2 使用原厂家(小压力板)
```ini
[DEVICES]
pressure_enable = True
pressure_vendor = small
pressure_use_mock = False
; pressure_source / pressure_shared_memory_* 无需改动small 管理器会忽略它们
```
### 4.3 无硬件联调(模拟数据)
```ini
pressure_enable = True
pressure_vendor = small ; 或 large均可
pressure_use_mock = True
```
---
## 5. 兼容性与迁移
| 场景 | 说明 |
| --- | --- |
| 旧配置无 `pressure_vendor` | 工厂回退为 `large`,与当前线上行为一致,**无感知迁移** |
| 前端无改动 | 两个管理器数据上报协议一致(`pressure_data` / `/devices`),前端不用改 |
| 运行中切换厂家 | 通过既有 `restart_device('pressure')` 接口热切换;或改配置后重启后端 |
| 模拟/真实切换 | 沿用各管理器内部 `pressure_use_mock` 逻辑,不受厂家切换影响 |
---
## 6. 测试计划
1. **工厂单元验证**venv 下直接调用):
- `pressure_vendor=small``get_pressure_manager_class()` 返回 `pressure_manager_small.PressureManager`
- `pressure_vendor=large` → 返回 `pressure_manager.PressureManager`
- `pressure_vendor=非法值/缺省` → 回退 `large`,日志有告警
2. **集成验证**`pressure_use_mock=True`,不接硬件):
- `large`:协调器初始化 → 压力板连接成功 → `start_streaming` → 收到 `pressure_data` 事件
- `small`:同上流程验证
3. **热切换验证**`set_all_device_configs` 修改 `pressure_vendor` → 调用 `restart_device('pressure')` → 日志确认切换为另一厂家、数据流恢复
4. **检测回归**:登录 → 创建检测 → 检测过程压力数据正常采集与保存
5. **打包回归**`build_backend.ps1` 打包后,分别在两种配置下运行 exe 验证初始化与数据流
---
## 7. 文件修改清单
| 文件 | 操作 | 说明 |
| --- | --- | --- |
| `backend/devices/pressure_device_factory.py` | **新增** | 厂家注册表 + `create_pressure_manager` 工厂入口 |
| `backend/devices/device_coordinator.py` | 修改 | 顶部 import、`_init_pressure()`、`restart_device()` pressure 分支改用工厂 |
| `backend/main.py` | 修改 | 移除/替换 L33 直接导入 |
| `backend/devices/__init__.py` | 修改 | 导出改为工厂接口 |
| `backend/devices/utils/config_manager.py` | 修改 | `_get_pressure_config()` 增加 `vendor`;批量更新支持 `vendor` |
| `backend/config.ini` | 修改 | `[DEVICES]` 增加 `pressure_vendor = large` |
| `backend/dist/BodyBalanceBackend/config.ini` | 修改 | 同步增加 `pressure_vendor = large` |
| `backend/build_app.py` | 修改 | `app.spec` 增加 `hiddenimports`(稳妥项) |
| `document/多厂家足底压力板适配开发方案.md` | 新增 | 本文档 |
---
## 8. 风险与注意事项
1. **DLL 与硬件耦合**`small` 厂家依赖 `dll/smitsense/Wrapper.dll`,切换回 `small` 时需确保该 DLL 存在于打包产物中;`large` 厂家依赖 X2 上位机软件创建的共享内存 `x2_pressure`,需先启动厂家采集程序。
2. **打包收集**:若日后将工厂改为 `importlib` 动态导入,必须同步在 `app.spec``hiddenimports` 中显式声明两个厂家模块,否则打包后切换厂家会 ImportError。
3. **两个模块内的 `RealPressureDevice` 重名**`small` 与 `large` 模块内部都有同名类,工厂按模块隔离引用,不产生冲突,但**禁止**在外部直接 `from xxx import RealPressureDevice` 混用。
4. **检测中切换厂家**:检测进行中不建议切换(会导致数据源中断),热切换应在非检测时段执行;检测启动前的"配置校验告警"可辅助提醒。
5. **`pressure_source` 语义**`large` 管理器内部仍以 `pressure_source``smitsense`/`shared_memory`)选择底层设备实现;本次方案中的 `pressure_vendor` 是"选哪个管理器文件"的更上层开关,两者不冲突、各司其职。