RFC: Migration zu neuer Weboberfläche

Hab’s gefixt (mit UChar) kann es aber mit Hardware erst am Wochenende testen.

1 „Gefällt mir“

Es fehlt ja noch ein Endpunkt zum Lesen/Schreiben der RFID-Zuweisungen. Bislang können Zuweisungen nur über das Socket mit „rfidAssign“ und „rfidMod“ gesetzt werden.

Dazu habe ich mal einen neuen HTTP-Endpunkt /rfid implementiert & möchte das erstmal zur Diskussion stellen:

  • GET ohne Parameter liefert alle Zuweisungen als JSON-Array zurück.
  • GET/rfid/12345678 oder /rfid?id=12345678 liefert die Zuweisung eines einzelnen Tags zurück
  • POST mit JSON-Daten speichert eine neue Zuweisung im NVS
  • DELETE löscht eine Zuweisung im NVS
  • Abspiel- & Modifikationskarten laufen über den gleichen Endpunkt und unterscheiden sich nur durch „modId“/„playMode“

Hier mal JSON-Beispiele aus meinem schon lauffähigen Code:

[
    {
        "id": "003108198106",
        "modId": 130
    },
    {
        "id": "064170251048",
        "fileOrUrl": "/Rabe Socke - Alles gefunden",
        "playMode": 2,
        "lastPlayPos": 195721,
        "trackLastPlayed": 6
    },
    {
        "id": "086114072041",
        "fileOrUrl": "/2 kleine Wölfe",
        "playMode": 4,
        "lastPlayPos": 217348,
        "trackLastPlayed": 2
    },
    {
        "id": "121240148153",
        "modId": 152
    }
]

Einzelnes Tag lesen mit http://espuino.local/rfid?id=064170251048 oder http://espuino.local/rfid/064170251048 :

{
    "id": "064170251048",
    "fileOrUrl": "/Rabe Socke - Alles gefunden",
    "playMode": 2,
    "lastPlayPos": 195721,
    "trackLastPlayed": 6
}

Konkret könnten wir den neuen Endpunkt zum Auflisten und evt. auch Bearbeiten/Löschen der Zuweisungen in der bestehenden Weboberfläche verwenden.
Was meint Ihr dazu?

5 „Gefällt mir“

Gefällt mir gut. Ich überlege nur, ob’s sinniger wäre, sowas wie playMode oder modId zu übersetzen beim GET. Also dass man da die Strings stehen hat und nicht die Integer. Wobei wenn man das macht, dann muss man es in die andere Richtung vermutlich auch akzeptieren.

playMode (1-12) bzw. modID (>=100) sindt klar definiert und lassen sich schlanker als Integer übertragen. Für die Darstellung im Frontend wird dann natürlich der (lokalisierte) Bezeichner angezeigt, z.B. „Hörbuch endlos“. Die Sprache wird ja im Frontend eingestellt.

2 „Gefällt mir“

Die /rfid Erweiterung habe mal zur Review eingereicht:

Schaut mal drüber, Frontend wird nachgereicht, könnte dann etwa so aussehem:

Etwas Feinarbeit ist noch notwendig…

2 „Gefällt mir“

cool das das (bald) über eine API möglich ist, gefällt mir.

Mal gucken vlt. baue ich mal eine Desktop App die das dann alles aus der Ferne ändern kann (und backup machen und…)

Für die Auflistung der RFID-Tags (/rfid GET) hatte ich zunächst die vorhandene Funktion Web_DumpNvsToSd() verwendet. Also zunächst die Tags in Datei „_backup.txt“ speichern und von dort einlesen & als JSON zurückgeben.
Das war schnell gemacht aber hat auch Nachteile: Der SD-Schreib-/Lesezugriff kann zu kurzem Audioruckeln führen und wenn man keine SD-Karte verwendet klappte die Auflistung nicht.

Habe den PR korrigiert: Die Tags werden jetzt direkt aus dem NVS ins JSON geschrieben . Dazu wirft die Funktion listNVSKeys() für jeden NVS-Key/Tag ein Callback-Event. Im jeweiligen Callback wird dann entweder in die Datei oder ins JSON-Array geschrieben.

3 „Gefällt mir“

Hast du dir Gedanken um die Speichergröße des JSON gemacht, wenn sehr viele RFID zugewiesen sind? Eventuell kann man die Eintrage streamen, statt erst alles in ein großes Array zu schreiben. AsyncWebServer bietet da glaube ich was in der Richtung.

Das mischen von nvs funktionen und preferences finde ich verwirrend, aber geht wohl nicht anders.

@SZenglein Du hast in beiden Punkten absolut recht:
Die Anzahl der gespeicherten Karten kann nicht genau vorhergesagt werden, es können auch über 100 sein. Wenn man von durchschnittlich 128 Bytes ausgeht, ist bei 80 Einträgen Schluss. Ein Streaming des JSON wäre schön. Das könnte in unserem Fall machbar sein, eventuell auch für die Verzeichnisauflistung. Wenn in einem Verzeichnis sehr viele Dateien / Ordner oder auch lange Dateinamen sind, könnte es auch hier zu einem Absturz kommen. Das habe ich zu dem Thema gefunden:

Wg. Mischung von NVS Funktion in web.cpp. Habe die vorhandene Funktion nur um ein Callback erweitert aber stimme zu das die dort eigentlich nicht reingehört. Verschieben nach z.B. nach… Tja wohin?

Ich meinte eher, dass für alles Preferences verwendet wird, aber man auf die low-level NVS lib zurückgreifen muss, weil man mit Preferences keine Keys auflisten kann. Das lässt sich aber (nach kurzer Recherche) nicht
einfach lösen.

Für das große JSON sollte man vermutlich so was hier nehmen: GitHub - me-no-dev/ESPAsyncWebServer: Async Web Server for ESP8266 and ESP32
Async Programmierung ist leider immer etwas umständlich.

Das Thema ist schon etwas älter aber ich habe jetzt eine Lösung für die abgeschnittenen Einträge bei /rfid gefunden:

Zunächst erzeuge ich eine Liste nur mit den RFID-Nummern. Die Details wie Dateipfad und Playmode werden dann mittels Chunked-Response ausgeliefert. Das verbraucht deutlich weniger Speicher als der 8KB Puffer zuvor und das ausgelieferte JSON ist immer vollständig. Damit werden alle RFID-Zuweisungen in der Weboberfläche angezeigt. Schaut doch mal drüber, gern auch @SZenglein :

2 „Gefällt mir“

Grundsätzlich gut, ich glaube aber an ein paar Stellen solltest du „vorsichtiger“ mit der bufferlänge sein. Kommt vielleicht daher der Code maxLen >> 1 oder woher hast du das?
Habe dazu ein bisschen auf Github kommentiert.

@SZenglein Schönen Dank für die Review :+1:

Ich habe den maximal beschreibbaren Chunk-Buffer mit dieser Zeile um die Hälfte reduziert:

maxLen = maxLen >> 1; // some sort of bug with actual size available, reduce the len

Chunked-Response habe ich ja schon für das Ausliefern des Coverbildes verwendet und hatte zunächst Stabilitätsprobleme mit dem Webserver. Nach Recherche in den Issues hatten das Problem mehrere Leute und als Lösung die Reduzierung des maximal nutzbaren Puffers empfohlen. Das genaue Issue finde ich gerade nicht (Es sind über 30 Bugreports dazu).

Der maximale Puffer liegt immer über 1200 Bytes, ein einzelner Tag-Eintrag kann nur maximal 512 Bytes lang sein. Also im schlimmsten Fall würde nur ein Chunk pro Eintrag gesendet. Ich baue dazu aber noch eine Sicherheitsabfrage ein. Kann ja auch mal sein das sich etwas am Webserver ändert.Ich schaue mir Deine anderen Anmerkungen auch nochmal an…

Getestet habe ich mit mehreren eingelesenen NVS Backup Dateien, mit 0, 35, 85 und einer künstlich erzeugten Liste mit 200 Einträgen.

4 „Gefällt mir“

Das ist gut, ich hatte dazu gerade keine Infos, aber wenn das garantiert ist, kann wenigstens der Fall, dass der Buffer zu kurz für einen Eintrag ist, nicht mehr auftreten.

1 „Gefällt mir“

Hallo tueddy,
Wie besprochen, anbei ein kurzes Code-Schnipsel wie ein REST-Angelehnter Endpoint aussehen könnte.

/rfid ← gibt ein Array mit den Tag-IDs zurück
/rfid&id=xxxx ← gibt die gesamte Information von dem Tag zurück

bool dumpToJson(const char *key, void *data) {
	JsonArray *arr = static_cast<JsonArray *>(data);
	arr->add(key);
	return true;
}

static void handleGetRFIDRequest(AsyncWebServerRequest *request) {
	// get the parameter of the request
	AsyncWebParameter *idParam = request->getParam("id");
	const bool idFound = idParam;

	if (idFound) {
		// try to get the ID from the NVS keystore
		const String jsonTag = tagIdToJsonStr(idParam->value().c_str());
		if (!jsonTag.isEmpty()) {
			// send the single Tag
			request->beginResponse(200, "application/json", jsonTag);
			return;
		}
	}

	// id parameter was not found or the id was not found in the nvs, send everything
	AsyncJsonResponse *response = new AsyncJsonResponse(true);
	JsonArray array = response->getRoot();
	listNVSKeys("rfidTags", &array, dumpToJson);
	response->setLength();
	request->send(response);
}

Es ist nicht getestet, soll nur einen groben Überblick geben, wie das /rfid Endpoint alternativ aussehen könnte.

2 „Gefällt mir“

Auch eine gute Idee, ich befürchte aber dutzende calls für die gesamte Liste sind am Ende teurer als ein einzelner großer, besonders mit chunked response.

Mehr RESTful wäre ein path-parameter. DELETE /rfid/{id} ist auch bereits möglich, genau so sollte/könnte das für GET und PUT auch gemacht werden.

2 „Gefällt mir“

Danke für Eure Verbesserungsvorschläge! Habe jetzt beide Möglichkeiten vorgesehen:

Für das bestehende Frontend ein Aufruf /rfid/details um die ID-Liste inklusive Details zu bekommen.
Es besteht aber auch die Möglichkeit mit /rfid nur die Tag-ID Namen zu bekommen und dann einzelne Details mit /rfid?id=<tagid> zu laden.

3 „Gefällt mir“

Ich persönlich finde es genau anders herum viel logischer:

/rfids für die Liste ohne ids
/rfids?details für die Liste mit Details
/rfids/{tagid} für ein tag
DELETE /rfids/{tagid}

Das ist dann auch konsistent mit dem bestehenden DELETE call.

Ich würde es auf jeden Fall versuchen konsistent zu halten, also im Zweifel den bestehenden DELETE auch zu /rfids?tag=tagid ändern.
Für alle anderen Ressourcen gilt eigentlich das gleiche, z.B. die WLan-Liste.

Aber man muss ja auch nicht REST machen :smiley:

Nicht das wir uns falsch verstehen, ich habe jetzt nur die GET Methode angepasst:

/rfid liefert nur die Tag-Namen zurück:

[
    "146017149028",
    "118065017041",
    "095211227254",
...
]

/rfid/details zusätzlich die Details with Dateiname, Spielmodus usw.:

[
    {
        "id": "146017149028",
        "fileOrUrl": "/Der Grüffelo",
        "playMode": 4,
        "lastPlayPos": 1944473,
        "trackLastPlayed": 2
    },
    {
        "id": "118065017041",
        "fileOrUrl": "/janosch-oh-wie-schoen-ist-panama/janosch-oh-wie-schoen-ist-panama-gesamt-data.mp3",
        "playMode": 4,
        "lastPlayPos": 23277357,
        "trackLastPlayed": 0
    },
    {
        "id": "095211227254",
        "modId": 140
    },
    {
        "id": "021221187040",
        "fileOrUrl": "/Paw Patrol",
        "playMode": 4,
        "lastPlayPos": 89804,
        "trackLastPlayed": 1
    }
..
]

/rfid/146017149028 oder /rfid?id=146017149028 serviert Details für ein einzelnes Tag:

{
    "id": "146017149028",
    "fileOrUrl": "/Der Grüffelo",
    "playMode": 4,
    "lastPlayPos": 1944473,
    "trackLastPlayed": 2
}

Was passt da für Dich nicht?

Es passt schon, ich hab es nicht so verstanden, dass bei /rfid/{tagid} beide Varianten gehen.

Nur /rfid/details hätte ich nur als /rfid?details genommen, aber ist vielleicht auch persönliche Präferenz.

Ich hatte mal einen Prof, der jedes mal wenn ein Student es wagte zu sagen er habe „REST“ gemacht, gefragt hat, ob das denn wirklich REST ist und ob denn alles richtig ist.
Ich glaube das hat abgefärbt.

Letztendlich muss ich ehrlich sein: wenn die grundlegenden HTTP-Methoden GET/POST/DELETE schon richtig verwendet werden steht man schon gut da…