Refactoring

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

Wie eine Katze. Kaum isses woanders schöner, da isser weg :joy: Nein, alles gut.

Idee: Es gibt die Möglichkeit, in VSC python-Scripte einzubinden, die zu verschiedenen Zeitpunkten ausgeführt werden: Redirecting...
Hier könntest vermutlich ein POST-Script einfügen, welches nach dem Kompilieren aufgerufen wird. Und im Script verschiebst du dann das File.

@Christian: Zerstückelt zu Übertragen wäre optimal. Der Web-Server beherrscht „chunked response“.
Am besten man benutzt dann auch nur Stack. Letztendlich läuft das im Kontext eines Tasks der AsyncTCP Lib, welcher mit 16K Stack ausgestattet ist.

@compactflash: Wie @biologist schon geschrieben hat, wäre das grundsätzlich möglich. Du kannst aber auch einfach im Finder-Fenster, wenn Safari es geöffnet hat, Shift+Command+Punkt drücken. Damit aktivierst du die Anzeige der versteckten Dateien und Ordner.
Zudem könntest du auch das updateFirmware.py Skript nehmen, falls du öfters die Firmware updaten willst. Einfach in den ESPuino-Ordner und dann das Skript ausführen, z.B.:

python3 updateFirmware.py .pio/build/lolin32/firmware.bin

Zuvor noch über pip folgende Module installieren:

python3 -m pip install requests zeroconf

Voraussetzung: MDNS muss bei ESPuino kompiliert/aktiviert werden.
Das Skript findet dann selbst den ESPuino und bügelt die neue Firmware drauf. Ich benutze das Skript, um von der Couch aus zu entwickeln :grinning_face_with_smiling_eyes:.

1 „Gefällt mir“

Tausend Dank @tuniii , ich habe gefühlt Stunden nach der Safari-Funktion gegoogelt und nichts gefunden , der Skript klappt auch , alles ok . So macht es noch mehr Spaß .
VG

1 „Gefällt mir“

Das könnte daran liegen, dass der Wert ( 0 = Low = GND; 1 = High = 3.3V ) und nicht die Pin-Numer ausgegeben wird:

Ich hab gestern ein „poc“ zum Thema „zerstückelt“ übertragen umgesetzt. Nur „offline“ mit einem lokalen websocket-server auf dem Notebook aber es funktioniert ganz gut.
Das Frontend fordert per websocket einen Ordner an und das Backend überträgt jede Datei als websocket Nachricht. Hab eine „Start-“ und „Ende-“ Nachricht der Sequenz vorgesehen. Hab es jetzt mal komlett ohne JSON gemacht. Einfach nur als String und parse entsprechend.
Frage mich gerade, in welchem Fork ich das nun mal testweise implementiere…?

1 „Gefällt mir“

Cool.
Auf jeden Fall im Refactoring-Branch. Fork halt am besten meinen oder der von tuniii. Sag ich jetzt mal :rofl:

Das meinte ich damit. War mir nicht sicher, ob Du grundsätzlich in diese Richtung gehen möchtest :wink:

Am Besten bei @biologist, da ich in meinem Fork den Code für den Arduino Core 2.0.0-alpha1 anpasse, um den ESP32 S2 zu unterstützen. Das wird dann nicht mehr mit den älteren Cores kompatibel sein.