ESP32 Neopixel: индикатор состояния и датчик на PCB шилде с Wi-Fi Manager

В этом проекте мы создадим PCB шилд — индикатор состояния для ESP32 с двумя рядами адресных RGB neopixel светодиодов, датчиком BME280 и кнопкой. Мы запрограммируем плату для отображения веб-сервера с показаниями датчика BME280 и показа диапазона температуры и влажности на светодиодах (как два прогресс-бара). Мы также настроим Wi-Fi Manager — светодиоды показывают, подключена ли плата к Wi-Fi сети или работает в режиме точки доступа.

ESP32 Neopixel Status Indicator and Sensor PCB Shield with Wi-Fi Manager

Wi-Fi Manager позволяет подключать платы ESP32 к различным точкам доступа (сетям) без необходимости жёстко задавать сетевые учётные данные (SSID и пароль) и загружать новый код на плату. Ваш ESP автоматически подключится к последней сохранённой сети или настроит точку доступа, которую вы можете использовать для настройки сетевых учётных данных.

Следуя этому проекту, вы узнаете больше о следующих концепциях:

  • Управление двумя адресными RGB LED «полосками» по отдельности с ESP32;

  • Создание веб-сервера с ESP32 с использованием Server-sent Events (SSE);

  • Обработка полей ввода HTML для сохранения данных на плате (SSID и пароль);

  • Сохранение переменных постоянно с использованием файлов в файловой системе;

  • Создание собственного Wi-Fi Manager с использованием библиотеки ESPAsyncWebServer;

  • Переключение между режимом станции и режимом точки доступа;

  • И многое другое…

Для лучшего понимания работы этого проекта рекомендуем ознакомиться со следующими руководствами:

Ресурсы

Все необходимые ресурсы для сборки этого проекта вы найдёте по ссылкам ниже (или можете посетить страницу проекта на GitHub):

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

Перед тем как перейти непосредственно к проекту, давайте рассмотрим особенности PCB шилда (аппаратные и программные).

Особенности PCB шилда

Шилд спроектирован с гребёнками для установки платы ESP32. По этой причине, если вы хотите собрать и использовать нашу PCB, вам нужно приобрести ту же плату разработки ESP32. Мы используем плату ESP32 DEVKIT DOIT V1 (модель с 36 GPIO).

ESP32 Status Indicator and Sensor Shield PCB

Если вы хотите следовать этому проекту и у вас другая модель ESP32, вы можете собрать схему на макетной плате или изменить разводку PCB в соответствии с распиновкой вашей платы ESP32. В этом проекте мы предоставляем все необходимые файлы, если вам нужно модифицировать PCB.

Кроме того, вы можете следовать этому проекту, собрав схему на макетной плате, если не хотите изготавливать PCB шилд.

Шилд состоит из:

  • Датчика температуры, влажности и давления BME280;

  • Кнопки;

  • Двух рядов по 5 адресных RGB светодиодов (WS2812B).

ESP32 Status Indicator Sensor Shield RGB LEDs Overview

Если вы повторяете этот проект на макетной плате, вместо отдельных адресных RGB светодиодов WS2812B вы можете использовать адресные RGB LED ленты.

Назначение пинов PCB шилда

В следующей таблице показано назначение пинов для каждого компонента на шилде:

Компонент

Назначение пинов ESP32

BME280

GPIO 21 (SDA), GPIO 22 (SCL)

Кнопка

GPIO 18

Адресные RGB LED (ряд 1)

GPIO 27

Адресные RGB LED (ряд 2)

GPIO 32

Программные возможности PCB

Вы можете программировать шилд различными способами. Мы запрограммируем ESP32 со следующими функциями:

Веб-сервер

Веб-сервер для отображения показаний датчика BME280: температура, влажность и давление. Он также отображает время последнего обновления. Показания обновляются автоматически каждые 30 секунд с помощью server-sent events. Узнайте больше о Server-Sent Events в этом проекте.

ESP32 BME280 Sensor Readings Web Server Table

Визуальный интерфейс (адресные RGB светодиоды)

RGB светодиоды на шилде ведут себя как прогресс-бар, показывающий диапазон значений температуры и влажности. Чем выше температура, тем больше светодиодов горит — то же самое для показаний влажности. Значения температуры отображаются оранжевым/жёлтым цветом, а влажность — бирюзовым.

ESP32 Status Indicator Sensor Shield RGB LEDs Overview Sensor Readings

Wi-Fi Manager

Wi-Fi Manager позволяет подключать плату ESP32 к различным точкам доступа (сетям) без необходимости жёстко задавать сетевые учётные данные (SSID и пароль) и загружать новый код на плату. Ваш ESP автоматически подключится к последней сохранённой сети или настроит точку доступа, которую вы можете использовать для настройки сетевых учётных данных.

ESP32 Wi-Fi Manager Web Page

Когда плата находится в режиме точки доступа (Wi-Fi Manager), все светодиоды горят красным. Когда плата находится в режиме станции (веб-сервер с показаниями датчиков), все светодиоды временно загораются зелёным/бирюзовым цветом, прежде чем показать диапазон температуры и влажности.

ESP32 Status Indicator Sensor Shield RGB LEDs Wi-Fi Manager

Тестирование схемы на макетной плате

Перед проектированием и изготовлением PCB важно протестировать схему на макетной плате. Если вы не хотите делать PCB, вы всё равно можете следовать этому проекту, собрав схему на макетной плате.

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

Для сборки схемы на макетной плате вам потребуются следующие компоненты (компоненты для PCB показаны в отдельном разделе):

Собрав все компоненты, соберите схему по следующей схеме:

ESP32 Status Indicator Sensor Shield RGB LEDs Breadboard Diagram

*В итоге мы не использовали кнопку в этом конкретном проекте, поэтому её не обязательно включать в вашу схему.

Проектирование PCB

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

Я не эксперт в проектировании PCB. Однако проектирование простых PCB, подобных той, что мы используем в этом руководстве, довольно простое. Проектирование схемы работает как в любом другом инструменте для работы со схемами: вы размещаете компоненты и соединяете их. Затем вы назначаете каждому компоненту посадочное место.

EasyEDA Circuit Diagram ESP32 Neopixel Shield PCB

Назначив компоненты, разместите каждый из них. Когда вы будете довольны расположением, выполните все соединения и разведите вашу PCB.

EasyEDA ESP32 Neopixels PCB Shield

Сохраните ваш проект и экспортируйте Gerber файлы.

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

Заказ PCB на PCBWay

Этот проект спонсирован PCBWay. PCBWay — это полнофункциональный сервис по производству печатных плат.

PCBWay banner

Превратите ваши DIY макетные схемы в профессиональные PCB — получите 10 плат примерно за $5 + доставка (которая варьируется в зависимости от вашей страны).

