# 前言

之前我们开发的 esp8266 程序都要在代码中将 wifi 的名称和密码全部定义好,然后烧写进芯片中,而且连接 wifi 成功后,自身得到的 ip 需要通过串口打印或者我们自己去无线路由器中查看才能知道.

有没有一种方式可以解决这 2 个问题,最好是可以选择一个 wifi 连接,并且可以有个界面输入 wifi 密码,连接成功后也可以自己返回 IP 号码被我们看到.

思考了一下,我们可以使用 esp8266 的 AP 模式,提供一个界面,同时扫描并显示 wifi 列表,选择相应的 wifi 并输入密码,连接成功后将 wifi 名称和密码再存储到闪存中,下次断电再上电后也可以自动连接.

技术架构图

想到就做到,花费了一早晨时间终于是完成了,代码如下:(实现过程略有点复杂)

# 代码

p
#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 是什么.
手机截图