ESP8266 Webserver Using LittleFS

Posted on

by

in

ESP8266 Webserver Using LittleFS

Introduction

Our Microcontrollers Units (MCU) especially the ESP8266 and ESP32 modules are capable of hoisting their own Webserver which you can use in your Internet of Things (IOT) projects. In this post, we will be creating our own ESP8266 Webserver using the LittleFS Filesystem that will serve web content to the calling client. I will show you how easy and simple your code will be by using the LittleFS in your program.

This post is the 2nd part of my ESP8266 LittleFS Tutorial Series where we try to explore the use cases in which knowledge of this feature will greatly help your project.

If you don’t have an idea of what LittleFS or SPIFFS Filesystem is then please read through Part 1 of this series.

Prerequisites

We will only need the following components in this project to follow along.

  • ESP8266 (I used NodeMCU ESP12E)
  • Breadboard

I used Visual Studio Code with PlatformIO extensions installed in this project. If you don’t have Visual Studio Code and you are in Windows then you can follow along with my Install Visual Studio Code or VSCode in Windows.

I am using the mincss stylesheet in this program so that it would render better on a mobile phone. When you see the file entireframework.min.css then it refers to the mincss. You can take a look at the example there to know more about this awesome small library.

What we are building?

We are building our own ESP8266 Webserver that will turn on or off the onboard LED of our NodeMCU ESP8266 using our mobile phone. To do this, we will be creating our own NodeMCU ESP8266 Webserver using LittleFS Filesystem.

We will first explore creating a Webserver without using LittleFS and point out the issues then we will show how using LittleFS (or SPIFFS) will solve those issues.

What is a Webserver?

A Webserver is both hardware and software that responds to client requests using HTTP (Hypertext Transfer Protocol). In our case below, a Webserver serves a webpage whenever a client like a web browser on your laptop or in your mobile phone types in the address of the Webserver.

The NodeMCU ESP8266 is capable of running a Webserver and at the same time controls sensors connected thru it by responding to requests coming from our webpage.

ESP8266 Webserver Using LittleFS - What is Webserver

ESP8266 Webserver – No LittleFS/SPIFFS

The following is the code for our ESP8266 Webserver with no Filesystem like LittleFS or SPIFFS involved. This is the only code in our project that powers our project and it includes the Webserver code and the logic to turn on the built-in LED.

The code for my project is available on my GitHub page which you can access from here. Let’s go over the code for you to understand.


/*
  Title:  ESP8266 Webserver - No Filesystem
  Description:  An ESP8266 webserver that hardcodes all web content and not using any file system
  Author: donsky
  For:    www.donskytech.com
  Date:   September 14, 2022
*/

#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>

/*
  Replace the SSID and Password according to your wifi
*/
const char *ssid = "<REPLACE_WITH_YOUR_WIFI_SSID>";
const char *password = "<REPLACE_WITH_YOUR_WIFI_PASSWORD>";

// Webserver
AsyncWebServer server(80);

String PARAM_MESSAGE = "status";

const int LED_PIN = LED_BUILTIN;

void notFound(AsyncWebServerRequest *request)
{
  request->send(404, "text/plain", "Not found");
}

// Prepare the HTML String
String prepareHTML()
{
  return "<!DOCTYPE html>"
         "<html>"
         "  <head>"
         "    <meta charset=\"UTF-8\" />"
         "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />"
         "    <title>ESP8266 LittleFS Webserver</title>"
         "    <link rel=\"stylesheet\" href=\"index.css\" />"
         "    <link rel=\"stylesheet\" href=\"entireframework.min.css\" />"
         "    <script src=\"index.js\"></script>"
         "  </head>"
         "  <body>"
         "    <nav class=\"nav\" tabindex=\"-1\" onclick=\"this.focus()\">"
         "      <div class=\"container\">"
         "        <a class=\"pagename current\" href=\"#\">www.donskytech.com</a>"
         "        <a href=\"#\">One</a>"
         "        <a href=\"#\">Two</a>"
         "        <a href=\"#\">Three</a>"
         "      </div>"
         "    </nav>"
         ""
         "    <div class=\"container\">"
         "      <div class=\"hero\">"
         "        <h1 class=\"title\">ESP8266 LittleFS Webserver</h1>"
         "        <div class=\"toggle-div\">"
         "            <p class=\"label\">Toggle Switch</p>"
         "            <input type=\"checkbox\" id=\"switch\" onclick=\"toggleButtonSwitch()\"/><label for=\"switch\">Toggle</label>"
         "        </div>"
         "      </div>"
         "    </div>"
         "  </body>"
         "</html>";
}

