Static Web site
A static web page contains HTML code and requires no server action other than returning the text as HTTP response. To simplify the file management, only a single page can be saved. The HTML text is transferred to the ESP32 using the saveHTML() command.
// SaveHTML1.ino
#include <linkup.h>
void s(const char* line)
{
saveHTML(line);
}
void setup()
{
Serial.begin(9600);
Wire.begin();
Serial.println("Transferring HTML file...");
s("<!DOCTYPE html>\n");
s("<html>\n");
s("<head>");
s("<meta name='viewport' content='width=device-width, initial-scale=1'>");
s("</head>\n");
s("<body>\n");
s("<h1>Welcome to the Arduino</h1>\n");
s("<p>Enjoy programming!</p>\n");
s("</body>\n");
s("</html>\n");
s("\0");
Serial.println("done");
}
void loop()
{}
The LinkUp first logs in a existing access point with the SSID/Password with the function connectAP that returns the IP address that must be known as browser URL. It is written to the Serial console. startHTTPServer(onRequest) is a blocking function that starts the server and registers the callback function onRequest that is called when a HTTP GET request arrives.
The parameters provides the information as char array from the URL, e.g. the host, the path (string) and the query parameters. response returns information for the HTTP response. Here nothing is returned, so just the downloaded HTML text is send to the browser. Only HTTP GET requests are supported.
// WebServer1.ino
#include <linkup.h>
void onRequest(char* clientIP, char* filename, char* params, char* response)
{
}
void setup()
{
char reply[20];
Serial.begin(9600);
Wire.begin();
Serial.println("Connecting");
terminateHTTPServer();
connectAP("myssid", "mypassword", reply);
Serial.println(reply);
startHTTPServer(onRequest);
}
void loop()
{}
Since the HTTPServer is blocking, the ESP32 should be resetted manually before the next program is run. It can also be deblocked by calling terminateHTTPServer() as in this example. The browser shows:
If a terminal program (e.g. puTTY, 115200 baud) is connected to the ESP32, debug information is written there:
Wait for master command...
Got command: 5
Content from file linkup.html restored
Starting HTTPServer
-->blocking accept
Got a connection from: 192.168.0.100
Got url with filename: / params: {}
Got reply from master: []
Turnaround time Slave-Master-Slave: 0.26 s
Total response time: 0.33 s
-->blocking accept
The output shows that the ESP32 is waiting for a command from the micro:bit. Command ID 5 starts the HTTP server that is waiting on a blocking accept() for an incomming TCP connection. When this happens it extracts the URL from the HTTP GET request, sends the information to the micro:bit and waits for a reply that arrives after 0.26 s. Then it sends the HTTP response to the client and waits for the next connection. The whole process is hidden to the application programmer.
Instead of using an external WLAN router, an local access point can be started on the ESP32 using the function createAP() that takes the ssid and the password (empty, if no password is reequired). This is useful for home automation and remote control.
// WebServer1.ino
#include <linkup.h>
void onRequest(char* clientIP, char* filename, char* params, char* response)
{
}
void setup()
{
Serial.begin(9600);
Wire.begin();
Serial.println("Creating access point");
terminateHTTPServer();
createAP("arduino", "");
startHTTPServer(onRequest);
}
void loop()
{}
To access it, you must first log in to the access point arduino with no password and then select the URL 192.168.4.1 in your browser
Interactive (dynamic) Web site
An interactive Web page contains information as part of the URL or as query parameters that is used by the Webserver to perform particular actions. In this example the URL is supplemented with /on or /off. that is used to switch on or off a hardware device (here for the demonstration just the LED on Arduino's port 13).
First the website is downloaded. It uses two links to generate the URL <host>/on and <host>/off.
# SaveHTML2.py
from linkup import *
html = """<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h2>Welcome to the micro:bit</h2>
<p><a href="on">Light On</a></p>
<p><a href="off">Light Off</a></p>
</body>
</html>
"""
print("Saving HTML...")
saveHTML(html)
print("Done")
The code to run the server that connects to an existing router is simple. The callback onRequest() uses the filename parameter to check if /on or /off has been sent.
// WebServer2.ino
#include <linkup.h>
void turnOn()
{
digitalWrite(13, HIGH);
Serial.println("Device switched on");
}
void turnOff()
{
digitalWrite(13, LOW);
Serial.println("Device switched off");
}
void onRequest(char* clientIP, char* filename, char* params, char* response)
{
if (strcmp(filename, "/on") == 0)
turnOn();
else if (strcmp(filename, "/off") == 0)
turnOff();
}
void setup()
{
char reply[20];
pinMode(13, OUTPUT);
turnOff();
Serial.begin(9600);
Wire.begin();
Serial.println("Connecting");
terminateHTTPServer();
connectAP("myssid", "mypassword", reply);
Serial.println(reply);
startHTTPServer(onRequest);
}
void loop()
{}
The browser shows:
Two buttons can be used to embellish the display:
# SaveHTML2a.py
from linkup import *
html = """<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h2>Welcome to the micro:bit</H2>
<button style="font-size:22px; height:50px;width:130px"
onclick="window.location.href='on'">Light On</button>
<button style="font-size:22px; height:50px;width:130px"
onclick="window.location.href='off'">Light Off</button>
</body>
</html>
"""
print("Saving HTML...")
saveHTML(html)
print("Done")
The browser now shows:
Use of URL query parameters
Information can transferred using query parameters. The callback function onRequest() receives these as a character array in the format {key1=value1,key2=value2,...}. In this example the returned Web page contains a text entry that reports the current state of the system, namely the line
Current state: on resp. Current state: off
To do this, string format parameters are used in the HTML text: Current state: %s<br>
The HTML uses a HTTP form element to generate the query strings and is downloaded with:
// SaveHTML3.ino
#include <linkup.h>
void s(const char* line)
{
saveHTML(line);
}
void setup()
{
Serial.begin(9600);
Wire.begin();
Serial.println("Transferring HTML file...");
s("<!DOCTYPE html>\n");
s("<html>\n");
s("<head>");
s("<meta name='viewport' content='width=device-width, initial-scale=1'>");
s("</head>\n");
s("<body>\n");
s("<h1>Arduino Switch</h1>\n");
s("<form method='get'>\n");
s("<input type='submit' style='font-size:22px; height:50px;\n");
s("width:110px' name='btn' value='on'/>\n");
s("<input type='submit' style='font-size:22px; height:50px;\n");
s("width:110px' name='btn' value='off'/>\n");
s("</form><br>\n");
s("Current state: %s<br>\n");
s("</body>\n");
s("</html>\n");
s("\0");
Serial.println("done");
}
void loop()
{}
The values of these format parameters are passed back to the ESP32 by the return value of the onRequest() calllback. When it is returned as a list in format [value1, value2, ...] the ESP32 recognizes the list elements as format parameters and inserts them in the HTML page before returning the page to the browser.
The code on the Arduino is as follows:
// WebServer3.ino
#include <linkup.h>
char state[10] = "off";
void turnOn()
{
digitalWrite(13, HIGH);
Serial.println("Device switched on");
}
void turnOff()
{
digitalWrite(13, LOW);
Serial.println("Device switched off");
}
void onRequest(char* clientIP, char* filename, char* params, char* response)
{
Serial.println(params);
if (strcmp(params, "{btn=on}") == 0)
{
strcpy(state, "on");
turnOn();
}
if (strcmp(params, "{btn=off}") == 0)
{
strcpy(state, "off");
turnOff();
}
strcpy(response, "[");
strcat(response, state);
strcat(response, "]");
}
void setup()
{
char reply[20];
pinMode(13, OUTPUT);
turnOff();
Serial.begin(9600);
Wire.begin();
Serial.println("Connecting");
terminateHTTPServer();
connectAP("myssid", "mypassword", reply);
Serial.println(reply);
startHTTPServer(onRequest);
}
void loop()
{}
When the Web client enters the first time, it does not send any query parameters and params is just the empty paranthesis {}. In this case only the current state is returned and no switching action is perfomed. As seen the return value is a list with the format parameters, which are inserted into the the HTML page (respecting the given order if more than one element is in the list).
For home automation applications, a local access point on the micro:bit could be used.
In a terminal window of the ESP32 you can follow what is actually going on:
-->blocking accept
Got a connection from: 192.168.4.2
Got url with filename: / params: {'btn': 'on'}
Got reply from master: ('on',)
Turnaround time Slave-Master-Slave: 0.29 s
Total response time: 0.38 s
-->blocking accept
Got a connection from: 192.168.4.2
Close request with filename: /favicon.ico params: {}
-->blocking accept
The following actions take place:
- The ESP32 is waiting for a client
- The client from IP address 192.168.4.2 makes a GET request (this address was provided by the DHCP on the ESP32)
- The client's URL is /?btn=on, so the filename is '/' and the query parameter btn = on
- This information is transmitted to the micro:bit via I²C and after some time the micro:bit responds by sending ('on')
- The ESP32 replaces the format string in the HTML page with 'on' and sends the HTTP response back to the client
- The turnaround time from ESP32 to micro:bit and back is 0.29s, which gives a response time for the HTTP request of 0.38s
- Then the client browser sends a request for the file favicon.ico (which is answered by the ESP32 with the HTTP response code 204 'no content').
|