4. 多页面UI切换与MQTT主题映射

任务目标

构建一个LVGL多页面UI(3个页面),每个页面绑定独立的MQTT主题。当用户切换页面时,自动取消订阅旧页面主题并订阅新页面主题,同时将接收到的消息显示在当前页面的Label控件上。

环境依赖

  • LVGL v8.x(已初始化,显示器/触摸驱动可用)
  • MQTT客户端库(如paho.mqtt.embedded-c或ESP-MQTT),需提供mqtt_subscribe()/mqtt_unsubscribe()接口
  • 开发板具备网络连接能力

核心实现

// 页面与主题映射表
typedef struct {
    lv_obj_t *page;          // 页面容器对象
    const char *topic;       // 该页面绑定的MQTT主题
    lv_obj_t *label;         // 消息显示Label
} page_topic_map_t;

static page_topic_map_t page_map[3];
static int current_page_idx = 0;

// 页面创建函数(简化,仅展示结构)
static void create_page(int idx, const char *topic) {
    lv_obj_t *page = lv_obj_create(lv_scr_act());
    lv_obj_set_size(page, 240, 240);
    lv_obj_align(page, LV_ALIGN_CENTER, 0, 0);
    lv_obj_clear_flag(page, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_t *lbl = lv_label_create(page);
    lv_label_set_text(lbl, "等待消息...");
    lv_obj_center(lbl);

    page_map[idx].page = page;
    page_map[idx].topic = topic;
    page_map[idx].label = lbl;
}

// 切换页面:隐藏所有页面,显示目标页,更新MQTT订阅
static void switch_page(int new_idx) {
    if (new_idx == current_page_idx) return;

    // 取消订阅旧主题
    mqtt_unsubscribe(page_map[current_page_idx].topic);

    // 隐藏所有页面
    for (int i = 0; i < 3; i++) {
        lv_obj_add_flag(page_map[i].page, LV_OBJ_FLAG_HIDDEN);
    }

    // 显示新页面
    lv_obj_clear_flag(page_map[new_idx].page, LV_OBJ_FLAG_HIDDEN);

    // 订阅新主题
    mqtt_subscribe(page_map[new_idx].topic);

    current_page_idx = new_idx;
}

// MQTT消息回调(由MQTT库在收到消息时调用)
void mqtt_message_callback(const char *topic, const char *payload) {
    // 查找消息主题对应的页面
    for (int i = 0; i < 3; i++) {
        if (strcmp(topic, page_map[i].topic) == 0) {
            // 仅当该页面是当前显示页时更新Label
            if (i == current_page_idx) {
                lv_label_set_text(page_map[i].label, payload);
            }
            break;
        }
    }
}

// 按钮事件:切换到指定页面
static void btn_event_cb(lv_event_t *e) {
    int target = (int)lv_event_get_user_data(e);
    switch_page(target);
}

// 初始化:创建页面、按钮、初始订阅
void ui_mqtt_pages_init(void) {
    // 创建3个页面,绑定不同主题
    create_page(0, "sensor/temp");
    create_page(1, "sensor/humidity");
    create_page(2, "sensor/pressure");

    // 创建3个切换按钮(底部导航)
    const char *btn_labels[] = {"温度", "湿度", "气压"};
    for (int i = 0; i < 3; i++) {
        lv_obj_t *btn = lv_btn_create(lv_scr_act());
        lv_obj_set_pos(btn, i * 80, 260);
        lv_obj_set_size(btn, 70, 40);
        lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, (void *)i);

        lv_obj_t *lbl = lv_label_create(btn);
        lv_label_set_text(lbl, btn_labels[i]);
        lv_obj_center(lbl);
    }

    // 默认显示第0页并订阅其主题
    switch_page(0);
}

运行与验证

  1. 编译烧录:将上述代码与LVGL、MQTT库链接,确保mqtt_subscribe/mqtt_unsubscribe已对接实际MQTT客户端。
  2. 启动流程:调用ui_mqtt_pages_init()完成UI创建和初始订阅。
  3. 验证步骤:
    • 启动后观察串口日志,确认已订阅sensor/temp主题。
    • 使用MQTT工具(如mosquitto_pub)向sensor/temp发布消息25.5,屏幕第0页Label应显示25.5。
    • 点击“湿度”按钮,串口应打印取消订阅sensor/temp、订阅sensor/humidity的日志。
    • 向sensor/humidity发布60%,屏幕第1页Label应显示60%。
    • 向sensor/temp发布30.1,当前页面(第1页)不应显示该值,验证取消订阅生效。
  4. 通过条件:页面切换时MQTT订阅关系正确变更,且仅当前页面的主题消息能更新UI。

关键说明

  • 页面切换采用LV_OBJ_FLAG_HIDDEN控制显隐,避免删除重建带来的资源开销。
  • MQTT回调中通过strcmp匹配主题,确保消息路由到正确的页面Label。
  • 实际工程中需将mqtt_subscribe/mqtt_unsubscribe替换为所用MQTT库的真实API,并确保回调在LVGL任务上下文中安全调用(必要时使用互斥锁或消息队列)。