2025年9月20日 星期六

抓YT音樂影片且轉成mp3 (Python)

 抓YT音樂影片且轉成mp3 (Python)







import tkinter as tk

from tkinter import messagebox, filedialog

from pytubefix import YouTube

from pytubefix.cli import on_progress # pytubefix 提供的進度回調函數

import ssl

import os

import threading


# 解決 pytubefix 在某些環境下可能遇到的 SSL 憑證問題

ssl._create_default_https_context = ssl._create_stdlib_context


class YouTubeDownloaderApp:

    def __init__(self, master):

        self.master = master

        master.title("YouTube MP3 下載器 (pytubefix)")

        master.geometry("500x200")

        master.resizable(False, False)


        # 網址輸入框

        self.url_label = tk.Label(master, text="YouTube 影片網址:")

        self.url_label.pack(pady=(20, 5))


        self.url_entry = tk.Entry(master, width=60)

        self.url_entry.pack(pady=5)

        # 預設一個範例網址

        self.url_entry.insert(0, 'https://www.youtube.com/watch?v=R93ce4FZGbc') # 您可以替換為任何 YouTube 網址


        # 下載按鈕

        self.download_button = tk.Button(master, text="下載 MP3", command=self.start_download_thread)

        self.download_button.pack(pady=10)


        # 狀態訊息標籤

        self.status_label = tk.Label(master, text="等待輸入網址...", fg="blue")

        self.status_label.pack(pady=5)


    def download_mp3(self, url):

        """實際執行 MP3 下載的函數"""

        try:

            self.status_label.config(text="正在處理網址...", fg="orange")

            

            # 建立 YouTube 物件並設定進度回調函數

            # 我們使用一個 lambda 函數來包裹 on_progress,以便在 Tkinter 中更新狀態

            yt = YouTube(url, on_progress_callback=lambda stream, chunk, bytes_remaining: self.update_progress(stream, chunk, bytes_remaining))

            

            self.status_label.config(text=f"找到影片: {yt.title},開始下載...", fg="orange")

            

            # 選擇純音頻流(因為您提到了 MP3 音樂)

            audio_stream = yt.streams.filter(only_audio=True).first()


            if not audio_stream:

                raise Exception("找不到可下載的音頻流。")

            

            # 彈出視窗讓使用者選擇儲存路徑和檔案名稱

            file_path = filedialog.asksaveasfilename(

                defaultextension=".mp3",

                initialfile=f"{yt.title}.mp3",

                title="選擇儲存 MP3 的位置",

                filetypes=[("MP3 files", "*.mp3")]

            )


            if not file_path:

                self.status_label.config(text="下載已取消。", fg="red")

                return


            self.status_label.config(text="下載中,請稍候...", fg="blue")

            

            # 下載音頻

            audio_stream.download(output_path=os.path.dirname(file_path), filename=os.path.basename(file_path))

            

            self.status_label.config(text=f"下載完成!", fg="green")

            messagebox.showinfo("下載完成", f"'{yt.title}' 已成功下載!")


        except Exception as e:

            self.status_label.config(text=f"下載失敗: {e}", fg="red")

            messagebox.showerror("下載錯誤", f"下載影片時發生錯誤: {e}")

        finally:

            self.download_button.config(state=tk.NORMAL) # 重新啟用按鈕


    def update_progress(self, stream, chunk, bytes_remaining):

        """自定義的進度回調函數,用於更新 Tkinter 狀態標籤"""

        total_size = stream.filesize

        bytes_downloaded = total_size - bytes_remaining

        percentage = (bytes_downloaded / total_size) * 100

        self.status_label.config(text=f"下載中: {percentage:.2f}%...", fg="blue")

        self.master.update_idletasks() # 強制更新 GUI 以顯示最新進度


    def start_download_thread(self):

        """在單獨的執行緒中啟動下載,避免 GUI 凍結"""

        url = self.url_entry.get()

        if not url:

            messagebox.showwarning("警告", "請輸入 YouTube 影片網址!")

            return

        

        self.download_button.config(state=tk.DISABLED) # 禁用按鈕防止重複點擊

        self.status_label.config(text="準備下載...", fg="blue")


        # 創建並啟動新執行緒

        download_thread = threading.Thread(target=self.download_mp3, args=(url,))

        download_thread.start()


# 啟動應用程式

if __name__ == "__main__":

    root = tk.Tk()

    app = YouTubeDownloaderApp(root)

    root.mainloop()



//=====================================================

import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from pytubefix import YouTube
import ssl
import os
import threading

# 解決 pytubefix 在某些環境下可能遇到的 SSL 憑證問題
ssl._create_default_https_context = ssl._create_stdlib_context

class YouTubeDownloaderApp:
    def __init__(self, master):
        self.master = master
        master.title("YouTube MP3 下載器 (pytubefix)")
        master.geometry("500x250")
        master.resizable(False, False)

        # 網址輸入框
        self.url_label = tk.Label(master, text="YouTube 影片網址:")
        self.url_label.pack(pady=(20, 5))

        self.url_entry = tk.Entry(master, width=60)
        self.url_entry.pack(pady=5)
        # 預設一個範例網址
        self.url_entry.insert(0, 'https://www.youtube.com/watch?v=R93ce4FZGbc')

        # 下載按鈕
        self.download_button = tk.Button(master, text="下載 MP3", command=self.start_download_thread)
        self.download_button.pack(pady=10)

        # 狀態訊息標籤
        self.status_label = tk.Label(master, text="等待輸入網址...", fg="blue")
        self.status_label.pack(pady=5)
        
        # 進度條
        self.progress_bar = ttk.Progressbar(
            master,
            orient="horizontal",
            length=400,
            mode="determinate"
        )
        self.progress_bar.pack(pady=10)
        self.progress_bar['value'] = 0

    def download_mp3(self, url):
        """實際執行 MP3 下載的函數"""
        try:
            self.status_label.config(text="正在處理網址...", fg="orange")
            self.progress_bar['value'] = 0
            
            # 建立 YouTube 物件並設定進度回調函數
            yt = YouTube(url, on_progress_callback=self.update_progress)
            
            self.status_label.config(text=f"找到影片: {yt.title},開始下載...", fg="orange")
            
            # 選擇純音頻流
            audio_stream = yt.streams.filter(only_audio=True).first()

            if not audio_stream:
                raise Exception("找不到可下載的音頻流。")
            
            # 彈出視窗讓使用者選擇儲存路徑和檔案名稱
            file_path = filedialog.asksaveasfilename(
                defaultextension=".mp3",
                initialfile=f"{yt.title}.mp3",
                title="選擇儲存 MP3 的位置",
                filetypes=[("MP3 files", "*.mp3")]
            )

            if not file_path:
                self.status_label.config(text="下載已取消。", fg="red")
                self.progress_bar['value'] = 0
                return

            self.status_label.config(text="下載中,請稍候...", fg="blue")
            
            # 下載音頻
            audio_stream.download(output_path=os.path.dirname(file_path), filename=os.path.basename(file_path))
            
            self.status_label.config(text=f"下載完成!", fg="green")
            self.progress_bar['value'] = 100
            messagebox.showinfo("下載完成", f"'{yt.title}' 已成功下載!")

        except Exception as e:
            self.status_label.config(text=f"下載失敗: {e}", fg="red")
            self.progress_bar['value'] = 0
            messagebox.showerror("下載錯誤", f"下載影片時發生錯誤: {e}")
        finally:
            self.download_button.config(state=tk.NORMAL) # 重新啟用按鈕

    def update_progress(self, stream, chunk, bytes_remaining):
        """用於更新 Tkinter 狀態標籤和進度條"""
        total_size = stream.filesize
        bytes_downloaded = total_size - bytes_remaining
        percentage = (bytes_downloaded / total_size) * 100
        
        # 更新進度條
        self.progress_bar['value'] = percentage
        
        # 更新狀態標籤
        self.status_label.config(text=f"下載中: {percentage:.2f}%...")
        self.master.update_idletasks() # 強制更新 GUI 以顯示最新進度

    def start_download_thread(self):
        """在單獨的執行緒中啟動下載,避免 GUI 凍結"""
        url = self.url_entry.get()
        if not url:
            messagebox.showwarning("警告", "請輸入 YouTube 影片網址!")
            return
        
        self.download_button.config(state=tk.DISABLED) # 禁用按鈕防止重複點擊
        self.status_label.config(text="準備下載...", fg="blue")

        # 創建並啟動新執行緒
        download_thread = threading.Thread(target=self.download_mp3, args=(url,))
        download_thread.start()

# 啟動應用程式
if __name__ == "__main__":
    root = tk.Tk()
    app = YouTubeDownloaderApp(root)
    root.mainloop()

