2026年1月20日 星期二

WOKWI ESP32 IR & MQTT

 WOKWI ESP32 IR & MQTT





#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <IRremote.hpp>
#include <LiquidCrystal_I2C.h>

// --- 設定區 ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "mqtt-dashboard.com";
const char* mqtt_topic = "alex9ufo/IRcommand";

#define IR_RECEIVE_PIN 2
#define I2C_ADDR    0x27
#define LCD_COLUMNS 16
#define LCD_LINES   2

// --- 物件初始化 ---
WiFiClient espClient;
PubSubClient client(espClient);
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);

// --- 函式:WiFi 連線 ---
void setup_wifi() {
  delay(10);
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");
}

// --- 函式:MQTT 斷線重連 ---
void reconnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // 建立隨機 Client ID
    String clientId = "ESP32Client-";
    clientId += String(random(0xffff), HEX);
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      client.subscribe(mqtt_topic);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void lcdPrint(const char* text, uint16_t cmd) {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("IR sent to MQTT");
  lcd.setCursor(0, 1);
  lcd.print(text);
  lcd.print(": ");
  lcd.print(cmd);
}

void translateIR() {
  uint16_t cmd = IrReceiver.decodedIRData.command;
  if (cmd == 0) return; // 過濾無效指令

  // 將數字轉為字串發送
  String payload = String(cmd);
  Serial.print("Sending to MQTT: ");
  Serial.println(payload);
  client.publish(mqtt_topic, payload.c_str());

  // 根據按鍵更新 LCD (舉例幾個常見按鍵)
  switch (cmd) {
    case 162: lcdPrint("POWER", cmd); break;
    case 104: lcdPrint("NUM 0", cmd); break;
    default:  lcdPrint("CMD", cmd); break;
  }
}

void setup() {
  Serial.begin(115200);
 
  lcd.init();
  lcd.backlight();
  lcd.print("Initializing...");

  setup_wifi();
  client.setServer(mqtt_server, 1883);
 
  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
  lcd.clear();
  lcd.print("MQTT & IR Ready");
}

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

  if (IrReceiver.decode()) {
    translateIR();
    IrReceiver.resume();
  }
}

MQTT Broker: 您使用的是 mqtt-dashboard.com (HiveMQ),這是一個公共伺服器。
您可以到 HiveMQ Web Client 訂閱 alex9ufo/IRcommand 來觀察是否有資料傳出。



在程式中建立一個字典(Dictionary),將接收到的指令代碼(例如 162)轉換為對應的按鍵名稱(例如 POWER)。



import tkinter as tk

from tkinter import scrolledtext

import paho.mqtt.client as mqtt

from datetime import datetime


# --- 設定區 ---

MQTT_SERVER = "mqtt-dashboard.com"

MQTT_TOPIC = "alex9ufo/IRcommand"


# --- 紅外線代碼對照表 (與 ESP32 程式碼對應) ---

IR_MAP = {

    "162": "POWER",

    "226": "MENU",

    "34":  "TEST",

    "2":   "PLUS (+)",

    "194": "BACK",

    "224": "PREV (<<)",

    "168": "PLAY",

    "144": "NEXT (>>)",

    "152": "MINUS (-)",

    "176": "C",

    "104": "數字 0",

    "48":  "數字 1",

    "24":  "數字 2",

    "122": "數字 3",

    "16":  "數字 4",

    "56":  "數字 5",

    "90":  "數字 6",

    "66":  "數字 7",

    "74":  "數字 8",

    "82":  "數字 9"

}


class MQTTApp:

    def __init__(self, root):

        self.root = root

        self.root.title("MQTT IR 遙控監控器")

        self.root.geometry("450x550")

        self.root.configure(bg="#f0f0f0")


        # --- UI 介面佈局 ---

        # 標題

        tk.Label(root, text="IR 紅外線指令監控", font=("Microsoft JhengHei", 16, "bold"), bg="#f0f0f0").pack(pady=10)


        # 連線狀態

        self.status_frame = tk.Frame(root, bg="#f0f0f0")

        self.status_frame.pack(pady=5, fill=tk.X, padx=30)

        

        tk.Label(self.status_frame, text="MQTT 狀態: ", font=("Microsoft JhengHei", 11), bg="#f0f0f0").pack(side=tk.LEFT)

        self.status_indicator = tk.Label(self.status_frame, text="正在連線...", fg="orange", font=("Microsoft JhengHei", 11, "bold"), bg="#f0f0f0")

        self.status_indicator.pack(side=tk.LEFT)


        # 顯示最新收到的按鍵名稱

        tk.Label(root, text="目前按下按鍵:", font=("Microsoft JhengHei", 10), bg="#f0f0f0").pack(pady=(15, 0))

        

        self.btn_name_var = tk.StringVar(value="等待訊號...")

        self.btn_label = tk.Label(root, textvariable=self.btn_name_var, font=("Microsoft JhengHei", 36, "bold"), fg="#e74c3c", bg="#f0f0f0")

        self.btn_label.pack(pady=5)


        # 顯示原始代碼

        self.raw_code_var = tk.StringVar(value="Code: ---")

        self.raw_label = tk.Label(root, textvariable=self.raw_code_var, font=("Consolas", 12), fg="#7f8c8d", bg="#f0f0f0")

        self.raw_label.pack(pady=(0, 15))


        # 歷史紀錄區

        tk.Label(root, text="接收歷史紀錄:", font=("Microsoft JhengHei", 9), bg="#f0f0f0").pack(anchor="w", padx=30)

        self.log_area = scrolledtext.ScrolledText(root, width=50, height=12, font=("Consolas", 10))

        self.log_area.pack(pady=5, padx=30)


        # --- MQTT 客戶端設定 ---

        self.client = mqtt.Client()

        self.client.on_connect = self.on_connect

        self.client.on_message = self.on_message


        try:

            self.client.connect(MQTT_SERVER, 1883, 60)

            self.client.loop_start()

        except Exception as e:

            self.update_status(f"連線失敗: {e}", "red")


    def update_status(self, text, color):

        self.status_indicator.config(text=text, fg=color)


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

        if rc == 0:

            self.update_status("已連線 (Connected)", "#27ae60")

            self.client.subscribe(MQTT_TOPIC)

        else:

            self.update_status(f"連線錯誤({rc})", "red")


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

        payload = msg.payload.decode("utf-8")

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

        

        # 轉換代碼為按鍵名稱

        btn_name = IR_MAP.get(payload, "未知按鈕")

        

        # 更新 UI

        self.root.after(0, self.update_ui, payload, btn_name, timestamp)


    def update_ui(self, payload, btn_name, timestamp):

        # 更新大字體名稱與原始代碼

        self.btn_name_var.set(btn_name)

        self.raw_code_var.set(f"原始代碼: {payload}")

        

        # 寫入歷史紀錄

        log_entry = f"[{timestamp}] {btn_name} (Code: {payload})\n"

        self.log_area.insert(tk.END, log_entry)

        self.log_area.see(tk.END)


if __name__ == "__main__":

    root = tk.Tk()

    app = MQTTApp(root)

    root.mainloop()


沒有留言:

張貼留言

經由MQTT協定的2個WOKWI ESP32 雙向通訊 (ESP32 to ESP32 MQTT Communication )

 經由MQTT協定的2個WOKWI ESP32 雙向通訊  (ESP32  to ESP32 MQTT Communication ) 使用兩個 ESP32 建立一個遠端控制系統。 MQTT Broker: mqtt-dashboard.com Topic (主題): ale...