ESP32: WiFi Manager với thư viện AsyncWebServer

Trong bài viết này, bạn sẽ được hướng dẫn cách sử dụng WiFi Manager với thư viện ESPAsyncWebServer thay vì cách thông thường, giúp thiết bị có thể được cấu hình WiFi một cách dễ dàng mà không cần khai báo cố định trong mã nguồn. Với WiFi Manager, thiết bị sẽ tự động kết nối vào mạng WiFi lần trước đó hoặc phát ra một mạng WiFi của riêng nó (chế độ Access Point), để người dùng có thể kết nối vào và cấu hình mạng WiFi mới cho thiết bị.

Cách hoạt động của thư viện WiFi Manager

Bạn hãy xem sơ đồ bên dưới đây để hiểu rõ hơn cách hoạt động của thư viện WiFi Manager mà chúng ta sẽ áp dụng trong bài hướng dẫn này.

Cách hoạt động của WiFi manager trên ESP32
  • Khi ESP32 khởi động, nó sẽ tìm kiếm các file ssid.txt, pass.txt và ip.txt  (1);
  • Nếu các file này không tồn tại hoặc không có dữ liệu (2) (thiết bị chạy lần đầu tiên sau khi nạp code), ESp32 sẽ bật mode Access Point và phát ra một mạng WiFi của riêng mình (3);
  • Người dùng sử dụng các thiết bị như máy tính hoặc điện thoại để kết nối vào mạng WiFi này và truy cập vào địa chỉ IP 192.168.4.1 để mở trang web cấu hình WiFi cho thiết bị (4);
  • Các thông tin cấu hình trong form như SSID, password, và địa chỉ IP address sẽ được lưu vào các file tương ứng trên ESP32 như: ssid.txt, pass.txt, and ip.txt (5);
  • Sau đó ESP32 khởi động lại (6);
  • Sau khi khởi động, các file cấu hình này có chứa dữ liệu nên ESP32 sẽ đọc và cố gắng kết nối đến mạng WiFi đã lưu (7);
  • Nếu kết nối thành công, quá trình kết nối WiFi hoàn tất và thiết bị sẽ thực hiện các tác vụ khác như trong chương trình được nạp (9). Ngược lại nếu không thành công, ESP32 sẽ chuyển về mode Access Point và phát ra mạng WiFi như bước số 3 ở trên .

Để demo tính năng WiFi Manager, chúng ta sẽ setup một web server cho phép điều khiển đèn LED trên board ESP32 (D13). Bạn có thể sử dụng WiFi Manager với thư viện ESPAsyncWebServer cho các dự án cần ESP32 kết nối với mạng WiFi.

Yêu cầu

Để thực hiện theo bài hướng dẫn này, bạn cần cài đặt Arduino IDE và add-on hỗ trợ lập trình cho ESP32 ở bài viết này.

Cài đặt thư viện trong Arduino IDE

Bạn cần cài đặt thêm các thư viện sau trong Arduino IDE để setup web server

Thư viện ESPAsyncWebServer, AsynTCP, và ESPAsyncTCP không có sẵn trong Arduino Library Manager, bạn cần phải download về máy và cài đặt trong Arduino thông qua menu Sketch Include Library > Add .zip Library.

Filesystem Uploader

Ngoài ra, bạn cũng cần cài đặt thêm một plugin cho Arduino IDE có tên là ESP32 Uploader.

Tổ chức các file

Để giúp tổ chức các file của dự án một cách ngăn nắp và dễ hiểu, chúng ta sẽ tạo ra 4 file khác nhau sau đây cho chức năng web server:

  • Arduino sketch chứa chương trình Arduino chính;
  • index.html: chứa nội dung của trang web để điều khiển đèn led;
  • style.css: chứa các format css của trang web;
  • wifimanager.html: chứa trang web hiển thị ra giao diện cho phép người dùng cấu hình mạng WiFi.
Cách tổ chức các file trong WiFi Manager
Cách tổ chức các file trong WiFi Manager

Các file HTML và CSS sẽ được chứa chung trong thư mục data trong thư mục của dự án. Chúng ta cần upload tất cả các file này vào filesystem của ESP32 (SPIFFS).

Bạn có thể download toàn bộ file của dự án này ở đây:

Tạo các file HTML

Trong dự án này, chúng ta sẽ cần 2 file HTML. Một file là chứa giao diện web để điều khiển đèn LED trên board (index.html) và file còn lại để hiển thị giao diện cấu hình WiFi (wifimanager.html).

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>ESP WEB SERVER</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" href="style.css">
    <link rel="icon" type="image/png" href="favicon.png">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
  </head>
  <body>
    <div class="topnav">
      <h1>ESP WEB SERVER</h1>
    </div>
    <div class="content">
      <div class="card-grid">
        <div class="card">
          <p class="card-title"><i class="fas fa-lightbulb"></i> GPIO 2</p>
          <p>
            <a href="on"><button class="button-on">ON</button></a>
            <a href="off"><button class="button-off">OFF</button></a>
          </p>
          <p class="state">State: %STATE%</p>
        </div>
      </div>
    </div>
  </body>
</html>

View raw code

Chúng ta sẽ không đi vào tìm hiểu kỹ nội dung các file HTML này vì nó nằm ngoài phạm vi của bài viết.

wifimanager.html

Trang web cấu hình WiFi cho WiFi Manager sẽ trông giống như sau:

Giao diện WiFi Manager
Giao diện WiFi Manager

Copy nội dung sau vào file wifimanager.html của bạn. File này bao gồm 1 form, 3 trường nhập dữ liệu và một 1 nút Submit để lưu dữ liệu.

<!DOCTYPE html>
<html>
<head>
  <title>ESP Wi-Fi Manager</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
  <div class="topnav">
    <h1>ESP Wi-Fi Manager</h1>
  </div>
  <div class="content">
    <div class="card-grid">
      <div class="card">
        <form action="/" method="POST">
          <p>
            <label for="ssid">SSID</label>
            <input type="text" id ="ssid" name="ssid"><br>
            <label for="pass">Password</label>
            <input type="text" id ="pass" name="pass"><br>
            <label for="ip">IP Address</label>
            <input type="text" id ="ip" name="ip" value="192.168.1.200"><br>
            <label for="gateway">Gateway Address</label>
            <input type="text" id ="gateway" name="gateway" value="192.168.1.1"><br>
            <input type ="submit" value ="Submit">
          </p>
        </form>
      </div>
    </div>
  </div>
</body>
</html>

File CSS

Copy nội dung sau vào file style.css của bạn.

html {
  font-family: Arial, Helvetica, sans-serif; 
  display: inline-block; 
  text-align: center;
}

h1 {
  font-size: 1.8rem; 
  color: white;
}

p { 
  font-size: 1.4rem;
}

.topnav { 
  overflow: hidden; 
  background-color: #0A1128;
}

body {  
  margin: 0;
}

.content { 
  padding: 5%;
}

.card-grid { 
  max-width: 800px; 
  margin: 0 auto; 
  display: grid; 
  grid-gap: 2rem; 
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}

.card { 
  background-color: white; 
  box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
}

.card-title { 
  font-size: 1.2rem;
  font-weight: bold;
  color: #034078
}

input[type=submit] {
  border: none;
  color: #FEFCFB;
  background-color: #034078;
  padding: 15px 15px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  width: 100px;
  margin-right: 10px;
  border-radius: 4px;
  transition-duration: 0.4s;
  }

input[type=submit]:hover {
  background-color: #1282A2;
}

input[type=text], input[type=number], select {
  width: 50%;
  padding: 12px 20px;
  margin: 18px;
  display: inline-block;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box;
}

