PTENES
MÓDULO 2.1 · PRÁCTICA

📱 Control desde el celular: Wi-Fi y BLE

El ESP32 ya tiene Wi-Fi y Bluetooth en el chip. En este módulo usas ambos para manejar el rover desde el celular: una página con botones, un joystick en el navegador sin retraso y una app BLE. Y, antes que nada, la regla que distingue un juguete de un robot seguro: si pierde la señal, el robot se detiene.

6
Temas
Intermedio
Nivel
Práctica
Tipo
Rover de la T1
Hardware
0%
0 de 0 temas leídos · Sección 1 de 6

Contenido detallado

1

📶 Wi-Fi: modo estación y punto de acceso

El Wi-Fi del ESP32 funciona de dos maneras. En modo estación (STA), se conecta a la red de tu casa o escuela, como cualquier celular, y recibe una dirección del router. En modo punto de acceso (AP), crea su propia red: el celular se conecta directamente al robot, sin router. Para un robot que va a la calle, a una feria de ciencias o a la cancha, el AP es la opción más predecible.

🌱 ¿Eres nuevo aquí?

O SSID es el nombre de la red que aparece en el celular. La dirección IP es el «número de casa» de cada dispositivo en la red (ej.: 192.168.4.1). Un router distribuye esas direcciones. En modo AP, el propio ESP32 cumple esa función y siempre está en 192.168.4.1.

ESTACIÓN (STA) PUNTO DE ACCESO (AP)router celularESP32 ESP32 celularIP asignada por el router IP fija 192.168.4.1

Cómo leer: a la izquierda, todo pasa por el router; si está lejos o la red pide iniciar sesión, el robot se queda sin control. A la derecha, el ESP32 es la propia red y el celular se comunica directamente con él. El sketch de abajo intenta la primera opción y pasa a la segunda si no lo consigue.

Objetivo: intentar conectarse a tu red durante 10 segundos; si falla, crear la red propia del robot. El Monitor Serial muestra qué modo funcionó y la dirección que debes abrir en el navegador.

wifi-sta-ou-ap.ino
// Wi-Fi: intenta el modo estación y, si falla, pasa al punto de acceso (paquete esp32 3.x)
#include <WiFi.h>

const char* REDE_SSID  = "<seu_wifi>";      // cámbialo por el nombre de tu red
const char* REDE_SENHA = "<sua_senha>";
const char* AP_SSID    = "RoverINEMA";
const char* AP_SENHA   = "troque1234";      // mínimo de 8 caracteres

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(REDE_SSID, REDE_SENHA);
  Serial.print("Conectando");
  unsigned long inicio = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - inicio < 10000) {
    delay(250);
    Serial.print(".");
  }
  Serial.println();
  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("Modo ESTACAO. Abra http://");
    Serial.println(WiFi.localIP());
  } else {
    WiFi.disconnect(true);
    WiFi.mode(WIFI_AP);
    WiFi.softAP(AP_SSID, AP_SENHA);
    Serial.print("Modo PONTO DE ACESSO. Conecte em RoverINEMA e abra http://");
    Serial.println(WiFi.softAPIP());
  }
}

void loop() {                                   // cada 5 s: cuántos dispositivos están en la red del robot
  if (WiFi.getMode() == WIFI_AP) { Serial.print("Clientes: "); Serial.println(WiFi.softAPgetStationNum()); }
  delay(5000);
}

Cómo verificar

  • ✓Con el SSID y la contraseña correctos: «Modo ESTACAO. Abra http://192.168.x.y».
  • ✓Si escribes mal la contraseña a propósito: después de 10 s, la red «RoverINEMA» aparece en el celular y el contador de clientes sube a 1 cuando te conectas.
  • ✗¿Nunca se conecta? El ESP32 solo detecta redes de 2,4 GHz; las redes de 5 GHz únicamente no aparecen.

Por qué aprender

Todo control remoto de este módulo, y la telemetría del 2.5, empieza con el ESP32 conectado a la red. Elegir el modo correcto evita la escena clásica de la feria: el robot funcionaba en casa y no se conecta al Wi-Fi de la escuela, que pide iniciar sesión en el navegador.

Conceptos clave

STA
se conecta a una red
AP
crea su propia red
IP
dirección en la red
2,4 GHz
la banda que usa el ESP32
2

🌐 Servidor web en el ESP32: una página con botones

