Die Esp8266 Dateiverwaltung spezifisch sortiert als Arduino Tab.
Spiffs.ino
// ****************************************************************
// Sketch Esp8266 Dateiverwaltung spezifisch Sortiert Modular(Tab)
// created: Jens Fleischer, 2020-08-03
// last mod: Jens Fleischer, 2020-12-20
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266
// Software: Esp8266 Arduino Core 2.4.2 - 2.7.4
// Geprüft: von 1MB bis 16MB Flash
// Getestet auf: Nodemcu, Wemos D1 Mini Pro, Sonoff Switch, Sonoff Dual
/******************************************************************
Copyright (c) 2020 Jens Fleischer. All rights reserved.
This file is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
*******************************************************************/
// Diese Version von Spiffs sollte als Tab eingebunden werden.
// #include <FS.h> #include <ESP8266WebServer.h> müssen im Haupttab aufgerufen werden
// Die Funktionalität des ESP8266 Webservers ist erforderlich.
// "server.onNotFound()" darf nicht im Setup des ESP8266 Webserver stehen.
// Die Funktion "spiffs();" muss im Setup aufgerufen werden.
/**************************************************************************************/
#include <list>
const char WARNING[] PROGMEM = R"(<h2>Der Sketch wurde mit "no SPIFFS" kompilliert!)";
const char HELPER[] PROGMEM = R"(<form method="POST" action="/upload" enctype="multipart/form-data">
<input type="file" name="[]" multiple><button>Upload</button></form>Lade die spiffs.html hoch.)";
void spiffs() { // Funktionsaufruf "spiffs();" muss im Setup eingebunden werden
SPIFFS.begin();
server.on("/list", handleList);
server.on("/format", formatSpiffs);
server.on("/upload", HTTP_POST, sendResponce, handleUpload);
server.onNotFound([]() {
if (!handleFile(server.urlDecode(server.uri())))
server.send(404, "text/plain", "FileNotFound");
});
}
void handleList() { // Senden aller Daten an den Client
FSInfo fs_info; SPIFFS.info(fs_info); // Füllt FSInfo Struktur mit Informationen über das Dateisystem
Dir dir = SPIFFS.openDir("/"); // Auflistung aller im Spiffs vorhandenen Dateien
typedef std::pair<String, int> prop;
std::list<prop> dirList; // Liste anlegen
while (dir.next()) dirList.emplace_back(dir.fileName().substring(1), dir.fileSize()); // Liste füllen
dirList.sort([](const prop & f, const prop & l) { // Liste sortieren
if (server.arg(0) == "1") {
return f.second > l.second;
} else {
for (uint8_t i = 0; i < 30; i++) {
if (tolower(f.first[i]) < tolower(l.first[i])) return true;
else if (tolower(f.first[i]) > tolower(l.first[i])) return false;
}
return false;
}
});
String temp = "[";
for (auto& p : dirList) {
if (temp != "[") temp += ',';
temp += "{\"name\":\"" + p.first + "\",\"size\":\"" + formatBytes(p.second) + "\"}";
}
temp += ",{\"usedBytes\":\"" + formatBytes(fs_info.usedBytes * 1.05) + "\"," + // Berechnet den verwendeten Speicherplatz + 5% Sicherheitsaufschlag
"\"totalBytes\":\"" + formatBytes(fs_info.totalBytes) + "\",\"freeBytes\":\"" + // Zeigt die Größe des Speichers
(fs_info.totalBytes - (fs_info.usedBytes * 1.05)) + "\"}]"; // Berechnet den freien Speicherplatz + 5% Sicherheitsaufschlag
server.send(200, "application/json", temp);
}
bool handleFile(String&& path) {
if (server.hasArg("delete")) {
SPIFFS.remove(server.arg("delete")); // Datei löschen
sendResponce();
return true;
}
if (!SPIFFS.exists("/spiffs.html"))server.send(200, "text/html", SPIFFS.begin() ? HELPER : WARNING); // ermöglicht das hochladen der spiffs.html
if (path.endsWith("/")) path += "index.html";
if (path == "/fs.html") sendResponce(); // Für den Admin Tab
return SPIFFS.exists(path) ? ({File f = SPIFFS.open(path, "r"); server.streamFile(f, getContentType(path)); f.close(); true;}) : false;
}
void handleUpload() { // Dateien ins SPIFFS schreiben
static File fsUploadFile; // enthält den aktuellen Upload
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
if (upload.filename.length() > 30) {
upload.filename = upload.filename.substring(upload.filename.length() - 30, upload.filename.length()); // Dateinamen auf 30 Zeichen kürzen
}
printf("handleFileUpload Name: /%s\n", upload.filename.c_str());
fsUploadFile = SPIFFS.open("/" + server.urlDecode(upload.filename), "w");
} else if (upload.status == UPLOAD_FILE_WRITE) {
printf("handleFileUpload Data: %u\n", upload.currentSize);
fsUploadFile.write(upload.buf, upload.currentSize);
} else if (upload.status == UPLOAD_FILE_END) {
printf("handleFileUpload Size: %u\n", upload.totalSize);
fsUploadFile.close();
}
}
void formatSpiffs() { // Formatiert den Speicher
SPIFFS.format();
sendResponce();
}
void sendResponce() {
server.sendHeader("Location", "spiffs.html");
server.send(303, "message/http");
}
const String formatBytes(size_t const& bytes) { // lesbare Anzeige der Speichergrößen
return bytes < 1024 ? static_cast<String>(bytes) + " Byte" : bytes < 1048576 ? static_cast<String>(bytes / 1024.0) + " KB" : static_cast<String>(bytes / 1048576.0) + " MB";
}
const String &getContentType(String& filename) { // ermittelt den Content-Typ
if (filename.endsWith(".htm") || filename.endsWith(".html")) filename = "text/html";
else if (filename.endsWith(".css")) filename = "text/css";
else if (filename.endsWith(".js")) filename = "application/javascript";
else if (filename.endsWith(".json")) filename = "application/json";
else if (filename.endsWith(".png")) filename = "image/png";
else if (filename.endsWith(".gif")) filename = "image/gif";
else if (filename.endsWith(".jpg")) filename = "image/jpeg";
else if (filename.endsWith(".ico")) filename = "image/x-icon";
else if (filename.endsWith(".xml")) filename = "text/xml";
else if (filename.endsWith(".pdf")) filename = "application/x-pdf";
else if (filename.endsWith(".zip")) filename = "application/x-zip";
else if (filename.endsWith(".gz")) filename = "application/x-gzip";
else filename = "text/plain";
return filename;
}
bool freeSpace(uint16_t const& printsize) { // Funktion um beim speichern in Logdateien zu prüfen ob noch genügend freier Platz verfügbar ist.
FSInfo fs_info; SPIFFS.info(fs_info); // Füllt FSInfo Struktur mit Informationen über das Dateisystem
//Serial.printf("Funktion: %s meldet in Zeile: %d FreeSpace: %s\n", __PRETTY_FUNCTION__, __LINE__, formatBytes(fs_info.totalBytes - (fs_info.usedBytes * 1.05)).c_str());
return (fs_info.totalBytes - (fs_info.usedBytes * 1.05) > printsize) ? true : false;
}
Wer Logdateien anlegt sollte auf den noch zur Verfügung stehenden Speicher achten.
Dafür kannst du die Funktion "freeSpace();" in deinem Sketch nutzen.
Aufruf der Logdatei um das überfüllen des Spiffs zu verhindern.
File f = SPIFFS.open("/logdatei.txt", "a");
if (f && freeSpace(200)) { // Anpassen an die zu schreibende Anzahl Byte
f.print("Fehler im Ablauf in die Logdatei schreiben\n");
}
f.close();
Das Webinterface zum Spiffs Tab.
spiffs.html
<!DOCTYPE HTML> <!-- For more information visit: https://fipsok.de -->
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
<title>Esp8266 Datei Manager</title>
<script>
var to = JSON.parse(localStorage.getItem('sortBy'));
document.addEventListener('DOMContentLoaded', () => {
list(to);
fs.addEventListener('change', () => {
for (var bytes = 0, j = 0; j < event.target.files.length; j++) bytes += event.target.files[j].size;
for (var out = `${bytes} Byte`, i = 0, circa = bytes / 1024; circa > 1; circa /= 1024) {
out = circa.toFixed(2) + [' KB', ' MB', ' GB'][i++];
}
if (bytes > free) {
si.innerHTML = `<li><b> ${out}</b><strong> Ungenügend Speicher frei</strong></li>`;
up.setAttribute('disabled', 'disabled');
}
else {
si.innerHTML = `<li><b>Dateigröße:</b> ${out}</li>`;
up.removeAttribute('disabled');
}
});
btn.addEventListener('click', () => {
if (!confirm(`Wirklich formatieren? Alle Daten gehen verloren.\nDu musst anschließend spiffs.html wieder laden.`)) event.preventDefault();
});
});
function list(arg){
let myList = document.querySelector('main');
fetch('list?sort='+arg).then( (response) => {
return response.json();
}).then((json) => {
myList.innerHTML = '';
for (var i = 0; i < json.length - 1; i++) {
let dir = `<li><a href ="${json[i].name}">${json[i].name}</a><small> ${json[i].size}</small><a href ="${json[i].name}"download="${json[i].name}"> Download </a>`;
if (json[i].name != 'spiffs.html') dir += `or <a href ="?delete=/${json[i].name}">Delete </a>`;
myList.insertAdjacentHTML('beforeend', dir);
}
myList.insertAdjacentHTML('beforeend', `<li><b id="so">${to ? '▼' : '▲'} SPIFFS</b> belegt ${json[i].usedBytes} von ${json[i].totalBytes}`);
free = json[i].freeBytes;
so.addEventListener('click', () => {
list(to=++to%2);
localStorage.setItem('sortBy', JSON.stringify(to));
});
});
}
</script>
</head>
<body>
<h2>ESP8266 Datei Manager</h2>
<form action="/upload" method="POST" enctype="multipart/form-data">
<input id="fs" type="file" name="upload[]" multiple>
<input id="up" type="submit" value="Upload" disabled>
</form>
<div>
<span id="si"></span>
<main></main>
</div>
<form action="/format" method="POST"><input id="btn" type="submit" value="Format SPIFFS"></form>
</body>
</html>
Das Webinterface mit zusätzlicher Sicherheitsabfrage vor dem löschen.
spiffs.html
<!DOCTYPE HTML> <!-- For more information visit: https://fipsok.de -->
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
<title>Esp8266 Datei Manager</title>
<script>
var to = JSON.parse(localStorage.getItem('sortBy'));
document.addEventListener('DOMContentLoaded', () => {
list(to);
fs.addEventListener('change', () => {
for (var bytes = 0, j = 0; j < event.target.files.length; j++) bytes += event.target.files[j].size;
for (var out = `${bytes} Byte`, i = 0, circa = bytes / 1024; circa > 1; circa /= 1024) {
out = circa.toFixed(2) + [' KB', ' MB', ' GB'][i++];
}
if (bytes > free) {
si.innerHTML = `<li><b> ${out}</b><strong> Ungenügend Speicher frei</strong></li>`;
up.setAttribute('disabled', 'disabled');
}
else {
si.innerHTML = `<li><b>Dateigröße:</b> ${out}</li>`;
up.removeAttribute('disabled');
}
});
btn.addEventListener('click', () => {
if (!confirm(`Wirklich formatieren? Alle Daten gehen verloren.\nDu musst anschließend spiffs.html wieder laden.`)) event.preventDefault();
});
});
function list(arg){
let myList = document.querySelector('main');
fetch('list?sort='+arg).then( (response) => {
return response.json();
}).then((json) => {
myList.innerHTML = '';
for (var i = 0; i < json.length - 1; i++) {
let dir = `<li><a href="${json[i].name}">${json[i].name}</a><small> ${json[i].size}</small>`;
dir += `<a href="${json[i].name}"download="${json[i].name}"> Download </a>or <a href="?delete=/${json[i].name}">Delete </a>`;
myList.insertAdjacentHTML('beforeend', dir);
}
myList.insertAdjacentHTML('beforeend', `<li><b id="so">${to ? '▼' : '▲'} SPIFFS</b> belegt ${json[i].usedBytes} von ${json[i].totalBytes}`);
document.querySelectorAll('[href*=delete]').forEach(node => {
node.addEventListener('click', () => {
if (!confirm('Sicher!')) event.preventDefault();
});
});
free = json[i].freeBytes;
so.addEventListener('click', () => {
list(to=++to%2);
localStorage.setItem('sortBy', JSON.stringify(to));
});
});
}
</script>
</head>
<body>
<h2>ESP8266 Datei Manager</h2>
<form action="/upload" method="POST" enctype="multipart/form-data">
<input id="fs" type="file" name="upload[]" multiple>
<input id="up" type="submit" value="Upload" disabled>
</form>
<div>
<span id="si"></span>
<main></main>
</div>
<form action="/format" method="POST"><input id="btn" type="submit" value="Format SPIFFS"></form>
</body>
</html>
Die Style Datei für die Optik.
style.css
/* For more information visit:https://fipsok.de */
body {
font-family: sans-serif;
background-color: #87cefa;
display: flex;
flex-flow: column;
align-items: center;
}
h1,h2 {
color: #e1e1e1;
text-shadow: 2px 2px 2px black;
}
li {
background-color: #feb1e2;
list-style-type: none;
margin-bottom: 10px;
padding: 2px 5px 1px 0;
box-shadow: 5px 5px 5px rgba(0,0,0,0.7);
}
li a:first-child, li b {
background-color: #8f05a5;
font-weight: bold;
color: white;
text-decoration:none;
padding: 2px 5px;
text-shadow: 2px 2px 1px black;
cursor:pointer;
}
li strong {
color: red;
}
input {
height:35px;
font-size:14px;
}
h1 + main {
display: flex;
}
aside {
display: flex;
flex-direction: column;
padding: 0.2em;
}
#left {
align-items:flex-end;
text-shadow: 0.5px 0.5px 1px #757474;
}
.note {
background-color: #fecdee;
padding: 0.5em;
margin-top: 1em;
text-align: center;
max-width: 320px;
border-radius: 0.5em;
}
[type=submit] {
height:40px;
font-size: 16px;
}
[value*=Format] {
margin-top: 1em;
box-shadow: 5px 5px 5px rgba(0,0,0,0.7);
}
button {
height:40px;
width:130px;
background-color: #7bff97;
font-size:16px;
margin-top: 1em;
box-shadow: 5px 5px 5px rgba(0,0,0,0.7);
}
form [title] {
background-color: skyblue;
font-size: 16px;
width: 125px;
}