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);
}
运行与验证
- 编译烧录:将上述代码与LVGL、MQTT库链接,确保
mqtt_subscribe/mqtt_unsubscribe已对接实际MQTT客户端。 - 启动流程:调用
ui_mqtt_pages_init()完成UI创建和初始订阅。 - 验证步骤:
- 启动后观察串口日志,确认已订阅
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页)不应显示该值,验证取消订阅生效。
- 启动后观察串口日志,确认已订阅
- 通过条件:页面切换时MQTT订阅关系正确变更,且仅当前页面的主题消息能更新UI。
关键说明
- 页面切换采用
LV_OBJ_FLAG_HIDDEN控制显隐,避免删除重建带来的资源开销。 - MQTT回调中通过
strcmp匹配主题,确保消息路由到正确的页面Label。 - 实际工程中需将
mqtt_subscribe/mqtt_unsubscribe替换为所用MQTT库的真实API,并确保回调在LVGL任务上下文中安全调用(必要时使用互斥锁或消息队列)。