2025年12月31日 星期三

Telegram 送出命令 -->Node-Red+ SQLite -->MQTT -->WOKWI ESP32 RFID + LED

  Telegram 送出命令 -->Node-Red+ SQLite -->MQTT -->WOKWI ESP32 RFID + LED








wokwi程式

#include <SPI.h>
#include <MFRC522.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// --- 硬體腳位 (您的指定) ---
#define SS_PIN    5
#define RST_PIN   22
#define LED_PIN   2
#define I2C_SDA   17
#define I2C_SCL   16

// --- 設定區 ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";

// MQTT 設定 (與您的 Python Tkinter 程式對接)
const char* mqtt_server = "broker.mqtt-dashboard.com";
const char* TOPIC_RFID_UID = "alex9ufo/rfid/UID";
const char* TOPIC_LED_CONTROL = "alex9ufo/rfid/led";
const char* TOPIC_LED_STATUS = "alex9ufo/rfid/ledStatus";

// 硬體物件
LiquidCrystal_I2C lcd(0x27, 16, 2);
MFRC522 mfrc522(SS_PIN, RST_PIN);
WiFiClient espClient;
PubSubClient mqttClient(espClient);

// --- FreeRTOS 隊列 ---
QueueHandle_t rfidQueue;
struct RfidMsg { char uid[20]; };

// 全域變數
bool isFlashing = false;

// --- MQTT 接收處理 (來自 Python 控制台) ---
void mqttCallback(char* topic, byte* payload, unsigned int length) {
  String message = "";
  for (int i = 0; i < length; i++) message += (char)payload[i];
 
  Serial.printf("\n[MQTT CMD]: %s\n", message.c_str());
 
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("MQTT Command:");
  lcd.setCursor(0, 1);

  isFlashing = false;
  if (message == "on") {
    digitalWrite(LED_PIN, HIGH);
    lcd.print("LED: ON");
    mqttClient.publish(TOPIC_LED_STATUS, "ON");
  }
  else if (message == "off") {
    digitalWrite(LED_PIN, LOW);
    lcd.print("LED: OFF");
    mqttClient.publish(TOPIC_LED_STATUS, "OFF");
  }
  else if (message == "flash") {
    isFlashing = true;
    lcd.print("MODE: FLASHING");
    mqttClient.publish(TOPIC_LED_STATUS, "FLASHING");
  }
  else if (message == "timer") {
    digitalWrite(LED_PIN, HIGH);
    lcd.print("TIMER: 5 SEC");
    mqttClient.publish(TOPIC_LED_STATUS, "TIMER_START");
    vTaskDelay(5000 / portTICK_PERIOD_MS); // 在 Task 中使用 vTaskDelay 不會卡死整個系統
    digitalWrite(LED_PIN, LOW);
    mqttClient.publish(TOPIC_LED_STATUS, "OFF");
  }
}

// --- Core 0: 負責 WiFi 與 MQTT 通訊 ---
void mqttTask(void *pvParameters) {
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { vTaskDelay(500 / portTICK_PERIOD_MS); }
 
  mqttClient.setServer(mqtt_server, 1883);
  mqttClient.setCallback(mqttCallback);

  RfidMsg rMsg;
  while (true) {
    // 維護 MQTT 連線
    if (!mqttClient.connected()) {
      Serial.print("Connecting to MQTT...");
      if (mqttClient.connect("ESP32_RFID_Gate_NoTG")) {
        Serial.println("Connected");
        mqttClient.subscribe(TOPIC_LED_CONTROL);
      } else {
        vTaskDelay(5000 / portTICK_PERIOD_MS);
      }
    }
    mqttClient.loop();

    // 接收來自 Core 1 的 RFID 訊息並發送到 MQTT
    if (xQueueReceive(rfidQueue, &rMsg, 0) == pdPASS) {
      mqttClient.publish(TOPIC_RFID_UID, rMsg.uid);
      Serial.printf("Sent UID to Python: %s\n", rMsg.uid);
    }

    // 處理閃爍邏輯
    if (isFlashing) {
      digitalWrite(LED_PIN, !digitalRead(LED_PIN));
      vTaskDelay(300 / portTICK_PERIOD_MS);
    }
   
    vTaskDelay(10 / portTICK_PERIOD_MS);
  }
}

// --- Core 1: 專門負責 RFID 掃描 (不處理網路) ---
void rfidTask(void *pvParameters) {
  SPI.begin();
  mfrc522.PCD_Init();
  RfidMsg rMsg;
  while (true) {
    if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
      String uidStr = "";
      for (byte i = 0; i < mfrc522.uid.size; i++) {
        uidStr += (mfrc522.uid.uidByte[i] < 0x10 ? "0" : "");
        uidStr += String(mfrc522.uid.uidByte[i], HEX);
      }
      uidStr.toUpperCase();
     
      // 更新 LCD 顯示
      lcd.clear();
      lcd.setCursor(0, 0); lcd.print("RFID Detected!");
      lcd.setCursor(0, 1); lcd.print("ID: " + uidStr);
     
      // 將卡號打包送入隊列,交給 Core 0 發送
      uidStr.toCharArray(rMsg.uid, 20);
      xQueueSend(rfidQueue, &rMsg, portMAX_DELAY);

      mfrc522.PICC_HaltA();
      mfrc522.PCD_StopCrypto1();
    }
    vTaskDelay(200 / portTICK_PERIOD_MS);
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
 
  // 初始化 I2C LCD
  Wire.begin(I2C_SDA, I2C_SCL);
  lcd.init();
  lcd.backlight();
  lcd.print("MQTT Connecting...");

  // 建立隊列
  rfidQueue = xQueueCreate(10, sizeof(RfidMsg));

  if (rfidQueue != NULL) {
    // 建立雙核心任務
    xTaskCreatePinnedToCore(mqttTask, "MQTT_Task", 8192, NULL, 1, NULL, 0);
    xTaskCreatePinnedToCore(rfidTask, "RFID_Task", 4096, NULL, 1, NULL, 1);
  }
}

void loop() {
  // FreeRTOS 架構下 loop 不需執行內容
  vTaskDelay(portMAX_DELAY);
}

Node-Red程式

https://alex9ufoexploer.blogspot.com/2025/10/node-red.html

Node-RED安裝與啟動 與 節點安裝 範例


Node-Red程式

