Ziel ist der einfache Aufbau einer Web-Site zur Manipulation des Dateisystems (SPIFFS). Es stehen Funktionen zur Anzeige der aktuellen Datei-Belegung, zum Hochladen und zum Löschen von Dateien zur Verfügung. Ebenfalls kann ein Neustart des Systems ausgelöst werden.
Inhaltsverzeichnis
Hinweis:
Beim Auskoppeln der Beispiele aus der Library wird ggf. der Order "data" mit dem SPIFFS-Inhalt nicht mit kopiert. Das muss dann manuell nachgeholt werden.
Der Ordner "data" enthält die Datei "spiffs.html", aus der die Kommentare entfern wurden und die für den Web-Server benutzt wird. Zusätzlich ist die Seite "spiffs-kommentiert.html" enthalten.
Zur Nutzung dieses Systems ist eine HTML-Seite notwendig, die abgerufen werden kann und Platzhalter für die anzuzeigenden Informationen enthält. Hinzu kommt die Klasse UrsAsyncSpiffsEditorClass, die die Ansteuerung der Seite übernimmt. In der Web-Server-Klasse muss dann ein Instanz dieser Klasse angelegt und konfiguriert werden.
Die Funktionsweise des SPIFFS-Editors kann man am besten elementweise am Zusammenspiel der Komponenten der HTML-Seite und der UrsAsyncSpiffsEditorClass erklären. Die Funktionseinheiten:
In dem HTML-Code wird ein Formular angelegt, über das eine Datei ausgewählt und hochgeladen werden kann. Details hierzu finden man unter selfhtml: HTML/Formulare/input/Datei-Upload.
<!-- Formular zum Datei hochladen
============================== -->
<form action="spiffs.html" enctype="multipart/form-data" method="post"> Datei hochladen
<input name="datei" size="50" type="file"> <button>Hochladen</button>
</form>
In der Klasse UrsAsyncSpiffsEditorClass wird der Standard-File-Upload-Handler fileUploadHandler aus der Klasse UrsAsyncFSHelper eingebunden. Dies geschieht im Konstruktor der Klasse:
UrsAsyncSpiffsEditorClass() : UrsAsyncWebSite(UrsAsyncWebServer::FileUpload::allowed) {}
Um den Upload muss man sich nicht weiter kümmern.
Zur Anzeige der Rückmeldung ist in der HTML-Datei ein einfaches Feld mit dem Platzhalter %file-result% vorgesehen.
<!-- Upload Ergebnis
============================== -->
<p id="result">%file-result%</p>
Im Request-Handler wird die lokale Variable lastFileActionResult definiert, die im Laufe der Verarbeitung des Requests gefüllt wird.
// Return-Code der letzten Datei-Aktion
UrsFileActionResultCode lastFileActionResult = UrsFileActionResultCode::None;
Beim Löschen einer Datei wird die Methode UrsAsyncFSHelper removeFile aus der Klasse UrsAsyncFSHelper aufgerufen. Diese liefert als Return-Code einen passenden Wert.
fn = request->arg(F("delete"));
if (fn.length()) {
lastFileActionResult = UrsAsyncFSHelper::removeFile(fn);
}
Beim Hochladen einer Datei wird das Ergebnis des File-Upload-Handler fileUploadHandler ausgewertet.
// Returncode von File Upload
// =======================================================================
if (request->_tempObject) {
auto ptr = (UrsFileActionResult*)(request->_tempObject);
lastFileActionResult = ptr->actionCode;
fn = ptr->fileName;
}
Diese Variable wird in einer Instanz der Klasse SpiffsResponse zwischengespeichert.
auto response = new SpiffsResponse(*this, lastFileActionResult, fn);
Bei der Auswertung der Platzhalter in der HTML-Datei werden dann dem Return-Code entsprechende Texte zurück geliefert. Diese Texte können den Platzhalter %fn% enthalten, der durch den betroffenen Dateinamen ersetzt wird.
if (var == "file-result") {
switch (lastFileActionResult)) {
case UrsFileActionResultCode::Uploaded:: return site.uploadSuccessMs;;
case UrsFileActionResultCode::ErrorUp:: return site.uploadErrorMs;;
case UrsFileActionResultCode::Deleted:: return site.deleteSuccessMs;;
case UrsFileActionResultCode::ErrorDel:: return site.deleteErrorMs;;
default: return site.NoFileActionMsg;
}
}
if (var == "fn") return fn;
Die Texte sind in der Klasse UrsAsyncSpiffsEditorClass mit Default-Werten versehen, können aber über entsprechende Setter-Methoden angepasst werden.
String uploadSuccessMsg = "Datei %fn% erfolgreich gespeichert";
String uploadErrorMsg = "Fehler beim Hochladen von %fn%";
String deleteSuccessMsg = "Datei %fn% gelöscht";
String deleteErrorMsg = "Fehler beim Löschen von %fn%";
String NoFileActionMsg = " ";
// ...
void setUploadSuccessMsg(const String & txt) { uploadSuccessMsg = txt; }
void setUploadErrorMsg(const String & txt) { uploadErrorMsg = txt; }
void setDeleteSuccessMsg(const String & txt) { deleteSuccessMsg = txt; }
void setDeleteErrorMsg(const String & txt) { deleteErrorMsg = txt; }
void setNoFileActionMsg(const String & txt) { NoFileActionMsg = txt; };
Dass die Meldung nach einer Anzeigedauer von etwa fünf Sekunden wieder erlischt, wird in der HTML-Datei als Script geregelt:
<script>
// Element #result leeren
function removeResultMessage() {
var textElement=document.getElementById("result");
textElement.innerHTML=" ";
}
</script>
...
<!-- Meldung nach Anzeigedauer von 5 Sekunden löschen -->
<body style="font-family: Helvetica, Arial, sans-serif" onload="setTimeout(removeResultMessage, 5000);">
Die Anzeige der SPIFFS-Inhalte besteht aus zwei Blöcken: der Übersichtsteil und die Dateiliste.
Für die Übersicht stehen folgende Platzhalter zur Verfügung
Der HTML-Code in der Beispiel-Seite ist eine Tabellenzeile (geschützte Leerzeichen wurden der Übersichtlichkeit halber entfernt):
<tr>
<th colspan="4" style="text-align: center">
%count% Dateien im SPIFFS total: %total% kB benutzt: %used% kB frei: %free% kB
</th>
</tr>
Die Erstellung der Dateiliste ist etwas aufwändiger. Der ESPAsyncWebServer machte Probleme, wenn die Platzhalter-Ersetzungstexte zu groß wurden. Um den zu übertragenden Code zu reduzieren wird die eigentliche Tabelle per JavaScript generiert. Vom Web-Server werden nur die Rohdaten geliefert.
Im HTML-Code wird eine Klasse definiert, die die benötigten Daten für eine einzelne Datei aufnehmen kann und ein Array, dass mit Objekten dieser Klasse gefüllt werden soll. Hinzu kommt der Platzhalter %files%, der von dem in der Web-Site-Klasse enthaltenen Text-Prozessor mit entsprechendem JavaScript-Code gefüllt wird.
function File(path, name, size) {
this.path= path; // Dateipfad im SPIFFS
this.name= name; // Anzeigename
this.size= size; // Dateigröße
}
var Files=[];
%files%
%files% wird beim Aufrufen der Seite durch einen String mit JavaScript-Code gefüllt. Für jede Datei wird ein Eintrag in der Form
Files.push( new File("/index.html", "index.html", 1993));
generiert. Wenn der Code dann im Browser ausgeführt wird, wird das Array Files gefüllt. Nachfolgender Code erstellt dann aus diesem Array die Tabelle.
Zunächst einmal ist in der Tabelle eine Musterzeile angelegt, die vom Script für jeden Eintrag in dem Array dupliziert und mit den Werten aus dem Array-Element gefüllt wird (geschützte Leerzeichen wurden der Übersichtlichkeit halber entfernt):
01: <!-- Template für Tabellenzeile -->
02: <tr id="template" style="visibility: collapse">
03: <td id="name" style="text-align: center; width: 400px"></td>
04: <td id="size" style="text-align: right; width: 60px"></td>
05: <td style="width: 156px">
06: <button class="path" name="download" type="submit" style="width:100px" value="">Download</button>
07: </td>
08: <td style="width: 156px; background-color: red">
09: <button class="path" name="delete" type="submit" value="">Löschen</button>
10: </td>
11: </tr>
Die Tabellenzeile hat die ID "template", um sie per Script ansteuern zu können (var template =
document.getElementById("template");
). Das Template an sich ist nicht sichtbar (style="visibility:
collapse"
).
Tabellenspalte zur Aufnahme des Anzeigenamens der Datei. ID ist "name".
Tabellenspalte zur Aufnahme der Größe der Datei. ID ist "size".
Tabellenspalte mit Formular mit Schaltfläche "Download". Die Schaltfläche hat die Klasse "path". Im folgenden Script wird bei diesen Elementen das Attribut "value" durch den Dateipfad ersetzt (s.u.).
Tabellenspalte mit Formular mit Schaltfläche "Löschen". Die Schaltfläche hat die Klasse "path". Im folgenden Script wird bei diesen Elementen das Attribut "value" durch den Dateipfad ersetzt (s.u.).
Das zugehörige Script:
var template = document.getElementById("template"); // Template für Tabellenzeile ermitteln
var table = document.getElementById("SPIFFS"); // Tabelle ermitteln
for (var i = 0; i < Files.length; i++) { // Über alle Datei-Einträge im Array
var clone = template.cloneNode(true); // Template kopieren -> "clone"
clone.style.visibility = "visible"; // neue Tabellenzeile sichtbar machen
clone.id = "file" + i; // mit fortlaufender ID versehen
var path = clone.querySelectorAll(".path"); // in "clone" alle Elemente mit Klasse "path" suchen
path.forEach(function (element) { // für diese alle
element.value = Files[i].path; // das Attribut "value" füllen
});
var size = clone.querySelector("#size"); // Feld "size" suchen
size.innerText = Files[i].size; // und füllen
var fileNname = clone.querySelector("#name"); // Feld "name" suchen
fileNname.innerText = Files[i].name; // und füllen
table.appendChild(clone); // Tabellenzeile der Tabelle hinzufügen
}
In die Dateiliste sind für jede zwei Schaltflächen integriert. Diese lösen einen erneuten Abruf der Seite mit den Query-Parametern "download=<filename>" bzw. "delete=<filename>". Auf diese Parameter reagiert der Code in UrsAsyncSpiffsEditorClass und löst entsprechende Aktionen aus.
Der HTML-Code enthält zum Schluss das Formular mit der Schaltfläche "Neustart". Diese löst einen erneuten Abruf der Seite mit dem Query-Parameter "reboot" aus.
<form action="spiffs.html" method="POST">
<p><input name="reboot" type="submit" value="ESP Neustart"></p>
</form>
Die UrsAsyncSpiffsEditorClass reagiert darauf indem UrsAsyncWebServer::shouldReboot auf true gesetzt wird. Außerdem wird eine entsprechende Meldung an den Browser zurück gesandt. Das Hauptprogramm sollte darauf hin einen Neustart des Prozessors durch führen wie bei UrsAsyncWebServer: Restart-Steuerung beschrieben.
// POST mit Parameter "reboot" führt zum Neustart
// =======================================================================
if (request->hasParam("reboot", true)) {
UrsAsyncWebServer::shouldReboot = true;
request->send(200, String(), F("Neustart des Servers ausgelöst"));
return; // fertig
}
Die Rückmeldung, die der Browser nach einer Restart-Anforderung erhält, sieht in etwa so aus:
UrsAsyncSpiffsEditorClass unterstützt die folgenden Ereignisse:
Ereignis | Registrierung | Funktionstyp der Callback-Funktion |
---|---|---|
Die Datei "spiffs.html" wird abgerufen. | onSpiffsEditStart | EvSpiffsRequestHandler |
Aus UrsAsyncHelper | ||
Eine Datei wurde aus dem SPIFFS gelöscht. | onFileRemoved | EvUrsFileActionCallback |
Eine Datei wurde in das SPIFFS hochgeladen | onFileUpload | EvUrsFileActionCallback |
Der UrsAsyncWebServer stellt für den Request nach "spiffs.html" folgende Platzhalter bereit:
Platzhalter | Wert |
---|---|
%count% | Anzahl der Dateien im SPIFFS |
%total% | Vorhandeber Gesamtspeicher in kB (1024 Bytes) |
%used% | Belegter Speicher in kB |
%free% | Freier Speicher in kB |
%files% | Für jede Datei im SPIFFS ein Eintrag in der Form
|
%file-result% | " " (default) oder "Datei erfolgreich gespeichert" bzw. "Fehler beim Hochladen" direkt nach einem File-Upload. |
Der Webserver reagiert auf drei Query-Parameter: