Refactoring

Das ist natürlich ein interessanter Punkt…
Wenn das daran liegt, bin ich nur etwas überrascht, dass man so gar nix davon fehlermäßig sieht.

Ich hab mal die 16k auf 4k reduziert und das Problem ist damit weg. D.h. er findet keine 16k „am Stück“. Aber ich vermute mal die 16k werden wohl für volle SD-Karten benötigt. Interessant wäre auch zu wissen wie die speicherintensiven Libs mit dem RAM umgehen. Wird jedes mal neu allokiert und wieder freigegeben oder wird allokierter Speicher wieder verwendet?

@MusikHexe Es hat nur ein Include gefehlt. Habe ich gefixt und hochgeladen.

Klar, mach ich gern. Sag Bescheid wenn ich testen soll. Kurze PN reicht.
Schöne Grüße
tueddy

P.S.: Gute Arbeit beim Refactoring bisher!

Ok dann hat es vorhin bei mir wahrscheinlich funktioniert, da aufgrund des PSRAMs der Heap weniger voll ist.
Tja, dann bleibt ja eigentlich nix Anderes, als den Heap initial für JSON zu allokieren. Oder halt einfach z.B. nur 8k zu allokieren. Initial allokiert kriegt man es möglicherweise über diesen Weg: https://learn.upesy.com/en/programmation/psram.html. Also da könnte man eine Weiche bauen, wie ich sie ja aktuell schon habe: Gibt es PSRAM, so wird er verwendet. Wenn nicht, dann wird halt Heap verwendet. Nur im Falle von Heap müsste man vermutlich hier einen Pointer zurückgeben zu einer Speicheradresse, die man vorher schon allokiert hat.
Irgendwie so…

Das mit dem Heap ist vielleicht auch ein Problem, das man beim Webupload hat: Probleme beim Upload per Web/FTP
Ich habe das vorhin mal beobachtet. Ich habe 2mal einen Upload gemacht. Der Lolin D32 pro hat ja eine SD-Karte integriert, die jedoch per SPI angebunden ist. Beim ersten Upload war die Datenrate durchweg viel zu hoch für SPI. Ergebnis: File da, aber 0 Bytes groß. Dann gleicher Upload nochmal. Der Durchsatz war dann ne Weile so, wie ich ihn erwartet hätte und plötzlich ist er massiv angestiegen. Ergebnis: Von den 103 MB waren nur 2,8 MB geschrieben.
Fehlermeldung sieht man auch hier keine.

@biologist

Danke, das Bauen klappt nun direkt. Löst aber leider das Problem nicht, dass man den ESPuino nach dem Eintreten in den deepsleep nicht mehr wecken kann (auch nicht per Knopf) und in der Konsole IRQ PIN: 0 statt 39 ausgegeben wird. Habt ihr da eine Idee?

Den PN5180 hast du dauerhaft mit Spannung versorgt? Weil ich arbeite hier ja üblicherweise mit einer 2stufigen Mosfet-Schaltung. Die darf man auf den PN5180 natürlich nicht anwenden, wenn man mit darüber aufwecken möchte.

Ansonsten habe ich @tueddy schon eine PM geschrieben. Er hat diesen Part damals in ESPuino implementiert.

Hi , habe eben mal das refactoring von @tuniii eingespielt , da ich OTA testen wollte ( sehr neugierig ) und auch Bluetooth dabei sein sollte mußte ich die partitions.csv Wrover-conform anpassen . Trotzt anfänglicher Orientierungslosigkeit habe ich das geschafft . Webupdate ging auf Anhieb , ist schon geil .

Ich war auch dabei mir das anzusehen, aber bin dann doch wieder abgebogen, hehe.
Die Partitionen sind ja nur 2MB groß - passt dann für Bluetooth nicht. Gut, hatte @tuniii ja schon angekündigt. Über BT haben sich jedoch viele Leute gefreut, insofern ist das für mich keine Lösung. D.h. anbieten kann ich das eigentlich nur für Leute, die einen WROVER verwenden (das hatten wir ja auch mal diskutiert). Wobei es aber auch hier unterschiedlich große Flashs beim WROVER gibt… das ist gar nicht so einfach.

Was ja schon länger auf meiner Agenda steht (und damit indirekt auch zu tun hat), ist eine Anpassung des Partitionslayout generell. Also spiffs kann weg, dafür wäre mehr NVS gut, damit man mehr Karten anlernen kann (irgendwer hier ist ja mal in den „Begrenzer“ gelaufen). Ich weiß nur nicht, ob ich das hinkriege, ohne das bestehende NVS zu löschen. Also das ist jetzt an für sich kein riesen Problem, da man das backup.txt ja einfach wieder einspielen kann. Aber ich befürchte, dass ich damit viele Leute kalt erwische, weil sie sowas bei einem x-beliebigen Update einfach nicht erwarten. Wobei das Problem natürlich nicht kleiner wird, da die Anzhal der ESPuino-User (hoffentlich) eher größer als kleiner wird.

