Forum: Mikrocontroller und Digitale Elektronik sscanf : Wie optimiere ich den String?


von Thorsten M. (cortex_user)


Lesenswert?

Moinsen,

ich habe einen ESP32 Server, der mit einem anderen ESP32 quasselt. Der 
Server bietet seine Daten im Format (erster Gedankenschuss!)

BEGIN,1245,020523,12.56,28.5,100,200,END
Startsignatur, Uhrzeit hhmm, Datum ttmmyy, %.2f float, %.2ffloat, 
int,int, END

sprintf Formatstring ist "%02d%02d%02d,%02d%02d%02d,%.2f,%.2f,%u,%u,END"

an, d.h. dieser String erscheint auch auf meinem Handy, wenn ich meine 
Fritzbox anfunke mit dem Browser. Klar kann man den String auch mühsam 
zerlegen mit den ganzen String Funktionen, wobei ChatGPT sehr wertvolle 
Hilfe liefert aber mit sscanf soll das doch besser gehen.

Ich weiss nicht mal ob ich die optimale Ausgabe gewäöhlt habe mit den 
führenden Nullen und dem %u

Sieht dann der sscanf Formatstring genauso aus? ich habe es noch nicht 
programmiert auf einem Test ESP32, wo ich das alles vorher ausprobiere 
aber ich frage mal vorher..... ist das komma der richtige Dilimeter? 
Sind die Formate ok?

Uhrzeit zerlegen:
stunde = zeit / 100;
min = zeit % 100;

usw.

JSON ist einfacher, man muss sich dann auch nicht drum kümmern das zu 
parsen. ChatGPT erzeugt da klasse Code Snippets, als Eingabe mit ein 
struct und raus kommt ein json konstrukt.


mfrg
Thorsten

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Thorsten M. schrieb:
> ChatGPT
Ist das dein neuer Freund/Geliebter?
Ich werde nicht mal versuchen, mit der Gehirnprothese zu konkurrieren.

von Harald K. (kirnbichler)


Lesenswert?

Thorsten M. schrieb:
> ist das komma der richtige Dilimeter?

D_e_l_i_miter.

Welchen Du verwendest, bleibt komplett Dir überlassen. Punkt wäre 
ungünstig, weil das bei Deinen Float-Zahlen auch der Dezimalpunkt ist.

Ja, die Formatstrings von sscanf und sprintf sind sich sehr ähnlich, was 
Du bei sscanf weglassen kannst, sind Angaben wie Feldbreite oder 
führende Nullen.

Json zu parsen ist deutlich mehr Aufwand (den aber nimmt Dir irgendeine 
Library ab, die Rechenleistung brauchts trotzdem), dafür aber deutlich 
sicherer, weil fehlende Elemente/ungültige Zeichen etc. je nach Qualität 
der verwendeten Library brauchbar abgefangen werden.

Wobei helfen "Begin" und "End"?

Was spricht dagegegen, einfach einzelne Textzeilen zu verwenden (die 
also mit CR, LF oder CRLF getrennt werden)?

von Thorsten M. (cortex_user)


Lesenswert?

Harald K. schrieb:
> Wobei helfen "Begin" und "End"?
>
> Was spricht dagegegen, einfach einzelne Textzeilen zu verwenden (die
> also mit CR, LF oder CRLF getrennt werden)?

Bei gar nichts, wird gar nicht verwendet.
Spricht nichts gegen. Macht das was besser?
Ich rechne nicht mit Übertragungsfehlern bei tcp/ip.... vermute ich 
zumindest.

: Bearbeitet durch User
Beitrag #7426044 wurde vom Autor gelöscht.
von Harald K. (kirnbichler)


Lesenswert?

Thorsten M. schrieb:
> Spricht nichts gegen. Macht das was besser?

Du willst herausfinden, wo der String beginnt, den Du sscanf übergibst. 
Daher ist eine definierte Trennung einzelner Strings nicht unwichtig.

Ein Stringende-Zeichen (CR, LF oder CRLF) macht halt klar, daß jetzt ein 
vollständiger String empfangen wurde. tcp überträgt üblicherweise einen 
Bytestrom, d.h. Dein Musterstring

> BEGIN,1245,020523,12.56,28.5,100,200,END

kann auch durchaus in mehreren Häppchen ankommen:

> BEGIN,1245,020523,12.56,28
> .5,100,200,END

oder auch mehrere davon hintereinander (je nachdem, wie schnell die 
Dinger gesendet werden):

> BEGIN,1245,020523,12.56,28.5,100,200,ENDBEGIN,1245,020523,12.56,28.5,100 
,200,ENDBEGIN,1245,020523,12.56,28.5,100,200,END

von Thorsten M. (cortex_user)


Lesenswert?

Also was heisst das jetzt? Doch besser so?

1245\r\n
020523\r\n
12.56\r\n
28.5\r\n
100\r\n
200\r\n

und mit welchem Format String?

Auf dem Handy erscheint immer nur eine Zeile, ich sende mit
server.send(200,"text/plain", message);

PS: Ein Server kann nicht senden. Der antwortet nur auf die Anfragen des 
Client. Also immer wenn ich mit dem Finger runter wische, alias F5 wird 
eine neue URI an den Server geschickt und die Seite wird neu aufgebaut. 
Da ich kein html verwende sind die Daten maschinenlesbar.

: Bearbeitet durch User
von Dirk B. (dirkb2)


Lesenswert?

Harald K. schrieb:
> Ja, die Formatstrings von sscanf und sprintf sind sich sehr ähnlich, was
> Du bei sscanf weglassen kannst, sind Angaben wie Feldbreite oder
> führende Nullen.

Wenn du aber das Datum 020523 wieder in 3 Variablen speichern möchtest, 
musst du auch beim scanf die Feldbreite angeben.

Besser wäre aber, sich beim Zeitformat an die ISO 8601 zu halten 
https://de.wikipedia.org/wiki/ISO_8601

Als Trennzeichen bietet sich auch Leerzeichen oder Tabulator an.
Die werden von fast allen Formatspecifier am Anfang einfach überlesen.

von Harald K. (kirnbichler)


Lesenswert?

Thorsten M. schrieb:
> Also was heisst das jetzt? Doch besser so?

Nein.

> 1245,020523,12.56,28.5,100,200\r\n

Deine Empfangsroutine empfänge einzelne Zeichen oder Blöcke vom 
Netzwerk, stoppelt die solange zu einer Zeile zusammen, bis \r\n (o.ä.) 
empfangen wurde, und ruft dann erst sscanf damit auf.

von Thorsten M. (cortex_user)


Lesenswert?

Dirk B. schrieb:
> Besser wäre aber, sich beim Zeitformat an die ISO 8601 zu halten
> https://de.wikipedia.org/wiki/ISO_8601

Du kannst auch die Epoch Time als 32 Bit übergeben und das alles mit 
localtime und mktime beim Empfänger wieder zusammen tüddeln.....

Wenn 12.45 Uhr als 1245 zahl übertragen werden also mit atoi zerlegt 
werden, dann ist

zeit.tm_hour = 1245 / 100;
zeit.tm_min = 1245 % 100;

sehr einfach.

von Thorsten M. (cortex_user)


Lesenswert?

Harald K. schrieb:
> empfangen wurde, und ruft dann erst sscanf damit auf.

ich weiss nicht was man da stöppeln muss

http.begin(serverPath.c_str());
int httpResponseCode = http.GET();
if (httpResponseCode>0) {
  String payload = http.getString();

holt einfach alles in einen String rein und fertig.

von Xanthippos (xanthippos)


Lesenswert?

Wenn du einen festen Formatstring hast, musst du bei jeder Erweiterung 
und auch bei manchen Bugfixes Server und Client gleichzeitig ändern.

Das solltest du nur machen, wenn es bei 2 ESPs bleibt. Spätestens, wenn 
du eine zusätzliche Weboberfläche für die Fehlersuche schreibst, wird 
dein eigenes Format aufwendig. JSON kodieren und Parsen machst so ein 
ESP nebenbei.

von Harald K. (kirnbichler)


Lesenswert?

Thorsten M. schrieb:
> String payload = http.getString();

Wenn die dort verwendete Klasse sich darum kümmert, musst Du Dich 
natürlich nicht selbst drum kümmern.

Wurde bislang irgendwo erwähnt, daß Du das Zeug verwendest?

von Thorsten M. (cortex_user)


Angehängte Dateien:

Lesenswert?

Xanthippos schrieb:
> JSON kodieren und Parsen machst so ein
> ESP nebenbei.

Jupp... bei JSON kann ich den ganzen Struct umwandeln, rüberschieben und 
den genauso wieder zusammensetzen. Trotzdem muss man bei Änderungen 
immer beide ändern. Für den Browser habe ich schon alles fertig, in bunt 
und schön optisch aufbereitet (Monat noch falsch). Jetzt fehlt nur noch 
ein Display im Wohnzimmer, wo die Daten auf Displays geschickt werden.

: Bearbeitet durch User
von Xanthippos (xanthippos)


Lesenswert?

> genauso wieder zusammensetzen

Nein, du kannst auch einen Parser nehmen, der ein JSON-Objekt erzeugt. 
Hat dann Methoden, mit denen du nachfragen kannst, welche Attribute 
vorhanden sind.

von Thorsten M. (cortex_user)


Lesenswert?

Es gibt noch eine einfachere Methode...

den ganzen Datenstruct, der zudem noch zwei interne Struct hat als reine 
Heex-Binärdaten rüberschieben, quasi Intel Hex Format für Flash Brenner.

563df28373f738e20a7b37673... usw

und auf der Gegenseite, die die gleiche CPU hat alles zurückbauen in den 
gleichen Struct. ggf. mit Packed arbeiten. Das geht mit atoi und strncpy 
ganz einfach. Checksumme hintendran und fertig.

: Bearbeitet durch User
von Thorsten M. (cortex_user)


Lesenswert?

Xanthippos schrieb:
> Nein, du kannst auch einen Parser nehmen, der ein JSON-Objekt erzeugt.

Vielleicht bin ich ja blöd aber ich erzeuge das alles manuell

doc["Strom"] = stromwert,
Create JSOnObject Zeit;
doc[Data][Zeit]["stunde"] = ....
SerializeJSON(....);

und auf der Gegenseite das gleiche umgekehrt. Der Struct füllt sich ja 
nicht von allein.

: Bearbeitet durch User
von Gerald K. (geku)


Lesenswert?

Viele Wege führen bekanntlich nach Rom.

- wenn man leichter debuggen will, dann ist ein "Plain-Text" sicher von 
Vorteil, aber binäre Daten brauchen weniger Platz
- "Plain-Text" kann auch zur Plausibliätsprüfung verwendet werden, 
Zahlen enthalten keine Buchstaben oder Sonderzeichen
- als Delimiter ist ';' üblich, Merkhilfe: De_limiter kommt von Limit 
(wer noch nie etwas falsch geschrieben hat, der werfe den ersten Stein, 
aber Hinweise helfen es in Zukunft besser zu machen)
- Formatierungsangaben (%02d oder %2d machen nur Sinn, wenn der gesamte 
Datensatz immer gleich lang sein soll und helfen nur wenn die 
Platzreserve genügen groß ist (z.B. 4711 braucht mindestens %04d oder 
%4d)
- BEGIN und END bzw. STX und ETX sind nicht notwendig, da die Daten in 
TCP/IP bzw. UDP/IP eingebettet sind
- eine Stunde hat 60 und nicht 100 Minuten, also stunde = 
zeit_in_minuten / 60; minuten = zeit_in_minuten % 60;
- "Plain Text" abstelle von binäre Daten macht Sinn wenn zwischen 
verschiedenen Rechnerplattformen kommuniziert wird. Stichwort: Byteorder

: Bearbeitet durch User
von Joe L. (joelisa)


Lesenswert?

Thorsten M. schrieb:

> BEGIN,1245,020523,12.56,28.5,100,200,END
> Startsignatur, Uhrzeit hhmm, Datum ttmmyy, %.2f float, %.2ffloat,
> int,int, END

Bei Zeit-/Datums-Angaben hält man sich vorteilhafterweise an die ISO 
8601.

Zusammenfassung (aus https://de.wikipedia.org/wiki/ISO_8601):
"Die Norm enthält verschiedene Datums- und Zeitformate, die jedoch rein 
formal und in den meisten Fällen schon durch die Anzahl der verwendeten 
Ziffern unterscheidbar sind. Die Norm ist vor allem bekannt für das 
Datumsformat YYYY-MM-DD, das oft auch als „internationales Datumsformat“ 
bezeichnet wird. Das üblichste Zeitformat der Norm ist hh:mm:ss. Ein 
Beispiel für das Datum ist 2004-06-14 (14. Juni 2004) und für die 
Uhrzeit 23:34:30 (23 Uhr, 34 Minuten und 30 Sekunden) und für beides 
zusammen 2004-06-14T23:34:30."

just my 2 ct

von Harald K. (kirnbichler)


Lesenswert?

Joe L. schrieb:
> Bei Zeit-/Datums-Angaben hält man sich vorteilhafterweise an die ISO
> 8601.

Welchen Vorteil bietet das? Wenn die Datums-/Zeitangabe in einer 
Datenbank gespeichert werden soll, d'accord, aber wenn nur zwei µCs 
miteinander reden, würde das alte Unix-Zeitformat (Sekunden seit 1970) 
völlig ausreichen, oder eine Abwandlung mit bekanntem festen Offset, 
damit das Jahr-2038-Problem verzögert wird. So etwas ist erheblich 
simpler zu parsen (eine Zahl, mehr nicht) und reicht völlig aus, um z.B. 
zwei Softwareuhren zu synchronisieren.

von Harry L. (mysth)


Lesenswert?

Harald K. schrieb:
> Joe L. schrieb:
>> Bei Zeit-/Datums-Angaben hält man sich vorteilhafterweise an die ISO
>> 8601.
>
> Welchen Vorteil bietet das? Wenn die Datums-/Zeitangabe in einer
> Datenbank gespeichert werden soll, d'accord, aber wenn nur zwei µCs
> miteinander reden, würde das alte Unix-Zeitformat (Sekunden seit 1970)
> völlig ausreichen, oder eine Abwandlung mit bekanntem festen Offset,
> damit das Jahr-2038-Problem verzögert wird. So etwas ist erheblich
> simpler zu parsen (eine Zahl, mehr nicht) und reicht völlig aus, um z.B.
> zwei Softwareuhren zu synchronisieren.

Und hat zusätzlich den nicht zu unterschätzenden Vorteil, daß 
Alarmzeitpunkte mit einem einzigen Vergleich erkannt werden können.

: Bearbeitet durch User
von Harald K. (kirnbichler)


Lesenswert?

Der einzige Nachteil: Es ist halt nicht menschenlesbar, was bei der 
Fehlersuche etwas lästig sein kann. Aber wie oft will man sich schon das 
Gelaber von Microcontrollern ansehen?

von Harry L. (mysth)


Lesenswert?

Harald K. schrieb:
> Der einzige Nachteil: Es ist halt nicht menschenlesbar, was bei der
> Fehlersuche etwas lästig sein kann. Aber wie oft will man sich schon das
> Gelaber von Microcontrollern ansehen?

Das wird aber -besonders von Anfänger- vollkommen überbewertet.
Der effektive Anteil der menschen-lesbaren Ein- und Ausgaben in einem 
Programm ist -gemessen an den Taktzyklen- i.d.R. verschwindend gering.

von Harald K. (kirnbichler)


Lesenswert?

Kommt drauf an, in welcher Häuftigkeit entsprechende Telegramme in einer 
Kommunikation verwendet werden. Und je nach Schnittstelle kann auch die 
reine Telegrammlänge relevant sein (hier nicht, hier wird Netzwerk 
genutzt, aber es gibt ja auch serielle Schnittstellen, die mit ganz 
erheblich geringeren Datenraten arbeiten).

Wenn nur alle paar Sekunden oder gar Minuten so ein Telegramm 
ausgetauscht wird, ist die für das Erstellen und Zerlegen erforderliche 
Rechenleistung ziemlich sicher völlig wurscht, wenn aber so etwas mit 
höherer Datenrate übertragen wird, kann die Angelegenheit schon sehr 
anders aussehen.

Man muss sich also schon auch das Umfeld und das Anwendungsszenario 
betrachten, bevor man sich für eine bestimmte Lösung entscheidet. 
Andererseits: eine programmiertechnisch simplere Lösung richtet selten 
Schaden an, auch wenn es nicht auf Performance, Codegröße o.ä. ankommt.

von Harry L. (mysth)


Lesenswert?

Harald K. schrieb:
> Man muss sich also schon auch das Umfeld und das Anwendungsszenario
> betrachten, bevor man sich für eine bestimmte Lösung entscheidet.
> Andererseits: eine programmiertechnisch simplere Lösung richtet selten
> Schaden an, auch wenn es nicht auf Performance, Codegröße o.ä. ankommt.

Nein, das muß man in so einem Fall nicht.

Menschen-lesbare Ausgaben sind immer die absolute Ausnahme gemessen am 
tatsächlichen Programflow.

Solche Ausgaben erfolgen maximal nur wenige Male pro Sekunde, und in der 
Zwischenzeit dreht dein µC Millionen Runden.

Das seltene Umstellen in Menschen-lesbare Form fällt da absolut nicht 
mehr ins Gewicht.

Eine zusätzliche Debug-Funktion, die so einen Wert lesbar ausgibt hat 
man sich auch schnell geschrieben.

Mit Unix- oder Epoch-Time lässt sich nun mal ausnahmslos sehr viel 
besser und effizienter arbeiten.

Ausserdem finden sich in der Standardlib auch gleich so praktische Dinge 
wie die Ermittlung des Wochentags etc.

von Harald K. (kirnbichler)


Lesenswert?

Harry L. schrieb:
> Solche Ausgaben erfolgen maximal nur wenige Male pro Sekunde

Wer legt das fest? Ist das irgendwo genormt?

von Harry L. (mysth)


Lesenswert?

Harald K. schrieb:
> Harry L. schrieb:
>> Solche Ausgaben erfolgen maximal nur wenige Male pro Sekunde
>
> Wer legt das fest? Ist das irgendwo genormt?

Nicht "genormt", aber unser Auge lässt nun mal nicht mehr zu.

von Harald K. (kirnbichler)


Lesenswert?

Harry L. schrieb:
> Nicht "genormt", aber unser Auge lässt nun mal nicht mehr zu.

Das weißt Du, das weiß ich, aber weiß es der unbedarfte Programmierer 
(oder neudeutsch "Progger"), der sich irgendein Protokoll einfallen 
lässt?

von Harry L. (mysth)


Lesenswert?

Harald K. schrieb:
> Harry L. schrieb:
>> Nicht "genormt", aber unser Auge lässt nun mal nicht mehr zu.
>
> Das weißt Du, das weiß ich, aber weiß es der unbedarfte Programmierer
> (oder neudeutsch "Progger"), der sich irgendein Protokoll einfallen
> lässt?

Nein, weiss er nicht, und deshalb ist es auch so wichtig, daß die 
Erfahreneren das erklären.

von Sebastian W. (wangnick)


Lesenswert?

Harald K. schrieb:
> das alte Unix-Zeitformat (Sekunden seit 1970) völlig ausreichen, oder
> eine Abwandlung mit bekanntem festen Offset, damit das Jahr-2038-Problem
> verzögert wird.

Mikrosekunden seit 1.1.1970. Dann braucht man eh 64 Bit, und dann hat 
man kein 2038-Problem mehr ...

LG, Sebastian

von Philipp K. (philipp_k59)


Lesenswert?

Waren nicht die meisten Zeitfunktionen iso8601 kompatibel?

Ist schon ein bisschen her, habe auch hin und her geparst, mit der ISO 
war das nicht mehr viel Arbeit.

von Thorsten M. (cortex_user)


Lesenswert?

Ok,

Schluss mit dem sscanf Zirkus, so sieht es jetzt aus und den Code habe 
nicht ich geschrieben sonder ChatGPT mit der Eingabe des Structs als 
Input und der Aufforderung die daten zu verpacken. Noch ein wenig dran 
herum geschliffen und der Client sieht jetzt ganz sauber das hier und 
das steht auch in den Variablen nach dem parsen des Strings.

Und eines begrüsse ich wirklich bei KI wie ChatGPT: Dinge, die man nicht 
wirklich verstehen muss, die einfach nur funktionieren sollen, dafür ist 
dieses Tool klasse. Denn die Einarbeitung in JSON interessiert mich 
überhaupt nicht weil ich das ganz sicher nie wieder nutzen werde oder 
nur rudimentär. In einem anderen projekt mit den Wetterdaten musste 
JSON5 auf JSON6 portiert werden, auch den Code hat er oder besser es 
einwandfrei gemeistert.

{
  "UBatt": 26.08999634,
  "PLast": 0,
  "PSolar": 28.23621559,
  "ILast": 0,
  "ISolar": 1.082262158,
  "AhSolar": 12.04978657,
  "WhSolar": 336.913147,
  "WhLast": 138.8211365,
  "ISolSensorCal": 1.602132797,
  "ILastSensorCal": 1.648699164,
  "ActualState": 49,
  "MinOfDay": 1145,
  "Solar": {
    "sunRiseMinute": 307,
    "sunSetMinute": 1296
  },
  "Timecode": {
    "tm_year": 2023,
    "tm_mon": 6,
    "tm_mday": 4,
    "tm_hour": 19,
    "tm_min": 5,
    "tm_sec": 33
  },
  "WhSolarHour": [
    1.345246526e-43,
    1.401298464e-45,
    0,
    0,
    0,
    0,
    0,
    0,
    0
  ]
}

1:1 Code von ChatGPT
1
void createJson() {
2
  StaticJsonDocument<512> doc;  // Größe des JSON-Dokuments anpassen, je nach Bedarf
3
4
  /* JSON Struktur erzeugen */
5
  doc["UBatt"] = Data.UBatt;
6
  doc["PLast"] = Data.PLast;
7
  doc["PSolar"] = Data.PSolar;
8
  doc["ILast"] = Data.ILast;
9
  doc["ISolar"] = Data.ISolar;
10
  doc["AhSolar"] = Data.AhSolar;
11
  doc["WhSolar"] = Data.WhSolar;
12
  doc["WhLast"] = Data.WhLast;
13
  doc["ISolSensorCal"] = Data.ISolSensorCal;
14
  doc["ILastSensorCal"] = Data.ILastSensorCal;
15
  doc["ActualState"] = Data.ActualState;
16
  doc["MinOfDay"] = Data.MinOfDay;
17
  
18
  JsonObject solar = doc.createNestedObject("Solar");
19
  solar["sunRiseMinute"] = Data.Solar.sunRiseMinute;
20
  solar["sunSetMinute"] = Data.Solar.sunSetMinute;
21
22
  JsonObject timecode = doc.createNestedObject("Timecode");
23
  timecode["tm_year"] = Data.Timecode.tm_year;
24
  timecode["tm_mon"] = Data.Timecode.tm_mon;
25
  timecode["tm_mday"] = Data.Timecode.tm_mday;
26
  timecode["tm_hour"] = Data.Timecode.tm_hour;
27
  timecode["tm_min"] = Data.Timecode.tm_min;
28
  timecode["tm_sec"] = Data.Timecode.tm_sec;
29
30
  JsonArray whSolarHour = doc.createNestedArray("WhSolarHour");
31
  for (int i = 0; i < 24; i++) {
32
    whSolarHour.add(Data.WhSolarHour[i]);
33
  }
34
35
  /* JSON Buffer mit Pretty Funktion ausgeben */
36
  char jsonBuffer[512];
37
  serializeJson(doc, jsonBuffer, sizeof(jsonBuffer));
38
  server.send(200, "text/plain",jsonBuffer);
39
}

: Bearbeitet durch User
von Harald K. (kirnbichler)


Lesenswert?

Kein Wunder, warum auch für Trivialaufgaben immer fettere Hardware 
benötigt wird.

von Harry L. (mysth)


Lesenswert?

Harald K. schrieb:
> Kein Wunder, warum auch für Trivialaufgaben immer fettere Hardware
> benötigt wird.

"Script-Kiddies" eben...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.