Когда у вас есть Gerber файлы, вы можете заказать PCB. Следуйте следующим шагам.

  1. Скачайте Gerber файлы — нажмите здесь, чтобы скачать .zip файл

  2. Перейдите на сайт PCBWay и откройте страницу PCB Instant Quote.

PCBWay Order PCB
  1. PCBWay может получить все данные PCB и автоматически заполнить их для вас. Используйте «Quick-order PCB (Autofill parameters)».

PCBWay Order PCB autofill parameters
  1. Нажмите кнопку «+ Add Gerber file», чтобы загрузить предоставленные Gerber файлы.

PCBWay Order PCB add gerber file button

И это всё. Вы также можете использовать OnlineGerberViewer, чтобы проверить, что ваша PCB выглядит так, как должна.

ESP32 Neopixel PCB Shield PCBWay Gerber Viewer

Если вы не торопитесь, вы можете использовать метод доставки China Post, чтобы значительно снизить стоимость. По нашему мнению, мы считаем, что они переоценивают время доставки China Post.

PCBWay Order PCB China Post shipping method

Вы можете увеличить количество заказанных PCB и изменить цвет паяльной маски. Как обычно, мы заказали синий цвет.

ESP32 Neopixel PCB Shield PCBWay Details

Когда будете готовы, вы можете заказать PCB, нажав «Save to Cart» и завершив заказ.

Распаковка PCB

Примерно через одну неделю при использовании метода доставки DHL, я получил PCB на свой адрес.

Status Indicator and Sensor Shield Unboxing PCBWay

Как обычно, всё хорошо упаковано, и PCB действительно высокого качества. Надписи на шелкографии действительно хорошо напечатаны и легко читаются.

Мы очень довольны сервисом PCBWay. Вот некоторые другие проекты, которые мы построили с использованием сервиса PCBWay:

Пайка компонентов

В нашей PCB мы использовали SMD светодиоды, SMD резисторы и SMD конденсаторы. Их может быть немного сложно паять, но PCB выглядит намного лучше. Если вы никогда не паяли SMD, рекомендуем посмотреть несколько видео, чтобы узнать, как это делается. Вы также можете получить набор для пайки SMD для практики.

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

ESP32 Status Indicator and Sensor Shield parts

Вот инструменты для пайки, которые мы использовали:

TS80 Soldering Iron

Начните с пайки SMD компонентов. Затем припаяйте гребёнки. И наконец, припаяйте остальные компоненты или используйте гребёнки, если не хотите подключать компоненты постоянно.

Вот как выглядит шилд ESP32 после сборки всех деталей.

ESP32 Board PCB Shield Neopixels

Плата ESP32 должна идеально устанавливаться на гребёнки на другой стороне PCB.

Status Indicator PCB Shield Stack ESP32

Программирование шилда

Как упоминалось ранее, мы запрограммируем плату со следующими функциями:

  • Веб-сервер для отображения показаний датчика BME280 (режим станции);

  • Визуальный интерфейс (адресные RGB светодиоды): RGB светодиоды на шилде ведут себя как два прогресс-бара, показывающие диапазон значений температуры и влажности;

  • Wi-Fi Manager: ESP32 автоматически подключится к последней сохранённой сети или настроит точку доступа, которую вы можете использовать для настройки сетевых учётных данных.

Следующая диаграмма обобщает, как работает проект.

ESP32 Shield WiFi Manager How it Works
  • Когда ESP впервые запускается, он пытается прочитать файлы ssid.txt, pass.txt и ip.txt (1);

  • Если файлы пусты (2) (при первом запуске платы файлы пусты), ваша плата настраивается как точка доступа, и все светодиоды загораются красным цветом (3);

  • С помощью любого устройства с Wi-Fi и браузером вы можете подключиться к вновь созданной точке доступа (имя по умолчанию ESP-WIFI-MANAGER);

  • После установления соединения с ESP-WIFI-MANAGER вы можете перейти по IP-адресу по умолчанию 192.168.4.1, чтобы открыть веб-страницу для настройки SSID и пароля (4);

  • SSID, пароль и IP-адрес, введённые в форму, сохраняются в соответствующих файлах: ssid.txt, pass.txt и ip.txt (5);

  • После этого плата ESP перезагружается (6);

  • На этот раз после перезагрузки файлы не пусты, поэтому ESP попытается подключиться к сети Wi-Fi в режиме станции, используя настройки, которые вы ввели в форму (7);

  • Если соединение установлено, процесс завершён успешно (8) (все светодиоды временно загораются зелёным/бирюзовым цветом);

  • Вы можете получить доступ к основной веб-странице с показаниями датчиков (9), и светодиоды загораются в соответствии с диапазоном температуры и влажности (10). В противном случае будет настроена точка доступа (3), и вы сможете перейти по IP-адресу по умолчанию (192.168.4.1), чтобы добавить другую комбинацию SSID/пароль.

Необходимые условия

Мы будем программировать плату ESP32 с помощью Arduino IDE. Убедитесь, что у вас установлен аддон платы ESP32.

Если вы хотите программировать ESP32/ESP8266 с помощью VS Code + PlatformIO, следуйте этому руководству:

Установка библиотек (Arduino IDE)

Для этого проекта вам необходимо установить все эти библиотеки в вашу Arduino IDE.

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

Загрузчик файловой системы

Перед продолжением вам необходимо установить плагин ESP32 Uploader Plugin в вашу Arduino IDE. Следуйте этому руководству:

Если вы используете VS Code с PlatformIO, следуйте этому руководству, чтобы узнать, как загружать файлы в файловую систему:

Организация файлов

Для поддержания порядка в проекте и упрощения понимания мы создадим пять различных файлов для построения веб-сервера:

  • Скетч Arduino, который обрабатывает веб-сервер;

  • index.html: для определения содержимого веб-страницы в режиме станции для отображения показаний датчиков;

  • style.css: для стилизации веб-страницы;

  • script.js: для программирования поведения веб-страницы — обработка ответов веб-сервера, событий, обновление времени и т.д.;

  • wifimanager.html: для определения содержимого веб-страницы Wi-Fi Manager, когда ESP32 находится в режиме точки доступа.

ESP32 Web Server Wi-Fi Manager Files

Вы должны сохранить HTML, CSS и JavaScript файлы в папке с именем data внутри папки скетча Arduino, как показано на предыдущей диаграмме. Мы загрузим эти файлы в файловую систему ESP32 (SPIFFS).

Вы можете скачать все файлы проекта:

Создание HTML файлов

Для этого проекта вам нужны два HTML файла. Один для создания главной страницы с показаниями датчиков (index.html) и другой для создания страницы Wi-Fi Manager (wifimanager.html).

index.html

Скопируйте следующий код в файл index.html.

