[SOLUCIONADO] Problema con métodos de una clase en una función Lambda

Buenas tardes a todos, primero que nada gracias por vuestra atención.

Mi problema es referente a la creación de una clase para ESP32 con el Ide de Arduino. Mi clase se basa en la gestión de un pin a través de un botón que creé en html( Me base en este proyecto Enlace).

En mi clase, declaro una clase dinámica que me permite la gestión del servidor web, cuyo nombre es "AsyncWebServer" de la librería con el mismo nombre. Pero el problema está en que cuando llamo la función "on", esta le pasa por referencia una función lambda veis abajo.

Dentro de la función MyServer->on,en el método de mi clase 'BarrierWebPage', el tercer parámetro es una función lambda. El problema está que al utilizar mi otro método BarrierControl() dentro de la función lambda, me pone: 'this' was not capture for this lambda function.

PD: He probado el programa de la página del enlace que he puesto arriba y funciona perfecto. Además si comento dichas funciones que me dan el problema al compilar, compila y la página funciona perfectamente. Solo que obviamente, el pin no se ve afectado.

PD: He eliminado la variable que contenía todo el código html, para reducir espacio en el post. Pero en la página del enlace, podéis verla.

Un saludo y gracias por vuestra atención.

/*FICHERO HEADER FILE (.H)*/
#include <WiFi.h>
#include <WebServer.h>
#include <WiFiServer.h>
#include <WiFiClient.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

#define Enable    1
#define Disable   0



class barrier_web 
{
  private:

    // store the wifi name and pass to access.
    const char* Wifi_Name;
    const char* Wifi_Pass;

    AsyncWebServer  *MyServer;   //Clase Declarada dentro de mi clase
   
    //Relee State store and Pin relee value
    uint8_t GPIO_PinRelay;

    
  public:

    barrier_web(uint8_t RelayPin,const char* WifiName,const char* WifiPass);//Constructor
    uint8_t BarrierInit();  //Inicialización del servidor y conexión wifi
    void BarrierWebPage();  //Solicitud y gestión GET de la página web
    void BarrierControl(uint8_t Value);

};





/**************FICHERO CPP*********/

barrier_web::barrier_web(uint8_t RelayPin,const char* WifiName,const char* WifiPass)
{
  RelayState = Disable;
  GPIO_PinRelay = RelayPin; // Pin Relay
  Wifi_Name = WifiName;
  Wifi_Pass = WifiPass;
  MyServer = new AsyncWebServer(80);  //Constructor de mi clase AsynWebServer para el puerto 80

  
}



void barrier_web::BarrierWebPage()
{
  //WebPage Send
  MyServer->on("/",HTTP_GET, [](AsyncWebServerRequest *request)
  {
    
    request->send_P(200, "text/html", web_page);
  });



  MyServer->on("/on",HTTP_GET, [](AsyncWebServerRequest *request)
  {
    BarrierControl(Enable); //NO ME DEJA COMPILAR PORQUE ESTOY LLAMANDO A ESTE MÉTODO DENTRO DE ESTA FUNCIÓN
    request->send(200, "text/plain","ok");
    
  });



  MyServer->on("/off",HTTP_GET, [](AsyncWebServerRequest *request)
  {
    BarrierControl(Disable); //NO ME DEJA COMPILAR PORQUE ESTOY LLAMANDO A ESTE MÉTODO DENTRO DE ESTA FUNCIÓN
    request->send(200, "text/plain","ok" );
    
  });

  MyServer->begin();
  
}





void barrier_web::BarrierControl(uint8_t Value)
{
  
 digitalWrite(GPIO_PinRelay,Value);
  
}



/*****************FICHERO .INO ARDUINO********************/

#include "barrier_web.h"

const char *Name= "SSID";
const char *Pass= "PASS";


barrier_web test1(2,Name,Pass);



void setup() {
  
  Serial.begin(115200);
  test1.BarrierInit();
  test1.BarrierWebPage();
}

void loop() {
}

Hola, vamos a ver

No tengo un wifi ahora para probarla, pero sí te digo que estas trayendo una estructura y una filosofía que está en el límite entre el C y el C++ por el manejo de punteros.

