2. MQTT 主题订阅列表界面

任务目标

构建一个LVGL图形界面,包含一个开关(Switch)控件。当用户切换开关时,通过MQTT协议发布包含开关状态的JSON消息到指定主题,远程LED设备订阅该主题并执行开关动作。本案例聚焦于LVGL事件回调与MQTT消息发布的联动实现。

环境依赖

  • LVGL v8.x(已初始化,显示器/触摸驱动可用)
  • MQTT客户端库:paho.mqtt.embedded-c 或等效C语言MQTT库
  • 网络协议栈(如lwIP)已就绪

核心实现

// mqtt_led_control.c
#include "lvgl/lvgl.h"
#include "MQTTClient.h"

// MQTT配置
#define BROKER_ADDR     "192.168.1.100"
#define BROKER_PORT     1883
#define TOPIC_LED_CTRL  "device/led/control"
#define CLIENT_ID       "lvgl_ui_02"

static Network network;
static MQTTClient client;
static int led_state = 0;  // 0: off, 1: on

// 发布LED状态到MQTT Broker
static void publish_led_state(int state) {
    char payload[32];
    int rc;
    MQTTMessage msg;

    snprintf(payload, sizeof(payload), "{\"led\":%d}", state);
    msg.qos = 1;
    msg.retained = 0;
    msg.payload = payload;
    msg.payloadlen = strlen(payload);

    rc = MQTTClient_publish(&client, TOPIC_LED_CTRL, &msg);
    if (rc != MQTT_SUCCESS) {
        LV_LOG_ERROR("MQTT publish failed, rc=%d", rc);
    } else {
        LV_LOG_INFO("Published: %s", payload);
    }
}

// LVGL开关事件回调
static void switch_event_cb(lv_event_t *e) {
    lv_obj_t *sw = lv_event_get_target(e);
    led_state = lv_obj_has_state(sw, LV_STATE_CHECKED) ? 1 : 0;
    publish_led_state(led_state);
}

// MQTT连接(阻塞式,失败重试)
static void mqtt_connect(void) {
    int rc;

    Network_init(&network);
    while (Network_connect(&network, BROKER_ADDR, BROKER_PORT) != 0) {
        LV_LOG_WARN("MQTT broker unreachable, retry in 2s...");
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
    MQTTClientInit(&client, &network, 1000, (uint8_t *)malloc(128), 128,
                   (uint8_t *)malloc(128), 128);

    MQTTString clientId = MQTTString_initializer;
    clientId.cstring = CLIENT_ID;
    rc = MQTTClient_connect(&client, clientId, NULL, NULL, 0, NULL, NULL, 0);
    if (rc != MQTT_SUCCESS) {
        LV_LOG_ERROR("MQTT connect failed, rc=%d", rc);
    }
}

// UI创建:开关控件
static void ui_create(lv_obj_t *parent) {
    lv_obj_t *sw = lv_switch_create(parent);
    lv_obj_set_size(sw, 80, 40);
    lv_obj_center(sw);
    lv_obj_add_event_cb(sw, switch_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
}

// 主任务入口(由RTOS调用)
void mqtt_led_ui_task(void *arg) {
    lv_obj_t *scr = lv_scr_act();

    mqtt_connect();
    ui_create(scr);

    while (1) {
        lv_timer_handler();
        vTaskDelay(pdMS_TO_TICKS(5));
    }
}

运行与验证

  1. 编译:将上述代码与LVGL、MQTT库链接,确保网络接口已初始化。
  2. 运行:启动设备,程序会阻塞重试直至连接上Broker(192.168.1.100:1883)。
  3. 验证步骤:
    • 在PC上启动MQTT订阅工具(如mosquitto_sub):
      mosquitto_sub -h 192.168.1.100 -t "device/led/control"
    • 在LVGL界面上点击开关,观察订阅终端是否收到JSON消息。
    • 通过条件:每次切换开关,终端收到且仅收到一条形如 {"led":1} 或 {"led":0} 的消息,且数值与开关状态一致。

关键说明

  • 事件回调使用 LV_EVENT_VALUE_CHANGED,仅在开关状态实际变化时触发,避免重复发布。
  • MQTT QoS设为1,保证消息至少送达一次;若需严格去重可改用QoS 2。
  • 远程LED端需订阅 device/led/control 并解析JSON中的 led 字段控制GPIO。
  • 本示例使用阻塞式连接,实际产品建议改为非阻塞+状态机方式。