[erledigt]char Strings an classe geben

Hallo,

für eine Anwendung habe ich ein Meldesystem gemacht. In Anlehnung and den Industriestandard mit den Einträgen Meldung kommt , Meldung geht, Meldung quittiert. Sammelmeldung usw. Dabei werden die einzelnen Zustände in je einem Bit eines 32 bit Wortes verarbeitet. Allerdings und jetzt kommt das “aber” ist das Ganze im Sketch eingebunden so daß man es schlecht wiederverwenden kann. Am gescheitesten wäre es natürlich das ganze in eine classe oder sogar lib zu packen. Leider scheitert es daran das ich die im Hauptsketch deffinierten Meldetexte nicht in die Klasse bekomme. Letztlich entstehen dann Methoden wie mld.kommt(mldnr) , mld.geht(mldnr) die bei eintreffen des Ereignisses aufgerufen werden. Das hab ich jetzt erst mal weggelassen. Zum Testen gibt es jetzt erst mal die methode disp(mldnr) bei der der zugehörige Text ausgegeben werden soll.
Es ist wie es ist bei * oder & stellen sich mir die Nackenhare auf :wink:

Hat da jemand einen heißen Tip wie ich das hinbekommen könnte.

Hier mal das grobe Gerüst

const char* text[] = {"Meldung 0", "Meldung 1",
                      "Meldung 2", "Meldung 3",
                      "das ist 5", "das ist6",
                      "AB", "Letzter "
                     };

byte max;

class Meldung {

  public:
    char* meld;

    // Konstruktor wie ???
    Meldung(char *meld): meld{meld} {}

    // erst mal sinnfreie Methode
    void disp(byte nr) {
      Serial.print(meld[nr]);
    }
};

Meldung mld(*text); // objekt erstellen

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  max = sizeof(text) / sizeof(text[0]);
  Serial.print ("Anzahl "); Serial.println(max);
  for (byte i = 0; i < max; i++) {
    Serial.println(text[i]);
  }

// Aufruf methode 
mld.disp(1);
  
}

void loop() {
  // put your main c//ode here, to run repeatedly:

}

Letztendlich wird das ja ein 2-dimensionales char-Array. Dabei muss die innere Dimension (die maximale Länge der Texte+1) konstant sein.

Also etwa so:

const byte textLen = 10; // incl. '\0'

char text[][textLen] = {"Meldung 0", "Meldung 1",
                                   "Meldung 2", "Meldung 3",
                                   "das ist 5", "das ist6",
                                   "AB", "Letzter "};
                                };

Gruß Tommy

Ich vermute mal, so einfach möchtest Du es nicht haben:

const char* text[] = {"Meldung 0", "Meldung 1",
                      "Meldung 2", "Meldung 3",
                      "das ist 5", "das ist6",
                      "AB", "Letzter "
                     };

class Meldung {
  public:
    void disp(byte nr) {
      Serial.println(text[nr]);
    }
};

Meldung mld; // objekt erstellen

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  byte max = sizeof(text) / sizeof(text[0]);
  Serial.print ("Anzahl "); Serial.println(max);
  for (byte i = 0; i < max; i++) {
    Serial.println(text[i]);
  }

  // Aufruf methode
  mld.disp(1);
  mld.disp(7);
}

void loop() {
  // put your main c//ode here, to run repeatedly:
}

Wenn Du Text global festlegst, kannst Du auch global drauf zugreifen. Außerdem wäre Progmem eine Überlegung wert.

Vermutlich möchtest Du aber andere Dinge tun, nur erschließt sich mir noch nicht, was so genau.

So ganz habe ich auch noch nicht verstanden, was Du eigentlich willst - oder besser gesagt, ich habe es überhaupt nicht verstanden.
Mal ganz unabhängig von deinem Problem mit den Texten. Erklär doch erstmal, was die Klasse machen soll, dann kann man vielleicht eher Tips geben, wie das zu realisieren ist.

Hallo,

@tommy56 ja das ist sicher wahr , zwei Dimmensionen , Ich spühre wieder meine Nackenhare, damit hab ich früher mal unter basic4.5 eine Bruchlandung erlebt. :wink: aus der ich mich mühsam rausgekämpft habe.

