## 說明
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:但此文件為系統文件無法直接修改, 在此文件內容中, 修改使用者權限後即可

* 允許其他匿名者使用, 開啟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"
```
在訂閱端可以看剛剛傳送的訊息就成功了

</br>
## 4. 在Windows架設MQTT Broker
* #### 方法一:使用服務開啟MQTT
開啟"服務", 找到"Mosquitto Broker", 點選"啟動"
預設為自動(開機時自動啟動), 如果不想開機自動啟動可改為手動

* #### 方法二:使用命令提示字元(系統管理員身分執行)
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就表示成功運行了

</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()
```
### 介面顯示