<!DOCTYPE html>
<html>
  <head>
    <title>ESP IOT DASHBOARD</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/png" href="favicon.png">
      <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
    <link rel="stylesheet" type="text/css" href="style.css">
  </head>
  <body>
    <div class="topnav">
      <h1>ESP WEB SERVER SENSOR READINGS</h1>
    </div>
    <div class="content">
      <div class="card-grid">
        <div class="card">
          <p class="card-title">BME280 Sensor Readings</p>
          <p>
            <table>
              <tr>
                <th>READING</th>
                <th>VALUE</th>
              </tr>
              <tr>
                <td>Temperature</td>
                <td><span id="temp"></span> &deg;C</td>
              </tr>
              <tr>
                <td>Humidity</td>
                <td><span id="hum"></span> &percnt;</td>
              </tr>
              <tr>
                <td>Pressure</td>
                <td><span id="pres"></span> hPa</td>
              </tr>
            </table>
          </p>
          <p class="update-time">Last update: <span id="update-time"></span></p>
        </div>
      </div>
    </div>
    <script src="script.js"></script>
  </body>
</html>

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

Этот файл создаёт таблицу для отображения показаний датчиков. Вот абзац, который отображает таблицу:

<p>
  <table>
    <tr>
      <th>READING</th>
      <th>VALUE</th>
    </tr>
    <tr>
      <td>Temperature</td>
      <td><span id="temp"></span> &deg;C</td>
    </tr>
    <tr>
       <td>Humidity</td>
       <td><span id="hum"></span> &percnt;</td>
    </tr>
    <tr>
      <td>Pressure</td>
      <td><span id="pres"></span> hPa</td>
    </tr>
  </table>
</p>

Для создания таблицы в HTML начните с тегов <table> и </table>. Они охватывают всю таблицу. Для создания строки используйте теги <tr> и </tr>. Таблица определяется серией строк. Используйте пару <tr></tr> для обрамления каждой строки данных. Заголовок таблицы определяется с помощью тегов <th> и </th>, а каждая ячейка таблицы определяется с помощью тегов <td> и </td>.

Обратите внимание, что ячейки для отображения показаний датчиков имеют теги <span> с определёнными идентификаторами для последующего управления через JavaScript для вставки обновлённых показаний. Например, ячейка для значения температуры имеет id temp.

<td><span id="temp"></span> &deg;C</td>

Наконец, есть абзац для отображения времени последнего обновления показаний:

<p class="update-time">Last update: <span id="update-time"></span></p>

Есть тег <span> с id update-time. Он будет использоваться позже для вставки даты и времени с помощью JavaScript.

wifimanager.html

Скопируйте следующий код в файл wifimanager.html.

<!DOCTYPE html>
<html>
<head>
  <title>ESP Wi-Fi Manager</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
  <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
  <div class="topnav">
    <h1>ESP Wi-Fi Manager</h1>
  </div>
  <div class="content">
    <div class="card-grid">
      <div class="card">
        <form action="/" method="POST">
          <p>
            <label for="ssid">SSID</label>
            <input type="text" id ="ssid" name="ssid"><br>
            <label for="pass">Password</label>
            <input type="text" id ="pass" name="pass"><br>
            <label for="ip">IP Address</label>
            <input type="text" id ="ip" name="ip" value="192.168.1.200">
            <input type ="submit" value ="Submit">
          </p>
        </form>
      </div>
    </div>
  </div>
  <script src="script.js"></script>
</body>
</html>

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

Этот файл создаёт HTML форму для ввода SSID и пароля сети, к которой вы хотите подключить ESP32. Вы также можете ввести IP-адрес, который вы хотите назначить вашему ESP32. Следующее изображение показывает веб-страницу Wi-Fi Manager — три поля ввода и кнопка отправки.

ESP32 Wi-Fi Manager Web Page

Мы хотим отправить введённые значения из полей ввода на сервер при нажатии кнопки Submit.

Вот HTML форма с тремя полями ввода:

<form action="/" method="POST">
  <p>
    <label for="ssid">SSID</label>
    <input type="text" id ="ssid" name="ssid"><br>
    <label for="pass">Password</label>
    <input type="text" id ="pass" name="pass"><br>
    <label for="ip">IP Address</label>
    <input type="text" id ="ip" name="ip" value="192.168.1.200">
    <input type ="submit" value ="Submit">
  </p>
</form>

Это поле ввода для SSID:

<label for="ssid">SSID</label>
<input type="text" id ="ssid" name="ssid"><br>

Поле ввода для пароля:

<label for="pass">Password</label>
<input type="text" id ="pass" name="pass"><br>

HTML форма содержит различные элементы формы. Все элементы формы заключены внутри тега <form>. Он содержит элементы управления (поля ввода) и метки для этих элементов.

Кроме того, тег <form> должен включать атрибут action, который указывает, что вы хотите сделать при отправке формы (он перенаправляет на корневой URL /, чтобы мы остались на той же странице). В нашем случае мы хотим отправить данные на сервер (ESP32), когда пользователь нажимает кнопку Submit. Атрибут method указывает HTTP метод (GET или POST), используемый при отправке данных формы. В данном случае мы будем использовать метод HTTP POST.

<form action="/" method="POST">

POST используется для отправки данных на сервер для создания/обновления ресурса. Данные, отправленные на сервер с помощью POST, хранятся в теле запроса HTTP. В данном случае, после отправки значений в полях ввода, тело HTTP POST запроса будет выглядеть так:

POST /
Host: localhost
ssid: YOUR-NETWORK-SSID
pass: YOUR-PASSWORD
ip: IP-ADDRESS

<input type="submit" value="Submit"> создаёт кнопку отправки с текстом «Submit». При нажатии этой кнопки данные, введённые в форму, отправляются на сервер.

<input type ="submit" value ="Submit">

И наконец, есть поле ввода для IP-адреса, который вы хотите назначить ESP в режиме станции. По умолчанию мы устанавливаем 192.168.1.200. Вы можете установить другой IP-адрес по умолчанию или удалить параметр value — тогда он не будет иметь значения по умолчанию, и сеть автоматически назначит действительный IP-адрес вашей плате.

<label for="ip">IP Address</label>
<input type="text" id ="ip" name="ip" value="192.168.1.200">

Создание CSS файла

Скопируйте следующие стили в ваш файл style.css.

