Lazy loaded image
🐍 Python教程
🧩嵌入式设备插件化架构:配置驱动、自动发现与统一管理
Words 4143Read Time≈ 11 min
2026-8-24
2026-9-2
🧩
核心结论
嵌入式设备的插件化,不是把所有代码都动态加载,而是把“设备类型、通信协议和数据处理方式”从主程序中拆出去:插件声明能力,注册表负责发现,配置决定实例,工厂负责创建,Manager 负责统一管理,安全核心负责最终约束。
本文以 ld1_rtu 中的传感器、执行器、Modbus RTU、CANopen、GPIO 和 Reactor 为背景,逐步把显式分支改造成配置驱动、插件自动发现的架构。

1. 先看问题:为什么设备一多,代码就开始失控?

在一个真实的 RTU 或机器人控制系统中,设备通常同时存在多个变化维度:
  • 设备种类不同:液位、角度、流量、水浸、推杆、比例阀、泵、报警器;
  • 通信协议不同:Modbus RTU、CANopen、GPIO、串口自定义协议;
  • 同一种设备的寄存器地址和数据格式不同;
  • 同一种传感器可能需要不同的滤波、校准和故障判定策略;
  • 设备数量和组合随项目变化。
如果把这些变化都写进主程序,代码通常会变成这样:
问题不在于 if 本身,而在于主程序开始同时承担了四种职责:
  1. 判断设备类型;
  1. 创建硬件对象;
  1. 解析通信协议;
  1. 处理业务控制逻辑。
这会导致一个后果:新增一个设备时,必须修改核心代码。核心代码越改越大,测试范围越广,硬件差异也越容易泄漏到自动控制和安全逻辑中。
正确的拆分原则是:主程序只负责“系统怎么运行”,插件负责“某一种设备怎么工作”。

2. 目标架构:从显式分支到插件系统

目标架构可以概括为:
启动过程变成:
主程序不需要知道 water_level 对应哪个 Python 类,也不需要知道寄存器地址是 4 还是 0x2004。

3. 设计模式如何分工?

模式
解决的问题
在本架构中的位置
工厂方法
根据配置创建具体对象
ConfigDrivenFactory
注册表
保存设备类型和创建函数
PluginRegistry
适配器
屏蔽 Modbus、CANopen、GPIO 差异
TransportAdapter
桥接
让设备抽象和通信实现独立变化
Sensor/Actuator + Transport
策略
替换滤波、校准、重试和健康判断算法
ProcessingStrategy
命令
统一执行、审计、排队和重试
ActuatorCommand
观察者
将状态变化通知多个消费者
SensorEvent / Reactor
中介者
避免设备之间相互直接调用
Reactor
模板方法
固化读取和诊断流程
SensorInterface
装饰器/代理
增加重试、监控和故障上报
ReliableSensor / MetricsProxy
状态模式
管理 READY、RUNNING、FAULT 等状态
DeviceState / SafetyGate
不要为了“使用设计模式”而使用设计模式。判断标准很简单:如果某个维度经常变化,就把它独立出来;如果它基本不会变化,就不要过度抽象。

4. 第一步:稳定设备接口

4.1 传感器接口

当前项目中的 SensorInterface 已经具备较好的基础。插件系统可以将接口收敛为:

4.2 执行器接口

这里有一个重要改进:命令格式应该由执行器自己声明,而不是由 ActuatorManager 根据类名维护一张大表。
不推荐:
推荐:
否则插件虽然自动发现了,Manager 仍然被具体类名绑死,所谓插件化就只完成了一半。

5. 第二步:建立插件注册表

注册表是插件系统的索引中心。它只关心三件事:
  • 插件属于哪一类;
  • 插件的类型名称是什么;
  • 如何创建它。
注册表解决的是“查找问题”,工厂解决的是“创建问题”。二者经常一起出现,但不是一回事。

6. 第三步:自动发现插件

6.1 使用 Python Entry Point

生产环境推荐通过 Python 包的 Entry Point 自动发现插件。
插件包的 pyproject.toml:
主程序只做一次发现:
插件自身负责向注册表注册:
新增设备插件时,只需要安装一个包:
主程序不需要增加新的导入语句。

6.2 嵌入式设备上的现实约束

“自动发现”不应该等于“扫描可写目录并执行所有 Python 文件”。生产环境建议:
  • 只扫描可信插件目录;
  • 校验插件 API 版本;
  • 检查插件签名或 SHA-256;
  • 禁止重复类型注册;
  • 检查插件依赖的通信总线是否存在;
  • 启动时完成设备自检;
  • 记录插件版本和硬件兼容范围。
硬件系统通常适合启动时发现、运行时稳定,不建议在电机运行过程中动态替换驱动插件。

7. 第四步:把通信协议做成适配器

传感器和执行器不应该直接依赖某一个通信类。先定义一个最小通信接口:
然后把当前项目的 Modbus RTU 包装成适配器:
这样,设备抽象和通信实现形成桥接:
如果没有这层隔离,最后会出现 ModbusWaterLevelSensor、CANopenWaterLevelSensor、MockWaterLevelSensor 等大量组合类。

8. 第五步:编写传感器插件

配置文件:
新增角度传感器时,不修改工厂,只新增:

9. 第六步:编写执行器插件

配置示例:
上层只使用语义化命令:
上层不需要知道执行器最终写入了哪个 Modbus 寄存器。

10. 第七步:配置驱动的统一装配

SensorManager 和 ActuatorManager 保留统一管理职责,工厂只负责创建对象。
启动代码变成:
这时主程序不再按设备类型写分支,只维护稳定的系统流程。

11. 第八步:用策略模式处理数据变化

设备驱动只负责“读到原始值”,单位换算、范围判断和滤波交给策略。
配置可以声明处理链:
以后把 EMA 换成中值滤波,不需要修改液位传感器驱动。

12. 第九步:用命令模式统一控制与审计

执行器命令可以统一封装来源、请求 ID、优先级和执行结果:
它可以同时服务于:
  • HMI Modbus TCP 命令;
  • MQTT 下行命令;
  • 现场 DI 点动命令;
  • 水位触发的自动命令。
这样可以统一记录:
MQTT 的 requestId 去重、ACK 重试和远程命令审计,都可以挂在命令对象或命令调度器上。

13. 第十步:观察者、中介者和安全状态

当前项目中的 FlipBoardReactor 已经体现了观察者和中介者思想:
传感器不需要直接调用 HMI、MQTT 或自动控制模块,只发出事件即可。
但安全控制不能完全交给插件。插件可以声明能力,不能绕过核心安全门:
原则:插件负责扩展设备能力,核心层负责决定设备在什么状态下可以动作。

14. 如何迁移现有 ld1_rtu?

不要一次性重写。推荐分四步迁移:

阶段一:保留现有 Manager,先统一接口

  • 保留 SensorInterface 和 Actuator;
  • 将命令校验从类名映射改为设备能力声明;
  • 给 SensorManager 的传感器集合增加名称索引;
  • 统一初始化失败和故障异常。

阶段二:把通信对象抽成 Transport

  • 包装 ModbusRTURS4854;
  • 包装 CANopen;
  • 包装 GPIO;
  • 由 TransportManager 负责串口复用、总线锁和重连。

阶段三:引入注册表和配置工厂

  • 将 _create_sensor_components() 中的设备创建逻辑迁移到插件;
  • 将 _create_actuator_components() 中的设备创建逻辑迁移到插件;
  • 主程序只保留 discover_plugins() 和 build_devices()。

阶段四:引入 Entry Point 和插件包

  • 先把内置插件做成 ld1_rtu_plugins 包;
  • 再把项目专用设备拆成独立包;
  • 最后增加版本、签名、自检和兼容性检查。
迁移完成后,新增一个设备的工作流应该是:
而不是:

15. 什么时候适合使用?

适合

  • 同一软件要适配多种硬件;
  • 设备组合由项目配置决定;
  • 需要第三方或独立团队开发设备驱动;
  • 需要真实设备、仿真设备和测试设备切换;
  • 通信协议经常变化;
  • 希望新增设备不修改核心控制逻辑。

不适合过度使用

  • 设备类型只有两三种且长期不变;
  • 代码需要严格静态审计;
  • 系统无法安装动态包;
  • 资源极端受限;
  • 插件数量很少,简单工厂已经足够。
插件化的收益来自“减少变化扩散”,不是来自代码层次越多越高级。

16. 最终架构总结

最终推荐的组合是:
  • Entry Point + 注册表:完成插件自动发现;
  • 工厂方法:根据配置创建设备;
  • 适配器 + 桥接:隔离 Modbus、CANopen、GPIO;
  • 策略模式:处理滤波、校准、重试和健康判定;
  • 命令模式:统一控制、审计、ACK 和重试;
  • 观察者 + 中介者:通过 Reactor 解耦采集、控制和上报;
  • 模板方法:复用传感器读取与诊断流程;
  • 装饰器/代理:增加可靠性和可观测性;
  • 状态模式:保证故障和停止状态下的安全边界。
一句话带走:
让插件声明“我是什么、我支持什么、我如何创建”,让配置决定“启用谁、使用什么参数”,让 Manager 负责“统一管理”,让 Reactor 负责“协作”,让安全核心负责“能不能动作”。这比把所有设备都塞进一个万能工厂,更容易扩展,也更适合嵌入式控制系统。

17. 面试表述

在这个项目中,我会把传感器、执行器和底层通信协议拆成可插拔的设备组件。设备通过统一接口提供初始化、数据读取、命令执行和状态查询能力;Modbus、CANopen 和 GPIO 通过适配器隔离。系统启动时通过 Entry Point 自动发现插件,将设备类型和创建函数注册到注册表,再根据配置文件由工厂自动实例化,最后统一交给 SensorManager 和 ActuatorManager 管理。这样新增设备只需要增加插件和配置,不需要修改核心控制流程。对于滤波、校准、重试和健康判断,我会使用策略和装饰器复用;对于控制命令,则统一封装来源、优先级、请求 ID、审计和执行结果。与此同时,设备插件不能绕过 Reactor 和安全状态机,故障、停止和运动许可仍由核心层统一控制。

参考

上一篇
Python 配置 JSON 文件最佳实践 —— 嵌入式场景的掉电安全设计
下一篇
Ardusub/ArduPilot 

Comments
Loading...