[{"id":"ca7e01ca8654162f","type":"telegram receiver","z":"448157ebc5541371","name":"","bot":"457874f8aa8a857a","saveDataDir":"","filterCommands":true,"x":110,"y":40,"wires":[["9e865f658c3f6af9"],[]]},{"id":"dc882fef65c76133","type":"function","z":"448157ebc5541371","name":"根據指令發佈 MQTT 訊息","func":"if (msg.payload === \"/on\") {\n    msg.topic = \"alex9ufo/rfid/led\";\n    msg.payload = \"on\";  // 開啟綠色 LED\n} else if (msg.payload === \"/off\") {\n    msg.topic = \"alex9ufo/rfid/led\";\n    msg.payload = \"off\"; // 關閉 LED\n} else if (msg.payload === \"/flash\") {\n    msg.topic = \"alex9ufo/rfid/led\";\n    msg.payload = \"flash\"; // LED 交替閃爍\n} else if (msg.payload === \"/timer\") {\n    msg.topic = \"alex9ufo/rfid/led\";\n    msg.payload = \"timer\"; // LED 定時 10 秒後關閉\n}\nreturn msg;\n//function 根據指令來發佈 MQTT 訊息","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":470,"y":40,"wires":[["7ec09b4c30e9b6cf"]]},{"id":"7ec09b4c30e9b6cf","type":"mqtt out","z":"448157ebc5541371","name":"","topic":"alex9ufo/rfid/led","qos":"1","retain":"true","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"b9efc827e98bf7f9","x":680,"y":40,"wires":[]},{"id":"13a8d28ef336440f","type":"mqtt in","z":"448157ebc5541371","name":"","topic":"alex9ufo/rfid/led","qos":"2","datatype":"auto-detect","broker":"b9efc827e98bf7f9","nl":false,"rap":true,"rh":0,"inputs":0,"x":100,"y":160,"wires":[["6610796333c1f034","67311733e039e4b1","e9e6a74a585f3b6c"]]},{"id":"9e865f658c3f6af9","type":"function","z":"448157ebc5541371","name":"function","func":"var content= msg.payload.content;\nmsg.payload=content;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":280,"y":40,"wires":[["dc882fef65c76133"]]},{"id":"2adb7ad76def92ca","type":"ui_led","z":"448157ebc5541371","order":1,"group":"5ed27e92f756bcf4","width":3,"height":2,"label":"","labelPlacement":"left","labelAlignment":"left","colorForValue":[{"color":"#ff0000","value":"false","valueType":"bool"},{"color":"#008000","value":"true","valueType":"bool"}],"allowColorForValueInMessage":false,"shape":"circle","showGlow":true,"name":"","x":750,"y":160,"wires":[]},{"id":"6610796333c1f034","type":"function","z":"448157ebc5541371","name":"function","func":"var content = msg.payload;\n\nif (content === 'on') {\n    msg.payload = true;\n    // Route to output 1\n    return [msg, null, null, null];\n}\n\nif (content === 'off') {\n    msg.payload = false;\n    // Route to output 2\n    return [null, msg, null, null];\n}\n\nif (content === 'flash') {\n    msg.payload = true;\n    // Route to output 3\n    return [null, null, msg, null];\n}\n\nif (content === 'timer') {\n    msg.payload = true;\n    // Route to output 4\n    return [null, null, null, msg]; // Changed to route 'timer' to the 4th output\n}\n\n// Optional: If none of the conditions are met, you can return 'null' to discard the message\n// or return [msg] to pass the original message through the first output.\n// For this example, we'll return null to stop the message.\nreturn null;","outputs":4,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":280,"y":180,"wires":[["2adb7ad76def92ca","48b63713aaa717b1"],["2adb7ad76def92ca","48b63713aaa717b1"],["8e4c7be415272248","48b63713aaa717b1"],["b340bf7e95798e33","2adb7ad76def92ca","48b63713aaa717b1"]]},{"id":"b340bf7e95798e33","type":"delay","z":"448157ebc5541371","name":"","pauseType":"delay","timeout":"10","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":400,"y":320,"wires":[["3f72babf6b7bde78"]]},{"id":"8e4c7be415272248","type":"delay","z":"448157ebc5541371","name":"","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":450,"y":220,"wires":[["2adb7ad76def92ca","7c61b065db9a43b5"]]},{"id":"3f72babf6b7bde78","type":"function","z":"448157ebc5541371","name":"function","func":"msg.payload=false;\nreturn msg;\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":540,"y":320,"wires":[["2adb7ad76def92ca"]]},{"id":"48b63713aaa717b1","type":"debug","z":"448157ebc5541371","name":"debug 363","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":510,"y":140,"wires":[]},{"id":"ce3cb5da10fc8fde","type":"function","z":"448157ebc5541371","name":"function","func":"msg.payload = !msg.payload;\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":540,"y":280,"wires":[["8e4c7be415272248"]]},{"id":"66070cb0acfb18ce","type":"mqtt in","z":"448157ebc5541371","name":"ledStatus","topic":"alex9ufo/rfid/ledStatus","qos":"1","datatype":"auto-detect","broker":"b9efc827e98bf7f9","nl":false,"rap":true,"rh":0,"inputs":0,"x":80,"y":420,"wires":[["a2a05e599a1c2803"]]},{"id":"a2a05e599a1c2803","type":"function","z":"448157ebc5541371","name":"根據指令發佈 MQTT 訊息","func":"\nif (msg.payload === \"ON\") {\n    msg.payload = \"LED Status: on\";  // 開啟綠色 LED\n} else if (msg.payload === \"OFF\") {\n    msg.payload = \"LED Status: off\"; // 關閉 LED\n} else if (msg.payload === \"FLASH\") {\n    msg.payload = \"LED Status: flash\"; // LED 交替閃爍\n} else if (msg.payload === \"TIMER\") {\n    msg.payload = \"LED Status: timer\"; // LED 定時 10 秒後關閉\n}\nreturn msg;\n//function 根據指令來發佈 MQTT 訊息","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":420,"wires":[["cf9ed47acf13328f","ccd143a24325d0cb"]]},{"id":"3b246f02576fb2c4","type":"telegram sender","z":"448157ebc5541371","name":"","bot":"457874f8aa8a857a","haserroroutput":false,"outputs":1,"x":710,"y":420,"wires":[[]]},{"id":"8623eccb1262afa5","type":"mqtt out","z":"448157ebc5541371","name":"ledStatus","topic":"alex9ufo/rfid/ledStatus","qos":"1","retain":"true","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"b9efc827e98bf7f9","x":540,"y":360,"wires":[]},{"id":"67311733e039e4b1","type":"function","z":"448157ebc5541371","name":"根據指令發佈 MQTT 訊息","func":"if (msg.payload === \"on\") {\n    msg.topic = \"alex9ufo/rfid/ledStatus\";\n    msg.payload = \"ON\";  // 開啟綠色 LED\n} else if (msg.payload === \"off\") {\n    msg.topic = \"alex9ufo/rfid/ledStatus\";\n    msg.payload = \"OFF\"; // 關閉 LED\n} else if (msg.payload === \"flash\") {\n    msg.topic = \"alex9ufo/rfid/ledStatus\";\n    msg.payload = \"FLASH\"; // LED 交替閃爍\n} else if (msg.payload === \"timer\") {\n    msg.topic = \"alex9ufo/rfid/ledStatus\";\n    msg.payload = \"TIMER\"; // LED 定時 10 秒後關閉\n}\nreturn msg;\n//function 根據指令來發佈 MQTT 訊息","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":360,"wires":[["8623eccb1262afa5"]]},{"id":"cf9ed47acf13328f","type":"debug","z":"448157ebc5541371","name":"debug 365","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":490,"y":460,"wires":[]},{"id":"ccd143a24325d0cb","type":"template","z":"448157ebc5541371","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"chatId\": 7965218469,\n\"type\":\"message\",\n\"content\":\"{{payload}}\"}","output":"json","x":550,"y":420,"wires":[["3b246f02576fb2c4"]]},{"id":"e9e6a74a585f3b6c","type":"function","z":"448157ebc5541371","name":"global.set","func":"var ledstatus = msg.payload;\n\n// 2. 將值設定為 Global 變數\nglobal.set(\"myledStatus\", ledstatus);\n\n// 3. 返回訊息,以便流程繼續\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":280,"y":120,"wires":[[]]},{"id":"7c61b065db9a43b5","type":"function","z":"448157ebc5541371","name":"global.get","func":"// 1. 取得 Global 變數的值\n//global.set(\"myledStatus\", ledstatus);\nvar myValue = global.get(\"myledStatus\");\nif (myValue=='flash'){\n    return [msg,null];\n    }\nelse    \n    return [true,true];","outputs":2,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":400,"y":280,"wires":[["ce3cb5da10fc8fde"],[]]},{"id":"a100deb0624e7c6b","type":"sqlite","z":"448157ebc5541371","mydb":"04085aab75f5ad41","sqlquery":"msg.topic","sql":"","name":"RFID","x":530,"y":780,"wires":[["1184a6ff8873cab5"]]},{"id":"ccec8a03b3ed4d2e","type":"function","z":"448157ebc5541371","name":"組合刪除一筆 SQL","func":"var id = flow.get('delete_id') || msg.payload;\nif(!id) return null;\nmsg.topic = `DELETE FROM rfid_logs WHERE id= $id`;\nmsg.payload = [id];\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":270,"y":700,"wires":[["640e917b338f3959","062d62b2355724f6"]]},{"id":"717f1b8d1458c3d6","type":"function","z":"448157ebc5541371","name":"組合新增 SQL","func":"var memo, LEDorRFID ;\n\nif (msg.payload === \"ON\") {\n    LEDorRFID = \"ON\";\n    memo = \"LED Status: on\";  // 開啟綠色 LED\n\n} else if (msg.payload === \"OFF\") {\n    LEDorRFID = \"OFF\";\n    memo = \"LED Status: off\"; // 關閉 LED\n} else if (msg.payload === \"FLASH\") {\n    LEDorRFID = \"FLASH\";\n    memo = \"LED Status: flash\"; // LED 交替閃爍\n} else if (msg.payload === \"TIMER\") {\n    LEDorRFID = \"TIMER\";\n    memo = \"LED Status: timer\"; // LED 定時 10 秒後關閉\n}\n\n\n\n// 建立一個 Date 物件,它包含運行 Node-RED 的電腦的當前時間\nvar now = new Date();\n// --- 提取日期時間的不同格式 ---\n// 1. 完整的 ISO 格式 (例如: 2025-10-25T03:28:08.500Z)\nvar isoString = now.toISOString();\n\n// 2. YYYY-MM-DD (日期部分)\n// slice(0, 10) 會擷取 ISO 字串的前 10 個字元\nvar dateOnly = now.toISOString().slice(0, 10); \n\n// 3. HH:MM:SS (時間部分)\n// toTimeString() 輸出類似 \"11:28:08 GMT+0800 (CST)\" 的字串\n// slice(0, 8) 會擷取開頭的 HH:MM:SS\nvar timeOnly = now.toTimeString().slice(0, 8);\n\n\nmsg.topic = \"INSERT INTO rfid_logs (date, time ,LEDorRFID,memo ) VALUES ($dateOnly, $timeOnly ,$LEDorRFID,$memo)\";\nmsg.payload = [dateOnly, timeOnly, LEDorRFID, memo]\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":260,"y":640,"wires":[["c6538f6c363c5177"]]},{"id":"c6538f6c363c5177","type":"sqlite","z":"448157ebc5541371","mydb":"04085aab75f5ad41","sqlquery":"msg.topic","sql":"","name":"RFID","x":430,"y":640,"wires":[["4077e1c59441a301","062d62b2355724f6"]]},{"id":"4077e1c59441a301","type":"debug","z":"448157ebc5541371","name":"debug ","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":590,"y":640,"wires":[]},{"id":"267b3bad3a0b05c1","type":"debug","z":"448157ebc5541371","name":"debug ","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":590,"y":700,"wires":[]},{"id":"640e917b338f3959","type":"sqlite","z":"448157ebc5541371","mydb":"04085aab75f5ad41","sqlquery":"msg.topic","sql":"","name":"RFID","x":450,"y":700,"wires":[["267b3bad3a0b05c1","062d62b2355724f6"]]},{"id":"b8ff3277ea021729","type":"ui_button","z":"448157ebc5541371","name":"刪除一筆","group":"729b8cad8d9aa5ca","order":6,"width":3,"height":1,"passthru":false,"label":"刪除一筆","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"","topicType":"str","x":80,"y":700,"wires":[["ccec8a03b3ed4d2e"]]},{"id":"1223aa8e351649aa","type":"ui_button","z":"448157ebc5541371","name":"顯示最新50筆","group":"729b8cad8d9aa5ca","order":3,"width":3,"height":1,"passthru":false,"label":"顯示全部設定","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"","topicType":"str","x":100,"y":780,"wires":[["062d62b2355724f6"]]},{"id":"062d62b2355724f6","type":"function","z":"448157ebc5541371","name":"組合顯示所有SQL","func":"\nmsg.topic = \"SELECT id, date, time, LEDorRFID, memo FROM rfid_logs ORDER BY id DESC LIMIT 50\";\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":780,"wires":[["a100deb0624e7c6b"]]},{"id":"1184a6ff8873cab5","type":"ui_table","z":"448157ebc5541371","group":"729b8cad8d9aa5ca","name":"設定資料表","order":7,"width":9,"height":9,"columns":[{"field":"id","title":"ID","width":"15%","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"date","title":"星期","width":"20%","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"time","title":"時間","width":"20%","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"LEDorRFID","title":"LED或RFID","width":"30%","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"memo","title":"備註","width":"30%","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}}],"outputs":0,"cts":false,"x":730,"y":780,"wires":[]},{"id":"9d44b2fe693cc821","type":"ui_dropdown","z":"448157ebc5541371","name":"模式選擇","label":"控制模式","tooltip":"","place":"","group":"5ed27e92f756bcf4","order":4,"width":6,"height":1,"passthru":true,"multiple":false,"options":[{"label":"新增","value":"create","type":"str"},{"label":"比對","value":"verify","type":"str"}],"payload":"","topic":"mode","topicType":"str","className":"","x":80,"y":840,"wires":[["104303cbaa7163f4"]]},{"id":"104303cbaa7163f4","type":"function","z":"448157ebc5541371","name":"暫存模式","func":"flow.set('mode', msg.payload);\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":260,"y":840,"wires":[["6a18874cf8db9321"]]},{"id":"6a18874cf8db9321","type":"debug","z":"448157ebc5541371","name":"debug  ","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":410,"y":840,"wires":[]},{"id":"61290d7a37f46f8a","type":"inject","z":"448157ebc5541371","name":"建立資料庫","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":100,"y":900,"wires":[["db16ac459547969f"]]},{"id":"db16ac459547969f","type":"function","z":"448157ebc5541371","name":"建立資料庫 SQL","func":"msg.topic = `CREATE TABLE IF NOT EXISTS rfid_logs (   id INTEGER PRIMARY KEY AUTOINCREMENT,  \n                date TEXT NOT NULL,\n                time TEXT NOT NULL,\n                LEDorRFID TEXT,\n                memo TEXT )`;\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":280,"y":900,"wires":[["75209616c5b3e25c"]]},{"id":"75209616c5b3e25c","type":"sqlite","z":"448157ebc5541371","mydb":"04085aab75f5ad41","sqlquery":"msg.topic","sql":"","name":"RFID","x":430,"y":900,"wires":[["996edd23a89ff222"]]},{"id":"996edd23a89ff222","type":"debug","z":"448157ebc5541371","name":"debug  ","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":550,"y":900,"wires":[]},{"id":"a4700ec6830e4740","type":"ui_button","z":"448157ebc5541371","name":"建立資料庫","group":"729b8cad8d9aa5ca","order":1,"width":3,"height":1,"passthru":false,"label":"建立資料庫","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"","topicType":"str","x":90,"y":940,"wires":[["db16ac459547969f"]]},{"id":"0b06ebd430fedead","type":"ui_button","z":"448157ebc5541371","name":"查詢一筆","group":"729b8cad8d9aa5ca","order":5,"width":3,"height":1,"passthru":false,"label":"查詢一筆","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"","topicType":"str","x":80,"y":560,"wires":[["22050570edbba3d8"]]},{"id":"22050570edbba3d8","type":"function","z":"448157ebc5541371","name":"組合查詢一筆 SQL","func":"var id = flow.get('delete_id') || msg.payload;\nif(!id) return null;\nmsg.topic = `SELECT id, date, time, LEDorRFID, memo FROM rfid_logs  WHERE id= $id`;\nmsg.payload = [id];\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":270,"y":560,"wires":[["8f85e8703e7b2348"]]},{"id":"8f85e8703e7b2348","type":"sqlite","z":"448157ebc5541371","mydb":"04085aab75f5ad41","sqlquery":"msg.topic","sql":"","name":"RFID","x":430,"y":560,"wires":[["1184a6ff8873cab5"]]},{"id":"8111ca8ecf61f32d","type":"mqtt in","z":"448157ebc5541371","name":"ledStatus","topic":"alex9ufo/rfid/ledStatus","qos":"1","datatype":"auto-detect","broker":"b9efc827e98bf7f9","nl":false,"rap":true,"rh":0,"inputs":0,"x":80,"y":640,"wires":[["717f1b8d1458c3d6"]]},{"id":"4035c5171ccf55a1","type":"function","z":"448157ebc5541371","name":"function   flow.set","func":"var idno =msg.payload;\nflow.set('delete_id',idno)\nmsg.payload= idno;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":260,"y":500,"wires":[["5f9fe616a7cef33e"]]},{"id":"da635fbb00a8997c","type":"ui_button","z":"448157ebc5541371","name":"刪除資料庫","group":"729b8cad8d9aa5ca","order":2,"width":3,"height":1,"passthru":false,"label":"刪除資料庫","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"","topicType":"str","x":90,"y":980,"wires":[["ca9c2e1244cf72f3"]]},{"id":"ca9c2e1244cf72f3","type":"function","z":"448157ebc5541371","name":"刪除資料庫 SQL","func":"msg.topic = `DROP TABLE IF EXISTS rfid_logs`;\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":280,"y":980,"wires":[["75209616c5b3e25c"]]},{"id":"5f9fe616a7cef33e","type":"debug","z":"448157ebc5541371","name":"debug 368","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":450,"y":500,"wires":[]},{"id":"bfca293065dbd0c9","type":"ui_numeric","z":"448157ebc5541371","name":"","label":"ID","tooltip":"","group":"729b8cad8d9aa5ca","order":4,"width":3,"height":1,"wrap":false,"passthru":true,"topic":"topic","topicType":"msg","format":"{{value}}","min":0,"max":"999999","step":1,"className":"","x":70,"y":500,"wires":[["4035c5171ccf55a1"]]},{"id":"85be98c91e459c6f","type":"telegram sender","z":"448157ebc5541371","name":"","bot":"457874f8aa8a857a","haserroroutput":false,"outputs":1,"x":1010,"y":1180,"wires":[[]]},{"id":"9e6f82181faf4bba","type":"inject","z":"448157ebc5541371","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":100,"y":1180,"wires":[["322c7cefeb6da2ef"]]},{"id":"256f3431cc5d9a01","type":"template","z":"448157ebc5541371","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\"chatId\": 7965218469,\n\"type\":\"message\",\n\"Parse Mode\": \"Markdown\",\n\"content\":\"{{payload}}\"}\n","output":"json","x":810,"y":1180,"wires":[["85be98c91e459c6f"]]},{"id":"322c7cefeb6da2ef","type":"function","z":"448157ebc5541371","name":"啟動時發送 Help 訊息給Telegram","func":"msg.payload = `啟動時發送 Help 訊息給 Telegram\n🎉 程式已啟動!\n\n可用命令: (所有命令前方都加上 斜線)\non - 開啟 LED (綠色) \noff - 關閉 LED (紅色)\nflash - LED 閃爍 (紅綠交替)\ntimer - LED 開啟 10 秒後自動關閉\nmode - 切換模式 (新增/比對)\nstatus - 顯示當前模式及最新記錄`;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":340,"y":1180,"wires":[["256f3431cc5d9a01"]]},{"id":"22dbd6f0dba2137a","type":"mqtt in","z":"448157ebc5541371","name":"","topic":"alex9ufo/rfid/UID","qos":"1","datatype":"auto-detect","broker":"b9efc827e98bf7f9","nl":false,"rap":true,"rh":0,"inputs":0,"x":100,"y":1100,"wires":[["be52afa039a5e931","f94d2439fd60b7d7"]]},{"id":"be52afa039a5e931","type":"function","z":"448157ebc5541371","name":"[新增]卡號號碼","func":"var mode = flow.get('mode');\nflow.set('uid', msg.payload);\nvar uid=msg.payload;\nif (mode=='create'){\n    msg.payload= '[新增]卡號號碼'+ uid;\n    return msg;\n}\nreturn null;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":280,"y":1100,"wires":[["256f3431cc5d9a01","5eab10d3417ccc67","51bd7abf40e987f8"]]},{"id":"f1436aa08fb9f73e","type":"sqlite","z":"448157ebc5541371","mydb":"04085aab75f5ad41","sqlquery":"msg.topic","sql":"","name":"RFID","x":690,"y":1100,"wires":[["c6f6859106efe540"]]},{"id":"5eab10d3417ccc67","type":"function","z":"448157ebc5541371","name":"組合新增 SQL","func":"var memo, LEDorRFID ;\nLEDorRFID = flow.get('uid');\nmemo =msg.payload;\n\n// 建立一個 Date 物件,它包含運行 Node-RED 的電腦的當前時間\nvar now = new Date();\n// --- 提取日期時間的不同格式 ---\n// 1. 完整的 ISO 格式 (例如: 2025-10-25T03:28:08.500Z)\nvar isoString = now.toISOString();\n\n// 2. YYYY-MM-DD (日期部分)\n// slice(0, 10) 會擷取 ISO 字串的前 10 個字元\nvar dateOnly = now.toISOString().slice(0, 10); \n\n// 3. HH:MM:SS (時間部分)\n// toTimeString() 輸出類似 \"11:28:08 GMT+0800 (CST)\" 的字串\n// slice(0, 8) 會擷取開頭的 HH:MM:SS\nvar timeOnly = now.toTimeString().slice(0, 8);\n\n\nmsg.topic = \"INSERT INTO rfid_logs (date, time ,LEDorRFID,memo ) VALUES ($dateOnly, $timeOnly ,$LEDorRFID,$memo)\";\nmsg.payload = [dateOnly, timeOnly, LEDorRFID, memo]\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":500,"y":1100,"wires":[["f1436aa08fb9f73e"]]},{"id":"c6f6859106efe540","type":"link out","z":"448157ebc5541371","name":"link out 78","mode":"link","links":["0a89297ad75019f9"],"x":795,"y":1100,"wires":[]},{"id":"0a89297ad75019f9","type":"link in","z":"448157ebc5541371","name":"link in 72","links":["c6f6859106efe540"],"x":235,"y":800,"wires":[["062d62b2355724f6"]]},{"id":"51bd7abf40e987f8","type":"ui_text","z":"448157ebc5541371","group":"5ed27e92f756bcf4","order":5,"width":0,"height":0,"name":"","label":"","format":"{{msg.payload}}","layout":"row-left","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":470,"y":1060,"wires":[]},{"id":"b8a8f3e9e8dc1b42","type":"inject","z":"448157ebc5541371","name":"查詢所有 UID","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":110,"y":1300,"wires":[["f94d2439fd60b7d7"]]},{"id":"f94d2439fd60b7d7","type":"function","z":"448157ebc5541371","name":"建立 SQL 查詢","func":"// Function Name: Get All Stored UIDs Query Generator\nvar mode = flow.get('mode');\nvar uid = msg.payload;\nflow.set('uid', msg.payload);\n\nif (mode == 'verify') {\n    msg.topic = \"SELECT * FROM rfid_logs WHERE LEDorRFID = $uid LIMIT 1\";\n    msg.payload = [uid]\n}\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":1300,"wires":[["6e0a917b63964b1e"]]},{"id":"6e0a917b63964b1e","type":"sqlite","z":"448157ebc5541371","mydb":"04085aab75f5ad41","sqlquery":"msg.topic","sql":"","name":"查詢 rfid_logs","x":480,"y":1300,"wires":[["9052d8c7127959db","d245523071a2381a"]]},{"id":"a41300479afbd0a2","type":"debug","z":"448157ebc5541371","name":"debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":790,"y":1380,"wires":[]},{"id":"1b225619fab2f4db","type":"ui_text","z":"448157ebc5541371","group":"5ed27e92f756bcf4","order":5,"width":0,"height":0,"name":"","label":"","format":"{{msg.payload}}","layout":"row-left","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":1050,"y":1240,"wires":[]},{"id":"9052d8c7127959db","type":"function","z":"448157ebc5541371","name":"(Result Processor)","func":"// Function Name: Convert DB Results to UID List\n\nvar dbResults = msg.payload;\nvar uidList = [];\n\nif (Array.isArray(dbResults)) {\n    // 遍歷結果陣列,提取每個物件的 LEDorRFID 值\n    uidList = dbResults.map(function (row) {\n        // 確保 row[0] 或 row.LEDorRFID 存在\n        if (row && row.LEDorRFID !== undefined) {\n            return row.LEDorRFID;\n        }\n        return null;\n    }).filter(function (uid) {\n        // 過濾掉 null 值 (雖然在 SQL 查詢中 LEDorRFID 應該不會是 NULL)\n        return uid !== null;\n    });\n} else {\n    // 處理非預期的錯誤\n    node.error(\"SQLite node did not return an array. Check DB connection.\", msg);\n    // 返回空列表\n    uidList = [];\n}\n\n// 將轉換後的 UID 列表存入 msg.payload\nmsg.payload = uidList;\n\n// 可以在 msg.status 中儲存操作狀態 (模擬 Python 的 True, results)\nmsg.status = (Array.isArray(dbResults));\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":670,"y":1300,"wires":[["a41300479afbd0a2","02d3a50734c8ba53"]]},{"id":"02d3a50734c8ba53","type":"function","z":"448157ebc5541371","name":"Status Checker","func":"// Function Name: Status Checker and Formatter (已修正)\n// 假設要比對的 UID 儲存在 msg.uid 中\n\nvar dbResults = msg.payload;\nvar outputUid = flow.get('uid');\n// 1. 檢查 msg.payload 是否為非空陣列\nif (Array.isArray(dbResults) && dbResults.length > 0) {\n    // 成功:找到至少一筆匹配的記錄\n    msg.payload = '比對正確 卡號:' + outputUid;\n\n    // 輸出到第一個埠 (Port 1: 成功)\n    return [msg, null]; \n    \n} else {\n    // 失敗:未找到匹配的記錄 (msg.payload 為空陣列或其他非陣列/空值)\n    \n    msg.payload = '比對錯誤';\n\n    // 輸出到第二個埠 (Port 2: 錯誤)\n    // 如果 Function 節點只有一個輸出埠,請使用 return [msg];\n    return [null, msg]; \n}\n\n// 注意:請確保此 Function 節點有兩個輸出埠 (Port 1 和 Port 2)","outputs":2,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":860,"y":1280,"wires":[["1b225619fab2f4db","256f3431cc5d9a01"],["1b225619fab2f4db","256f3431cc5d9a01"]]},{"id":"d245523071a2381a","type":"debug","z":"448157ebc5541371","name":"debug  ","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":650,"y":1260,"wires":[]},{"id":"457874f8aa8a857a","type":"telegram bot","botname":"@alextest999_bot","usernames":"","chatids":"7965218469","baseapiurl":"","testenvironment":false,"updatemode":"polling","pollinterval":"300","usesocks":false,"sockshost":"","socksprotocol":"socks5","socksport":"6667","socksusername":"anonymous","sockspassword":"","bothost":"","botpath":"","localbothost":"0.0.0.0","localbotport":"8443","publicbotport":"8443","privatekey":"","certificate":"","useselfsignedcertificate":false,"sslterminated":false,"verboselogging":false},{"id":"b9efc827e98bf7f9","type":"mqtt-broker","name":"broker.mqtt-dashboard.com","broker":"broker.mqtt-dashboard.com","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closeRetain":"false","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"5ed27e92f756bcf4","type":"ui_group","name":"控制台","tab":"955b4de83b0e88a3","order":1,"disp":true,"width":"6","collapse":false,"className":""},{"id":"04085aab75f5ad41","type":"sqlitedb","db":"rfid_node-red.db","mode":"RWC"},{"id":"729b8cad8d9aa5ca","type":"ui_group","name":"資料表","tab":"955b4de83b0e88a3","order":2,"disp":true,"width":9,"collapse":false,"className":""},{"id":"955b4de83b0e88a3","type":"ui_tab","name":"RFID 控制","icon":"dashboard","order":1}]

Telegram 送出命令 --> Python +TKinter -->MQTT -->WOKWI ESP32 RFID + LED

 Telegram 送出命令 --> Python +TKinter -->MQTT -->WOKWI ESP32 RFID + LED


// MQTT 設定 (與您的 Python Tkinter 程式對接)
const char* mqtt_server = "broker.mqtt-dashboard.com";
const char* TOPIC_RFID_UID = "alex9ufo/rfid/UID";
const char* TOPIC_LED_CONTROL = "alex9ufo/rfid/led";
const char* TOPIC_LED_STATUS = "alex9ufo/rfid/ledStatus";






CREATE TABLE "rfid_logs" (

"id" INTEGER,

"date" TEXT NOT NULL,

"time" TEXT NOT NULL,

"LEDorRFID" TEXT,

"memo" TEXT,

PRIMARY KEY("id" AUTOINCREMENT)

);





wokwi 程式

#include <SPI.h>
#include <MFRC522.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// --- 硬體腳位 (您的指定) ---
#define SS_PIN    5
#define RST_PIN   22
#define LED_PIN   2
#define I2C_SDA   17
#define I2C_SCL   16

// --- 設定區 ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";

// MQTT 設定 (與您的 Python Tkinter 程式對接)
const char* mqtt_server = "broker.mqtt-dashboard.com";
const char* TOPIC_RFID_UID = "alex9ufo/rfid/UID";
const char* TOPIC_LED_CONTROL = "alex9ufo/rfid/led";
const char* TOPIC_LED_STATUS = "alex9ufo/rfid/ledStatus";

// 硬體物件
LiquidCrystal_I2C lcd(0x27, 16, 2);
MFRC522 mfrc522(SS_PIN, RST_PIN);
WiFiClient espClient;
PubSubClient mqttClient(espClient);

// --- FreeRTOS 隊列 ---
QueueHandle_t rfidQueue;
struct RfidMsg { char uid[20]; };

// 全域變數
bool isFlashing = false;

// --- MQTT 接收處理 (來自 Python 控制台) ---
void mqttCallback(char* topic, byte* payload, unsigned int length) {
  String message = "";
  for (int i = 0; i < length; i++) message += (char)payload[i];
 
  Serial.printf("\n[MQTT CMD]: %s\n", message.c_str());
 
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("MQTT Command:");
  lcd.setCursor(0, 1);

  isFlashing = false;
  if (message == "on") {
    digitalWrite(LED_PIN, HIGH);
    lcd.print("LED: ON");
    mqttClient.publish(TOPIC_LED_STATUS, "ON");
  }
  else if (message == "off") {
    digitalWrite(LED_PIN, LOW);
    lcd.print("LED: OFF");
    mqttClient.publish(TOPIC_LED_STATUS, "OFF");
  }
  else if (message == "flash") {
    isFlashing = true;
    lcd.print("MODE: FLASHING");
    mqttClient.publish(TOPIC_LED_STATUS, "FLASHING");
  }
  else if (message == "timer") {
    digitalWrite(LED_PIN, HIGH);
    lcd.print("TIMER: 5 SEC");
    mqttClient.publish(TOPIC_LED_STATUS, "TIMER_START");
    vTaskDelay(5000 / portTICK_PERIOD_MS); // 在 Task 中使用 vTaskDelay 不會卡死整個系統
    digitalWrite(LED_PIN, LOW);
    mqttClient.publish(TOPIC_LED_STATUS, "OFF");
  }
}

// --- Core 0: 負責 WiFi 與 MQTT 通訊 ---
void mqttTask(void *pvParameters) {
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { vTaskDelay(500 / portTICK_PERIOD_MS); }
 
  mqttClient.setServer(mqtt_server, 1883);
  mqttClient.setCallback(mqttCallback);

  RfidMsg rMsg;
  while (true) {
    // 維護 MQTT 連線
    if (!mqttClient.connected()) {
      Serial.print("Connecting to MQTT...");
      if (mqttClient.connect("ESP32_RFID_Gate_NoTG")) {
        Serial.println("Connected");
        mqttClient.subscribe(TOPIC_LED_CONTROL);
      } else {
        vTaskDelay(5000 / portTICK_PERIOD_MS);
      }
    }
    mqttClient.loop();

    // 接收來自 Core 1 的 RFID 訊息並發送到 MQTT
    if (xQueueReceive(rfidQueue, &rMsg, 0) == pdPASS) {
      mqttClient.publish(TOPIC_RFID_UID, rMsg.uid);
      Serial.printf("Sent UID to Python: %s\n", rMsg.uid);
    }

    // 處理閃爍邏輯
    if (isFlashing) {
      digitalWrite(LED_PIN, !digitalRead(LED_PIN));
      vTaskDelay(300 / portTICK_PERIOD_MS);
    }
   
    vTaskDelay(10 / portTICK_PERIOD_MS);
  }
}

// --- Core 1: 專門負責 RFID 掃描 (不處理網路) ---
void rfidTask(void *pvParameters) {
  SPI.begin();
  mfrc522.PCD_Init();
  RfidMsg rMsg;
  while (true) {
    if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
      String uidStr = "";
      for (byte i = 0; i < mfrc522.uid.size; i++) {
        uidStr += (mfrc522.uid.uidByte[i] < 0x10 ? "0" : "");
        uidStr += String(mfrc522.uid.uidByte[i], HEX);
      }
      uidStr.toUpperCase();
     
      // 更新 LCD 顯示
      lcd.clear();
      lcd.setCursor(0, 0); lcd.print("RFID Detected!");
      lcd.setCursor(0, 1); lcd.print("ID: " + uidStr);
     
      // 將卡號打包送入隊列,交給 Core 0 發送
      uidStr.toCharArray(rMsg.uid, 20);
      xQueueSend(rfidQueue, &rMsg, portMAX_DELAY);

      mfrc522.PICC_HaltA();
      mfrc522.PCD_StopCrypto1();
    }
    vTaskDelay(200 / portTICK_PERIOD_MS);
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
 
  // 初始化 I2C LCD
  Wire.begin(I2C_SDA, I2C_SCL);
  lcd.init();
  lcd.backlight();
  lcd.print("MQTT Connecting...");

  // 建立隊列
  rfidQueue = xQueueCreate(10, sizeof(RfidMsg));

  if (rfidQueue != NULL) {
    // 建立雙核心任務
    xTaskCreatePinnedToCore(mqttTask, "MQTT_Task", 8192, NULL, 1, NULL, 0);
    xTaskCreatePinnedToCore(rfidTask, "RFID_Task", 4096, NULL, 1, NULL, 1);
  }
}

void loop() {
  // FreeRTOS 架構下 loop 不需執行內容
  vTaskDelay(portMAX_DELAY);
}

Python 程式

import tkinter as tk

from tkinter import messagebox

import paho.mqtt.client as mqtt

import threading

import time

import asyncio

import sqlite3

import winsound

import sys

from datetime import datetime

from telegram import Update

from telegram.ext import Application, CommandHandler, ContextTypes


# --- 1. 全域設定 ---

DB_NAME = "rfid115.db"

MQTT_BROKER = "broker.mqtt-dashboard.com"

MQTT_PORT = 1883

TOPIC_RFID_UID = "alex9ufo/rfid/UID"

TOPIC_LED_CONTROL = "alex9ufo/rfid/led"

TOPIC_LED_STATUS = "alex9ufo/rfid/ledStatus"


# Telegram 設定

TELEGRAM_BOT_TOKEN = "8022700986:AAGymymK9_d1HcTGJWl3mtqHmilxB64_5Zw"

TARGET_CHAT_ID = 7965218469


MODE_ADD = "新增模式"

MODE_COMPARE = "比對模式"


# --- 2. 資料庫操作 ---

class DatabaseManager:

    def __init__(self, db_name):

        self.db_name = db_name

        self.create_table()

    

    def connect(self):

        return sqlite3.connect(self.db_name)


    def create_table(self):

        conn = self.connect()

        conn.cursor().execute("""

            CREATE TABLE IF NOT EXISTS rfid_logs (

                id INTEGER PRIMARY KEY AUTOINCREMENT,

                date TEXT NOT NULL,

                time TEXT NOT NULL,

                LEDorRFID TEXT, 

                memo TEXT

            )

        """)

        conn.commit()

        conn.close()


    def add_log(self, led_or_rfid, memo):

        conn = self.connect()

        try:

            now = datetime.now()

            conn.cursor().execute(

                "INSERT INTO rfid_logs (date, time, LEDorRFID, memo) VALUES (?, ?, ?, ?)",

                (now.strftime("%Y-%m-%d"), now.strftime("%H:%M:%S"), led_or_rfid, memo)

            )

            conn.commit()

        except Exception as e:

            print(f"資料庫錯誤: {e}")

        finally:

            conn.close()


    def get_latest_logs(self):

        conn = self.connect()

        res = conn.cursor().execute("SELECT * FROM rfid_logs ORDER BY id DESC LIMIT 50").fetchall()

        conn.close()

        return res


    def get_all_rfid_uids(self):

        conn = self.connect()

        res = conn.cursor().execute("SELECT DISTINCT LEDorRFID FROM rfid_logs WHERE memo LIKE '[新增]%'").fetchall()

        conn.close()

        return [row[0] for row in res if row[0]]


# --- 3. Telegram Bot 處理 ---

class TelegramBotHandler:

    def __init__(self, app, token):

        self.app = app

        self.token = token

        self.tg_loop = None 

        self.application = Application.builder().token(token).build()

        

        for cmd in ["on", "off", "flash", "timer"]:

            self.application.add_handler(CommandHandler(cmd, self.handle_commands))

        

        self.bot_thread = threading.Thread(target=self._run_bot, daemon=True)


    def start_bot(self):

        self.bot_thread.start()


    def _run_bot(self):

        loop = asyncio.new_event_loop()

        asyncio.set_event_loop(loop)

        self.tg_loop = loop

        self.application.run_polling(poll_interval=0.5)


    async def handle_commands(self, update: Update, context: ContextTypes.DEFAULT_TYPE):

        if update.message.chat_id != TARGET_CHAT_ID: return

        cmd = update.message.text.replace("/", "")

        self.app.master.after(0, self.app.process_incoming_command, f"Telegram:{cmd}", cmd)


    def send_message(self, text):

        if self.tg_loop:

            asyncio.run_coroutine_threadsafe(

                self.application.bot.send_message(chat_id=TARGET_CHAT_ID, text=text), self.tg_loop

            )


# --- 4. Tkinter 主應用 ---

class RfidControlApp:

    def __init__(self, master):

        self.master = master

        master.title("RFID & MQTT 監控系統")

        self.db = DatabaseManager(DB_NAME)

        

        self.status_var = tk.StringVar(value="系統連線中...")

        self.mode_var = tk.StringVar(value=MODE_ADD)

        

        self._setup_gui()

        

        self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)

        self.client.on_connect = self._on_connect

        self.client.on_message = self._on_message

        

        self.telegram_bot = TelegramBotHandler(self, TELEGRAM_BOT_TOKEN)

        self.telegram_bot.start_bot()


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


        # 啟動後 3 秒送出提示

        self.master.after(3000, self._send_welcome_message)


    def _setup_gui(self):

        f_top = tk.Frame(self.master); f_top.pack(pady=10)

        tk.Label(f_top, text="當前模式:", font=("Arial", 12)).pack(side=tk.LEFT)

        tk.Label(f_top, textvariable=self.mode_var, font=("Arial", 12, "bold"), fg="blue").pack(side=tk.LEFT, padx=10)

        tk.Button(f_top, text="切換模式", command=self._toggle_mode).pack(side=tk.LEFT)

        

        self.info_label = tk.Label(self.master, textvariable=self.status_var, 

                                   font=("Microsoft JhengHei", 14, "bold"), bg="#FFFFE0", relief="sunken", width=45)

        self.info_label.pack(pady=10, padx=20)


        self.log_display = tk.Text(self.master, height=15, width=80, font=("Consolas", 10))

        self.log_display.pack(pady=10, padx=10)

        

        tk.Button(self.master, text="清空資料庫(Reset)", command=self._db_reset, fg="red").pack(pady=5)

        self._handle_db_show()


    def _send_welcome_message(self):

        welcome_text = (

            "🚀 RFID 監控系統已啟動!\n\n"

            "請使用以下指令控制 LED:\n"

            "/on    - 開啟 LED\n"

            "/off   - 關閉 LED\n"

            "/timer - 開啟 5 秒後關閉\n"

            "/flash - 進入閃爍模式"

        )

        self.telegram_bot.send_message(welcome_text)

        self.status_var.set("已向 Telegram 送出指令提示")


    def _play_beep(self, freq, duration, repeat=1):

        def run():

            for _ in range(repeat):

                winsound.Beep(freq, duration)

                if repeat > 1: time.sleep(0.1)

        threading.Thread(target=run, daemon=True).start()


    def _toggle_mode(self):

        new_m = MODE_COMPARE if self.mode_var.get() == MODE_ADD else MODE_ADD

        self.mode_var.set(new_m)

        self.status_var.set(f"模式切換: {new_m}")


    def process_incoming_command(self, display_text, mqtt_cmd):

        """核心修正:更正常數名稱為 TOPIC_LED_CONTROL"""

        self.status_var.set(f"收到指令: {display_text}")

        # 修正這裡的 TOP_LED_CONTROL -> TOPIC_LED_CONTROL

        self.client.publish(TOPIC_LED_CONTROL, mqtt_cmd) 

        self.db.add_log("CMD_IN", display_text)

        self._handle_db_show()


    def _mqtt_thread(self):

        try:

            self.client.connect(MQTT_BROKER, MQTT_PORT, 60)

            self.client.loop_forever()

        except Exception as e:

            print(f"MQTT 連線失敗: {e}")


    def _on_connect(self, client, userdata, flags, rc, properties):

        # 修正這裡的 TOP_RFID_UID -> TOPIC_RFID_UID

        client.subscribe(TOPIC_RFID_UID)

        client.subscribe(TOPIC_LED_STATUS)

        self.master.after(0, lambda: self.status_var.set("MQTT 已連線,系統就緒"))


    def _on_message(self, client, userdata, msg):

        payload = msg.payload.decode()

        if msg.topic == TOPIC_RFID_UID:

            self.master.after(0, self._handle_rfid, payload)

        elif msg.topic == TOPIC_LED_STATUS:

            info = f"ESP32回報狀態: {payload.upper()}"

            self.master.after(0, lambda: self.status_var.set(info))

            self.db.add_log("MQTT_STATUS", info)

            self.master.after(0, self._handle_db_show)


    def _handle_rfid(self, uid):

        mode = self.mode_var.get()

        if mode == MODE_ADD:

            self.db.add_log(uid, f"[新增] 卡號: {uid}")

            self._play_beep(880, 1000, 3)

            msg = f"RFID 已註冊: {uid}"

            self.telegram_bot.send_message(f"✅ 已存入卡片: {uid}")

        else:

            uids = self.db.get_all_rfid_uids()

            if uid in uids:

                msg = f"比對正確: {uid}"

                self._play_beep(2000, 200)

                self.telegram_bot.send_message(f"🔓 通過: {uid}")

            else:

                msg = f"非法卡片: {uid}"

                self._play_beep(1200, 5000)

                self.telegram_bot.send_message(f"⚠️ 警報! 未授權卡片: {uid}")

        

        self.status_var.set(msg)

        self._handle_db_show()


    def _handle_db_show(self):

        logs = self.db.get_latest_logs()

        self.log_display.delete(1.0, tk.END)

        header = f"{'ID':<4} | {'日期':<10} | {'時間':<8} | {'LEDorRFID':<15} | 備註\n"

        self.log_display.insert(tk.END, header + "-"*75 + "\n")

        for r in logs:

            self.log_display.insert(tk.END, f"{r[0]:<4} | {r[1]:<10} | {r[2]:<8} | {str(r[3]):<15} | {r[4]}\n")


    def _db_reset(self):

        if messagebox.askyesno("確認", "將刪除所有記錄?"):

            conn = sqlite3.connect(DB_NAME)

            conn.cursor().execute("DROP TABLE IF EXISTS rfid_logs")

            conn.commit()

            conn.close()

            self.db.create_table()

            self._handle_db_show()

            self.status_var.set("資料庫已重置")


if __name__ == '__main__':

    if sys.platform == "win32":

        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

    root = tk.Tk()

    app = RfidControlApp(root)

    root.mainloop()


WOKWI ESP32 建立 RFID模擬 的方式 

WOKWI ESP32 建立 RFID模擬 的方式








1) 建立 rfid-rc522.chip.json 

