me-no-dev / ESPAsyncWebServer

Async Web Server for ESP8266 and ESP32
3.68k stars 1.21k forks source link

Is it possible to make a Keep-Alive effect? #1430

Open butaikis opened 1 month ago

butaikis commented 1 month ago

@me-no-dev Hello. Is it possible to make a "Keep-Alive" effect? So that the web server does not close the socket for a while?

Belleson commented 1 month ago

I made a keep-alive using a Ticker from this library to socket.textAll() clients every 30 seconds, and they reply back to server. It seems to work okay.

butaikis commented 1 month ago

@Belleson Can I have an example of how the EspAsyncWebServer library works? It's just that the problem is that it is the library that forcibly closes the socket. Maybe I don't understand something)

Belleson commented 1 month ago

This is a very good tutorial on web socket.

Prevent watch dog time-outs by

  1. Don't use event callbacks, only socket message handlers
  2. No delays in message handler, instead make message handler asynchronous with Ticker callbacks

Be aware callbacks for a single ticker must have enough time to finish before another message arrives. If not, tickerCallback2 will stop execution of tickerCallback1, so this is good for human interface but not for fast client-server data processing.

Client javascript code will also have a message handler function.

Sample server code for asynchronous message processing (not tested to compile or run):

#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <Ticker.h>

AsyncWebServer webServer;
AsyncWebSocket socket("/ws");
Ticker socketActionTicker;
Ticker keepAlive;

// Call from main.setup()
void initSocket()
{
    socket.onEvent(onSocketEvent);
    webServer.addHandler(&socket);
    keepAlive.attach(30, tickleSocketClient); // attach Keep Alive to timer ticker, execute twice per minute
}

void notifyClients(const char *key, const char *data)
{
    const uint8_t size = JSON_OBJECT_SIZE(8);
    StaticJsonDocument<size> json;
    json[key] = data;

    char buffer[512];
    size_t len = serializeJson(json, buffer);
    socket.textAll(buffer, len);
}

void tickerCallback1(uint_fast8_t param)
{

    // Add server processing code for client message 1

    char ld[16];
    memset(ld, 0, sizeof(ld));
    sprintf(ld, "%d", param); 

    notifyClients("paramName1", ld);
}

void tickerCallback2(uint_fast8_t param)
{
    // Add server processing code for client message 2

    char ld[16];
    memset(ld, 0, sizeof(ld));
    sprintf(ld, "%d", param); 

    notifyClients("paramName2", ld);
}

void handleSocketMsg(void *arg, uint8_t *data, size_t len)
{
    AwsFrameInfo *info = (AwsFrameInfo *)arg;
    if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT)
    {
        const uint8_t size = JSON_OBJECT_SIZE(8);
        StaticJsonDocument<size> json;
        DeserializationError err = deserializeJson(json, data);
        if (err)
        {
            // LOG ERROR HERE
            return;
        }

        if (strcmp(action, "msg1") == 0)
        {
            uint_fast8_t val1 = json["value"];  // client sends JSON to this server message handler
            socketActionTicker.once_ms(1, tickerCallback1, val1); // call function with param value sent from client
        }
        else if (strcmp(action, "msg2") == 0)
        {
            uint_fast8_t val2 = json["value"];  // client sends JSON to this server message handler
            socketActionTicker.once_ms(1, tickerCallback2, val2); // call function with param value sent from client
        }
    }
}

void onSocketEvent(AsyncWebSocket *socksvr,
                      AsyncWebSocketClient *sockclient,
                      AwsEventType type,
                      void *arg,
                      uint8_t *data,
                      size_t len)
{
        switch (type)
        {
            case WS_EVT_CONNECT:
            Serial.printf("\nWebSocket client #%u connected from %s\n", sockclient->id(), sockclient->remoteIP().toString().c_str());
            break;
        case WS_EVT_DISCONNECT:
            Serial.printf("\nWebSocket client #%u disconnected\n", sockclient->id());
            break;
        case WS_EVT_DATA:
            handleSocketMsg(arg, data, len);
            break;
        case WS_EVT_PONG:
        case WS_EVT_ERROR:
            break; // TODO something here?
    }
}

void tickleSocketClient()
{
    const uint8_t size = JSON_OBJECT_SIZE(2);
    StaticJsonDocument<size> json;
    json["keepAlive"] = WiFi.localIP().toString();
    notifyClients(json);
}

Good luck, I hope you find a solution.

butaikis commented 4 weeks ago

@Belleson I don't use web sockets, I only have a standard http server https://github.com/me-no-dev/ESPAsyncWebServer/blob/master/examples/simple_server/simple_server.ino

Belleson commented 4 weeks ago

My mistake, when you said keep the socket open I made a bad assumption that you were using web sockets. Maybe you could use a Ticker in a similar way so the server reloads a one pixel graphic on the page as a refresh?