National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 72 24°C PM2.5 13
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 16

MQTT 與物聯網

發布、訂閱與和其他裝置交換資料
Interaction 25 minute(s)

Learning Objectives

  • 說明發布/訂閱的運作方式,以及訊息代理(broker)扮演的角色
  • 設計主題階層,用萬用字元 + 與 # 一次訂閱多個主題
  • 用 paho-mqtt 2.x 發布感測器資料(JSON),以及訂閱主題控制 LED
  • 說明 QoS、保留訊息與連線安全的基本概念

很多台裝置要怎麼互相說話

上一課的 Flask 是「一問一答」:瀏覽器問,樹莓派答。如果一棟樓有 30 間教室,每間一個溫度感測器, 管理室的螢幕、手機 App、冷氣控制器都想知道溫度,讓每個人一直去問每一台感測器,很快就會亂成一團。

MQTT 換一個方式:裝置不直接互相連線,而是都連到一台訊息代理(broker),

  • 感測器把資料發布(publish)到一個主題(topic),例如 classroom/101/temp;
  • 想要資料的程式向 broker 訂閱(subscribe)它關心的主題;
  • broker 把每一則訊息轉送給所有訂閱者。
101 溫度感測器 ──publish classroom/101/temp──▶ broker ──▶ 管理室螢幕(訂閱 classroom/+/temp)
                                                      └─▶ 冷氣控制器(訂閱 classroom/101/temp)

發布者不需要知道有誰在聽,訂閱者也不需要知道資料從哪來;新增一台裝置,其他裝置都不用改。 MQTT 的訊息很小、連線維持很省資源,所以常用在物聯網。常見的 broker 軟體是開源的 Mosquitto,預設的連接埠是 1883。

主題與萬用字元

主題是用斜線 / 分層的字串,像檔案路徑一樣由大到小:校區/大樓/教室/測量項目。 訂閱時可以用兩種萬用字元:

寫法 意思 classroom/+/temp 的例子
+ 剛好一層,任何名稱 收得到 classroom/101/temp、classroom/102/temp
# 這一層與以下所有層(只能放在最後) classroom/# 收得到 classroom 底下的全部

classroom/+/temp 收不到 classroom/101/2F/temp(多了一層);也收不到 classroom/101/humidity(最後一層不同)。

設計主題時,把「會想一起訂閱的東西」放在同一層:例如測量項目放最後一層,就能用 classroom/+/temp 收所有教室的溫度。

下面的模擬器把整個流程畫出來,試試不同的主題篩選。

互動練習:發布、訂閱與萬用字元

感測器每 2 秒(模擬時間)發布一次溫度;藍點是溫度訊息,金色的點是給風扇的指令。照任務操作:調高溫度讓風扇開啟、幫手機 App 選主題篩選、用萬用字元一次收兩個主題,再比較規則放在雲端與教室閘道的反應時間。這裡的延遲都是示意值。

在樹莓派上用 Python:paho-mqtt

Python 最常用的 MQTT 用戶端是 Eclipse 的 paho-mqtt。2.0 版(2024 年)改了回呼函式的寫法, 建立用戶端時要指定 CallbackAPIVersion,舊教材的 mqtt.Client("名字") 在新版會出錯:

import paho.mqtt.client as mqtt

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
client.connect("broker 的位址", 1883)
client.publish("主題", "內容")

發布的內容(payload)是字串或位元組。要送好幾個數值時,常用 JSON:json.dumps({"room": "101", "temp": 26.1}), 收到的一方用 json.loads 還原成字典。

client.loop_start() 在背景處理網路的收發(送出訊息、回應 broker 的心跳);程式結束前用 loop_stop() 與 disconnect() 收尾。

逐步看:每秒發布一次溫度

電路畫面下方會列出 MQTT 的連線與發布紀錄。注意第 17 行把字典變成 JSON 字串,第 18 行送出。

訂閱:用回呼函式處理收到的訊息

訂閱的一方不知道訊息什麼時候會來,所以用回呼函式(callback):先告訴 paho「發生某件事時呼叫這個函式」, 然後讓它一直等。這和第 06 課 gpiozero 的 when_pressed 是同一個想法。

回呼函式 什麼時候被呼叫 VERSION2 的參數
on_connect 連上 broker 之後 client, userdata, flags, reason_code, properties
on_message 收到訂閱主題的訊息 client, userdata, msg(msg.topic、msg.payload)

