ESPhome eigene Klasse und Wire.h

Moin!

Ich wollte gerade meine eigene Klasse schreiben, die über I2C Daten an einem Raspberry zur Verfügung stellen soll. Allerdings Scheiter ich wohl schon an den Basics.

Kann mir hier jemand weiter helfen?

Der Fehler ist folgendes (übrigens das gilt das selbe für die onRequest Funktion, wenn diese nicht auskommentiert ist):

src/I2C_transmitter.h: In member function 'virtual void I2C_transmitter::setup()':
src/I2C_transmitter.h:11:32: error: invalid use of non-static member function
     Wire.onRequest(requestEvent); // register event
                                ^
*** [.pioenvs\suzs\src\main.cpp.o] Error 1

Mein Code (erst einmal die Basics :wink:

class I2C_transmitter : public Component {
 public:
  void setup() override {
    Wire.begin(8);                // join i2c bus with address #8
    Wire.onRequest(requestEvent); // register event
//    Wire.onReceive(receiveEvent);
}
  
  void loop() override {

}
  void requestEvent() {
  Wire.write("hello "); // respond with message of 6 bytes
  // as expected by master
}

  void receiveEvent(int howMany) {

    myRegisterAddress = Wire.read();
}
};

Hat jemand eine Idee?

Du kannst keine Methoden als Callback nutzen.
Zumindest nicht ohne jeden Wrapper.

evtl, löst

#include <functional>

Einen Teil deiner Sorgen.

Danke für die rasche Rückmeldung.

Das include ändert leider nichts. Liegt es daran, dass es eine "externe" Klasse ist? Denn sonst habe ich es immer so genutzt und im " Master Reader/Slave Sender" Beispiel ist es auch so implementiert.

Du hast den Unterschied zwischen Methoden ("member function") und Funktionen nicht verstanden

Anderes Stichwort: this-Zeiger

Und auch mal darüber nachdenken was "static" in Klassen macht. Dann verstehst du auch weshalb das in der Fehlermeldung erwähnt wird

Du sollst das ja auch nicht nur hinschreiben, sondern nachschauen wie man es verwendet. Dazu musst du aber erst mal verstehen wieso es so nicht geht.

Man kann kann das per Hand machen. Mit einer Wrapper- und/oder Labmda-Funktion. Functional nimmt einem lediglich etwas von der Arbeit ab. Wobei es gut es zu wissen was da geschieht

Ja, nee...

Hier mit 2 einfachen Lambda Funktionen als Wrapper, und ohne functional, damit es auch auf AVR Arduinos läuft.

#include <Wire.h>


class Component 
{
  public:
  virtual void setup() = 0;
  virtual void loop()  = 0;
};

class I2C_transmitter : public Component
{
  public:
    virtual void setup() override
    {
      //    Wire.onReceive(receiveEvent);
    }

    virtual void loop() override
    {

    }
    void requestEvent()
    {
      Wire.write("hello "); // respond with message of 6 bytes
      // as expected by master
    }

    void receiveEvent(int howMany)
    {

    //  myRegisterAddress = Wire.read();
    }
};


I2C_transmitter trans;

void setup()
{
  Wire.begin(8);                // join i2c bus with address #8
  Wire.onRequest([](){trans.requestEvent();});
  Wire.onReceive([](int howMany){trans.receiveEvent(howMany);});
}

void loop()
{
}

Deswegen mag ich lambda-Funktionen nicht. :upside_down_face:

Und wenn es wie hier nur um ein einzelnes statisches (globales) Objekt geht, braucht man ja gar keine Methode als callback, sondern schreibt eine simple Funktion.

Uff, ich wollte doch einfach nur Werte aus ESPHome über I2C verfügbar machen :weary:

Vielen Dank für das Beispiel! Hätte aber nicht gedacht, dass es so kompliziert ist...
Ich werde mal schauen, wie weit ich komme.

Ist es richtig, dass die Override Funktionen leer bleiben und alles nur in die "normalen voids" kommt?

Das sind keine Funktionen, sondern virtuelle Methode, welche die (leeren) Methoden der "pure abstract" Basisklasse überschreiben.

Sowas gibts nicht, void heißt "unbestimmt" oder "nicht vorhanden". Womit hier gemeint ist, dass diese Methoden keinen Wert zurückgeben.

Das bleibt dir überlassen.......
Habe nur dein, bis zur untestbarkeit verstümmeltes, Beispiel lauffähig gemacht.
Was du da in den Methoden erledigen willst, davon habe ich überhaupt keine Ahnung.

Tipp:

Einfacher geht es doch kaum. Das einzige was sich unterscheidet ist die Übergabe der Adresse der Funktion/Methode. Der Rest ist nur Teil des Beispiels. Du brauchst da keine virtuellen Methoden, aber es kann ja niemand wissen wie Component bei dir aussieht.

Lambda-Funktion sind letztlich auch nicht so kompliziert wie sie aussehen. Siehe hier:

Die eckigen Klammern geben nur an welche Variablen in der Lamda-Funktion sichtbar sind. Eben keine.

Das ist aber sehr schön, weil man es sich sparen kann eine extra Wrapper-Funktion schreiben. Dadurch ist es eben einfacher als andere Optionen

Ich halte C++ (auch) für eine recht "hässliche" Sprache.
Aber andererseits auch für sehr mächtig.
Es ist ein knackiges Werkzeug mit vielen Ecken und scharfen Kanten.

Zudem ist die Anzahl der Alternativen im Arduino Umfeld sonst eher nahe Null. C++, C und ASM, das wars.
ASM und C sind auch nicht wirklich "schöner", als C++

Eigentlich ist es schon etwas traurig, dass Wire kein Interface für sowas bereit stellt. z.B. in einer vereinfachten Form des:

Die Lamda Funktionen sind nur ein ganz naiver Abklatsch des:

Der Hinweis auf den Unterschied zwischen Methode und Funktion scheint untergegangen zu sein.
Wenn dir (@tsaG1337) alles zu kompliziert vorkommt, schreib eine einfache Funktion als callback, die bei Bedarf das I2C_transmitter-Objekt trans verwendet (evtl. eine dessen Methoden aufruft).

Genau wie Wire ein Singleton-Objekt ist, könntest du das auch mit deinem I2C_transmitter machen, aber das wird dann wieder verwirrend, fürchte ich.

Ja!
Das Singletone Pattern ist nicht gerade trivial.

#include <Wire.h> // die Lib findest du selber ;-)


template<typename ClientClass>
class Singleton
{
     
  private:
    static ClientClass *client;
    
  protected:
    Singleton(){}
    Singleton(const Singleton<ClientClass>&)              =delete;
    Singleton(Singleton<ClientClass>&&)                   =delete;
    Singleton & operator=(const Singleton<ClientClass>&)  =delete;
    Singleton & operator=(Singleton<ClientClass>&&)       =delete;

  public:
    static  ClientClass &instance()
    {
      if (!client) client = new ClientClass;
      return *client;
    }
     
    virtual ~Singleton()
    {
       delete client;
       client = nullptr;
    }
  };
 
template<typename ClientClass> ClientClass* Singleton<ClientClass>::client = nullptr;


class Component 
{
  public:
  virtual void setup() =0;
  virtual void loop()  =0;
};

class I2C_transmitter : public Component, public Singleton<I2C_transmitter>
{
    private:
    friend class Singleton;
    I2C_transmitter(){}
    
  public:
    virtual void setup() override
    {
       Wire.begin(8);                // join i2c bus with address #8
       Wire.onRequest(requestEvent);
       Wire.onReceive(receiveEvent);
    }

    virtual void loop() override
    {

    }
    static void requestEvent()
    {
      Wire.write("hello "); // respond with message of 6 bytes
      // as expected by master
    }

    static void receiveEvent(int howMany)
    {
       (void)howMany; // "unused" Meldung verhindern
    //  myRegisterAddress = Wire.read();
    }
};



void setup()
{
  I2C_transmitter::instance().setup();
}

void loop()
{  
  I2C_transmitter::instance().loop();

}

Hi, danke für die vielen Antworten!

Ja, ich habe gerade mit meinen eigenen Klassen angefangen und ich komme auch nicht aus dem IT Sektor, daher sind das für mich alles noch Böhmische Dörfer.

Werde versuchen mich mal schlau zu lesen.
Was ich wollte, war eigentlich nur eine eigene Klasse um ein paar Variable von ESPhome über I2C Zugänglich zu machen. :smiley:

Damit wollte ich nur sagen, dass ich mich gewundert habe, das in deinem obigen Beispiel die "normalen voids" sowohl als auch die overrides dazu drin waren.

Hier weiss ich leider nicht was Du meinst. Ich scheiterte schon daran die "on Receive" und "on Request" Funktion zu implementieren. Verstümmelt habe ich da nichts, da es bisher auch nicht mehr gab :slight_smile:

Dann lass mal die overrides weg und sehe was passiert. Abstrakte Methoden ("pure virtual function" in C++; mit dem =0) muss man implementieren. Das ist anders als bei einfachen virtuellen Methoden keine Option.

#include <Wire.h>


class Component 
{
  public:
  virtual void setup() = 0;
  virtual void loop()  = 0;
};

class I2C_transmitter : public Component
{
  public:
 


I2C_transmitter trans;

void setup()
{
  Wire.begin(8);                // join i2c bus with address #8
  Wire.onRequest([](){trans.requestEvent();});
  Wire.onReceive([](int howMany){trans.receiveEvent(howMany);});
}

void loop()
{
}

Fehlermeldung:

In file included from src/main.cpp:124:0:
src/I2C_transmitter.h:11:32: error: reference to 'Component' is ambiguous
 class I2C_transmitter : public Component
                                ^
src/I2C_transmitter.h:4:7: note: candidates are: class Component
 class Component
       ^
In file included from src/esphome\components\adc\adc_sensor.h:3:0,
                 from src/esphome.h:2,
                 from src/main.cpp:3:
src/esphome/core/component.h:58:7: note:                 class esphome::Component
 class Component {
       ^
In file included from src/main.cpp:124:0:
src/I2C_transmitter.h:17:17: error: field 'trans' has incomplete type 'I2C_transmitter'
 I2C_transmitter trans;
                 ^
src/I2C_transmitter.h:11:7: note: definition of 'class I2C_transmitter' is not complete until the closing brace
 class I2C_transmitter : public Component
       ^
src/main.cpp:127:6: error: 'void I2C_transmitter::setup()' cannot be overloaded
 void setup() {
      ^
In file included from src/main.cpp:124:0:
src/I2C_transmitter.h:19:6: error: with 'void I2C_transmitter::setup()'
 void setup()
      ^
c:\Dropsbpox\Elektronik\Lichterfest\Firmware\ESPHome\Config.yaml:407:6: error: 'void I2C_transmitter::loop()' cannot be overloaded
In file included from src/main.cpp:124:0:
src/I2C_transmitter.h:26:6: error: with 'void I2C_transmitter::loop()'
 void loop()
      ^
c:\Dropsbpox\Elektronik\Lichterfest\Firmware\ESPHome\Config.yaml:409:1: error: expected '}' at end of input
In file included from src/main.cpp:124:0:
src/I2C_transmitter.h: In lambda function:
src/I2C_transmitter.h:22:23: error: 'this' was not captured for this lambda function
   Wire.onRequest([](){trans.requestEvent();});
                       ^
src/I2C_transmitter.h:22:23: error: invalid use of non-static data member 'I2C_transmitter::trans'
src/I2C_transmitter.h:17:17: note: declared here
 I2C_transmitter trans;
                 ^
src/I2C_transmitter.h: In lambda function:
src/I2C_transmitter.h:23:34: error: 'this' was not captured for this lambda function
   Wire.onReceive([](int howMany){trans.receiveEvent(howMany);});
                                  ^
src/I2C_transmitter.h:23:34: error: invalid use of non-static data member 'I2C_transmitter::trans'
src/I2C_transmitter.h:17:17: note: declared here
 I2C_transmitter trans;
                 ^
c:\Dropsbpox\Elektronik\Lichterfest\Firmware\ESPHome\Config.yaml: At global scope:
c:\Dropsbpox\Elektronik\Lichterfest\Firmware\ESPHome\Config.yaml:409:1: error: expected unqualified-id at end of input
*** [.pioenvs\suzs\src\main.cpp.o] Error 1
======================================================= [FAILED] Took 4.02 seconds ======================================================= 
PS C:\Dropsbpox\Elektronik\Lichterfest\Firmware\ESPHome>

Nein. So nicht. Jetzt hast du die Klasse völlig verstümmelt und der Compiler weiß nicht mehr was was ist. Das meiste sind lediglich Folge-Fehler weil die schließende Klammer der Klasse fehlt

Dieser Satz ist unverständlich!

  1. Du verwendest die Worte falsch. Ich kenne weder "voids", so auch keine "normalen voids" oder "anormale voids", und auch keine "overrides".

  2. Falls du die beiden override Statements/Attribute in meiner Klasse meinst, sorry, aber die sind auf deinen Mist gewachsen. Für die muss ich sämtliche Verantwortung ablehnen, außer, dass ich es lauffähig gemacht habe.

  3. setup() und loop() hast du in die Klasse eingeführt. Also habe ich sie auch da gelassen! Die onRequest() und onReceive() willst du ja auch in deiner Klasse haben. Die habe ich nur nutzbar gemacht.

Also:
Ich verstehe nicht, dein Wundern.
(frei nach Joda)

Okay, dann habe ich es falsch verstanden, danke für die Erklärung.

Generell habe ich dieses "Beispiel" mit den overrides hierher:
https://esphome.io/custom/custom_component.html

Nur, dass ich dies nun mit I2C nutzen möchte. Frei nach diesem Beispiel: