API HTTP en ESP32 para mensajes LoRa

Resumen

Construir una API HTTP en ESP32 te permite conectar un celular como módem wifi a la tarjeta de desarrollo y, desde el navegador, enviar o recibir mensajes que viajan por radio LoRa. Esta guía está pensada para makers y desarrolladores de IoT que ya tienen un servidor HTTP corriendo en ESP32 y quieren agregar dos endpoints clave: uno para leer el último mensaje recibido y otro para transmitir mensajes vía LoRa.

¿Cómo funciona la arquitectura del chat con ESP32 y LoRa?

El flujo es directo y elegante. El celular se conecta por wifi a la ESP32, abre la IP en el navegador y, desde ahí, interactúa con el dispositivo. Mientras tanto, la tarjeta escucha mensajes entrantes vía radio LoRa y los guarda en memoria.

El frontend pregunta cada cierto tiempo si hay nuevos mensajes mediante una petición HTTP. Si la variable tiene contenido, se devuelve; si está vacía, se ignora. Para enviar, el navegador hace una petición a api/send con el mensaje, la tarjeta lo recibe y lo retransmite por LoRa hacia la segunda tarjeta [01:00].

¿Qué es un endpoint en una API HTTP? Es una URL específica que ejecuta una función cuando el servidor recibe una petición. En este proyecto, api-message devuelve mensajes y api-send los transmite.

¿Por qué deshabilitar el ping continuo antes de continuar?

En la etapa anterior, una tarea repetía la transmisión de un ping cada dos segundos para probar la radio. Ahora ese envío automático estorba, porque la lógica de mensajería será bajo demanda.

Basta con comentar la línea donde se crea esa tarea, sin borrar la función sendmsg, ya que la seguirás usando para enviar mensajes desde HTTP [02:30].

¿Cómo configurar los endpoints api-message y api-send en ESP32?

La estrategia es duplicar la configuración existente del handler del logo y adaptarla. Cada endpoint necesita su URL, su tipo (GET) y un handler propio.

  • api-message: URL /api-message, tipo GET, handler api_message_get_handler.
  • api-send: URL /api-send, tipo GET, handler api_send_get_handler.
  • Ambos se registran en el servidor HTTP junto a los handlers ya existentes.

Una vez registradas las URLs, el servidor sabe qué función ejecutar cuando llegue una petición a cada ruta.

¿Cómo devolver el último mensaje recibido en formato JSON?

El handler de api-message debe responder con application/json. La estructura es simple: {"message":"contenido"}. Cuando no hay mensaje, devuelve cadena vacía.

Los pasos dentro de la función son:

  • Declarar un char resp[400] con espacio fijo para trabajar.
  • Definir por defecto {"message":""} escapando comillas con diagonal invertida porque es C.
  • Si la variable global msg tiene contenido, rellenar con sprintf(resp, "{\"message\":\"%s\"}", msg).
  • Responder con httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN).

La variable msg se declara al inicio del archivo (línea 21) y se rellena automáticamente cuando la función receptora de LoRa detecta un mensaje entrante [05:00].

¿Por qué limpiar la variable msg después de leerla?

Si no limpias el buffer, la próxima petición devolverá el mismo mensaje y aparecerá duplicado en la web. La solución es asignar msg[0] = 0, convirtiendo el primer carácter en terminador de cadena. Así C interpreta la variable como vacía y queda lista para el siguiente mensaje LoRa.

¿Cómo recibir un mensaje por HTTP y retransmitirlo por LoRa?

El handler de api-send extrae el mensaje desde el query string de la URL, por ejemplo api/send?msg=hello. La librería de ESP32 ofrece funciones específicas para esto.

El proceso requiere tres pasos: medir el largo del query, reservar memoria, y extraer el parámetro.

  • httpd_req_get_url_query_len(req) + 1 devuelve el tamaño del query string.
  • malloc(bufLen) reserva memoria dinámica para almacenarlo.
  • httpd_req_get_url_query_str(req, buf, bufLen) copia el query completo al buffer.
  • httpd_query_key_value(buf, "msg", param, sizeof(param)) extrae solo el valor de msg.

El tamaño máximo de param se fija en 240 caracteres, que es el límite práctico de un mensaje LoRa [10:00].

¿Qué hace httpd_query_key_value? Recibe un query string completo y devuelve el valor asociado a una clave específica. Es útil cuando vienen varias variables en la URL y solo necesitas una.

¿Cómo enviar el mensaje extraído por radio LoRa?

Una vez que param contiene el texto, se llama a sendmsg((char*)param, sizeof(param)). El cast a char* asegura compatibilidad con la firma esperada por la función. También conviene imprimir en consola con printf("msg = %s\n", param) para depurar.

No olvides liberar la memoria con free(buf) al final del handler. Olvidar free después de un malloc provoca fugas de memoria que terminan colgando la ESP32 tras varias peticiones.

¿Cómo probar la API de mensajería entre dos tarjetas?

Con ambas ESP32 flasheadas y conectadas vía wifi al celular, abre dos pestañas del navegador, una por cada IP.

  • En la tarjeta receptora, accede a /api-message. Verás {"message":""} si no hay nada.
  • En la tarjeta emisora, llama a /api-send?msg=hello y presiona Enter.
  • Recarga /api-message en la receptora y aparecerá {"message":"hello"}.
  • Vuelve a recargar y el mensaje regresa a vacío, confirmando que el buffer se limpió.

Este ciclo confirma que la comunicación bidireccional funciona usando una sola variable global compartida entre el receptor LoRa y el servidor HTTP.

¿Ya probaste el endpoint enviando mensajes desde dos celulares distintos? Cuéntame en los comentarios qué cambiarías para soportar múltiples mensajes en cola en lugar de uno solo.