2025年11月15日 星期六

ESP32 8 Port Telegram 告警器

ESP32 8 Port   Telegram 告警器



1) ESP32 程式 燒入板子上 關閉 Arduino IDE程式 不然 Com Port會佔用

2) Python Tkinter 程式 規劃 wifi ssid ,password , Telegram Token , ID , 輸入1-8

    告警要顯示的訊息  (ESP32 程式 板子 Reset 後5秒內 要執行 不然就是執行 

     正常功能的程式了 ) 



3) 使用 WeMos D1 R32 板子 IO Pin 重新規劃 

 



Python TKinter程式

import tkinter as tk

from tkinter import ttk, messagebox

import serial

import serial.tools.list_ports

import threading

import time

import requests # 新增: 用於發送 Telegram API 請求


# --- 配置參數 ---

BAUD_RATE = 115200

TIMEOUT = 1

NUM_ALARM_GROUPS = 8

TELEGRAM_TEST_MESSAGE = "✅ 這是 telegram 告警 測試訊息 ✅"

# ----------------


class ESP32ConfigApp:

    # ... (__init__ 和 create_widgets 保持不變,除了 test_telegram 函式)

    def __init__(self, master):

        self.master = master

        master.title("ESP32 告警機配置工具")

        

        self.ser = None

        self.progress_var = tk.DoubleVar(value=0.0)


        # --- 配置變數 ---

        self.com_port_var = tk.StringVar(master)

        self.telegram_token_var = tk.StringVar(master)

        self.group_id_var = tk.StringVar(master)

        self.ssid_var = tk.StringVar(master, value="ASUS_30")

        self.password_var = tk.StringVar(master, value="beard_7354")

        self.flag_release_var = tk.BooleanVar(master, value=False)

        self.flag_persist_var = tk.BooleanVar(master, value=False)

        

        # 8 組警報訊息變數

        self.alarm_msg_vars = [tk.StringVar(master, value=f"警報觸發測試第{i+1:02d}組") for i in range(NUM_ALARM_GROUPS)]


        self.create_widgets()

        self.update_ports()

        

        root.protocol("WM_DELETE_WINDOW", self.on_closing)


    def create_widgets(self):

        main_pane = ttk.Panedwindow(self.master, orient=tk.HORIZONTAL)

        main_pane.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)


        # --- 左側區域 (COM Port & 警報訊息) ---

        left_frame = ttk.Frame(main_pane)

        main_pane.add(left_frame, weight=1)

        

        # COM/功能區

        func_frame = ttk.Frame(left_frame)

        func_frame.pack(fill='x', pady=5)

        

        ttk.Label(func_frame, text="COM Port:").pack(side=tk.LEFT, padx=5)

        self.port_combobox = ttk.Combobox(func_frame, textvariable=self.com_port_var, width=10)

        self.port_combobox.pack(side=tk.LEFT, padx=5)

        self.port_combobox.bind('<<ComboboxSelected>>', self.close_serial_connection)

        

        self.connect_button = ttk.Button(func_frame, text="連接", command=self.connect_serial)

        self.connect_button.pack(side=tk.LEFT, padx=5)

        ttk.Button(func_frame, text="讀取", command=self.start_read_config).pack(side=tk.LEFT, padx=5)

        # 修正:儲存按鈕將所有設定(包含 Telegram)寫入 ESP32

        ttk.Button(func_frame, text="儲存", command=self.start_write_config).pack(side=tk.LEFT, padx=5) 

        

        # 進度條

        self.progress_bar = ttk.Progressbar(left_frame, orient=tk.HORIZONTAL, length=200, mode='determinate', variable=self.progress_var)

        self.progress_bar.pack(fill='x', pady=5)

        ttk.Label(left_frame, textvariable=self.progress_var, text="%").pack(pady=5)

        

        # 警報訊息內容

        msg_frame = ttk.LabelFrame(left_frame, text="警報訊息內容")

        msg_frame.pack(fill='both', expand=True, pady=10)

        

        for i in range(NUM_ALARM_GROUPS):

            ttk.Label(msg_frame, text=f"{i+1}").grid(row=i, column=0, padx=5, pady=2, sticky='w')

            ttk.Entry(msg_frame, textvariable=self.alarm_msg_vars[i], width=40).grid(row=i, column=1, padx=5, pady=2, sticky='ew')

        

        msg_frame.grid_columnconfigure(1, weight=1)


        # --- 右側區域 (Telegram/Flags/Wi-Fi) ---

        right_frame = ttk.Frame(main_pane)

        main_pane.add(right_frame, weight=1)


        # Telegram 區

        tele_frame = ttk.LabelFrame(right_frame, text="Telegram 配置")

        tele_frame.pack(fill='x', pady=5)

        

        ttk.Label(tele_frame, text="Telegram Token 輸入").pack(pady=5)

        ttk.Entry(tele_frame, textvariable=self.telegram_token_var, width=40).pack(pady=2, padx=5)

        

        ttk.Label(tele_frame, text="群組 ID 輸入").pack(pady=5)

        ttk.Entry(tele_frame, textvariable=self.group_id_var, width=40).pack(pady=2, padx=5)

        

        # 修正:按鈕功能改為在 PC 端立即測試

        ttk.Button(tele_frame, text="發送訊息至群組 (PC 測試)", command=self.start_telegram_test_api).pack(pady=10) 


        # 警報類型旗標

        flag_frame = ttk.LabelFrame(right_frame, text="警報類型")

        flag_frame.pack(fill='x', pady=5)

        

        ttk.Checkbutton(flag_frame, text="警報解除是否發送", variable=self.flag_release_var).pack(anchor='w', padx=10, pady=2)

        ttk.Checkbutton(flag_frame, text="警報持續是否發送", variable=self.flag_persist_var).pack(anchor='w', padx=10, pady=2)


        # Wi-Fi 配置

        wifi_frame = ttk.LabelFrame(right_frame, text="Wi-Fi 配置")

        wifi_frame.pack(fill='x', pady=5)

        

        ttk.Label(wifi_frame, text="Wi-Fi SSID:").grid(row=0, column=0, padx=5, pady=5, sticky='w')

        ttk.Entry(wifi_frame, textvariable=self.ssid_var, width=25).grid(row=0, column=1, padx=5, pady=5)

        

        ttk.Label(wifi_frame, text="Wi-Fi Password:").grid(row=1, column=0, padx=5, pady=5, sticky='w')

        ttk.Entry(wifi_frame, textvariable=self.password_var, show="*", width=25).grid(row=1, column=1, padx=5, pady=5)



    # --- 新增/修改:Telegram 測試功能 (PC 網路) ---

    

    def start_telegram_test_api(self):

        """啟動一個新線程來執行網路測試,避免 GUI 卡頓"""

        threading.Thread(target=self._test_telegram_api, daemon=True).start()


    def _test_telegram_api(self):

        """直接使用 requests 庫發送 Telegram 測試訊息"""

        token = self.telegram_token_var.get()

        chat_id = self.group_id_var.get()

        

        if not token or not chat_id:

            messagebox.showerror("測試失敗", "請輸入有效的 Telegram Token 和 群組 ID。")

            return


        url = f"https://api.telegram.org/bot{token}/sendMessage"

        payload = {

            'chat_id': chat_id,

            'text': TELEGRAM_TEST_MESSAGE

        }

        

        try:

            # 發送 POST 請求

            response = requests.post(url, data=payload, timeout=5)

            

            if response.status_code == 200 and response.json().get("ok"):

                messagebox.showinfo("測試成功", f"測試訊息已成功發送!\n請檢查您的 Telegram ({chat_id}) 是否收到此訊息。")

            else:

                # 處理 API 錯誤

                error_info = response.json().get("description", "未知錯誤")

                messagebox.showerror("測試失敗", f"Telegram API 響應錯誤 ({response.status_code})。\n原因: {error_info}\n請檢查 Token 和群組 ID 是否正確,並確保 Bot 已被加入群組。")


        except requests.exceptions.Timeout:

            messagebox.showerror("網路錯誤", "發送請求超時,請檢查您的 PC 網路連線。")

        except requests.exceptions.RequestException as e:

            messagebox.showerror("連線錯誤", f"發送 Telegram 請求失敗:{e}")



    # --- 串口/功能處理 (其他部分保持與上一個回答相同) ---


    def update_ports(self):

        """刷新並更新 COM 端口列表"""

        ports = serial.tools.list_ports.comports()

        port_list = [p.device for p in ports]

        self.port_combobox['values'] = port_list

        if port_list:

            self.com_port_var.set(port_list[0])

        else:

            self.com_port_var.set("無可用端口")


    def connect_serial(self):

        """連接串口"""

        port = self.com_port_var.get()

        if port == "無可用端口" or not port:

            messagebox.showerror("錯誤", "請選擇一個有效的 COM 端口。")

            return

        

        self.close_serial_connection()


        try:

            self.ser = serial.Serial(port, BAUD_RATE, timeout=TIMEOUT)

            self.master.after(2000, lambda: messagebox.showinfo("連接成功", f"成功連接到 {port}"))

            self.connect_button.config(text="已連接", state=tk.DISABLED)

        except serial.SerialException as e:

            messagebox.showerror("連接失敗", f"無法連接到 {port}:\n{e}")

            self.ser = None


    def close_serial_connection(self, event=None):

        """關閉串口連接"""

        if self.ser and self.ser.is_open:

            self.ser.close()

            self.ser = None

            self.connect_button.config(text="連接", state=tk.NORMAL)

            print("串口連接已關閉。")

            self.progress_var.set(0.0) # 重置進度


    def send_and_wait_for_response(self, command, expected_prefix=None):

        """發送命令並等待 ESP32 的 100% 響應和數據響應"""

        if not self.ser or not self.ser.is_open:

            messagebox.showerror("錯誤", "請先連接到 ESP32 的 COM 端口。")

            return None, None

        

        self.progress_var.set(0.0)

        self.ser.write((command + "\n").encode('utf-8'))

        print(f"Sent: {command}")

        

        progress_done = False

        response_data = None

        start_time = time.time()

        

        while time.time() - start_time < 10: # 最多等待 10 秒

            if self.ser.in_waiting > 0:

                line = self.ser.readline().decode('utf-8', errors='ignore').strip()

                print(f"Received: {line}")

                

                # 更新進度條

                if line == "100%":

                    self.progress_var.set(100.0)

                    progress_done = True

                elif line.endswith("%"):

                    try:

                        self.progress_var.set(float(line.strip('%')))

                    except ValueError:

                        pass

                

                if expected_prefix and line.startswith(expected_prefix):

                    response_data = line[len(expected_prefix):]

                    break

            self.master.update()

            time.sleep(0.05) 


        return progress_done, response_data


    # --- 寫入/儲存功能 ---


    def start_write_config(self):

        threading.Thread(target=self._write_config_thread, daemon=True).start()


    def _write_config_thread(self):

        """將所有配置組合成單一字串並發送到 ESP32"""

        # 1. 組裝資料: TOKEN|CHATID|SSID|PASS|FLAG_R|FLAG_P|MSG1|...|MSG8

        flag_r = "1" if self.flag_release_var.get() else "0"

        flag_p = "1" if self.flag_persist_var.get() else "0"

        

        # 警報訊息列表

        msgs = [var.get() for var in self.alarm_msg_vars]

        

        data_parts = [

            self.telegram_token_var.get(),

            self.group_id_var.get(),

            self.ssid_var.get(),

            self.password_var.get(),

            flag_r,

            flag_p

        ] + msgs

        

        payload = "|".join(data_parts)

        command = f"SAVE_CFG:{payload}"

        

        progress_done, _ = self.send_and_wait_for_response(command)


        if progress_done:

            messagebox.showinfo("儲存成功", "所有配置已成功寫入 ESP32 Preferences!")

        else:

            messagebox.showerror("儲存失敗", "寫入命令超時,請檢查串口連接和 ESP32 是否正在運行。")


    # --- 讀取功能 ---

    

    def start_read_config(self):

        threading.Thread(target=self._read_config_thread, daemon=True).start()


    def _read_config_thread(self):

        """發送讀取命令並解析 ESP32 返回的配置字串"""

        progress_done, response_data = self.send_and_wait_for_response("LOAD_CFG", "LOAD_RESP:")

        

        if not progress_done or not response_data:

            messagebox.showerror("讀取失敗", "未收到有效的配置數據,請檢查串口。")

            return

        

        # 解析數據: TOKEN|CHATID|SSID|PASS|FLAG_R|FLAG_P|MSG1|...|MSG8

        try:

            parts = response_data.split('|')

            if len(parts) != 6 + NUM_ALARM_GROUPS:

                 raise ValueError("數據格式錯誤或缺失")


            # 更新變數

            self.telegram_token_var.set(parts[0])

            self.group_id_var.set(parts[1])

            self.ssid_var.set(parts[2])

            self.password_var.set(parts[3])

            self.flag_release_var.set(parts[4] == "1")

            self.flag_persist_var.set(parts[5] == "1")

            

            # 更新 8 組警報訊息

            for i in range(NUM_ALARM_GROUPS):

                self.alarm_msg_vars[i].set(parts[6 + i])

                

            messagebox.showinfo("讀取成功", "已成功從 ESP32 讀取所有配置數據。")


        except Exception as e:

            print(f"Parsing error: {e}")

            messagebox.showerror("讀取錯誤", f"解析數據時發生錯誤:{e}")


    def on_closing(self):

        """應用程式關閉時的清理工作"""

        self.close_serial_connection()

        self.master.destroy()


