2026年7月3日 星期五

MQTT 說明練習 EX1 (PC MQTTX Client , online mqttgo.io , 手機 Iot MQTT Panel)

MQTT 說明練習 EX1 (PC MQTTX Client , online mqttgo.io , 手機 Iot MQTT Panel) 



設定 MQTT 連線時,最核心的四個基本參數為:Broker(伺服器網址)、Port(連接埠)、Topic(主題)以及 Client ID(客戶端識別碼)。 
無論您是在設定手機 App(如 IoT MQTT Panel)、微控制器(如 ESP32、Arduino),或是智慧家庭系統(如 Home Assistant),都需要依照以下指南填入對應的資訊:
核心欄位設定指南
  • Broker / Host / Server:輸入您的 MQTT 伺服器 IP 位址或網址。如果是自建伺服器填入該電腦的區網 IP;如果是使用雲端測試平台,可填入免費的公共伺服器(例如 HiveMQ 公共測試伺服器 的網址 mqtt:// broker.emqx.io  ,   mqtt:// mqttgo.io ,  broker.hivemq.com   )。 
  • Port(連接埠)
    • 1883:一般的未加密 TCP 連線(最常用於測試)。
    • 8883:安全加密的 SSL/TLS 連線(生產環境建議使用)。
    • 8083 / 8084:網頁端 WebSocket 連線專用。 [1, 2]
  • Client ID:每個裝置在伺服器上的唯一識別碼,通常可以隨意設定(例如 my_esp32_01),但同一個伺服器下不可有兩個相同的 Client ID,否則會互相斷線擠下線。 
  • Username / Password:如果您的伺服器有開啟安全驗證,請在此輸入帳號與密碼;若使用匿名連線的公共測試伺服器,則保持空白即可
Topic(主題)命名與溝通規則
MQTT 是透過「發布(Publish)」與「訂閱(Subscribe)」主題來傳遞資訊。  
  • 命名格式:建議使用英文、數字命名,並使用斜線 / 來區分層級(例如:alex9ufo/temperature ,  alex9ufo/humidity , alex9ufo/ledstatus  , alex9ufo/ledcontrol   )。 
  • 連線邏輯
    • 負責傳送資料的裝置(如溫度感測器),必須對該主題進行發布 (Publish)
    • 負責接收資料的裝置(如手機 App 或螢幕),必須訂閱 (Subscribe) 相同名稱的主題才能收到資料。
高級進階參數(依需求調整)
  • Keep Alive(心跳時間):預設通常為 60 秒。這是裝置用來定時向伺服器報平安的時間間隔,避免因網路短暫閒置而被判定斷線。
  • QoS(服務質量)
    • 0:最多傳送一次(最快、最不佔頻寬,漏一兩筆沒關係的資料適用)。
    • 1:最少傳送一次(確保對方一定會收到,但可能重複接收)。
    • 2:剛好傳送一次(最嚴謹,通常用在扣款或不能重複的指令)。
  • Retain(訊息保留):若勾選或設為 True,伺服器會永久保留這個主題的最後一筆訊息。當有新裝置新訂閱這個主題時,會立刻收到最新的歷史狀態,不需等待下一次資料更新。
// --- 設定區 ---
const char* mqtt_server = "mqttgo.io"; // 請替換成你的 MQTT 伺服器網址 (例如 io.adafruit.com)
const int mqtt_port = 1883;

// --- MQTT 主題定義 ---
const char* topic_temp = "alex9ufo/temperature";
const char* topic_humi = "alex9ufo/humidity";
const char* topic_ctrl = "alex9ufo/ledcontrol";
const char* topic_status = "alex9ufo/ledstatus";

IoT MQTT Panel (手機)

MQTTX 下載點 (PC)

https://www.emqx.com/en/downloads/MQTTX


https://mqttgo.io/   (online)

WOKWI 程式 與 線路

🛠️ Wokwi 元件接線指南

在 Wokwi 中,請將元件依照以下引腳(Pin)連接:

  • DHT22: VCC -> 3V3, GND -> GND, SDA (Data) -> GPIO 15

  • I2C LCD 16x2: VCC -> 5V/3V3, GND -> GND, SDA -> GPIO 21, SCL -> GPIO 22

  • LED1 (紅): 陽極 (Anode) -> GPIO 12(記得串聯 220Ω 電阻到 GND)

  • LED2 (綠): 陽極 (Anode) -> GPIO 14(記得串聯 220Ω 電阻到 GND)







#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// --- 設定區 ---
const char* ssid = "Wokwi-GUEST";       // Wokwi 專用模擬 WiFi
const char* password = "";
const char* mqtt_server = "mqttgo.io"; // 請替換成你的 MQTT 伺服器網址 (例如 io.adafruit.com)
const int mqtt_port = 1883;
const char* mqtt_user = ""; // 如果需要認證,請填寫
const char* mqtt_pass = ""; // 如果需要認證,請填寫

// --- 引腳定義 ---
#define DHTPIN 15
#define DHTTYPE DHT22
#define LED1_PIN 12
#define LED2_PIN 14

// --- MQTT 主題定義 ---
const char* topic_temp = "alex9ufo/temperature";
const char* topic_humi = "alex9ufo/humidity";
const char* topic_ctrl = "alex9ufo/ledcontrol";
const char* topic_status = "alex9ufo/ledstatus";

// --- 初始化物件 ---
DHT dht(DHTPIN, DHTTYPE);
LiquidCrystal_I2C lcd(0x27, 16, 2); // 模擬器中 I2C 位址通常為 0x27
WiFiClient espClient;
PubSubClient client(espClient);

unsigned long lastMsg = 0;
const long interval = 5000; // 每 5 秒發行一次溫濕度

void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Connecting WiFi");

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("WiFi Connected");
  delay(1000);
}

// 接收到訂閱訊息的回呼函式 (Callback)
void callback(char* topic, byte* payload, unsigned int length) {
  String messageTemp;
  Serial.print("Message arrived on topic: ");
  Serial.print(topic);
  Serial.print(". Message: ");

  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
    messageTemp += (char)payload[i];
  }
  Serial.println();

  // 顯示接收到的控制指令到 LCD
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("MQTT Recv:");
  lcd.setCursor(0, 1);
  lcd.print(messageTemp);

  // 判斷指令並控制 LED
  if (String(topic) == topic_ctrl) {
    if (messageTemp == "on1") {
      digitalWrite(LED1_PIN, HIGH);
      client.publish(topic_status, "ON1");
      Serial.println("LED1 turned ON");
    } else if (messageTemp == "off1") {
      digitalWrite(LED1_PIN, LOW);
      client.publish(topic_status, "OFF1");
      Serial.println("LED1 turned OFF");
    } else if (messageTemp == "on2") {
      digitalWrite(LED2_PIN, HIGH);
      client.publish(topic_status, "ON2");
      Serial.println("LED2 turned ON");
    } else if (messageTemp == "off2") {
      digitalWrite(LED2_PIN, LOW);
      client.publish(topic_status, "OFF2");
      Serial.println("LED2 turned OFF");
    }
  }
  delay(1500); // 讓使用者看清 LCD 上的控制訊息,隨後會被溫濕度刷掉
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Connect MQTT...");
   
    // 嘗試連接 (使用隨機 Client ID 避免衝突)
    String clientId = "ESP32Client-";
    clientId += String(random(0xffff), HEX);
   
    // 如果不需要帳密,請改為 client.connect(clientId.c_str())
    if (client.connect(clientId.c_str(), mqtt_user, mqtt_pass)) {
      Serial.println("connected");
      lcd.setCursor(0, 1);
      lcd.print("MQTT Connected!");
     
      // 訂閱控制主題
      client.subscribe(topic_ctrl);
      delay(1000);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      lcd.setCursor(0, 1);
      lcd.print("Failed! Retry...");
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
 
  pinMode(LED1_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);
  digitalWrite(LED1_PIN, LOW);
  digitalWrite(LED2_PIN, LOW);

  // 初始化 LCD
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Initializing...");

  // 初始化 DHT22
  dht.begin();

  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  unsigned long now = millis();
  if (now - lastMsg > interval) {
    lastMsg = now;

    // 讀取溫濕度
    float h = dht.readHumidity();
    float t = dht.readTemperature();

    // 檢查讀取是否失敗
    if (isnan(h) || isnan(t)) {
      Serial.println("Failed to read from DHT sensor!");
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("DHT Read Error");
      return;
    }

    // 顯示於 Serial Monitor
    Serial.print("Humidity: ");
    Serial.print(h);
    Serial.print("%  Temperature: ");
    Serial.print(t);
    Serial.println("°C");

    // 顯示於 16x2 LCD
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Temp: ");
    lcd.print(t, 1);
    lcd.print((char)223); // 顯示 ° 符號
    lcd.print("C");
   
    lcd.setCursor(0, 1);
    lcd.print("Humi: ");
    lcd.print(h, 1);
    lcd.print("%");

    // 發行到 MQTT
    client.publish(topic_temp, String(t).c_str());
    client.publish(topic_humi, String(h).c_str());
   
    Serial.println("Data published to MQTT.");
  }
}