// Prepare the index.css
String prepareIndexCSS()
{
  return "/******** mincss **********/"
         ".hero {"
         "    background: #eee;"
         "    padding: 20px;"
         "    border-radius: 10px;"
         "    margin-top: 1em;"
         "    text-align: center;"
         "}"
         ".hero h1 {"
         "    margin-top: 0;"
         "    margin-bottom: 0.3em;"
         "    color: red;"
         "}"
         "/****** Custom toggle Switch**********/"
         "/* https://codepen.io/alvarotrigo/pen/abVPyaJ */"
         "/*********************/"
         ".toggle-div{"
         "    display: flex;"
         "    justify-content: center;"
         "    align-items: center;"
         "    margin-top: 5em;"
         "}"
         ".label{"
         "    font-size: 1.5em;"
         "    font-weight: bolder;"
         "    margin-right: 0.5em;"
         "    /* color: blue */"
         "}"
         "/* Styles for the toggle switch */"
         "input[type=checkbox] {"
         "    height: 0;"
         "    width: 0;"
         "    visibility: hidden;"
         "  }"
         "  "
         "  label {"
         "    cursor: pointer;"
         "    text-indent: -9999px;"
         "    width: 150px;"
         "    height: 60px;"
         "    background: grey;"
         "    display: block;"
         "    border-radius: 100px;"
         "    position: relative;"
         "  }"
         "  "
         "  label:after {"
         "    content: \"\";"
         "    position: absolute;"
         "    top: 5px;"
         "    left: 5px;"
         "    width: 70px;"
         "    height: 50px;"
         "    background: #fff;"
         "    border-radius: 90px;"
         "    transition: 0.3s;"
         "  }"
         "  "
         "  input:checked + label {"
         "    background: #bada55;"
         "  }"
         "  "
         "  input:checked + label:after {"
         "    left: calc(100% - 5px);"
         "    transform: translateX(-100%);"
         "  }"
         "  "
         "  label:active:after {"
         "    width: 130px;"
         "  }";
}