if __name__ == "__main__":

    root = tk.Tk()

    app = ESP32ConfigApp(root)

    root.mainloop()




ESP32 WeMos D1 R32


ESP32 Arduino程式

#include <Arduino.h>

#include <WiFi.h>

#include <WiFiClientSecure.h> 

#include <UniversalTelegramBot.h> 

#include <Preferences.h> // 引入 Preferences 庫來儲存配置


// --- 配置參數 ---

#define BOT_TOKEN_LEN 50

#define CHAT_ID_LEN 15

#define SSID_LEN 32

#define PASS_LEN 32

#define ALARM_MSG_LEN 40

#define NUM_INPUTS 8

const char* PREFS_NAME = "alarm_config"; // Preferences 命名空間

const long DEFAULT_CHAT_ID = 0; // 預設 Chat ID

const int BOT_REQUEST_DELAY = 1000; // Telegram 訊息檢查間隔 (ms)

const unsigned long PERSIST_INTERVAL = 60000; // 警報持續發送間隔 (60 秒)


// --- 狀態/配置變數 ---

Preferences preferences; 

char telegramToken[BOT_TOKEN_LEN];

char chatIDStr[CHAT_ID_LEN];

char wifiSSID[SSID_LEN];

char wifiPassword[PASS_LEN];

bool sendOnRelease = false; 

bool sendOnPersist = false; 

char alarmMessages[NUM_INPUTS][ALARM_MSG_LEN]; 

bool serialConfigMode = false; // 序列埠配置模式旗標,用於防止硬體輸出干擾


// --- 硬體定義 ---

struct InputPin {

    int id;       // 警報訊息組編號 (1 to 8)

    int pin;      // 實際連接的 ESP32 GPIO

};


InputPin inputMap[NUM_INPUTS] = {

    {1, 13}, {2, 12}, {3, 14}, {4, 27}, 

    {5, 16}, {6, 17}, {7, 25}, {8, 26}

};

int lastPinState[NUM_INPUTS]; 

unsigned long lastTriggerTime[NUM_INPUTS] = {0};

long lastTimeCheck = 0; 


// --- 程式庫實例化 (暫時為空,將在 loadConfig 後初始化) ---

WiFiClientSecure client;

UniversalTelegramBot* bot = nullptr; 


// ----------------------------------------------------

//                函式宣告

// ----------------------------------------------------

void loadConfig();

void saveConfig(const String& payload);

void initializeInputs();

void connectWifi();

void checkSerialConfig();

void handleNewMessages(int numNewMessages);

void checkAndSendAlarm(int index);


// ----------------------------------------------------

