Syntax Funktionsübergabe

Da steht klar dort dass du versucht ein std::function Objekt einem void(*)() Funktionszeiger zuzuweisen

Naja..... wenn man es lesen kann..... ich sage dir lieber nicht was für mich da steht.... :slight_smile:

Ich habe deinen Vorschlag jetzt in meine "ESP8266_Basic_webServer", hoffentlich richtig, eingebunden.

ESP8266_Basic_webServer.h

  #include <functional>
  typedef std::function<void(void)> CallbackFunction;

class ESP8266_Basic_webServer{

public:
  ESP8266_Basic_webServer();

  CFG *cfg;
  void set_cfgPointer(CFG *p);
  //void set_saveConfigCallback( void (*func)(void) );
  //void (*_saveConfigCallback)(void) = NULL;

  void set_saveConfigCallback(CallbackFunction c);
  CallbackFunction callback;
  //typedef std::function<void(void)> THandlerFunction;
  //void set_saveConfigCallback(THandlerFunction handler);

  
private:
  ESP8266WebServer webServer;

ESP8266_Basic_webServer.cpp

void ESP8266_Basic_webServer::set_saveConfigCallback(CallbackFunction c){
//void ESP8266_Basic_webServer::set_saveConfigCallback( void (*func)(void) ){
  Serial.print("saving WEB data");
  callback = c;
  //_saveConfigCallback = handler; 
}


void ESP8266_Basic_webServer::cfgPageHandler()
{
  // Check if there are any GET parameters
  if (webServer.hasArg("webUser")) strcpy(cfg->webUser, webServer.arg("webUser").c_str());

 webServer.send(200, "text/html", rm);
 
  //if ( _saveConfigCallback != NULL) {
    //_saveConfigCallback();
  //}
  if ( callback != NULL) {
    callback();
  }
}

Der Aufruf aus der "ESP8266_Basic" sieht jetzt so aus:

ESP8266_Basic.cpp

ESP8266_Basic::ESP8266_Basic() : mqtt_client(wifi_client), webServer(){

/*
  static ESP8266_Basic* obj = NULL;
  obj = this;
  
  webServer.set_saveConfigCallback(
    []() { obj->saveConfigCallback(); }
  );
*/

  webServer.set_saveConfigCallback(std::bind(&ESP8266_Basic::saveConfigCallback, this));
}

Das läßt sich compilieren, gibt aber lauter Resets

Theoretisch sollte es funktionieren. Aber ich kann nicht sagen was da in deinem System genau wo und wie aufgerufen wird.

Selbst wenn ich den ganzen Code hätte fehlt mir ein ESP8266 um das zu Testen

Das sollte übrigens private sein:

CFG *cfg;
CallbackFunction callback;

Ändert aber am Problem nichts

Selbst wenn ich den ganzen Code hätte fehlt mir ein ESP8266 um das zu Testen

Na das läßt sich ändern, es wäre mir eine freude dir einen zu schicken!
Hast du 3V3 und ein Breadborad? Das Ding macht echt Laune!

Theoretisch sollte es funktionieren. Aber ich kann nicht sagen was da in deinem System genau wo und wie aufgerufen wird.

Ich werde mal einen leeren Sketch aufmachen und das ClassA und ClassB Beispiel mal anlegen und schauen......

Interessieren tut es mich schon. Aber ich bin bisher noch nicht dazu gekommen.

Hier mal die komplette std::function Version:

#include <functional>

typedef std::function<void(void)> CallbackFunction;

class A
{
public:
  void setCallback(CallbackFunction c)
  {
     callback = c;
  }

  void testFunction()
  {
     Serial.println("calling callback");

     if (callback != nullptr)
        callback();
     else
       Serial.println("null");
   }

private:
  CallbackFunction callback;
};

class B
{
public:
  B(A& a)
  {
    a.setCallback(std::bind(&B::doSomething, this));
  }

  void doSomething()
  {
     Serial.println("do something");
  }

private:
};

void setup()
{ 
   Serial.begin(9600);

   A a;
   B b(a);

   a.testFunction();
}

Auch hier existiert testFunction() nur damit ich einen einfach Weg habe den von außen den Callback zu aktiveren!!

Die Namen kann man natürlich auch ändern. "Handler" ist auch eine oft verwendeter Name für solche Paramater

Schade, dass es mit avr-gcc wohl nicht geht

fatal error: functional: No such file or directory

Aber man kommt ja meist auch mit "normalen" callback Funktionen aus.

Nein, geht nicht. :frowning: Deshalb habe ich ja erst eine einfache statische Wrapper Funktion vorgeschlagen. Und dann eine Lambda Funktion, was nur eine Abkürzung der gleichen Idee ist.

Dass der ESP Zumindest Teile der STL Templates hat habe ich nicht gewusst. Ist aber sehr interessant.

Was mir aber gerade auffällt:
Ging es nicht am Anfang mal darum einen Klassen-Funktion an einen C Funktionszeiger in einer fremden Klasse zuzuweisen? Wieso sind wir jetzt plötzlich bei einer Klasse die ein std::function Funktionsobjekt als Callback-Variable hat?! Und zuletzt sah es sogar danach aus dass dies in einer selbst geschriebenen Klasse steht...

Interessieren tut es mich schon. Aber ich bin bisher noch nicht dazu gekommen.

Dann wäre doch jetzt der richtige Zeitpunkt damit anzufangen.... :sunglasses:

Wie gesagt, kurze PN ..... ich schmeiß dir dann alles in die Post, damit würde ich mich gerne bei dir revanchieren.

Was mir aber gerade auffällt:
Ging es nicht am Anfang mal darum einen Klassen-Funktion an einen C Funktionszeiger in einer fremden Klasse zuzuweisen? Wieso sind wir jetzt plötzlich bei einer Klasse die ein std::function Funktionsobjekt als Callback-Variable hat?! Und zuletzt sah es sogar danach aus dass dies in einer selbst geschriebenen Klasse steht...

Ist auch so.....
der erste Teil funktioniert ja dank deiner Hilfe!
Jetz sitze ich aber gerade an einer "eigenen" WEB-Server-Class für mein Project.
Ich könnte das ja auch über globale Varuablen machen, da ich ja jetzt aber weiß, das es callbacks gibt nehme ich natürlich die....

Der WEB-Server soll nach getaner Arbeit ein Callback an meine Basic-Class aufrufen.

Ziel soll eine ESP_Basic-Class sein, die den gesamten WiFi, WEB, OTA und MQTT Verbindungs- und Configurationkram regelt.

In meinem Sketch soll eigentlich nur noch ein startWiFi stehen.
Der ESP soll dann immer über ein WEB-IF configuriert werden können, falls sich mal was ändert.
Ändert sich das WiFi mal oder ist der Chip neu, dann soll auch ein Accesspoint aufgemacht werden.

Die Ganzen Einzelteile laufen schon, jetzt soll alles hübsch in Klassen verpackt werden, damit ich so etwas wie eine ESP8266_Basic - Library habe.
Dann kann ich mich im Sketch ums Wesendliche kümmern.

Juupp...läuft!!
Ich habe dein Beispiel mal in Sketch, Class A und B gesplittet.
So hat vielleicht der nächste auch noch was davon...... :slight_smile:

Sketch

#include <A.h>
A espClient;

void setup() {
  Serial.begin(115200); 
  Serial.println("");
  Serial.println("Start Callback Demo");
  espClient.startDemo();
  
}  

void loop() {
  espClient.ShowTestFunction();
}

Class A.h

#include <Arduino.h>
#include <functional>
#include <B.h>

class A{

public:
  A();
  B b;
  void startDemo();
  void doSomething();
  
  void ShowTestFunction();

private:
};

Class A.cpp

#include <A.h>

A::A() : b(){
  b.setCallback(std::bind(&A::doSomething, this));
}

void A::startDemo(){
  Serial.println("Demo running");
};

void A::ShowTestFunction(){
  b.testFunction();
}

void A::doSomething(){
  Serial.println("incomming Callback");
}

Class B.h

#include <Arduino.h>
#include <functional>

typedef std::function<void(void)> CallbackFunction;

class B{
public:

