ESP32 WiFi – Giao tiếp giữa 2 ESP32 với nhau

Trong hướng dẫn này chúng ta sẽ nói về chủ đề ESP32 WiFi. Cụ thể, mình sẽ hướng dẫn bạn cách thiết lập giao tiếp HTTP giữa hai mạch ESP32 nhằm trao đổi dữ liệu qua WiFi, mà không cần đến các bộ định tuyến kết nối Internet. Nói cách khác, bạn sẽ học được cách gửi dữ liệu từ mạch này sang mạch khác bằng các yêu cầu HTTP. 

Chúng ta sẽ sử dụng Aduino IDE để lập trình các mạch ESP32. Chúng ta sẽ thực hành một dự án sau: 

  • Gửi kết quả đọc dữ liệu cảm biến từ mạch A sang mạch B
  • Mạch B nhận được thông tin sẽ hiển thị kết quả lên màn hình OLED

Tổng quan về ESP32 WiFi và dự án

Để giao tiếp giữa 2 ESP32, chúng ta sẽ cần hai mạch ESP32 khác nhau: một mạch đóng vai trò là Server (máy chủ) và mạch còn lại đóng vai trò là máy khách (Client). 

Đây là sơ đồ cách hai mạch hoạt động với nhau:

ESP32 WiFi - Giao tiếp giữa 2 ESP32
ESP32 WiFi – Giao tiếp giữa 2 ESP32
  • ESP32 Server tạo một mạng không dây riêng, các thiết bị WiFi khác có thể kết nối với mạng đó (thông tin mạng: SSID: ESP32-Access-Point, Mật khẩu: 123456789).
  • ESP32 Client là một trạm, chúng có thể kết nối với mạng không dây của ESP32 Server
  • ESP32 Client có thể thực hiện các yêu cầu HTTP GET tới máy chủ để lấy các dữ liệu từ cảm biến hoặc bất kỳ thông tin nào khác. Các thiết bị này sẽ dùng địa chỉ IP của Server để thực hiện yêu cầu trên một route nhất định, ví dụ như /pressure, /humidity hoặc là /temperature
  • Sever nhận các yêu cầu gửi đến và gửi kết quả tương ứng dựa trên thông tin đọc được từ cảm  biến
  • Client nhận được kết quả và hiển thị chúng trên màn hình OLED

Ví dụ cụ thể: ESP32 Client cần các thông tin về nhiệt độ độ ẩm và áp suất. Chúng sẽ gửi yêu cầu đến máy chủ bằng cách tạo ra các yêu cầu tương ứng trên IP máy chủ như /pressure, /humidity và /temperature.

Server sẽ nhận các yêu cầu này và gửi kết quả dữ liệu đọc được từ cảm biến thông qua phản hồi HTTP.

Chuẩn bị

Để thực hành dự án này, bạn cần chuẩn bị một số thiết bị điện tử sau:

  • Mạch ESP32
  • Cảm biến BME280
  • Màn hình OLED
  • Dây jumper
  • Breaboard

Cài đặt thư viện

Để thực hành dự án chủ đề ESP32 WiFi này, bạn cần cài đặt các thư viện sau:

Thư viện Asynchronous Web Server

Chúng ta sẽ sử dụng các thư viện sau để xử lý yêu cầu HTTP: 

Các thư viện này không có sẵn trong trình quản lý thư viện của phần mềm. Do đó, bạn phải tải về từ liên kết trên, giải nén và di chuyển chúng vào thư mục thư viện cài đặt trong Arduino IDE nhé!

Một cách khác là bạn truy cập vào Sketch > Include Library > Add .ZIP library… và chọn thư viện bạn vừa mới tải về.

Thư viện BME280

Bạn có thể cài thư viện này thông qua trình quản lý thư viện của Arduino, bằng cách truy cập Sketch > Include Library> Manage Libraries và tìm kiếm tên thư viện sau:

  • Adafruit_BME280 library
  • Adafruit unified sensor library

Thư viện OLED I2C SSD1306

Để làm việc với màn hình OLED, bạn cần tải các thư viện bên dưới, bằng cách truy cập Sketch > Include Library> Manage Libraries và tìm tên thư viện:

  • Adafruit SSD1306
  • Adafruit GFX Library

Thư viện xong chúng ta hãy tiến hành làm việc với ESP32 WiFi để thực hiện dự án nhé!

ESP32 Server (Điểm truy cập)

