Hallo, ich habe einen Temperaturserver mit einem ESP8266 dargestellt, der Daten sammelt. Ich möchte absichtlich erst mal keine Speichermöglichkeit wie SSD, Flash etc. nutzen sondern sammle die Daten im RAM des ESP (Circular Buffer). Wenn dann der Client anfragt, möchte ich die bis dahin gesammelten Werte übertragen und mache das per JSON Array. Das Interessante ist nun, dass ich versucht habe, das zu übertragene Array immer größer zu machen und es eine harte Grenze gibt. Bis 25 Datensätze (unsigned long ntp Zeit, float Temperatur) lassen sich in einem Rutsch übertragen. Wenn ich dann auf 30 gehe, sehe ich im Browser, dass dort nichts mehr ankommt. Ich weiß ehrlich gesagt nicht, warum. Wenn ich wieder auf 25 oder weniger gehe, dann klappt es wieder. Jetzt bin ich auf die Idee gekommen, einfach mehrmals die 25 Datensätze zu schicken - das geht leider ebenfalls nicht und nur der erste Datensatz mit 25 Einträgen kommt an - der 2. Satz usw. kommt dann ebenfalls nicht mehr an. Kann mir jemand auf die Sprünge helfen, warum das so ist? Vielleicht gibt es auch irgendein Tutorial, wo größere Datenmengen per GET und JSON übertragen werden? Lieben Dank.
Du kannst mit dem ESP beliebig große Datenmengen übertragen. Es hängt serh davon ab, wie dein Code das macht. Insbesondere solltest du bedenken, dass nur wenige Kilobyte Stack zur Verfügung stehen.
Hi, wie viele byte belegt denn ein Datensatz als json? Ich vermute auch das der stack zu läuft. Es kann aber auch sein, das du die send/receive buffer des tcpstacks vergrößern musst.
Ein Datensatz besteht aus einem unsigned Long und einem Float - beides 25mal zusammen gefasst in JSON Struktur. Der Code des Arduino läuft sauber durch und ich habe den Eindruck, dass er die Daten versendet. In der Console des Browsers dagegen kommt nichts an, wenn die Daten größer als 25 Sätze werden. Ich weiß nicht, wo die Limitierung liegt… Ich kann auch mal den Code schicken, wenn das hilft…
Stephan D. schrieb: > In > der Console des Browsers dagegen kommt nichts an, wenn die Daten größer > als 25 Sätze werden. Mit Wireshark kontrollieren. LG, Sebastian
Stephan D. schrieb: > Ein Datensatz besteht aus einem unsigned Long und einem Float - beides > 25mal zusammen gefasst in JSON Struktur. Zeige doch mal ein Beispiel. So ein JSON Dokument besteht ja aus mehr als den reinen Nutzdaten. Ein Kilobyte oder gar etwas mehr sind durchaus vorstellbar.
Beitrag #7348614 wurde von einem Moderator gelöscht.
Beitrag #7348622 wurde von einem Moderator gelöscht.
Man muss (hoffentlich) nicht unbedingt alles an einem Stück senden.
Hallo, ich weiß jetzt nicht, welche ESP8266-IDF Version er benutzt und ob Espressif/Arduino da was verändert hat, unter 2.7.4 war es so: TCP-Buffer war irgendwas knapp unter 2000 Byte, spielte aber keine Rolle, die Zerlegung in Pakete mache nie Probleme im TCP-Stack. Stringfunktionen kamen mit Strings bis knapp 8kB klar, allerdings gab es keine Fehlermeldungen, wenn z.B. sHTML.replace("~t_bz~", sensor_array[0]) lieferte kommentralos einen leeren String zurück wenn interne Buffer der Stringfunktion nicht genug Speicher bekamen. Gruß aus Berlin Michael
Stephan D. schrieb: > Der Code des Arduino Arduino oder ESP8266? Für dem ESP8266 sind Antworten von mehrern KB sicher kein Problem. Wie viele KB hat dein JSON mit 30 Sätzen? JSON zu fuß oder mit einer Library? Welche Library? Welche Version Welche ESP Core Version? Kompletter compilierbarer Code sieht wie aus?
Hallo, so viele Fragen - da hilft nur Code schicken :-) Also ich habe jetzt einen funktionierenden Webserver angehängt, der schöne Temperaturgrafiken schickt. Wenn ihr den Temperaturfühler nicht zur Hand habt, könnt ihr ja auch einfach Zufallsfloat Werte schicken. Jetzt kommt der Punkt: Wenn man die Anzahl der Buffer von 25 auf 30 dreht und auch 30 Datensätze wegschicken will, dann kommt im Browser nichts mehr an und ich weiß nicht warum. Im Ziel würde ich gerne um die 500 Datenpakete auf einen Rutsch schicken. Der Buffer des ESP8266 ist groß genug. Herzlichen Dank.
Stephan D. schrieb: > Wenn man die Anzahl der Buffer von 25 auf 30 dreht und auch 30 > Datensätze wegschicken will, dann kommt im Browser nichts mehr an und > ich weiß nicht warum. Noch mal: Wireshark! Erst einmal sehen was auf der Leitung ist! LG, Sebastian
Stephan D. schrieb: > Wenn man die Anzahl der Buffer von 25 auf 30 dreht und auch 30 > Datensätze wegschicken will, dann kommt im Browser nichts mehr an und > ich weiß nicht warum. Bekommst du dann eine Fehlermeldung auf der seriellen Konsole, vielleicht vom Watchdog? Du hättest vielleicht mal erwähnen sollen, dass du in deinem Konstrukt SSE verwendest, das ist nämlich sehr speziell. In deinem Fall wird nach einem GET Request dieser beantwortet und parallel dazu auch noch über den SSE Kanal Daten gesendet. Das braucht entsprechend viel RAM. Im Quelltext der Bibliothek habe ich diese Zeilen gefunden:
1 | if(client->space() < len){ |
2 | return 0; |
3 | }
|
Da wird einfach ohne jede Warnung das Senden unterlassen, wenn nicht genug Platz im Puffer frei ist. Offenbar ist es hier nicht vorgesehen, die Response (dein JSON Dokument) ggf. in mehreren Stücken zu senden. Für mal an der gezeigten Stelle eine Warnmeldung ein, die du auf dem seriellen port ausgibst. Sie wird dir Zeigen, ob ich Recht habe.
Hallo, es ist immer eine Hürde, etwas zu nutzen, was man nicht kennt. Aber ich bin ja noch jung und stelle mich den Herausforderungen :-) Also es gibt einen klaren Unterschied, den Wireshark ausgibt: Bei 25 Datensätzen gibt es eine Anzeige, wo "Stream" steht und die Daten zu sehen sind. Bei 30 Datensätzen gibt es diesen Eintrag nicht. Also war ich doch auf dem falschen Dampfer und der Arduino schickt es nicht weg?
Steve van de Grens schrieb: > Bekommst du dann eine Fehlermeldung auf der seriellen Konsole, > vielleicht vom Watchdog? Nein > Du hättest vielleicht mal erwähnen sollen, dass du in deinem Konstrukt > SSE verwendest, das ist nämlich sehr speziell. In deinem Fall wird nach > einem GET Request dieser beantwortet und parallel dazu auch noch über > den SSE Kanal Daten gesendet. Das braucht entsprechend viel RAM. Leider muss ich zugeben, dass ich einfach ein Tutorial umgebaut habe und keine Ahnung von dem Kram habe. SSE habe ich noch nie gehört - deshalb konnte ich es dir auch nicht sagen. Aber ich lerne dazu :-) > Im Quelltext der Bibliothek habe ich diese Zeilen gefunden: >
1 | > if(client->space() < len){ |
2 | > return 0; |
3 | > } |
4 | >
|
> > Da wird einfach ohne jede Warnung das Senden unterlassen, wenn nicht > genug Platz im Puffer frei ist. Offenbar ist es hier nicht vorgesehen, > die Response (dein JSON Dokument) ggf. in mehreren Stücken zu senden. > > Für mal an der gezeigten Stelle eine Warnmeldung ein, die du auf dem > seriellen port ausgibst. Sie wird dir Zeigen, ob ich Recht habe. Volltreffer!!! Bei 30 Datensätze bekomme ich die Ausgabe über die Serielle Schnittstelle - bei 25 Datensätzen nicht. Welcher Speicher Puffer Buffer ist denn jetzt voll? Kann ich was dagegen tun? Was wird denn per SSE parallel verschickt? Kann man das nicht nacheinander machen? Vielen Dank!
Löst zwar nicht das Problem, aber NTP brauchst du am ESP8266 schon lange keine 3rd Party Library mehr. Das kommt schon im ESP Core mit.
Stephan D. schrieb: > Welcher Speicher Puffer Buffer ist denn jetzt voll? Kann ich was > dagegen tun? Vermutlich ist das sehr kompliziert zu ändern. Ich würde an deiner Stelle auf einen ESP32 wechseln, der hat mehr RAM. Damit geht es vielleicht schon "einfach so". Oder du verzichtest auf den ganzen SSE Hokuspokus und wechselst auf AJAX, wobei du die gesamte Ausgabe innerhalb von server.on("/readings", HTTP_GET, ... erzeugst. Wenn du dich von SSE trennst, musst du aber auch das Javascript entsprechend umschreiben. Schau dir https://de.wikipedia.org/wiki/Ajax_(Programmierung) an. Sprintf() ist dein Freund, um die Ausgabe zu generieren. Du musst das JSON Dokument oder HTML Fragment nicht unbedingt an einem Stück als String haben, du kannst es auch on-demand Zeile für Zeile während der Ausgabe generieren. Das spart viel RAM. Diagramme kann man relativ einfach im SVG Format generieren, das ist nämlich eine Textdatei! Auch diese kannst du Zeile für Zeile ausgeben, statt an einem Stück.
Mir ist noch ein anderer Workaround eingefallen, der zumindest mir leichter fallen würde: Du fragst nicht alle Daten am Stück ab, sondern einzeln. Dazu muss dein Javascript die index Nummer an den GET Request anhängen und viele GET Requests nacheinander senden. Der ESP würde daraufhin jeweils nur noch diesen einen Datensatz liefern.
Beitrag #7351810 wurde von einem Moderator gelöscht.
Ich bin jetzt mal auf den ESP32 umgestiegen und möchte das probieren. Leider kompiliert der Code jetzt schon seit 15 Minuten!!! Ich habe Antivir und habe eine Ausnahme für den Arduino Ordner gemacht. Mein Laptop ist bestimmt nicht der schnellste, aber das compilieren dauert unglaublich lange. Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz 2.70 GHz 8GB RAM. Windows 10. Das mit dem partiellen Schicken probiere ich vielleicht aus, wenn das mit dem ESP nicht klappt...
Stephan D. schrieb: > Ich bin jetzt mal auf den ESP32 umgestiegen und möchte das probieren. > Leider kompiliert der Code jetzt schon seit 15 Minuten Das ist nicht normal. Ich habe letzte Woche auf einem 6 Jahre alten Laptop (ebenfalls mit 8 GB RAM) den ESP32 Core zum ersten mal installiert und ein Example compiliert. Das Compilieren hat keine keine 5 Minuten gedauert.
Ich habe herausgefunden, dass Antivir doch der Verursacher ist. Es reicht wohl nicht, das Verzeichnis als Ausnahme zu erklären, sondern man muss Avira für eine begrenzte Zeit deaktivieren. Jetzt hat es in 1 Minute kompiliert...
Stephan D. schrieb: > Jetzt hat es in 1 Minute kompiliert... Kann auch an der Wiederholung liegen. 90% des Codes wird nämlich vom vorherigen Lauf wieder verwendet.
Stephan D. schrieb: > Ich habe herausgefunden, dass Antivir doch der Verursacher ist. Wenn der Antivir das Avira Antivirus ist: Das ist eine bekannte Funktionseinschraenkung. Ich nehme mal an, die Scan-Maschine analysiert jedes Include-File (und das ist bei C-Programmen schon recht viel). Falls Du ein Qualitaetsprodukt aus Redmond benutzt: Die Libraries und aehnliches liegen in AppData. Stefan: > Kann auch an der Wiederholung liegen. 90% des Codes wird nämlich vom > vorherigen Lauf wieder verwendet. Schoen waers: Zumindest die klassische IDE ist sehr limitiert und kompiliert wie bekloppt. Gruesse Th.
Steve van de Grens schrieb: > Mir ist noch ein anderer Workaround eingefallen, der zumindest mir > leichter fallen würde: > > Du fragst nicht alle Daten am Stück ab, sondern einzeln. Dazu muss dein > Javascript die index Nummer an den GET Request anhängen und viele GET > Requests nacheinander senden. Der ESP würde daraufhin jeweils nur noch > diesen einen Datensatz liefern. Hallo, ich bin wie gesagt auf den ESP32 umgestiegen und kann nun statt 25 Datensätze immerhin knapp 200 Datensätze in einem Rutsch schicken. Das ist schneller als gedacht und funktioniert gut. Leider reichen mir die 200 Datensätze nicht. Ich brauche ca. 500 Datensätze. Also würde ich auf deine Idee zurück kommen und das mit dem Index am GET Request versuchen wollen. Gibt es denn irgendwo ein Bsp. oder eine Dokumentation, wie ich das genau machen muss? Per Google finde ich nichts (vermutlich die falschen Stichworte...) Vielen Dank.
Stephan D. schrieb: > Gibt es denn irgendwo ein Bsp. oder eine Dokumentation, wie ich das > genau machen muss? Ich denke nicht. Das musst du dir schon selbst erarbeiten.
Stephan D. schrieb: > Leider reichen mir die > 200 Datensätze nicht. Ich brauche ca. 500 Datensätze. Also würde ich auf > deine Idee zurück kommen und das mit dem Index am GET Request versuchen > wollen. Du benutzt Eh schon den ESPAsyncWebServer. Der kann auch am ESP8266 Millionen an Datensätzen in einem Request schicken. Aber eben nur als Response auf ein normales GET oder POST. Dein Code macht das in SSE ("Server Send Events"), implementiert in der AsyncEventSource. Da geht das nicht, die Antwort muss auf einmal in den Speicher passen. Und Vorsicht: Wenn mehrere Webbrowser an der EventSource hängen, bleibt die Nachricht am ESP im RAM, solange bis sie an ALLE zugestellt wurde. Kombinier das damit, dass die Nachricht auch noch mehrfach im Speicher gehalten wird (JSON->String->c_str...) und schon ist der ganze RAM verbraucht. Nachdem du die Events sowieso nicht millisekunden-genau sendest, sondern per Timer in deiner loop(): Du brauchst die Events überhaupt nicht, und kannst genausogut den Refresh-Timer auf die Webseite verlagern, die sich dann regelmäßig neue Daten per GET holt. Also: - Alles mit "events" raus. - GET-Url-Handler zum Abrufen der aktuellen Daten rein - Möglichst auf Json-to-String verzichten. Der Webserver kann direkt JSON senden: https://github.com/me-no-dev/ESPAsyncWebServer#json-body-handling-with-arduinojson AsyncJson.h Nachtrag: Für noch mehr Messwerte, verzichte ganz auf das JSON-Objekt auf ESP-Seite. Verwende das "Response aus Callback"-Pattern, und bastel dir dein Json Zeilenweise zusammen. Die paar '[]', '{}', ',' kriegst du schon von Hand richtig zusammengesetzt, vor allem weil jede Datenzeile identisch aussieht.
:
Bearbeitet durch User
Hallo, ich empfinde deine Antwort als mega wertvoll. Sie gibt mir den Überblick, den ich noch nicht habe und ich habe verstanden, worauf es ankommt. Trotzdem ist es natürlich hart: jetzt habe ich ca. 2 Wochen meiner spärlichen Freizeit investiert und bin bei 90% Ergebnis angekommen (alles funktioniert super - außer dass ich nicht genug Daten senden kann). Und jetzt reiß ich alles ein und fange wieder von vorne an. Puuh. Hätte ich das mit dem 2x hintereinander schicken im WEB gefunden, wäre meine Frage nicht hier aufgetaucht...
Dein Problem ist, dass die Vorlage/Tutorial, die du hergenommen hast, einfach SCH*** ist. Der Autor hat keine Ahnung, weder von C++, noch von HTTP, noch von Javascript. Oder er war betrunken, auf Crack, LSD und Kokain, alles gleichzeitig. Also: in deinem INO-File: Alles mit der Event-Source muss raus. Arduino-JSON kann raus. und wir ändern den "/readings" endpunkt so, dass er auch gleich die Readings zurückgibt:
1 | // Request for the latest sensor readings
|
2 | server.on("/readings", HTTP_GET, [](AsyncWebServerRequest *request){ |
3 | Serial.println("Got Readings Request"); |
4 | request->send(request->beginChunkedResponse("application/json",[zeile=0](uint8_t *buffer, size_t maxLen, size_t index) mutable -> size_t { |
5 | if (maxLen < 50) return RESPONSE_TRY_AGAIN; // Wenn nicht genug platz im Buffer ist, warten wir lieber nochmal. |
6 | String json_part; |
7 | if (zeile==0) json_part += '['; // Begin Json Array |
8 | if (zeile<buffer_temp.size()) { // Nur Daten senden, die auch vorhanden sind |
9 | json_part += "{\"time1\":"; |
10 | json_part += buffer_time[zeile]; |
11 | json_part += ",\"temp1\":"; |
12 | json_part += buffer_temp[zeile]; |
13 | json_part += "}"; |
14 | zeile++; |
15 | if (zeile==buffer_temp.size()) { |
16 | json_part += ']'; // Array zu |
17 | } else { |
18 | json_part += ",\n"; // Neue Zeile |
19 | }
|
20 | }
|
21 | json_part.getBytes(buffer, maxLen); |
22 | return json_part.length(); // Ist "0", wenn wir am Ende des CircularBuffers sind, damit beenden wir den Request |
23 | }));
|
24 | });
|
Fertig. ein "GET /readings" gibt jetzt sofort den Inhalt deiner CircuarBuffers zurück, und auch nur den Teil der Buffer, der bereits geschrieben ist. Und wenn mehrere GETs gleichzeitig laufen, kommen die sich nicht in die Quere, jeder hat seine eigene "zeile"-Variable. Dann: Das script.js anpassen: Dort alles mit Event-Source raus. Den ganzen 1990er-Style "XMLHTTPRequest"-Teil ersetzen wir durch:
1 | // Function to get current readings on the webpage
|
2 | async function getReadings(){ |
3 | console.log("Getting Readings"); |
4 | plotTemperature(await (await fetch("/readings")).json()); |
5 | }
|
6 | |
7 | setInterval(getReadings,5000); |
in der "plotTemperatur"-Funktion muss die "for"-Loop angepasst werden, weil jetzt nicht immer exakt 25 Zeilen zurückkommen:
1 | function plotTemperature(jsonValue) { |
2 | // Lese Temperatur
|
3 | for (const zeile of jsonValue) { |
4 | var y = Number(zeile.temp1); |
5 | // Lese Zeit
|
6 | var x = Number(zeile.time1)*1000; |
7 | console.log(x,y); |
8 | chartT.series[0].addPoint([x, y], true, false, false); |
9 | }
|
10 | }
|
Fertig. Was evtl. noch fehlt: Der Code scheint immer nur Punkte zum Chart hinzuzufügen, nie welche zu löschen.
PS: Im JSON-Erzeuger-Code oben ist ein kleiner Bug. Kann man ignorieren oder leicht beheben. Gibt Bonuspunkte, wenn du ihn findest. :) PS2: Wegen der asynchronen Abarbeitung kann es vorkommen, dass in die CircularBuffer neue Werte kommen, während die Antwort noch rausgeschrieben wird. Stört vermutlich nicht groß, ansonsten müsste man die Temperatur-Abfrage verzögern, bis alle Requests abgearbeitet sind.
:
Bearbeitet durch User
Du bist der Hammer 😂 Hocke mich nächste Woche hin. Vielen Dank 🙏
Stephan D. schrieb: > Bei 25 Datensätzen gibt es eine Anzeige, wo "Stream" steht und die Daten > zu sehen sind. Bei 30 Datensätzen gibt es diesen Eintrag nicht. Dein Wireshark-Screenshot zeigt allerdings eine Grafikdatei im PNG-Format. Erzeugst Du diese Datei auf dem Server? Dann könnte auch das natürlich ein Showstopper sein, wenn er hard- oder softwareseitige Limits überschreitet. Wenn Du schicke Diagramme zeichnen möchtest, würde ich Dir deswegen eher zu einer Java- / ECMAScript-Bibliothek wie Flot, jqPlot oder Ähnliche raten. Die kann Dein Browser aus einem Component Data Network (CDN) im Netz laden, so daß sie nicht vom ESP vorgehalten und ausgeliefert werden müssen, und Du kannst damit schicke Diagramme im Browser erzeugen, während sich Dein ESP nurmehr um das Ausliefern Deiner Daten dazu kümmern muß. Und der Clou: mit einigen der Java- / ECMAScript-Bibliotheken lassen sich sogar interaktive Diagramme erzeugen. Übrigens kannst Du womöglich auch beim JSON-Format sparen:
1 | [ |
2 | { |
3 | "timestamp": 123456.7, |
4 | "value": 1.2 |
5 | }, |
6 | { |
7 | "timestamp": 123456.8, |
8 | "value": 3.4 |
9 | }, |
10 | { |
11 | "timestamp": 123456.9, |
12 | "value": 5.6 |
13 | }, |
14 | ] |
enthält zwar dieselben Daten, hat aber einen viel kleineren Footprint als eine andere Repräsentation, wie sie viele Plotting-Frameworks erwarten:
1 | { |
2 | "timestamp": [123456.7, 123456.8, 123456.9], |
3 | "value": [1.2, 3.4, 5.6] |
4 | } |
Tipp: die Arrays mit den Werten müssen natürlich gleich lang sein... ;-) HTH, YMMV.
mal mit 1000 Datensätzen getestet, sh. Screenshot. Läuft. Braucht dann aber (ESP8266) so ~4 Sekunden & 35kB pro Request. An der Stelle ließe sich noch einiges Optimieren (z.B: pro Aufruf des Content-Callback-Lambdas mehere Zeilen schreiben, bis der verfügbare Buffer-Platz nicht mehr ausreicht), aber dann wird die Funktion schnell unübersichtlich, als Lern-Beispiel nicht so gut. Ein T. schrieb: > Dein Wireshark-Screenshot zeigt allerdings eine Grafikdatei im > PNG-Format. Da ist ein favicon.png dabei, statisch im Littlefs abgelegt. Ein T. schrieb: > Wenn Du schicke Diagramme zeichnen möchtest, würde ich Dir deswegen eher > zu einer Java- / ECMAScript-Bibliothek wie Flot, jqPlot oder Ähnliche > raten. Die kann Dein Browser aus einem Component Data Network (CDN) im > Netz laden, so daß sie nicht vom ESP vorgehalten und ausgeliefert werden > müssen, und Du kannst damit schicke Diagramme im Browser erzeugen, > während sich Dein ESP nurmehr um das Ausliefern Deiner Daten dazu > kümmern muß. er hat ja schon Highcharts drinnen, geladen von deren Webseite/CDN. Ein T. schrieb: > Übrigens kannst Du womöglich auch beim JSON-Format sparen: Da geht noch mehr, High-Charts kann man direkt eine CSV-Url angeben, dann läd das selber die Daten von dort. Als CSV sind die Daten nochmal sparsamer und einfacher zu Übertragen...
:
Bearbeitet durch User
Εrnst B. schrieb: > Ein T. schrieb: >> Dein Wireshark-Screenshot zeigt allerdings eine Grafikdatei im >> PNG-Format. > > Da ist ein favicon.png dabei, statisch im Littlefs abgelegt. Mag sein, aber man sieht halt nicht den eigentlichen Inhalt, das JSON. > er hat ja schon Highcharts drinnen, geladen von deren Webseite/CDN. Ah... jo, das geht auch. > Ein T. schrieb: >> Übrigens kannst Du womöglich auch beim JSON-Format sparen: > > Da geht noch mehr, High-Charts kann man direkt eine CSV-Url angeben, > dann läd das selber die Daten von dort. Als CSV sind die Daten nochmal > sparsamer und einfacher zu Übertragen... Mit Header tut sich das bei meinem zweiten Formatvorschlag ziemlich wenig, methinks, und JSON könnte vermutlich schneller geparst werden. Müßte man mal ausprobieren, würde ich sagen.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.