Zahlen aus char array extrahieren

Moin,

sorry, falls es nicht die richtige Nomenklatur sein sollte.
Ich glaube es handelt sich um ein char Array.

Also, ich habe einen ESP8266 an einen MQTT Broker subscribed und bekomme mit folgender Funktion:

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.println();
}

folgende Ausgaben:

Message arrived [volkszaehler/Wasser/Verbrauch] timestamp=1534194143956 value=1.7996292763691
Message arrived [volkszaehler/Wasser/Verbrauch] timestamp=1534194154117 value=177.14791851196
Message arrived [volkszaehler/Wasser/Verbrauch] timestamp=1534194158913 value=375.31276063386
Message arrived [volkszaehler/Wasser/Verbrauch] timestamp=1534194163333 value=407.23981900452

Nun hätte ich gerne die beiden Zahlen Zeichenfolgen extrahiert.
1534194143956 ist ein timestamp in Millisekunden nach 1970. Den hätte ich gerne in ein neues char Array "Timestamp" geschrieben.
1.7996292763691 soll in eine float Zahl "Wert" umgewandelt werden.

Nun könnte man die Startstellen auszählen, wann der Wert beginnt. Und die jeweilige Länge.
Der Timestamp fängt an der 11. Stelle an, und ist 13 Stellen lang.
Der Wert fängt an der 31. Stelle an und ist 15 Stellen lang.
Is das eine gute Idee?

Oder kann man irgendwie das "timestamp=" und das "value=" erkennen und danach dann die Stellen bis keine Zahl, bzw. besser bis ein Leerzeichen kommt.

Bis dahin bin ich theoretisch schon.
Nun fehlt mir nur irgendwie der Startimpuls, wie ich das umsetzen kann?!

Lieben Gruß,
Chris

Oder kann man irgendwie das "timestamp=" und das "value=" erkennen

Ja

Dann wäre C++ aber wirklich eine scheußliche Programmiersprache, wenn man das nicht könnte!

Hmmm.....jaaaaaa.....danke.

Leider komme ich mit meinem rumgerate nicht weiter:

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]);
  }
  char PosOfString = (strstr ((char)payload[50], "value="));
  float Wert = atof(PosOfString +15);
  Serial.print(" ");
  Serial.print(Wert,10);
  Serial.println();
}

Gelesen hab ich jetzt schon viel.
Verstanden hab ich davon noch nicht viel...

Lieben Gruß,
Chris

strstr() liefert dir einen Zeiger. Kein Zeichen

Und was du bei atof() addierst ist die Länge des Teil-Strings (damit die Konvertierung danach beginnt). Das ist bei "value=" sicherlich nicht 15

Hier habe ich dazu erst vor Kurzem ein Beispiel gezeigt:
http://forum.arduino.cc/index.php?topic=563223.msg3837839#msg3837839

....bestimmt, aber ich bin doof und versteh das nicht. Das ist das Problem.

Ich habe mir mal dein Beispiel aus dem Link angeschaut.
Ich denke das verstehe ich soweit.
strstr() liefert mir eine Zahl zurück, die der Position im char Array entspricht. (ich hoffe das ist ein Zeiger?! wissen tue ich es nicht)

Ich habe meinen Code soweit angepasst.
Das Problem steckt nun aber an anderer Stelle:
char* PosOfString = (strstr ((char)payload[50], "value="));
will nicht, habs auch schon ohne * probiert.

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*);*

  • }*
    _ char* PosOfString = (strstr ((char)payload[50], "value="));_
  • if (PosOfString != NULL)*
  • Wert = atof(PosOfString + 1);*
  • Serial.print(" ");*
  • Serial.print(Wert, 10);*
  • Serial.println();*
    }

Ein Zeiger ist die Adresse der Variable im Speicher.

Den Quatsch mit payload hatte ich gar nicht gesehen. So:

char* PosOfString = strstr((char*)payload, "value=");

Merke: Array-Variablen sind äquivalent zu Zeigern auf das erste Element. Der Parameter payload ist daher ein Zeiger. Das ist ganz normal wenn man Arrays/C-Strings übergibt. Das Array zerfällt dann bei der Übergabe in den Zeiger

Immer noch falsch:

Wert = atof(PosOfString + 1);

Ich habe gesagt die Länge des Teil-Strings addieren. Wie lang ist "value="? 6!
strstr() liefert einen Zeiger auf den Anfang des gefundenen Teil-Strings. Wenn du zu der Adresse also die Länge addierst bist du genau bei dem Zeichen danach.

Du kannst hier auch strlen() verwenden wenn du es 100%ig verständlich willst. Aber die Länge ist fix, daher muss man das eigentlich nicht zur Laufzeit berechnen

Okay, verstanden (hoffe ich)
So far, so good, die Extrahierung des Wertes scheint zu klappen
Ich bekomme folgenden Output:
Message arrived [volkszaehler/Wasser/Verbrauch] timestamp=1534203356679 value=17.307026652821 17.3070259094
Message arrived [volkszaehler/Wasser/Verbrauch] timestamp=1534203366735 value=178.99761336516 178.9976196289
Message arrived [volkszaehler/Wasser/Verbrauch] timestamp=1534203371475 value=379.74683544304 379.7468261719

Das die Zahlen nicht ganz identisch sind, liegt vermutlich an der Genauigkeit der Float Variable im ESP8266 ?!

themanfrommoon:
Das die Zahlen nicht ganz identisch sind, liegt vermutlich an der Genauigkeit der Float Variable im ESP8266 ?!

Korrekt. Genauer geht es mit double. Float und Double ist bei den 8-Bit AVRs identisch und du hast nur 6-7 signifikante Stellen. Aber auf den 32-Bit Prozessoren hat double 64 Bit und daher 15 signifikante Stellen

Jawoll, habs auf double geändert und sieht besser aus :slight_smile:

Nun fehlt nur noch das auslesen des Timestamps. Funktioniert das mit memcpy?

Genauso mit strstr()

Ja, sorry, falsch formuliert...
Das Suchen geht natürlich genauso mit strstr()

Was ich meinte war das Zuweisen anstatt mit atof für das double jetzt mit memcpy für das char Array.
Ich habe jetzt folgenden Code der bereits funktioniert.

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* PosOfString = strstr((char*)payload, "timestamp=");
  if (PosOfString != NULL)
    memcpy(Timestamp, PosOfString + 10, 13);
  Serial.print(Timestamp);

  PosOfString = strstr((char*)payload, "value=");
  if (PosOfString != NULL)
    Wert = atof(PosOfString + 6);
  Serial.print(" ");
  Serial.print(Wert, 15);
  Serial.println();
}

Haut das so hin?

Für ein char Array fehlt da die Null-Terminierung. Das sollte man eher so machen:

if (PosOfString != NULL)
{
   memset(Timestamp, NULL, sizeof(Timestamp));
   memcpy(Timestamp, PosOfString + 10, 13);
}

Und auch aufpassen dass das Array 14 Zeichen lang ist und nicht 13!

Normal nimmt da eher Integer Variablen entsprechender Größe

Variablen fangen in C/C++ übrigens üblicherweise mit Kleinbuchstaben an. Das kompiliert auch so, aber du wist kaum jemanden finden der so Code schreibt

Okay, nochmal verbessert.
Das Array hatte ich schon 14 Zeichen lang.

Hier der Code, getestet und funktioniert.

char timestamp[14];

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)
    wert = atof(pos + 6);
  Serial.print(" ");
  Serial.print(wert, 15);
  Serial.println();
}

Vielen Dank nochmal an alle. Ihr seid Spitze!
Nu aber gute Nacht 8)

Liebe Grüße,
Chris

Hi

Gegen die Ungenauigkeit würde helfen, wenn Du mit Festkomma-Arithmetik arbeitest.
Sofern Dir 20 Stellen reichen (VOR + HINTER dem Komma) und die Zahl vorne mit <=1844674407 anfängt (genauer ist der Linux-Taschenrechner nicht, bei 64bit unsigned sagt Er halt 1,8...x10^19), kannst Du die übergebenen Zahlen einfachst zusammen rechnen.
UNSIGNED LONG LONG ENDZAHL=0; (oder uint64_t)
Von Vorne beginnend suchst Du ein Zahlzeichen.
Du Multiplizierst ENDZAHL mit 10, addierst dieses Zeichen zu Deiner ENDZAHL und ziehst 0x30 (oder '0') davon wieder ab.
Wenn Du das Komma (Punkt) erkennst, 'merkst' Du Dir, wo Dieses hin muß - dieses Zeichen wird nicht verrechnet.
Mit den folgenden Ziffern machst Du das Gleiche.
Du bekommst eine Zahl mit maximal 20 Stellen in Dezimal, wo Du an der entsprechenden Stelle nur das Komma wieder einsetzen musst.
Also, wenn Du Dir gemerkt hast, daß drei Zeichen vor dem Komma waren, gibst Du die ersten drei Zahlzeichen aus, ein Komma hinterher, die restlichen Zahlzeichen ausgeben.
Im Nachbar-Thread haben wir die Tage viel mit 64-Bit-Zahlen und Deren Anzeige (z.B. per Serial.print) - in diese Richtung gehen meine Gedanken.
Da Du hier bei INT bleibst (bzw. unsigned int), hast Du keine Rundungsfehler - eigentlich hast Du auch keine Komma-Stellen - Die machst Du Dir erst wieder selber rein durch Deine 'Festkomma-Stelle'.

Verständlich?

MfG