第二次作業 模擬 RFID(要按PB) 輸出UID至MQTT Broker 後給Python TKinter 暨 Node-Red (Sqlite資料庫) 另一路至Telegram

第二次作業   

模擬 RFID(要按PB) 輸出UID至MQTT Broker 後給Python TKinter 暨 Node-Red (Sqlite資料庫) 另一路至Telegram 



結果  https://www.youtube.com/watch?v=U-_k7d53rYI&t=10s

Telegram 安裝


參考  https://alex9ufoexploer.blogspot.com/2025/04/telegram.html

Telegram 完全教學攻略:從註冊、中文化、設定到各種應用技巧一次學會
https://www.techbang.com/posts/76688-telegram-complete-lying-on-teaching-strategies-from-registration-chinese-setting-stoking-to-a-variety-of-application-techniques-learned-at-once
iOS下載網址:https://reurl.cc/VaKlNQ
Android下載網址:https://reurl.cc/alQALY
PC/Mac/Linux下載網址:https://reurl.cc/lL8Ag6
macOS下載網址:https://reurl.cc/EKkeeK
網頁版連結:https://reurl.cc/mdaAAM







WOKWI硬體 利用PB壓下後產生亂數模擬RFID UID碼




(需修改 )

// Telegram
#define BOTtoken "8023906815:AAE0KApbm5Ng00VCvO57JWA_XKtbx6a4IXM"
#define CHAT_ID "7965218469"
否則您的telegram 會無法收到訊息
Wokwi程式 

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>   // Telegram Bot Library
#include <ArduinoMqttClient.h>

// WiFi 參數
char ssid[] = "Wokwi-GUEST";
char pass[] = "";

// MQTT
WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);
const char broker[] = "broker.mqttgo.io";
int port = 1883;
const char *PubTopic3 = "alex9ufo/esp32/RFID";

// Telegram
#define BOTtoken "802131906815:AAE0KApbm5Ng100VCvO57JWA1_XKtbx6a4IXM"
#define CHAT_ID "796152218469"
WiFiClientSecure client1;
UniversalTelegramBot bot(BOTtoken, client1);

// 狀態變數
int inPin = 12;
String RFIDjson = "";
unsigned long lastCheck = 0;

//===========================================================
// WiFi 初始化
void setup_wifi() {
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected, IP: " + WiFi.localIP().toString());
}

//===========================================================
void setup() {
  Serial.begin(115200);
  pinMode(inPin, INPUT);
  randomSeed(analogRead(0));

  setup_wifi();
  client1.setInsecure();

  bot.sendMessage(CHAT_ID, "ESP32 啟動完成,按PB健", "");

  if (!mqttClient.connect(broker, port)) {
    Serial.println("MQTT connection failed!");
    while (1);
  }
  Serial.println("Connected to MQTT broker!");
}

//===========================================================
void loop() {
  // MQTT 維持連線
  mqttClient.poll();

  // 每 50ms 檢查一次 RFID (避免太頻繁觸發)
  if (millis() - lastCheck > 50) {
    lastCheck = millis();

    if (digitalRead(inPin) == HIGH) {
      // 模擬 RFID UID
      struct RFID_UID { uint8_t uidByte[10]; uint8_t size; };
      RFID_UID uid;
      uid.size = 4;
      for (int i = 0; i < uid.size; i++) {
        uid.uidByte[i] = random(256);
      }

      RFIDjson = "{ \"uid\": \"";
      for (int i = 0; i < uid.size; i++) {
        RFIDjson += String(uid.uidByte[i], HEX);
      }
      RFIDjson += "\" }";

      // 發送到 MQTT
      if (mqttClient.connect(broker, port)) {
        mqttClient.beginMessage(PubTopic3, RFIDjson.length(), false, 1, false);
        mqttClient.print(RFIDjson);
        mqttClient.endMessage();

        // 發送到 Telegram
        bot.sendMessage(CHAT_ID, RFIDjson, "");  

        Serial.println("RFID sent: " + RFIDjson);
      }

      delay(500);  // 防止連續觸發
    }
  }
}




# 程式中 資料庫路徑

DB_PATH = r"D:\2025RFID\2025-09下學期\EX2\rfid.db"

請修改為您存放的路徑

Python程式


import tkinter as tk

from tkinter import ttk

import sqlite3

import paho.mqtt.client as mqtt

from datetime import datetime


# 資料庫路徑

DB_PATH = r"D:\2025RFID\2025-09下學期\EX2\rfid.db"

MQTT_BROKER = "broker.mqttgo.io"

MQTT_PORT = 1883

MQTT_TOPIC = "alex9ufo/esp32/RFID"


# 建立資料表(如果尚未存在)

def init_db():

    conn = sqlite3.connect(DB_PATH)

    cursor = conn.cursor()

    cursor.execute("""

        CREATE TABLE IF NOT EXISTS rfid_logs (

            id INTEGER PRIMARY KEY AUTOINCREMENT,

            uid TEXT NOT NULL,

            date TEXT NOT NULL,

            time TEXT NOT NULL

        )

    """)

    conn.commit()

    conn.close()


# 插入新資料

def insert_uid(uid):

    now = datetime.now()

    date_str = now.strftime("%Y-%m-%d")

    time_str = now.strftime("%H:%M:%S")

    conn = sqlite3.connect(DB_PATH)

    cursor = conn.cursor()

    cursor.execute("INSERT INTO rfid_logs (uid, date, time) VALUES (?, ?, ?)", (uid, date_str, time_str))

    conn.commit()

    conn.close()

    refresh_table()


# 讀取資料並更新表格

def refresh_table():

    for row in tree.get_children():

        tree.delete(row)

    conn = sqlite3.connect(DB_PATH)

    cursor = conn.cursor()

    cursor.execute("SELECT * FROM rfid_logs ORDER BY id DESC")

    rows = cursor.fetchall()

    for row in rows:

        tree.insert("", tk.END, values=row)

    conn.close()


# MQTT 訊息處理

def on_connect(client, userdata, flags, rc):

    client.subscribe(MQTT_TOPIC)


def on_message(client, userdata, msg):

    uid = msg.payload.decode()

    insert_uid(uid)


# 建立 GUI

root = tk.Tk()

root.title("RFID 資料顯示")

root.geometry("600x400")


columns = ("id", "uid", "date", "time")

tree = ttk.Treeview(root, columns=columns, show="headings")

for col in columns:

    tree.heading(col, text=col)

    tree.column(col, width=100)

tree.pack(fill=tk.BOTH, expand=True)


# 初始化資料庫與表格

init_db()

refresh_table()


# 啟動 MQTT 客戶端

client = mqtt.Client()

client.on_connect = on_connect

client.on_message = on_message

client.connect(MQTT_BROKER, MQTT_PORT, 60)

client.loop_start()


# 啟動 GUI 主迴圈

root.mainloop()


模擬一個 RFID 打卡記錄系統的桌面端監控程式。

  1. MQTT 即時監聽 (Real-time Listener): 程式作為一個 MQTT 客戶端,持續連線到 broker.mqttgo.io:1883 並訂閱 alex9ufo/esp32/RFID 主題。

  2. 數據處理與儲存 (Data Logging): 一旦收到來自 MQTT 的訊息(被視為 RFID 卡號 UID),程式會自動獲取當前的日期和時間,將 UID、日期、時間 一起寫入本地的 rfid.db 資料庫中的 rfid_logs 資料表。

  3. 圖形化介面顯示 (GUI): 使用 Tkinter 創建一個簡單的視窗,內含一個表格 (ttk.Treeview),用於即時顯示資料庫中的所有打卡記錄。每次有新的記錄插入時,表格會自動更新,確保使用者看到最新的數據。

import tkinter as tk

from tkinter import ttk

import sqlite3

import paho.mqtt.client as mqtt

from datetime import datetime


# ==============================================================================

# 程式配置區塊 (Configuration Block)

# ==============================================================================


# 資料庫路徑

DB_PATH = r"D:\2025RFID\2025-09下學期\EX2\rfid.db"

# MQTT Broker 伺服器位址

MQTT_BROKER = "broker.mqttgo.io"

# MQTT Broker 連接埠 (標準未加密連線)

MQTT_PORT = 1883

# MQTT 訂閱及發佈的主題名稱

MQTT_TOPIC = "alex9ufo/esp32/RFID"


# ==============================================================================

# 資料庫操作函式 (Database Functions)

# ==============================================================================


# 建立資料表(如果尚未存在)

def init_db():

    # 連接到 SQLite 資料庫檔案

    conn = sqlite3.connect(DB_PATH)

    cursor = conn.cursor()

    # 執行 SQL 語句:建立 rfid_logs 資料表

    # 包含 id (主鍵, 自動遞增)、uid (卡號)、date (日期)、time (時間)

    cursor.execute("""

        CREATE TABLE IF NOT EXISTS rfid_logs (

            id INTEGER PRIMARY KEY AUTOINCREMENT,

            uid TEXT NOT NULL,

            date TEXT NOT NULL,

            time TEXT NOT NULL

        )

    """)

    conn.commit() # 提交變更

    conn.close() # 關閉連線


# 插入新資料 (由 MQTT 觸發)

def insert_uid(uid):

    # 獲取當前時間

    now = datetime.now()

    # 格式化日期為 YYYY-MM-DD

    date_str = now.strftime("%Y-%m-%d")

    # 格式化時間為 HH:MM:SS

    time_str = now.strftime("%H:%M:%S")

    

    conn = sqlite3.connect(DB_PATH)

    cursor = conn.cursor()

    # 執行 INSERT 語句,使用 '?' 作為佔位符防止 SQL 注入

    cursor.execute("INSERT INTO rfid_logs (uid, date, time) VALUES (?, ?, ?)", (uid, date_str, time_str))

    conn.commit()

    conn.close()

    

    # 插入新數據後,立即更新 GUI 表格顯示

    refresh_table()


# 讀取資料並更新表格 (GUI 介面)

def refresh_table():

    # 1. 清空 Treeview 中現有的所有行

    for row in tree.get_children():

        tree.delete(row)

        

    # 2. 從資料庫讀取最新數據

    conn = sqlite3.connect(DB_PATH)

    cursor = conn.cursor()

    # 查詢所有數據,並按 ID 倒序排列 (DESC),最新的記錄在最上面

    cursor.execute("SELECT * FROM rfid_logs ORDER BY id DESC")

    rows = cursor.fetchall() # 獲取所有結果

    conn.close()

    

    # 3. 將數據插入到 Treeview

    for row in rows:

        # tree.insert(父節點, 位置, 值)

        tree.insert("", tk.END, values=row)


# ==============================================================================

# MQTT 客戶端回調函式 (MQTT Client Callbacks)

# ==============================================================================


# 連線成功時的回調函式

def on_connect(client, userdata, flags, rc):

    # rc = 0 代表連線成功

    if rc == 0:

        # 連線成功後,訂閱指定的主題

        client.subscribe(MQTT_TOPIC)

        # 可以添加 print("MQTT 連線成功並已訂閱") 進行除錯

    

# 收到 MQTT 訊息時的回調函式

def on_message(client, userdata, msg):

    # 將收到的二進制 payload 轉換為字串 (即 RFID UID)

    uid = msg.payload.decode()

    # 呼叫函式將此 UID 寫入資料庫

    insert_uid(uid)


# ==============================================================================

# GUI 介面設定 (GUI Setup)

# ==============================================================================


# 建立主視窗

root = tk.Tk()

root.title("RFID 資料顯示")

root.geometry("600x400") # 設定視窗初始大小


# 定義表格的欄位名稱

columns = ("id", "uid", "date", "time")

# 建立 Treeview 表格組件

tree = ttk.Treeview(root, columns=columns, show="headings")


# 配置每個欄位

for col in columns:

    tree.heading(col, text=col) # 設定欄位標題

    tree.column(col, width=100) # 設定欄位寬度

    

# 將表格組件放置到主視窗中,fill=tk.BOTH 擴展填充,expand=True 允許隨視窗縮放

tree.pack(fill=tk.BOTH, expand=True)


# ==============================================================================

# 初始化與啟動 (Initialization and Start)

# ==============================================================================


# 執行資料庫初始化 (建立資料表)

init_db()

# 首次讀取資料庫數據並顯示在表格中

refresh_table()


# 啟動 MQTT 客戶端

client = mqtt.Client()

# 設定連線成功和收到訊息時的回調函式

client.on_connect = on_connect

client.on_message = on_message

# 連接到 Broker

client.connect(MQTT_BROKER, MQTT_PORT, 60)

# 以非阻塞方式在後台運行網路循環,確保 GUI 不會被 MQTT 循環阻塞

client.loop_start()


# 啟動 GUI 主迴圈 (程式從此進入事件驅動模式,等待用戶操作或 MQTT 訊息)

root.mainloop()



//========================================

Node-RED 安裝步驟暨執行node-red程式

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

Node-Red安裝 與 dashboard 安裝與使用範例

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

Node-Red安裝 與 dashboard 安裝

https://sites.google.com/site/wenyunotify/12-node-red/01-%E5%85%A5%E9%96%80%E7%AF%87

