2025年7月10日 星期四

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

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






1. Telegram 機器人設定

首先,您需要一個 Telegram 機器人來與您的 ESP32 互動。

  • 建立機器人:在 Telegram 上與 BotFather 對話,並使用 /newbot 命令。按照指示操作,以取得您機器人的 HTTP API Token。請妥善保管此 Token;它就像您機器人的密碼一樣重要。

  • 取得聊天 ID:若要傳送訊息給自己(或特定的聊天室),您需要您的 Chat ID。您可以透過將機器人的訊息轉發給 Telegram 上的 userinfobot 來取得此 ID。


2. Wokwi ESP32 專案設定

Wokwi 是一個線上模擬器,非常適合原型開發。

  • 建立新專案:前往 Wokwi 網站並建立一個新的 ESP32 專案

  • 新增元件

    • ESP32 開發板模組:這是您的微控制器。

    • LED:將一個 LED 連接到 ESP32 的數位腳位(例如 GPIO 2),並串聯一個限流電阻(例如 220 歐姆)。

    • DHT22 感測器:將 DHT22 感測器連接到另一個數位腳位(例如 GPIO 4)。


3. ESP32 程式碼 (Arduino IDE)

您將使用 Arduino IDE 編寫程式碼,然後將其上傳到 Wokwi。

核心函式庫

您需要在 Arduino IDE 中安裝以下函式庫(Sketch > Include Library > Manage Libraries):

  • UniversalTelegramBot:用於與 Telegram Bot API 互動。

  • WiFiManager (可選但推薦):用於輕鬆管理 Wi-Fi 憑證,而無需將其寫死在程式碼中。

  • DHT sensor library:用於讀取 DHT22 的數據。

  • Adafruit Unified Sensor:DHT 函式庫的依賴項。

核心邏輯

以下是程式碼的概念性概述:


#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
#include <DHT.h>
#include <Adafruit_Sensor.h> // Required for DHT library

// --- Telegram Bot Configuration ---
//#define BOT_TOKEN "YOUR_TELEGRAM_BOT_TOKEN" // 從 BotFather 取得你的 Bot Token
//#define CHAT_ID "YOUR_CHAT_ID"             // 你的 Telegram Chat ID

#define BOT_TOKEN "773289402a54:AAHbrWu95ovb1BKPQy1WsbNSjNxfCG4CrEWU-o"
#define CHAT_ID "7965218469"

// --- Wokwi Wi-Fi Configuration ---
// 針對 Wokwi 模擬器,使用 Wokwi-GUEST 網路
const char* ssid = "Wokwi-GUEST";
const char* password = "";

// --- Hardware Pin Definitions ---
const int ledPin = 2; // LED 接到 ESP32 GPIO 2 (請串接電阻)
#define DHTPIN 4      // DHT22 數據腳位接駁到 ESP32 GPIO 4
#define DHTTYPE DHT22 // DHT 感測器類型 (DHT11, DHT21, DHT22)

// --- Global Variables & FreeRTOS Handles ---
// DHT 感測器實例
DHT dht(DHTPIN, DHTTYPE);

// Telegram Bot 實例
WiFiClientSecure client;
UniversalTelegramBot bot(BOT_TOKEN, client);

// 用於追蹤 Telegram 更新的最後 ID
// 這個變數將取代 bot.lastUpdateID,因為您的庫版本中沒有它
long lastProcessedUpdateId = 0;

// LED 狀態變數
bool ledState = false; // true = ON, false = OFF
long timerStartTime = 0;
long timerDuration = 10000; // 10 秒 (10000 毫秒)
bool timerActive = false;

// FreeRTOS 任務句柄
TaskHandle_t telegramTaskHandle = NULL;
TaskHandle_t deviceControlTaskHandle = NULL;

// 訊息佇列 (Queue) 用於在任務間傳遞命令 (Telegram -> DeviceControl)
QueueHandle_t commandQueue;
// 新增:訊息佇列 (Queue) 用於在任務間傳遞 Telegram 回覆 (DeviceControl -> Telegram)
QueueHandle_t responseQueue;

