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).
Inhaltsverzeichnis
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.
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:
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:
Die aktuell gültigen Authentifizierungsdaten sind in den Settings-Parametern configUser und configPassword hinterlegt. Und so sieht die Seite aus:
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.
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 Neustart"></p>
</form>
</div>
</body>
</html>
Platzhalter | Wert | Anmerkung |
---|---|---|
%config% | Inhalt der Datei "config.txt" | Wird beim Aufruf der Seite mit dem Inhalt der Datei gefüllt. |
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.
Die Methode handleConfigHTML prüft die Inhalte des HTTP-Request und stellt die geforderten Inhalte bereit.
void UrsWeatherServerClass::handleConfigHTML(AsyncWebServerRequest * request) {
...
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.
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:
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.
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);
}
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();
}
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:
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.
Die HTML-Datei besteht aus drei wesentlichen Blöcken:
<!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"> Datei hochladen
<input name="datei" size="50" type="file"> <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
total: %total% kB
benutzt: %used% kB
frei: %free% kB
</th>
</tr>
<tr>
<th colspan="4" style="text-align: center"><b><span style="color: red">Vorsicht beim Löschen:
es wird SOFORT gelöscht!</span></b>
</th>
</tr>
<tr>
<th style="width:400px">Name</th>
<th style="width:60px">Größ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">
Löschen </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>
Hier der entsprechende Auszug aus der HTML-Datei:
<!-- 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>
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.
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
total: %total% kB
benutzt: %used% kB
frei: %free% kB
</th>
</tr>
<tr>
<th colspan="3 style="text-align: center"><b><span style="color: red">Vorsicht beim Löschen:
es wird SOFORT gelöscht!</span></b>
</th>
</tr>
<tr>
<th style="width:400px">Name</th>
<th style="width:60px">Größe</th>
<th>Aktion</th>
</tr>
...
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">
Löschen </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. |
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.
Dieser Teil ist identisch mit dem in "config.html".
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
|
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); });
Die Methode handleSpiffsHTML prüft die Inhalte des HTTP-Request und stellt die geforderten Inhalte bereit.
void UrsWeatherServerClass::handleSpiffsHTML(AsyncWebServerRequest * request) {
...
Analog zu der in "config.html".
Analog zu der in "config.html".
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.
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);
}
...
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.
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:
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();
}
Das Hochladen von Dateien wird i.W. von der Bibliothek gemanagt. Die hinterlegte Upload-Methode (fileUploadSpiffs) wird für jeden Chunk der Datei aufgerufen:
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.