String zerlegen und in array aufteilen?

Hi,
wenn ich einen String als payload von meinem mqtt-Server in der Art wie folgt bekomme:

Außen:2.9;Innen:22.6;Dachboden:15.0;Keller:17.3

ist es möglich diesen in ein Array mit einem einfachen Befehl wie "split" aufzuteilen zwecks weiterer Verarbeitung?

Wetterdaten[4] = { Außen:2.9, Innen:22.6, Dachboden:15.0, Keller:17.3 }

In C werden die Trennzeichen in so einem String gerne durch Nullbytes ersetzt und ein Pointer auf den Anfang von jedem Substring gesetzt. Das spart Speicherplatz und Zeit fürs Kopieren.

Ja, aber sorry, :slight_smile: wie tut man in C trennen?

Würde ich nie so machen.... Aber sollte gehen:

String myString = "Außen:2.9;Innen:22.6;Dachboden:15.0;Keller:17.3";
String newString[4] = "";
uint8_t zaehler = 0;
void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start..."));
  uint16_t start = 0;
  uint16_t trennung = myString.indexOf(';');
  uint16_t lastTrennung = myString.lastIndexOf(';');
  if (lastTrennung > trennung)
  {
    trennung = myString.indexOf(';', start);
    newString[zaehler] = myString.substring(start, trennung);
    zaehler++;
    start = trennung;
  }
  while (lastTrennung > trennung)
  {
    trennung = myString.indexOf(';', start + 1);
    newString[zaehler] = myString.substring(start + 1, trennung);
    zaehler++;
    start = trennung;
  }
  newString[zaehler] = myString.substring(myString.lastIndexOf(';') + 1, myString.length());
  for (byte b = 0; b < sizeof(newString) / sizeof(newString[0]); b++)
    Serial.println(newString[b]);
}

void loop()
{
}

OK, Danke, das ist nicht gerade ein kleiner Befehl. Wie würdest Du es machen?

Das wird auch nicht kleiner :wink:
Erstmal musst Du feststellen, ob in Deinem String überhaupt ein Trennzeichen drin ist.
Dann musst Du feststellen, ob das erste und das letzte Trennzeichen eine unterschiedliche Position hat.
Dann gehört eine Prüfung rein, ob die Anzahl der Trennzeichen +1 in Dein Array passt.
Und erst dann gehört das geteilt.

Wenn Du mit CharArray und nicht mit String arbeitest, ginge strtok um die Zeichenkette aufzulösen.
Damit könntest dann auch gleich eine Zuordnung zu einer Variablen bauen.
Wie ich so einen Puffer zerlege, habe ich hier gezeigt - im ersten Sketch

void teileBuf(char *buf)

Woher der buf stammt ist dabei vollkommen egal...

Mit strtok. Ich habe in meiner Arduino Anfangszeit mal eine RGB-LED über eine Eingabezeile vom seriellen Monitor gesteuert. Evtl. hilft Dir das beim Verständnis.

Gruß Tommy

Auch mit strtok:

String myString = "Außen:2.9;Innen:22.6;Dachboden:15.0;Keller:17.3";
char wetterdaten[4][20] = {"\0", "\0", "\0", "\0"};

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start..."));
  char zeile[80];
  snprintf( zeile, sizeof(zeile), "%s", myString.c_str() );
  snprintf( wetterdaten[0], sizeof(wetterdaten[0]), "%s", strtok(zeile, ";") );
  snprintf( wetterdaten[1], sizeof(wetterdaten[1]), "%s", strtok(NULL, ";") );
  snprintf( wetterdaten[2], sizeof(wetterdaten[2]), "%s", strtok(NULL, ";") );
  snprintf( wetterdaten[3], sizeof(wetterdaten[3]), "%s", strtok(NULL, ";") );
  Serial.println(wetterdaten[0]);
  Serial.println(wetterdaten[1]);
  Serial.println(wetterdaten[2]);
  Serial.println(wetterdaten[3]);
}

void loop(){}

@anbad

der Ansatz von @agmue hat mir gefallen. Vieleicht will man ja die Werte einzeln haben um damit z.B. weiterzurechnen.

Wenn es immer Wertpaare sind (getrennt mit : und ; ) dann kann man das auch entsprechend mit strtok einlesen. Es würde sich dann eine Struktur anbieten: Jedes Paar besteht aus einem Ort und dem Wert.

// Convert an Arduino String into an array
// https://forum.arduino.cc/t/string-zerlegen-und-in-array-aufteilen/955678/9
// by noiasca