Yo te recomiendo que declares un procedimiento dinámico en vez de pasar un puntero, ya que el compilador de C++ de Arduino no es de última generación, creo que es el 10

En el contexto de Arduino ¿Por qué no te planeas crear una clase MADRE con un método virtual?

class MAMA
{
public:
    virtual void yavere() {
    }
};

Ahora puedes heredar a MAMA y definir a "yavere" en tu nueva clase

class nueva : public MAMA {
    virtual void yavere() {
               < --- MUCHAS MUCHAS COSAS --->
          }
}

Si tuviese un módulo WIFI lo probaba, pero es que no tengo ahora uno aquí, pero creo que simplificará tu código y te hará ver mejor qué hacer.
Por ejemplo me ha costado un mundo darme cuenta que en la línea 63

    request->send_P(200, "text/html", web_page);
  });

cierras el paréntesis y estás definiendo la función mientras la llamas, me parece complejo de leer y difícil, por no imposible de poner Serial.Print's que necesitas para ver qué está sucediendo

Espero que te sirva la recomendación

Hola Tony,

Con respecto a crear una clase Madre y declarar un proceso virtual, no entiendo como podría solucionar el problema. Me refiero a que no entiendo el concepto de como al declarar mi proceso(entiendo que el 'BarrierControl' sería le método virtual) solucionaría el problema. También entiendo que mi clase hija sería "barrier_web".

He intentado como has dicho, de aplicar un Serial.println("") dentro de la función lambda. Esto si compila y carga, pero al abrir la consola y pulsar el botón, no aparece nada.

Como dije en el post, el código para la gestión del servidor, la cogí de la página, y lo curioso es que él, pone un digitalWrite dentro de la función lambda , y funciona perfectamente. Por eso intenté extrapolarlo a mi clase, pero no funciona. Incluso si pongo digitalWrite en la función Lambda de mi clase, compila y carga, pero tampoco funciona.

Un saludo

Se perdió toda mi respuesta y ahora no me acuerdo de todo lo que dije, bueno.

El tema es que estás interpretando un código complejo y creo que hay un problema sobre el tipo de clase de puntero que se espera e incompatibilidad con el tipo de clase, algo en lo que C++ se pone muy exquisito cuando usas punteros a métodos de clases.

Te pedía que la reescribieras en un contexto que tu manejes el nivel de abstracción con tu propio esquema mental y, para olvidarte de los punteros el mejor método es las funciones virtuales, que para eso se crearon.

Cuando pueda le echaré un vistazo lento, pero ahora necesito poder hacer debug mental porque no tengo el módulo wifi y eso me llevará un rato.

La librería ESPAsyncWebServer.h en sus líneas 61 y siguientes indica una estructura

typedef enum {
  HTTP_GET     = 0b00000001,
  HTTP_POST    = 0b00000010,
  HTTP_DELETE  = 0b00000100,
  HTTP_PUT     = 0b00001000,
  HTTP_PATCH   = 0b00010000,
  HTTP_HEAD    = 0b00100000,
  HTTP_OPTIONS = 0b01000000,
  HTTP_ANY     = 0b01111111,
} WebRequestMethod;

Y cuando la usas en

 MyServer->on("/on",
                       HTTP_GET,
                                 [](AsyncWebServerRequest *request)
  {
    BarrierControl(Enable); //NO ME DEJA COMPILAR PORQUE ESTOY LLAMANDO A ESTE MÉTODO DENTRO DE ESTA FUNCIÓN
    request->send(200, "text/plain","ok");
   
  });

No encuentro la asignación de la estructura previa a una variable real, no usas namespace ni usas

WebRequestMethod::HTTP_GET

Sin embargo no indicas que

void barrier_web::BarrierWebPage()
{
  //WebPage Send
  MyServer->on("/",
                      HTTP_GET,
                                 [](AsyncWebServerRequest *request)
  {
   
    request->send_P(200, "text/html", web_page);
  });

te esté dando problemas.
Me falta código o tienes que simplificar tu manejo de las clases porque se hace verdareramente invisible, al menos para mi, ver que pasa

Prueba esto, no puedo descargar la tarjeta ESP82

&BarrierControl(Enable)

Creo que necesitas la referencia porque no la llamas aquí sino en MyServer->on

Buenas Tony,

A ver con respecto a la clase "AsynWebServer" se pueden ver los métodos en el fichero "ESPAsyncWebServer.h" en la línea 397 empieza la declaración de la clase. Más abajo puedes ver los distintos métodos. El que uso yo es.

AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest);

en WebRequestMethodComposite method es donde se define el métoto get para la extracción de información, que creo que es a lo que te refieres con lo de HTTP_GET.

Por otro lado, la clase funciona, porque la página html se carga, con la ip asignada. Ya que cuando solicito la pagina, "MyServer->On", funciona.

MyServer->on("/",HTTP_GET,[](AyncWebServerRequest *request){request->send_P(200, "text/html", web_page);});

El problema lo tengo con la función Lambda( Vendría a ser "ArRequestHandlerFunction OnRequest") que no reconoce el método de mi clase, es como si fuese una función local dentro de mi propio método, que no es capaz de leer nada externo. He probado como me comentaste en el último post de usar:

&BarrierControl(Enable);

Pero tampoco funciona.
Lo que más me molesta es que ni usando un digitalWrite ni el Serial.Println dentro de la función lambda me da ningún resultado. La página se carga correctamente, pero no hay respuesta en el puerto serie ni el pin.

MyServer->on("/on",HTTP_GET, [](AsyncWebServerRequest *request){
   Serial.println("Esta On");
   digitalWrite(2,HIGH);
   request->send(200, "text/plain","ok");}
   );

He incluso intentando hacer mi clase, como una clase "Hija" de AsyncWebServer para poder heredar los métodos de AsyncWebServer, pero llego al mismo resultado a la hora de introducir algo en la función Lambda de "On".

Un saludo y gracias por tu tiempo.

Me está gustando el reto, pero ahora ni puedo descargar la tarjeta ESP32 para mi IDE, estoy esperando que me respondan.

El problema es de punteros, de eso estoy casi seguro, por eso te he sugerido que uses la referencia & a la función en la lambda, porque creo que hay una conversión de tipos que es la que falla y creo que es más culpa del compilador de c++ empleado en arduino, de echo, pude hacer una "trampa" para utilizar el conversor implícito en el return tiempo atrás

https://forum.arduino.cc/index.php?topic=713589.0

De echo, mi problema era parecido al tuyo, lo que sucede es que tu estás pasando la función y no puedes hacerlo igual que yo, porque ya he visto que tienes complicado usar una virtual si no modificas la librería original, que no te recomiendo por la locura que representa.

¿Probaste a poner el & en la lambda para ver si pasando la referencia el compilador lo acepta?

Creo que la conversión de tipos es función, que no es nada generosa en C++ (y si lo era en C) es el problema, de echo, como te dije, la página original está programada más en C que en otra cosa.

Si logro que funcione mi IDE o lo logro con el VSC intentaré probar la compilación, pero es que ahora estoy con algo que me lleva tiempo y he de entregar el martes y voy a ratos sueltos con el reto, porque divertido es un rato.

Me cuentas qué mensaje te da el compilador cuando le induces la referencia a tu función en la Lambda.

Por cierto, la que te funciona es porque es lambda no tiene implícito la llamada a una función, fíjate en la librería original las sobreescrituras de constructores que hay para el método on, creo que 5 si mal no recuerdo, ese podría ser otro problema si no le mandas el tipo correcto, que decida usar un constructor y no otro

Un saludo

Como te dice TonyDiana, puedes probar con capturar por referencia todos los miembros del scope desde el que se crea la función anónima con:

  MyServer->on("/off",HTTP_GET, [&](AsyncWebServerRequest *request)
  {
    BarrierControl(Disable); //NO ME DEJA COMPILAR PORQUE ESTOY LLAMANDO A ESTE MÉTODO DENTRO DE ESTA FUNCIÓN
    request->send(200, "text/plain","ok" );
   
  });

