← Índice del curso

Comunicación entre Dispositivos · Tema 32 · Aplicaciones

HTTP y MQTT para dispositivos

TCP entrega bytes en orden, pero no dice qué significan. Para que un sensor, un servidor y una aplicación de celular se entiendan, hace falta un protocolo de aplicación: un acuerdo sobre el formato de los mensajes y sobre quién habla cuándo. Hay dos que dominan el mundo de los dispositivos conectados. HTTP, el de la web, es de pedido y respuesta, en texto, y lo entiende cualquier lenguaje, navegador y servicio en la nube. MQTT es de publicar y suscribirse: los dispositivos no hablan entre sí sino con un intermediario (el broker) a través de temas, con paquetes binarios mínimos y niveles de calidad de servicio pensados para redes poco confiables. Este tema recorre ambos, el formato JSON con el que suelen viajar los datos, y cuándo conviene cada uno.

01 · Lo que significan los bytes

La capa de aplicación

Con los temas 29 a 31 un dispositivo ya puede abrir una conexión TCP con otro equipo y mandar bytes. Todavía falta lo más importante: ponerse de acuerdo en qué quieren decir. Un protocolo de aplicación define cuatro cosas:

  • El formato de los mensajes (texto o binario, qué campos, en qué orden).
  • El diálogo: quién inicia, quién responde, qué pasa si algo sale mal.
  • El direccionamiento de lo que se pide o se publica (una ruta, un tema).
  • Las garantías que ofrece o que delega en TCP.

Es el mismo papel que Modbus (tema 25) cumple sobre RS-485, o que los protocolos de CANopen y J1939 (tema 28) cumplen sobre CAN, pero en una escala distinta: aquí el «otro extremo» puede ser un servidor en otro continente, y el mismo protocolo lo comparten dispositivos de miles de fabricantes. Dos filosofías muy diferentes dominan el área: el modelo cliente-servidor de HTTP, donde el cliente pregunta y el servidor contesta, y el modelo de publicación y suscripción de MQTT, donde un intermediario reparte los mensajes.

02 · La web

HTTP: pedidos y respuestas

Una petición y una respuesta HTTP La petición tiene una línea con el método, la ruta y la versión, varios encabezados, una línea en blanco y un cuerpo JSON. La respuesta tiene una línea de estado con la versión, el código 201 y su motivo, encabezados, una línea en blanco y el cuerpo. Petición del dispositivoPOST /api/lecturas HTTP/1.1línea de peticiónHost: 192.168.1.10encabezadosContent-Type: application/jsonContent-Length: 10línea en blanco{"t":23.5}cuerpo Respuesta del servidorHTTP/1.1 201 Createdlínea de estadoContent-Type: application/jsonencabezadosContent-Length: 10línea en blanco{"id": 17}cuerpo HTTP es texto, de pedido y respuesta: el cliente pregunta siempre primero, y el servidor contesta una vez.

HTTP (HyperText Transfer Protocol) funciona sobre TCP (puerto 80, o 443 con TLS) y se basa en un intercambio simple: el cliente envía una petición y el servidor devuelve una respuesta. El cliente siempre inicia; el servidor nunca puede enviar algo por su cuenta. Ambos mensajes son texto con la misma estructura:

  1. Una línea inicial. En la petición: el método, la ruta del recurso y la versión (POST /api/lecturas HTTP/1.1). En la respuesta: la versión, el código de estado y un texto (HTTP/1.1 201 Created).
  2. Los encabezados, uno por línea, con el formato Nombre: valor. Los más usados: Host (obligatorio en HTTP/1.1: a qué servidor va), Content-Type (formato del cuerpo, por ejemplo application/json), Content-Length (cuántos bytes tiene el cuerpo), Authorization (credenciales) y Connection.
  3. Una línea en blanco, que marca el fin de los encabezados.
  4. El cuerpo, opcional, con los datos. Su largo lo dice Content-Length, así el receptor sabe cuándo terminó (recordá que TCP no delimita mensajes).

Cada línea termina con dos bytes, CR LF (\r\n). Es un detalle que importa cuando se arma un mensaje a mano en un microcontrolador: println envía ambos.

Los métodos expresan la acción sobre el recurso:

Métodos HTTP más usados.
MétodoAcción¿Con cuerpo?¿Idempotente?
GETLeer un recursoNoSí
POSTCrear algo o enviar datos para procesarSíNo
PUTReemplazar un recurso por completoSíSí
PATCHModificar una parteSíNo necesariamente
DELETEBorrar un recursoNoSí

«Idempotente» quiere decir que repetir la petición produce el mismo resultado que hacerla una sola vez: si no llegó la respuesta a un PUT, se puede repetir sin miedo; con un POST se arriesga a duplicar el efecto (dos lecturas guardadas, dos pedidos).

La respuesta empieza con un código de estado de tres dígitos. La primera cifra indica la familia:

Códigos de estado de HTTP.
FamiliaSignificadoEjemplosQué hace el dispositivo
2xxÉxito200 OK, 201 Created, 204 No ContentContinuar
3xxRedirección301 Moved Permanently, 304 Not ModifiedSeguir la nueva dirección (o usar la copia guardada)
4xxError del cliente400, 401, 403, 404, 429No repetir igual: corregir; en el 429, esperar
5xxError del servidor500, 502, 503, 504Reintentar más tarde, con esperas crecientes

El HTTP original abría una conexión TCP por cada petición. HTTP/1.1 (la versión que usan los dispositivos pequeños) mantiene la conexión abierta por defecto para varias peticiones (keep-alive); con Connection: close el servidor la cierra al terminar. HTTP/2 y HTTP/3 multiplexan muchas peticiones y usan formato binario, pero son pesados para un microcontrolador y rara vez se usan en ellos.

03 · Datos legibles

JSON: datos en texto

