基础草图架构
基础草图架构
Section titled “基础草图架构”本节介绍完整的”基础草图”架构,它结合了Wi-Fi连接、MQTT通信和非阻塞定时。通过本节学习,你将能够:
- 理解PlatformIO ESP32草图的多文件项目结构
- 实现规范化的Wi-Fi + MQTT基础草图,作为所有课程项目的基础
- 将ESP32代码组织为功能模块(Wi-Fi、MQTT、传感器、主程序)
- WiFi连接实现(01-09)
- MQTT客户端设置(01-10)
- PlatformIO IDE设置(01-05)
基础草图架构
Section titled “基础草图架构”本课程中的所有物联网项目遵循相同的架构模式:
┌──────────────────────────────────────────────────┐│ main.cpp ││ setup() → init serial → connect WiFi → ││ init MQTT → init sensors ││ loop() → maintain MQTT → read sensors → ││ publish data → non-blocking delays │└──────────────────────────────────────────────────┘ │ ┌──────────────┼──────────────┐ ▼ ▼ ▼┌─────────────────┐ ┌──────────┐ ┌──────────────┐│ wifi_mqtt.h │ │credentials.h│ │ sensor.h ││ - WiFi connect │ │ - SSID │ │ - read data ││ - MQTT callback │ │ - Password│ │ - process ││ - reconnect │ │ - Broker │ │ - format │└─────────────────┘ └──────────┘ └──────────────┘这种关注点分离意味着:
- Wi-Fi/MQTT代码 可在项目间复用
- 凭证 隔离在单个文件中(排除在版本控制之外)
- 传感器逻辑 是模块化且可替换的
- main.cpp 是编排者,而非实现者
文件结构(PlatformIO)
Section titled “文件结构(PlatformIO)”basic-sketch/ ├── platformio.ini # 开发板、框架、库 └── src/ ├── main.cpp # setup()和loop()编排 ├── wifi_mqtt.h # Wi-Fi和MQTT连接函数 └── credentials.h # SSID、密码、Broker地址第一步:创建项目
Section titled “第一步:创建项目”- 创建一个新的PlatformIO项目:
esp32-basic-sketch - 开发板:
Espressif ESP32 Dev Module - 框架:
Arduino - 添加到
platformio.ini:
[env:esp32dev]platform = espressif32board = esp32devframework = arduinomonitor_speed = 115200upload_speed = 921600
lib_deps = knolleary/PubSubClient @ ^2.8 bblanchon/ArduinoJson @ ^7.0第二步:创建credentials.h
Section titled “第二步:创建credentials.h”#ifndef CREDENTIALS_H#define CREDENTIALS_H
// Wi-Ficonst char* ssid = "YourWiFiSSID";const char* password = "YourWiFiPassword";
// MQTT Brokerconst char* mqttServer = "192.168.1.100";const int mqttPort = 1883;const char* mqttUser = ""; // 无认证时留空const char* mqttPassword = "";
#endif第三步:创建wifi_mqtt.h
Section titled “第三步:创建wifi_mqtt.h”#ifndef WIFI_MQTT_H#define WIFI_MQTT_H
#include <Arduino.h>#include <WiFi.h>#include <PubSubClient.h>
// 前向声明void connectToWiFi();void connectToMQTT();void mqttCallback(char* topic, byte* payload, unsigned int length);void sendMQTTValues();
// Wi-Fi客户端和MQTT客户端对象WiFiClient wifiClient;PubSubClient mqttClient(wifiClient);
// 客户端标识符const char* clientId = "esp32-basic";
// MQTT主题const char* topicPublish = "esp32/data";const char* topicSubscribe = "esp32/commands";
void connectToWiFi() { Serial.print("Connecting to Wi-Fi"); WiFi.begin(ssid, password);
int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 40) { delay(500); Serial.print("."); attempts++; }
if (WiFi.status() == WL_CONNECTED) { Serial.println(); Serial.println("Wi-Fi connected!"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } else { Serial.println(); Serial.println("Wi-Fi connection failed!"); Serial.println("Restarting in 2 seconds..."); delay(2000); ESP.restart(); }}
void mqttCallback(char* topic, byte* payload, unsigned int length) { Serial.print("MQTT message arrived ["); Serial.print(topic); Serial.print("]: ");
String message = ""; for (unsigned int i = 0; i < length; i++) { message += (char)payload[i]; } Serial.println(message);
// 基于主题的路由 if (String(topic) == "esp32/commands") { if (message == "ON") { Serial.println("Command: ON"); // 在此添加开逻辑 } else if (message == "OFF") { Serial.println("Command: OFF"); // 在此添加关逻辑 } }}
void connectToMQTT() { mqttClient.setServer(mqttServer, mqttPort); mqttClient.setCallback(mqttCallback);
while (!mqttClient.connected()) { Serial.print("Attempting MQTT connection...");
if (mqttClient.connect(clientId, mqttUser, mqttPassword)) { Serial.println("connected!");
// 订阅主题 mqttClient.subscribe(topicSubscribe);
// 发布在线状态 mqttClient.publish("esp32/status", "online"); } else { Serial.print("failed, rc="); Serial.print(mqttClient.state()); Serial.println(" retrying in 5 seconds"); delay(5000); } }}
#endif第四步:创建main.cpp
Section titled “第四步:创建main.cpp”#include <Arduino.h>#include "credentials.h"#include "wifi_mqtt.h"
// 常量const unsigned long PUBLISH_INTERVAL = 5000; // 每5秒发布一次const int LED_PIN = 2; // 大多数DevKit上的内置LED
// 全局变量unsigned long previousMillis = 0;int ledState = LOW;
void setup() { Serial.begin(115200); delay(1000);
Serial.println("ESP32 Basic Sketch"); Serial.println("=================");
// 初始化硬件 pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW);
// 连接Wi-Fi connectToWiFi();
// 连接MQTT connectToMQTT();}
void loop() { unsigned long currentMillis = millis();
// 1. 维护MQTT连接(必须频繁调用) if (!mqttClient.connected()) { // 先检查Wi-Fi if (WiFi.status() != WL_CONNECTED) { connectToWiFi(); } connectToMQTT(); } mqttClient.loop();
// 2. 非阻塞LED闪烁(状态指示) digitalWrite(LED_PIN, ledState);
// 3. 定期数据发布 if (currentMillis - previousMillis >= PUBLISH_INTERVAL) { previousMillis = currentMillis;
// 每次发布切换LED状态 ledState = !ledState;
// 发布传感器数据 sendMQTTValues(); }}
void sendMQTTValues() { // 模拟传感器数据(替换为真实传感器读数) int temperature = random(20, 35); int humidity = random(40, 80);
// 创建JSON负载 String payload = "{\"temperature\":"; payload += temperature; payload += ",\"humidity\":"; payload += humidity; payload += "}";
// 发布到MQTT主题 if (mqttClient.publish(topicPublish, payload.c_str())) { Serial.print("Published: "); Serial.println(payload); } else { Serial.println("Publish failed!"); }}第五步:完整流程描述
Section titled “第五步:完整流程描述”- 上电:ESP32启动并执行
setup() - 串行初始化:串行端口以115200波特率打开
- LED初始化:内置LED引脚配置为输出
- Wi-Fi连接:连接到Wi-Fi网络(最多重试40次=~20秒)
- MQTT连接:连接到MQTT Broker,订阅命令主题
- 循环开始:进入主循环
- MQTT维护:调用
client.loop()处理传入消息并保持连接 - 定时器检查:每5秒发布模拟传感器数据
- LED切换:每次发布切换内置LED(视觉心跳指示)
第六步:测试完整草图
Section titled “第六步:测试完整草图”- 将草图上载到ESP32
- 打开串行监视器(115200波特率)
- 观察连接序列
- 验证每5秒出现”Published”消息
- 从命令行订阅以接收数据:
Terminal window mosquitto_sub -h 192.168.1.100 -t "esp32/data" - 发送命令:
Terminal window mosquitto_pub -h 192.168.1.100 -t "esp32/commands" -m "ON"
- 串行监视器显示完整的连接序列(Wi-Fi → MQTT)
- 每5秒向正确的MQTT主题发布数据
- 内置LED随每次发布切换(视觉确认)
- MQTT订阅者正确接收JSON负载
- 通过
mosquitto_pub发送的命令在回调中接收 - 断开并重新连接Broker后自动恢复
草图未启动,只显示”Connecting to Wi-Fi”
Section titled “草图未启动,只显示”Connecting to Wi-Fi””原因:Wi-Fi凭证错误,或路由器不可达。
解决方案:
- 验证
credentials.h中的SSID和密码 - 检查路由器是否开机并广播
- 验证ESP32在信号范围内
- 检查2.4 GHz vs 5 GHz:ESP32仅支持2.4 GHz
MQTT已连接但未发布消息
Section titled “MQTT已连接但未发布消息”原因:
- 发布间隔定时器未复位
- MQTT客户端断开但未被检测到
解决方案:
- 验证
PUBLISH_INTERVAL设置为合理值(5000 ms) - 检查
mqttClient.connected()返回true - 确保每次迭代都调用
mqttClient.loop() - 使用
mqttClient.publish()的返回值检测失败
ESP32数小时后断开Wi-Fi
Section titled “ESP32数小时后断开Wi-Fi”原因:
- 路由器DHCP租约超时
- Wi-Fi无线电干扰
- 电源问题
解决方案:
- 启用自动重连:在
setup()中添加WiFi.setAutoReconnect(true) - 在
loop()中添加定期Wi-Fi状态检查 - 实现看门狗定时器(ESP32内置),如果连接丢失超过5分钟则重启
- 每个新项目从此基础草图开始:它提供了经过验证的Wi-Fi、MQTT和定时基础设施
- 使用多文件结构:将凭证、Wi-Fi/MQTT和传感器逻辑分离到不同文件中
- 实现非阻塞模式:生产代码中永远不要使用
delay()进行定时 - 添加状态LED:心跳LED(每次数据发布时切换)提供即时视觉反馈
- 记录所有连接状态变化:这有助于远程诊断现场问题
- 先使用
random()或虚拟数据:在添加真实传感器前让通信层正常工作
- 基础草图架构将关注点分离到
main.cpp、wifi_mqtt.h和credentials.h中 loop()函数维护MQTT、检查定时器、发布数据和回调处理- Wi-Fi和MQTT重连逻辑确保对网络中断的健壮性
- 使用
millis()的非阻塞定时支持多个并发任务 - 此架构是所有后续课程项目的基础