核心结论
嵌入式设备的插件化,不是把所有代码都动态加载,而是把“设备类型、通信协议和数据处理方式”从主程序中拆出去:插件声明能力,注册表负责发现,配置决定实例,工厂负责创建,Manager 负责统一管理,安全核心负责最终约束。
本文以
ld1_rtu 中的传感器、执行器、Modbus RTU、CANopen、GPIO 和 Reactor 为背景,逐步把显式分支改造成配置驱动、插件自动发现的架构。1. 先看问题:为什么设备一多,代码就开始失控?
在一个真实的 RTU 或机器人控制系统中,设备通常同时存在多个变化维度:
- 设备种类不同:液位、角度、流量、水浸、推杆、比例阀、泵、报警器;
- 通信协议不同:Modbus RTU、CANopen、GPIO、串口自定义协议;
- 同一种设备的寄存器地址和数据格式不同;
- 同一种传感器可能需要不同的滤波、校准和故障判定策略;
- 设备数量和组合随项目变化。
如果把这些变化都写进主程序,代码通常会变成这样:
问题不在于
if 本身,而在于主程序开始同时承担了四种职责:- 判断设备类型;
- 创建硬件对象;
- 解析通信协议;
- 处理业务控制逻辑。
这会导致一个后果:新增一个设备时,必须修改核心代码。核心代码越改越大,测试范围越广,硬件差异也越容易泄漏到自动控制和安全逻辑中。
正确的拆分原则是:主程序只负责“系统怎么运行”,插件负责“某一种设备怎么工作”。
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 和安全状态机,故障、停止和运动许可仍由核心层统一控制。
参考
- Author:felixfixit
- URL:http://www.felixmicrospace.top/article/embedded_device_plugin_architecture
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!