html {
  font-family: Arial, Helvetica, sans-serif;
  display: inline-block;
  text-align: center;
}
h1 {
  font-size: 1.8rem;
  color: white;
}
p {
  font-size: 1.4rem;
}
.topnav {
  overflow: hidden;
  background-color: #0A1128;
}
body {
  margin: 0;
}
.content {
  padding: 50px;
}
.card-grid {
  max-width: 800px;
  margin: 0 auto;
  display: grid;
  grid-gap: 2rem;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.card {
  background-color: white;
  box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
}
.card-title {
  font-size: 1.2rem;
  font-weight: bold;
  color: #034078
}
th, td {
  text-align: center;
  padding: 8px;
}
tr:nth-child(even) {
  background-color: #f2f2f2
}
tr:hover {
  background-color: #ddd;
}
th {
  background-color: #50b8b4;
  color: white;
}
table {
  margin: 0 auto;
  width: 90%
}
.update-time {
  font-size: 0.8rem;
  color:#1282A2;
}

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

Этот файл стилизует предыдущие веб-страницы.

Создание JavaScript файла

Скопируйте следующий код в файл script.js.

// Get current sensor readings when the page loads
window.addEventListener('load', getReadings);

//Function to add date and time of last update
function updateDateTime() {
  var currentdate = new Date();
  var datetime =  currentdate.getDate() + "/"
  + (currentdate.getMonth()+1)  + "/"
  + currentdate.getFullYear() + " at "
  + currentdate.getHours() + ":"
  + currentdate.getMinutes() + ":"
  + currentdate.getSeconds();
  document.getElementById("update-time").innerHTML = datetime;
  console.log(datetime);
}

// Function to get current readings on the webpage when it loads for the first time
function getReadings() {
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var myObj = JSON.parse(this.responseText);
      console.log(myObj);
      document.getElementById("temp").innerHTML = myObj.temperature;
      document.getElementById("hum").innerHTML = myObj.humidity;
      document.getElementById("pres").innerHTML = myObj.pressure;
      updateDateTime();
    }
  };
  xhr.open("GET", "/readings", true);
  xhr.send();
}

// Create an Event Source to listen for events
if (!!window.EventSource) {
  var source = new EventSource('/events');

  source.addEventListener('open', function(e) {
    console.log("Events Connected");
  }, false);

  source.addEventListener('error', function(e) {
    if (e.target.readyState != EventSource.OPEN) {
      console.log("Events Disconnected");
    }
  }, false);

  source.addEventListener('new_readings', function(e) {
    console.log("new_readings", e.data);
    var obj = JSON.parse(e.data);
    document.getElementById("temp").innerHTML = obj.temperature;
    document.getElementById("hum").innerHTML = obj.humidity;
    document.getElementById("pres").innerHTML = obj.pressure;
    updateDateTime();
  }, false);
}

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

Этот JavaScript обрабатывает события, отправляемые сервером, и обновляет показания датчиков в соответствующих местах. Он также запрашивает дату и время при каждом новом показании. Этот файл также делает запрос на последние показания датчиков при первом подключении к серверу.

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

Получение показаний

Когда вы впервые открываете веб-страницу, она делает запрос к серверу для получения текущих показаний датчиков. В противном случае нам пришлось бы ждать поступления новых показаний (через Server-Sent Events), что может занять некоторое время в зависимости от интервала, установленного на сервере.

Добавьте слушатель событий, который вызывает функцию getReadings при загрузке веб-страницы.

window.addEventListener('load', getReadings);

Объект window представляет открытое окно в браузере. Метод addEventListener() устанавливает функцию, которая будет вызвана при определённом событии. В данном случае мы вызовем функцию getReadings при загрузке страницы („load“) для получения текущих показаний датчиков.

Функция updateDateTime()

Функция updateDateTime() получает текущую дату и время и помещает их в HTML элемент с id update-time.

function updateDateTime() {
  var currentdate = new Date();
  var datetime =  currentdate.getDate() + "/"
  + (currentdate.getMonth()+1)  + "/"
  + currentdate.getFullYear() + " at "
  + currentdate.getHours() + ":"
  + currentdate.getMinutes() + ":"
  + currentdate.getSeconds();
  document.getElementById("update-time").innerHTML = datetime;
  console.log(datetime);
}

Функция getReadings()

Теперь давайте рассмотрим функцию getReadings. Она отправляет GET запрос к серверу по URL /readings и обрабатывает ответ — JSON строку, содержащую показания датчиков. Она также помещает значения температуры, влажности и давления в HTML элементы с соответствующими идентификаторами.

function getReadings() {
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var myObj = JSON.parse(this.responseText);
      console.log(myObj);
      document.getElementById("temp").innerHTML = myObj.temperature;
      document.getElementById("hum").innerHTML = myObj.humidity;
      document.getElementById("pres").innerHTML = myObj.pressure;
      updateDateTime();
    }
  };
  xhr.open("GET", "/readings", true);
  xhr.send();
}

Обработка событий

Теперь нам нужно обработать события, отправляемые сервером (Server-Sent Events).

Создайте новый объект EventSource и укажите URL страницы, отправляющей обновления. В нашем случае это /events.

if (!!window.EventSource) {
  var source = new EventSource('/events');

После создания экземпляра источника событий вы можете начать прослушивать сообщения от сервера с помощью addEventListener().

Это стандартные слушатели событий, как показано в документации AsyncWebServer.

source.addEventListener('open', function(e) {
  console.log("Events Connected");
}, false);

source.addEventListener('error', function(e) {
  if (e.target.readyState != EventSource.OPEN) {
    console.log("Events Disconnected");
  }
}, false);

Затем добавьте слушатель событий для события „new_readings“.

source.addEventListener('new_readings', function(e) {
  console.log("new_readings", e.data);
  var obj = JSON.parse(e.data);
  document.getElementById("temp").innerHTML = obj.temperature;
  document.getElementById("hum").innerHTML = obj.humidity;
  document.getElementById("pres").innerHTML = obj.pressure;
  updateDateTime();
}, false);

Когда доступны новые показания, ESP отправляет событие („new_readings“) клиенту с JSON строкой, содержащей показания датчиков.

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

console.log("new_readings", e.data);

Затем преобразуйте данные в JSON объект с помощью метода parse() и сохраните в переменной obj.

var obj = JSON.parse(e.data);

JSON строка приходит в следующем формате:

{
  "temperature" : "25",
  "humidity" : "50",
  "pressure" : "1015"
}

Вы можете получить температуру с помощью obj.temperature, влажность с помощью obj.humidity и давление с помощью obj.pressure.

Следующие строки помещают полученные данные в элементы с соответствующими идентификаторами (temp, hum и pres) на веб-странице.

document.getElementById("temp").innerHTML = obj.temperature;
document.getElementById("hum").innerHTML = obj.humidity;
document.getElementById("pres").innerHTML = obj.pressure;

Скетч Arduino

Скопируйте следующий код в вашу Arduino IDE или в файл main.cpp, если вы используете PlatformIO.

/*********
  Rui Santos
  Complete instructions at https://RandomNerdTutorials.com/esp32-status-indicator-sensor-pcb/

  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*********/

#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "SPIFFS.h"
#include <Arduino_JSON.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>

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

// Create an Event Source on /events
AsyncEventSource events("/events");

// Search for parameter in HTTP POST request
const char* PARAM_INPUT_1 = "ssid";
const char* PARAM_INPUT_2 = "pass";
const char* PARAM_INPUT_3 = "ip";

//Variables to save values from HTML form
String ssid;
String pass;
String ip;

// File paths to save input values permanently
const char* ssidPath = "/ssid.txt";
const char* passPath = "/pass.txt";
const char* ipPath = "/ip.txt";

IPAddress localIP;
//IPAddress localIP(192, 168, 1, 200); // hardcoded

// Set your Gateway IP address
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 0, 0);

