赞
踩
原文地址:https://my.oschina.net/u/4269090/blog/3329239
废话少说,本篇博文的目的就是深入学习 WifiManager 这个github上非常火爆的ESP8266 web配网库,让初学者不再对web配网感到迷茫,并且通过学习第三方库来自定义自己的web配网功能。等你学习整篇博文,你会发现 So easy!!
直接点击 github 下载代码
一句话概括:
ESP8266 WiFi连接管理器,主要是提供Web页面配置功能,包括Web配网、自定义参数配置;
第一步:
这个库是跟ESP8266 Arduino核心库一起使用,所以用户需要配置好Arduino For ESP8266平台,具体请参考 ESP8266开发之旅 基础篇② 如何安装ESP8266的Arduino开发环境
第二步:
可以通过Arduino IDE库管理器安装WiFiManager,搜索“WiFiManager”,然后点击下载(适合新手小白,直接上手使用)
也可以直接通过github下载源码,放到你的Arduino libraries文件夹(深入学习者,可以查看源码然后改造源码,达到自定义配网功能)
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热点,如果连接成功,就返回到我们的正常业务,连接失败,就会再次重复上面的操作
非常简单,直接引入:
#include <WiFiManager.h>
至于怎么样去彻底了解这个库的使用方法,请看博主下面的源码分析。
通过查阅WifiManager的源码,整体代码非常简单,提供出来的方法调用也不多。这里博主就一个个方法详细讲解,并且深入去分析代码,以便后面自定义自己的WifiManager。主要分为几类方法:
博主建议:先了解有什么方法,然后在后面例子讲解中去感受方法的使用。
主要包括:
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;
- }
注意点:
3.1.2 setConnectTimeout —— 设置sta连接超时时间
函数说明:
- /**
- * 功能描述:设置sta连接超时,超过时间就返回连接状态
- * @param seconds 秒数为单位
- */
- void WiFiManager::setConnectTimeout(unsigned long seconds) {
- _connectTimeout = seconds * 1000;
- }
注意点:
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;
- }
注意点:
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;
- }
注意点:
3.1.6 setMinimumSignalQuality —— 设置最小的信号质量
函数说明:
- /**
- * 功能描述:设置能够接受的最低信号强度
- * @param quality 信号强度,默认为8
- */
- void WiFiManager::setMinimumSignalQuality(int quality) {
- _minimumQuality = quality;
- }
注意点:
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;
- }
注意点:
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;
- }
所谓连接类方法,也就是跟wifi连接过程中相关的方法。此类方法包括:
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);
- }
注意点:
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;
- }
- }
注意点:
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;
- }
注意点:
关键方法 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配网也是从这里修改)。
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);
- }
注意点:
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页面效果:
这里包含了多个功能入口
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"));
- }
注意点:
上图是启用了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);
- }
使用WifiManager三部曲:
#include <WiFiManager.h>
请读者烧录以下代码: 例子源码:
- /**
- * 功能:测试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。 由于代码中打开了串口调试功能,所以可以看到以下调试信息:
美中不足:
下面就是博主基于wifiManger去打造专属自己的web配网,暂且命名为 CustomWifiManager。此库,博主已经上传到QQ技术交流群。
博主基于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;
- }
例子源码:
- /**
- * 功能:测试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,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
- }
请读者安装好自定义库之后烧录以上代码进行测试,不出意外的话,就是以下页面效果:
大叔黄(博主命名请勿喷)
萌弟蓝(博主命名请勿喷)
少女粉(博主命名请勿喷
这里,博主仅仅是列举了三个样式,读者可以自行打造专属自己的web配网。但是博主需要提醒读者几个小点:
本篇主要是讲解wifimanger的源码解析,并且尝试去打造专属自己的web配网。 如果你觉得本篇对你有帮助,麻烦点赞关注,谢谢支持。本篇内容涵盖了多方面知识,希望读者可以打好基础,多去阅读博主关于esp8266系列的文章,我敢保证肯定收益匪浅。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。