Language: EN

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

Communicating a web page with AsyncWebsockets on ESP8266 or ESP32

We continue with the tutorials of the ESP8266 and ESP32 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 for the ESP32, adjusting the name of the libraries. 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 set up a server, and then we saw how to set up an asynchronous server with the AsyncWebServer library, the same library incorporates a plugin for Async Websockets.

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

Therefore, we could consider them “an improved version” of the Websockets implementation in the ESP8266. Otherwise, the operating principles 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 is, the code executes example 2, which uses broadcast.

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

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

Just highlight the use of the broadcast function, which we use in example 2 (just like 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());
}

On the other hand, our ‘Server.hpp’ file is identical to the case of “normal” Websockets, except that we are using port 80 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");
}

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 the events of Async Websocket and, when we receive a complete package, we launch 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");
}

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 every time we receive a request. We will only use it in Example 1 (but there is no need to comment it out 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);
}

On the other hand, regarding the front-end that 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>

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

We remember that the code, as it is, corresponds to example 2, in which the server broadcasts to all clients.

While the commented code is for example 1, in which 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');
};

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 up to here the post about 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 brief pause to present communication using UDP. And then we will return to client-server communication, expanding what we have seen about Ajax and Websockets with Json files and Rest API. See you soon!

Download the code

All the code in 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