La biblioteca WebServer, que ya viene en el paquete esp32 3.x, convierte la placa en un pequeño sitio web. Registras rutas («si alguien solicita /cmd?d=F, ejecuta esta función») y el navegador del celular se convierte en el control remoto. No hace falta instalar ninguna aplicación.

🌱 ¿Eres nuevo aquí?

HTTP es la conversación entre el navegador y un sitio web: el navegador hace una solicitud (GET /cmd?d=F) y el servidor envía una respuesta (texto o página). Cada clic abre una conversación nueva, que tarda algunas decenas de milisegundos. Para los botones basta; para un joystick, veremos en el tema 3 que no.

Objetivo: servir una página con cinco botones. Cada toque llama a una ruta, el ESP32 escribe el comando en el Serial Monitor y enciende el LED del GPIO 2 mientras el robot «avanza». Primero haz la prueba sin motores; en el tema 6 cambiarás el LED por los motores.

botones-web.ino
// Página con botones servida por el ESP32 (WebServer del paquete esp32 3.x)
#include <WiFi.h>
#include <WebServer.h>

WebServer server(80);
const int PINO_LED = 2;

const char PAGINA[] = R"html(
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<style>body{font-family:sans-serif;text-align:center;background:#111;color:#eee}
button{width:90px;height:60px;margin:6px;font-size:18px;border-radius:10px}</style></head><body><h2>Rover INEMA</h2>
<button onclick="c('F')">Frente</button><br><button onclick="c('L')">Esq.</button><button onclick="c('S')">Parar</button>
<button onclick="c('R')">Dir.</button><br><button onclick="c('B')">Ré</button><p id="st">pronto</p>
<script>function c(d){fetch('/cmd?d='+d).then(r=>r.text()).then(t=>document.getElementById('st').textContent=t)}</script>
</body></html>)html";

void paginaInicial() { server.send(200, "text/html", PAGINA); }

void comando() {
  String d = server.arg("d");                   // F, B, L, R ou S
  digitalWrite(PINO_LED, d != "S");            // LED encendido = "en movimiento"
  Serial.print("Comando: "); Serial.println(d);
  server.send(200, "text/plain", "ok: " + d);
}

void setup() {
  Serial.begin(115200);
  pinMode(PINO_LED, OUTPUT);
  WiFi.softAP("RoverINEMA", "troque1234");
  server.on("/", paginaInicial);
  server.on("/cmd", comando);
  server.begin();
  Serial.print("Conéctate a RoverINEMA y abre http://");
  Serial.println(WiFi.softAPIP());
}

void loop() {
  server.handleClient();                          // atiende las solicitudes recibidas
}

Cómo verificar

  • ✓En el celular, conéctate a la red RoverINEMA y abre http://192.168.4.1: verás los cinco botones.
  • ✓Al tocar «Frente», aparece «Comando: F» en el Serial Monitor, se enciende el LED y la página muestra «ok: F».
  • ✗¿La página se abre, pero los botones no responden? Es posible que el celular haya salido de la red del robot por «falta de internet»; indícale que mantenga la conexión.

✓ Hazlo

  • ✓Llamar server.handleClient() en cada vuelta del loop.
  • ✓Guardar la página en una constante y responder rápido.
  • ✓Usar un botón «Parar» grande y siempre visible.

✗ Evita

  • ✗delay() largo en el loop: la página se bloquea hasta que termine.
  • ✗Cargar imágenes y bibliotecas de internet: en modo AP no hay internet.
  • ✗Comando «avanzar» sin el comando «parar» correspondiente.

Por qué aprender

Una página servida por el propio robot funciona en cualquier celular, sin tienda de aplicaciones. Después, el mismo servidor mostrará telemetría, ajustará las ganancias del PID del 2.3 y activará y desactivará modos, sin volver a grabar el ESP32.

Conceptos clave

Ruta
dirección → función
GET + parámetro
/cmd?d=F
handleClient
atiende en el loop
fetch
solicitud sin recargar
3

⚡ WebSocket: control sin demora

Con HTTP, cada comando abre y cierra una conversación. Un joystick envía la posición diez veces por segundo; abrir diez conversaciones por segundo genera una cola, demoras y comandos que llegan fuera de orden. WebSocket WebSocket abre un solo canal, que permanece abierto, y los mensajes circulan en ambos sentidos con unos pocos milisegundos de demora.

HTTP abre·envía·cierra abre·envía·cierraabre·envía·cierraWebSocket canal abierto una sola vez · cada punto es una posición del joysticktiempo →

Cómo leer: en la línea de arriba, tres comandos ocupan toda la línea y cada uno paga el costo de abrir y cerrar. En la de abajo, el mismo tiempo transporta diez comandos. Para conducir, lo que importa es el tiempo entre mover el dedo y que la rueda responda.

SituaciónUsaPor qué
Botones, ajustes, activar un modoHTTPpocas solicitudes, fáciles de probar
Joystick, volante, telemetría continuaWebSocketflujo constante, poca demora, comunicación en ambos sentidos
Sin red, solo con el celular cercaBLEbajo consumo, sin router (tema 4)

💡 Biblioteca adicional

El paquete esp32 incluye WiFi e WebServer, pero no un servidor WebSocket. Instala desde el Administrador de bibliotecas de Arduino IDE la biblioteca WebSockets, de Markus Sattler (links2004), y usa la versión más reciente indicada en el repositorio. Funciona en el puerto 81, junto al servidor web del puerto 80.

El protocolo que vas a definir es mínimo: el navegador envía el texto x,y, con cada número entre -100 y 100 (por ejemplo: 0,80 = avance al 80%). El ESP32 lo convierte en la velocidad de cada rueda. El código completo, con la página, WebSocket y motores, está en el tema 6.

Por qué aprender

Un robot controlado con medio segundo de demora choca contra la pared antes de obedecer. WebSocket también es la base del panel en vivo del 2.5: el mismo canal que transmite el comando devuelve la velocidad y el ángulo del robot.

Conceptos clave

Latencia
dedo → rueda
Canal abierto
una conexión, muchos mensajes
Comunicación en ambos sentidos
el comando va, el dato vuelve
Protocolo «x,y»
texto corto y fácil de depurar
4

🔵 BLE: controlar desde la aplicación del celular

O BLE (Bluetooth Low Energy) conecta el celular con el robot sin necesidad de una red y consume poca batería. En lugar de páginas, organiza los datos en servicios e características : el robot anuncia «tengo un servicio de comandos», la app se conecta y escribe un valor en una característica. El paquete esp32 3.x ya incluye la biblioteca BLE; la alternativa NimBLE-Arduino, más ligera, es opcional.

🌱 ¿Eres nuevo aquí?

Piensa en una característica como una cajita con nombre (un UUID, código largo y único) donde la app puede escribir o leer. Un servicio es el cajón que agrupa cajitas. Anunciar (advertising) es cuando el robot anuncia su propio nombre para que los celulares cercanos lo encuentren.

celularapp BLEescribe «F» ESP32 «RoverINEMA» servicio de comandos característica RXpuede escribir motores

Cómo leer: el recuadro punteado es el servicio; dentro de él, la característica resaltada es el único lugar donde la app escribe. La letra que llega allí se convierte en un comando para el motor. Los UUID del código de abajo siguen el estándar «UART sobre BLE», que muchas apps genéricas ya reconocen.

Objetivo: recibir letras por BLE (F, B, L, R, S), mostrarlas en el Serial Monitor y encender el LED. Usa una app genérica gratuita como nRF Connect o LightBlue para escribir en la característica.

ble-comandos.ino
// Comandos por BLE con la biblioteca BLE incluida en el paquete esp32 3.x
#include <BLEDevice.h>
#include <BLEServer.h>

#define SERVICO_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define RX_UUID      "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
const int PINO_LED = 2;
volatile char ultimoCmd = 'S';

class ConexaoCb : public BLEServerCallbacks {
  void onConnect(BLEServer* s) { Serial.println("Celular conectado"); }
  void onDisconnect(BLEServer* s) {
    ultimoCmd = 'S';                              // perdió el celular: se detiene
    Serial.println("Se desconectó: DETENIDO. Anunciando de nuevo.");
    BLEDevice::startAdvertising();
  }
};

class RxCb : public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic* c) {
    String v = c->getValue();                       // en la 3.x el valor llega como String
    if (v.length() == 0) return;
    char cmd = toupper(v[0]);
    if (strchr("FBLRS", cmd)) { ultimoCmd = cmd; Serial.print("Comando BLE: "); Serial.println(cmd); }
  }
};

void setup() {
  Serial.begin(115200);
  pinMode(PINO_LED, OUTPUT);
  BLEDevice::init("RoverINEMA");
  BLEServer* srv = BLEDevice::createServer();
  srv->setCallbacks(new ConexaoCb());
  BLEService* servico = srv->createService(SERVICO_UUID);
  BLECharacteristic* rx = servico->createCharacteristic(RX_UUID,
      BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
  rx->setCallbacks(new RxCb());
  servico->start();
  BLEDevice::getAdvertising()->addServiceUUID(SERVICO_UUID);
  BLEDevice::startAdvertising();
  Serial.println("BLE listo: busca RoverINEMA en la app");
}

void loop() {
  digitalWrite(PINO_LED, ultimoCmd != 'S');         // aquí se agregan los motores (tema 6)
  delay(20);
}

Cómo verificar

  • ✓En la app, «RoverINEMA» aparece en la búsqueda. Conéctate: el Serial Monitor muestra «Celular conectado».
  • ✓Escribe el texto F en la característica 6E400002: aparece «Comando BLE: F» y el LED se enciende. S lo apaga.
  • ✓Apaga el Bluetooth del celular mientras el LED está encendido: aparece «Se desconectó: DETENIDO» y el LED se apaga.

💡 Wi-Fi y BLE en el mismo sketch

Cada uno por separado cabe en la memoria predeterminada. Juntos, el programa suele superar el espacio de aplicación: en Herramientas → Partition Scheme, elige «Huge APP». El ESP32 comparte una sola radio entre ambos, así que el alcance y la latencia empeoran un poco. Para el rover, elige uno de los dos.

Por qué aprender

BLE funciona donde no hay Wi-Fi, consume menos y es el mismo estándar de los controles de videojuegos, relojes y sensores de gimnasio. Entender los servicios y las características también te ayuda a leer sensores BLE listos para usar en el robot, sin cables.

Conceptos clave

Servicio
cajón de datos
Característica
cajita donde escribe la app
UUID
nombre único
Advertising
el robot se anuncia
5

🛡️ Seguridad: contraseña y detención si se pierde la conexión

Con una conexión inalámbrica, pueden ocurrir dos cosas: alguien que no debería toma el control, o la señal se corta en medio de un comando de «avanzar». En el primer caso, una contraseña lo resuelve. En el segundo, la respuesta es el watchdog de comandos: el robot solo se mueve mientras recibe comandos nuevos. Si pasa poco tiempo (300 ms, por ejemplo) sin que llegue nada, se detiene solo, aunque el último comando haya sido «avanzar».

🌱 ¿Eres nuevo aquí?

Watchdog («perro guardián») es un temporizador al que hay que «alimentar» de vez en cuando. Si nadie lo alimenta, actúa. Aquí, cada comando que llega alimenta al perro; el silencio hace que el robot se detenga. A esto se le llama diseñar para una falla segura: si no hay información, el estado es detenido.

comandos se perdió la señalmotor encendido 300 ms sin recibir nada detenidoel último comando era «avanzar», pero el silencio tiene prioridad

Cómo leer: cada trazo cian representa la llegada de un comando. Mientras llegan, el motor (línea azul) sigue encendido. Cuando dejan de llegar, la escala ámbar cuenta 300 ms y el motor baja a cero. Sin el watchdog, la línea azul se quedaría arriba hasta que se agotara la batería o el robot chocara.

✓ Robot inalámbrico seguro

  • ✓Contraseña en el AP (8 caracteres o más) y cambia la contraseña de ejemplo.
  • ✓Watchdog de comandos: detenerse tras 200 a 500 ms de silencio.
  • ✓Detenerse al desconectarse (WebSocket o BLE).
  • ✓Velocidad máxima limitada en el ESP32, no solo en la página.

✗ Receta para un accidente

  • ✗Red abierta, sin contraseña, en una feria llena de celulares.
  • ✗«Avanzar» hasta que llegue un comando de «detenerse».
  • ✗Confiar en que la página del celular enviará el comando de «detenerse».
  • ✗Probar el primer control con el robot en el suelo, cerca de la escalera.

Prueba del watchdog en 4 pasos

1

Ruedas en el aire

Apoya el rover sobre un estuche o una caja, con las ruedas libres.

2

Mantén presionado «avanzar»

Con el joystick del tema 6, mantén el dedo hacia arriba: las ruedas giran.

3

Corta la señal

Activa el modo avión del celular sin levantar el dedo.

4

Verifica

Las ruedas se detienen en menos de medio segundo y el Serial Monitor muestra «watchdog: sin comando, DETENIDO». Solo entonces pon el robot en el suelo.

Por qué aprender

Los robots reales, desde las aspiradoras hasta los montacargas autónomos, se detienen cuando pierden el control. Vas a usar la misma idea en el 3.5, cuando Raspberry Pi envíe comandos al ESP32, y en la Ruta 4, con el tema /cmd_vel de ROS 2.

Conceptos clave

Watchdog
silencio = detenerse
Falla segura
sin datos, sin movimiento
Timeout
200 a 500 ms
Contraseña WPA2
solo entra quien debe
6

🕹️ Joystick en el navegador para el rover

Ahora, todo junto: el ESP32 crea la red, muestra una página con un joystick redondo, recibe la posición del dedo por WebSocket y la convierte en la velocidad de las dos ruedas. La conversión es la mezcla diferencial: rueda izquierda = y + x, rueda derecha = y − x. Si llevas el dedo hacia arriba, ambas giran igual; si lo llevas hacia la derecha, la izquierda acelera y la derecha frena, y el robot gira.

xy x = 50, y = 40izquierda = y + x = 90 derecha = y − x = −10izquierda con fuerza, derecha casi detenida: el rover hace un giro cerrado hacia la derecha

Cómo leer: el punto azul es tu dedo, a 50 hacia la derecha y 40 hacia arriba. Los dos cálculos de la derecha determinan la velocidad de cada rueda, en porcentaje. Los valores negativos hacen que la rueda gire hacia atrás; por encima de 100, el código limita el valor a 100.

Objetivo: conducir el rover de la Trilha 1 desde el celular, con joystick, WebSocket, parada al desconectarse y watchdog de 300 ms. Conexión al TB6612FNG: PWMA 25, AIN1 26, AIN2 27, PWMB 14, BIN1 16, BIN2 17, STBY conectado a 3V3 (el mismo mapa de toda la Trilha 2; cámbialo si tu rover usa otros pines). Necesitas la biblioteca WebSockets (tema 3).

rover-joystick.ino
// Rover con joystick en el navegador: WebServer + WebSocket + watchdog (paquete esp32 3.x)
#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsServer.h>   // biblioteca "WebSockets" (Markus Sattler)

const int PWMA = 25, AIN1 = 26, AIN2 = 27;        // motor izquierdo
const int PWMB = 14, BIN1 = 16, BIN2 = 17;        // motor derecho
const int VEL_MAX = 200;                          // límite de PWM (0 a 255): límite de seguridad
const unsigned long TIMEOUT_MS = 300;             // watchdog de comando

WebServer server(80);
WebSocketsServer ws(81);
unsigned long ultimoComando = 0;
bool andando = false;

const char PAGINA[] = R"html(
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<style>body{font-family:sans-serif;text-align:center;background:#111;color:#eee}
#pad{width:260px;height:260px;margin:20px auto;border-radius:50%;background:#222;position:relative;touch-action:none}#dot{width:70px;height:70px;border-radius:50%;background:#3b82f6;position:absolute;left:95px;top:95px}</style>
</head><body><h2>Rover INEMA</h2><div id="pad"><div id="dot"></div></div><p id="st">conectando...</p>
<script>
var pad=document.getElementById('pad'),dot=document.getElementById('dot'),st=document.getElementById('st');
var ws=new WebSocket('ws://'+location.hostname+':81/'),x=0,y=0,ativo=false;
ws.onopen=function(){st.textContent='conectado'};ws.onclose=function(){st.textContent='desconectado'};function mover(e){var r=pad.getBoundingClientRect(),dx=e.clientX-r.left-130,dy=e.clientY-r.top-130,d=Math.hypot(dx,dy);
if(d>95){dx=dx*95/d;dy=dy*95/d}dot.style.left=(95+dx)+'px';dot.style.top=(95+dy)+'px';
x=Math.round(dx/95*100);y=Math.round(-dy/95*100)}
function soltar(){ativo=false;x=0;y=0;dot.style.left='95px';dot.style.top='95px'}
pad.onpointerdown=function(e){ativo=true;pad.setPointerCapture(e.pointerId);mover(e)};
pad.onpointermove=function(e){if(ativo)mover(e)};pad.onpointerup=soltar;pad.onpointercancel=soltar;
setInterval(function(){if(ws.readyState===1)ws.send(x+','+y)},100);
</script></body></html>)html";

void motor(int pwm, int in1, int in2, int v) {   // v de -255 a 255
  v = constrain(v, -VEL_MAX, VEL_MAX);
  digitalWrite(in1, v > 0);
  digitalWrite(in2, v < 0);
  ledcWrite(pwm, abs(v));                             // API 3.x: ledcWrite recibe el PIN
}

void parar() { motor(PWMA, AIN1, AIN2, 0); motor(PWMB, BIN1, BIN2, 0); andando = false; }

void aoReceber(uint8_t num, WStype_t tipo, uint8_t* dados, size_t tam) {
  if (tipo == WStype_TEXT) {
    char buf[24];
    size_t n = tam < sizeof(buf) - 1 ? tam : sizeof(buf) - 1;
    memcpy(buf, dados, n); buf[n] = 0;
    int x = 0, y = 0;
    if (sscanf(buf, "%d,%d", &x, &y) == 2) {
      motor(PWMA, AIN1, AIN2, (y + x) * 255 / 100);  // mezcla diferencial
      motor(PWMB, BIN1, BIN2, (y - x) * 255 / 100);
      ultimoComando = millis();                      // alimenta el watchdog
      andando = (x != 0 || y != 0);
    }
  } else if (tipo == WStype_DISCONNECTED) {
    parar();
    Serial.printf("Cliente %u salió: PARADO\n", num);
  } else if (tipo == WStype_CONNECTED) {
    Serial.printf("Cliente %u se conectó\n", num);
  }
}

void setup() {
  Serial.begin(115200);
  int saidas[] = {AIN1, AIN2, BIN1, BIN2};
  for (int p : saidas) pinMode(p, OUTPUT);
  ledcAttach(PWMA, 20000, 8);                         // 20 kHz, 8 bits (0 a 255)
  ledcAttach(PWMB, 20000, 8);
  parar();
  WiFi.softAP("RoverINEMA", "troque1234");
  server.on("/", []() { server.send(200, "text/html", PAGINA); });
  server.begin();
  ws.begin();
  ws.onEvent(aoReceber);
  Serial.print("Conéctate a RoverINEMA y abre http://");
  Serial.println(WiFi.softAPIP());
}

void loop() {
  server.handleClient();
  ws.loop();
  if (andando && millis() - ultimoComando > TIMEOUT_MS) {
    parar();
    Serial.println("watchdog: sin comando, PARADO");
  }
}

Cómo verificar

  • ✓Con las ruedas en el aire, abre http://192.168.4.1:; la página dice "conectado" y el Serial Monitor, "Cliente 0 conectou".
  • ✓Dedo hacia arriba: las dos ruedas avanzan. Dedo hacia la derecha: las ruedas giran en sentidos opuestos. Suelta: se detienen.
  • ✓Cerrar la pestaña con el dedo hacia arriba: "Cliente 0 salió: PARADO". Modo avión: "watchdog: sin comando, PARADO".
  • ✗¿Una rueda gira al revés? Invierte los dos cables de ese motor en el driver, no el código.

🌱 ¿Eres nuevo aquí?

El texto entre R"html( e )html" es una cadena sin procesar de C++: todo lo que contiene, incluidas las comillas y los saltos de línea, se convierte en texto sin necesidad de barras invertidas. Es la forma más limpia de guardar una página HTML completa dentro del sketch.

Por qué aprender

Con el rover manejable desde el celular, puedes probar cada módulo siguiente con el robot en el suelo y las manos libres: lo llevas hasta la pista, lo colocas y lo sueltas. También es la base del modo "manual" que querrás en cualquier robot autónomo, para sacarlo de una esquina cuando falle la autonomía.

Conceptos clave

Mezcla diferencial
y + x e y − x
Pointer events
dedo y mouse por igual
VEL_MAX
límite en el robot
10 envíos/s
alimenta el watchdog

✅ Resumen del módulo

✓
Estación o punto de acceso: conéctate a la red de tu casa o crea la del robot; el AP es lo más predecible cuando estás fuera de casa.
✓
WebServer para botones: rutas sencillas, no necesitas instalar ninguna app.
✓
WebSocket para control continuo: un canal abierto, comandos con poco retraso.
✓
BLE sin red: servicio, característica y una app genérica.
✓
Sin señal = detenerse: watchdog de comando, parada al desconectarse y velocidad máxima en el propio robot.

Para profundizar

Siguiente módulo

2.2 · IMU y encoders: el robot empieza a saber cuánto giró, cuánto avanzó y dónde está.