← zur Übersichtsseite

Das Konfigurationssystem hat drei Komponenten: Eine Klasse zur Veraltung der Applikationseinstellungen (WeatherSettingsClass), eine Seite (config.html) zum Editieren der Konfigurationsdatei und eine (spiffs.html) zum Verwalten des Dateisystems (SPIFFS).

In­halts­ver­zeich­nis

Einstellungen

WeatherSettingsClass

Web Site: config.html

Seitenaufbau

zugehöriger ESP-Code

Web Site: spiffs.html

Seitenaufbau

zugehöriger ESP-Code


Einstellungen

Um die Applikation möglichst flexibel zu gestalten, werden viele Parameter per Außensteuerung eingestellt. D.h. in diesem Fall, die Parameter werden zum Applikationsstart aus einer Datei ausgelesen. Es wird UrsSettings benutzt.

WeatherSettingsClass

Dem UrsWeatherServer werden die Einstellungsdaten arduino-typisch über die Klasse WeaterSettingsClass und das instanziierte Objekt WeatherSettíngs zur Verfügung gestellt:

// WeatherSettingsClass, Stand 2017-09-19
// --------------------------------------

#include <UrsSettings.h>

class WeatherSettingsClass : public UrsSettingsBaseClass {
 public:
   // ID-Daten für WiFi Station
   StringSetting(ssid, "default-ssid");
   StringSetting(password, "default-password");
   StringSetting(hostname, "ESP8266-Weather");

   // ID-Daten für den Access Point
   StringSetting(ap_name, "ESP8266-Weather");
   StringSetting(ap_pass, "ap-password");
   IPSetting(ap_ip, "192.168.4.1");

   // Authentifizierung zum Zugriff auf config.html, 
   // Wird beim Aufruf von config.html im Browser abgefragt
   StringSetting(configUser, "config-user-name");
   StringSetting(configPassword, "config-password");

   // WebSite
   StringSetting(location, "Wetter Server"); // Ort in index.html
   IntSetting(pageRefresh, 300);  // Anzahl Sekunden, nach der ein automatischer
                                  // Refresh der Seite durchgeführt werden soll.
};

extern WeatherSettingsClass UrsWeatherServer;

Es können vier Bereiche der Applikation konfiguriert werden:

Web-Site: config.html

Diese Seite dient zur Online-Editierung der Einstellungsparameter für die Applikation. Nach dem Aufruf wird zunächst eine Authentifizierung verlangt. Hier die Firefox-Variante:

Authentifizierung

Die aktuell gültigen Authentifizierungsdaten sind in den Settings-Parametern configUser und configPassword hinterlegt. Und so sieht die Seite aus:

config

Der Text kann im Browser-Fenster bearbeitet und über die Schaltfläche "Speichern" gespeichert werden. Folgende Zugriffe auf die Einstellungen greifen auf die neuen Daten zu. Einige Änderungen werden allerdings erst nach einem Neustart des Systems wirksam. Dieser Neustart kann über die Schaltfläche "ESP Neustart" ausgelöst werden.

Seitenaufbau

Die wesentlichen Bestandteile der Seite sind zwei <form>-Elemente. Eines mit einem <textarea>-Element für die Textverarbeitung und einer Schaltfläche zum übermitteln der Daten. Der Inhalt des <textarea>-Elements ist der Platzhalter %config%, der vom ESP-Code beim Aufruf der Seite mit dem aktuellen Inhalt der Datei "config.txt" gefüllt wird.

 Das zweite Formular besitzt nur die Schaltfläche zum Neustart des Prozessors.

<!DOCTYPE HTML>
<html lang="de">

<head>
   <title>Weather Server Configuration</title>
   <meta content="width=device-width, initial-scale=1.0, user-scalable=yes" name="viewport">
   <link href="favicon.ico" rel="shortcut icon" type="image/x-icon">
   <meta charset="UTF-8">
</head>

<body style="font-family: Helvetica, Arial, sans-serif">

<div style="text-align: center">
   <h1>Configuration Editor</h1>
   <!-- Formular zum Editieren und Speichern der Einstellungsdaten -->
   <form action="config.html" method="POST">
     <p>config.txt</p>
     <textarea cols="50" name="config" rows="20">%config%</textarea>
     <p><button type="submit">Speichern</button></p>
   </form>
   <p>================================================</p>
   <!-- Formular zum Auslösen eines Neustarts -->
   <form action="config.html" method="POST">
      <p><input name="reboot" type="submit" value="ESP&nbsp;&nbsp;Neustart"></p>
   </form>
</div>

</body>
</html>

Platzhalterübersicht

Platzhalter Wert Anmerkung
%config% Inhalt der Datei "config.txt" Wird beim Aufruf der Seite mit dem Inhalt der Datei gefüllt.

Zugehöriger ESP-Code

Es sind folgende drei Zugriffe zu behandeln:

Die Methoden zu Behandlung von "config.html" wurden in einer eigenen Quelldatei ("WebSiteConfig.cpp") zusammen gefasst. Zentrale Methode ist handleConfigHTML. Bei der Initialisierung des Systems wird im UrsWeatherServer-Objekt diese Methode zur Behandlung von "config.html" im Constructor eingestellt (man beachte die Verwendung von Lambda-Funktionen zur Bindung an Member-Funktionen):

UrsWeatherServerClass::UrsWeatherServerClass(…) : … {
  on("/config.html", HTTP_ANY, [this](AsyncWebServerRequest *request) { handleConfigHTML(request); });
…
}

handleConfigHTML ist Member-Funktion gestaltet. Somit ist eine Ersetzung in einer Ableitung möglich. Da AsyncWebServer.on() die Adresse einer statischen Methoden erwartet, muss der Aufruf von H innerhalb einer Lambda-Funktion erfolgen, die den this-Zeiger der aufrufenden Klasse bindet (UrsWeatherServerClass::UrsWeatherServerClass() ist eine Methode der Klasse UrsWeatherServerClass und damit die Lambda-Funktion an das entsprechende Objekt dieser Klasse für das der Constructor gerade aktiv ist). Details zu dieser Vorgehensweise ist in Wetter Server: AsyncWebServer-Klasse beschrieben.

Anfragen an "config.html" behandeln

Die Methode handleConfigHTML prüft die Inhalte des HTTP-Request und stellt die geforderten Inhalte bereit.

void UrsWeatherServerClass::handleConfigHTML(AsyncWebServerRequest * request) {
...

1. Authentifizierung

Als erstes wird die Authentifizierung überprüft und ggf. abgefragt:

...
// Authentifierzung prüfen, ggf. abfragen
if (!request->authenticate(WeatherSettings.configUser.value.c_str(), WeatherSettings.configPassword.value.c_str()))
  return request->requestAuthentication((const char*) WeatherSettings.hostname);
...

Dann erfolgt die Behandlung der unterschiedlichen Abfragen.

2. Neustart-Anforderung

 Zunächst die Neustart-Anforderung. Sie wird am Vorhandensein des Query-Parameters "reboot" erkannt:

...
// POST mit Parameter "reboot" führt zum Neustart 
// =======================================================================
if (request->hasParam("reboot", true)) {
  shouldReboot = true;
  request->send(200, String(), F("Neustart des Servers ausgelöst"));
  return; // fertig
}
...

Die direkte Verwendung von ESP.restart() in einer asynchronen Methode ist wegen des dort enthaltenen internen Aufruf von yield() nicht ohne sofortigen Programm-Absturz möglich. Dies ist zwar gewünscht, es soll aber vorher noch eine kurze Bestätigung versandt werden. Deshalb ist ein kleiner Umweg notwendig. Die Member-Variable shouldReboot wird auf true gesetzt. Diese wird in (Arduino-) loop() abgefragt und ggf. der Neustart ausgelöst.

void loop() {
...
  if (MyWebServer.shouldReboot) {
    Serial.println("Rebooting...");
    delay(100);
    ESP.restart();
  }
...
}

Als Bestätigung wird der einfache Text "Neustart des Servers ausgelöst" zurück gesandt:

bestätigung der Neustart-Auslösung

3. Neue Konfiguration speichern

Der neue Inhalt der Konfigurationsdatei wird mit der HTTP-Methode POST übermittelt. Das gespeichert werden soll wird am Query-Parameter "config" erkannt. Der Parameterwert ist der neue Dateiinhalt:

...
// POST mit Parameter "config" führt zum Speichern der neuen Konfiguration
// =======================================================================
if (request->hasParam("config", true)) { 
  String configurationText = request->arg("config");
  File configfile = SPIFFS.open("/config.txt", "w"); // config.txt überschreiben
  if (configfile) {
    configfile.write((uint8_t*)configurationText.c_str(),(size_t) configurationText.length()); // Daten schreiben 
    configfile.close();
    WeatherSettings.loadFromString(configurationText);  // Eisntellungen aus geänderter Datei neu laden
  }
  else { // config.txt nicht geöffnet
    request->send(200, String(), F("Fehler: 'config.txt' kann nicht beschrieben werden!"));
  }
}
...

Wenn die Datei erfolgreich beschrieben wurde, werden die neuen Einstellungsdaten mit WeatherSettings.loadFromString(configurationText) neu bestückt. Nicht alle Änderungen, wie z.B. die Accesspoint-Einstellungen, werden sofort wirksam. Hierzu ist ein Neustart (s.o.) erforderlich.

Konnte die Datei nicht beschrieben werden, wird die Meldung "Fehler: 'config.txt' kann nicht beschrieben werden!" zurück gesandt. 

4. Seiteninhalt bereit stellen

Zur Bereitstellung des Seiteninhalts wurde die Klasse ConfigResponse als spezielle Ableitung der Klasse AsyncFileResponse entworfen (s.u.). Nach der Prüfung auf Existenz der von "config.html" wird eine neue Instanz von ConfigResponse erstellt und versandt:

...
  // Seiteninhalt bereitstellen
  // =======================================================================
  if (!SPIFFS.exists("/config.html")) {
    request->send(404, String(), String(F("Fehler: Datei 'config.html' nicht vorhanden!")));
    return;
  }

  // Eine Instanz von ConfigResponse erstellen und versenden
  AsyncWebServerResponse *response = new ConfigResponse(SPIFFS, "/config.html");
  request->send(response);
}

5. Klasse ConfigResponse

Die Klasse ConfigResponse ist für die Zwischenspeicherung des Dateiinhalts von "config.txt" für den Platzhalter %config% notwendig. Für die Platzhalter-Ersetzung ist die Methode configProcessor zuständig (s. Wetter Server: AsyncWebServer-Klasse).
Die Klasse ist von AsyncFileResponse abgeleitet. Die Basis-Klasse stellt alle sonstig notwendigen Funktionalitäten bereit.

class ConfigResponse : public AsyncFileResponse{
public:
  ConfigResponse(fs::FS &fs, const String& path);
  String configProcessor(const String & var);

  String configurationText;
};

Der Constructor hat die Aufgabe, die Basis-Klasse zu initialisieren und die Datei "config.txt" einzulesen und deren Inhalt in der Member-Variablen configurationText abzulegen. Man achte auf die Verwendung einer Lambda-Funktion zur Einbindung von configProcessor:

ConfigResponse::ConfigResponse(fs::FS & fs, const String & path)
  : AsyncFileResponse(fs, path, String(), false,
                      [this](const String& var)->String { return configProcessor(var); }) {

  // config.txt einlesen
  fs::File configfile = SPIFFS.open("/config.txt", "r");        
  if (configfile) {
    configurationText = readString(configfile);
    configfile.close();
  }
  else { // Keine Datei config.txt
    configurationText = "";
  }
}

Das Einlesen der Datei kann nicht mit der dafür vorgesehenen Methode File::read erfolgen. Ein intern aufgerufenes yield() führt zum Programm-Absturz. Deshalb wird eine eigenständige Methode benutzt, die der Standard-Funktion nachempfunden ist:

static String readString(File s) {
  String ret;
  int c = s.read();
  while (c >= 0) {
    ret += (char)c;
    c = s.read();
  }
  return ret;
}

Bleibt nur die Methode configProcessor, die den Ersatz der Platzhalter vornimmt:

String ConfigResponse::configProcessor(const String & var) {
  if (var == "config")
    return configurationText;

  return String();
}

Web-Site: spiffs.html

Diese Seite dient zur Online-Verwaltung des Dateisystems. Die Authentifizierung und Auslösung des Neustarts ist identisch mit der zu "config.html".

 So sieht die Seite aus:

config

Der obere Bereich ermöglicht das Hochladen neuer Dateien. Die untere Tabelle führt alle im SPIFFS vorhandene Dateien auf und ermöglichst den Download und das Löschen einzelner Dateien.

