ESP32:Примеры/Веб-сервер на базе ESP32: управление выходными контактами

Материал из Онлайн справочника
Версия от 19:51, 12 января 2019; Myagkij (обсуждение | вклад) (→‎Код)
(разн.) ← Предыдущая версия | Текущая версия (разн.) | Следующая версия → (разн.)
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Черновик


Веб-сервер на базе ESP32: управление выходными контактами

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

Что будет делать наш веб-сервер:

  • Наш веб-сервер будет управлять двумя светодиодами, подключенными к контактам GPIO26 и GPIO27 на плате ESP32;
  • Чтобы получить доступ к веб-серверу на базе ESP32, в браузере устройства, подключенного к локальной сети, нужно будет написать IP-адрес этой ESP32;
  • Кликая на кнопки на веб-странице, мы будем менять состояния светодиодов;

Справочная информация

Введение в веб-серверы

В этом Разделе мы разберемся, что такое веб-сервер и как работает ESP32, когда выполняет функцию веб-сервера. Мы рассмотрим ряд терминов, которые вы, возможно, уже слышали ранее, но плохо понимаете, что они значат.

Запрос/ответ

«Запрос/ответ» – это схема обмена сообщениями, при которой инициатор запроса отправляет сообщение-запрос системе-ответчику, которая получает и обрабатывает этот запрос, а затем отправляет ответное сообщение. Это простая и эффективная схема обмена сообщениями, и особенно – для архитектур типа «клиент-сервер».

Клиент-сервер

Когда вы вписываете URL в адресную строку браузера, а затем жмете на  ↵ Enter , то это значит, что вы (то есть клиент) отправляете запрос серверу при помощи протокола HTTP (англ. «hypertext transfer protocol», т.е. «протокол передачи гипертекста»). Получив запрос, сервер отправляет ответ (тоже по HTTP), в результате чего вы видите в браузере запрошенную веб-страницу. Клиенты и серверы коммуницируют друг с другом по компьютерной сети.

Хост-сервер

На хосте-сервере работает одна или несколько серверных программ, позволяющих ему делиться своими ресурсами с клиентами. То есть веб-сервер можно представить как некое ПО, которое прослушивает входящие HTTP-запросы, а также отправляет ответы после получения запросов.

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

IP-адрес

IP-адрес – это "числовая подпись", которая присвоена каждому устройству, подключенному к компьютерной сети. Следовательно, обратившись к нужному IP-адресу, мы можем отправить сетевому устройству любую информацию. У вашей ESP32 тоже есть свой IP-адрес.

Веб-сервер на базе ESP32

Давайте разберем практический -пример, где ESP32 служит веб-сервером в локальной сети.

Как правило, веб-сервер на базе ESP32 в локальной сети выглядит примерно так: это ESP32, работающая в режиме веб-сервера и подключенная к роутеру по WiFi. Ваши компьютер, смартфон или планшет тоже подключены к роутеру (через WiFi или Ethernet-кабель). Таким образом, ESP32 и ваши устройства, находятся в одной и той же сети.

Когда вы пишите в браузере IP-адрес своей ESP32, а затем жмете на  ↵ Enter , вы отправляете ей HTTP-запрос. После этого ESP32, как правило, отвечает вам при помощи сообщения-ответа, содержащего значение, считанное с датчика, и HTML-код, который нужно показать на веб-странице, или какие-то другие запрограммированные вами данные.

Пример веб-сервера

Итак, как же объединить все это, чтобы сделать IoT-проект на базе ESP32? У вашей ESP32 есть GPIO-контакты, при помощи которых к ней можно подключить различные устройства, а затем управлять ими через интернет.

Скриншот ниже – пример веб-сервера для управления устройством вывода данных. Здесь изображено, что будет, если ввести в браузере IP-адрес своей ESP32, а потом нажать на  ↵ Enter .

Если нажать на кнопку «ON», URL поменяется – к концу IP-адреса ESP32 будет приписано «/on». ESP32 получает запрос на этот URL, смотрит, что нужно сделать при получении такого URL, и соответствующим образом меняет значение светодиода.

Если нажать на кнопку «OFF», к ESP32 будет сделан новый запрос – к концу URL будет приписано «/off». ESP32 снова посмотрит, что нужно сделать при этом запросе, а затем выключит светодиод.

Этот же принцип можно использовать и для управления многими другими устройствами вывода данных с помощью ESP32.

Итого

Это был краткий обзор того, по каким принципам работает веб-сервер на базе ESP32.

Основы HTML и CSS

В этом примере мы научимся создавать веб-сервер на базе ESP32, с помощью которого можно управлять несколькими устройствами вывода данных. После того, как клиент (т.е. вы) обращается к IP-адресу ESP32, в браузере появляется веб-страница с некоторым контентом – это происходит благодаря тому, что после запроса клиента (т.е. вашего запроса) ESP32 отправляет клиенту (т.е. вам) некоторый HTML-код, с помощью которого и генерируется эта веб-страница. Вы можете модифицировать то, как она выглядит, поменяв HTML-код, который ESP32 отправляет в браузер. Для того, чтобы сделать это, необходимо некоторое знание языков HTML и CSS.

В этом Разделе мы шаг за шагом создадим веб-страницу из этого примера, чтобы вам было проще понять, какие изменения нужно сделать, чтобы страница выглядела, как вам нужно. Затем мы добавим этот HTML-код в код для IDE Arduino, чтобы ESP32 показала эту веб-страницу после вашего запроса к ее IP-адресу.

Введение в HTML