Estas funciones se crean en el momento de ejecucion y no en la compilacion, por lo que es más eficiente pasar el puntero this ya que pasas todas las referencia colocando un solo dato en la pila. Con & colocas un puntero a cada unos de los miembros en la pila
Para capturar this:

 MyServer->on("/off",HTTP_GET, [this](AsyncWebServerRequest *request)
  {
    BarrierControl(Disable); //NO ME DEJA COMPILAR PORQUE ESTOY LLAMANDO A ESTE MÉTODO DENTRO DE ESTA FUNCIÓN
    request->send(200, "text/plain","ok" );
   
  });

Si lo capturas con this no debes usar la referencia 'this->miembro'. Se usa solo 'miembro', creo recordar... ahora no estoy seguro que igual me confundo con Java...

Por cierto, ya te avisaba el compilador: "me pone: 'this' was not capture for this lambda function."

Saludos.

SI, ya te digo, todo el tiempo tienes un problema de punteros y conversiones.
Si tengo claro cómo funciona una lambda, lo que te comento es que en el contexto que te falla no es la lambda, porque en

void barrier_web::BarrierWebPage()

Usas otra lambda y no es el problema

El problema no está en la lambda, sino del manejo que haces, o tienes que hacer más correctamente explicado, de los punteros a funciones en la lambda, que es el verdadero problema:

*this' was not capture *

La lambda, tal y como está definida, no captura el puntero a tu función, pero no es la lambda, no nos engañemos, es la sobreescritura de los métodos on de la función/método que utilizas.

¿Por qué no lo sacas de una lambda y le pasas el puntero a la función externa?

Declárala global, porque la única forma de analizar el responsable en el proceso es empezar a demenuzarlo en partes más pequeñas.

  • Si te funciona con una función no lambda, es seguro que es una de las sobrescrituras del constructor del objeto el responsable del problemas.
  • Si te falla, es el compilador, o casi seguro en este caso que no tienes el constructor correcto en la clase que llamas y te va a tocar sobrescribir el que necesitas.

TonyDiana:
SI, ya te digo, todo el tiempo tienes un problema de punteros y conversiones.

TonyDiana, yo no tengo ningún problema con los punteros :slight_smile:
Estaba contestando a Edu87.

No te digo a ti, digo que creo que el problema es con el manejo de punteros a la función que hace la lambda y que realmente recibe el constructor, que no me expliqué bien, jejejeje

Es lo que creo pero necesito probarlo a fondo y, ahora estoy en otra cosa y no tengo ni un ESP32 ni un wifiduino para probar

TonyDiana:
No te digo a ti, digo que creo que el problema es con el manejo de punteros a la función que hace la lambda y que realmente recibe el constructor, que no me expliqué bien, jejejeje

Es lo que creo pero necesito probarlo a fondo y, ahora estoy en otra cosa y no tengo ni un ESP32 ni un wifiduino para probar

A ver, no es un problema de punteros.
La lambda no compila debido a que dentro de ella no se conocen las variables de fuera, solo se conocen las de su alcance (locales y parámetros pasado).
Para que conozca las funciones de fuera hay que capturarlas. Si la función que crea la lambda tiene una variable que se llama a y otra que se llama b y quieres usarlas dentro de la función lambda tienes que capturarlas con [=a,=b] si quieres capturarlas por referencia pones [&a,&b], si quieres capturar todas las variables por referencia pones [&] y si quieres capturar todas por copia pones [=]. Si la función que está creando la lambda es un miembro de una clase puedes usar todo lo de la clase capturando this, [this].
void barrier_web::BarrierWebPage() no compila ya que no se encuentra web_page, pero no tiene que ver con
que la función sea lambda, web_page no está definido en ningún sitio...
Saludos.

Puchas, tienes razón, me estaba complicando la vida yo sólo. Esa es la respuesta y tiene implícita la solución

Hola a todos, gracias por contestar y perdonad la tardía en contestar.

A ver vamos vamos por partes.

Con respecto a la función lambda, si es correcto, es como si fuese una función dentro de una función, por lo tanto si declaro una variable global y la llamo dentro de la función lambda(cosa que hago y compila) también. De echo al utilizar this en la función lambda, se traga todo lo que le pongas, pero no funciona. Lo que me molesta, es que en el código original, el programador usa el digitalWrite dentro de la función lambda, y está le funciona. Pero como dije con anterioridad, si la utilizo dentro de la lambda, no funciona en ningún momento.

uint8_t temp;

void barrier_web::BarrierWebPage()
{
  

  //WebPage Send
  MyServer->on("/",HTTP_GET, [](AsyncWebServerRequest *request){request->send_P(200, "text/html", web_page);});



  MyServer->on("/on",HTTP_GET, [this](AsyncWebServerRequest *request)
  {
     digitalWrite(2,HIGH);
    temp=HIGH;
    request->send(200, "text/plain","ok");
  });



  MyServer->on("/off",HTTP_GET,[this](AsyncWebServerRequest *request)
  {
    digitalWrite(2,LOW);
    temp=LOW;
    request->send(200, "text/plain","ok" );
  });
  
 
  BarrierControl(temp);
  MyServer->begin();
   
}

A pesar de todo esto, el led no enciende. Yo creo que es más fallo de la compilación de la clase que otra cosa.

También he programado la función BarrierControl(uint8_t var) como una función local dentro del CPP, para ver si llamándola desde la función hace algo. E incluso como dije antes, poniéndo digitalWrite(2,LOW/HIGH) dentro de la propia lambda, esta hace caso omiso.

En fin , gracias por vuestro tiempo y respuesta, seguiré peleando con esto a ver si llego a algo o sino tomaré otro camino.

Saludos.

Te lo dijo @harkonnen claro que es lo que yo nunca tuve en cuenta (no uso lambdas, estudiarlas en su día y punto), lambda no usa ni puede scope global, así que sólo tienes las siguientes opciones:

Crear la función dentro del lambda
Crear una función no lambda
Pasar en el parámetro del creador lambda el puntero a la función externa y pasársela al constructor por referencia

No se me ocurren otras

Hola, digitalWrite si que funciona, pero en tu código no veo pinMode(2,OUTPUT) y por defecto los pines están configurados como entradas...

Saludos.

Buenos días,

Bueno al final he dado con el problema que tenía y también como poder llamar al método de la clase dentro de la función lambda.

Mi primer problema, no era fallo del código de arduino, sino del código de HTML, tardé en darme cuenta de que había una mala sintaxis en el HTML, y eso me daba el problema de no cambiar los valores al pulsar el botón.

Luego de solucionar ese problema, no me dejaba llamar al método por que como era una función lambda, no podía leer el método desde dentro. Luego de buscar información, en la página de microsoft de funciones lambda en c++(Link). Vemos que pone "Para usar expresiones lambda en el cuerpo de un método de clase, pase el this

  • puntero a la cláusula Capture para proporcionar acceso a los métodos y miembros de datos de la clase envolvente." * Entonces lo apliqué a mi código y queda así;
void barrier_web::BarrierWebPage()
{
 
  //WebPage Send
  MyServer->on("/",HTTP_GET, [](AsyncWebServerRequest *request){request->send_P(200, "text/html", web_page);});

  MyServer->on("/Led1on",HTTP_GET,[this](AsyncWebServerRequest *request){
    this->BarrierControl(Enable);
    request->send(200, "text/plain", "ok");
   
    });

  MyServer->on("/Led1off",HTTP_GET,[this](AsyncWebServerRequest *request){

    this->BarrierControl(Disable);
    request->send(200, "text/plain", "ok");
   });
  
  
  MyServer->begin();
  
}

Con esto el método quedo funcional, y al llamarlo desde el main, funciona perfecto.
Muchas gracias a todos por vuestro tiempo y atención.
Doy el problema por solucionado.

1 Like

Pues eso es lo que te estaba diciendo, que debes pasarle el puntero de tu función, lo que no me acordaba porque no me gustan y no uso es cómo se hacía en una lambda, pero mira, ya aprendí algo yo también.

Un saludo