IoT 筆記 - 多想十幾個小時(Orz),你可以不要寫死 WiFi 密碼
22 |
上回聊到程式開發的老鳥魔咒,提到我在寫 ESP32/ESP8266 程式時有個心魔無法克服 - 幾乎所有 Arduino 範例都把 WiFi 基地台的 SSID 跟密碼寫死在程式裡,讓我頻頻呼喊花惹發?把密碼用明碼寫進程式有安全疑慮並有改密碼要重新編譯的後遺症,嚴重違背了我的信仰。如果是 C#,我用左手花十分鐘就能寫出安全又好維護的做法,但這是他 X 的 C++ 啊! 我像沒了鎚子的雷神索爾,體驗如何當一個心很大想很多但能力超鳥的平凡人。
在 ESP 無線網路設定,其實已有一些程式庫想解決在程式寫死網路設定的問題,做法多半是在執行階段提供 Web 介面輸入 SSID 及密碼,這很接近我心中理想的做法。其運作原理是善用 ESP8266/ESP32 能在 AP 模式與 STA(Station) 模式間切換的能力,AP 模式是指由 ESP 模組擔任無線基地台,連上它可形成封閉的區域網路;STA 模式則是 ESP 用 SSID 及密碼連上其他基地台取得 IP,之後便可上網,也能跑伺服器對區域網路提供服務。(延伸閱讀:How to Set an ESP32 Access Point (AP) for Web Server)
因此,當 ESP 未設定 SSID 或 SSID/密碼有誤無法連上其他無線基地台時,自動切換成 AP 模式並啟動一個小網站,我們從手機或筆電的無線 AP 清單找到並連上它,就能登入設定 WiFi SSID 跟密碼的小網站,設好 SSID 及密碼,ESP 再切換 STA 模式,連上第三方 AP 取得 IP,連上作業環境的無線網路執行日常作業。
實踐這種想法的程式庫還不少:
- WiFiManager by tzapu
- WiFiManager by Ken Taylor (ESP8266 only)
- ESPAsyncWiFiManager by alanswx
- ESP_WiFiManager by Khoi Hoang
- ESPAsync_WiFiManager by Khoi Hoang
有許多不但介面華麗功能還很強大,像是支援mDNS(不需註冊 DNS 伺服器,透過網路廣播方式找到 xxx.local 主機)、Capative Portal(連上 ESP 執行的 AP,不管輸入什麼網址都會導到 ESP 上的網站伺服器)、掃瞄並列出範圍內的無線基地台... 等等,對我來說這些功能太多了也太複雜。我反而想要一輕巧、簡單、安全,可中文化的版本,於是我犯了全天下程式開發人員都會犯的錯,對! 我造了自己想要的輪子。
我構想的運作流程如下:
- 開機後先重連上次使用的無線網路
- 若成功連上,就執行原本的流程,不需要執下面的步驟
- 若從未設定過或原本設的 SSID 與密碼失效,則進入 WiFi 設定介面模式
- WiFi 設定介面很簡單,一個 SSID、一個密碼欄位(一定要 type="password" 呀! 不少程式庫用明碼欄,位再次踩到我的紅線,也是讓我想自己寫旳原因之一)、一個儲存設定鈕,一個重新啟動鈕
- 輸入 SSID、密碼後按【儲存設定】,ESP 會嘗試連線該 AP 取得 IP 並顯示出來,如果 IP 是透過 DHCP 指派,稍後才知道該用什麼 IP 連上 ESP
- 確試 SSID 與密碼可用後,按下【重新啟動】讓 ESP 重啟,回到步驟 1,這次 ESP 會連上基地台取得 IP 進入正常流程
在開發過程中,我再次摔進「想套用 C# 設計技巧讓架構好擴充易維護,但對映到 C++ 不知如何下手」的深坑,大半天爬不出來,連續幾個週末花了超過十個小時查資料,修修改改,終於寫出一個自己能接受的版本。
我用了物件導向技巧,寫了一個 GuineapigWiFiConfig 類別,提供全域物件 - WiFiConfig。主程式引用 Guineapig.WiFiConfig.h,在 setup() 時呼叫 WiFiConfig.connectWiFi(),它會用上次設定的 SSID 跟密碼試連無線網路,若成功取得 IP 傳回 true,繼續正常流程;若無線網路設定無效,則進入 AP 模式,啟動一個 WiFi 設定網站讓使用使用者設定 SSID、密碼。程式範例如下:
#include <Arduino.h>
#include "Guineapig.WiFiConfig.h"
#pragma region Async Web Server ***************************
#include <ESPAsyncWebServer.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
AsyncWebServer webServer(80);
#pragma endregion *****************************************
bool resetWifiFlag = false;
void setup()
{
Serial.begin(115200);
//嘗試連上無線網路,若成功傳回 true 繼續作業;若失敗則啟用 AP 模式讓使用者連上來設定網路
if (WiFiConfig.connectWiFi())
{
pinMode(LED_BUILTIN, OUTPUT);
//建立一個小網站讓使用者開關LED燈及清除網路設定(方便實驗觀察用,一般應用時不太需要)
webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", R"===(
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
a { /** 略 **/ }
</style>
</head>
<body>
<a href='/led/on' target=hiddenFrame>LED ON</a>
<a href='/led/off' target=hiddenFrame>LED OFF</a>
<a href='/reset-wifi'>RESET WIFI</a>
<iframe name=hiddenFrame style='display:none'></iframe>
</body>
</html>
)===");
});
//點燈
webServer.on("/led/on", HTTP_GET, [](AsyncWebServerRequest *request) {
digitalWrite(LED_BUILTIN, LOW);
request->send(200, "text/plain", "OK");
});
//關燈
webServer.on("/led/off", HTTP_GET, [](AsyncWebServerRequest *request) {
digitalWrite(LED_BUILTIN, HIGH);
request->send(200, "text/plain", "OK");
});
//重設網路
webServer.on("/reset-wifi", HTTP_GET, [](AsyncWebServerRequest *request) {
resetWifiFlag = true;
request->send(200, "text/plain", "Reset and rebooting...");
});
webServer.begin();
}
}
void loop()
{
if (resetWifiFlag)
{
delay(3000);
WiFiConfig.clearWiFiConfig();
}
delay(500);
}
第一次使用或上次設的 SSID 或密碼失效時,ESP 會進入 AP 模式,由 Serial Monitor 可以看到進入 AP 模式訊息、AP 名稱及 IP:
無網基地台清單也可以看到名為 ESP-xxxx 的基地台:
連上 ESP-xxxx AP,瀏覽器開啟 192.168.4.1 可看到 WiFi 設定頁面:
操作流程如下,設好 SSID、密碼,儲存設定後按重新啟動,ESP 會重開機,之後瀏覽器改切回正常無線網路就能連上 ESP 服務器
偵錯訊息預設只輸出到 Serial,而我上週為 SSD1306 OLED 加入的自動捲動文字顯示功能派上用場,WiFiConfig.logCallback 有個 logCallback 可以指定自訂偵錯誤訊輸出函式,我將偵錯誤訊息用 ttyPrintln() 輸出到 OLED:
void printOled(String msg)
{
OLED.ttyPrint(msg);
OLED.display();
}
void setup()
{
//...略...
//將偵測訊息輸出導一份到 OLED
WiFiConfig.logCallback = printOled;
}
薑! 薑! 薑! 講~~~ 比用 Serial Monitor 方便多了。
程式碼還在早期發展階段,可能會有大幅修改,我打算等成熟點再放上 Github。如果有人現在想當白老鼠或想參考程式寫法,我先放了一份在 Gist - Guineapig.WiFiConfig.h、Guineapig.WiFiConfig.cpp。
經過幾個星期的掙扎,終於擠出一些自己覺得還行的東西,這也讓重當菜鳥的我復習了程式開發基本技巧,像是別一口氣寫一大段程式期待它會動,一旦出問題會不知從何查起,甚至查錯方向白花時間。要用 Bottom-Up 方式,先確認基本環節沒問題,再往上疊加把系統搭起來。這些是我在教新同學時常耳提面命的基本技巧,但自己仗著對 C#/.NET/JavaScript 熟,則習慣一次寫完開測,反正有問題很快能抓出來;來到陌生環境,我卻把這種老司機專屬的水溝蓋跑法給帶過來,難怪要摔到鼻青臉腫。老鳥當久了,偶爾受受這種刺激也好,也算另一種「跨出舒適圈」吧!
Trying to implement my version of self WiFi setting web UI on ESP8266/ESP32 to prevent from storing SSID and password in code.
Comments
# by J.C.
您好,編譯的過程一直欠缺檔案與發生錯誤,可以分享完整的程式碼嗎? 感謝。
# by J.C.
ESP8266
# by Jeffrey
to J.C., OK,待我整理一下,近期再放上 Github。
# by Nelson
這個可以透過 SmartConfig跟WiFiMulti搞定,SmartConfig是神器!
# by Jeffrey
to Nelson, 感謝分享,又學到新知了。
# by 胖子
最近也是苦惱跟您相同的問題,只是我沒能力解決,剛好瀏覽到您的文章,請問大師,您放在GitHub的完整連結在哪裡?可否分享?我目前只找到您網頁分享的Guineapig.WiFiConfig.h、Guineapig.WiFiConfig.cpp 這兩個檔案
# by Jeffrey
to 胖子,過幾天我會整理一個較乾淨(無 OLED 顯示,純 Serial 輸出偵錯)的可執行範例,再上傳 github,屆時會再發文說明。
# by Jeffrey
to 胖子,範例程式來了 https://blog.darkthread.net/blog/esp-wifi-conf-demo/
# by shaman
esp_wifi_get_config() 的 return 值能拿來判別 ssid/password 已經儲存在 nvs 嗎?我發現 ESP_OK,而隨後它也確實連接上 ssid,但是在當下輸出 wifi_config_t 的 sta.ssid 部分,卻發現其內容是空字串。
# by Jeffrey
to shaman, 有人回報類似問題 https://github.com/espressif/arduino-esp32/issues/1743 疑似 IDF Bug,討論串有 Workaround,可參考看看。
# by J
想請問html有被R"===()==="包起來 請問這段是什麼意思 謝謝
# by Jeffrey
to J, 這是 C 的一種字串表示法,術語是 Raw String Literal https://www.geeksforgeeks.org/raw-string-literal-c/,兩個括號中間可以包含 "、換行等字元不用寫成 \",\n。
# by EvenChen
ESP8266(nodemcuv2,實際開發板是NodeMCU Lua WIFI V3)SSID跟PSK似乎都是空白無法儲存,一直跳192.168.4.1,ESP32(esp32doit-devkit-v1)則沒問題。 另外,AP模式SSID應該是你重新算出來的(沒有全大寫、沒補0),其實不用這麼麻煩,預設即可: WiFi.softAP(WiFi.softAPSSID().c_str()); 理論上給NULL應該也可以: WiFi.softAP(NULL); 但有些裝置會看得到SSID但無法正常連線,也許是WiFi晶片太舊?可以參考看看
# by Lulu
請問連上 ESP後要如何自動跳出 192.168.4.1的網頁?
# by Jeffrey
to Lulu, 指連上後不管輸入什麼都連到設定畫面嗎?這種做法的術語叫 Captive Portal,你可以用關鍵字查到相關程式庫
# by Lulu
Hi Jeffrey, 是的我指的是Captive Portal 有辦法加入至您寫的Guineapig.WiFiConfig程式庫嗎?
# by Jeffrey
to Lulu,引用另一篇文章提過的心路歷程:https://blog.darkthread.net/blog/esp-wifi-conf-demo/ 在決定自己動手寫之前,我試用過五種程式庫,其中不乏介面華麗功能還很強大的,像是支援mDNS(不需註冊 DNS 伺服器,透過網路廣播方式找到 xxx.local 主機)、Captive Portal(連上 ESP 執行的 AP,不管輸入什麼網址都會導到 ESP 上的網站伺服器)、掃瞄並列出範圍內的無線基地台... 等等,但對我來說功能太多也太複雜。我只想要一個輕巧、簡單、安全,可中文化的版本,夠用就好。 程式庫旨在追求簡單輕巧,結果又包進一堆複雜機制違反初衷,若 Captive Portal 是你的主要需求,引用對映的程式庫應是較有效率的做法。
# by Kyle
最近剛跑來玩Arduino 也是遇到這個有一些類似的問題。 因為之後燒好的ESP可能不會再與電腦連線,甚至都封裝了。 感覺是不是可以透過這個方式架設一個ESP的AP主機,之後封裝的ESP都預設為這個路徑。 這樣未來不管是更改SSID 還是 密碼應該都可以透過這個AP來作設定或是更新。
# by Makodo
Dear 版主: 是否可以當您的小白鼠, 參考您這個專案的所有檔案嗎?
# by Jeffrey
to Makodo, 我有包成程式庫了也有應用範例,可以參考這篇:https://blog.darkthread.net/blog/esp-wifi-conf-demo/
# by spock
Dear 版大, 謝謝您的分享, 我也測試成功了, 都能正常運作. 好奇想向您請教有關程式的部份, 我們設定了新的SSID和密碼, 成功之後, 這個ID和PD的資料是被存在哪裡呢? 為什麼可以一直被保存著?
# by Jeffrey
to spock, Arduino 或 ESP 晶片中有 EEPROM 可以儲存資料,空間不大,但重開機也不會消失。