MQTTX 設定畫面










https://mqttgo.io/   設定畫面




IoT MQTT Panel   設定畫面

2個 switch , 2個 LED , 1個 Gauge , 1個 Line Graph

最後的畫面
元件設定畫面


裡面的Panel name 需修改成 SW1 , LED1 , SW2 ,LED2,溫度 濕度





















rfid nxp iso14443 的 編碼方式

 rfid nxp iso14443 的 編碼方式





https://ww1.microchip.com/downloads/en/devicedoc/doc2056.pdf


import tkinter as tk

from tkinter import ttk, messagebox


# 建立主視窗

root = tk.Tk()

root.title("ISO 14443 Type A 規範與訊號模擬教學")

root.geometry("800x700")

root.configure(bg="#f5f5f5")


# 設定樣式

style = ttk.Style()

style.configure("TLabel", background="#f5f5f5", font=("Microsoft JhengHei", 12))

style.configure("Header.TLabel", font=("Microsoft JhengHei", 16, "bold"), foreground="#003366")

style.configure("Action.TButton", font=("Microsoft JhengHei", 11, "bold"))


# ==================== 上半部:保留原先的規範說明 ====================


title_label = ttk.Label(root, text="RFID / NFC - ISO 14443 Type A 標準與訊號模擬", style="Header.TLabel")

title_label.pack(pady=15)


intro_text = "• Type A 是 NXP 最具代表性的標準(廣泛應用於 MIFARE Classic, Ultralight 等)。"

intro_label = ttk.Label(root, text=intro_text, wraplength=700, justify="left", foreground="#333333")

intro_label.pack(pady=5, padx=20, anchor="w")


ttk.Separator(root, orient='horizontal').pack(fill='x', padx=20, pady=10)


display_frame = tk.LabelFrame(root, text="靜態規範說明(點選按鈕切換)", font=("Microsoft JhengHei", 11, "bold"), bg="#ffffff", fg="#333333", padx=15, pady=10)

display_frame.pack(fill="x", padx=20, pady=5)


detail_title = tk.Label(display_frame, text="請選擇通訊方向...", font=("Microsoft JhengHei", 13, "bold"), bg="#ffffff", anchor="w")

detail_title.pack(fill="x", pady=(0, 5))


detail_content = tk.Label(display_frame, text="點選下方按鈕查看讀寫器或標籤的調變與編碼規範。", font=("Microsoft JhengHei", 11), bg="#ffffff", justify="left", anchor="nw")

detail_content.pack(fill="x")


def show_pcd_to_picc():

    detail_title.config(text="讀寫器 到 標籤 (Reader to Card, PCD -> PICC)", fg="#d9534f")

    content = "【調變方式】100% ASK (振幅鍵移調變) —— 訊號完全中斷來代表資料邏輯。\n【編碼方式】修正密勒編碼 (Modified Miller Coding) —— 於特定位置加入脈衝,維持能量。"

    detail_content.config(text=content)


def show_picc_to_pcd():

    detail_title.config(text="標籤 到 讀寫器 (Card to Reader, PICC -> PCD)", fg="#0275d8")

    content = "【調變方式】負載調變 (Load Modulation) —— 改變天線阻抗影響磁場。\n【編碼與副載波】曼徹斯特編碼 (Manchester Coding) / 副載波 847.5 kHz —— 抗干擾能力強。\n【通信速率】標準速率為 106 kbit/s"

    detail_content.config(text=content)