Seitenaufbau

Die HTML-Datei besteht aus drei wesentlichen Blöcken:

  1. Ein Formular zum Hochladen neuer Dateien.
  2. Eine Tabelle mit je einer Zeile für jede Datei im SPIFFS.
  3. Ein Script, das die Tabelle aufbaut. Die Verwendung eines Scripts entkoppelt die Darstellung von den konkreten Inhalten. Das Serverprogramm liefert nur die Inhalte. Um die Darstellung kümmert sich der HTML-Code (hier insbesondere das enthaltene Script).
  4. Ein Formular zum Neustart des Prozessors.
       In Bearbeitung
<!DOCTYPE HTML>
<html lang="de">

<head>
   <title>Weather Server SPIFFS Explorer</title>
   <meta content="width=device-width, initial-scale=1.0, user-scalable=yes" name="viewport">
   <link href="favicon.ico" rel="shortcut icon" type="image/x-icon">
   <meta charset="UTF-8">
   <style>
table, td, th {
    border: 1px solid black;
}
</style>
</head>

<body>

<div style="text-align: center">

   <h1>SPIFFS-Explorer</h1>

   <!-- 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>
   <br>

   <!-- Dateien anzeigen 
   ============================== -->
   <table id="SPIFFS" style="width: 800px; margin-left: auto; margin-right: auto">
      <tr>
         <th colspan="4" style="text-align: center">%count% Dateien im SPIFFS
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;total: %total% kB
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;benutzt: %used% kB
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;frei: %free% kB
         </th>
      </tr>
      <tr>
         <th colspan="4" style="text-align: center"><b><span style="color: red">Vorsicht beim L&ouml;schen: 
             es wird SOFORT gel&ouml;scht!</span></b>
         </th>
      </tr>
      <tr>
         <th style="width:400px">Name</th>
         <th style="width:60px">Gr&ouml;&szlig;e</th>
         <th colspan="2" style="width:110px">Aktion</th>
      </tr>
      <tr id="master" style="visibility:collapse">
         <td colspan="4" style="padding:0px;border:0;border-collapse:collapse">
         <form class="container" action="spiffs.html" method="POST">
             <table style="border:0px;margin:0px;border-spacing:0px" >
               <tr>
                 <td id="name" style="text-align:center; width:400px">
                     <input id="path" name="filename" type="hidden" value="/index.html">index.html</td>
                 <td style="border:0; width:0px"></td>
                 <td id="size" style="text-align:right; width:60px">99999</td>
                 <td style="border:0; width:0px"></td>
                 <td style="width:156px"><button name="download" type="submit">Download</button></td>
                 <td style="border:0; width:0px"></td>
                 <td style="width:156px; background-color: red"><button name="delete" type="submit">
                     &nbsp;&nbsp;&nbsp;L&ouml;schen&nbsp;&nbsp;&nbsp;</button></td>
              </tr>
           </table>

         </form>
     </tr>
  </table>

   <!-- Datei-Tabelle aufbauen 
   ============================== -->

   <script>
   var master = document.getElementById("master");
   var table = document.getElementById("SPIFFS");

   function File(path, name, size) {
     this.path= path;
     this.name= name;
     this.size= size;
   }  

  var Files=[];
  %files%
//  Files.push( new File("/index.html", "index.html", 1993));


for (var i = 0; i < Files.length; i++) {
  var clone = master.cloneNode(true);
  clone.style.visibility = "visible";
  var path = clone.querySelector("#path");
  path.value = Files[i].path;
  var size = clone.querySelector("#size");
  size.innerText = Files[i].size;

  var fileNname = clone.querySelector("#name");
  fileNname.lastChild.data = Files[i].name;

  table.appendChild(clone);
}
</script>
</div>
</body>

</html>

1. Datei hochladen

Hier der entsprechende Auszug aus der HTML-Datei:

<!-- 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>

HTML-Part: File upload

Das <input>-Element mit dem type-Attribut "file" sorgt für die Anzeige der "Durchsuchen..."-Schaltfläche, deren Funktion und die Anzeige der ausgewählten Datei. Die Schaltfläche "Hochladen" startet den Upload. Details hierzu findet man bei selfhtml.

2. Dateiliste anzeigen