// Timer variables (check wifi)
unsigned long previousMillis = 0;
const long interval = 10000;  // interval to wait for Wi-Fi connection (milliseconds)

// WS2812B Addressable RGB LEDs
#define STRIP_1_PIN    27  // GPIO the LEDs are connected to
#define STRIP_2_PIN    32  // GPIO the LEDs are connected to
#define LED_COUNT  5  // Number of LEDs
#define BRIGHTNESS 50  // NeoPixel brightness, 0 (min) to 255 (max)
Adafruit_NeoPixel strip1(LED_COUNT, STRIP_1_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip2(LED_COUNT, STRIP_2_PIN, NEO_GRB + NEO_KHZ800);

// Create a sensor object
Adafruit_BME280 bme;         // BME280 connect to ESP32 I2C (GPIO 21 = SDA, GPIO 22 = SCL)

//Variables to hold sensor readings
float temp;
float hum;
float pres;

// Json Variable to Hold Sensor Readings
JSONVar readings;

// Timer variables (get sensor readings)
unsigned long lastTime = 0;
unsigned long timerDelay = 30000;

//-----------------FUNCTIONS TO HANDLE SENSOR READINGS-----------------//

// Init BME280
void initBME(){
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
}

// Get Sensor Readings
void getSensorReadings(){
  temp = bme.readTemperature();
  hum = bme.readHumidity();
  pres= bme.readPressure()/100.0F;
}

// Return JSON String from sensor Readings
String getJSONReadings(){
  readings["temperature"] = String(temp);
  readings["humidity"] =  String(hum);
  readings["pressure"] = String(pres);
  String jsonString = JSON.stringify(readings);
  return jsonString;
}

//Update RGB LED colors accordingly to temp and hum values
void updateColors(){
  strip1.clear();
  strip2.clear();

  //Number of lit LEDs (temperature)
  int tempLEDs;
  if (temp<=0){
    tempLEDs = 1;
  }
  else if (temp>0 && temp<=10){
    tempLEDs = 2;
  }
  else if (temp>10 && temp<=20){
    tempLEDs = 3;
  }
  else if (temp>20 && temp<=30){
    tempLEDs = 4;
  }
  else{
    tempLEDs = 5;
  }

  //Turn on LEDs for temperature
  for(int i=0; i<tempLEDs; i++) {
    strip1.setPixelColor(i, strip1.Color(255, 165, 0));
    strip1.show();
  }

  //Number of lit LEDs (humidity)
  int humLEDs = map(hum, 0, 100, 1, LED_COUNT);

  //Turn on LEDs for humidity
  for(int i=0; i<humLEDs; i++) { // For each pixel...
    strip2.setPixelColor(i, strip2.Color(25, 140, 200));
    strip2.show();
  }
}

//-----------------FUNCTIONS TO HANDLE SPIFFS AND FILES-----------------//

// Initialize SPIFFS
void initSPIFFS() {
  if (!SPIFFS.begin(true)) {
    Serial.println("An error has occurred while mounting SPIFFS");
  }
  else{
    Serial.println("SPIFFS mounted successfully");
  }
}

// Read File from SPIFFS
String readFile(fs::FS &fs, const char * path){
  Serial.printf("Reading file: %s\r\n", path);

  File file = fs.open(path);
  if(!file || file.isDirectory()){
    Serial.println("- failed to open file for reading");
    return String();
  }

  String fileContent;
  while(file.available()){
    fileContent = file.readStringUntil('\n');
    break;
  }
  return fileContent;
}

// Write file to SPIFFS
void writeFile(fs::FS &fs, const char * path, const char * message){
  Serial.printf("Writing file: %s\r\n", path);

  File file = fs.open(path, FILE_WRITE);
  if(!file){
    Serial.println("- failed to open file for writing");
    return;
  }
  if(file.print(message)){
    Serial.println("- file written");
  } else {
    Serial.println("- frite failed");
  }
}

// Initialize WiFi
bool initWiFi() {
  if(ssid=="" || ip==""){
    Serial.println("Undefined SSID or IP address.");
    return false;
  }

  WiFi.mode(WIFI_STA);
  localIP.fromString(ip.c_str());

  if (!WiFi.config(localIP, gateway, subnet)){
    Serial.println("STA Failed to configure");
    return false;
  }
  WiFi.begin(ssid.c_str(), pass.c_str());
  Serial.println("Connecting to WiFi...");

  unsigned long currentMillis = millis();
  previousMillis = currentMillis;

  while(WiFi.status() != WL_CONNECTED) {
    currentMillis = millis();
    if (currentMillis - previousMillis >= interval) {
      Serial.println("Failed to connect.");
      return false;
    }
  }

  Serial.println(WiFi.localIP());
  return true;
}

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

  // Initialize strips
  strip1.begin();
  strip2.begin();

  // Set brightness
  strip1.setBrightness(BRIGHTNESS);
  strip2.setBrightness(BRIGHTNESS);

  // Init BME280 senspr
  initBME();

  // Init SPIFFS
  initSPIFFS();

  // Load values saved in SPIFFS
  ssid = readFile(SPIFFS, ssidPath);
  pass = readFile(SPIFFS, passPath);
  ip = readFile(SPIFFS, ipPath);
  /*Serial.println(ssid);
  Serial.println(pass);
  Serial.println(ip);*/

  if(initWiFi()) {
    // If ESP32 inits successfully in station mode light up all pixels in a teal color
    for(int i=0; i<LED_COUNT; i++) { // For each pixel...
      strip1.setPixelColor(i, strip1.Color(0, 255, 128));
      strip2.setPixelColor(i, strip2.Color(0, 255, 128));

      strip1.show();   // Send the updated pixel colors to the hardware.
      strip2.show();   // Send the updated pixel colors to the hardware.
    }

    //Handle the Web Server in Station Mode
    // Route for root / web page
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
      request->send(SPIFFS, "/index.html", "text/html");
    });
    server.serveStatic("/", SPIFFS, "/");

    // Request for the latest sensor readings
    server.on("/readings", HTTP_GET, [](AsyncWebServerRequest *request){
      getSensorReadings();
      String json = getJSONReadings();
      request->send(200, "application/json", json);
      json = String();
    });

    events.onConnect([](AsyncEventSourceClient *client){
      if(client->lastId()){
        Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
      }
    });
    server.addHandler(&events);

    server.begin();
  }
  else {
    // else initialize the ESP32 in Access Point mode
    // light up all pixels in a red color
    for(int i=0; i<LED_COUNT; i++) { // For each pixel...
      strip1.setPixelColor(i, strip1.Color(255, 0, 0));
      strip2.setPixelColor(i, strip2.Color(255, 0, 0));
      //strip1.setPixelColor(i, strip1.Color(128, 0, 21));
      //strip2.setPixelColor(i, strip2.Color(128, 0, 21));

      strip1.show();   // Send the updated pixel colors to the hardware.
      strip2.show();   // Send the updated pixel colors to the hardware.
    }

    // Set Access Point
    Serial.println("Setting AP (Access Point)");
    // NULL sets an open Access Point
    WiFi.softAP("ESP-WIFI-MANAGER", NULL);

    IPAddress IP = WiFi.softAPIP();
    Serial.print("AP IP address: ");
    Serial.println(IP);

    // Web Server Root URL For WiFi Manager Web Page
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
      request->send(SPIFFS, "/wifimanager.html", "text/html");
    });

    server.serveStatic("/", SPIFFS, "/");

    // Get the parameters submited on the form
    server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) {
      int params = request->params();
      for(int i=0;i<params;i++){
        AsyncWebParameter* p = request->getParam(i);
        if(p->isPost()){
          // HTTP POST ssid value
          if (p->name() == PARAM_INPUT_1) {
            ssid = p->value().c_str();
            Serial.print("SSID set to: ");
            Serial.println(ssid);
            // Write file to save value
            writeFile(SPIFFS, ssidPath, ssid.c_str());
          }
          // HTTP POST pass value
          if (p->name() == PARAM_INPUT_2) {
            pass = p->value().c_str();
            Serial.print("Password set to: ");
            Serial.println(pass);
            // Write file to save value
            writeFile(SPIFFS, passPath, pass.c_str());
          }
          // HTTP POST ip value
          if (p->name() == PARAM_INPUT_3) {
            ip = p->value().c_str();
            Serial.print("IP Address set to: ");
            Serial.println(ip);
            // Write file to save value
            writeFile(SPIFFS, ipPath, ip.c_str());
          }
          //Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
        }
      }
      request->send(200, "text/plain", "Done. ESP will restart, connect to your router and go to IP address: " + ip);
      delay(3000);
      // After saving the parameters, restart the ESP32
      ESP.restart();
    });
    server.begin();
  }
}

void loop() {
  // If the ESP32 is set successfully in station mode...
  if (WiFi.status() == WL_CONNECTED) {

    //...Send Events to the client with sensor readins and update colors every 30 seconds
    if (millis() - lastTime > timerDelay) {
      getSensorReadings();
      updateColors();

      String message = getJSONReadings();
      events.send(message.c_str(),"new_readings" ,millis());
      lastTime = millis();
    }
  }
}

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

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

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

Сначала подключите все необходимые библиотеки:

#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "SPIFFS.h"
#include <Arduino_JSON.h>
#include <Adafruit_BME280.h>

Следующие переменные используются для поиска SSID, пароля и IP-адреса в HTTP POST запросе при отправке формы.

// Search for parameter in HTTP POST request
const char* PARAM_INPUT_1 = "ssid";
const char* PARAM_INPUT_2 = "pass";
const char* PARAM_INPUT_3 = "ip";

Переменные ssid, pass и ip сохраняют значения SSID, пароля и IP-адреса, отправленные через форму.

// Variables to save values from HTML form
String ssid;
String pass;
String ip;

SSID, пароль и IP-адрес при отправке сохраняются в файлах в файловой системе ESP. Следующие переменные указывают на пути этих файлов.

// File paths to save input values permanently
const char* ssidPath = "/ssid.txt";
const char* passPath = "/pass.txt";
const char* ipPath = "/ip.txt";

IP-адрес станции отправляется через форму Wi-Fi Manager. Однако вам нужно установить шлюз и подсеть в вашем коде:

IPAddress localIP;
//IPAddress localIP(192, 168, 1, 200); // hardcoded

// Set your Gateway IP address
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 0, 0);

Настройки WS2812B

Определите пины, управляющие RGB светодиодами. В данном случае у нас есть два отдельных ряда, подключённых к GPIO 27 и 32.

#define STRIP_1_PIN    27  // GPIO the LEDs are connected to
#define STRIP_2_PIN    32  // GPIO the LEDs are connected to

Определите количество светодиодов и яркость. Вы можете изменить яркость по желанию.

#define LED_COUNT  5  // Number of LEDs
#define BRIGHTNESS 20  // NeoPixel brightness, 0 (min) to 255 (max)

Наконец, инициализируйте два объекта Adafruit_Neopixel для управления каждой полоской: strip1 (температура) и strip2 (влажность):

Adafruit_NeoPixel strip1(LED_COUNT, STRIP_1_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip2(LED_COUNT, STRIP_2_PIN, NEO_GRB + NEO_KHZ800);

initBME()

Функция initBME() инициализирует датчик BME280 на стандартных I2C пинах ESP32:

void initBME(){
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
}

Узнайте больше о датчике BME280: ESP32 with BME280 Sensor using Arduino IDE (Pressure, Temperature, Humidity).

getSensorReadings()

Функция getSensorReadings() получает температуру, влажность и давление от датчика BME280 и сохраняет значения в переменных temp, hum и pres.

// Get Sensor Readings
void getSensorReadings(){
  temp = bme.readTemperature();
  hum = bme.readHumidity();
  pres= bme.readPressure()/100.0F;
}

getJSONReadings()

Функция getJSONReadings() возвращает JSON строку из текущих значений температуры, влажности и давления.

// Return JSON String from sensor Readings
String getJSONReadings(){
  readings["temperature"] = String(temp);
  readings["humidity"] =  String(hum);
  readings["pressure"] = String(pres);
  String jsonString = JSON.stringify(readings);
  return jsonString;
}

updateColors()

Функция updateColors() зажигает RGB светодиоды в соответствии с диапазоном значений температуры и влажности.

Сначала нужно очистить полоски методом clear():

strip1.clear();
strip2.clear();

Нам нужно определить, сколько светодиодов мы хотим зажечь, учитывая значения температуры и влажности. Мы сохраняем количество светодиодов для включения в переменных tempLEDs и humLEDs.

Для температуры, если температура равна или ниже нуля градусов Цельсия, мы зажигаем один светодиод:

if (temp<=0){
  tempLEDs = 1;
}

Вот другие диапазоны:

  • 0<temperature<=10 –> 2 LED

  • 10<temperature<=20 –> 3 LED

  • 20<temperature<=30 –> 4 LED

  • 30<temperature –> 5 LED

//Number of lit LEDs (temperature)
int tempLEDs;
if (temp<=0){
  tempLEDs = 1;
}
else if (temp>0 && temp<=10){
  tempLEDs = 2;
}
else if (temp>10 && temp<=20){
  tempLEDs = 3;
}
else if (temp>20 && temp<=30){
      tempLEDs = 4;
}
else{
  tempLEDs = 5;
}

Определив, сколько светодиодов должно быть включено, нам нужно фактически зажечь эти светодиоды. Для включения светодиода мы можем использовать метод setPixelColor() объекта strip1, за которым следует метод show(). Нам нужен цикл for для включения всех светодиодов.

//Turn on LEDs for temperature
for(int i=0; i<tempLEDs; i++) {
  strip1.setPixelColor(i, strip1.Color(255, 165, 0));
  strip1.show();
}

Мы следуем аналогичной процедуре для влажности. Сначала определяем, сколько светодиодов должно быть включено:

//Number of lit LEDs (humidity)
int humLEDs = map(hum, 0, 100, 1, LED_COUNT);

И наконец, зажигаем светодиоды влажности (strip2):

for(int i=0; i<humLEDs; i++) { // For each pixel...
  strip2.setPixelColor(i, strip2.Color(25, 140, 200));
  strip2.show();
}

initSPIFFS()

Эта функция инициализирует файловую систему ESP32 SPIFFS. В этом проекте мы сохраняем HTML, CSS и JavaScript файлы для построения страниц веб-сервера в файловой системе. У нас также есть файлы .txt для сохранения SSID, пароля и IP-адреса.

void initSPIFFS() {
  if (!SPIFFS.begin(true)) {
    Serial.println("An error has occurred while mounting SPIFFS");
  }
  else{
    Serial.println("SPIFFS mounted successfully");
  }
}

readFile()

Функция readFile() читает и возвращает содержимое файла.

String readFile(fs::FS &fs, const char * path){
  Serial.printf("Reading file: %s\r\n", path);

  File file = fs.open(path);
  if(!file || file.isDirectory()){
    Serial.println("- failed to open file for reading");
    return String();
  }

  String fileContent;
  while(file.available()){
    fileContent = file.readStringUntil('\n');
    break;
  }
  return fileContent;
}

writeFile()

Функция writeFile() записывает содержимое в файл.

void writeFile(fs::FS &fs, const char * path, const char * message){
  Serial.printf("Writing file: %s\r\n", path);

  File file = fs.open(path, FILE_WRITE);
  if(!file){
    Serial.println("- failed to open file for writing");
    return;
  }
  if(file.print(message)){
    Serial.println("- file written");
  } else {
    Serial.println("- frite failed");
  }
}

initWiFi()

Функция initWiFi() возвращает булевое значение (true или false), указывающее, успешно ли плата ESP подключилась к сети.

Сначала она проверяет, пусты ли переменные ssid и ip. Если да, она не сможет подключиться к сети, поэтому возвращает false.

if(ssid=="" || ip==""){
  Serial.println("Undefined SSID or IP address.");
  return false;
}

Если это не так, мы попытаемся подключиться к сети, используя SSID и пароль, сохранённые в переменных ssid и pass, и установим IP-адрес.

WiFi.mode(WIFI_STA);
localIP.fromString(ip.c_str());

if (!WiFi.config(localIP, gateway, subnet)){
  Serial.println("STA Failed to configure");
  return false;
}
WiFi.begin(ssid.c_str(), pass.c_str());
Serial.println("Connecting to WiFi...");

Если через 10 секунд (переменная interval) не удалось подключиться к Wi-Fi, функция вернёт false.

unsigned long currentMillis = millis();
previousMillis = currentMillis;

while(WiFi.status() != WL_CONNECTED) {
  currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    Serial.println("Failed to connect.");
    return false;
  }
}

Serial.println(WiFi.localIP());

Если ни одно из предыдущих условий не выполнено, это означает, что ESP успешно подключился к сети в режиме станции (возвращает true).

return true;

setup()

В setup() инициализируйте Serial Monitor.

Serial.begin(115200);

Инициализируйте ряды адресных RGB светодиодов (полоски):

// Initialize strips
strip1.begin();
strip2.begin();

Установите яркость полосок. Вы можете изменить яркость в переменной BRIGHTNESS.

// Set brightness
strip1.setBrightness(BRIGHTNESS);
strip2.setBrightness(BRIGHTNESS);

Вызовите функцию initBME() для инициализации датчика:

// Init BME280 senspr
initBME();

Инициализируйте файловую систему:

// Init SPIFFS
initSPIFFS();

Прочитайте файлы для получения ранее сохранённых SSID, пароля и IP-адреса.

// Load values saved in SPIFFS
ssid = readFile(SPIFFS, ssidPath);
pass = readFile(SPIFFS, passPath);
ip = readFile(SPIFFS, ipPath);

Если ESP успешно подключается в режиме станции (функция initWiFi() возвращает true):

if(initWiFi()) {

Зажгите все светодиоды бирюзовым цветом, чтобы мы знали, что ESP32 успешно подключился к сети Wi-Fi:

// If ESP32 inits successfully in station mode light up all pixels in a teal color
for(int i=0; i<LED_COUNT; i++) { // For each pixel...
  strip1.setPixelColor(i, strip1.Color(0, 255, 128));
  strip2.setPixelColor(i, strip2.Color(0, 255, 128));

  strip1.show();   // Send the updated pixel colors to the hardware.
  strip2.show();   // Send the updated pixel colors to the hardware.
}

Затем мы можем настроить команды для обработки запросов веб-сервера. Отправьте файл index.html при обращении к корневому URL:

//Handle the Web Server in Station Mode
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
  request->send(SPIFFS, "/index.html", "text/html");
});

Отправьте CSS и JavaScript файлы, запрошенные HTML файлом (которые также сохранены в SPIFFS):

server.serveStatic("/", SPIFFS, "/");

Когда вы впервые открываете страницу веб-сервера, она делает запрос к серверу по URL /readings для получения последних показаний датчиков. Когда это происходит, отправьте JSON строку с показаниями:

// Request for the latest sensor readings
server.on("/readings", HTTP_GET, [](AsyncWebServerRequest *request){
  getSensorReadings();
  String json = getJSONReadings();
  request->send(200, "application/json", json);
  json = String();
});

Настройте server-sent events на сервере:

events.onConnect([](AsyncEventSourceClient *client){
  if(client->lastId()){
    Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
  }
});
server.addHandler(&events);