// Prepare the entireframework.min.css
String prepareIndexMinCSS()
{
  return "/* Copyright 2014 Owen Versteeg; MIT licensed */body,textarea,input,select{background:0;border-radius:0;font:16px sans-serif;margin:0}.smooth{transition:all .2s}.btn,.nav a{text-decoration:none}.container{margin:0 20px;width:auto}label>*{display:inline}form>*{display:block;margin-bottom:10px}.btn{background:#999;border-radius:6px;border:0;color:#fff;cursor:pointer;display:inline-block;margin:2px 0;padding:12px 30px 14px}.btn:hover{background:#888}.btn:active,.btn:focus{background:#777}.btn-a{background:#0ae}.btn-a:hover{background:#09d}.btn-a:active,.btn-a:focus{background:#08b}.btn-b{background:#3c5}.btn-b:hover{background:#2b4}.btn-b:active,.btn-b:focus{background:#2a4}.btn-c{background:#d33}.btn-c:hover{background:#c22}.btn-c:active,.btn-c:focus{background:#b22}.btn-sm{border-radius:4px;padding:10px 14px 11px}.row{margin:1% 0;overflow:auto}.col{float:left}.table,.c12{width:100%}.c11{width:91.66%}.c10{width:83.33%}.c9{width:75%}.c8{width:66.66%}.c7{width:58.33%}.c6{width:50%}.c5{width:41.66%}.c4{width:33.33%}.c3{width:25%}.c2{width:16.66%}.c1{width:8.33%}h1{font-size:3em}.btn,h2{font-size:2em}.ico{font:33px Arial Unicode MS,Lucida Sans Unicode}.addon,.btn-sm,.nav,textarea,input,select{outline:0;font-size:14px}textarea,input,select{padding:8px;border:1px solid #ccc}textarea:focus,input:focus,select:focus{border-color:#5ab}textarea,input[type=text]{-webkit-appearance:none;width:13em}.addon{padding:8px 12px;box-shadow:0 0 0 1px #ccc}.nav,.nav .current,.nav a:hover{background:#000;color:#fff}.nav{height:24px;padding:11px 0 15px}.nav a{color:#aaa;padding-right:1em;position:relative;top:-1px}.nav .pagename{font-size:22px;top:1px}.btn.btn-close{background:#000;float:right;font-size:25px;margin:-54px 7px;display:none}@media(min-width:1310px){.container{margin:auto;width:1270px}}@media(max-width:870px){.row .col{width:100%}}@media(max-width:500px){.btn.btn-close{display:block}.nav{overflow:hidden}.pagename{margin-top:-11px}.nav:active,.nav:focus{height:auto}.nav div:before{background:#000;border-bottom:10px double;border-top:3px solid;content:'';float:right;height:4px;position:relative;right:3px;top:14px;width:20px}.nav a{padding:.5em 0;display:block;width:50%}}.table th,.table td{padding:.5em;text-align:left}.table tbody>:nth-child(2n-1){background:#ddd}.msg{padding:1.5em;background:#def;border-left:5px solid #59d}";
}

// Prepare the index.js
String prepareIndexJS()
{
  return "function toggleButtonSwitch(e) {"
         "  var switchButton = document.getElementById(\"switch\");"
         "  "
         "  var toggleValue = \"\";"
         "  if (switchButton.checked) {"
         "    console.log(\"On!\");"
         "    toggleValue = \"ON\";"
         "  } else {"
         "    console.log(\"Off!\");"
         "    toggleValue = \"OFF\""
         "  }"
         "  fetch( `/toggle?status=${toggleValue}`)"
         "  .then( response => {"
         "    console.log(response);"
         "  } )"
         "}";
}

void toggleLED(String status)
{
  if (status == "ON")
    digitalWrite(LED_PIN, LOW);
  else
    digitalWrite(LED_PIN, HIGH);
}

void setup()
{

  Serial.begin(115200);

  // Connect to WIFI
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  if (WiFi.waitForConnectResult() != WL_CONNECTED)
  {
    Serial.printf("WiFi Failed!\n");
    return;
  }

  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  // LED PIN
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);

  // Route for root index.html
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(200, "text/html", prepareHTML()); });

  server.on("/index.css", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(200, "text/css", prepareIndexCSS()); });

  server.on("/entireframework.min.css", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(200, "text/css", prepareIndexMinCSS()); });

  server.on("/index.js", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(200, "text/javascript", prepareIndexJS()); });

  // Respond to toggle event
  server.on("/toggle", HTTP_GET, [](AsyncWebServerRequest *request)
            {
        String status;
        if (request->hasParam(PARAM_MESSAGE)) {
            status = request->getParam(PARAM_MESSAGE)->value();
            if(status == "ON"){
              toggleLED("ON");
            }else{
              toggleLED("OFF");
            }
        } else {
            status = "No message sent";
        }
        request->send(200, "text/plain", "Turning Built In LED : " + status); });

  server.onNotFound(notFound);

  server.begin();
}

void loop()
{
}
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>

Include all the header files necessary to run this project.

/*
  Replace the SSID and Password according to your wifi
*/
const char *ssid = "<REPLACE_WITH_YOUR_WIFI_SSID>";
const char *password = "<REPLACE_WITH_YOUR_WIFI_PASSWORD>";

Replace this with your own wifi credentials.

// Webserver
AsyncWebServer server(80);

String PARAM_MESSAGE = "status";

const int LED_PIN = LED_BUILTIN;

void notFound(AsyncWebServerRequest *request)
{
  request->send(404, "text/plain", "Not found");
}

We define our Webserver here and our built in LED pin. We also define a notFound() function here that gets called when you request resources from our ESP8266 Webserver that is not present.

// Prepare the HTML String
String prepareHTML()
{
  return "<!DOCTYPE html>"
  .
  . // More Code
}

// Prepare the index.css
String prepareIndexCSS()
{
  return "/******** mincss **********/"
  .
  . // More Code
}

// Prepare the entireframework.min.css
String prepareIndexMinCSS()
{
  return "/* 
  .
  . // More Code
}

// Prepare the index.js
String prepareIndexJS()
{
  .
  . // More Code
}

The functions prepareHTML(), prepareIndexCSS(), prepareIndexMinCSS() and the prepareIndexJS() returns a String representation of our web content. As you can see, we have coded our HTML, CSS (Cascading Stylesheets), and Javascript inside our Arduino Sketch. Yikes!

Isn’t this code very easy and lovely to read? You can imagine the nightmare of what will happen if we need to change this code.

void toggleLED(String status)
{
  if (status == "ON")
    digitalWrite(LED_PIN, LOW);
  else
    digitalWrite(LED_PIN, HIGH);
}

Toggle the LED status by calling this function.

void setup()
{

  Serial.begin(115200);

  // Connect to WIFI
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  if (WiFi.waitForConnectResult() != WL_CONNECTED)
  {
    Serial.printf("WiFi Failed!\n");
    return;
  }

  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  // LED PIN
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);

Initialize our Serial monitor baud rate then connect to our Wifi and then configure our LED pin.

  // Route for root index.html
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(200, "text/html", prepareHTML()); });

  // Route for root index.css
  server.on("/index.css", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(200, "text/css", prepareIndexCSS()); });

  // Route for root entireframework.min.css
  server.on("/entireframework.min.css", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(200, "text/css", prepareIndexMinCSS()); });

  // Route for root index.js
  server.on("/index.js", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(200, "text/javascript", prepareIndexJS()); });

  // Respond to toggle event
  server.on("/toggle", HTTP_GET, [](AsyncWebServerRequest *request)
            {
        String status;
        if (request->hasParam(PARAM_MESSAGE)) {
            status = request->getParam(PARAM_MESSAGE)->value();
            if(status == "ON"){
              toggleLED("ON");
            }else{
              toggleLED("OFF");
            }
        } else {
            status = "No message sent";
        }
        request->send(200, "text/plain", "Turning Built In LED : " + status); });

  server.onNotFound(notFound);

  server.begin();

For every route or request from our browser, we respond here by using the code above. So for example,

  • if the browser request for “/”, we respond by calling our prepareHTML()
  • if the browser request for “/index.css”, we respond by calling our prepareIndexCSS()
  • if the browser request for “/entireframework.min.css”, we respond by calling our prepareIndexMinCSS()
  • if the browser request for “/index.js”, we respond by calling our prepareIndexJS()
  • if the browser request for “/toggle”, then we extract the status if it will be on or off then we call the toggleLED() function

We then start the Webserver by calling the server.begin();

Can you now see the problem? We are coding our HTML/CSS/Javascript inside our Arduino sketches then it would be hard to maintain later. Please see the following image for a better representation of what is happening.

It would be hard to do it this way so we needed a way to work around this.

ESP8266 Webserver – Using LittleFS/SPIFFS

To work around the problem above then we can leverage the power of LittleFS or SPIFFS. Let us look at the diagram below for you to see better.

We have separately coded our HTML/CSS/Javascript in separate files. Afterwhich, we upload those files into our LittleFS or SPIFFS filesystem. We are using LittleFS in this post.

ESP8266 Webserver Using LittleFS - Using LittleFS or SPIFFS

The code for this is available on my Github page. We have created separate files for our web content so that it would be easier to do the changes.

  • index.html
  • index.css
  • index.js
  • entireframework.min.css
ESP8266 Webserver Using LittleFS - data folder

The data folder will then need to be uploaded to the NodeMCU ESP8266 Filesystem.

What changed in the code?


/*
  Title:  ESP8266 Webserver - No Filesystem
  Description:  An ESP8266 webserver that uses LittleFS to load web content
  Author: donsky
  For:    www.donskytech.com
  Date:   September 14, 2022
*/

#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>

/*
  Replace the SSID and Password according to your wifi
*/
const char *ssid = "<REPLACE_WITH_YOUR_WIFI_SSID>";
const char *password = "<REPLACE_WITH_YOUR_WIFI_PASSWORD>";


// Webserver
AsyncWebServer server(80);

String PARAM_MESSAGE = "status";

const int LED_PIN = LED_BUILTIN;

void notFound(AsyncWebServerRequest *request)
{
  request->send(404, "text/plain", "Not found");
}

void toggleLED(String status)
{
  if (status == "ON")
    digitalWrite(LED_PIN, LOW);
  else
    digitalWrite(LED_PIN, HIGH);
}

void setup()
{

  Serial.begin(115200);
  Serial.println("Starting the LittleFS Webserver..");

  // Begin LittleFS
  if (!LittleFS.begin())
  {
    Serial.println("An Error has occurred while mounting LittleFS");
    return;
  }

  // Connect to WIFI
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  if (WiFi.waitForConnectResult() != WL_CONNECTED)
  {
    Serial.printf("WiFi Failed!\n");
    return;
  }

  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  // LED PIN
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);

  // Route for root index.html
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(LittleFS, "/index.html", "text/html"); });

  // Route for root index.css
  server.on("/index.css", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(LittleFS, "/index.css", "text/css"); });

  // Route for root entireframework.min.css
  server.on("/entireframework.min.css", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(LittleFS, "/entireframework.min.css", "text/css"); });

  // Route for root index.js
  server.on("/index.js", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(LittleFS, "/index.js", "text/javascript"); });

  // Respond to toggle event
  server.on("/toggle", HTTP_GET, [](AsyncWebServerRequest *request)
            {
        String status;
        if (request->hasParam(PARAM_MESSAGE)) {
            status = request->getParam(PARAM_MESSAGE)->value();
            if(status == "ON"){
              toggleLED("ON");
            }else{
              toggleLED("OFF");
            }
        } else {
            status = "No message sent";
        }
        request->send(200, "text/plain", "Turning Built In LED : " + status); });

  server.onNotFound(notFound);

  server.begin();
}

void loop()
{
}

It’s almost similar to the earlier code except that we are not coding the HTML/CSS/Javascript files inside our main.cpp.

#include <LittleFS.h>

Import the header file for LittleFS.

  // Begin LittleFS
  if (!LittleFS.begin())
  {
    Serial.println("An Error has occurred while mounting LittleFS");
    return;
  }

Start the LittleFS filesystem.

  // Route for root index.html
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(LittleFS, "/index.html", "text/html"); });

  // Route for root index.css
  server.on("/index.css", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(LittleFS, "/index.css", "text/css"); });

  // Route for root entireframework.min.css
  server.on("/entireframework.min.css", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(LittleFS, "/entireframework.min.css", "text/css"); });

  // Route for root index.js
  server.on("/index.js", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(LittleFS, "/index.js", "text/javascript"); });

We have removed all those functions that return string (e.g. prepareHTML()). We are loading now the web contents (HTML/CSS/Javascript) from the LittleFS filesystem. Isn’t this much better rather than coding those long strings?

Once you understand the code then start uploading it to the NodeMCU ESP8266 Filesystem.

Changes to platform.ini

platformio.ini set filesystem to littlefs

We need to set the platform.ini filesystem to use LittleFS.

Wrap up

I have discussed how to create your own NodeMCU ESP8266 Webserver using LittleFS filesystem in this post. I have shown how using LittleFS in serving your web content such as HTML/CSS/Javascript would make your code readable and easier to maintain.

In the last part of this series, we will discuss how LittleFS could help you in saving configurations or settings in your IOT projects.

Stay Tuned! Happy Exploring!

If you like my post then please consider sharing this. Thanks!

Leave a Reply

Your email address will not be published.