2019年10月21日 星期一

ESP8266 and the Arduino IDE Part 2: Control an LED from a web page using Access Point Mode (AP)

ESP8266 and the Arduino IDE Part 2: Control an LED from a web page using Access Point Mode (AP)

In the first part I explained how to set up the IDE and got the basic blink sketch working. Here I go through building a web page control panel to control the LED remotely. I will start with a basic web page and then slowly refine it so we end with a simple but elegant control panel.
For this example I will be using the ESP8266 as an access point (AP Mode). This means the ESP8266 will create its own little network which we can connect to. The ESP8266 will then serve a small web page which we can view on a mobile device or any web enabled device such as a laptop.
I still have the LED connected to pin D1 but now I want to turn it on and off from a web page viewed on a mobile device. The web page will need 2 buttons, one for on and one for off. It would be nice if it also showed the current LED status. Something like “LED is on” and “LED is off”. Since this is a first example of a web control the actual web page should be as simply as possible.
I won’t go in to detail about creating web pages, if you are new to this there are many other sites to help you.

IP address

To view a web page you need to enter it’s url or IP address. In the following examples I will be using the default IP address of 192.168.4.1. This means, after connecting to the ESP8266 I set a browser to go to 192.168.4.1.

Strings

A typical Arduino has very limited memory and due to how Strings (with a capital S) eat and corrupt memory using Strings is, at best, frowned upon and at worst seen as pure evil. Not so with the ESP8266. The ESP8266 has a lot more memory and the fact that web pages are a lot of text, Strings are the way to go. If you do not need to edit the html at run time, char arrays are still better though.

ESP8266wifi library

Thanks to libraries using the ESP8266 to create web apps is fairly easy and the ESP8266 core comes with several libraries including ESP8266WiFi which I am using in the following example.

The basic web page

I will start with a very basic web page and try to explain what it does.
<!DOCTYPE html>
<html>
  <head>
    <title>LED Control</title>
  </head>
  <body>
    <div id='main'>
 <h2>LED Control</h2>
 <form id='F1' action='LEDON'>
   <input class='button' type='submit' value='LED ON' >
 </form>
 <br>
 <form id='F2' action='LEDOFF'>
   <input class='button' type='submit' value='LED OFF' >
 </form>
 <br>
    </div>
  </body>
</html>
<!DOCTYPE html> - Tell the browser we are using html 5
<html> - Start the html
<head> = Start the head section
<title>LED Control</title> - Call the page LED Control. The title is displayed in the browser tab
</head> - close the head section
 
<body> - Open the body section.
 
<div id='main'> = start a div. This comes in handy a little later.
<h2>LED Control</h2> - Display a title at the top of the page
 
<form id='F1' action='LEDON'> - I am using forms that contain a button
<input class='button' type='submit' value='LED ON' > - The LED ON button. The button has a class called "button". This is used later.
</form>
 
<form id='F2' action='LEDOFF'>
<input class='button' type='submit' value='LED OFF' > - The LED OFF button
</form>
<br>
 
</div>
 
</body>
</html>
The above will display a title and 2 buttons. Each button is inside its own form. Using 2 forms means I have 2 actions.
1, action=’LEDON’ for when the ON button is clicked, and
2, action=’LEDOff’ for when the off button is clicked.
The action value gets appended to the ip address such as
1, 192.168.4.1/LEDON, and
2, 192.168.4.1/LEDOFF.
and when the ESP8266 receives the request (more later) the “LEDON” and “LEDOFF” part can be checked for and acted upon if found.

The sketch

/*
 * Sketch: ESP8266_LED_Control_02
 * Control an LED from a web browser
 * Intended to be run on an ESP8266
 * 
 * connect to the ESP8266 AP then
 * use web broswer to go to 192.168.4.1
 * 
 */
 
 
#include <ESP8266WiFi.h>
const char WiFiPassword[] = "12345678";
const char AP_NameChar[] = "LEDControl" ;
 
WiFiServer server(80);
 
String header = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
String html_1 = "<!DOCTYPE html><html><head><title>LED Control</title></head><body><div id='main'><h2>LED Control</h2>";
String html_2 = "<form id='F1' action='LEDON'><input class='button' type='submit' value='LED ON' ></form><br>";
String html_3 = "<form id='F2' action='LEDOFF'><input class='button' type='submit' value='LED OFF' ></form><br>";
String html_4 = "</div></body></html>";
 
String request = "";
int LED_Pin = D1;
 
void setup() 
{
    pinMode(LED_Pin, OUTPUT); 
 
    boolean conn = WiFi.softAP(AP_NameChar, WiFiPassword);
    server.begin();
 
} // void setup()
 
 
void loop() 
{
 
    // Check if a client has connected
    WiFiClient client = server.available();
    if (!client)  {  return;  }
 
    // Read the first line of the request
    request = client.readStringUntil('\r');
 
    if       ( request.indexOf("LEDON") > 0 )  { digitalWrite(LED_Pin, HIGH);  }
    else if  ( request.indexOf("LEDOFF") > 0 ) { digitalWrite(LED_Pin, LOW);   }
 
    client.flush();
 
    client.print( header );
    client.print( html_1 );
    client.print( html_2 );
    client.print( html_3 );
    client.print( html_4);
 
    delay(5);
  // The client will actually be disconnected when the function returns and 'client' object is detroyed
 
} // void loop()

Sketch details

At the top of the sketch the ESP8266wifi library is added and the password and ssid for the ESP8266 server is set. The ssid is the name that is broadcast by the ESP8266. Remember that we are using the ESP8266 in AP mode so it will create its own network. The network will be called “LEDControl”.
#include <ESP8266WiFi.h>
const char WiFiPassword[] = "12345678";
const char AP_NameChar[] = "LEDControl" ;
I have condensed the html and placed it inside 4 Strings. The Strings will be sent to a connected web browser when a request is made. I have used several variables to make it easier to see but just one can be if wished.
String html_1 = "<!DOCTYPE html><html><head><title>LED Control</title></head><body><div id='main'><h2>LED Control</h2>";
String html_2 = "<form id='F1' action='LEDON'><input class='button' type='submit' value='LED ON' ></form><br>";
String html_3 = "<form id='F2' action='LEDOFF'><input class='button' type='submit' value='LED OFF' ></form><br>";
String html_4 = "</div></body></html>";
To tell the browser what type of file we are sending and what format the file is in we use a HTTP header. In this case we are using HTTP1.1 and plain text.
String header = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
When the LED On button is clicked, /LEDON is added to the address and when the LED OFF button is clicked “LEDOFF” is added. I check for these with
if       ( request.indexOf("LEDON") > 0 )  { digitalWrite(LED_Pin, HIGH);  }
else if  ( request.indexOf("LEDOFF") > 0 ) { digitalWrite(LED_Pin, LOW);   }
indexof returns the position of the search text or 0 if it is not found. This means if the result is > 0 we know we have a match. If we have “LEDON” we turn the LED on with “digitalWrite(LED_Pin, HIGH);” and if we have “LEDOFF” we turn the LED on with “digitalWrite(LED_Pin, LOW);”

HTTP requests

When a web browser contacts a web server it makes a request. The request is in plain text and can contain various types of information. Using Firefox on an Android mobile phone, when I connect to the ESP8266 using address 192.168.4.1 the request contains:
GET / HTTP/1.1
Host: 192.168.4.1
User-Agent: Mozilla/5.0 (Android 6.0.1; Mobile; rv:51.0) Gecko/51.0 Firefox/51.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

Now if I click the LEDON button I get:
GET /LEDON HTTP/1.1
Host: 192.168.4.1
User-Agent: Mozilla/5.0 (Android 6.0.1; Mobile; rv:51.0) Gecko/51.0 Firefox/51.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.4.1/
Connection: keep-alive
Upgrade-Insecure-Requests: 1

You will notice the extra “LEDON” in the first line. If I click the LEDOFF button it would be “LEDOFF”. This is the bit we need to know whether or not the LED is being turned on or off. Because it is in the first line, the first line is all we need and since the lines end with a carriage return (\r) character we can read just the first line with:
request = client.readStringUntil('\r');
We can then check the request variable to see if contains either “LEDON” or LEDOFF”. We do not need to worry about the rest of the request.
For more details about HTTP requests start with the List of HTTP header fields at wikipedia, HTTP headers at MDN, and Header Field Definitions over at w3.org

Giving it a go

Connect the LED, upload the code and connect to the ESP8266 from a mobile device using the ip address 192.168.4.1 (the default ip address for the ESP8266wifi library).
ESP8266_030_web_server_1200
Because we defined a password when we initialized the server we need to enter it when we connect.
The first thing you will notice is that the page is very small. It is very very small. Zoom in and click the “LED ON” button. If everything is good the LED will turn on. Then, just for the pure joy of it, click the “LED OFF” button (after zooming in again) to turn off the LED.
ESP8266_031_LED_Control_1200
Unless you have really good eyesight and very thin fingers we need to make the text larger. We could just change the font size but when using a browser on a regular PC the text would be too big. A better way is to tell the browser to resize if we are on a mobile device and we can do this using the meta “viewport” tag. And since we are adding meta statements let’s add the correct character encoding tag as well. Meta statements go in the head section and I have placed these above the title tag.
<head>
<meta name='viewport' content='width=device-width, initial-scale=1.0'/>
<meta charset='utf-8'>
<title>LED Control</title>
</head>
This should make the page a little larger.
ESP8266_032_LED_Control_360
The page is bigger but still on the small size. To change this we can use css. First we make the text bigger with
body {font-size:140%;}
This makes all text 40% bigger. To make the page a little more presentable next we centre by adding css the the div “main”
#main {display: table; margin: auto; padding: 0 10px 0 10px;}
Since I am adding css I might as well add a bit more to make the page a even prettier. The buttons are very plain and we can change this with
.button {padding:10px 10px 10px 10px; width:100%; background-color: #4CAF50; font-size: 120%;}
and finally let’s center the page title with
h2 {text-align:center;}

CSS also goes in the head section and is enclosed in “style” tags. Adding all the above we get
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta charset="utf-8">
<style>
body {font-size:140%;} 
#main {display: table; margin: auto;  padding: 0 10px 0 10px; } 
h2,{text-align:center; } 
.button { padding:10px 10px 10px 10px; width:100%;  background-color: #4CAF50; font-size: 120%;}
</style>
<title>LED Control</title>
</head>
The new sketch looks like this (the only difference is the html used). Download link at the bottom of the page.
/*
 * Sketch: ESP8266_LED_Control_02A
 * Now with added CSS
 * Control an LED from a web browser
 * Intended to be run on an ESP8266
 * 
 * connect to the ESP8266 AP then
 * use web broswer to go to 192.168.4.1
 * 
 */
 
#include <ESP8266WiFi.h>
const char WiFiPassword[] = "12345678";
const char AP_NameChar[] = "LEDControl" ;
 
WiFiServer server(80);
 
String header = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
String html_1 = "<!DOCTYPE html><html><head><meta name='viewport' content='width=device-width, initial-scale=1.0'/><meta charset='utf-8'><style>body {font-size:140%;} #main {display: table; margin: auto;  padding: 0 10px 0 10px; } h2,{text-align:center; } .button { padding:10px 10px 10px 10px; width:100%;  background-color: #4CAF50; font-size: 120%;}</style><title>LED Control</title></head><body><div id='main'><h2>LED Control</h2>";
String html_2 = "<form id='F1' action='LEDON'><input class='button' type='submit' value='LED ON' ></form><br>";
String html_3 = "<form id='F2' action='LEDOFF'><input class='button' type='submit' value='LED OFF' ></form><br>";
String html_4 = "</div></body></html>";
 
String request = "";
int LED_Pin = D1;
 
void setup() 
{
    pinMode(LED_Pin, OUTPUT); 
 
    boolean conn = WiFi.softAP(AP_NameChar, WiFiPassword);
    server.begin();
 
} // void setup()
 
 
 
void loop() 
{
 
    // Check if a client has connected
    WiFiClient client = server.available();
    if (!client)  {  return;  }
 
    // Read the first line of the request
    request = client.readStringUntil('\r');
 
    if       ( request.indexOf("LEDON") > 0 )  { digitalWrite(LED_Pin, HIGH);  }
    else if  ( request.indexOf("LEDOFF") > 0 ) { digitalWrite(LED_Pin, LOW);   }
 
    client.flush();
 
    client.print( header );
    client.print( html_1 );
    client.print( html_2 );
    client.print( html_3 );
    client.print( html_4);
 
    delay(5);
  // The client will actually be disconnected when the function returns and 'client' object is detroyed
 
} // void loop()
and that little bit of css has completely changed how the web page looks
ESP8266_033_LED_Control_360

LED status

The page now looks a lot nicer but we still don’t know if the LED is on or off. To add a status we need to check the D2 pin and if it is HIGH the LED is on and if it is LOW we know the LED is off. We can then add a message in the web page showing the LED status.
To make things easier I have introduced a new String variable called “html_LED”. This will be used to store the LED status message. In the main loop we check the pin status and create the appropriate LED status message. The should be placed after we have checked for “LEDON” and “LEDOFF” and set the D2 pin.
// Get the LED pin status and create the LED status message
if (digitalRead(LED_Pin) == HIGH) { html_LED = "The LED is on<br><br>"; }
else                              { html_LED = "The LED is off<br><br>"; }
and below that we need to add “html_LED” when we send the html to the connected device with
client.print( header );
client.print( html_1 );    
client.print( html_LED );
client.print( html_2 );
client.print( html_3 );
client.print( html_4);
The full sketch. Download link at the bottom of the page.
/*
 * Sketch: ESP8266_LED_Control_02B
 * Now with added CSS and LED status
 * Control an LED from a web browser
 * Intended to be run on an ESP8266
 * 
 * connect to the ESP8266 AP then
 * use web broswer to go to 192.168.4.1
 * 
 */
 
 
#include <ESP8266WiFi.h>
const char WiFiPassword[] = "12345678";
const char AP_NameChar[] = "LEDControl" ;
 
WiFiServer server(80);
 
String header = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
String html_1 = "<!DOCTYPE html><html><head><meta name='viewport' content='width=device-width, initial-scale=1.0'/><meta charset='utf-8'><style>body {font-size:140%;} #main {display: table; margin: auto;  padding: 0 10px 0 10px; } h2,{text-align:center; } .button { padding:10px 10px 10px 10px; width:100%;  background-color: #4CAF50; font-size: 120%;}</style><title>LED Control</title></head><body><div id='main'><h2>LED Control</h2>";
String html_LED = "";
String html_2 = "<form id='F1' action='LEDON'><input class='button' type='submit' value='LED ON' ></form><br>";
String html_3 = "<form id='F2' action='LEDOFF'><input class='button' type='submit' value='LED OFF' ></form><br>";
String html_4 = "</div></body></html>";
 
String request = "";
int LED_Pin = D1;
 
void setup() 
{
    pinMode(LED_Pin, OUTPUT); 
 
    boolean conn = WiFi.softAP(AP_NameChar, WiFiPassword);
    server.begin();
 
} // void setup()
 
 
 
void loop() 
{
 
    // Check if a client has connected
    WiFiClient client = server.available();
    if (!client)  {  return;  }
 
    // Read the first line of the request
    request = client.readStringUntil('\r');
 
    if       ( request.indexOf("LEDON") > 0 )  { digitalWrite(LED_Pin, HIGH);  }
    else if  ( request.indexOf("LEDOFF") > 0 ) { digitalWrite(LED_Pin, LOW);   }
 
    // Get the LED pin status and create the LED status message
    if (digitalRead(LED_Pin) == HIGH) { html_LED = "The LED is on<br><br>"; }
    else                              { html_LED = "The LED is off<br><br>"; }
 
 
    client.flush();
 
    client.print( header );
    client.print( html_1 );    
    client.print( html_LED );
    client.print( html_2 );
    client.print( html_3 );
    client.print( html_4);
 
    delay(5);
  // The client will actually be disconnected when the function returns and 'client' object is detroyed
 
} // void loop()
ESP8266_034_LED_Control_525

LED status part 2. One button

I personally do not like to have multiple buttons controlling the same thing. We have one LED so we should be able to use just one button. Since we are checking the LED status before we send out the html file, we can change the html we send based on the LED status. We are already doing this with the LED status message so why not do it with the buttons (or more specifically the button).
Using a single button, if the LED is off, the button can say “Turn on the LED” and if the LED is on, it can say “Turn off the LED”. An easy way to do this is to copy the appropriate form to a String before sending.
Using the same idea as with the LED status message change the String variables that contain the html
String header = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
String html_1 = "<!DOCTYPE html><html><head><meta name='viewport' content='width=device-width, initial-scale=1.0'/><meta charset='utf-8'><style>body {font-size:140%;} #main {display: table; margin: auto;  padding: 0 10px 0 10px; } h2,{text-align:center; } .button { padding:10px 10px 10px 10px; width:100%;  background-color: #4CAF50; font-size: 120%;}</style><title>LED Control</title></head><body><div id='main'><h2>LED Control</h2>";
String html_2 = "";
String html_4 = "</div></body></html>";
and then, after we know the LED status we copy 1 of 2 forms in to html_2
// Get the LED pin status and create the LED status message
if (digitalRead(LED_Pin) == HIGH) 
{
    // the LED is on so the button needs to say turn it off
    html_2 = "<form id='F1' action='LEDOFF'><input class='button' type='submit' value='Turn of the LED' ></form><br>";
}
else                              
{
    // the LED is off so the button needs to say turn it on
    html_2 = "<form id='F1' action='LEDON'><input class='button' type='submit' value='Turn on the LED' ></form><br>";
}
Notice that both forms have the same id value
Then send the html using
client.print( header );
client.print( html_1 );    
client.print( html_2 );
client.print( html_4);

The final sketch

Download link at the bottom of the page.
/*
 * Sketch: ESP8266_LED_Control_02C
 * Now with added CSS and a single button
 * Control an LED from a web browser
 * Intended to be run on an ESP8266
 * 
 * connect to the ESP8266 AP then
 * use web broswer to go to 192.168.4.1
 * 
 */
 
 
#include <ESP8266WiFi.h>
const char WiFiPassword[] = "12345678";
const char AP_NameChar[] = "LEDControl" ;
 
WiFiServer server(80);
 
String header = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
String html_1 = "<!DOCTYPE html><html><head><meta name='viewport' content='width=device-width, initial-scale=1.0'/><meta charset='utf-8'><style>body {font-size:140%;} #main {display: table; margin: auto;  padding: 0 10px 0 10px; } h2,{text-align:center; } .button { padding:10px 10px 10px 10px; width:100%;  background-color: #4CAF50; font-size: 120%;}</style><title>LED Control</title></head><body><div id='main'><h2>LED Control</h2>";
String html_2 = "";
String html_4 = "</div></body></html>";
 
String request = "";
int LED_Pin = D1;
 
void setup() 
{
    pinMode(LED_Pin, OUTPUT); 
 
    boolean conn = WiFi.softAP(AP_NameChar, WiFiPassword);
    server.begin();
 
} // void setup()
 
 
 
void loop() 
{
 
    // Check if a client has connected
    WiFiClient client = server.available();
    if (!client)  {  return;  }
 
    // Read the first line of the request
    request = client.readStringUntil('\r');
 
    if       ( request.indexOf("LEDON") > 0 )  { digitalWrite(LED_Pin, HIGH);  }
    else if  ( request.indexOf("LEDOFF") > 0 ) { digitalWrite(LED_Pin, LOW);   }
 
 
    // Get the LED pin status and create the LED status message
    if (digitalRead(LED_Pin) == HIGH) 
    {
        // the LED is on so the button needs to say turn it off
       html_2 = "<form id='F1' action='LEDOFF'><input class='button' type='submit' value='Turn of the LED' ></form><br>";
    }
    else                              
    {
        // the LED is off so the button needs to say turn it on
        html_2 = "<form id='F1' action='LEDON'><input class='button' type='submit' value='Turn on the LED' ></form><br>";
    }
 
 
    client.flush();
 
    client.print( header );
    client.print( html_1 );    
    client.print( html_2 );
    client.print( html_4);
 
    delay(5);
  // The client will actually be disconnected when the function returns and 'client' object is detroyed
 
} // void loop()
And here is the final web page
ESP8266_035_LED_Control_525

Downloads

Download the above sketches: ESP8266_LED_Control_02(ABC).

 

沒有留言:

張貼留言

WOKWI DHT22 & LED , Node-Red + SQLite database

 WOKWI DHT22 & LED , Node-Red + SQLite database Node-Red程式 [{"id":"6f0240353e534bbd","type":"comment&...