ESP32 Async HTTP web server: websockets introduction

In this tutorial we will check how to setup a websocket endpoint on a HTTP web server, using the ESP32 and running the Arduino core. The tests were performed using a DFRobot’s ESP32 module integrated in a ESP32 development board.

 

Introduction

In this tutorial we will check how to setup a websocket endpoint on a HTTP web server, using the ESP32 and running the Arduino core.

In order to setup the server, we will use the ESP32 async HTTP web server libraries. For a detailed tutorial on how to install the libraries and how to get started using them, please consult this previous post. Also, in the “Related Posts” section at the end of this post, you can find more tutorials using these libraries.

Besides many other features, the mentioned HTTP web server libraries allow us to setup a websocket endpoint using their asynchronous websocket plugin.

Note that we have already covered some websocket functionalities on previous posts. Nonetheless, in those tutorials, we were using a library that only implements the websockets functionalities.

The advantage of using the HTTP asynchronous web server and its websockets plugin is that we can add the websockets functionality without using another listening service or port [1].

In order words, this means that we can setup a server that is able to handle both websocket clients and also regular HTTP clients.

The websocket plugin exposes a lot of functionalities but, in this introductory tutorial, we will start with something simple. So, in our code, we will define a handling function that will be executed when websocket related events occur, more precisely when a client connects or disconnects.

For the client implementation, we will develop a very simple Python program. It will connect to the websocket endpoint, wait for a message, print it and disconnect.

In order to have access to the websocket related functionality in Python we will need to install a module. The mentioned module is called websocket-client and it can be easily installed with pip (a Python package installer) using the command below:

pip install websocket-client

For this tutorial to work, both the ESP32 and the computer that will run the Python code need to be connected to the same WiFi network.

The present tutorial is based on this example from the HTTP asynchronous webserver libraries, which covers a lot more functionalities. This is a very good example which I encourage you to try.

The tests were performed using a DFRobot’s ESP32 module integrated in a ESP32 development board.


The Arduino code

Includes and global variables

The first thing we need to do is including some libraries. One is the WiFi.h library, which will allow us to connect the ESP32 to a WiFi network.

Additionally, we will need the ESPAsyncWebServer.h library, which will expose all the HTTP webserver and websocket functionality.

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

Since we will connect the ESP32 to a WiFi network, we need to store the credentials of that network, more precisely the network name and the password. We will save them in to global variables at the top of our code, so they are easy to change.

When testing the code, you should replace the placeholders below by the actual credentials of your WiFi network.

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

Next, we will need an object of class AsyncWebServer, which is needed to configure the HTTP asynchronous webserver.

Note that the constructor of this class receives as input the number of the port where the server will be listening for incoming HTTP requests. We will use port 80, which is the default HTTP port.

AsyncWebServer server(80);

Additionally, we will need an object of class AsyncWebSocket, which we will use to configure our websocket endpoint and corresponding handler function.

The constructor of this class receives as input the websocket endpoint. We will be listening on the “/ws” endpoint for this test, but you can try with another endpoint if you want, as long as you adapt the Python code accordingly.

AsyncWebSocket ws("/ws");


Setup

Moving on to the Arduino Setup function, we will start by opening a serial connection and connecting the ESP32 to the WiFi network. Remember that we have stored the WiFi network credentials as global variables.

Note that after the connection is established, we are printing the local IP assigned to the ESP32 on the network. The IP outputted here is the one that we need to use in the Python code, when specifying the websocket server address.

Serial.begin(115200);

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
 delay(1000);
 Serial.println("Connecting to WiFi..");
}

Serial.println(WiFi.localIP());

Next, we will bind our websocket endpoint to a handler function that will be executed when websocket related events occur.

We do this by calling the onEvent method on our AsyncWebSocket object. As input this method receives a function that needs to respect this signature (defined by the type AwsEventHandler).

Note that the signature contemplates a lot of parameters, but we will only used a few for this tutorial. We will cover the creation of this handling function below.

ws.onEvent(onWsEvent);

Next we need to register our AsyncWebSocket object in the asynchronous HTTP web server. We do this by calling the addHandler method on the AsyncWebServer object, passing as input the address of the AsyncWebSocket object.

server.addHandler(&ws);

Finally, we call the begin method on the AsyncWebServer object, in order for it to start listening to incoming requests.

server.begin();


Websocket handling function

As mentioned before, the websocket events handling function needs to follow a predefined function signature, specified by the AwsEventHandler type. The whole function signature with all the parameters is shown below.

void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
// handling code
}

For this introductory tutorial, we will only focus on the second and third arguments. The second argument is a pointer to an object of class AsyncWebSocketClient and it is the object we will use to send data back to the client, when it connects to our server.

The third argument corresponds to a variable of type AwsEventType, which is an enum. This enum contains all the websocket events that can trigger the execution of our handling function.

Thus, we use this third argument to check what event has occurred and handle it accordingly.

In our example, we will handle two event types: the connection and the disconnection of the websocket client. We can do this handling with some simple conditional blocks applied to the event type function parameter.

So, in case the event corresponds to the connection of a client, the enumerated value will be WS_EVT_CONNECT.

if(type == WS_EVT_CONNECT){
//websocket client connected
}

When we receive that event, we will simply print a message to the serial port indicating a new client has connected and then send a “hello world” message back to the client.

We can send the message to the client by calling the text method on our client object, passing as input the text to send. Note that since we have access to a pointer to the client object, then we need to use the -> operator in order to access the text method.

if(type == WS_EVT_CONNECT){

    Serial.println("Websocket client connection received");
    client->text("Hello from ESP32 Server");
}

For the client disconnection event, we receive the enumerated value WS_EVT_DISCONNECT, so we will have another condition looking for this. In this case, we will simply print a message to the serial port indicating the disconnection of the client.

} else if(type == WS_EVT_DISCONNECT){
    Serial.println("Client disconnected");
}

The full handling function can be seen below.

void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){

  if(type == WS_EVT_CONNECT){

    Serial.println("Websocket client connection received");
    client->text("Hello from ESP32 Server");

  } else if(type == WS_EVT_DISCONNECT){
    Serial.println("Client disconnected");

  }
}


The final Arduino code

The final Arduino source code can be seen below. Note that since this is a asynchronous solution, we don’t need to periodically call any client handling function in the Arduino main loop.

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

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

AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){

  if(type == WS_EVT_CONNECT){

    Serial.println("Websocket client connection received");
    client->text("Hello from ESP32 Server");

  } else if(type == WS_EVT_DISCONNECT){
    Serial.println("Client disconnected");

  }
}

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

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  Serial.println(WiFi.localIP());

  ws.onEvent(onWsEvent);
  server.addHandler(&ws);

  server.begin();
}

void loop(){}


The Python code

The Python code will be really simple. We start by importing the previously installed websocket module, so we can create the websocket client.

import websocket

Then, we need to create an object of class WebSocket, which is defined in the module we have just imported.

ws = websocket.WebSocket()

Then, we establish the connection to the server by calling the connect method on the WebSocket object.

This method receives as input a string with the websocket endpoint address, in the format below. You should change #ESP_IP# by the IP of your ESP32 (remember that it will be printed in the serial port when the Arduino program connects to the WiFi network) and #websocket_endpoint# by the server endpoint (in the Arduino code we have configured it to be “/ws”):

ws://#ESP_IP#/#websocket_endpoint#

You can check the method call with the endpoint for my ESP32. Note that the local IP assigned to your ESP32 will most likely be different from mine.

ws.connect("ws://192.168.1.78/ws")

Then, to receive data from the server, we simply call the recv method on our WebSocket object. This method will return the data received from the server, in case there is any data available. Otherwise, it will block waiting for the data.

After receiving the data, we will print it, to later check if it matches the message we specified in the ESP32 Arduino program.

result = ws.recv()
print(result)

To finalize, we call the close method on our WebSocket object to disconnect from the server.

ws.close()

The final complete code can be seen below.

import websocket

ws = websocket.WebSocket()
ws.connect("ws://192.168.1.78/ws")

result = ws.recv()
print(result)

ws.close()


Testing the code

To test the whole system, start by uploading the Arduino code to your ESP32 using the Arduino IDE. Once the procedure finishes, open the Arduino IDE serial monitor.

After the ESP32 successfully connects to the WiFi network, it should print the local IP assigned to it. That is the IP that you should use on the Python code, when specifying the websocket server address.

After this, simply run the Python code. As indicated in figure 1, it should output the message we have specified in the Arduino code. Note that the Python client immediately disconnects after receiving the message.

ESP32 Async HTTP web server web socket From Python client.png

Figure 1 – Output of the Python program.

If you go back to the Arduino IDE serial monitor, you should get the an output similar to figure 2, which shows that both the client connection and disconnection events were detected.

ESP32 async webserver websocket detect python client connect disconnect events.png

Figure 2 – Websocket client connection and disconnection events detected on the ESP32.


Related Posts

 

References

[1] https://github.com/me-no-dev/ESPAsyncWebServer#async-websocket-plugin

26 thoughts on “ESP32 Async HTTP web server: websockets introduction”

  1. Pingback: ESP32 websocket server: receiving binary frames – techtutorialsx

  2. Pingback: ESP32 websocket server: receiving binary frames – techtutorialsx

  3. Pingback: ESP32 Websocket server: Sending binary frame to client – techtutorialsx

  4. Pingback: ESP32 Websocket server: Sending binary frame to client – techtutorialsx

    1. Hi!

      You’re welcome 🙂

      I haven’t tested but my expectation is that if we create more AsyncWebSocket objects with different endpoints and register them on the server, that it should work.

      At least, it makes sense to work like that and I don’t recall reading about any limitation on the number of endpoints.

      Nonetheless, as mentioned, never had the chance to test it.

      Let us know if you have the chance to test it 🙂

      Best regards,
      Nuno Santos

    1. Hi!
      You’re welcome 🙂
      I haven’t tested but my expectation is that if we create more AsyncWebSocket objects with different endpoints and register them on the server, that it should work.
      At least, it makes sense to work like that and I don’t recall reading about any limitation on the number of endpoints.
      Nonetheless, as mentioned, never had the chance to test it.
      Let us know if you have the chance to test it 🙂
      Best regards,
      Nuno Santos

  5. Thank you very much for that awesome tutorial – and of course your others as well. I do want to use this setup with callback – no the “async” tutorial with SoftAP having a function running all the time in the main loop.
    Hence, I do have to use softAP with this tutorial. Instead of using your Wifi.begin and ifs I added: “WiFi.softAP(ssid, password);

    IPAddress IP = WiFi.softAPIP();
    Serial.print(“AP IP address: “);
    Serial.println(IP);” just fort setting up my own server.
    Rest is kept the same. Though, I am not able to connect to the webserver on the printed IP/ws.

    How could I get this one to work?

  6. this is the error I get from python side right after I call
    ws.connect(“ws://192.168.1.30/ws”)
    Any ideas?

    File “C:\Users\…\AppData\Local\Programs\Python\Python38\lib\site-packages\websocket\_handshake.py”, line 165, in _get_resp_headers
    raise WebSocketBadStatusException(“Handshake status %d %s”, status, status_message, resp_headers)
    websocket._exceptions.WebSocketBadStatusException: Handshake status 500 Internal Server Error

Leave a Reply to ElonCancel reply

Discover more from techtutorialsx

Subscribe now to keep reading and get access to the full archive.

Continue reading