QQ技術互動交流群:ESP8266&32 物聯網開發 群號622368884,不喜勿噴
1.前言
廢話少說,本篇博文的目的就是深入學習 WifiManager 這個github上非常火爆的ESP8266 web配網庫,讓初學者不再對web配網感到迷茫,並且通過學習第三方庫來自定義自己的web配網功能。等你學習整篇博文,你會發現 So easy!!
2.WiFiManager
2.1 WiFiManager源碼地址
- 直接點擊 github 下載代碼
2.2 WiFiManager是什么
一句話概括:
- ESP8266 WiFi連接管理器,主要是提供Web頁面配置功能,包括Web配網、自定義參數配置;
2.3 安裝WiFiManager 庫
第一步:
- 這個庫是跟ESP8266 Arduino核心庫一起使用,所以用戶需要配置好Arduino For ESP8266平台,具體請參考 ESP8266開發之旅 基礎篇② 如何安裝ESP8266的Arduino開發環境
第二步:
- 可以通過Arduino IDE庫管理器安裝WiFiManager,搜索“WiFiManager”,然后點擊下載(適合新手小白,直接上手使用)
- 也可以直接通過github下載源碼,放到你的Arduino libraries文件夾(深入學習者,可以查看源碼然后改造源碼,達到自定義配網功能)
2.4 WiFiManager工作原理
- 1、當你的ESP8266上電啟動時,它首先進入STA模式,然后嘗試去連接之前已經保存過的AP熱點(如果你不了解STA和AP的區別,麻煩參考 ESP8266開發之旅 網絡篇③ Soft-AP——ESP8266WiFiAP庫的使用 和 ESP8266開發之旅 網絡篇④ Station——ESP8266WiFiSTA庫的使用);
- 2、如果連接失敗(可能是因為沒有保存上一次的熱點信息),那么該庫就會讓ESP進入AP模式並且提供AP熱點,啟動並監聽DNS服務以及Web服務(默認ip地址是192.168.4.1)(如果你不了解 DNS服務 和 Web服務的概念,麻煩參考 ESP8266開發之旅 網絡篇⑮ DNSServer——真正的域名服務 和 ESP8266開發之旅 網絡篇⑪ WebServer——ESP8266WebServer庫的使用)
- 3、使用任何可以連接WiFi並且可以使用瀏覽器的設備(包括電腦、手機、平板)去連接ESP生成的AP熱點
- 4、由於 DNS服務 Captive Portal功能的存在,會導致任何的網絡請求都重定向到 認證配置頁面(也就是我們的Web配置頁面)
- 5、選擇任意一個掃描到的AP熱點,輸入熱點密碼,點擊保存
- 6、ESP會嘗試去連接新AP熱點,如果連接成功,就返回到我們的正常業務,連接失敗,就會再次重復上面的操作
2.5 如何引入WiFiManager庫
非常簡單,直接引入:
#include <WiFiManager.h>
至於怎么樣去徹底了解這個庫的使用方法,請看博主下面的源碼分析。
3.WifiManager源碼解析
通過查閱WifiManager的源碼,整體代碼非常簡單,提供出來的方法調用也不多。這里博主就一個個方法詳細講解,並且深入去分析代碼,以便后面自定義自己的WifiManager。主要分為幾類方法:
- 配置類方法
- 連接類方法
博主建議:先了解有什么方法,然后在后面例子講解中去感受方法的使用。
3.1 配置類方法
主要包括:
- setConfigPortalTimeout
- setTimeout
- setConnectTimeout
- setDebugOutput
- setMinimumSignalQuality
- setAPStaticIPConfig
- setSTAStaticIPConfig
- setAPCallback
- setSaveConfigCallback
- addParameter
- setBreakAfterConfig
- setCustomHeadElement
- setRemoveDuplicateAPs
- resetSettings
- getConfigPortalSSID
3.1.1 setConfigPortalTimeout/setTimeout —— 配置認證超時
函數說明:
/**
* 功能描述:配置認證超時
* @param seconds 秒數為單位
*/
void WiFiManager::setTimeout(unsigned long seconds) {
setConfigPortalTimeout(seconds);
}
/**
* 功能描述:配置認證超時
* @param seconds 秒數為單位
*/
void WiFiManager::setConfigPortalTimeout(unsigned long seconds) {
_configPortalTimeout = seconds * 1000;
}
注意點:
- 配置界面超時,指的是用戶進入配置界面后多少秒之內如果沒有處理完畢就退出整個配置過程;
- 默認情況下,_configPortalTimeout 等於0,也就是不限制時間;
3.1.2 setConnectTimeout —— 設置sta連接超時時間
函數說明:
/**
* 功能描述:設置sta連接超時,超過時間就返回連接狀態
* @param seconds 秒數為單位
*/
void WiFiManager::setConnectTimeout(unsigned long seconds) {
_connectTimeout = seconds * 1000;
}
注意點:
- 正常情況下,_connectTimeout等於0,也就是直接等待連接狀態。
3.1.3 setDebugOutput —— 設置是否打開debug模式
函數說明:
/**
* 功能描述:設置是否打開debug
* @param debug true or false
*/
void WiFiManager::setDebugOutput(boolean debug) {
_debug = debug;
}
注意點:
- 建議調試階段把該功能打開,可以清晰知道整個運行流程。等待調試完畢,關閉調試功能;
3.1.4 setAPStaticIPConfig —— 設置固定AP,包括ip、網關、子網掩碼
函數說明:
/**
* 功能描述:設置固定AP,包括ip、網關、子網掩碼
* @param ip ip地址
* @param gw 網關地址
* @param sn 子網掩碼
*/
void WiFiManager::setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn) {
_ap_static_ip = ip;
_ap_static_gw = gw;
_ap_static_sn = sn;
}
注意點:
- 此方法用於設置ESP在AP模式下固定的AP信息,不設置默認為192.168.4.1
3.1.5 setSTAStaticIPConfig —— 設置固定STA,包括ip、網關、子網掩碼
函數說明:
/**
* 功能描述:設置固定STA,包括ip、網關、子網掩碼
* @param ip ip地址
* @param gw 網關地址
* @param sn 子網掩碼
*/
void WiFiManager::setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn) {
_sta_static_ip = ip;
_sta_static_gw = gw;
_sta_static_sn = sn;
}
注意點:
- 此方法用於設置ESP在STA模式下固定的STA信息,不設置默認由DHCP分配;
3.1.6 setMinimumSignalQuality —— 設置最小的信號質量
函數說明:
/**
* 功能描述:設置能夠接受的最低信號強度
* @param quality 信號強度,默認為8
*/
void WiFiManager::setMinimumSignalQuality(int quality) {
_minimumQuality = quality;
}
注意點:
- 此方法用來過濾信號強度低於quality的AP熱點,用於scan掃描時過濾;
3.1.7 setBreakAfterConfig —— 設置WEB配置失敗后是否退出配置
函數說明:
/**
* 功能描述:設置WEB配置失敗后是否退出配置
* @param shouldBreak 是否退出配置,默認是false
*/
void WiFiManager::setBreakAfterConfig(boolean shouldBreak) {
_shouldBreakAfterConfig = shouldBreak;
}
3.1.8 setAPCallback —— 設置開啟AP模式配置時的通知回調
函數說明:
/**
* 功能描述:設置開啟AP模式配置時的通知回調
* @param func 回調函數
*/
void WiFiManager::setAPCallback( void (*func)(WiFiManager* myWiFiManager) ) {
_apcallback = func;
}
注意點:
- 此方法主要是告知我們已經進入了配置模式了;
3.1.9 setSaveConfigCallback —— 設置保存Web配置后的回調
函數說明:
//start up save config callback
/**
* 功能描述:設置保存Web配置后的回調
* @param func 回調函數
*/
void WiFiManager::setSaveConfigCallback( void (*func)(void) ) {
_savecallback = func;
}
注意點:
- 此方法主要是告知我們已經點擊保存配置信息了;
3.1.10 addParameter —— 配置頁面添加自定義參數
函數說明:
/**
* 功能描述:配置頁面添加自定義參數
* @param WiFiManagerParameter 自定義參數
*/
bool WiFiManager::addParameter(WiFiManagerParameter *p) {
if(_paramsCount + 1 > _max_params)
{
// rezise the params array
_max_params += WIFI_MANAGER_MAX_PARAMS;
DEBUG_WM(F("Increasing _max_params to:"));
DEBUG_WM(_max_params);
WiFiManagerParameter** new_params = (WiFiManagerParameter**)realloc(_params, _max_params * sizeof(WiFiManagerParameter*));
if (new_params != NULL) {
_params = new_params;
} else {
DEBUG_WM(F("ERROR: failed to realloc params, size not increased!"));
return false;
}
}
_params[_paramsCount] = p;
_paramsCount++;
DEBUG_WM(F("Adding parameter"));
DEBUG_WM(p->getID());
return true;
}
注意點:
- 普通情況下我們一般只配置SSID和Password,如果你還需要添加額外的參數(比如MQTT IP和port等等),那么這個方法就比較有用;
WiFiManagerParameter:
class WiFiManagerParameter {
public:
/**
Create custom parameters that can be added to the WiFiManager setup web page
@id is used for HTTP queries and must not contain spaces nor other special characters
*/
WiFiManagerParameter(const char *custom);
WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length);
WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom);
~WiFiManagerParameter();
// 這里比較需要注意的是 id值,這個會在html submit的時候返回給webserver,一定不能包含空格或者其他特殊字符
const char *getID();
const char *getValue();
const char *getPlaceholder();
int getValueLength();
const char *getCustomHTML();
private:
const char *_id;
const char *_placeholder;
char *_value;
int _length;
const char *_customHTML;
void init(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom);
friend class WiFiManager;
};
3.1.11 setCustomHeadElement——設置自定義頁面的頭部元素
函數說明:
/**
* 功能描述:添加一個自定義的element到頁面頭部 比如加個頭像
* @param element element樣式的字符串
*/
void WiFiManager::setCustomHeadElement(const char* element) {
_customHeadElement = element;
}
注意點:
- 如果你覺得官方提供的界面樣式看起來不爽,那么你可以試試這個方法,試圖去改變點樣式;
3.1.12 setRemoveDuplicateAPs——設置是否過濾重復的AP熱點
函數說明:
//if this is true, remove duplicated Access Points - defaut true
/**
* 功能描述:設置是否過濾重復的AP熱點,默認過濾
* @param removeDuplicates true or false,default true
*/
void WiFiManager::setRemoveDuplicateAPs(boolean removeDuplicates) {
_removeDuplicateAPs = removeDuplicates;
}
3.1.13 resetSettings——重置配置
函數說明:
/**
* 功能描述:重置配置
* @param seconds 秒數為單位
*/
void WiFiManager::resetSettings() {
DEBUG_WM(F("settings invalidated"));
DEBUG_WM(F("THIS MAY CAUSE AP NOT TO START UP PROPERLY. YOU NEED TO COMMENT IT OUT AFTER ERASING THE DATA."));
// 斷開wifi連接,設置當前配置SSID和pwd為null 設置為true,那么就會關閉Station模式
WiFi.disconnect(true);
//delay(200);
}
3.1.14 getConfigPortalSSID——獲取WEB配置的SSID
函數說明:
/**
* 功能描述:獲取AP配置的SSID
*/
String WiFiManager::getConfigPortalSSID() {
return _apName;
}
3.2 連接類方法
所謂連接類方法,也就是跟wifi連接過程中相關的方法。此類方法包括:
- autoConnect
- startConfigPortal
3.2.1 autoConnect —— 自動連接到上一次保存的AP熱點,如果連接失敗,進入AP配網模式
函數說明:
/**
* 功能說明:自動連接到上一次保存的AP熱點,如果連接失敗,進入AP配網模式
*/
boolean WiFiManager::autoConnect() {
// 默認的AP ssid
String ssid = "ESP" + String(ESP.getChipId());
return autoConnect(ssid.c_str(), NULL);
}
/**
* 功能說明:自動連接到上一次保存的AP熱點,如果連接失敗,進入AP配網模式
* @param apName ap模式下的名字
* @param apPassword ap模式下的密碼
*/
boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) {
DEBUG_WM(F(""));
DEBUG_WM(F("AutoConnect"));
// read eeprom for ssid and pass 這里可以從eeprom獲取ssid和password
//String ssid = getSSID();
//String pass = getPassword();
// attempt to connect; should it fail, fall back to AP
WiFi.mode(WIFI_STA);
if (connectWifi("", "") == WL_CONNECTED) {
DEBUG_WM(F("IP Address:"));
DEBUG_WM(WiFi.localIP());
//connected
return true;
}
// 如果連接失敗 直接進入配網管理
return startConfigPortal(apName, apPassword);
}
注意點:
- 這兩個方法都會自動連接到上一次保存的AP熱點,連接失敗,就會進入web配網頁面(也就是 startConfigPortal 這個方法)。用戶可以自定義AP模式下的SSID和Password(也就是調用方法2)。
connectWifi方法代碼:
/**
* 功能描述:連接Ap熱點
* @param ssid 熱點名稱
* @param pass 熱點密碼
* @return 連接狀態
*/
int WiFiManager::connectWifi(String ssid, String pass) {
DEBUG_WM(F("Connecting as wifi client..."));
// check if we've got static_ip settings, if we do, use those.
// 判斷我們是否配置了sta(ip地址、網關地址、子網掩碼),有配置就設置配置內容
if (_sta_static_ip) {
DEBUG_WM(F("Custom STA IP/GW/Subnet"));
WiFi.config(_sta_static_ip, _sta_static_gw, _sta_static_sn);
DEBUG_WM(WiFi.localIP());
}
//fix for auto connect racing issue
// 如果連接成功了 直接返回
if (WiFi.status() == WL_CONNECTED && (WiFi.SSID() == ssid)) {
DEBUG_WM(F("Already connected. Bailing out."));
return WL_CONNECTED;
}
//check if we have ssid and pass and force those, if not, try with last saved values
// 判斷有沒有傳入自定義的 ssid和 pass,沒有就用上次存儲的
if (ssid != "") {
// 連接wifi
WiFi.begin(ssid.c_str(), pass.c_str());
} else {
if (WiFi.SSID() != "") {
DEBUG_WM(F("Using last saved values, should be faster"));
//trying to fix connection in progress hanging
ETS_UART_INTR_DISABLE();
wifi_station_disconnect();
ETS_UART_INTR_ENABLE();
// 連接wifi
WiFi.begin();
} else {
DEBUG_WM(F("No saved credentials"));
}
}
// 等待連接結果
int connRes = waitForConnectResult();
DEBUG_WM ("Connection result: ");
DEBUG_WM ( connRes );
//not connected, WPS enabled, no pass - first attempt
#ifdef NO_EXTRA_4K_HEAP
if (_tryWPS && connRes != WL_CONNECTED && pass == "") {
startWPS();
//should be connected at the end of WPS
connRes = waitForConnectResult();
}
#endif
return connRes;
}
/**
* 功能描述:等待連接結果
* 如果沒有設置連接超時 直接返回狀態
* 如果設置了連接超時 在規定時間內等待連接狀態
*/
uint8_t WiFiManager::waitForConnectResult() {
if (_connectTimeout == 0) {
return WiFi.waitForConnectResult();
} else {
DEBUG_WM (F("Waiting for connection result with time out"));
unsigned long start = millis();
boolean keepConnecting = true;
uint8_t status;
while (keepConnecting) {
status = WiFi.status();
// 判斷連接是否超時
if (millis() > start + _connectTimeout) {
keepConnecting = false;
DEBUG_WM (F("Connection timed out"));
}
if (status == WL_CONNECTED) {
keepConnecting = false;
}
delay(100);
}
return status;
}
}
注意點:
- DEBUG_WM debug功能的實現由 setDebugOutput 控制
- _sta_static_ip 的值 由 setSTAStaticIPConfig 設置
- waitForConnectResult 里面 的 _connectTimeout 由 setConnectTimeout 設置
3.2.2 startConfigPortal —— 啟動web配置
函數說明:
/**
* 功能說明:配置認證,默認ssid為esp+chipid
*/
boolean WiFiManager::startConfigPortal() {
String ssid = "ESP" + String(ESP.getChipId());
return startConfigPortal(ssid.c_str(), NULL);
}
/**
* 功能說明:配置認證,切換到AP模式 然后配置webserver和dnsserver,響應web請求
* @param apName ap名字
* @param apPassword ap密碼
*/
boolean WiFiManager::startConfigPortal(char const *apName, char const *apPassword) {
// 如果沒有連接上網絡 切換到AP模式
if(!WiFi.isConnected()){
WiFi.persistent(false);
// disconnect sta, start ap
WiFi.disconnect(); // this alone is not enough to stop the autoconnecter
WiFi.mode(WIFI_AP);
WiFi.persistent(true);
}
else {
//setup AP 切換到AP-STA模式
WiFi.mode(WIFI_AP_STA);
DEBUG_WM(F("SET AP STA"));
}
_apName = apName;
_apPassword = apPassword;
//notify we entered AP mode
// 通知我們進入AP模式,可以執行回調方法
if ( _apcallback != NULL) {
_apcallback(this);
}
connect = false;
// 配置信息 這是非常關鍵的一個方法,主要是配置webserver、DNSserver
setupConfigPortal();
while(1){
// check if timeout 判斷是否配置超時
if(configPortalHasTimeout()) break;
//DNS 處理dns請求
dnsServer->processNextRequest();
//HTTP 處理web請求
server->handleClient();
if (connect) {
connect = false;
delay(2000);
DEBUG_WM(F("Connecting to new AP"));
// using user-provided _ssid, _pass in place of system-stored ssid and pass
// 使用用戶提供的wifi賬號密碼來設置
if (connectWifi(_ssid, _pass) != WL_CONNECTED) {
DEBUG_WM(F("Failed to connect."));
} else {
//connected
WiFi.mode(WIFI_STA);
//notify that configuration has changed and any optional parameters should be saved
// 通知外面wifi連接成功,可以做一些額外操作
if ( _savecallback != NULL) {
//todo: check if any custom parameters actually exist, and check if they really changed maybe
_savecallback();
}
break;
}
// 判斷配置后是否斷開
if (_shouldBreakAfterConfig) {
//flag set to exit after config after trying to connect
//notify that configuration has changed and any optional parameters should be saved
if ( _savecallback != NULL) {
//todo: check if any custom parameters actually exist, and check if they really changed maybe
_savecallback();
}
break;
}
}
yield();
}
server.reset();
dnsServer.reset();
return WiFi.status() == WL_CONNECTED;
}
注意點:
- configPortalHasTimeout 用來判斷是否在認證界面配置超時
- _apcallback 通過 setAPCallback 設置
- _savecallback 通過 setSaveConfigCallback 設置,主要用於配置完畢后保存一些信息;
- _shouldBreakAfterConfig 通過 setBreakAfterConfig 設置,主要用來判斷配置失敗后是否保存信息退出;
關鍵方法 setupConfigPortal(一定要看博主在源碼中的注釋):
/**
* 功能描述:配置web服務以及dns重定向功能
*/
void WiFiManager::setupConfigPortal() {
// 配置dns服務(這里主要是用到重定向功能)
dnsServer.reset(new DNSServer());
// 配置web服務 主要用來顯示web頁面 實現AP配網之類的功能
server.reset(new ESP8266WebServer(80));
DEBUG_WM(F(""));
_configPortalStart = millis();
DEBUG_WM(F("Configuring access point... "));
DEBUG_WM(_apName);
if (_apPassword != NULL) {
if (strlen(_apPassword) < 8 || strlen(_apPassword) > 63) {
// fail passphrase to short or long!
DEBUG_WM(F("Invalid AccessPoint password. Ignoring"));
_apPassword = NULL;
}
DEBUG_WM(_apPassword);
}
//optional soft ip config
// 配置自定義固定AP信息
if (_ap_static_ip) {
DEBUG_WM(F("Custom AP IP/GW/Subnet"));
WiFi.softAPConfig(_ap_static_ip, _ap_static_gw, _ap_static_sn);
}
// 配置AP熱點
if (_apPassword != NULL) {
WiFi.softAP(_apName, _apPassword);//password option
} else {
WiFi.softAP(_apName);
}
delay(500); // Without delay I've seen the IP address blank
DEBUG_WM(F("AP IP address: "));
DEBUG_WM(WiFi.softAPIP());
/* Setup the DNS server redirecting all the domains to the apIP */
// 配置DNS 重定向功能 全部請求都重定向到AP
dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
dnsServer->start(DNS_PORT, "*", WiFi.softAPIP());
/* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */
// 配置Web服務器的功能 這里就是實現AP配網功能
server->on(String(F("/")).c_str(), std::bind(&WiFiManager::handleRoot, this));
server->on(String(F("/wifi")).c_str(), std::bind(&WiFiManager::handleWifi, this, true));
server->on(String(F("/0wifi")).c_str(), std::bind(&WiFiManager::handleWifi, this, false));
server->on(String(F("/wifisave")).c_str(), std::bind(&WiFiManager::handleWifiSave, this));
server->on(String(F("/i")).c_str(), std::bind(&WiFiManager::handleInfo, this));
server->on(String(F("/r")).c_str(), std::bind(&WiFiManager::handleReset, this));
//server->on("/generate_204", std::bind(&WiFiManager::handle204, this)); //Android/Chrome OS captive portal check.
server->on(String(F("/fwlink")).c_str(), std::bind(&WiFiManager::handleRoot, this)); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
server->onNotFound (std::bind(&WiFiManager::handleNotFound, this));
server->begin(); // Web server start
DEBUG_WM(F("HTTP server started"));
}
注意點:
- _ap_static_ip 通過 setAPStaticIPConfig 設置,設置AP模式下的固定IP信息
- handleRoot 處理域的web請求,比如 192.168.4.1
- handleWifi 處理web配置頁面的請求
- handleWifiSave 處理wifi配置信息保存請求
- handleInfo 處理wifi信息的展示
- handleReset 處理wifi配置重置的請求
接下來,重點講解handleXXX方法的具體內容以及展示頁面(后面打造專屬自己的web配網也是從這里修改)。
3.2.2.1 handleRoot —— 根目錄頁面
函數說明:
/** Handle root or redirect to captive portal */
/**
* 功能描述:響應root請求,也就是ip請求
*/
void WiFiManager::handleRoot() {
DEBUG_WM(F("Handle root"));
if (captivePortal()) { // If caprive portal redirect instead of displaying the page.
return;
}
// 這里就會顯示一個通用頁面了
String page = FPSTR(HTTP_HEADER);
page.replace("{v}", "Options");
page += FPSTR(HTTP_SCRIPT);
page += FPSTR(HTTP_STYLE);
page += _customHeadElement;
page += FPSTR(HTTP_HEADER_END);
page += String(F("<h1>"));
page += _apName;
page += String(F("</h1>"));
page += String(F("<h3>WiFiManager</h3>"));
page += FPSTR(HTTP_PORTAL_OPTIONS);
page += FPSTR(HTTP_END);
server->sendHeader("Content-Length", String(page.length()));
server->send(200, "text/html", page);
}
注意點:
- 此方法是整個配置頁面的入口頁面,會包含后面一些列操作的入口
- 這里拼接了一個web頁面返回,包括自定義的 _customHeadElement
Root 頁面的HTML+CSS+JS代碼(這里是我們后面打造專屬自己樣式的關鍵):
const char HTTP_HEADER[] PROGMEM = "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/><title>{v}</title>";
const char HTTP_STYLE[] PROGMEM = "<style>.c{text-align: center;} div,input{padding:5px;font-size:1em;} input{width:95%;} body{text-align: center;font-family:verdana;} button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;} .q{float: right;width: 64px;text-align: right;} .l{background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6OSk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eAXvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==\") no-repeat left center;background-size: 1em;}</style>";
const char HTTP_SCRIPT[] PROGMEM = "<script>function c(l){document.getElementById('s').value=l.innerText||l.textContent;document.getElementById('p').focus();}</script>";
const char HTTP_HEADER_END[] PROGMEM = "</head><body><div style='text-align:left;display:inline-block;min-width:260px;'>";
const char HTTP_PORTAL_OPTIONS[] PROGMEM = "<form action=\"/wifi\" method=\"get\"><button>Configure WiFi</button></form><br/><form action=\"/0wifi\" method=\"get\"><button>Configure WiFi (No Scan)</button></form><br/><form action=\"/i\" method=\"get\"><button>Info</button></form><br/><form action=\"/r\" method=\"post\"><button>Reset</button></form>";
const char HTTP_ITEM[] PROGMEM = "<div><a href='#p' onclick='c(this)'>{v}</a> <span class='q {i}'>{r}%</span></div>";
const char HTTP_FORM_START[] PROGMEM = "<form method='get' action='wifisave'><input id='s' name='s' length=32 placeholder='SSID'><br/><input id='p' name='p' length=64 type='password' placeholder='password'><br/>";
const char HTTP_FORM_PARAM[] PROGMEM = "<br/><input id='{i}' name='{n}' maxlength={l} placeholder='{p}' value='{v}' {c}>";
const char HTTP_FORM_END[] PROGMEM = "<br/><button type='submit'>save</button></form>";
const char HTTP_SCAN_LINK[] PROGMEM = "<br/><div class=\"c\"><a href=\"/wifi\">Scan</a></div>";
const char HTTP_SAVED[] PROGMEM = "<div>Credentials Saved<br />Trying to connect ESP to network.<br />If it fails reconnect to AP to try again</div>";
const char HTTP_END[] PROGMEM = "</div></body></html>";
Root頁面效果:
這里包含了多個功能入口
- Configure WiFi、Configure WiFi(No Scan)—— handleWifi
- Info —— handleInfo
- Reset —— handleReset
3.2.2.2 handleWifi —— 配置Wifi信息
用戶點擊 Configure WiFi、Configure WiFi(No Scan) 就會響應此方法。
函數說明:
/** Wifi config page handler */
/**
* 功能描述:wifi配置頁面
* @param scan true or false,是否開啟掃描
*/
void WiFiManager::handleWifi(boolean scan) {
String page = FPSTR(HTTP_HEADER);
page.replace("{v}", "Config ESP");
page += FPSTR(HTTP_SCRIPT);
page += FPSTR(HTTP_STYLE);
page += _customHeadElement;
page += FPSTR(HTTP_HEADER_END);
// 判斷是否掃描
if (scan) {
// 獲取掃描結果
int n = WiFi.scanNetworks();
DEBUG_WM(F("Scan done"));
if (n == 0) {
DEBUG_WM(F("No networks found"));
page += F("No networks found. Refresh to scan again.");
} else {
//sort networks
int indices[n];
for (int i = 0; i < n; i++) {
indices[i] = i;
}
// RSSI SORT
// old sort 排序信號強度,高的放在前面
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) {
std::swap(indices[i], indices[j]);
}
}
}
/*std::sort(indices, indices + n, [](const int & a, const int & b) -> bool
{
return WiFi.RSSI(a) > WiFi.RSSI(b);
});*/
// remove duplicates ( must be RSSI sorted )
// 是否去掉重復的AP
if (_removeDuplicateAPs) {
String cssid;
for (int i = 0; i < n; i++) {
if (indices[i] == -1) continue;
cssid = WiFi.SSID(indices[i]);
for (int j = i + 1; j < n; j++) {
if (cssid == WiFi.SSID(indices[j])) {
DEBUG_WM("DUP AP: " + WiFi.SSID(indices[j]));
indices[j] = -1; // set dup aps to index -1
}
}
}
}
//display networks in page
// 拼接每一個需要顯示的ap熱點信息
for (int i = 0; i < n; i++) {
if (indices[i] == -1) continue; // skip dups
DEBUG_WM(WiFi.SSID(indices[i]));
DEBUG_WM(WiFi.RSSI(indices[i]));
int quality = getRSSIasQuality(WiFi.RSSI(indices[i]));
// 判斷信號強度是否大於我們設置的最小強度
if (_minimumQuality == -1 || _minimumQuality < quality) {
String item = FPSTR(HTTP_ITEM);
String rssiQ;
rssiQ += quality;
item.replace("{v}", WiFi.SSID(indices[i]));
item.replace("{r}", rssiQ);
if (WiFi.encryptionType(indices[i]) != ENC_TYPE_NONE) {
item.replace("{i}", "l");
} else {
item.replace("{i}", "");
}
//DEBUG_WM(item);
page += item;
delay(0);
} else {
DEBUG_WM(F("Skipping due to quality"));
}
}
page += "<br/>";
}
}
page += FPSTR(HTTP_FORM_START);
char parLength[5];
// add the extra parameters to the form
// 添加我們自定義的參數
for (int i = 0; i < _paramsCount; i++) {
if (_params[i] == NULL) {
break;
}
String pitem = FPSTR(HTTP_FORM_PARAM);
if (_params[i]->getID() != NULL) {
pitem.replace("{i}", _params[i]->getID());
pitem.replace("{n}", _params[i]->getID());
pitem.replace("{p}", _params[i]->getPlaceholder());
snprintf(parLength, 5, "%d", _params[i]->getValueLength());
pitem.replace("{l}", parLength);
pitem.replace("{v}", _params[i]->getValue());
pitem.replace("{c}", _params[i]->getCustomHTML());
} else {
pitem = _params[i]->getCustomHTML();
}
page += pitem;
}
if (_params[0] != NULL) {
page += "<br/>";
}
if (_sta_static_ip) {
String item = FPSTR(HTTP_FORM_PARAM);
item.replace("{i}", "ip");
item.replace("{n}", "ip");
item.replace("{p}", "Static IP");
item.replace("{l}", "15");
item.replace("{v}", _sta_static_ip.toString());
page += item;
item = FPSTR(HTTP_FORM_PARAM);
item.replace("{i}", "gw");
item.replace("{n}", "gw");
item.replace("{p}", "Static Gateway");
item.replace("{l}", "15");
item.replace("{v}", _sta_static_gw.toString());
page += item;
item = FPSTR(HTTP_FORM_PARAM);
item.replace("{i}", "sn");
item.replace("{n}", "sn");
item.replace("{p}", "Subnet");
item.replace("{l}", "15");
item.replace("{v}", _sta_static_sn.toString());
page += item;
page += "<br/>";
}
page += FPSTR(HTTP_FORM_END);
page += FPSTR(HTTP_SCAN_LINK);
page += FPSTR(HTTP_END);
server->sendHeader("Content-Length", String(page.length()));
server->send(200, "text/html", page);
DEBUG_WM(F("Sent config page"));
}
注意點:
- _minimumQuality 由 setMinimumSignalQuality 設置
配置頁面效果:
上圖是啟用了scan功能的效果。
上圖沒有啟用scan功能,通常用於隱藏SSID的配置。
關於scan功能,麻煩讀者參考 ESP8266開發之旅 網絡篇⑤ Scan WiFi——ESP8266WiFiScan庫的使用
3.2.2.3 handleWifiSave —— 保存Wifi信息
用戶在wifi配置頁面點擊 save 操作就會響應此方法。
函數說明:
/** Handle the WLAN save form and redirect to WLAN config page again */
/**
* 功能描述:保存wifi設置
*/
void WiFiManager::handleWifiSave() {
DEBUG_WM(F("WiFi save"));
//SAVE/connect here 保存用戶設置的ssid和psw
_ssid = server->arg("s").c_str();
_pass = server->arg("p").c_str();
//parameters
// 保存用戶設置的自定義參數
for (int i = 0; i < _paramsCount; i++) {
if (_params[i] == NULL) {
break;
}
//read parameter
String value = server->arg(_params[i]->getID()).c_str();
//store it in array
value.toCharArray(_params[i]->_value, _params[i]->_length + 1);
DEBUG_WM(F("Parameter"));
DEBUG_WM(_params[i]->getID());
DEBUG_WM(value);
}
if (server->arg("ip") != "") {
DEBUG_WM(F("static ip"));
DEBUG_WM(server->arg("ip"));
//_sta_static_ip.fromString(server->arg("ip"));
String ip = server->arg("ip");
optionalIPFromString(&_sta_static_ip, ip.c_str());
}
if (server->arg("gw") != "") {
DEBUG_WM(F("static gateway"));
DEBUG_WM(server->arg("gw"));
String gw = server->arg("gw");
optionalIPFromString(&_sta_static_gw, gw.c_str());
}
if (server->arg("sn") != "") {
DEBUG_WM(F("static netmask"));
DEBUG_WM(server->arg("sn"));
String sn = server->arg("sn");
optionalIPFromString(&_sta_static_sn, sn.c_str());
}
// 拼接一個 wifi 保存的頁面通知 確保響應了
String page = FPSTR(HTTP_HEADER);
page.replace("{v}", "Credentials Saved");
page += FPSTR(HTTP_SCRIPT);
page += FPSTR(HTTP_STYLE);
page += _customHeadElement;
page += FPSTR(HTTP_HEADER_END);
page += FPSTR(HTTP_SAVED);
page += FPSTR(HTTP_END);
server->sendHeader("Content-Length", String(page.length()));
server->send(200, "text/html", page);
DEBUG_WM(F("Sent wifi save page"));
connect = true; //signal ready to connect/reset
}
頁面效果:
3.2.2.4 handleInfo —— 獲取ESP的信息
用戶在wifi配置頁面點擊 Info 操作就會響應此方法。
函數說明:
/** Handle the info page */
/**
* 功能描述:展示詳情信息
*/
void WiFiManager::handleInfo() {
DEBUG_WM(F("Info"));
String page = FPSTR(HTTP_HEADER);
page.replace("{v}", "Info");
page += FPSTR(HTTP_SCRIPT);
page += FPSTR(HTTP_STYLE);
page += _customHeadElement;
page += FPSTR(HTTP_HEADER_END);
page += F("<dl>");
// 芯片id
page += F("<dt>Chip ID</dt><dd>");
page += ESP.getChipId();
page += F("</dd>");
// flash 芯片id
page += F("<dt>Flash Chip ID</dt><dd>");
page += ESP.getFlashChipId();
page += F("</dd>");
// flash大小
page += F("<dt>IDE Flash Size</dt><dd>");
page += ESP.getFlashChipSize();
page += F(" bytes</dd>");
page += F("<dt>Real Flash Size</dt><dd>");
page += ESP.getFlashChipRealSize();
page += F(" bytes</dd>");
page += F("<dt>Soft AP IP</dt><dd>");
// AP ip地址
page += WiFi.softAPIP().toString();
page += F("</dd>");
page += F("<dt>Soft AP MAC</dt><dd>");
// AP mac地址
page += WiFi.softAPmacAddress();
page += F("</dd>");
page += F("<dt>Station MAC</dt><dd>");
page += WiFi.macAddress();
page += F("</dd>");
page += F("</dl>");
page += FPSTR(HTTP_END);
server->sendHeader("Content-Length", String(page.length()));
server->send(200, "text/html", page);
DEBUG_WM(F("Sent info page"));
}
頁面效果:
3.2.2.5 handleReset —— 重置Wifi模塊信息
用戶在wifi配置頁面點擊 Reset 操作就會響應此方法。
函數說明:
/** Handle the reset page */
void WiFiManager::handleReset() {
DEBUG_WM(F("Reset"));
String page = FPSTR(HTTP_HEADER);
page.replace("{v}", "Info");
page += FPSTR(HTTP_SCRIPT);
page += FPSTR(HTTP_STYLE);
page += _customHeadElement;
page += FPSTR(HTTP_HEADER_END);
page += F("Module will reset in a few seconds.");
page += FPSTR(HTTP_END);
server->sendHeader("Content-Length", String(page.length()));
server->send(200, "text/html", page);
DEBUG_WM(F("Sent reset page"));
delay(5000);
// 重置信息
ESP.reset();
delay(2000);
}
頁面效果:
4.官方實例操作
使用WifiManager三部曲:
- 引入頭文件
#include <WiFiManager.h>
- 配置具體參數
- 啟動WifiManager服務
請讀者燒錄以下代碼:
例子源碼:
/**
* 功能:測試WifiManager功能demo
* 作者:單片機菜鳥
* 時間:2019-12-13
* 描述:
* 1.啟動wifimanager AP配置頁面,通過手機連接esp8266生成的AP熱點來進入設置頁面
* 2.請在手機瀏覽器上輸入 192.168.4.25
* 2.測試官方方法的使用
*/
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
//needed for library
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
#include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson
//for LED status
#include <Ticker.h>
void initSystem();
void initWifiManager();
void configModeCallback(WiFiManager *myWiFiManager);
void saveConfigCallback();
void tick();
//flag for saving data
bool shouldSaveConfig = false;
//for LED status
Ticker ticker;
//define your default values here, if there are different values in config.json, they are overwritten.
char mqtt_server[40];
char mqtt_port[6] = "8080";
char api_key[34] = "Your ApiKey";
void setup() {
// put your setup code here, to run once:
initSystem();
initWifiManager();
//if you get here you have connected to the WiFi
Serial.println("connected...so easy :)");
ticker.detach();
//keep LED on
digitalWrite(BUILTIN_LED, LOW);
}
void loop() {
// put your main code here, to run repeatedly:
}
/**
* 功能描述:初始化esp8266
*/
void initSystem(){
Serial.begin(115200);
Serial.println();
//set led pin as output
pinMode(BUILTIN_LED, OUTPUT);
// start ticker with 0.5 because we start in AP mode and try to connect
ticker.attach(0.6, tick);
}
/**
* 功能描述:初始化wifimanager
*/
void initWifiManager(){
/*** 步驟一:創建 wifimanager對象 **/
WiFiManager wifiManager;
/*************************************/
/*** 步驟二:進行一系列配置,參考配置類方法 **/
// 重置保存的修改 目標是為了每次進來都是去掉配置頁面
wifiManager.resetSettings();
// 配置連接超時
wifiManager.setConnectTimeout(60);
// 打印調試內容
wifiManager.setDebugOutput(true);
// 設置最小信號強度
wifiManager.setMinimumSignalQuality(30);
// 設置固定AP信息
IPAddress _ip = IPAddress(192, 168, 4, 25);
IPAddress _gw = IPAddress(192, 168, 4, 1);
IPAddress _sn = IPAddress(255, 255, 255, 0);
wifiManager.setAPStaticIPConfig(_ip, _gw, _sn);
// 設置進入AP模式的回調
wifiManager.setAPCallback(configModeCallback);
// 設置點擊保存的回調
wifiManager.setSaveConfigCallback(saveConfigCallback);
// 設置 如果配置錯誤的ssid或者密碼 退出配置模式
wifiManager.setBreakAfterConfig(true);
// 設置過濾重復的AP 默認可以不用調用 這里只是示范
wifiManager.setRemoveDuplicateAPs(true);
// 添加額外的參數 博哥這里只是示范 比如加入 mqtt 服務器地址 port 端口號 apikey 后面可以結合onenet使用
WiFiManagerParameter custom_mqtt_server("server", "mqtt server", mqtt_server, 40);
WiFiManagerParameter custom_mqtt_port("port", "mqtt port", mqtt_port, 6);
WiFiManagerParameter custom_apikey("apikey", "onenet apikey", api_key, 32);
wifiManager.addParameter(&custom_mqtt_server);
wifiManager.addParameter(&custom_mqtt_port);
wifiManager.addParameter(&custom_apikey);
/*************************************/
/*** 步驟三:嘗試連接網絡,失敗去到配置頁面 **/
// ssid 命名為danpianjicainiao pwd是123456
if (!wifiManager.autoConnect("danpianjicainiao","123456")) {
Serial.println("failed to connect and hit timeout");
//reset and try again, or maybe put it to deep sleep
ESP.reset();
delay(1000);
}
/*************************************/
// 讀取配置頁面配置好的信息
strcpy(mqtt_server, custom_mqtt_server.getValue());
strcpy(mqtt_port, custom_mqtt_port.getValue());
strcpy(api_key, custom_apikey.getValue());
// 保存自定義信息
if (shouldSaveConfig) {
Serial.println("saving config");
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.createObject();
json["mqtt_server"] = mqtt_server;
json["mqtt_port"] = mqtt_port;
json["api_key"] = api_key;
json.printTo(Serial);
//end save
}
Serial.println("local ip");
Serial.println(WiFi.localIP());
}
/**
* 功能描述:配置進入AP模式通知回調
*/
void configModeCallback (WiFiManager *myWiFiManager) {
Serial.println("Entered config mode");
Serial.println(WiFi.softAPIP());
//if you used auto generated SSID, print it
Serial.println(myWiFiManager->getConfigPortalSSID());
//entered config mode, make led toggle faster
ticker.attach(0.2, tick);
}
/**
* 功能描述:設置點擊保存的回調
*/
void saveConfigCallback () {
Serial.println("Should save config");
shouldSaveConfig = true;
}
/**
* 功能描述:設置LED燈閃爍,提示用戶進入配置模式
*/
void tick(){
//toggle state
int state = digitalRead(BUILTIN_LED); // get the current state of GPIO1 pin
digitalWrite(BUILTIN_LED, !state); // set pin to the opposite state
}
這里重點講解了各個方法的使用,並且通過led燈閃爍來表示工作模式,請讀者自行下載燒錄體會。沒有意外情況的下會有以下控制頁面(直接看博主截圖說明):
輸入密碼后,就可以連接上新wifi。
由於代碼中打開了串口調試功能,所以可以看到以下調試信息:
美中不足:
- 對於習慣於中文的初學者來說,肯定是希望可以支持中文;
- 對於愛好潮流的人來說,肯定希望打造自己的web配網頁面;
下面就是博主基於wifiManger去打造專屬自己的web配網,暫且命名為 CustomWifiManager。此庫,博主已經上傳到QQ技術交流群。
5.打造專屬自己的web配網
5.1 CustomWifiManager —— 站在巨人肩膀上
博主基於WifiManger改造了幾個方面,適合簡單的樣式修改。
5.1.1 setHeadImgBase64 —— 支持自定義ICON頭像
函數說明:
void WiFiManager::setHeadImgBase64(String imgBase64) {
_imgBase64= imgBase64;
}
5.1.2 setButtonBackground—— 自定義按鈕背景顏色
函數說明:
void WiFiManager::setButtonBackground(const char* backgroundColor){
_backgroundColor= backgroundColor;
}
5.1.3 setButtonTextColor—— 自定義按鈕字體顏色
函數說明:
void WiFiManager::setButtonTextColor(const char* textColor) {
_textColor= textColor;
}
5.1.4 setPageTitle—— 自定義頁面標題
函數說明:
void WiFiManager::setPageTitle(const char* pageTitle){
_pageTitle = pageTitle;
}
5.2 案例
例子源碼:
/**
* 功能:測試WifiManager功能demo
* 作者:單片機菜鳥
* 時間:2019-12-13
* 描述:
* 1.啟動wifimanager AP配置頁面,通過手機連接esp8266生成的AP熱點來進入設置頁面
* 2.請在手機瀏覽器上輸入 192.168.4.25
* 3.分別支持大叔黃 萌弟藍 少女粉
*/
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
//needed for library
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <CustomWiFiManager.h> //https://github.com/tzapu/WiFiManager
#include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson
//for LED status
#include <Ticker.h>
// 自定義我的icon 請隨便使用一個圖片轉base64工具轉換
const char yellowWifi[] PROGMEM = "data:image/png;base64,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yqKo0OXf6/P5CDc0cPZML3vuu5cPBibZu9OgqkwSSxbeDRacAqahj6E/9ynoGoJ/PQp1Yb2hkGr2upsFotBoZdsMwyCZlOzd+wX+7MtfgVSCcH39RVY+1NR6lxwd4Yfff4FvfuspTnaM8MDtJp9uds97lF70SCyrGz4TnvuZPhJLZvRW2W8yi1MCDU7BzoTAcRSRSC3bt2+fAi17UjTzoGTW84BSEpm3Yriuy+DAIEfb3uH4yQ+J1MB9m0y2rXNJO7kYLgoAPXrenA7CSa/Kc7wXhmKzg3WlLoDI82QhSoEn2kULJdPB04EkMzltyZplQFMj/O4qkz+4TrJisWIiqSM/H6/mXRdwJZQHwGfAyCT0DXsFTqFFrtwPr5yEF97U9YFs/aCQFZvbMy1bRoslYfctsOHaXM1QKc3QJbWSqjKF7eg6wnyPxue9FzAN3WnCOyhdvWT2CXBzVK8Wz/0UQgFd8XULsNotdt43o9+UrQ8399wKO27SZ/9TI+j5kHZ01UiIC6sLXNBu0BC6R1flqjVT5k2VPbfC0jo4+LIesYogUyyZrwlytYXxBNSGYd8fwqbVMJ4s8MwFBj713KUujkqvONo5CN/9Cfy0Q18v8+u5CrkfS8wMOrsQOG6OxreshD//NFxZT9Hl7GLtkgMAOTEEOPpLOHxcl8cnkrmRMvJGLFsadySgoDoEv3MN3L4GWpu8stcFzOuSAwC5s/hQQI9215Aup717BvpGdJYZ8wCpLNOANUXguiu0llzdoL9j0tsHzafM9WsFQD4QoIUzYGn9sB0tbCmvxlDm121+SwOS9kTvcgaetcv+G6FsAGlb0zi7nlsmVHq9S6nreGnbS7XF5Q/8VwZA1oSYfi6vFDgeO7ICeLl/EFVSAOYEpZSde/ax/6HkAgCldqDUtgBAqR0otS0AUGoHSm0LAJTagVLbAgCldqDU9n+vbbJY13ln1QAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOS0wMS0wOVQwMjo0Njo0MCswODowMELuAxIAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTktMDEtMDlUMDI6NDY6NDArMDg6MDAzs7uuAAAAQ3RFWHRzb2Z0d2FyZQAvdXNyL2xvY2FsL2ltYWdlbWFnaWNrL3NoYXJlL2RvYy9JbWFnZU1hZ2ljay03Ly9pbmRleC5odG1svbV5CgAAABh0RVh0VGh1bWI6OkRvY3VtZW50OjpQYWdlcwAxp/+7LwAAABh0RVh0VGh1bWI6OkltYWdlOjpIZWlnaHQANTEyj41TgQAAABd0RVh0VGh1bWI6OkltYWdlOjpXaWR0aAA1MTIcfAPcAAAAGXRFWHRUaHVtYjo6TWltZXR5cGUAaW1hZ2UvcG5nP7JWTgAAABd0RVh0VGh1bWI6Ok1UaW1lADE1NDY5NzMyMDBYBo4+AAAAEnRFWHRUaHVtYjo6U2l6ZQAzMjE5MUJVXU35AAAAYnRFWHRUaHVtYjo6VVJJAGZpbGU6Ly8vaG9tZS93d3dyb290L25ld3NpdGUvd3d3LmVhc3lpY29uLm5ldC9jZG4taW1nLmVhc3lpY29uLmNuL2ZpbGVzLzExMi8xMTIzMjIxLnBuZ7vHiF8AAAAASUVORK5CYII=";
const char blueWifi[] PROGMEM = "data:image/png;base64,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ZXguaHRtbL21eQoAAABjdEVYdHN2Zzpjb21tZW50ACBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIM5IkAsAAAAYdEVYdFRodW1iOjpEb2N1bWVudDo6UGFnZXMAMaf/uy8AAAAYdEVYdFRodW1iOjpJbWFnZTo6SGVpZ2h0ADQxMo5PObYAAAAXdEVYdFRodW1iOjpJbWFnZTo6V2lkdGgANTI03jL1KgAAABl0RVh0VGh1bWI6Ok1pbWV0eXBlAGltYWdlL3BuZz+yVk4AAAAXdEVYdFRodW1iOjpNVGltZQAxNTQ2OTc3NzgzgH8AMAAAABJ0RVh0VGh1bWI6OlNpemUAMjA4NDVC9X3U4QAAAGJ0RVh0VGh1bWI6OlVSSQBmaWxlOi8vL2hvbWUvd3d3cm9vdC9uZXdzaXRlL3d3dy5lYXN5aWNvbi5uZXQvY2RuLWltZy5lYXN5aWNvbi5jbi9maWxlcy8xMjAvMTIwNTg1OS5wbmctUab6AAAAAElFTkSuQmCC";
const char pinkWifi[] PROGMEM = "data:image/png;base64,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";
void initSystem();
void initWifiManager();
void configModeCallback(WiFiManager *myWiFiManager);
void saveConfigCallback();
void tick();
//flag for saving data
bool shouldSaveConfig = false;
//for LED status
Ticker ticker;
//define your default values here, if there are different values in config.json, they are overwritten.
char mqtt_server[40];
char mqtt_port[6] = "8080";
char api_key[34] = "Your ApiKey";
void setup() {
// put your setup code here, to run once:
initSystem();
initWifiManager();
//if you get here you have connected to the WiFi
Serial.println("connected...so easy :)");
ticker.detach();
//keep LED on
digitalWrite(BUILTIN_LED, LOW);
}
void loop() {
// put your main code here, to run repeatedly:
}
/**
* 功能描述:初始化esp8266
*/
void initSystem(){
Serial.begin(115200);
Serial.println();
//set led pin as output
pinMode(BUILTIN_LED, OUTPUT);
// start ticker with 0.5 because we start in AP mode and try to connect
ticker.attach(0.6, tick);
}
/**
* 功能描述:初始化wifimanager
*/
void initWifiManager(){
/*** 步驟一:創建 wifimanager對象 **/
WiFiManager wifiManager;
/*************************************/
/*** 步驟二:進行一系列配置,參考配置類方法 **/
// 重置保存的修改 目標是為了每次進來都是去掉配置頁面
wifiManager.resetSettings();
// 配置連接超時
// wifiManager.setConnectTimeout(240);
// 打印調試內容
wifiManager.setDebugOutput(true);
// 設置個人圖標
// 大叔黃
//wifiManager.setHeadImgBase64(FPSTR(yellowWifi));
//wifiManager.setButtonBackground("#E08E00");
// 萌弟藍
wifiManager.setHeadImgBase64(FPSTR(blueWifi));
wifiManager.setButtonBackground("#2394BC");
// 少女粉
//wifiManager.setHeadImgBase64(FPSTR(pinkWifi));
//wifiManager.setButtonBackground("#D5BADB");
// 設置最小信號強度
wifiManager.setMinimumSignalQuality(40);
// 設置固定AP信息
IPAddress _ip = IPAddress(192, 168, 4, 25);
IPAddress _gw = IPAddress(192, 168, 4, 1);
IPAddress _sn = IPAddress(255, 255, 255, 0);
wifiManager.setAPStaticIPConfig(_ip, _gw, _sn);
// 設置進入AP模式的回調
wifiManager.setAPCallback(configModeCallback);
// 設置點擊保存的回調
wifiManager.setSaveConfigCallback(saveConfigCallback);
// 設置 如果配置錯誤的ssid或者密碼 退出配置模式
wifiManager.setBreakAfterConfig(true);
// 設置過濾重復的AP 默認可以不用調用 這里只是示范
wifiManager.setRemoveDuplicateAPs(true);
// 添加額外的參數 博哥這里只是示范 比如加入 mqtt 服務器地址 port 端口號 apikey 后面可以結合onenet使用
WiFiManagerParameter custom_mqtt_server("server", "mqtt server", mqtt_server, 40);
WiFiManagerParameter custom_mqtt_port("port", "mqtt port", mqtt_port, 6);
WiFiManagerParameter custom_apikey("apikey", "onenet apikey", api_key, 32);
wifiManager.addParameter(&custom_mqtt_server);
wifiManager.addParameter(&custom_mqtt_port);
wifiManager.addParameter(&custom_apikey);
/*************************************/
/*** 步驟三:嘗試連接網絡,失敗去到配置頁面 **/
// ssid 命名為danpianjicainiao pwd是123456
if (!wifiManager.autoConnect("danpianjicainiao","123456")) {
Serial.println("failed to connect and hit timeout");
//reset and try again, or maybe put it to deep sleep
ESP.reset();
delay(1000);
}
/*************************************/
// 讀取配置頁面配置好的信息
strcpy(mqtt_server, custom_mqtt_server.getValue());
strcpy(mqtt_port, custom_mqtt_port.getValue());
strcpy(api_key, custom_apikey.getValue());
// 保存自定義信息
if (shouldSaveConfig) {
Serial.println("saving config");
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.createObject();
json["mqtt_server"] = mqtt_server;
json["mqtt_port"] = mqtt_port;
json["api_key"] = api_key;
json.printTo(Serial);
//end save
}
Serial.println("local ip");
Serial.println(WiFi.localIP());
}
/**
* 功能描述:配置進入AP模式通知回調
*/
void configModeCallback (WiFiManager *myWiFiManager) {
Serial.println("Entered config mode");
Serial.println(WiFi.softAPIP());
//if you used auto generated SSID, print it
Serial.println(myWiFiManager->getConfigPortalSSID());
//entered config mode, make led toggle faster
ticker.attach(0.2, tick);
}
/**
* 功能描述:設置點擊保存的回調
*/
void saveConfigCallback () {
Serial.println("Should save config");
shouldSaveConfig = true;
}
/**
* 功能描述:設置LED燈閃爍,提示用戶進入配置模式
*/
void tick(){
//toggle state
int state = digitalRead(BUILTIN_LED); // get the current state of GPIO1 pin
digitalWrite(BUILTIN_LED, !state); // set pin to the opposite state
}
請讀者安裝好自定義庫之后燒錄以上代碼進行測試,不出意外的話,就是以下頁面效果:
- 大叔黃(博主命名請勿噴)
- 萌弟藍(博主命名請勿噴)
- 少女粉(博主命名請勿噴)
這里,博主僅僅是列舉了三個樣式,讀者可以自行打造專屬自己的web配網。但是博主需要提醒讀者幾個小點:
- icon不宜太復雜太大,建議使用小圖標,比如博主從 這里 下載。下載完圖標之后需要轉成base64,可以通過 圖片在線轉換 來獲取。
- 最好是圖標的顏色和button的顏色一樣,讀者可以通過 傳圖識色 來獲取顏色值。
6.總結
本篇主要是講解wifimanger的源碼解析,並且嘗試去打造專屬自己的web配網。
如果你覺得本篇對你有幫助,麻煩點贊關注,謝謝支持。本篇內容涵蓋了多方面知識,希望讀者可以打好基礎,多去閱讀博主關於esp8266系列的文章,我敢保證肯定收益匪淺。