Motivation

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.

In­halts­ver­zeich­nis

SPIFFS-Editor

Funktionseinheiten

1. Datei hochladen

2. Rückmeldung zu Datei-Aktionen

3. Anzeige SPIFFS-Inhalte

4. Datei Downloaden/Löschen

5. System-Restart

Ereignisse

Platzhalterübersicht

Query-Parameter


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.


 SPIFFS -Editor

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.

Screenshot SPIFFS-Editor

Funktionseinheiten

Die Funktionsweise des SPIFFS-Editors kann man am besten elementweise am Zusammenspiel der Komponenten der HTML-Seite und der UrsAsyncSpiffsEditorClass erklären. Die Funktionseinheiten:

Funktionseinheiten

1. Datei hochladen

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">&nbsp;Datei hochladen 
   <input name="datei" size="50" type="file"> &nbsp;&nbsp;<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.

2. Rückmeldung zu Datei-Aktionen

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&ouml;scht";
String deleteErrorMsg   = "Fehler beim L&ouml;schen von %fn%";
String NoFileActionMsg  = "&nbsp;";

// ...

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="&nbsp;";
  }
</script>

...

<!-- Meldung nach Anzeigedauer von 5 Sekunden löschen -->
<body style="font-family: Helvetica, Arial, sans-serif" onload="setTimeout(removeResultMessage, 5000);">

3. Anzeige SPIFFS-Inhalte

Die Anzeige der SPIFFS-Inhalte besteht aus zwei Blöcken: der Übersichtsteil und die Dateiliste.

Übersicht

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>

Dateiliste

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.

Tabelle generieren

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&ouml;schen</button>
10:    </td>
11: </tr>
Zeile 2:

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").

Zeile 3:

Tabellenspalte zur Aufnahme des Anzeigenamens der Datei. ID ist "name".

Zeile 4:

Tabellenspalte zur Aufnahme der Größe der Datei. ID ist "size".

Zeilen 5..7:

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.).

Zeilen 8..10

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
}

4. Datei Downloaden/Löschen

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.

5. System-Restart

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&nbsp;&nbsp;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:

Restart-Rückmeldung

Ereignisse

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

Platzhalterübersicht

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
Files.push( new File("/index.html", "index.html", 1993));
%file-result% "&nbsp;" (default) oder "Datei erfolgreich gespeichert" bzw. "Fehler beim Hochladen" direkt nach einem File-Upload.

Query-Parameter

Der Webserver reagiert auf drei Query-Parameter: