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 80 24°C PM2.5 21
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 18

專題:Wi-Fi 感測節點

讀取感測器、上傳資料並接收指令的完整作品
Interaction 30 minute(s)

Learning Objectives

  • 從需求出發,規劃感測節點的電路、MQTT 主題與訊息格式
  • 把 Wi-Fi、MQTT、感測與指令處理整合成一個結構清楚的程式
  • 用 ticks_diff 安排主迴圈:常常檢查指令、每隔一段時間才量測發布
  • 處理感測器讀取失敗與連線逾時,讓節點長時間穩定運作

把前面學過的東西組成一個作品

這一課的目標是一個教室環境感測節點:

  • 每隔一段時間量測溫濕度,發布到 MQTT,讓儀表板或手機看得到。
  • 接收遠端指令:開關燈號、調整量測間隔。
  • 溫度太高時發出警報。
  • 斷電重開、Wi-Fi 不穩、感測器線鬆掉,都不會讓它「卡住」。

用到的每一塊你都學過:GPIO(第 3 課)、DHT22(第 9 課)、Wi-Fi(第 12 課)、MQTT(第 14 課)、省電(第 15 課)、設定檔(第 16 課)。 專題的難處不在新語法,而在先規劃清楚,再一塊一塊接起來、一塊一塊測試。

第一步:寫下規格

項目 決定
量測 DHT22 溫濕度,預設每 2 秒一次(正式使用可以改成幾分鐘一次)
輸出 板上 LED(GPIO2)當作遠端可控制的燈號
供電 USB 變壓器,一直開著(要用電池時改成第 15 課的深度睡眠,只發布、不收指令)
網路 Wi-Fi+MQTT,Wi-Fi 名稱、密碼、broker、節點名稱放在 config.json

第二步:設計主題與訊息

主題 方向 內容(JSON)
uedu/node1/data 節點發布 {"temp": 26.2, "hum": 61.7}
uedu/node1/status 節點發布(保留訊息) online
uedu/node1/alert 節點發布 {"temp": 31.2}
uedu/node1/cmd 節點訂閱 {"led": 1}、{"interval": 5}

所有主題都以 uedu/節點名稱/ 開頭:之後多裝幾個節點,只要改設定檔裡的名稱;儀表板訂閱 uedu/+/data 就能一次收到全部節點。 溫度與濕度放在同一則 JSON 裡,兩個數字一定是同一次量測的,接收端也只要處理一則訊息。

逐步看:讀設定、有逾時的連線、宣告上線

程式拆成兩個函式:connect_wifi 負責 Wi-Fi,connect_mqtt 負責 broker。工作目錄裡已經有一份 config.json。 注意 connect_wifi 不會永遠等下去:超過時間就回傳 False,由主程式決定怎麼辦。

主迴圈怎麼安排

節點同時有兩件事要做,節奏不一樣:

  • 檢查指令:越常越好,按下手機按鈕之後燈才會馬上亮。
  • 量測發布:每隔幾秒或幾分鐘一次就夠了;DHT22 本身也不適合讀得太頻繁。

如果迴圈寫成「量測、發布、sleep(2)」,指令最慢要等 2 秒才處理(第 14 課練習的問題)。 比較好的寫法是:迴圈每 0.2 秒轉一圈、每圈都檢查指令,再用 ticks_diff 看看「距離上次量測過了多久」,到了才量測:

last = time.ticks_ms() - interval_ms     # 讓第一次馬上量測
while True:
    client.check_msg()                   # 每圈都做
    now = time.ticks_ms()
    if time.ticks_diff(now, last) >= interval_ms:
        last = now                       # 時間到了才做
        ...量測、發布...
    time.sleep(0.2)

這個寫法也讓「量測間隔」變成一個變數,可以被遠端指令修改。

逐步看:常常檢查指令、定時發布

模擬的 broker 在 1.5 秒送來 {"led": 1}、2.6 秒送來 {"interval": 1}。 看網路紀錄:一開始每 2 秒發布一次資料,收到 interval 指令之後變成每 1 秒一次。可以用時間軸拖到後半段比較。

會壞的地方要先想好

感測節點常常裝在沒人看著的地方,要假設每一個外部的東西都可能出錯:

可能出錯 會發生什麼 怎麼處理
感測器線鬆掉 measure() 拋出 OSError 用 try 接住,這次不發布,記下失敗次數
Wi-Fi 連不上 isconnected() 一直是 False 等待加上逾時,隔一段時間重試,最後可以先睡覺
broker 斷線 publish()、check_msg() 拋出 OSError 接住後重新連線(必要時重新連 Wi-Fi)
程式卡住 節點不再回應 看門狗計時器(machine.WDT):程式太久沒「餵」它就自動重開機
意外斷電 重新開機 程式存成 main.py,開機自動執行;設定都在 config.json

感測器讀取失敗的寫法:

def read_temp():
    try:
        sensor.measure()
        return sensor.temperature()
    except OSError:          # 感測器沒有回應(例如線鬆了)
        return None          # 告訴呼叫的人「這次沒有數字」

temp = read_temp()
if temp is not None:         # 量不到就不發布,不要發布 0 或上一次的值
    client.publish(BASE + "data", json.dumps({"temp": temp}))

連線前還可以用 client.set_last_will(base + "status", "offline", retain=True) 交給 broker 一則遺囑訊息: 節點意外斷線時,儀表板會看到 offline。

下一個範例的 Wi-Fi 密碼少打一個字,看程式怎麼重試、最後先睡覺,而不是卡在等待的迴圈裡。

逐步看:連不上時重試,不要卡住

密碼錯了,每一次連線都會失敗。看 connect_wifi 怎麼在 1 秒後放棄,for 迴圈怎麼重試三次、每次多等一點,最後進入深度睡眠(第 15 課)。

互動練習:完成感測節點

起始程式已經會連線、每 2 秒發布溫濕度,也收得到指令,但收到指令只會印出來。 模擬的 broker 在第 2 秒送來 {"led": 1}、第 4.5 秒送來 {"led": 0};溫度從 27 度慢慢升到 33 度(可以用滑桿改)。依序完成任務:

  1. 直接執行,看網路紀錄裡的發布與收到的指令。
  2. 完成 on_cmd:依 cmd["led"] 開關 LED。
  3. 溫度超過 30 度時,發布警報到 uedu/node1/alert(內容自訂,例如溫度的 JSON)。
  4. 回答一個問題。

小測驗:規劃與結構

1.

有 5 個感測節點,名稱 node1~node5,資料都發布到 uedu/節點名稱/data。儀表板想一次收到全部節點的資料,要訂閱什麼?

2.

主迴圈每 0.2 秒轉一圈,interval_ms 是 3000。大約每幾圈會量測發布一次?

3.

DHT22 讀取失敗時,下面哪一種處理最恰當?

第 1 次作答

小測驗:程式細節

1.

下面的程式會印出什麼?

interval_ms = 2000

def on_cmd(cmd):
    interval_ms = cmd["interval"] * 1000

on_cmd({"interval": 5})
print(interval_ms)
2.

下面的程式模擬主迴圈的時間判斷,會印出什麼?

last = 0
interval = 1000
sent = []
for now in range(0, 3001, 200):
    if now - last >= interval:
        last = now
        sent.append(now)
print(sent)
第 1 次作答

部署與測試清單

程式在模擬器與 Thonny 裡都正常之後:

  1. 把程式存成 main.py,設定存成 config.json,上傳到板子(第 16 課)。
  2. 用 USB 電源(不接電腦)重新開機,確認它自己連線、開始發布。
  3. 用另一個 MQTT 用戶端(手機 App 或電腦上的程式)訂閱 uedu/node1/#,看得到 data 與 status,發布 cmd 指令看燈有沒有反應。
  4. 故意製造問題:拔掉 DHT22 的線、關掉 Wi-Fi 分享器再打開、拔電再插上。每一種情況節點都要能自己恢復。
  5. 把各種情況與結果記下來,就是專題報告裡的「測試」一節。

可以繼續擴充的方向:加 OLED 顯示目前溫度(第 9 課)、用 NeoPixel 顯示舒適度(第 8 課)、 改成電池供電+深度睡眠(第 15 課),或在樹莓派上架自己的 broker 與儀表板(樹莓派軌)。

重點整理

  • 專題先寫規格:量測什麼、輸出什麼、怎麼供電、主題與訊息格式。主題用「專案/節點/項目」分層,內容用 JSON。
  • 程式拆成函式(連 Wi-Fi、連 MQTT、讀感測器、處理指令),每一塊可以單獨測試。
  • 連線要有逾時;上線狀態用保留訊息,意外離線用遺囑訊息。
  • 主迴圈每圈都 check_msg(),用 ticks_diff 判斷量測時間到了沒;回呼函式要修改全域設定時用 global。
  • 感測器失敗時用 try 接住、不發布假的數字;部署成 main.py+config.json,再逐一測試斷線、斷電、感測器脫落。

ESP32 軌到這裡結束。你已經能從一顆 LED 做到一個連網、可遠端控制的感測節點。

這一課完成了!
看我的學習進度
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。