# 前言
之前我们开发的 esp8266 程序都要在代码中将 wifi 的名称和密码全部定义好,然后烧写进芯片中,而且连接 wifi 成功后,自身得到的 ip 需要通过串口打印或者我们自己去无线路由器中查看才能知道.
有没有一种方式可以解决这 2 个问题,最好是可以选择一个 wifi 连接,并且可以有个界面输入 wifi 密码,连接成功后也可以自己返回 IP 号码被我们看到.
思考了一下,我们可以使用 esp8266 的 AP 模式,提供一个界面,同时扫描并显示 wifi 列表,选择相应的 wifi 并输入密码,连接成功后将 wifi 名称和密码再存储到闪存中,下次断电再上电后也可以自动连接.

想到就做到,花费了一早晨时间终于是完成了,代码如下:(实现过程略有点复杂)
# 代码
| #include <ESP8266WiFi.h> | |
| #include <ESP8266WebServer.h> | |
| #include <ESP8266mDNS.h> | |
| #include <Arduino_JSON.h> | |
| #include <EEPROM.h> | |
| #include <ESP8266WiFiMulti.h> | |
| //a 写入字符串长度,b 是起始位,str 为要保存的字符串 | |
| void set_String(int a,int b,String str){ | |
| EEPROM.write(a, str.length());//EEPROM 第 a 位,写入 str 字符串的长度 | |
|   // 把 str 所有数据逐个保存在 EEPROM | |
| for (int i = 0; i < str.length(); i++){ | |
| EEPROM.write(b + i, str[i]); | |
|   } | |
| EEPROM.commit(); | |
| } | |
| //a 位是字符串长度,b 是起始位 | |
| String get_String(int a, int b){ | |
| String data = ""; | |
|   // 从 EEPROM 中逐个取出每一位的值,并链接 | |
| for (int i = 0; i < a; i++){ | |
| data += char(EEPROM.read(b + i)); | |
|   } | |
| return data; | |
| } | |
| const char* ssid = "ESP-Access-Point"; | |
| const char* password = "00000000"; | |
| const int led = 2; | |
| const int RSSI_MAX =-50; | |
| const int RSSI_MIN =-100; | |
| JSONVar wifiList; | |
| ESP8266WebServer server(80); | |
| ESP8266WiFiMulti WiFiMulti; | |
| WiFiClient espClient; | |
| String myIP = ""; | |
| void getList(){ | |
| digitalWrite(led, 1); | |
| String jsonStr = JSON.stringify(wifiList); | |
| server.send(200, "application/json", jsonStr); | |
| digitalWrite(led, 0); | |
| } | |
| void handleRoot(){ | |
| const String Html = "<!DOCTYPE html>\ | |
| <html>\ | |
| <head>\ | |
| <meta charset=\"utf-8\">\ | |
| <title>Hello ESP8266!</title>\ | |
| </head>\ | |
| <body>\ | |
| <h1>看到的这个网页为ESP8266提供<h1>\ | |
| </body>\ | |
|     </html>"; | |
| digitalWrite(led,1); | |
| server.send(200,"text/html",Html); | |
| digitalWrite(led,0); | |
| } | |
| void handleNotFound() { | |
| digitalWrite(led, 0); | |
| String message = "File Not Found\n\n"; | |
| message += "URI: "; | |
| message += server.uri(); | |
| message += "\nMethod: "; | |
| message += (server.method() == HTTP_GET) ? "GET" : "POST"; | |
| message += "\nArguments: "; | |
| message += server.args(); | |
| message += "\n"; | |
| for (uint8_t i = 0; i < server.args(); i++) { | |
| message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; | |
|   } | |
| server.send(404, "text/plain", message); | |
| digitalWrite(led, 1); | |
| } | |
| int dBmtoPercentage(int dBm) | |
| { | |
| int quality; | |
| if(dBm <= RSSI_MIN) | |
|   { | |
| quality = 0; | |
|   } | |
| else if(dBm >= RSSI_MAX) | |
|   {   | |
| quality = 100; | |
|   } | |
|   else | |
|   { | |
| quality = 2 * (dBm + 100); | |
|   } | |
| return quality; | |
| } | |
| // 选择 wifi 页面 | |
| void handleInitGet(){ | |
| const String Html1 = "<!DOCTYPE html>\ | |
| <html>\ | |
| <head>\ | |
| <meta charset=\"utf-8\">\ | |
| <title>连接WIFI!</title>\ | |
| <style>\ | |
|           body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\ | |
| </style>\ | |
| </head>\ | |
| <body>\ | |
| <h1>连接WIFI!<h1>\ | |
| <form method=\"post\" enctype=\"application/x-www-form-urlencoded\" action=\"/set_wifi\">\ | |
| <div>\ | |
|             <select name=\"ssid\">"; | |
| String Html2 = ""; | |
|     // 循环 wifi 列表 | |
| for(int i = 0;i<wifiList.length();i++){ | |
| String ssid = wifiList[i]; | |
| Html2 += "<option value=\""; | |
| Html2 += ssid; | |
| Html2 += "\">"; | |
| Html2 += ssid; | |
| Html2 += "</option>"; | |
|     } | |
| const String Html3 = "</select>\ | |
| </div>\ | |
| <div>\ | |
| <input type=\"password\" name=\"password\" value=\"\" placeholder=\"请输入wifi密码\"><br>\ | |
| </div>\ | |
| <div>\ | |
| <input type=\"submit\" value=\"连接\">\ | |
| </div>\ | |
| </form>\ | |
| </body>\ | |
|     </html>"; | |
| digitalWrite(led,1); | |
| server.send(200,"text/html",Html1+Html2+Html3); | |
| digitalWrite(led,0); | |
| } | |
| void handleInitPost(){ | |
| if(server.method() != HTTP_POST){ | |
| digitalWrite(led, 1); | |
| server.send(405, "text/plain", "Method Not Allowed"); | |
| digitalWrite(led, 0); | |
| }else{ | |
| digitalWrite(led, 1); | |
| String message = "POST form was:\n"; | |
| String ssid = ""; | |
| String password = ""; | |
| for (uint8_t i = 0; i < server.args(); i++) { | |
| message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; | |
|       // 判断是否是 ssid 和 password | |
| if(server.argName(i) == "ssid"){ | |
| ssid = server.arg(i); | |
| }else if(server.argName(i) == "password"){ | |
| password = server.arg(i); | |
|       } | |
|     } | |
| String html = "<!DOCTYPE html>\ | |
| <html>\ | |
| <head>\ | |
| <meta charset=\"utf-8\">\ | |
| <title>连接WIFI中...</title>\ | |
| <style>\ | |
|           body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\ | |
| </style>\ | |
| </head>\ | |
| <body>\ | |
|         <h1 style=\"color:#f69;\">连接WIFI:("; | |
| html += ssid; | |
| html += ")中...</h1>\ | |
| <a href=\"/\" style=\"font-size:20px;\">返回上一页</a>\ | |
| </body>\ | |
| <script>\ | |
|         setInterval(function(){\ | |
| var xhr = new XMLHttpRequest();\ | |
| xhr.withCredentials = true;\ | |
|           xhr.addEventListener(\"readystatechange\", function() {\ | |
|             if(this.readyState === 4) {\ | |
| alert(\"IP:\"+this.responseText);\ | |
| }\ | |
| });\ | |
| xhr.open(\"GET\", \"http://192.168.4.1/ip\");\ | |
| xhr.send();\ | |
| },2000);\ | |
| </script>\ | |
|     </html>"; | |
| server.send(200, "text/html", html); | |
|     // 连接 wifi | |
| int tryNum = 0; | |
| WiFiMulti.addAP(ssid.c_str(),password.c_str()); | |
| while(tryNum < 5 && WiFiMulti.run() != WL_CONNECTED){ | |
| digitalWrite(led, 0); | |
| Serial.print("."); | |
| delay(500); | |
| tryNum += 1; | |
| digitalWrite(led, 1); | |
|     } | |
| if(tryNum >= 5){ | |
| Serial.println("wifi连接失败!"); | |
|       // 清空存储 | |
| for (int i = 0; i < 512; i++) { EEPROM.write(i, 0); } | |
| EEPROM.end(); | |
| setAp(); | |
| }else{ | |
| Serial.println("wifi连接成功!"); | |
| Serial.print("IP address: "); | |
| Serial.print(WiFi.localIP()); | |
| myIP = WiFi.localIP().toString(); | |
|       // 连接成功之后将 wifi 信息存储到闪存中 | |
| set_String(0,2,ssid); | |
| set_String(1,256,password); | |
| setSta(); | |
|     } | |
| digitalWrite(led, 0); | |
|   } | |
| } | |
| // 返回已连接成功的 ip | |
| void handleGetIp(){ | |
| digitalWrite(led, 0); | |
| String jsonStr = "{\"ip\":\""+myIP+"\"}"; | |
| server.send(200, "application/json", jsonStr); | |
| digitalWrite(led, 1); | |
| } | |
| // 作为 sta 模式运行 | |
| void setSta(){ | |
| server.on("/ip",handleGetIp); | |
| server.on("/",handleRoot); | |
| server.onNotFound(handleNotFound); | |
| server.begin(); | |
| Serial.println("wifi已连接!"); | |
| } | |
| // 作为 ap 模式运行 | |
| void setAp(){ | |
| server.on("/ip",handleGetIp); | |
| server.on("/",handleInitGet); | |
| server.on("/set_wifi",handleInitPost); | |
| server.onNotFound(handleNotFound); | |
| server.begin(); | |
| Serial.println("AP服务已启动!"); | |
| } | |
| void setup() { | |
| pinMode(led,OUTPUT); | |
| digitalWrite(led,1); | |
| Serial.begin(115200); | |
| EEPROM.begin(512); | |
| String wifi_ssid = get_String(EEPROM.read(0),2); | |
| String wifi_password = get_String(EEPROM.read(1),256); | |
|   // 判断有存储的 wifi 名称和密码,尝试连接该 wifi | |
| int tryNum = 0; | |
| if(wifi_ssid.length() && wifi_password.length()){ | |
| Serial.print("链接wifi中:"); | |
| Serial.print(wifi_ssid+":"+wifi_password); | |
| WiFi.mode(WIFI_AP_STA); | |
| WiFiMulti.addAP(wifi_ssid.c_str(),wifi_password.c_str()); | |
|     // 此处尝试链接 5 次 | |
| while(tryNum < 5 && WiFiMulti.run() != WL_CONNECTED){ | |
| Serial.print("."); | |
| delay(500); | |
| tryNum += 1; | |
|     } | |
|     //5 次以后如果还没有连接上则代表连接失败 | |
| if(tryNum >= 5){ | |
| Serial.println("wifi连接失败!"); | |
|       // 清空存储 | |
| for (int i = 0; i < 512; i++) { EEPROM.write(i, 0); } | |
| EEPROM.end(); | |
| }else{ | |
| Serial.println("wifi连接成功!"); | |
| Serial.print("IP address: "); | |
| Serial.print(WiFi.localIP()); | |
|       // 作为 sta 运行 | |
| setSta(); | |
|     } | |
| }else{ | |
|     // 没有存储的 wifi 和密码 | |
| Serial.println(); | |
| Serial.println("没有闪存,设置AP中..."); | |
|     // WiFi.mode(WIFI_AP); | |
| WiFi.softAP(ssid,password); | |
| IPAddress IP = WiFi.softAPIP(); | |
|     // 作为 AP 默认的 IP 为 192.168.4.1 | |
| Serial.print("AP IP address: "); | |
| Serial.print(IP); | |
| if (MDNS.begin("esp8266")) { | |
| Serial.println("MDNS responder started"); | |
|     } | |
|     // 作为 AP 运行 | |
| setAp(); | |
|     // 扫描周围 wifi | |
| int n = WiFi.scanNetworks(); | |
| Serial.println("Wifi scan ended"); | |
| if (n == 0) { | |
|       // 没有找到 wifi | |
| Serial.println("no networks found"); | |
| }else{ | |
|       // 找到 wifi | |
| Serial.print(n); | |
| Serial.println(" networks found!"); | |
| for(int i = 0;i<n;++i){ | |
| String wifiName = ""; | |
| wifiName += WiFi.SSID(i); | |
| Serial.print(WiFi.SSID(i));//wifi 名称 | |
| Serial.print(WiFi.RSSI(i));//wifi 强度 | |
| Serial.print("dBm ("); | |
| Serial.print(dBmtoPercentage(WiFi.RSSI(i))); | |
| Serial.print("% )"); | |
| if(WiFi.encryptionType(i) == ENC_TYPE_NONE) | |
|         { | |
| Serial.println(" <<***OPEN***>>"); | |
| }else{ | |
| Serial.println(); | |
|         } | |
| wifiList[i] = wifiName; | |
| delay(10); | |
|       } | |
| String jsonStr = JSON.stringify(wifiList); | |
| Serial.println(jsonStr); | |
|     } | |
|   } | |
| digitalWrite(led,0); | |
| } | |
| void loop() { | |
| server.handleClient(); | |
| MDNS.update(); | |
| } | 
# 测试
上电之后,用手机搜索名为 ESP-Access-Point 的 Wifi, 密码为 8 个 0, 链接上之后使用浏览器访问 192.168.4.1 , 成功显示如下页面

然后选择我们已知的 wifi, 输入密码,点击连接按钮,成功进入下一个页面
这里使用了 Ajax 去获取 ip 并且 alert 出来,我们就成功知道了连接 wifi 之后的 ip 是什么.
