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







沒有留言:
張貼留言