Die im SPIFFS vorhandenen Dateien werden in einer Tabelle angezeigt. Die Tabelle besitzt zunächst drei Überschriftzeilen mit Platzhaltern für Gesamtangaben (%count%, %total%, %used%, %free%):

<table id="SPIFFS" style="width: 800px; margin-left: auto; margin-right: auto">
   <tr>
      <th colspan="3" style="text-align: center">%count% Dateien im SPIFFS
         &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;total: %total% kB
         &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;benutzt: %used% kB
         &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;frei: %free% kB
      </th>
   </tr>
   <tr>
      <th colspan="3 style="text-align: center"><b><span style="color: red">Vorsicht beim L&ouml;schen: 
          es wird SOFORT gel&ouml;scht!</span></b>
      </th>
   </tr>
   <tr>
      <th style="width:400px">Name</th>
      <th style="width:60px">Gr&ouml;&szlig;e</th>
      <th>Aktion</th>
   </tr>
...

Überschrift Dateiliste

Danach folgt eine nicht sichtbare (visibility:collapse) Musterzeile (id="master") für die Anzeige einer einzelnen Datei. Diese Zeile wird im nachfolgend beschriebenen Script für jede vorhandene Datei dupliziert.

...
    <tr id="master" style="visibility:collapse">
       <td colspan="3" style="padding:0px;border:0;border-collapse:collapse">
       <form class="container" action="spiffs.html" method="POST">
           <table style="border:0px;margin:0px;border-spacing:0px" >
             <tr>
               <td id="name" style="text-align:center; width:400px">
                   <input id="path" name="filename" type="hidden" value="/index.html">index.html</td>
               <td style="border:0; width:0px"></td>
               <td id="size" style="text-align:right; width:60px">99999</td>
               <td style="border:0; width:0px"></td>
               <td style="width:156px"><button name="download" type="submit">Download</button></td>
               <td style="border:0; width:0px"></td>
               <td style="width:156px; background-color: red"><button name="delete" type="submit">
                   &nbsp;&nbsp;&nbsp;L&ouml;schen&nbsp;&nbsp;&nbsp;</button></td>
            </tr>
         </table>
       </form>
   </tr>
</table>

Die einzige Spalte der Zeile nimmt die gesamte Breite der Tabelle ein (colspan="3") und enthält als einziges Element ein Formular. Das Formular selbst enthält wiederum eine Tabelle mit nur einer einzelnen Zeile. Diese Tabelle dient zur exakten Ausrichtung der einzelnen Formular-Elemente.

Jede Tabellenzeile enthält vier funktionale Spalten. Die Spalten der Form <td style="border:0; width:0px"></td> dienen nur Anzeige der Zwischenräume zwischen den Spalten. Die Spalten im einzelnen:

Spalte1:

Die Spalte besteht aus einem unsichtbaren input-Element, das den Dateipfad aufnimmt, gefolgt von einem Text-Inhalt, dem nackten Dateinamen. Beim Versenden des Formulars wird hierdurch der Query-Parameter mit Name "filename" (name-Attribut) generiert. Der Wert des Parameters ist der Dateipfad und ergibt sich aus dem value-Attribut.

Der Zugriff auf den Anzeigename erfolgt über das id-Attribut der Spalte. Der Zugriff auf den Dateipfad erhält man über das id-Attribut des input-Elements und dann über das value-Attribut.

Spalte 3: Diese Spalte ist eine reine Textspalte und zeigt die Dateigröße an. Zugriff auf den Text erhält man über das id-Attribut der Spalte.

Spalte 5: Diese Spalte enthält die Schaltfläche zum Download der zugehörigen Datei. Das Auslösen der Schaltfläche führt zu einem Query-Parameter mit dem Namen "download". Der Parameterwert bleibt unberücksichtigt.

Spalte 7: Diese Spalte enthält die Schaltfläche zum Löschen der zugehörigen Datei. Das Auslösen der Schaltfläche führt zu einem Query-Parameter mit dem Namen "delete". Der Parameterwert bleibt unberücksichtigt.

3. Dateiliste aufbauen

Die unsichtbare Musterzeile wird von einem Java-Script für jede Datei im SPIFFS dupliziert, angepasst und sichtbar gemacht.

<!-- Datei-Tabelle aufbauen 
============================== -->

