Klassen und Objekte verschachteln. Wie geht's richtig?

Hallo,

Ich brauche ein Objekt, das zur Laufzeit erstellt wird, welches andere
Objekte enthält, die zur Objekterstellung erstellt werden sollen. Das
ganze soll 1-x LEDs ansteuern.

Schlauerle in mir sagt: klar, zwei Klassen - eine LED() und eine
LEDController().

LEDController bekommt eine attach()-Methode in der LED-Objekte zu einer
Liste hinzugefügt werden.

So weit, so theoretisch. Nur dann steige ich mit den Pointern aus.
Bei einer "modernen" Sprache (bitte nicht hauen) würde ich in etwa
soetwas machen:
(VORSICHT! - PseudoCode)

public class LEDController
{
  int ledCount;
  LED LEDArray[10];
  public LEDController()
  {
    ledCount = 0;
  }

  public void attach(int _pin)
  {
    LEDArray[ledCount] = new LED(_pin);
  }

  public LED getChannel(int _channel)
  {
    return LEDArray[_channel];
  }


}

public class LED
{
  int _pin;
  public LED(int pin)
  {
    pinMode(pin, OUTPUT);
      _pin = pin;
  }

  public void blink()
  {
    digitalWrite(_pin, HIGH);
    delay(250);
    digitalWrite(_pin, LOW);
    delay(250);   
  } 
}


LEDController c1 = new LEDController();

void setup()
{
  c1.attach(D0);
  c1.attach(D1);
  c1.attach(D2);
}

void loop()
{
  c1.getChannel(0).blink();
}

In der richtigen Welt hab ich folgendes probiert:

test.ino

#include "ledcontroller.h"
#include "led.h"

LEDController myLedController;

void setup()
{
  
  myLedController.attach(LED(D1));

  
}

void loop()
{
  myLedController.getChannel(0)->blink();
  
}

LEDController.h

#ifndef ledController_h
#define ledController_h

#include "Arduino.h"
#include "led.h"


class LEDController
{
  public:
    LEDController();
    void attach(LED _led);
    LED getChannel(int _channel);

  private:
    LED _ledArray[10]{};
    int _ledcount;
};
#endif


LEDController.cpp

LEDController::LEDController()
{
  _ledcount = 0;
}

void LEDController::attach(LED _led)
{
  _ledArray[_ledcount] = _led;
  _ledcount++;
}

LED LEDController::getChannel(int _channel)
{
  return _ledArray[_channel];
}

LED.h

#ifndef led_h
#define led_h

#include "Arduino.h"

class LED
{
  public:
    LED(int pin);
    LED();
    void blink();
  private:
    int _pin;
};
#endif

LED.cpp

#include "Arduino.h"
#include "led.h"

LED::LED(int pin)
{
  pinMode(pin, OUTPUT);
  _pin = pin;
  _state = 0;
}

void LED::blink()
{
  digitalWrite(_pin, HIGH);
  delay(250);
  digitalWrite(_pin, LOW);
  delay(250);  
}

Aber - klar, funktioniert so nicht. Ich muss das irgendwie miteinander
verschwurbeln, krieg es aber nicht hin... Ich glaube zu wissen, dass die
Lösung Pointer hat - einfach nur, weil ich sogut wie keine habe :slight_smile:

Erstaunlich!

Deine Erklärungen/Wunschliste verstehe ich noch nicht vollständig....

Aber es kommen einige Schlüsselworte vor, welche mich an meine letzte (noch nicht ganz fertige) "Erfindung" erinnern

Liste
dynamisch erzeugen
LED
Blink

Mit Code, Leid, und abschließendem "Geht doch" (nur nicht ganz so, wie ich wollte)
https://forum.arduino.cc/index.php?topic=596279.msg4049776
evt. kannst dir da ja etwas abschauen

Die dynamische Liste ist das kleinste Problem. Da hab ich mir einfach mit nem std::vector beholfen - der tut was er soll und auf nem ESP8266 fällt der nicht ins Gewicht. Bei z.B. nem ATtiny wüsste ich es nicht.

Ich hab jetzt eine Lösung, die - zumindest in einer einzelnen Datei - funktioniert, wie sie soll.
Ob das die richtige oder falsche Lösung ist, oder ob man das eleganter hätte lösen können, weiß ich noch nicht.

#include <vector>

class LED 
{
	private: 
		int _LEDPin;

	public: 
		LED (int newLEDPin)
		{
			_LEDPin = newLEDPin;
			pinMode(_LEDPin, OUTPUT);
			digitalWrite(_LEDPin, LOW);
		}

		void blink()
		{
			digitalWrite(_LEDPin,HIGH);
			delay(1000);
			digitalWrite(_LEDPin, LOW);
			delay(1000);
		}
};

class LEDController
{
	private:
		std::vector<LED> LEDList;
	
	public:
		void attach(int pin)
		{
			LEDList.push_back(LED(pin));
		}

		LED getChannel(int channel)
		{
			return LEDList[channel];
		}
};

LEDController myController;
void setup()
{
	myController.attach(D1);
	myController.attach(D2);
}
 
void loop()
{
	myController.getChannel(1).blink();	
}

Wir wissen was du meinst, aber ein Vektor ist keine Liste. "Liste" hat in der Informatik eine ganz bestimmte Bedeutung, nämlich eine verkettete Liste:

Ein Vektor dagegen wird im Hintergrund als dynamisches Array implementiert und belegt einen durchgehenden Speicherbereich.

Ja, dass ein Vector keine Liste im eigentlichen Sinn ist, weiß ich. std::list wäre eine Liste - aber der Vector hat funktioniert :wink: Dass die komfortable Lösung auch die Resourcenhungrigste ist, war mir fast klar. Aber das ist mir wie gesagt beim ESP egal. Nachdem aber jetzt offensichtlich jeder verstanden hat, was ich will, gibt's eventuell jemanden, der mir erklären kann, wie man es richtig macht?

P.S: Was ist eigentlich mit meinen ganzen Posts und Karmas passiert? hab mich knapp ein Jahr nichtmehr angemeldet, jetzt ist alles auf Null?

Ich bin zwar kein C++ Experte, aber mir fehlen da "new" für neue LEDs und "&" für die Übergabe per Referenz. Oder sollen für die nirgendwo explizit abgelegten LED Objekte beim Eintrag in die Liste Kopien erstellt werden?

P.S: Was ist eigentlich mit meinen ganzen Posts und Karmas passiert? hab mich knapp ein Jahr nichtmehr angemeldet, jetzt ist alles auf Null?

Date Registered: Feb 13, 2019, 09:38 pm

Ich vermute, dass du dich jetzt unter einem anderen Namen hier registriert hast.
Also dich hier wieder gänzlich neu angemeldet hast.

Wenn du deine alten Threads/Beiträge findest, wird sich das zeigen.
evtl. kann Uwe dich da retten

Aber das ist mir wie gesagt beim ESP egal.

Vom ESP war vorher nicht die Rede!
Denn da gibts ja die STL

Nachdem aber jetzt offensichtlich jeder verstanden hat, was ich will, gibt's eventuell jemanden, der mir erklären kann, wie man es richtig macht?

Habe ich nicht!

Aber ich versuchs mal....

Wenn du Objekte zu Laufzeit erzeugen möchtest, dann geht das nur über den Konstruktor.
Also wohl nicht ohne new.

Um sie dann zu halten, ist schon irgendein Container nötig.
Welcher ist recht egal. Das liegt dann eher daran, wie du in den Container rein greifen möchtest, und wie oft Änderungen am/im Container stattfinden.

Du bist ja nicht der Erste mit solchen Problemen in der C++ Welt.
Und du hast die STL auf deinem ESP.

Du kennst die "OOP Design Patern"?

Ich sachs mal so:
Willst du gleichartige Objekte erzeugen, verwende eine Fabrik.
Auch das Flyweight Pattern könnte was für dich sein.
Alle male findest du in den Pattern einen Ansatz.

Auf den kleineren Arduinos mögen viele der Pattern übertrieben sein, aber auf dem ESP kann man sich das schon eher leisten.

?

Seltsam... ich hab mich ganz normal mit meinen gespeicherten Daten angemeldet, wie immer. Dann kam ne Hinweis zum Datenschutz und dann ging es normal weiter - nur dass ich offensichtlich einen neuen Account gezimmert bekommen hab. Ich werd Uwe mal anfunken deswegen...

Aber zum Thema:
Gehen wir kurz davon aus, dass ich nur überschaubare C++ Kenntnisse habe. Schmeiß mir C#, Java, Javascript, Python, PHP oder was weiß ich was vor die Füße und ich bau dir das in null Komma nix. Aber in C++ steige ich regelmäßig beim verteilen von Pointern und Referenzen aus.

Das, was ich gebastelt habe, funktioniert zwar, gefällt mir aber nicht besonders. Der Vector braucht wahrscheinlich mehr Platz und Zeit als ein einfaches Array mit fester Länge - und man hat ja eigentlich eine endliche Anzahl an GPIOs, also macht die dynamische Addressierung nur begrenzt Sinn. Das war wahrscheinlich ein Hirnpfurz von wegen "eigentlich will ich eine Klasse, die alles für immer kann".

Ich hab aber z.B. jetzt das Problem, dass ich keinen Schimmer habe, wie ich z.B. das Blinken ohne Delays in die Klasse implementiert bekomme. Ich habs mit nem 'static' hinbekommen, aber dann muss eine Variable für alle Instanzen reichen - kein synchromes blinken. Ich bekomm aber anders keinen Zugriff auf die Variable.

unsigned long last;

void blink()
{
   unsigned long now = millis();

   if (now - last >= 2000UL)
   {
      last = now;
      _state = _state==LED_OFF ? LED_ON : LED_OFF;
      digitalWrite(_pin, _state);
      Serial.println(_state);
   }
   yield();
}

Das hier "klappt" - bis ich ne zweite blinken brauch:

[code]

void blink()
{
   unsigned long now = millis();
   static unsigned long last;

   if (now - last >= 2000UL)
   {
      last = now;
      _state = _state==LED_OFF ? LED_ON : LED_OFF;
      digitalWrite(_pin, _state);
      Serial.println(_state);
   }
   yield();
}

[/code]

Ich habs mit nem 'static' hinbekommen, aber dann muss eine Variable für alle Instanzen reichen - kein synchromes blinken.

Ja, das ist die Bedeutung/Aufgabe von static an der Stelle.

Also weg lassen!
Du musst aus der lokalen statischen eine Instanzvariable machen.

Ich bekomm aber anders keinen Zugriff auf die Variable.

Wieso nicht?

class Test
{
  private:
    int test; // gute definition

  public:
  Test():test(42){}
  
  void tuDasEine()
  {
    Serial.println(test);
  }
  
  void tuDasAndere()
  {
    static int test = 43; // boese definition
    Serial.println(test);
  }
};

marcusw:
Der Vector braucht wahrscheinlich mehr Platz und Zeit als ein einfaches Array mit fester Länge

Ein Vektor ist im Hintergrund auch nur ein Array. Klar hast du etwas Overhead, aber zeitmäßig schlägt der erst zu wenn die Größe geändert werden muss.

Wenn du eine maximale Anzahl an Objekte hast tut es natürlich auch ein Array

Hmm... ich hab jetzt ein bisschen debuggen versucht - ich versteh ehrlich gesagt nicht, was er tut und wieso es nicht funktioniert:

      void blink()
      {
      unsigned long now = millis();
      if (now - last >= 2000UL)
      {
        Serial.println();
        sprintf (_msg , "now: %d ; last: %d ; now-last: %d  ", now, last, (now - last));
        Serial.println (_msg);

        last = now;
        _state = _state==LED_OFF ? LED_ON : LED_OFF;
        digitalWrite(_pin, _state);

        sprintf (_msg , "now: %d ; last: %d ; now-last: %d  ", now, last, (now - last));
        Serial.println (_msg);
      }
      yield();
    }

generiert folgende Ausgabe:

now: 241 ; last: 241 ; now-last: 0  
now: 241 ; last: 241 ; now-last: 0  

