ESP32 Arduino async HTTP server: Serving a HTML page from FLASH memory

The objective of this post is to explain how to serve a simple HTML page from the ESP32, using the Arduino core. The tests of this ESP32 tutorial were performed using a DFRobot’s ESP-WROOM-32 device integrated in a ESP32 FireBeetle board.


Introduction

The objective of this post is to explain how to serve a simple HTML page from the ESP32, using the Arduino core. You can check on this previous post how to set the libraries needed for us to create a HTTP server.

Our web page will be a simple HTML form that we will serve from the asynchronous HTTP server. Please note that we will not create any CSS for styling our form, so it will be a pretty raw one, just to illustrate how to serve the HTML.

The tests of this ESP32 tutorial were performed using a DFRobot’s ESP-WROOM-32 device integrated in a ESP32 FireBeetle board.


The HTML code

We will start by designing the HTML code independently from the Arduino code. Then, we will use some tools to convert it to a compact string format we can use on the ESP32.

Explaining in detail the HTML code and how everything works is outside the scope of this post. So, basically, we are going to create a very simple form with two inputs and a submit button, as can be seen below.

<form onSubmit = "event.preventDefault()">
	<label class="label">Network Name</label>
	<input type = "text" name = "ssid"/>
	<br/>
	<label>Password</label>
	<input type = "text" name = "pass"/>
	<br/>
	<input type="submit" value="Submit">
</form>

We are labeling the inputs as “Network Name” and “Password”, so this could be a possible implementation of a form to input a network’s WiFi credentials.

Please note that in this tutorial we are only going to cover the part of displaying the HTML content, so we are not going to develop the endpoint to receive the data from the form. Thus, we do the event.preventDefault() call on the submit action so nothing actually happens when we click the button.

Since we are developing for a microcontroller, which is a resource constrained device, we can compress the previous HTML for a single line format, thus getting rid of all the unnecessary tabs and new lines.

Although this will make the code hard to read for a person, it makes no difference to the client which will interpret it and allows us to save some space on the ESP32.

To perform this operation, we can use this online tool, which will minify our HTML code. To prevent problems, when working with complex HTML pages, my recommendation is that you always try the code locally after compressing it, to confirm no problem has occurred in the process.

To do so, you can simply paste the minified code in a text file, save with with a .html extension and open it with a web browser. If everything is ok, then you can use it on the ESP32.

You can check below at figure 1 the result of minifying the code with the online tool mentioned.

ESP32 Arduino HTML code minifying.png

Figure 1 – Minification of the HTML code.

Nonetheless, we can not directly use this code as a string in Arduino because of the quotes it has. Thus, we will need to escape them.

Since HTML code typically has lots of quotes, the easiest way to get the escaped version is by using a tool such as this online one.

So, to escape the code, simply copy the previously minified version of the HTML and paste it on the tool, unchecking the “split output into multiple lines” option before starting the operation. You can check the expected result in figure 2.

ESP32 Arduino escaping minified HTML code.png

Figure 2 – Escaping the minified HTML code in an online tool.

Now that we have the HTML code as a single line escaped C string, we can move on to the Arduino code.


The code

The code for this tutorial is similar to this previous one, with the exception that we are now going to serve a more complex HTML code, which we will store in the FLASH memory.

Since the HTML content is static, putting it on the FLASH Memory avoids consuming RAM, leaving this resource available for other uses. This is useful specially when our program serves a lot of static pages, thus consuming a lot of RAM if not placed in the FLASH.

To start the code, we will write all the needed library includes. We will also declare as global variables the credentials needed for connecting to the WiFi network.

#include <WiFi.h>
#include <FS.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

const char* ssid = "yourNetworkSSID";
const char* password =  "yourNetworkPass";

Next we will declare an object of class AsyncWebServer, which we will use to setup the server. Note that as input of the constructor we need to pass the port where the server will be listening for requests.

AsyncWebServer server(80);

To finalize the global declarations, we will create a variable in the PROGMEM that will contain the HTML code we have prepared.

Update: As explained in this blog post, in the ESP32 constant data is automatically stored in FLASH memory and can be accessed directly from FLASH memory without previously copying it to RAM. So, there is no need to use the PROGMEM keyword below and it is only defined due to compatibility with other platforms. You can check the PROGMEM define on this file. This forum post also contains more information about this subject. Thanks to MOAM Industries and Russell P Hintze for pointing this. The PROGMEM keyword was removed from the final code below, but you can test with it to confirm that it is indeed defined and doesn’t cause any compilation problem.

const char HTML[] PROGMEM = "<form onSubmit=\"event.preventDefault()\"><label class=\"label\">Network Name</label><input type=\"text\" name=\"ssid\"/><br/><label>Password</label><input type=\"text\" name=\"pass\"/><br/><input type=\"submit\" value=\"Submit\"></form>";

Moving on to the setup function, we will start by opening a serial connection and then connect the ESP32 to the WiFi network. You can check in more detail here how to connect the ESP32 to a WiFi network.

Serial.begin(115200);

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
}

Serial.println(WiFi.localIP());

After the connection to the WiFi network, we will take care of the webserver setup. So, we will need to bind a server route to a handling function that will return the HTML we defined when a client makes a request. We will use the “/html” route.

If you need a more detailed explanation on the functions signatures and how the binding works, please consult this previous post.

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

Note that we are specifying the return content-type as “text/html” so the client (in our case it will be a web browser) knows that it must interpret the received content as such.

Also, take in consideration that the last parameter of the send function is the content that we are actually returning to the client and, in our case, it is the HTML variable we defined early.

Now that we have handled the configurations of the server, we simply need to call the begin function on the server object, so it starts listening to incoming HTTP requests. Since the server works asynchronously, we don’t need to perform any call on the main loop, which may be left empty.

You can check the full source code below.

#include <WiFi.h>
#include <FS.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

const char* ssid = "yourNetworkSSID";
const char* password =  "yourNetworkPassword";

AsyncWebServer server(80);

const char HTML[] = "<form onSubmit=\"event.preventDefault()\"><label class=\"label\">Network Name</label><input type=\"text\" name=\"ssid\"/><br/><label>Password</label><input type=\"text\" name=\"pass\"/><br/><input type=\"submit\" value=\"Submit\"></form>";

void setup(){
  Serial.begin(115200);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  Serial.println(WiFi.localIP());

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

  server.begin();
}

void loop(){
}


Testing the code

To test the code, you simply need to compile it and upload it to your device using the Arduino IDE. Once it is ready, just open the serial monitor and wait for the connection to the WiFi network.

When it finishes, the local IP of the ESP32 on your Wireless network should get printed to the serial monitor. Copy that IP, since we will need it to connect to the server.

Then, open a web browser of you choice and write the following URL on the address bar, changing {yourEspIp} by the IP you have just obtained.

http://{yourEspIp}/html

You should get an output similar to figure 3, which shows the HTML page being served and rendered by the browser.

ESP32 Arduino Async HTTP server web form.png

Figure 3 – HTML form rendered in the browser.


Related posts

 

Advertisements

22 Replies to “ESP32 Arduino async HTTP server: Serving a HTML page from FLASH memory”

  1. Could you tell me how to server multiple web pages this way? For example, assuming I have 3 web pages (index, page1, page2) and index is loaded when server starts. How to serve page1 and page2 when user clients on the link on the index page that link to the corresponding page?

    Liked by 1 person

    1. Sorry. This is to correct some typos in my question above.

      Could you tell me how to serve multiple web pages this way? For example, assuming I have 3 web pages (index, page1, page2) and index is loaded when server starts. How to serve page1 and page2 when user clicks on the link on the index page that links to the corresponding page?

      Liked by 1 person

    2. Hi! The simplest way is having multiple routes in your server serving different HTML pages.

      You can declare more routes using the on method.

      As long as the links in your index point to your other webpages, the client (if it is a browser) should automatically request the other pages from the server when the user clicks the link.

      Since you are going to be handling navigation inside your own server, you can use relative links (check the difference in the article below):
      https://www.coffeecup.com/help/articles/absolute-vs-relative-pathslinks/

      Note however that what you mention about loading the index when the server starts is not how it will work.

      Basically, when the server starts, all the 3 routes will be available and if a client makes a request to them, the HTML content will be returned (unless you implement some kind of logic in your handling function).

      So what typically happens is that the client know the address of the index page of a website a puts it on the URL bar of a browser to access it, and then he navigates inside that website by clicking in links (as you mentioned).

      Nonetheless, the index page is not loaded when the server starts but rather is typically the first page someone accesses.

      Hope this helped clarifying 🙂

      Like

  2. Your tutorials are great — informative and exremely helpful.

    Hopefully you can shed light on some confusion that I have.

    Your code above prompted be to look further into the PROGMEM attribute.

    My question is: Why can you use HTML as the last parameter of your “Send function” without having to employ any of the macros and/or functions described in the docs for the ESP8266/Arduino implemenration of PROGMEM (see –> https://github.com/esp8266/Arduino/blob/master/doc/PROGMEM.rst)?

    Your tutorial and another I came across in my search (https://circuits4you.com/2016/12/16/esp8266-web-server-html/) each directly access the data stored in flash without employing those macros and/or functions, while virtually every other example of PROGMEM I’ve come accross relies on the material into linked PROGMEM docs.

    I know that my confusion comes from my poor programming foundation, and I apologize for taking up your time, but if you could point me toward an explaination I would be grateful.

    Thank you.

    Liked by 1 person

    1. Hi!

      Thank you very much for the feedback, I’m very happy to know that you are finding the tutorials useful 🙂

      I’m not aware of all the lower level implementation details but as MOAM Industries pointed in a comment below and from the mentioned forum post it seems the data can be directly accessed from flash, without previously copying to RAM:
      https://esp32.com/viewtopic.php?t=2737

      From this post, it seems like everything is handled by ESP32’s memory management unit:
      https://esp32.com/viewtopic.php?t=1995

      I’ve never investigated how the ESP8266 handles PROGMEM, but most likely the architecture works in a different way, and implies the use of the macros you have mentioned.

      From the same first forum post, it seems that constant data is actually stored in flash automatically, so the PROGMEM keyword isn’t even needed (I’v taken a look at the definition on the Arduino core and it is indeed a no operation).

      I will update the tutorial to reflect this information, which I think is very interesting for other users. At the time I’ve written this post, I was not aware that the PROGMEM was actually not doing anything and it was the const that was placing things in Flash.

      No need to apologize, it was a very interesting you have mentioned and it makes perfect sense to ask.

      Best regards,
      Nuno Santos

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s