Aber auch vor dem Hintergrund, dass gerade eh noch andere Bugs am Start sind, die behoben werden müssen, aber ich weitere Änderungen dann doch erstmal unterlassen.

Also für mich kann ich das Thema OTA recht einfach lösen, aber ich weiß nicht, wie am besten im Sinne der ganzen Nutzer :woman_shrugging: Vielleicht hat dazu ja jmd. eine Meinung oder einen Vorschlag :slight_smile:

Man könnte für die Boards, welche 4MB oder 16MB Flash haben, separate Paritionierungs-Dateien erstellen und jeweils eine eigene PIO Konfig anbieten. Viele Sache in der platformio.ini sind aktuell auch redundant. Das könnte man etwas entschlacken. Bei der 4MB Variante könnte man dann auch einfach Web-Update nicht anbieten. Somit wäre dann 16MB Flash die Voraussetzung für Web-Update und man hat keine Abhängigkeit zu irgendwelchen Software-Schaltern. Kam jetzt einfach mal aus der Hüfte geschossen.

Aktuell gibt es beim Init der ganzen Module noch mehrere Heap Corruptions. Ich kann aber aktuell noch nicht sagen, ob die neu sind oder vor dem Refactoring auch schon da waren. Ich melde mich sobald ich das behoben habe. Größte Baustelle ist für mich erst mal der RAM. Eigentlich ist genug da, aber er kann nicht optimal genutzt werden.

@biologist
Ich komme leider jetzt erst zum Antworten, gestern war mein Hirn schon schlafen gegangen.

Um ehrlich zus ein habe ich keine Ahnung. Wir haben einfach deine Platine verwendet und bestückt nach der Anleitung. Mit dem Master hat das aufwecken durch den Reader und deepsleep und aufwachen durch Drehencoder auch geklappt
Die einzigen Abweichungen im Aufbau zu deiner Anleitung sind:

  • infrarot Fernbedienung statt Kopfhörerplatine
  • nur 2 Knöpfe und Drehencoder

Die Heap Corruption passiert in der Funktion x_strndup(). Wenn die Funktion nicht mehr verwendet wird, dann ist der Fehler weg. Weiter analysieren werde ich das jetzt nicht mehr.

Ich habe mich jetzt für die String-Klasse entschieden, da es sich nicht lohnt wegen ein paar Bytes die Verzweigung zwischen RAM und PSRAM zu machen.

Nachdem die Heap Corruption nun beseitigt ist, funktioniert die Funktion heap_caps_get_largest_free_block() nun ohne Reset.

Nach dem Start:

Free heap: 126720
Largest free heap block: 70228
Free PSRAM: 0

Nach dem Aufrufen der Web-Seite:

Free heap: 127140
Largest free heap block: 53548
Free PSRAM: 0

Nach dem Starten eines Web-Streams:

Free heap: 83172
Largest free heap block: 23900
Free PSRAM: 0

Danach funktioniert die Anzeige des Explorers nur noch sporadisch.
Neben den 16k für den Json Buffer wird dann zusätzlich noch der String beim Serialisieren auf dem Heap in der Funktion explorerHandleListRequest() angelegt. Da wirds dann eng.

Danke für deine Analyse. Nur kurz zur Einordnung: Bevor ich auf die Idee kam, den FTP-Speicher auf dem Heap zu allokieren (damals war der FTP auch immer aktiv und wurde nicht erst dynamisch zugeschaltet), bin ich mehrfach gegen die Wand gefahren, weil der statische RAM voll war. In der Folge habe ich dann an allen Stellen Speicher zusammengekratzt und dann auch so kleine Sachen in den Heap gelegt.

Zum Thema Heap-Corruption habe ich noch was gefunden - das schreibe ich hier mehr als Gedankenstützt für mich rein: Heap Memory Debugging - ESP32 - — ESP-IDF Programming Guide latest documentation
Das Thema werde ich mir auf jeden Fall nochmal anschauen. strndup() verwende ich bei MQTT auf jeden Fall schon ganz lange (auch in einem anderen Projekt).

Zu String habe ich irgendwie in Erinnerung, dass dessen exzessive Nutzung irgendwie kritisch sein kann. Warum weiß ich aber nicht mehr. War’s vielleicht auch eine Heap-Fragmentierung?!

Jut, die Frage ist nun, was man mit den 16k macht. Wenn ich deiner Analyse folge, dann sind 16k nicht haltbar und man müsste eher vielleicht auf 8k gehen. Oder?

Ich werde mal zwei Ideen dazu verfolgen:

  • Benötigen wir wirklich JSON für die Übertragung? Ja, es ist bequem, aber ich behaupte, dass es mit einem einfachen parsen nach Trennzeichen auch getan sein könnte.
  • Ich übertrage nicht per GET ein kompletten Datensatz sondern immer nur einen Dateinamen per Websocket und „baue“ das Array/JSON im Javascript (im Browser) zusammen // oder man überträgt jeden einzelnen Eintrag direkt in JSTree. (Wobei ichnicht weiß, wie das dann mit der Formatierung ist). Dann benötigt man keine so großen Buffer.

