0

I am struggling with encapsulate a webserver and a websocket server into a c++ class.

this is the structure i want to pack in a class

SemaphoreHandle_t smphr;

AsyncWebServer webserver(80);
AsyncWebSocket websocket("/ws");

void onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) {
  if (xSemaphoreTake(smphr, 50) == pdTRUE) {
    if (type == WS_EVT_DATA) {
      Serial.print("Event Data received\n");
    }
    xSemaphoreGive(smphr);
  }
}

void setupWebserver() {
  webserver.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send_P(200, "text/html", index_html);
  });
  websocket.onEvent(onWsEvent);
  webserver.addHandler(&websocket);
}

here the line websocket.onEvent(onWsEvent); is ok without any compile errors

and here is my try in encapsulate the servers in a class:

// webservice.h
class WebService {
public:
    explicit WebService();
    virtual ~WebService();

    static WebService* getInstance() { return s_instance; } 

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

private:
    static WebService* s_instance;

    SemaphoreHandle_t _smphr;

    AsyncWebServer *webserver = new AsyncWebServer(80);
    AsyncWebSocket *websocket = new AsyncWebSocket("/ws");
};

// webservice.cpp
WebService* WebService::s_instance = nullptr;

WebService::WebService()
{
    s_instance = this;
    _smphr = xSemaphoreCreateBinary();
    xSemaphoreGive(_smphr);  // release semaphores for first use
}

WebService::~WebService() {
  vSemaphoreDelete(_smphr);
  delete webserver;
  delete websocket;
}

void WebService::onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) {
  if (xSemaphoreTake(_smphr, 50) == pdTRUE) {
    if (type == WS_EVT_DATA) {
      Serial.print("Event Data received\n");
    }
    xSemaphoreGive(_smphr);
  }
}

void WebService::setup() {
  webserver->on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send_P(200, "text/html", index_html);
  });
  websocket->onEvent(onWsEvent); //error: invalid use of non-static member function
  webserver->addHandler(websocket);
}

Question: can anybody tell me what i have to do, to get rid of the compile error "invalid use of non-static member function"?

Edit

Compile Output:

Compiling .pio/build/esp32-poe/libd4b/webService/webService.cpp.o
lib/webService/webService.cpp: In member function 'void WebService::setup()':
lib/webService/webService.cpp:172:33: error: invalid use of non-static member function
   m_websocket->onEvent(onWsEvent);
                                 ^
*** [.pio/build/esp32-poe/libd4b/webService/webService.cpp.o] Error 1
stif
  • 429
  • 4
  • 7
  • Please show the complete error message. – kiner_shah Jul 21 '22 at 09:19
  • Also, why `delete[] webserver` and not `delete webserver`? `webserver` doesn't seem to be an array. – kiner_shah Jul 21 '22 at 09:19
  • 1
    @kiner_shah: complete error message was added. and thanks for pointing out the `delete [] webserver` issue. i corrected this line, but it does not affect the compile error – stif Jul 21 '22 at 09:31
  • onWsEvent is non static, but does onEvent expect a static function? If yes, then you need to create a static function and pass it to onEvent. – kiner_shah Jul 21 '22 at 09:33
  • well onWsEvent is non static in the non class version as well, and there it compiles without errors – stif Jul 21 '22 at 09:38
  • 1
    In non-class version, the function isn't a method of any class and is accessible publicly. You need to make your onWsEvent as static or move it outside the class. – kiner_shah Jul 21 '22 at 09:43

3 Answers3

1

If you follow the idea from post bind using a lambda, you should be able to simplify your code with the same wanted result:

old version: you use a lambda to you capture 'this' and you call a static member function that call the wanted member function of your object instance:

m_websocket->onEvent([this](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
    WebService::staticWsEvent(server, client, type, arg, data, len, this);
});

new version: using a lambda capturing 'this' you can directly use the captured 'this' pointer to call the wanted member function of your object instance:

m_websocket->onEvent([this](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
    this->onWsEvent(server, client, type, arg, data, len);
});
Samuel
  • 19
  • 4
0

like @kiner_shah suggested in the comments i had to either make the onWsEvent() Function static or move it outside the class. Making it static did not work because i am using semaphores, so i had to move the function outside the class.

SemaphoreHandle_t smphr;

void onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) {
  if (xSemaphoreTake(smphr, 50) == pdTRUE) {
    if (type == WS_EVT_DATA) {
      Serial.print("Event Data received\n");
    }
    xSemaphoreGive(smphr);
  }
}

void WebService::setup() {

  smphr = xSemaphoreCreateBinary();
  xSemaphoreGive(smphr);  // release semaphores for first use

  webserver->on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send_P(200, "text/html", index_html);
  });
  websocket->onEvent(onWsEvent);
  webserver->addHandler(websocket);
}

It works now, but i dont really like the solution (i have no access to the class variables and functions inside onWsEvent). And i have to admit i dont understand why onWsEvent must be outside the class. Maybe it has something to do with "free-fuctions" like mentioned here: https://devblogs.microsoft.com/oldnewthing/20140127-00/?p=1963

I also looked up the source code of AsyncWebserver to see if onEvent is declared static, but found no evidence:

typedef std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)> AwsEventHandler;

//WebServer Handler implementation that plays the role of a socket server
class AsyncWebSocket: public AsyncWebHandler {

  private:
    ...
    AwsEventHandler _eventHandler;
    ...
  public:
    //event listener
    void onEvent(AwsEventHandler handler){
      _eventHandler = handler;
    }

stif
  • 429
  • 4
  • 7
0

with a little help from chatgpt i finally solved it the way i want!

The problem lies in using a non-static member function as a callback function for the onEvent event. Since non-static member functions require an implicit this pointer, which refers to the object they are called for, they cannot be used as regular functions.

One way to solve this problem is to use a static member function or a lambda function as the callback function and pass the this instance of the class as a user argument to the callback. This way, you can access the class instance and its members.

// webservice.h
class WebService {
public:
    explicit WebService();
    virtual ~WebService();

    static WebService* getInstance() { return s_instance; } 

    void setup();
    // below is new:
    static void staticWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len, void *thisweb) {
        WebService *self = reinterpret_cast<WebService *>(thisweb);
        self->onWsEvent(server, client, type, arg, data, len);
    }
    // end new
    void onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);

private:
    static WebService* s_instance;

    SemaphoreHandle_t _smphr;

    AsyncWebServer *webserver = new AsyncWebServer(80);
    AsyncWebSocket *websocket = new AsyncWebSocket("/ws");
};

// webservice.cpp
WebService* WebService::s_instance = nullptr;

WebService::WebService()
{
    s_instance = this;
    _smphr = xSemaphoreCreateBinary();
    xSemaphoreGive(_smphr);  // release semaphores for first use
}

WebService::~WebService() {
  vSemaphoreDelete(_smphr);
  delete webserver;
  delete websocket;
}

void WebService::onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) {
  if (xSemaphoreTake(_smphr, 50) == pdTRUE) {
    if (type == WS_EVT_DATA) {
      Serial.print("Event Data received\n");
    }
    xSemaphoreGive(_smphr);
  }
}

void WebService::setup() {
  webserver->on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send_P(200, "text/html", index_html);
  });
  m_websocket->onEvent([this](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
    WebService::staticWsEvent(server, client, type, arg, data, len, this);
  }); // now works!!
  webserver->addHandler(websocket);
}
stif
  • 429
  • 4
  • 7