使用 Python 掌握物聯網 MQTT 協議。本深入指南涵蓋原理、Paho-MQTT 函式庫、安全性及真實專案實作。
Python 物聯網:MQTT 實作綜合指南
互聯的世界:為什麼物聯網協議很重要
我們生活在一個前所未有的互聯互通時代。物聯網 (IoT) 不再是未來的概念,而是一個全球性的現實,默默地編織著由數十億個智慧設備組成的網路,這些設備監測我們的環境、自動化我們的家庭、優化我們的行業,並簡化我們的城市。從首爾一間房屋中的智慧恆溫器,到肯亞鄉村田野中的農業感測器,這些設備正在產生大量的資料。但是,它們如何相互以及與雲端通訊?尤其是當它們通常體積小、功耗低,並且在不可靠的網路中運作時?答案就在於專用的通訊協議。
雖然 HTTP 協議為我們每天使用的大部分網站提供支持,但對於物聯網受限的世界來說,它通常過於繁重且耗電。這就是專為機器對機器 (M2M) 通訊設計的協議發揮作用的地方。其中,一種協議已成為主導力量:MQTT。
本綜合指南專為全球想要使用 Python (物聯網領域中最通用和最流行的程式語言之一) 來利用 MQTT 強大功能的開發人員、工程師和業餘愛好者而設計。我們將從 MQTT 的基本概念到建構安全、穩健和可擴展的物聯網應用程式。
什麼是 MQTT?為約束而建構的協議
MQTT 代表 訊息佇列遙測傳輸。它由 IBM 的 Andy Stanford-Clark 博士和 Arcom (現在是 Cirrus Link) 的 Arlen Nipper 於 1999 年發明,用於監測不可靠衛星網路上的石油管道。它的起源故事完美地概括了它的用途:成為一種輕量級、可靠且高效的訊息傳輸協議,適用於在重大約束下運作的設備。
發布/訂閱 (Pub/Sub) 模型說明
MQTT 的核心是優雅的發布/訂閱架構模式。這與許多開發人員熟悉的 HTTP 的請求/回應模型有根本上的不同。通訊不再是客戶端直接從伺服器請求資訊,而是解耦的。
想像一下一家全球新聞通訊社。記者 (發布者) 不會將他們的故事直接發送給每一位讀者。相反,他們將故事發送到通訊社的中央樞紐 (代理伺服器),並將它們分類到特定主題下,例如「世界政治」或「科技」。讀者 (訂閱者) 無需向記者索取更新;他們只需告訴通訊社他們感興趣的主題。然後,通訊社會自動將有關這些主題的任何新故事轉發給感興趣的讀者。記者和讀者永遠不需要知道彼此的存在、位置或狀態。
在 MQTT 中,此模型將傳送資料的設備 (發布者) 與接收資料的設備或應用程式 (訂閱者) 解耦。這對於物聯網來說非常強大,因為:
- 空間解耦: 發布者和訂閱者不需要知道彼此的 IP 位址或位置。
- 時間解耦: 他們不需要同時運行。感測器可以發布讀數,如果系統設計為這樣做,應用程式可以在數小時後收到它。
- 同步解耦: 無需停止雙方的操作來等待另一方完成訊息交換。
MQTT 生態系統的關鍵組件
MQTT 架構建立在幾個核心組件之上:
- 代理伺服器: 中央樞紐或伺服器。它是 MQTT 世界的郵局。代理伺服器負責接收來自發布者的所有訊息,按主題篩選它們,並將它們傳送給適當的訂閱者。流行的代理伺服器包括像 Mosquitto 和 VerneMQ 這樣的開放原始碼選項,以及像 AWS IoT Core、Azure IoT Hub 和 Google Cloud IoT Core 這樣的託管雲端服務。
- 客戶端: 連接到代理伺服器的任何設備或應用程式。客戶端可以是發布者、訂閱者或兩者。物聯網感測器是一個客戶端,處理感測器資料的伺服器應用程式也是一個客戶端。
- 主題: 一個 UTF-8 字串,用作訊息的位址或標籤。代理伺服器使用主題來路由訊息。主題是分層的,使用正斜線作為分隔符,很像檔案系統路徑。例如,倫敦一棟建築物內客廳的溫度感測器的理想主題可能是:
UK/London/Building-A/Floor-1/LivingRoom/Temperature。 - 有效負載: 這是訊息的實際資料內容。MQTT 與資料無關,這意味著有效負載可以是任何東西:簡單的字串、整數、JSON、XML 甚至加密的二進制資料。JSON 因其靈活性和可讀性而成為非常常見的選擇。
為什麼 MQTT 在物聯網通訊中佔據主導地位
MQTT 的設計原則使其非常適合應對物聯網的挑戰:
- 輕量級: MQTT 訊息的標頭非常小 (至少 2 個位元組),最大限度地減少了網路頻寬使用。這對於使用昂貴的蜂窩網路計畫或低頻寬網路 (如 LoRaWAN) 的設備至關重要。
- 高效: 協議的低開銷直接轉化為更低的功耗,使電池供電的設備能夠運行數月甚至數年。
- 可靠: 它包括確保訊息傳遞的功能,即使在不穩定、高延遲的網路上也是如此。這是通過服務品質級別管理的。
- 可擴展: 單個代理伺服器可以同時處理來自數千甚至數百萬個客戶端的連接,使其適用於大規模部署。
- 雙向: MQTT 允許從設備到雲端 (遙測) 和從雲端到設備 (命令) 的通訊,這是遠端控制設備的重要需求。
了解服務品質 (QoS)
MQTT 提供三個服務品質 (QoS) 等級,允許開發人員為其特定用例選擇可靠性和開銷之間的適當平衡。
- QoS 0 (最多一次): 這是一個「即發即棄」等級。訊息只傳送一次,沒有收到來自代理伺服器或最終訂閱者的確認。它是最快的方法,但不提供傳遞保證。用例: 非關鍵、高頻率的感測器資料,例如每 10 秒傳送一次的環境室溫讀數。丟失一個讀數沒有問題。
- QoS 1 (至少一次): 此等級保證訊息將至少傳遞一次。傳送者儲存訊息,直到它收到來自接收者的確認 (PUBACK 封包)。如果沒有收到確認,則會重新傳送訊息。如果確認遺失,這有時會導致重複訊息。用例: 打開智慧燈的命令。您需要確保收到命令,並且收到兩次不會造成損害。
- QoS 2 (恰好一次): 這是最可靠但也是最慢的等級。它使用四部分握手來確保訊息只傳遞一次,沒有重複項。用例: 重複項可能造成災難性後果的關鍵操作,例如金融交易、配發精確劑量藥物的命令,或控制工廠中的機械手臂。
設定您的 Python MQTT 環境
現在,讓我們開始實踐。要開始使用 Python 建構 MQTT 應用程式,您需要兩件事:用於 MQTT 客戶端的 Python 函式庫和用於通訊的 MQTT 代理伺服器。
選擇 Python MQTT 函式庫:Paho-MQTT
Python 最廣泛使用和成熟的 MQTT 函式庫是 Eclipse 基金會的 Paho-MQTT。它是一個穩健、功能豐富的函式庫,提供一個客戶端類別來連接到代理伺服器並發布或訂閱主題。使用 Python 的套件管理器 pip 安裝它非常簡單。
打開您的終端機或命令提示字元並執行:
pip install paho-mqtt
這個單一命令會安裝您開始在 Python 中編寫 MQTT 客戶端所需的一切。
設定 MQTT 代理伺服器
您有多個代理伺服器選項,從在您的本機上運行一個用於開發,到使用強大的雲端服務用於生產。
- 本機代理伺服器 (用於開發和學習): 本機代理伺服器最受歡迎的選擇是 Mosquitto,這是另一個 Eclipse 專案。它輕量級、開放原始碼且易於安裝。
- 在基於 Debian 的 Linux 上 (例如 Ubuntu、Raspberry Pi OS):
sudo apt-get update && sudo apt-get install mosquitto mosquitto-clients - 在 macOS 上 (使用 Homebrew):
brew install mosquitto - 在 Windows 上: 從 Mosquitto 網站下載原生安裝程式。
127.0.0.1或localhost) 來使用它。 - 在基於 Debian 的 Linux 上 (例如 Ubuntu、Raspberry Pi OS):
- 公共/雲端代理伺服器 (用於快速測試): 對於無需安裝任何東西的初始實驗,您可以使用免費的公共代理伺服器。兩個流行的代理伺服器是
test.mosquitto.org和broker.hivemq.com。重要提示: 這些是公共的且未加密的。不要將任何敏感或私人資料發送到它們。它們僅用於學習和測試目的。
動手做:使用 Python 發布和訂閱
讓我們編寫我們的第一個 Python MQTT 應用程式。我們將建立兩個單獨的腳本:一個傳送訊息的發布者和一個接收訊息的訂閱者。對於這個例子,我們假設您正在運行本機 Mosquitto 代理伺服器。
建立一個簡單的 MQTT 發布者 (publisher.py)
這個腳本將連接到代理伺服器並每兩秒將訊息「Hello, MQTT!」發布到主題 `python/mqtt/test`。
建立一個名為 `publisher.py` 的檔案並添加以下程式碼:
import paho.mqtt.client as mqtt
import time
# --- Configuration ---
BROKER_ADDRESS = "localhost" # Use 'test.mosquitto.org' for a public broker
PORT = 1883
TOPIC = "python/mqtt/test"
# --- Callback for connection ---
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("Connected to MQTT Broker!")
else:
print(f"Failed to connect, return code {rc}")
# --- Main script ---
# 1. Create a client instance
client = mqtt.Client("PublisherClient")
# 2. Assign the on_connect callback
client.on_connect = on_connect
# 3. Connect to the broker
client.connect(BROKER_ADDRESS, PORT, 60)
# 4. Start a background thread for the network loop
client.loop_start()
try:
count = 0
while True:
count += 1
message = f"Hello, MQTT! Message #{count}"
# 5. Publish a message
result = client.publish(TOPIC, message)
# Check if publish was successful
status = result[0]
if status == 0:
print(f"Sent `{message}` to topic `{TOPIC}`")
else:
print(f"Failed to send message to topic {TOPIC}")
time.sleep(2)
except KeyboardInterrupt:
print("Publication stopped.")
finally:
# 6. Stop the network loop and disconnect
client.loop_stop()
client.disconnect()
print("Disconnected from the broker.")
建立一個簡單的 MQTT 訂閱者 (subscriber.py)
這個腳本將連接到同一個代理伺服器,訂閱 `python/mqtt/test` 主題,並列印它收到的任何訊息。
建立另一個名為 `subscriber.py` 的檔案:
import paho.mqtt.client as mqtt
# --- Configuration ---
BROKER_ADDRESS = "localhost"
PORT = 1883
TOPIC = "python/mqtt/test"
# --- Callback functions ---
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("Connected to MQTT Broker!")
# Subscribe to the topic upon successful connection
client.subscribe(TOPIC)
else:
print(f"Failed to connect, return code {rc}")
def on_message(client, userdata, msg):
# Decode the message payload from bytes to string
payload = msg.payload.decode()
print(f"Received message: `{payload}` on topic `{msg.topic}`")
# --- Main script ---
# 1. Create a client instance
client = mqtt.Client("SubscriberClient")
# 2. Assign callbacks
client.on_connect = on_connect
client.on_message = on_message
# 3. Connect to the broker
client.connect(BROKER_ADDRESS, PORT, 60)
# 4. Start the network loop (blocking call)
# This function handles reconnecting and processing messages automatically.
print("Subscriber is listening...")
client.loop_forever()
執行範例
- 打開兩個單獨的終端機視窗。
- 在第一個終端機中,執行訂閱者腳本:
python subscriber.py - 您應該會看到訊息「Subscriber is listening...」。它現在正在等待訊息。
- 在第二個終端機中,執行發布者腳本:
python publisher.py - 您將會看到發布者每兩秒傳送訊息。同時,這些訊息將會出現在訂閱者的終端機視窗中。
恭喜!您剛剛使用 Python 建立了一個完整的、可運作的 MQTT 通訊系統。
超越基礎:進階 Paho-MQTT 功能
真實世界的物聯網系統需要比我們的簡單範例更強大的功能。讓我們探索一些進階 MQTT 功能,這些功能對於建構可投入生產的應用程式至關重要。
遺囑 (LWT)
如果一個關鍵設備,例如安全攝影機或心臟監護器,由於電源故障或網路遺失而意外斷線,會發生什麼情況?LWT 功能是 MQTT 的解決方案。當客戶端連接時,它可以向代理伺服器註冊一個「遺囑」訊息。如果客戶端以不正常的方式斷線 (未傳送 DISCONNECT 封包),代理伺服器將會自動代表它將此遺囑訊息發布到指定的主題。
這對於設備狀態監控來說非常寶貴。您可以讓設備在連接時使用有效負載「online」發布 `devices/device-123/status` 訊息,並使用相同的有效負載 `offline` 向同一主題註冊 LWT 訊息。任何訂閱此主題的監控服務都會立即知道設備的狀態。
要在 Paho-MQTT 中實作 LWT,您需要在連接之前設定它:
client.will_set('devices/device-123/status', payload='offline', qos=1, retain=True)
client.connect(BROKER_ADDRESS, PORT, 60)
保留訊息
通常,如果訂閱者連接到一個主題,它將只會收到在它訂閱之後發布的訊息。但是,如果您需要立即獲得最新值,該怎麼辦?這就是保留訊息的用途。當訊息發布時,如果將 `retain` 旗標設定為 `True`,代理伺服器會為該特定主題儲存該訊息。每當有新的客戶端訂閱該主題時,它都會立即收到上次保留的訊息。
這非常適合狀態資訊。設備可以使用 `retain=True` 發布其狀態 (例如,`{"state": "ON"}`)。任何啟動並訂閱的應用程式都會立即知道設備的目前狀態,而無需等待下次更新。
在 Paho-MQTT 中,您只需將 `retain` 旗標添加到您的發布呼叫中:
client.publish(TOPIC, payload, qos=1, retain=True)
持久性會話和清潔會話
客戶端連接請求中的 `clean_session` 旗標控制代理伺服器如何處理客戶端的會話。
- 清潔會話 (
clean_session=True,預設值): 當客戶端斷線時,代理伺服器會丟棄關於它的所有資訊,包括它的訂閱和任何佇列的 QoS 1 或 2 訊息。當它重新連接時,它就像一個全新的客戶端。 - 持久性會話 (
clean_session=False): 當一個具有唯一客戶端 ID 的客戶端以這種方式連接時,代理伺服器會在它斷線後維護其會話。這包括它的訂閱和任何在它離線時發布的 QoS 1 或 2 訊息。當客戶端重新連接時,代理伺服器會傳送所有錯過的訊息。這對於無法承受遺失關鍵命令的不穩定網路上的設備至關重要。
要建立持久性會話,您必須提供一個穩定、唯一的客戶端 ID,並在建立客戶端實例時設定 `clean_session=False`:
client = mqtt.Client(client_id="my-persistent-device-001", clean_session=False)
安全不是一種選擇:使用 Python 保護 MQTT 的安全
在任何真實世界的應用程式中,安全至關重要。不安全的 MQTT 代理伺服器是一個公開的邀請,供惡意行為者竊聽您的資料、向您的設備傳送錯誤命令或發起阻斷服務攻擊。保護 MQTT 的安全涉及三個關鍵支柱:身份驗證、加密和授權。
身份驗證:你是誰?
身份驗證驗證連接到代理伺服器的客戶端的身份。最簡單的方法是使用用戶名和密碼。您可以將您的 Mosquitto 代理伺服器配置為需要憑證,然後在您的 Python 客戶端中提供它們。
在您的 Python 客戶端中,使用 `username_pw_set()` 方法:
client.username_pw_set(username="myuser", password="mypassword")
client.connect(BROKER_ADDRESS, PORT, 60)
加密:使用 TLS/SSL 保護傳輸中的資料
如果用戶名和密碼以純文字形式在網路上傳送,則它們幾乎毫無用處。加密確保客戶端和代理伺服器之間的 所有通訊都被加擾,並且對於在網路上窺探的任何人來說都是不可讀的。這是使用傳輸層安全 (TLS) 實現的,TLS 與保護網站安全 (HTTPS) 的技術相同。
要將 TLS 與 MQTT (通常稱為 MQTTS) 結合使用,您需要將您的代理伺服器配置為支持它 (通常在 8883 端口上),並將必要的憑證提供給您的客戶端。這通常涉及憑證授權單位 (CA) 憑證來驗證代理伺服器的身份。
在 Paho-MQTT 中,您可以使用 `tls_set()` 方法:
client.tls_set(ca_certs="path/to/ca.crt")
client.connect(BROKER_ADDRESS, 8883, 60)
授權:您被允許做什麼?
一旦客戶端通過了身份驗證,授權就會決定它被允許做什麼。例如,只應允許溫度感測器發布到它自己的主題 (例如,`sensors/temp-A/data`),但不應發布到用於控制工廠機械的主題 (例如,`factory/floor-1/robot-arm/command`)。這通常使用存取控制清單 (ACL) 在代理伺服器上處理。您可以通過規則配置代理伺服器,這些規則定義了哪些用戶可以 `read` (訂閱) 或 `write` (發布) 到特定主題模式。
將它們放在一起:一個簡單的智慧環境監控專案
讓我們建構一個稍微更現實的專案來鞏固這些概念。我們將模擬一個感測器設備,該設備將環境資料作為 JSON 物件發布,以及一個監控應用程式,該應用程式訂閱此資料並顯示它。
專案概述
- 感測器 (發布者): 一個 Python 腳本,用於模擬感測器讀取溫度和濕度。它會將此資料打包到一個 JSON 有效負載中,並每 5 秒將其發布到主題
smart_env/device01/telemetry。 - 監視器 (訂閱者): 一個 Python 腳本,用於訂閱 `smart_env/device01/telemetry`,接收 JSON 資料,解析它,並列印一個使用者友好的狀態更新。
感測器程式碼 (sensor_publisher.py)
import paho.mqtt.client as mqtt
import time
import json
import random
BROKER_ADDRESS = "localhost"
PORT = 1883
TOPIC = "smart_env/device01/telemetry"
client = mqtt.Client("SensorDevice01")
client.connect(BROKER_ADDRESS, PORT, 60)
client.loop_start()
print("Sensor publisher started...")
try:
while True:
# Simulate sensor readings
temperature = round(random.uniform(20.0, 30.0), 2)
humidity = round(random.uniform(40.0, 60.0), 2)
# Create a JSON payload
payload = {
"timestamp": time.time(),
"temperature": temperature,
"humidity": humidity
}
payload_str = json.dumps(payload)
# Publish the message with QoS 1
result = client.publish(TOPIC, payload_str, qos=1)
result.wait_for_publish() # Block until publish is confirmed
print(f"Published: {payload_str}")
time.sleep(5)
except KeyboardInterrupt:
print("Stopping sensor publisher...")
finally:
client.loop_stop()
client.disconnect()
監視儀表板程式碼 (monitor_subscriber.py)
import paho.mqtt.client as mqtt
import json
import datetime
BROKER_ADDRESS = "localhost"
PORT = 1883
TOPIC = "smart_env/device01/telemetry"
def on_connect(client, userdata, flags, rc):
print(f"Connected with result code {rc}")
client.subscribe(TOPIC)
def on_message(client, userdata, msg):
print("--- New Message Received ---")
try:
# Decode the payload string and parse it as JSON
payload = json.loads(msg.payload.decode())
timestamp = datetime.datetime.fromtimestamp(payload.get('timestamp'))
temperature = payload.get('temperature')
humidity = payload.get('humidity')
print(f"Device: {msg.topic}")
print(f"Time: {timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Temperature: {temperature}°C")
print(f"Humidity: {humidity}%")
except json.JSONDecodeError:
print("Error decoding JSON payload.")
except Exception as e:
print(f"An error occurred: {e}")
client = mqtt.Client("MonitoringDashboard")
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER_ADDRESS, PORT, 60)
print("Monitoring dashboard is running...")
client.loop_forever()
從原型到生產:MQTT 最佳實務
將您的專案從一個簡單的腳本轉移到一個穩健、可擴展的生產系統需要仔細的規劃。以下是一些必要的最佳實務:
- 設計一個清晰的主題層次結構: 從一開始就仔細規劃您的主題結構。一個好的層次結構是描述性的、可擴展的,並且允許使用萬用字元進行靈活的訂閱。一個常見的模式是
<site>/<area>/<device_type>/<device_id>/<measurement>。 - 妥善處理網路斷線: 網路是不可靠的。您的客戶端程式碼應該實作穩健的重新連接邏輯。Paho-MQTT 中的 `on_disconnect` 回呼是開始此操作的理想位置,實作一個類似於指數退避的策略,以避免以重新連接嘗試淹沒網路。
- 使用結構化的資料有效負載: 始終為您的訊息有效負載使用結構化的資料格式,例如 JSON 或 Protocol Buffers。這使您的資料具有自我描述性、可版本化,並且易於讓不同的應用程式 (以任何語言編寫) 進行解析。
- 預設情況下保護一切: 不要部署沒有安全性的物聯網系統。至少,使用用戶名/密碼身份驗證和 TLS 加密。對於更高的安全需求,探索基於客戶端憑證的身份驗證。
- 監控您的代理伺服器: 在生產環境中,您的 MQTT 代理伺服器是一個關鍵的基礎設施。使用監控工具來追蹤它的健康狀況,包括 CPU/記憶體使用量、已連接客戶端的數量、訊息速率和丟棄的訊息。許多代理伺服器公開一個特殊的 `$SYS` 主題層次結構,該層次結構提供此狀態資訊。
結論:您與 Python 和 MQTT 的旅程
我們已經從 MQTT 的基本「為什麼」走到了使用 Python 實作它的實際「如何」。您已經了解了發布/訂閱模型的強大功能、QoS 的重要性以及安全性的關鍵作用。您已經看到 Paho-MQTT 函式庫如何使建構可以發布感測器資料和訂閱命令的複雜客戶端變得非常簡單。
MQTT 不僅僅是一種協議;它是物聯網的一種基礎技術。它的輕量級特性和穩健的功能使其成為全球數百萬台設備的首選,從智慧城市到互聯農業再到工業自動化。
旅程並未在此結束。下一步是採用這些概念並將它們應用於真實硬體。嘗試使用 Raspberry Pi、ESP32 或其他微控制器。連接物理感測器、與雲端物聯網平台整合,以及建構與物理世界互動的應用程式。借助 Python 和 MQTT,您可以獲得一個強大的工具包來建構下一代互聯解決方案。