comunicar-una-pagina-web-con-asyncwebsockets-en-el-esp8266

Communicating a web page with AsyncWebsockets on ESP8266 or ESP32

  • 6 min

We continue with the ESP8266 and ESP32 tutorials by seeing how to use Asynchronous Websockets to communicate from a served web page.

We will refer to the ESP8266, but the same code is compatible with the ESP32, adjusting the library names. At the end, you have the code for both the ESP8266 and the ESP32.

In the previous post, we saw how to use Websockets as an alternative to Ajax requests for projects that require low lag or server-to-client communication.

Well, just as we saw how to configure a server, and then we saw how to configure an asynchronous server with the AsyncWebServer library, the same library includes a plugin for Async Websockets.

The advantages of Async Websockets over “normal” Websockets implemented in the ESP8266 libraries are that we can serve multiple clients without needing a new address or port.

Therefore, we could consider them “an improved version” of the Websockets implementation on the ESP8266. Otherwise, the operating fundamentals are the same.

Let’s see its use with the same example we saw with Ajax and Websockets, that is, updating a counter with the value of ‘millis()’ received from the server. Which, again, are actually 2 examples.

  • Example 1: The client will send data periodically and receives ‘millis()’ as a response
  • Example 2: The server uses a broadcast to inform clients of the value of ‘millis()’

Example 1 is commented in the code. As it stands, the code runs example 2, which uses broadcast.

Simple, but enough to illustrate the connection without “masking” it with additional elements. Let’s get to work!

On one hand, our main program is basically identical to “normal” Websockets, simply adapting the method names to the library.

Just highlight the use of the broadcast function, which we use in example 2 (same as we did in the previous post).

#include <ESP8266WiFi.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <WebSocketsServer.h>

#include "config.h"  // Replace with your network data
#include "Websocket.hpp"
#include "Server.hpp"
#include "ESP8266_Utils.hpp"
#include "ESP8266_Utils_AWS.hpp"

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

  InitWebSockets();
  InitServer();
}

void loop(void)
{
  // Example 2, called from server
  ws.textAll(GetMillis());
}
Copied!

On the other hand, our ‘Server.hpp’ file is identical to the case of “normal” Websockets, except that we are using port 80 itself to serve the web page and send the Websockets, instead of using port 81.

AsyncWebServer server(80);

void InitServer()
{ 
  server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");

  server.onNotFound([](AsyncWebServerRequest *request) {
    request->send(400, "text/plain", "Not found");
  });

  server.begin();
  Serial.println("HTTP server started");
}
Copied!

Regarding our file with reusable functions for Websockets, it is now called ‘ESP8266_Utils_AWS.hpp’ and has changed considerably compared to the previous one.

Fortunately, by encapsulating this part of the code in this file we won’t have to deal with it frequently. Basically, we receive Async Websocket events and, when we receive a complete packet, we call the ProcessRequest() function.

void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ 
  if(type == WS_EVT_CONNECT){
    //Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
    client->printf("Hello Client %u :)", client->id());
    client->ping();
  } else if(type == WS_EVT_DISCONNECT){
    //Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
  } else if(type == WS_EVT_ERROR){
    //Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
  } else if(type == WS_EVT_PONG){
    //Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
  } else if(type == WS_EVT_DATA){
    AwsFrameInfo * info = (AwsFrameInfo*)arg;
    String msg = "";
    if(info->final && info->index == 0 && info->len == len){
      if(info->opcode == WS_TEXT){
        for(size_t i=0; i < info->len; i++) {
          msg += (char) data[i];
        }
      } else {
        char buff[3];
        for(size_t i=0; i < info->len; i++) {
          sprintf(buff, "%02x ", (uint8_t) data[i]);
          msg += buff ;
        }

        if(info->opcode == WS_TEXT)
        ProcessRequest(client, msg);
        
      } else {
        //message is comprised of multiple frames or the frame is split into multiple packets
        if(info->opcode == WS_TEXT){
          for(size_t i=0; i < len; i++) {
            msg += (char) data[i];
          }
        } else {
          char buff[3];
          for(size_t i=0; i < len; i++) {
            sprintf(buff, "%02x ", (uint8_t) data[i]);
            msg += buff ;
          }
          Serial.printf("%s\n",msg.c_str());

          if((info->index + len) == info->len){
            if(info->final){
              if(info->message_opcode == WS_TEXT)
              ProcessRequest(client, msg);
            }
          }
        }
      }
    }
  }
}

void InitWebSockets()
{
  ws.onEvent(onWsEvent);
  server.addHandler(&ws);
  Serial.println("WebSocket server started");
}
Copied!

Finally, we have the ‘Websocket.hpp’ file, where we define the logic of our “API” for Websockets.

In this simple example, we only send the value of ‘millis()’ encoded as text each time we receive a request. We will only use it in Example 1 (but there’s no need to comment it because we don’t even call it from the web).

AsyncWebSocket ws("/ws");

String GetMillis()
{
  return String(millis(), DEC);
}

void ProcessRequest(AsyncWebSocketClient * client, String request)
{
  String response = GetMillis();
  client->text(response);
}
Copied!

On the other hand, regarding the frontend we serve to the client

The HTML remains exactly the same as the previous post.

<!DOCTYPE html>
<html class="no-js" lang="">
   <head>
      <meta charset="utf-8">
      <meta http-equiv="x-ua-compatible" content="ie=edge">
      <title>ESP8266 Websocket Async</title>
      <meta name="description" content="">
      <meta name="viewport" content="width=device-width, initial-scale=1">
   </head>
 
   <body>
    <h1>Millis</h1>
        <div id="counterDiv">---</div>
    </body>
  
    <script type="text/javascript" src="./js/main.js"></script>
</html>
Copied!

What does change, but slightly, is the Javascript ‘main.js’, with the only modification being that we launch the websocket against port 80 itself, instead of port 81 as we did in the previous post.

Remember that the code, as it is, corresponds to example 2, where the server broadcasts to all clients.

While the commented code is for example 1, where the client periodically makes requests to the server and receives the content of ‘millis()’ as a response.

var myDiv = document.getElementById('counterDiv');

function updateCounterUI(counter)
{
  myDiv.innerHTML = counter; 
}

var connection = new WebSocket('ws://' + location.hostname + '/ws', ['arduino']);

connection.onopen = function () {
  console.log('Connected: ');
  
  // Example 1, request from the client
  //(function scheduleRequest() {
  //  connection.send("");
  //  setTimeout(scheduleRequest, 100);
  //})();
};

connection.onerror = function (error) {
  console.log('WebSocket Error ', error);
};

connection.onmessage = function (e) {
  updateCounterUI(e.data);
  console.log('Server: ', e.data);
};

connection.onclose = function () {
  console.log('WebSocket connection closed');
};
Copied!

Result

If we now load the web page, we will see our counter incrementing (and it’s the last time, I promise) correctly and at full speed.

esp8266-ajax-resultado

And that’s it for the post on Async Websockets on the ESP8266, with which we have finished presenting the most common ways of communication between frontend and backend.

In the next ESP8266 tutorial, we will take a small break to present communication via UDP. And then we will return to client-server communication by expanding what we have seen about Ajax and Websockets with Json files and Rest API. See you soon!

Download the Code

All the code from this post is available for download on Github.

github-full

Version for the ESP8266: https://github.com/luisllamasbinaburo/ESP8266-Examples

Version for the ESP32: https://github.com/luisllamasbinaburo/ESP32-Examples