// 定義命令類型 (從 Telegram 傳給設備)
enum CommandType {
  CMD_NONE,
  CMD_LED_ON,
  CMD_LED_OFF,
  CMD_LED_FLASH,
  CMD_LED_TIMER,
  CMD_GET_TEMP,
  CMD_MAX // 用於數組大小或枚舉範圍檢查
};

// 命令結構體
struct Command {
  CommandType type;
  // 未來可以增加更多參數,例如 timerDuration 或 flashCount
};

// 新增:回覆訊息結構體 (從設備傳給 Telegram)
struct BotResponse {
  String message; // 要發送的訊息內容
  // String chatId; // 如果需要回覆給不同的人,可以加上 chatId
};

// --- Function Prototypes ---
void connectToWiFi();
void telegramTask(void *pvParameters);
void deviceControlTask(void *pvParameters);
void handleTelegramMessages(int numNewMessages); // 處理從 Telegram 收到的訊息

void setup() {
  Serial.begin(115200);
  Serial.println("\nStarting ESP32 Dual Core FreeRTOS Telegram Bot...");

  // 設定 LED 腳位為輸出,並確保初始為關閉
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  ledState = LOW; // 更新 LED 狀態變數

  // 初始化 DHT 感測器
  dht.begin();
  Serial.println("DHT sensor initialized.");

  // 連接 Wi-Fi
  connectToWiFi();

  // 設定 Telegram 的根憑證,確保安全連線
  client.setCACert(TELEGRAM_CERTIFICATE_ROOT);
  Serial.println("Telegram CA certificate set.");

  // 創建命令佇列 (Telegram -> DeviceControl)
  commandQueue = xQueueCreate(5, sizeof(Command));
  if (commandQueue == NULL) {
    Serial.println("Error creating command queue. Restarting...");
    ESP.restart(); // 創建失敗則重啟
  }

  // 創建回覆佇列 (DeviceControl -> Telegram)
  // 佇列大小為 5,每個元素是一個 BotResponse 結構體
  // 注意:String 佔用記憶體較多,如果訊息很長,需謹慎考慮佇列大小
  responseQueue = xQueueCreate(5, sizeof(BotResponse));
  if (responseQueue == NULL) {
    Serial.println("Error creating response queue. Restarting...");
    ESP.restart(); // 創建失敗則重啟
  }

  // 創建 Telegram 任務 (在 Core 0 上運行,處理 Wi-Fi 和網路通訊)
  xTaskCreatePinnedToCore(
    telegramTask,         // 任務函數
    "TelegramTask",       // 任務名稱
    8192,                 // 棧大小 (位元組),視需求調整,Telegram 庫較耗記憶體
    NULL,                 // 傳遞給任務的參數
    5,                    // 任務優先級 (1-24,數字越大優先級越高)
    &telegramTaskHandle,  // 任務句柄
    0                     // 綁定到 Core 0
  );
  Serial.println("Telegram Task created on Core 0.");

  // 創建設備控制任務 (在 Core 1 上運行,處理 LED 和 DHT 感測器)
  xTaskCreatePinnedToCore(
    deviceControlTask,    // 任務函數
    "DeviceControlTask",  // 任務名稱
    4096,                 // 棧大小 (位元組),一般應用較小
    NULL,                 // 傳遞給任務的參數
    4,                    // 任務優先級 (略低於 Telegram 任務)
    &deviceControlTaskHandle, // 任務句柄
    1                     // 綁定到 Core 1
  );
  Serial.println("Device Control Task created on Core 1.");
}

// 主循環 (loop),在 FreeRTOS 環境下,通常不會在 loop() 中放置太多邏輯
// 它會不斷地執行任務排程器
void loop() {
  vTaskDelay(pdMS_TO_TICKS(100)); // 簡單的延遲,讓排程器有機會切換任務
}

// --- Function Definitions ---

void connectToWiFi() {
  Serial.print("Connecting to Wi-Fi: ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }

  Serial.println("\nWiFi Connected!");
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());
}

// Telegram 任務 - 運行在 Core 0
void telegramTask(void *pvParameters) {
  (void) pvParameters; // 避免編譯器警告

  unsigned long botLastUpdateTime = 0;
  const int botRequestDelay = 1000; // 1 秒檢查一次新訊息
  BotResponse resp; // 用於接收回覆訊息

  for (;;) { // FreeRTOS 任務通常是無限循環
    // 檢查是否有新的 Telegram 訊息
    if (millis() > botLastUpdateTime + botRequestDelay) {
      int numNewMessages = bot.getUpdates(lastProcessedUpdateId + 1);

      while (numNewMessages) {
        handleTelegramMessages(numNewMessages); // 處理收到的訊息

        // 更新 lastProcessedUpdateId 到最後一條訊息的 ID
        if (numNewMessages > 0) {
            lastProcessedUpdateId = bot.messages[numNewMessages - 1].update_id;
        }
        numNewMessages = bot.getUpdates(lastProcessedUpdateId + 1);
      }
      botLastUpdateTime = millis();
    }

    // 檢查是否有來自 DeviceControlTask 的回覆訊息需要發送
    // 等待 0 毫秒,表示非阻塞式檢查,如果沒有訊息立即返回
    if (xQueueReceive(responseQueue, &resp, 0) == pdPASS) {
        Serial.print("Telegram Task: Sending response: ");
        Serial.println(resp.message);
        if (WiFi.status() == WL_CONNECTED) {
            bot.sendMessage(CHAT_ID, resp.message);
        } else {
            Serial.println("Telegram Task: WiFi not connected, cannot send response.");
        }
    }
   
    vTaskDelay(pdMS_TO_TICKS(50)); // 短暫延遲,讓出 CPU 時間給其他任務
  }
}

// 處理 Telegram 訊息的函數
void handleTelegramMessages(int numNewMessages) {
  Serial.print("Telegram Task: Received ");
  Serial.print(numNewMessages);
  Serial.println(" new message(s).");

  for (int i = 0; i < numNewMessages; i++) {
    String chat_id = String(bot.messages[i].chat_id);
    String text = bot.messages[i].text;

    Serial.print("Telegram Task: Message from Chat ID: ");
    Serial.print(chat_id);
    Serial.print(", Text: ");
    Serial.println(text);

    // --- 安全檢查: 只回應來自你的 CHAT_ID 的訊息 ---
    if (chat_id != CHAT_ID) {
      bot.sendMessage(chat_id, "Unauthorized user.", "");
      Serial.println("Telegram Task: Unauthorized message ignored.");
      continue;
    }

    // --- 命令處理 ---
    Command cmd; // 創建一個命令結構體
    cmd.type = CMD_NONE; // 預設為無命令

    if (text == "/start") {
      String welcome = "Hello! I'm your ESP32 Telegram Bot.\n";
      welcome += "Control me with these commands:\n";
      welcome += "/on - Turn LED ON\n";
      welcome += "/off - Turn LED OFF\n";
      welcome += "/flash - Flash LED 3 times\n";
      welcome += "/timer - Activate 10-second LED timer\n";
      welcome += "/temp - Get current temperature and humidity\n";
      bot.sendMessage(chat_id, welcome);
      Serial.println("Telegram Task: '/start' command executed.");

    } else if (text == "/on") {
      cmd.type = CMD_LED_ON;
      // bot.sendMessage(chat_id, "LED is now ON (command sent to Core 1)."); // 由設備任務回應
      Serial.println("Telegram Task: Sending CMD_LED_ON.");

    } else if (text == "/off") {
      cmd.type = CMD_LED_OFF;
      // bot.sendMessage(chat_id, "LED is now OFF (command sent to Core 1)."); // 由設備任務回應
      Serial.println("Telegram Task: Sending CMD_LED_OFF.");

    } else if (text == "/flash") {
      cmd.type = CMD_LED_FLASH;
      // bot.sendMessage(chat_id, "Flashing LED (command sent to Core 1)."); // 由設備任務回應
      Serial.println("Telegram Task: Sending CMD_LED_FLASH.");

    } else if (text == "/timer") {
      cmd.type = CMD_LED_TIMER;
      // bot.sendMessage(chat_id, "LED timer started (command sent to Core 1)."); // 由設備任務回應
      Serial.println("Telegram Task: Sending CMD_LED_TIMER.");

    } else if (text == "/temp") {
      cmd.type = CMD_GET_TEMP;
      // bot.sendMessage(chat_id, "Getting temperature and humidity (request sent to Core 1)."); // 由設備任務回應
      Serial.println("Telegram Task: Sending CMD_GET_TEMP.");

    } else {
      bot.sendMessage(chat_id, "Sorry, I don't understand that command. Try /start for help.");
      Serial.println("Telegram Task: Unknown command received.");
    }

    // 如果是有效的控制命令,則發送到設備控制任務的佇列
    if (cmd.type != CMD_NONE) {
      // 嘗試將命令發送到佇列,等待最多 100ms
      if (xQueueSend(commandQueue, &cmd, pdMS_TO_TICKS(100)) != pdPASS) {
        bot.sendMessage(chat_id, "Error: Could not send command to device. Queue full?", "");
        Serial.println("Telegram Task: Failed to send command to queue.");
      } else {
        // 命令成功發送後,可以立即給用戶一個確認回覆
        // 這裡可以選擇是否回覆,如果設備任務會發送最終狀態,這裡可以簡化
        if (text != "/start") { // /start 命令已經有回覆了
             bot.sendMessage(chat_id, "Command received, processing...");
        }
      }
    }
  }
}

// 設備控制任務 - 運行在 Core 1
void deviceControlTask(void *pvParameters) {
  (void) pvParameters; // 避免編譯器警告
  Command receivedCommand;
  BotResponse resp; // 用於發送回覆訊息

  // 將可能引起 "jump to case label" 錯誤的變數聲明移到 switch 外部
  float h, t;

  for (;;) { // FreeRTOS 任務通常是無限循環
    // 等待從命令佇列接收命令,如果沒有命令則等待 100ms
    if (xQueueReceive(commandQueue, &receivedCommand, pdMS_TO_TICKS(100))) {
      Serial.print("Device Control Task: Received command: ");
      // 根據收到的命令類型執行操作
      switch (receivedCommand.type) {
        case CMD_LED_ON:
          Serial.println("CMD_LED_ON");
          digitalWrite(ledPin, HIGH);
          ledState = HIGH;
          timerActive = false; // 停止任何進行中的計時器
          resp.message = "LED is now ON."; // 準備回覆訊息
          xQueueSend(responseQueue, &resp, portMAX_DELAY); // 發送給 Telegram 任務
          break;
        case CMD_LED_OFF:
          Serial.println("CMD_LED_OFF");
          digitalWrite(ledPin, LOW);
          ledState = LOW;
          timerActive = false; // 停止任何進行中的計時器
          resp.message = "LED is now OFF.";
          xQueueSend(responseQueue, &resp, portMAX_DELAY);
          break;
        case CMD_LED_FLASH:
          Serial.println("CMD_LED_FLASH");
          timerActive = false; // 停止任何進行中的計時器
          resp.message = "Flashing LED..."; // 先發送一個提示
          xQueueSend(responseQueue, &resp, portMAX_DELAY);
          for (int j = 0; j < 3; j++) {
            digitalWrite(ledPin, HIGH);
            vTaskDelay(pdMS_TO_TICKS(200)); // 使用 vTaskDelay 代替 delay()
            digitalWrite(ledPin, LOW);
            vTaskDelay(pdMS_TO_TICKS(200));
          }
          ledState = LOW; // 閃爍後回到關閉狀態
          resp.message = "LED flashed 3 times."; // 閃爍完成後的最終回覆
          xQueueSend(responseQueue, &resp, portMAX_DELAY);
          break;
        case CMD_LED_TIMER:
          Serial.println("CMD_LED_TIMER");
          digitalWrite(ledPin, HIGH);
          ledState = HIGH;
          timerStartTime = millis(); // 記錄計時器開始時間
          timerActive = true;        // 激活計時器
          resp.message = "LED timer started! LED will turn off in 10 seconds.";
          xQueueSend(responseQueue, &resp, portMAX_DELAY);
          break;
        case CMD_GET_TEMP:
          Serial.println("CMD_GET_TEMP");
          h = dht.readHumidity();    // 讀取濕度
          t = dht.readTemperature(); // 讀取攝氏溫度

          if (isnan(h) || isnan(t)) { // 檢查讀取是否失敗
            Serial.println("Device Control Task: Error reading from DHT sensor.");
            resp.message = "Error: Could not read data from DHT sensor.";
            xQueueSend(responseQueue, &resp, portMAX_DELAY);
          } else {
            String message = "Temperature: " + String(t, 1) + " °C\n";
            message += "Humidity: " + String(h, 1) + " %";
            Serial.println("Device Control Task: DHT read successful.");
            resp.message = message;
            xQueueSend(responseQueue, &resp, portMAX_DELAY);
          }
          break;
        case CMD_NONE: // 不應該收到此命令,但為了完整性保留
        default:
          Serial.println("Unknown command received in Device Control Task.");
          break;
      }
    }

    // 處理計時器邏輯
    if (timerActive && ledState == HIGH && (millis() - timerStartTime >= timerDuration)) {
      digitalWrite(ledPin, LOW);
      ledState = LOW;
      timerActive = false;
      Serial.println("Device Control Task: LED timer finished, LED OFF.");
      // 計時器結束後,發送訊息回 Telegram
      resp.message = "LED timer finished. LED is OFF.";
      xQueueSend(responseQueue, &resp, portMAX_DELAY);
    }

    vTaskDelay(pdMS_TO_TICKS(10)); // 短暫延遲,讓出 CPU 時間
  }
}


好的,這段程式碼各部分的中文解釋如下:


程式碼各部分說明

  • 引入函式庫 (Includes)

    • 這部分包含了程式執行所需的所有必要函式庫。

  • 定義/常數 (Defines/Constants)

    • 這裡定義了您的機器人 Token、聊天 ID、LED 腳位以及 DHT 感測器的詳細資訊。

    • 請記得將 YOUR_TELEGRAM_BOT_TOKENYOUR_CHAT_ID 這類的佔位符替換成您的實際數值。

  • setup() 函式

    • 初始化序列通訊:啟動與電腦之間的序列埠通訊,用於除錯輸出。

    • 設定 LED 腳位為輸出:將連接 LED 的 GPIO 腳位配置為輸出模式。

    • 啟動 DHT 感測器:初始化 DHT 感測器,使其準備好讀取數據。

    • Wi-Fi 連線:在實際專案中,您會在這裡處理 Wi-Fi 連線邏輯。但在 Wokwi 模擬器中,您需要在 diagram.json 檔案中設定 Wi-Fi 連線資訊。

    • client.setCACert(TELEGRAM_CERTIFICATE_ROOT):這對於建立與 Telegram 的安全 HTTPS 連線至關重要。此憑證通常由 UniversalTelegramBot 函式庫的範例提供。

  • handleNewMessages(int numNewMessages) 函式

    • 當您的機器人收到新訊息時,就會呼叫此函式。

    • 遍歷每個新訊息:它會逐一處理收到的每條新訊息。

    • 聊天 ID 檢查:建議最佳做法是檢查訊息是否來自您的 CHAT_ID,以防止未經授權的控制。

    • 命令解析:程式會檢查收到的文本是否符合 /on/off/flash/timer/temp 等命令。

    • LED 控制:使用 digitalWrite(ledPin, HIGH) 開啟 LED,LOW 關閉 LED。/flash 命令則使用簡單的 delay() 迴圈來實現閃爍。

    • 計時器邏輯:當收到 /timer 命令時,timerActive 變數會設為 truelastTime 會記錄當前的 millis() 時間,並且 LED 會被開啟。

    • DHT22 讀取:對於 /temp 命令,它會使用 dht.readHumidity()dht.readTemperature() 來讀取濕度和溫度數據。此外,也包含了感測器讀取失敗時的錯誤處理。

    • 傳送回覆:使用 bot.sendMessage() 函式將訊息傳送回使用者。

  • loop() 函式

    • 機器人更新bot.getUpdates() 會定期被呼叫,以檢查 Telegram 是否有新訊息。botRequestDelay 用於避免過度請求 Telegram API。

    • 計時器管理:這裡實際實現了 10 秒計時器的邏輯。如果 timerActivetrue 且自 lastTime 以來已經過了 timerDuration 時間,LED 就會被關閉,並發送一條訊息,然後 timerActive 會被重設。