Was meint ihr? Ein Ansatz?

@Harry Hattest Du damals eigentlich den Browser komplett neu implementiert oder die Funktionen übernommen?

Mein Ansatz war damals einfach: Machst es state-of-the-art und das ist heutzutage ja üblicherweise JSON :slight_smile: Aber beim Arbeiten mit ArduinoJSON dann auch gemerkt, dass es schon auch schon irgendwie sperrig ist. Weil man will auf nem uC jetzt auch nicht mit Speicher rumschlampen, aber auf der anderen Seite weiß man vor dem Allokieren ja gar nicht, wieviel Speicher man tatsächlich benötigt.

Variante speichereffizient / langsam:
Ich parse die Files der SD-Karte (was ja nicht sooo schnell geht), schreibe den Inhalt in einen Buffer. Wenn ich damit fertig bin zähle ich durch, rechne mir den Speicherbedarf aus, verwerfe den Buffer, allokiere das JSON-Objekt passend, parse erneut und schreibe es in das JSON-Objekt.
Vorteil: Braucht weniger Speicher
Nachteil: Unperformat, weil ich parse SD doppelt (und das will man wirklich nicht!)

Variante speichereffizient / schnell:
Wie zuvor, jedoch parse ich kein zweites Mal, sondern transferiere die Daten aus dem Buffer direkt in das JSON-Objekt.
Vorteil: Performant.
Nachteil: Braucht beim Kopieren viel Speicher.

Variante aktuell:
Ich allokiere pauschal und zähle vorher nicht durch
Vorteile: Performant, relativ einfach zu implementieren
Nachteil: Es wird mehr Speicher allokiert, als üblicherweise gebraucht wird. Und offenbar ist es auch problematisch, diesen Heap zu allokieren.

Alle Varianten haben gemein, dass sie nicht skalieren. Weil iwann kriegst den Heap nicht mehr allokiert oder du hast mehr Daten, als du für den Heap vorgesehen hast.

Insofern: Ja, ich glaube es wäre sehr cool, wenn man das fragmentiert übertragen könnte und die GUI daraus was zusammenbauen würde. Im einfachsten Falle benötigt es dafür ein bisschen JSON-Logik im Server-Code, so dass man valides JSON bereits rausschickt, welches die GUI „nur noch“ wieder konkattenieren muss, so dass man es dann in JSTree stecken kann.

Die Idee hatte ich in einem anderen Projekt gesehen. Dort war aber der Browser auch nur für eine statische Ansicht ausgelegt. Ich habe das Ganze dann mit dem jstree Ansatz von Mario Lukas, dessen Browser ja davor benutzt wurde und mit eigenen Code ergänzt und rausgekommen ist der aktuelle Browser. Er funktioniert aber hat sicherlich noch verbesserungspotential.

@Christian: dein zweiter Ansatz das Ganze in mehrere Teile zu zerstückeln habe ich auch schon länger im Kopf. War mir aber unsicher wie gut das mit dem Websocket funktioniert, da ich da noch keine Erfahrung habe.

Vorteile:

  • Das würde Speicher sparen.
  • Der Browser würde reaktiver wirken, da man den Baum schon aufbauen kann, sobald die ersten Daten ankommen.
  • Es ist dann auch egal wieviele Dateien im Ordner sind, die Anzeige wird dann nicht mehr abgeschnitten sobald der reservierte Speicher voll ist.
  • Man kommt auch von der HTTP Request/Response Architektur weg. Der AsyncWebserver neigt beim Auflisten/Löschen vieler Daten zu Instabilitäten, da dort ein Watchdog-Trigger anschlägt, wenn die Callback Funktion zu lange braucht.

Man merkt ich wäre von einer solchen Lösung begeistert :smiley:

2 „Gefällt mir“

Ich auch nicht und bin auch unsicher wie zuverlässig das klappt. Ich würde mich mal an einem „poc“ versuchen. Du würdest dann aber auch eher direkt ins JSTree übertragen?

Ja ich würde die Elemente direkt in den Baum hängen. Das sollte auch gut klappen

Hi @tuniii
Ich verwende seit gestern deinen Code und spiele mit OTA . Klappt hervorragend bis auf eine Sache die mir aufgefallen ist . Plattformio legt die firmware.bin im versteckten Ordner .pio ab . Damit findet es mein Browser (Safari) nicht auch wenn " Dateien anzeigen" aktiviert ist und ich kopiere die Datei immer in einen anderen Ordner. Unter Windows geht es . Ich weiß nicht ob das eine Einstellung in Safari ist und vielleicht kann man ja auch in Platformio den Ausgabepfad ändern . Ich habe dazu nichts gefunden . Hast du eine Idee?
VG