Variable beim parsen nicht geleert?!

Moin,

ich habe folgende Routine um eine MQTT Message zu zerlegen und in Variablen zu stopfen:

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.print(" Stringlaenge: ");
  Serial.print(length);
  Serial.print(" ");
  char* pos = strstr((char*)payload, "timestamp=");
  if (pos != NULL) {
    memset(timestamp, NULL, sizeof(timestamp));
    memcpy(timestamp, pos + 10, 13);
  }
  Serial.print(timestamp);
  pos = strstr((char*)payload, "value=");
  if (pos != NULL)
    LiterProStunde = atof(pos + 6);
  Serial.print(" ");
  Serial.print(LiterProStunde, 15);
  Serial.print(" l/h ");
  LiterProMinute = LiterProStunde / 60;
  while (LiterProMinute != oldLiterProMinute) {
    tft.setTextDatum(TR_DATUM);
    tft.setTextFont(4);
    tft.setTextSize(1);                  
    tft.setTextColor(BackgroundColor);
    tft.drawFloat(oldLiterProMinute, 2, 255, 55 + 4 * 26, 4);
    tft.setTextColor(TFT_WHITE);           
    tft.drawFloat(LiterProMinute, 2, 255, 55 + 4 * 26, 4);
    tft.setCursor(258, 55 + 4 * 26);
    tft.print(" l/min");
    oldLiterProMinute = LiterProMinute;                
  }
  Serial.print(LiterProMinute, 2);
  Serial.print(" l/min");
  Serial.println();
}

Normalerweise ist der Wert hinter value= etwa 15 Zeichen lang.
Es kommt aber auch vor, dass hinter value= nur eine “0” steht.
Jetzt habe ich das Problem, wenn hinter value= nur eine “0” steht, dann wird nur die erste Stelle vom alten value gelöscht, alle anderen bleiben stehen.

Hier mal ein Beispiel:
Message arrived [volkszaehler/Wasser/Verbrauch] timestamp=1534542153765 value=1572.0524017467 Stringlaenge: 45 1534542153765 1572.052401746700979 l/h 26.20 l/min
Message arrived [volkszaehler/Wasser/Verbrauch] timestamp=1534542154765 value=0 Stringlaenge: 31 1534542154765 572.052401746700297 l/h 9.53 l/min

Wie krieg ich das jetzt hin, dass der alte value sauber gelöscht wird und z.B. ein Wert “1572.0524017467” komplett gelöscht und danach nur noch eine “0” drinnen steht anstatt einer 0572.0524017467 ?

Lieben Gruß,
Chris

Variable beim parsen nicht geleert?!

Falsche Idee...

Du übermalst den Kram auf dem Bildschirm nicht.
Die Variable, ist voll ok.

Das kann doch nicht sein, in der seriellen Ausgabe ist ja das gleiche Problem, und die scrollt ja durch?!
Die beiden Beispielzeilen sind aus der Ausgabe vom seriellen Monitor.
Der Witz ist ja das damit sogar noch gerechnet wird:

Hier nochmal das Beispiel aus dem seriellem Monitor:
Message arrived [volkszaehler/Wasser/Verbrauch] timestamp=1534542153765 value=1572.0524017467 Stringlaenge: 45 1534542153765 1572.052401746700979 l/h 26.20 l/min
Message arrived [volkszaehler/Wasser/Verbrauch] timestamp=1534542154765 value=0 Stringlaenge: 31 1534542154765 572.052401746700297 l/h 9.53 l/min
Grün wird aus Blau/60 errechnet.

Hallo,

kann es sein das deine "Variable" value ein char array ist? Also kein Datentyp wie int oder long o.ä. Vermute ich aus dem Auszug heraus.
Meine Denkweise geht in Richtung Nullterminator falsch gesetzt/verwendet oder sowas in der Richtung.
Weil Ziffer Null ist ja ungleich Steuerzeichen Null. Nicht das hier was verwechselt wurde.

Da mag was dran sein.
Ich wüsste nicht, dass ich irgendwo bewusst einen Nullterminator gesetzt hätte.
Welche Variable meinst du?

themanfrommoon:
Das kann doch nicht sein, in der seriellen Ausgabe ist ja das gleiche Problem, und die scrollt ja durch?!

Nagut ...
Dennoch buddelst du auf der falschen Baustelle. (?!?!?)

void setup() 
{
  Serial.begin(9600);
  Serial.println("Start");

  char number[] = "1572.0524017467";
  
  Serial.println(number);
  Serial.println(atof(number),15);
}

void loop() 
{

}

Bevor du payload befüllst, solltest du sie komplett mit ˋ0ˋ befüllen. Per Schleife oder memcpy

@ElEspanol
Lasse dich bitte nicht verwirren.

atof() überschreibt die Variable völlig.
Und dann erfolgt ein Print mit 15 Nachkommastellen.

Wobei die Arduino Doku da recht klar ist:

Floats have only 6-7 decimal digits of precision.
Das bedeutet, dass sich unser themanfrommoon sich bei jeder Ausgabe ca 8 Müllzeichen anzeigen lässt. Und er beschwert sich über diese Müllzeichen. ( ? ? ? )

Um den Zusammenhang zu beweisen, habe ich extra das Testprogramm für einen UNO geschrieben.

Leider sagt unser themanfrommoon nicht, welchen µC er nutzt.
Und ein testbares Programm führt er auch nicht vor.
Scheint ihm beides noch nicht wichtig zu sein.

Daran sieht man:
Er ist wohl noch in der Jammerphase.
Aber irgendwann wird dann das Gehirn aktiv, und nach einer Lösung gesucht.
(hoffentlich)

Aber was solls, im Augenblick wird noch alles wichtige ausgeblendet, und somit der eigentliche Fehler gleich mit.

Der übliche Weg:

  1. Problem erkenenn
  2. Jammern und heulen
  3. Lösung erarbeiten.

Schritt 2 scheint überflüssig.
Ist es in technischen Dingen meist auch.
Aber bei sozialen Fragen sieht das ganz anders aus.
Jetzt kann man sich natürlich fragen, macht es Sinn, soziale Problembewältigungstrategien auf technische Probleme anzuwenden?

Bevor du payload befüllst, solltest du sie komplett mit ˋ0ˋ befüllen. Per Schleife oder memcpy

Und selbst, wenn du recht hast!
Was durchaus sein kann....
Scheint mir ein Eingriff in die Library nicht sinnvoll.
(Begründung recht weit am Ende dieses Textes)

Es wird der Bereich ausgeblendet, der interessant ist.
Wir/Ich sehen nicht was da passiert.

Fest steht, dass atof() alle Zahlen verarbeitet, welche man ihm vorwirft.
Es kann nicht zwischen erwünschten und unerwünschten unterscheiden.
Nicht aus eigener Kraft. Das muss schon der Programmierer passend vorbereiten.
Hier wird evtl über length hinaus gelesen!
Man bedenke: Length wird vermutlich nicht ohne Grund übergeben.

Dieses könnte Abhilfe schaffen.

payload[length] = 0; // das array muss dafür groß genug sein
Oder für eine Begrenzung in einen Buffer kopieren.
Die /0 als Endekennung, das ist der wesentliche Unterschied, zwischen Byte Arrays und Char Arrays(C-Strings).
atof() erwartet einen C-String, kein Bytearray.
Ein einfacher Cast auf (char *) ist nicht ausreichend.
Das unterbindet Meldungen, macht aber kein Stringende dahin.
Und schon gar nicht, sucht es sich selber die richtige Position für das Stringende.
Wie auch?

Den Unterschied zwischen float und double kennt er schon. Da er einen 32 Bit Prozessor hat geht auch double. Aber auch da muss man beachten dass "signifikante Stellen" nicht "Nachkommastelle" bedeutet! Wenn man 4 Stellen vor dem Komma hat sind das weniger relevante Stellen danach

Ich dachte schon dass Payload korrekt terminiert ist und dass payload nur ein byte Array ist um den Code allgemein zu handeln. Kann aber auch falsch sein.
Aber auch ohne Terminator bricht atof() bei einem Leerzeichen oder Buchstaben ab! Solange nach den Ziffern so ein Zeichen kommt muss es also nicht unbedingt terminiert sein. Das ist bei dem String hier eigentlich der Fall

Da er einen 32 Bit Prozessor hat...

Habe ich was übersehen?

Ich dachte schon dass Payload korrekt terminiert ist und dass payload nur ein byte Array ist um den Code allgemein zu handeln. Kann aber auch falsch sein.

Warum sollte man ein Bytearray terminieren?
Es fällt mir schwer das einzusehen.
Denn 0x00 ist ein durchaus gültiges Byte.
Hat aber (mitten) in einem String nichts zu suchen.

Ich schätze mal, dass unser themanfrommoon einfach im Kopf ein ByteArray zu einem String umdeklariert, ohne jegliche Randbedingungen zu beachten.

Dieses ist ein "Beweis" für die (fehlerhafte) Umdeklarierung:

Serial.print(" Stringlaenge: ");
Serial.print(length);

Natürlich ist die Länge keine Stringlänge!
Sondern nur ein Maß für die Anzahl gültiger Daten.
Der Begriff ist also falsch gewählt, und führt damit zu einer irrigen Annahme.

Im folgenden Code wird dann die Länge gekonnt ignoriert.
Und locker flockig über das Ende hinweg gelesen.
Das scheint mir ein erheblicher Teil des Problems zu sein.

Die Ursache liegt aber im Verständnis der Datentypen.
Und, wie C/C++ damit umgeht.

Es handelt sich um so ein Webprojekt auf einem ESP

Ja ein Byte Array wird nicht terminiert sein. Das hatte ich bisher übersehen. Aber in diesem Fall ist das egal. Das ist sein String:

"timestamp=1534542153765 value=1572.0524017467 "

Hinter den gewünschten Werten steht ein Leerzeichen wodurch atof() korrekt abbricht

EDIT: Der obere Absatz ist falsch. Ich hatte die Ausgabe nicht richtig interpretiert

Und das hier sind 14 signifikante Stellen:
1572.0524017467

Passt also. Wie gesagt die Vorkommastellen zählen dazu. Es sein den man hat komplett führende Nullen. Dann sind das keine signifikanten Stellen. z.B. 0.00000545
Man hat also nur 10 richtige Nachkommastellen und gibt 15 aus. Die hinteren sind dann falsch. Was zu erwarten ist

Es handelt sich um so ein Webprojekt auf einem ESP

Sehe ich nicht.
Aber ich glaube dir mal.....

Hinter den gewünschten Werten steht ein Leerzeichen

Auch das sehe ich nicht......

wodurch atof() korrekt abbricht

Aber das sehe ich, dass atof() eben nicht abbricht.
Es ließt über length hinaus.
(zumindest meine ich das zu sehen)

Tipp, an mich selber:
Vielleicht sollte ich mich mal (intensiver) mit MQTT beschäftigen...

Mhh, jetzt sehe ich das Problem. Ja da kommt kein Leerzeichen :frowning: Das hat ja seine Ausgabe gemacht

Ja, der String muss terminiert werden. Also die Länge von Payload nehmen und den Terminator ans Ende setzten!

Was ElEspanol geschrieben hat war auch nicht 100% falsch

Bevor du payload befüllst, solltest du sie komplett mit ˋ0ˋ befüllen. Per Schleife oder memcpy

Allerdings ist es 0, '\0' oder NULL. Aber NICHT '0'
Und man nimmt memset() statt memcopy().

Allerdings muss man auch nicht alles auf 0 setzten. Die Länge der Nutzdaten ist ja bekannt. Da reicht ein Zeichen am Ende

Bei Timestamp und memcpy() hätte man das auch so machen können. Aber ich dachte der Code mit memset() wäre da einfacher verständlich da hier etwas Hintergrundwissen über C Strings und Zeiger fehlt

Hi

So wie ich Das sehe, wird die übermittelte Zahl - also die lange Komma-Zahl oder eben die einzelne Null, in einen String/char-Array kopiert - eben, um in der Ausgabe, Die später gemacht wird, die 'neue Zahl' drin zu haben.
Wenn ich jetzt aber nur ein Zeichen habe, wird auch nur ein Zeichen kopiert und aus meiner 1572.0.... wird eine 0572.0...., Welche dann korrekt als 572.0... weiter verarbeitet wird - die führende Null ist verschwunden.

Also entweder den Datensatz einfach verwerfen, wenn die enthaltenen Daten ersichtlich falsch sind, oder den Fehlerfall anders ausbügeln - wobei ich Dann nicht weiß, wie.
Da ja auch der aktuelle Verbrauch berechnet wird und dafür die Differenz der beiden Zählerwerte benötigt wird, gibt Das auf jeden Fall 'irgend einen Mist' - aber keinen aktuellen Wert.

MfG

So wie ich Das sehe, wird die übermittelte Zahl - also die lange Komma-Zahl oder eben die einzelne Null, in einen String/char-Array kopiert

Nein. Das steht in einem Byte Array das nicht terminiert ist

value=1572.0524017467
value=0

Daraus wird dann eben value=0572.0524017467

Das mit dem memcpy() kopieren ist ist für den Timetime Stamp, den er aus irgendeinem Grund nicht Parsen will oder muss.

payload[length] = '\0' sollte das Problem lösen

Ich meinte natürlich 0, nicht ˋ0ˋ. Wenn das ganze array bereits mit 0 gefüllt ist, muss ich mir keine Sorgen machen, an welche Stelle ich die Terminierungs-Null schreibe.

Bei
payload[length] = '\0' sollte das Problem lösen
Wird doch das Array erst am letzter Stelle terminiert, der andere Müll davor, der nicht mit neuen Daten überschrieben wurde, bleibt doch dann erhalten

Hi

Dann interpretiere 'lenght' mit 'Länge der übertragenen Ziffern', nicht mit 'Länge des Array'.
Dann bekommst Du hinter die letzte aktuell eingetragene Ziffer einen 0-Terminator.

MfG

dann ja. Aber dann kann ich ja gleich beim reinkopieren des Zeichens eine 0 dahinter machen:

payload(index)= received byte;
payload(index+1)=0;

Viele Wege führen nach Rom

ElEspanol:
dann ja. Aber dann kann ich ja gleich beim reinkopieren des Zeichens eine 0 dahinter machen:

Wenn ich das richtig verstehe hat er darüber aber keine Kontrolle. Das macht die Library glaube ich automatisch. Danach wird eine Callback Funktion aufgerufen und man hat die Daten fertig vorliegen

Serenifly:
Wenn ich das richtig verstehe

Das übliche Problem, wichtige Sachen bleiben verborgen, …

Dann eben die letzten Stellen merken und mit dem vorigen Datensatz vergleichen. Oder die neuen Daten auf führende ‘0’ prüfen.

Oder den Entwickler der Lib kontaktieren