  B();
  void setCallback(CallbackFunction c);
  void testFunction();

private:
  CallbackFunction callback;
};

Class B.cpp

#include <B.h>

B::B(){
}

void B::setCallback(CallbackFunction c){

  callback = c;
}

void B::testFunction(){
  delay(5000);
  Serial.println("calling callback");
  
  if (callback != nullptr)
    callback();
  else
     Serial.println("null");
}

Ok, kannst du das jetzt so auf die reale Anwendung übertragen? Oder sieht das dann groß anders aus? (mal abgesehen davon dass z.B. testFunction() wegfällt)

In Klasse A sollte B b vielleicht private sein damit es nach außen nicht sichtbar ist. Sonst kannst du sowas machen:

a.b.setCallback();

Kann auch gewünscht sein, aber normal macht man das nicht

Moin,

Ok, kannst du das jetzt so auf die reale Anwendung übertragen? Oder sieht das dann groß anders aus? (mal abgesehen davon dass z.B. testFunction() wegfällt)

Ne, sollte passen, ist ja aber im Grunde schon so wie ich es hatte.
Daher muss da noch ein Kincken drin sein...werde ich morgen suchen....

Soll ich dir noch ein wenig "Appetit" auf den ESP machen.... :slight_smile:

Gruß
Pf@ne

Solche Resets können auch passieren wenn man Unsinn mit Zeigern macht. Das hast du ja schon gemerkt. Wobei da laut dem anderen Thread der ESP auch etwas besser ist und in manchen Situationen sowas wie eine Exception kommt.

Aber passe da generell auf und mache eine Abfrage auf != nullptr wenn die Möglichkeit besteht dass ein Zeiger auf nichts zeigt. Hier sei z.B. der Zeiger auf dein Config struct genannt.

Ich habe heute nochmal alles auf Basis des vorherigen Posts stückweise zusammengesetzt um zu schauen wann es anfängt zu haken......

Am Ende lief es dann.....ich habe aber im Grunde nix verändert...!?
Was solls, hauptsache es läuft!

Coole Sache mit den Callbacks.
Ich hoffe, dass ich den nächsten Callback alleine hinbekomme.

Ich muss hier nochmal nachhaken....

Ich möchte einer Klasse (pubsubclient) meine Callbackfunktion übergeben.
Anders als bisher hat die Klasse selbst ein typedef für die Callbackfunktion:

pubsubclient.h

#ifdef ESP8266
#include <functional>
#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback
#else
#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
#endif

public:
   PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE);

In meiner Klasse habe ich den Aufruf wie folgt definiert:

.h

public:
  void mqttBroker_callback(char* topic, byte* payload, unsigned int length);

.cpp

ESP8266_Basic::ESP8266_Basic() : webServer(), 
                                 mqtt_client(wifi_client)
								 {
								 
  webServer.set_saveConfig_Callback(std::bind(&ESP8266_Basic::cfgChange_Callback, this));
  mqtt_client.setCallback(std::bind(&ESP8266_Basic::mqttBroker_callback, this));
}

void ESP8266_Basic::mqttBroker_callback(char* topic, byte* payload, unsigned int length) {
}

Scheinbar will die Klasse den Aufruf aber anders haben?

C:\Users\Admin\Documents\Arduino\libraries\ESP8266_Basic\ESP8266_Basic.cpp: In constructor 'ESP8266_Basic::ESP8266_Basic()':

C:\Users\Admin\Documents\Arduino\libraries\ESP8266_Basic\ESP8266_Basic.cpp:8:79: error: no matching function for call to 'PubSubClient::setCallback(std::_Bind_helper<false, void (ESP8266_Basic::)(char, unsigned char*, unsigned int), ESP8266_Basic* const>::type)'

mqtt_client.setCallback(std::bind(&ESP8266_Basic::mqttBroker_callback, this));

^

C:\Users\Admin\Documents\Arduino\libraries\ESP8266_Basic\ESP8266_Basic.cpp:8:79: note: candidate is:

In file included from C:\Users\Admin\Documents\Arduino\libraries\ESP8266_Basic/ESP8266_Basic.h:16:0,

from C:\Users\Admin\Documents\Arduino\libraries\ESP8266_Basic\ESP8266_Basic.cpp:1:

C:\Users\Admin\Documents\Arduino\libraries\PubSubClient\src/PubSubClient.h:121:18: note: PubSubClient& PubSubClient::setCallback(std::function<void(char*, unsigned char*, unsigned int)>)

PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE);

^

C:\Users\Admin\Documents\Arduino\libraries\PubSubClient\src/PubSubClient.h:121:18: note: no known conversion for argument 1 from 'std::_Bind_helper<false, void (ESP8266_Basic::)(char, unsigned char*, unsigned int), ESP8266_Basic* const>::type {aka std::_Bind<std::_Mem_fn<void (ESP8266_Basic::)(char, unsigned char*, unsigned int)>(ESP8266_Basic*)>}' to 'std::function<void(char*, unsigned char*, unsigned int)>'

exit status 1
Fehler beim Kompilieren.

Muss ich in meiner Klasse die typedef MQTT_CALLBACK_SIGNATURE verwenden bzw. selber definieren?

Muss ich in meiner Klasse die typedef MQTT_CALLBACK_SIGNATURE verwenden bzw. selber definieren?

Natürlich. typedef ist was ganz anderes als #define, auch wenn der Effekt ähnlich ist

Das sah mal grundlegend so aus:

#include <functional>
typedef std::function<void(void)> CallbackFunction;

...

 void setCallback(CallbackFunction c)
 {
    callback = c;
 }

...

private:
  CallbackFunction callback;

Da muss aber noch mehr mit Compile-Schaltern gemacht werden wenn das auf einem Standard-Arduino kompilieren soll...

C++11 hat da übrigens auch eine schöne Alternative zu typedef. Aliases mit using:

Das sah mal grundlegend so aus:

Das war meine eigene Klasse, die pubsuclient ist eine fertife Klasse.
Die wollte es zwecks Updatebarkeit nach Möglichket nicht anfassen.

Daher müsste ich den Callback-Aufruf aus meiner Klasse entsprechend anpassen.
Daher ja die Frage wie die Übergabe des Zeigers aus meiner Klasse heraus aussehen muss.

Ich schaue mir das nochmal an, wie ich die typedef in meine Klasse einbinden müsste.

Mir ging es um das typedef und wie der Parameter von setCallback deklariert ist. Das deine Funktionen andere Parameter haben ist nebensächlich

In meiner Klasse haben wir über typedef in der Funktion die zurückruft einen Funktionstypen definiert >CallbackFunction.

Zur Übergabe des Zeigers wird eine Fuktion vom Typ CallbackFunction aufgerufen.

Mich verwirrt jetzt die die definition über #define, hier kommt noch das MQTT_CALLBACK_SIGNATURE hinzu.

Muss die Callbackfunktion in meiner Klasse vom Typ MQTT_CALLBACK_SIGNATURE oder >callback sein?

.h

  #include <functional>
  #define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback  


public:
  void mqttBroker_callback(MQTT_CALLBACK_SIGNATURE);

.cpp

ESP8266_Basic::ESP8266_Basic() : webServer(), 
                                 mqtt_client(wifi_client)
								 {
								 
  webServer.set_saveConfig_Callback(std::bind(&ESP8266_Basic::cfgChange_Callback, this));
  mqtt_client.setCallback(std::bind(&ESP8266_Basic::mqttBroker_callback, this));
}


void ESP8266_Basic::mqttBroker_callback(MQTT_CALLBACK_SIGNATURE) {
}

???

mqtt_client.setCallback(std::bind(&ESP8266_Basic::mqttBroker_callback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));

Das hier funktioniert auch außerhalb des Konstruktors.

Das schaut halt wieder nach "wild Code eintippen und hoffen dass es funktioniert" aus :slight_smile:

Wenn es Dinge wie typedef und Typ-Aliasing gibt muss man nicht sowas unsicheres wie ein Makro verwenden.