Ta có thể nói, ESP32 Server là một điểm truy cập (Access Point – AP), có vai trò nhận các yêu cầu từ URL /temperature, /humidity and /pressure. Khi nhận được yêu cầu trên các URL đó, Server sẽ gửi trả về kết quả đọc được mới nhất từ cảm biến BME280.

Trong hướng dẫn chủ đề ESP32 WiFi này, mình sử dụng cảm biến BME280 để minh họa. Nhưng bạn có thể sử dụng bất kỳ cảm biến nào bạn thích với cách xây dựng chương trình tương tự.

Kết nối phần cứng

Kết nối cảm biến BME280 vào mạch ESP32 như sau:

ESP32 WiFi - Kết nối phần cứng ESP32 Server
ESP32 WiFi – Kết nối phần cứng ESP32 Server

Lưu ý: Bạn kết nối các chân theo thứ tự sau:

BME280ESP32
VIN/VCC3,3V
GNDGND
SCLGPIO 22
SDAGPIO 21

Chương trình lập trình

Bạn hãy nạp đoạn chương trình sau vào mạch ESP32 của bạn:

// Import required libraries
#include "WiFi.h"
#include "ESPAsyncWebServer.h"

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// Set your access point network credentials
const char* ssid = "ESP32-Access-Point";
const char* password = "123456789";

/*#include <SPI.h>
#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/

Adafruit_BME280 bme; // I2C
//Adafruit_BME280 bme(BME_CS); // hardware SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI

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

String readTemp() {
  return String(bme.readTemperature());
  //return String(1.8 * bme.readTemperature() + 32);
}

String readHumi() {
  return String(bme.readHumidity());
}

String readPres() {
  return String(bme.readPressure() / 100.0F);
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);
  Serial.println();
  
  // Setting the ESP as an access point
  Serial.print("Setting AP (Access Point)…");
  // Remove the password parameter, if you want the AP (Access Point) to be open
  WiFi.softAP(ssid, password);

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

  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readTemp().c_str());
  });
  server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readHumi().c_str());
  });
  server.on("/pressure", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readPres().c_str());
  });
  
  bool status;

  // default settings
  // (you can also pass in a Wire library object like &Wire2)
  status = bme.begin(0x76);  
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
  
  // Start server
  server.begin();
}
 
void loop(){
  
}

Dưới đây, mình sẽ giải thích chi tiết cách chương trình hoạt động:

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

Đầu tiên, chúng ta sẽ khai báo các thư viện cần dùng:

  1. Thư viện xử lý các yêu cầu HTTP
#include "WiFi.h"
#include "ESPAsyncWebServer.h"
  1. Thư viện để làm việc với cảm biến BME280
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

Cùng xác thực điểm truy cập mạng không dây của mình bằng các biến sau:

const char* ssid = "ESP32-Access-Point";
const char* password = "123456789";

Như dòng lệnh trang, mình đang đặt SSID thành ESP32-Access-Point. Tuy nhiên, bạn có thể đặt chúng thành tên nào khác bất kỳ mà bạn thích. Tương tự, bạn cũng có thể đổi mật khẩu WiFi thành mật khẩu có độ khó cao hơn, thay vì sử dụng chuỗi số từ 1 đến 9 như của mình.

Tạo một phiên bản cho cảm biến BME280 với tên bme:

Adafruit_BME280 bme;

Tạo một máy chủ Web không đồng bộ (Asynchronous web Server) ở cổng 80:

AsyncWebServer server(80);

Sau đó, tạo 3 hàm để trả về các kết quả nhiệt độ, độ ẩm, áp suất dưới dạng biến kiểu chuỗi (String variable)

String readTemp() {
  return String(bme.readTemperature());
  //return String(1.8 * bme.readTemperature() + 32);
}

String readHumi() {
  return String(bme.readHumidity());
}

String readPres() {
  return String(bme.readPressure() / 100.0F);
}

Bên trong setup(), khởi tạo một Serial Monitor để theo dõi:

Serial.begin(115200);

Thiết lập mạch ESP32 làm điểm truy cập mạng với tên SSID và mật khẩu mà bạn đã chọn trước đó:

WiFi.softAP(ssid, password);

Sau đó, chúng ta sẽ xử lý các route mà ESP32 Server nhận các yêu cầu gửi từ Client.

Ví dụ, khi nhận được yêu cầu từ /temperature URL, ESP32 Server sẽ gửi nhiệt độ trả về từ hàm readTemp() dưới dạng ký tự (đó là lý do chúng ta cần dùng đến c_str())

server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/plain", readTemp().c_str());
});

Thực hiện tương tự với /humidity URL và /pressure URL:

server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/plain", readHumi().c_str());
});
server.on("/pressure", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/plain", readPres().c_str());
});

Khởi tạo cảm biến BME280:

bool status;

// default settings
// (you can also pass in a Wire library object like &Wire2)
status = bme.begin(0x76);
if (!status) {
  Serial.println("Could not find a valid BME280 sensor, check wiring!");
  while (1);
}

Cuối cùng, chúng ta khởi động máy chủ:

server.begin();

Vì đây là máy chủ Web không đồng bộ, nên trong vòng lặp loop() sẽ không có câu lệnh nào:

void loop(){

}

Kiểm tra máy chủ

Bạn hãy nạp chương trình vào mạch ESP32 của bạn và mở Serial Monitor để theo dõi. Bạn sẽ thấy màn hình hiển thị kết quả như sau:

Kết quả hiển thị trên Serial Monitor
Kết quả hiển thị trên Serial Monitor

Điều này đồng nghĩa với việc bạn đã thiết lập điểm truy cập cho ESP32 Server thành công! 

Bây giờ, để xác định rằng Server đang trong trạng thái có thể nhận các yêu cầu về nhiệt độ, độ ẩm và áp suất, bạn cần kết nối với mạng của nó.

Để thực hiện, bạn chỉ cần mở điện thoại, vào mục WiFi và kết nối với mạng WiFi có tên là ESP32-Access-Point và nhấn kết nối. Mật khẩu WiFi là 123456789 như chúng ta đã đặt.

Kết nối với ESP32 WiFi qua điện thoại
Kết nối với ESP32 WiFi qua điện thoại134

Sau khi kết nối WiFi xong, bạn hãy mở trình duyệt và rõ đường URL sau: 192.168.4.1/temperature

Sau đó, trên màn hình điện thoại sẽ hiển thị giá trị nhiệt độ như sau:

Giá trị nhiệt độ hiển thị - ESP32 WIFi

Tương tự, bạn có thể truy cập vào URL sau để biết độ ẩm là bao nhiêu: 192.168.4.1/humidity

Hiển thị giá trị độ ẩm - ESP32 WIFi

URL để xem giá trị áp suất: 192.168.4.1/pressure

Hiển thị giá trị áp suất - ESP32 WIFi

Nếu kết quả hiển thị trên màn hình đều hợp lệ (như các hình trên) điều đó có nghĩa là Server đang hoạt động bình thường. 

Bây giờ, chúng ta hãy chuyển sang thiết lập cho ESP32 Client để gửi các yêu cầu đó và hiển thị kết quả trên màn hình OLED nhé!

ESP32 Client (Máy trạm)

ESP32 Client là trạm WiFi được kết nối với ESP32 Server. Client gửi yêu cầu HTTP GET trên các URL /temperature, /humidity, và /pressure đến máy chủ để nhận thông tin, sau đó hiển thị lên màn hình OLED.

Cấu hình ESP32 Client - Dự án chủ đề ESP32 WiFi
Cấu hình ESP32 Client – Dự án chủ đề ESP32 WiFi

Kết nối phần cứng

Kết nối các thiết bị như hình dưới:

Kết nối phần cứng ESP32 Client - Dự án chủ đề ESP32 WiFi

Lưu ý: Bạn nên nối các chân theo thứ tự sau:

OLEDESP32
VIN/VCCĐẾN
GNDGND
SCLGPIO 22
SDAGPIO 21

Chương trình lập trình

Bạn hãy nạp đoạn chương trình sau vào mạch ESP32 Client của bạn:

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-client-server-wi-fi/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <WiFi.h>
#include <HTTPClient.h>

const char* ssid = "ESP32-Access-Point";
const char* password = "123456789";

//Your IP address or domain name with URL path
const char* serverNameTemp = "http://192.168.4.1/temperature";
const char* serverNameHumi = "http://192.168.4.1/humidity";
const char* serverNamePres = "http://192.168.4.1/pressure";

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

String temperature;
String humidity;
String pressure;

unsigned long previousMillis = 0;
const long interval = 5000; 

void setup() {
  Serial.begin(115200);
  
  // Address 0x3C for 128x64, you might need to change this value (use an I2C scanner)
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }
  display.clearDisplay();
  display.setTextColor(WHITE);
  
  WiFi.begin(ssid, password);
  Serial.println("Connecting");
  while(WiFi.status() != WL_CONNECTED) { 
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  unsigned long currentMillis = millis();
  
  if(currentMillis - previousMillis >= interval) {
     // Check WiFi connection status
    if(WiFi.status()== WL_CONNECTED ){ 
      temperature = httpGETRequest(serverNameTemp);
      humidity = httpGETRequest(serverNameHumi);
      pressure = httpGETRequest(serverNamePres);
      Serial.println("Temperature: " + temperature + " *C - Humidity: " + humidity + " % - Pressure: " + pressure + " hPa");
      
      display.clearDisplay();
      
      // display temperature
      display.setTextSize(2);
      display.setTextColor(WHITE);
      display.setCursor(0,0);
      display.print("T: ");
      display.print(temperature);
      display.print(" ");
      display.setTextSize(1);
      display.cp437(true);
      display.write(248);
      display.setTextSize(2);
      display.print("C");
      
      // display humidity
      display.setTextSize(2);
      display.setCursor(0, 25);
      display.print("H: ");
      display.print(humidity);
      display.print(" %"); 
      
      // display pressure
      display.setTextSize(2);
      display.setCursor(0, 50);
      display.print("P:");
      display.print(pressure);
      display.setTextSize(1);
      display.setCursor(110, 56);
      display.print("hPa");
           
      display.display();
      
      // save the last HTTP GET Request
      previousMillis = currentMillis;
    }
    else {
      Serial.println("WiFi Disconnected");
    }
  }
}

String httpGETRequest(const char* serverName) {
  WiFiClient client;
  HTTPClient http;
    
  // Your Domain name with URL path or IP address with path
  http.begin(client, serverName);
  
  // Send HTTP POST request
  int httpResponseCode = http.GET();
  
  String payload = "--"; 
  
  if (httpResponseCode>0) {
    Serial.print("HTTP Response code: ");
    Serial.println(httpResponseCode);
    payload = http.getString();
  }
  else {
    Serial.print("Error code: ");
    Serial.println(httpResponseCode);
  }
  // Free resources
  http.end();

  return payload;
}

Dưới đây, mình sẽ giải thích chi tiết cách chương trình hoạt động:

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

Khởi tạo các thư viện cần thiết để kết nối WiFi ESP32 Server và thực hiện các yêu cầu HTTP:

#include <WiFi.h>
#include <HTTPClient.h>

Chèn các thông tin liên quan đến ESP32 WiFi từ Server như tên WiFi và mật khẩu. Nếu bạn thay đổi mật khẩu hoặc tên WiFi của máy chủ, bạn cần phải chỉnh sửa lại ở mục này tương ứng:

#include <WiFi.h>
#include <HTTPClient.h>

Lưu các URL để ESP32 Client có thể thực hiện các yêu cầu HTTP. Vì ESP32 Server có địa chỉ IP là 192.168.4.1 nên chúng ta sẽ đưa ra các yêu cầu như sau:

const char* ssid = "ESP32-Access-Point";
const char* password = "123456789";

Khởi tạo thư viện để làm việc  với màn hình OLED:

const char* serverNameTemp = "http://192.168.4.1/temperature";
const char* serverNameHumi = "http://192.168.4.1/humidity";
const char* serverNamePres = "http://192.168.4.1/pressure";

Xác định kích thước màn hình OLED:

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Tạo một đối tượng display với kích cỡ bạn vừa xác định, sử dụng giao thức truyền thông I2C:

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

Khởi tạo các biến dạng chuỗi để chứa các thông tin về nhiệt độ, độ ẩm và áp suất:

String temperature;
String humidity;
String pressure;

Đặt một thời gian tạm nghỉ giữa mỗi yêu cầu. Thông thường, thời gian này là 5 giây. Tuy nhiên, bạn có thể đổi thành bất kỳ thời gian nào khác mà bạn thích.

const long interval = 5000; 

Bên trong setup(), bạn hãy khởi tạo màn hình OLED:

// Address 0x3C for 128x64, you might need to change this value (use an I2C scanner)
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
  Serial.println(F("SSD1306 allocation failed"));
  for(;;); // Don't proceed, loop forever
}
display.clearDisplay();
display.setTextColor(WHITE);

Lưu ý: Nếu màn hình OLED không hoạt động, bạn hãy thử kiểm tra lại địa chỉ I2C thông  qua I2C Scanner sketch và thay đổi chương trình tương ứng.

Kết nối ESP32 Client với ESP32 Server thông qua mạng WIFi:

WiFi.begin(ssid, password);
Serial.println("Connecting");
while(WiFi.status() != WL_CONNECTED) { 
  delay(500);
  Serial.print(".");
}
Serial.println("");
Serial.print("Connected to WiFi network with IP Address: ");

Bên trong vòng lặp loop(), chúng ta sẽ thực hiện các yêu cầu HTTP GET. Chúng ta sẽ sử dụng một hàm là HTTPGETRequest() gồm thông tin là đường dẫn URL nơi thực hiện yêu cầu và gửi trả kết quả dưới dạng chuỗi (String).

Bạn có thể sử dụng chức năng này trong dự án để làm chương trình được đơn giản hơn:

String httpGETRequest(const char* serverName) {
  HTTPClient http;
    
  // Your IP address with path or Domain name with URL path 
  http.begin(serverName);
  
  // Send HTTP POST request
  int httpResponseCode = http.GET();
  
  String payload = "--"; 
  
  if (httpResponseCode>0) {
    Serial.print("HTTP Response code: ");
    Serial.println(httpResponseCode);
    payload = http.getString();
  }
  else {
    Serial.print("Error code: ");
    Serial.println(httpResponseCode);
  }
  // Free resources
  http.end();

  return payload;
}

Lấy thông tin về nhiệt độ, độ ẩm và áp suất từ Server:

temperature = httpGETRequest(serverNameTemp);
humidity = httpGETRequest(serverNameHumi);
pressure = httpGETRequest(serverNamePres);

In kết quả  ra màn hình Serial Monitor để theo dõi và gỡ lỗi nếu có:

Serial.println("Temperature: " + temperature + " *C - Humidity: " + humidity + " % - Pressure: " + pressure + " hPa");

Hiển thị các thông tin ra màn hình OLED:

  1. Nhiệt độ
display.setTextSize(2);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.print("T: ");
display.print(temperature);
display.print(" ");
display.setTextSize(1);
display.cp437(true);
display.write(248);
display.setTextSize(2);
display.print("C");
  1. Độ ẩm
display.setTextSize(2);
display.setCursor(0, 25);
display.print("H: ");
display.print(humidity);
display.print(" %"); 

  1. Áp suất
display.setTextSize(2);
display.setCursor(0, 50);
display.print("P:");
display.print(pressure);
display.setTextSize(1);
display.setCursor(110, 56);
display.print("hPa");

display.display();

Chúng ta sử dụng bộ hẹn giờ thay vì sử dụng độ trễ để thực hiện yêu cầu cứ sau mỗi số giây nhất định. Đó là lý do chúng ta sử dụng biến previousMillis, currentMillis và hàm millis().

Hãy nạp chương trình trên vào mạch ESP32 Client của bạn và kiểm tra nhé!

Kiểm tra máy khách

Khi đặt 2 mạch ESP32 gần nhau và cấp nguồn cho chúng, bạn sẽ thấy mạch ESP32 Client đang  nhận các thông tin về nhiệt độ, độ ẩm và áp suất từ ESP32 Server và sau mỗi 5 giây thì thông tin này sẽ được cập nhật.

Trên đây là các thông tin hiển thị trên màn hình Serial Monitor của ESP32 Client:

Kiểm tra máy khách - hoàn thành dự án về ESP32 WiFi
Kiểm tra máy khách – hoàn thành dự án về ESP32 WiFi

Các thông tin từ cảm biến cũng được in ra trên màn hình OLED như sau:

ESP32 Wifi - Hiển thị thông tin trên OLED của ESP32 Client
ESP32 Wifi – Hiển thị thông tin trên OLED của ESP32 Client

Đây là hình chụp thực tế 2 mạch:

Hoàn thành dự án chủ đề ESP32 Wifi
Hoàn thành dự án chủ đề ESP32 Wifi

Kết luận

Trong hướng dẫn trên, IoTZone đã hướng dẫn chi tiết các kiến thức về ESP32 cho bạn. Cụ thể, chúng ta đã thực hành gửi dữ liệu từ một mạch ESP32 này đến mạch ESP32 khác qua WiFi bằng các yêu cầu HTTP mà không cần kết nối Internet. 

Bạn đã thực hiện được dự án trên chưa? Nếu gặp bất kỳ khó khăn gì, bạn có thể cmt ở bài viết bên dưới nhé!

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 *