基于FireBeetle 2 ESP32-C5 智能环境监测灯光控制
本帖最后由 腿毛利小五郎 于 2025-10-5 10:10 编辑展示:
https://www.bilibili.com/video/BV1ZKx4zWEzj/?vd_source=7ae4abbb911ab0730053993f679f945a
前言:
参考社区【指南】使用 MQTT 将 ESP32-C5 连接到 Home Assistant DF创客社区大佬的项目,在树莓派上使用mosquitto跑了MQTT服务
利用手头已有的硬件,简单实现灯带的控制系统,使用Freertos管理任务。
FireBeetle 2 ESP32-C5的资源还有MQTT啥的也不介绍了,社区很多大佬都介绍了很多很多了。
硬件:
FireBeetle 2 ESP32-C5 + WS2812 灯带 + DHT22 温湿度传感器 + OLED 显示屏 + 按键
功能:
[*]MQTT + Wi-Fi 远程配置与控制
[*]实时温湿度采集上传
[*]OLED 实时数据显示(温度、湿度、时间、Wi-Fi 信号、LED 模式)
[*]灯带多种显示模式可本地切换或远程控制
[*]参数持久化(Preferences)
环境搭建:
[*]MQTT(mosquitto)
我在树莓派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;
WiFiClientnetClient;
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;
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 = {0}; rgb = 255;
int cur = flowIndex % strip.getLedCount();
if (prevLed >= 0) strip.setLedColorData(prevLed, 0,0,0);
strip.setLedColorData(cur, rgb,rgb,rgb);
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);
// 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://github.com/tuimaoli/ESP32C5_MQTT_LED/releases/tag/V1.0.0
页:
[1]