下面我将通过实例段代码中关于 DHT11 温湿度传感器 的全部运用,包括:RPC 通信机制、多线程设计、UI 交互以及启动/停止的控制流程,说明线程运用。

一、整体架构

程序通过 RPC(远程过程调用) 与后端服务器通信,后端服务器负责实际驱动 DHT11 硬件读取温湿度。
前端是一个 Qt 界面,包含一个“温湿度”按钮(Temp_HumiButton)、两个显示标签(Humi_label 显示湿度,Temp_label 显示温度)。

核心组件:

  • DHT11Thread:继承自 QThread,在子线程中循环读取温湿度。

  • rpc_dht11_read:RPC 客户端函数,向服务器发送 JSON 请求,并解析返回的温湿度数据。

  • MainWindow::on_Temp_HumiButton_clicked:按钮槽函数,用于启动或停止读取线程,并建立信号槽连接。

二、RPC 客户端实现(rpc_dht11_read

int rpc_dht11_read(char *humi, char *temp)

功能:通过 TCP socket 向 RPC 服务器发送一个 JSON 格式的方法调用,获取 DHT11 的湿度(humi)和温度(temp)。
参数:两个输出指针,用于返回读取到的整数值(湿度为百分比,温度为摄氏度)。

执行步骤:

  1. 构造 JSON 请求

    sprintf(buf, "{\"method\": \"dht11_read\", \"params\": [0], \"id\": \"2\" }");
    • method:调用的远程方法名 dht11_read

    • params:参数数组,这里传入 0(占位,无实际意义)。

    • id:请求标识(这里固定为 "2")。

  2. 通过全局 socket g_SocketClient 发送请求
    使用 send 发送 JSON 字符串,并检查是否全部发送成功。

  3. 读取服务器响应

    • 循环调用 read,跳过仅包含 \r 或 \n 的空行。

    • 读取到的有效响应存入 buf

  4. 解析 JSON 响应

    • 使用 cJSON_Parse 解析 JSON。

    • 获取 result 数组,其第 0 个元素是湿度,第 1 个元素是温度。

    • 将整数值赋给 *humi 和 *temp

    • 释放 cJSON 对象。

  5. 返回值:成功返回 0,失败返回 -1。

注意g_SocketClient 需要通过 RPC_Client_Init() 提前初始化并连接服务器(代码片段中未展示调用,但一般在 main 函数中执行)。

三、DHT11 线程类(DHT11Thread

类定义 MainWindows.h

class DHT11Thread : public QThread {
    Q_OBJECT
public:
    void run() override;
    void Setlabels(QLabel *labelHumi, QLabel *labelTemp); /********/
signals:
    void updateHumi(QString humi);
    void updateTemp(QString temp);
private:
    QLabel *LabelHumi;   // 实际上未在 run 中使用,通过信号槽更新 UI
    QLabel *LabelTemp;
};

1. run() 函数实现zz

void DHT11Thread::run() {
    char humi, temp;
    char buf[50];
    while(!isInterruptionRequested()) {
        if (0 == rpc_dht11_read(&humi, &temp)) {
            sprintf(buf, "%d%%", humi);
            emit updateHumi(QString(buf)); //发送线程读到的rpc请求
            sprintf(buf, "%d", temp);
            emit updateTemp(QString(buf)); //发射更新后的数据
            msleep(1000);   // 成功读取后等待 1 秒
        }
        // 无论成功或失败,都等待 1 秒,并在此过程中可响应中断请求
        for (int i = 0; i < 10 && !isInterruptionRequested(); ++i)
            msleep(100);   // 分 10 次共睡眠 1 秒,提高中断响应速度
        //此1秒间如果刚好有中断请求
    }
}

关键点

  • 循环检查 isInterruptionRequested(),支持外部请求终止线程。

  • 调用 rpc_dht11_read 读取温湿度。若成功:

    • 格式化湿度为 "XX%",温度格式化为 "XX"

    • 通过 emit updateHumi 和 updateTemp 发送信号。

  • 若读取失败,不发送信号,但仍会进入下方的睡眠等待。

  • 睡眠采用 分片睡眠(10×100ms),而不是直接 msleep(1000),这样当主线程调用 requestInterruption() 时,线程可以更快地检查到中断请求并退出,提高响应速度。

2. Setlabels 方法

cpp

void DHT11Thread::Setlabels(QLabel *labelHumi, QLabel *labelTemp) {
    this->LabelHumi = labelHumi;
    this->LabelTemp = labelTemp;
}
  • 此方法将 UI 上的标签指针保存到线程对象中。

  • 但在 run() 中并未使用这两个指针,因为更新 UI 是通过信号槽机制完成的(信号连接到标签的 setText)。

  • 所以 Setlabels 实际上可以省略,保留它可能是历史遗留或为了其他未展示的用途。

  • 线程通过信号发送真实数据,主线程接受数据

四、主窗口(MainWindow)中的 DHT11 交互

1. 按钮槽函数 on_Temp_HumiButton_clicked

void MainWindow::on_Temp_HumiButton_clicked()
{
    static int status = 1;   // 1: 启动,0: 停止
    if(status)
    {
        thread = new DHT11Thread();
        thread->Setlabels(ui->Humi_label, ui->Temp_label);

        connect(thread, &DHT11Thread::updateHumi, ui->Humi_label, &QLabel::setText);
        //将线程的 updateHumi信号连接到 ui->Humi_label的 setText槽。更新QT界面

        connect(thread, &DHT11Thread::updateTemp, ui->Temp_label, &QLabel::setText);

        thread->start();
    } 
    else
    {
        if (thread)
        {
            thread->requestInterruption();   // 请求线程中断
            thread->quit();             // 退出线程事件循环(对于 QThread 非必须,但安全)
            thread->wait();                  // 等待线程真正结束
            delete thread;
            thread = nullptr;
        }
    }
    status = !status;
}

启动流程

  • status 静态变量记录当前状态(1=未启动,0=已启动),每次点击切换。

  • 创建 DHT11Thread 对象。

  • 调用 Setlabels(虽然后续未用到,但保留)。

  • 将线程的 updateHumi 信号连接到 ui->Humi_label 的 setText 槽。

  • 将线程的 updateTemp 信号连接到 ui->Temp_label 的 setText 槽。

  • 启动线程 thread->start(),即执行 run() 函数。

停止流程

  • 调用 thread->requestInterruption(),设置中断标志。

  • thread->quit() 退出线程的事件循环(本线程没有事件循环,但调用无害)。

  • thread->wait() 阻塞等待线程结束,确保资源安全释放。

  • 删除线程对象并置空。

2. UI 标签

  • ui->Humi_label:显示湿度,格式为 "XX%"

  • ui->Temp_label:显示温度,格式为 "XX"(单位隐含为摄氏度)。


五、完整的工作时序

  1. 程序启动main 函数中调用 RPC_Client_Init() 连接到 RPC 服务器(代码未给出,但必须存在)。

  2. 用户点击“温湿度”按钮

    • 创建 DHT11Thread,连接信号槽,启动线程。

  3. 线程循环

    • 每秒钟调用一次 rpc_dht11_read

    • 若成功,发射 updateHumi 和 updateTemp 信号。

    • 主线程(Qt 事件循环)接收到信号后,自动更新 UI 标签的文本。

  4. 用户再次点击按钮

    • 请求线程中断,等待线程退出,销毁线程对象。

    • 停止读取。


六、关键设计要点总结

设计点 实现方式 优点
硬件访问隔离 通过 RPC 与后端服务器通信 前端不直接操作硬件,解耦,便于调试和远程部署
不阻塞 UI 温湿度读取放在独立 QThread 中 界面保持流畅,不会因网络或硬件延迟卡顿
线程安全更新 UI 使用信号槽(队列连接) Qt 自动处理跨线程 UI 更新,无需手动加锁
线程优雅停止 requestInterruption() + 分片睡眠 能够快速响应停止请求,避免 msleep(1000) 长时间阻塞
错误处理 读取失败时不发送信号,继续循环 不会因单次失败导致线程退出,增加鲁棒性
JSON RPC 通信 简单文本协议,使用 cJSON 解析 易于实现,与语言无关,便于与不同后端集成

七、注意事项 

  • RPC 全局 socketg_SocketClient 在多线程中未加锁,但只有一个线程(DHT11Thread)会调用 rpc_dht11_read,且主线程中其他 RPC 调用(LED、VRV、PRV)也在主线程,实际上不会有并发冲突,但严格来说如果多个线程同时使用 socket 需要保护。

  • RPC 响应解析rpc_dht11_read 中假定响应中的 result 是数组且包含两个整数,未做充分错误检查,可以增强健壮性。

  • 单位显示:温度没有显示单位(℃),可以在标签中加上或格式化时追加。

  • 初始状态:按钮上的文字没有变化(未显示“启动/停止”),用户体验可改进。


八、总结

这段代码完整演示了在 Qt 应用中如何:

  • 使用 RPC 客户端 远程读取 DHT11 传感器。

  • 利用 QThread 实现后台周期性数据采集。

  • 通过 信号槽 安全地更新 UI 控件。

  • 实现线程的 优雅启动与停止

整个设计清晰地将硬件访问、业务逻辑和界面显示分离,适用于嵌入式 Linux 或物联网设备上的图形界面开发。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