如果以后需要动态添加协议插件,这些插件应该放在哪里?
┌─────────────────────────────────────────┐
│ 守护进程 (soft_bus_daemon) │
│ │
│ ┌────────────────────────────────────┐ │
│ │ SerialManager │ │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ SerialParserThread │ │ │
│ │ │ - parseModbusRTU() │ │ │
│ │ │ - parseModbusASCII() │ │ │
│ │ │ - parseCustomProtocol() │ │ │
│ │ │ (硬编码协议解析) │ │ │
│ │ └──────────────────────────────┘ │ │
│ └────────────────────────────────────┘ │
│ │
│ 硬件设备 → 串口 → 数据接收 → 协议解析 │
└─────────────────────────────────────────┘
问题:
SerialParserThread 中┌─────────────────────────────────────────────────────────┐
│ 守护进程 (soft_bus_daemon) │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ SerialManager │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ SerialParserThread │ │ │
│ │ │ ┌───────────────────────────────────────┐ │ │ │
│ │ │ │ ProtocolPluginManager │ │ │ │
│ │ │ │ - loadPlugin(pluginPath) │ │ │ │
│ │ │ │ - unloadPlugin(pluginId) │ │ │ │
│ │ │ │ - getPlugin(protocolName) │ │ │ │
│ │ │ └──────────────────────────────────────┘ │ │ │
│ │ │ │ │ │ │
│ │ │ ▼ │ │ │
│ │ │ ┌──────────────────────────────────────┐ │ │ │
│ │ │ │ IProtocolParser (接口) │ │ │ │
│ │ │ │ - parseFrame(data) │ │ │ │
│ │ │ │ - extractFrame(buffer) │ │ │ │
│ │ │ │ - getName() │ │ │ │
│ │ │ └──────────────────────────────────────┘ │ │ │
│ │ │ ▲ │ │ │
│ │ │ │ 实现 │ │ │
│ │ │ ┌──────┴──────┬──────────┬─────────────┐ │ │ │
│ │ │ │ ModbusRTU │ Modbus │ Custom │ │ │ │
│ │ │ │ Plugin │ ASCII │ Protocol │ │ │ │
│ │ │ │ (.so) │ Plugin │ Plugin │ │ │ │
│ │ │ │ │ (.so) │ (.so) │ │ │ │
│ │ │ └─────────────┴──────────┴─────────────┘ │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ 插件目录: /usr/lib/soft_bus/protocols/ │
│ - modbus_rtu.so │
│ - modbus_ascii.so │
│ - custom_protocol.so │
│ - ... │
└─────────────────────────────────────────────────────────┘
▲
│ D-Bus 接口
│ (加载/卸载插件)
│
┌────────┴─────────────────────────────────────────────┐
│ 主应用 (soft_bus) │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ DaemonClient │ │
│ │ - loadProtocolPlugin(pluginPath) │ │
│ │ - unloadProtocolPlugin(pluginId) │ │
│ │ - listProtocolPlugins() │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 插件管理界面: │
│ - 显示已加载的协议插件 │
│ - 安装/卸载协议插件 │
│ - 配置协议参数 │
└───────────────────────────────────────────────────────┘
答案:不是!主应用有更重要的职责。
职责:硬件资源管理和协议解析执行
✅ 硬件资源管理
- 串口设备打开/关闭
- CAN设备打开/关闭
- 硬件数据接收
✅ 协议解析执行
- 加载协议插件
- 解析协议数据
- 将解析结果写入数据库
✅ 数据采集
- 实时数据采集
- 数据存储到数据库
- 设备状态监控
❌ 不负责
- UI显示
- 用户配置(只执行配置)
- 数据可视化
职责:配置管理、数据展示、用户交互
✅ 配置管理(通过D-Bus发送给守护进程)
- 设备配置(串口参数、CAN参数)
- 协议配置(选择协议、协议参数)
- 插件管理(安装/卸载协议插件)
✅ 数据展示
- 从数据库读取历史数据
- 实时数据可视化(图表、表格)
- 设备状态显示
- 消息统计展示
✅ 用户交互
- 设备发现界面
- 设备配置界面
- 数据查询界面
- 插件管理界面
✅ 数据库查询(只读)
- 查询设备列表
- 查询历史消息
- 查询统计数据
❌ 不负责
- 直接操作硬件(通过D-Bus请求守护进程)
- 协议解析(守护进程执行)
- 实时数据采集(守护进程执行)
┌─────────────────────────────────────────────────────────┐
│ 硬件设备 │
└──────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 守护进程 (soft_bus_daemon) │
│ ┌───────────────────────────────────────────────────┐ │
│ │ SerialManager / CanManager │ │
│ │ - 打开硬件设备 │ │
│ │ - 接收原始数据 │ │
│ └──────────────────┬────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ ProtocolPluginManager │ │
│ │ - 加载协议插件 │ │
│ │ - 解析协议数据 │ │
│ └──────────────────┬────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ DeviceBusCore │ │
│ │ - 存储解析结果到数据库 │ │
│ │ - 更新设备状态 │ │
│ └──────────────────┬────────────────────────────────┘ │
└────────────────────┼─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 数据库 (soft_bus_db) │
│ - 设备信息 │
│ - 原始数据 │
│ - 解析后的数据 │
│ - 设备状态 │
└──────────────────┬──────────────────────────────────────┘
│
│ 读取
▼
┌─────────────────────────────────────────────────────────┐
│ 主应用 (soft_bus) │
│ ┌───────────────────────────────────────────────────┐ │
│ │ DeviceBusCore (只读) │ │
│ │ - 从数据库读取设备列表 │ │
│ │ - 从数据库读取历史数据 │ │
│ └──────────────────┬────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ UI组件 │ │
│ │ - 设备树(显示设备列表) │ │
│ │ - 数据表格(显示历史数据) │ │
│ │ - 图表(数据可视化) │ │
│ │ - 配置界面(发送配置到守护进程) │ │
│ └───────────────────────────────────────────────────┘ │
│ │ │
│ │ D-Bus │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ DaemonClient │ │
│ │ - 请求守护进程打开/关闭设备 │ │
│ │ - 请求守护进程加载/卸载插件 │ │
│ │ - 查询守护进程状态 │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
主应用的 CoreService 和 DeviceBusCore 不是多余的,它们负责:
// 主应用:从数据库读取历史消息 QList messages = m_busCore->queryMessages(...);
2. **UI状态管理**
```cpp
// 主应用:管理UI中的设备列表状态
// 主应用:管理数据表格的显示状态
// 主应用:管理图表的显示状态
// 主应用:通过D-Bus请求守护进程打开设备 daemonClient->openDevice(device.portname);
### ⚠️ 重要区别
| 操作 | 守护进程 | 主应用 |
|------|---------|--------|
| **打开串口** | ✅ 直接打开 | ❌ 通过D-Bus请求 |
| **接收数据** | ✅ 直接接收 | ❌ 不接收 |
| **协议解析** | ✅ 直接解析 | ❌ 不解析 |
| **写入数据库** | ✅ 写入解析结果 | ❌ 不写入 |
| **读取数据库** | ✅ 读取设备配置 | ✅ 读取历史数据 |
| **显示UI** | ❌ 无UI | ✅ 显示界面 |
| **用户配置** | ❌ 只执行 | ✅ 接收并保存 |
### 🎯 总结
**主应用 soft_bus 不是只起到界面显示的作用!**
主应用的职责:
1. ✅ **配置管理**:用户通过UI配置设备,保存到数据库,通过D-Bus通知守护进程
2. ✅ **数据展示**:从数据库读取数据,在UI中展示(表格、图表)
3. ✅ **用户交互**:提供友好的界面,让用户管理设备、查看数据
4. ✅ **插件管理**:通过D-Bus管理守护进程中的协议插件
守护进程的职责:
1. ✅ **硬件管理**:直接操作硬件设备(串口、CAN)
2. ✅ **协议解析**:加载插件,解析协议数据
3. ✅ **数据采集**:实时采集数据,写入数据库
**简单理解**:
- 守护进程 = "工人":负责实际干活(硬件操作、数据采集、协议解析)
- 主应用 = "经理":负责管理和展示(配置管理、数据展示、用户交互)
两者通过**数据库**(共享数据)和**D-Bus**(控制命令)协作!🚀
## 📝 实现方案
### 1. 定义协议解析器接口
```cpp
// src/core/pal/protocols/IProtocolParser.h
/**
* @file IProtocolParser.h
* @brief 协议解析器接口 - 所有协议插件必须实现此接口
*/
#pragma once
#include <QByteArray>
#include <QString>
#include <QJsonObject>
class IProtocolParser {
public:
virtual ~IProtocolParser() = default;
/**
* @brief 获取协议名称
* @return 协议名称(如 "modbus-rtu", "modbus-ascii")
*/
virtual QString getName() const = 0;
/**
* @brief 获取协议描述
*/
virtual QString getDescription() const = 0;
/**
* @brief 获取协议版本
*/
virtual QString getVersion() const = 0;
/**
* @brief 从缓冲区提取完整帧
* @param buffer 数据缓冲区
* @param maxLength 最大帧长度
* @return 提取的帧数据,如果没有完整帧则返回空
*/
virtual QByteArray extractFrame(const QByteArray& buffer, int maxLength = 256) = 0;
/**
* @brief 解析协议帧
* @param frame 完整的协议帧
* @param portName 端口名称(用于设备识别)
* @return 解析后的JSON数据,解析失败返回空对象
*/
virtual QJsonObject parseFrame(const QByteArray& frame, const QString& portName) = 0;
/**
* @brief 验证帧是否有效
* @param frame 协议帧
* @return 有效返回true
*/
virtual bool validateFrame(const QByteArray& frame) const = 0;
/**
* @brief 获取协议配置参数(用于UI配置)
* @return JSON格式的配置参数定义
*/
virtual QJsonObject getConfigSchema() const = 0;
/**
* @brief 设置协议配置参数
* @param config JSON格式的配置参数
*/
virtual void setConfig(const QJsonObject& config) = 0;
};
// 插件导出函数(C接口,避免C++名称修饰问题)
extern "C" {
/**
* @brief 创建协议解析器实例
* @return 协议解析器指针,调用者负责释放
*/
IProtocolParser* createProtocolParser();
/**
* @brief 获取插件信息
* @return JSON格式的插件元数据
*/
const char* getPluginInfo();
}
// src/core/pal/protocols/ProtocolPluginManager.h
/**
* @file ProtocolPluginManager.h
* @brief 协议插件管理器 - 管理协议插件的加载和卸载
*/
#pragma once
#include "IProtocolParser.h"
#include <QMap>
#include <QString>
#include <QLibrary>
#include <QMutex>
class ProtocolPluginManager {
public:
static ProtocolPluginManager* instance();
/**
* @brief 加载协议插件
* @param pluginPath 插件文件路径(.so文件)
* @return 插件ID,失败返回空字符串
*/
QString loadPlugin(const QString& pluginPath);
/**
* @brief 卸载协议插件
* @param pluginId 插件ID
* @return 成功返回true
*/
bool unloadPlugin(const QString& pluginId);
/**
* @brief 获取协议解析器
* @param protocolName 协议名称(如 "modbus-rtu")
* @return 协议解析器指针,不存在返回nullptr
*/
IProtocolParser* getParser(const QString& protocolName);
/**
* @brief 获取所有已加载的插件信息
* @return JSON格式的插件列表
*/
QJsonArray getLoadedPlugins() const;
/**
* @brief 从目录自动加载插件
* @param pluginDir 插件目录路径
*/
void loadPluginsFromDirectory(const QString& pluginDir);
private:
ProtocolPluginManager() = default;
~ProtocolPluginManager();
struct PluginInfo {
QString id;
QString path;
QString name;
QString version;
QLibrary* library;
IProtocolParser* parser;
void (*destroyFunc)(IProtocolParser*);
};
QMap<QString, PluginInfo> m_plugins; // pluginId -> PluginInfo
QMap<QString, QString> m_protocolMap; // protocolName -> pluginId
mutable QMutex m_mutex;
};
// src/serial_manager/serial_parser_thread.cpp
#include "core/pal/protocols/ProtocolPluginManager.h"
void SerialParserThread::run()
{
ProtocolPluginManager* pluginManager = ProtocolPluginManager::instance();
while (!m_stopped) {
// ... 现有代码 ...
// 获取设备配置
DeviceInfo device = m_busCore->getDeviceInfoByPortname(portName);
// 提取帧
QByteArray frame = buffer->extractFrame(device.protocol, device.protocol_detail);
// 使用插件解析
if (!device.protocol.isEmpty()) {
IProtocolParser* parser = pluginManager->getParser(device.protocol_detail);
if (parser) {
QJsonObject parsedData = parser->parseFrame(frame, portName);
if (!parsedData.isEmpty()) {
emit modbusDataParsed(parsedData); // 或统一的数据解析信号
continue;
}
}
}
// 如果插件解析失败,尝试其他协议(向后兼容)
// ...
}
}
// src/daemon/daemon_service.h
public slots:
// ... 现有接口 ...
/**
* @brief 加载协议插件
* @param pluginPath 插件文件路径
* @return 插件ID,失败返回空字符串
*/
QString loadProtocolPlugin(const QString& pluginPath);
/**
* @brief 卸载协议插件
* @param pluginId 插件ID
* @return 成功返回true
*/
bool unloadProtocolPlugin(const QString& pluginId);
/**
* @brief 获取已加载的协议插件列表
* @return JSON格式的插件列表
*/
QString getProtocolPlugins() const;
// src/daemon/daemon_service.cpp
#include "core/pal/protocols/ProtocolPluginManager.h"
QString DaemonService::loadProtocolPlugin(const QString& pluginPath)
{
ProtocolPluginManager* manager = ProtocolPluginManager::instance();
return manager->loadPlugin(pluginPath);
}
bool DaemonService::unloadProtocolPlugin(const QString& pluginId)
{
ProtocolPluginManager* manager = ProtocolPluginManager::instance();
return manager->unloadPlugin(pluginId);
}
QString DaemonService::getProtocolPlugins() const
{
ProtocolPluginManager* manager = ProtocolPluginManager::instance();
QJsonArray plugins = manager->getLoadedPlugins();
return QJsonDocument(plugins).toJson(QJsonDocument::Compact);
}
// plugins/modbus_rtu/modbus_rtu_plugin.cpp
#include "core/pal/protocols/IProtocolParser.h"
class ModbusRTUParser : public IProtocolParser {
public:
QString getName() const override {
return "modbus-rtu";
}
QString getDescription() const override {
return "Modbus RTU Protocol Parser";
}
QString getVersion() const override {
return "1.0.0";
}
QByteArray extractFrame(const QByteArray& buffer, int maxLength) override {
// Modbus RTU 帧提取逻辑
// 检查CRC,提取完整帧
// ...
}
QJsonObject parseFrame(const QByteArray& frame, const QString& portName) override {
// Modbus RTU 解析逻辑
// 解析功能码、数据等
// ...
}
bool validateFrame(const QByteArray& frame) const override {
// 验证CRC等
// ...
}
QJsonObject getConfigSchema() const override {
// 返回配置参数定义
// ...
}
void setConfig(const QJsonObject& config) override {
// 设置配置参数
// ...
}
};
extern "C" {
IProtocolParser* createProtocolParser() {
return new ModbusRTUParser();
}
const char* getPluginInfo() {
return R"({
"name": "modbus-rtu",
"version": "1.0.0",
"description": "Modbus RTU Protocol Parser",
"author": "Your Name"
})";
}
}
soft_bus/
├── plugins/
│ ├── modbus_rtu/
│ │ ├── CMakeLists.txt
│ │ ├── modbus_rtu_plugin.cpp
│ │ └── modbus_rtu_plugin.h
│ ├── modbus_ascii/
│ │ └── ...
│ └── custom_protocol/
│ └── ...
└── install/
└── lib/
└── soft_bus/
└── protocols/
├── modbus_rtu.so
├── modbus_ascii.so
└── custom_protocol.so
// daemon_main.cpp
int main(int argc, char *argv[])
{
// ... 初始化 ...
// 加载协议插件
ProtocolPluginManager* pluginManager = ProtocolPluginManager::instance();
pluginManager->loadPluginsFromDirectory("/usr/lib/soft_bus/protocols");
// ... 其他初始化 ...
}
// 主应用代码
DaemonClient* client = DaemonClient::instance();
client->connectToDaemon();
// 加载新插件
QString pluginId = client->loadProtocolPlugin("/path/to/new_protocol.so");
// 查看已加载的插件
QString pluginsJson = client->getProtocolPlugins();
协议解析在守护进程
支持动态插件
主应用管理插件
IProtocolParser 接口ProtocolPluginManagerSerialParserThread 使用插件DaemonService 添加插件管理接口DaemonClient 添加插件管理接口回答你的问题:协议插件应该放在守护进程中,但通过插件机制支持动态加载。
原因:
这样既保证了性能,又支持了灵活扩展!🚀