String von MQTT zerlegen

Ich hätte mal eine Frage, wie zerlegt man einen String, der von MQTT gesendet wird?

Hier die Passage:

void callback(char* topic, byte* payload, unsigned int length) {
  String sTopic = String(topic);
  payload[length] = '\0';
  String value1 = String((char*)payload).c_str();
    else if (sTopic == "/Wohnung/Kinderzimmer/Jonas/Display/Uhrzeit") {
      Uhrzeit = value1;
      }
valueChanged = true;
}

Die Uhrzeit wird so übermittelt: Stunden : Minuten Uhr

Nun möchte ich diesen String gerne in folgendes zerlegen:

  1. "Stunden"
  2. " : "
  3. "Minuten"
  4. "Uhr"

Verzichte wenn möglich auf die Benutzung von String (großes S), da dies zu Speicherfragmentierung und damit zu undefiniertem Verhalten führen kann (und vielen Fällen auch wird). Ich sehe bei dir auch überhaupt keinen Grund die String-Klasse zu verwenden.

Da du die Payload als byte Array bekommst, kannst du den string (kleines s) mittels 'strtok' in einzelne Teile aufteilen und so deine Stunden und Minuten herausfiltern. Diese kannst du dann mit 'atoi' in Zahlen umwandeln.

void callback(char* topic, byte* payload, unsigned int length) 
{
  // Sicherstellen, dass der payload-string null-terminiert ist
  payload[length] = '\0';
  
  // Ist es auch das richtige Topic?
  if (strcmp(topic,"/Wohnung/Kinderzimmer/Jonas/Display/Uhrzeit") != 0) 
  {
    // Nein, also raus hier
    return;
  }
  
  // Stunden herausfiltern
  char* hours = strtok(payload, " ");
  
  // " : " überspringen
  strtok(NULL, " ");
  
  // Minuten herausfiltern
  char* minutes = strtok(NULL, " ");

  // Geänderten Wert mitteilen
  valueChanged = true;
}

Der Code funktioniert nur bei folgendem Format: "hh : mm Uhr" (mit Leerzeichen). Wenn das Format "hh:mm Uhr" ist, musst du die delimiter in strtok ändern:

void callback(char* topic, byte* payload, unsigned int length) 
{
  // Sicherstellen, dass der payload-string null-terminiert ist
  payload[length] = '\0';
  
  // Ist es auch das richtige Topic?
  if (strcmp(topic,"/Wohnung/Kinderzimmer/Jonas/Display/Uhrzeit") != 0) 
  {
    // Nein, also raus hier
    return;
  }
  
  // Stunden herausfiltern
  char* hours = strtok(payload, ":");
  
  // Minuten herausfiltern
  char* minutes = strtok(NULL, " ");

  // Geänderten Wert mitteilen
  valueChanged = true;
}

Danke schon einmal für den Code, so klingt das relativ einfach.

Aber wie bekomme ich das zerhackte in variablen, die ich dann weiter verwenden kann?

Das hat er Dir doch schon hin geschrieben. Du brauchst doch nur noch atoi anwenden.

int stunden = atoi(hours);
int minuten = atoi(minutes);

Gruß Tommy

Ich bin leider noch relativer Neuling auf dem Gebiet. Wenn ich nun den Teil einfüge, kommt folgender Fehler:

D:/Dropbox/Backup/ATOM-Projekte/Nextion-Display/Test 2019 nach Haus-Automatisierung/src/main.ino:877:34: error: invalid conversion from 'byte* {aka unsigned char*}' to 'char*' [-fpermissive]
In file included from C:\users\admin\.platformio\packages\framework-arduinoespressif8266\tools\sdk\libc\xtensa-lx106-elf\include/stdlib.h:11:0,
from C:\users\admin\.platformio\packages\framework-arduinoespressif8266\cores\esp8266/Arduino.h:27,
from c:\users\admin\appdata\local\temp\tmpjrefl9:1:
C:\users\admin\.platformio\packages\framework-arduinoespressif8266\tools\sdk\libc\xtensa-lx106-elf\include/string.h:42:15: error:   initializing argument 1 of 'char* strtok(char*, const char*)' [-fpermissive]
char  *_EXFUN(strtok,(char *__restrict, const char *__restrict));
^

Hier mal der gesamte Abschnitt:

void callback(char* topic, byte* payload, unsigned int length) {
  String sTopic = String(topic);

  // Workaround to get int from payload
  /*#############################################################################
  #                                                                           #
  #   Erster Absatz uint_32 ---> Ganze Zahlen ( INT )                         #
  #                                                                           #
  #############################################################################*/
  payload[length] = '\0';
  uint32_t value = String((char*)payload).toInt();

  if (sTopic == "/Wohnung/Kinderzimmer/Jonas/Display/Temperatur_IST") {
    gaugeTemperatur = value;
  } else if (sTopic == "/Wohnung/Kinderzimmer/Jonas/Display/Feuchte") {
    gaugeFeuchte = value;
  } else if (sTopic == "/Wohnung/Kinderzimmer/Jonas/Display/top") {
    gaugeTop = value;
  }
  /* else if (sTopic == "/SmartHome/Buero/Display/junk/grey") {
    junkGrey = value;
  } else if (sTopic == "/SmartHome/Buero/Display/junk/yellow") {
    junkYellow = value;
  } else if (sTopic == "/SmartHome/Buero/Display/junk/blue") {
    junkBlue = value;
  } else if (sTopic == "/SmartHome/Buero/Display/junk/green") {
    junkGreen = value;
  }*/

  /*###########################################################################
  #                                                                           #
  #   Zweiter Absatz Verarbeitung eines Strings                               #
  #                                                                           #
  #############################################################################*/

  payload[length] = '\0';
  String value1 = String((char*)payload).c_str();
   if (sTopic == "/Wohnung/Kinderzimmer/Jonas/Display/Temperatur_SOLL") {
    Temperatur_SOLL = value1;
    }
    else if (sTopic == "/Wohnung/Kinderzimmer/Jonas/Display/Temperatur_IST") {
     Temperatur_IST = value1;

     }
    else if (sTopic == "/Wohnung/Kinderzimmer/Jonas/Display/Feuchte") {
      Feuchte = value1;
     }
  /*  else if (sTopic == "/Wohnung/Kinderzimmer/Jonas/Display/Uhrzeit") {
      Uhrzeit = value1;
    }*/
     // Ist es auch das richtige Topic?
else if (strcmp(topic,"/Wohnung/Kinderzimmer/Jonas/Display/Uhrzeit") != 0) {
{
  // Nein, also raus hier
  return;
}

// Stunden herausfiltern
char* hours = strtok(payload, ":");
//int stunden = atoi(hours);

// Minuten herausfiltern
char* minutes = strtok(NULL, " "); }
//int minutes = atoi(minutes);}

      else if (sTopic == "/Wohnung/Kinderzimmer/Jonas/Display/Rollo_Rückmeldung") {
        Rollo_Rueckmeldung = value1;
        }
      else if (sTopic == "/Wohnung/Kinderzimmer/Jonas/Display/Steuerung_Display") {
        Daten_Steuerung_Display = value1;
        Serial.print(value1);
        for(int i = 0; i < 3;i++) {Serial.write(0xff);}
      }

valueChanged = true;
}

Bitte verurteilt mich nicht… :frowning:

An den Fehlermeldungen kannst du erkennen, was nicht stimmt:

error: invalid conversion from 'byte* {aka unsigned char*}' to 'char*' [-fpermissive]

Das sagt dir, dass der Compiler irgendeiner Stelle vom Datentyp byte* (entspricht unsigned char*) zum Datentyp char* konvertieren muss. Das ist mit -fpermissive nicht zulässig (gibt ansonsten nur eine Warnung).

Jetzt fehlt nur noch wo:

error:   initializing argument 1 of 'char* strtok(char*, const char*)' [-fpermissive]

Also beim Aufruf von 'strtok'. Das ergibt ja auch Sinn, schließlich übergibst du als Parameter die Variable 'payload' und die ist vom Datentype byte*.

// Diese Zeile muss also geändert werden
char* hours = strtok(payload, ":");

// Und zwar zu
char* hours = strtok((char*)payload, ":");

Ich würde allerdings deine ganze callback Funktion ändern, die ist viel zu lang und nur schwer lesbar. Außerdem solltest du wirklich auf jegliche Benutzung von String verzichten.

Was ist übrigens "framework-arduinoespressif8266"? Das klingt nicht nach der Arduinoumgebung. Die eine Variable heißt bei mir übrigens minuten und nicht minutes.

Gruß Tommy

LightuC: Außerdem solltest du wirklich auf jegliche Benutzung von String verzichten.

Warum?

Weil beim Esp8266 schon genug in den Bibliotheken drin sind?

Gruß Fips

Den ESP hat er uns ja bis dahin verschwiegen. Der kam erst zufällig in der Fehlermeldung in #4 ans Licht.

Für die AVR ist der Hinweis richtig gewesen.

Gruß Tommy

Tommy56: Fehlermeldung in #4 ans Licht.

Esp in #4.

Ok.

In #5 ...jegliche Benutzung von String verzichten.

Gruß Fips

Derfips:
Warum?

Weil beim Esp8266 schon genug in den Bibliotheken drin sind?

Gruß Fips

String sTopic = String(topic);

Wenn nicht genug dynamischer Speicher verfügbar ist um den topic-string aufzunehmen, wird sTopic leer sein. Die Daten wurden zwar empfangen und befinden sich auch schon im Speicher des Arduino’s, aber eine Verarbeitung der Daten scheitert, weil die bereits vorhandenen Daten nicht in den dynamischen Speicherbereich kopiert werden konnten. Gerade in Zusammenhang mit der String-Klasse ist es praktisch unmöglich herauszufinden, ob der Kopiervorgang in den dynamischen Speicherbereich geklappt hat.

Deshalb weise ich gerne insbesonders Anfänger darauf hin, damit solche Fehler erst gar nicht passieren. Natürlich kann man die String-Klasse nutzen, wenn man weiß was im Hintergrund passiert. Man kann beispielsweise im Setup einmalig genügend Speicher für den String reservieren, so dass dieser im Verlauf des Programms nicht nochmal Speicher anfordern muss.

Beim ESP ist das natürlich relativ unkritisch, da gebe ich dir vollkommen Recht (Habe ehrlich gesagt auch nicht auf die Dateinamen in der Fehlermeldung geguckt :D). Aber man sollte sich dennoch dessen bewusst sein, zumal die String-Klasse in dieser Methode einfach keinen Mehrwert hat.

Derfips: Esp in #4. In #5 ...jegliche Benutzung von String verzichten.

Wobei ich das eher als Wiederholung von 1 sehe, zumal der ESP auch nirgends explizit erwähnt wurde.

Gruß Tommy

LightuC: Aber man sollte sich dennoch dessen bewusst sein, zumal die String-Klasse in dieser Methode einfach keinen Mehrwert hat.

Dass das was der TO in #1 gepostet hat, keinen Mehrwert hat ist unstrittig.

void callback(char* topic, byte* payload, unsigned int length) { String sTopic = String(topic); String value1 = String((char*)payload).c_str();

Ich hatte die ganze Zeit einen Esp vermutet, weshalb ich die Fehlermeldung genauer angeschaut habe.

Gruß Fips

OK, nachmal, es handelt sich um einen Wemos D1 mini. Also ESP8266.

Und ja ich raff das gerade nicht.

Klar, mag sein das mein Projekt eventuell nicht so den Programmiercharme at, aber wie gesagt fange ich erst an und dies ist so mein erstes komlexes Projekt. Darum auch bitte keine Veruteiungen.

LightuC: An den Fehlermeldungen kannst du erkennen, was nicht stimmt:

error: invalid conversion from 'byte* {aka unsigned char*}' to 'char*' [-fpermissive]

Das sagt dir, dass der Compiler irgendeiner Stelle vom Datentyp byte* (entspricht unsigned char*) zum Datentyp char* konvertieren muss. Das ist mit -fpermissive nicht zulässig (gibt ansonsten nur eine Warnung).

Jetzt fehlt nur noch wo:

error:   initializing argument 1 of 'char* strtok(char*, const char*)' [-fpermissive]

Also beim Aufruf von 'strtok'. Das ergibt ja auch Sinn, schließlich übergibst du als Parameter die Variable 'payload' und die ist vom Datentype byte*.

// Diese Zeile muss also geändert werden
char* hours = strtok(payload, ":");

// Und zwar zu char* hours = strtok((char*)payload, ":");




Ich würde allerdings deine ganze callback Funktion ändern, die ist viel zu lang und nur schwer lesbar. Außerdem solltest du wirklich auf jegliche Benutzung von String verzichten.

Das habe ich nun eingebunden und er meckert jetzt nicht mehr und kompiliert durch. Aber in einem anderen Absatz versende ich das wieder. Der Absatz sieht so aus, jedoch kann ich hier nichts einfügen, ohne das es meckert. Hab nun sämtliche konstellationen durch die ich kenne:

void refreshDisplay(){
  if (valueChanged || pageChanged || wifiChanged) {
    digitalWrite(BUILTIN_LED, LOW); // Turn ON internal LED

    if (currentPage == PAGE_HEIZ) {
      zTemperatur.setValue(calculateGauge_Temperatur_Value(gaugeTemperatur));
      zFeuchte.setValue(calculateGauge_Feuchte_Value(gaugeFeuchte));
      //zTop.setValue(calculateTopGaugeValue(gaugeTop));
      //tTop.setText(String(gaugeTop, DEC).c_str());
      tTemperatur_Soll.setText(String(Temperatur_SOLL).c_str());
      tTemperatur_IST.setText(String(Temperatur_IST).c_str());
      tFeuchte.setText(String(Feuchte).c_str());

    }
   else if (currentPage == PAGE_HOME) {
      if (wifiState == WIFI_CONNECTED) {
        pWifi.setPic(6);
      } else if (wifiState == WIFI_DISCONNECTED) {
        pWifi.setPic(5);
      }
      tUhrzeit.setText(String(Uhrzeit).c_str());


      }
}

Ich weis, Ich und meine String..... soll aber nicht heißen das ich so etwas trage :stuck_out_tongue_closed_eyes:

quorle: Klar, mag sein das mein Projekt eventuell nicht so den Programmiercharme at, aber wie gesagt fange ich erst an und dies ist so mein erstes komlexes Projekt. Darum auch bitte keine Veruteiungen.

Das sind alles nur Ratschläge, keine Verurteilungen.

quorle: Aber in einem anderen Absatz versende ich das wieder. Der Absatz sieht so aus, jedoch kann ich hier nichts einfügen, ohne das es meckert. Hab nun sämtliche konstellationen durch die ich kenne:

Was bedeutet denn einfügen? Was heißt meckern? Es? Sämtliche Konstellationen von was? Welchen Datentyp haben die Variablen? Was ändert sie, da sie offentsichtlich global sind? Wie sind die Funktionen setText und setValue definiert? Wie sind die Funktionen calculateGauge_Temperatur_Value, calculateGauge_Feuchte_Value, calculateTopGaugeValue definiert?

Wie du siehst, können wir ohne konkrete Fehlermeldungen und Kenntnisse des gesamten Codes oft nur wenig helfen.

quorle: Ich weis, Ich und meine String..... soll aber nicht heißen das ich so etwas trage :stuck_out_tongue_closed_eyes:

Wie aus den vorhergegangen Posts zu entnehmen: Beim ESP spielt das keine so große Rolle (dennoch bitte im Hinterkopf haben, nur für den Fall).

Also, hier habe ich eimal den gesamten Code. Wie etwas weiter oben beschrieben möchte ich den zerlegten String dann weiter senden in 4 einzelne Teile. 1. Stunden 2. Dopperpunkt ( : ) 3. Minuten 4. Text ( Uhr )

Da der Code zu lange ist um ihn hier zu posten, habe ich ihn bei Pastebin eingestellt:

Code

Du meinst, den liest dort jemand? Es ist besser, den dann als Attachment anzuhängen.

Gruß Tommy

Ich würde die SSID und das WLAN-Passwort aus dem Code rausnehmen ;)

Mit 700+ Codezeilen würde ich dir doch ans Herz legen, deinen Code mal ordentlich zu strukturieren. Insbesondere die Benutzerschnittstelle vom Backend zu trennen würde den Code schon wesentlich lesbarer machen. Oder zumindest das ganze in mehrere Dateien aufzuteilen.

Wie lautet denn die Fehlermeldung, wenn du versuchst in deiner Methode neue Sachen einzufügen?

quorle: Also, hier habe ich eimal den gesamten Code. Wie etwas weiter oben beschrieben möchte ich den zerlegten String dann weiter senden in 4 einzelne Teile. 1. Stunden 2. Dopperpunkt ( : ) 3. Minuten 4. Text ( Uhr )

Das funktioniert doch schon, oder? Der gezeigte Absatz handelt jedenfalls von Temperatur- und Feuchtewerten und nicht von Uhrzeiten (EDIT: Hab das mit den Uhrzeiten überlesen, die Fehlermeldung wäre dennoch hilfreich).