跳转到内容

基础草图架构

本节介绍完整的”基础草图”架构,它结合了Wi-Fi连接、MQTT通信和非阻塞定时。通过本节学习,你将能够:

  • 理解PlatformIO ESP32草图的多文件项目结构
  • 实现规范化的Wi-Fi + MQTT基础草图,作为所有课程项目的基础
  • 将ESP32代码组织为功能模块(Wi-Fi、MQTT、传感器、主程序)
  • WiFi连接实现(01-09)
  • MQTT客户端设置(01-10)
  • PlatformIO IDE设置(01-05)

本课程中的所有物联网项目遵循相同的架构模式:

┌──────────────────────────────────────────────────┐
│ 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 是编排者,而非实现者
basic-sketch/
├── platformio.ini # 开发板、框架、库
└── src/
├── main.cpp # setup()和loop()编排
├── wifi_mqtt.h # Wi-Fi和MQTT连接函数
└── credentials.h # SSID、密码、Broker地址
  1. 创建一个新的PlatformIO项目:esp32-basic-sketch
  2. 开发板:Espressif ESP32 Dev Module
  3. 框架:Arduino
  4. 添加到 platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
upload_speed = 921600
lib_deps =
knolleary/PubSubClient @ ^2.8
bblanchon/ArduinoJson @ ^7.0
#ifndef CREDENTIALS_H
#define CREDENTIALS_H
// Wi-Fi
const char* ssid = "YourWiFiSSID";
const char* password = "YourWiFiPassword";
// MQTT Broker
const char* mqttServer = "192.168.1.100";
const int mqttPort = 1883;
const char* mqttUser = ""; // 无认证时留空
const char* mqttPassword = "";
#endif
#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
#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!");
}
}
  1. 上电:ESP32启动并执行 setup()
  2. 串行初始化:串行端口以115200波特率打开
  3. LED初始化:内置LED引脚配置为输出
  4. Wi-Fi连接:连接到Wi-Fi网络(最多重试40次=~20秒)
  5. MQTT连接:连接到MQTT Broker,订阅命令主题
  6. 循环开始:进入主循环
  7. MQTT维护:调用 client.loop() 处理传入消息并保持连接
  8. 定时器检查:每5秒发布模拟传感器数据
  9. LED切换:每次发布切换内置LED(视觉心跳指示)
  1. 将草图上载到ESP32
  2. 打开串行监视器(115200波特率)
  3. 观察连接序列
  4. 验证每5秒出现”Published”消息
  5. 从命令行订阅以接收数据:
    Terminal window
    mosquitto_sub -h 192.168.1.100 -t "esp32/data"
  6. 发送命令:
    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凭证错误,或路由器不可达。

解决方案

  1. 验证 credentials.h 中的SSID和密码
  2. 检查路由器是否开机并广播
  3. 验证ESP32在信号范围内
  4. 检查2.4 GHz vs 5 GHz:ESP32仅支持2.4 GHz

原因

  • 发布间隔定时器未复位
  • MQTT客户端断开但未被检测到

解决方案

  1. 验证 PUBLISH_INTERVAL 设置为合理值(5000 ms)
  2. 检查 mqttClient.connected() 返回true
  3. 确保每次迭代都调用 mqttClient.loop()
  4. 使用 mqttClient.publish() 的返回值检测失败

原因

  • 路由器DHCP租约超时
  • Wi-Fi无线电干扰
  • 电源问题

解决方案

  1. 启用自动重连:在 setup() 中添加 WiFi.setAutoReconnect(true)
  2. loop() 中添加定期Wi-Fi状态检查
  3. 实现看门狗定时器(ESP32内置),如果连接丢失超过5分钟则重启
  • 每个新项目从此基础草图开始:它提供了经过验证的Wi-Fi、MQTT和定时基础设施
  • 使用多文件结构:将凭证、Wi-Fi/MQTT和传感器逻辑分离到不同文件中
  • 实现非阻塞模式:生产代码中永远不要使用 delay() 进行定时
  • 添加状态LED:心跳LED(每次数据发布时切换)提供即时视觉反馈
  • 记录所有连接状态变化:这有助于远程诊断现场问题
  • 先使用 random() 或虚拟数据:在添加真实传感器前让通信层正常工作
  1. 基础草图架构将关注点分离到 main.cppwifi_mqtt.hcredentials.h
  2. loop() 函数维护MQTT、检查定时器、发布数据和回调处理
  3. Wi-Fi和MQTT重连逻辑确保对网络中断的健壮性
  4. 使用 millis() 的非阻塞定时支持多个并发任务
  5. 此架构是所有后续课程项目的基础