Аббревиатура HTML расшифровывается как «HyperText Markup Language» («язык гипертекстовой разметки»), и это самый распространенный язык разметки для создания веб-страниц. Веб-браузеры умеют читать HTML-файлыHTML-теги сообщают веб-браузеру, как он должен показать контент на веб-странице. То, как работают эти HTML-теги, мы разберем в примере ниже.

Создаем базовую структуру HTML-документа

Фрагмент кода ниже демонстрирует общую структуру HTML-документа:

<!DOCTYPE html>
<html>
<head>
</head>
<body>
</body>
</html>

Первой строчкой любого HTML-документа всегда является <!DOCTYPE html>. Она говорит браузеру, что этот документ – HTML-файл.

Структура веб-страницы должна находиться между тегами <html> и </html>. Тег <html> указывает на начало веб-страницы, а </html> – на ее конец.

HTML-документ состоит из двух главных частей: заголовка и тела. Заголовок обозначается тегами <head> и </head>, а тело – тегами <body> и </body>.

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

Откройте текстовый редактор (можно использовать любой) и скопируйте туда HTML-текст выше. Сохраните этот файл как «index.html». Откройте браузер и переместите в него этот HTML-файл. В результате должна открыться пустая страница, потому что в этот HTML-документ пока не было добавлено никакого контента.

Примечание: Обратите внимание, что файл должен называться не «index.html.txt», а «index.html».

Тег title (заголовок HTML-документа)

Этим тегом задается текст, показываемый во вкладке веб-браузера. Этот текст должен располагаться между тегами <title> и </title>, которые в свою очередь должны находиться между тегами <head> и </head>. Итак, давайте зададим заголовок для нашей веб-страницы, вписав текст между тегами <title> и </title>, как показано во фрагменте кода ниже:

<!DOCTYPE html>
<html>
<head>
 <title>ESP32 Web Server</title>
</head>
<body>
</body>
</html>

Итак, в данном случае заголовком у нашего HTML-документа будет «ESP32 Web Server», но вы можете вписать сюда что хотите.

Сохраните файл «index.html» и обновите его вкладку в веб-браузере. Теперь у нее должен появиться заголовок (см. скриншот ниже).

Теги h1, h2 и т.д. (заголовки текста в документе)

Эти теги используются для того, чтобы структурировать текст на веб-странице. Тег заголовка начинается с буквы «h», после которой идет число, обозначающее важность заголовка. К примеру, теги <h1> и </h1> используются для заголовка 1 уровня (это самый важный заголовок), теги <h2> и </h2> – для заголовка 2 уровня (это второй по важности заголовок) и т.д. до заголовка 6 уровня. Эти теги текстовых заголовков должны быть между тегами <body> и </body>.

Давайте добавим один такой заголовок внутрь нашего HTML-документа (см. код ниже):

<!DOCTYPE html>
<html>
<head>
 <title>ESP32 Web Server</title>
</head>
<body>
 <h1>ESP32 Web Server</h1>
</body>
</html>

Здесь мы добавили заголовок 1 уровня с текстом «ESP32 Web Server». Рекомендуем попрактиковаться и добавить еще несколько заголовков при помощи тегов разного уровня. Сохраните файл «index.html» и обновите его вкладку в браузере. У вас должно получиться примерно следующее:

Тег p (абзац)

Этот тег используется для структурирования текста. Каждый абзац должен находиться между тегами <p> и </p>. Давайте добавим два абзаца, в которых будут показаны состояния контактов GPIO26 и GPIO27.

<!DOCTYPE html>
<html>
<head>
 <title>ESP32 Web Server</title>
</head>
<body>
 <h1>ESP32 Web Server</h1>
 <p>GPIO 26 - State</p>
 <p>GPIO 27 - State</p>
</body>
</html>

Сохраните файл «index.html» и обновите его страницу в веб-браузере. У вас должно получиться примерно следующее:

Состояния «ON» и «OFF» будут добавляться в эти абзацы при помощи переменных «output26State» и «output27State» в коде IDE Arduino.

Тег button (кнопка)

Кнопка добавляется на веб-страницу при помощи тегов <button> и </button>. Между этими тегами вписывается код, показываемый внутри кнопки. Давайте добавим по кнопке «ON» и «OFF» для каждого GPIO-контакта.

Теги <button> и </button> ставятся внутри тегов абзаца <p> и </p>.

<!DOCTYPE html>
<html>
<head>
<title>ESP32 Web Server</title>
</head>
<body>
 <h1>ESP32 Web Server</h1>
 <p>GPIO 26 - State</p>
 <p><button>ON</button></p>
 <p><button>OFF</button></p>
 <p>GPIO 27 - State</p>
 <p><button>ON</button></p>
 <p><button>OFF</button></p>
</body>
</html>

Теперь на вашей веб-странице должно появиться 4 кнопки (см. скриншот ниже).

Понажимайте на эти кнопки. Как видите, это не дает никакого эффекта, потому что у этих кнопок нет никаких гиперссылок (мы добавив их ниже).

У веб-сервера в этом примере только по одной кнопке для каждого GPIO-контакта – в зависимости от их текущего состояния. То, какую из этих двух кнопок («ON» или «OFF») нужно показать, задано в коде для IDE Arduino:

  • Если текущее состояние контакта – это «ON», на странице будет показана кнопка «OFF». ESP32 должна отправить HTML-код для отображения кнопки «OFF».
  • Если текущее состояние контакта – это «OFF», на странице будет показана кнопка «ON». ESP32 должна отправить HTML-код для отображения кнопки «ON».

Мы рассмотрим эту часть кода чуть ниже.

Тег a (гиперссылка)

HTML-ссылки называют «гиперссылками». Их можно добавить в текст, изображения, кнопки и многие другие HTML-элементы. Для добавления гиперссылки используются теги <a> и </a>. Примерно так:

<a href="url">element</a>

Между тегами <a> и </a> помещается HTML-элемент, к которому вы хотите добавить гиперссылку. Давайте для примера добавим ссылку к одной из кнопок «OFF»:

<a href="url"><button>OFF</button></a>

Атрибут «href» указывает на то, куда должна вести эта ссылка. Нам нужно, чтобы при клике на кнопку «ON» для GPIO26 пользователя перенаправляло на корневую страницу веб-сервера с припиской «.../26/on». Для этого нам нужно добавить этот URL к атрибуту «href». Вот так:

<a href="/26/on"><button>ON</button></a>

Также нам надо, чтобы при клике по кнопке «OFF» для GPIO26 пользователя перенаправляло к «.../26/off». Делаем это следующим образом:

<a href="/26/off"><button>OFF</button></a>

Вам нужно добавить соответствующие гиперссылки в каждую из кнопок. Вот так:

<!DOCTYPE html>
<html>
<head>
<title>ESP32 Web Server</title>
</head>
<body>
 <h1>ESP32 Web Server</h1>
 <p>GPIO 26 - State</p>
 <p><a href="/26/on"><button>ON</button></a></p>
 <p><a href="/26/off"><button>OFF</button></a></p>
 <p>GPIO 27 - State</p>
 <p><a href="/27/on"><button>ON</button></a></p>
 <p><a href="/27/off"><button>OFF</button></a></p>
</body>
</html>

Сохраните файл «index.html» и обновите его вкладку в веб-браузере. Внешний вид страницы не изменится, но если понажимать по кнопкам, вас переправит к заданным выше ссылкам. К примеру, при клике на кнопку «ON» для GPIO26 вас перенаправит к ссылке «/26/on»(разные браузеры на этом этапе будут реагировать по разному, некоторые будут пытаться перейти по данной ссылке и выдавать ошибку, другие никак не отреагируют).

В результате мы получим ошибку «файл не найден», т.к. этот URL не ведет ни к какому файлу. Эта проблема будет решена в коде IDE Arduino: там мы зададим, чтобы ESP32 отправляла клиенту разный HTML-код в зависимости от того, на какую кнопку кликает пользователь. Поэтому в данный момент об этой ошибке беспокоиться не нужно.

Теперь нам лишь нужно проверить, чтобы нажатия на кнопки ввели к правильным URL.

Атрибут «class»

Этот атрибут нужен для того, чтобы задать для HTML-элемента одно или более имен класса. Они используются, к примеру, чтобы задать CSS-стили. К примеру, чтобы задать стиль для группы HTML-элементов, мы можем перед этим задать для всех них одно и то же имя класса.

Чтобы присвоить HTML-элементу имя класса, понадобится следующий синтаксис:

<element class="classname">

HTML-элемент может обладать несколькими именами класса, которые отделяются друг от друга с помощью пробелов. В нашем примере мы задаем для кнопок «ON» класс «button». Для кнопок «OFF» мы задаем классы «button» и «button2». Следовательно, кнопкам «ON» и «OFF» для контакта GPIO26 понадобится следующий код:

<p><a href="/26/on"><button class="button">ON</button></a></p>
<p><a href="/26/off"><button class="button button2">OFF</button></a></p>

То же самое нужно сделать и для кнопок контакта GPIO27. Следовательно, HTML-код для всех 4 кнопок будет выглядеть следующим образом:

<!DOCTYPE html>
<html>
<head>
<title>ESP32 Web Server</title>
</head>
<body>
 <h1>ESP32 Web Server</h1>
 <p>GPIO 26 - State</p>
 <p><a href="/26/on"><button class="button">ON</button></a></p>
 <p><a href="/26/off"><button class="button
button2">OFF</button></a></p>
 <p>GPIO 27 - State</p>
 <p><a href="/27/on"><button class="button">ON</button></a></p>
 <p><a href="/27/off"><button class="button
button2">OFF</button></a></p>
</body>
</html>
Тег <meta> (метаданные)

В заголовке файла «index.html» также нужно задать тег <meta>, содержимое которого адаптирует веб-страницу под любой веб-браузер:

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

В теге <meta> задаются метаданные о HTML-документе. На самой странице они не показываются, но предоставляют браузеру полезную информацию о том, как отображать веб-контент.

Нам также нужно добавить следующую строчку:

<link rel="icon" href="data:,">

Она нужна, чтобы браузер не делал к ESP32 запросов об иконке сайта (это маленькое изображение, которое появляется во вкладке сайта в браузере).

Введение в CSS

Аббревиатура CSS означает «каскадная таблица стилей» – это язык, используемый для описания того, как HTML-элементы должны выглядеть на веб-странице. С его помощью можно описать определенную часть страницы вроде отдельного тега или группы тегов. CSS-код можно добавить в HTML-файл или в отдельный файл, на который будет ссылаться HTML-файл. В нашем случае мы добавим CSS-код напрямую в HTML-файл – так нам будет проще добавить его в IDE Arduino.

CSS-код в HTML-файле должен находиться между тегами <style> и </style>, которые в свою очередь должны находиться в заголовке HTML-документа.

Таким образом, заголовок HTML-документа будет выглядеть следующим образом:

<!DOCTYPE html>
<html>
<head>
 <title>ESP32 Web Server</title>
 <style>YOUR CSS GOES HERE<\style>
</head>

В языке CSS используются так называемые «селекторы». Они указывают на HTML-элемент, для которого нужно задать некоторый стиль. У селекторов есть свойства, а у свойств – значения.

selector {
 property: value;
}

Стиль для селектора должен находиться внутри фигурных скобок ({}). Значение свойству привязывается при помощи двоеточия (:). Каждое значение должно заканчиваться точкой с запятой (;). Каждому селектору можно задать больше одного свойства.

Задаем стиль для веб-страницы

В нашем случае HTML-элемент будет иметь следующий стиль:

html {
font-family: Helvetica;
display: inline-block;
margin: 0px auto;
text-align: center;
}

Свойства, заданные для HTML-элемента, будут применены ко всей веб-странице – помните, что контент всей вашей веб-страницы находится между тегами <html> и </html>. Семейством шрифтов делаем Helvetica, показываемым контентом – блок, отступом – 0 пикселей, а также выравниваем всю страницу по центру. В результате весь текст тоже будет выровнен по центру. Таким образом, на данном этапе наш файл «index.html» будет выглядеть следующим образом:

<!DOCTYPE html>
<html>
<head>
 <title>ESP32 Web Server</title>
 <meta name="viewport" content="width=device-width, initialscale=1">
 <link rel="icon" href="data:,">
 <style>
 html {
 font-family: Helvetica;
 display: inline-block;
 margin: 0px auto;
 text-align: center;
 }
 </style>
</head>
<body>
 <h1>ESP32 Web Server</h1>
 <p>GPIO 26 - State</p>
 <p><a href="/26/on"><button class="button">ON</button></a></p>
 <p><a href="/26/off"><button class="button
button2">OFF</button></a></p>
 <p>GPIO 27 - State</p>
 <p><a href="/27/on"><button class="button">ON</button></a></p>
 <p><a href="/27/off"><button class="button
button2">OFF</button></a></p>
</body>
</html>

Сама веб-страница будет выглядеть примерно так:

Внешний вид вашей веб-страницы можно изменить, задав свойствам какие-то другие значения. Советуем поиграться с этими значениями, чтобы понять, как их изменение влияет на внешний вид веб-страницы. О других значениях для заданных нами свойств можно узнать по ссылкам ниже:

Задаем стиль для кнопок

Выше мы задали класс «button» для кнопок «ON», а также классы «button» и «button2» для кнопок «OFF».

Чтобы выбрать элементы, относящиеся к какому-либо классу, в коде нужно написать точку (.), а после нее – имя нужного класса (см. ниже).

.button {
  background-color: #4CAF50;
  border: none;
  color: white;
  padding: 16px 40px;
  text-decoration: none;
  font-size: 30px;
  margin: 2px;
  cursor: pointer;
}

Свойство «background-color» задает фоновый цвет кнопки. Цвет можно задать с помощью его названия на английском языке (HTML распознает названия основных цветов), а также при помощи шестнадцатеричного или RGB-кода (чтобы узнать шестнадцатеричный код нужного цвета, погуглите «hexadecimal color picker»). Мы будем использовать именно шестнадцатеричный код.

В свойстве «border» ставим «none», а текст кнопки делаем белым. Свойство «padding» задает поля вокруг текста – в нашем случае это 16 и 40 пикселей. В свойстве «text-decoration» ставим «none», размером шрифта делаем 30 пикселей, а в свойстве «margin» ставим 2 пикселя. Курсором делаем «pointer» – в результате при перемещении мышки над кнопкой курсор превращался в «ладонь».

Для всех этих свойств можно задать и другие значения. Чтобы найти эти значения, забейте в Google название нужного свойства, а затем фразу «значения CSS». К примеру, чтобы найти значения для свойства «cursor», вбейте в Google «cursor значения CSS».

Вставьте CSS-код выше в свой файл «index.html» между тегами <style> и </style>. После этого сохраните файл «index.html» и обновите его вкладку в браузере. В результате у вас должно получиться примерно следующее:

Кнопка «OFF» принадлежит к классам «button» и «button2» и потому будет иметь свойства обоих этих классов. Теперь давайте зададим стиль для класса «button2»:

.button2 {
 background-color: #555555
}

В этом фрагменте мы лишь задаем серый цвет для кнопки «OFF». Вы, разумеется, вместо серого цвета можете выбрать любой другой.

Добавьте этот фрагмент с CSS-стилем для класса «button2» в HTML-файл, сохраните его и обновите веб-страницу. В результате в браузере должно появиться примерно следующее:

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

HTML-файл полностью

Ниже показано, как должен выглядеть ваш HTML-файл целиком.

<!DOCTYPE html>
<html>
<head>
  <title>ESP32 Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <style>
    html {
      font-family: Helvetica;
      display: inline-block;
      margin: 0px auto;
      text-align: center;
    }
    .button {
      background-color: #4CAF50;
      border: none;
      color: white;
      padding: 16px 40px;
      text-decoration: none;
      font-size: 30px;
      margin: 2px;
      cursor: pointer;
    }
    .button2 {
      background-color: #555555;
    }
  </style>
</head>
<body>
  <h1>ESP32 Web Server</h1>
  <p>GPIO 26 - State</p>
  <p><a href="/26/on"><button class="button">ON</button></a></p>
  <p><a href="/26/off"><button class="button button2">OFF</button></a></p>
  <p>GPIO 27 - State</p>
  <p><a href="/27/on"><button class="button">ON</button></a></p>
  <p><a href="/27/off"><button class="button button2">OFF</button></a></p>
</body>
</html>

В следующем Разделе мы научимся показывать в браузере созданную здесь веб-страницу при помощи ESP32 и IDE Arduino.

HTML в IDE Arduino

В предыдущем Разделе мы изучили основы HTML и CSS, а также создали страницу для веб-сервера. В этом Разделе мы научимся добавлять HTML-текст в код IDE Arduino. Как правило, ESP32 отправляет этот HTML-текст в виде ответа на запрос, сделанный на IP-адрес ESP32.

