2025年7月8日 星期二

ESP32 雙核心控制 LED 與 DHT22 溫濕度感測器 (Wokwi 模擬) EX4 -- Telegram

ESP32 雙核心控制 LED 與 DHT22 溫濕度感測器 (Wokwi 模擬) EX4 --  Telegram


這是一個結合 Wokwi、ESP32、DHT22 與 Telegram 的雙核心應用,包含以下功能:

功能總覽

  1. LED 控制模式(4 種)

    • on: 常亮

    • off: 關閉

    • flash: 閃爍

    • timer: 亮 10 秒後自動關閉

  2. 讀取 DHT22 溫濕度資料

  3. 透過 Telegram Bot 控制 LED 與查詢溫濕度

  4. 使用 ESP32 雙核心

    • Core 0:負責 DHT22 感測與資料上傳

    • Core 1:負責 Telegram 接收與 LED 控制


🧠 EEPROM 儲存策略

  • 使用 EEPROM 儲存 LED 模式字串,例如:onoffflashtimer

  • 開機讀取 EEPROM 恢復 LED 模式

硬體模擬設定(Wokwi)

ini
{ "version": 1, "devices": [ { "type": "esp32", "id": "esp", "top": 100, "left": 100 }, { "type": "dht22", "id": "dht", "pin": 15, "top": 40, "left": 100 }, { "type": "led", "id": "led", "pin": 2, "top": 160, "left": 180 } ] }

Arduino 程式碼(雙核心設計)

請先在 Arduino IDE 安裝:

  • DHT sensor library

  • Adafruit Unified Sensor

  • UniversalTelegramBot

  • WiFi

cpp

#include <WiFi.h>
#include <DHT.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
// #include <EEPROM.h> // 移除 EEPROM 庫

// --- WiFi 設定 ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";

// --- Telegram 設定 ---
// 請確保這是你的 Telegram Bot Token 和 Chat ID
// CHAT_ID 是你與 Bot 對話的 ID,可以透過 /getid 指令獲取
#define BOT_TOKEN "717535894054:A1AHbr5W9ovb1BKPQy5WsbSjNxfCGC6EWU-o" // <<< 你的 Telegram Bot Token
#define CHAT_ID "76218469" // <<< 你的 Telegram Chat ID

// --- DHT 設定 ---
#define DHTPIN 4    // 使用 GPIO4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

// --- LED 設定 ---
const int LED_PIN = 2;
String ledMode = "off"; // 初始 LED 模式,不再從 EEPROM 讀取

// --- 計時變數 ---
unsigned long ledTimerStart = 0; // LED 定時模式開始時間
bool timerRunning = false;       // 定時模式是否正在運行
unsigned long lastDHTSend = 0;   // 上次自動發送 DHT 數據的時間
unsigned long lastTelegramPoll = 0; // 上次輪詢 Telegram 訊息的時間
const long TELEGRAM_POLL_INTERVAL = 5000; // Telegram 輪詢間隔 (毫秒),建議至少 5 秒

// --- Telegram 客戶端物件 ---
WiFiClientSecure client;
UniversalTelegramBot bot(BOT_TOKEN, client);

// --- 任務句柄 ---
TaskHandle_t Core0Task;
TaskHandle_t Core1Task;

// --- 函式宣告 ---
void setup_wifi(); // 由於 Wokwi 環境通常會自動連接,這個函式可以簡化或移除
void setLEDMode(String mode);
void handleTelegramMessages();
void TaskCore0(void *parameter); // Core 0 任務 (DHT 讀取與自動發送)
void TaskCore1(void *parameter); // Core 1 任務 (LED 控制)

// --- LED 模式設定函式 ---
// 這個函式負責設定 LED 模式並處理定時器的啟動
void setLEDMode(String mode) {
  if (ledMode != mode) { // 只有當模式改變時才執行
    ledMode = mode;
    Serial.println("LED 模式已變更為: " + ledMode);

    if (mode == "timer") {
      ledTimerStart = millis(); // 僅在進入 timer 模式時設定計時器開始時間
      timerRunning = true;
      digitalWrite(LED_PIN, HIGH); // 進入 timer 模式時先開啟 LED
    } else {
      timerRunning = false; // 非 timer 模式時,重置 timerRunning
    }
  }
}

// --- Telegram 訊息處理函式 ---
void handleTelegramMessages() {
  // 檢查是否有新的 Telegram 訊息
  int numNewMessages = bot.getUpdates(bot.last_message_received + 1);

  if (numNewMessages) { // 處理所有新訊息
    Serial.printf("[Telegram] 收到 %d 條新訊息。\n", numNewMessages);
  }

  for (int i = 0; i < numNewMessages; i++) {
    String text = bot.messages[i].text;
    String chat_id = String(bot.messages[i].chat_id); // 獲取發送訊息的 Chat ID

    Serial.printf("[Telegram] 訊息來自 Chat ID: %s, 內容: %s\n", chat_id.c_str(), text.c_str());

    // 檢查 Chat ID 是否符合預設的 CHAT_ID,防止未授權控制
    if (chat_id != CHAT_ID) {
      bot.sendMessage(chat_id, "⚠️ 未授權的 Chat ID。您的 Chat ID 是: " + chat_id);
      Serial.println("[Telegram] 警告:收到來自未授權 Chat ID 的訊息!");
      continue; // 跳過此訊息,不處理
    }

    if (text == "/start") {
      bot.sendMessage(CHAT_ID, "哈囉!我是你的 ESP32 IoT 助理。\n"
                               "你可以使用以下指令:\n"
                               "/on - 開啟 LED\n"
                               "/off - 關閉 LED\n"
                               "/flash - 讓 LED 閃爍\n"
                               "/timer - LED 亮 10 秒後自動關閉\n"
                               "/dht - 查詢溫濕度\n"
                               "/status - 查詢目前 LED 模式\n"
                               "/getid - 獲取你的 Chat ID");
    } else if (text == "/on") {
      setLEDMode("on");
      bot.sendMessage(CHAT_ID, "✅ LED 已開啟");
    } else if (text == "/off") {
      setLEDMode("off");
      bot.sendMessage(CHAT_ID, "❎ LED 已關閉");
    } else if (text == "/flash") {
      setLEDMode("flash");
      bot.sendMessage(CHAT_ID, "⚡ LED 閃爍中");
    } else if (text == "/timer") {
      setLEDMode("timer"); // setLEDMode 會負責啟動計時器和開啟 LED
      bot.sendMessage(CHAT_ID, "🕒 LED 將亮 10 秒");
    } else if (text == "/dht") {
      float temp = dht.readTemperature();
      float hum = dht.readHumidity();
      String msg;
      if (isnan(temp) || isnan(hum)) {
        msg = "⚠️ 無法讀取 DHT 感測器數據。";
        Serial.println("[DHT] 無法讀取感測器數據!");
      } else {
        msg = "🌡 溫度: " + String(temp, 1) + "°C\n💧 濕度: " + String(hum, 1) + "%";
        Serial.printf("[DHT] 溫度: %.1f°C, 濕度: %.1f%%\n", temp, hum);
      }
      bot.sendMessage(CHAT_ID, msg);
    } else if (text == "/status") {
      bot.sendMessage(CHAT_ID, "📟 目前 LED 模式為: " + ledMode);
    } else if (text == "/getid") {
      bot.sendMessage(chat_id, "你的 Chat ID 是: " + chat_id + "\n請將此 ID 填入程式碼的 CHAT_ID 中。");
    } else {
      bot.sendMessage(CHAT_ID, "⚠️ 無效指令,請輸入 /on /off /flash /timer /dht /status /getid");
    }
  }
}

// --- Core 0 任務:DHT 讀取與自動發送 ---
void TaskCore0(void *parameter) {
  for (;;) { // 無限循環
    float temp = dht.readTemperature();
    float hum = dht.readHumidity();

    if (isnan(temp) || isnan(hum)) {
      // 錯誤已在 handleTelegramMessages 或定時發送時處理,這裡只做日誌
      Serial.println("[DHT] 無法讀取感測器數據 (任務)。");
    } else {
      // 在 handleTelegramMessages 中已打印日誌,這裡可以選擇是否重複打印
      // Serial.printf("[DHT] 溫度: %.1f°C, 濕度: %.1f%%\n", temp, hum);

      // 每 60 秒自動發送一次 DHT 數據到 Telegram
      if (millis() - lastDHTSend > 60000) {
        String msg = "📡 定時回報:\n🌡 溫度: " + String(temp, 1) + "°C\n💧 濕度: " + String(hum, 1) + "%";
        bot.sendMessage(CHAT_ID, msg); // 這是一個阻塞操作
        lastDHTSend = millis();
        Serial.println("[Telegram] 已發送定時 DHT 回報。");
      }
    }
    vTaskDelay(pdMS_TO_TICKS(3000)); // 每 3 秒讀取一次 DHT
  }
}

// --- Core 1 任務:LED 控制 ---
void TaskCore1(void *parameter) {
  for (;;) { // 無限循環
    if (ledMode == "on") {
      digitalWrite(LED_PIN, HIGH);
    } else if (ledMode == "off") {
      digitalWrite(LED_PIN, LOW);
    } else if (ledMode == "flash") {
      digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // 反轉 LED 狀態
      vTaskDelay(pdMS_TO_TICKS(500)); // 閃爍間隔
      continue; // 繼續下一次循環,不執行下面的通用延遲
    } else if (ledMode == "timer") {
      digitalWrite(LED_PIN, HIGH); // 確保 LED 在計時期間保持開啟
      if (timerRunning && millis() - ledTimerStart >= 10000) {
        setLEDMode("off"); // 自動關閉 LED 並將模式設為 "off"
        digitalWrite(LED_PIN, LOW);
        bot.sendMessage(CHAT_ID, "⏱ 10 秒已到,LED 關閉"); // 發送通知
        Serial.println("[LED] 定時結束。");
      }
    }
    vTaskDelay(pdMS_TO_TICKS(100)); // 短暫延遲,讓出 CPU
  }
}

// --- 主設定函式 ---
void setup() {
  Serial.begin(115200);
  dht.begin();
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW); // 確保 LED 初始關閉

  Serial.println("--- ESP32 啟動中 ---");
  Serial.println("連接 WiFi...");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500); Serial.print(".");
  }
  Serial.println("\n✅ WiFi 已連線!");
  Serial.print("IP 位址: ");
  Serial.println(WiFi.localIP());

  // 設置 Telegram 客戶端為不安全模式 (僅限測試,正式環境請載入憑證)
  client.setInsecure();
  Serial.println("--- 安全警告 ---");
  Serial.println("已啟用不安全的 SSL 連線 (client.setInsecure())。");
  Serial.println("在生產環境中,請務必載入根憑證以確保安全性。");
  Serial.println("--- --- ---");

  // EEPROM 相關程式碼已移除,ledMode 預設為 "off"
  // Serial.println("恢復 LED 模式: " + ledMode); // 這裡會打印 "off"

  // 啟動雙核心任務
  // TaskCore0: DHT 讀取與自動發送 (Core 0),堆疊增加至 6144
  xTaskCreatePinnedToCore(TaskCore0, "Core0Task", 6144, NULL, 1, &Core0Task, 0);
  // TaskCore1: LED 控制 (Core 1),堆疊增加至 8192 (與之前相同,但確保夠用)
  xTaskCreatePinnedToCore(TaskCore1, "Core1Task", 8192, NULL, 1, &Core1Task, 1);

  // 初始發送 Bot 啟動訊息
  bot.sendMessage(CHAT_ID, "🚀 ESP32 已啟動並連線!");
  Serial.println("[Telegram] 已發送 Bot 啟動訊息。");
  Serial.println("--- 設定完成 ---");
}

void loop() {
  // 主程式只負責 Telegram 訊息的輪詢 (getUpdates)
  if (millis() - lastTelegramPoll > TELEGRAM_POLL_INTERVAL) {
    handleTelegramMessages();
    lastTelegramPoll = millis();
  }
  vTaskDelay(pdMS_TO_TICKS(10)); // 短暫延遲,讓出 CPU
}


操作步驟

  1. 取得你的 Telegram Bot Token 和 Chat ID

    • 在 Telegram 中搜尋 BotFather,按照指示建立你的 Bot 並取得 HTTP API Token

    • 在 Telegram 中,找到你的 Bot 並開始對話。然後傳送 /getid 指令給它。Bot 會回覆你的 Chat ID。記下這兩個值,並替換程式碼中的 BOT_TOKENCHAT_ID

  2. Wokwi 設定

    • 前往 Wokwi.com,建立一個新的 ESP32 Dev Kit C 專案。

    • 將上面修正後的 ESP32 Arduino 程式碼貼到 sketch.ino

    • 確認 diagram.json 內容與上方範例一致。

    • 點擊 Wokwi 上的「Run」按鈕。

    • 打開 Wokwi 的 Serial Monitor。你應該會看到 Wi-Fi 連線、SSL 警告(因為 setInsecure()),以及 DHT22 讀取日誌。

  3. 透過 Telegram 應用程式控制

    • 在你的 Telegram App 中,搜尋你 Bot 的使用者名稱,開始對話。

    • 當 ESP32 成功啟動並連線後,你的 Bot 會自動發送一條「🚀 ESP32 已啟動並連線!」訊息給你。

    • 傳送 /start:Bot 會回覆可用指令列表。

    • 控制 LED:傳送 /on, /off, /flash, /timer。觀察 Wokwi 模擬器中的 LED 變化。

    • 獲取感測器數據:傳送 /dht。Bot 會回覆最新的溫度和濕度。

    • 查詢模式:傳送 /status。Bot 會回覆當前 LED 模式。

    • 獲取 Chat ID:傳送 /getid。Bot 會回覆你的 Chat ID,你可以用它來確認或更新程式碼中的 CHAT_ID


📱 Telegram 指令整理

指令功能說明


哈囉!我是你的 ESP32 IoT 助理。
你可以使用以下指令:
/on - 開啟 LED
/off - 關閉 LED
/flash - 讓 LED 閃爍
/timer - LED 亮 10 秒後自動關閉
/dht - 查詢溫濕度
/status - 查詢目前 LED 模式
/getid - 獲取你的 Chat ID






沒有留言:

張貼留言

ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite

 ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite  ESP32 VS Code 程式 ; PlatformIO Project Configuration File ; ;   Build op...