#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
#include <ArduinoJson.h>
#include <SPI.h>
#include <MFRC522.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
#include <ESP32Servo.h>
// ==========================
// 1. Wi-Fi & Telegram 設定
// ==========================
const char* ssid = "Wokwi-GUEST";
const char* password = "";
//#define BOTtoken "YOUR_TELEGRAM_BOT_TOKEN"
//#define CHAT_ID "YOUR_TELEGRAM_CHAT_ID"
#define BOTtoken "838345708831:AAEuRr5eX54FRyv1m--jGK7gOCn8MiEsWXqpMT0" // 請替換為您的 Bot Token
#define CHAT_ID "379652418469" // 請替換為您的 Chat ID
WiFiClientSecure client;
UniversalTelegramBot bot(BOTtoken, client);
unsigned long lastBotCheckTime = 0;
const unsigned long BOT_MTBS = 1000; // Telegram 每秒輪詢一次
// ==========================
// 2. MFRC522 (SDA=5, SCK=18, MOSI=23, MISO=19, RST=4)
// ==========================
#define SS_PIN 5
#define RST_PIN 4
MFRC522 rfid(SS_PIN, RST_PIN);
const String whitelist[] = {
"01020304",
"11223344",
"55667788",
"AABBCCDD",
"04112233445566",
"C0FFEE99"
};
const int whitelistSize = sizeof(whitelist) / sizeof(whitelist[0]);
// ==========================
// 3. LCD I2C (SDA=21, SCL=22)
// ==========================
LiquidCrystal_I2C lcd(0x27, 16, 2);
// ==========================
// 4. 周邊腳位定義
// ==========================
#define BUZZER_PIN 15
#define BELL_PIN 34 // 門鈴按鈕
#define SERVO_PIN 2 // Servo 馬達控制腳位
Servo doorServo;
// ==========================
// 5. HC-SR04 超音波感測器 (TRIG=16, ECHO=17)
// ==========================
#define TRIG_PIN 16
#define ECHO_PIN 17
// ==========================
// 6. 4x4 Keypad 矩陣鍵盤
// ==========================
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {26, 25, 33, 32};
byte colPins[COLS] = {13, 12, 14, 27};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// ==========================
// 全域變數與非阻塞 Timer 管理
// ==========================
String inputBuffer = "";
bool isCardMode = true; // true: 標準/密碼模式, false: 工作證模式
unsigned long lastMotionTime = 0;
bool lcdBacklightOn = true;
bool lastBellState = HIGH;
// 非阻塞開鎖控制變數
bool doorIsOpen = false;
unsigned long doorOpenStartTime = 0;
const unsigned long DOOR_OPEN_DURATION = 3000; // 開門保持 3 秒
// 液晶清屏非阻塞控制
unsigned long lcdMessageTime = 0;
bool lcdNeedsReset = false;
// 函式宣告
void triggerDoorOpen();
void updateDoorState();
void playBeep(bool success);
void checkProximity();
void checkDoorbell();
void handleNewMessages(int numNewMessages);
void sendWelcomeMessage();
void sendTelegramMessage(String msg, String parseMode = "");
void setup() {
Serial.begin(115200);
delay(100);
Serial.println("\n==========================================");
Serial.println(" ESP32 門禁監控系統 啟動中 (Keypad優化版) ");
Serial.println("==========================================");
// 硬體腳位初始化
pinMode(BUZZER_PIN, OUTPUT);
pinMode(BELL_PIN, INPUT);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
// 馬達初始化
doorServo.attach(SERVO_PIN);
doorServo.write(0);
// LCD 初始化
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("System Starting");
// SPI & RFID 初始化
SPI.begin();
rfid.PCD_Init();
// 優化 Keypad 去彈跳與回應速度
keypad.setDebounceTime(10); // 去彈跳延遲降低至 10ms,提高反應靈敏度
keypad.setHoldTime(100);
// Wi-Fi 連線
WiFi.begin(ssid, password);
Serial.print("[WiFi] 正在連線至 ");
Serial.print(ssid);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\n[WiFi] 連線成功!IP: " + WiFi.localIP().toString());
client.setInsecure();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Access System");
lcd.setCursor(0, 1);
lcd.print("Ready...");
sendWelcomeMessage();
lastMotionTime = millis();
}
void loop() {
// 1. 馬達與 LCD 畫面非阻塞狀態更新(防止 delay 卡住鍵盤)
updateDoorState();
// 2. Telegram 訊息輪詢處理
if (millis() - lastBotCheckTime > BOT_MTBS) {
int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
while (numNewMessages) {
handleNewMessages(numNewMessages);
numNewMessages = bot.getUpdates(bot.last_message_received + 1);
}
lastBotCheckTime = millis();
}
// 3. 超音波感測與 LCD 省電控制
checkProximity();
// 4. 門鈴按鈕監測
checkDoorbell();
// 5. RFID 刷卡檢查
if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial()) {
String cardUID = "";
for (byte i = 0; i < rfid.uid.size; i++) {
if (rfid.uid.uidByte[i] < 0x10) cardUID += "0";
cardUID += String(rfid.uid.uidByte[i], HEX);
}
cardUID.toUpperCase();
Serial.println("\n[RFID] 刷卡 UID: " + cardUID);
bool authorized = false;
for (int i = 0; i < whitelistSize; i++) {
if (cardUID == whitelist[i]) {
authorized = true;
break;
}
}
if (authorized) {
Serial.println("[RFID] 驗證成功");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Card Access OK!");
lcd.setCursor(0, 1);
lcd.print("UID:" + cardUID.substring(0, 8));
playBeep(true);
sendTelegramMessage("✅ [刷卡成功] UID: " + cardUID + " 已開門。");
triggerDoorOpen();
} else {
Serial.println("[RFID] 未授權卡號");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Card Denied!");
lcd.setCursor(0, 1);
lcd.print("UID:" + cardUID.substring(0, 8));
playBeep(false);
sendTelegramMessage("⚠️ [刷卡失敗] 未授權 UID: " + cardUID);
lcdNeedsReset = true;
lcdMessageTime = millis();
}
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
lastMotionTime = millis();
}
// 6. Keypad 鍵盤輸入處理 (非阻塞高靈敏掃描)
char key = keypad.getKey();
if (key) {
Serial.print("[KEYPAD] 按下: ");
Serial.println(key);
lastMotionTime = millis();
if (!lcdBacklightOn) {
lcd.backlight();
lcdBacklightOn = true;
}
if (key == '#') {
inputBuffer = "";
isCardMode = false;
Serial.println("[KEYPAD] 切換至工作證模式");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ID Card Mode:");
lcd.setCursor(0, 1);
lcd.print("#");
} else if (key == '*') {
inputBuffer = "";
isCardMode = true;
Serial.println("[KEYPAD] 輸入已清除");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Input Cleared");
lcdNeedsReset = true;
lcdMessageTime = millis();
} else {
inputBuffer += key;
Serial.println("[KEYPAD] 當前輸入: " + inputBuffer);
lcd.setCursor(0, 1);
if (isCardMode) {
lcd.print("PIN: ");
for (int i = 0; i < inputBuffer.length(); i++) lcd.print("*");
} else {
lcd.print("#" + inputBuffer);
}
// 檢查 6 位密碼 (888888)
if (isCardMode && inputBuffer == "888888") {
Serial.println("[KEYPAD] 密碼正確 (888888)");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("PIN Verified!");
playBeep(true);
sendTelegramMessage("🔑 [密碼驗證] 6位數密碼 (888888) 正確,已開門。");
triggerDoorOpen();
inputBuffer = "";
isCardMode = true;
}
// 檢查 8 位密碼 / 工作證
else if (inputBuffer.length() == 8) {
if (isCardMode && inputBuffer == "88888888") {
Serial.println("[KEYPAD] 密碼正確 (88888888)");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("PIN Verified!");
playBeep(true);
sendTelegramMessage("🔑 [密碼驗證] 8位數密碼 (88888888) 正確,已開門。");
triggerDoorOpen();
} else if (!isCardMode) {
Serial.println("[KEYPAD] 工作證號碼: #" + inputBuffer);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Work ID Sent!");
playBeep(true);
sendTelegramMessage("🆔 [工作證登錄] 輸入號碼: #" + inputBuffer);
lcdNeedsReset = true;
lcdMessageTime = millis();
} else {
Serial.println("[KEYPAD] 密碼錯誤: " + inputBuffer);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Invalid PIN!");
playBeep(false);
sendTelegramMessage("❌ [密碼錯誤] 輸入內容: " + inputBuffer);
lcdNeedsReset = true;
lcdMessageTime = millis();
}
inputBuffer = "";
isCardMode = true;
}
}
}
}
// ==========================
// 輔助與非阻塞管理函式
// ==========================
// 觸發開門(設定標記與計時器,不阻塞 loop)
void triggerDoorOpen() {
doorServo.write(90);
doorIsOpen = true;
doorOpenStartTime = millis();
Serial.println("[ACTION] 門鎖已開啟 (Servo 90度)");
}
// 非阻塞檢查馬達與 LCD 自動復位
void updateDoorState() {
unsigned long now = millis();
// 馬達倒數 3 秒關閉
if (doorIsOpen && (now - doorOpenStartTime >= DOOR_OPEN_DURATION)) {
doorServo.write(0);
doorIsOpen = false;
Serial.println("[ACTION] 3秒倒數結束,門鎖自動關閉 (Servo 0度)");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Door Closed");
lcdNeedsReset = true;
lcdMessageTime = now;
}
// 提示訊息顯示 1.5 秒後自動還原提示畫面
if (lcdNeedsReset && (now - lcdMessageTime >= 1500)) {
lcdNeedsReset = false;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Access System");
lcd.setCursor(0, 1);
lcd.print("Ready...");
}
}
void sendTelegramMessage(String msg, String parseMode) {
Serial.println("[TG SEND] " + msg);
bot.sendMessage(CHAT_ID, msg, parseMode);
}
void sendWelcomeMessage() {
String message = "🤖 *ESP32 門禁系統已啟動!*\n\n";
message += "📋 *指令說明:*\n";
message += "🔹 `/open` 或 `/1` - 遠端開門 3 秒\n";
message += "🔹 `/status` - 查詢系統狀態\n";
message += "🔹 `/help` - 重新發送選單";
sendTelegramMessage(message, "Markdown");
}
void playBeep(bool success) {
if (success) {
tone(BUZZER_PIN, 2000, 150);
} else {
tone(BUZZER_PIN, 400, 300);
}
}
void checkProximity() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH, 20000); // 縮短超音波 timeout 至 20ms 防止拖慢掃描
float distance = (duration * 0.0343) / 2;
if (distance > 0 && distance < 50) {
lastMotionTime = millis();
if (!lcdBacklightOn) {
lcd.backlight();
lcdBacklightOn = true;
}
}
if (lcdBacklightOn && (millis() - lastMotionTime > 10000)) {
lcd.noBacklight();
lcdBacklightOn = false;
}
}
void checkDoorbell() {
bool currentBellState = digitalRead(BELL_PIN);
if (lastBellState == HIGH && currentBellState == LOW) {
Serial.println("\n[HARDWARE] 門鈴按下");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Doorbell Ring!");
playBeep(true);
sendTelegramMessage("🔔 [門鈴通知] 有訪客按下門鈴!");
lastMotionTime = millis();
lcdNeedsReset = true;
lcdMessageTime = millis();
}
lastBellState = currentBellState;
}
void handleNewMessages(int numNewMessages) {
for (int i = 0; i < numNewMessages; i++) {
String text = bot.messages[i].text;
String from_name = bot.messages[i].from_name;
Serial.println("[TG RECV] " + from_name + ": " + text);
if (text == "/open" || text == "/1" || text == "open") {
sendTelegramMessage("🔓 收到 Telegram 指令,正在開門...");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Telegram Open");
playBeep(true);
triggerDoorOpen();
} else if (text == "/status") {
String statusMsg = "📊 [系統狀態]\n- Wi-Fi: 已連線\n- 門鎖: " + String(doorIsOpen ? "開啟中" : "鎖定中");
sendTelegramMessage(statusMsg);
} else if (text == "/start" || text == "/help") {
sendWelcomeMessage();
}
}
}