String myString = "Außen:2.9;Innen:22.6;Dachboden:15.0;Keller:17.3";

constexpr byte size = 20;    // Größe für den Bezeichner
struct Wetterdaten
{
  char ort[size];            // von welchem Sensor kommen die Daten
  float value = 0;           // der Wert
};
Wetterdaten wetterdaten[4];  // wir benötigen für das Beispiel 4 Instanzen von Wetterdaten

void string2array()          // konvertiert einen Arduino String in ein Array
{
  char zeile[80];            // einen Buffer für die ganze Zeile zur Verfügung stellen
  snprintf( zeile, sizeof(zeile), "%s", myString.c_str() ); // String zu char buffer machen
  char val[size];            // einen Buffer für die Werte-Konvertierung
  snprintf(wetterdaten[0].ort, size, "%s", strtok(zeile, ":;") ); // zerteilen beginnen
  snprintf(val, size, "%s", strtok(NULL, ":;") );                 // Wert in Buffer einlesen
  wetterdaten[0].value = atof(val);                               // buffer in float konvertieren
  for (size_t i = 1; i< sizeof(wetterdaten)/sizeof(wetterdaten[0]); i++) // die restlichen 3 Wertpaare einlesen
  {
    snprintf(wetterdaten[i].ort, size, "%s", strtok(NULL, ":;") );  
    snprintf(val, size, "%s", strtok(NULL, ":;") );
    wetterdaten[i].value = atof(val);
  }
}

void ausgabe()                  // Einfache Ausgabe aller aktuell gespeicherten Werte
{
  for (auto &i : wetterdaten)   // durch das ganze struct array durchgehe
  {
    Serial.print(i.ort);
    Serial.print(" ");
    Serial.println(i.value);
  }
}

void berechnen()                // eine Berechnung mit den gespeicherten Werten vornehmen
{
  float unterschied = wetterdaten[0].value - wetterdaten[1].value;
  Serial.print(F("Der Temperaturunterschied zwischen "));
  Serial.print(wetterdaten[0].ort);   // Zugriff auf das erste Element im Array
  Serial.print(F(" und "));
  Serial.print(wetterdaten[1].ort);   // Zugriff auf das zweite Element im Array
  Serial.print(F(" beträgt "));
  Serial.print(unterschied, 1);       // Float Ausgabe mit einer Nachkommastelle
}

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start..."));
  string2array();  // konvertieren
  ausgabe();       // alles ausgeben
  berechnen();     // etwas mit den Werten tun
}

void loop() {}

soll ausgeben

Start...
Außen 2.90
Innen 22.60
Dachboden 15.00
Keller 17.30
Der Temperaturunterschied zwischen Außen und Innen beträgt -19.7
1 Like

Ist es sinnvoll, Programme ausschließlich auf einem externen Server wie wokwi.com zu zeigen? Der könnte irgendwann verschwunden sein, womit dieser Beitrag unverständlich würde.

Daher eine Kopie:

String myString = "Außen:2.9;Innen:22.6;Dachboden:15.0;Keller:17.3";

constexpr byte size = 20;    // Größe für den Bezeichner
struct Wetterdaten
{
  char ort[size];            // von welchem Sensor kommen die Daten
  float value = 0;           // der Wert
};
Wetterdaten wetterdaten[4];  // wir benötigen für das Beispiel 4 Instanzen von Wetterdaten

void string2array()          // konvertiert einen Arduino String in ein Array
{
  char zeile[80];            // einen Buffer für die ganze Zeile zur Verfügung stellen
  snprintf( zeile, sizeof(zeile), "%s", myString.c_str() ); // String zu char buffer machen
  char val[size];            // einen Buffer für die Werte-Konvertierung
  snprintf(wetterdaten[0].ort, size, "%s", strtok(zeile, ":;") ); // zerteilen beginnen
  snprintf(val, size, "%s", strtok(NULL, ":;") );                 // Wert in Buffer einlesen
  wetterdaten[0].value = atof(val);                               // buffer in float konvertieren
  for (size_t i = 1; i< sizeof(wetterdaten)/sizeof(wetterdaten[0]); i++) // die restlichen 3 Wertpaare einlesen
  {
    snprintf(wetterdaten[i].ort, size, "%s", strtok(NULL, ":;") );  
    snprintf(val, size, "%s", strtok(NULL, ":;") );
    wetterdaten[i].value = atof(val);
  }
}

void ausgabe()                  // Einfache Ausgabe aller aktuell gespeicherten Werte
{
  for (auto &i : wetterdaten)   // durch das ganze struct array durchgehe
  {
    Serial.print(i.ort);
    Serial.print(" ");
    Serial.println(i.value);
  }
}

void berechnen()                // eine Berechnung mit den gespeicherten Werten vornehmen
{
  float unterschied = wetterdaten[0].value - wetterdaten[1].value;
  Serial.print(F("Der Temperaturunterschied zwischen "));
  Serial.print(wetterdaten[0].ort);   // Zugriff auf das erste Element im Array
  Serial.print(F(" und "));
  Serial.print(wetterdaten[1].ort);   // Zugriff auf das zweite Element im Array
  Serial.print(F(" beträgt "));
  Serial.print(unterschied, 1);       // Float Ausgabe mit einer Nachkommastelle
}

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start..."));
  string2array();  // konvertieren
  ausgabe();       // alles ausgeben
  berechnen();     // etwas mit den Werten tun
}

void loop() {}
1 Like

Du sprichst mir aus der Seele. Bisher haben wir immer Wert darauf gelegt, dass der Code direkt hier im Forum liegt. Das sollten wir beibehalten bzw. wieder verstärkt durchsetzen.
Code auf anderen Servern sollten wir ignorieren.

Gruß Tommy

1 Like

Der Ausdruck "ignorieren" ist mir zu absolut, denn ergänzend ein Programm in einem Simulator probieren zu können, finde ich gut.

Ergänzend ja, aber der Code sollte trotzen (auch) vom jeweiligen TO hier eingestellt werden.

Gruß Tommy

Mir ist das nicht zu absolut, sondern zu anstrengend.
Ich ignoriere, solchen Code, noch nicht einmal.

Beispiel:
Angenommen sscanf() wäre ein probates Mittel solche Probleme zu lösen. Dann scheitert der Simulator schon daran, dass man Float darin nicht aktivieren kann, oder es darin schon aktiviert ist.

Ja, so wünsche ich mir das auch :slightly_smiling_face:

Bei mir sähe das so aus, wenn ich die Anzahl der Datensätze im String nicht sicher vorhersagen kann:

const int MaxString = 5;
String strings[MaxString];

String Teststring = "Aussen:2.9;Innen:22.6;Dachboden:15.0;Keller:17.3;Aussen:3.9;Innen:23.6;Dachboden:16.0;Keller:18.3";

size_t Split(String& input, char Sep) {
    int count = 0;
    int p = input.indexOf(Sep);
    while (p >= 0 && count < MaxString-1) {
        strings[count] = input.substring(0,p);
        count++;
        input = input.substring(p+1);
        p = input.indexOf(Sep);
    }
    if (input.length() > 0) {
       if (input.indexOf(Sep) < 0) {
              strings[count] = input;
              count++;
              input = "";
        }
      }  
    return count;
}

void PrintStrings(int No) {
    for (int i = 0; i < No; i++) {
      Serial.println(strings[i]);
    }
}

void setup(){
  Serial.begin(115200);
  Serial.println("... Split ...");
  while (Teststring.length() > 0){
      int w = Split(Teststring,';');
      PrintStrings(w);
  }
}

void loop(){}

Über const int MaxString = ... kann man dabei den Speicherbedarf regulieren; je mehr hier möglich ist, um so seltener muss die Split() Funktion wiederholt werden (siehe while-Schleife im setup() ).

Mit Nullbytes '\0'.

So wie hier?

#include <Streaming.h> // die Lib findest du selber ;-)
Print &cout = Serial; // cout Emulation für "Arme"

// vorsicht der String wird beim auswerten modifiziert
char kette[] {"Aussen:2.9;Innen:22.6;Dachboden:15.0;Keller:17.3;Aussen:3.9;Innen:23.6;Dachboden:16.0;Keller:18.3;"};

void auswertung(char *kette)
{

  while(kette=strtok(kette,":"))
  {
    cout << kette << "  --> ";
    kette += strlen(kette)+1;
    cout << strtok(kette,";") << endl;
    kette += strlen(kette)+1;
  }
}




void setup() 
{
  Serial.begin(9600);
  cout << F("Start: ") << F(__FILE__) << endl;
  auswertung(kette);
}

void loop() 
{

}

So etwa, nur in C ohne "<<" :wink:

Och.....
In Sachen \0 gibts da, glaube ich, wenig Unterschied, zwischen C und C++.
Wobei uns Arduinos sowieso C einigermaßen fern ist.
Denn selbst Serial ist klipp und klar und ausschließlich C++
Die << machen den Braten wohl nicht fett, außer, dass man dann auch moderne C++ Bücher besser zu lesen versteht.