Demo 46: How to turn ESP with a sdcard or SPIFFS to a web file server

1. Introduction
In this post I will show you how to turn ESP with a sdcard or SPIFFS to a web file server.
Figure: ESP web file server
With this file server you can upload data to sdcard or SPIFFS or download data from sdcard or SPIFFS easily.
I am going to reuse the other posts, so you need to review them:
Demo 12: How to turn the Arduino ESP32 into a Web Server
Demo 7: How to use Arduino ESP32 to store data to sdcard
Demo 45: Copy data from/to SPIFFS without using mkspiffs (web file server)
2. Hardware
If you are using SPIFFS, you can refer Demo 45: Copy data from/to SPIFFS without using mkspiffs (web file server)
If you are using sdcard, please connect ESP with sdcard module as Demo 7: How to use Arduino ESP32 to store data to sdcard
3. Software
Here is the full source code with comments:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#include <WiFiClient.h>
#include <ESP32WebServer.h>
#include <WiFi.h>
#include <ESPmDNS.h>
#include <SPI.h>
#include <mySD.h>

String serverIndex = "<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
    "<input type='file' name='update'>"
    "<input type='submit' value='Upload'>"
"</form>"
"<div id='prg'>progress: 0%</div>"
"<script>"
"$('form').submit(function(e){"
    "e.preventDefault();"
      "var form = $('#upload_form')[0];"
      "var data = new FormData(form);"
      " $.ajax({"
            "url: '/update',"
            "type: 'POST',"               
            "data: data,"
            "contentType: false,"                  
            "processData:false,"  
            "xhr: function() {"
                "var xhr = new window.XMLHttpRequest();"
                "xhr.upload.addEventListener('progress', function(evt) {"
                    "if (evt.lengthComputable) {"
                        "var per = evt.loaded / evt.total;"
                        "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
                    "}"
               "}, false);"
               "return xhr;"
            "},"                                
            "success:function(d, s) {"    
                "console.log('success!')"
           "},"
            "error: function (a, b, c) {"
            "}"
          "});"
"});"
"</script>";

const char* ssid = "TRUONG AN";
const char* password = "0909505150";

ESP32WebServer server(80);
File root;
bool opened = false;

String printDirectory(File dir, int numTabs) {
  String response = "";
  dir.rewindDirectory();
  
  while(true) {
     File entry =  dir.openNextFile();
     if (! entry) {
       // no more files
       //Serial.println("**nomorefiles**");
       break;
     }
     for (uint8_t i=0; i<numTabs; i++) {
       Serial.print('\t');   // we'll have a nice indentation
     }
     // Recurse for directories, otherwise print the file size
     if (entry.isDirectory()) {
       printDirectory(entry, numTabs+1);
     } else {
       response += String("<a href='") + String(entry.name()) + String("'>") + String(entry.name()) + String("</a>") + String("</br>");
     }
     entry.close();
   }
   return String("List files:</br>") + response + String("</br></br> Upload file:") + serverIndex;
}

void handleRoot() {
  root = SD.open("/");
  String res = printDirectory(root, 0);
  server.send(200, "text/html", res);
}

bool loadFromSDCARD(String path){
  path.toLowerCase();
  String dataType = "text/plain";
  if(path.endsWith("/")) path += "index.htm";

  if(path.endsWith(".src")) path = path.substring(0, path.lastIndexOf("."));
  else if(path.endsWith(".jpg")) dataType = "image/jpeg";
  else if(path.endsWith(".txt")) dataType = "text/plain";
  else if(path.endsWith(".zip")) dataType = "application/zip";  
  Serial.println(dataType);
  File dataFile = SD.open(path.c_str());

  if (!dataFile)
    return false;

  if (server.streamFile(dataFile, dataType) != dataFile.size()) {
    Serial.println("Sent less data than expected!");
  }

  dataFile.close();
  return true;
}

void handleNotFound(){
  if(loadFromSDCARD(server.uri())) return;
  String message = "SDCARD Not Detected\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i=0; i<server.args(); i++){
    message += " NAME:"+server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
  Serial.println(message);
}

void setup(void){
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  
  //use IP or iotsharing.local to access webserver
  if (MDNS.begin("iotsharing")) {
    Serial.println("MDNS responder started");
  }
  if (!SD.begin(26, 14, 13, 27)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");
  //handle uri  
  server.on("/", handleRoot);
  server.onNotFound(handleNotFound);

  /*handling uploading file */
  server.on("/update", HTTP_POST, [](){
    server.sendHeader("Connection", "close");
  },[](){
    HTTPUpload& upload = server.upload();
    if(opened == false){
      opened = true;
      root = SD.open((String("/") + upload.filename).c_str(), FILE_WRITE);  
      if(!root){
        Serial.println("- failed to open file for writing");
        return;
      }
    } 
    if(upload.status == UPLOAD_FILE_WRITE){
      if(root.write(upload.buf, upload.currentSize) != upload.currentSize){
        Serial.println("- failed to write");
        return;
      }
    } else if(upload.status == UPLOAD_FILE_END){
      root.close();
      Serial.println("UPLOAD_FILE_END");
      opened = false;
    }
  });
  server.begin();
  Serial.println("HTTP server started");
}

void loop(void){
  server.handleClient();
}
4. Result
Type "iotsharing.local" in web-browser you will see this.
Figure: GUI of demo

Post a Comment

7 Comments

How do we apply CSS styling to the webpage?
Take Surveys said…
The survey is free to take and only takes about 10 minutes to complete. You must be over 18 years old to be eligible for the drawing. Once you complete the survey, you will receive your prize. This is the best way to earn free fuel points and other rewards. You do not even need to purchase to participate. You can complete the sweepstakes within seven days of receipt.
Get a chance to win 50 fuel points as a bonus if you participate in the Global guest satisfaction survey by Kroger. For more details, please visit
https://patronsurveys.com/krogerfeedback-50-fuel-pt-bonus/
The first step in participating in the sweepstakes is to complete the survey. You will need to enter your receipts with the date, time, and entry ID. Once you submit these, different questions will pop up. You must also enter your contact details and loyalty card number. The reward is 50 fuel points that can be used at the retail store. If you want to earn more rewards, you should join the Krogerfeedback survey and make use of it. You may even win some discount coupons for future purchases.
patronsurveys said…
The survey is free to take and only takes about 10 minutes to complete. You must be over 18 years old to be eligible for the drawing. Once you complete the survey, you will receive your prize. This is the best way to earn free fuel points and other rewards. You do not even need to purchase to participate. You can complete the sweepstakes within seven days of receipt.
Get a chance to win 50 fuel points as a bonus if you participate in the Global guest satisfaction survey by Kroger. For more details, please visit
Kroger 50 fuel pt bonus survey is officially running to collect public opinions about their service, staff behavior, maintenance.

The first step in participating in the sweepstakes is to complete the survey. You will need to enter your receipts with the date, time, and entry ID. Once you submit these, different questions will pop up. You must also enter your contact details and loyalty card number. The reward is 50 fuel points that can be used at the retail store. If you want to earn more rewards, you should join the Krogerfeedback survey and make use of it. You may even win some discount coupons for future purchases.
Anonymous said…
do you have alternative for library
Anonymous said…
do you have alternative for library mySD.h
You're reading this right now, and that means you need homework help. Very well, I will do my best to provide you with what you need. It may not always be easy to learn everything you'll want to know, but I'll try my best.
Minkerstinker said…
Is it possible to add a captive portal to this so that everyone joining the wifi just ends up on the main page with download files?

Would be nice as mini pirate box to share files from SD card for everyone as a accesspoint!