Erste Gehversuche mit Klassen

Hallo zusammen,

Ich versuche Meinen Code gerade lesbarer zu machen und Elemente zu kapseln.
=> Aktuell scheitere ich an POST #6 und #12 :face_exhaling:
Hier mal meine soll_so_funktionieren cpp Datei.

class PWM_PARA{
	public:
		PWM_PARA(pwmPin);
		int maxPos; //Endpunkt Max
		int nullPos; // mittelpunkt
		int minPos; //Endpunkt Min
		void saveData(){/* Daten Persistent speichern*/};
		void readData(){/* Persitente Daten speichern*/};
		void calibrirePwm(){/* funktion um werte zu ändern  */} ;
};

class RC_CAR{
	public:
		PWM_PARA pwmLenkwPara;
		RC_CAR();
		int iLenkwinkel;
		
		int RC_AR::funcLenkwinkel(ptrController Controller){ //Übergabe Pointer Controllerobjekt
			if(Controller.l_XAchse >= 0.0){// Controller.l_XAchse = Werte zwischen -1.0 und 1.0 für Position von links bis rechts
				this->iLenkwinkel = pwmLenkwPara.nullPos +((pwmLenkwPara.maxPos - pwmLenkwPara.nullPos)* Controller.l_XAchse);
			else 
				this->iLenkwinkel = pwmLenkwPara.nullPos - ((pwmLenkwPara.nullPos - pwmLenkwPara.minPos ) * Controller.l_XAchse);
			};
			return this->iLenkwinkel;
		};

};


class CONTROLLER{
	public l_XAchse;
//... Klassen um Controllerstatus auf Objektvariablen zu schreiben

};

die passende INO Datei würd so aussehen:

#include <ESP32Servo.h>
#include <RC_Classes.h> // Class RC_CAR und CONTROLLER

#define LENKSERVO_PIN 8


Servo pwmLenkung;
CONTROLLER myXboxContr;
RC_CAR myRcTruck;
int swLenkwinkel;

void setup() {
	myXboxContr.setBtAddr("5C:BA:37:0C:F3:C5") // Mit Controller Verbinden
	pwmLenkung.writeMicroseconds(myRcTruck.pwmLenkwPara.nullPos); // PWM Frequenz zu Beginn setzen
	pwmLenkung.attach(LENKSERVO_PIN); // Pin als Servo PWM definieren

};


void loop(){

	myXboxContr.update(); //neue Daten einlesen
	swLenkwinkel = myRcTruck.funcLenkwinkel(*myXboxContr); // ???  wie kann ich ein Pointer auf das Objekt übergeben an die Funktion übergeben
	pwmLenkung.writeMicroseconds(swLenkwinkel);
};

Die Fragen die ich mir stelle sind:

  1. Wie kann ich die Referenz (pointer) auf das Controllerobjekt an die Funktion übergeben?(ggf. Sogar den Pointer im RC_Car-Objekt speichern und die Funktionen im inneren des RC-Car Objekts ausführen?)
  2. Ich sehe in Beispielcodes öfter mal "xyz -> funcAbc();". Was bedeutet denn diese '->' in dem Zusammenhang? - xyz scheint ein pointer zu sein?
  3. würdet Ihr für alle 3 Klassen eine eigene .h/.cpp -Datei erstellen?

Vielen Dank.

Versuche besser mit Referenzen, als mit Pointern zu arbeiten.
Interne Membervariablen als Public zu deklarieren verstößt gegen das Gebot der Datenkapselung, ist also mindestens schlechter Stil.
Schau Dir vor der Arbeit mit Code ein paar allgemeine Grundlagen der OOP an.

Gruß Tommy

Hi und danke für den Tipp,

hat du ein Link für ein Tutorial zu OOP-Grundlagen?

Und wie würde ich mit Referenzen arbeiten so in etwa?

void writePwm(servo &refServoObjekt){
refServoObjekt.writeMicroseconds(1200);
};

servo direktServoObjekt

//call by reference ?
writePwm(direktServoObjekt);

Ich bevorzuge *.h only, also nutze eher keine *.cpp Dateien

Wenn die Klassen eng zusammen gehören, dann in einer *.h
Sind sie Thematisch getrennt dann bekommt jede eine eigene *.h

Siehe: Member access operators - cppreference.com

Großschrift wird in der Regel nur für defines, bzw. Makros, verwendet.
Selbst gebauten Datentypen schreibt man eher so: class PwmPara. Also KamelHöckerSchreibweise, mit beginnendem Großbuchstaben.

Einen Link zur Referenz hast du schon von mir bekommen.
Empfehlenswert: http://www.cppbuch.de/

