Learning Objectives
- 用 network.WLAN 啟動 Wi-Fi、掃描附近的網路並解讀訊號強度
- 說明 connect() 為什麼要搭配等待迴圈與逾時
- 用 status() 判斷連線失敗的原因,寫出可重複使用的連線函式
- 讀懂 ifconfig() 回傳的 IP 位址、子網路遮罩、閘道與 DNS
從一塊板子變成網路上的裝置
到目前為止,ESP32 讀到的資料只能顯示在燈、螢幕或序列埠上。ESP32 內建 Wi-Fi,連上網路之後就能 把感測資料上傳到伺服器、從網路取得天氣、讓手機用瀏覽器控制它,這就是物聯網(IoT)的起點。
這一課要回答:
- 怎麼讓 ESP32 找到並連上一個 Wi-Fi 網路?
- 連線要花時間,程式要怎麼等?連不上的時候怎麼辦?
- 連上之後拿到的 IP 位址、閘道是什麼?
兩種模式
| 模式 | MicroPython | 像什麼 | 用途 |
|---|---|---|---|
| 工作站(station) | network.WLAN(network.STA_IF) |
你的手機 | 連上教室、宿舍的基地台,再連到網際網路 |
| 基地台(access point) | network.WLAN(network.AP_IF) |
家裡的路由器 | ESP32 自己發出一個 Wi-Fi,讓手機直接連過來 |
依 Espressif 的規格書,ESP32 支援 2.4 GHz 的 802.11 b/g/n,兩種模式也可以同時開。如果基地台只開 5 GHz,ESP32 會找不到它。
這一課只用工作站模式。較新版本的 MicroPython 文件改寫成 network.WLAN.IF_STA,意思一樣。
逐步看:掃描附近的 Wi-Fi
scan() 回傳一個串列,每個網路是一個元組:(名稱, BSSID, 頻道, 訊號強度, 加密方式, 是否隱藏)。
注意第 6 行讓時間前進了 0.8 秒:掃描要逐一聽各個頻道,需要一點時間。
訊號強度與連線的過程
RSSI 用 dBm 表示,是負數:-48 比 -83 強很多。dBm 是對數單位,每差 10 dBm,功率大約差 10 倍。
裝置的位置、牆壁、金屬外殼都會影響訊號,裝在現場前可以先用 scan() 看看訊號夠不夠。
wlan.connect(名稱, 密碼) 只是送出連線要求就馬上返回,真正的連線(認證、從基地台拿到 IP 位址)要再花一段時間。
所以接下來一定要等:
wlan.connect(SSID, PASSWORD)
while not wlan.isconnected(): # 拿到 IP 才算連上
time.sleep(0.4)
這樣寫有個問題:密碼打錯、基地台關掉的時候,迴圈永遠不會結束。所以一定要加逾時:等太久就放棄,做別的處理 (例如在螢幕顯示「連不上網路」,或是先進入省電模式、等一下再試)。
逐步看:連線、等待、讀取 IP
模擬中的基地台在 1.2 秒後完成連線。看上方的 Wi-Fi 狀態從「連線中」變成「已連線」,以及迴圈裡 status() 的狀態碼。
ifconfig() 的四個值
| 值 | 範例 | 意思 |
|---|---|---|
| IP 位址 | 192.168.1.42 | ESP32 在這個區域網路裡的地址 |
| 子網路遮罩 | 255.255.255.0 | 前三段相同的位址在同一個區域網路裡 |
| 閘道 | 192.168.1.1 | 通往其他網路的出口,通常就是基地台 |
| DNS | 192.168.1.1 | 把 example.com 這種網域名稱查成 IP 位址的伺服器 |
192.168.x.x 屬於私有 IP 位址(RFC 1918 定義了 10.x.x.x、172.16~31.x.x、192.168.x.x 三段),只在區域網路內有效。
同一個網路裡的手機可以連到 192.168.1.42,但外面的網際網路看不到它。下一課的網頁伺服器就是讓同一個網路裡的手機連過來。
MicroPython 較新的文件把 ifconfig() 標成「不建議使用」,改用 wlan.ipconfig("addr4");目前的韌體兩種都能用,這一課用 ifconfig()。
連不上的時候
status() 回傳狀態碼,可以和 network 模組的常數比較:
| 常數 | 意思 |
|---|---|
network.STAT_IDLE |
沒有連線、沒有動作 |
network.STAT_CONNECTING |
連線中 |
network.STAT_WRONG_PASSWORD |
密碼錯誤 |
network.STAT_NO_AP_FOUND |
找不到這個名稱的基地台 |
network.STAT_GOT_IP |
連線成功、拿到 IP |
依 MicroPython 文件,ESP32 連線失敗後預設會一直自動重試,重試期間狀態可能一直是 STAT_CONNECTING。
所以不能只等「錯誤狀態」出現,程式一定要自己設逾時,放棄時呼叫 wlan.disconnect() 停止重試。
逐步看:可重複使用的連線函式
把連線的步驟包成 connect_wifi():成功回傳 IP 位址,失敗回傳 None。第一次故意用錯的密碼,看函式怎麼提早結束;第二次用對的密碼。
小測驗:掃描與連線
scan() 找到三個網路,訊號強度分別是 -48、-71、-83 dBm,哪一個訊號最強?
只有 while not wlan.isconnected(): time.sleep(0.4)、沒有逾時,密碼又打錯了,會怎樣?
下面的程式會印出什麼?
cfg = ("192.168.1.42", "255.255.255.0", "192.168.1.1", "192.168.1.1")
ip, mask, gateway, dns = cfg
print(ip.split(".")[-1], gateway == dns)
小測驗:IP 與網路
子網路遮罩是 255.255.255.0、ESP32 的 IP 是 192.168.1.42,下面哪一個位址和它在同一個區域網路?
下面的程式會印出什麼?(模擬一個會回傳 None 的連線函式)
def connect_wifi(ok):
if ok:
return "192.168.1.42"
return None
for ok in (False, True):
ip = connect_wifi(ok)
print("離線" if ip is None else ip)
你想把專題的程式放到 GitHub 給同學參考,程式裡寫著宿舍 Wi-Fi 的密碼,最好的做法是?
重點整理
wlan = network.WLAN(network.STA_IF)建立工作站模式、wlan.active(True)啟動;ESP32 只支援 2.4 GHz。scan()回傳 (名稱, BSSID, 頻道, RSSI, 加密, 隱藏) 的串列;RSSI 越接近 0 訊號越強。connect()送出要求就返回,要用isconnected()等待,而且一定要有逾時。status()和network.STAT_…常數比較,判斷是連線中、密碼錯誤或找不到基地台;放棄時disconnect()。ifconfig()回傳 IP 位址、子網路遮罩、閘道、DNS;192.168.x.x 是只在區域網路內有效的私有位址。- 密碼不要寫死在要分享的程式裡。
下一課用 HTTP:從網路服務取得資料,也讓 ESP32 自己當一個小小的網頁伺服器。