ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

物联网教程:ESP32-S3 基于 Wi-Fi + MQTT 的远程 LED 控制

物联网教程:ESP32-S3 基于 Wi-Fi + MQTT 的远程 LED 控制 ESP32-S3 实战基于 Wi-Fi MQTT 实现消息发布/订阅与远程 LED 控制前言最近在学习 ESP32-S3 和物联网通信相关内容。让 ESP32 连接 Wi-Fi 和 MQTT Broker通过 MQTT 完成消息发布与订阅并根据远程发送的on指令控制 GPIO4 上的 LED。虽然功能只是“远程控制一个 LED”但从物联网系统的角度来看这实际上已经打通了一条完整的数据链路上位机 / MQTTX │ │ MQTT ▼ MQTT Broker │ │ MQTT ▼ ESP32 │ ▼ GPIO4 │ ▼ LED与此同时ESP32 还会主动向 Broker 发布消息因此它同时具有MQTT Publisher发布者MQTT Subscriber订阅者两个角色。一、项目目标本项目使用 ESP32 实现一个基础的物联网通信闭环主要完成以下功能ESP32 连接 Wi-Fi获取局域网 IP 地址Wi-Fi 断线后自动重新连接连接 MQTT BrokerESP32 主动发布 MQTT 消息ESP32 订阅 LED 控制 TopicMQTTX 向 ESP32 下发控制指令ESP32 接收 MQTT 数据判断 Payload 内容根据 MQTT 指令控制 GPIO4 LED。本次使用的 MQTT 参数如下MQTT Broker broker.emqx.io:1883 ESP32 发布 Topic esp32/test ESP32 订阅 Topic esp32/led 控制命令 on → LED亮 其他内容 → LED灭 LED GPIO GPIO4二、整体通信架构整个系统的结构可以表示为Wi-Fi │ ▼ ┌─────────┐ │ ESP32 │ └────┬────┘ │ │ MQTT ▼ ┌───────────────────┐ │ MQTT Broker │ │ broker.emqx.io │ │ :1883 │ └──────┬─────┬──────┘ │ │ Publish │ │ Subscribe │ │ esp32/test esp32/led │ │ ▼ ▼ ┌─────────┐ │ MQTTX │ └─────────┘ESP32 在系统中承担两个角色。1. PublisherESP32 MQTT 连接成功以后会主动发布Topic esp32/test Payload hello esp32因此ESP32 ↓ esp32/test ↓ MQTT Broker ↓ MQTTX这条链路主要用于验证ESP32 → Broker → MQTT 客户端这一方向的数据通信是否正常。2. SubscriberESP32 同时会订阅esp32/led当 MQTTX 向这个 Topic 发布onBroker 会将消息转发给 ESP32MQTTX │ │ Publish ▼ esp32/led │ ▼ MQTT Broker │ │ 转发 ▼ ESP32 │ ▼ 判断 Payload │ ▼ GPIO4 │ ▼ LED这样就实现了最基础的物联网远程控制。三、程序整体流程程序整体执行顺序如下ESP32 上电 ↓ 配置 GPIO4 ↓ 初始化 NVS ↓ 初始化网络接口 ↓ 创建默认事件循环 ↓ 创建 Wi-Fi STA 接口 ↓ 初始化 Wi-Fi ↓ 配置 SSID 和密码 ↓ 注册 Wi-Fi 事件 ↓ 启动 Wi-Fi ↓ 连接路由器 ↓ 是否获取 IP ↓ 是 ↓ 初始化 MQTT Client ↓ 连接 MQTT Broker ↓ MQTT连接成功 ↓ 发布 hello esp32 ↓ 订阅 esp32/led ↓ 等待 MQTT 数据 ↓ 收到数据 ↓ 判断是否为 on ↙ ↘ 是 否 ↓ ↓ GPIO41 GPIO40 ↓ ↓ LED亮 LED灭从这里能够看到程序实际上可以分为三个部分Wi-Fi 网络连接 ↓ MQTT 网络通信 ↓ GPIO 设备控制这三个模块组合起来就是一个最简单的 IoT Device。四、Wi-Fi 初始化首先定义 LED GPIO#defineGPIO_LED4然后定义一个变量保存当前 Wi-Fi 状态staticbool wifi_connectedfalse;在app_main()中首先配置 GPIOgpio_set_direction(GPIO_LED,GPIO_MODE_OUTPUT);然后初始化 ESP32 网络相关组件nvs_flash_init();esp_netif_init();esp_event_loop_create_default();esp_netif_create_default_wifi_sta();其中可以简单理解为nvs_flash_init() ↓ 初始化 NVS esp_netif_init() ↓ 初始化网络接口 esp_event_loop_create_default() ↓ 创建系统事件循环 esp_netif_create_default_wifi_sta() ↓ 创建 Wi-Fi STA 网络接口接下来初始化 Wi-Fiwifi_init_config_tcfgWIFI_INIT_CONFIG_DEFAULT();esp_wifi_init(cfg);配置需要连接的 Wi-Fiwifi_config_twifi_config{.sta{.ssidwifi名字,.passwordwifi密码,},};然后设置为 STA 模式esp_wifi_set_mode(WIFI_MODE_STA);并写入配置esp_wifi_set_config(WIFI_IF_STA,wifi_config);这里的 STA 可以理解为ESP32 作为 Wi-Fi 客户端连接路由器。和手机连接家里的 Wi-Fi 是类似的。五、Wi-Fi 事件处理Wi-Fi 并不是简单地执行一次esp_wifi_connect();然后程序就认为连接成功。ESP-IDF 中大量网络功能都是基于事件驱动机制实现的。这里主要监听两个事件WIFI_EVENT_STA_DISCONNECTED IP_EVENT_STA_GOT_IP对应的事件处理函数如下staticvoidwifi_event_handle(void*arg,esp_event_base_tevent_base,int32_tevent_id,void*event_data){if(event_baseWIFI_EVENTevent_idWIFI_EVENT_STA_DISCONNECTED){printf(wifi disconnected\n);wifi_connectedfalse;esp_wifi_connect();}elseif(event_baseIP_EVENTevent_idIP_EVENT_STA_GOT_IP){ip_event_got_ip_t*got_ip(ip_event_got_ip_t*)event_data;wifi_connectedtrue;printf(wifi connected!\n);printf(IPIPSTR\n,IP2STR(got_ip-ip_info.ip));}}整个逻辑其实非常简单。Wi-Fi 断开如果检测到WIFI_EVENT_STA_DISCONNECTED就执行wifi_connectedfalse;esp_wifi_connect();也就是Wi-Fi断开 ↓ 更新连接状态 ↓ 重新连接Wi-Fi这对于物联网设备非常重要。因为实际设备不可能因为路由器短暂断线一次就必须人工重启设备。因此一个最基本的联网设备就应该具备断线 ↓ 发现断线 ↓ 自动重连六、为什么要等待获取 IP在主程序中还有这样一段while(!wifi_connected){vTaskDelay(pdMS_TO_TICKS(100));}它的意思就是wifi_connected false ↓ Wi-Fi还没有获取IP ↓ 等待100ms ↓ 再次判断直到发生IP_EVENT_STA_GOT_IP程序才会wifi_connectedtrue;然后跳出循环继续初始化 MQTT。为什么不能直接启动 MQTT因为 MQTT 是建立在 TCP/IP 网络之上的。如果 ESP32 连 IP 地址都还没有获取ESP32 ↓ 没有可用网络 ↓ DNS / TCP 无法正常工作 ↓ 无法连接 MQTT Broker因此程序的逻辑是Wi-Fi成功 ↓ 获取IP ↓ MQTT启动而不是一上电就直接连接 MQTT。七、初始化 MQTT ClientWi-Fi 成功以后开始配置 MQTT。Broker 设置为esp_mqtt_client_config_tmqtt_cfg{.broker.address.urimqtt://broker.emqx.io:1883,};这里使用的是 EMQX 提供的公共 MQTT Brokerbroker.emqx.io Port 1883然后创建 MQTT Clientesp_mqtt_client_handle_tclientesp_mqtt_client_init(mqtt_cfg);注册 MQTT 事件esp_mqtt_client_register_event(client,ESP_EVENT_ANY_ID,esp_mqtt_event_handle,NULL);最后启动esp_mqtt_client_start(client);整体流程就是配置 MQTT Broker ↓ esp_mqtt_client_init() ↓ 创建 MQTT Client ↓ 注册 MQTT Event Handler ↓ esp_mqtt_client_start() ↓ 连接 MQTT Broker八、MQTT 事件处理MQTT 的核心同样是事件机制。这里主要处理两个事件MQTT_EVENT_CONNECTED MQTT_EVENT_DATA分别表示MQTT_EVENT_CONNECTED ↓ MQTT Broker连接成功以及MQTT_EVENT_DATA ↓ 收到订阅Topic的数据事件处理函数staticvoidesp_mqtt_event_handle(void*handle_args,esp_event_base_tbase,int32_tevent_id,void*event_data){...}九、MQTT PublishESP32 主动发布数据MQTT Broker 连接成功以后if(event_idMQTT_EVENT_CONNECTED){printf(MQTT connected!\n);}首先获取 MQTT Eventesp_mqtt_event_handle_tevent(esp_mqtt_event_handle_t)event_data;然后发布消息intmsg_idesp_mqtt_client_publish(event-client,esp32/test,hello esp32,0,0,0);几个参数分别表示event-client 当前 MQTT Client esp32/test 需要发布的 Topic hello esp32 消息 Payload 0 自动计算 Payload 长度 0 QoS 0 0 retain false因此 ESP32 连接 MQTT 成功以后会发送Topic esp32/test Payload hello esp32如果 MQTTX 订阅esp32/test就可以收到hello esp32这样就验证了ESP32 ↓ MQTT Broker ↓ MQTTX这条上行链路。十、MQTT SubscribeESP32 订阅控制 Topic连接成功以后ESP32 同时订阅esp_mqtt_client_subscribe(event-client,esp32/led,0);也就是告诉 Broker如果以后esp32/led出现新的 MQTT 消息就把消息转发给这个 ESP32。所以 MQTTX 可以向esp32/led发布on消息经过MQTTX ↓ broker.emqx.io ↓ ESP32最终进入 MQTT Event Handler。十一、处理 MQTT 数据ESP32 收到订阅消息以后会产生MQTT_EVENT_DATA因此elseif(event_idMQTT_EVENT_DATA){...}首先获取消息esp_mqtt_event_handle_tevent(esp_mqtt_event_handle_t)event_data;然后打印 Topicprintf(Topic:%.*s\n,event-topic_len,event-topic);打印 Payloadprintf(Data: %.*s\n,event-data_len,event-data);例如 MQTTX 发送Topic esp32/led Payload onESP32 串口会看到Topic:esp32/led Data:on十二、为什么这里使用 strncmp而不是 strcmp控制 LED 的核心代码为if(event-data_len2strncmp(event-data,on,2)0){printf(led on\n);gpio_set_level(GPIO_LED,1);}else{gpio_set_level(GPIO_LED,0);}这里有一个值得注意的问题。很多刚开始写 C 的时候可能会直接写strcmp(event-data,on)但是 MQTT 接收到的event-data并不能简单认为一定是一个以\0结束的标准 C 字符串。因此这里同时利用event-data_len和strncmp()判断event-data_len2并比较前两个字符strncmp(event-data,on,2)这样更加符合 MQTT 数据的处理方式。十三、远程控制 LED最终逻辑非常简单if(event-data_len2strncmp(event-data,on,2)0){gpio_set_level(GPIO_LED,1);}else{gpio_set_level(GPIO_LED,0);}也就是收到 on ↓ GPIO4 1 ↓ LED亮否则收到其他数据 ↓ GPIO4 0 ↓ LED灭完整的数据链路为MQTTX │ │ Publish │ │ Topic: esp32/led │ Payload: on ▼ MQTT Broker │ │ 消息转发 ▼ ESP32 │ │ MQTT_EVENT_DATA ▼ 读取Payload │ ▼ 判断是不是on │ ├── 是 → GPIO4 1 → LED亮 │ └── 否 → GPIO4 0 → LED灭这样就形成了一个最简单的网络命令 → MCU → GPIO → 物理设备控制流程。十四、完整代码下面是本次实验的完整程序#includestdint.h#includestdio.h#includedriver/gpio.h#includeesp_event_base.h#includeesp_netif_types.h#includeesp_wifi_types_generic.h#includehal/gpio_types.h#includemqtt_client.h#includestdbool.h#includestring.h#includenvs_flash.h#includeesp_netif.h#includeesp_event.h#includeesp_wifi.h#includefreertos/FreeRTOS.h#includefreertos/task.h#defineGPIO_LED4staticbool wifi_connectedfalse;/* Wi-Fi事件处理 */staticvoidwifi_event_handle(void*arg,esp_event_base_tevent_base,int32_tevent_id,void*event_data){if(event_baseWIFI_EVENTevent_idWIFI_EVENT_STA_DISCONNECTED){printf(wifi disconnected\n);wifi_connectedfalse;esp_wifi_connect();}elseif(event_baseIP_EVENTevent_idIP_EVENT_STA_GOT_IP){ip_event_got_ip_t*got_ip(ip_event_got_ip_t*)event_data;wifi_connectedtrue;printf(wifi connected!\n);printf(IPIPSTR\n,IP2STR(got_ip-ip_info.ip));}}/* MQTT事件处理 */staticvoidesp_mqtt_event_handle(void*handle_args,esp_event_base_tbase,int32_tevent_id,void*event_data){if(event_idMQTT_EVENT_CONNECTED){printf(MQTT connected!\n);esp_mqtt_event_handle_tevent(esp_mqtt_event_handle_t)event_data;/* 发布测试消息 */intmsg_idesp_mqtt_client_publish(event-client,esp32/test,hello esp32,0,0,0);printf(publish msg_id: %d\n,msg_id);/* 订阅LED控制Topic */esp_mqtt_client_subscribe(event-client,esp32/led,0);}elseif(event_idMQTT_EVENT_DATA){esp_mqtt_event_handle_tevent(esp_mqtt_event_handle_t)event_data;printf(Topic:%.*s\n,event-topic_len,event-topic);printf(Data: %.*s\n,event-data_len,event-data);/* 判断MQTT命令 */if(event-data_len2strncmp(event-data,on,2)0){printf(led on\n);gpio_set_level(GPIO_LED,1);}else{gpio_set_level(GPIO_LED,0);}}}voidapp_main(void){/* GPIO初始化 */gpio_set_direction(GPIO_LED,GPIO_MODE_OUTPUT);/* 初始化NVS */nvs_flash_init();/* 初始化网络接口 */esp_netif_init();/* 创建默认事件循环 */esp_event_loop_create_default();/* 创建Wi-Fi STA */esp_netif_create_default_wifi_sta();/* Wi-Fi初始化 */wifi_init_config_tcfgWIFI_INIT_CONFIG_DEFAULT();esp_wifi_init(cfg);/* Wi-Fi配置 */wifi_config_twifi_config{.sta{.ssid304-2,.passwordabcd123456,},};/* STA模式 */esp_wifi_set_mode(WIFI_MODE_STA);/* 设置Wi-Fi配置 */esp_wifi_set_config(WIFI_IF_STA,wifi_config);/* 注册Wi-Fi断开事件 */esp_event_handler_register(WIFI_EVENT,WIFI_EVENT_STA_DISCONNECTED,wifi_event_handle,NULL);/* 注册获取IP事件 */esp_event_handler_register(IP_EVENT,IP_EVENT_STA_GOT_IP,wifi_event_handle,NULL);/* 启动Wi-Fi */esp_wifi_start();esp_wifi_connect();/* 等待Wi-Fi连接 */while(!wifi_connected){vTaskDelay(pdMS_TO_TICKS(100));}/* MQTT配置 */esp_mqtt_client_config_tmqtt_cfg{.broker.address.urimqtt://broker.emqx.io:1883,};/* 创建MQTT Client */esp_mqtt_client_handle_tclientesp_mqtt_client_init(mqtt_cfg);/* 注册MQTT事件 */esp_mqtt_client_register_event(client,ESP_EVENT_ANY_ID,esp_mqtt_event_handle,NULL);/* 启动MQTT */esp_mqtt_client_start(client);}十五、MQTTX 测试方法程序烧录以后ESP32 首先连接 Wi-Fi然后连接broker.emqx.io:18831. 测试 ESP32 PublishMQTTX 连接Host broker.emqx.io Port 1883然后订阅esp32/testESP32 MQTT 连接成功后应该可以收到hello esp32说明ESP32 → MQTT Broker → MQTTX通信正常。2. 测试 ESP32 SubscribeMQTTX 发布Topic esp32/ledPayloadonESP32 串口应该输出Topic:esp32/led Data:on led on与此同时GPIO4 1LED 被点亮。如果发送其他内容off或者123当前程序都会执行gpio_set_level(GPIO_LED,0);因此 LED 熄灭。十六、当前程序存在的几个改进点虽然现在已经完成基本功能但这个程序还只是学习阶段的 Demo还有不少地方可以继续完善。1. 明确区分 on 和 off现在的逻辑是on → 开灯 其他所有数据 → 关灯例如abc 123 hello off都会关灯。后面可以修改成on → 开灯 off → 关灯 其他 → 不处理这样控制协议会更加规范。2. Topic 不应该过于简单目前使用esp32/led由于broker.emqx.io属于公共 MQTT Broker因此 Topic 过于简单可能和其他测试用户产生冲突。实际开发时可以设计成hwy/esp32s3/device01/led/set例如hwy └── esp32s3 └── device01 └── led └── set后期设备数量增加以后这样的 Topic 结构也更加容易管理。3. 增加设备状态上报目前MQTT → ESP32 → LED只有控制链路。下一步可以增加LED状态 ↓ ESP32 ↓ MQTT Publish ↓ 平台例如Topic hwy/esp32s3/device01/led/statePayloadon这样平台发送控制命令以后还能收到设备真正执行后的状态。这就形成下发命令 ↓ 设备执行 ↓ 状态反馈完整闭环。
返回列表