Руководство · Средний · 1.5 hours

Управление роботом из браузера с помощью веб-сервера ESP32

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

Введение

HC-05 требует приложения последовательного терминала и танца с сопоставлением. ESP32 может сделать кое-что лучше: быть собственной сетью и раздавать собственную страницу управления. Подключаете телефон к Wi-Fi робота, открываете браузер и управляете. Ничего устанавливать не нужно, и это работает с любого устройства с браузером.

В ESP32 на кристалле есть Wi-Fi и Bluetooth, два ядра по 240 MHz и достаточно ОЗУ (RAM), чтобы хранить веб-страницу во flash-памяти и раздавать её. В этом весь секрет.

Вертикальная блок-схема из пяти этапов. Браузер телефона отправляет HTTP-запрос GET на маршрут команды движения вперёд. ESP32, работающий как программная точка доступа или подключённый к сети, принимает его. Его HTTP-сервер передаёт запрос обработчику. Обработчик управляет выводами направления и PWM драйвера двигателя. Колёса вращаются, и робот движется. Примечание предупреждает, что сторожевой таймер останавливает двигатели, если запросы перестают приходить.
Все слои между большим пальцем и колесом. Самое важное — сторожевой таймер: без него потеря соединения оставляет робот движущимся. Скачать SVG

Точка доступа или существующая сеть?

Есть два режима, и выбор важнее, чем кажется.

Точка доступа (AP) — ESP32 создаёт собственную сеть Wi-Fi, и телефон подключается к ней.

  • Работает где угодно, без маршрутизатора. Робот самодостаточен.
  • Телефон теряет интернет-соединение во время управления, и некоторые телефоны молча переключатся на мобильные данные и отключают вас.
  • Адрес всегда один и тот же, обычно 192.168.4.1.

Станция (STA) — ESP32 подключается к существующей сети Wi-Fi.

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

Для переносного робота режим AP почти всегда правильный:

#include <WiFi.h>
#include <WebServer.h>

WebServer server(80);

void setup() {
  WiFi.softAP("RobotCar", "drive1234");   // password must be 8+ characters
  Serial.println(WiFi.softAPIP());        // 192.168.4.1
  server.on("/", handleRoot);
  server.on("/cmd", handleCommand);
  server.begin();
}

void loop() {
  server.handleClient();
  applyDeadman();                          // see below — this is not optional
}

Раздача страницы управления

Страница хранится во flash-памяти как необработанная строка. Делайте её небольшой — каждый байт раздаётся с микроконтроллера, — а элементы управления сделайте удобными для касания, потому что весь смысл состоит в управлении с телефона.

const char PAGE[] PROGMEM = R"(
<!doctype html><meta name=viewport content="width=device-width,initial-scale=1">
<style>
  body{font-family:system-ui;margin:0;display:grid;place-items:center;height:100vh}
  button{width:90px;height:90px;font-size:24px;margin:4px;touch-action:manipulation}
</style>
<div>
  <div style="text-align:center"><button data-c="F">▲</button></div>
  <div><button data-c="L">◀</button><button data-c="S">■</button><button data-c="R">▶</button></div>
  <div style="text-align:center"><button data-c="B">▼</button></div>
</div>
<script>
let held = null;
const send = c => fetch('/cmd?c=' + c);
for (const b of document.querySelectorAll('button')) {
  // Repeat while held, so releasing and losing signal look the same to the robot.
  b.onpointerdown = e => { held = b.dataset.c; send(held);
                           b.setPointerCapture(e.pointerId); };
  b.onpointerup = b.onpointercancel = () => { held = null; send('S'); };
}
setInterval(() => held && send(held), 100);
</script>
)";

void handleRoot() { server.send_P(200, "text/html", PAGE); }

touch-action: manipulation на кнопках важен: без него мобильные браузеры ждут около 300 ms, чтобы понять, не было ли это двойным нажатием, и робот кажется сломанным.

Аварийный тайм-аут, снова

Всё, что учебник по Bluetooth говорит о потере связи, применимо здесь ещё сильнее: дальность действия Wi-Fi обрывается резко, а не плавно.

unsigned long lastCommandAt = 0;

void handleCommand() {
  char c = server.arg("c")[0];
  drive(c);
  lastCommandAt = millis();
  server.send(200, "text/plain", "ok");
}

void applyDeadman() {
  if (millis() - lastCommandAt > 400) stop();
}

Браузер отправляет удерживаемую команду каждые 100 ms, поэтому тайм-аут 400 ms допускает три потерянных запроса до остановки. Это соотношение и нужно настраивать: слишком жёсткое — робот заикается на слабой связи, слишком мягкое — продолжает движение после потери связи.

Когда переходить на WebSockets

Подход с fetch выше открывает новое HTTP-соединение для каждой команды. Он прост, его легко отлаживать на вкладке сети браузера, а при 10 командах в секунду он вполне подходит.

Он перестаёт подходить, когда нужна телеметрия обратно. Опрос значений датчиков дополнительными HTTP-запросами удваивает трафик и добавляет задержку в обоих направлениях. WebSocket поддерживает одно открытое соединение и позволяет любой стороне говорить в любой момент:

#include <WebSocketsServer.h>
WebSocketsServer ws(81);

void onWsEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t len) {
  if (type == WStype_TEXT) { drive(payload[0]); lastCommandAt = millis(); }
  if (type == WStype_DISCONNECTED) stop();   // the link closing IS a stop command
}

Последняя строка — настоящее улучшение. При HTTP робот может только предположить потерю соединения по молчанию; WebSocket сообщает момент закрытия, поэтому робот останавливается сразу, а не после тайм-аута.

Начните с HTTP. Переходите на WebSockets, когда роботу нужно передавать данные обратно.

Питание

ESP32 — микросхема на 3.3 V, и он потребляет ток резкими импульсами при передаче по радио — значительно больше 200 mA в пике. Отсюда два следствия.

Не питайте его от вывода 3.3 V Arduino. Этот стабилизатор не выдержит пиков передачи, и симптомом будет перезагрузка платы при загруженном Wi-Fi, что выглядит как ошибка кода, но ею не является.

Выводы GPIO не допускают 5 V. Всё, что подаёт сигнал на ESP32 от логики 5 V, нуждается в делителе или преобразователе уровня — как линия RX HC-05.

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

Проверьте перед движением

  1. Раздайте страницу. Подключите телефон к RobotCar, откройте http://192.168.4.1 и убедитесь, что появились кнопки.
  2. Наблюдайте за командами. Печатайте каждую принятую команду по USB. Удерживайте кнопку и убедитесь, что повторы приходят примерно 10 раз в секунду.
  3. Разорвите связь. Удерживайте движение вперёд, затем выключите Wi-Fi телефона. Робот должен остановиться за время тайм-аута.

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

Исследуйте граф

Входит в эти сборки

Проекты и учебные пути, в которые входит это руководство.

Дополнительные материалы

Источники