In this tutorial we will learn how to use placeholders to replace values at run time in a HTML file served from the ESP32 file system. We will be using the ESP32 async web server library and the Arduino core.
As we have been covering in previous tutorials, in order to upload to the SPIFFS file system the file to be served by the ESP32, we will be using this Arduino IDE plugin. For an introductory tutorial on how to use it, please check here.
In short, to upload the file, we need to go to the Arduino code sketch folder and create a folder called “data“. Inside that folder, we place the file to be uploaded to the ESP32 file system.
We will call our file “test.html” and cover its contents in the section below. Once you place the file there, simply go to the tools menu of the Arduino IDE and click in the “ESP32 Sketch Data Upload” option. After that, the uploading procedure should start.
Note that the file will be placed in the root of the ESP32 file system, with the same name the file had in the computer. So, the path to the file will be “/test.html“
The tests shown here were performed using a DFRobot’s ESP32 module integrated in a ESP32 development board.
The HTML code for this tutorial will be extremely simple and will consist only of a paragraph that has some text and then a placeholder, which will later be dynamically replaced by the ESP32 async web server framework template engine.
Our aim is to simulate a web page that returns a value with some sensor measurement, which will be different depending when the page is fetched from the server.
Recall from the previous tutorial that the placeholders are specified with the following notation:
%PLACEHOLDER_NAME%
Note that we can have multiple placeholders in the same HTML code, as covered in this previous post. In our case, our placeholder will be called PLACEHOLDER. So, in our HTML code, we should put %PLACEHOLDER% where we want to assign a value later at run time.
<p>Sensor measurement: %PLACEHOLDER%</p>
We will start by including all the libraries needed for our tutorial. We need the WiFi.h, the SPIFFS.h and the ESPAsyncWebServer.h. These are needed to be able to connect the ESP32 to a WiFi network, to be able to interact with the ESP32 SPIFFS file system and to be able to configure the HTTP web server, respectively.
After that, we will need to store the credentials of the WiFi network, so the ESP32 can connect to it. As usual, we need the SSID (network name) and the password. Please note that, in the code below, you should change the values of these two variables by the credentials of your network.
Finally, we need an object of class AsyncWebServer, which we will need to use in the Arduino setup function in order to configure the routes of our HTTP web server.
#include "WiFi.h" #include "SPIFFS.h" #include "ESPAsyncWebServer.h" const char* ssid = "yourNetworkName"; const char* password = "yourNetworkPass"; AsyncWebServer server(80);
Now, we will take care of the Arduino setup function. Like we usually do, we start by opening a serial connection, so we can output content to the serial port, to be later displayed in the Arduino IDE serial monitor.
After that, we need to mount the SPIFFS file system, before we start accessing the files it contains. We do that by calling the begin method on the SPIFFS extern variable that is available after we include the SPIFFS.h library. For an introductory tutorial on the SPIFFS file system of the ESP32, please check here.
We will also need to connect the ESP32 to the WiFi network, using the credentials previously declared. After establishing the connection, we will print the local IP assigned to the ESP32 in the WiFi network. We need to know the IP address to be able to contact the server from a HTTP client.
Serial.begin(115200); if(!SPIFFS.begin()){ Serial.println("An Error has occurred while mounting SPIFFS"); return; } WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi.."); } Serial.println(WiFi.localIP());
Next we need to configure the routes of the web server, using the previously declared AsyncWebServer object. Remember from previous tutorials that we configure a route by calling the on method on our server object.
Since we will just have a single HTML page to serve, we will only have a route that we will call “/html“. This route will only listen to HTTP GET requests, since the client will only fetch a HTML page.
As usual, we will specify the route handling function with the C++ lambda syntax, so we can declare it anonymously, thus making the code more compact and elegant.
server.on("/html", HTTP_GET, [](AsyncWebServerRequest *request){ // Handling function implementation });
The implementation of the handling function will consist on fetching the HTML file from the file system, applying the template processor and returning the result to the client. Although this may seem complex, the HTTP web server framework handles all of the complexity for us, and we can do all of these procedures just by calling a simple class method.
Basically, we only need to call the send method on the AsyncWebServerRequest object pointer that is passed to our handling function by the HTTP web server framework.
This is the same method that we have used in a lot of previous tutorials, which has some overloaded versions. In our case, we are going to use the signature version of the method that allows to specify the file to be returned and to apply the template processor.
So, as first input of the method, we need to pass the SPIFFS object that we previously used to initialize the file system, and that is also used to access the ESP32 file system. Note that we are not going to need to call any method of this object to read the actual files, since all of that is done under the hood by the HTTP web server libraries.
Although we are not going to directly interact with the file system, we need to specify which file should be served to the client. So, as second parameter of the send method, we need to pass the path of the HTML file in the ESP32 file system. Remember that the file is located in the “/test.html” path, since it was uploaded to the root directory and it has the same name it had in the computer where it was created.
As third argument, we need to specify the content-type. Since we are going to return a HTML page, the content type should be “text/html“.
The fourth argument corresponds to a Boolean value that will specify how to set the content-disposition HTTP header of the response. This header indicates if the content returned by the server should be displayed inline in the browser (as a Web page) or downloaded and saved (as an attachment) [1].
So, if we set this send method parameter to true, it tells to a web browser that it should download the file. Otherwise, if we set it to false, it tells to the browser to render it and display it as a web page. In our case, we want the browser to display it, so we will pass the Boolean value false.
As final argument we need to pass as input the function that will be used as a template processor. We will call it processor and define its implementation below.
request->send(SPIFFS, "/test.html", "text/html", false, processor);
After the route declaration, we still need to call the begin method on our server object, so it starts listening to incoming HTTP requests from clients.
Note that from this point onward, the server is up and running and working asynchronously, which means we don’t need to poll any object to check for incoming requests. The framework will take care of calling the route handling function whenever a request is made to that route.
server.begin();
To finalize, we need to declare the template processor function. Note that it needs to follow the signature defined here.
Basically, this function will receive as input the placeholder value and should return as output the string that will be used to substitute the placeholder. It’s important to mention that, although in the original content the placeholder name is enclosed between two percentage signs (%PLACEHOLDER%), this function will receive the placeholder name only (PLACEHOLDER).
Although we have only defined one placeholder, we will confirm that the variable we receive has indeed the value PLACEHOLDER and, if it has, we will return a random number to replace it, simulating a sensor measurement.
Note that the number needs to be converted to a string, since this is the return type defined by the template processor function signature. For more on random number generation on the ESP32, please check here.
Just as a safeguard, we will return an empty string in case the placeholder is not found.
String processor(const String& var) { if(var == "PLACEHOLDER"){ return String(random(1,20)); } return String(); }
The final code can be seen below.
#include "WiFi.h" #include "SPIFFS.h" #include "ESPAsyncWebServer.h" const char* ssid = "yourNetworkName"; const char* password = "yourNetworkPass"; AsyncWebServer server(80); String processor(const String& var) { if(var == "PLACEHOLDER"){ return String(random(1,20)); } return String(); } void setup(){ Serial.begin(115200); if(!SPIFFS.begin()){ Serial.println("An Error has occurred while mounting SPIFFS"); return; } 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(SPIFFS, "/test.html", "text/html", false, processor); }); server.begin(); } void loop(){}
To test the previous code, simply compile it and upload it to the ESP32, assuming that you have already uploaded the HTML file to the file system.
When the procedure finishes, open the Arduino IDE serial monitor and wait for the connection to the WiFi network to be established. After that, an IP address should be printed to the monitor. Copy it.
Then, open a web browser of your choice and type the following in the address bar, changing #yourESP32IP# by the IP you have just copied from the serial monitor.
http://#yourESP32IP#/html
You should get an output similar to figure 1, which shows the HTML code we have defined being rendered and with the placeholder replaced by a simulated measurement. If you refresh the page multiple times, the values should be different, since that part of the content is being randomly generated and substituted at run time.
Figure 1 – Rendered HTML page, after placeholder substitution and served by the ESP32.