ESP32 Client-Server Wi-Fi Communication Between Two Boards

This guide shows how to setup an HTTP communication between two ESP32 boards to exchange data via Wi-Fi without an internet connection (router). In simple words, you’ll learn how to send data from one board to the other using HTTP requests. The ESP32 boards will be programmed using Arduino IDE.

ESP32 Client-Server Wi-Fi Communication Between Two Boards

For demonstration purposes, we’ll send BME280 sensor readings from one board to the other. The receiver will display the readings on an OLED display.

If you have an ESP8266 board, you can read this dedicated guide: ESP8266 NodeMCU Client-Server Wi-Fi Communication.

Watch the Video Demonstration

To see how the project works, you can watch the following video demonstration:

Project Overview

One ESP32 board will act as a server and the other ESP32 board will act as a client. The following diagram shows an overview of how everything works.

ESP32 Client-Server Wi-Fi Communication Between Project overview
  • The ESP32 server creates its own wireless network (ESP32 Soft-Access Point). So, other Wi-Fi devices can connect to that network (SSID: ESP32-Access-Point, Password: 123456789).
  • The ESP32 client is set as a station. So, it can connect to the ESP32 server wireless network.
  • The client can make HTTP GET requests to the server to request sensor data or any other information. It just needs to use the IP address of the server to make a request on a certain route: /temperature, /humidity or /pressure.
  • The server listens for incoming requests and sends an appropriate response with the readings.
  • The client receives the readings and displays them on the OLED display.

As an example, the ESP32 client requests temperature, humidity and pressure to the server by making requests on the server IP address followed by /temperature, /humidity and /pressure, respectively.

The ESP32 server is listening on those routes and when a request is made, it sends the corresponding sensor readings via HTTP response.

Parts Required

Parts required for ESP32 Client-Server Communication

For this tutorial, you need the following parts:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Installing Libraries

For this tutorial you need to install the following libraries:

Asynchronous Web Server Libraries

We’ll use the following libraries to handle HTTP request:

These libraries are not available to install through the Library Manager. So, you need to unzip the libraries and move them to the Arduino IDE installation libraries folder.

Alternatively, you can go to Sketch Include Library > Add .ZIP library… and select the libraries you’ve just downloaded.

You may also like: Build an Asynchronous Web Server with the ESP32

BME280 Libraries

The following libraries can be installed through the Arduino Library Manager. Go to Sketch Include LibraryManage Libraries and search for the library name.

You may also like: Interface BME280 with ESP32 (Guide)

I2C SSD1306 OLED Libraries

To interface with the OLED display you need the following libraries. These can be installed through the Arduino Library Manager. Go to Sketch > Include Library> Manage Libraries and search for the library name.

You may also like: I2C SSD1306 OLED Display with ESP32 (Guide)

#1 ESP32 Server (Access Point)

ESP32 Server with BME280 temperature humidity pressure

The ESP32 server is an Access Point (AP), that listens for requests on the /temperature, /humidity and /pressure URLs. When it gets requests on those URLs, it sends the latest BME280 sensor readings.

For demonstration purposes, we’re using a BME280 sensor, but you can use any other sensor by modifying a few lines of code.

Schematic Diagram

Wire the ESP32 to the BME280 sensor as shown in the following schematic diagram.

ESP32 BME280 Wiring Schematic Diagram
BME280ESP32
VIN/VCC3.3V
GNDGND
SCLGPIO 22
SDAGPIO 21

Arduino Sketch for #1 ESP32 Server

Upload the following code to your board.

/*
  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.
*/

// 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(){
  
}

View raw code

How the code works

Start by including the necessary libraries. Include the WiFi.h library and the ESPAsyncWebServer.h library to handle incoming HTTP requests.

#include "WiFi.h"
#include "ESPAsyncWebServer.h"

Include the following libraries to interface with the BME280 sensor.

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

In the following variables, define your access point network credentials:

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

We’re setting the SSID to ESP32-Access-Point, but you can give it any other name. You can also change the password. By default, its set to 123456789.

Create an instance for the BME280 sensor called bme.

Adafruit_BME280 bme;

Create an asynchronous web server on port 80.

AsyncWebServer server(80);

Then, create three functions that return the temperature, humidity, and pressure as String variables.

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);
}

In the setup(), initialize the Serial Monitor for demonstration purposes.

Serial.begin(115200);

Set your ESP32 as an access point with the SSID name and password defined earlier.

WiFi.softAP(ssid, password);

Then, handle the routes where the ESP32 will be listening for incoming requests.

For example, when the ESP32 server receives a request on the /temperature URL, it sends the temperature returned by the readTemp() function as a char (that’s why we use the c_str() method.

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

The same happens when the ESP receives a request on the /humidity and /pressure URLs.

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());
});

The following lines initialize the BME280 sensor.

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);
}

Finally, start the server.

server.begin();

Because this is an asynchronous web server, there’s nothing in the loop().

void loop(){

}

Testing the ESP32 Server

Upload the code to your board and open the Serial Monitor. You should get something as follows:

Testing the ESP32 Server Serial Monitor Arduino IDE

This means that the access point was set successfully.

Now, to make sure it is listening for temperature, humidity and pressure requests, you need to connect to its network.

In your smartphone, go to the Wi-Fi settings and connect to the ESP32-Access-Point. The password is 123456789.

ESP32 Server Access Point AP Connection

While connected to the access point, open your browser and type 192.168.4.1/temperature

You should get the temperature value in your browser:

ESP32 Server Access Point AP Test request temperature

Try this URL path for the humidity 192.168.4.1/humidity:

ESP32 Server Access Point AP Test request humidity

Finally, go to 192.168.4.1/pressure URL:

ESP32 Server Access Point AP Test request pressure

If you’re getting valid readings, it means that everything is working properly. Now, you need to prepare the other ESP32 board (client) to make those requests for you and display them on the OLED display.

#2 ESP32 Client (Station)

ESP32 Client Receive Readings via HTTP GET Request BME280

The ESP32 Client is a Wi-Fi station that is connected to the ESP32 Server. The client requests the temperature, humidity and pressure from the server by making HTTP GET requests on the /temperature, /humidity, and /pressure URL routes. Then, it displays the readings on an OLED display.

Schematic Diagram

Wire the ESP32 to the OLED display as shown in the following schematic diagram.

OLEDESP32
VIN/VCCVIN
GNDGND
SCLGPIO 22
SDAGPIO 21

Arduino Sketch for #2 ESP32 Client

Upload the following code to the other ESP32:

/*
  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) {
  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;
}

View raw code

How the code works

Include the necessary libraries for the Wi-Fi connection and for making HTTP requests:

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

Insert the ESP32 server network credentials. If you’ve changed the default network credentials in the ESP32 server, you should change them here to match.

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

Then, save the URLs where the client will be making HTTP requests. The ESP32 server has the 192.168.4.1 IP address, and we’ll be making requests on the /temperature, /humidity and /pressure URLs.

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 the libraries to interface with the OLED display:

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

Set the OLED display size:

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

Create a display object with the size you’ve defined earlier and with I2C communication protocol.

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

Initialize string variables that will hold the temperature, humidity and pressure readings retrieved by the server.

String temperature;
String humidity;
String pressure;

Set the time interval between each request. By default, it’s set to 5 seconds, but you can change it to any other interval.

const long interval = 5000; 

In the setup(), initialize the OLED display:

// 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);

Note: if your OLED display is not working, check its I2C address using an I2C scanner sketch and change the code accordingly.

Connect the ESP32 client to the ESP32 server network.

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: ");

In the loop() is where we make the HTTP GET requests. We’ve created a function called httpGETRequest() that accepts as argument the URL path where we want to make the request and returns the response as a String.

You can use the next function in your projects to simplify your code:

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;
}

We use that function to get the temperature, humidity and pressure readings from the server.

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

Print those readings in the Serial Monitor for debugging.

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

Then, display the temperature in the OLED display:

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");

The humidity:

display.setTextSize(2);
display.setCursor(0, 25);
display.print("H: ");
display.print(humidity);
display.print(" %"); 

Finally, the pressure reading:

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();

We use timers instead of delays to make a request every x number of seconds. That’s why we have the previousMillis, currentMillis variables and use the millis() function. We have an article that shows the difference between timers and delays that you might find useful (or read ESP32 Timers).

Upload the sketch to #2 ESP32 (client) to test if everything is working properly.

Testing the ESP32 Client

Having both boards fairly close and powered, you’ll see that ESP #2 is receiving new temperature, humidity and pressure readings every 5 seconds from ESP #1.

This is what you should see on the ESP32 Client Serial Monitor.

Testing the ESP32 Client Serial Monitor Arduino IDE

The sensor readings are also displayed in the OLED.

ESP32 Client Server Communication example data exchange for sensor readigns

That’s it! Your two boards are talking with each other.

ESP32 to ESP32 Talking with each other with Wireless Communication

Wrapping Up

In this tutorial you’ve learned how to send data from one ESP32 to another ESP32 board via Wi-Fi using HTTP requests without the need to connect to the internet. For demonstration purposes, we’ve shown how to send BME280 sensor readings, but you can use any other sensor or send any other data. Other recommended sensors:

We have a similar tutorial for the ESP8266 that you might find useful:

We hope you’ve found this tutorial useful. We’re preparing more tutorials like these. So, stay tuned and subscribe to our blog!

Thanks for reading.


Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »

Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »


Enjoyed this project? Stay updated by subscribing our weekly newsletter!

28 thoughts on “ESP32 Client-Server Wi-Fi Communication Between Two Boards”

  1. Have you done anything that has successfully sets the host node name – I am having trouble setting the host on my project it seem to default to espessif when I look at my router and the devices connected. I have use the SetHostName in the Arduino IDE without success. I would like to “ping” the name of the devices… I am using the ESP-WROOM-32 chipset and Arduino IDE on a custom board. I would like unique names…

    Reply
    • Gregg, I believe there is a bug in the ESP32 That causes it not to send the hostname correctly. There was some chat about this on the ESP32 Gitter forum

      Reply
  2. Great project. Would you consider taking on a bit larger project sample?

    I’d like to implement a network of several room temperature samplers around my house that would feed to (or be polled by) a central server that could then use logic to send to other ESP32s that control heating duct valves or fans to direct warm air to colder rooms. Taking this a step further would include maintaining a database of temperature samples to be able to report, room by room, heating requirements.

    Such a system could also be adapted to sample soil dampness and intelligently control segmented sprinklers so as to avoid wasted watering during the summer. I’m sure it could be a generic control system for many applications.

    Thanks…..Paul

    Reply
  3. Why are you using async webserver for synchronous communication?
    In my projects.. for instance OPC UA protocol i am using normal synchronous webserver without any problem. I can read multiple values in POST or GET request without any problem, also send reply to client JSON or OPC UA (Soap) encoded.

    Reply
  4. Hi Rui and Sara

    Another great video .Do you have an idea baout the maximum distance possible between the two ESP’s ?

    Rgds

    Domingos

    Reply
  5. Thanks for a nice tutorial. It would be interesting to implement this kind of project using the ESP-NOW protocol (and maybe also ESP-MESH), which is peer-peer (no access point required), lower power from wakeup, and supposedly longer distances. Maybe something for the future?

    Reply
  6. Love this project and would like to use it to measure tide in the salt water canal outside our home.

    What is the expected nominal range between server and client?

    I am struggling with what sensor to use to measure tidal height. A simple sonic range finder will measure distance to top of the water but it is not resistant to salt water micro spray in the atmosphere. Any housing seems to interfere with the sonic signal. Any ideas?

    You two are great, keep it up.

    Reply
    • Hi Jim.
      With the built-in ESP32 antenna, we got a stable communication between boards up to 52 meters (approximately 164ft) in open field without obstacles.
      As for the sensor, you really have to experiment and see what’s the best sensor for your case.
      Thank you for your nice words 😀
      Regards,
      Sara

      Reply
  7. Hi,
    Thanks for this project. I can’t wait to try it. However, I have a few ESP01’s that are currently unused, is it possible to run this on an ESP8266 platform?
    Thanks
    Jos

    Reply
  8. Hello, This is very interesting, Congratuletion… another Question, can you tell me if you have any tutotial about IPS(Indoor position System).

    Best Regards.

    Ramón Reyes

    Reply
  9. Excellent project! Is it possible to have multiple clients attached to one ESP32 server?

    I’m envisioning an application with multiple remote clients all connected to one server. 🙂

    thanks much,
    ben

    Reply

Leave a Comment

Download our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.