2021年12月3日 星期五

ESP32 Control Outputs with Web Server and a Physical Button Simultaneously

ESP32 Control Outputs with Web Server and a Physical Button Simultaneously











/*********

  Rui Santos

  Complete project details at https://RandomNerdTutorials.com/esp32-esp8266-web-server-physical-button/

  

  The above copyright notice and this permission notice shall be included in all

  copies or substantial portions of the Software.

*********/


// Import required libraries

  #include <WiFi.h>

  #include <AsyncTCP.h>

  

// ESP8266

//  #include <ESP8266WiFi.h>

//  #include <ESPAsyncTCP.h>


#include <ESPAsyncWebServer.h>


// Replace with your network credentials

//const char* ssid = "REPLACE_WITH_YOUR_SSID";

//const char* password = "REPLACE_WITH_YOUR_PASSWORD";

const char* ssid     = "PTS-2F";

const char* password = "PTS6662594";


const char* PARAM_INPUT_1 = "state";


const int output = 2;

const int buttonPin = 4;


// Variables will change:

int ledState = LOW;          // the current state of the output pin

int buttonState;             // the current reading from the input pin

int lastButtonState = LOW;   // the previous reading from the input pin


// the following variables are unsigned longs because the time, measured in

// milliseconds, will quickly become a bigger number than can be stored in an int.

unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled

unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers


// Create AsyncWebServer object on port 80

AsyncWebServer server(80);


const char index_html[] PROGMEM = R"rawliteral(

<!DOCTYPE HTML><html>

<head>

  <title>ESP Web Server</title>

  <meta name="viewport" content="width=device-width, initial-scale=1">

  <style>

    html {font-family: Arial; display: inline-block; text-align: center;}

    h2 {font-size: 3.0rem;}

    p {font-size: 3.0rem;}

    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}

    .switch {position: relative; display: inline-block; width: 120px; height: 68px} 

    .switch input {display: none}

    .slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 34px}

    .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px}

    input:checked+.slider {background-color: #2196F3}

    input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}

  </style>

</head>

<body>

  <h2>ESP Web Server</h2>

  %BUTTONPLACEHOLDER%

<script>function toggleCheckbox(element) {

  var xhr = new XMLHttpRequest();

  if(element.checked){ xhr.open("GET", "/update?state=1", true); }

  else { xhr.open("GET", "/update?state=0", true); }

  xhr.send();

}


setInterval(function ( ) {

  var xhttp = new XMLHttpRequest();

  xhttp.onreadystatechange = function() {

    if (this.readyState == 4 && this.status == 200) {

      var inputChecked;

      var outputStateM;

      if( this.responseText == 1){ 

        inputChecked = true;

        outputStateM = "On";

      }

      else { 

        inputChecked = false;

        outputStateM = "Off";

      }

      document.getElementById("output").checked = inputChecked;

      document.getElementById("outputState").innerHTML = outputStateM;

    }

  };

  xhttp.open("GET", "/state", true);

  xhttp.send();

}, 1000 ) ;

</script>

</body>

</html>

)rawliteral";


// Replaces placeholder with button section in your web page

String processor(const String& var){

  //Serial.println(var);

  if(var == "BUTTONPLACEHOLDER"){

    String buttons ="";

    String outputStateValue = outputState();

    buttons+= "<h4>Output - GPIO 2 - State <span id=\"outputState\"></span></h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"output\" " + outputStateValue + "><span class=\"slider\"></span></label>";

    return buttons;

  }

  return String();

}


String outputState(){

  if(digitalRead(output)){

    return "checked";

  }

  else {

    return "";

  }

  return "";

}


void setup(){

  // Serial port for debugging purposes

  Serial.begin(115200);


  pinMode(output, OUTPUT);

  digitalWrite(output, LOW);

  pinMode(buttonPin, INPUT_PULLUP); //原來的 INPUT 改成  INPUT_PULLUP

  

  // Connect to Wi-Fi

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {

    delay(1000);

    Serial.println("Connecting to WiFi..");

  }


  // Print ESP Local IP Address

  Serial.println(WiFi.localIP());

  Serial.println("Control Outputs with Web Server and a Physical Button Simultaneously");

  // Route for root / web page

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){

    request->send_P(200, "text/html", index_html, processor);

  });


  // Send a GET request to <ESP_IP>/update?state=<inputMessage>

  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {

    String inputMessage;

    String inputParam;

    // GET input1 value on <ESP_IP>/update?state=<inputMessage>

    if (request->hasParam(PARAM_INPUT_1)) {

      inputMessage = request->getParam(PARAM_INPUT_1)->value();

      inputParam = PARAM_INPUT_1;

      digitalWrite(output, inputMessage.toInt());

      ledState = !ledState;

    }

    else {

      inputMessage = "No message sent";

      inputParam = "none";

    }

    Serial.println(inputMessage);

    request->send(200, "text/plain", "OK");

  });


  // Send a GET request to <ESP_IP>/state

  server.on("/state", HTTP_GET, [] (AsyncWebServerRequest *request) {

    request->send(200, "text/plain", String(digitalRead(output)).c_str());

  });

  // Start server

  server.begin();

}

  

void loop() {

  // read the state of the switch into a local variable:

  int reading = digitalRead(buttonPin);


  // check to see if you just pressed the button

  // (i.e. the input went from LOW to HIGH), and you've waited long enough

  // since the last press to ignore any noise:


  // If the switch changed, due to noise or pressing:

  if (reading != lastButtonState) {

    // reset the debouncing timer

    lastDebounceTime = millis();

  }


  if ((millis() - lastDebounceTime) > debounceDelay) {

    // whatever the reading is at, it's been there for longer than the debounce

    // delay, so take it as the actual current state:


    // if the button state has changed:

    if (reading != buttonState) {

      buttonState = reading;


      // only toggle the LED if the new button state is HIGH

      if (buttonState == HIGH) {

        ledState = !ledState;

      }

    }

  }


  // set the LED:

  digitalWrite(output, ledState);


  // save the reading. Next time through the loop, it'll be the lastButtonState:

  lastButtonState = reading;

}





ESP32/ESP8266: Control Outputs with Web Server and a Physical Button Simultaneously

This tutorial shows how to control the ESP32 or ESP8266 outputs using a web server and a physical button simultaneously. The output state is updated on the web page whether it is changed via physical button or web server.

ESP32 ESP8266 NodeMCU: Control Outputs with Web Server and a Physical Button Simultaneously Arduino IDE

Recommended reading: Input Data on HTML Form ESP32/ESP8266 Web Server

The ESP32/ESP8266 boards will be programmed using Arduino IDE. So make sure you have these boards installed:

Project Overview

Let’s take a quick look at how the project works.

ESP32 ESP8266 NodeMCU Web Server With Physical Button Project Overview
  • The ESP32 or ESP8266 hosts a web server that allows you to control the state of an output;
  • The current output state is displayed on the web server;
  • The ESP is also connected to a physical pushbutton that controls the same output;
  • If you change the output state using the physical puhsbutton, its current state is also updated on the web server.

In summary, this project allows you to control the same output using a web server and a push button simultaneously. Whenever the output state changes, the web server is updated.

Schematic Diagram

Before proceeding, you need to assemble a circuit with an LED and a pushbutton. We’ll connect the LED to GPIO 2 and the pushbutton to GPIO 4.

Parts Required

Here’s a list of the parts to you need to build the circuit:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

ESP32 Schematic

ESP32 Digital Input and Digital Output Schematic Circuit LED Pushbutton

ESP8266 NodeMCU Schematic

ESP8266 NodeMCU Digital Input and Digital Output Schematic Circuit LED Pushbutton

Installing Libraries – Async Web Server

To build the web server you need to install the following libraries:

These libraries aren’t available to install through the Arduino Library Manager, so you need to copy the library files to the Arduino Installation Libraries folder. Alternatively, in your Arduino IDE, you can go to Sketch Include Library > Add .zip Library and select the libraries you’ve just downloaded.

ESP Web Server Code

Copy the following code to your Arduino IDE.

/*********
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-esp8266-web-server-physical-button/
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*********/

// Import required libraries
#ifdef ESP32
  #include <WiFi.h>
  #include <AsyncTCP.h>
#else
  #include <ESP8266WiFi.h>
  #include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

const char* PARAM_INPUT_1 = "state";

const int output = 2;
const int buttonPin = 4;

// Variables will change:
int ledState = LOW;          // the current state of the output pin
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin

// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>ESP Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 3.0rem;}
    p {font-size: 3.0rem;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
    .switch {position: relative; display: inline-block; width: 120px; height: 68px} 
    .switch input {display: none}
    .slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 34px}
    .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px}
    input:checked+.slider {background-color: #2196F3}
    input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
  </style>
</head>
<body>
  <h2>ESP Web Server</h2>
  %BUTTONPLACEHOLDER%
<script>function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?state=1", true); }
  else { xhr.open("GET", "/update?state=0", true); }
  xhr.send();
}

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var inputChecked;
      var outputStateM;
      if( this.responseText == 1){ 
        inputChecked = true;
        outputStateM = "On";
      }
      else { 
        inputChecked = false;
        outputStateM = "Off";
      }
      document.getElementById("output").checked = inputChecked;
      document.getElementById("outputState").innerHTML = outputStateM;
    }
  };
  xhttp.open("GET", "/state", true);
  xhttp.send();
}, 1000 ) ;
</script>
</body>
</html>
)rawliteral";

// Replaces placeholder with button section in your web page
String processor(const String& var){
  //Serial.println(var);
  if(var == "BUTTONPLACEHOLDER"){
    String buttons ="";
    String outputStateValue = outputState();
    buttons+= "<h4>Output - GPIO 2 - State <span id=\"outputState\"></span></h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"output\" " + outputStateValue + "><span class=\"slider\"></span></label>";
    return buttons;
  }
  return String();
}

String outputState(){
  if(digitalRead(output)){
    return "checked";
  }
  else {
    return "";
  }
  return "";
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

  pinMode(output, OUTPUT);
  digitalWrite(output, LOW);
  pinMode(buttonPin, INPUT);
  
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print ESP Local IP Address
  Serial.println(WiFi.localIP());

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });

  // Send a GET request to <ESP_IP>/update?state=<inputMessage>
  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage;
    String inputParam;
    // GET input1 value on <ESP_IP>/update?state=<inputMessage>
    if (request->hasParam(PARAM_INPUT_1)) {
      inputMessage = request->getParam(PARAM_INPUT_1)->value();
      inputParam = PARAM_INPUT_1;
      digitalWrite(output, inputMessage.toInt());
      ledState = !ledState;
    }
    else {
      inputMessage = "No message sent";
      inputParam = "none";
    }
    Serial.println(inputMessage);
    request->send(200, "text/plain", "OK");
  });

  // Send a GET request to <ESP_IP>/state
  server.on("/state", HTTP_GET, [] (AsyncWebServerRequest *request) {
    request->send(200, "text/plain", String(digitalRead(output)).c_str());
  });
  // Start server
  server.begin();
}
  
void loop() {
  // read the state of the switch into a local variable:
  int reading = digitalRead(buttonPin);

  // check to see if you just pressed the button
  // (i.e. the input went from LOW to HIGH), and you've waited long enough
  // since the last press to ignore any noise:

  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state:

    // if the button state has changed:
    if (reading != buttonState) {
      buttonState = reading;

      // only toggle the LED if the new button state is HIGH
      if (buttonState == HIGH) {
        ledState = !ledState;
      }
    }
  }

  // set the LED:
  digitalWrite(output, ledState);

  // save the reading. Next time through the loop, it'll be the lastButtonState:
  lastButtonState = reading;
}

View raw code

You just need to enter your network credentials (SSID and password) and the web server will work straight away. The code is compatible with both the ESP32 and ESP8266 boards and controls GPIO 2 – you can change the code to control any other GPIO.

How the Code Works

We’ve already explained in great details how web servers like this work in previous tutorials (DHT Temperature Web Server), so we’ll just take a look at the relevant parts for this project.

Network Credentials

As said previously, you need to insert your network credentials in the following lines:

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Button State and Output State

The ledState variable holds the LED output state. For default, when the web server starts, it is LOW.

int ledState = LOW; // the current state of the output pin

The buttonState and lastButtonState are used to detect whether the pushbutton was pressed or not.

int buttonState;            // the current reading from the input pin
int lastButtonState = LOW;  // the previous reading from the input pin

Button (web server)

We didn’t include the HTML to create the button on the the index_html variable. That’s because we want to be able to change it depending on the current LED state that can also be changed with the pushbutton.

So, we’ve create a placeholder for the button %BUTTONPLACEHOLDER% that will be replaced with HTML text to create the button later on the code (this is done in the processor() function).

<h2>ESP Web Server</h2>
%BUTTONPLACEHOLDER%

processor()

The processor() function replaces any placeholders on the HTML text with actual values. First, it checks whether the HTML texts contains any placeholders %BUTTONPLACEHOLDER%.

if(var == "BUTTONPLACEHOLDER"){

Then, call the outputState() function that returns the current output state. We save it in the outputStateValue variable.

String outputStateValue = outputState();

After that, use that value to create the HTML text to display the button with the right state:

buttons+= "<h4>Output - GPIO 2 - State <span id=\"outputState\"><span></h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"output\" " + outputStateValue + "><span class=\"slider\"></span></label>";

HTTP GET Request to Change Output State (JavaScript)

When you press the button, the toggleCheckbox() function is called. This function will make a request on different URLs to turn the LED on or off.

function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?state=1", true); }
  else { xhr.open("GET", "/update?state=0", true); }
  xhr.send();
}

To turn on the LED, it makes a request on the /update?state=1 URL:

if(element.checked){ xhr.open("GET", "/update?state=1", true); }

Otherwise, it makes a request on the /update?state=0 URL.

HTTP GET Request to Update State (JavaScript)

To keep the output state updated on the web server, we call the following function that makes a new request on the /state URL every second.

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var inputChecked;
      var outputStateM;
      if( this.responseText == 1){ 
        inputChecked = true;
        outputStateM = "On";
      }
      else { 
        inputChecked = false;
        outputStateM = "Off";
      }
      document.getElementById("output").checked = inputChecked;
      document.getElementById("outputState").innerHTML = outputStateM;
    }
  };
  xhttp.open("GET", "/state", true);
  xhttp.send();
}, 1000 ) ;

Handle Requests

Then, we need to handle what happens when the ESP32 or ESP8266 receives requests on those URLs.

When a request is received on the root / URL, we send the HTML page as well as the processor.

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

The following lines check whether you received a request on the /update?state=1 or /update?state=0 URL and changes the ledState accordingly.

server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
  String inputMessage;
  String inputParam;
  // GET input1 value on <ESP_IP>/update?state=<inputMessage>
  if (request->hasParam(PARAM_INPUT_1)) {
    inputMessage = request->getParam(PARAM_INPUT_1)->value();
    inputParam = PARAM_INPUT_1;
    digitalWrite(output, inputMessage.toInt());
    ledState = !ledState;
  }
  else {
    inputMessage = "No message sent";
    inputParam = "none";
  }
  Serial.println(inputMessage);
  request->send(200, "text/plain", "OK");
});

When a request is received on the /state URL, we send the current output state:

server.on("/state", HTTP_GET, [] (AsyncWebServerRequest *request) {
  request->send(200, "text/plain", String(digitalRead(output)).c_str());
});

loop()

In the loop(), we debounce the pushbutton and turn the LED on or off depending on the value of the ledState variable.

digitalWrite(output, ledState);

Demonstration

Upload the code to your ESP32 or ESP8266 board.

Then, open the Serial Monitor at a baud rate of 115200. Press the on-board EN/RST button to get is IP address.

ESP32 ESP8266 NodeMCU Web Server IP Address Arduino IDE Serial Monitor

Open a browser on your local network, and type the ESP IP address. You should have access to the web server as shown below.

ESP32 ESP8266 NodeMCU Control Outputs with Web Server and a Physical Button Simultaneously Turn Off

You can toggle the button on the web server to turn the LED on.

ESP32 ESP8266 NodeMCU Control Outputs with Web Server and a Physical Button Simultaneously Turn On

You can also control the same LED with the physical pushbutton. Its state will always be updated automatically on the web server.

ESP32 Input Output Button Pressed LED On Arduino IDE

Watch the next quick video for a live demonstration:


沒有留言:

張貼留言

Messaging API作為替代方案

  LINE超好用功能要沒了!LINE Notify明年3月底終止服務,有什麼替代方案? LINE Notify將於2025年3月31日結束服務,官方建議改用Messaging API作為替代方案。 //CHANNEL_ACCESS_TOKEN = 'Messaging ...