BodyBalanceEvaluation/document/多厂家足底压力板适配开发方案.md
zhengsl d91af11b7d 修复录屏0字节问题,支持压力板多厂家切换
- 录屏: 高DPI缩放导致奇数尺寸触发libx264编码失败,宽高取偶; ffmpeg stderr写入日志

- 压力板: 新增 pressure_manager_small.py 原厂家小压力板,与现有大压力板并存

- 打包: 新增 build_backend.ps1,更新 requirements_build.txt / config.ini

- 前端: Electron 本地化配置
2026-08-11 18:17:13 +08:00

16 KiB
Raw Blame 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新厂家
真实设备 RealPressureDeviceSMiTSense Wrapper.dll USB 读取) SharedMemoryPressureDeviceX2 共享内存)为主,同时保留 RealPressureDevice
模拟设备 MockPressureDevice MockPressureDevice
数据源切换方式 use_mock use_mock + pressure_sourcesmitsense / 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 内容一致):

[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_sourcepressure_shared_memory_*pressure_manager.py 内部通过 get_config_value('DEVICES', ...) 直接读取。


3. 设计方案

3.1 新增配置参数:pressure_vendor

[DEVICES] 段新增参数,作为厂家选择的总开关

[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(与当前线上部署行为一致,保证向后兼容)

厂商专用参数共存策略smalllarge 各自的参数(pressure_sourcepressure_shared_memory_*pressure_dll_path 等)全部保留在 [DEVICES] 段中,互不冲突。加载哪个管理器,就由哪个管理器读取自己关心的参数,其余参数被忽略,无需拆分配置段。

3.2 新增设备工厂(适配器):pressure_device_factory.py

新建 backend/devices/pressure_device_factory.py,作为唯一的压力板管理器入口

#!/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,改为导入工厂:

    try:
        from .pressure_device_factory import create_pressure_manager
    except ImportError:
        from pressure_device_factory import create_pressure_manager
    
  • _init_pressure()L421-436改为

    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同步改为

    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 / L19PressureManager 导出改为通过工厂间接暴露,建议调整为:

    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 字段:

    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新增

    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.pypressure_manager.py,一般无需额外配置。

  • 稳妥起见,在 backend/build_app.py 生成的 app.spechiddenimports 中追加:

    hiddenimports=['devices.pressure_manager_small', 'devices.pressure_manager', 'devices.pressure_device_factory'],
    
  • 若某厂家依赖额外 DLLdll/smitsense/Wrapper.dll),需确认打包时该 DLL 已被收集(build_app.py 后处理逻辑中已包含 dll 目录拷贝,需核对 smitsense 子目录是否在内)。


4. 配置文件示例

4.1 使用新厂家(大压力板,当前部署)

[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 使用原厂家(小压力板)

[DEVICES]
pressure_enable = True
pressure_vendor = small
pressure_use_mock = False
; pressure_source / pressure_shared_memory_* 无需改动small 管理器会忽略它们

4.3 无硬件联调(模拟数据)

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=smallget_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.spechiddenimports 中显式声明两个厂家模块,否则打包后切换厂家会 ImportError。
  3. 两个模块内的 RealPressureDevice 重名smalllarge 模块内部都有同名类,工厂按模块隔离引用,不产生冲突,但禁止在外部直接 from xxx import RealPressureDevice 混用。
  4. 检测中切换厂家:检测进行中不建议切换(会导致数据源中断),热切换应在非检测时段执行;检测启动前的"配置校验告警"可辅助提醒。
  5. pressure_source 语义large 管理器内部仍以 pressure_sourcesmitsense/shared_memory)选择底层设备实现;本次方案中的 pressure_vendor 是"选哪个管理器文件"的更上层开关,两者不冲突、各司其职。