HTTP dice cómo viaja el mensaje, no qué datos lleva. El formato más usado hoy es JSON (JavaScript Object Notation): texto estructurado que entienden todos los lenguajes. Tiene seis tipos de valor:

  • Objeto: un conjunto de pares clave-valor entre llaves, con las claves siempre en texto entre comillas dobles: {"id":"nodo1","t":23.5}.
  • Arreglo: una lista ordenada entre corchetes: [21.5, 21.7, 22.0].
  • Texto: entre comillas dobles, en UTF-8, con barra invertida para escapar (\", \n).
  • Número: entero o decimal, con punto (23.5, -4, 1.2e3). No hay NaN ni infinito.
  • Lógico: true o false, en minúsculas.
  • Nulo: null, ausencia de valor.

Las reglas son estrictas y los errores típicos de quien escribe JSON a mano son siempre los mismos: comillas simples en lugar de dobles, una coma de más antes de la llave de cierre, claves sin comillas, comentarios (no existen) y números con coma decimal. Un solo error y el receptor rechaza todo el documento.

Las ventajas de JSON son que se puede leer y escribir a mano, se depura con cualquier visor, todos los lenguajes lo procesan, y es extensible: agregar un campo nuevo no rompe a los programas viejos que lo ignoran. El costo: es voluminoso. Cada número viaja como dígitos de texto y cada clave se repite en cada mensaje. El objeto {"id":"nodo1","t":23.5,"ok":true} mide 33 bytes para transportar un texto, un número y un lógico que en binario cabrían en una decena de bytes. En un enlace Ethernet o Wi-Fi no importa; en uno celular o de baja velocidad (LoRa, tema 34 en adelante), sí. Hay alternativas binarias (CBOR, MessagePack, Protocol Buffers) que conservan la estructura y ahorran espacio.

En un microcontrolador el análisis de JSON no es trivial porque consume memoria. La biblioteca más usada en Arduino y ESP32 es ArduinoJson, que reserva un bloque de memoria fijo para el documento. Reglas prácticas: mantener los mensajes pequeños, dimensionar el documento según lo máximo esperado, y comprobar siempre el resultado del análisis antes de usar los campos.

04 · HTTP desde el hardware

HTTP en un dispositivo

Un dispositivo usa HTTP de dos maneras. Como cliente, envía sus lecturas a un servicio (un POST con un JSON) o consulta configuración y comandos (un GET). Como servidor, expone una pequeña página o una API en su propia dirección IP: sirve para configurarlo desde el navegador o leer su estado, sin ninguna aplicación especial. Los dos casos son muy útiles, y los dos tienen un límite estructural.

Ese límite es que el servidor no puede avisar. Si un dispositivo quiere recibir una orden de la nube, solo puede preguntar cada tanto: sondeo (polling). Con un sondeo cada 5 segundos, la orden tarda hasta 5 segundos en llegar, y mientras no haya órdenes se están enviando cientos de peticiones inútiles por hora, cada una con unos 100 bytes de encabezados y su saludo TCP. Existen soluciones (long polling, WebSockets, eventos enviados por el servidor), pero son más complejas. Y si el dispositivo está detrás de NAT (tema 30), nadie puede conectarse a él desde afuera: por eso un dispositivo en una casa normalmente actúa de cliente.

Otros puntos para tener en cuenta:

  • Reintentos con espera creciente. Ante un 5xx, un timeout o un 429, esperar 1, 2, 4, 8... segundos hasta un máximo. Si miles de dispositivos reintentan al mismo tiempo, tumban el servidor (con un poco de variación aleatoria se evita).
  • Tiempos límite. Cada conexión y lectura tiene que tener un máximo: un servidor que no contesta no puede trabar el programa.
  • Pocos bytes. Enviar solo lo necesario, con claves cortas, y agrupar varias lecturas en un solo POST si el tiempo lo permite (tema 29, eficiencia).
  • HTTPS. La versión segura (HTTP sobre TLS, puerto 443) cifra el tráfico y autentica al servidor. Para un microcontrolador pequeño tiene un costo real: decenas de kilobytes de memoria, un saludo más largo y la gestión de certificados (qué autoridades se aceptan, cuándo vencen). El ESP32 la soporta; un Arduino con un W5500 de hardware básico, no. Nunca enviar contraseñas o claves por HTTP sin TLS.

05 · Un cartero en el medio

MQTT: publicar y suscribirse

Publicar y suscribirse con un broker Dos sensores publican mensajes en temas: casa/sala/temperatura y casa/cocina/temperatura. El broker, en el centro, los recibe y los reenvía a los suscriptores cuyo filtro coincide. El tablero está suscrito a casa/+/temperatura y recibe los dos, mientras que la alarma está suscrita solo a casa/cocina/humo y no recibe ninguno. Los publicadores y los suscriptores no se conocen entre sí. Sensor sala Sensor cocina Detector de humo Broker recibe, filtra por tema y reenvía Tablero: casa/+/temperatura Registro: casa/# Alarma: casa/cocina/humo casa/sala/temperaturacasa/cocina/temperaturacasa/cocina/humo Quien publica no sabe quién recibe, y quien recibe no sabe quién publicó: los acopla el tema.

MQTT (Message Queuing Telemetry Transport) nació en 1999 para monitorear oleoductos por enlaces satelitales lentos y caros, y hoy es el protocolo más usado en IoT. Su idea: los dispositivos no se hablan entre sí. Cada uno abre una conexión TCP con un broker (un servidor intermediario, como Mosquitto, EMQX o HiveMQ, o los servicios de IoT de las nubes), y el broker reparte los mensajes. Hay dos acciones:

  • Publicar (publish): enviar un mensaje a un tema (topic), por ejemplo casa/sala/temperatura con el dato 23.5.
  • Suscribirse (subscribe): decirle al broker «mandame todo lo que se publique en los temas que coincidan con este filtro».

Esta separación tiene consecuencias muy valiosas. El que publica no sabe quién recibe, ni si hay alguien: se desacopla en espacio (no hace falta conocer direcciones IP) y en tiempo (con mensajes retenidos, el suscriptor puede conectarse después). Un mismo dato llega a muchos suscriptores sin que el publicador envíe nada más, y agregar un nuevo consumidor (un registro, una alarma, un tablero) no toca a los dispositivos. Y como todas las conexiones salen del dispositivo hacia el broker, funciona detrás de NAT y firewalls: el dispositivo recibe comandos por la misma conexión que usa para publicar, sin que nadie tenga que conectarse a él.

Los temas son cadenas jerárquicas con niveles separados por «/»: casa/sala/temperatura. No hay que crearlos: existen desde que alguien publica en ellos. Un buen esquema de nombres es lo más importante del diseño de una red MQTT: de lo general a lo particular (planta/linea2/motor5/temperatura), sin una barra inicial, en minúsculas, con nombres que describan la magnitud y no el dispositivo, y separando datos (.../temperatura), comandos (.../luz/set) y estado (.../estado).

Árbol de temas y comodines Un árbol de temas con la raíz casa, que tiene las ramas sala y cocina. Sala tiene temperatura y luz. Cocina tiene temperatura y humo. El filtro casa/+/temperatura coincide con casa/sala/temperatura y casa/cocina/temperatura. El filtro casa/# coincide con todos los temas de abajo. casa salacocina temperaturaluztemperaturahumo casa/+/temperatura → las dos ramas en naranja (un nivel cualquiera en el medio) casa/# → todo lo que cuelga de «casa», a cualquier profundidad

Quien se suscribe usa filtros, que pueden llevar dos comodines:

  • + reemplaza exactamente un nivel: casa/+/temperatura coincide con casa/sala/temperatura y con casa/cocina/temperatura, pero no con casa/sala/luz/temperatura.
  • # reemplaza todos los niveles restantes y solo puede ir al final: casa/# coincide con todo lo que cuelga de casa (incluida casa misma). El filtro # solo recibe todo.

El que publica siempre usa un tema concreto, sin comodines. Los temas que empiezan con $ (como $SYS, donde el broker publica estadísticas) quedan fuera de los comodines del primer nivel.

06 · Binario y mínimo

Los paquetes MQTT

Estructura de un paquete MQTT PUBLISH Un PUBLISH con QoS 0 empieza con el byte 0x30: el tipo 3 en los cuatro bits altos y las marcas en los bajos. Sigue el largo restante, aquí 27 en un solo byte. Luego van el largo del tema en dos bytes, el tema y los datos. En total son 29 bytes para entregar 4 bytes de dato. 30tipo 3 · QoS 01Blargo restante00 15largo del temacasa/sala/temperaturatema (21 bytes)23.5datos (4) encabezado fijo encabezado variable y datos (27 bytes = el largo restante) Con QoS 1 o 2, después del tema se agrega un identificador de paquete de 2 bytes. Bits del primer byte: 0011 (PUBLISH) · DUP · QoS (2 bits) · RETAIN.

A diferencia de HTTP, MQTT es binario y sus paquetes son muy pequeños. Todo paquete empieza con un encabezado fijo de al menos 2 bytes: el primero tiene el tipo de paquete en los cuatro bits altos y marcas en los bajos; después viene el largo restante (cuántos bytes siguen), codificado con una técnica de largo variable: cada byte aporta 7 bits de valor y su bit alto indica si sigue otro byte. Así, hasta 127 bytes se codifican con un byte, hasta 16 383 con dos, y el máximo (256 MB) con cuatro. Por ejemplo, 27 es 1B, 128 es 80 01 y 16 384 es 80 80 01.

Paquetes principales de MQTT 3.1.1.
PaquetePrimer byteQuién lo envíaPara qué
CONNECT0x10ClienteAbrir la sesión: identificador, latido, usuario, última voluntad
CONNACK0x20BrokerAceptar o rechazar la conexión
PUBLISH0x30 a 0x3FAmbosEnviar un mensaje a un tema (las marcas son DUP, QoS y RETAIN)
PUBACK / PUBREC / PUBREL / PUBCOMP0x40 / 0x50 / 0x62 / 0x70AmbosConfirmaciones de QoS 1 y 2
SUBSCRIBE / SUBACK0x82 / 0x90Cliente / brokerPedir y confirmar una suscripción
PINGREQ / PINGRESP0xC0 / 0xD0Cliente / brokerMantener viva la conexión
DISCONNECT0xE0ClienteCerrar la sesión de forma ordenada

Un CONNECT mínimo para el cliente sensor1 con latido de 60 s: 10 13 00 04 4D 51 54 54 04 02 00 3C 00 07 73 65 6E 73 6F 72 31, es decir, tipo CONNECT, largo 19, el nombre del protocolo «MQTT» (precedido por su largo de 2 bytes), el nivel de protocolo 4 (versión 3.1.1), las marcas de conexión (02 = sesión limpia), el latido (00 3C = 60) y el identificador del cliente con su largo. El broker responde con un CONNACK de 4 bytes (20 02 00 00) que dice que aceptó.

Un PUBLISH lleva, después del encabezado fijo, el largo del tema en 2 bytes, el tema, un identificador de paquete de 2 bytes (solo si QoS es 1 o 2) y los datos. Publicar 23.5 en casa/sala/temperatura con QoS 0 son 29 bytes: 30 1B 00 15, los 21 caracteres del tema y 32 33 2E 35. La misma información en una petición HTTP mínima ocupa casi el triple, y la respuesta es otro mensaje aparte. El broker no interpreta los datos: son bytes opacos (texto, JSON, binario), de hasta 256 MB, aunque en la práctica se mantienen en decenas o cientos de bytes.

MQTT 5 (2019) agrega códigos de motivo en las respuestas, propiedades de usuario en los mensajes (como los encabezados de HTTP), caducidad de mensajes, temas por alias y suscripciones compartidas. La versión 3.1.1 sigue siendo la más extendida en dispositivos pequeños y es la que describimos.

07 · Cuánta garantía

Calidad de servicio

Flujo de paquetes de los tres niveles de QoS Con QoS 0 el emisor envía un PUBLISH y no espera respuesta. Con QoS 1 el emisor envía PUBLISH y el receptor responde PUBACK; si no llega, el emisor reenvía. Con QoS 2 hay un intercambio de cuatro paquetes: PUBLISH, PUBREC, PUBREL y PUBCOMP, que garantiza que el mensaje se entrega exactamente una vez. EmisorReceptor QoS 0: como máximo una vezPUBLISH QoS 1: al menos una vezPUBLISH (id = 10)PUBACK (id = 10) QoS 2: exactamente una vezPUBLISH (id = 10)PUBREC (recibido)PUBREL (liberar)PUBCOMP (completo) QoS 1 puede duplicar el mensaje (si se pierde el PUBACK, el emisor reenvía con la marca DUP). El QoS efectivo de un suscriptor es el menor entre el del publicador y el de su suscripción.

MQTT corre sobre TCP, que ya entrega los bytes en orden, pero una conexión TCP puede cortarse en cualquier momento (un dispositivo móvil que pierde cobertura), y los mensajes en tránsito se pierden. Para eso MQTT define tres niveles de calidad de servicio (QoS) por mensaje:

  • QoS 0: como máximo una vez. Se envía el PUBLISH y listo, sin confirmación. Si la conexión falla, el mensaje se pierde. Es el más barato (un solo paquete) y suficiente para datos que se repiten, como una temperatura cada segundo.
  • QoS 1: al menos una vez. El receptor responde con un PUBACK con el identificador del paquete; si el emisor no lo recibe a tiempo, reenvía el PUBLISH con la marca DUP. El mensaje seguro llega, pero puede llegar duplicado (si se perdió el PUBACK, no el PUBLISH). Es el nivel más usado.
  • QoS 2: exactamente una vez. Un intercambio de cuatro paquetes (PUBLISH, PUBREC, PUBREL, PUBCOMP) que garantiza que el mensaje se entrega una sola vez. Es el más lento y pesado, y se usa donde un duplicado causaría daño (una orden de pago, un conteo).
Niveles de QoS de MQTT.
QoSGarantíaPaquetesDuplicadosUso típico
0Como máximo una vez1NoTelemetría periódica
1Al menos una vez2PosiblesEventos, comandos que se pueden repetir
2Exactamente una vez4NoOperaciones que no admiten repetirse

Dos precisiones. El QoS se negocia en cada tramo: el publicador envía al broker con su QoS, y el broker entrega a cada suscriptor con el menor entre el QoS del mensaje y el de la suscripción. Y QoS no es lo mismo que la confiabilidad de TCP: TCP asegura que los bytes lleguen en una conexión viva; QoS asegura que el mensaje sobreviva a la caída de la conexión, porque cliente y broker guardan los mensajes sin confirmar y los retoman al reconectar (si la sesión es persistente).

Para el diseño: usar QoS 0 para lo que se repite, QoS 1 para lo que no debe perderse y la aplicación puede tolerar duplicados (los comandos idempotentes, como «poner la luz en encendida»; no «alternar la luz»), y QoS 2 solo si es imprescindible. Hay bibliotecas pequeñas que soportan solo QoS 0 al publicar: conviene verificarlo antes de depender de ello.

08 · Funciones pensadas para IoT

Retenidos, última voluntad y latido

MQTT trae tres mecanismos que resuelven problemas típicos de los dispositivos conectados:

  • Mensaje retenido (retain). Si un PUBLISH lleva la marca RETAIN, el broker guarda el último mensaje de ese tema y se lo entrega a todo suscriptor nuevo apenas se suscribe. Es el mecanismo para publicar estados: un tablero que se conecta a las 3 de la tarde recibe de inmediato el último valor conocido, sin esperar al próximo ciclo del sensor. Para borrar un retenido se publica un mensaje retenido vacío en el mismo tema.
  • Última voluntad (last will and testament). Al conectarse, el cliente le deja al broker un mensaje y un tema. Si el cliente se desconecta de forma anormal (corte de energía, cable, error), el broker lo publica en su nombre; si se desconecta con DISCONNECT, no. Combinado con el retenido, el patrón clásico es: el dispositivo publica conectado (retenido) en su tema de estado al conectarse, con la última voluntad desconectado (retenida) en el mismo tema. Así cualquiera sabe en todo momento si el dispositivo está vivo.
  • Latido (keep alive). En CONNECT el cliente declara un intervalo (por ejemplo 60 s). Si no envía nada en ese tiempo, debe mandar un PINGREQ. Si el broker no oye nada durante 1,5 veces el intervalo, considera caído al cliente, cierra la conexión y publica la última voluntad. Así se detectan las caídas «silenciosas» que TCP solo notaría tras mucho tiempo.

También se configura si la sesión es limpia o persistente. Con sesión limpia, al reconectar el broker olvida las suscripciones y los mensajes pendientes; con sesión persistente (identificador de cliente fijo y sesión no limpia), el broker conserva las suscripciones y los mensajes QoS 1 y 2 acumulados mientras el cliente estuvo desconectado y se los entrega al volver. Un dispositivo que duerme para ahorrar batería y despierta a leer comandos necesita esto.

El identificador de cliente tiene que ser único en el broker: si dos dispositivos se conectan con el mismo, el broker echa al más viejo, y los dos pasan el día reconectándose uno al otro. Es un error muy común al clonar un programa en varias placas: hay que derivar el identificador de algo propio (la MAC, por ejemplo).

09 · Una decisión de diseño

HTTP o MQTT, y seguridad

Comparación de HTTP y MQTT para dispositivos.
AspectoHTTPMQTT
ModeloPedido y respuesta (cliente-servidor)Publicar y suscribirse (con broker)
FormatoTextoBinario
Encabezado típico100 a 500 bytes2 a 10 bytes
ConexiónCorta, o larga con pedidos por turnosUna sola, permanente
Enviar al dispositivoSolo preguntando (sondeo)Inmediato por la misma conexión
Detrás de NATFunciona como clienteFunciona: la conexión sale del dispositivo
Uno a muchosNo directamenteNatural: los suscriptores
GarantíasLas de TCP, más reintentos del clienteQoS 0, 1 y 2, retenidos, última voluntad
RequisitosUn servidor web (o una API)Un broker
HerramientasCualquier navegador, curlClientes MQTT (por ejemplo MQTT Explorer, mosquitto_sub)

Una regla práctica: HTTP para configurar un dispositivo desde un navegador, para integrarse con servicios web y para enviar datos de vez en cuando; MQTT para telemetría continua, comandos en tiempo real y redes con muchos dispositivos y consumidores. En muchos proyectos conviven: el dispositivo publica por MQTT y ofrece una página HTTP para configurarlo.

Sobre la seguridad, ambos protocolos son de texto claro por omisión (HTTP en el puerto 80, MQTT en el 1883), de modo que cualquiera que vea el tráfico lee todo, incluidas las contraseñas. La protección se logra con TLS: HTTPS (443) y MQTT sobre TLS (puerto 8883). Los puntos a cuidar:

  • Cifrado con TLS en todo lo que salga de la red local.
  • Autenticación: usuario y contraseña (o certificados) en el CONNECT de MQTT, y un token o clave en el encabezado Authorization de HTTP. Cada dispositivo con su propia credencial, para poder revocarla.
  • Autorización: que cada cliente solo pueda publicar y suscribirse a sus temas (listas de control de acceso del broker).
  • No dejar un broker sin autenticación abierto a Internet. Es una de las fallas más frecuentes y cualquiera puede leer y comandar los dispositivos.
  • Secretos fuera del código fuente que se comparte.

10 · Con el microcontrolador

Con el microcontrolador

El primer programa usa HTTP con el módulo W5500: cada 10 segundos envía un POST con un JSON a un servidor de la red local y lee el código de estado de la respuesta. Arma la petición a mano, para que se vean los encabezados del texto (println agrega el CR LF):

#include <SPI.h>
#include <Ethernet.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };    // cambiar el último byte en cada placa
const int PIN_CS = 10;
IPAddress servidor(192, 168, 1, 10);                    // la computadora con el servicio: ajustarla
const uint16_t PUERTO = 8000;
EthernetClient cliente;

int enviarLectura(float temperatura) {
    if (!cliente.connect(servidor, PUERTO)) return -1;               // no se pudo abrir la conexión TCP
    String cuerpo = "{\"t\":" + String(temperatura, 1) + "}";
    cliente.println("POST /api/lecturas HTTP/1.1");
    cliente.println("Host: 192.168.1.10");
    cliente.println("Content-Type: application/json");
    cliente.print("Content-Length: "); cliente.println(cuerpo.length());
    cliente.println("Connection: close");
    cliente.println();                                               // línea en blanco: terminan los encabezados
    cliente.print(cuerpo);
    unsigned long t0 = millis();
    while (!cliente.available() && millis() - t0 < 3000) delay(5);   // espera la respuesta, con tiempo límite
    String estado = cliente.readStringUntil('\n');                   // por ejemplo "HTTP/1.1 201 Created"
    cliente.stop();
    return estado.length() >= 12 ? estado.substring(9, 12).toInt() : -2;
}

void setup() {
    Serial.begin(115200);
    Ethernet.init(PIN_CS);
    if (Ethernet.begin(mac, 10000) == 0) { Serial.println("Sin DHCP"); while (true) delay(1000); }
}

void loop() {
    static unsigned long ultimo = 0;
    if (millis() - ultimo >= 10000) {
        ultimo = millis();
        int codigo = enviarLectura(23.5);
        Serial.print("Respuesta: "); Serial.println(codigo);        // 201 si todo salió bien
        // -1 = sin conexión, -2 = respuesta ilegible; 4xx no reintentar igual; 5xx reintentar con espera
    }
    Ethernet.maintain();
}

Para probarlo, un servidor mínimo en una computadora (copialo a un archivo y ejecutalo con Python; escucha en el puerto 8000, muestra lo que recibe y responde 201):

from http.server import BaseHTTPRequestHandler, HTTPServer
import json

class Manejador(BaseHTTPRequestHandler):
    def do_POST(self):
        largo = int(self.headers.get("Content-Length", 0))
        datos = json.loads(self.rfile.read(largo))
        print("Lectura recibida:", datos)
        respuesta = b'{"id": 17}'
        self.send_response(201)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(respuesta)))
        self.end_headers()
        self.wfile.write(respuesta)

HTTPServer(("0.0.0.0", 8000), Manejador).serve_forever()

El segundo programa usa MQTT con la biblioteca PubSubClient. Se conecta a un broker (por ejemplo Mosquitto en la misma computadora), declara una última voluntad retenida, publica su estado y una temperatura cada 5 segundos y se suscribe a un tema de comandos. Si la conexión se cae, reintenta sin trabar el programa:

#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
const int PIN_CS = 10;
IPAddress broker(192, 168, 1, 10);                       // el broker: ajustarlo
const char *ID_CLIENTE = "sensor1";                       // único en el broker: derivarlo de la MAC si hay varias placas
EthernetClient red;
PubSubClient mqtt(red);

void alRecibir(char *tema, byte *datos, unsigned int largo) {      // se llama desde mqtt.loop()
    String mensaje;
    for (unsigned int i = 0; i < largo; i++) mensaje += (char)datos[i];
    Serial.print(tema); Serial.print(" = "); Serial.println(mensaje);
    if (String(tema) == "casa/sala/luz/set") digitalWrite(LED_BUILTIN, mensaje == "1" ? HIGH : LOW);
}

bool conectar() {
    // última voluntad: "desconectado", retenida, QoS 1, en el tema de estado
    if (!mqtt.connect(ID_CLIENTE, nullptr, nullptr, "casa/sala/sensor1/estado", 1, true, "desconectado")) return false;
    mqtt.publish("casa/sala/sensor1/estado", "conectado", true);   // retenido: quien se suscriba lo ve de inmediato
    mqtt.subscribe("casa/sala/luz/set");                           // comandos para este nodo
    return true;
}

void setup() {
    Serial.begin(115200);
    pinMode(LED_BUILTIN, OUTPUT);
    Ethernet.init(PIN_CS);
    if (Ethernet.begin(mac, 10000) == 0) { Serial.println("Sin DHCP"); while (true) delay(1000); }
    mqtt.setServer(broker, 1883);
    mqtt.setKeepAlive(30);                                         // latido de 30 s
    mqtt.setCallback(alRecibir);
}

void loop() {
    static unsigned long ultimoIntento = 0, ultimoEnvio = 0;
    if (!mqtt.connected()) {
        if (millis() - ultimoIntento >= 5000) {                    // reintento cada 5 s, sin bloquear
            ultimoIntento = millis();
            Serial.println(conectar() ? "Conectado al broker" : "No se pudo conectar");
        }
    } else {
        mqtt.loop();                                               // atiende los paquetes entrantes y el latido
        if (millis() - ultimoEnvio >= 5000) {
            ultimoEnvio = millis();
            mqtt.publish("casa/sala/temperatura", "23.5");         // QoS 0
        }
    }
    Ethernet.maintain();
}

Para probarlo desde la computadora con Mosquitto instalado: mosquitto_sub -h 192.168.1.10 -t "casa/#" -v muestra todo lo que publica la placa, y mosquitto_pub -h 192.168.1.10 -t casa/sala/luz/set -m 1 le envía un comando. Dos advertencias sobre la biblioteca: publish solo envía con QoS 0 (para QoS 1 al publicar hay que usar otra), y el tamaño máximo del paquete es de 256 bytes por omisión (se cambia con setBufferSize); un mensaje más grande se descarta sin aviso.

11 · Analogía con programación

Aplicaciones en Python

Los protocolos de aplicación son formatos de datos y reglas de diálogo, así que se entienden bien escribiéndolos. El modelo siguiente arma una petición HTTP y lee una respuesta, serializa JSON, codifica el largo restante de MQTT, construye los paquetes CONNECT, PUBLISH y SUBSCRIBE a mano, implementa los filtros con comodines y simula un pequeño broker con mensajes retenidos.

Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

import json


def peticion_http(metodo, ruta, host, cuerpo=""):
    lineas = [f"{metodo} {ruta} HTTP/1.1", f"Host: {host}"]
    if cuerpo:
        lineas += ["Content-Type: application/json", f"Content-Length: {len(cuerpo.encode())}"]
    lineas.append("Connection: close")
    return ("\r\n".join(lineas) + "\r\n\r\n" + cuerpo).encode()


def leer_respuesta(datos):
    cabecera, _, cuerpo = datos.partition(b"\r\n\r\n")
    lineas = cabecera.decode().split("\r\n")
    version, codigo, motivo = lineas[0].split(" ", 2)
    encabezados = dict(l.split(": ", 1) for l in lineas[1:])
    return int(codigo), motivo, encabezados, cuerpo


def largo_restante(n):
    salida = []
    while True:
        byte, n = n % 128, n // 128
        salida.append(byte | (0x80 if n else 0))
        if not n:
            return bytes(salida)


def cadena(texto):
    b = texto.encode()
    return len(b).to_bytes(2, "big") + b


def mqtt_publish(tema, datos, qos=0, retener=False, id_paquete=10):
    variable = cadena(tema) + (id_paquete.to_bytes(2, "big") if qos else b"")
    cuerpo = variable + datos
    return bytes([0x30 | (qos << 1) | int(retener)]) + largo_restante(len(cuerpo)) + cuerpo


def mqtt_connect(id_cliente, keepalive=60):
    variable = cadena("MQTT") + bytes([4, 0x02]) + keepalive.to_bytes(2, "big")   # nivel 4 = MQTT 3.1.1, sesión limpia
    cuerpo = variable + cadena(id_cliente)
    return bytes([0x10]) + largo_restante(len(cuerpo)) + cuerpo


def mqtt_subscribe(filtro, qos=0, id_paquete=1):
    cuerpo = id_paquete.to_bytes(2, "big") + cadena(filtro) + bytes([qos])
    return bytes([0x82]) + largo_restante(len(cuerpo)) + cuerpo


def coincide(filtro, tema):
    f, t = filtro.split("/"), tema.split("/")
    if t[0].startswith("$") and f[0] in ("#", "+"):
        return False
    for i, nivel in enumerate(f):
        if nivel == "#":
            return True
        if i >= len(t) or (nivel != "+" and nivel != t[i]):
            return False
    return len(f) == len(t)


class Broker:
    def __init__(self):
        self.suscripciones = []                  # (cliente, filtro)
        self.retenidos = {}

    def suscribir(self, cliente, filtro):
        self.suscripciones.append((cliente, filtro))
        return [(t, d) for t, d in self.retenidos.items() if coincide(filtro, t)]   # lo último retenido llega al suscribirse

    def publicar(self, tema, datos, retener=False):
        if retener:
            self.retenidos[tema] = datos
        return [c for c, f in self.suscripciones if coincide(f, tema)]


print("Petición:", repr(peticion_http("POST", "/api/lecturas", "192.168.1.10", '{"t":23.5}')))
print("  tamaño:", len(peticion_http("POST", "/api/lecturas", "192.168.1.10", '{"t":23.5}')), "bytes")
resp = b'HTTP/1.1 201 Created\r\nContent-Type: application/json\r\nContent-Length: 10\r\n\r\n{"id": 17}'
codigo, motivo, enc, cuerpo = leer_respuesta(resp)
print("Respuesta:", codigo, motivo, enc["Content-Type"], json.loads(cuerpo))

dato = {"id": "nodo1", "t": 23.5, "ok": True}
print("JSON:", json.dumps(dato, separators=(",", ":")), "->", len(json.dumps(dato, separators=(",", ":"))), "bytes")

print("Largo restante de 27, 128, 16383 y 16384:", [largo_restante(n).hex(" ") for n in (27, 128, 16383, 16384)])
print("CONNECT:", mqtt_connect("sensor1").hex(" ").upper())
print("PUBLISH QoS 0:", mqtt_publish("casa/sala/temperatura", b"23.5").hex(" ").upper())
print("PUBLISH QoS 1 retenido:", mqtt_publish("casa/sala/temperatura", b"23.5", 1, True).hex(" ").upper())
print("SUBSCRIBE:", mqtt_subscribe("casa/+/temperatura", 1).hex(" ").upper())

for filtro, tema in (("casa/+/temperatura", "casa/sala/temperatura"), ("casa/+/temperatura", "casa/sala/humedad"),
                     ("casa/#", "casa/sala/luz/estado"), ("casa/#", "casa"), ("+/sala", "casa/sala"), ("#", "$SYS/uptime")):
    print(f"filtro {filtro!r} con tema {tema!r}:", coincide(filtro, tema))

b = Broker()
b.suscribir("tablero", "casa/+/temperatura")
b.suscribir("alarma", "casa/#")
print("Recibe 'casa/sala/temperatura':", b.publicar("casa/sala/temperatura", b"23.5", retener=True))
print("Recibe 'garaje/puerta':", b.publicar("garaje/puerta", b"abierta"))
print("Nuevo suscriptor recibe retenidos:", b.suscribir("celular", "casa/#"))
print("Paquetes por mensaje según QoS 0, 1 y 2:", [1, 2, 4])

Resultado: la petición POST de {"t":23.5} mide 132 bytes y la respuesta 201 Created se lee como código 201 con cuerpo {"id": 17}. El JSON {"id":"nodo1","t":23.5,"ok":true} mide 33 bytes compactado. El largo restante de MQTT para 27, 128, 16383 y 16384 es 1b, 80 01, ff 7f y 80 80 01. El CONNECT del cliente sensor1 es 10 13 00 04 4D 51 54 54 04 02 00 3C 00 07 73 65 6E 73 6F 72 31, el PUBLISH QoS 0 de «23.5» en casa/sala/temperatura empieza con 30 1B 00 15 y mide 29 bytes, con QoS 1 y retenido empieza con 33 1D y lleva el identificador 00 0A, y el SUBSCRIBE a casa/+/temperatura es 82 17 00 01 00 12 …. Los filtros dan: casa/+/temperatura coincide con casa/sala/temperatura y no con casa/sala/humedad; casa/# coincide con casa/sala/luz/estado y con casa; +/sala con casa/sala; y # no coincide con $SYS/uptime. En el broker, la publicación en casa/sala/temperatura llega a tablero y alarma, la de garaje/puerta a nadie, y un suscriptor nuevo a casa/# recibe el último valor retenido.

Observá que los paquetes MQTT son realmente chicos, que el largo restante se codifica de a 7 bits y que el broker solo necesita dos estructuras (las suscripciones y los retenidos) para dar el comportamiento de publicar y suscribirse: el resto es red y calidad de servicio.

12 · Exploración interactiva

Laboratorio: HTTP, JSON y MQTT

El laboratorio tiene cinco herramientas. La primera arma una petición HTTP y mide cuánto ocupa. La segunda interpreta los códigos de estado y propone qué hacer. La tercera valida un JSON. La cuarta arma un paquete MQTT PUBLISH y lo compara con HTTP. La quinta prueba filtros de temas con comodines.

1. Petición HTTP

Petición (⏎ = CR LF)—
Tamaño total—
Cuerpo—
Parte de encabezados—
Veredicto—

Elegí el método, la ruta y el cuerpo.

2. Código de estado

Familia—
Código—
De quién es la falla—
Qué hace el dispositivo—

Escribí un código de estado.

3. Validar JSON

Resultado—
Tamaño—
Campos—
Compactado—
Veredicto—

Escribí o modificá un JSON.

4. Paquete MQTT PUBLISH

Paquete (hexadecimal)—
Primer byte—
Largo restante—
Tamaño—
Paquetes del intercambio—
El equivalente en HTTP—

Elegí el tema, los datos y el QoS.

5. Filtro de temas

Filtro—
Tema—
Resultado—
Por qué—

Escribí un filtro y un tema.