client.println()

В этом примере ESP32 отправляет текст клиенту при помощи метода client.println(). Параметром у этой функции должен быть текст, который вы хотите отправить – это должна быть строка. Следовательно, отправляемый текст должен быть помещен в двойные кавычки. К примеру, чтобы отправить первую строчку файла «index.html», в коде нужно вписать следующее:

client.println("<!DOCTYPE html><html>");
Экранирование двойных кавычек

В HTML-коде есть фрагменты, где присутствуют двойные кавычки (""). Как здесь:

<p><a href="/27/on"><button class="button">ON</button></a></p>

Чтобы отправить клиенту эти двойные кавычки, не мешая кавычкам в методе client.println(""), нам нужно будет поставить перед символом кавычек экранирующий символ «обратный слеш» (\).

К примеру, чтобы отправить клиенту HTML-строчку выше, вам нужно будет поставить обратный слеш в 4 местах (см. ниже):

client.println("<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>");
Выделение абзацев

В языке HTML структурировать текст в абзацы не нужно – на конечный результат это не влияет. Поэтому вы можете поместить весь HTML-код в одну строчку, и все будет работать также, как если бы вы разбили этот текст по абзацам.

<!DOCTYPE html><html><head><title>ESP32 Web Server</title><metaname="viewport" content="width=device-width, initial-scale=1"><link rel="icon" href="data:,"><style> html {font-family: Helvetica;display: inline-block;margin: 0px auto;text-align: center;}.button {background-color: #4CAF50;border: none;color: white;padding: 16px 40px;text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}.button2 {background-color: #555555;}</style></head><body> <h1>ESP32 Web Server</h1><p>GPIO 26 - State</p><p><a href="/26/on"><button class="button">ON</button></a></p><p><a href="/26/off"><button class="button button2">OFF</button></a></p><p>GPIO 27 - State</p><p><a href="/27/on"><button class="button">ON</button></a></p><p><a href="/27/off"><button class="button button2">OFF</button></a></p></body></html>

Следовательно, вы можете поместить весь этот HTML-код в одну строчку с методом client.println(""). Впрочем, если вы так сделаете, ваш код будет очень сложно прочесть. Поэтому мы советуем все же разбить HTML-код на абзацы – чтобы он оставался одновременно компактным и читаемым.

Отправка HTML-кода клиенту

Итак, чтобы отправить весь HTML-код клиенту, нужно сделать следующее:

  1. «Упаковать» свой HTML-код в несколько строчек, чтобы он оставался читаемым
  2. Поставить обратные слеши (\) перед каждым символом двойных кавычек
  3. Отправить HTML-код клиенту при помощи метода client.prinln()

Таким образом, чтобы отправить весь HTML-код клиенту, в коде IDE Arduino нужно написать следующее:

// показываем веб-страницу с помощью этого HTML-кода:
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // с помощью CSS задаем стиль кнопок «ON» и «OFF»;
            // если есть желание, можно поэкспериментировать
            // с фоновым цветом и атрибутами размера шрифта:
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button2 {background-color: #555555;}</style></head>");
            
            // заголовок веб-страницы:
            client.println("<body><h1>ESP32 Web Server</h1>");
            
            // рисуем кнопку для контакта GPIO26
            // и показываем его текущее состояние (ON/OFF): 
            client.println("<p>GPIO 26 - State " + output26State + "</p>");
            // если на контакте «output26State» значение «off»,
            // показываем кнопку «ON»:
            if (output26State=="off") {
              client.println("<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>");
            } else {
              client.println("<p><a href=\"/26/off\"><button class=\"button button2\">OFF</button></a></p>");
            } 
               
            // рисуем кнопку для контакта GPIO27
            // и показываем его текущее состояние (ON/OFF): 
            client.println("<p>GPIO 27 - State " + output27State + "</p>");
            // если на контакте «output27State» значение «off»,
            // показываем кнопку «ON»:
            if (output27State=="off") {
              client.println("<p><a href=\"/27/on\"><button class=\"button\">ON</button></a></p>");
            } else {
              client.println("<p><a href=\"/27/off\"><button class=\"button button2\">OFF</button></a></p>");
            }
            client.println("</body></html>");
Использование переменных для показа текущего состояния контактов

Тестируя веб-сервер на базе ESP32, вы, вероятно, видели, что при нажатии на какую-либо кнопку состояние GPIO-контакта обновляется автоматически. Чтобы реализовать это, в коде используются переменные «output26State» и «output27State»:

String output26State = "off";
String output27State = "off";

В них хранятся состояния обоих GPIO-контактов. Чтобы показать текущее состояние контакта на веб-странице, вам нужно лишь отправить вместе с HTML-кодом переменную с этим состоянием. Другими словами, вам нужно объединить эту переменную вместе с отправляемым HTML-кодом. К примеру, чтобы показать состояние контакта GPIO27, в код нужно вписать следующее:

client.println("<p>GPIO 27 - State " + output27State + "</p>");

Этот метод можно использовать для отправки любых переменных.

Добавление условий

Наш веб-сервер, показывает по одной кнопке для каждого GPIO-контакта – в зависимости от ее текущего состояния:

Если текущим состоянием GPIO-контакта является «off», код покажет кнопку «ON», а если нет – кнопку «OFF». Для этого в код нужно добавить условия, благодаря которым мы будем отправлять клиенту либо HTML-код для кнопки «ON», либо HTML-код для кнопки «OFF» (см. ниже).

if (output26State=="off") {
 client.println("<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>");
} else {
 client.println("<p><a href=\"/26/off\"><button class=\"button button2\">OFF</button></a></p>");
}
Итого

Итак мы научились делать при помощи языков HTML и CSS простую веб-страницу. Мы также научились отправлять HTML-текст клиенту при помощи кода для IDE Arduino. Эти разделы призваны дать вам основы работы с этими инструментами, чтобы вы могли создавать и модифицировать собственные веб-страницы.

Что нужно сделать перед загрузкой кода

Задаем учетные данные сети

Строчки кода ниже нужно отредактировать, вписав в них данные своей сети: SSID и пароль. Найти эти строчки можно по соответствующему комментарию:

// здесь пишем учетные данные своей сети:
const char* ssid     = "";
const char* password = "";

Загрузка и проверка работы веб-сервера

Ищем IP-адрес ESP32

Перед загрузкой кода на ESP32 проверьте, правильно ли выбраны в IDE Arduino плата и COM-порт (в противном случае во время загрузки выскочит ошибка). Затем загрузите код. Откройте монитор порта на скорости 115200 бод. Иконка кнопки монитора порта выглядит следующим образом:

Далее ESP32 подключится к WiFi-сети и покажет в мониторе порта свой IP-адрес. Скопируйте его, потому что он понадобится нам позднее для получения доступа к веб-серверу на базе ESP32.

Примечание: Если в мониторе порта ничего не появилось, нажмите на кнопку «EN» на ESP32 (она находится рядом с портом MicroUSB).

Подключение к веб-серверу

Откройте браузер, вставьте в адресную строку найденный ранее IP-адрес ESP32 и нажмите на  ↵ Enter . Вы должны увидеть примерно следующее:

Если взглянуть на монитор порта, то можно увидеть, что в данный момент происходит «за кулисами». Там описываются подробности получения платой HTTP-запроса от нового клиента (т.е. от вашего браузера). В частности, поля HTTP-заголовка, задающие операционные параметры HTTP-передачи данных.

Тестирование веб-сервера

Теперь давайте протестируем веб-сервер. Кликните на кнопку, чтобы переключить контакт GPIO26 в состояние «ON». После этого в мониторе порта появится сообщение о том, что ESP32 получила запрос на ссылку «.../26/on».

Получив запрос, ESP32 включит светодиод, подсоединенный к контакту GPIO26. Кроме того, состояние светодиода обновится и на веб-странице в браузере.

Теперь протестируйте кнопку для контакта GPIO27. Она должна работать по такому же принципу.

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

Теперь давайте подробнее рассмотрим этот код, чтобы понять, как он работает. После этого вы сможете модифицировать его под собственные нужды.

Во-первых, нам нужно подключить библиотеку WiFi. Это та же библиотека, что используется для создания веб-серверов на базе Arduino и модуля Ethernet Shield.

// подключаем библиотеку для WiFi-связи:
#include <WiFi.h>

Как мы уже писали ранее, вам также нужно вписать SSID и пароль для входа в свою WiFi-сеть. Это делается в этих строчках (внутри двойных кавычек):

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

Затем задаем для веб-сервера номер порта «80»:

WiFiServer server(80);

Строчка ниже создает переменную для хранения заголовка HTTP-запроса:

String header;

Далее создаем вспомогательные переменные для хранения текущего состояния выходных контактов. Если вы хотите добавить еще выходных контактов, а также сохранять их состояния, вам нужно вписать здесь для них дополнительные переменные:

// вспомогательные переменные
// для хранения текущего состояния выходных контактов:
String output26State = "off";
String output27State = "off";

Вам также нужно задать, какие именно GPIO-контакты будут использоваться в этом проекте. В данном случае это GPIO26 и GPIO27, но вы можете использовать любые другие GPIO-контакты.

// задаем номера для выходных GPIO-контактов:
const int output26 = 26;
const int output27 = 27;

setup()

Теперь переходим к блоку setup(). Этот блок запустится только раз при первой загрузке ESP32.

Во-первых, запускаем последовательную коммуникацию на скорости 115200 бод (для отладки).

Serial.begin(115200);

Делаем выходными контакты GPIO26 и GPIO27, а также присваиваем им значение «LOW».

// делаем эти GPIO-контакты выходными:
pinMode(output26, OUTPUT);
pinMode(output27, OUTPUT);
// присваиваем им значение «LOW»:
digitalWrite(output26, LOW);
digitalWrite(output27, LOW);

Фрагмент кода ниже запускает WiFi-соединение при помощи функции WiFi.begin(ssid, password), ждет установки соединения, а затем печатает в мониторе порта IP-адрес ESP32.

Serial.print("Connecting to ");
           //  "Подключаемся к "
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // печатаем в мониторе порта локальный IP-адрес
  // и запускаем веб-сервер:
  Serial.println("");
  Serial.println("WiFi connected.");  //  "WiFi подключен."
  Serial.println("IP address: ");  //  "IP-адрес: "
  Serial.println(WiFi.localIP());
  server.begin();

loop()

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

ESP32 всегда прослушивает входящих клиентов с помощью этой строчки:

WiFiClient client = server.available();

Получив запрос от клиента, сохраняем пришедшие данные. Далее, пока клиент будет оставаться подключенным, ESP32 будет постоянно повторять цикл while(). Мы не рекомендуем менять этот фрагмент кода (разве что вы уверены точно в том, что делаете).

if (client) {                       // если подключился новый клиент,     
    Serial.println("New Client.");  // печатаем в мониторе порта
                                    // сообщение «Новый клиент.»;
    String currentLine = "";        // создаем строку для хранения
                                    // входящих данных от клиента;
    while (client.connected()) {    // цикл while() будет работать
                                    // все то время, пока клиент
                                    // будет подключен к серверу;
      if (client.available()) {     // если у клиента есть данные,
                                    // которые можно прочесть, 
        char c = client.read();     // считываем байт, а затем    
        Serial.write(c);            // печатаем его в мониторе порта 
        header += c;
        if (c == '\n') {            // если этим байтом является
                                    // символ новой строки
          // если мы получим два символа новой строки подряд 
          // то это значит, что текущая строчка пуста;
          // это конец HTTP-запроса клиента,
          // а значит – пора отправлять ответ:
          if (currentLine.length() == 0) {
            // HTTP-заголовки всегда начинаются
            // с кода ответа (например, «HTTP/1.1 200 OK»)
            // и информации о типе контента
            // (чтобы клиент понимал, что получает);
            // в конце пишем пустую строчку:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
                       //  "Соединение: отключено"
            client.println();

В следующем фрагменте с операторами if и else мы проверяем, какая кнопка была нажата на веб-странице и соответствующим образом управляем выходными контактами. Ранее уже объяснялось, что мы будем делать запросы к разным URL – в зависимости от нажимаемой кнопки.

            // с помощью этого кода
            // включаем и выключаем GPIO-контакты:
            if (header.indexOf("GET /26/on") >= 0) {
              Serial.println("GPIO 26 on");  //  "GPIO26 включен"
              output26State = "on";
              digitalWrite(output26, HIGH);
            } else if (header.indexOf("GET /26/off") >= 0) {
              Serial.println("GPIO 26 off");  //  "GPIO26 выключен"
              output26State = "off";
              digitalWrite(output26, LOW);
            } else if (header.indexOf("GET /27/on") >= 0) {
              Serial.println("GPIO 27 on");  //  "GPIO27 включен"
              output27State = "on";
              digitalWrite(output27, HIGH);
            } else if (header.indexOf("GET /27/off") >= 0) {
              Serial.println("GPIO 27 off");  //  "GPIO27 выключен"
              output27State = "off";
              digitalWrite(output27, LOW);
            }

К примеру, если вы нажали на кнопку «ON» для контакта GPIO26, ESP32 получит запрос на ссылку «.../26/ON», и мы получим данные об этом в HTTP-заголовке. То есть это дает нам возможность проверить, есть ли в заголовке фраза «GET /26/on». Если есть, печатаем сообщение в мониторе порта, меняем значение в переменной «output26state» на «ON» и включаем светодиод.

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

Показываем веб-страницу при помощи HTML-кода

Далее нам нужно создать веб-страницу. ESP32 ответит при помощи отправки на ваш браузер HTML-кода, с помощью которого и будет сгенерирована веб-страница.

Веб-страница отправляется клиенту при помощи метода client.println(). Параметром для этой функции будет служить как раз то, что вы хотите отправить клиенту.

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

<!DOCTYPE HTML><html>

Затем пишем строчку, которая адаптирует веб-страницу под любой веб-браузер:

client.println("<head><meta name=\"viewport\" content=\"width=devicewidth, initial-scale=1\">");

Строчка ниже используется, чтобы избежать запроса к иконке сайта (вам об этой строчке беспокоиться не нужно).

client.println("<link rel=\"icon\" href=\"data:,\">");

Задаем стиль веб-страницы

Далее у нас немного CSS-текста, с помощью которого мы задаем стиль кнопок и внешнего вида веб-страницы. Выбираем шрифт Helvetica, рисуем блочный элемент и задаем выравнивание по центру.

client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");

Задаем для кнопок цвет #4CAF50, контур не делаем, текст будет белого цвета, а отступ – 16 и 40 пикселей. Оформление текста не задаем, но задаем размер шрифта и отступ. Значком курсора делаем «ладонь».

client.println(".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px;");
client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");

Также задаем стиль для второй кнопки – с такими же свойствами, как и для заданной ранее первой кнопки, за исключением цвета. Это будет стиль для кнопки «OFF».

client.println(".button2{background-color:#555555;}");

Задаем заголовок первого уровня для веб-страницы

В следующей строчке мы задаем главный заголовок для веб-страницы. В нашем случае это будет «ESP32 Web Server» («Веб-сервер на базе ESP32»), но вы можете вписать сюда любой текст.

// задаем заголовок для веб-страницы:
client.println("<body><h1>ESP32 Web Server</h1>");

Показываем кнопки и их состояния

Затем делаем абзац, показывающий текущее состояние контакта GPIO26. Как видите, мы используем для этого переменную «output26State», благодаря чему состояние кнопки на экране будет обновлено сразу же после изменения значения в этой переменной.

client.println("<p>GPIO 26 - State " + output26State + "</p>");

Затем рисуем кнопку «OFF» или «ON» – в зависимости от текущего состояния GPIO-контакта. Если текущее состояние контакта – это «off», то рисуем кнопку «ON», а если «on», то «OFF».

if (output27State=="off") {
 client.println("<p><a href=\"/27/on\"><button
class=\"button\">ON</button></a></p>");
} else {
 client.println("<p><a href=\"/27/off\"><button class=\"button
button2\">OFF</button></a></p>");
}

Затем все то же самое делаем для контакта GPIO27.

Отключаем соединение

Наконец, после отправки ответа очищаем переменную «header» и отключаем соединение с клиентом при помощи метода client.stop().

// очищаем переменную «header»:
header = "";
// отключаем соединение:
client.stop();

Необходимое оборудование

Схема

На данной схеме используется плата ESP32S-HiLetgo, если вы используете другую, сверьтесь с вашей распиновкой.


Код

/*********
  Руи Сантос
  Более подробно о проекте читайте тут: http://randomnerdtutorials.com  
*********/

// подключаем библиотеку для WiFi-связи:
#include <WiFi.h>

// здесь пишем учетные данные своей сети:
const char* ssid     = "";
const char* password = "";

// задаем номер порта для веб-сервера («80»):
WiFiServer server(80);

// переменная для хранения HTTP-запроса:
String header;

// вспомогательные переменные
// для хранения текущего состояния выходных контактов:
String output26State = "off";
String output27State = "off";

// задаем номера для выходных GPIO-контактов:
const int output26 = 26;
const int output27 = 27;

void setup() {
  Serial.begin(115200);
  // делаем эти GPIO-контакты выходными:
  pinMode(output26, OUTPUT);
  pinMode(output27, OUTPUT);
  // присваиваем им значение «LOW»:
  digitalWrite(output26, LOW);
  digitalWrite(output27, LOW);

  // подключаемся к WiFi-сети при помощи заданных выше SSID и пароля:
  Serial.print("Connecting to ");
           //  "Подключаемся к "
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // печатаем в мониторе порта локальный IP-адрес
  // и запускаем веб-сервер:
  Serial.println("");
  Serial.println("WiFi connected.");  //  "WiFi подключен."
  Serial.println("IP address: ");  //  "IP-адрес: "
  Serial.println(WiFi.localIP());
  server.begin();
}

void loop(){
  // начинаем прослушивать входящих клиентов:
  WiFiClient client = server.available();

  if (client) {                     // если подключился новый клиент,     
    Serial.println("New Client.");  // печатаем сообщение
                                    // «Новый клиент.»
                                    // в мониторе порта;
    String currentLine = "";        // создаем строку для хранения
                                    // входящих данных от клиента;
    while (client.connected()) {    // цикл while() будет работать
                                    // все то время, пока клиент
                                    // будет подключен к серверу;
      if (client.available()) {     // если у клиента есть данные,
                                    // которые можно прочесть, 
        char c = client.read();     // считываем байт, а затем    
        Serial.write(c);            // печатаем его в мониторе порта 
        header += c;
        if (c == '\n') {            // если этим байтом является
                                    // символ новой строки
          // если мы получим два символа новой строки подряд 
          // то это значит, что текущая строчка пуста;
          // это конец HTTP-запроса клиента,
          // а значит – пора отправлять ответ:
          if (currentLine.length() == 0) {
            // HTTP-заголовки всегда начинаются
            // с кода ответа (например, «HTTP/1.1 200 OK»)
            // и информации о типе контента
            // (чтобы клиент понимал, что получает);
            // в конце пишем пустую строчку:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
                       //  "Соединение: отключено"
            client.println();
            
            // с помощью этого кода
            // включаем и выключаем GPIO-контакты:
            if (header.indexOf("GET /26/on") >= 0) {
              Serial.println("GPIO 26 on");  //  "GPIO26 включен"
              output26State = "on";
              digitalWrite(output26, HIGH);
            } else if (header.indexOf("GET /26/off") >= 0) {
              Serial.println("GPIO 26 off");  //  "GPIO26 выключен"
              output26State = "off";
              digitalWrite(output26, LOW);
            } else if (header.indexOf("GET /27/on") >= 0) {
              Serial.println("GPIO 27 on");  //  "GPIO27 включен"
              output27State = "on";
              digitalWrite(output27, HIGH);
            } else if (header.indexOf("GET /27/off") >= 0) {
              Serial.println("GPIO 27 off");  //  "GPIO27 выключен"
              output27State = "off";
              digitalWrite(output27, LOW);
            }
            
            // показываем веб-страницу с помощью этого HTML-кода:
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // с помощью CSS задаем стиль кнопок «ON» и «OFF»;
            // если есть желание, можно поэкспериментировать
            // с фоновым цветом и атрибутами размера шрифта:
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button2 {background-color: #555555;}</style></head>");
            
            // заголовок веб-страницы:
            client.println("<body><h1>ESP32 Web Server</h1>");
            
            // рисуем кнопку для контакта GPIO26
            // и показываем его текущее состояние (ON/OFF): 
            client.println("<p>GPIO 26 - State " + output26State + "</p>");
            // если на контакте «output26State» значение «off»,
            // показываем кнопку «ON»:
            if (output26State=="off") {
              client.println("<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>");
            } else {
              client.println("<p><a href=\"/26/off\"><button class=\"button button2\">OFF</button></a></p>");
            } 
               
            // рисуем кнопку для контакта GPIO27
            // и показываем его текущее состояние (ON/OFF): 
            client.println("<p>GPIO 27 - State " + output27State + "</p>");
            // если на контакте «output27State» значение «off»,
            // показываем кнопку «ON»:
            if (output27State=="off") {
              client.println("<p><a href=\"/27/on\"><button class=\"button\">ON</button></a></p>");
            } else {
              client.println("<p><a href=\"/27/off\"><button class=\"button button2\">OFF</button></a></p>");
            }
            client.println("</body></html>");
            
            // конец HTTP-ответа задается 
            // с помощью дополнительной пустой строки:
            client.println();
            // выходим из цикла while:
            break;
          } else {  // если получили символ новой строки,
                    // очищаем текущую строку «currentLine»:
            currentLine = "";
          }
        } else if (c != '\r') {  // если получили любые данные,
                                 // кроме символа возврата каретки,
          currentLine += c;      // добавляем эти данные 
                                 // в конец строки «currentLine»
        }
      }
    }
    // очищаем переменную «header»:
    header = "";
    // отключаем соединение:
    client.stop();
    Serial.println("Client disconnected.");
               //  "Клиент отключен."
    Serial.println("");
  }
}

См.также

Внешние ссылки