ESP32 Arduino: WebSerial

Introduction

In this tutorial we are going to learn how to use a web based Serial monitor for the ESP32, implemented by the WebSerial library.

Being able to output messages from a microcontroller is very useful for debugging. In the Arduino framework, one of the most common ways of achieving that is by using the UART of the microcontroller, which is typically connected to a serial-to-USB converter chip on the development board. Then, we just plug a USB cable between the device and a computer and we can see the messages on the Arduino IDE serial monitor.

Nonetheless, in cases where we are testing a WiFi based application, we might want to move to scenarios where the device is no longer connected to a computer but we still want to get debugging messages. As such, in a microcontroller like the ESP32, we can leverage the WiFi capabilities to establish the serial communication over it.

The library we are going to analyze on this post offers to us a very simple UI that allows to establish a web based serial connection with the ESP32. It allows both to send and receive messages to / from the ESP32. The Web UI is implemented with the Vue.js framework.

The UI is served by the ESP32, meaning that we don’t need any additional application and we only need a computer with a web browser connected to the same WiFi network. In order to be able to serve the file with the UI, the WebSerial library depends on the ESPAsyncWebServer library. For an introductory tutorial on the ESPAsyncWebServer library and how to install it, please check here.

The actual communication between the UI (rendered on a web browser) and the ESP32 is done using websockets, which are also managed under the hood by the ESPAsyncWebServer framework.

Although this tutorial covers the usage of the WebSerial library on the ESP32, the ESP8266 is also supported [1].

Getting started with WebSerial

We will start our code by the library includes:

  • WiFi.h: Needed to connect the ESP32 to a WiFi network;
  • ESPAsyncWebServer.h: Needed to create an AsyncWebServer instance, which is used under the hood by the WebSerial library;
  • WebSerial.h: Needed for all the WebSerial related functionalities. It will expose to us a variable called WebSerial, which is an object of class WebSerialClass.
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <WebSerial.h>

Then we will define two variables to hold the WiFi credentials: the network name (SSID) and the network password. I’m using placeholder strings which you should replace by the actual credentials of your network.

const char* ssid = "yourNetworkName";
const char* password =  "yourNetworkPassword";

We will also instantiate an object of class AsyncWebServer, which is used to setup a HTTP web server on the ESP32. As input, the constructor of this class receives the port where the server will be listening to incoming requests. We will be using port 80, which corresponds to the default HTTP port.

AsyncWebServer server(80);

Moving on to the Arduino setup, we will start by opening a wired serial connection. This is not needed for the WebSerial functionality but rather for us to print the IP address assigned to the ESP32 on the WiFi network. This IP address is needed for us to reach the ESP32 server.

Serial.begin(115200);

After this we will connect the ESP32 to the WiFi network, making use of the previously defined variables. After the connection is finished, we will print the IP address to the serial port.

WiFi.begin(ssid, password);
    
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
        Serial.println("Could not initialize WiFi");
        return;
}
    
Serial.println(WiFi.localIP());    

Now we are going to initialize the WebSerial instance with a call to the begin method. As input, this method receives the address of our AsyncWebServer object.

Note that this method also receives a second optional parameter to define the name of the HTTP server endpoint to access the WebSerial UI in our browser. If not specified, it defaults to “/webserial“. We will stay with the default value.

Under the hood, the begin method will take care of registering a new route on our server, which will be responsible for serving the webpage (HTML, CSS and JavaScript) with the WebSerial UI. Additionally, it will also setup a Websocket endpoint, which is used by the UI to communicate with the ESP32.

WebSerial.begin(&server);

To finalize the setup function, we will start our async server with a call to the begin method on the server object. Only after this call the server will start listening to incoming requests.

server.begin();

The complete setup function is shown below.

void setup() {
  
    Serial.begin(115200);

    WiFi.begin(ssid, password);
    
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
        Serial.println("Could not initialize WiFi");
        return;
    }
    
    Serial.println(WiFi.localIP());
    
    WebSerial.begin(&server);
    
    server.begin();
}

Now we will check our Arduino main loop. Note that since we are using the Async web server solution under the hood, we don’t need to periodically call any function to handle client connections. Instead, we are going to make use of the Arduino loop to periodically print content to our WebSerial interface, which should then appear on the Web browser UI.

Printing content to the WebSerial console is as easy as calling the Print or Println methods and passing as input the content we want to print, pretty much like we would do for a wired serial connection. You can consult here the overloaded definitions of these methods.

We are going to print a very simple “Hello World” message.

WebSerial.println("Hello World");

The complete loop can be seen below. We have added a delay of 1 second between each iteration of the loop, which should match the cadence of messages we see getting printed in the web console.

void loop() {
    WebSerial.println("Hello World");
    delay(1000);
}

The final code can be seen in the snippet below.

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <WebSerial.h>

const char* ssid = "yourNetworkName";
const char* password =  "yourNetworkPassword";

AsyncWebServer server(80);

void setup() {
  
    Serial.begin(115200);

    WiFi.begin(ssid, password);
    
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
        Serial.println("Could not initialize WiFi");
        return;
    }
    
    Serial.println(WiFi.localIP());
    
    WebSerial.begin(&server);
    
    server.begin();
}

void loop() {
    WebSerial.println("Hello World");
    delay(1000);
}

To test the end to end system, first compile and upload the previous code to your device using the Arduino IDE. When the procedure finishes, open the IDE serial monitor and wait for the WiFi connection to be established.

After that, you should see the IP address of the ESP32 getting printed to your serial monitor, like shown in figure 1. Copy it since we are going to need it to reach the server.

Local IP address of the ESP32 on the WiFi network.
Figure 1 – Local IP address of the ESP32 on the WiFi network.

After copying the IP address, open a web browser of your choice. Then, access the following URL:

http://YourESPIp/webserial

You should then see the UI illustrated in figure 2. The “Hello World” message should get printed every second, like we defined in the Arduino code.

WebSerial UI, showing the "Hello World" message.
Figure 2 – WebSerial UI, showing the “Hello World” message.

Note that the “Send” button is used to send data to the ESP32. Nonetheless, it won’t have any effect on this first example since we are not handling the received messages on the ESP32 side. We will learn how to do it on the next section.

To finish our example, we can confirm that the messages are being received through a websocket by opening the browser inspector, as shown in figure 3.

Inspecting the Websocket request that is used by WebSerial.
Figure 3 – Inspecting the Websocket request that is used by WebSerial.

We can further inspect that request and check the messages being received, like shown in figure 4 ( tested on Google Chrome).

Figure 4 – Messages received through the Websocket.

Handling Received data

In this section we will check how to handle data sent from the web console. Most of the code will be similar, except for the handling of the incoming messages.

So, we will focus our attention on the setup function, where we will add an extra line of code to register a callback function. This will function will be called every time we send data from the web console to the ESP32.

To register the callback, we only need to call the msgCallback method on the WebSerial object, passing as input the function. We will check its implementation below, but we will call it onMessage.

WebSerial.msgCallback(onMessage);

The callback function needs to follow a predefined signature: it returns void and it receives as input the following parameters:

  • A pointer to the received data buffer;
  • The length of the received data.
void onMessage(uint8_t *data, size_t len){
   // Implementation of the callback
}

The implementation will be very simple. We will iterate through the bytes of the received data and print them to the serial port.

for(int i=0; i < len; i++){
    Serial.print((char)data[i]);
}

You can check the complete callback below.

void onMessage(uint8_t *data, size_t len){

  Serial.print("Message received: ");
  for(int i=0; i < len; i++){
      Serial.print((char)data[i]);
  }
  Serial.println();
}

The complete code is shown below.

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <WebSerial.h>

const char* ssid = "yourNetworkName;
const char* password =  "yourNetworkPass";

AsyncWebServer server(80);

void onMessage(uint8_t *data, size_t len){

  Serial.print("Message received: ");
  for(int i=0; i < len; i++){
      Serial.print((char)data[i]);
  }
  Serial.println();
}

void setup() {
  
    Serial.begin(115200);

    WiFi.begin(ssid, password);
    
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
        Serial.println("Could not initialize WiFi");
        return;
    }
    
    Serial.println(WiFi.localIP());
    
    WebSerial.msgCallback(onMessage);
    WebSerial.begin(&server);
    
    server.begin();
}

void loop() {}

To test the code, once again compile it and upload it to your device. Then, go to the WebSerial console on a web browser, type some content in the text input and click the send button.

Like shown in figure 5, the messages sent from the web browser console should get printed in the Arduino IDE serial monitor.

Printing the WebSerial received messages to the Arduino IDE serial monitor.
Figure 5 – Printing the WebSerial received messages to the Arduino IDE serial monitor.

Suggested ESP32 readings

References

[1] https://github.com/ayushsharma82/WebSerial

Leave a Reply

%d bloggers like this: