本帖最后由 腿毛利小五郎 于 2025-10-4 23:36 编辑
展示:

前言:
参考社区【指南】使用 MQTT 将 ESP32-C5 连接到 Home Assistant DF创客社区大佬的项目,在树莓派上使用mosquitto跑了MQTT服务
利用手头已有的硬件,简单实现灯带的控制系统,使用Freertos管理任务。
FireBeetle 2 ESP32-C5的资源还有MQTT啥的也不介绍了,社区很多大佬都介绍了很多很多了。
硬件:
FireBeetle 2 ESP32-C5 + WS2812 灯带 + DHT22 温湿度传感器 + OLED 显示屏 + 按键
功能: 环境搭建:
我在树莓派ubuntu下使用docker部署的Home Assistant,其中Settings -> Devices & Services(设置 -> 设备和服务)这一项并没有,如果有的话可以直接在其中安装mosquitto。为图简单,没有找为啥在里面找不到服务。直接使用apt install 去安装mosquitto
- #更新软件包索引
- sudo apt update
-
- #安装 Mosquitto 服务端与命令行客户端工具
- sudo apt install -y mosquitto mosquitto-clients
-
- #启用 Mosquitto 服务并立即启动(systemd 管理)
- sudo systemctl enable --now mosquitto
-
- #创建 MQTT 用户并生成密码文件(首次使用 -c 会新建文件)
- sudo mosquitto_passwd -c /etc/mosquitto/passwd mqttuser
- #按提示输入密码并确认(两次必须一致)
-
- #写入 Mosquitto 配置文件,启用认证并禁用匿名访问
- sudo tee /etc/mosquitto/conf.d/99-local.conf > /dev/null <<'EOF'
- listener 1883 # 监听端口 1883(默认 MQTT)
- allow_anonymous false # 禁止匿名连接
- password_file /etc/mosquitto/passwd # 指定密码文件路径
- persistence true # 启用持久化(保存订阅与消息)
- persistence_location /var/lib/mosquitto/ # 持久化数据存储目录
- log_dest file /var/log/mosquitto/mosquitto.log # 日志输出到文件
- EOF
-
- #创建持久化与日志目录,确保权限正确
- sudo mkdir -p /var/lib/mosquitto /var/log/mosquitto /run/mosquitto
- sudo chown -R mosquitto:mosquitto /var/lib/mosquitto /var/log/mosquitto /run/mosquitto
- sudo chmod 750 /var/lib/mosquitto /var/log/mosquitto /run/mosquitto
-
- #重启 Mosquitto 服务以应用新配置
- sudo systemctl restart mosquitto
-
- #查看服务状态,确认是否启动成功
- sudo systemctl status mosquitto --no-pager --full
-
- #测试订阅:监听主题 test/topic 并显示消息内容
- mosquitto_sub -h 127.0.0.1 -p 1883 -u mqttuser -P '你的密码' -t test/topic -v
-
- #测试发布:向主题 test/topic 发送一条消息
- mosquitto_pub -h 127.0.0.1 -p 1883 -u mqttuser -P '你的密码' -t test/topic -m hello
复制代码
如果收到了类似如下显示,MQTT环境就好了 ,也可以用其他MQTT工具测试发布订阅,这里不用其他工具测试了
肯定也有MQTT服务的端口建立

DHT22的库里面有异常值处理,我代码里没有对其值获取错误的处理,仅过滤了一下



其他库都是官方的
业务逻辑
1 系统启动与初始化 设备上电后,会依次完成: 加载上次保存的配置信息(如 Wi-Fi、MQTT、灯带模式等);
初始化传感器、OLED、LED 灯带、按键;
连接 Wi-Fi,并建立 MQTT 通信(若配置可用);
启动多个任务(如数据采集、显示刷新、MQTT 通信等)。
2 数据采集与显示 传感器周期性采集温度和湿度;
系统获取当前时间和 Wi-Fi 信号强度;
OLED 显示温湿度、时间、Wi-Fi 信号以及当前灯带模式;
若出现异常(如 Wi-Fi 断开)可通过 OLED 或日志反馈。
3 灯带控制 用户可通过按键或远程指令切换灯带模式;
各模式控制灯带显示不同效果(呼吸、跑马、彩虹等);
当前模式会在 OLED 上同步显示。
4 网络通信 通过 MQTT 或本地命令接收远程指令(如切换模式、修改参数等);
上传传感器数据至服务器,实现远程监测与控制。
5 参数持久化 所有重要参数(如 Wi-Fi 配置、MQTT 参数、灯带模式等)在修改后会写入 Flash 存储(Preferences 或 NVS);
下次上电时自动读取,无需重新配置;
保证设备掉电重启后仍能恢复原工作状态。
MQTT工具交互
修改闪灯模式的:

修改wifi信息的,略
修改上传间隔的,略
代码
- #include <Arduino.h>
- #include <Wire.h>
- #include <WiFi.h>
- #include <PubSubClient.h>
- #include <Preferences.h>
- #include "ESP32_WS2812_Lib.h"
- #include <U8g2lib.h>
- #include "DHT22.h"
- #include <ArduinoJson.h>
-
- // —— 硬件定义 ——
- #define LEDS_COUNT 7
- #define LEDS_PIN 2
- #define CHANNEL 0
- #define BUTTONPIN 7
- #define DHT_PIN 28
- #define SDA_PIN 9
- #define SCL_PIN 10
-
- // —— 默认 Wi-Fi 凭据 ——
- const char* WIFI_SSID = "您的wifi账号";
- const char* WIFI_PASSWORD = "您的wifi密码";
-
- // —— MQTT 参数 ——
- const char* MQTT_SERVER = "您的mqtt服务器";
- const int MQTT_PORT = 1883; // 您的mqtt端口
- const char* MQTT_USER = "mqttuser"; //您的mqtt用户名
- const char* MQTT_PASS = "123456"; // 您的mqtt密码
-
- // —— 上传间隔宏定义 ——
- #define UPLOAD_INTERVAL_MS 5000
-
- // —— 全局结构体定义 ——
- struct SensorInfo {
- float temperature;
- float humidity;
- };
-
- struct WiFiInfo {
- String ssid;
- String password;
- int rssi;
- };
-
- struct SystemConfig {
- SensorInfo sensor;
- WiFiInfo wifi;
- unsigned long uploadInterval;
- };
-
- // —— 全局对象 ——
- ESP32_WS2812 strip(LEDS_COUNT, LEDS_PIN, CHANNEL, TYPE_GRB);
- U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
- DHT22 dht22(DHT_PIN);
- Preferences prefs;
- WiFiClient netClient;
- PubSubClient mqtt(netClient);
- SystemConfig gConfig;
-
- // —— 状态变量 ——
- int ledMode = 0;
- volatile bool buttonPressed = false;
- volatile unsigned long lastPress = 0;
-
- // —— 发布控制 ——
- unsigned long lastPublish = 0;
-
- // —— 任务句柄 ——
- TaskHandle_t ledTaskHandle;
- TaskHandle_t oledTaskHandle;
- TaskHandle_t dhtTaskHandle;
- TaskHandle_t wifiTaskHandle;
- TaskHandle_t mqttTaskHandle;
-
- // —— 清空灯带 ——
- void clearStrip() {
- for (int i = 0; i < strip.getLedCount(); i++) {
- strip.setLedColorData(i, 0, 0, 0);
- }
- strip.show();
- }
-
- // —— 按键中断 ——
- void IRAM_ATTR buttonISR() {
- unsigned long now = millis();
- if (now - lastPress > 200) {
- buttonPressed = true;
- lastPress = now;
- }
- }
-
- // —— Wi-Fi 连接函数 ——
- void connectWiFi(const String& ssid, const String& pass) {
- if (ssid.isEmpty()) return;
- Serial.printf("→ Wi-Fi connecting: %s\n", ssid.c_str());
- WiFi.disconnect(true);
- WiFi.begin(ssid.c_str(), pass.c_str());
- unsigned long start = millis();
- while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) {
- delay(200);
- }
- if (WiFi.status() == WL_CONNECTED) {
- Serial.printf("→ Wi-Fi IP: %s\n", WiFi.localIP().toString().c_str());
- } else {
- Serial.println("→ Wi-Fi failed");
- }
- }
-
- // —— MQTT 回调 ——
- void mqttCallback(char* topic, byte* payload, unsigned int length) {
- String msg;
- msg.reserve(length);
- for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
- Serial.printf("← MQTT [%s]: %s\n", topic, msg.c_str());
-
- String t = String(topic);
-
- if (t == "devices/config") {
- StaticJsonDocument<256> doc;
- DeserializationError err = deserializeJson(doc, msg);
- if (!err) {
- if (doc.containsKey("ssid") && doc.containsKey("pass")) {
- gConfig.wifi.ssid = doc["ssid"].as<String>();
- gConfig.wifi.password = doc["pass"].as<String>();
- prefs.putString("wifi_ssid", gConfig.wifi.ssid);
- prefs.putString("wifi_pass", gConfig.wifi.password);
- Serial.printf("→ Saved WiFi: %s / %s\n",
- gConfig.wifi.ssid.c_str(), gConfig.wifi.password.c_str());
- connectWiFi(gConfig.wifi.ssid, gConfig.wifi.password);
- }
-
- if (doc.containsKey("interval")) {
- gConfig.uploadInterval = doc["interval"].as<unsigned long>();
- Serial.printf("→ Updated upload interval: %lu ms\n", gConfig.uploadInterval);
- }
-
- if (doc.containsKey("mode")) {
- ledMode = doc["mode"].as<int>() % 4;
- prefs.putInt("ledMode", ledMode);
- Serial.printf("→ LED mode updated: %d\n", ledMode);
- }
- } else {
- Serial.println("⚠️ JSON parse failed");
- }
- }
- }
-
- // —— MQTT 连接保持 ——
- void ensureMqttConnected() {
- while (!mqtt.connected()) {
- Serial.print("→ MQTT connect... ");
- if (mqtt.connect("ESP32Client", MQTT_USER, MQTT_PASS)) {
- Serial.println("OK");
- mqtt.subscribe("devices/config");
- Serial.println("→ Subscribed to devices/config");
- } else {
- int rc = mqtt.state();
- Serial.printf("Failed rc=%d\n", rc);
- delay(5000);
- }
- }
- mqtt.loop();
- }
-
- // —— LED 任务 ——
- void ledTask(void* pv) {
- const TickType_t interval = pdMS_TO_TICKS(20);
- TickType_t nextWake = xTaskGetTickCount();
- int flowIndex = 0, prevLed = -1, breathLevel = 0, breathDir = 1, rainPos = 0;
-
- while (true) {
- if (buttonPressed) {
- buttonPressed = false;
- ledMode = (ledMode + 1) % 4;
- prefs.putInt("ledMode", ledMode);
- Serial.printf("→ Button: LED mode %d\n", ledMode);
- }
-
- switch (ledMode) {
- case 0:
- clearStrip();
- break;
- case 1: {
- int ci = (flowIndex / strip.getLedCount()) % 3;
- uint8_t rgb[3] = {0}; rgb[ci] = 255;
- int cur = flowIndex % strip.getLedCount();
- if (prevLed >= 0) strip.setLedColorData(prevLed, 0,0,0);
- strip.setLedColorData(cur, rgb[0],rgb[1],rgb[2]);
- strip.show();
- prevLed = cur; flowIndex++;
- break;
- }
- case 2:
- for (int i = 0; i < strip.getLedCount(); i++) {
- uint8_t v = breathLevel;
- strip.setLedColorData(i,
- ((millis()/3000)%3)==0 ? v : 0,
- ((millis()/3000)%3)==1 ? v : 0,
- ((millis()/3000)%3)==2 ? v : 0);
- }
- strip.show();
- breathLevel += breathDir;
- if (breathLevel == 0 || breathLevel == 255) breathDir = -breathDir;
- break;
- case 3:
- for (int i = 0; i < strip.getLedCount(); i++) {
- int pos = (i * 256 / strip.getLedCount() + rainPos) & 0xFF;
- strip.setLedColorData(i, strip.Wheel(pos));
- }
- strip.show();
- rainPos++;
- break;
- }
- vTaskDelayUntil(&nextWake, interval);
- }
- }
-
- // —— OLED 任务 ——
- void oledTask(void* pv) {
- const TickType_t interval = pdMS_TO_TICKS(1000);
- TickType_t nextWake = xTaskGetTickCount();
- struct tm ti;
- const char* modeName[] = {
- "Close",
- "blink",
- "breath",
- "Rainbow"
- };
- while (true) {
- if (getLocalTime(&ti)) {
- u8g2.firstPage();
- do {
- u8g2.setFont(u8g2_font_7x14_tf);
- u8g2.setCursor(5, 14);
- u8g2.print("System Show State");
-
- // 传感器数据
- u8g2.setFont(u8g2_font_6x12_tf);
- u8g2.setCursor(0, 30);
- u8g2.printf("Tem: %.1fC", gConfig.sensor.temperature);
- u8g2.setCursor(65, 30);
- u8g2.printf("Hum: %.1f%%", gConfig.sensor.humidity);
- u8g2.setCursor(0, 42);
- u8g2.printf("LED Mode:%s", modeName[ledMode]);
- // WiFi 信号
- u8g2.setCursor(0, 54);
- u8g2.printf("RSSI:%ddBm", WiFi.RSSI());
-
- // 时间
- u8g2.setFont(u8g2_font_6x10_tf);
- u8g2.setCursor(40, 64);
- u8g2.printf("%02d:%02d:%02d", ti.tm_hour, ti.tm_min, ti.tm_sec);
- // 分隔线
- u8g2.drawLine(0, 16, 128, 16);
- } while(u8g2.nextPage());
- }
- vTaskDelayUntil(&nextWake, interval);
- }
- }
-
- // —— DHT22 任务 ——
- void dhtTask(void* pv) {
- const TickType_t interval = pdMS_TO_TICKS(2000);
- TickType_t nextWake = xTaskGetTickCount();
-
- while (true) {
- vTaskDelayUntil(&nextWake, interval);
- float t = dht22.getTemperature();
- float h = dht22.getHumidity();
- if (fabs(t) <= 100.0) gConfig.sensor.temperature = t;
- if (fabs(h) <= 100.0) gConfig.sensor.humidity = h;
- }
- }
-
- // —— Wi-Fi & NTP 任务 ——
- void wifiTask(void* pv) {
- const TickType_t retry = pdMS_TO_TICKS(5000);
- bool ntpDone = false;
-
- connectWiFi(gConfig.wifi.ssid, gConfig.wifi.password);
-
- while (true) {
- if (WiFi.status() != WL_CONNECTED) {
- Serial.println("→ Wi-Fi lost, retry...");
- connectWiFi(gConfig.wifi.ssid, gConfig.wifi.password);
- vTaskDelay(retry);
- } else {
- if (!ntpDone) {
- configTime(8*3600, 0, "pool.ntp.org", "ntp.aliyun.com");
- Serial.println("→ NTP sync");
- ntpDone = true;
- }
- vTaskDelay(pdMS_TO_TICKS(10000));
- }
- }
- }
-
- // —— MQTT 任务 ——
- void mqttTask(void* pv) {
- mqtt.setServer(MQTT_SERVER, MQTT_PORT);
- mqtt.setCallback(mqttCallback);
-
- while (true) {
- if (WiFi.status() == WL_CONNECTED) {
- ensureMqttConnected();
- unsigned long now = millis();
- if (now - lastPublish > gConfig.uploadInterval) {
- lastPublish = now;
- gConfig.wifi.rssi = WiFi.RSSI();
-
- // 构造 JSON 上传包
- StaticJsonDocument<256> doc;
- doc["temp"] = gConfig.sensor.temperature;
- doc["hum"] = gConfig.sensor.humidity;
- doc["rssi"] = gConfig.wifi.rssi;
- String payload;
- serializeJson(doc, payload);
-
- mqtt.publish("devices/status", payload.c_str());
- Serial.printf("→ Pub JSON: %s\n", payload.c_str());
- }
- }
- vTaskDelay(pdMS_TO_TICKS(100));
- }
- }
-
- // —— setup ——
- void setup() {
- Serial.begin(115200);
- Wire.begin(SDA_PIN, SCL_PIN);
- prefs.begin("wifi_cfg", false);
-
- gConfig.sensor.temperature = 0;
- gConfig.sensor.humidity = 0;
- gConfig.uploadInterval = UPLOAD_INTERVAL_MS;
- gConfig.wifi.ssid = prefs.getString("wifi_ssid", WIFI_SSID);
- gConfig.wifi.password = prefs.getString("wifi_pass", WIFI_PASSWORD);
-
- strip.begin();
- strip.setBrightness(30);
- u8g2.begin();
-
- ledMode = prefs.getInt("ledMode", 0);
- Serial.printf("→ Start: LED mode %d\n", ledMode);
-
- pinMode(BUTTONPIN, INPUT_PULLUP);
- attachInterrupt(BUTTONPIN, buttonISR, FALLING);
-
- xTaskCreate(ledTask, "LED", 2048, NULL, 1, &ledTaskHandle);
- xTaskCreate(oledTask, "OLED", 2048, NULL, 1, &oledTaskHandle);
- xTaskCreate(dhtTask, "DHT22", 2048, NULL, 1, &dhtTaskHandle);
- xTaskCreate(wifiTask, "WiFi", 4096, NULL, 1, &wifiTaskHandle);
- xTaskCreate(mqttTask, "MQTT", 4096, NULL, 1, &mqttTaskHandle);
- }
-
- // —— loop ——
- void loop() {
- delay(1000);
- }
复制代码
视频:https://b23.tv/Mkv6fhc
|