label {
  font-size: 1.2rem; 
}
.value{
  font-size: 1.2rem;
  color: #1282A2;  
}
.state {
  font-size: 1.2rem;
  color: #1282A2;
}
button {
  border: none;
  color: #FEFCFB;
  padding: 15px 32px;
  text-align: center;
  font-size: 16px;
  width: 100px;
  border-radius: 4px;
  transition-duration: 0.4s;
}
.button-on {
  background-color: #034078;
}
.button-on:hover {
  background-color: #1282A2;
}
.button-off {
  background-color: #858585;
}
.button-off:hover {
  background-color: #252524;
} 

Thiết lập Web Server

Chương trình của chúng ta sẽ như sau:

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

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Search for parameter in HTTP POST request
const char* PARAM_INPUT_1 = "ssid";
const char* PARAM_INPUT_2 = "pass";
const char* PARAM_INPUT_3 = "ip";
const char* PARAM_INPUT_4 = "gateway";


//Variables to save values from HTML form
String ssid;
String pass;
String ip;
String gateway;

// File paths to save input values permanently
const char* ssidPath = "/ssid.txt";
const char* passPath = "/pass.txt";
const char* ipPath = "/ip.txt";
const char* gatewayPath = "/gateway.txt";

IPAddress localIP;
//IPAddress localIP(192, 168, 1, 200); // hardcoded

// Set your Gateway IP address
IPAddress localGateway;
//IPAddress localGateway(192, 168, 1, 1); //hardcoded
IPAddress subnet(255, 255, 0, 0);

// Timer variables
unsigned long previousMillis = 0;
const long interval = 10000;  // interval to wait for Wi-Fi connection (milliseconds)

// Set LED GPIO
const int ledPin = 2;
// Stores LED state

String ledState;

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

// Read File from SPIFFS
String readFile(fs::FS &fs, const char * path){
  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;
}

// Write file to SPIFFS
void writeFile(fs::FS &fs, const char * path, const char * message){
  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("- write failed");
  }
}

// Initialize WiFi
bool initWiFi() {
  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;
}

// Replaces placeholder with LED state value
String processor(const String& var) {
  if(var == "STATE") {
    if(digitalRead(ledPin)) {
      ledState = "ON";
    }
    else {
      ledState = "OFF";
    }
    return ledState;
  }
  return String();
}

void setup() {
  // Serial port for debugging purposes
  Serial.begin(115200);

  initSPIFFS();

  // Set GPIO 2 as an OUTPUT
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  
  // Load values saved in SPIFFS
  ssid = readFile(SPIFFS, ssidPath);
  pass = readFile(SPIFFS, passPath);
  ip = readFile(SPIFFS, ipPath);
  gateway = readFile (SPIFFS, gatewayPath);
  Serial.println(ssid);
  Serial.println(pass);
  Serial.println(ip);
  Serial.println(gateway);

  if(initWiFi()) {
    // Route for root / web page
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
      request->send(SPIFFS, "/index.html", "text/html", false, processor);
    });
    server.serveStatic("/", SPIFFS, "/");
    
    // Route to set GPIO state to HIGH
    server.on("/on", HTTP_GET, [](AsyncWebServerRequest *request) {
      digitalWrite(ledPin, HIGH);
      request->send(SPIFFS, "/index.html", "text/html", false, processor);
    });

    // Route to set GPIO state to LOW
    server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request) {
      digitalWrite(ledPin, LOW);
      request->send(SPIFFS, "/index.html", "text/html", false, processor);
    });
    server.begin();
  }
  else {
    // Connect to Wi-Fi network with SSID and password
    Serial.println("Setting AP (Access Point)");
    // NULL sets an open Access Point
    WiFi.softAP("ESP-WIFI-MANAGER", NULL);

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

    // Web Server Root URL
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
      request->send(SPIFFS, "/wifimanager.html", "text/html");
    });
    
    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()){
          // HTTP POST ssid value
          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());
          }
          // HTTP POST pass value
          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());
          }
          // HTTP POST ip value
          if (p->name() == PARAM_INPUT_3) {
            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());
          }
          // HTTP POST gateway value
          if (p->name() == PARAM_INPUT_4) {
            gateway = p->value().c_str();
            Serial.print("Gateway set to: ");
            Serial.println(gateway);
            // Write file to save value
            writeFile(SPIFFS, gatewayPath, gateway.c_str());
          }
          //Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
        }
      }
      request->send(200, "text/plain", "Done. ESP will restart, connect to your router and go to IP address: " + ip);
      delay(3000);
      ESP.restart();
    });
    server.begin();
  }
}

