串口资源管理方案.md 14 KB

串口资源管理方案

🔴 问题描述

当前架构中,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 中添加串口管理接口:

// 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 中实现:

// 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<QSerialPortInfo> ports = QSerialPortInfo::availablePorts();
    for (const QSerialPortInfo& info : ports) {
        if (info.portName() == portName) {
            // 设置串口配置
            SerialManager::SerialConfig config;
            config.baudRate = static_cast<QSerialPort::BaudRate>(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 构造函数中连接信号:

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 中添加串口操作接口:

// 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 中实现:

// daemon_client.cpp
bool DaemonClient::openSerialPort(const QString& portName, int baudRate)
{
    if (!m_interface || !m_interface->isValid()) {
        return false;
    }
    
    QDBusReply<bool> 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<QString> 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<bool> reply = m_interface->call("writeSerialData", portName, base64Data);
    return reply.isValid() && reply.value();
}

6. 连接 D-Bus 信号

daemon_client.cppconnectToDaemon() 中连接信号:

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

// 原来的方式(不再使用)
// 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. 守护进程自动启动串口发现

在守护进程启动时,自动开始串口发现:

// 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 操作串口,可以:

  • ✅ 避免串口资源冲突
  • ✅ 确保底层硬件数据持续接收
  • ✅ 主应用可以正常使用串口功能
  • ✅ 提高系统稳定性和可维护性