Telegram 控制 WOKWI(ESP32+LED+DHT22)
將所有與 Telegram 的網路呼叫集中到單一 telegramTask(避免在多個任務同時做 TLS/HTTPS),其餘任務(LED、DHT)透過一個 FreeRTOS queue 向 telegramTask 傳送要發出的訊息或由 telegramTask 處理收到的指令。這樣能顯著降低 crash(backtrace)風險、減少記憶體競爭,並保留雙核心執行。
Commands: /on /off /flash /timer /status
WOKWI程式
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
#include <DHT.h>
#include <DHT_U.h>
// ====================== 配置區 ======================
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// --- Telegram: 填入你的 Bot Token 與授權 Chat ID(string) ---
//#define BOT_TOKEN "YOUR_BOT_TOKEN_HERE"
//#define AUTH_CHAT_ID "YOUR_CHAT_ID_HERE" // 例如 "123456789"(請放引號)
#define BOT_TOKEN "802257009856:AAGymymK9_d1HcT5GJWl3mtqHmil5xB64_5Zw"
#define AUTH_CHAT_ID "79152158469"
// DHT 設定
#define DHTPIN 4
#define DHTTYPE DHT22
// LED 引腳
const int ledPin = 2;
// ====================== 全域物件 ======================
WiFiClientSecure clientSecure;
UniversalTelegramBot bot(BOT_TOKEN, clientSecure);
DHT dht(DHTPIN, DHTTYPE);
// LED 狀態
enum LedMode { LED_ON, LED_OFF, LED_FLASH, LED_TIMER };
volatile LedMode currentLedMode = LED_OFF;
volatile unsigned long timerStartTime = 0;
volatile bool ledState = false;
// DHT 資料快存(在 DHT 任務更新,由 telegramTask 讀取 / 回報)
volatile float g_temperature = NAN;
volatile float g_humidity = NAN;
// Queue 用來把要發的文字訊息交給 telegramTask(所有發送集中處理)
struct TgMsg {
char text[256];
// 若未指定 chatId,默認發到 AUTH_CHAT_ID
};
static QueueHandle_t tgQueue = NULL;
// 任務 handle(如需在未來終止或檢查)
TaskHandle_t TaskTelegram = NULL;
TaskHandle_t TaskLEDControl = NULL;
TaskHandle_t TaskDHTSensor = NULL;
// ====================== Helper ======================
bool enqueueTgMessage(const char* txt) {
if (!tgQueue) return false;
TgMsg m;
strncpy(m.text, txt, sizeof(m.text) - 1);
m.text[sizeof(m.text) - 1] = '\0';
if (xQueueSend(tgQueue, &m, pdMS_TO_TICKS(100)) == pdTRUE) return true;
return false;
}
void printMemoryStats(const char* tag) {
Serial.printf("[%s] FreeHeap=%u\n", tag, ESP.getFreeHeap());
}
// ====================== telegramTask: 負責 poll 訊息 & 發送所有訊息(單一 TLS 來源) ======================
void telegramTask(void *pvParameters) {
(void) pvParameters;
clientSecure.setInsecure(); // Wokwi / 測試環境可直接使用;實作時可替換為根憑證
unsigned long lastPoll = 0;
const unsigned long pollInterval = 2000; // 每 2 秒檢查一次新的 Telegram 訊息
Serial.println("[TelegramTask] started");
printMemoryStats("TelegramTask start");
for (;;) {
// 1) 處理要發送的訊息(來自其他任務)
TgMsg msg;
while (xQueueReceive(tgQueue, &msg, 0) == pdTRUE) {
// 發送訊息(所有實際的 HTTPS 都在此處)
Serial.printf("[TelegramTask] sendMessage: %s\n", msg.text);
bot.sendMessage(String(AUTH_CHAT_ID).c_str(), msg.text, "");
vTaskDelay(pdMS_TO_TICKS(500)); // 小延遲以避免頻繁呼叫
printMemoryStats("after sendMessage");
}
// 2) 週期性 poll 新訊息
if (millis() - lastPoll >= pollInterval) {
int numNew = bot.getUpdates(bot.last_message_received + 1);
if (numNew > 0) {
Serial.printf("[TelegramTask] Received %d new messages\n", numNew);
for (int i = 0; i < numNew; i++) {
String chat_id = String(bot.messages[i].chat_id);
String text = bot.messages[i].text;
Serial.printf("[TelegramTask] msg from %s : %s\n", chat_id.c_str(), text.c_str());
// 只接受授權 chat id 的指令
if (chat_id != String(AUTH_CHAT_ID)) {
bot.sendMessage(chat_id.c_str(), "⚠️ Unauthorized user.", "");
continue;
}
// 處理指令(由 telegramTask 直接操控全域 LED 模式)
if (text == "/on") {
currentLedMode = LED_ON;
digitalWrite(ledPin, HIGH);
bot.sendMessage(chat_id.c_str(), "💡 LED ON", "");
} else if (text == "/off") {
currentLedMode = LED_OFF;
digitalWrite(ledPin, LOW);
bot.sendMessage(chat_id.c_str(), "💡 LED OFF", "");
} else if (text == "/flash") {
currentLedMode = LED_FLASH;
bot.sendMessage(chat_id.c_str(), "⚡ LED FLASH mode", "");
} else if (text == "/timer") {
currentLedMode = LED_TIMER;
timerStartTime = millis();
digitalWrite(ledPin, HIGH);
bot.sendMessage(chat_id.c_str(), "⏱ LED TIMER 10s started", "");
} else if (text == "/status") {
// 回報最新的溫濕度與 LED 狀態
char buf[256];
float t = g_temperature;
float h = g_humidity;
if (isnan(t) || isnan(h)) {
snprintf(buf, sizeof(buf), "📡 Status:\nTemperature: N/A\nHumidity: N/A\nLED: %s",
(currentLedMode==LED_ON)?"ON":(currentLedMode==LED_OFF)?"OFF":(currentLedMode==LED_FLASH)?"FLASH":"TIMER");
} else {
snprintf(buf, sizeof(buf), "📡 Status:\nTemperature: %.1f °C\nHumidity: %.1f %%\nLED: %s",
t, h,
(currentLedMode==LED_ON)?"ON":(currentLedMode==LED_OFF)?"OFF":(currentLedMode==LED_FLASH)?"FLASH":"TIMER");
}
bot.sendMessage(chat_id.c_str(), buf, "");
} else {
bot.sendMessage(chat_id.c_str(), "❓ Commands: /on /off /flash /timer /status", "");
}
vTaskDelay(pdMS_TO_TICKS(200)); // 小 delay 以避免快速連續回覆
}
}
lastPoll = millis();
}
// 稍作休息
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// ====================== LED task(跑在 Core 1) ======================
void ledControlTask(void *pvParameters) {
(void) pvParameters;
Serial.println("[LED Task] started");
// 印出 stack high water mark(剛啟動時)
Serial.printf("[LED Task] stack high water mark: %u\n", uxTaskGetStackHighWaterMark(NULL));
for (;;) {
switch (currentLedMode) {
case LED_ON:
digitalWrite(ledPin, HIGH);
break;
case LED_OFF:
digitalWrite(ledPin, LOW);
break;
case LED_FLASH:
digitalWrite(ledPin, ledState ? HIGH : LOW);
ledState = !ledState;
vTaskDelay(pdMS_TO_TICKS(500));
continue; // 直接 continue 跳到下一輪,避免再 vTaskDelay(10)
case LED_TIMER:
if (millis() - timerStartTime >= 10000UL) {
digitalWrite(ledPin, LOW);
currentLedMode = LED_OFF;
// 通知使用者(透過 queue)
enqueueTgMessage("⏰ LED timer finished. LED OFF.");
}
break;
default:
digitalWrite(ledPin, LOW);
break;
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// ====================== DHT task(跑在 Core 1) ======================
void dhtSensorTask(void *pvParameters) {
(void) pvParameters;
Serial.println("[DHT Task] started");
Serial.printf("[DHT Task] stack high water mark: %u\n", uxTaskGetStackHighWaterMark(NULL));
const TickType_t readInterval = pdMS_TO_TICKS(30000); // 每 30 秒讀一次(節流)
for (;;) {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
g_humidity = h;
g_temperature = t;
Serial.printf("[DHT Task] Temp: %.2f C Hum: %.2f %%\n", t, h);
// 主動上報(節流):每 30 秒主動發送一則狀態(如果你想關掉主動上報,註解掉下面列)
char buf[200];
snprintf(buf, sizeof(buf), "🌡 Temperature: %.1f °C\n💧 Humidity: %.1f %%", t, h);
enqueueTgMessage(buf);
} else {
Serial.println("[DHT Task] Failed to read from DHT sensor");
}
// 印出 free heap 以供 debug(可視情況註解)
printMemoryStats("DHT Task loop");
vTaskDelay(readInterval);
}
}
// ====================== setup & loop ======================
void setup_wifi() {
Serial.print("Connecting to WiFi ");
Serial.println(ssid);
WiFi.begin(ssid, password);
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(".");
if (millis() - start > 20000) {
Serial.println("\n[setup_wifi] connect timeout, retrying...");
start = millis();
}
}
Serial.println("\nWiFi connected");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
}
void setup() {
Serial.begin(115200);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
setup_wifi();
// 初始化 DHT(在 DHT 任務也會使用)
dht.begin();
// 建立 queue(可緩衝 8 條訊息)
tgQueue = xQueueCreate(8, sizeof(TgMsg));
if (!tgQueue) {
Serial.println("Failed to create tgQueue");
}
// 建立 telegramTask(單一 TLS 呼叫來源),放在 Core 0,給較大 stack
BaseType_t ok;
ok = xTaskCreatePinnedToCore(telegramTask, "TelegramTask", 16384, NULL, 1, &TaskTelegram, 0);
if (ok != pdPASS) Serial.println("Failed to create TelegramTask");
// 建立 LED task(Core 1) — 較輕量
ok = xTaskCreatePinnedToCore(ledControlTask, "LEDTask", 4096, NULL, 1, &TaskLEDControl, 1);
if (ok != pdPASS) Serial.println("Failed to create LEDTask");
// 建立 DHT task(Core 1)
ok = xTaskCreatePinnedToCore(dhtSensorTask, "DHTTask", 4096, NULL, 1, &TaskDHTSensor, 1);
if (ok != pdPASS) Serial.println("Failed to create DHTTask");
// 啟動時發個訊息(透過 queue,等 telegramTask 啟動後會發)
enqueueTgMessage("🤖 ESP32 Telegram Bot is online. Commands: /on /off /flash /timer /status");
// 最後印一次記憶體狀態
printMemoryStats("setup end");
}
void loop() {
// 主迴圈不做網路或 DHT 操作,避免阻塞;所有工作由任務處理
vTaskDelay(pdMS_TO_TICKS(1000));
}






沒有留言:
張貼留言