Learning Objectives
- 說明 MQTT 的發布/訂閱模型:裝置不直接互相連線,而是透過 broker 依「主題」轉送訊息
- 用 umqtt.simple 的 MQTTClient 連上 broker,把感測值發布到主題
- 訂閱主題、設定回呼函式,用 check_msg 收到指令後控制 LED
- 看懂主題萬用字元 + 與 #,以及 QoS、保留訊息的用途
感測節點需要什麼樣的通訊
上一課用 HTTP 向網站要資料:ESP32 問一次,伺服器答一次。可是物聯網裝置常常要做兩件事:
- 一直送資料出去:教室的溫度每隔幾秒就要回報一次,而且可能有很多人、很多程式想看。
- 隨時接收指令:手機上按一下「開燈」,教室裡的 ESP32 要馬上知道。
如果用 HTTP,ESP32 得一直問「有沒有我的指令?」(大部分時候答案是沒有),手機也得知道每一塊 ESP32 的 IP 位址。 MQTT 是為這種情況設計的通訊協定:訊息小、連線一直開著,裝置之間不必知道彼此在哪裡。
發布/訂閱:所有訊息都經過 broker
MQTT 裡有一台中間的伺服器,叫做 broker(代理伺服器)。每個裝置都只連到 broker:
- 發布(publish):把一則訊息送給 broker,並標上一個主題(topic),例如
uedu/room101/temp。 - 訂閱(subscribe):告訴 broker「這個主題有新訊息時轉給我」。
ESP32(101 教室) broker 手機 App、儀表板
發布 uedu/room101/temp = 25.6 ──▶ 依主題轉送 ──▶ 訂閱了 uedu/room101/temp 的人都收到
訂閱 uedu/room101/led ◀── 依主題轉送 ◀── 發布 uedu/room101/led = on
發布的人不知道誰會收到,訂閱的人也不知道是誰發的;兩邊只約定好主題名稱。 新增一個儀表板,只要讓它訂閱同一個主題,ESP32 的程式一行都不用改。
MQTT 是公開的標準,目前由 OASIS 維護,常見的版本是 3.1.1 與 5.0。
MicroPython 的 umqtt.simple 是一個小巧的用戶端,這一課用它。
逐步看:把溫度發布到 broker
上方電路畫面的網路紀錄會列出每一個 MQTT 動作。注意:publish 的兩個參數都是字串,
所以溫度要先用 str() 轉成文字;MQTT 只負責送出一串位元組,內容是什麼格式由你決定。
主題怎麼命名、萬用字元與 QoS
主題是用 / 分層的字串,像檔案路徑。常見的命名方式是「專案/地點/裝置或量測項目」:
| 主題 | 內容 |
|---|---|
uedu/room101/temp |
101 教室的溫度 |
uedu/room101/hum |
101 教室的濕度 |
uedu/room101/led |
給 101 教室的開關燈指令 |
uedu/room202/temp |
202 教室的溫度 |
訂閱時可以用萬用字元,一次訂閱很多主題(發布時不能用):
| 訂閱 | 收得到 | 收不到 |
|---|---|---|
uedu/+/temp |
uedu/room101/temp、uedu/room202/temp |
uedu/room101/hum |
uedu/room101/# |
uedu/room101/temp、uedu/room101/led、uedu/room101 |
uedu/room202/temp |
+代表剛好一層,任何名稱都可以。#代表這一層以下的全部,只能放在最後面。
QoS:訊息要保證送到幾次
發布與訂閱時可以指定服務品質(Quality of Service,QoS):
| QoS | 意思 | 適合 |
|---|---|---|
| 0 | 最多送一次:送出去就不管了,可能遺失 | 每幾秒一筆的感測值,掉一筆沒關係 |
| 1 | 至少送一次:收到確認才算數,可能重複 | 開關指令、警報 |
| 2 | 剛好一次:多一次來回確認 | 計費、不能重複的動作 |
umqtt.simple 的 publish 支援 QoS 0 與 1。QoS 越高,來回的封包越多、越耗電。
另外兩個常用功能
- 保留訊息(retain):
publish(主題, 內容, retain=True),broker 會記住這個主題的最後一則訊息, 之後才訂閱的人一連上就先收到它。適合「目前狀態」,例如裝置在線、燈現在是開還是關。 - 遺囑訊息(last will):連線時先用
set_last_will()交給 broker 一則訊息;如果裝置沒有正常斷線就消失 (斷電、當機),broker 會替它發布出去,例如uedu/room101/status = offline。
逐步看:訂閱指令,遠端開關燈
這次 ESP32 是接收的一方。模擬的 broker 會在 1.8 秒送來 on、3.2 秒送來 off(電路規格裡的時間表)。
注意 on_message 不是程式自己呼叫的:是 check_msg() 發現有新訊息時才呼叫它。看第 27 行之後有沒有跳進第 15 行。
逐步看:一個訂閱收很多主題,內容用 JSON
走廊的 ESP32 控制兩間教室的燈,只訂閱一次 uedu/+/led。時間表裡有 4 則訊息,
其中一則是 uedu/room101/fan(風扇),看它有沒有被收到。訊息內容用 JSON,可以一次帶好幾個欄位。
小測驗:發布、訂閱與主題
手機 App 發布 uedu/room101/led = on,教室的 ESP32 怎麼收到這則訊息?
ESP32 訂閱了 uedu/+/temp,下面哪一個主題的訊息收不到?
溫度每 5 秒發布一次,偶爾掉一筆沒關係,想讓封包最少、最省電,QoS 選哪一個?
小測驗:回呼函式收到的資料
回呼函式收到的 msg 是位元組。下面的程式會印出什麼?
msg = b"on"
print(msg == "on", msg.decode() == "on")
下面的程式會印出什麼?
import json
topic = b"uedu/room202/led"
msg = b'{"on": 1, "level": 80}'
room = topic.decode().split("/")[1]
cmd = json.loads(msg.decode())
print(room, cmd["level"])
主迴圈裡改用 client.wait_msg() 取代 client.check_msg(),會有什麼不同?
上線前要注意的事
- 公開的測試 broker 任何人都能訂閱,包括
#(全部主題)。練習時不要發布個人資料、位置或密碼,主題名稱也要取得不容易撞到別人。 - 自己的 broker 要設定帳號密碼(
MQTTClient(..., user=..., password=...)),可能的話使用加密連線(ssl=True,預設連接埠 8883;不加密是 1883)。 - client_id 不要重複:兩個裝置用同一個 client_id,broker 會把先連上的那一個踢掉,兩塊板子會輪流斷線。
- 斷線要重連:Wi-Fi 或 broker 斷線時,
publish會拋出OSError;第 18 課的專題會把重連寫進程式。
重點整理
- MQTT 用發布/訂閱:裝置只連 broker,依主題轉送訊息,發布者和訂閱者不必知道彼此。
MQTTClient(client_id, broker)→connect()→publish(主題, 內容);內容是文字或位元組,數字要先轉成文字,多個欄位用 JSON。- 接收:
set_callback(函式)、subscribe(主題),迴圈裡呼叫check_msg()(不等待)或wait_msg()(等到有訊息)。回呼收到的主題與內容都是位元組。 - 訂閱可用萬用字元:
+剛好一層、#以下全部。 - QoS 0/1/2 決定送達保證;保留訊息讓後來的訂閱者拿到最新狀態;遺囑訊息在裝置意外離線時由 broker 代發。
下一課討論用電池供電時怎麼省電:Wi-Fi 很耗電,感測節點常常是「醒來、量測、發布、睡覺」。