ESP32/ESP8266: Управление выходами через веб-сервер и физическую кнопку одновременно

В этом руководстве показано, как управлять выходами ESP32 или ESP8266 с помощью веб-сервера и физической кнопки одновременно. Состояние выхода обновляется на веб-странице независимо от того, было ли оно изменено через физическую кнопку или веб-сервер.

ESP32 ESP8266 NodeMCU: Управление выходами через веб-сервер и физическую кнопку одновременно Arduino IDE

Рекомендуемое чтение: Ввод данных через HTML-форму на веб-сервере ESP32/ESP8266

Платы ESP32/ESP8266 будут программироваться с помощью Arduino IDE. Убедитесь, что у вас установлены эти платы:

Обзор проекта

Давайте кратко рассмотрим, как работает проект.

ESP32 ESP8266 NodeMCU Веб-сервер с физической кнопкой - обзор проекта
  • ESP32 или ESP8266 размещает веб-сервер, который позволяет управлять состоянием выхода;

  • Текущее состояние выхода отображается на веб-сервере;

  • ESP также подключён к физической кнопке, которая управляет тем же выходом;

  • Если вы измените состояние выхода с помощью физической кнопки, его текущее состояние также обновится на веб-сервере.

Таким образом, этот проект позволяет управлять одним и тем же выходом с помощью веб-сервера и кнопки одновременно. Всякий раз, когда состояние выхода изменяется, веб-сервер обновляется.

Схема подключения

Прежде чем продолжить, вам нужно собрать схему со светодиодом и кнопкой. Мы подключим светодиод к GPIO 2, а кнопку к GPIO 4.

Необходимые компоненты

Вот список компонентов, необходимых для сборки схемы:

Вы можете использовать ссылки выше или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все компоненты для ваших проектов по лучшей цене!

Схема для ESP32

ESP32 схема подключения светодиода и кнопки

Схема для ESP8266 NodeMCU

ESP8266 NodeMCU схема подключения светодиода и кнопки

Установка библиотек – Async Web Server

Для создания веб-сервера вам необходимо установить следующие библиотеки:

Вы можете установить эти библиотеки в менеджере библиотек Arduino IDE. Перейдите в Sketch > Include Library > Manage Libraries и найдите библиотеки по названию.

Код веб-сервера ESP

Скопируйте следующий код в вашу Arduino IDE.

/*********
  Rui Santos & Sara Santos - Random Nerd Tutorials
  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").innerText = 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(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;
}

Просмотреть исходный код

Вам нужно только ввести свои сетевые учётные данные (SSID и пароль), и веб-сервер сразу заработает. Код совместим с платами ESP32 и ESP8266 и управляет GPIO 2 – вы можете изменить код для управления любым другим GPIO.

Как работает код

Мы уже подробно объясняли, как работают подобные веб-серверы в предыдущих руководствах (Веб-сервер температуры DHT), поэтому рассмотрим только соответствующие части для этого проекта.

Сетевые учётные данные

Как было сказано ранее, вам нужно вставить свои сетевые учётные данные в следующие строки:

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

Состояние кнопки и состояние выхода

Переменная ledState хранит состояние выхода светодиода. По умолчанию, при запуске веб-сервера, оно равно LOW.

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

Переменные buttonState и lastButtonState используются для определения того, была ли нажата кнопка или нет.

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

Кнопка (веб-сервер)

Мы не включили HTML для создания кнопки в переменную index_html. Это потому, что мы хотим иметь возможность изменять её в зависимости от текущего состояния светодиода, которое также может быть изменено физической кнопкой.

Поэтому мы создали заполнитель для кнопки %BUTTONPLACEHOLDER%, который будет заменён HTML-текстом для создания кнопки далее в коде (это делается в функции processor()).

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

processor()

Функция processor() заменяет все заполнители в HTML-тексте фактическими значениями. Сначала она проверяет, содержит ли HTML-текст заполнитель %BUTTONPLACEHOLDER%.

if(var == "BUTTONPLACEHOLDER"){

Затем вызывает функцию outputState(), которая возвращает текущее состояние выхода. Мы сохраняем его в переменной outputStateValue.

String outputStateValue = outputState();

После этого использует это значение для создания HTML-текста для отображения кнопки с правильным состоянием:

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-запрос для изменения состояния выхода (JavaScript)

Когда вы нажимаете кнопку, вызывается функция toggleCheckbox(). Эта функция отправляет запрос на различные URL для включения или выключения светодиода.

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();
}

Для включения светодиода отправляется запрос на URL /update?state=1:

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

В противном случае отправляется запрос на URL /update?state=0.

HTTP GET-запрос для обновления состояния (JavaScript)

Для поддержания актуального состояния выхода на веб-сервере вызывается следующая функция, которая делает новый запрос на URL /state каждую секунду.

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").innerText = outputStateM;
    }
  };
  xhttp.open("GET", "/state", true);
  xhttp.send();
}, 1000 ) ;

Обработка запросов

Далее нам нужно обработать то, что происходит, когда ESP32 или ESP8266 получает запросы на эти URL.

Когда получен запрос на корневой URL /, мы отправляем HTML-страницу вместе с процессором.

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

Следующие строки проверяют, получен ли запрос на URL /update?state=1 или /update?state=0, и соответственно изменяют ledState.

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");
});

Когда получен запрос на URL /state, мы отправляем текущее состояние выхода:

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

loop()

В loop() мы выполняем дебаунс кнопки и включаем или выключаем светодиод в зависимости от значения переменной ledState.

digitalWrite(output, ledState);

Демонстрация

Загрузите код на вашу плату ESP32 или ESP8266.

Затем откройте монитор порта (Serial Monitor) со скоростью 115200 бод. Нажмите встроенную кнопку EN/RST, чтобы получить IP-адрес.

ESP32 ESP8266 NodeMCU IP-адрес в мониторе порта Arduino IDE

Откройте браузер в вашей локальной сети и введите IP-адрес ESP. Вы должны получить доступ к веб-серверу, как показано ниже.

ESP32 ESP8266 NodeMCU Управление выходами через веб-сервер и физическую кнопку - выключено

Вы можете переключить кнопку на веб-сервере, чтобы включить светодиод.

ESP32 ESP8266 NodeMCU Управление выходами через веб-сервер и физическую кнопку - включено

Вы также можете управлять тем же светодиодом с помощью физической кнопки. Его состояние всегда будет автоматически обновляться на веб-сервере.

ESP32 нажата кнопка - светодиод включён Arduino IDE

Заключение

В этом руководстве вы узнали, как управлять выходами ESP32/ESP8266 с помощью веб-сервера и физической кнопки одновременно. Состояние выхода всегда обновляется независимо от того, было ли оно изменено через веб-сервер или с помощью физической кнопки.

Другие проекты, которые могут вас заинтересовать:

Узнайте больше об ESP32 и ESP8266 с помощью наших ресурсов:

Спасибо за чтение.

Примечание

Это перевод статьи ESP32/ESP8266: Control Outputs with Web Server and a Physical Button Simultaneously от Random Nerd Tutorials, авторы Rui Santos и Sara Santos.