DB Browser for SQLite 的安裝

https://alex9ufoexploer.blogspot.com/2025/07/db-browser-for-sqlite.html





(安裝node-red , dashboard , sqlitedb, telegrambot 節點 才能匯入程式 )

留意 rfid.db 存放位置 D:\2025RFID\2025-09下學期\EX2\rfid.db 

請修改為您存放的路徑

Node-Red程式 

[

    {

        "id": "ec87aef83806bf77",

        "type": "tab",

        "label": "RFID_2025",

        "disabled": false,

        "info": "",

        "env": []

    },

    {

        "id": "5318e15b20b2d057",

        "type": "mqtt in",

        "z": "ec87aef83806bf77",

        "name": "RFID UID In",

        "topic": "alex9ufo/esp32/RFID",

        "qos": "1",

        "datatype": "auto-detect",

        "broker": "450dc18180d64bb4",

        "nl": false,

        "rap": true,

        "rh": 0,

        "inputs": 0,

        "x": 110,

        "y": 380,

        "wires": [

            [

                "43e89ebe38071896",

                "d189cd151fb44415"

            ]

        ]

    },

    {

        "id": "09f57e8989839b3f",

        "type": "ui_text",

        "z": "ec87aef83806bf77",

        "group": "01def7a22d7e0b47",

        "order": 1,

        "width": 4,

        "height": 1,

        "name": "UID",

        "label": "UID",

        "format": "{{msg.payload}} ",

        "layout": "row-left",

        "className": "",

        "style": false,

        "font": "",

        "fontSize": "",

        "color": "#000000",

        "x": 390,

        "y": 420,

        "wires": []

    },

    {

        "id": "a5f9d8b1a41e041a",

        "type": "ui_text",

        "z": "ec87aef83806bf77",

        "group": "01def7a22d7e0b47",

        "order": 2,

        "width": 4,

        "height": 1,

        "name": "Current Time",

        "label": "",

        "format": "<span style=\"font-weight:bold;\">{{msg.payload}}</span>",

        "layout": "row-spread",

        "className": "",

        "style": false,

        "font": "",

        "fontSize": "",

        "color": "#000000",

        "x": 530,

        "y": 660,

        "wires": []

    },

    {

        "id": "3276e8ef114117e3",

        "type": "inject",

        "z": "ec87aef83806bf77",

        "name": "Current Time",

        "props": [

            {

                "p": "payload"

            },

            {

                "p": "topic",

                "vt": "str"

            }

        ],

        "repeat": "1",

        "crontab": "",

        "once": true,

        "onceDelay": "0.1",

        "topic": "",

        "payload": "",

        "payloadType": "date",

        "x": 120,

        "y": 660,

        "wires": [

            [

                "b69f3cf69db70f15"

            ]

        ]

    },

    {

        "id": "b69f3cf69db70f15",

        "type": "function",

        "z": "ec87aef83806bf77",

        "name": "Format Date/Time",

        "func": "const now = new Date(msg.payload);\nconst date = now.toLocaleDateString();\nconst time = now.toLocaleTimeString();\nmsg.payload = `${date} ${time}`;\nreturn msg;",

        "outputs": 1,

        "timeout": "",

        "noerr": 0,

        "initialize": "",

        "finalize": "",

        "libs": [],

        "x": 330,

        "y": 660,

        "wires": [

            [

                "a5f9d8b1a41e041a"

            ]

        ]

    },

    {

        "id": "c43ba373c649a667",

        "type": "function",

        "z": "ec87aef83806bf77",

        "name": "Log UID",

        "func": "const now = new Date();\nconst rdate = now.toISOString().split('T')[0];\nconst rtime = now.toTimeString().split(' ')[0];\nvar rtemp= msg.payload ;\nmsg.topic = \"INSERT INTO rfid_logs (uid ,date, time) VALUES ($temp , $date, $time )\";\nmsg.payload = [rtemp ,rdate, rtime ];\nreturn msg;\n\n//CREATE TABLE IF NOT EXISTS rfid_logs \n//(id INTEGER PRIMARY KEY AUTOINCREMENT,\n//uid TEXT NOT NULL,\n//date TEXT NOT NULL,\n//time TEXT NOT NULL)",

        "outputs": 1,

        "timeout": "",

        "noerr": 0,

        "initialize": "",

        "finalize": "",

        "libs": [],

        "x": 400,

        "y": 380,

        "wires": [

            [

                "eaf68880f7e1e8a3"

            ]

        ]

    },

    {

        "id": "121608f3028a5a08",

        "type": "sqlite",

        "z": "ec87aef83806bf77",

        "mydb": "9c0419f225af3721",

        "sqlquery": "msg.topic",

        "sql": "{{msg.topic}}",

        "name": "RFID SQLite DB",

        "x": 500,

        "y": 320,

        "wires": [

            [

                "306c76656d27a7b8"

            ]

        ]

    },

    {

        "id": "5616bc8197f9d0b3",

        "type": "inject",

        "z": "ec87aef83806bf77",

        "name": "Refresh DB",

        "props": [

            {

                "p": "payload"

            },

            {

                "p": "topic",

                "vt": "str"

            }

        ],

        "repeat": "",

        "crontab": "",

        "once": true,

        "onceDelay": "0.1",

        "topic": "SELECT id, date, time, event FROM events ORDER BY id DESC",

        "payload": "",

        "payloadType": "date",

        "x": 130,

        "y": 560,

        "wires": [

            [

                "0963eb3d0d4dcbd0"

            ]

        ]

    },

    {

        "id": "9fad4c0dccccf994",

        "type": "ui_table",

        "z": "ec87aef83806bf77",

        "group": "01def7a22d7e0b47",

        "name": "Database View",

        "order": 3,

        "width": 8,

        "height": 12,

        "columns": [

            {

                "field": "id",

                "title": "ID碼",

                "width": "",

                "align": "center",

                "formatter": "plaintext",

                "formatterParams": {

                    "target": "_blank"

                }

            },

            {

                "field": "uid",

                "title": "UID號碼",

                "width": "",

                "align": "left",

                "formatter": "plaintext",

                "formatterParams": {

                    "target": "_blank"

                }

            },

            {

                "field": "date",

                "title": "日期",

                "width": "",

                "align": "center",

                "formatter": "plaintext",

                "formatterParams": {

                    "target": "_blank"

                }

            },

            {

                "field": "time",

                "title": "時間",

                "width": "",

                "align": "center",

                "formatter": "plaintext",

                "formatterParams": {

                    "target": "_blank"

                }

            }

        ],

        "outputs": 0,

        "cts": false,

        "x": 700,

        "y": 560,

        "wires": []

    },

    {

        "id": "0963eb3d0d4dcbd0",

        "type": "sqlite",

        "z": "ec87aef83806bf77",

        "mydb": "9c0419f225af3721",

        "sqlquery": "msg.topic",

        "sql": "{{msg.topic}}",

        "name": "RFID Query DB",

        "x": 480,

        "y": 560,

        "wires": [

            [

                "9fad4c0dccccf994"

            ]

        ]

    },

    {

        "id": "f3f1c42856e97243",

        "type": "inject",

        "z": "ec87aef83806bf77",

        "name": "Create Table Once",

        "props": [

            {

                "p": "payload"

            },

            {

                "p": "topic",

                "vt": "str"

            }

        ],

        "repeat": "",

        "crontab": "",

        "once": true,

        "onceDelay": "2",

        "topic": "CREATE TABLE IF NOT EXISTS rfid_logs (id INTEGER PRIMARY KEY AUTOINCREMENT,uid TEXT NOT NULL,date TEXT NOT NULL,time TEXT NOT NULL)",

        "payload": "",

        "payloadType": "date",

        "x": 170,

        "y": 320,

        "wires": [

            [

                "121608f3028a5a08"

            ]

        ]

    },

    {

        "id": "306c76656d27a7b8",

        "type": "debug",

        "z": "ec87aef83806bf77",

        "name": "debug ",

        "active": true,

        "tosidebar": true,

        "console": false,

        "tostatus": false,

        "complete": "payload",

        "targetType": "msg",

        "statusVal": "",

        "statusType": "auto",

        "x": 710,

        "y": 320,

        "wires": []

    },

    {

        "id": "eaf68880f7e1e8a3",

        "type": "sqlite",

        "z": "ec87aef83806bf77",

        "mydb": "9c0419f225af3721",

        "sqlquery": "msg.topic",

        "sql": "{{msg.topic}}",

        "name": "RFID SQLite DB",

        "x": 560,

        "y": 380,

        "wires": [

            [

                "306c76656d27a7b8"

            ]

        ]

    },

    {

        "id": "e7e7a5822097d8ec",

        "type": "inject",

        "z": "ec87aef83806bf77",

        "name": "Current Time",

        "props": [

            {

                "p": "payload"

            },

            {

                "p": "topic",

                "vt": "str"

            }

        ],

        "repeat": "5",

        "crontab": "",

        "once": true,

        "onceDelay": "0.1",

        "topic": "",

        "payload": "",

        "payloadType": "date",

        "x": 120,

        "y": 600,

        "wires": [

            [

                "d1656423a801e0d6"

            ]

        ]

    },

    {

        "id": "d1656423a801e0d6",

        "type": "function",

        "z": "ec87aef83806bf77",

        "name": "function  query",

        "func": "msg.topic = \"SELECT id, uid, date, time FROM rfid_logs ORDER BY id DESC\";\nreturn msg;",

        "outputs": 1,

        "timeout": 0,

        "noerr": 0,

        "initialize": "",

        "finalize": "",

        "libs": [],

        "x": 300,

        "y": 600,

        "wires": [

            [

                "0963eb3d0d4dcbd0"

            ]

        ]

    },

    {

        "id": "c35bdfe07b11b681",

        "type": "comment",

        "z": "ec87aef83806bf77",

        "name": "RFID_uid",

        "info": "CREATE TABLE IF NOT EXISTS rfid_logs (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    uid TEXT NOT NULL,\n    date TEXT NOT NULL,\n    time TEXT NOT NULL\n)\n\nCREATE TABLE \"rfid_logs\" (\n\t\"id\"\tINTEGER,\n\t\"uid\"\tTEXT NOT NULL,\n\t\"date\"\tTEXT NOT NULL,\n\t\"time\"\tTEXT NOT NULL,\n\tPRIMARY KEY(\"id\" AUTOINCREMENT)\n);",

        "x": 480,

        "y": 280,

        "wires": []

    },

    {

        "id": "43e89ebe38071896",

        "type": "debug",

        "z": "ec87aef83806bf77",

        "name": "debug 345",

        "active": true,

        "tosidebar": true,

        "console": false,

        "tostatus": false,

        "complete": "false",

        "statusVal": "",

        "statusType": "auto",

        "x": 210,

        "y": 480,

        "wires": []

    },

    {

        "id": "d189cd151fb44415",

        "type": "json",

        "z": "ec87aef83806bf77",

        "name": "",

        "property": "payload",

        "action": "str",

        "pretty": false,

        "x": 250,

        "y": 380,

        "wires": [

            [

                "c43ba373c649a667",

                "09f57e8989839b3f",

                "fd411fc6c641a651"

            ]

        ]

    },

    {

        "id": "fd411fc6c641a651",

        "type": "debug",

        "z": "ec87aef83806bf77",

        "name": "debug 346",

        "active": true,

        "tosidebar": true,

        "console": false,

        "tostatus": false,

        "complete": "false",

        "statusVal": "",

        "statusType": "auto",

        "x": 430,

        "y": 480,

        "wires": []

    },

    {

        "id": "450dc18180d64bb4",

        "type": "mqtt-broker",

        "name": "broker.mqttgo.io",

        "broker": "broker.mqttgo.io",

        "port": "1883",

        "clientid": "",

        "autoConnect": true,

        "usetls": false,

        "protocolVersion": "4",

        "keepalive": "60",

        "cleansession": true,

        "autoUnsubscribe": true,

        "birthTopic": "",

        "birthQos": "1",

        "birthPayload": "",

        "birthMsg": {},

        "closeTopic": "",

        "closeQos": "0",

        "closePayload": "",

        "closeMsg": {},

        "willTopic": "",

        "willQos": "0",

        "willPayload": "",

        "willMsg": {},

        "userProps": "",

        "sessionExpiry": ""

    },

    {

        "id": "01def7a22d7e0b47",

        "type": "ui_group",

        "name": "Default",

        "tab": "2fcd675506c05c36",

        "order": 1,

        "disp": true,

        "width": 8,

        "collapse": false,

        "className": ""

    },

    {

        "id": "9c0419f225af3721",

        "type": "sqlitedb",

        "db": "D:\\2025RFID\\2025-09下學期\\EX2\\rfid.db",

        "mode": "RWC"

    },

    {

        "id": "2fcd675506c05c36",

        "type": "ui_tab",

        "name": "RFID",

        "icon": "dashboard",

        "disabled": false,

        "hidden": false

    }

]

ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite

 ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite  ESP32 VS Code 程式 ; PlatformIO Project Configuration File ; ;   Build op...