兩個習慣:

  • 在 on_connect 裡訂閱:網路斷了、paho 自動重新連線之後,on_connect 會再被呼叫,訂閱也跟著恢復。
  • msg.payload 是位元組(bytes),要先 .decode("utf-8") 變成字串再比較。

client.loop_forever() 讓程式停在這裡一直處理網路,直到斷線或程式被停止。

逐步看:收到訊息就開關燈

訊息時間表:1 秒 classroom/101/led 送 on、2 秒 classroom/102/led 送 on、3 秒 classroom/101/led 送 off。只訂閱了 101,看 2 秒那一則有沒有進到 on_message。

互動練習:訂閱、萬用字元與回報狀態

訊息時間表:0.8 秒 101 開、1.8 秒 102 關、2.8 秒 101 關、3.6 秒 102 開。依序完成任務:

  1. 直接執行,看收到哪幾則訊息。
  2. 改成用萬用字元訂閱所有教室的 led 主題,讓 102 的訊息也收得到(燈會跟著所有教室的指令開關)。
  3. 每次開關燈之後,發布燈的狀態到 classroom/101/status,內容是 "on" 或 "off"。
  4. 回答一個問題。

QoS、保留訊息與安全

QoS(服務品質)決定一則訊息的送達保證,發布與訂閱時都可以指定:

QoS 保證 適合
0 最多一次:送出就不管,可能遺失 每秒更新的感測數值,少一筆沒關係
1 至少一次:確認收到為止,可能重複 重要的指令,接收端要能處理重複
2 剛好一次:多一次往返確認 不能重複也不能遺失的事件

保留訊息(retained message):發布時加 retain=True,broker 會記住這個主題最後一則保留訊息, 之後才訂閱的人會立刻收到它。適合「目前狀態」這種資料(例如燈現在是開還是關),新加入的 App 不用等下一次更新。

安全:

  • 公開的測試用 broker 誰都可以訂閱,不要送任何個人或敏感資料。
  • 自己架設的 broker 要設定帳號密碼(Mosquitto 2.0 起,有設定監聽埠時預設不允許匿名連線),並為連線加密(TLS)。
  • 控制硬體的主題(例如 .../led)要限制誰能發布,否則任何人都能開關你的燈。

小測驗:主題與萬用字元

1.

訂閱 campus/+/temp,下面哪一則訊息會收到?

2.

下面的函式模擬 MQTT 主題比對(只處理 + 萬用字元),會印出什麼?

def match(pattern, topic):
    p, t = pattern.split("/"), topic.split("/")
    if len(p) != len(t):
        return False
    return all(a == "+" or a == b for a, b in zip(p, t))

print(match("classroom/+/led", "classroom/102/led"),
      match("classroom/+/led", "classroom/102/2F/led"))
第 1 次作答

小測驗:paho-mqtt 與 QoS

1.

為什麼建議把 client.subscribe(...) 寫在 on_connect 裡,而不是寫在 connect() 的下一行?

2.

下面的程式會印出什麼?

import json
payload = b'{"room": "101", "temp": 26.5}'
data = json.loads(payload.decode("utf-8"))
print(data["temp"] + 1, type(payload).__name__)
3.

教室溫度每 5 秒發布一次,偶爾少一筆沒有關係。最適合用哪一個 QoS?

第 1 次作答

重點整理

  • MQTT 是發布/訂閱:裝置都連到 broker,發布者送到主題,broker 轉給所有訂閱者,彼此不必直接連線。
  • 主題用 / 分層;+ 代表剛好一層,# 代表這一層以下全部(只能放最後)。
  • paho-mqtt 2.x:mqtt.Client(mqtt.CallbackAPIVersion.VERSION2);在 on_connect 裡訂閱,on_message 收訊息,msg.payload 要解碼。
  • 多個數值用 JSON 傳送;QoS 0/1/2 決定送達保證;保留訊息讓新訂閱者立刻拿到目前狀態。
  • 公開 broker 不送敏感資料;自己的 broker 要設帳號密碼與加密,限制誰能發布控制指令。

下一課讓記錄、網頁與 MQTT 程式在樹莓派開機時自動執行,當機了也會自己重新啟動。

這一課完成了!
Next lesson:開機自動執行:systemd 與 cron
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。