• EUR€
  • £GBP
  • $USD

ESP32 Arduino HTTP server: controlling a relay remotely

DFRobot Mar 15 2018 597

In this ESP32 tutorial, we will check how to remotely control a relay using the Arduino core and the HTTP async web server libraries for the ESP32.


In this ESP32 tutorial, we will check how to remotely control a relay. To do it, we will use the HTTP async web server library, which will allow us to set a web server on the ESP32, listening for requests that will change the state of the relay.

For an introduction on how to control a relay using the ESP32, please consult this previous tutorial.

The relay is a device that can be used to control circuits powered by the mains. In this tutorial, we will only cover the ESP32 code and the electric diagram to control the state of the relay.

Please stay safe and don’t work with the mains if you don’t have experience with it, since it is dangerous.

The ESP32 board used for this tutorial was a NodeMCU


The electric diagram

The electric diagram needed for this tutorial is basically the same as the previous tutorial and is shown in figure 1. If you need more information about it, please consult the previous post which contains a more detailed explanation.


Figure 1 – Electric diagram for connecting the ESP32 to the relay board.

Depending on your ESP32 board, you may be able to power the relay from a 5 v digital pin that some boards have. If you are not sure if you can power the relay from your board, the best option is to use an external power supply. You can find very cheap 5 v power supplies for breadboards here.

Note that the code that we are using is agnostic to the type of actuator, as long as it can be controlled by a digital output pin. So, instead of controlling a relay, you can use it to control a LED or a motor, for example, as long you use the correct electronic schematic for the device to control.


The code

As mentioned in the introductory section, we will use the async HTTP web server libraries to set a web server on the ESP32 and to control the relay from a client over WiFi. You can check how to setup these libraries here.

So, the first part will be equal to what we have been doing in previous posts about the async web server. We include all the libraries and declare the global variables needed to connect the ESP32 to a WiFi network and to set the server.

#include <WiFi.h> #include <FS.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> const char* ssid = "yourNetworkName"; const char* password =  "yourNetworkPass"; AsyncWebServer server(80);

We will declare an additional global variable to hold the number of the digital pin that will control the relay. This way, we have a centralized place to change the pin number, which prevents having to go through all the code when we want to use a different pin.

int relayPin = 23;

Moving on to the Arduino setup, we will first set our GPIO as an output pin, so we can use it to control the relay. To do it, we call the Arduino pinMode function, which receives as first input the number of the pin we want to configure and as second a constant indicating the mode.

We want our pin to work as output, so we pass to the function the constant OUTPUT.

pinMode(relayPin, OUTPUT);

We will explicitly set the state of the pin to low, so we always know the state of the relay when our program starts running.

This is done by calling the Arduino digitalWrite function, which receives as input the pin number and the state. For the state we pass the constant LOW, so the pin is set to GND.

digitalWrite(relayPin, LOW);

Note that if we wanted to initialize the pin with a digital high value, we would pass the HIGH constant, so the pin would be set to VCC.

Followed by that we will open a serial connection and then connect the ESP to the WiFi network, using the credentials we previously declared as global variables. After the procedure finishes, we will print the local IP assigned to the device, so the client knows the address where it can reach the server.

Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) {    delay(1000);    Serial.println("Connecting to WiFi.."); } Serial.println(WiFi.localIP());

Once we finish the WiFi connection procedure, we need to declare the routes of our server.

In our case and to illustrate a slightly more complex API, we are going to have three routes for actuating on the relay: one for turning it on, one for turning it off and another to toggle it. Additionally, we will set a route to get the current state of the relay.

Note that this is one of the many options that we can follow to model our API. Another one could be having a single route taking a parameter that indicates the action to perform.

Nonetheless, one interesting pattern that we can take advantage of by using different routes is that their name can already reflect the action that will be performed, making it easier to understand for clients just by reading the URL.

Since we are going to update the state of a resource (the relay) that already exists in our server, we can follow an approach more aligned with the REST pattern and use a PATCH HTTP method [1].

Note that the HTTP method that should be used depends on what we consider a resource and if we are doing a partial or total update. Thus the use of the PATCH method may be arguable accordingly to what we consider a resource, but since we didn’t do that more conceptual analysis, the main point to highlight is that we should not use a GET method, since we are going to affect the state of the relay.

The first route we will declare will be used to turn off the relay. We will call it “/relay/off“.

In order to listen to PATCH methods, we need to pass the HTTP_PATCH enum value as second argument of the on method.

Remember from previous tutorials that we use the on method of the server object to bind a handling function to a route and that its second argument receives an enum that specifies the HTTP methods allowed on the route.

Our handling function will simply correspond to setting the state of the digital pin that controls the relay to low. This is done by calling the digitalWrite function with the same arguments we used in the setup function (the pin number and the constant LOW).

Note that before the mentioned operation, we return the answer to the client right away, so the actual execution of the command is performed after answering the client. This is a common pattern to avoid leaving the client hanging, specially when the commands may take some time to execute.

Naturally, this is an optimistically approach since we return an “OK” answer to the client before actually making sure that the command is successfully executed.

In our case there’s not much to fail and thus it is safe to follow this approach, but in more complex procedures we need to carefully analyze before going to this asynchronous command execution approach.

server.on("/relay/off", HTTP_PATCH, [](AsyncWebServerRequest *request){    request->send(200, "text/plain", "ok");    digitalWrite(relayPin, LOW); });

The next method will turn the relay on. Thus, following the previous pattern, the route will be “/relay/on”.

The method allowed on the route will again be PATCH and the handling function will set the pin to a high value.

server.on("/relay/on", HTTP_PATCH, [](AsyncWebServerRequest *request){    request->send(200, "text/plain","ok");    digitalWrite(relayPin, HIGH); });

The route for the toggle operation will be “/relay/toggle” and it will again listen to the PATCH method.

Our handling function will be similar to the previous ones, with the particularity that the new state of the pin will depend on the current one. So, to get the current state of the pin, we can call the Arduino digitalRead function, passing as input the number of the pin.

This will return the current state of the pin, which can be zero (LOW) or one (HIGH). You can confirm the values defined for these constants here.

So, we can obtain the next state by negating the current state with the “!” operator (logical not). Negating 1 will give 0 and negating 0 will give 1, which is the toggle we want.

Taking this in consideration, we can call the digitalRead function, apply the “!” operator and pass the result to the second argument of the digitalWrite function and the state of the pin will be toggled.

server.on("/relay/toggle", HTTP_PATCH, [](AsyncWebServerRequest *request){    request->send(200, "text/plain","ok");    digitalWrite(relayPin, !digitalRead(relayPin)); });

To finalize our route declaration, we will have an endpoint to return to the client the current state of the relay. It will be listening on the “/relay” route and HTTP GET methods.

To obtain the current state of the relay we use again the digitalRead function. Then, we convert the result to a string and return its value to the client.

Just as a comparison note regarding the previous commands, when the client asks for information to the server, the execution is usually synchronous so the client gets the result in the same request.

Alternative approaches may be triggering the information fetch asynchronously with a first request and then having the client polling for the result in subsequent requests.

In our case, our operation of fetching the relay state is really fast, so we opt for the simplest synchronous approach.

server.on("/relay", HTTP_GET, [](AsyncWebServerRequest *request){    request->send(200, "text/plain", String(digitalRead(relayPin))); });

To finalize, we need to call the begin method on our server object, so it starts listening to incoming requests. The final source code for this tutorial can be seen below and already includes this call.

#include <WiFi.h> #include <FS.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> const char* ssid = "yourNetworkName"; const char* password =  "yourNetworkPass"; AsyncWebServer server(80); int relayPin = 23; void setup(){  pinMode(relayPin, OUTPUT);  digitalWrite(relayPin, LOW);  Serial.begin(115200);  WiFi.begin(ssid, password);  while (WiFi.status() != WL_CONNECTED) {    delay(1000);    Serial.println("Connecting to WiFi..");  }  Serial.println(WiFi.localIP());  server.on("/relay/off", HTTP_PATCH, [](AsyncWebServerRequest *request){    request->send(200, "text/plain", "ok");    digitalWrite(relayPin, LOW);  });  server.on("/relay/on", HTTP_PATCH, [](AsyncWebServerRequest *request){    request->send(200, "text/plain","ok");    digitalWrite(relayPin, HIGH);  });  server.on("/relay/toggle", HTTP_PATCH, [](AsyncWebServerRequest *request){    request->send(200, "text/plain","ok");    digitalWrite(relayPin, !digitalRead(relayPin));  });  server.on("/relay", HTTP_GET, [](AsyncWebServerRequest *request){    request->send(200, "text/plain", String(digitalRead(relayPin)));  });  server.begin(); } void loop(){}


Testing the code

To test the code, simply compile it and upload it to your ESP32, after finishing all the electronic wiring needed.

After the procedure finishes, open the Arduino IDE serial monitor and wait for the device to connect to the WiFi network.

Once the connection is established, the local IP of the ESP32 on the network will be printed. Copy that IP.

In order to send commands to the ESP32, we need to use a tool such as Postman, which allows us to perform HTTP PATCH requests, amongst many other functionalities.

So, open Postman and put the following in the URL bar, changing #yourDeviceIp# by the IP you have just copied and #command# by one of the 3 commands we have defined.


Then, on the method dropdown of Postman, select PATCH and click the send button. You should get an output similar to figure 2, where we have sent a toggle command.

Figure 2 – Sending the toggle command using Postman.

You can also get the current state of the relay by sending a GET HTTP request to the server. For this, you don’t need Postman and can simply access the URL below on a web browser.


In the video below you can check a live demo of toggling the relay.