В этом руководстве рассмотрим, как управлять выходами ESP32 или ESP8266 одновременно с помощью веб-сервера и физической кнопки. Состояние вывода обновляется на веб-странице независимо от того, было ли оно изменено с помощью кнопки или сервера.
Платы ESP32 / ESP8266 будут программироваться с использованием Arduino IDE. Найти информацию о том, как установить ESP32 в Arduino IDE вы можете здесь:
Обзор проекта
Давайте посмотрим, как должен работать проект.
- ESP32 или ESP8266 создает веб-сервер, который позволяет управлять выводами;
- Текущее состояние вывода отображается на веб-сервере;
- ESP подключен к физической кнопке, которая управляет тем же выходом;
- При изменении состояния вывода с помощью кнопки, его текущее состояние также обновляется и на веб-сервере.
Таким образом, здесь мы покажем, как управлять одним и тем же выводом, используя веб-сервер и кнопку одновременно. Всякий раз, когда состояние вывода изменяется, веб-сервер обновляется.
Схема
Прежде чем продолжить, необходимо собрать схему со светодиодом и кнопкой. Подключим светодиод к GPIO 2, а кнопку к GPIO 4.
Требуемые детали
Вот список деталей, необходимых для сборки схемы:
Схема ESP32
Схема ESP8266 NodeMCU
Установка библиотек - AsyncWebServer
Вам необходимо установить следующие библиотеки:
- для ESP32: установите библиотеки ESPAsyncWebServer и AsyncTCP .
- для ESP8266: – ESPAsyncWebServer и ESPAsyncTCP
Эти библиотеки недоступны для установки через менеджер библиотек Arduino, поэтому вам необходимо перейти в Скетч > Подключить библиотеку > Добавить .ZIP библиотеку и выбрать указанные библиотеки.
Код веб-сервера для ESP
Скопируйте следующий код в свою Arduino IDE.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
/********* 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. *********/ // Подключаем библиотеки #ifdef ESP32 #include <WiFi.h> #include <AsyncTCP.h> #else #include <ESP8266WiFi.h> #include <ESPAsyncTCP.h> #endif #include <ESPAsyncWebServer.h> // Укажите свой учетные данные сети const char* ssid = "ЗАМЕНИТЕ_СВОИМ_SSID"; const char* password = "УКАЖИТЕ_ПАРОЛЬ"; const char* PARAM_INPUT_1 = "state"; const int output = 2; const int buttonPin = 4; // Переменные int ledState = LOW; // текущее состояние выхода int buttonState; // состояние кнопки int lastButtonState = LOW; // предыдущее состояние кнопки // следующие переменные будут типа unsigned long потому, что время // в миллисекундах достаточно быстро выйдет за пределы типа int unsigned long lastDebounceTime = 0; // время, когда состояние пина изменилось последний раз unsigned long debounceDelay = 50; // задержка для защиты от дребезга контактов; увеличьте, если наблюдается самопроизвольное мигание на выходе // Создаем сервер (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"; // Заменяет заполнитель на кнопку на веб-странице 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.begin(115200); pinMode(output, OUTPUT); digitalWrite(output, LOW); pinMode(buttonPin, INPUT); // Подключаемся к Wi-Fi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi.."); } // Выводим IP адрес платы Serial.println(WiFi.localIP()); // Маршрут для стартовой веб-страницы server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/html", index_html, processor); }); // Отправляем запрос GET <ESP_IP>/update?state=<inputMessage> server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) { String inputMessage; String inputParam; // получаем значение input1 <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"); }); // Отправляем запрос GET на <ESP_IP>/state server.on("/state", HTTP_GET, [] (AsyncWebServerRequest *request) { request->send(200, "text/plain", String(digitalRead(output)).c_str()); }); // Запускаем сервер server.begin(); } void loop() { // считываем состояние переключателя в локальную переменную: int reading = digitalRead(buttonPin); // подождите немного и проверьте не изменился ли сигнал // (с LOW на HIGH) с момента последнего нажатия чтобы исключить дребезг: // Если состояние изменилось из-за дребезга или случайного нажатия: if (reading != lastButtonState) { // сбрасываем таймер lastDebounceTime = millis(); } if ((millis() - lastDebounceTime) > debounceDelay) { // вне зависимости от действительного состояния, //если оно длится больше задержки, то принимаем его за текущее: // если состояние кнопки изменилось: if (reading != buttonState) { buttonState = reading; // включаем светодиод только если сигнал HIGH if (buttonState == HIGH) { ledState = !ledState; } } } // выводим состояние светодиода: digitalWrite(output, ledState); // сохраняем значение в lastButtonState: lastButtonState = reading; } |
Вам нужно ввести свои данные сети (SSID и пароль), и веб-сервер заработает. Код совместим как с платами ESP32, так и с ESP8266 и управляет GPIO 2 – вы также можете изменить код для управления любым другим GPIO.
Как работает код?
Как было сказано ранее, вам необходимо ввести свои данные сети в следующие строки:
1 2 |
const char * ssid = "ЗАМЕНИТЕ_СВОИМ_SSID"; const char * пароль = "УКАЖИТЕ_ПАРОЛЬ"; |
Состояние кнопки и состояние выхода
Переменная ledState хранит состояние выхода светодиода. По умолчанию, когда веб-сервер запускается, это LOW.
1 2 3 4 5 6 7 |
int ledState = LOW; // текущее состояние выхода ButtonState и lastButtonState используются для определения нажатия кнопки. int buttonState; // текущее состояние пина int lastButtonState = LOW; // предыдущее состояние пина |
Кнопка (на веб-странице)
Мы не создали кнопку на веб-странице с помощью HTML потому, что требуется изменять состояние пина с помощью физической кнопки.
Итак, мы создали заполнитель для кнопки %BUTTONPLACEHOLDER%, который будет заменен текстом HTML для создания кнопки на странице в функции processor().
1 2 3 |
<h2>ESP Web Server</h2> %BUTTONPLACEHOLDER% |
processor()
Функция processor () заменяет любые заполнители в тексте HTML фактическими значениями. Во-первых, проверяем, содержит ли текст HTML какие-либо заполнители %BUTTONPLACEHOLDER%.
1 |
if (var == "BUTTONPLACEHOLDER") { |
Затем вызываем функцию outputState (), которая возвращает текущее состояние пина. Сохраняем его в переменной outputStateValue.
1 |
String outputStateValue = outputState(); |
После этого используем это значение, чтобы преобразовать его в HTML для отображения кнопки в правильном текущем состоянии:
1 2 3 |
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-запрос на предмет изменения состояния вывода (JavaScript)
Когда мы нажимаем кнопку, вызывается функция toggleCheckbox (). Эта функция обращается к нескольким URL-адресам для изменения состояния светодиода.
1 2 3 4 5 6 7 8 9 10 11 |
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(); } |
Чтобы включить светодиод, он делает запрос по адресу /update? State = 1 :
1 |
if(element.checked){ xhr.open("GET", "/update?state=1", true); } |
В противном случае он делает запрос по URL-адресу /update? State = 0 .
HTTP-запрос для обновления состояния выхода (JavaScript)
Чтобы обновить состояние выхода на сервере, мы вызываем следующую функцию, которая каждую секунду делает новый запрос по URL-адресу /state .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
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 ) ; |
Обработка запросов
Теперь нам нужно определить, что будет происходить, при получении запросов по этим URL-адресам.
Когда запрос получен по корневому адресу URL-адресу, отправляется HTML-страница вместе с processor() .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
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"); }); |
Когда запрос получен по URL-адресу /state , мы отправляем текущее состояние выхода:
1 2 3 4 5 |
server.on("/state", HTTP_GET, [] (AsyncWebServerRequest *request) { request->send(200, "text/plain", String(digitalRead(output)).c_str()); }); |
loop()
В цикле loop () мы нажимаем кнопку и включаем или выключаем светодиод в зависимости от значения переменной ledState.
1 |
digitalWrite(output, ledState); |
Демонстрация
Загрузите код на свою плату ESP32 или ESP8266.
Затем откройте монитор порта со скоростью 115200 бод. Нажмите кнопку EN / RST, чтобы получить IP-адрес платы.
Откройте браузер и введите IP-адрес ESP. Должна открыться стартовая страница.
Нажмите кнопку на странице, чтобы загорелся светодиод.
Вы также можете управлять этим же светодиодом с помощью физической кнопки. Его состояние всегда будет автоматически обновляться на веб-сервере.
Заключение
В этом руководстве вы узнали, как управлять выходами ESP32 / ESP8266 с помощью веб-сервера и физической кнопки. Состояние вывода всегда обновляется независимо от того, было ли оно изменено с веб-страницы или с помощью физической кнопки.
Другие проекты, которые могут вам понравиться:
Вопросы по прошивке и работе с кодом лучше писать напрямую автору в комментариях к статье (на англ. языке)
15 комментариев. Оставить новый
Ярослав, Благодарю Вас!
Материал раскрыт.
Программа работает без заморочек.
Как сделать темную тему?
Есть у кого на две кнопки?
Здравствуйте. Как здесь добавить реле и веб кнопки и физические кнопки ?
Здравствуйте! Реле можно подключить вместо светодиода к пину GPIO2(D4) на контроллере. В таком случае код можно оставить как есть. Если вы хотите подключить больше оного реле с кнопкой, то вы можете воспользоваться свободными пинами на микроконтроллере и подключить к ним дополнительные кнопки и реле по аналогии с первым, тогда вам также будет необходимо добавить в скетч код для обработки нажатий с других кнопок и управления реле.
Вот теперь вопрос как добавить, вот проблема. Посидел и у меня кнопка дублируется. Помогите, 5 кнопок надо 🙏. Web 5 шт., 5 физических. Хотя бы вторую добавить а я там сам дальше разберусь. )))
Один проект у вас удобно написан, но нет возможности добавить физические кнопки.
Если можно там дописать скетч.
#define NUM_RELAYS 5
// Присваиваем каждому реле свой выход
int relayGPIOs[NUM_RELAYS] = {2, 26…, …
Это довольно трудная задача для объяснения в текстовом формате. Если у вас возникли трудности с кодом, мы бы порекомендовали предварительно изучить основы программирования на платформе Arduino. Это поможет вам лучше понимать ваш проект.
Здравствуйте.
Воспроизвел ваш пример, и у меня, почему-то сервер стартует с кнопкой в статусе “checked”.
Т.е. на страничке, сразу после старта сервера, кнопка уже нажата.
В результате, у меня при нажатии кнопки она отключается, а светодиод, наоборот, включается.
Добрый день, возможно вы неправильно установили начальные значения переменных состояния:
int ledState = LOW; // текущее состояние выхода
int buttonState; // состояние кнопки
int lastButtonState = LOW; // предыдущее состояние кнопки
Я не изменял ваш код.
Неправильно объяснил проблему.
Кнопка на страничке в правильном статусе – unchecked.
Я использовал её для управления встроенным светодиодом pin 2 по нумерации в коде.
Так вот почему-то сервер стартует с горящим встроенным светодиодом, и, соответственно: unchecked – светодиод горит, а при нажатии – тухнет. Не нашел нигде в разделе setup где его включают и почему он сразу горит.
В таком случае, возможно, на вашей плате ко второму пину подключен катод светодиода, а не анод. Тогда, чтобы включить светодиод, вам нужно установить на пине 2 уровень низкого напряжения, а чтобы выключить — высокого.
Чтобы это исправить, замените в коде следующие строки:
–
int ledState = HIGH;
— установите начальное значение как HIGH– в функции
outputState()
замените условие наif(!digitalRead(output))
, тогда оно будет истинно, если на пине будет уровень LOW, и ложно в противном случае– в функции
setup()
установите начальное состояние в HIGH, чтобы отключить светодиод:digitalWrite(output, HIGH);
supportvoltiq,
Не помогло.
Я заметил, что сразу после перезагрузки модуля светодиод не горит, и загорается в момент выдачи IP в serial monitor.
Может быть вы неправильно подключили кнопку, поэтому контроллер считывает с неё логическую единицу и включает светодиод. Проверьте, соответствует ли ваше подключение нашей схеме.