btn_frame = ttk.Frame(root)

btn_frame.pack(pady=5)

btn_pcd = ttk.Button(btn_frame, text="1. 讀寫器 -> 標籤 (PCD->PICC)", command=show_pcd_to_picc, style="Action.TButton")

btn_pcd.pack(side="left", padx=15)

btn_picc = ttk.Button(btn_frame, text="2. 標籤 -> 讀寫器 (PICC->PCD)", command=show_picc_to_pcd, style="Action.TButton")

btn_picc.pack(side="left", padx=15)


ttk.Separator(root, orient='horizontal').pack(fill='x', padx=20, pady=10)


# ==================== 下半部:延伸設計 - 訊號波形模擬區 ====================


simulation_frame = tk.LabelFrame(root, text="延伸設計:互動式訊號波形模擬器", font=("Microsoft JhengHei", 11, "bold"), bg="#fcfcfc", fg="#006633", padx=15, pady=10)

simulation_frame.pack(fill="both", expand=True, padx=20, pady=5)


# 輸入區域

input_frame = ttk.Frame(simulation_frame)

input_frame.pack(fill="x", pady=5)


input_label = ttk.Label(input_frame, text="請輸入二進位信號 (例如 01011011):", font=("Microsoft JhengHei", 11))

input_label.pack(side="left", padx=5)


signal_entry = ttk.Entry(input_frame, font=("Consolas", 12), width=20)

signal_entry.insert(0, "01011011")  # 預設值

signal_entry.pack(side="left", padx=5)


# 畫布(用於繪製文字與偽波形)

canvas = tk.Canvas(simulation_frame, bg="#1e1e1e", height=320)

canvas.pack(fill="both", expand=True, pady=5)


def draw_wave():

    # 驗證輸入

    bit_string = signal_entry.get().strip()

    if not bit_string or not all(b in '01' for b in bit_string):

        messagebox.showerror("輸入錯誤", "請輸入有效的二進位字串(僅包含 0 與 1)!")

        return

    

    if len(bit_string) > 12:

        messagebox.showwarning("字串過長", "為了最佳顯示效果,請輸入 12 位元以內的訊號。")

        bit_string = bit_string[:12]

    

    canvas.delete("all")  # 清空畫布

    

    # 畫布參數

    start_x = 180

    bit_width = 45

    

    # 繪製時間軸與格線

    for i in range(len(bit_string) + 1):

        x = start_x + i * bit_width

        canvas.create_line(x, 20, x, 300, fill="#333333", dash=(2, 2))

    

    # 寫出上方原始資料位元

    for i, bit in enumerate(bit_string):

        x = start_x + i * bit_width + (bit_width / 2)

        canvas.create_text(x, 15, text=bit, fill="#ffffff", font=("Consolas", 12, "bold"))

    

    # --- 1) 100% ASK ---

    canvas.create_text(85, 50, text="1) 100% ASK", fill="#ff9999", font=("Microsoft JhengHei", 11, "bold"), anchor="w")

    # 示意圖:用密集波形代表有能量,完全平線代表100%中斷

    x_curr = start_x

    for bit in bit_string:

        if bit == '1': # 假設 1 有訊號,0 由於 Modified Miller 配合會全斷或局部斷,此處簡化示意 ASK 本身特性

            canvas.create_line(x_curr, 50, x_curr + bit_width, 50, fill="#ff4d4d", width=3)

        else:

            canvas.create_line(x_curr, 65, x_curr + bit_width, 65, fill="#ff4d4d", width=1, dash=(4,4))

        x_curr += bit_width

    canvas.create_text(start_x, 80, text="說明: 邏輯由載波的「有/無(100%斷開)」決定,斷開時卡片完全接收不到能量。", fill="#aaaaaa", font=("Microsoft JhengHei", 9), anchor="w")


    # --- 2) Modified Miller ---

    canvas.create_text(85, 120, text="2) Modified Miller", fill="#ffcc99", font=("Microsoft JhengHei", 11, "bold"), anchor="w")

    x_curr = start_x

    last_bit = '1'

    for bit in bit_string:

        # 修正密勒編碼:

        # '1' -> 位元中間有一個下降脈衝下降

        # '0' -> 若前一個是'1'則整段高電平;若前一個是'0'則在位元起始點有下降脈衝

        y_high, y_low = 120, 135

        if bit == '1':

            canvas.create_line(x_curr, y_high, x_curr + bit_width/2, y_high, fill="#ffa64d", width=2)

            canvas.create_line(x_curr + bit_width/2, y_high, x_curr + bit_width/2, y_low, fill="#ffa64d", width=2)

            canvas.create_line(x_curr + bit_width/2, y_low, x_curr + bit_width, y_low, fill="#ffa64d", width=2)

        else: # bit == '0'

            if last_bit == '1':

                canvas.create_line(x_curr, y_high, x_curr + bit_width, y_high, fill="#ffa64d", width=2)

            else:

                canvas.create_line(x_curr, y_low, x_curr, y_high, fill="#ffa64d", width=2)

                canvas.create_line(x_curr, y_high, x_curr + bit_width, y_high, fill="#ffa64d", width=2)

        x_curr += bit_width

        last_bit = bit

    canvas.create_text(start_x, 150, text="說明: 脈衝位置取決於目前與前一個位元。特點是省電且能保持同步。", fill="#aaaaaa", font=("Microsoft JhengHei", 9), anchor="w")


    # --- 3) Load Modulation ---

    canvas.create_text(85, 190, text="3) Load Modulation", fill="#99ccff", font=("Microsoft JhengHei", 11, "bold"), anchor="w")

    x_curr = start_x

    for bit in bit_string:

        # 用振幅大小變化來展示阻抗切換帶來的負載調變

        y_max, y_min = 185, 195

        if bit == '1':

            canvas.create_line(x_curr, y_max, x_curr + bit_width, y_max, fill="#3399ff", width=4)

        else:

            canvas.create_line(x_curr, y_min, x_curr + bit_width, y_min, fill="#3399ff", width=2)

        x_curr += bit_width

    canvas.create_text(start_x, 220, text="說明: 卡片主動切換內部負載阻抗,使讀寫器天線端感應到微弱的電壓振幅起伏。", fill="#aaaaaa", font=("Microsoft JhengHei", 9), anchor="w")


    # --- 4) Manchester Coding ---

    canvas.create_text(85, 260, text="4) Manchester", fill="#99ff99", font=("Microsoft JhengHei", 11, "bold"), anchor="w")

    x_curr = start_x

    for bit in bit_string:

        y_high, y_low = 255, 270

        # Type A 採用的 Manchester:

        # 邏輯 1 = 位元前半段為低,後半段為高 (自低往高跳變)

        # 邏輯 0 = 位元前半段為高,後半段為低 (自高往低跳變)

        if bit == '1':

            canvas.create_line(x_curr, y_low, x_curr + bit_width/2, y_low, fill="#5cd65c", width=2)

            canvas.create_line(x_curr + bit_width/2, y_low, x_curr + bit_width/2, y_high, fill="#5cd65c", width=2)

            canvas.create_line(x_curr + bit_width/2, y_high, x_curr + bit_width, y_high, fill="#5cd65c", width=2)

        else:

            canvas.create_line(x_curr, y_high, x_curr + bit_width/2, y_high, fill="#5cd65c", width=2)

            canvas.create_line(x_curr + bit_width/2, y_high, x_curr + bit_width/2, y_low, fill="#5cd65c", width=2)

            canvas.create_line(x_curr + bit_width/2, y_low, x_curr + bit_width, y_low, fill="#5cd65c", width=2)

        

        # 連接相鄰位元的轉折線

        if x_curr > start_x:

            canvas.create_line(x_curr, 255, x_curr, 270, fill="#225522", width=1)

        x_curr += bit_width

    canvas.create_text(start_x, 292, text="說明: 以位元正中央的「跳變方向」決定邏輯。1為上升沿,0為下降沿,抗噪性極佳。", fill="#aaaaaa", font=("Microsoft JhengHei", 9), anchor="w")


# 模擬按鈕

btn_sim = ttk.Button(input_frame, text="產生模擬波形", command=draw_wave, style="Action.TButton")

btn_sim.pack(side="left", padx=10)


# 初始化繪圖

draw_wave()


# 啟動主程式迴圈

root.mainloop()


2026年6月14日 星期日

Node-Red --> MQTT --> Fuxa 開源碼網頁式圖控平台

Node-Red --> MQTT --> Fuxa  




  






FUXA(一個開源的 Web HMI / SCADA 自動化監控軟體)的專案設定檔

這份設定檔完整定義了 HMI 監控畫面的後端通訊(MQTT 連線、點位標籤)前端網頁圖形介面(SVG 畫布、視窗排版、開關元件)

以下為您詳細說明這支程式(專案)的核心結構與運作原理:

一、 後端通訊與標籤設定 (Devices & Tags)

FUXA 透過這個區塊與外界的物聯網設備或 Node-RED 進行資料交換:

  1. 通訊客戶端 (MQTT Client)

    • 專案中配置了一個名為 mqtt1 的通訊介面,連接到公共的 MQTT 代理伺服器(Broker):mqtt://mqttgo.io:1883

    • 系統內建了一個系統標籤 mqtt1 Connection Status,用來監測此 MQTT 連線是否正常。

  2. 點位標籤 (Tags Mapping): 專案中定義了 4 個與前端元件連動的按鈕標籤(Tags),其對應的 MQTT 主題 (Topic) 分別為:

    • button1 ➔ 訂閱與發送主題:alex9ufo/btn/1/set

    • button2 ➔ 訂閱與發送主題:alex9ufo/btn/2/set

    • button3 ➔ 訂閱與發送主題:alex9ufo/btn/3/set

    • button4 ➔ 訂閱與發送主題:alex9ufo/btn/4/set

    當前端畫面上的開關被切換,或是遠端設備透過這些主題發送數據時,FUXA 都會進行雙向的同步更新。

二、 前端圖形視覺介面 (Views & UI Components)