En la herramienta 1, GET y DELETE no llevan cuerpo; en los otros métodos, si hay cuerpo se agregan Content-Type y Content-Length. En la 4, el identificador de paquete (QoS 1 y 2) vale 10, y la petición HTTP equivalente es la mínima posible: POST con el tema como ruta, Host y Content-Length. En la 5, los temas que empiezan con «$» no coinciden con comodines en el primer nivel.

Recorrido guiado

  1. Con los valores iniciales (POST a /api/lecturas, cuerpo {"t":23.5}): 132 bytes en total, con 10 de cuerpo; el 92 % son encabezados. Cambiá el método a GET: se ignora el cuerpo, y no hay Content-Type ni Content-Length.
  2. En la herramienta 2, con 201: «Created», éxito. Probá 404 (error del cliente: revisar la dirección), 429 (esperar), 503 (error del servidor: reintentar con espera) y 302 (redirección).
  3. En la herramienta 3, el JSON inicial es válido, de 33 bytes, con un texto, un número y un lógico. Cambiá las comillas dobles por simples, o agregá una coma antes de la llave final: «JSON inválido». Probá [21.5, 21.7, 22.0]: un arreglo válido.
  4. En la herramienta 4, con los valores iniciales: 30 1B 00 15 63 61 73 61 2F …, primer byte 0x30 (PUBLISH, QoS 0), largo restante 27, 29 bytes en total y un solo paquete. La petición HTTP equivalente es de 83 bytes, casi el triple.
  5. Subí el QoS a 1: el primer byte pasa a 0x32, el largo a 29 (se suma el identificador de 2 bytes), 31 bytes en total y dos paquetes (PUBLISH y PUBACK). Con QoS 2: 0x34 y cuatro paquetes. Poné Retenido = Sí con QoS 1: 0x33.
  6. Con un dato de 200 caracteres, el largo restante pasa de 127 y ocupa dos bytes: aparece la marca de continuación.
  7. En la herramienta 5, con el filtro casa/+/temperatura y el tema casa/sala/temperatura: coincide. Con el tema casa/sala/humedad: no. Con casa/sala/luz/temperatura: no (el + cubre un solo nivel).
  8. Probá el filtro casa/# con casa/sala/luz/estado (coincide) y con casa (también). Con el filtro # y el tema $SYS/uptime: no coincide. El filtro casa/#/luz es inválido: el # va solo y al final.
Desafío: el costo de la telemetría

Un sensor envía cada 10 segundos una temperatura de 4 caracteres. Comparemos el tráfico en un día. Con MQTT QoS 0 son 29 bytes por mensaje (herramienta 4): 8640 mensajes por día, unos 250 KB. Con HTTP, la petición mínima es de 83 bytes (y la respuesta agrega 100 o más), y si cada lectura abre su propia conexión TCP se suman el saludo (tres segmentos de al menos 40 bytes) y el cierre: más de 400 bytes por lectura, unos 3,5 MB por día, más de diez veces. En una conexión celular que se paga por megabyte, o en un enlace lento, la diferencia define cuál protocolo conviene.

13 · Un procedimiento

Diagnóstico de aplicaciones de red

Cuando una aplicación de red no funciona, se avanza por las capas hasta encontrar el nivel que falla:

  1. Conectividad. ¿El dispositivo tiene IP, puerta de enlace y DNS (tema 30)? ¿Llega al servidor (ping)? ¿Está abierto el puerto (tema 31)?
  2. Con otra herramienta, el mismo servicio. Probar el servidor con curl (HTTP) o con mosquitto_pub y mosquitto_sub (MQTT) desde una computadora. Si anda con la herramienta y no con el dispositivo, el problema está en el dispositivo; si no anda con ninguna, en el servicio o en la red.
  3. Mirar el diálogo. Un analizador de paquetes (Wireshark) entiende HTTP y MQTT y muestra los mensajes. En un broker, habilitar el registro detallado. En un servidor HTTP, el registro de accesos.
  4. Revisar el formato. Un JSON mal formado, un Content-Length incorrecto, una ruta equivocada, un tema con mayúsculas distintas.
  5. Revisar credenciales y permisos (401, 403, CONNACK de rechazo).
  6. Revisar memoria y tiempos en el dispositivo: buffers chicos, tiempos límite cortos, bloqueos en el bucle principal.
Síntomas y causa probable.
SíntomaCausa probableQué hacer
La petición HTTP no obtiene respuesta o se cuelgaFalta la línea en blanco final, o Content-Length no coincide con el cuerpoRevisar los encabezados y el CR LF; contar los bytes del cuerpo
400 Bad RequestJSON mal formado o ruta/parámetros incorrectosValidar el JSON; comparar con una petición hecha con curl
401 o 403Credenciales faltantes, incorrectas o sin permisoRevisar el token y los permisos
El cliente MQTT no conecta (CONNACK de error)Usuario/contraseña, identificador repetido o broker inaccesibleProbar con mosquitto_sub; mirar el registro del broker
Dos dispositivos se desconectan mutuamenteMismo identificador de clienteIdentificador único por dispositivo
Los mensajes se publican pero nadie los recibeTema escrito distinto (mayúsculas, barra inicial) o filtro incorrectoSuscribirse a # un momento y ver el tema real
Los mensajes grandes se pierden, los chicos lleganBuffer del cliente MQTT demasiado chicoAumentar el buffer (setBufferSize) o reducir el mensaje
El dispositivo aparece «conectado» aunque esté apagadoSin latido ni última voluntadConfigurar keep alive y la última voluntad retenida
Un tablero nuevo no ve el estado actual hasta que cambiaLos mensajes no se publicaron como retenidosPublicar el estado con RETAIN

14 · Comprensión

Confusiones frecuentes

«HTTP es solo para páginas web»

Es un protocolo genérico de pedido y respuesta. Hoy la mayoría de las APIs (de servicios en la nube, de dispositivos, de aplicaciones móviles) usan HTTP con JSON.

«Un 200 significa que mi dato se procesó bien»

Significa que el servidor aceptó la petición. Si se procesa luego (202), o si la aplicación devuelve un error dentro del cuerpo con un 200, hay que mirar también el cuerpo. Conviene usar el código de estado correcto en el servidor.

«Con MQTT los dispositivos se hablan entre sí»

Nunca: cada uno habla con el broker. Dos dispositivos no se conocen ni se necesitan; se comunican a través de los temas.

«El broker entiende los datos que lleva»

Solo mira el tema (y los metadatos). Los datos son bytes opacos: el formato lo acuerdan publicador y suscriptor.

«QoS 1 garantiza que el mensaje llega una sola vez»

Garantiza que llega al menos una vez; puede duplicarse. Para evitar duplicados hace falta QoS 2, o diseñar los mensajes para que repetirlos sea inofensivo (idempotentes).

«El QoS es entre el publicador y el suscriptor»

Se negocia por tramos: publicador a broker, y broker a cada suscriptor. El efectivo es el menor de los dos.

«Los comodines sirven para publicar»

Solo para suscribirse. Un mensaje se publica siempre en un tema concreto.

«Un broker sin contraseña en mi red está bien si el router no abre puertos»

Quizá, pero cualquier equipo de la red local puede leer y comandar todo, y un error de configuración del router lo expone a Internet. Autenticación y permisos por tema son la protección mínima.

15 · Práctica guiada

Predecí antes de comprobar

1. Content-Length

Un dispositivo envía por POST el cuerpo {"t":21.5,"h":60}. ¿Qué valor lleva Content-Length?

Ver solución

Se cuentan los caracteres: {"t":21.5,"h":60} son 1 + 3 + 1 + 4 + 1 + 3 + 1 + 2 + 1 = 17 bytes (llave, "t", dos puntos, 21.5, coma, "h", dos puntos, 60, llave). Content-Length: 17.

2. Qué hacer con un código

¿Qué debe hacer el dispositivo si recibe un 400, un 404, un 429 y un 503?

Ver solución

400: el pedido está mal formado; reintentarlo igual dará lo mismo, hay que corregirlo. 404: la ruta no existe; revisar la dirección. 429: demasiadas peticiones; esperar (hasta el tiempo de Retry-After) antes de volver a enviar. 503: servidor sobrecargado o en mantenimiento; reintentar con esperas crecientes.

3. Un JSON inválido

¿Qué está mal en {'id': "n1", "t": 23,5, }?

Ver solución

Tres errores: la clave 'id' usa comillas simples (tienen que ser dobles), 23,5 usa coma decimal (debe ser 23.5, y la coma separaría campos) y la coma final antes de la llave no está permitida.

4. Largo restante

Un PUBLISH con QoS 0 al tema a/b (3 caracteres) con 5 bytes de datos, ¿cuántos bytes mide y cuál es su largo restante?

Ver solución

Largo restante = 2 (largo del tema) + 3 (tema) + 5 (datos) = 10, que cabe en un byte (0A). Total = 1 (primer byte) + 1 (largo restante) + 10 = 12 bytes: 30 0A 00 03 61 2F 62 ….

5. Comodines

Un tablero se suscribe a planta/+/motor/temperatura. ¿Cuáles de estos temas recibe: planta/l1/motor/temperatura, planta/l1/l2/motor/temperatura, planta/motor/temperatura?

Ver solución

Solo el primero. El + reemplaza exactamente un nivel: el segundo tiene dos niveles en ese lugar y el tercero ninguno.

6. Elegir el QoS

Elegí un QoS para: (a) una temperatura enviada cada 2 segundos; (b) el comando «abrir la válvula»; (c) un contador de piezas producidas que se suma en el servidor.

Ver solución

(a) QoS 0: un valor perdido se compensa con el siguiente. (b) QoS 1: tiene que llegar, y abrir una válvula ya abierta no cambia nada (idempotente). (c) QoS 2: un duplicado haría sumar de más; también se podría diseñar el mensaje para que lleve el total acumulado y no el incremento, y usar QoS 1.

7. El estado de un nodo

¿Cómo se logra que cualquier tablero sepa en todo momento si un sensor está conectado?

Ver solución

Con mensaje retenido y última voluntad en el mismo tema de estado: al conectar, el sensor publica conectado con RETAIN; en el CONNECT declara como última voluntad desconectado con RETAIN. Si cae de forma anormal, el broker publica la última voluntad (detectada por el latido), y el tablero que se suscriba, ahora o después, recibe el último estado.

16 · Síntesis

Ideas para recordar

  • Un protocolo de aplicación define formato, diálogo, direccionamiento y garantías sobre TCP. HTTP es de pedido y respuesta; MQTT es de publicar y suscribirse con un broker.
  • HTTP son mensajes de texto: línea inicial, encabezados (Host, Content-Type, Content-Length), línea en blanco y cuerpo, con CR LF. Los métodos (GET, POST, PUT, DELETE) expresan la acción; el código de estado dice cómo salió: 2xx éxito, 3xx redirección, 4xx falla del cliente (no repetir igual), 5xx falla del servidor (reintentar con espera creciente).
  • JSON representa datos con objetos, arreglos, textos, números, true/false y null, con reglas estrictas (comillas dobles, sin comas finales). Es legible y universal, pero voluminoso; hay alternativas binarias.
  • En HTTP el servidor no puede avisar: el dispositivo tiene que preguntar (sondeo). Conviene usar tiempos límite, reintentos con espera creciente, mensajes chicos y TLS fuera de la red local.
  • MQTT desacopla a los participantes: cada dispositivo abre una conexión TCP con el broker, publica en temas jerárquicos y se suscribe con filtros (+ un nivel, # el resto). Como la conexión sale del dispositivo, atraviesa NAT y permite recibir comandos al instante.
  • Los paquetes MQTT son binarios y mínimos: primer byte con el tipo, largo restante de 7 bits por byte, tema, identificador (si QoS > 0) y datos. Publicar 4 bytes de dato en un tema de 21 caracteres son 29 bytes.
  • QoS 0 (como máximo una vez, 1 paquete), QoS 1 (al menos una vez, 2 paquetes, posibles duplicados) y QoS 2 (exactamente una vez, 4 paquetes). Se negocia por tramo y vale el menor. Retenido guarda el último mensaje del tema, la última voluntad avisa de una caída anormal, y el latido la detecta.
  • HTTP para configurar, integrar con servicios web y enviar de vez en cuando; MQTT para telemetría continua, comandos y muchos dispositivos. Ambos van en claro por omisión: usar TLS (443 y 8883), credenciales por dispositivo y permisos por tema, y no exponer un broker sin autenticación.

¿Listo para el siguiente tema?

Antes de avanzar, calculá el Content-Length de {"v":7} (7 bytes); decidí si casa/+/luz coincide con casa/sala/luz (sí) y con casa/sala/techo/luz (no); y explicá por qué un dispositivo detrás de NAT puede recibir comandos por MQTT pero no por un servidor HTTP propio (en MQTT la conexión con el broker la abre el propio dispositivo hacia afuera). Si podés justificar las tres respuestas, tenés los fundamentos de las aplicaciones de red para dispositivos.

En el próximo y último taller de esta parte integramos todo: un sensor en la red con Ethernet, con un módulo W5500, un servidor web de configuración, un cliente MQTT y el envío periódico de lecturas.