上回聊到程式開發的老鳥魔咒,提到我在寫 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,連上作業環境的無線網路執行日常作業。

實踐這種想法的程式庫還不少:

有許多不但介面華麗功能還很強大,像是支援mDNS(不需註冊 DNS 伺服器,透過網路廣播方式找到 xxx.local 主機)、Capative Portal(連上 ESP 執行的 AP,不管輸入什麼網址都會導到 ESP 上的網站伺服器)、掃瞄並列出範圍內的無線基地台... 等等,對我來說這些功能太多了也太複雜。我反而想要一輕巧、簡單、安全,可中文化的版本,於是我犯了全天下程式開發人員都會犯的錯,對! 我造了自己想要的輪子。

我構想的運作流程如下:

  1. 開機後先重連上次使用的無線網路
  2. 若成功連上,就執行原本的流程,不需要執下面的步驟
  3. 若從未設定過或原本設的 SSID 與密碼失效,則進入 WiFi 設定介面模式
  4. WiFi 設定介面很簡單,一個 SSID、一個密碼欄位(一定要 type="password" 呀! 不少程式庫用明碼欄,位再次踩到我的紅線,也是讓我想自己寫旳原因之一)、一個儲存設定鈕,一個重新啟動鈕
  5. 輸入 SSID、密碼後按【儲存設定】,ESP 會嘗試連線該 AP 取得 IP 並顯示出來,如果 IP 是透過 DHCP 指派,稍後才知道該用什麼 IP 連上 ESP
  6. 確試 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.hGuineapig.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。

Post a comment


82 + 4 =