Наконец, запустите сервер:

server.begin();

Если ESP32 не может подключиться к сети Wi-Fi, функция initWiFi() возвращает false. В этом случае зажгите все светодиоды красным цветом, чтобы мы знали, что ESP32 будет в режиме точки доступа:

for(int i=0; i<LED_COUNT; i++) { // For each pixel...
  strip1.setPixelColor(i, strip1.Color(128, 0, 21));
  strip2.setPixelColor(i, strip2.Color(128, 0, 21));

  strip1.show();   // Send the updated pixel colors to the hardware.
  strip2.show();   // Send the updated pixel colors to the hardware.
}

Настройте ESP как точку доступа:

// Set Access Point
Serial.println("Setting AP (Access Point)");
// NULL sets an open Access Point
WiFi.softAP("ESP-WIFI-MANAGER", NULL);

IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);

Для настройки точки доступа мы используем метод softAP() и передаём в качестве аргументов имя точки доступа и пароль. Мы хотим, чтобы точка доступа была открытой, поэтому устанавливаем пароль в NULL. Вы можете добавить пароль по желанию. Чтобы узнать больше о настройке точки доступа, прочитайте следующее руководство:

Когда вы подключаетесь к точке доступа, она показывает веб-страницу для ввода сетевых учётных данных в форму. Поэтому ESP должен отправить файл wifimanager.html при получении запроса по корневому URL /.

// Web Server Root URL For WiFi Manager Web Page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(SPIFFS, "/wifimanager.html", "text/html");
});

Мы также должны обработать, что происходит при отправке формы через HTTP POST запрос.

Следующие строки сохраняют отправленные значения в переменных ssid, pass и ip и записывают эти переменные в соответствующие файлы.

// Get the parameters submited on the form
server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) {
  int params = request->params();
  for(int i=0;i<params;i++){
    AsyncWebParameter* p = request->getParam(i);
    if(p->isPost()){
      // HTTP POST ssid value
      if (p->name() == PARAM_INPUT_1) {
        ssid = p->value().c_str();
        Serial.print("SSID set to: ");
        Serial.println(ssid);
        // Write file to save value
        writeFile(SPIFFS, ssidPath, ssid.c_str());
      }
      // HTTP POST pass value
      if (p->name() == PARAM_INPUT_2) {
        pass = p->value().c_str();
        Serial.print("Password set to: ");
        Serial.println(pass);
        // Write file to save value
        writeFile(SPIFFS, passPath, pass.c_str());
      }
      // HTTP POST ip value
      if (p->name() == PARAM_INPUT_3) {
        ip = p->value().c_str();
        Serial.print("IP Address set to: ");
        Serial.println(ip);
        // Write file to save value
        writeFile(SPIFFS, ipPath, ip.c_str());
      }
      //Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
    }
  }

После отправки формы отправьте ответ с текстом, чтобы мы знали, что ESP получил данные формы:

request->send(200, "text/plain", "Done. ESP will restart, connect to your router and go to IP address: " + ip);

Через три секунды перезагрузите плату ESP с помощью ESP.restart():

delay(3000);
// After saving the parameters, restart the ESP32
ESP.restart();

После перезагрузки плата будет иметь SSID и пароль, сохранённые в файлах, и успешно инициализируется в режиме станции.

loop()

В loop() проверьте, успешно ли ESP32 подключён к Wi-Fi станции:

if (WiFi.status() == WL_CONNECTED) {

Если да, выполняйте следующее каждые 30 секунд (переменная timerDelay):

  • получить последние показания датчиков: вызвать функцию getSensorReadings();

  • обновить цвета RGB светодиодов в соответствии со значениями температуры и влажности: вызвать функцию updateColors();

  • отправить событие в браузер с последними показаниями датчиков в формате JSON.

if (millis() - lastTime > timerDelay) {
  getSensorReadings();
  updateColors();

  String message = getJSONReadings();
  events.send(message.c_str(),"new_readings" ,millis());
  lastTime = millis();
}

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

После успешной загрузки всех файлов вы можете открыть Serial Monitor. Если код запускается впервые, он попытается прочитать файлы ssid.txt, pass.txt и ip.txt, и это не удастся, потому что эти файлы ещё не были созданы. Поэтому он запустит точку доступа.

Serial Monitor ESP32 Access Point Mode

Все светодиоды на шилде загорятся красным, указывая, что плата настроена как точка доступа.

ESP32 Status Indicator Sensor PCB Shield Access Point Mode

На вашем компьютере или смартфоне перейдите в настройки сети и подключитесь к точке доступа ESP-WIFI-MANAGER.

Connect to ESP32 Wi-Fi Manager Access Point

Затем откройте браузер и перейдите по адресу 192.168.4.1. Должна открыться веб-страница Wi-Fi Manager.

ESP32 Wi-Fi Manager Web Page

Введите ваши сетевые учётные данные: SSID и пароль, а также доступный IP-адрес в вашей локальной сети. После этого вы будете перенаправлены на следующую страницу:

ESP32 connected to station success Wi-Fi Manager

В то же время ESP должен вывести следующее в Serial Monitor, указывая, что введённые вами параметры были успешно сохранены в соответствующих файлах:

ESP32 Wi-Fi Manager Save Credentials Serial Monitor

Через несколько секунд ESP перезагрузится. И если вы ввели правильные SSID и пароль, он запустится в режиме станции:

ESP32 Station Mode Success Serial Monitor

Все светодиоды на шилде загорятся бирюзовым цветом на 30 секунд.

ESP32 Status Indicator Sensor PCB Shield Station Mode

На этот раз откройте браузер в вашей локальной сети и введите IP-адрес ESP. Вы должны получить доступ к веб-странице с показаниями датчиков:

ESP32 BME280 Sensor Readings Web Server Table

Светодиоды на шилде загорятся в соответствии с диапазоном температуры и влажности.

ESP32 Status Indicator Sensor Shield Temperature Humidity Range RGB LEDs

Примечание: предыдущая фотография и скриншот веб-сервера были сделаны в разное время (поэтому значения в таблице не совпадают с количеством горящих светодиодов).

Заключение

В этом руководстве вы узнали, как создать шилд для ESP32 с датчиком BME280, двумя рядами адресных RGB светодиодов и кнопкой. Вы также узнали, как настроить Wi-Fi Manager для ваших проектов с веб-сервером. С Wi-Fi Manager вы можете легко подключать ваши ESP веб-серверы к различным сетям без необходимости жёстко задавать сетевые учётные данные. Вы можете применить Wi-Fi Manager к любому проекту с веб-сервером.

Если вам понравилось это руководство, у нас есть другие похожие проекты, включающие создание и проектирование PCB:

Узнайте больше об ESP32 с нашими ресурсами: