ESP32/ESP8266: Управление выходами через веб-сервер и физическую кнопку одновременно
В этом руководстве показано, как управлять выходами ESP32 или ESP8266 с помощью веб-сервера и физической кнопки одновременно. Состояние выхода обновляется на веб-странице независимо от того, было ли оно изменено через физическую кнопку или веб-сервер.
Рекомендуемое чтение: Ввод данных через HTML-форму на веб-сервере ESP32/ESP8266
Платы ESP32/ESP8266 будут программироваться с помощью Arduino IDE. Убедитесь, что у вас установлены эти платы:
Установка платы ESP32 в Arduino IDE (Windows, Mac OS X и Linux)
Установка платы ESP8266 в Arduino IDE (Windows, Mac OS X, Linux)
Обзор проекта
Давайте кратко рассмотрим, как работает проект.
ESP32 или ESP8266 размещает веб-сервер, который позволяет управлять состоянием выхода;
Текущее состояние выхода отображается на веб-сервере;
ESP также подключён к физической кнопке, которая управляет тем же выходом;
Если вы измените состояние выхода с помощью физической кнопки, его текущее состояние также обновится на веб-сервере.
Таким образом, этот проект позволяет управлять одним и тем же выходом с помощью веб-сервера и кнопки одновременно. Всякий раз, когда состояние выхода изменяется, веб-сервер обновляется.
Схема подключения
Прежде чем продолжить, вам нужно собрать схему со светодиодом и кнопкой. Мы подключим светодиод к GPIO 2, а кнопку к GPIO 4.
Необходимые компоненты
Вот список компонентов, необходимых для сборки схемы:
ESP32 (читайте Лучшие платы ESP32) или ESP8266
Вы можете использовать ссылки выше или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все компоненты для ваших проектов по лучшей цене!
Схема для ESP32
Схема для ESP8266 NodeMCU
Установка библиотек – Async Web Server
Для создания веб-сервера вам необходимо установить следующие библиотеки:
ESP32: установите библиотеки ESPAsyncWebServer и AsyncTCP (от ESP32Async).
ESP8266: установите библиотеки ESPAsyncWebServer и ESPAsyncTCP (от ESP32Async).
Вы можете установить эти библиотеки в менеджере библиотек 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-адрес.
Откройте браузер в вашей локальной сети и введите IP-адрес ESP. Вы должны получить доступ к веб-серверу, как показано ниже.
Вы можете переключить кнопку на веб-сервере, чтобы включить светодиод.
Вы также можете управлять тем же светодиодом с помощью физической кнопки. Его состояние всегда будет автоматически обновляться на веб-сервере.
Заключение
В этом руководстве вы узнали, как управлять выходами ESP32/ESP8266 с помощью веб-сервера и физической кнопки одновременно. Состояние выхода всегда обновляется независимо от того, было ли оно изменено через веб-сервер или с помощью физической кнопки.
Другие проекты, которые могут вас заинтересовать:
ESP32/ESP8266 Веб-сервер: Управление выходами с помощью кнопки без фиксации
Веб-сервер ESP32 с использованием SPIFFS (файловая система SPI Flash)
Узнайте больше об ESP32 и ESP8266 с помощью наших ресурсов:
Спасибо за чтение.
Примечание
Это перевод статьи ESP32/ESP8266: Control Outputs with Web Server and a Physical Button Simultaneously от Random Nerd Tutorials, авторы Rui Santos и Sara Santos.