WIFI Менеджер для ESP32

04 ноября 2022

Всем привет! Сегодня разберем как написать wifi manager для esp32 с нуля и пользоваться им.

Если вы делали проекты на esp, то рано или поздно вы столкнетесь с проблемой, когда прописанные в скетче константы имени и пароля от wifi сети необходимо заменить, не имея доступа к скетчу. Например, вы сделали проект умной лампы на esp32 и продаете её для домашнего пользования. Чтобы пользователь мог управлять лампой из своей сети он должен указать свои реквизиты wifi. И тут нам на помощь приходит Wifi Manager. Как он работает?

При первом включении, esp раздает точку доступа, с заранее известным именем и паролем. Мы подключаемся к этой точке доступа и попадаем на локальную страничку, где нам предлагают сконфигурировать wifi. Там мы вводим пароль от своей сети и esp перезагрузившись подключается к нашему wifi. Если esp не может найти заданную сеть, то она опять раздает точку доступа. Таким образом мы можем подключать наше умное устройство к любому wifi не изменяя константы непосредственно в скетче.

Итак, перейдем к практике.

Создадим два html файла: index.html и conf.html. Первый будет отображаться после успешного подключения к wifi, а второй будет содержать форму настройки сети.

Хранить эти файлы мы будем в SPIFFS, если вы не знакомы с этой системой, рекомендую прочесть нашу предыдущую статью.

В файл index.html поместите содержимое:

<!DOCTYPE html>
<html lang="ru">
    <head>
        <title>MET ONLINE</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="bootstrap.min.css">
    </head>
    <body class="bg-dark text-light">
        <div class="position-absolute top-50 start-50 translate-middle">
            <div class="container-fluid">
                <div class="card text-light bg-success mb-3">
                    <div class="card-header"><h2><center><b>Успешно подключено!</b></center></h2></div>
                </div>
            </div>
        </div>
    </body>
</html>

Далее файл с формой конфигурации conf.html:

<!DOCTYPE html>
<html lang="ru">
    <head>
        <title>Wi-Fi Manager</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="bootstrap.min.css">
    </head>
    <body class="bg-dark text-light">
        <div class="position-absolute top-50 start-50 translate-middle">
            <div class="container-fluid">
                <div class="card text-dark bg-info mb-3">
                    <div class="card-header"><h2><center><b>ESP32 Wi-Fi Manager</b></center></h2></div>
                    <div class="card-body">
                        <h5 class="card-title">
                            <form method="POST">
                                <div class="input-group mb-3">
                                    <span class="input-group-text">SSID</span>
                                    <input type="text" class="form-control" name="ssid" aria-describedby="inputGroup-sizing-default">
                                </div>
                                <div class="input-group mb-3">
                                    <span class="input-group-text">PSK</span>
                                    <input type="password" name="pass" class="form-control">
                                </div>
                                <div class="input-group mb-3">
                                    <span class="input-group-text">IP Addr</span>
                                    <input type="text" class="form-control" name="ip" aria-describedby="inputGroup-sizing-default" placeholder="192.168.0.100">
                                </div>
                                <div class="input-group mb-3">
                                    <span class="input-group-text">Gateway Addr</span>
                                    <input type="text" class="form-control" name="gateway" aria-describedby="inputGroup-sizing-default" placeholder="192.168.0.1">
                                </div>
                                <center><button type="submit" class="btn btn-success">Submit</button></center>
                            </form>
                        </h5>
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

Сюда вы можете добавить свои поля для доп настройки, например соединения с mqqt-сервером.

Напишем скетч:

Для понимания посмотрим на схему:

Подключаем необходимые библиотеки:

#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include "SPIFFS.h"

Для хранения имени, пароля, Ip адреса и адреса роутера мы создадим 4 файла в spiffs. И будем записывать в них пришедшие из формы данные.

//Пути файлов
const char* ssidPath = "/ssid.txt";
const char* passPath = "/pass.txt";
const char* ipPath = "/ip.txt";
const char* gatewayPath = "/gateway.txt";

Для хранения данных с формы создадим переменные:

const char* PARAM_INPUT_1 = "ssid";
const char* PARAM_INPUT_2 = "pass";
const char* PARAM_INPUT_3 = "ip";
const char* PARAM_INPUT_4 = "gateway";

String ssid;
String pass;
String ip;
String gateway;

Настраиваем объекты:

AsyncWebServer server(80); //Инициализируем сервер
IPAddress localIP;
IPAddress localGateway;
IPAddress subnet(255, 255, 0, 0); //Маска

Переменные:

unsigned long previousMillis = 0;
const long interval = 10000; //Сколько ждем перед открытием портала
const int ledPin = 2;
String ledState;

Первоначальная настройка (setup)

void setup() {
  Serial.begin(115200); //Открываем порт
  initSPIFFS(); //Инициализируем spiffs
  pinMode(ledPin, OUTPUT); //Светодиод на выход
  digitalWrite(ledPin, LOW);

  //Загружаем в переменные данные из SPIFFS
  ssid = readFile(SPIFFS, ssidPath);
  pass = readFile(SPIFFS, passPath);
  ip = readFile(SPIFFS, ipPath);
  gateway = readFile (SPIFFS, gatewayPath);
  //Если есть wifi
  if(initWiFi()) {
    // Открываем успешную страницу index.html
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
      request->send(SPIFFS, "/index.html", "text/html");
    });
    server.serveStatic("/", SPIFFS, "/");
    
    server.on("/bootstrap.min.css", HTTP_GET, [](AsyncWebServerRequest *request){ //Подключаем стили
      request->send(SPIFFS, "/bootstrap.min.css", "text/css");
    });
    server.begin();
  }
  //Иначе
  else {
    Serial.println("Setting AP (Access Point)"); //Раздаем точку доступа
    WiFi.softAP("ESP-CONNECT", NULL); // Создаем открытую точку доступа с именем ESP-CONNECT

    IPAddress IP = WiFi.softAPIP();
    Serial.print("AP IP address: ");
    Serial.println(IP); 

    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { //Запускаем сервер с формой конфигурации
      request->send(SPIFFS, "/conf.html", "text/html");
    });

    server.on("/bootstrap.min.css", HTTP_GET, [](AsyncWebServerRequest *request){ //Подключаем стили
      request->send(SPIFFS, "/bootstrap.min.css", "text/css");
    });
    
    server.serveStatic("/", SPIFFS, "/");
    
    //Получаем данные из формы, если пришел запрос
    server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) {
      int params = request->params();
      for(int i=0;i<params;i++){
        AsyncWebParameter* p = request->getParam(i);
        if(p->isPost()){
          if (p->name() == PARAM_INPUT_1) { // Получаем имя сети из формы
            ssid = p->value().c_str();
            Serial.print("SSID set to: ");
            Serial.println(ssid);
            // Write file to save value
            writeFile(SPIFFS, ssidPath, ssid.c_str());
          }
          if (p->name() == PARAM_INPUT_2) { // Получаем пароль из формы
            pass = p->value().c_str();
            Serial.print("Password set to: ");
            Serial.println(pass);
            // Write file to save value
            writeFile(SPIFFS, passPath, pass.c_str());
          }
          if (p->name() == PARAM_INPUT_3) { // Получаем POST запрос про IP
            ip = p->value().c_str();
            Serial.print("IP Address set to: ");
            Serial.println(ip);
            // Write file to save value
            writeFile(SPIFFS, ipPath, ip.c_str());
          }
          if (p->name() == PARAM_INPUT_4) { // Получаем POST запрос про Gateway путь
            gateway = p->value().c_str();
            Serial.print("Gateway set to: ");
            Serial.println(gateway);
            // Write file to save value
            writeFile(SPIFFS, gatewayPath, gateway.c_str());
          }
        }
      }
      request->send(200, "text/plain", "Успешно, esp перезагрузиться и получит адрес:" + ip);
      delay(3000);
      ESP.restart(); //Перезагружаем esp
    });
    server.begin(); //Запускаем сервер в любом случае
  }
}

Часть loop-а не понадобиться, потому разберем некоторые функции, которые облегчат работу:

String readFile(fs::FS &fs, const char * path){ //Чтение файла из spiffs
  Serial.printf("Reading file: %s\r\n", path);
  File file = fs.open(path);
  if(!file || file.isDirectory()){
    Serial.println("- failed to open file for reading");
    return String();
  }
  String fileContent;
  while(file.available()){
    fileContent = file.readStringUntil('\n');
    break;     
  }
  return fileContent;
}

Запись файла с константами:

void writeFile(fs::FS &fs, const char * path, const char * message){ //Функция записи файла в spiffs
  Serial.printf("Writing file: %s\r\n", path);

  File file = fs.open(path, FILE_WRITE);
  if(!file){
    Serial.println("- failed to open file for writing");
    return;
  }
  if(file.print(message)){
    Serial.println("- file written");
  } else {
    Serial.println("- frite failed");
  }
}

Инициализация wifi вынесена в отдельную функцию чтобы, быстро подключаться к данным из spiffs и получать bool true или false.

bool initWiFi() { //Функция инициализации wifi
  if(ssid=="" || ip==""){
    Serial.println("Undefined SSID or IP address.");
    return false;
  }

  WiFi.mode(WIFI_STA);
  localIP.fromString(ip.c_str());
  localGateway.fromString(gateway.c_str());


  if (!WiFi.config(localIP, localGateway, subnet)){
    Serial.println("STA Failed to configure");
    return false;
  }
  WiFi.begin(ssid.c_str(), pass.c_str());
  Serial.println("Connecting to WiFi...");

  unsigned long currentMillis = millis();
  previousMillis = currentMillis;

  while(WiFi.status() != WL_CONNECTED) {
    currentMillis = millis();
    if (currentMillis - previousMillis >= interval) {
      Serial.println("Failed to connect.");
      return false;
    }
  }

  Serial.println(WiFi.localIP());
  return true;
}

Ну и функция инициализации spiffs:

void initSPIFFS() {
  if (!SPIFFS.begin(true)) {
    Serial.println("An error has occurred while mounting SPIFFS");
  }
  Serial.println("SPIFFS mounted successfully");
}

Скачать полный скетч и файлы можно тут.

Загружаем...

Видео с демонстрацией работы:

На этом сегодня все, всем спасибо за внимание! Теперь вы знаете как усовершенствовать свое умное устройство на базе esp32. Удачи в проектах!


Данная статья является собственностью Amperkot.ru. При перепечатке данного материала активная ссылка на первоисточник, не закрытая для индексации поисковыми системами, обязательна.


Поделиться: