RFC: Migration zu neuer Weboberfläche

Ach, stimmt es wird ja noch ein spezieller JQuery Slder verwendet.

So ich habe die Arbeiten soweit abgeschlossen und reiche das mal offiziell ein!
Ich habe versucht die Änderungen minimal zu halten, trotzdem ist da was zusammengekommen:

Wenn Ihr über die geänderten Dateien schaut sollten die Änderungen jetzt besser im DIFF zu sehen sein.

Der HTTP-GET Endpunkt „/settings“ liefert alle Einstellungen zurück, man kann aber auch einzelne Bereiche mit dem Parameter „section“ zurückbekommen, Bsp. „/settings?section=mqtt“.

Zusätzlicher Bugfixes:

  • Einstellungen Allgemein, der „Reset“ Schalter funktionierte nicht. Jetzt kann man auf Werkseinstellungen zurücksetzen und nach Absenden speichern
  • Die Feature Compilerflags wurden in der UI nicht beachtet: Kompiliert man z.B. ohne NEOPIXEL_ENABLE oder MEASURE_BATTERY_VOLTAGE werden die entsprechenden Controls auch nicht angezeigt.

Wenn wir diesen Schritt durchhaben könnten wir die Backend-API ein wenig aufräumen und vielleicht auch eine Schnittstelle mit z.B. Swagger oder OpenAPI festlegen und dokumentieren. Jetzt ist das JSON für die Einstellungen gegliedert und sieht so aus:

{
  "current": {
    "volume": 3,
    "rfidTagId": "",
    "hostname": "Espuino"
  },
  "general": {
    "iVol": 5,
    "mVolSpeaker": 21,
    "mVolHeadphone": 18,
    "iTime": 10
  },
  "neopixel": {
    "iBright": 16,
    "nBright": 2
  },
  "battery": {
    "vWarning": 3.400000095,
    "vIndLow": 3,
    "vIndHi": 4.199999809,
    "vInt": 10
  },
  "defaults": {
    "iVol": 5,
    "mVolSpeaker": 21,
    "mVolHeadphone": 18,
    "iBright": 16,
    "nBright": 2,
    "iTime": 10,
    "vWarning": 3.400000095,
    "vIndLow": 3,
    "vIndHi": 4.199999809,
    "vInt": 10
  },
  "ftp": {
    "ftpUser": "esp32",
    "ftpPwd": "esp32",
    "maxUserLength": 9,
    "maxPwdLength": 9
  },
  "bluetooth": {
    "enabled": true,
    "deviceName": "",
    "pinCode": ""
  }
}

Gerne Kommentare & Anmerkungen!

5 „Gefällt mir“

ich glaube das „nicht“ ist da zu viel :wink:

Aber sonst krasse Arbeit :muscle:t6: :+1:t6:

2 „Gefällt mir“

Sehr cool, @tueddy! Danke für deine ganze Arbeit!
Da muss ich mich wohl mal ranmachen, das zu testen.

3 „Gefällt mir“

Um noch einmal auf „RFC“ zurückzukommen.

  • JSON ist ja nur der Payload, aber grundsätzlich reden wir hier über REST, oder? Aktuell gibt es ja auch noch die Websocket-Verbindung. Ich finde die für bestimmte Dinge auch sehr praktisch. Idee / Verständnis-Frage: Die Rest API wird für die Datenübertragung genutzt (Setting - PUT - GET / File-Tree - GET). Websocket bleibt, wird aber nur für „notification“ verwendet. Damit hätte der ESP die Möglichkeit bestimtm Events zu triggern. Man hätte eine kläre Trennung zwischen Daten und Trigger. GUI können damit zyklisch pollen oder auf ein Event warten (wenn sie websockets unterstützen):
    • Beispiel
      • Neue Karte erkannt
      • Notification per websocket „refresh general“
      • GUI fordert per REST-API die Setting „general“ an, wo u.a. die neue Karten-ID übertragen wird.
      • Das könnte so auch für „Neues Cover“, „Neuer Titel“, „WIFI Scan beendet“ usw. verwendet werden
  • Das war ja damals der Grund für meinen Ansatz das alles per Websocket zu übertragen und dabei mehrere kleine JSONS zu bilden. Primäre ging es damals um den Filetree.
    • Wenn man bei REST bleiben möchte, könnte man das beim Filetree auch umsetzen:
      • GUI fordert den Filetree an
      • Backend erzeugt ein JSON mit max. 20 Einträgen.
      • Sollten mehr als 20 Einträge vorhanden sein, gibt es im JSON ein Feld „moreData: true
      • GUI fordert dann erneut den Filetree an, mit dem Hinweis die ersten 20 Einträge zu überspringen
      • Der Filetree würde damit in kleineren Blöcken übertragen
      • Die Anzahl (20) kann dann an den zur Verfügung stehenden Speicher angepasst werden.

Die Spotify API macht das u.a. so…

Hier würde ich es gut finden, wenn man einfach immer alle Einstellungen überträgt. Egal ob diese genutzt werden oder nicht und dann z.B. unter general eine Feature-Liste erzeugt. Mit dieser Liste werden dann Bereiche in der GUI ausgeblendet.

@Christian Danke für Deine Ideen, ich stimme dir zu 90% zu. Ich glaube, wir wollen alle so ziemlich das Gleiche erreichen. Aber bitte in kleinen Schritten, denn alles andere ist schon gescheitert.

Mein Pull-Request entfernt zunächst nur den Template-Prozessor und nicht mehr.

Ich schlage das Laden der Einstellungen per Websocket vor & habe mich dazu entschieden, weil das aus meiner Sicht schneller ist (Verbindung bereits geöffnet). Man kann das aber auch mit einer Zeile auf GET hier ändern, habe damit kein Problem. Der JSON Payload ist dann eh gleich, bin da total wertfrei.

Filetree & Paginierung sind hier komplett außen vor (Entwicklung in kleinen Schritten). Außerdem spielt das eigentlich keine Rolle mehr, da das Dateisystem mittlerweile um den Faktor 20 beschleunigt wurde und schnell genug lädt, Pagiinierung nicht mehr notwendig.

Kurzum: Eine schöne REST-API + Websocket Echtzeitbenachrichtigung ist erst der übernächste Schritt . Ich möchte das nur vorbereiten, dann können die Web&Backend-Experten ran und eine schöne API entwerfen :wink:

Gern noch mehr Review da das schon einige Zeilen sind…

1 „Gefällt mir“

Das ist schon klar. Ich hatte diese Diskussion aber auch als „Brainstorming“ verstanden um den Rahmen, das Zielbild zu diskutieren - bevor man mit Änderungen anfängt (oder parallel dazu).
Wenn dem so nicht ist, bin ich einfach still :wink:

Ich hatte die Richtung ESP->Client gemeint. Aber ja, wie es dann übertragen wird fast wurscht. Nur warum reden wir über unterschiedliche „Endpoints“ wenn dann doch alles per Websockt gemacht wird.
Und ja, auch das sollten kleine Schritte sein - aber am Ende machst Du diese kleinen Schritte zweimal, wenn man das noch einmal ändern möchte.

Hier geht es um Speicher und nicht um Geschwindigkeit. Aber ja, auch das ist unabhängig und kann separat betrachtet werden.

Noch einmal, das Vorgehen halte ich für komplett richtig und es stimmt auch, dass ich damals zu viel gleichzeitig geändert habe.

@Christian Ah OK, Du bist schon einen Schritt weiter :wink: Die Diskussion dazu macht Sinn & Brainstorming auch!

Gibt es noch konkrete Vorschläge für den laufenden PR (Template-processor)? Natürlich wollen wir keine Schritte zweimal machen.
Bsp. zusätzliches Feature-Objekt: Gibt es einen Vorteil? Das würde den JSON Payload größer machen. Jetzt frage ich im JavaScript einfach ab ob es jeweilige Objekt gibt oder nicht

Hier oder im Github PR?