在 Wokwi 上運行的步驟

  1. 打開 Wokwi:前往 Wokwi.com

  2. 啟動新專案:建立一個新的 ESP32 專案

  3. 繪製 電路圖 內容。 

  4. 複製 Arduino 程式碼:將(上面提供的)Arduino 程式碼複製到 Wokwi 的 main.ino 分頁中。

  5. 替換佔位符:在 main.ino 中,用您實際的 Telegram 機器人 Token聊天 ID 替換掉佔位符。

  6. 點擊「開始模擬」:啟動模擬器。

  7. 開啟序列埠監控器:在 Wokwi 中打開「Serial Monitor」,查看 ESP32 的輸出,包括 Wi-Fi 連線狀態。

  8. 發送命令:前往 Telegram,向您的機器人發送命令(/on/off/flash/timer/temp)。

  9. 觀察結果:觀察 Wokwi 中 LED 的行為以及 Telegram 中機器人的回覆。


核心概念與注意事項

  • 非同步操作loop() 函式會檢查新訊息並處理計時器,而不會造成阻塞。這對於程式的響應能力非常重要。

  • 錯誤處理:DHT 感測器讀取包含 isnan() 檢查,用於判斷讀數是否無效。在實際應用場景中,您需要為網路問題、API 錯誤等添加更強健的錯誤處理機制。

  • 安全性

    • 機器人 Token:切勿分享您的機器人 Token。

    • 聊天 ID:使用 CHAT_ID 來限制存取是一種基本的安全措施。對於更進階的專案,您可能需要實作使用者身份驗證。

    • HTTPSclient.setCACert() 這行程式碼確保了與 Telegram 伺服器的安全 HTTPS 連線。

  • 功耗:對於電池供電的設備,持續輪詢 Telegram 更新可能會消耗大量電力。如果功耗是一個問題,請考慮使用睡眠模式或替代的通訊方法。

  • 可擴展性:對於更複雜的機器人或許多用戶的場景,您可以探索 Telegram 的 Webhook 方法,而非長輪詢 (long polling);但對於一個簡單的 ESP32 專案來說,長輪詢通常是可行的。

  • 計時器:使用 millis() 進行計時對於非阻塞操作至關重要。請避免在 loop() 中使用 delay(),因為它會使微控制器凍結。

這套設定為您透過 Telegram 控制 ESP32 並接收感測器數據提供了堅實的基礎。


Hello! I'm your ESP32 Telegram Bot.

Control me with these commands:

/on - Turn LED ON

/off - Turn LED OFF

/flash - Flash LED 3 times

/timer - Activate 10-second LED timer

/temp - Get current temperature and humidity




<<另一寫法>>








#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
#include <DHT.h>

// WiFi 設置
const char* ssid = "Wokwi-GUEST";
const char* password = ""; // 如果Wokwi-GUEST沒有密碼,就留空

// Telegram Bot 設置
#define BOT_TOKEN "7738940254:AAHbrWu9ovb1BKPQyWsbNSjNxfCGCrEWU-o" // 請替換成你自己的 Bot Token
#define CHAT_ID "7965218469" // 請替換成你自己的 Chat ID

// LED 控制狀態變數 (只宣告一次)
String ledMode = "off";
unsigned long ledTimerStart = 0;
unsigned long lastFlashTime = 0;
bool ledState = false; // 用於未來可能的常規 LED 狀態追蹤,儘管目前 flashLedState 更常用
bool flashLedState = false; // 用於閃爍模式的 LED 狀態追蹤

WiFiClientSecure secured_client;
UniversalTelegramBot bot(BOT_TOKEN, secured_client);

// DHT22 感測器設置
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

// LED 引腳設置
#define ledPin 2

// 任務 Handle (FreeRTOS 相關)
TaskHandle_t TaskCore0;
TaskHandle_t TaskCore1;

// 感測值儲存 (全域變數,供不同任務存取)
float temp = 0.0;
float humi = 0.0;

// 連接 WiFi 函數
void connectWiFi() {
  Serial.print("Connecting to WiFi");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
  }
  Serial.println(" connected!");
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());
}

void setup() {
  Serial.begin(115200); // 設置序列埠通訊速率
  pinMode(ledPin, OUTPUT); // 設定 LED 引腳為輸出模式
  dht.begin(); // 啟動 DHT 感測器
  connectWiFi(); // 連接 WiFi
  secured_client.setInsecure(); // 跳過 SSL 憑證檢查(在 Wokwi 或某些開發環境中是必要的)

  // 建立雙核心任務 (使用 FreeRTOS 特性)
  // 任務名稱, 堆疊大小, 參數, 優先級, 任務句柄, 核心號碼 (0 或 1)
  xTaskCreatePinnedToCore(
    DHT_Task,                 // 任務函數
    "DHT22_Task",             // 任務名稱
    4096,                     // 堆疊大小 (位元組)
    NULL,                     // 任務參數
    1,                        // 任務優先級 (較低)
    &TaskCore0,               // 任務句柄
    0                         // 運行在 Core 0
  );

  xTaskCreatePinnedToCore(
    Telegram_LED_Task,        // 任務函數
    "Telegram_LED_Task",      // 任務名稱
    8192,                     // 堆疊大小 (位元組,Telegram 任務可能需要更多)
    NULL,                     // 任務參數
    1,                        // 任務優先級 (較低)
    &TaskCore1,               // 任務句柄
    1                         // 運行在 Core 1
  );
}

// 主迴圈 (在 FreeRTOS 環境下通常只做少量工作或留空)
void loop() {
  // FreeRTOS 會負責調度任務,所以主迴圈通常只需要一個短暫的延遲,
  // 防止它在沒有其他任務時過度消耗 CPU
  delay(100);
}

void DHT_Task(void *pvParameters) {
  for (;;) { // 無限迴圈,任務持續運行
    float h = dht.readHumidity();    // 讀取濕度
    float t = dht.readTemperature(); // 讀取溫度

    if (!isnan(h) && !isnan(t)) { // 檢查讀取是否成功
      humi = h; // 更新濕度全域變數
      temp = t; // 更新溫度全域變數
      Serial.printf("[DHT22] 溫度: %.2f°C   濕度: %.2f%%\n", temp, humi);
    } else {
      Serial.println("[DHT22] 讀取失敗");
    }
    delay(5000); // 每 5 秒讀取一次感測器,可調整
  }
}

void Telegram_LED_Task(void *pvParameters) {
  for (;;) { // 無限迴圈,任務持續運行
    // 檢查是否有新的 Telegram 訊息
    int numNewMessages = bot.getUpdates(bot.last_message_received + 1);

    // 迴圈處理所有新訊息
    while (numNewMessages) {
      for (int i = 0; i < numNewMessages; i++) {
        String chat_id = bot.messages[i].chat_id; // 獲取聊天 ID
        String msg = bot.messages[i].text;       // 獲取訊息內容
        msg.toLowerCase();                       // 將訊息轉為小寫,方便比對

        // 判斷指令並執行相應操作
        if (msg == "on" || msg == "off" || msg == "flash" || msg == "timer") {
          ledMode = msg; // 設定 LED 模式
          if (msg == "timer") {
            ledTimerStart = millis(); // 如果是 timer 模式,記錄開始時間
          }
          bot.sendMessage(chat_id, "✅ LED 模式: " + msg, ""); // 回覆使用者
        } else if (msg == "status") {
          // 如果是 status 指令,回覆溫濕度資訊
          String dhtInfo = "🌡️ 溫度: " + String(temp) + "°C\n💧 濕度: " + String(humi) + "%";
          bot.sendMessage(chat_id, dhtInfo, "");
        } else {
          // 如果是未知指令,提供詳細的幫助訊息
          String helpMessage = "您好!我是您的智慧裝置控制Bot。\n";
          helpMessage += "請輸入以下指令來控制設備或查詢狀態:\n";
          helpMessage += "💡 **LED 控制指令:**\n";
          helpMessage += "  `/on` - 開啟 LED\n";
          helpMessage += "  `/off` - 關閉 LED\n";
          helpMessage += "  `/flash` - 讓 LED 閃爍\n";
          helpMessage += "  `/timer` - 讓 LED 亮 10 秒後自動關閉\n";
          helpMessage += "🌡️ **感測器查詢指令:**\n";
          helpMessage += "  `/status` - 查詢目前的溫度與濕度\n";
          helpMessage += "\n請注意,指令不區分大小寫。"; // 告知大小寫不敏感
         
          // Telegram Bot API 支援 MarkdownV2,你可以用 `parse_mode` 參數來格式化訊息
          // 但這裡為了簡單,先用普通文字,如果需要,可以自行研究 Telegram 的 Markdown 語法
          bot.sendMessage(chat_id, helpMessage, "Markdown"); // 使用 Markdown 模式發送訊息

          // 也可以直接使用普通模式,如果不想處理 Markdown
          // bot.sendMessage(chat_id, helpMessage, "");
        }
      }
      // 處理完當前批次訊息後,再次檢查是否有更多新訊息
      numNewMessages = bot.getUpdates(bot.last_message_received + 1);
    }

    // 根據 ledMode 變數控制 LED 的實際行為
    if (ledMode == "on") {
      digitalWrite(ledPin, HIGH); // LED 長亮
      Serial.println("開啟 LED\n");
    } else if (ledMode == "off") {
      digitalWrite(ledPin, LOW); // LED 熄滅
      Serial.println("關閉 LED\n");
    } else if (ledMode == "flash") {
      // 閃爍模式:使用非阻塞方式控制 LED 狀態
      Serial.println("LED 閃爍\n");
      if (millis() - lastFlashTime >= 500) { // 每 500 毫秒切換一次狀態
        lastFlashTime = millis();
        flashLedState = !flashLedState; // 反轉 LED 狀態
        digitalWrite(ledPin, flashLedState);
      }
    } else if (ledMode == "timer") {
      Serial.println("LED 亮 10 秒後自動關閉\n");
      // 計時器模式:LED 亮 10 秒後自動關閉
      if (millis() - ledTimerStart < 10000) { // 檢查是否在 10 秒內
        digitalWrite(ledPin, HIGH); // LED 亮
      } else {
        digitalWrite(ledPin, LOW); // 超過 10 秒則熄滅
        ledMode = "off"; // 重置模式為 off
      }
    }

    // 控制 Telegram 訊息檢查頻率
    // 將延遲縮短為 10ms,以提高反應速度
    if (ledMode != "flash") {
        delay(10); // 可以根據需要調整這個值,越小反應越快,但可能增加 Wokwi 負載
    } else {
        // 在 flash 模式下,因為已經有 500ms 的內部計時,這裡不需要額外的大延遲
        delay(1); // 讓出 CPU 時間給其他任務
    }
  }
}

您好!我是您的智慧裝置控制Bot。

請輸入以下指令來控制設備或查詢狀態:

💡 LED 控制指令:

  on - 開啟 LED

  off - 關閉 LED

  flash - 讓 LED 閃爍

 /timer - 讓 LED 亮 10 秒後自動關閉

🌡️ 感測器查詢指令:

  status - 查詢目前的溫度與濕度

沒有留言:

張貼留言

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...