Wie macht das eigendlich Serial.print() Damit klappt das ja . Da kann man den char zeiger übergeben und den Index und das klappt. Ich vermute mal stream spielt da mit.

@agmue ja da geht sicher auch , widerspräche aber sicher der Kapselung von Werten für Klassen.

@MicroBahner

ja was soll ich sagen, stell jetzt mal zur Anschauung eine abgespeckte Version von dem derzeitigen funktionerenden Sketch ein. Dabei werden Messwerte simuliert, die bei besimmten Ereignissen eine Meldung machen sollen. Die könnte man dann auf SD karte oder in eine Protokoll Datenbank schreiben. Was auch immer.
Letztlich wollte ich die functionen Störauswertung(), stKommt(), stGeht() und was sonst noch nötig ist in einer classe zusammen fassen die man leichter wieder verwenden kann.

der Sketch läuft so wie er soll , man benötigt etwas Geduld, die Meldungen sollen einen ja nicht zuknallen. Was fehlt ist die Angabe von Datum/Zeit aber das ist nicht das Problem, hab ich jetzt mal rausgenommen damit nur das Wesentliche bleibt. In der Version hab ich noch 2x16 bit Worte verwendet, das wollte ich auf 1x32 bit umstellen, aber das ist ja Wurscht.

Klar man könnte die Ausgabe der Texte auch im Hauptsketch machen, dann muss die Classe den Text nicht kennen. Das währe eine andere Variante.

Letztlich sowas z.B

05.02.2021 10:39.12 Max Temperatur kommt
05.02.2021 10:41:30 Max Temperatur geht
05.02.2021 10:42:01 Max Füllstand kommt
05.02.2021 10:42.00 Max Temperatur quittiert
05.02.2021 11:45:12 Max Füllstand geht

hier der lauffähige Sketch

/* Beispiel Störauswertung max 32 Störungen mit Texten
    Störung kommt, Störung geht , Störung quittiert
    Störtexte liegen in eimen C String Array
    Meldung unter Angabe der Störnummer

   Es fehlt Angabe Datum Zeit
*/

#define MAXWORD 2
uint16_t stoerword[MAXWORD];
uint16_t stoerspeicher[MAXWORD];
uint16_t quitword[MAXWORD];

const char* Stoertext[] = {"Max Füllstand",
                           "Maxtemperatur", "Mintemperatur",
                           "Maxdruck", "Mindruck"
                          };

const byte pinHupe = 7;
const byte pinLimit = 5;
const byte pinquitHupe = 3;
const byte pinquitStoer = 4;
bool quitStoer;
bool quitHupe;
bool Limitswich;

int wert1;    // Werte für die Simulation
int wert2;
int inc;
uint32_t altzeit;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(pinHupe, OUTPUT);
  pinMode(pinLimit, INPUT_PULLUP);
  pinMode(pinquitStoer, INPUT_PULLUP);
  pinMode(pinquitHupe, INPUT_PULLUP);

}

void loop() {

  // Werte simulieren
  if (millis() - altzeit >= 10000) { // geht schön langsam 
    altzeit = millis();

    if (wert1 > 30) inc = -1; // läuft zwischen 20 und 30
    if (wert1 < 20 ) inc = 1;
    wert1 += inc;
    wert2 = 200 + random(-20, 20);
    Serial.print(wert1); Serial.print('\t');
    Serial.print(wert2); Serial.print('\n');

  }

  // Eingänge einlesen
  Limitswich = !digitalRead(pinLimit);
  quitStoer = !digitalRead(pinquitStoer);
  quitHupe = !digitalRead(pinquitHupe);


  // Bitstörung Störung Nr 0
  if (Limitswich)StKommt(0); else StGeht(0);

  // Grenzwerte
  if (wert1 >= 27) StKommt(1); else StGeht( 1);
  if (wert1 <= 22) StKommt (2) ; else StGeht(2);

  //Grenzwert mit Hysterese
  if (wert2 >= 215) StKommt(3); else if (wert2 <= 210) StGeht(3);
  if (wert2 <= 195) StKommt(4); else if (wert2 <= 190) StGeht( 4);


  stoerauswertung();
  // Serial.print(Limit); Serial.print('\t');
  // Serial.print(quitStoer); Serial.print('\t');
  // Serial.print(quitHupe); Serial.print('\n');

  delay(10);// test entprellen 
}


void stoerauswertung() {
  byte nr;
  if (quitHupe) digitalWrite(pinHupe, LOW);
  for (byte wort = 0; wort < MAXWORD; wort++) {
    for (byte bit = 0; bit <= 15; bit++) {
      nr = wort * 15 + bit;

      if (bitRead(stoerword[wort], bit) & !bitRead(quitword[wort], bit)) {
        bitSet(stoerspeicher[wort], bit);
        digitalWrite(pinHupe, HIGH);
        Serial.print(Stoertext[nr]); Serial.println(" kommt");
      }
      else if (!bitRead(stoerword[wort], bit) && bitRead(quitword[wort], bit)) {
        Serial.print(Stoertext[nr]); Serial.println(" geht");
      }
      else if (!bitRead(stoerword[wort], bit)& bitRead(stoerspeicher[wort], bit)&quitStoer) {
        bitClear(stoerspeicher[wort], bit);
        Serial.print(Stoertext[nr]); Serial.println(" quittiert");
      }
    }
    quitword[wort] = stoerword[wort];
  }
}
void StKommt(int nr) {
  byte bit;
  byte wort;
  wort = nr / 16;
  bit = nr % 16;
  bitSet(stoerword[wort], bit);
  //Serial.print(wort); Serial.println(bit);

}

void StGeht(int nr) {
  byte bit;
  byte wort;
  wort = nr / 16;
  bit = nr % 16;
  bitClear(stoerword[wort], bit);

}

Rentner:
@agmue ja da geht sicher auch , widerspräche aber sicher der Kapselung von Werten für Klassen.

Geht es Dir um eine pragmatische Lösung oder um die Kapselung?

Ich habe sowas schon mal mit PROGMEM gelöst, weil bei AVRs Speicher kostbar ist. Daher würde ich Dich gerne für die pragmatische Lösung in diese Richtung schubsen.

Bei veränderlichen Texte sähe das natürlich anders aus.

agmue:
Bei veränderlichen Texte sähe das natürlich anders aus.

Das ist ein wesentlicher Punkt. Wenn die Texte fest sind, gehören sie mit in die Klasse rein.
Wenn sie variabel sind, hängt es noch davon ab, ob sie sich während der Anwendung ändern können, oder ob sie instanzspezifisch sind, dann aber innerhalb einer Instanz fest.

Eine Lösung mit PROGMEM:

#include <avr/pgmspace.h>
const char M0[] PROGMEM = {"Meldung 0"};
const char M1[] PROGMEM = {"Meldung 1"};
const char M2[] PROGMEM = {"Meldung 2"};
const char M3[] PROGMEM = {"Meldung 3"};
const char M4[] PROGMEM = {"das ist 4"};
const char M5[] PROGMEM = {"das ist 5"};
const char M6[] PROGMEM = {"AB"};
const char M7[] PROGMEM = {"Letzter "};

const char *const texte[] PROGMEM = {M0, M1, M2, M3, M4, M5, M6, M7};
const byte anzahltexte = sizeof(texte) / sizeof(texte[0]);

class Meldung {
  public:
    void disp(byte nr) {
      char buf[30];  // make sure this is large enough for the largest string it must hold
      strcpy_P(buf, (char *)pgm_read_word(&(texte[nr])));  // Necessary casts and dereferencing, just copy.
      Serial.print(F("Methode disp: "));
      Serial.println(buf);
    }
};

Meldung mld; // objekt erstellen

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  Serial.print ("\nAnzahl "); Serial.println(anzahltexte);
  char buffer[30];  // make sure this is large enough for the largest string it must hold
  for (byte j = 0; j < anzahltexte; j++) {
    strcpy_P(buffer, (char *)pgm_read_word(&(texte[j])));  // Necessary casts and dereferencing, just copy.
    Serial.println(buffer);
  }

  // Aufruf methode
  mld.disp(1);
  mld.disp(7);
}