Geht Beides, für Code Verbesserungen würde ich direkt Anmerkungen in Github & für größere Sachen das Forum hier vorschlagen…

Ich kann gerade nicht testen, aber ich hätte zwei Ideen:

JSONToSettings

Könnte man nicht zur Verschlankung aus

uint8_t iVol = doc["general"]["iVol"].as<uint8_t>();
gPrefsSettings.putUInt("initVolume", iVol) 
if (gPrefsSettings.getUInt("initVolume", 0) != iVol ) {
				Log_Println("Failed to save general settings", LOGLEVEL_ERROR);
				return false;
			}

So etwas pro Setting machen

if (gPrefsSettings.putUInt("initVolume", doc["general"]["iVol"].as<uint8_t>()) == 0 {
				return false;
			}

Ich finde die Zuordnung der unterschiedlichen Bezeichnungen unübersichtlich.
In einem nächsten Schritt wäre es ohnehin gut, wenn man die Bezeichnungen anpassen würde.
Also nicht iVol = initVolume.

Und bei

static void settingsToJSON(JsonObject obj, String section) {
	if ((section == "") || (section == "current")) {
		// current values
		JsonObject curObj = obj.createNestedObject("current");
		curObj["volume"].set(AudioPlayer_GetCurrentVolume());
		curObj["rfidTagId"] = String(gCurrentRfidTagId);
		curObj["hostname"] = Wlan_GetHostname();
	}

die Benennung jeweils anpassen:

static void settingsToJSON(JsonObject obj, String section) {
	if ((section == "") || (section == "current")) {
		// current values
		JsonObject newObj = obj.createNestedObject("current");
		newObj["volume"].set(AudioPlayer_GetCurrentVolume());
		newObj["rfidTagId"] = String(gCurrentRfidTagId);
		newObj["hostname"] = Wlan_GetHostname();
	}

Gibt hier vermutlich kein richtig oder falsch. Ist mir einfach aufgefallen.

2 „Gefällt mir“

Ich würde sehr gerne drüber schauen, habe aber maximal am Wochenende mal Zeit. Wenn der PR noch ein paar Tage offen ist, schaue ich drüber. Die Beschreibung klingt aber sinnvoll!

Es spricht meiner Meinung nach auch nichts dagegen, zweigleisig zu fahren und einen Pfad später zu löschen (oder auch drin lassen). Aber das später.

Christians Feedback stimme ich mindestens da zu, dass mir „iVol“ auch nicht so gut gefällt, und sprechende Namen besser sind.

2 „Gefällt mir“

Kein Problem, ich lasse den PR noch ein wenig länger offen, sollen ja Alle zufrieden sein :wink:

Die Bennennung habe ich bewusst nicht geändert um den PR so klein wie möglich zu halten. Es geht ja hier nur um die Ersetzung des Template-Prozessors & nicht mehr.
„iVol“ bleibt zunächst unverändert und wird hier bereits verwendet.
Stimme aber auch zu - Das ist nicht elegant, ich hatte auch Probleme die einzelnen Einstellungen richtig zuzuordnen.
Aber evt. machen wir die Neubenennung im nächsten Schritt, evt. mit einer definierten API Beschreibung?

Habe Eure Anmerkungen/Wünsche umgesetzt und den PR aktualisiert:

@Christian die Verschlankung ist drin und spart einige Zeilen. Funktioniert aber nicht für leere Strings, daher habe ich es bei den Strings zunächst so belassen.

Die Umbenennung war jetzt auch schnell gemacht und kann natürlich noch geändert werden. Lesbarkeit vor Kürze, so sieht das JSON jetzt aus:

{
	"current": {
		"volume": 2,
		"rfidTagId": "086114072041"
	},
	"general": {
		"initVolume": 5,
		"maxVolumeSp": 21,
		"maxVolumeHp": 18,
		"sleepInactivity": 10
	},
	"wifi": {
		"hostname": "Espuino",
		"scanOnStart": true
	},
	"neopixel": {
		"initBrightness": 16,
		"nightBrightness": 2
	},
	"battery": {
		"warnLowVoltage": 3.400000095,
		"indicatorLow": 3,
		"indicatorHi": 4.199999809,
		"voltageCheckInterval": 10
	},
	"ftp": {
		"username": "esp321",
		"password": "mutti",
		"maxUserLength": 9,
		"maxPwdLength": 9
	},
	"mqtt": {
		"enable": false,
		"clientID": "ESP32-ESPuino",
		"server": "192.168.2.43",
		"port": 1883,
		"username": "mqtt-user",
		"password": "geheim",
		"maxUserLength": 15,
		"maxPwdLength": 15,
		"maxClientIdLength": 15,
		"maxServerLength": 31
	},
	"bluetooth": {
		"deviceName": "My POGS Wireless Headphone",
		"pinCode": "0000"
	}
}

ClientID oder ClientId? Kommentare gern hier.

2 „Gefällt mir“

Der PR ist ja schin länger offen und wurde durch die Review geschickt, Danke an @SZenglein !
Einige Kleinigkeiten habe ich noch korrigiert, z.B.wird jetzt auch die WLAN-Liste beim Laden der Webseite über das Websocket gefüllt, konsistent zum Laden der Einstellungen.

Habe Alles getestet soweit es geht & würde das die nächsten Tage in den DEV-Branch übernehmen…

2 „Gefällt mir“

Als letztes habe ich mir den „/info“ Endpunkt vorgeknöpft um die Trennung von Frontend/Backend weiter voranzutreiben.
Dazu liefert „/info“ jetzt nur noch die Werte als Application/JSON zurück und die Ausgabe/Formatierung wird im dann Frontend gemacht.
Damit verschiebt sich auch die Sprachumschaltung konsequent ins Frontend. Analog zum /settings Endpunkt kann man sich alle Informationen zurückgeben lassen oder nur einen einzelnen Bereich wie „battery“ oder „memory“ z.B. mit dem Aufruf http://espuino.local/info?section=battery

Wer möchte kann hier nochmal drüberschauen:

So sieht das /info-JSON dann aus:

{
   "software":{
      "version":"Software-revision: 20230804-1",
      "git":"Git-revision: 5f09f28-dirty",
      "arduino":"2.0.9",
      "idf":"v4.4.4"
   },
   "hardware":{
      "model":"ESP32-D0WD-V3",
      "revision":3,
      "freq":240
   },
   "memory":{
      "freeHeap":74144,
      "largestFreeBlock":3407860,
      "freePSRam":3478599
   },
   "wifi":{
      "ip":"192.168.1.55",
      "rssi":-67
   },
   "audio":{
      "playtimeTotal":35106000,
      "playtimeSinceStart":462000
   },
   "battery":{
      "currVoltage":3.938959122,
      "chargeLevel":77.03112793
   }
}

Das Infofenster bleibt nach der Änderung nahezu gleich:

2 „Gefällt mir“

Ist geplant dass über so einen Endpoint auch den Inhalt der backup.txt auszulesen um die im Frontend zu bearbeiten? Ich weiß, dass das schonmal irgendwo angesprochen wurde ich weiß nur nicht mehr in welchem Thread.

Es ist bereits möglich, Dateien von der SD-Karte zu laden und schreiben, daher kannst du das manuell ja schon. Ein Editor für die backup.txt direkt im Frontend ist wohl eher nicht geplant.

Ich überlegte das selbst hinzuzufügen. Die Frage zielte eher dahin ob eine Schnittstelle zum auslesen der RFIDs geplant ist. Dann müsste es nur im Frontend integriert werden.

Das war wohl dieser Thread:

Die Backup.txt kann man mit rechter Maustaste „Download“ oder diesem Befehl laden:
http://espuino.local/explorerdownload?path=/backup.txt

Die kann dann mit einem Texteditor bearbeitet und evt. wieder eingespielt werden.
@chrischan Zu welchem Zweck willst Du eine extra RFID-Bearbeitung in der Weboberfläche einbauen?
Weil das manuelle Triggern von Aktionen ist ja jetzt schon möglich.

1 „Gefällt mir“