void loop() {

}

Giải thích chương trình

Các biến sau để lưu các thông tin về mạng WiFi như SSID, password, địa chỉ IP, và gateway sau khi người dùng điền vào form và nhấn nút submit.

// Search for parameter in HTTP POST request
const char* PARAM_INPUT_1 = "ssid";
const char* PARAM_INPUT_2 = "pass";
const char* PARAM_INPUT_3 = "ip";
const char* PARAM_INPUT_4 = "gateway";

Các biến ssid, pass, ip, và gateway dùng để lưu các giá trị nhận được.

//Variables to save values from HTML form
String ssid;
String pass;
String ip;
String gateway;

Đường dẫn chứa các file cấu hình WiFi trong file system của ESP32.

// File paths to save input values permanently
const char* ssidPath = "/ssid.txt";
const char* passPath = "/pass.txt";
const char* ipPath = "/ip.txt";
const char* gatewayPath = "/gateway.txt";

Các thông tin về gateway và submit. Bạn có thể bỏ qua nếu muốn các thông tin này sẽ được tự động lấy tùy theo từng mạng WiFi.

IPAddress localIP;
//IPAddress localIP(192, 168, 1, 200); // hardcoded

// Set your Gateway IP address
IPAddress localGateway;
//IPAddress localGateway(192, 168, 1, 1); //hardcoded
IPAddress subnet(255, 255, 0, 0);

initWiFi()

Hàm initWiFi() trả về giá trị boolean (đúng hoặc sai) tùy theo trạng thái kết nối WiFi thành công thay thất bại của ESP32.

bool initWiFi() {
  if(ssid=="" || ip==""){
    Serial.println("Undefined SSID or IP address.");
    return false;
  }

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

  if (!WiFi.config(localIP, gateway, 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;
}

Trước tiên, kiểm tra ssid và ip có rỗng không. Nếu thông tin là rỗng thì không thể kết nối với WiFi được và trả về false ngay.

if(ssid=="" || ip==""){

Nếu không rỗng, cho ESp32 kết nối với mạng WiFi có SSID và password đã lưu.

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

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

Nếu không thể kết nối WiFi trong 10 giây, trả về false.

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

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

Nếu kết nối thành công thì trả về true.

return true;

setup()

Trong hàm setup(), bắt đầu đọc các file chứa SSID, password, IP address, và gateway.

ssid = readFile(LittleFS, ssidPath);
pass = readFile(LittleFS, passPath);
ip = readFile(LittleFS, ipPath);
gateway = readFile (LittleFS, gatewayPath);

Nếu ESP32 kết nối WiFi thành công (hàm initWiFi() trả về true), bắt đầu xử lý các yêu cầu gửi đến web server:

if(initWiFi()) {
  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(SPIFFS, "/index.html", "text/html", false, processor);
  });
  server.serveStatic("/", SPIFFS, "/");
    
  // Route to set GPIO state to HIGH
  server.on("/on", HTTP_GET, [](AsyncWebServerRequest *request) {
    digitalWrite(ledPin, HIGH);
    request->send(SPIFFS, "/index.html", "text/html", false, processor);
  });

  // Route to set GPIO state to LOW
  server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request) {
    digitalWrite(ledPin, LOW);
    request->send(SPIFFS, "/index.html", "text/html", false, processor);
  });
  server.begin();
}