void loop() {
  // put your main c//ode here, to run repeatedly:
}

“Globale Variablen verwenden 196 Bytes”

Du kannst die Texte auch in der Methode lagern:

class Meldung {
  public:
    void stoerauswertung() {
      disp(1);
      disp(2);
      disp(7);
    }
    
    void disp(byte nr) {
      const char * texte[] = {"Meldung 0","Meldung 1","Meldung 2","Meldung 3","das ist 4","das ist 5","AB","Letzter "};
      const byte anzahltexte = sizeof(texte) / sizeof(texte[0]);
      Serial.print ("\nAnzahl "); Serial.println(anzahltexte);
      Serial.print(F("Methode disp: "));
      Serial.println(texte[nr]);
    }
};

Meldung mld; // objekt erstellen

void setup() {
  Serial.begin(9600);
  // Aufruf methode
  mld.stoerauswertung();
}

void loop() {
  // put your main c//ode here, to run repeatedly:
}

“Globale Variablen verwenden 284 Bytes”

Konstante Texte kann man auf einem AVR ruhig global im PROGMEM verwenden. Speicher sparen ist da wichtiger als Kapselung. Nur meine unmaßgebliche Meinung.

Hilft Dir das?

Das eine schließt das andere doch nicht aus :wink: :

#include <avr/pgmspace.h>

class Meldung {
  private:
   // 'static' sind Klassenvariable ( gibt's nur einmal für alle Instanzen, müssen außerhalb der Klasse definiert werden )
    static const char M0[] PROGMEM;
    static const char M1[] PROGMEM;
    static const char M2[] PROGMEM;
    static const char M3[] PROGMEM;
    static const char M4[] PROGMEM;
    static const char M5[] PROGMEM;
    static const char M6[] PROGMEM;
    static const char M7[] PROGMEM;
    static const char *const texte[] PROGMEM;
    static const byte anzahltexte;

    static uint8_t instanzZaehler;

    uint8_t _instanzNr;

  public:
    Meldung() { 
      _instanzNr = instanzZaehler++;
    }
    void disp(byte nr) {
      char buf[30];  // make sure this is large enough for the largest string it must hold
      strcpy_P(buf, (char *)pgm_read_word(&(texte[nr])));  // Necessary casts and dereferencing, just copy.
      Serial.print(F("Instanz Nr. "));
      Serial.print(_instanzNr);
      Serial.print(F(" Methode disp: "));
      Serial.println(buf);
    }

    uint8_t getAnzahlTexte() {
      return anzahltexte;
    }
};

// Definitionen der Klassenvariablen:
uint8_t Meldung::instanzZaehler = 0;
const char Meldung::M0[] PROGMEM = {"Meldung 0"};
const char Meldung::M1[] PROGMEM = {"Meldung 1"};
const char Meldung::M2[] PROGMEM = {"Meldung 2"};
const char Meldung::M3[] PROGMEM = {"Meldung 3"};
const char Meldung::M4[] PROGMEM = {"das ist 4"};
const char Meldung::M5[] PROGMEM = {"das ist 5"};
const char Meldung::M6[] PROGMEM = {"AB"};
const char Meldung::M7[] PROGMEM = {"Letzter "};

const char *const Meldung::texte[] PROGMEM = {M0, M1, M2, M3, M4, M5, M6, M7};
const byte Meldung::anzahltexte = sizeof(texte) / sizeof(texte[0]);
// ^^^^^^^^^^^^^^Ende Definitionen der Klassenvariablen^^^^^^^^^^^^

Meldung mld; // objekt erstellen
Meldung mld2; // objekt erstellen

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.print ("\nAnzahl "); Serial.println(mld.getAnzahlTexte() );

  // Aufruf methode
  mld.disp(1);
  mld2.disp(0);
  mld.disp(7);
  mld2.disp(6);
}

void loop() {
  // put your main c//ode here, to run repeatedly:
}

Hallo,

Das sieht gut aus muss ich mir jetzt mal rein ziehen. Mit PROGMEM hab ich bisher noch nichts gemacht , also doch was dazu gelernt.

Danke

Gruß Heinz

Rentner:
... also doch was dazu gelernt.

Das ist unvermeidlich!

Ich auch, der springende Punkt, das tanzende Komma: "müssen außerhalb der Klasse definiert werden"

Danke!

Hallo,

@agmue die Texte innerhalb der classe festlegen war schon klar, aber das war nicht der Sinn. Die Texte gehören ja zu der Anwendung. Ein Meldung "MaxFüllstand" kann in einer anderen Anwendung ein "Min Fahrweg" sein. Die Melde classe sollte universal sein.

danke nochmal auch an Franz-Peter Thema ist gelöst muss das jetzt erst mal verdauen :wink: :wink: .

Gruß Heinz

Rentner:
die Texte innerhalb der classe festlegen war schon klar, aber das war nicht der Sinn.

Dann solltest Du bei der Instanziierung die notwendigen Texte übergeben. Etwa so:

#include <avr/pgmspace.h>
const char M0[] PROGMEM = {"Meldung 0"};
const char M1[] PROGMEM = {"Meldung 1"};
const char M2[] PROGMEM = {"Meldung 2"};
const char M3[] PROGMEM = {"Meldung 3"};
const char M4[] PROGMEM = {"das ist 4"};
const char M5[] PROGMEM = {"das ist 5"};
const char M6[] PROGMEM = {"AB"};
const char M7[] PROGMEM = {"Letzter "};

const char *const texte1[] PROGMEM = {M0, M1, M2, M3, M4};
const char *const texte2[] PROGMEM = { M5, M6, M7};
const byte anzahltexte1 = sizeof(texte1) / sizeof(texte1[0]);
const byte anzahltexte2 = sizeof(texte2) / sizeof(texte2[0]);


class Meldung {
  private:
   // 'static' sind Klassenvariable ( gibt's nur einmal für alle Instanzen, müssen außerhalb der Klasse definiert werden )
    static uint8_t instanzZaehler;

    const char *const *_texte PROGMEM;
    const byte _anzahltexte;
    uint8_t _instanzNr;

  public:
    // Initiierungsliste für die als 'const' deklarierten Instanzvariablen:
    Meldung(const char *const *texte, uint8_t anzahl) : _texte( texte ), _anzahltexte( anzahl ) { 
      _instanzNr = instanzZaehler++;
    }
    void disp(byte nr) {
      char buf[30];  // make sure this is large enough for the largest string it must hold
      strcpy_P(buf, (char *)pgm_read_word(&(_texte[nr])));  // Necessary casts and dereferencing, just copy.
      Serial.print(F("Instanz Nr. "));
      Serial.print(_instanzNr);
      Serial.print(F(" Methode disp: "));
      Serial.println(buf);
    }

    uint8_t getAnzahlTexte() {
      return _anzahltexte;
    }
};

// Definitionen der Klassenvariablen:
uint8_t Meldung::instanzZaehler = 0;
// ^^^^^^^^^^^^^^Ende Definitionen der Klassenvariablen^^^^^^^^^^^^

Meldung mld(texte1, anzahltexte1); // objekt erstellen
Meldung mld2(texte2, anzahltexte2); // objekt erstellen

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.print ("\nAnzahl mld "); Serial.println(mld.getAnzahlTexte() );
  Serial.print ("Anzahl mld2 "); Serial.println(mld2.getAnzahlTexte() );

  // Aufruf methode
  mld.disp(0);
  mld2.disp(0);
  mld.disp(1);
  mld2.disp(1);
}

void loop() {
  // put your main c//ode here, to run repeatedly:
}
#include <Streaming.h>

using FlashString = __FlashStringHelper *;

const char meldungen[7][20] PROGMEM =
               {
                 {"Meldung 1"}  , 
                 {"Meldung 2"}  , 
                 {"Meldung 3"}  , 
                 {"das ist 4"}  , 
                 {"das ist 5"}  , 
                 {"AB"}         , 
                 {"Letzter "}
               };

void setup()
{
  Serial.begin(9600);
  Serial << "Start: " << __FILE__ << endl;

  for(const char * m:meldungen)
  { 
      Serial.println(FlashString(m));
  }

  Serial <<  F("-> ") << FlashString(meldungen[0]) << endl;
  Serial <<  F("-> ") << FlashString(meldungen[6]) << endl;
}

void loop()
{

}

MicroBahner:
Dann solltest Du bei der Instanziierung die notwendigen Texte übergeben. Etwa so:

das war meine Ursprüngliche Idee gewesen siehe#1 , hat aber nicht geklappt. PROGMEM hat sich dann dazu gesellt.

Heinz

Hallo Heinz,

Rentner:
das war meine Ursprüngliche Idee gewesen siehe#1 , hat aber nicht geklappt. PROGMEM hat sich dann dazu gesellt.

Das war doch schon ‘fast’ richtig - nur die ‘*’ … :wink:

const char* text[] = {"Meldung 0", "Meldung 1",
                      "Meldung 2", "Meldung 3",
                      "das ist 5", "das ist6",
                      "AB", "Letzter "
                     };

byte max;

class Meldung {

  private:
    const char **meld;

  public:
    // Konstruktor wie ???
    Meldung(const char **meld): meld{meld} {}

    // erst mal sinnfreie Methode
    void disp(byte nr) {
      Serial.print(meld[nr]);
    }
};

Meldung mld(text); // objekt erstellen

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  max = sizeof(text) / sizeof(text[0]);
  Serial.print ("Anzahl "); Serial.println(max);
  for (byte i = 0; i < max; i++) {
    Serial.println(text[i]);
  }

  // Aufruf methode
  mld.disp(1);

}

void loop() {
  // put your main c//ode here, to run repeatedly:

}

Aber bei dem knappen RAM auf den standard-Arduinos hat PROGMEM auch schon was für sich 8)

Rentner:
Die Texte gehören ja zu der Anwendung. Ein Meldung "MaxFüllstand" kann in einer anderen Anwendung ein "Min Fahrweg" sein. Die Melde classe sollte universal sein.

Dann kannst Du den Text übergeben wie bei Print:

class Meldung {
  public:
    void stoerauswertung_A() {
      const char * texte[] = {"Meldung 0","Meldung 1","Meldung 2","Meldung 3","das ist 4","das ist 5","AB","Letzter "};
      const byte anzahltexte = sizeof(texte) / sizeof(texte[0]);
      Serial.print ("\nAnzahl "); Serial.println(anzahltexte);
      disp(texte[1]);
    }
    
    void stoerauswertung_B() {
      const char * texte[] = {"Stoerung 0", "Stoerung 1", "Stoerung 2", "Stoerung 3", "Stoerung 4"};
      const byte anzahltexte = sizeof(texte) / sizeof(texte[0]);
      Serial.print ("\nAnzahl "); Serial.println(anzahltexte);
      disp(texte[4]);
    }
    
    void disp(const char * mld) {
      Serial.print(F("Methode disp: "));
      Serial.println(mld);
    }
};

Meldung mld; // objekt erstellen

void setup() {
  Serial.begin(9600);
  // Aufruf methode
  mld.stoerauswertung_A();
  mld.stoerauswertung_B();
}

void loop() {
  // put your main c//ode here, to run repeatedly:
}

Das Büchlein https://forum.arduino.cc/index.php?topic=715817.0 ist wirklich schick - ich hab damit angefangen, mehr nicht...

Kurze Frage - nicht dreschen - ginge als Lösungsansatz auch vektor???

Bei AVR nicht, da nix vector.

Die größeren Kesselchen SAM, STM32, ESP, K210 usw. haben das.

combie:
Bei AVR nicht, da nix vector.

Da bin ich mir nicht ganz sicher.

Da hat sich anscheinend jemand Gedanken gemacht. Ich hatte das nur während dem lesen in einem anderen sort()-Beispiel gelesen... das es eine solche lib geben muss. Der Verweis lief auf: