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");

        }

    }

}




沒有留言:

張貼留言

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