Die Buchempfehlung von @combie kann ich empfehlen, ich habe bereits die 2. Ausfertigung gekauft.
Einige Einstiege findet man in der Suchmaschine Deiner Wahl unter "Grundlagen OOP", z.B. das hier.

Gruß Tommy

Vielen dank für Euren Input.

Habe jetzt mal bei '0' angefangen.
Im Projekt habe ich jetzt eine ino und ein .h Datei.
Die sehen so aus:
h-Datei

#include <Bluepad32.h>
#include <uni.h>
#include <Arduino.h> 

class XboxController {
    private:
     bool initOk;
     unsigned long lastUpdate;
     //BP32.forgetBluetoothKeys();
     ControllerPtr myControllers[1];// maximal 1 Controller darf sich verbinden
    
    // This callback gets called any time a new gamepad is connected.
    // Up to 4 gamepads can be connected at the same time.
    void onConnectedController(ControllerPtr ctl) {
        bool foundEmptySlot = false;
        for (int i = 0; i < BP32_MAX_GAMEPADS; i++) {
            if (myControllers[i] == nullptr) {
                Serial.printf("CALLBACK: Controller is connected, index=%d\n", i);
                // Additionally, you can get certain gamepad properties like:
                // Model, VID, PID, BTAddr, flags, etc.
                ControllerProperties properties = ctl->getProperties();
                Serial.printf("Controller model: %s, VID=0x%04x, PID=0x%04x\n", ctl->getModelName().c_str(), properties.vendor_id,
                              properties.product_id);
                  const uint8_t* addr =   properties.btaddr;         
                Serial.printf("BD Addr: %2X:%2X:%2X:%2X:%2X:%2X\n", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
                myControllers[i] = ctl;
                foundEmptySlot = true;
                break;
            }
        };

        if (!foundEmptySlot) {
            Serial.println("CALLBACK: Controller connected, but could not found empty slot");
        };
    };//onConnectedController

    void onDisconnectedController(ControllerPtr ctl) {
        bool foundController = false;

        for (int i = 0; i < BP32_MAX_GAMEPADS; i++) {
            if (myControllers[i] == ctl) {
                Serial.printf("CALLBACK: Controller disconnected from index=%d\n", i);
                myControllers[i] = nullptr;
                foundController = true;
                break;
            }
        }

        if (!foundController) {
            Serial.println("CALLBACK: Controller disconnected, but not found in myControllers");
        }
    };//onDisconnectedController

    void processControllers() {
    for (auto myController : this->myControllers) {
        if (myController && myController->isConnected() ){
          if( myController->hasData() && myController->isGamepad()) {
            processGamepad(myController);
          };
        };
     };
    };

    void processGamepad(ControllerPtr ctl) {
    // There are different ways to query whether a button is pressed.
    // By query each button individually:
    //  a(), b(), x(), y(), l1(), etc...
    if (ctl->a()) {
        //maxLimit = 150;
    //        colorIdx++;
    };

    if (ctl->y()) {
        // Turn on the 4 LED. Each bit represents one LED.
      //maxLimit = 0;
    };
    if (ctl->x() /*|| getUpdate*/) {

        //maxLimit = 110;

    };

    // Another way to query controller data is by getting the buttons() function.
    // See how the different "dump*" functions dump the Controller info.
    //dumpGamepad(ctl);
    };

    public:
      XboxController(){
        initOk = false;

      };
      void setup(){
         bd_addr_t controller_addr;
        // Parse human-readable Bluetooth address.
        //sscanf_bd_addr(controller_addr_string, controller_addr);
        Serial.printf("Firmware: %s\n", BP32.firmwareVersion());
        const uint8_t* addr = BP32.localBdAddress();
        Serial.printf("BD Addr: %2X:%2X:%2X:%2X:%2X:%2X\n", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
        // Setup the Bluepad32 callbacks
        BP32.setup(&XboxController::onConnectedController, &XboxController::onDisconnectedController);

        // "forgetBluetoothKeys()" should be called when the user performs
        // a "device factory reset", or similar.
        // Calling "forgetBluetoothKeys" in setup() just as an example.
        // Forgetting Bluetooth keys prevents "paired" gamepads to reconnect.
        // But it might also fix some connection / re-connection issues.
        //BP32.forgetBluetoothKeys();
      };

      void setBtAdress(const char * controller_addr_string){
        BP32.forgetBluetoothKeys();
        // Somewhere in your "setup" 
        bd_addr_t controller_addr;

        // Parse human-readable Bluetooth address.
        sscanf_bd_addr(controller_addr_string, controller_addr);

        // Notice that this address will be added in the Non-volatile-storage (NVS).
        // If the device reboots, the address will still be stored.
        // Adding a duplicate value will do nothing.
        // You can add up to four entries in the allowlist.
        uni_bt_allowlist_add_addr(controller_addr);

        // Finally, enable the allowlist.
        // Similar to the "add_addr", its value gets stored in the NVS.
        uni_bt_allowlist_set_enabled(true);

      };

      unsigned long update(){
        bool dataUpdated = BP32.update();
        if (dataUpdated){//*
        this->lastUpdate = millis();
        processControllers();
        };
        return (this->lastUpdate - millis() );


      };

};

INO-Datei

#include <C:\Users\User1796\Documents\Arduino\NewRcEsp32\Controller.h>

XboxController myXboxContrl;
unsigned long ctrSilenceTime;

void setup() {
  myXboxContrl.setup();
  ctrSilenceTime = 0;
  // put your setup code here, to run once:

}

void loop() {
  ctrSilenceTime = myXboxContrl.update();

  // put your main code here, to run repeatedly:

}

Allerdings kommt der Fehler:

Compilation error: no matching function for call to 'Bluepad32::setup(void (XboxController::)(ControllerPtr), void (XboxController::)(ControllerPtr))'

und

In file included from C:\Users\User1796\AppData\Local\Arduino15\packages\esp32-bluepad32\hardware\esp32\4.1.0/tools/sdk/esp32s3/include/bluepad32_arduino/include/Bluepad32.h:7,
from C:\Users\User1796\Documents\Arduino\NewRcEsp32\Controller.cpp:1:
C:\Users\User1796\AppData\Local\Arduino15\packages\esp32-bluepad32\hardware\esp32\4.1.0/tools/sdk/esp32s3/include/bluepad32_arduino/include/ArduinoBluepad32.h:68:10: note: candidate: 'void Bluepad32::setup(const ControllerCallback&, const ControllerCallback&)'
void setup(const ControllerCallback& onConnect, const ControllerCallback& onDisconnect);
^~~~~

und

C:\Users\User1796\AppData\Local\Arduino15\packages\esp32-bluepad32\hardware\esp32\4.1.0/tools/sdk/esp32s3/include/bluepad32_arduino/include/ArduinoBluepad32.h:68:10: note: candidate: 'void Bluepad32::setup(const ControllerCallback&, const ControllerCallback&)'
void setup(const ControllerCallback& onConnect, const ControllerCallback& onDisconnect);
^~~~~

Also habe ich noch ein Problem bei der Übergabe der Referenz auf die Funktionen an die Unterfunktion BP32.setup(&XboxController::onConnectedController, &XboxController::onDisconnectedController);.

Hier noch der Beispielcode: BP32_Controler_Example_Line_48

Wie kann ich den Konflikt lösen?

Die Fehlermeldung heißt vermutlich (anders zitiert):

Compilation error: no matching function for call to 'Bluepad32::setup(void (XboxController::*)(ControllerPtr), void (XboxController::* )(ControllerPtr))'

Selbst wenn du die zwei & weglässt, also

BP32.setup(XboxController::onConnectedController, XboxController::onDisconnectedController);

schreibst, ist ein XboxController::ControllerPtr etwas anderes als ein ControllerCallback

Für was genaueres wäre die Definition des Objekts BP32 hilfreich. Generell gibt es Probleme, nicht-statische Methoden einer Klasse woanders als Funktionsnamen (callback) zu verwenden. Das macht auch der Beispielcode nicht.

Hallo,

BP32 ist leider in der Bluepad32 lib versteckt -.-*. Die Stelle mit der Erzeugung von BP32 hab ich selbst auch noch nicht gefunden.
Ohne "XboxController::" meckert der Compiler ja auch.

Mein Ziel ist es ja gerade den Beispiel code -gekürzt- in eine Klasse zu transferieren...

Um zu ergründen was eigentlich gefordert ist, müsste man jetzt die Definition von ControllerCallback finden um zu schauen welche Art Referenz erwartet wird, oder?

Wenn die an BP32.setup übergebenen callback-Funktionen unbedingt in der Klasse definiert sein sollen, mach sie static.

Entschuldige das verstehe ich noch nicht. Die class Static machen oder die BP32.Setup funktion?

Und wie und wo?

class XboxController {
  [...]
     static void onConnectedController(ControllerPtr ctl) {
        bool foundEmptySlot = false;
        ...

onDisconnectedController ebenso, dann kannst du
BP32.setup(XboxController::onConnectedController, ...
aufrufen.

Wie und warum du dort über das einzige Element von myControllers iterierst, verstehe allerdings ich nicht.

Hi,
wenn ich die Funktionen mit static deklariere meckert er das die Variable darin nicht static ist.
Wenn ich die BP32.setup in den Hauptcode (die INO) ziehe meckert er. das er den Scope meine Klasse im Bluepad32 nicht findet.

BP32 wird übrigens hier definiert : git Bluepad32 line 308

wobei setup() hier die referenzen auf die Funktionen auf interne Variablen verschiebt und diese durch die "Update Funktion" In Zeile 185/189 die Ursprungfunktion mit der aktuellen Controllerreferenz versorgen.

Was "ControllerCallback" in Zeile 243/244 bedeutet ist mir allerdings schleierhaft. Es ist ein Objekt das eine Funktionsreferenz widerspiegelt????

Eine Lösung für mein Problem (wie bekomme ich die beiden Funktionen in mein Klassen-Objekt und kann sie trotzdem dem BP32.Setup übergeben) habe trotzdem noch nicht so richtig...

Ja. Die Methode einer Klasse ist etwas anderes als eine Funktion, die ohne ein zugehöriges Objekt ( this-> ) auskommt. Hast dir einen Weg mit einigen Stolpersteinen für deine "Erste Gehversuche mit Klassen" ausgesucht.
Um weiterzukommen müsste mir / dir
ControllerPtr myControllers[1];// maximal 1 Controller darf sich verbinden
klar sein. Willst du nicht einfach solch ein Objekt etwas aufbohren, also als Basisklasse verwenden?
(Mal ganz abgesehen davon, dass ein Array mit 1 Element nicht viel Sinn macht)

Soweit ich das Ganze bisher verstehe:
Es gibt 1 Objekt BP32 mit der Methode setup(), die einmal aufgerufen wird um die einzigen zwei callback-Funktionen zu registrieren. Diese werden dann später mit dem jeweils aktuellen ControllerPtr ctl aufgerufen.

Wofür soll nun deine Klasse XboxController gut sein? Willst du davon mehrere Objekte generieren und XboxController myXboxContrl; ist nur ein erster Test, oder ist das auch so ein einzelnes Objekt wie BP32 ?

Um eine nicht statische Methode als "Callback" zu verwenden, muss man schon etwas tiefer in die Wundertüte greifen.
Lesestoff: std::bind - cppreference.com
Leider gibt es den std Kram eher nicht für AVRs.

Behelfen könnte man sich auch mit Lambda Funktionen.

Hi,

ich versuche den Beispielcode innerhalb einer Klassen zu definieren um in der Main(ino-Datei) mich aufs wesentlich zu konzentrieren also welcher Controller Befehl Steuer welche Funktion im RC-Modell.

Den ganzen "mache beim verbinden des Controllers dies und nimm nur jene BT-Addr und Konvertiere die Signale so..." wollte ich gerne auf 2-3 Funktionsaufrufcodezeile verschlanken.

Das mit dem Array rührt auch noch aus dem Beispiel code dort können sich noch bis zu 4 Controller verbinden. Allerdings ist das für das RC-Modell erstmal nicht geplant. Ich wollte die funktionierenden Beispiel code nur noch nicht soo stark verändern um Fehlerquellen zu minimieren.

Sprich es wird nur ein Controller geben der sich mit einem RC-Model verbinden. Also 1 Obj Controller (E) das mit dem Funktionen eines weiteren Obj RC_Car (V) dann den Output über Servo und binär Signale(A) realisiert.

=> kann ich das BP32 Objekt über eine weitere .cpp erweitern oder müsste ich in der Lib rumwerkeln?

Vielen dank.

Du kannst von BP32 erben und in der Kindklasse dann neue Funktionalitäten hinzu bauen.

Gruß Tommy

Dass mit der cpp verstehe ich nicht.
Würde zu nein tendieren.

In der Lib rum werkeln? Vielleicht.

Vererbung hilft dir nicht?
Denn das ist die übliche Methode um vorhandene Klassen zu nutzen, um zusätzliche Funktionalität hinzuzufügen.

Hi,

Vererbung habe ich ebenso wenig wie Klassen gemacht. Auf die Schnelle wird auch Immer nur von "Vererbung von Klassen" gesprochen.

BP32 ist ja ein Objekt/eine Instanz der Klasse Bluepad32.
D.h. ich müsste die Bluepad32 und nicht das Objekt BP32 vererben um dann die Referenz auf die Kindklasse-Methode(&onConnectedController und &onDisconnectedController) der Mutterklassen-Methode (setup) zu übergeben?
Habe ich das so richtig verstanden?

Vielen Dank.

Also von Bluepad32 erben. Wenn Du die Methoden nicht überschreibst, existieren die genau so im Kind weiter.
Ich habe den Eindruck, dass das von @combie vorgeschlagene Buch so langsam dringend gebraucht wird.

Gruß Tommy

Das sind in der Klasse Bluepad32 keine Methoden sondern Funktionen.
Das solltest du so beibehalten. Die .ino Datei kannst du trotzdem schlank halten.