## 說明 ESP32 與 Windows 之間透過 MQTT 通信協議相互傳送訊息。相比於 HTTP,MQTT 的數據傳輸更為精簡,且傳輸速度顯著快於 HTTP,使用起來十分方便。 MQTT詳細說明可以參考以下幾個網站 https://mosquitto.org/ (官網) https://swf.com.tw/?p=1002 (中文-好心人分享) https://jimirobot.tw/esp32-mosquitto-windows-mqtt-tutorial/ (中文-好心人分享) 操作步驟: 1. Windows 設置: 建立 MQTT Broker(安裝、環境設定與啟動) 2. ESP32 設置: 傳送傳感器資料至 MQTT Broker,並訂閱控制 LED 開關的主題 3. Windows 介面程式: 使用Python開發一個程式來讀取 ESP32 傳送的資料,顯示在介面上,並能控制 ESP32 上的 LED 開關 ## 1. 在Windows安裝MQTT * Mosquitto 官網下載 https://mosquitto.org/download/ * 安裝及環境設置可以參考以下網站: https://jimirobot.tw/esp32-mosquitto-windows-mqtt-tutorial/ ## 2. Mosquitto conf 設定 MQTT預設為本機使用者執行,其他使用者無法使用, 因此需修改mosquitto.conf文件, 預設路徑為"C:\Program Files\mosquitto\mosquitto.conf" NOTE:但此文件為系統文件無法直接修改, 在此文件內容中, 修改使用者權限後即可 ![image](https://hackmd.io/_uploads/r1y07Bb00.png =50%x) * 允許其他匿名者使用, 開啟mosquitto.conf文件, 到內容最下面新增以下文字 ``` allow_anonymous true listener 1883 ``` * 不允許其他匿名者使用, 引用使用者清單 (1) 在mosquitto.conf文件在新增以下文字 ``` allow_anonymous false password_file : C:\Program Files\mosquitto\usrlist.txt listener 1883 ``` (2) 在mosquitto資料夾中, 新增文件usrlist.txt, 文件中寫入使用者帳號&密碼 參考以下網站: https://jimirobot.tw/esp32-mosquitto-conf-mqtt-tutorial/ ## 3. Windows測試 在Windows, MQTT安裝完成後, 使用命令提示字元輸入以下指令 移動到mosquitto的資料夾, 在本地端建立topic為"testchanel"進行測試 ``` $ cd C:\Program Files\mosquitto ``` 訂閱"testchanel" ``` $ mosquitto_sub -h localhost -p 1883 -t "testchanel" -v ``` 再開啟另一個命令提示字元的視窗, 傳送訊息"Hello MQTT"到topic""testchanel" ``` $ mosquitto_pub -h localhost -p 1883 -t "testchanel" -m "Hello MQTT" ``` 在訂閱端可以看剛剛傳送的訊息就成功了 ![image](https://hackmd.io/_uploads/ryaJJNb0C.png) </br> ## 4. 在Windows架設MQTT Broker * #### 方法一:使用服務開啟MQTT 開啟"服務", 找到"Mosquitto Broker", 點選"啟動" 預設為自動(開機時自動啟動), 如果不想開機自動啟動可改為手動 ![image](https://hackmd.io/_uploads/SJsZuE-0A.png) * #### 方法二:使用命令提示字元(系統管理員身分執行) 1. 使用系統管理員身分執行命令提示字元 2. 移動到MQTT的安裝路徑 ``` $ cd C:\Program Files\mosquitto ``` 3. 啟動/關閉 mosquitto broker的服務 ``` $ net start mosquitto #開啟MQTT $ net stop mosquitto #關閉MQTT ``` 4. 確認Mosquitto的狀態 ``` $ sc query mosquitto ``` > 有看到RUNNING就表示成功運行了 ![image](https://hackmd.io/_uploads/HkljhvH-0A.png) </br> ## 5. ESP32使用MQTT與Windows通信 ESP32使用AHT20+BMP280傳感器取得溫濕度+氣壓資料 燒錄程式如下 引用函示庫PubSubClient.h用來處理MQTT的發佈與訂閱功能 ```c= #include <WiFi.h> #include <PubSubClient.h> #include <Adafruit_AHTX0.h> #include <Adafruit_BMP280.h> const char* ssid = "TP-Link_C669"; // Wi-Fi名稱 const char* password = "12384440"; // Wi-Fi密碼 const char* mqtt_server = "192.168.0.103"; // LED引腳與感測器物件 const int ledpin = 6; // LED引腳 Adafruit_AHTX0 aht; // 將AHTX0設為aht Adafruit_BMP280 bmp; // 將BMP280設為bmp // 變數宣告 float temperature = 0.0; float humidity = 0.0; float pressure = 0.0; float altitude = 0.0; String clientID = "ESP32_"; WiFiClient espClient; PubSubClient client(espClient); unsigned long lastMsg = 0; // unsigned long為非負整數值 #define MSG_BUFFER_SIZE 100 char msg[MSG_BUFFER_SIZE]; int value = 0; // Wi-Fi連接函數 void setup_wifi() { delay(10); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.mode(WIFI_STA); // 設定Wi-Fi為客戶端模式 WiFi.begin(ssid, password); // 等待Wi-Fi連接 while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } randomSeed(micros()); // 使用微秒作為隨機種子 Serial.println("WiFi connected. "); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } // MQTT訊息回呼函數 void callback(char* topic, byte* payload, unsigned int length) { Serial.print("Message arrived ["); Serial.print(topic); Serial.print("] "); // 顯示接收到的訊息 for (int i = 0; i < length; i++) { Serial.print((char)payload[i]); } Serial.println(); // 收到訊息為1時, LED開, 否則為關 if ((char)payload[0] == '1') { digitalWrite(ledpin, HIGH); // LED關 } else { digitalWrite(ledpin, LOW); // LED開 } } // MQTT重新連接函數 void reconnect() { while (!client.connected()) { Serial.print("Attempting MQTT connection..."); clientID += String(random(0xffff), HEX); // 生成隨機客戶端ID if (client.connect(clientID.c_str())) // 連接MQTT伺服器 // if (client.connect(clientID.c_str(), mqttUserName, mqttPass)) // 如果有設User & pw { Serial.println("connected with Client ID: "); Serial.println(clientID); client.subscribe("studio/led_switch"); // 訂閱LED控制主題 } else { Serial.print("failed, rc="); Serial.print(client.state()); Serial.println(" try again in 5 seconds"); delay(5000); // 重試間隔5秒 } } } void setup() { Wire.begin(9, 10); // 設定AHT20 SDA 和 SCL 的 GPIO if (!aht.begin()) { // // 初始化AHT20, 並檢查是否成功初始化 Serial.println("Error initializing AHT20"); return; } Serial.println("AHT20 sensor initialized."); if (!bmp.begin(0x77)) { // // 初始化BMP280, 並檢查是否成功初始化 Serial.println("Error initializing BMP280"); return; } Serial.println("BMP280 sensor initialized."); pinMode(ledpin, OUTPUT); // 設置LED引腳為輸出模式 Serial.begin(115200); // 設置串口波特率 setup_wifi(); // 呼叫Wi-Fi連接函數 client.setServer(mqtt_server, 1883); // 設置MQTT伺服器 client.setCallback(callback); // 設置MQTT回呼函數 } void loop() { if (!client.connected()) { // 檢查MQTT是否已連接,若未連接則重連 reconnect(); } client.loop(); // 保持MQTT的運作 unsigned long now = millis(); // 取得自系統啟動後經過的時間 if (now - lastMsg > 5000) { // 每5秒執行一次 lastMsg = now; ++value; // 讀取AHT20感測器數據 sensors_event_t temp_event, humidity_event; aht.getEvent(&humidity_event, &temp_event); float temperature = temp_event.temperature; float humidity = humidity_event.relative_humidity; // 讀取BMP280數據 float pressure = bmp.readPressure() / 100.0F; // 讀取氣壓數據,轉換為 hPa float altitude = bmp.readAltitude(1007.2); // 以海平面氣壓=1007.2 hPa 計算高度 // 將數據發佈到對應的子主題 client.publish("sensor/temperature", String(temperature).c_str()); client.publish("sensor/humidity", String(humidity).c_str()); client.publish("sensor/pressure", String(pressure).c_str()); client.publish("sensor/altitude", String(altitude).c_str()); // 顯示數據到Serial Serial.print("Temperature: "); Serial.println(temperature); Serial.print("Humidity: "); Serial.println(humidity); Serial.print("Pressure: "); Serial.println(pressure); Serial.print("Altitude: "); Serial.println(altitude); } } ``` </br> ## 6. 在Windows上讀取資料和控制LED 使用Python設計一個介面, 顯示ESP32傳感器的數據, 並且設計一個LED按鈕可以控制EPS32 LED的開和關。 ```py= import tkinter as tk import paho.mqtt.client as mqtt import datetime # MQTT 設定 mqtt_broker = "192.168.0.103" mqtt_port = 1883 mqtt_topic_temperature = "sensor/temperature" mqtt_topic_humidity = "sensor/humidity" mqtt_topic_pressure = "sensor/pressure" mqtt_topic_altitude = "sensor/altitude" mqtt_topic_led = "studio/led_switch" # 初始化UI root = tk.Tk() root.geometry('840x400') root.title("Sensor Data and LED Control") root.configure(bg="#282C34") # 設定字體和標籤樣式 label_style = {"font": ("Arial", 16), "bg": "#282C34", "fg": "white"} button_style = {"font": ("Helvetica", 12), "bg": "lightblue", "activebackground": "blue", "width": 8, "height": 1} data_label_style = { 'font': ('Helvetica', 14), 'bg': 'white', 'fg': 'black', 'relief': 'sunken', 'bd': 3, 'width': 8, 'height': 1 } # 數據顯示變數 temp_value = tk.StringVar(value='--') humidity_value = tk.StringVar(value='--') pressure_value = tk.StringVar(value='--') altitude_value = tk.StringVar(value='--') # MQTT 設定 def on_connect(client, userdata, flags, rc): if rc == 0: print("Connected to MQTT Broker") client.subscribe(mqtt_topic_temperature) client.subscribe(mqtt_topic_humidity) client.subscribe(mqtt_topic_pressure) client.subscribe(mqtt_topic_altitude) else: print(f"Failed to connect, return code {rc}") # 取得MQTT Broker數據 def on_message(client, userdata, msg): if msg.topic == mqtt_topic_temperature: temperature = float(msg.payload.decode()) temp_value.set(temperature) elif msg.topic == mqtt_topic_humidity: humidity = float(msg.payload.decode()) humidity_value.set(humidity) elif msg.topic == mqtt_topic_pressure: pressure = float(msg.payload.decode()) pressure_value.set(pressure) elif msg.topic == mqtt_topic_altitude: altitude = float(msg.payload.decode()) altitude_value.set(altitude) # 連接到MQTT Broker client = mqtt.Client() client.on_connect = on_connect client.on_message = on_message client.connect(mqtt_broker, mqtt_port, 60) def toggle_led(): global current_status if current_status == 0: client.publish(mqtt_topic_led, "1") LED_status.set('OFF') current_status = 1 else: client.publish(mqtt_topic_led, "0") LED_status.set('ON') current_status = 0 # LED開關 led_status_label = tk.Label(root, text="LED Status: ", **label_style).place(x=50, y=200) LED_status = tk.StringVar(value='ON') current_status = 0 led_button = tk.Button(root, textvariable=LED_status, command=toggle_led, **button_style) led_button.place(x=180, y=200) # 數據顯示 tk.Label(root, text="Temperature: ", **label_style).place(x=50, y=50) tk.Label(root, textvariable=temp_value, **data_label_style).place(x=60, y=90) tk.Label(root, text="Humidity: ", **label_style).place(x=250, y=50) tk.Label(root, textvariable=humidity_value, **data_label_style).place(x=260, y=90) tk.Label(root, text="Pressure: ", **label_style).place(x=450, y=50) tk.Label(root, textvariable=pressure_value, **data_label_style).place(x=460, y=90) tk.Label(root, text="Altitude: ", **label_style).place(x=650, y=50) tk.Label(root, textvariable=altitude_value, **data_label_style).place(x=660, y=90) # MQTT 客戶端啟動 client.loop_start() # 開始主循環 root.mainloop() # 停止MQTT循環(當窗口關閉時) client.loop_stop() client.disconnect() ``` ### 介面顯示 ![image](https://hackmd.io/_uploads/Syv019b0C.png)