Refactoring

Ich habe das jetzt gerade mal auf einem Lolin D32 pro getestet - also mit PSRAM.

Infio:
Free heap: 89620
Free PSRAM: 3570332

Also damit habe ich keine Probleme mit dem Dateibrowser, wenn ein Webstream läuft. Ich habe ja so ein bisschen den Logbuffer in Verdacht - 2k ist schon ein bisschen was.

Hallo,
wir haben jetzt auch noch einmal weiter probiert. Mit den neuen Commits konnten wir die Firmware bauen und aufspielen. Beim Ausprobieren stellten wir fest, dass das LPCD beim PN5180 nicht aktiviert ist. Das hatten wir tatsächlich übergangen. Nach einkommentieren von „#define PN5180_ENABLE_LPCD“ kam beim bauen die Fehlermeldung, dass „Port_Read“ in „RfidPn5180.cpp“ nicht deklariert sei. Daraufhin haben wir dort „Port.h“ eingebunden und konnten bauen und aufspielen. Allerdings ist es nun nicht möglich den ESPuino aus dem deepsleep zu holen, weder per Karte noch per Taste (bei uns Drehenkoder-Taste) und er geht meistens direkt in den deepsleep nach dem Flashen. Zum abspielen überzeugt man ihn maximal beim Reset mit direkt vor dem RFID-Reader liegenden Karte, aber falls der wieder in deepsleep geht kann man wieder nicht mehr aufwecken. Wir haben auch ausprobiert „Port.h“ und die beiden Aufrufe von „Port_Read“ (sind nur Konsolenausgaben) auszukommentieren und zu schauen ob das das Verhalten ändert, dies war aber nicht der Fall. Dabei ist aufgefallen (wenn die Ausgaben da sind) das für IRQ statt dem Pin 39 der Wert 0 in der Konsole ausgegeben wird (einmal wurde auch die RFID-Reader Firmwareversion als 255.255 ausgegeben, was nach Zugriffen auf falsche Werte aussieht, aber bei Folgeversuchen wurde die Richtige Version 4.1 ausgegeben, IRQ blieb aber bei 0). Habt ihr eine Idee woran das liegen kann?
Wenn „#define PN5180_ENABLE_LPCD“ wieder auskommentiert ist, dann funktioniert der Knopf zum wieder Aufwecken und nach dem Start wartet der ESPuino noch auf Aktivität bevor er nach längerer Inaktivität erst in deepsleep geht, also erwartetes Verhalten.

@MusikHexe Das ist tatsächlich ein Parameter, den ich seit dem Refactoring noch nicht getestet hatte. Kann den Bug bestätigen, schaue ich mir gleich an.

@tueddy Ich habe gerade PN5180 hier, der 4.1 geflasht hat und zudem an einem JST PH 2.0 hängt. Würdest du, wenn ich das gefixt habe, mal einen Blick drauf werfen? Du kennst den PN5180 auch am besten :+1:

Ich gehe mal davon aus, dass du den Logbuffer für Web meinst. Den kannst du verkleinern oder auch ganz rausnehmen. Ich vermute, dadurch wird das Problem nicht wirklich beseitigt. Grundsätzlich ist ja noch genug RAM verfügbar, jedoch kann es sein, dass dieser stark fragmentiert ist. Ich habe jetzt auch mal kurz geschaut und es werden ja jedes mal 16k auf dem Heap für die Datei-Liste allokiert:

DynamicJsonDocument jsonBuffer(16384);

16k RAM „am Stück“ auf dem Heap zu finden ist schon eine Herausforderung. Mir ist auch aufgefallen, dass der Heap sehr stark schwankt, bei mir teilweise ± 5k. Das bedeutet, dass der Heap sehr stark „genutzt“ wird und über die Zeit eine starke Fragmentierung des Heaps sehr wahrscheinlich wird.
Man könnte sich die Statistik für den Heap mal ausgeben, um zu sehen, wie stark der Heap fragmentiert ist. Interessant wäre, ob das Problem verschwindet, wenn man die 16k statisch allokiert.

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.