<script>
  function File(path, name, size) {
    this.path= path;
    this.name= name;
    this.size= size;
  }

  var Files=[];
  %files%
  //  Files.push( new File("/index.html", "index.html", 1993));

  var table  = document.getElementById("SPIFFS");
  var master = document.getElementById("master");

  for (var i = 0; i < Files.length; i++) {
    var clone = master.cloneNode(true);
    clone.style.visibility = "visible";
    var path = clone.querySelector("#path");
    path.value = Files[i].path;
    var size = clone.querySelector("#size");
    size.innerText = Files[i].size;

    var fileNname = clone.querySelector("#name");
    fileNname.lastChild.data = Files[i].name;

    table.appendChild(clone);
  }
</script>

Zunächst wird der Typ File definiert, der zur Aufnahme der Datei-Daten dient. Des Array Files dient zur Aufnahme sämtlicher File-Elemente, je eins für jede einzelne SPIFFS-Datei.

Das Füllen des Feldes erfolgt über den Platzhalter %files%. Das ESP-Programm ersetzt den Platzhalter mit je einer Zeile pro Datei in der Form Files.push( new File("/index.html", "index.html", 1993)); (s.u.).

Das Script identifiziert die Tabelle (Variable table) und die Musterzeile (Variable master). Für jedes Element von Files wird die Musterzeile dupliziert, sichtbar gemacht, die Inhalte angepasst und der Tabelle hinzugefügt.

4. Neustart des Prozessor

Dieser Teil ist identisch mit dem in "config.html".

Platzhalterübersicht

       In Bearbeitung
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));

Zugehöriger ESP-Code

Es sind folgende vier Zugriffe zu behandeln:

Die Methoden zu Behandlung von "spiffs.html" wurden in einer eigenen Quelldatei ("WebSiteSPIFFS.cpp") zusammen gefasst. Zentrale Methoden ist handleSpiffsHTML und fileUploadSpiffs zur Behandlung des File-Uploads. Bei der Initialisierung des Systems wird im UrsWeatherServer-Objekt diese Methode zur Behandlung von "spiffs.html" im Constructor eingestellt (man beachte die Verwendung von Lambda-Funktionen zur Bindung an Member-Funktionen):

on("/spiffs.html", HTTP_ANY, [this](AsyncWebServerRequest *request) { handleSpiffsHTML(request); },
                             [this](AsyncWebServerRequest * request, const String & filename,
                                    size_t index, uint8_t * data, size_t len, bool final) {
                                       fileUploadSpiffs(request, filename, index, data, len, final); });

Anfragen an "spiffs.html" behandeln

Die Methode handleSpiffsHTML prüft die Inhalte des HTTP-Request und stellt die geforderten Inhalte bereit.

void UrsWeatherServerClass::handleSpiffsHTML(AsyncWebServerRequest * request) {
...

1. Authentifizierung

Analog zu der in "config.html".

2. Neustart-Anforderung

 Analog zu der in "config.html".

3. File Download und Löschen

Diese Anforderungen werden daran erkannt, dass ein Query-Parameter mit dem Namen "filename" existiert. Zwischen Download und Delete wird anhand des Vorhandenseins eines weiteren Parameters unterscheiden:

...
// File Download oder File Delete
if (request->hasParam("filename", true)) { // Download oder Delete
  if (request->hasArg("download")) { // Datei download
    AsyncWebServerResponse *response = request->beginResponse(SPIFFS, request->arg("filename"), String(), true);
    request->send(response);
    return;
  }
  if (request->hasArg("delete")) { // Datei delete
    SPIFFS.remove(request->arg("filename"));
  }
}
...

Zum Download wird eine entsprechende Instanz von AsyncWebServerResponse erstellt (Parameter download=true) und versandt. Den Rest erledigen die Bibliotheksroutinen. Das Datei-Löschen erfolgt im Code.

4. Seiteninhalt bereitstellen

Der Mechanismus ist analog zu dem bei "config.html" (s.o.). Zur Bereitstellung des Seiteninhalts wurde die Klasse SpiffsResponse als spezielle Ableitung der Klasse AsyncFileResponse entworfen (s.u.). Nach der Prüfung auf Existenz der von "spiffs.html" wird eine neue Instanz von SpiffsResponse erstellt und versandt:

...
// Seiteninhalt bereitstellen
// =======================================================================
if (!SPIFFS.exists("/spiffs.html")) {
  request->send(404, String(), String(F("Fehler: Datei 'spiffs.html' nicht vorhanden!")));
  return;
}

AsyncWebServerResponse *response = new SpiffsResponse(SPIFFS, "/spiffs.html");
request->send(response);
}
...

5. Klasse SpiffsResponse

Die Klasse SpiffsResponse ist für die Zwischenspeicherung der SPIIFS-Daten für den Ersatz der Platzhalter notwendig. Für die Platzhalter-Ersetzung ist die Methode spiffsProcessor zuständig (s. Wetter Server: AsyncWebServer-Klasse).
Die Klasse ist von AsyncFileResponse abgeleitet. Die Basis-Klasse stellt alle sonstig notwendigen Funktionalitäten bereit.

       In Bearbeitung
class SpiffsResponse : public AsyncFileResponse {
public:
  SpiffsResponse(::FS &fs, const String& path);
  String spiffsProcessor(const String & var);
  int fileCount;
  int totalKB;
  int usedKB;
  int freeKB;
  int index = 0;
  String tableContent;
};

Der Constructor hat die Aufgabe, die Basis-Klasse zu initialisieren, dabei das SPIFFS zu untersuchen und die Ergebnisse in den entsprechenden Member-Variablen abzulegen. Man achte auf die Verwendung einer Lambda-Funktion zur Einbindung von spiffsProcessor:

       In Bearbeitung
SpiffsResponse::SpiffsResponse(::FS & fs, const String & path) 
                     : AsyncFileResponse(fs, path, String(), false,
                      [this](const String& var)->String { return spiffsProcessor(var); }) {
  FSInfo fs_info;
  SPIFFS.info(fs_info);
  fileCount = 0;
  totalKB = fs_info.totalBytes / 1024;
  usedKB = fs_info.usedBytes / 1024;
  freeKB = (fs_info.totalBytes - fs_info.usedBytes) / 1024;

  Dir dir = SPIFFS.openDir("/");
  tableContent = "";

  while (dir.next()) {
    String filename = dir.fileName();
    fs::File dirFile = dir.openFile("r");
    size_t filelen = dirFile.size();

    String filename_temp = filename;
    filename_temp.replace("/", "");
    String tableRow = "Files.push(new File('" + filename + "', '" + filename_temp + "', " + filelen + "));";
    tableContent += tableRow;
    fileCount++;
  }
}

Die Variable tableRow enthält eine Java-Script-Anweisung zur Anlage eines File-Objekt in der Form Files.push( new File("/index.html", "index.html", 1993));. In tableContent werden diese einzelnen Anweisungen zusammengefast.

Bleibt nur die Methode spiffsProcessor, die den Ersatz der Platzhalter vornimmt:

String SpiffsResponse::spiffsProcessor(const String & var) {
  if (var == "count") return String(fileCount);
  if (var == "total") return String(totalKB);
  if (var == "used")  return String(usedKB);
  if (var == "free")  return String(freeKB);
  if (var == "files") return tableContent;

  return String();
}

6. Datei-Upload

Das Hochladen von Dateien wird i.W. von der Bibliothek gemanagt. Die hinterlegte Upload-Methode (fileUploadSpiffs) wird für jeden Chunk der Datei aufgerufen:

       In Bearbeitung
void UrsWeatherServerClass::fileUploadSpiffs(AsyncWebServerRequest * request, const String & filename, 
                                             size_t index, uint8_t * data, size_t len, bool final) {
  static File f;
  String fn = filename;

  if (!index) {
    if (!fn.startsWith("/"))
      fn = "/" + filename;
    f = SPIFFS.open(fn, "w");
  }

  f.write(data, len);

  if (final) {
    f.close();
  }
}

index ist die aktuelle Position in der Datei. Ein Wert von Null markiert den Dateianfang. len ist die Länge des aktuellen Chunks und final gibt an, das der übergeben Chunk der letzte war. Damit lässt sich das Speichern der Datei relativ einfach realisieren. Zu beachten ist, dass das SPIFFS immer eine korrekte Pfadangabe benötigt, d.h. u.a. ein "/" zu Beginn. Ist dieser nicht vorhanden, wird er eingefügt.

← zur Übersichtsseite