這部分定義了操作員在瀏覽器畫面上看見的 SCADA / HMI 控制面板:

  1. 畫布與主視覺設定 (Views)

    • 建立了一個名為 v_67ba4a88 的中大型監控畫面(寬 1520 像素、高 850 像素)。

    • 背景顏色設定為帶有科技感的深灰色(#2A2A2A),非常符合工業自動化控制台的暗色系風格。

  2. 前端圖形元件 (SVG Elements): 畫面上配置了數個基於 SVG 向量圖形的動態控制元件,包含:

    • 文字與排版:包含了標題文字,並使用特定字型(如 Arial)與色彩配置。

    • 互動式切換開關 (NGX-SWITCH):畫面上擺放了對應按鈕的網頁開關切換元件。

      • 當開關處於關閉狀態時,畫面上會渲染出紅色、並顯示 "OFF" 字樣的圓形按鈕。

      • 這些開關元件在 HTML/SVG 結構中被賦予了特定的 id,並直接與後端的 button1 ~ button4 標籤進行綁定(Binding)。

三、 專案整體運作邏輯

這份設定檔在 FUXA 系統中啟動後,會實現以下物聯網(IoT)雙向控制情境:

  • 下達控制指令(發送端): 當操作員在瀏覽器打開這個 FUXA HMI 網頁,手動點擊畫面上的開關由 OFF 變更為 ON 時,FUXA 會立刻透過 mqttgo.io 伺服器,向 alex9ufo/btn/x/set 主題發送變更訊號,藉此操控遠端的實體設備(如 PLC 或 ESP32 開發板)。

  • 接收設備狀態(接收端): 如果遠端實體設備的狀態改變(例如由現場按鈕觸發),設備會發送最新的狀態到相同的 MQTT 主題,FUXA 收到通知後,網頁畫面上的紅色 "OFF" 開關就會自動跳轉為 ON 的狀態,達到即時監控的效果。

總結

這是一個非常標準的 Web-based HMI/SCADA(網頁型人機介面)專案設定。它利用 FUXA 的視覺化優勢,結合 MQTT 輕量化通訊協定,為管理員提供了一個跨平台、直覺且具備動態反饋的遠端設備控制面板。




Node-RED 的流程設定檔(Flows)。它的核心功能是建立一個網頁圖形介面(Dashboard),讓使用者可以用來遠端控制 4 個虛擬按鈕,並將控制指令透過 MQTT 物聯網通訊協定發送出去,用以控制現場的實體設備(如 HMI 系統 FUXA、PLC 或 LED 燈)。

以下為您詳細拆解這段程式的運作邏輯與各個節點的功能:

1. 核心運作邏輯(運作流程)

這段程式主要分為兩大邏輯區塊

  • 【發送控制端】網頁開關 ➔ 發送 MQTT 指令: 當使用者在 Node-RED 網頁上切換「遠端控制按鈕」時,程式會根據開關狀態(開啟/關閉),透過 MQTT 發送數字 10 到雲端 MQTT 伺服器,用來設定遠端設備。

  • 【接收監聽端】監聽 MQTT ➔ 偵錯輸出: 程式另外拉出了一條線,專門監聽控制按鈕 1 的主題(Topic)。只要該主題有任何資料變動,就會在 Node-RED 的後台偵錯視窗(Debug)印出數值,方便開發者確認指令是否有成功送出。

2. 節點詳細功能說明

⓵ 使用者介面(UI Dashboard 節點)

  • ui_tab (工業物聯網控制台) & ui_group (現場設備控制 (4按鈕)): 在網頁上建立一個名為「工業物聯網控制台」的頁面,並在裡面規劃了一個寬度為 5 的區塊,專門擺放這 4 個按鈕。

  • ui_switch (UI 控制按鈕 1 ~ 4): 這是網頁上的 4 個切換開關(Switch)。

    • 當切換到 ON (開啟):會送出數值 1 (onvalue": "1")。

    • 當切換到 OFF (關閉):會送出數值 0 (offvalue": "0")。

    • 每個按鈕都有自己獨立的 topic(分別為 "1", "2", "3", "4"),代表它們的編號。

⓶ 雲端通訊(MQTT 節點)

  • mqtt-broker (mqttgo): 這是通訊的基礎設定。這段程式使用了一個免費公開的 MQTT 伺服器,網址為 mqttgo.io,通訊連接埠為 1883

  • mqtt out (發送控制指令至 FUXA): 當網頁按鈕被觸發時,這四個節點會負責把 10 打向雲端伺服器,它們發送的 MQTT 主題 (Topic) 分別為:

    • 按鈕 1 ➔ alex9ufo/btn/1/set

    • 按鈕 2 ➔ alex9ufo/btn/2/set

    • 按鈕 3 ➔ alex9ufo/btn/3/set

    • 按鈕 4 ➔ alex9ufo/btn/4/set (註:名稱中的 FUXA 代表這些指令預期是要給一個叫做 FUXA 的 Web HMI/SCADA 自動化監控軟體接收並執行的。)

⓷ 監聽與除錯(MQTT In & Debug 節點)

  • mqtt in 節點: 獨立監聽了 alex9ufo/btn/1/set(按鈕 1)的主題。當任何地方(不論是網頁端或外部實體設備)更改了按鈕 1 的數值,它都會接收到。

  • debug (debug 392) 節點: 接收到 mqtt in 的資料後,直接顯示在 Node-RED 介面右側的 Debug 欄位中,用來即時觀測按鈕 1 的狀態變化。

總結

這是一段非常標準的 IoT 設備雙向通訊/控制雛形。使用者只要啟動這個 Node-RED 流程,開啟網頁瀏覽器,就可以直接點擊畫面上的 4 個開關,透過雲端的 mqttgo.io 伺服器,對遠端的工業設備或微控制器(如 ESP32、PLC)進行開關(0與1)控制。


[{"id":"ui.switch.btn1","type":"ui_switch","z":"f8a1b2c3.flow1","name":"UI 控制按鈕 1","label":"🔘 遠端控制按鈕 1","tooltip":"","group":"ui.group.btn","order":1,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"1","topicType":"str","style":"","onvalue":"1","onvalueType":"num","onicon":"","oncolor":"","offvalue":"0","offvalueType":"num","officon":"","offcolor":"","animate":true,"className":"","x":140,"y":220,"wires":[["mqtt.out.btn"]]},{"id":"ui.switch.btn2","type":"ui_switch","z":"f8a1b2c3.flow1","name":"UI 控制按鈕 2","label":"🔘 遠端控制按鈕 2","tooltip":"","group":"ui.group.btn","order":2,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"2","topicType":"str","style":"","onvalue":"1","onvalueType":"num","onicon":"","oncolor":"","offvalue":"0","offvalueType":"num","officon":"","offcolor":"","animate":true,"className":"","x":140,"y":260,"wires":[["ad806271d836e060"]]},{"id":"ui.switch.btn3","type":"ui_switch","z":"f8a1b2c3.flow1","name":"UI 控制按鈕 3","label":"🔘 遠端控制按鈕 3","tooltip":"","group":"ui.group.btn","order":3,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"3","topicType":"str","style":"","onvalue":"1","onvalueType":"num","onicon":"","oncolor":"","offvalue":"0","offvalueType":"num","officon":"","offcolor":"","animate":true,"className":"","x":140,"y":300,"wires":[["dc6f8581d5294e65"]]},{"id":"ui.switch.btn4","type":"ui_switch","z":"f8a1b2c3.flow1","name":"UI 控制按鈕 4","label":"🔘 遠端控制按鈕 4","tooltip":"","group":"ui.group.btn","order":4,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"4","topicType":"str","style":"","onvalue":"1","onvalueType":"num","onicon":"","oncolor":"","offvalue":"0","offvalueType":"num","officon":"","offcolor":"","animate":true,"className":"","x":140,"y":340,"wires":[["62cf2b25030192df"]]},{"id":"mqtt.out.btn","type":"mqtt out","z":"f8a1b2c3.flow1","name":"發送控制指令至 FUXA (SET1)","topic":"alex9ufo/btn/1/set","qos":"1","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"192c2b20bef1e71a","x":450,"y":220,"wires":[]},{"id":"ad806271d836e060","type":"mqtt out","z":"f8a1b2c3.flow1","name":"發送控制指令至 FUXA (SET2)","topic":"alex9ufo/btn/2/set","qos":"1","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"192c2b20bef1e71a","x":450,"y":260,"wires":[]},{"id":"dc6f8581d5294e65","type":"mqtt out","z":"f8a1b2c3.flow1","name":"發送控制指令至 FUXA (SET3)","topic":"alex9ufo/btn/3/set","qos":"1","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"192c2b20bef1e71a","x":450,"y":300,"wires":[]},{"id":"62cf2b25030192df","type":"mqtt out","z":"f8a1b2c3.flow1","name":"發送控制指令至 FUXA (SET4)","topic":"alex9ufo/btn/4/set","qos":"1","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"192c2b20bef1e71a","x":450,"y":340,"wires":[]},{"id":"5067b3d71bbb541e","type":"debug","z":"f8a1b2c3.flow1","name":"debug 392","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":390,"y":400,"wires":[]},{"id":"19327f627d40f5c3","type":"mqtt in","z":"f8a1b2c3.flow1","name":"","topic":"alex9ufo/btn/1/set","qos":"1","datatype":"auto-detect","broker":"192c2b20bef1e71a","nl":false,"rap":true,"rh":0,"inputs":0,"x":150,"y":400,"wires":[["5067b3d71bbb541e"]]},{"id":"ui.group.btn","type":"ui_group","name":"現場設備控制 (4按鈕)","tab":"ui.tab.main","order":1,"disp":true,"width":"5","collapse":false,"className":""},{"id":"192c2b20bef1e71a","type":"mqtt-broker","name":"mqttgo","broker":"mqttgo.io","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":"ui.tab.main","type":"ui_tab","name":"工業物聯網控制台","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

MQTT 說明練習 EX1 (PC MQTTX Client , online mqttgo.io , 手機 Iot MQTT Panel)

MQTT 說明練習 EX1 (PC MQTTX Client , online mqttgo.io , 手機 Iot MQTT Panel)  設定 MQTT 連線時,最核心的四個基本參數為:Broker(伺服器網址)、Port(連接埠)、Topic(主題)以及 Client I...