{
  "name": "RC522 RFID Reader",
  "author": "Anton Shumeyko",
  "pins": [
    "CS",
    "SCK",
    "MOSI",
    "MISO",
    "IRQ",    
    "GND",
    "RST",
    "VCC",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    ""
  ],
  "controls": [
    {
      "id": "selectedCard",
      "label": "Select Card \n (0 - card not selected, 1-10 - card UID index)",
      "type": "range",
      "min": 0,
      "max": 10,
      "step": 1
    }
  ]
}

2) 建立 rfid-rc522.chip.c

// This file implements an emulation of the MFRC522 RFID chip for Wokwi projects.
// It allows you to simulate SPI communication with the MFRC522, including basic MIFARE commands.
// For more information and source code, see: https://github.com/anton21m
// Author: Anton21m blackdark20@mail.ru

#include "wokwi-api.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#define VERSION_REG 0x37
#define NUM_REGISTERS 64
#define FIFO_SIZE 64

// MIFARE commands
#define CMD_REQA 0x26
#define CMD_WUPA 0x52
#define CMD_ANTICOLL 0x93
#define CMD_SEL_CL1 0x93
#define CMD_SEL_CL2 0x95
#define CMD_SEL_CL3 0x97
#define CMD_CT 0x88
#define CMD_AUTH_A 0x60
#define CMD_AUTH_B 0x61
#define CMD_READ 0x30
#define CMD_WRITE 0xA0
#define REG_VERSION 0x92

// Добавьте недостающие:
#define CMD_CALC_CRC      0x03
#define CMD_IDLE          0x00
#define CMD_MEM           0x01
#define CMD_GEN_RANDOM_ID 0x02
#define CMD_TRANSMIT      0x04
#define CMD_RECEIVE       0x08
#define CMD_DECREMENT     0xC0 // MIFARE Decrement
#define CMD_INCREMENT     0xC1 // MIFARE Increment
#define CMD_RESTORE       0xC2 // MIFARE Restore
#define CMD_TRANSFER      0xB0 // MIFARE Transfer
#define CMD_UL_WRITE      0xA2 // MIFARE Ultralight Write

// Pre-defined UIDs for 5 different cards
static const uint8_t CARD_UIDS[10][4] = {
    {0x50, 0x9D, 0x39, 0x23}, // UId1
    {0x77, 0x18, 0x40, 0x05}, // Uid2
    {0x9F, 0xD6, 0xB1, 0xBD}, // Uid3
    {0x0A, 0x1B, 0x2C, 0x3D}, // Uid4
    {0xF1, 0xE2, 0xD3, 0xC4}, // Uid5
    {0x52, 0x9D, 0xC9, 0x83}, // UId6
    {0x77, 0xA8, 0x40, 0x35}, // Uid7
    {0x9B, 0xD6, 0xB1, 0x4D}, // Uid8
    {0xAB, 0xD6, 0xB3, 0x6B}, // Uid9
    {0xF6, 0xE5, 0xD3, 0xCB}  // Uid10
};

typedef enum {
  SPI_STATE_IDLE,
  SPI_STATE_WAIT_DATA,
} spi_transaction_state_t;

typedef struct {
  pin_t cs_pin;
  uint32_t spi;

  uint8_t registers[NUM_REGISTERS];
  uint8_t fifo[FIFO_SIZE];
  uint8_t fifo_len;

  uint8_t spi_buffer[18];
  spi_transaction_state_t spi_transaction_state;
  uint8_t current_address;
  bool is_read;
  uint8_t read_count;

  // Emulated card data (MIFARE Classic 1K = 16 sectors * 4 blocks * 16 bytes)
  uint8_t card_data[16 * 4 * 16];
  uint8_t uid[4];

  // NEW: Selected card index and Wokwi attribute ID
  uint32_t selected_card_attr_id;
  uint8_t selected_card_index; // 0 = no card, 1-5 = CARD_UIDS index + 1

  // New internal data register for MIFARE Value Block operations (Restore/Transfer)
  uint8_t internal_data_register[16];

  // State variables
  bool card_selected;
  bool authenticated;

  // Флаг: карта уже была обнаружена (для IsNewCardPresent)
  bool card_was_present;

  // Internal variables for anticollision, auth, etc.
  uint8_t anticoll_step;
  bool uid_read_completed;
  uint8_t cascade_level;
  uint8_t current_level_known_bits;
 
  // New variables for proper SELECT handling
  bool select_completed;
  uint8_t select_response_sent;

  // Streaming write state: true while CS is low and we are writing consecutive bytes into FIFO
  bool stream_write_to_fifo;

  // Backdoor variables
  bool uid_backdoor_step1;
  bool uid_backdoor_open;

  // MIFARE write command state
  int8_t pending_write_block; // -1 if no pending write, otherwise block address
  uint8_t pending_write_len;  // Expected length of data for pending write

  // NEW: MIFARE two-step command state
  int8_t pending_mifare_twostep_command; // -1 if no pending, otherwise the command (CMD_DECREMENT, CMD_INCREMENT, etc.)
  uint8_t pending_mifare_twostep_block_addr; // The block address for the pending two-step command
} chip_state_t;

// Forward declarations
static void chip_pin_change(void *user_data, pin_t pin, uint32_t value);
static void chip_spi_done(void *user_data, uint8_t *buffer, uint32_t count);

// MIFARE command processing functions
static void handle_reqa_wupa_command(chip_state_t *chip);
static void handle_anticoll_command(chip_state_t *chip);
static void handle_select_command(chip_state_t *chip);

// SPI read/write functions
static void handle_spi_read_command(chip_state_t *chip);
static void handle_spi_write_command(chip_state_t *chip, uint8_t val);
static void read_version_register(chip_state_t *chip);
static void read_comirq_register(chip_state_t *chip);
static void read_fifo_level_register(chip_state_t *chip);
static void read_fifo_data_register(chip_state_t *chip);
static void write_fifo_register(chip_state_t *chip, uint8_t val);
static void write_command_register(chip_state_t *chip, uint8_t val);

// FIFO management functions
static void fifo_push(chip_state_t *chip, uint8_t val);
static void fifo_remove_bytes(chip_state_t *chip, int bytes_to_remove);
static void update_fifo_level_register(chip_state_t *chip);

// State management functions
static void reset_chip_state(chip_state_t *chip);
static void set_irq_flag(chip_state_t *chip);
static void clear_irq_flag(chip_state_t *chip, uint8_t flag);
static void set_specific_irq_flag(chip_state_t *chip, uint8_t flag);
static void log_chip_state(chip_state_t *chip);
void send_ack_response(chip_state_t *chip);

// CRC_A для ISO14443A (полином 0x8408, начальное значение 0x6363)
static void calc_crc_a(const uint8_t *data, size_t len, uint8_t *crc) {
    uint16_t crcval = 0x6363;
    for (size_t i = 0; i < len; i++) {
        crcval ^= data[i];
        for (int j = 0; j < 8; j++) {
            if (crcval & 0x0001)
                crcval = (crcval >> 1) ^ 0x8408;
            else
                crcval = (crcval >> 1);
        }
    }
    crc[0] = crcval & 0xFF;
    crc[1] = (crcval >> 8) & 0xFF;
}

static void perform_crc_calculation(chip_state_t *chip) {
  // Выполнить CRC_A для текущего содержимого FIFO
  if (chip->fifo_len == 0) {
    // Нечего считать - возвращаем 0
    chip->registers[0x22] = 0x00; // CRCResultRegL
    chip->registers[0x21] = 0x00; // CRCResultRegH
  } else {
    uint8_t crc[2];
    calc_crc_a(chip->fifo, chip->fifo_len, crc);
    chip->registers[0x22] = crc[0]; // CRCResultRegL
    chip->registers[0x21] = crc[1]; // CRCResultRegH
  }
  // Установить бит CRCIRq (0x04) в DivIrqReg (адрес 0x05)
  chip->registers[0x05] |= 0x04;
//   printf("CRC calculated for %d bytes -> %02X %02X, DivIrqReg: 0x%02X\n", chip->fifo_len, chip->registers[0x22], chip->registers[0x21], chip->registers[0x05]);
}

// Helper to decode a 4-byte value from a MIFARE value block
static int32_t decode_mifare_value(const uint8_t *buffer) {
    return (int32_t)(buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24));
}

// Helper to encode a 4-byte value into a MIFARE value block format
static void encode_mifare_value(uint8_t *buffer, int32_t value, uint8_t block_address) {
    buffer[0] = (uint8_t)(value & 0xFF);
    buffer[1] = (uint8_t)((value >> 8) & 0xFF);
    buffer[2] = (uint8_t)((value >> 16) & 0xFF);
    buffer[3] = (uint8_t)((value >> 24) & 0xFF);

    buffer[4] = ~buffer[0];
    buffer[5] = ~buffer[1];
    buffer[6] = ~buffer[2];
    buffer[7] = ~buffer[3];

    buffer[8] = buffer[0];
    buffer[9] = buffer[1];
    buffer[10] = buffer[2];
    buffer[11] = buffer[3];

    buffer[12] = block_address;
    buffer[13] = block_address;
    buffer[14] = block_address;
    buffer[15] = ~block_address;
}

void chip_init(void) {
  chip_state_t *chip = calloc(1, sizeof(chip_state_t));
  chip->cs_pin = pin_init("CS", INPUT_PULLUP);

  // Initialize Wokwi control for card selection
  chip->selected_card_attr_id = attr_init("selectedCard", 0); // Default to 0 (no card)
  chip->selected_card_index = attr_read(chip->selected_card_attr_id);

  // Initialize UID (will be updated when card is selected)
  if (chip->selected_card_index > 0 && chip->selected_card_index <= 5) {
      memcpy(chip->uid, CARD_UIDS[chip->selected_card_index - 1], 4);
  } else {
      // Default UID if no card selected or invalid index
      memset(chip->uid, 0, 4);
  }

  // Initialize registers, set version reg to typical MFRC522 version
  chip->registers[VERSION_REG] = 0x92;

  printf("INIT, UID %02X %02X %02X %02X\n",
    chip->uid[0], chip->uid[1], chip->uid[2], chip->uid[3]);

  // Initialize card data with default MIFARE Classic 1K structure
  memset(chip->card_data, 0, sizeof(chip->card_data));

  // Populate Block 0 (Manufacturer Block) with UID and BCC
  chip->card_data[0] = chip->uid[0];
  chip->card_data[1] = chip->uid[1];
  chip->card_data[2] = chip->uid[2];
  chip->card_data[3] = chip->uid[3];
  chip->card_data[4] = chip->uid[0] ^ chip->uid[1] ^ chip->uid[2] ^ chip->uid[3]; // BCC
  // The rest of block 0 is manufacturer data, can be left as 0.

  // Populate all sector trailers with default keys and access bits.
  // This mimics a fresh MIFARE Classic card.
  const uint8_t default_trailer[16] = {
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Key A
    0xFF, 0x07, 0x80,                   // Access Bits (default configuration)
    0x69,                               // User Data Byte (GPB)
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF  // Key B
  };

  // MIFARE Classic 1K has 16 sectors.
  for (int sector = 0; sector < 16; sector++) {
    // The trailer is the last block of the sector (block 3).
    int trailer_block_index = sector * 4 + 3;
    memcpy(&chip->card_data[trailer_block_index * 16], default_trailer, 16);
  }

  // Setup pin watching and SPI
  pin_watch_config_t watch_cfg = {
    .edge = BOTH,
    .pin_change = chip_pin_change,
    .user_data = chip,
  };
  pin_watch(chip->cs_pin, &watch_cfg);

  spi_config_t spi_cfg = {
    .sck = pin_init("SCK", INPUT),
    .miso = pin_init("MISO", INPUT),
    .mosi = pin_init("MOSI", INPUT),
    .done = chip_spi_done,
    .user_data = chip,
  };
  chip->spi = spi_init(&spi_cfg);

  // Initialize important registers
  memset(chip->registers, 0, NUM_REGISTERS);
  chip->registers[VERSION_REG] = 0x92;  // Version
  chip->registers[0x04] = 0x00;        // ComIrqReg - все флаги сброшены
  chip->registers[0x06] = 0x00;        // ErrorReg - нет ошибок
  chip->registers[0x0A] = 0x00;        // FIFOLevelReg
  chip->registers[0x0C] = 0x80;        // ControlReg (PowerOn=1)
  chip->registers[0x26] = 0x70;        // RFCfgReg (default to 48dB gain)
 
  // Clear FIFO
  chip->fifo_len = 0;
  memset(chip->fifo, 0, FIFO_SIZE);
 
  // Initialize state
  chip->card_selected = false;
  chip->authenticated = false;
  chip->anticoll_step = 0;
  chip->uid_read_completed = false;
  chip->cascade_level = 1;
  chip->current_level_known_bits = 0;
  chip->select_completed = false;
  chip->select_response_sent = 0;
  chip->stream_write_to_fifo = false;
  chip->spi_transaction_state = SPI_STATE_IDLE;
  chip->pending_write_block = -1;
  chip->pending_write_len = 0;
  chip->pending_mifare_twostep_command = -1; // NEW
  chip->pending_mifare_twostep_block_addr = 0; // NEW
  chip->card_was_present = false; // Инициализируем флаг
 
  // Initialize internal data register to all zeros
  memset(chip->internal_data_register, 0, sizeof(chip->internal_data_register));
 
  printf("Chip initialized - ComIrqReg: 0x%02X\n", chip->registers[0x04]);
 
  // Проверяем состояние всех важных регистров
  printf("Initial register state:\n");
  printf("ComIrqReg (0x04): 0x%02X\n", chip->registers[0x04]);
  printf("FIFOLevelReg (0x0A): 0x%02X\n", chip->registers[0x0A]);
  printf("ControlReg (0x0C): 0x%02X\n", chip->registers[0x0C]);
  printf("VersionReg (0x37): 0x%02X\n", chip->registers[VERSION_REG]);
}

void chip_pin_change(void *user_data, pin_t pin, uint32_t value) {
  chip_state_t *chip = (chip_state_t *)user_data;
  if (pin == chip->cs_pin) {
    if (value == LOW) {
      chip->spi_transaction_state = SPI_STATE_IDLE;
      spi_start(chip->spi, chip->spi_buffer, 1);
    } else {
      spi_stop(chip->spi);
      // Do NOT reset anticoll_step here. It must be preserved across transactions
      // during card selection sequence. It will be reset by REQA/WUPA or SELECT completion.
      // chip->anticoll_step = 0;
      chip->stream_write_to_fifo = false;
    }
  }
}

void chip_spi_done(void *user_data, uint8_t *buffer, uint32_t count) {
  chip_state_t *chip = (chip_state_t*)user_data;

  // NEW: Read selected card from Wokwi control and update UID if changed
  uint8_t new_selected_card_index = attr_read(chip->selected_card_attr_id);
  if (new_selected_card_index != chip->selected_card_index) {
      chip->selected_card_index = new_selected_card_index;
      if (chip->selected_card_index > 0 && chip->selected_card_index <= 10) {
          memcpy(chip->uid, CARD_UIDS[chip->selected_card_index - 1], 4);
          chip->card_was_present = false; // Карта убрана или новая — сбросить флаг
      } else {
          memset(chip->uid, 0, 4);
          chip->card_was_present = false; // Карта убрана — сбросить флаг
      }
      printf("Selected card changed to: %d\n", chip->selected_card_index);
      // Reinitialize card data for new card
      memset(chip->card_data, 0, sizeof(chip->card_data));
      chip->card_data[0] = chip->uid[0];
      chip->card_data[1] = chip->uid[1];
      chip->card_data[2] = chip->uid[2];
      chip->card_data[3] = chip->uid[3];
      chip->card_data[4] = chip->uid[0] ^ chip->uid[1] ^ chip->uid[2] ^ chip->uid[3]; // BCC
      const uint8_t default_trailer[16] = {
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Key A
        0xFF, 0x07, 0x80,                   // Access Bits (default configuration)
        0x69,                               // User Data Byte (GPB)
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF  // Key B
      };
      for (int sector = 0; sector < 16; sector++) {
        int trailer_block_index = sector * 4 + 3;
        memcpy(&chip->card_data[trailer_block_index * 16], default_trailer, 16);
      }
  }

  if (pin_read(chip->cs_pin) == HIGH) {
    return; // CS is high, transaction is over.
  }

  switch (chip->spi_transaction_state) {
    case SPI_STATE_IDLE: {
      uint8_t cmd_byte = buffer[0];
      chip->current_address = (cmd_byte >> 1) & 0x3F;
      chip->is_read = (cmd_byte & 0x80) != 0;
     
      if (chip->stream_write_to_fifo && (chip->current_address != 0x09 || chip->is_read)) {
        chip->stream_write_to_fifo = false;
      }

    // if (chip->current_address == 0x09 || chip->current_address == 0x01) {
    //       printf("SPI cmd: 0x%02X, addr: 0x%02X, read: %s\n", cmd_byte, chip->current_address, chip->is_read ? "yes" : "no");
    // }

    if (chip->is_read) {
      handle_spi_read_command(chip);
        if (chip->read_count > 0) {
      spi_start(chip->spi, chip->spi_buffer, chip->read_count);
        }
        chip->spi_transaction_state = SPI_STATE_IDLE; // Stay idle, ready for next command
      } else { // It's a write command
        if (chip->current_address == 0x09) {
            chip->stream_write_to_fifo = true;
        }
        chip->spi_transaction_state = SPI_STATE_WAIT_DATA;
        spi_start(chip->spi, chip->spi_buffer, 1); // Wait for the data byte
      }
      break;
    }

    case SPI_STATE_WAIT_DATA: {
      uint8_t data_byte = buffer[0];
      handle_spi_write_command(chip, data_byte);

      if (chip->stream_write_to_fifo) {
        chip->spi_transaction_state = SPI_STATE_WAIT_DATA;
    spi_start(chip->spi, chip->spi_buffer, 1);
  } else {
        chip->spi_transaction_state = SPI_STATE_IDLE;
      }
      break;
    }
  }
}

// MIFARE command processing functions
static void handle_reqa_wupa_command(chip_state_t *chip) {
  // Only respond if a card is selected (index > 0)
  if (chip->selected_card_index > 0) {
      if (!chip->card_was_present) {
          chip->fifo[0] = 0x04;  // ATQA
          chip->fifo[1] = 0x00;
          chip->fifo_len = 2;
          update_fifo_level_register(chip);
          // Устанавливаем только RxIRq для успешного приема данных
      set_specific_irq_flag(chip, 0x20);  // RxIRq (corrected from 0x04)
          chip->anticoll_step = 0;
          chip->registers[0x0C] &= ~0x07; // Сброс RxLastBits в 0, так как ATQA - это полные байты
          chip->card_was_present = true; // Установить флаг: карта обнаружена
      } else {
          // Карта уже была обнаружена, не отвечаем повторно
          chip->fifo_len = 0;
          update_fifo_level_register(chip);
      }
  } else {
      chip->fifo_len = 0; // Clear FIFO if no card is selected
      update_fifo_level_register(chip); // Update FIFO level register
  }
}

static void handle_anticoll_command(chip_state_t *chip) {
//   printf("ANTICOLL command - step: %d, fifo_len: %d\n", chip->anticoll_step, chip->fifo_len);
//   log_chip_state(chip, "before ANTICOLL processing");
 
//   if (chip->fifo_len > 0) {
//     printf("ANTICOLL fifo content: ");
//     for (int i = 0; i < chip->fifo_len && i < 4; i++) {
//       printf("%02X ", chip->fifo[i]);
//     }
//     printf("\n");
//   }
 
  // Only process if a card is selected
  if (chip->selected_card_index == 0) {
      printf("ANTICOLL - no card selected, no response\n");
      chip->fifo_len = 0;
      update_fifo_level_register(chip);
      return;
  }

  // Обработка ANTICOLLISION для Cascade Level 1
  if (chip->anticoll_step == 0 && chip->fifo_len >= 1 && chip->fifo[0] == CMD_SEL_CL1) {
    // Очищаем FIFO перед формированием ответа
    chip->fifo_len = 0;
    printf("ANTICOLL - responding with UID for card %d\n", chip->selected_card_index);
    chip->fifo[chip->fifo_len++] = chip->uid[0];
    chip->fifo[chip->fifo_len++] = chip->uid[1];
    chip->fifo[chip->fifo_len++] = chip->uid[2];
    chip->fifo[chip->fifo_len++] = chip->uid[3];
    uint8_t bcc = chip->uid[0] ^ chip->uid[1] ^ chip->uid[2] ^ chip->uid[3];
    chip->fifo[chip->fifo_len++] = bcc;  // UID + BCC
    update_fifo_level_register(chip);
    set_specific_irq_flag(chip, 0x20);  // RxIRq (corrected from 0x04)
    chip->anticoll_step = 1;
    chip->current_level_known_bits = 32;  // Все 32 бита известны
    chip->registers[0x0C] &= ~0x07; // Сброс RxLastBits в 0, так как UID - это полные байты
//     printf("ANTICOLL processed - UID and BCC in FIFO: ");
//     for (int i = 0; i < chip->fifo_len; i++) {
//       printf("%02X ", chip->fifo[i]);
//     }
//     printf("\n");
  }
 
//   log_chip_state(chip, "after ANTICOLL processing");
}

static void handle_select_command(chip_state_t *chip) {
//   printf("SELECT command detected - processing full UID selection\n");
//   printf("SELECT command content: ");
//   for (int i = 0; i < chip->fifo_len; i++) {
//     printf("%02X ", chip->fifo[i]);
//   }
//   printf("\n");

  // Only process if a card is selected
  if (chip->selected_card_index == 0) {
      printf("SELECT - no card selected, no response\n");
      chip->fifo_len = 0;
      update_fifo_level_register(chip);
      return;
  }

  // Check if this is the correct SELECT command for our UID
  if (chip->fifo[2] == chip->uid[0] && chip->fifo[3] == chip->uid[1] &&
      chip->fifo[4] == chip->uid[2] && chip->fifo[5] == chip->uid[3]) {
    printf("SELECT - UID match for card %d, sending SAK\n", chip->selected_card_index);

    // Clear FIFO before sending SAK
      chip->fifo_len = 0;

    // Send SAK with CRC
    chip->fifo[0] = 0x08;  // SAK for MIFARE Classic 1K
    uint8_t crc[2];
    calc_crc_a(chip->fifo, 1, crc);
    chip->fifo[1] = crc[0];
    chip->fifo[2] = crc[1];
    chip->fifo_len = 3;

      update_fifo_level_register(chip);
    set_specific_irq_flag(chip, 0x20);  // RxIRq (corrected from 0x04)

      chip->card_selected = true;
    chip->authenticated = false; // Reset authentication state on new selection
    chip->select_completed = true;
    chip->registers[0x0C] &= ~0x07; // Сброс RxLastBits в 0, так как SAK - это полный байт
//     printf("SELECT completed - SAK+CRC sent: %02X %02X %02X\n", chip->fifo[0], chip->fifo[1], chip->fifo[2]);
    } else {
    printf("SELECT failed - UID mismatch for card %d\n", chip->selected_card_index);
    printf("Expected UID: %02X %02X %02X %02X\n",
           chip->uid[0], chip->uid[1], chip->uid[2], chip->uid[3]);
    printf("Received UID: %02X %02X %02X %02X\n",
           chip->fifo[2], chip->fifo[3], chip->fifo[4], chip->fifo[5]);
      chip->fifo_len = 0;
      update_fifo_level_register(chip);
    }
  // Reset anticoll_step after SELECT is done
      chip->anticoll_step = 0;
}

void process_mifare_command(chip_state_t *chip) {
  if (chip->fifo_len == 0) return;

  // Обработка второй фазы MIFARE WRITE, если она ожидается
  if (chip->pending_write_block != -1 && chip->fifo_len == 18) {
    printf("Processing MIFARE WRITE Phase 2 (block 0x%02X) - received 18 bytes (16 data + 2 CRC)\n", chip->pending_write_block);
    // Проверяем, авторизован ли доступ к сектору
    bool allow_write = chip->authenticated;
    if (chip->pending_write_block == 0) { // UID block
      allow_write = true; // Always allow write to block 0 for now (can be restricted later by authentication or backdoor)
    }

    if (allow_write) {
      // Копируем только 16 байт данных, игнорируя последние 2 байта CRC
      memcpy(&chip->card_data[chip->pending_write_block * 16], chip->fifo, 16);
      if (chip->pending_write_block == 0) {
        // Update UID from block 0
        memcpy(chip->uid, &chip->card_data[0], 4);
      }
     
      // Отправляем 4-битный ACK
      send_ack_response(chip);
    } else {
      printf("WRITE Phase 2 failed: not authenticated for this sector.\n");
      // Отправляем NACK или ничего не отправляем, позволяя таймауту произойти
      chip->fifo_len = 0; // Clear FIFO
      update_fifo_level_register(chip);
    }

    // Сбрасываем состояние записи
    chip->pending_write_block = -1;
    chip->pending_write_len = 0;
    return; // Завершаем обработку команды
  }
 
  // NEW: Обработка второй фазы двухступенчатых MIFARE команд (Decrement, Increment, Restore, Transfer)
  // These commands are split into two PCD_MIFARE_Transceive calls.
  // First call: command + block address
  // Second call: 4 bytes of data (for Increment/Decrement) or 0 (for Restore/Transfer)
  if (chip->pending_mifare_twostep_command != -1 && chip->fifo_len == 4) { // Expecting 4 bytes of data
      uint8_t command = chip->pending_mifare_twostep_command;
      uint8_t blockAddr = chip->pending_mifare_twostep_block_addr;
     
      if (chip->authenticated) {
          switch (command) {
              case CMD_DECREMENT: {
                  int32_t delta = decode_mifare_value(chip->fifo);
                  memcpy(chip->internal_data_register, &chip->card_data[blockAddr * 16], 16);
                  int32_t currentValue = decode_mifare_value(chip->internal_data_register);
                  currentValue -= delta;
                  encode_mifare_value(chip->internal_data_register, currentValue, blockAddr);
                  memcpy(&chip->card_data[blockAddr * 16], chip->internal_data_register, 16);
                  printf("MIFARE DECREMENT executed on block 0x%02X with delta %d. New value: %d\n", blockAddr, delta, currentValue);
                  break;
              }
              case CMD_INCREMENT: {
                  int32_t delta = decode_mifare_value(chip->fifo);
                  memcpy(chip->internal_data_register, &chip->card_data[blockAddr * 16], 16);
                  int32_t currentValue = decode_mifare_value(chip->internal_data_register);
                  currentValue += delta;
                  encode_mifare_value(chip->internal_data_register, currentValue, blockAddr);
                  memcpy(&chip->card_data[blockAddr * 16], chip->internal_data_register, 16);
                  printf("MIFARE INCREMENT executed on block 0x%02X with delta %d. New value: %d\n", blockAddr, delta, currentValue);
                  break;
              }
              case CMD_RESTORE:\
                  // For RESTORE, the action (copying to internal_data_register) happens in Phase 1.
                  // This Phase 2 just needs to send ACK.
                  printf("MIFARE RESTORE Phase 2 (data) received, data ignored. Command for block 0x%02X\n", blockAddr);
                  break;
              case CMD_TRANSFER:\
                  // For TRANSFER, the action (copying from internal_data_register to block) happens in Phase 1.
                  // This Phase 2 just needs to send ACK.
                  printf("MIFARE TRANSFER Phase 2 (data) received, data ignored. Command for block 0x%02X\n", blockAddr);
                  break;
          }
          // Send 4-bit ACK
          send_ack_response(chip);
      } else {
          printf("Two-step command (0x%02X) Phase 2 failed: not authenticated for block 0x%02X.\n", command, blockAddr);
          chip->fifo_len = 0;
          update_fifo_level_register(chip);
      }
      chip->pending_mifare_twostep_command = -1;
      chip->pending_mifare_twostep_block_addr = 0;
      return;
  }


  uint8_t cmd = chip->fifo[0];
  printf("Processing MIFARE command: 0x%02X (fifo_len=%d, anticoll_step=%d)\n", cmd, chip->fifo_len, chip->anticoll_step);

  switch (cmd) {
    case CMD_REQA:
    case CMD_WUPA:
      // printf("Handling REQA/WUPA command\n");
      handle_reqa_wupa_command(chip);
      break;

    case CMD_SEL_CL1:
    case CMD_SEL_CL2:
    case CMD_SEL_CL3:
      if (chip->anticoll_step == 1 && chip->fifo_len >= 9) {
        handle_select_command(chip);
      } else {
      handle_anticoll_command(chip);
      }
      break;

    case CMD_READ:
      // printf("Handling READ command (block 0x%02X)\n", chip->fifo[1]);
      if (chip->authenticated) {
        if (chip->fifo_len >= 2) {
          uint8_t blockAddr = chip->fifo[1];
          if (blockAddr < 64) { // 16 sectors * 4 blocks/sector
            printf("Reading block %d\n", blockAddr);
            // Copy 16 bytes from emulated card memory
            chip->fifo_len = 0; // Clear FIFO before filling
            memcpy(chip->fifo, &chip->card_data[blockAddr * 16], 16);
           
            // Append CRC
            uint8_t crc[2];
            calc_crc_a(chip->fifo, 16, crc);
            chip->fifo[16] = crc[0];
            chip->fifo[17] = crc[1];
            chip->fifo_len = 18;
            update_fifo_level_register(chip);
            set_specific_irq_flag(chip, 0x20); // RxIRq
            chip->registers[0x0C] &= ~0x07; // Clear RxLastBits to 0 (8 valid bits)
          } else {
            printf("READ failed: block address %d is out of bounds.\n", blockAddr);
            chip->fifo_len = 0;
            update_fifo_level_register(chip);
          }
        } else {
            printf("READ failed: command too short.\n");
            chip->fifo_len = 0;
            update_fifo_level_register(chip);
        }
      } else {
        printf("READ failed: not authenticated for this sector.\n");
        // Don't respond, let it time out.
        chip->fifo_len = 0;
        update_fifo_level_register(chip);
      }
      break;

    case CMD_WRITE:
      printf("Handling WRITE command (block 0x%02X)\n", chip->fifo[1]);
      uint8_t blockAddr = chip->fifo[1];
      bool allow_write = false;
      if (chip->authenticated) {
        allow_write = true;
      }
      // 如果後門打開,則允許寫入區塊 0。
      if (blockAddr == 0 && chip->uid_backdoor_open) {
        allow_write = true;
        printf("Backdoor open: allowing write to block 0 without authentication!\n");
        chip->uid_backdoor_open = false; // Сбросить после успешной записи
      }
      if (allow_write) {
        if (chip->fifo_len >= 2) { // CMD_WRITE + block_addr + CRC
          // First phase of MIFARE WRITE: send ACK and set up for next 16 bytes
          chip->pending_write_block = blockAddr;
          chip->pending_write_len = 16;

          // Send 4-bit ACK
          send_ack_response(chip);
        } else {
          printf("WRITE failed: command too short for phase 1.\n");
          chip->fifo_len = 0;
          update_fifo_level_register(chip);
        }
      }
      else {
        printf("WRITE failed: not authenticated for this sector.\n");
        chip->fifo_len = 0;
        update_fifo_level_register(chip);
      }
      break;

    case CMD_DECREMENT: // 0xC0
    case CMD_INCREMENT: // 0xC1
    case CMD_RESTORE:   // 0xC2
    case CMD_TRANSFER:  // 0xB0
      // Phase 1 of MIFARE Two-Step Commands (command + block address)
      if (chip->fifo_len >= 2) {
          uint8_t blockAddr = chip->fifo[1];
          if (blockAddr < 64) {
              chip->pending_mifare_twostep_command = cmd;
              chip->pending_mifare_twostep_block_addr = blockAddr;
             
              if (cmd == CMD_RESTORE) {
                  if (chip->authenticated) {
                      memcpy(chip->internal_data_register, &chip->card_data[blockAddr * 16], 16);
                      printf("MIFARE RESTORE executed: block 0x%02X restored to internal register.\n", blockAddr);
                  } else {
                      printf("MIFARE RESTORE failed: not authenticated for block 0x%02X.\n", blockAddr);
                      chip->fifo_len = 0;
                      update_fifo_level_register(chip);
                      return;
                  }
              } else if (cmd == CMD_TRANSFER) {
                  if (chip->authenticated) {
                      memcpy(&chip->card_data[blockAddr * 16], chip->internal_data_register, 16);
                      printf("MIFARE TRANSFER executed: internal register transferred to block 0x%02X.\n", blockAddr);
                  } else {
                      printf("MIFARE TRANSFER failed: not authenticated for block 0x%02X.\n", blockAddr);
                      chip->fifo_len = 0;
                      update_fifo_level_register(chip);
                      return;
                  }
              }

              // Send 4-bit ACK for the first phase
              send_ack_response(chip);
          } else {
              printf("Two-step command (0x%02X) failed: block address out of bounds.\n", cmd);
              chip->fifo_len = 0;
              update_fifo_level_register(chip);
          }
      } else {
          printf("Two-step command (0x%02X) failed: command too short for phase 1.\n", cmd);
          chip->fifo_len = 0;
          update_fifo_level_register(chip);
      }
      break;

    case CMD_UL_WRITE:
      printf("Handling MIFARE ULTRALIGHT WRITE command (page 0x%02X)\n", chip->fifo[1]);
      if (chip->fifo_len >= 6) { // CMD + pageAddr + data (4 bytes) + CRC (2 bytes)
        uint8_t pageAddr = chip->fifo[1];
        // MIFARE Ultralight имеет 16 страниц (0-15), каждая по 4 байта.
        // Проверяем, что адрес страницы допустим.
        // Page 0 is R/O UID, Page 1 is R/O internal, Page 2 is R/W
        // The lib tests write to page 4.
        if (pageAddr >= 2 && pageAddr < 16) {
          memcpy(&chip->card_data[pageAddr * 16], &chip->fifo[2], 4); // Copy only 4 bytes
          // Отправляем 4-битный ACK
          send_ack_response(chip);
        } else {
          printf("MIFARE ULTRALIGHT WRITE failed: page address %d out of bounds or read-only.\n", pageAddr);
          chip->fifo_len = 0;
          update_fifo_level_register(chip);
        }
      } else {
        printf("MIFARE ULTRALIGHT WRITE failed: invalid command length.\n");
        chip->fifo_len = 0;
        update_fifo_level_register(chip);
      }
      break;

    case 0x50: // HALT
      reset_chip_state(chip); // 重置狀態以重新連接
      chip->fifo_len = 0;
      chip->uid_backdoor_step1 = true; // 為下一個命令做好準備 0x40
      // set_specific_irq_flag(chip, 0x10); // IdleIRq - 此行已被刪除
      printf("HALT command received. Card state reset for re-discovery. No response will be sent.\n");
      break;
    case 0x40:
      if (chip->uid_backdoor_step1) {
        send_ack_response(chip);
        chip->uid_backdoor_step1 = false;
        chip->uid_backdoor_open = true; // 我們授權下一步
      } else {
        chip->fifo_len = 0;
      }
      break;
    case 0x43:
      if (chip->uid_backdoor_open) {
        send_ack_response(chip);
        // 現在允許寫入磁區 0。
        chip->uid_backdoor_open = true;
      } else {
        chip->fifo_len = 0;
      }
      break;

    case CMD_AUTH_A:
    case CMD_AUTH_B:
      // printf("Handling AUTHENTICATE command\n");
      // Simplified authentication: we just check the command in FIFO.
      // A real implementation would check the key against the sector trailer.
      if (chip->fifo_len >= 1 && (chip->fifo[0] == CMD_AUTH_A || chip->fifo[0] == CMD_AUTH_B)) {
          printf("Authentication successful (simulated)\n");
          chip->authenticated = true;
          // The command completes when IdleIRq is set.
          set_specific_irq_flag(chip, 0x10); // IdleIRq
      } else {
          printf("Authentication failed: incorrect command in FIFO (len=%d)\n", chip->fifo_len);
          // Maybe set an error flag? For now, do nothing and let it time out.
      }
      chip->fifo_len = 0; // Clear FIFO after auth attempt
      update_fifo_level_register(chip);
      chip->registers[0x01] = 0; // Go to Idle
      break;

    case CMD_CT:
      // printf("Handling CT command (Transceive)\n");
      // This command is typically used for Transmit and Receive.
      // For self-test, we just acknowledge it.
      set_specific_irq_flag(chip, 0x20); // TxIRq
      chip->registers[0x01] = 0; // Go to Idle
      break;

    case CMD_CALC_CRC:
      // printf("Handling CALC_CRC command\n");
      perform_crc_calculation(chip);
      chip->registers[0x01] = 0; // Go to Idle
      break;

    case CMD_IDLE:
      // printf("Handling IDLE command\n");
      chip->registers[0x01] = 0; // Go to Idle
      break;

    case CMD_MEM:
      // printf("Handling MEM command (Transfer FIFO to internal buffer)\n");
      // This command transfers FIFO data to internal buffer for self-test
      // In our emulation, we just acknowledge the command
      set_specific_irq_flag(chip, 0x10); // IdleIRq
      chip->registers[0x01] = 0; // Go to Idle
      break;

    case CMD_GEN_RANDOM_ID:
      // printf("Handling GEN_RANDOM_ID command\n");
      // This command generates a random ID for self-test
      // In our emulation, we just acknowledge the command
      set_specific_irq_flag(chip, 0x10); // IdleIRq
      chip->registers[0x01] = 0; // Go to Idle
      break;

    case CMD_TRANSMIT:
      // printf("Handling TRANSMIT command\n");
      // This command transmits data from FIFO for self-test
      // In our emulation, we just acknowledge the command
      set_specific_irq_flag(chip, 0x20); // TxIRq
      chip->registers[0x01] = 0; // Go to Idle
      break;

    case CMD_RECEIVE:
      // printf("Handling RECEIVE command\n");
      // This command activates the receiver for self-test
      // In our emulation, we just acknowledge the command
      set_specific_irq_flag(chip, 0x10); // IdleIRq
      chip->registers[0x01] = 0; // Go to Idle
      break;

    default:
      printf("Unknown MIFARE command: 0x%02X\n", cmd);
      break;
  }
}

// SPI read/write functions
static void read_version_register(chip_state_t *chip) {
  chip->spi_buffer[0] = 0x92;
  chip->read_count = 1;
}

static void read_comirq_register(chip_state_t *chip) {
  chip->spi_buffer[0] = chip->registers[0x04];
  chip->read_count = 1;
//   printf("ComIrqReg read: 0x%02X (IRQ flags: RxIRq=%d, IdleIRq=%d, LoAlertIRq=%d, ErrIRq=%d, TimerIRq=%d, TxIRq=%d)\n",
//          chip->registers[0x04],
//          (chip->registers[0x04] & 0x20) ? 1 : 0,  // RxIRq
//          (chip->registers[0x04] & 0x10) ? 1 : 0,  // IdleIRq
//          (chip->registers[0x04] & 0x02) ? 1 : 0,  // LoAlertIRq
//          (chip->registers[0x04] & 0x08) ? 1 : 0,  // ErrIRq
//          (chip->registers[0x04] & 0x10) ? 1 : 0,  // TimerIRq (corrected from 0x01 to 0x10 previously)
//          (chip->registers[0x04] & 0x40) ? 1 : 0); // TxIRq (corrected from 0x20 to 0x40 previously)
}

static void read_fifo_level_register(chip_state_t *chip) {
  chip->spi_buffer[0] = chip->fifo_len;
  chip->read_count = 1;
//   printf("FIFOLevelReg read: %d bytes\n", chip->fifo_len);
}

static void read_fifo_data_register(chip_state_t *chip) {
  if (chip->fifo_len > 0) {
    uint8_t bytes_to_read = chip->fifo_len;
    if (bytes_to_read > 18) bytes_to_read = 18;
//     printf("FIFO READ: fifo_len=%d, bytes_to_read=%d, first_byte=0x%02X\n",
//            chip->fifo_len, bytes_to_read, chip->fifo[0]);
   
    memcpy(chip->spi_buffer, chip->fifo, bytes_to_read);
    chip->read_count = bytes_to_read;
   
    // Удаляем прочитанные байты из FIFO, но не очищаем полностью
      fifo_remove_bytes(chip, bytes_to_read);
   
    // Устанавливаем RxIRq только если в FIFO еще есть данные
    if (chip->fifo_len > 0) {
      set_specific_irq_flag(chip, 0x20); // RxIRq
//       printf("RxIRq (0x20) set because FIFO still has %d bytes\n", chip->fifo_len);
    } else {
      clear_irq_flag(chip, 0x20); // Сбросить RxIRq (0x20)
//       printf("RxIRq (0x20) cleared because FIFO is now empty\n");
    }
   
//     printf("FIFO READ complete: buffer prepared with %d bytes, fifo now has %d bytes\n", bytes_to_read, chip->fifo_len);
  } else {
    chip->spi_buffer[0] = 0;
    chip->read_count = 1;
    // printf("FIFO READ: empty FIFO, returning 0\n", chip->fifo_len);
  }
}

static void handle_spi_read_command(chip_state_t *chip) {
  uint8_t val = chip->registers[chip->current_address];
 
  if (chip->current_address == VERSION_REG) {
    read_version_register(chip);
  } else if (chip->current_address == 0x04) {
    read_comirq_register(chip);
  } else if (chip->current_address == 0x0A) {
    read_fifo_level_register(chip);
  } else if (chip->current_address == 0x09) {
    read_fifo_data_register(chip);
  } else if (chip->current_address == 0x0C) {
    // 讀取 ControlReg 對於 MFRC522 庫很重要。
    chip->spi_buffer[0] = val;
    chip->read_count = 1;
//     printf("ControlReg read: 0x%02X (RxLastBits=%d)\n", val, val & 0x07);
  } else if (chip->current_address == 0x06) {
    // 讀取 ErrorReg 對於 MFRC522 函式庫很重要。
    chip->spi_buffer[0] = val;
    chip->read_count = 1;
//     printf("ErrorReg read: 0x%02X\n", val);
  } else if (chip->current_address == 0x36) {
    // Чтение AutoTestReg - важно для self-test
    chip->spi_buffer[0] = val;
    chip->read_count = 1;
//     printf("AutoTestReg read: 0x%02X\n", val);
  } else {
    chip->spi_buffer[0] = val;
    chip->read_count = 1;
  }
}

static void write_fifo_register(chip_state_t *chip, uint8_t val) {
  if (chip->fifo_len < FIFO_SIZE) {
    fifo_push(chip, val);
   
    // Отладка для всех входящих байтов
//     printf("FIFO push: 0x%02X (len %d)", val, chip->fifo_len);
   
    // Если это начало SELECT команды
//     if (val == 0x93 && chip->fifo_len == 1) {
//       printf(" - SELECT command start\n");
//     }
//     // Если это второй байт SELECT (0x70)
//     else if (chip->fifo_len == 2 && chip->fifo[0] == 0x93 && val == 0x70) {
//       printf(" - SELECT second byte\n");
//     }
//     // Если это байты UID в SELECT команде
//     else if (chip->fifo_len >= 3 && chip->fifo_len <= 6 && chip->fifo[0] == 0x93 && chip->fifo[1] == 0x70) {
//       printf(" - SELECT UID byte %d\n", chip->fifo_len - 2);
//     }
//     // Если это байты CRC в SELECT команде
//     else if (chip->fifo_len >= 7 && chip->fifo_len <= 9 && chip->fifo[0] == 0x93 && chip->fifo[1] == 0x70) {
//       printf(" - SELECT CRC byte %d\n", chip->fifo_len - 7);
//     } else {
//       printf("\n");
//     }

    // Проверяем, собираем ли мы SELECT команду
    if (chip->fifo[0] == CMD_SEL_CL1 && chip->anticoll_step == 1) {
//       printf("Building SELECT command: %d bytes received: ", chip->fifo_len);
//       for (int i = 0; i < chip->fifo_len; i++) {
//         printf("%02X ", chip->fifo[i]);
//       }
//       printf("\n");
     
      // Если получили все 9 байт SELECT команды
      if (chip->fifo_len == 9) {
//         printf("Full SELECT command received, processing...\n");
        process_mifare_command(chip);
      }
    }
  } else {
    printf("FIFO full, ignoring: 0x%02X\n", val);
  }
}

static void write_command_register(chip_state_t *chip, uint8_t val) {
//   printf("CommandReg = 0x%02X\n", val);
  switch (val) {
    case CMD_IDLE: // 0x00
      // printf("Command 0x00 - PCD_Idle (Idle)\n");
      // Clear command related IRQ flags: IdleIRq, RxIRq, TxIRq, ErrIRq
      chip->registers[0x04] &= ~(0x10 | 0x20 | 0x40 | 0x08);
      chip->registers[0x01] = 0x00; // Явно устанавливаем CommandReg в Idle
      break;

    case CMD_MEM: // 0x01
      // printf("Command 0x01 - PCD_Mem (Transfer FIFO to internal buffer)\n");
      set_specific_irq_flag(chip, 0x10); // IdleIRq
      chip->registers[0x01] = 0x00; // Go to Idle
      break;

    case CMD_GEN_RANDOM_ID: // 0x02
      // printf("Command 0x02 - PCD_GenerateRandomID (Generate random ID)\n");
      set_specific_irq_flag(chip, 0x10); // IdleIRq
      chip->registers[0x01] = 0x00; // Go to Idle
      break;

    case CMD_CALC_CRC: // 0x03
      // printf("Command 0x03 - PCD_CalcCRC (Calculate CRC)\n");
      if (chip->registers[0x36] == 0x09) { // Self-test mode
        // printf("Self-test mode detected - generating 64 bytes of test data\n");
        const uint8_t self_test_data[64] = {
          0x00, 0xEB, 0x66, 0xBA, 0x57, 0xBF, 0x23, 0x95,
          0xD0, 0xE3, 0x0D, 0x3D, 0x27, 0x89, 0x5C, 0xDE,
          0x9D, 0x3B, 0xA7, 0x00, 0x21, 0x5B, 0x89, 0x82,
          0x51, 0x3A, 0xEB, 0x02, 0x0C, 0xA5, 0x00, 0x49,
          0x7C, 0x84, 0x4D, 0xB3, 0xCC, 0xD2, 0x1B, 0x81,
          0x5D, 0x48, 0x76, 0xD5, 0x71, 0x61, 0x21, 0xA9,
          0x86, 0x96, 0x83, 0x38, 0xCF, 0x9D, 0x5B, 0x6D,
          0xDC, 0x15, 0xBA, 0x3E, 0x7D, 0x95, 0x3B, 0x2F
        };
        chip->fifo_len = 0;
        memcpy(chip->fifo, self_test_data, 64);
        chip->fifo_len = 64;
        update_fifo_level_register(chip);
        // printf("Self-test data generated: 64 bytes in FIFO\n");
      } else {
        perform_crc_calculation(chip);
      }
      chip->registers[0x01] = 0x00; // Go to Idle
      break;

    case CMD_TRANSMIT: // 0x04
      // printf("Command 0x04 - PCD_Transmit (Transmit data from FIFO)\n");
      set_specific_irq_flag(chip, 0x20); // TxIRq
      chip->registers[0x01] = 0x00; // Go to Idle
      break;

    case CMD_RECEIVE: // 0x08
      // printf("Command 0x08 - PCD_Receive (Activate receiver)\n");
      set_specific_irq_flag(chip, 0x10); // IdleIRq
      chip->registers[0x01] = 0x00; // Go to Idle
      break;

    case 0x0C: // PCD_Transceive
      // printf("Command 0x0C - PCD_Transceive (Transmit and receive)\n");
      if (chip->fifo_len > 0) {
    process_mifare_command(chip);
      }
      chip->registers[0x01] = 0x00; // Go to Idle
      break;

    case 0x0E: // PCD_MFAuthent
      // printf("Command 0x0E - PCD_MFAuthent (MIFARE Authenticate)\n");
      if (chip->fifo_len >= 1 && (chip->fifo[0] == CMD_AUTH_A || chip->fifo[0] == CMD_AUTH_B)) {
          printf("Authentication successful (simulated)\n");
          chip->authenticated = true;
          set_specific_irq_flag(chip, 0x10); // IdleIRq
    } else {
          printf("Authentication failed: incorrect command in FIFO (len=%d)\n", chip->fifo_len);
      }
      chip->fifo_len = 0;
      update_fifo_level_register(chip);
      chip->registers[0x01] = 0x00; // Go to Idle
      break;

    case 0x0F: { // PCD_SoftReset
      // printf("Command 0x0F - PCD_SoftReset (Soft Reset)\n");
      reset_chip_state(chip);
      chip->registers[0x01] = 0x00; // Явно сбрасываем CommandReg в Idle
      break;
    }

    case 0x10: { // Special handling for PowerDown bit (bit 4 of CommandReg)
      // This case handles writing 0x10 to CommandReg, which implies PCD_SoftPowerDown.
      // printf("Command 0x10 - PCD_SoftPowerDown (Power Down)\n");
      chip->registers[0x01] = val; // Set the command to PowerDown
      chip->registers[0x04] |= 0x10; // Set IdleIRq (from datasheet, or observation)
      chip->registers[0x04] &= ~(0x20 | 0x40); // Clear RxIRq and TxIRq
      break;
    }

    default:
      // Other commands may just set the CommandReg and let other logic handle it.
      // If it's a command that transitions from PowerDown to Active (PCD_SoftPowerUp)
      // The library does this by writing any non-0x10 value to CommandReg
      if ((chip->registers[0x01] & 0x10) && !(val & 0x10)) {
        // printf("Transition from PowerDown to Active (PCD_SoftPowerUp) by writing 0x%02X\n", val);
        chip->registers[0x01] = val; // Store the new command
        chip->registers[0x04] &= ~0x10; // Clear IdleIRq (indicating wake-up)
        // LoAlertIRq and HiAlertIRq might be set here depending on specific conditions
        // For simplicity, we can set them if the library expects them for a successful power up.
        // As per MFRC522.cpp, PCD_SoftPowerUp expects PowerDown bit to be cleared.
        // It does not explicitly check HiAlertIRq/LoAlertIRq.
      } else {
        chip->registers[0x01] = val; // Default: just store the value
      }
      break;
  }
}

static void handle_spi_write_command(chip_state_t *chip, uint8_t val) {
  uint8_t reg = chip->current_address;

  if (reg == 0x09) {
    write_fifo_register(chip, val);
  } else if (reg == 0x0A) {
    // FIFOLevelReg — возможен флаг FlushBuffer (бит 7)
    if (val & 0x80) {
//       printf("FIFO flush requested (write 0x%02X to FIFOLevelReg). Clearing FIFO (was %d bytes)\n", val, chip->fifo_len);
      chip->fifo_len = 0;
      update_fifo_level_register(chip);
    }
    chip->registers[reg] = val & 0x7F; // сохраняем без бита FlushBuffer
  } else if (reg == 0x01) {
    write_command_register(chip, val);
  }
  else if (reg == 0x04) {
    // Специальная обработка ComIrqReg
    {
//       printf("Direct write to ComIrqReg: 0x%02X (before: 0x%02X)\n", val, chip->registers[reg]);
      // If the library attempts to clear flags by writing 0x7F, clear all flags in our emulator.
      if (val == 0x7F) {
          chip->registers[reg] = 0x00; // Correctly clear all IRQ flags
          // printf("ComIrqReg cleared to 0x00 after 0x7F write.\n");
  } else {
          // For other values, just update the register. Specific flags will be set/cleared by other handlers.
          chip->registers[reg] = val;
//           printf("ComIrqReg after write: 0x%02X\n", chip->registers[reg]);
      }
      return; // Заменено break; на return;
    }
  } else if (reg == 0x08) { // Status2Reg
//     printf("Write to Status2Reg: 0x%02X\n", val);
    // If MFCrypto1On (bit 3) is being cleared, we should exit authenticated state.
    if ((chip->registers[reg] & 0x08) && !(val & 0x08)) {
//         printf("Exiting authenticated state.\n");
        chip->authenticated = false;
    }
    chip->registers[reg] = val;
  } else if (reg == 0x36) { // AutoTestReg
//     printf("Write to AutoTestReg: 0x%02X\n", val);
    chip->registers[reg] = val;
  }
  else {
    chip->registers[reg] = val;
  }
  chip->spi_buffer[0] = 0;
}

// FIFO management functions
static void fifo_push(chip_state_t *chip, uint8_t val) {
  if (chip->fifo_len < FIFO_SIZE) {
    chip->fifo[chip->fifo_len] = val;
    chip->fifo_len++;
    update_fifo_level_register(chip);
  }
}

static void fifo_remove_bytes(chip_state_t *chip, int bytes_to_remove) {
  if (bytes_to_remove > 0 && bytes_to_remove <= chip->fifo_len) {
//     printf("FIFO REMOVE: before memmove, len=%d, content: ", chip->fifo_len);
//     for(int i = 0; i < chip->fifo_len; ++i) printf("%02X ", chip->fifo[i]);
//     printf("\n");

    memmove(chip->fifo, chip->fifo + bytes_to_remove,
            (chip->fifo_len - bytes_to_remove) * sizeof(chip->fifo[0]));
    memset(chip->fifo + (chip->fifo_len - bytes_to_remove), 0, bytes_to_remove); // Explicitly zero out removed part

    chip->fifo_len -= bytes_to_remove;
    update_fifo_level_register(chip);
   
    // MFRC522 сбрасывает RxIRq, когда FIFO пуст после удаления байтов
    if (chip->fifo_len == 0) {
        clear_irq_flag(chip, 0x20); // Сбросить RxIRq (0x20)
//         printf("RxIRq (0x20) cleared because FIFO is empty after remove.\n");
    }
//     printf("FIFO REMOVE: after memmove and zeroing, len=%d, content: ", chip->fifo_len);
//     for(int i = 0; i < chip->fifo_len; ++i) printf("%02X ", chip->fifo[i]);
//     printf("\n");
  }
}

static void update_fifo_level_register(chip_state_t *chip) {
  chip->registers[0x0A] = chip->fifo_len;
}

// State management functions
static void reset_chip_state(chip_state_t *chip) {
  chip->authenticated = false;
  chip->card_selected = false;
  chip->anticoll_step = 0;
  chip->uid_read_completed = false;
  chip->cascade_level = 1;
  chip->current_level_known_bits = 0;
  chip->select_completed = false;
  chip->select_response_sent = 0;
  chip->registers[0x04] = 0;  // Сбрасываем все флаги IRQ
//   printf("Chip state reset - ComIrqReg cleared to 0x00\n");
}

static void set_irq_flag(chip_state_t *chip) {
//   printf("set_irq_flag called - before: ComIrqReg: 0x%02X\n", chip->registers[0x04]);
  chip->registers[0x04] |= (0x10 | 0x20);  // IdleIRq (0x10) + RxIRq (0x20)
  chip->registers[0x0D] &= ~0x80;
//   printf("set_irq_flag called - after: ComIrqReg: 0x%02X\n", chip->registers[0x04]);
}

static void clear_irq_flag(chip_state_t *chip, uint8_t flag) {
  chip->registers[0x04] &= ~flag;
//   printf("IRQ flag 0x%02X cleared - ComIrqReg: 0x%02X\n", flag, chip->registers[0x04]);
}

static void set_specific_irq_flag(chip_state_t *chip, uint8_t flag) {
//   printf("Before setting flag 0x%02X - ComIrqReg: 0x%02X\n", flag, chip->registers[0x04]);
  chip->registers[0x04] |= flag;
//   printf("After setting flag 0x%02X - ComIrqReg: 0x%02X\n", flag, chip->registers[0x04]);
}

static void log_chip_state(chip_state_t *chip) {
//   printf("=== CHIP STATE ===\n");
//   printf("ComIrqReg: 0x%02X, FIFOLevel: %d, ControlReg: 0x%02X\n",
//          chip->registers[0x04], chip->registers[0x0A], chip->registers[0x0C]);
//   printf("FIFO content: ");
//   for (int i = 0; i < chip->fifo_len && i < 8; i++) {
//     printf("%02X ", chip->fifo[i]);
//   }
//   printf("(len=%d)\n", chip->fifo_len);
//   printf("anticoll_step: %d, card_selected: %s, uid_read_completed: %s, select_completed: %s\n",
//          chip->anticoll_step, chip->card_selected ? "true" : "false",
//          chip->uid_read_completed ? "true" : "false", chip->select_completed ? "true" : "false");
//   printf("====================\n");
}

void send_ack_response(chip_state_t *chip) {
    chip->fifo_len = 0;
    chip->fifo[0] = 0x0A;
    chip->fifo_len = 1;
    update_fifo_level_register(chip);
    set_specific_irq_flag(chip, 0x20); // RxIRq
    chip->registers[0x0C] = (chip->registers[0x0C] & ~0x07) | 0x04; // Set RxLastBits to 4
//    printf("Sent ACK (0x0A). FIFO len: %d\n", chip->fifo_len);
}

// Global chip state
static chip_state_t g_chip_state;

void chip_pin_change(void *user_data, pin_t pin, uint32_t value);
void chip_spi_done(void *user_data, uint8_t *buffer, uint32_t count);

3) 建立 diagram.json
{
  "version": 1,
  "author": "Student",
  "editor": "wokwi",
  "parts": [
    { "type": "board-esp32-devkit-c-v4", "id": "esp", "top": -48, "left": -71.96, "attrs": {} },
    { "type": "chip-rfid-rc522", "id": "chip1", "top": -18.18, "left": 216, "attrs": {} },
    {
      "type": "wokwi-lcd1602",
      "id": "lcd1",
      "top": 73.6,
      "left": 207.2,
      "attrs": { "pins": "i2c" }
    },
    {
      "type": "wokwi-led",
      "id": "led1",
      "top": 82.8,
      "left": 71.4,
      "attrs": { "color": "red", "flip": "1" }
    },
    {
      "type": "wokwi-resistor",
      "id": "r1",
      "top": 176.75,
      "left": 38.4,
      "attrs": { "value": "220" }
    }
  ],
  "connections": [
    [ "chip1:CS", "esp:5", "red", [ "h-67.2", "v76.8" ] ],
    [ "chip1:SCK", "esp:18", "violet", [ "h-86.4", "v57.6" ] ],
    [ "chip1:MOSI", "esp:23", "blue", [ "h-105.6", "v-19.2" ] ],
    [ "chip1:MISO", "esp:19", "purple", [ "h-134.4", "v0", "v28.8" ] ],
    [ "chip1:GND", "esp:GND.1", "black", [ "h-48", "v153.6", "h-249.6", "v-86.4" ] ],
    [ "chip1:RST", "esp:22", "green", [ "h-115.2", "v-48" ] ],
    [ "chip1:VCC", "esp:3.3V", "red", [ "v0" ] ],
    [ "chip1:VCC", "esp:3V3", "red", [ "h-38.4", "v-124.8", "h-249.6", "v48" ] ],
    [ "esp:TX", "esp:RX", "red", [ "h28.8", "v9.6" ] ],
    [ "chip1:GND", "lcd1:GND", "black", [ "h-48", "v67.2" ] ],
    [ "chip1:VCC", "lcd1:VCC", "red", [ "h-38.4", "v57.6" ] ],
    [ "esp:17", "lcd1:SDA", "green", [ "h115.2", "v48" ] ],
    [ "esp:16", "lcd1:SCL", "green", [ "h96", "v48", "h9.6" ] ],
    [ "esp:2", "led1:A", "green", [ "h28.8", "v19.2" ] ],
    [ "led1:C", "r1:2", "green", [ "v0" ] ],
    [ "esp:GND.1", "r1:1", "black", [ "h-9.45", "v86.4" ] ]
  ],
  "dependencies": {}
}

3) 建立 撰寫 sketch.ino

4) 建立 Library Manager 的程式庫

Telegram +ESP32自動發報機

  Telegram   +ESP32自動發報機 這套系統是一個典型的 IoT(物聯網)架構 ,結合了遠端配置(Python)、通訊中介(MQTT)與硬體執行(ESP32)。 以下我為您拆解這兩支程式的核心運作原理: 一、 系統架構流程 Python 端 (控制台) :使用者輸入...