Nếu kết nối bị thất bại, hàm initWiFi() trả về false, ESP32 sẽ bật mode access point và phát ra một mạng WiFi có tên là ESP-WIFI-MANAGER không có password bằng hàm softAP():

else {
  // Connect to Wi-Fi network with SSID and password
  Serial.println("Setting AP (Access Point)");
  // NULL sets an open Access Point
  WiFi.softAP("ESP-WIFI-MANAGER", NULL);

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

Khi chúng ta sử dụng máy tính hoặc điện thoại kết nối vào mạng WiFi này và truy cập địa chỉ 192.168.4.1, ESP32 sẽ hiển thị trang web để cấu hình mạng WiFibằng file wifimanager.html file.

// Web Server Root URL
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(SPIFFS, "/wifimanager.html", "text/html");
});

Chúng ta cũng cần xử lý yêu cầu khi người dùng điền form và nhấn nút Submit để lưu thông tin WiFi là lưu xuống các file có tên tương ứng.

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()){
      // HTTP POST ssid value
      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());
      }
      // HTTP POST pass value
      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());
      }
      // HTTP POST ip value
      if (p->name() == PARAM_INPUT_3) {
        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());
      }
     // HTTP POST gateway value
      if (p->name() == PARAM_INPUT_4) {
        gateway = p->value().c_str();
        Serial.print("Gateway set to: ");
        Serial.println(gateway);
        // Write file to save value
        writeFile(SPIFFS, gatewayPath, gateway.c_str());
      }
      //Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
    }
  }

Sau khi submit form, ESP32 xuất ra mã code 200 báo hiệu xử lý thành công và dòng thông báo cho người dùng biết kết quả:

request->send(200, "text/plain", "Done. ESP will restart, connect to your router and go to IP address: " + ip);

Sau 3 giây, ESP32 khởi động lại bằng hàm ESP.restart():

delay(3000);
ESP.restart();

Uploading Code và các file dữ liệu

Upload các file trong thư mục data vào ESP32bằng menu Tools ESP32 Sketch Data Upload.

Upload code cho dự án WiFi Manager trên ESP32
Upload code cho dự án WiFi Manager trên ESP32

Sau khi upload các file data thành công, bạn hãy upload chương trình vào board ESP32.

Kết quả

Sau khi upload thành công các file data và chương trình, bạn mở cửa sổ Serial Monitor và quan sát kết quả khi chạy lần đầu và sau khi cấu hình WiFi.

Kết quả dự án WiFi Manager với ESP32 trên Serial Monitor
Kết quả dự án WiFi Manager với ESP32 trên Serial Monitor

Kết nối máy tính hoặc điện thoại vào mạng WiFi Access Point của ESP32 để cấu hình WiFi

Cấu hình WiFi Manager

Mở trình duyệt và truy cập 192.168.4.1. Trang web cấu hình WiFi sẽ hiện ra.

Website để cấu hình WiFi Manager bằng ESP32
Website để cấu hình WiFi Manager bằng ESP32

Nhập thông tin như: SSID và Password và địa chỉ IP address.

Nhập thông tin WiFi Manager
Cấu hình WiFi Manager

Sau khi ESP32 reset và kết nối WiFi thành công, bạn có thể mở trang web với địa chỉ IP của ESP32 để điều khiển đèn LED trên board:

Cấu hình WiFi Manager
Cấu hình WiFi Manager

Lời kết

Bài viết trên đã hướng dẫn bạn cách cấu hình WiFi Manager để phục vụ cho các dự án Web Server cùng mạch ESP. Với hướng dẫn trên, bạn có thể dễ dàng kết nối mạch ESP tới bất kỳ mạng nào. Chúc các bạn thành công!

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *