ESP32 整合 MQTT 與 Telegram 控制 LED 及溫濕度顯示
這是一個整合了 MQTT 訂閱控制 LED、DHT22 溫濕度數據發布到 MQTT,
https://alex9ufoexploer.blogspot.com/2025/06/mqtt-mqttx-clinet.html
並且新增 Telegram 接收 LED 控制指令和發送溫濕度數據到 Telegram 的 ESP32 Arduino 程式碼。
由於 Telegram bot 的實作需要額外的函式庫和設定,這裡將使用 UniversalTelegramBot 函式庫,並透過 ESP32 的 多工處理 (FreeRTOS) 來確保 MQTT 和 Telegram 任務能同時且順暢地運行。
ESP32 整合 MQTT 與 Telegram 控制 LED 及溫濕度顯示
這個程式碼將包含兩個主要的任務:
- Core 0 任務 (DHT22 & MQTT 發布):負責讀取 DHT22 感測器數據並發布到 MQTT Broker。
- Core 1 任務 (MQTT 訂閱 & Telegram 互動):負責處理 MQTT 訂閱接收 LED 控制指令,以及與 Telegram Bot 進行互動(接收控制指令和發送溫濕度)。
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <UniversalTelegramBot.h> // Telegram 函式庫
#include <WiFiClientSecure.h> // 支援 HTTPS 用於 Telegram
#include <ArduinoJson.h>
// WiFi 設定
const char* ssid = "Wokwi-GUEST"; // "你的WiFi名稱" <<--- 請修改成你的 WiFi 名稱
const char* password = "" ; // "你的WiFi密碼" <<--- 請修改成你的 WiFi 密碼
// --- MQTT 設定 ---
const char* mqtt_broker = "broker.mqttgo.io";
const int mqtt_port = 1883;
const char* mqtt_client_id = "ESP32_DHT22_LED_Telegram_Client"; // 請為你的裝置設定一個獨特的 ID
// 發布主題
const char* mqtt_publish_topic_temphumi = "alex9ufo/esp32/dht/temphumi";
// 訂閱主題 (LED 控制)
const char* mqtt_subscribe_topic_led_control = "alex9ufo/esp32/led/control";
// 回覆主題 (LED 狀態)
const char* mqtt_publish_topic_led_status = "alex9ufo/esp32/led/status";
// --- Telegram 設定 ---
#define BOT_TOKEN "72713891420254:AAHbrWu9ovb1BKPQyWsbNSjNxfCGCrEWU-o" // <<--- 請修改成你的 Bot Token
// 從 BotFather 取得,格式為 "YOUR_BOT_TOKEN"
// 找到你個人或群組的聊天 ID,可以用 @userinfobot 或 @get_id_bot 取得
// 為了安全,建議將此 ID 寫死,或透過 Bot 發送指令後從 Serial 監控器中取得。
#define CHAT_ID "7926542181469" // <<--- 請修改成你的 Telegram 聊天 ID (通常是負數代表群組ID)
WiFiClientSecure client_telegram;
UniversalTelegramBot bot(BOT_TOKEN, client_telegram);
unsigned long lastTimeBotChecked; // 上次檢查 Telegram 訊息的時間
const unsigned long BOT_MTBS = 1000; // 檢查 Telegram 訊息的間隔 (毫秒)
// --- DHT22 設定 ---
#define DHTPIN 4 // DHT22 連接到 ESP32 的 GPIO Pin 4
#define DHTTYPE DHT22 // DHT 22 (AM2302)
DHT dht(DHTPIN, DHTTYPE);
// --- LED 設定 ---
const int ledPins[] = {16, 17, 18, 19}; // 假設 LED 連接到 GPIO 16, 17, 18, 19
const int numLeds = sizeof(ledPins) / sizeof(ledPins[0]);
bool ledStatus[numLeds]; // 儲存 LED 的狀態 (on/off)
WiFiClient espClient_mqtt;
PubSubClient client_mqtt(espClient_mqtt);
// --- 全局變數用於跨任務存取 DHT 數據 ---
float currentTemperature = 0.0;
float currentHumidity = 0.0;
SemaphoreHandle_t dhtMutex; // 互斥鎖,用於保護 DHT 數據的讀寫
float lastTemp = NAN;
float lastHum = NAN;
//=================================================================
// --- Function Prototypes ---
void setup_wifi();
void reconnect_mqtt();
void mqtt_callback(char* topic, byte* payload, unsigned int length);
void handleNewMessages(int numNewMessages);
void sendLedStatus(int ledNum, bool status);
void parseAndControlLED(String message); // 新增解析 LED 控制指令的函式
// --- FreeRTOS 任務函式 ---
void dht_mqtt_task(void * parameter);
void telegram_led_task(void * parameter);
//=================================================================
void setup() {
Serial.begin(115200);
dht.begin();
// 初始化 LED 腳位為輸出模式,並關閉所有 LED
for (int i = 0; i < numLeds; i++) {
pinMode(ledPins[i], OUTPUT);
digitalWrite(ledPins[i], LOW);
ledStatus[i] = false; // 初始狀態為關閉
}
setup_wifi();
// 配置 Telegram HTTPS Client
// 保持這行,因為它通常能幫助解決 SSL 相關的連線問題,特別是在時間同步不佳時。
client_telegram.setInsecure();
client_mqtt.setServer(mqtt_broker, mqtt_port);
client_mqtt.setCallback(mqtt_callback);
// 創建互斥鎖
dhtMutex = xSemaphoreCreateMutex();
// 創建 FreeRTOS 任務
xTaskCreatePinnedToCore(
dht_mqtt_task, // 任務函式
"DHTMQTTTask", // 任務名稱
4096, // 堆疊大小 (位元組)
NULL, // 傳遞給任務的參數
1, // 任務優先級 (0 最低,configMAX_PRIORITIES-1 最高)
NULL, // 任務句柄
0 // 運行在 Core 0
);
xTaskCreatePinnedToCore(
telegram_led_task, // 任務函式
"TelegramLEDTask", // 任務名稱
8192, // 堆疊大小 (Telegram 需要更多)
NULL, // 傳遞給任務的參數
1, // 任務優先級
NULL, // 任務句柄
1 // 運行在 Core 1
);
Serial.println("ESP32 setup complete. Tasks created.");
}
//=================================================================
void loop() {
// loop() 保持空閒,所有邏輯都在 FreeRTOS 任務中運行
vTaskDelete(NULL); // 刪除 loop() 任務,讓兩個核心專注於 FreeRTOS 任務
}
//=================================================================
// --- WiFi 連線函式 ---
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
//=================================================================
// --- MQTT 相關函式 ---
void reconnect_mqtt() {
while (!client_mqtt.connected()) {
Serial.print("Attempting MQTT connection...");
if (client_mqtt.connect(mqtt_client_id)) {
Serial.println("connected");
// 訂閱 LED 控制主題
client_mqtt.subscribe(mqtt_subscribe_topic_led_control);
Serial.print("Subscribed to MQTT topic: ");
Serial.println(mqtt_subscribe_topic_led_control);
} else {
Serial.print("failed, rc=");
Serial.print(client_mqtt.state());
Serial.println(" try again in 5 seconds");
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
}
//=================================================================
void mqtt_callback(char* topic, byte* payload, unsigned int length) {
Serial.print("MQTT Message arrived [");
Serial.print(topic);
Serial.print("] ");
String message = "";
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.println(message);
// 檢查是否是 LED 控制主題
if (String(topic) == mqtt_subscribe_topic_led_control) {
parseAndControlLED(message);
}
}
//=================================================================
// --- LED 狀態發送函式 ---
void sendLedStatus(int ledNum, bool status) {
String statusPayload = "";
for (int i = 0; i < numLeds; i++) {
if (i > 0) {
statusPayload += ",";
}
statusPayload += String(i + 1);
statusPayload += (ledStatus[i] ? "on" : "off");
}
if (client_mqtt.connected()) {
client_mqtt.publish(mqtt_publish_topic_led_status, statusPayload.c_str());
Serial.print("Published MQTT LED status: ");
Serial.println(statusPayload);
}
}
//=================================================================
// --- LED 控制指令解析函式 (通用於 MQTT 和 Telegram) ---
void parseAndControlLED(String message) {
int ledIndex = -1; // LED 陣列的索引 (0-3)
bool turnOn = false;
// 將收到的訊息與預期的 payload 進行比較
if ((message == "1on") || (message == "/1on")) {
ledIndex = 0;
turnOn = true;
} else if ((message == "1off") || (message == "/1off")) {
ledIndex = 0;
turnOn = false;
} else if ((message == "2on") || (message == "/2on")) {
ledIndex = 1;
turnOn = true;
} else if ((message == "2off") || (message == "/2off")) {
ledIndex = 1;
turnOn = false;
} else if ((message == "3on") || (message =="/3on")) {
ledIndex = 2;
turnOn = true;
} else if ((message == "3off") || (message =="/3off")) {
ledIndex = 2;
turnOn = false;
} else if ((message == "4on") || (message =="/4on")) {
ledIndex = 3;
turnOn = true;
} else if ((message == "4off") || (message =="/4off")) {
ledIndex = 3;
turnOn = false;
}
if (ledIndex != -1) {
if (turnOn) {
digitalWrite(ledPins[ledIndex], HIGH);
ledStatus[ledIndex] = true;
Serial.print("LED ");
Serial.print(ledIndex + 1); // 顯示第幾個 LED (1-4)
Serial.println(" ON");
} else {
digitalWrite(ledPins[ledIndex], LOW);
ledStatus[ledIndex] = false;
Serial.print("LED ");
Serial.print(ledIndex + 1); // 顯示第幾個 LED (1-4)
Serial.println(" OFF");
}
// 回覆 LED 狀態 (透過 MQTT)
sendLedStatus(ledIndex + 1, ledStatus[ledIndex]);
} else {
Serial.println("Invalid LED control payload.");
}
}
//=================================================================
// --- Telegram 訊息處理函式 ---
void handleNewMessages(int numNewMessages) {
Serial.print("Handling ");
Serial.print(numNewMessages);
Serial.println(" new messages");
for (int i = 0; i < numNewMessages; i++) {
String chat_id = String(bot.messages[i].chat_id);
String text = bot.messages[i].text;
Serial.print("Received Telegram message from ID ");
Serial.print(chat_id);
Serial.print(": ");
Serial.println(text);
// 檢查訊息是否來自允許的 CHAT_ID
if (chat_id == CHAT_ID) {
if (text == "/start") {
String welcome = "歡迎使用 ESP32 智慧控制!\n";
welcome += "你可以使用以下指令控制 LED:\n";
welcome += " /1on, /1off\n";
welcome += " /2on, /2off\n";
welcome += " /3on, /3off\n";
welcome += " /4on, /4off\n";
welcome += "查詢溫濕度:\n";
welcome += " /temphumi\n";
bot.sendMessage(CHAT_ID, welcome);
} else if (text == "/temphumi") {
String tempHumiMessage = "";
// 獲取受保護的 DHT 數據
if (xSemaphoreTake(dhtMutex, pdMS_TO_TICKS(100)) == pdTRUE) { // 嘗試獲取互斥鎖,避免長時間阻塞
tempHumiMessage = "當前溫度: " + String(currentTemperature, 1) + "°C\n";
tempHumiMessage += "當前濕度: " + String(currentHumidity, 1) + "%";
xSemaphoreGive(dhtMutex);
} else {
tempHumiMessage = "無法讀取溫濕度數據,請稍後再試。";
}
bot.sendMessage(CHAT_ID, tempHumiMessage);
} else {
// 嘗試解析 LED 控制指令
parseAndControlLED(text);
// 如果是 LED 控制指令,回覆狀態
if (text.endsWith("on") || text.endsWith("off")) {
String currentLedStates = "LED 狀態更新:\n";
for(int j=0; j<numLeds; j++){
currentLedStates += String(j+1);
currentLedStates += (ledStatus[j] ? "on" : "off");
if(j < numLeds -1){
currentLedStates += ", ";
}
}
bot.sendMessage(CHAT_ID, currentLedStates);
} else {
bot.sendMessage(CHAT_ID, "無法識別的指令:" + text);
}
}
} else {
bot.sendMessage(chat_id, "抱歉,您無權控制此裝置。");
Serial.print("Unauthorized access from chat ID: ");
Serial.println(chat_id);
}
}
}
//=================================================================
// --- FreeRTOS 任務實作 ---
// DHT22 讀取與 MQTT 發布任務 (運行在 Core 0)
void dht_mqtt_task(void * parameter) {
while (true) {
if (WiFi.status() == WL_CONNECTED) {
if (!client_mqtt.connected()) {
reconnect_mqtt();
}
client_mqtt.loop(); // 保持 MQTT 連線活躍
// 讀取濕度
float h = dht.readHumidity();
// 讀取溫度 (攝氏)
float t = dht.readTemperature();
// 檢查讀取是否失敗,如果是則跳過
if (isnan(h) || isnan(t)) {
Serial.println("Failed to read from DHT sensor!");
} else {
// 保護共享變數
if (xSemaphoreTake(dhtMutex, pdMS_TO_TICKS(100)) == pdTRUE) { // 嘗試獲取互斥鎖,避免長時間阻塞
currentTemperature = t;
currentHumidity = h;
xSemaphoreGive(dhtMutex); // 釋放互斥鎖
}
if (t != lastTemp || h != lastHum) {
lastTemp = t;
lastHum = h;
// 格式化溫濕度字串
String temperatureString = String(t, 1); // 顯示一位小數
String humidityString = String(h, 1); // 顯示一位小數
String mqttPayload = "{\"temperature\": " + temperatureString + ", \"humidity\": " + humidityString + "}";
// 發布到 MQTT 主題
if (client_mqtt.connected()) {
client_mqtt.publish(mqtt_publish_topic_temphumi, mqttPayload.c_str());
Serial.print("Published MQTT temphumi: ");
Serial.println(mqttPayload);
}
}
}
}
vTaskDelay(pdMS_TO_TICKS(5000)); // 每 5 秒讀取並發布一次
}
}
//=================================================================
// Telegram 互動與 LED 控制任務 (運行在 Core 1)
void telegram_led_task(void * parameter) {
while (true) {
if (WiFi.status() == WL_CONNECTED) {
// 檢查 Telegram 訊息
if (millis() > lastTimeBotChecked + BOT_MTBS) {
int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
while(numNewMessages) {
Serial.println("got response");
handleNewMessages(numNewMessages);
numNewMessages = bot.getUpdates(bot.last_message_received + 1);
}
lastTimeBotChecked = millis();
}
}
vTaskDelay(pdMS_TO_TICKS(50)); // 短暫延遲,避免佔用所有 CPU 資源
}
}
//=================================================================





沒有留言:
張貼留言