授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力。希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石。。。
共同学习成长QQ群
622368884
,不喜勿加,里面有一大群志同道合的探路人
快速导航
单片机菜鸟的博客快速索引(快速找到你要的)
如果觉得有用,麻烦点赞收藏,您的支持是博主创作的动力。
废话少说,本篇博文的目的就是深入学习 WifiManager 这个github上非常火爆的ESP8266 web配网库,让初学者不再对web配网感到迷茫,并且通过学习第三方库来自定义自己的web配网功能。等你学习整篇博文,你会发现 So easy!!
一句话概括:
第一步:
第二步:
非常简单,直接引入:
#include
至于怎么样去彻底了解这个库的使用方法,请看博主下面的源码分析。
通过查阅WifiManager的源码,整体代码非常简单,提供出来的方法调用也不多。这里博主就一个个方法详细讲解,并且深入去分析代码,以便后面自定义自己的WifiManager。主要分为几类方法:
博主建议:先了解有什么方法,然后在后面例子讲解中去感受方法的使用。
主要包括:
函数说明:
/**
* 功能描述:配置认证超时
* @param seconds 秒数为单位
*/
void WiFiManager::setTimeout(unsigned long seconds) {
setConfigPortalTimeout(seconds);
}
/**
* 功能描述:配置认证超时
* @param seconds 秒数为单位
*/
void WiFiManager::setConfigPortalTimeout(unsigned long seconds) {
_configPortalTimeout = seconds * 1000;
}
注意点:
函数说明:
/**
* 功能描述:设置sta连接超时,超过时间就返回连接状态
* @param seconds 秒数为单位
*/
void WiFiManager::setConnectTimeout(unsigned long seconds) {
_connectTimeout = seconds * 1000;
}
注意点:
函数说明:
/**
* 功能描述:设置是否打开debug
* @param debug true or false
*/
void WiFiManager::setDebugOutput(boolean debug) {
_debug = debug;
}
注意点:
函数说明:
/**
* 功能描述:设置固定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;
}
注意点:
函数说明:
/**
* 功能描述:设置固定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;
}
注意点:
函数说明:
/**
* 功能描述:设置能够接受的最低信号强度
* @param quality 信号强度,默认为8
*/
void WiFiManager::setMinimumSignalQuality(int quality) {
_minimumQuality = quality;
}
注意点:
函数说明:
/**
* 功能描述:设置WEB配置失败后是否退出配置
* @param shouldBreak 是否退出配置,默认是false
*/
void WiFiManager::setBreakAfterConfig(boolean shouldBreak) {
_shouldBreakAfterConfig = shouldBreak;
}
函数说明:
/**
* 功能描述:设置开启AP模式配置时的通知回调
* @param func 回调函数
*/
void WiFiManager::setAPCallback( void (*func)(WiFiManager* myWiFiManager) ) {
_apcallback = func;
}
注意点:
函数说明:
//start up save config callback
/**
* 功能描述:设置保存Web配置后的回调
* @param func 回调函数
*/
void WiFiManager::setSaveConfigCallback( void (*func)(void) ) {
_savecallback = func;
}
注意点:
函数说明:
/**
* 功能描述:配置页面添加自定义参数
* @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;
}
注意点:
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;
};
函数说明:
/**
* 功能描述:添加一个自定义的element到页面头部 比如加个头像
* @param element element样式的字符串
*/
void WiFiManager::setCustomHeadElement(const char* element) {
_customHeadElement = element;
}
注意点:
函数说明:
//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;
}
函数说明:
/**
* 功能描述:重置配置
* @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);
}
函数说明:
/**
* 功能描述:获取AP配置的SSID
*/
String WiFiManager::getConfigPortalSSID() {
return _apName;
}
所谓连接类方法,也就是跟wifi连接过程中相关的方法。此类方法包括:
函数说明:
/**
* 功能说明:自动连接到上一次保存的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);
}
注意点:
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;
}
}
注意点:
函数说明:
/**
* 功能说明:配置认证,默认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;
}
注意点:
关键方法 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"));
}
注意点:
接下来,重点讲解handleXXX方法的具体内容以及展示页面(后面打造专属自己的web配网也是从这里修改)。
函数说明:
/** 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(""
));
page += _apName;
page += String(F(""));
page += String(F("WiFiManager
"));
page += FPSTR(HTTP_PORTAL_OPTIONS);
page += FPSTR(HTTP_END);
server->sendHeader("Content-Length", String(page.length()));
server->send(200, "text/html", page);
}
注意点:
Root 页面的HTML+CSS+JS代码(这里是我们后面打造专属自己样式的关键):
const char HTTP_HEADER[] PROGMEM = "{v} ";
const char HTTP_STYLE[] PROGMEM = "";
const char HTTP_SCRIPT[] PROGMEM = "";
const char HTTP_HEADER_END[] PROGMEM = "";
const char HTTP_PORTAL_OPTIONS[] PROGMEM = "
";
const char HTTP_ITEM[] PROGMEM = "{v} {r}%";
const char HTTP_FORM_START[] PROGMEM = ";
const char HTTP_FORM_PARAM[] PROGMEM = "
";
const char HTTP_FORM_END[] PROGMEM = "
";
const char HTTP_SCAN_LINK[] PROGMEM = "
";
const char HTTP_SAVED[] PROGMEM = "Credentials Saved
Trying to connect ESP to network.
If it fails reconnect to AP to try again";
const char HTTP_END[] PROGMEM = "";
Root页面效果:
用户点击 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 += "
";
}
}
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 += "
";
}
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 += "
";
}
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"));
}
注意点:
配置页面效果:
上图是启用了scan功能的效果。
上图没有启用scan功能,通常用于隐藏SSID的配置。
关于scan功能,麻烦读者参考 ESP8266开发之旅 网络篇⑤ Scan WiFi——ESP8266WiFiScan库的使用
用户在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
}
用户在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(""
);
// 芯片id
page += F("Chip ID " );
page += ESP.getChipId();
page += F("");
// flash 芯片id
page += F("Flash Chip ID " );
page += ESP.getFlashChipId();
page += F("");
// flash大小
page += F("IDE Flash Size " );
page += ESP.getFlashChipSize();
page += F(" bytes");
page += F("Real Flash Size " );
page += ESP.getFlashChipRealSize();
page += F(" bytes");
page += F("Soft AP IP " );
// AP ip地址
page += WiFi.softAPIP().toString();
page += F("");
page += F("Soft AP MAC " );
// AP mac地址
page += WiFi.softAPmacAddress();
page += F("");
page += F("Station MAC " );
page += WiFi.macAddress();
page += F("");
page += F("");
page += FPSTR(HTTP_END);
server->sendHeader("Content-Length", String(page.length()));
server->send(200, "text/html", page);
DEBUG_WM(F("Sent info page"));
}
用户在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);
}
使用WifiManager三部曲:
#include
请读者烧录以下代码:
例子源码:
/**
* 功能:测试WifiManager功能demo
* 作者:单片机菜鸟
* 时间:2019-12-13
* 描述:
* 1.启动wifimanager AP配置页面,通过手机连接esp8266生成的AP热点来进入设置页面
* 2.请在手机浏览器上输入 192.168.4.25
* 2.测试官方方法的使用
*/
#include //https://github.com/esp8266/Arduino
//needed for library
#include
#include
#include //https://github.com/tzapu/WiFiManager
#include //https://github.com/bblanchon/ArduinoJson
//for LED status
#include
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。
由于代码中打开了串口调试功能,所以可以看到以下调试信息:
美中不足:
下面就是博主基于wifiManger去打造专属自己的web配网,暂且命名为 CustomWifiManager。此库,博主已经上传到QQ技术交流群。
博主基于WifiManger改造了几个方面,适合简单的样式修改。
函数说明:
void WiFiManager::setHeadImgBase64(String imgBase64) {
_imgBase64= imgBase64;
}
函数说明:
void WiFiManager::setButtonBackground(const char* backgroundColor){
_backgroundColor= backgroundColor;
}
函数说明:
void WiFiManager::setButtonTextColor(const char* textColor) {
_textColor= textColor;
}
函数说明:
void WiFiManager::setPageTitle(const char* pageTitle){
_pageTitle = pageTitle;
}
例子源码:
/**
* 功能:测试WifiManager功能demo
* 作者:单片机菜鸟
* 时间:2019-12-13
* 描述:
* 1.启动wifimanager AP配置页面,通过手机连接esp8266生成的AP热点来进入设置页面
* 2.请在手机浏览器上输入 192.168.4.25
* 3.分别支持大叔黄 萌弟蓝 少女粉
*/
#include //https://github.com/esp8266/Arduino
//needed for library
#include
#include
#include //https://github.com/tzapu/WiFiManager
#include //https://github.com/bblanchon/ArduinoJson
//for LED status
#include
// 自定义我的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,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH4wEJAAoyLlQcvAAAGW9JREFUeNrtmnm0XXd13z/f/TvnvtGyBsuSbGuwLFke8EAMeEI2xsaURUIAm3RhIAwNSSghhAUL0oShKclaUFpTp1AvhgaCTWkgCbNT8GzAAza2bJBlydYsS9Zs6Q16797z++3+ce4599wnkeCErnat8nvrvDPcM/z2vPd3/+BX4//vof/bEwB46p4ttCcj+VDAYwLAzHADHFZevuw5v/OBnzzaJbAk8UUXnPv/JgOevGsLqJyoux+XUjpLMCGzLYPZ0Hg7TjNdHCEPLVa+5NSf+571d24iKpGTAeBeMjGlRPLEyPQQB0cOc+5lq/5xBmz98a6Sb6UgcHMAlr5o0S+d+Kfu2orjZCEjpvhS4EPu/gJJU8BGnFuBT2Uh212kDiDG501y/vPOBODxuzfw5LJ1rNr0PAKBIRtg0qeOEzopJT/ZPV3hsAPjmyHaM49duYlTHj6Biy4472gGbH1gDxmBSEQYyTstgKDQLjnqJE84jlxMzJ/irJWn/uLUzhhrfvoEoweGgQTo5bh/2sUWoW8CeEpXgy52fIukP89b+bc7nU6UQyCQQiqljDFdTI0GZWfiXIOzGnEqMNthqPyA35nEGw2ekYvTX9qbtwB+9uRWdn7iSU5/6zlYsFlyfxnub5UUgHXArcn9Kff0DK4x5LiXD4/NO8LzVi1/zgx44vZNBDMknYX4H8i/mPC/Ch4OS3BkemqwlbfOlXgXrisQf4P8z0gcBmGZ8pR8cYrpte7+G8A5OHMAvEmdO15O9k8Y5eMcIZ35khX9DNh8325MwvGVJt2AcwUw2DUCBFPufsDdNyN+AtxUxLgmD1mR3AkOheDUS3pmsnbDJgBm7RwmtlL3Y8JxcIhFpBOLoSyEm5E/HbP0PivUHolDbDmwjblz5xFkkDGgpFcYuh7xk+T+F4JTQG/D/fkOS3HkFeXyn8fz7ZiuAjZIxorVi8s5rVm/ieMPjiAxQOKvgX8980lVPO29ezfwPcQXgIc67c6440jUhFaOTRLWMhEZEFLFAEOpE4vf9JT+IMlfT+LpUqMmeP45Z/Ho2icY2TcEAZh0GNLVhj6HM+QwjPsIpeM8Bq3es26pOnShdwI3JhIrL1ta/rzxhzvpzvwCSd8TzOt7QfcD9ZXGBx3G3P3HuN8QU7zHTMNAS2geYpYnvwBYAQwDiyVZ+ZwjR0jLZfrjNB2/PHDaCIenDnH2Gaf1kfLkvVuRIEwYPujXyvUFh9FStaspes+buVNLol/R6Qrt1cDUisuWAJC5wNsFyu1fuTOvSaNwjmJw/aMAjsP9SuBCoXXuPgsYchiVaxgYqAKxu9NTJC8P3Z8icbdyUeye5uwLT+PHj/yUkX2DpOCoA1NjU4Qs0BrKcfNvWcduw/3V3iBNUikOqY/4ShPpaiJiudAcYFd1T5ZShMCgp/TSYylSjxm9yTd/cHcEo8AL5VSk1faiHrEzhAGIm6beuWfH0I0LWHFhKZGhPS2SJSwP5p20SFLbZIcnnp2cbrXydsiz7+H+KhxrClhdodR+hoopPVNEDCGGmjRmKSaQjsNZ0Jx+RYOXTrBJU213lVT77ukx5RjsFI1vjAN3ZNfPRbG8+4HvrqluHvUj6U8RlwFjRVFMWm43dVKxIbbTlUKm5lubUu8eN/fVhjRLMB+xaeP9T3PaRSeTgcAZBmZRT7FHPO59mlDrgdNwQP2mckwGuHfvq33KXvCtAMWJ0+VkEwQF2kX7dxS0GNM7caZwLlPi092wHIEHXXTc/RTgJCDrqXtDwRrEd7cBpLmVpm58YAdZEQuAMWAfaHEl5qOdq/cdepegpiZUx9XWp/FHv3CH4wcAfu2sM8orCcYmx7I8yy929y9nCmvOffuZrPns49fiFI7faMG+lmXZLqSYijTf3a8MFt4r6QwcvPSuOEyBHxRqd/1CC5gr+BiB9vDTw7dPLJ4go3TMR8AP1wblZUirYysVUbVq9DGlZkCDSKn0fupdqB1U1xDuXjRx4sT2456pn4lDiQXtE4uxMLYjyUfNAw9/Ye35ecze5Mb7lu5c9NUtC58muXct3cdbRf75NMIaJX1NaJmkrYjPC92L2GGyqe5HBxGny/RBSZ9vnzx9TSvmD1tXNR0nNpSHruvoP++zp558pa5Vlpld0+aOMgN3LwuU5Dt3Du7hPVffUP9sbhzwgxSp+ErsFK+e9valtP3dndj5wXSY/sZTJ2+jyCJ7Wvs456pVxMHE1Gib1qHwUDt2buwUnWeKGN9cTHb+HLgD2ABsA7ZJ2iD0HaS3u5On6O9oe8xMQRyZ6Ew53JOSk5KTvNxi47gScGX75ZaaBOGeyntTwlMipUTsbqm7eUpdjXGcxCdv/cOaAUdGp2mrwwXfueYhM/tLc/2xuV0r9I3WVN4eZYQXXHUOV11+KQC/dvnZmIupoTbg35E0jnvSgIFBkrPi8iWsuHwJGhQEI7qvNbP7g9nqVsjmm1yEHNqdzqOdoig6RUGn06Hd6dApOhRFQVEUxKKgiOVxESMxxgZhXhOYvLul/i12nylirN6ZxSLi5jzy/XUAvOTSC8kscP9vfBVz+6EnPuvuY47viEQ2a9tMP8J5V5xJCIHc8vFgwUMeXhey8LzZJ8xBwIa7twCw/IWnEI4YA5Ynk+2V2SxJo9mukYPMK44DeAo4CMxvenMANe2/z/y9Nu+e/6CXfnazMjWyM6+1yV/hgZtHNXKwXUzz8HfWlibXl9T5JBA8+RDACQPzjmLAQ4+uhb2lBQHPJKWvBgtvPrTv2XuHi6GvT+ZH6nsHLKND6k609JbZlS86n0dufQpgv9BBd+aLngevfb2qzErUpeCMvKD5DMyIAn1DSLrSk//d4UOH1lmwe4KFXY5LQMKzFNNlkq6QaZ6kVYq677FLN3HfQ2u4+AXn128a3j2AZYbLVwodSuaPKGhcSR9rtzo/ychqtemMJlxJIYURkAvFDCClBHDAZJuA05tk9DKsvvlTRYuub+upRe0s6NOCKp9oaEwLdEXIsisEb3e8qL4ipBDCYJ3gGG9OA/7d829fuZcR+Omd67FW4OxLVmA/CESLQ1kRfhfjWwO0JlLLt1jbWjiLuk6wdrK4BkBLkI4Abfvx2icJIVRbzEIgBCOEgHW3+tgMqdrU2KvP4ZdRweq9mZV7qT6WVJ6Xz+dCQ5R1xBAw2K0gKoZcFgr7z8hPzg8GQhGwI2L97ZvBOL6V8g+b7AyZ3aUhG8lj9mqTHTGzp2Rlxrz9mQNYMMzC8Wa2wMyeVrAD2ZyDs5gOU4Cf485FfZI6Suil6eCVrTvu3SLX1c0djs4JZiZB9bsaF8p8vd+IemaHCb1RUefGPH3BsEeEiIpLNa1rPWi9pHUW9WdM+SIXLZneHztxfz7SKu3/mYxExMVpglMQN+UeJrLpYgrwDOftZRkr+hLtyit5qdNdV1ZaqzdMRXTPy2ysYuTMTPDY9XtP2s0Cpt73+Hae0PVC0wAmG5TsYXd/U4yxnWXZMsMWufuW2QPHb97vB0mdyJ51h6GA1nmz6aw5fG2Xz3dHd/T43dsBlgN3Akv6pFRrgzfSXe8LBrWkmtdmEt98V5VDNIn2ukDu40WVcKmZcDU3E2Y2YbLPOP4xk+3tzrYs0CQGB4fBKNHioHNwbhEcdOOlwL5MCDNd7e6L64Kilnw3afXSNzezv5oIn0nI0QT3399fK3hfxOlnQh8DKjPp/hkGCRJpBPEemS5CfEzSHa2sNVH6HAOJjc9u08o5yy40578gnSLYKnEEQOt/sGNE0t8DV0OF25UTrdV9Zo1c0XUMtdYxiO9XhGbkaBRUHH1vXcc3Na1b88t6e6ucsWnCZPfL7LNm4RGTpmQ6Dnib0BskLew65v2IV8j1YBZCWIxzRtMKrRZJ1+51TFoKoFOafldFvU+xexJvakgjZM7UgkrqlSN1lRD8DF/QNbmetjouSYPACHAlsNrd9zu0SQxIWihTHblMmofp95XpsSwST5FrdhOxbaJ+fcADjIMeBb4Lvg73gwnhnhqWM1P1U88Euscz06OGa+0jsnpOCJdjWG0CJQhTo7AmaVmK6WUuf7FSWiyzRS4hC1gNjhhmCXdDrquJrMgMnSZpuFZ5NRjgtejd3e9A+hikH6fkh2tMpgmS9lDKHtENLThWeD3WMBnuTpZlWRYyhSz0a0ED5qqc4b6d++4cnxr/0sJFC5fkef5Gs/BuSfOq3MVMvX2ZhywQXJQF2UmSsn79oqkGEbg+xfhxxP6iiGRZjnti5UtO5pcx1t++EciQEkXRGTDZizC93j2dUcTCilT0HGGXaFN/6T1nwZx9c5n7bZm+efILn/7o0w8ufsBCuNGk5UdFDyiTL3Gqtt67Z00yP6+J2NJnp3xSpg/iPrnghPls2b6ds698bi2xJ24vmyQx9iAHmTjrqrJD8+3P3cqpS5bSyvOlKaX/ALwKMbuHQVQYX4+ISqJ1pCivFyZ7WNIHYkp3tfLWlZJuNrOFtf1bD6uQs1bb7t877mJkJrDZHY968l8HduCzCNl4X2/A3ZGM5KkPOWqag+N4dMYnxoOZzSpiHBsZHipCCCxfvZgn79xCTJGU0klm9hW5LusVld3ssCc16KXPFQOipN2SCsRck41KthV4a5bld0p8UNK/lxSsAdhgQpGdWcJbPRvuSZ7Sy3/S3XeELBDjmEBnCea7iMBa4ABl8nQq4EjrcN9LWVKfSRlQNmHsHh4Z/khK6RUDcJfMPpSSj1cWNzQ0ZEcmJ9/r7pe5e/Lk33J8g2TWY0Av5CXhErOldJ2kb5nZR1QWN5e5+38DXyrp+hiLV4aQ/XfHrzM4M3X9nHDkBqiVmUQdUWrbF+C7he4lOJ4cI7kne0vE/9CdCP67uN/snt7j7u909wj+R+7+GXf/A+ADjiec33P3fwCuBV+ZYKFi/DQl/kDRKSg6naWSrunaeVvSf82y7I7tW3dp4+an/DM3fIaTFszh2dnGiuMW89G/+AgTkxNLJb1C0v3Ak5LhiR9KPial42V2nuDlU5MTXxgZHn7ApTNrXKLyH4GhzMwKRN6Xu5axdXtyf6biSggZyf1WSe+UMyQ4U7KBmOKZ7p7jniN/eTD7YrtTLMd9wN3HHd+44vIl+9bfsfn9iN9y9+958h0AWZaRPCHp+Z58Uf11kdqdDvMXzvUTF12YX3Txi7Ke7Rtj42Mpy7KBrkb8tqT1wNMh2Nskndi9VzJ7zfDg4JfM7BakN2CW98wKSBSZF/6sh7JbohofBYkpQwWCxRfOZ/sDexCsJ7EHfGmCiz3Gk4Aldf/AfVWMcVEXrwfYLWnrtkd2kWXZRnf/ont61q1k66zRWQQzHC5DtHrM73KiBG3eJek1iIiqJow8xjhobie6fNDMfl1iP3CWzPJQOTk4DWm2hbALqZCUl2GQ0o8E9mVk3Cvpmtr0VSfDfZ2GVGaFe4EtOEuFLwDOcPcTnQogZS7uy919QTearHV8XzwUB8H/k7u/FNjkzlUutktGLO1yoC876B62Qgt3Pwvx4r70V2qmwI/H6H+yfPXCsZ0P7n+ZpMsthEErPf4o0qCZCdHNAaowCrT4QeYtvm8dfhPIvCmCmtu9WSnFSUJ4DLjcXXPc42qkEbkiyMBnJffzgNGuGG/BmU4pDYEPuJO5+2Cvbi7BVHf3Zqpbg0ml/3lY0lxJhVIdBl3JLkryJZJcUrHt/j0IFVXa7sFxDIGnEMrs0btaXq0miNqYWbRNiEnBrLqm1zHaW+4oBIDvgn5H8oXJ9F6cVvL0GeB5wKUy/am7zwYmhNbWEvUuDll3XMphZkeV11XO2A5t8pR/FvE5M2t0o8wcvkRVvlfevTT9rnY0w+UMxKr830ZstNTyJ4Fd7tQ9gFSVrKkX15devKDC+zckT3tjSvLkrZQc4IfJ033dZ+Z58uDJ9yRP20qI3HtQeaxg9K6+Vb91ewzNyjJMBxyXu2cppYAIkoJMuUmZdTNCsxKuqzd1YbdgyEwWhAUjBBF6zEkmTWfTs4pdgwfy+xVZJa8gr26Fp35Yowue7nF8qztLSt+XJt19U0rpgLu/y0vvj7s/6e57q8ZJLdm6DBa33XxPaQINs6vKXYA8z0me3iXpNSaLNcYok6QzrYk7hlBqgRmhOjdzwLsuoNFTFogDBDZlgztCOw3GbytynUPezIZmojoJZ93N902ccd3Fj+O+ulvhHQB2ppLYg+6+sNv9uUXSkaIo+ohvVnyzZ81mbHysDIVdsr1bBgPlEj1nFfDiCmOqOrsz29+hkeXVAK7ZPpzDqhx7DWc6ErfYnLAuS+VSqMeRdss5pYd2+wjuLYe6syCJlb/1AlKMt7int7p7C9gJ7E+xyN3Z7e4L3X3K3R+rfEkvqNGssNl/cD9AZrJF1fsrXluwsiOVYuzW8N1c3mqk2UppewghTk1Pk2dZkhkGBAeDLSSbTMlPlcjV7RyrpOlrvitOZylzioG4YeBI9lcq9OFeTs8ySUuAn26+bzenXryAUy85iQ13bsXdfwT6fcq1P1sdJrt0foQScR0XWtPz6XRA14N/1fFDOAd6PzEneVpRgxyQdZsm8yMxIJ1VR6FGNddTU19YxPhqS2lS6GoLdWrfSe5ffuDBv06rL33LKyVl3SQQxFqMR2SgrQ/tLTN2YxVtbsc5udf45P2dzvQnBgaG6HQ6rFx9Mhvu2lrTVXllB4pOp5pQH1jR6V5voDdUjfN2exrgVZK+ghgWVlV+hWTeJThTOXqS72aEXW3wYNYp1T7kIZhCyLBgd6SUrhkZGZoL3C5jWQWMYPxRa9xuGF9UkLUXJeyA44HNeSfci/vrGjjdvw0hvyXGYu3K1Sez5f7dLLtoAf/S8aOvPVROHk40Cx8oie+13UVXWjPq+Cq09Tx+wILJzFqhZEC3mWPrzez9I3NHD8UjnY+aaVm9hE/aj3F3Z5YTOtUKrouczdfvJg34lWrzt558dhUGwf+XpLe02+3d3aSFc69e+c8m/o6bfkiet0gpzgNukNkbat9c1fzdLI+a4NCH6JhZ3c2y/v14COH2PB/44M4d23+25NRl7whBnzDTSBUJkL7uQ3oTYmJkYRk5WLtpO9m4gTEYJuxLFLzOU6x6/R5j/G5RxPcNDg6ub09PMzk5SdEpCFngktc+/zkx4LYv3UOK0UKW/RvJXo88VG65XlhZqXd13PXqoXJ81XkIhCxTsDAh0/dl9mBRFA+Pjo7EELJ3m9m/C0FzyneBBTYyoDdQ8MDAcU5xfNbzJzvu3st4a5pOK56VT4WvKPq55YKGGsxcB3yyKOI/jI+N7ZkzZ057enoaTwnciTFWaW2XceVCiViDoT1oOQs509MTAzElUa2wcS+zwq4fyPKAyciyDLNyn+d533nIMvIsI8uzdOjgs50TFiwY9pTOD1l4r2SvNNOAhS56hMYU9Mb8aX2rfY4zPCvQabTiefiJTdASoz9KxLMHLmA83kj0F9YrP8qFDp0U07aU0npgm5dZTl/DIyXvW11WQgLU583FVXWro4H00LD3qqF6rH1oZn5mHrJsNMuy00OWrQohzAndJq8FI5iSTB/PBsJHoqeOD0B+Qqh9cz1++sR2OlZw/NgwxcT0uZ74VEppdSnZ3moQby55qZbEeO96taCqigjNFltda1Yfb8Bdza7yTG9vM9S/YkLl/EIwQpaVx1nV7TZCsKSgv/TMP6yoMbkYOS2jnX7O+oWf3LsechgZH6RIcam7f8hTui6lNNQkvmZCgwGN6q6UdmpKu39BI42YXku2KeWGw6sJD6FBtDXb+jOcYcUwPYv4uHI+lTo+7tEJKzJGyqIOoLHctDsuuGQVQ50BJnaMg7NVpnfGlN6WUnospVgBmP0Ln7pMqPxA73qvwPFGodVcSFUvUjtqO/Yao3Jralpj0XSlSQDyhwi8ceSE7D/ijMuhtSLvI/6YGlCNex/5GaN7BiCI1I5Ej6fEGK9z99/DWd50kCUR/cQeuzfYa2zUvdemzVcLKCrph9BMefs0ITuG9EMIHvKwOYTwWeQ3tch2xrysM2yJMWhHyfufbNLwyPeeIAwG2mNtFg6cwDPt/ctjUbw2xfTbntJp7j7c5wf6GFD/q/GlGpOvnGCN+1s/8Q1Vr8vd0LD7iugyTO6zYGvd+ZssD98fu+3wxnmvnEd7TiQ/bBy/fPDn0vdPMgDg27fdzYLJuSQ5mQIL5sxn264dJ7r7eQbvcLjAUzrB3YebEaH5lb6l6zOu90Fc1mWAGs6vItTq5TuTIYQDFsJTWRb+NmTZD1qDA5vHD4yNDR43yMDIALGI2NKcWa3wj9L2CzGgGl+/5TYWTM4jqQSb3J2BPB+MKS4sirgMOC/F+MqU0ukOJ+A+7LjwGZ9UF5uvuz3MqPREKKVeWAjtYHbIzHaHLGzMQnabhbDezLaaae9QPjTmGWR5RjaUkzqROauGfmGanhMDmuO+v3uYTrvNwOAQuFMUkWDGobHDw4IT8zxfbGbL3X2Wu18GnAjMlTSrigaNbK+QtEtmUdIek+42syizZ4LZPjPbg7HXYXy4NdRWGd7KztCAoIDOwsRpSxY+Zzr+2Qxojq994xbmT8yFIIp2m5ScPMvqsNcpOi3JQjAdJ2moqvTq/D5YMgsHJSUzRU/ebsb68rg0E8/L1vg5l6/4F876l8iAY42b/uffc9WhS1gzvI6W51SLr3o9vZ4GVD38KgnyzDn70dO479LHeNlLLvk/NcVfjV8N4H8DvpM4yZ/1GykAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDEtMDlUMDA6MTA6NTArMDg6MDCSUWrCAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTAxLTA5VDAwOjEwOjUwKzA4OjAw4wzSfgAAAEN0RVh0c29mdHdhcmUAL3Vzci9sb2NhbC9pbWFnZW1hZ2ljay9zaGFyZS9kb2MvSW1hZ2VNYWdpY2stNy8vaW5kZXguaHRtbL21eQoAAAAYdEVYdFRodW1iOjpEb2N1bWVudDo6UGFnZXMAMaf/uy8AAAAYdEVYdFRodW1iOjpJbWFnZTo6SGVpZ2h0ADI1NunDRBkAAAAXdEVYdFRodW1iOjpJbWFnZTo6V2lkdGgAMjU2ejIURAAAABl0RVh0VGh1bWI6Ok1pbWV0eXBlAGltYWdlL3BuZz+yVk4AAAAXdEVYdFRodW1iOjpNVGltZQAxNTQ2OTYzODUwFYbWHQAAABJ0RVh0VGh1bWI6OlNpemUAMzY2MDFCMtvoDwAAAGB0RVh0VGh1bWI6OlVSSQBmaWxlOi8vL2hvbWUvd3d3cm9vdC9uZXdzaXRlL3d3dy5lYXN5aWNvbi5uZXQvY2RuLWltZy5lYXN5aWNvbi5jbi9maWxlcy81Mi81MjU5NjgucG5n+YEv+wAAAABJRU5ErkJggg==";
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
}
注意:
// 重置保存的修改 目标是为了每次进来都是去掉配置页面
wifiManager.resetSettings();
这行代码是为了每次进来都跑去配网,所以实际使用中需要删除这行
请读者安装好自定义库之后烧录以上代码进行测试,不出意外的话,就是以下页面效果:
这里,博主仅仅是列举了三个样式,读者可以自行打造专属自己的web配网。但是博主需要提醒读者几个小点:
本篇主要是讲解wifimanger的源码解析,并且尝试去打造专属自己的web配网。
如果你觉得本篇对你有帮助,麻烦点赞关注,谢谢支持。本篇内容涵盖了多方面知识,希望读者可以打好基础,多去阅读博主关于esp8266系列的文章,我敢保证肯定收益匪浅。
单片机菜鸟的博客快速索引(快速找到你要的)
如果觉得有用,麻烦点赞收藏,您的支持是博主创作的动力。
共同学习成长QQ群
622368884
,不喜勿加,里面有一大群志同道合的探路人