Tutorial · Intermediário · 1,5 horas

Controle um robô pelo navegador com um servidor web do ESP32

Transforme um ESP32 em um ponto de acesso Wi-Fi para servir uma página de controle e controlar o robô pelo navegador sem aplicativo.

Introdução

Um HC-05 precisa de um aplicativo de terminal serial e de uma dança de pareamento. Um ESP32 pode fazer algo melhor: ser sua própria rede e servir sua própria página de controle. Você conecta o celular à rede Wi-Fi do robô, informa a senha, abre um navegador e controla o robô. Não há nada para instalar, e funciona em qualquer dispositivo com navegador.

O ESP32 tem Wi-Fi e Bluetooth integrados no mesmo chip, dois núcleos a 240 MHz e RAM suficiente para guardar uma página web na flash e servi-la. Esse é todo o truque.

Diagrama de fluxo vertical de cinco estágios. O navegador de um celular envia uma requisição HTTP GET para a rota de avanço. O ESP32, agindo como ponto de acesso por software ou conectado a uma rede, recebe a requisição. Seu servidor HTTP roteia a requisição para um tratador. O tratador aciona os pinos de direção e o PWM do driver de motor. As rodas giram e o robô se move. Uma nota avisa que um watchdog para os motores se as requisições pararem de chegar.
Cada camada entre um polegar e uma roda. O watchdog é o mais importante: sem ele, uma conexão interrompida deixa o robô se movendo. Baixar SVG

Ponto de acesso ou rede existente?

Dois modos, e a escolha importa mais do que parece.

Ponto de acesso (AP) — o ESP32 cria sua própria rede Wi-Fi e o celular se conecta a ela.

  • Funciona em qualquer lugar, sem roteador. O robô é autossuficiente.
  • O celular perde a conexão com a internet enquanto você controla o robô, e alguns celulares voltam silenciosamente para os dados móveis e encerram a conexão.
  • O endereço é sempre o mesmo, normalmente 192.168.4.1.

Estação (STA) — o ESP32 entra na sua rede Wi-Fi existente.

  • O celular mantém a conexão com a internet.
  • O IP do robô é atribuído pelo roteador e pode mudar entre inicializações.
  • Não serve fora do alcance daquela rede específica, o que inviabiliza demonstrações fora desse ambiente.

Para um robô que você carrega, o modo AP quase sempre é o certo:

#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
}

Servindo a página de controle

A página fica na flash como um literal de texto. Mantenha-a pequena — cada byte é servido pelo microcontrolador — e faça os controles adequados para toque, porque o ponto todo é controlar o robô pelo celular.

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 nos botões importa: sem isso, navegadores móveis esperam cerca de 300 ms para descobrir se você quis dar um toque duplo, e o robô parece quebrado.

O tempo limite de segurança, novamente

Tudo o que o tutorial de Bluetooth diz sobre perder a conexão se aplica aqui, e isso é ainda mais importante — o sinal do Wi-Fi cai de repente quando você se afasta, em vez de desaparecer gradualmente.

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();
}

O navegador envia o comando mantido a cada 100 ms, então um tempo limite de 400 ms tolera três requisições perdidas antes de parar. Essa proporção é a coisa a ajustar: se for muito apertada, o robô dá solavancos em uma conexão fraca; se for muito folgada, continua se movendo depois que você perdeu a conexão.

Quando migrar para WebSockets

A abordagem com fetch acima abre uma nova conexão HTTP para cada comando. É simples, fácil de depurar na aba de rede do navegador e, a dez comandos por segundo, funciona bem.

Ela deixa de funcionar bem quando você quer telemetria de volta. Consultar valores de sensores com mais requisições HTTP dobra o tráfego e acrescenta latência nos dois sentidos. Um WebSocket mantém uma conexão aberta e permite que qualquer uma das partes fale a qualquer momento:

#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
}

Essa última linha é a verdadeira evolução. Com HTTP, o robô só pode inferir uma conexão perdida pelo silêncio; um WebSocket informa o momento em que ela fecha, então o robô para imediatamente, em vez de depois de um tempo limite.

Comece com HTTP. Migre para WebSockets quando quiser que o robô converse de volta.

Alimentação

O ESP32 opera em 3,3 V e consome corrente em picos quando o rádio transmite — bem acima de 200 mA no pico. Duas consequências:

Não alimente-o pelo pino de 3,3 V do Arduino. Esse regulador não fornece os picos de transmissão, e o sintoma é uma placa que reinicia sempre que o Wi-Fi fica ocupado, o que parece uma falha de código, mas não é.

Seus pinos GPIO não são tolerantes a 5 V. Qualquer sinal de lógica de 5 V alimentado ao ESP32 precisa de um divisor ou de um tradutor de nível, assim como a linha RX do HC-05.

Dê a ele uma fonte de alimentação adequada a partir do pacote de bateria por um regulador próprio, mantendo a fonte do motor separada.

Verifique antes de controlar

  1. Sirva a página. Conecte um celular à RobotCar, abra http://192.168.4.1 e confirme que os botões aparecem.
  2. Observe os comandos. Imprima cada comando recebido pelo USB. Mantenha um botão pressionado e confirme que as repetições chegam a cerca de 10 por segundo.
  3. Interrompa a conexão. Mantenha o avanço pressionado e desligue o Wi-Fi do celular. O robô precisa parar dentro do tempo limite.

Depois coloque-o em um robô: o projeto de robô controlado por celular constrói tudo, por rádio, em um chassi que também é capaz de se controlar sozinho.

Explore o grafo

Faz parte destas montagens

Projetos e caminhos de aprendizagem que incluem este tutorial.

Leitura complementar

Referências