| 本帖最后由 腿毛利小五郎 于 2025-10-5 10:10 编辑 
 展示:
 
  前言:
 参考社区【指南】使用 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
 
 如果收到了类似如下显示,MQTT环境就好了 ,也可以用其他MQTT工具测试发布订阅,这里不用其他工具测试了复制代码#更新软件包索引
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服务的端口建立
 
  
 
 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);
}
 
 
 
 |