Variable beim parsen nicht geleert?!

Es wurde doch schon geklärt, dass length die Länge des Telegramms ist. Was sollte das sonst sein?

Das gibt er in seinem Test Code ja aus:

Serial.print(length);

Wie gesagt, das ist eine Callback Funktion! Die Library meldet dir dass etwas gemacht wurde/vorliegt. Length ist etwas dass dir von der Library mitgeteilt wird. Nicht etwas was du übergibst.

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.

Ja, aber....
Du weißt ja nicht, wie groß das Byte Array ist.
Zumindest weiß ich das nicht.
Ohne diese Information, ist es gefährlich, ein Array mit irgendwas zu fluten.

Darum ist auch payload[length] = '\0' u.U. ein derbes Problem.

Denn es widerspricht dem Mantra:

Programmiere immer nur gegen die Schnittstelle,
niemals gegen die Implementierung.

Die Schnittstelle ergibt sich aus der Funktionssignatur.
Zumindest mir, ist die Lib völlig unbekannt.

Teil der Schnittstelle ist ein Zeiger auf ein Byte Array, und die Anzahl verarbeitbarer Daten.

payload[length] = '\0' schreibt ein Byte zu weit!
Das kann klappen, kann aber auch derbe ins Auge gehen.
Es liegt ganz daran, wie groß das Array ist.
(ja, ich weiß, der Vorschlag kam von mir)

Der richtige, der wahre, der goldene Weg, wäre also einen lokalen Buffer zu verwenden.
Die Zeichen bis length da rein zu kopieren und diesen Buffer dann atof() vorwerfen.

Oder den Entwickler der Lib kontaktieren

Tut keine Not!
Die Schnittstelle ist klar.
Besser gehts nicht.

Man kann doch nicht den Entwickler dafür verantwortlich machen, wenn man selber BEWUSST(oder aus Ignoranz) über das Ende hinweg liest.

themanfrommoon:
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 ?

Also halt doch einfach die Position von value+2=='0' prüfen.

Also halt doch einfach die Position von value+2=='0' prüfen.

0.4711 ist auch eine gültige Float Zeichenkette

Position von value+2=='0' && value+3==' ' prüfen.

themanfrommoon hat sich schon lange nicht mehr geäussert. Problem vielleicht schon gelöst?

Ja, wenn die übertragenen Daten den Puffer genau auffüllen geht das mit dem Terminieren schief...

Dann wäre sowas in der Tat besser:

char buffer[length + 1];
memcpy(buffer, payload, length);
buffer[length] = '\0';

Das geht in dank dem GNU Compiler (variable length array): Variable Length (Using the GNU Compiler Collection (GCC))

Und der Cast ist dann auch nicht mehr nötig :wink:

Aber an der Stelle nach der führenden 0 steht dann immer noch Müll, oder nicht?

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 ?

Aber an der Stelle nach der führenden 0 steht dann immer noch Müll, oder nicht?

Wenn du deinen gedanklichen Fokus auf den Müll richtest, dann siehst du den Müll.
Richtest du den Fokus auf die Längenangabe, dann blendest du den Müll aus, und hast nur noch Nutzdaten.

Der Müll ist weg (exakter formuliert: ausgeblendet) und stört nicht.

Diese Denke ermöglicht einem(mir und dir) ein Programm zu schreiben, welches auf Nutzdaten reagiert und den Müll ignoriert.
Einfach nur durch den gedanklichen Fokus auf "Length".

Was kommt den nun an?
[volkszaehler/Wasser/Verbrauch] timestamp=1534542154765 value=0 Stringlaenge: 31 1534542154765 572.052401746700297 l/h 9.53 l/min

Oder

timestamp=1534542154765 value=0

?
Bei letzterem geht das mit 31, bei ersterem nicht.

Ein Zeiger auf "volkszaehler/Wasser/Verbrauch" findet sich in topic.

Es kommt ein Zeiger, auf die Payload(von der wir wissen, dass wir nur length Bytes verwenden dürfen), an.
Und halt die Längenangabe.

Siehe Signatur:

void callback(char* topic, byte* payload, unsigned int length)

Was wir sehen, sind nur die Ausgaben des TE.

combie:
Der Rest sind Ausgaben des TE.

Der sich bis dato nicht mehr dafür interessiert

Daran muss es ja nicht liegen.....

Vielleicht habe ich ihn beleidigt....
War zu gemein...

Meine recht krasse Ablehnung des Verfahrens eine Variable/Array Null zu setzen, von der man nicht weiß, wie groß sie ist. Und auch die Kritik am naiven um deklarieren eines Arrays zum String...
Auch "ignorieren von Length", alles das kann man schon persönlich nehmen.
Wenn man sich (zu sehr) mit seinen eigenen Ideen identifiziert.

Doch doch, alles gut, ich werde mich melden.
Ich habe die ganzen Antworten bisher immer nur kurz auf dem Handy verfolgen können.
Nun muss ich das erstmal detailliert am Rechner nachvollziehen.
Ich habe momentan aber ein paar Baustellen am laufen und werde mich erst in den nächsten Tagen damit beschäftigen können. So wie ich es bisher verfolgt habe bin ich mir ziemlich sicher dass die Lösung schon dabei ist.
Also hier schonmal vielen Dank für die rege Beteiligung. ICH muss mich da erstmal durcharbeiten.

Lieben Dank und liebe Grüße,
Chris

So, nachdem nun einige andere Baustellen bedient sind und ich bei anderen nicht weiter komme, geht es heute Abend hier weiter :slight_smile:
Zwischenzeitlich gab es hier ja schon rege Diskussionen, die schau ich mir jetzt erstmal detailliert an.
Und hier dann nun hoffentlich die allumfassenden Antworten auf eure Fragen und Ideen:

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.

Ich weiss jetzt nicht genau welche Variable du meinst.
Diese callback Routine kommt aus einem Beispiel der pubsubclient Library.
Es wird ein MQTT Broker subscribed, und wenn der MQTT Broker eine neue Message hat, dann wird diese callback Routine aufgerufen.
Die Message wird dann mit

  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }

auf den seriellen Monitor geschrieben.
Ich lese daraus, das jedes Zeichen einzeln geschrieben wird, und daher wird es sich wohl um ein char Array handeln?!
Danach parse ich dieses gesamte char Array dann in den "timestamp" als char Array (weil das an anderer Stelle aus einem char Array weiterverarbeitet wird, daher wollte ich hier den gleichen variablentyp nehmen) und in

"LiterProStunde" als double (da hatte ich vorher ein float, und habe bemerkt, dass die Anzahl der Stellen nicht ausreicht, um die gleiche Genauigkeit zu erhalten wie im char Array. Deswegen habe ich diese Variable auch im seriellen

Monitor mit mehr Stellen anzeigen lassen, als sie eigentlich hat, um zu Prüfen wie sich die Nachkommastellen dort entwickeln. Die Stellen, die ich verglichen habe sind rot markiert. Da sollte eigentlich schon farblich markiert

auffallen, das ich die letzten 5 Müllzeichen gar nicht miteinander verglichen habe. Aber alle vom char Array gelieferten Ziffern sind identisch mit den ins double geparsten Zahlen. (Beim float war das noch nicht so).

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.

Das ganze läuft auf einem nodeMCU mit ESP8266.
Das Testprogramm läuft auch dadrauf.
Mir ist klar, dass in der Ausgabe mehr Zeichen dargestellt sind, als es sinnvoll ist. Das habe ich extra so gemacht, und es wird nu im Serial Output zum debuggen angezeigt. Auf dem TFT Display werden nur 2 Nachkommastellen

angezeigt (die später wohl noch auf 1 Nachkommastelle, oder sogar noch auf 0,5er Schritte reduziert werden.)

Ein testbaren Programm ist ein wenig schwierig. Der Code ist inzwischen etwas über 35.000 Zeichen lang. Den könnte ich als Anhang hochladen. In Codetags hat da wohl keiner Spaß dran. Da erwarte ich auch nicht wirklich das sich da

einer die Mühe macht und sich darin zurechtfindet. Nächstes Problem ist, dass ich die Kommentare leider nicht mehr gepflegt habe. Daher würde es verwirren, wenn das jemand anderes ließt. Und ein weiteres Problem ist, das dort

inzwischen 12 teilweise wohl relativ spezielle Libraries aufgerufen werden und das ganze ja in auf gewisse Peripherie und Hardware zugreift um die Dateien einzulesen. Das habt ihr ja alles so nicht bei euch. Insofern ist es wohl

relativ sinnfrei (hab ich mir jedenfalls gedacht) den ganzen Code hier hochzuladen. UND ich kann mir gut vorstellen, dass der eine oder andere die Hände über dem Kopf zusammenschlägt. Ich glaube dass dort ein paar Programmierstile

zusammengewürfelt sind, und kein ganz roter Faden zu erkennen ist. Das liegt schlicht und einfach daran, dass ich wie gesagt auf inzwischen 12 verschiedene Libraries und Teile aus deren Beispielen zurückgreife, die ich dann zu

einem Sketch zusammengebaut habe und zum funktionieren gebracht habe. An den Libraries möchte ich auch ungern rumändern. Warum soll ich mir die Arbeit machen, die andere schon gemacht haben und freundlicherweise zur Verfügung

stellen? Klar, damit man mehr lernt und irgendwann alles kann. Bei mir ist das Ziel aber, das das Programm läuft und nicht das ich bis ins Detail eine Programmiersprache lerne, die ist leider nur Mittel zum Zweck. Aber wenn's hilft

lade ich den Code hier auch als Anhang hoch. Muss nur einer HIER HOCHLADEN! schreiben.

Serenifly:
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

Genau richtig

themanfrommoon hat sich schon lange nicht mehr geäussert. Problem vielleicht schon gelöst?

Das hätte ich natürlich mitgeteilt, so fair bin ich natürlich. Nein, ich hatte einfach noch keine Zeit dafür.....aber jetzt :slight_smile:

Und auch die Kritik am naiven um deklarieren eines Arrays zum String...
Auch "ignorieren von Length", alles das kann man schon persönlich nehmen.
Wenn man sich (zu sehr) mit seinen eigenen Ideen identifiziert.

Hihi, schön, combie ist schon ein wenig speziell. Macht aber nix, das bin ich auch. Sowas kann ich gut ab, ich mach das genauso :slight_smile:

Ich hab heute 2,9t und gestern 1,5t Steine für meinen Carport Boden geschleppt, das sitzt mir noch ein wenig in den Knochen.
Ich hoffe ich bin bisher keine Antwort schuldig geblieben die ich beantworten kann und bin auf jede Frage eingegangen, ansonsten bitte nochmal fragen.

Ich habe jetzt so gefühlt 3-4 Ideen gelesen, und auch noch einige Fragestellungen zu Variablentypen und Array Größen usw.
Wie kann ich jetzt weitermachen, soll ich was testen, oder den gesamten Code mit über 35.000 Zeichen hochladen oder in den Libraries irgendwelche Variablendeklarationen nachschauen.

Lieben Gruß und vielen Dank für die ganze Diskussion und die Ideen!
Chris

Wenns hilft, dann ist hier der komplette (leicht anonymisierte (IPs, SSID, WLAN Passwort, UUIDs, usw.)) Code, allerdings zumindest teilweise mit wohlmöglich irreführenden ungepflegten Kommentaren. Sorry dafür. Geratene 80% der Kommentare sollten aber ungefähr stimmen.

Display.ino (33.8 KB)

Es handelt sich um ein Array aus Bytes. Das sieht man ja aus an dem Zeiger auf Byte. Damit kann beliebige Daten übertragen, je nachdem wie man sie interpretiert. Von der Stelle betrachtet ist der Cast nicht falsch.

Du willst einen C String übertragen. Das sind Null-terminierte char Arrays. Allerdings fehlt die Terminierung. Und deshalb wird wird dann beim Parsen unter gewissen Umständen mehr gemacht als du eigentlich willst (wenn immer wirklich 15 Stellen übertragen werden fällt das nicht auf). Und hier sieht man dann das ein einfacher Cast nicht ausreicht um daraus einen richtigen String zu machen

Die korrekte Lösung ist wie gesagt das Array in ein temporäres char Array zu Kopieren das um 1 größer ist. Und dieses dann zu Terminieren.
Wenn du das ursprüngliche Array nach dem letzten Zeichen terminierst bekommst du einen Pufferüberlauf in dem Fall wo das Array voll ist. Man könnte auch im letzten gültigen Index terminieren (length - 1). Aber das überschreibt dann das letzte Zeichen. Auf einem ESP hast du so viel RAM dass am man es auch ruhig richtig machen kann und nicht faul sein muss.

Okay, einverstanden!

Wenn du das ursprüngliche Array nach dem letzten Zeichen terminierst bekommst du einen Pufferüberlauf in dem Fall wo das Array voll ist.

Da würde ich gerne mal nachschauen wie groß das Array ist. Ich vermute es ist noch größer.
Aber wo finde ich das?
Ansonsten die Variante mit dem temporären char.
Die letzte Stelle abschneiden möchte ich nicht, auch wenn sie völlig irrelevant ist und die Lösung sicher funktionieren wird........wenn da jetzt nur eine "0" drin steht, dann wird diese doch gelöscht und durch eine Terminierung "\0" ersetzt. Somit wäre das char Array dann komplett leer, anstatt einer "0" und danach einer "\0". (Oh Gott, hoffentlich hab ich mich jetzt nicht zu weit aus dem Fenster gelehnt, ich hoffe das ist richtig was ich gerade geschrieben habe?!)

Lieben Gruß,
Chris

Ich habe das jetzt mit dem Buffer gemacht, und es scheint zu funktionieren:

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(" ");

  // den timestamp aus der MQTT Message extrahieren und in ein char array timestamp speichern
  char* pos = strstr((char*)payload, "timestamp=");
  if (pos != NULL) {
    memset(timestamp, NULL, sizeof(timestamp));
    memcpy(timestamp, pos + 10, 13);
  }
  Serial.print(timestamp);

  // aus dem byte array "payload" ein char array "buffer" machen und dieses an der letzten Stelle terminieren
  char payloadbuffer[length + 1];
  memcpy(payloadbuffer, payload, length);
  payloadbuffer[length] = '\0';

  // den value aus dem payloadbuffer extrahieren und in die double variable "LiterProStunde" schreiben 
  pos = strstr((char*)payloadbuffer, "value=");
  if (pos != NULL)
    LiterProStunde = atof(pos + 6);

  Serial.print(" ");
  Serial.print(LiterProStunde, 15);
  Serial.print(" l/h ");
  LiterProMinute = LiterProStunde / 60;
  LiterSeitStart += 0.5;

  while (LiterSeitStart != OldLiterSeitStart) {               // nur wenn der neu gelesene Wert "LiterSeitStart" anders ist als der alte gelesene Wert "OldLiterSeitStart" soll diese Schleife durchlaufen werden
    tft.setTextDatum(TR_DATUM);                               // Text rechtsbündig ausrichten auf dem Display setzen
    tft.setTextFont(4);                                       // Font 4 auswählen
    tft.setTextSize(1);                                       // Text Größe 1 auf dem Display setzen
    tft.setTextColor(BackgroundColor);                        // Hintergrundfarbe setzen
    tft.drawFloat(OldLiterSeitStart, 1, 197, 55 + 3 * 26, 4); // den alten Wert mit Hintergrundfarbe auf 1 Nachkommastelle gerundet an Position x=197 und y=55+3*26 mit Font 4 auf dem Display ausgeben (= auf Display löschen)
    tft.setTextColor(TFT_WHITE);                              // Textfarbe TFT_WHITE auf dem Display setzen
    tft.drawFloat(LiterSeitStart, 1, 197, 55 + 3 * 26, 4);    // den neuen gelesenen Wert auf 1 Nachkommastelle gerundet an Position x=197 und y=55+3*26 mit Font 4 auf dem Display ausgeben
    tft.setCursor(202, 55 + 3 * 26);
    tft.print("l seit ..:..");
    OldLiterSeitStart = LiterSeitStart;                       // aktuellen Wert in Variable "OldLiterSeitStart" schreiben, um sich diesen zu merken
  }

  while (LiterProMinute != oldLiterProMinute) {               // nur wenn der neu gelesene Wert "LiterProMinute" anders ist als der alte gelesene Wert "OldLiterProMinute" soll diese Schleife durchlaufen werden
    tft.setTextDatum(TR_DATUM);                               // Text rechtsbündig ausrichten auf dem Display setzen
    tft.setTextFont(4);                                       // Font 4 auswählen
    tft.setTextSize(1);                                       // Text Größe 1 auf dem Display setzen
    tft.setTextColor(BackgroundColor);                        // Hintergrundfarbe setzen
    tft.drawFloat(oldLiterProMinute, 2, 255, 55 + 4 * 26, 4); // den alten Wert mit Hintergrundfarbe auf 2 Nachkommastellen gerundet an Position x=255 und y=55+4*26 mit Font 4 auf dem Display ausgeben (= auf Display löschen)
    tft.setTextColor(TFT_WHITE);                              // Textfarbe TFT_WHITE auf dem Display setzen
    tft.drawFloat(LiterProMinute, 2, 255, 55 + 4 * 26, 4);    // den neuen gelesenen Wert auf 2 Nachkommastelle gerundet an Position x=255 und y=55+4*26 mit Font 4 auf dem Display ausgeben
    tft.setCursor(258, 55 + 4 * 26);
    tft.print("l/min");
    oldLiterProMinute = LiterProMinute;                       // aktuellen Wert in Variable "oldLiterProMinute" schreiben, um sich diesen zu merken
  }
  Serial.print(LiterProMinute, 2);
  Serial.print(" l/min");
  Serial.println();
}

Vielen Dank nochmal für die rege zielführende Diskussion :slight_smile:

Lieben Gruß,
Chris

Da würde ich gerne mal nachschauen wie groß das Array ist. Ich vermute es ist noch größer.
Aber wo finde ich das?

Natürlich in der Lib....

Aber:
Das ist in Implementierungsdetail, welches durchaus hoch dynamisch sein kann.
z.B. sich von µC zu µC oder auch Versionsabhängig andern kann.

Darum, das Mantra:

Immer gegen die Schnittstelle programmieren,
nie gegen die Implementierung.

Dann klappt das auch!
Zumindest sind die Chancen besser.


Herzlichen Dank, für deinen Dank.

Mach das Kopieren am Anfang. Dann kannst du dir bei beiden strstr()-Aufrufen den Cast auf char* sparen