//                SETUP

// ----------------------------------------------------

void setup() {

    Serial.begin(115200);

    delay(500); 

    Serial.println("\n--- ESP32 Alarm System Booting ---");

    

    // 1. 載入配置數據

    loadConfig();


    // 2. 初始化 Telegram Bot

    client.setInsecure(); // 允許不安全的 HTTPS 連線 (測試環境用)

    bot = new UniversalTelegramBot(telegramToken, client);

    

    // 3. 初始化輸入腳位

    initializeInputs();


    // 4. 連接 Wi-Fi

    connectWifi();

    

    lastTimeCheck = millis();

    Serial.println("System Ready.");


    if (String(chatIDStr).length() > 5 && WiFi.status() == WL_CONNECTED) {

        bot->sendMessage(chatIDStr, "【系統啟動】ESP32 告警機已連線並啟動。", "");

    }

}


// ----------------------------------------------------

//                LOOP

// ----------------------------------------------------

void loop() {

    // 1. **優先處理序列埠配置命令** (新增:防止配置時被其他輸出干擾)

    checkSerialConfig(); 


    // 2. 確保 Wi-Fi 連線

    if (WiFi.status() != WL_CONNECTED) {

        connectWifi();

        delay(1000); 

        return;

    }


    // 3. **只有在非配置模式下才執行警報監控和 Telegram 處理**

    if (!serialConfigMode) {

        // 監控硬體輸入並發送警報

        for (int i = 0; i < NUM_INPUTS; i++) {

            checkAndSendAlarm(i);

        }


        // 處理傳入的 Telegram 訊息 (每 BOT_REQUEST_DELAY 毫秒檢查一次)

        if (millis() > lastTimeCheck + BOT_REQUEST_DELAY) {

            int numNewMessages = bot->getUpdates(bot->last_message_received + 1);

            while(numNewMessages) {

                handleNewMessages(numNewMessages);

                numNewMessages = bot->getUpdates(bot->last_message_received + 1);

            }

            lastTimeCheck = millis();

        }

    }


    delay(10); 

}


// ----------------------------------------------------

//                Preferences 配置處理

// ----------------------------------------------------


void loadConfig() {

    preferences.begin(PREFS_NAME, true); // 只讀模式

    

    // 載入 Telegram/Wi-Fi/旗標 (使用 3 參數格式: key, buffer, maxLen)

    // 預設值在讀取到空字串時手動設置

    preferences.getString("token", telegramToken, BOT_TOKEN_LEN);

    preferences.getString("chat_id", chatIDStr, CHAT_ID_LEN);

    preferences.getString("ssid", wifiSSID, SSID_LEN);

    preferences.getString("pass", wifiPassword, PASS_LEN);


    // 手動設置預設值 (如果讀取到空字串)

    if (String(telegramToken).length() == 0) strncpy(telegramToken, "", BOT_TOKEN_LEN);

    if (String(chatIDStr).length() == 0) strncpy(chatIDStr, String(DEFAULT_CHAT_ID).c_str(), CHAT_ID_LEN);

    if (String(wifiSSID).length() == 0) strncpy(wifiSSID, "ASUS_30", SSID_LEN);

    if (String(wifiPassword).length() == 0) strncpy(wifiPassword, "beard_7354", PASS_LEN);


    sendOnRelease = preferences.getBool("flag_r", false);

    sendOnPersist = preferences.getBool("flag_p", false);


    // 載入 8 組警報訊息

    for (int i = 0; i < NUM_INPUTS; i++) {

        String key = "msg" + String(i + 1);

        

        // 使用 3 參數格式讀取到 char 陣列中

        size_t len = preferences.getString(key.c_str(), alarmMessages[i], ALARM_MSG_LEN);

        

        // 如果讀取到的長度為 0,則設定預設值 (修正字串拼接錯誤)

        if (len == 0) {

            String defaultMsg = String("警報觸發測試第") + (i + 1 < 10 ? "0" : "") + String(i + 1) + String("組");

            strncpy(alarmMessages[i], defaultMsg.c_str(), ALARM_MSG_LEN);

        }

    }


    preferences.end();

    

    Serial.println("Config loaded.");

    Serial.printf("SSID: %s, ChatID: %s\n", wifiSSID, chatIDStr);

}


void saveConfig(const String& payload) {

    preferences.begin(PREFS_NAME, false); // 讀寫模式


    // 解析數據: TOKEN|CHATID|SSID|PASS|FLAG_R|FLAG_P|MSG1|...|MSG8

    String data = payload;

    int next;

    

    // Token

    next = data.indexOf('|');

    String token = data.substring(0, next);

    token.toCharArray(telegramToken, BOT_TOKEN_LEN);

    preferences.putString("token", telegramToken);

    data = data.substring(next + 1);


    // Chat ID

    next = data.indexOf('|');

    String chatID = data.substring(0, next);

    chatID.toCharArray(chatIDStr, CHAT_ID_LEN);

    preferences.putString("chat_id", chatIDStr);

    data = data.substring(next + 1);


    // SSID

    next = data.indexOf('|');

    String ssid = data.substring(0, next);

    ssid.toCharArray(wifiSSID, SSID_LEN);

    preferences.putString("ssid", wifiSSID);

    data = data.substring(next + 1);


    // Password

    next = data.indexOf('|');

    String pass = data.substring(0, next);

    pass.toCharArray(wifiPassword, PASS_LEN);

    preferences.putString("pass", wifiPassword);

    data = data.substring(next + 1);


    // Flags

    next = data.indexOf('|');

    sendOnRelease = (data.substring(0, next) == "1");

    preferences.putBool("flag_r", sendOnRelease);

    data = data.substring(next + 1);


    next = data.indexOf('|');

    sendOnPersist = (data.substring(0, next) == "1");

    preferences.putBool("flag_p", sendOnPersist);

    data = data.substring(next + 1);


    // Messages

    for (int i = 0; i < NUM_INPUTS; i++) {

        next = data.indexOf('|');

        String msg = (next == -1) ? data : data.substring(0, next);

        

        // 確保訊息長度不超限

        if (msg.length() >= ALARM_MSG_LEN) {

             msg = msg.substring(0, ALARM_MSG_LEN - 1);

        }


        msg.toCharArray(alarmMessages[i], ALARM_MSG_LEN);

        preferences.putString(("msg" + String(i+1)).c_str(), alarmMessages[i]);


        if (next != -1) {

            data = data.substring(next + 1);

        }

    }


    preferences.end();

    Serial.println("Config data saved. Rebooting...");

    

    // 重啟以應用新的配置

    ESP.restart(); 

}


// ----------------------------------------------------

//                序列埠命令處理 (解決 PC 程式超時問題的關鍵)

// ----------------------------------------------------


void checkSerialConfig() {

    if (Serial.available()) {

        String input = Serial.readStringUntil('\n');

        input.trim();

        

        if (input.startsWith("SAVE_CFG:")) {

            serialConfigMode = true; // 進入配置模式

            Serial.println("50%"); // 提供進度響應

            

            String payload = input.substring(9);

            saveConfig(payload); 

            

            return; 


        } else if (input.startsWith("LOAD_CFG")) {

            serialConfigMode = true; // 進入配置模式

            Serial.println("50%"); // 提供進度響應


            preferences.begin(PREFS_NAME, true); 

            String response = "";


            // 讀取所有數據並組裝: TOKEN|CHATID|SSID|PASS|FLAG_R|FLAG_P|MSG1|...|MSG8

            // 修正字串拼接錯誤

            response += preferences.getString("token", "") + String("|");

            response += preferences.getString("chat_id", String(DEFAULT_CHAT_ID)) + String("|");

            response += preferences.getString("ssid", "ASUS_30") + String("|");

            response += preferences.getString("pass", "beard_7354") + String("|");

            response += (preferences.getBool("flag_r", false) ? "1" : "0") + String("|");

            response += (preferences.getBool("flag_p", false) ? "1" : "0");

            

            for (int i = 0; i < NUM_INPUTS; i++) {

                String key = "msg" + String(i + 1);

                String defaultMsg = String("警報觸發測試第") + (i + 1 < 10 ? "0" : "") + String(i + 1) + String("組");

                response += String("|") + preferences.getString(key.c_str(), defaultMsg.c_str());

            }


            preferences.end();


            // 回傳數據,並發送 100% 響應讓 PC 知道操作完成

            Serial.print("LOAD_RESP:");

            Serial.println(response);

            Serial.println("100%"); 

            

            serialConfigMode = false; // 退出配置模式

        }

    }

}


// ----------------------------------------------------

//                Wi-Fi & 初始化

// ----------------------------------------------------


void initializeInputs() {

    for (int i = 0; i < NUM_INPUTS; i++) {

        // 設置為 INPUT_PULLUP,連接 LOW (接地) 時觸發 (警報)

        pinMode(inputMap[i].pin, INPUT_PULLUP); 

        lastPinState[i] = digitalRead(inputMap[i].pin); 

    }

}


void connectWifi() {

    if (WiFi.status() == WL_CONNECTED) {

        return;

    }


    Serial.print("Connecting to Wi-Fi SSID: ");

    Serial.println(wifiSSID);

    

    WiFi.begin(wifiSSID, wifiPassword);


    int attempts = 0;

    while (WiFi.status() != WL_CONNECTED && attempts < 30) {

        delay(500);

        Serial.print(".");

        attempts++;

    }


    if (WiFi.status() == WL_CONNECTED) {

        Serial.println("\nWiFi Connected!");

        Serial.print("IP Address: ");

        Serial.println(WiFi.localIP());

    } else {

        Serial.println("\nWiFi Connection Failed! Will retry in loop.");

    }

}


// ----------------------------------------------------

//                硬體與警報處理

// ----------------------------------------------------


void checkAndSendAlarm(int index) {

    if (!bot || String(chatIDStr).length() < 5) return; // 檢查 Bot 是否初始化和 Chat ID 是否有效


    int currentState = digitalRead(inputMap[index].pin);

    String message = alarmMessages[index];

    unsigned long currentTime = millis();

    String finalMsg;

    String chatIDString = String(chatIDStr);

    

    // 警報觸發 (狀態從 HIGH 變為 LOW)

    if (lastPinState[index] == HIGH && currentState == LOW) {

        finalMsg = "[🚨 警報觸發] " + message + " (Port " + String(inputMap[index].id) + ")";

        bot->sendMessage(chatIDString, finalMsg, ""); 

        lastTriggerTime[index] = currentTime;

        Serial.println(finalMsg); 


    // 警報解除 (狀態從 LOW 變為 HIGH)

    } else if (lastPinState[index] == LOW && currentState == HIGH) {

        if (sendOnRelease) { 

            finalMsg = "[✅ 警報解除] " + message + " (Port " + String(inputMap[index].id) + ")";

            bot->sendMessage(chatIDString, finalMsg, "");

            Serial.println(finalMsg);

        }

        lastTriggerTime[index] = 0; 


    // 警報持續 (狀態持續為 LOW)

    } else if (lastPinState[index] == LOW && currentState == LOW) {

        if (sendOnPersist && (currentTime - lastTriggerTime[index] >= PERSIST_INTERVAL)) { 

            finalMsg = "[⚠️ 持續警告] " + message + " (Port " + String(inputMap[index].id) + ")";

            bot->sendMessage(chatIDString, finalMsg, "");

            lastTriggerTime[index] = currentTime; 

            Serial.println(finalMsg);

        }

    }


    lastPinState[index] = currentState;

}


// ----------------------------------------------------

//                Telegram 訊息接收處理

// ----------------------------------------------------


void handleNewMessages(int numNewMessages) {

    String chatIDString = String(chatIDStr);

    if (!bot) return;


    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;

        String from_name = bot->messages[i].from_name;


        // 關鍵除錯輸出:印出 Chat ID 以便您修正程式碼

        Serial.printf("Received [%s] from %s. **Chat ID: %s**\n", text.c_str(), from_name.c_str(), chat_id.c_str());


        // 安全檢查

        if (chat_id != chatIDString) {

            bot->sendMessage(chat_id, "Access Denied. Your Chat ID is not authorized. Correct ID: " + chatIDString, "");

            continue;

        }

        

        if (text == "/status") {

            String statusMsg = "🚨 **當前告警狀態** 🚨\n\n";

            bool hasAlarm = false;

            

            for (int j = 0; j < NUM_INPUTS; j++) {

                int currentPin = digitalRead(inputMap[j].pin);

                

                String status = (currentPin == LOW) ? "**觸發中** ⚠️" : "正常 ✅";

                statusMsg += "Port " + String(inputMap[j].id) + " (" + String(alarmMessages[j]) + "): " + status + "\n";

                

                if (currentPin == LOW) {

                    hasAlarm = true;

                }

            }

            

            if (!hasAlarm) {

                statusMsg = "✅ **所有 Port 均正常,無告警。**";

            }

            

            bot->sendMessage(chat_id, statusMsg, "Markdown"); 

        

        } else if (text == "/start" || text == "/help") {

            String welcome = "歡迎!我是 ESP32 8-Port 告警機。\n\n";

            welcome += "使用以下指令查詢狀態:\n";

            welcome += "`/status` - 查詢當前所有 Port 的硬體狀態\n";

            welcome += "\n注意:只有您 (" + from_name + ") 才能發送指令。";

            bot->sendMessage(chat_id, welcome, "Markdown");

        }

    }

}




沒有留言:

張貼留言

  清除項目編譯快取 執行 flutter clean 重新取得套件依賴 執行 flutter pub get 完整重新編譯並執行 flutter run 1. 新增專案套件 (pubspec.yaml) 在 Flutter 專案根目錄下的 pubspec.yaml 中,新增 ...