now: 242 ; last: 241 ; now-last: 1  
now: 242 ; last: 242 ; now-last: 0  

now: 244 ; last: 241 ; now-last: 3  
now: 244 ; last: 244 ; now-last: 0  

now: 250 ; last: 241 ; now-last: 9  
now: 250 ; last: 250 ; now-last: 0  

now: 257 ; last: 241 ; now-last: 16  
now: 257 ; last: 257 ; now-last: 0  

now: 264 ; last: 241 ; now-last: 23  
now: 264 ; last: 264 ; now-last: 0  

now: 271 ; last: 241 ; now-last: 30  
now: 271 ; last: 271 ; now-last: 0  

now: 278 ; last: 241 ; now-last: 37  
now: 278 ; last: 278 ; now-last: 0  

now: 285 ; last: 241 ; now-last: 44  
now: 285 ; last: 285 ; now-last: 0  

now: 291 ; last: 241 ; now-last: 50  
now: 291 ; last: 291 ; now-last: 0

Jeweils ein Aufruf von blink(), erste Zeile vor dem Zurücksetzen von "last", zweite danach.
beim zweiten Durchlauf ist last wieder 241?

unsigned long now = 0;
if (now - last >= 2000UL)
Was meist du was dabei raus kommt?

Deine Klasse ist bis zur untestbarkeit verstümmelt!
Die fasse ich so nicht an.

Für das Blinken gibt es eine einfache und eine komplizierte Lösung. Die einfache Lösung geht mit 1 LED, in loop() muß nur die PinNr, Zeit und Intervall bekannt sein. Sollen mehrere LEDs gleichzeitig aber asynchron blinken, dann braucht man die Daten für jede einzelne LED, also in dieser Klasse. In loop() wird dann geprüft, welche aller bekannten LEDs aktuell umgeschaltet werden sollen.

Das läßt sich noch optimieren, indem das jeweils nächstliegende Ereignis ermittelt wird, und damit eine einzige Prüfung reicht. Hint: diese Optimierung kommt von Simula67, ist also schon seit über 50 Jahren bekannt :slight_smile:

combie:
unsigned long now = 0;
if (now - last >= 2000UL)
Was meist du was dabei raus kommt?

Deine Klasse ist bis zur untestbarkeit verstümmelt!
Die fasse ich so nicht an.

Ja... das passierte durch das rumkopieren ins Forum, der hat mir ein paar whitespaces rausgehauen und ich hab dann im Forumstext rumgefrickelt.
Die korrigierte Version ist jetzt im Thread.

es heißt natürlich

void blink()
{
      unsigned long now = millis();
      if (now - last >= 2000UL)
      {
        Serial.println();
        sprintf (_msg , "now: %d ; last: %d ; now-last: %d  ", now, last, (now - last));
        Serial.println (_msg);

        last = now;
        _state = _state==LED_OFF ? LED_ON : LED_OFF;
        digitalWrite(_pin, _state);

        sprintf (_msg , "now: %d ; last: %d ; now-last: %d  ", now, last, (now - last));
        Serial.println (_msg);
      }
      yield();
    }

EDIT: Ich hab mal das komplette Projekt mit hochgeladen.
Mir fällt gerade auf: Wieso ist der überhaupt in der 'if'-Verzweigung? Ist 0 >= 2000?

EDIT2:

DrDiettrich:
Für das Blinken gibt es eine einfache und eine komplizierte Lösung. Die einfache Lösung geht mit 1 LED, in loop() muß nur die PinNr, Zeit und Intervall bekannt sein. Sollen mehrere LEDs gleichzeitig aber asynchron blinken, dann braucht man die Daten für jede einzelne LED, also in dieser Klasse.[...]

Ich glaube, Du fliegst am Ziel vorbei. Die loop()-routine interessiert momentan nicht (?). Das Objekt sollte über den Controller mit dem Vector und seiner blink()-Methode getriggert werden. Das kommt in den Loop. Die blink() soll dann abprüfen ob 2000ms vergangen sind und wenn ja, tu was. Aber momentan funktioniert noch nichtmal die Überprüfung.

Controller.ino (265 Bytes)

LEDController.h (352 Bytes)

LEDObject.h (1023 Bytes)

Mein Code ist besessen... und verhext.
Wenn ich es jetzt laufen lasse, bekomm ich folgende Ausgabe:

now: 2000 ; last: 0 ; now-last: 2000  
now: 2000 ; last: 2000 ; now-last: 0  

now: 2000 ; last: 0 ; now-last: 2000  
now: 2000 ; last: 2000 ; now-last: 0  

now: 2003 ; last: 0 ; now-last: 2003  
now: 2003 ; last: 2003 ; now-last: 0

Wenigstens ist er jetzt in der richtigen Verzweigung. Aber 'last' bleibt 0;

Umgekrempelt, aber nicht poliert

//#include "arduino.h"
#include "Arduino.h"

class LED 
{ 
  
  private: 
    const byte       pin;
   // int       _brightness;
   // int       _minValue, _maxValue;
   // uint8_t   _state = LED_OFF; 
    bool state;
    const unsigned long interval;
    unsigned long last;
    char msg[50];

  public:
   // enum { LED_OFF = LOW, LED_ON = HIGH };

    LED (const byte pin,const unsigned long interval = 2000UL):pin(pin),state(false),interval(interval){}
    
    void begin()
    {
//      _pin = pin;

      pinMode(pin, OUTPUT);
      digitalWrite(pin,state);
    }
/*        
    void fade()
    {
      for (int i=0; i<_maxValue; i++)
      {
        analogWrite(_pin,i);
      }
    }

*/
    
    void blink()
    {
      unsigned long now = millis();

      if (now - last >= interval)
      {
        Serial.println();
        sprintf (msg , "now: %lu ; last: %lu ; now-last: %lu  ", now, last, (now - last));
        Serial.println (msg);

        last = now;
        state = !state;
        digitalWrite(pin, state);

        sprintf (msg , "now: %lu ; last: %lu ; now-last: %lu  ", now, last, (now - last));
        Serial.println (msg);
      }
     // yield();
    }
};

LED led  {13,500};

void setup() 
{
 Serial.begin(9600);
 Serial.println("Start");
 led.begin(); // nach der Hardwareinitialisierung
}

void loop() 
{
  led.blink();
}

LEDController.h

Hier wird das LED Objekt kopiert.

    LED getChannel(int channel)
    {
      return LEDList[channel];
    }

Du willst das aber nicht

    LED &getChannel(int channel)
    {
      return LEDList[channel];
    }

Alternativ

    LED *getChannel(int channel)
    {
      return &LEDList[channel];
    }

ungetestet

Super Vielen Dank. Ich muss mich da mal durchwühlen... Ich hab da so einen Verdacht, was der eigentliche Fehhler war. Was ist eigentlich der Unterschied zwischen der Inline Initialisierung des Konstruktors und der Art, wie ich es gemacht hab?

Au Backe.

combie, du hast mir in den letzen 8 Stunden mehr über C++ beigebracht, als ich selbst die letzten 8 Jahre, Danke.

Meine ALLLERERSTE Vermutung war richtig. Und deine allerletzte Ergänzung war die Lösung. Das komplett erratische Verhalten war einem Schluckauf geschuldet, weil ich jedesmal das komplette Vectorobject kopiert hab. Als ich es durch

LED &getChannel(int channel)

ersetzt hab funktioniert es natürlich.

Ich hasse C++. KOPF->TISCH

Danke für die Blumen!

Ich hasse C++. KOPF->TISCH

Der Tisch kann nichts dafür.
Ich empfehle dir ein schönes dickes, über 1000 seitiges, Buch

Was ist eigentlich der Unterschied zwischen der Inline Initialisierung des Konstruktors und der Art, wie ich es gemacht hab?

Die Initialisierungsliste erlaubt es Konstanten und Referenzen in das Objekt zu injizieren.

Auch musste pinMode() aus dem Konstruktor raus.
Da zu dem Zeitpunkt noch nicht unbedingt gewährleistet ist, dass die Peripherie schon fertig eingerichtet ist.