---
# System prepended metadata

title: ESP32使用MQTT與Windows通信
tags: [Python, MQTT, ESP32]

---

## 說明

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)


















