# 串口资源管理方案 ## 🔴 问题描述 当前架构中,`soft_bus`(主应用)和 `soft_bus_daemon`(守护进程)是**两个独立的进程**,各自有独立的 `SerialManager` 实例。 **问题**: - 如果守护进程占用串口(因为底层硬件持续传输数据) - 主应用再尝试打开同一个串口会**失败**(串口是独占资源) - 导致主应用无法使用串口功能 ## ✅ 解决方案:守护进程统一管理串口 ### 架构设计 ``` ┌──────────────────────┐ ┌──────────────────────┐ │ soft_bus │ │ soft_bus_daemon │ │ (主应用) │ │ (守护进程) │ │ │ │ │ │ ┌────────────────┐ │ │ ┌────────────────┐ │ │ │ SoftBusAPI │ │ │ │ SoftBusAPI │ │ │ │ (不创建串口) │ │ │ │ (管理串口) │ │ │ └────────────────┘ │ │ └────────────────┘ │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ ┌────────────────┐ │ │ │ │ │ │ SerialManager │ │ │ │ │ │ │ (打开串口) │ │ │ │ │ │ └────────────────┘ │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ ┌────────────────┐ │ │ │ │ │ │ /dev/ttyUSB0 │ │ │ │ │ │ │ (串口设备) │ │ │ │ │ │ └────────────────┘ │ │ │ │ │ │ └─────────┼────────────┘ └─────────┼────────────┘ │ │ │ D-Bus 通信 │ │ ┌─────────────────────────────┐ │ └─►│ DaemonService │◄─────────┘ │ (串口操作接口) │ └─────────────────────────────┘ 数据流向: 1. 主应用通过 D-Bus 请求打开/关闭串口 2. 守护进程管理串口,接收数据 3. 数据通过 D-Bus 信号或数据库传递给主应用 ``` ### 实现步骤 #### 1. 扩展 DaemonService 接口 在 `daemon_service.h` 中添加串口管理接口: ```cpp // daemon_service.h public slots: // ... 现有接口 ... /** * @brief 打开串口 * @param portName 串口名称(如 "ttyUSB0") * @param baudRate 波特率 * @return 成功返回 true */ bool openSerialPort(const QString& portName, int baudRate); /** * @brief 关闭串口 * @param portName 串口名称 */ void closeSerialPort(const QString& portName); /** * @brief 获取已打开的串口列表 * @return 串口名称列表(JSON 格式) */ QString getOpenedSerialPorts() const; /** * @brief 发送数据到串口 * @param portName 串口名称 * @param data 数据(Base64 编码) * @return 成功返回 true */ bool writeSerialData(const QString& portName, const QString& data); signals: // ... 现有信号 ... /** * @brief 串口数据接收时发出 * @param portName 串口名称 * @param data 数据(Base64 编码) */ void serialDataReceived(const QString& portName, const QString& data); /** * @brief 串口状态变化时发出 * @param portName 串口名称 * @param isOpen 是否打开 */ void serialPortStatusChanged(const QString& portName, bool isOpen); ``` #### 2. 实现 DaemonService 串口接口 在 `daemon_service.cpp` 中实现: ```cpp // daemon_service.cpp #include "api/SoftBusAPI.h" bool DaemonService::openSerialPort(const QString& portName, int baudRate) { if (!m_coreService || !m_coreService->isInitialized()) { return false; } SoftBusAPI* api = SoftBusAPI::instance(); if (!api) { return false; } // 查找串口信息 QList ports = QSerialPortInfo::availablePorts(); for (const QSerialPortInfo& info : ports) { if (info.portName() == portName) { // 设置串口配置 SerialManager::SerialConfig config; config.baudRate = static_cast(baudRate); api->setSerialConfig(config); // 打开串口 return api->openSerialPort(info); } } return false; } void DaemonService::closeSerialPort(const QString& portName) { SoftBusAPI* api = SoftBusAPI::instance(); if (api) { api->closeSerialPort(portName); } } QString DaemonService::getOpenedSerialPorts() const { QJsonArray ports; SoftBusAPI* api = SoftBusAPI::instance(); if (api) { QStringList portList = api->getOpenedSerialPorts(); for (const QString& port : portList) { ports.append(port); } } QJsonObject result; result["ports"] = ports; return QJsonDocument(result).toJson(QJsonDocument::Compact); } bool DaemonService::writeSerialData(const QString& portName, const QString& data) { SoftBusAPI* api = SoftBusAPI::instance(); if (!api) { return false; } // Base64 解码 QByteArray byteData = QByteArray::fromBase64(data.toUtf8()); return api->writeSerialData(portName, byteData); } ``` #### 3. 连接串口数据信号 在 `daemon_service.cpp` 构造函数中连接信号: ```cpp DaemonService::DaemonService(CoreService* coreService, QObject* parent) : QObject(parent) , m_coreService(coreService) // ... 其他初始化 ... { // ... 现有代码 ... // 连接串口数据信号 SoftBusAPI* api = SoftBusAPI::instance(); if (api) { connect(api, &SoftBusAPI::serialDataReceived, this, [this](const QString& portName, const QByteArray& data) { // Base64 编码后通过 D-Bus 发送 QString base64Data = QString::fromUtf8(data.toBase64()); emit serialDataReceived(portName, base64Data); }); } } ``` #### 4. 扩展 DaemonClient 接口 在 `daemon_client.h` 中添加串口操作接口: ```cpp // daemon_client.h public: // ... 现有接口 ... /** * @brief 打开串口(通过守护进程) * @param portName 串口名称 * @param baudRate 波特率 * @return 成功返回 true */ bool openSerialPort(const QString& portName, int baudRate); /** * @brief 关闭串口(通过守护进程) * @param portName 串口名称 */ void closeSerialPort(const QString& portName); /** * @brief 获取已打开的串口列表 * @return 串口名称列表 */ QStringList getOpenedSerialPorts() const; /** * @brief 发送数据到串口(通过守护进程) * @param portName 串口名称 * @param data 数据 * @return 成功返回 true */ bool writeSerialData(const QString& portName, const QByteArray& data); signals: // ... 现有信号 ... /** * @brief 串口数据接收时发出 * @param portName 串口名称 * @param data 数据 */ void serialDataReceived(const QString& portName, const QByteArray& data); /** * @brief 串口状态变化时发出 * @param portName 串口名称 * @param isOpen 是否打开 */ void serialPortStatusChanged(const QString& portName, bool isOpen); ``` #### 5. 实现 DaemonClient 串口接口 在 `daemon_client.cpp` 中实现: ```cpp // daemon_client.cpp bool DaemonClient::openSerialPort(const QString& portName, int baudRate) { if (!m_interface || !m_interface->isValid()) { return false; } QDBusReply reply = m_interface->call("openSerialPort", portName, baudRate); return reply.isValid() && reply.value(); } void DaemonClient::closeSerialPort(const QString& portName) { if (m_interface && m_interface->isValid()) { m_interface->call("closeSerialPort", portName); } } QStringList DaemonClient::getOpenedSerialPorts() const { if (!m_interface || !m_interface->isValid()) { return QStringList(); } QDBusReply reply = m_interface->call("getOpenedSerialPorts"); if (reply.isValid()) { QJsonDocument doc = QJsonDocument::fromJson(reply.value().toUtf8()); QJsonObject obj = doc.object(); QJsonArray ports = obj["ports"].toArray(); QStringList result; for (const QJsonValue& value : ports) { result.append(value.toString()); } return result; } return QStringList(); } bool DaemonClient::writeSerialData(const QString& portName, const QByteArray& data) { if (!m_interface || !m_interface->isValid()) { return false; } // Base64 编码 QString base64Data = QString::fromUtf8(data.toBase64()); QDBusReply reply = m_interface->call("writeSerialData", portName, base64Data); return reply.isValid() && reply.value(); } ``` #### 6. 连接 D-Bus 信号 在 `daemon_client.cpp` 的 `connectToDaemon()` 中连接信号: ```cpp bool DaemonClient::connectToDaemon() { // ... 现有代码 ... // 连接串口信号 connection.connect( "com.softbus.Daemon", "/com/softbus/Daemon", "com.softbus.Daemon", "serialDataReceived", this, SLOT(onSerialDataReceived(QString, QString)) ); connection.connect( "com.softbus.Daemon", "/com/softbus/Daemon", "com.softbus.Daemon", "serialPortStatusChanged", this, SLOT(onSerialPortStatusChanged(QString, bool)) ); // ... 其他代码 ... } void DaemonClient::onSerialDataReceived(const QString& portName, const QString& base64Data) { QByteArray data = QByteArray::fromBase64(base64Data.toUtf8()); emit serialDataReceived(portName, data); } void DaemonClient::onSerialPortStatusChanged(const QString& portName, bool isOpen) { emit serialPortStatusChanged(portName, isOpen); } ``` #### 7. 修改主应用的串口使用方式 在主应用中,不再直接使用 `SoftBusAPI` 的串口接口,而是通过 `DaemonClient`: ```cpp // 原来的方式(不再使用) // SoftBusAPI* api = SoftBusAPI::instance(); // api->openSerialPort(portInfo); // 新的方式(通过守护进程) DaemonClient* client = DaemonClient::instance(); if (client->connectToDaemon()) { client->openSerialPort("ttyUSB0", 9600); // 连接数据接收信号 connect(client, &DaemonClient::serialDataReceived, this, [this](const QString& portName, const QByteArray& data) { // 处理接收到的数据 qDebug() << "Received data from" << portName << ":" << data.toHex(); }); } ``` #### 8. 守护进程自动启动串口发现 在守护进程启动时,自动开始串口发现: ```cpp // daemon_main.cpp int main(int argc, char *argv[]) { // ... 现有代码 ... // 初始化核心服务 CoreService* coreService = CoreService::instance(); if (!coreService->initialize()) { qCritical() << "Failed to initialize CoreService"; return 1; } // 启动设备发现(包括串口) SoftBusAPI* api = SoftBusAPI::instance(); if (api) { api->startDeviceDiscovery(5000); // 每5秒扫描一次 } // ... 其他代码 ... } ``` ## 📋 实施建议 ### 阶段1:基础实现(推荐先做) 1. ✅ 扩展 `DaemonService` 添加串口接口 2. ✅ 扩展 `DaemonClient` 添加串口接口 3. ✅ 实现基本的打开/关闭/发送功能 ### 阶段2:数据传递 1. ✅ 实现 D-Bus 信号传递串口数据 2. ✅ 主应用接收并显示数据 ### 阶段3:完整功能 1. ✅ 串口配置(波特率等)通过 D-Bus 传递 2. ✅ 错误处理和状态反馈 3. ✅ 自动重连机制 ## ⚠️ 注意事项 1. **串口独占性**:确保只有守护进程打开串口,主应用不再直接打开 2. **数据编码**:D-Bus 传递二进制数据需要使用 Base64 编码 3. **性能考虑**:高频数据可能影响 D-Bus 性能,考虑批量传输或数据库存储 4. **错误处理**:守护进程关闭或重启时,主应用需要重新连接 ## 🎯 优势 1. ✅ **资源统一管理**:串口由守护进程统一管理,避免冲突 2. ✅ **进程隔离**:主应用崩溃不影响串口通信 3. ✅ **数据共享**:数据通过数据库和 D-Bus 共享 4. ✅ **灵活扩展**:可以轻松添加更多硬件资源管理 ## 📝 总结 通过让守护进程统一管理串口资源,主应用通过 D-Bus 操作串口,可以: - ✅ 避免串口资源冲突 - ✅ 确保底层硬件数据持续接收 - ✅ 主应用可以正常使用串口功能 - ✅ 提高系统稳定性和可维护性