Saltar al contenido
IDE libre

IoT

Wi-Fi, HTTP y MQTT en la ESP32 con MicroPython

Wi-Fi con network.WLAN, HTTP con requests y MQTT con umqtt.simple en la ESP32: estados de la conexión, errores de la placa y cómo probarlo en el simulador.

Por Prof. Rodrigo Faustino · Publicado el · actualizado el · 13 min de lectura

Métricas de este artículo

Palabras
2430
Caracteres
14.418
Tiempo de lectura
~13 min

Hasta aquí, la ESP32 conversó con el mundo por los pines: LEDs, botones, sensores y pantallas. Lo que la diferencia de muchas placas de microcontrolador, sin embargo, es la radio Wi-Fi integrada en el chip. Con ella, la placa busca el pronóstico del tiempo en internet, envía lecturas de sensores a un panel, recibe comandos del celular. Es la base de lo que se llama Internet de las Cosas (IoT).

Este artículo recorre las tres capas de ese camino en MicroPython: el Wi-Fi con el módulo network, las solicitudes HTTP con el módulo requests y los mensajes MQTT con umqtt.simple. En cada una, los comportamientos de la placa que sorprenden a los principiantes (la conexión que no es instantánea, los códigos de error, el ADC2 que deja de funcionar) y cómo PyPlaca simula todo eso con seguridad, sin que la placa tenga que salir del navegador.

El Wi-Fi como interfaz de red

En MicroPython, el Wi-Fi es la clase WLAN del módulo network. La ESP32 tiene dos interfaces Wi-Fi:

  • STA (station, estación): la placa se conecta a un router, como un celular. Es el modo de casi todos los proyectos;
  • AP (access point, punto de acceso): la placa crea su propia red, y otros aparatos se conectan a ella.

network.WLAN(network.STA_IF) devuelve la interfaz de estación, y siempre el mismo objeto, por más veces que se llame. Antes de cualquier cosa, la interfaz tiene que activarse con active(True), que enciende la radio. Solo entonces funciona connect(); llamado antes, da un OSError con el motivo en texto (como Wifi Not Started).

import network
import time

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('laboratorio', 'esp32lab')

inicio = time.ticks_ms()
while not wlan.isconnected():
    if time.ticks_diff(time.ticks_ms(), inicio) > 10000:
        print('no conectó; status =', wlan.status())
        break
    time.sleep_ms(100)
else:
    print('¡conectado! IP:', wlan.ifconfig()[0])

En PyPlaca, la placa simulada "ve" una red llamada laboratorio, con la contraseña esp32lab. El botón Wi-Fi del banco de trabajo muestra el estado de la conexión y deja que el profesor cambie el nombre y la contraseña de esa red. Abre el ejemplo y ejecútalo: después de un instante, el Serial muestra la IP que el "router" del laboratorio le dio a la placa.

La conexión no es instantánea

El punto más importante del ejemplo es el bucle de espera. El connect() vuelve al instante: solo le pide al sistema de la radio que empiece a conectar. La conexión de verdad (encontrar la red, negociar la contraseña, recibir una IP por DHCP) ocurre después, en segundo plano. Un programa que llama a connect() e inmediatamente intenta usar la red falla.

Por eso todo programa de Wi-Fi espera a que isconnected() sea verdadero. Y espera con límite de tiempo: si la contraseña es incorrecta o la red no existe, el bucle sin límite se quedaría preso para siempre. El ejemplo usa ticks_diff, la misma técnica del artículo Programar sin colgarse, y el else del while solo se ejecuta si el bucle termina sin break.

En el simulador, la conexión tarda cerca de 1,5 s de tiempo virtual. Es un valor de referencia elegido para la simulación: el tiempo real depende del router y todavía se va a medir en una placa del laboratorio.

Los estados de la conexión

wlan.status() dice en qué punto está la conexión, con las constantes de MicroPython (los números son los de ESP-IDF, el sistema de Espressif que hay debajo de MicroPython):

ConstanteValorSignificado
network.STAT_IDLE1000parado, sin intentar conectar
network.STAT_CONNECTING1001conectando
network.STAT_GOT_IP1010conectado y con IP
network.STAT_NO_AP_FOUND201la red no se encontró
network.STAT_WRONG_PASSWORD202contraseña incorrecta
network.STAT_CONNECT_FAIL203falló por otro motivo
Diagrama de los estados de la conexión Wi-Fi de la ESP32: después de connect, el status pasa a STAT_CONNECTING (1001) y luego a STAT_GOT_IP (1010) con la IP del router, o a STAT_WRONG_PASSWORD (202) con la contraseña incorrecta, o a STAT_NO_AP_FOUND (201) si la red no existe. disconnect lleva a STAT_IDLE (1000).
Los estados de wlan.status() en el MicroPython de la ESP32: el connect() vuelve al instante, y la conexión ocurre después; por eso el programa espera el STAT_GOT_IP.

Dos detalles del MicroPython real que el simulador reproduce: el motivo de un fallo no se pone a cero solo por llamar de nuevo a connect(); y después de disconnect() el status vuelve a STAT_IDLE. Cambia la contraseña del ejemplo por una incorrecta y ejecuta de nuevo para ver el 202.

Buscar redes

wlan.scan() lista las redes al alcance. Cada elemento es una tupla con el nombre (en bytes), la dirección del router, el canal, la fuerza de la señal (RSSI, en dBm: cuanto más cerca de cero, mejor), el tipo de seguridad y si la red es oculta. La lista viene de la señal más fuerte a la más débil, y el scan() bloquea el programa mientras busca.

import network

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
for ssid, bssid, canal, rssi, seguranca, oculta in wlan.scan():
    print(ssid.decode(), 'canal', canal, 'señal', rssi, 'dBm')

El ADC2 deja de funcionar

Un efecto colateral del Wi-Fi sorprende a mucha gente: con la radio encendida, los pines del ADC2 (GPIO 0, 2, 4, 12 a 15 y 25 a 27) ya no pueden hacer lecturas analógicas, porque el sistema del Wi-Fi se queda con ese conversor. La lectura da OSError: [Errno 116] ETIMEDOUT, y el simulador muestra el aviso adc2-wifi. La regla, explicada en el artículo sobre el ADC de la ESP32, es simple: los proyectos con Wi-Fi leen los sensores analógicos en el ADC1, los GPIO 32 a 39.

FastMindAsistente de IA local

La IA que recuerda tu vida y vive en tu ordenador

  • Memoria a largo plazo con la fuente de cada respuesta
  • Funciona sin conexión: tus datos se quedan en tu PC
  • Lee PDF, diapositivas, fotos y grabaciones de reuniones
Descargar gratis para Windows

Plan Esencial gratis para siempre

HTTP con requests

Con la placa en la red, el camino más común para buscar datos es el HTTP, el mismo protocolo de las páginas web. El firmware de MicroPython para la ESP32 trae el módulo requests (también importable como urequests), de la biblioteca oficial micropython-lib. El simulador ejecuta el mismo archivo original, sin adaptaciones, sobre una pila de red simulada.

import network
import time
import requests

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('laboratorio', 'esp32lab')
while not wlan.isconnected():
    time.sleep_ms(100)

r = requests.get('http://api.laboratorio.test/clima')
print('status:', r.status_code)
dados = r.json()
print('temperatura:', dados['temperatura'], '°C')
print('humedad:', dados['umidade'], '%')
r.close()

La respuesta tiene status_code (200 es "salió bien"), text (el cuerpo como texto), content (como bytes) y json(), que transforma el cuerpo en diccionario cuando el servidor envía JSON. El close() del final devuelve la conexión: la ESP32 tiene poca memoria y pocos sockets, y un programa que hace muchas solicitudes sin cerrar acaba quedándose sin ellos.

Para enviar datos, requests.post() acepta un diccionario en el argumento json=:

r = requests.post('http://api.laboratorio.test/leituras', json={'temperatura': 23.4})
print(r.status_code)   # 201: creado
r.close()

Qué es "internet" en el simulador

El código del alumno no puede salir a buscar cualquier dirección de internet desde el navegador: sería un riesgo para el alumno y para la plataforma. Por eso la red del simulador funciona así:

  • respuestas listas (mocks): direcciones como api.laboratorio.test responden con datos fijos, sin salir del navegador. Cada solicitud aparece en una línea corta en el Serial, con el método, la dirección y el status;
  • lista permitida: solo unos pocos dominios aprobados por la plataforma se consultan de verdad, y solo cuando la actividad lo permite;
  • todo lo demás: el nombre no se encuentra, como en una red sin internet. El getaddrinfo de la placa devuelve OSError: -202 y el simulador muestra el aviso rede-fora-da-lista.

El mismo -202 aparece si el programa intenta usar un nombre antes de que el Wi-Fi conecte: es la placa real diciendo "no pude resolver ese nombre". Si el servidor tarda más que el tiempo límite del socket, el error es OSError: [Errno 116] ETIMEDOUT.

HTTP y programas que no se cuelgan

Un requests.get() bloquea el programa hasta que llega la respuesta. En un proyecto que además hace parpadear LEDs y lee botones, eso significa que todo se detiene durante la solicitud. Dos prácticas ayudan: hacer solicitudes pocas veces (cada minuto, no en cada vuelta del bucle), controladas por ticks_diff, y preferir MQTT cuando la placa necesita intercambiar mensajes con frecuencia.

MQTT: mensajes por topics

El HTTP funciona con el modelo "pregunta y respuesta": la placa pregunta, el servidor responde. Para IoT, eso tiene un problema: el servidor no puede avisarle nada a la placa; ella tendría que preguntar todo el tiempo. MQTT lo resuelve con otro modelo, el de publicar y suscribirse:

  • existe un servidor central, el broker;
  • cada dispositivo se conecta al broker y mantiene la conexión abierta;
  • quien quiere enviar algo publica un mensaje en un topic, un nombre como lab/sala1/temperatura;
  • quien quiere recibir se suscribe al topic, y el broker entrega el mensaje a todos los suscriptores.

Nadie habla directamente con nadie. La placa que publica la temperatura no sabe si quien lee es un celular, un panel o diez dispositivos más, y no necesita saberlo.

Diagrama del MQTT: la ESP32 publica en el topic lab/contador y se suscribe a lab/led; en el centro, el broker; a la derecha, un celular que se suscribe a lab/contador y publica liga en el topic lab/led. Flechas muestran los mensajes pasando siempre por el broker.
En MQTT nadie habla directamente con nadie: todos publican y se suscriben a topics en un broker, que entrega cada mensaje a quien se suscribió.

Topics y comodines

Los topics son jerárquicos, separados por /. Al suscribirse, se pueden usar comodines:

  • + sustituye un nivel: lab/+/temperatura recibe lab/sala1/temperatura y lab/sala2/temperatura;
  • # sustituye todos los niveles siguientes: lab/# recibe todo lo que empieza por lab/.

Al publicar, no se permiten comodines. Un mensaje puede ser retenido (retain=True): el broker guarda el último mensaje del topic y se lo entrega a quien se suscriba después; es útil para "el estado actual del LED", que un panel recién abierto necesita saber.

umqtt.simple en la práctica

El firmware de la ESP32 trae el cliente umqtt.simple, también de micropython-lib, y el simulador usa el original. El ejemplo de abajo conecta al Wi-Fi, se suscribe al topic lab/led para recibir comandos y publica un contador en lab/contador cada 2 segundos, sin colgarse, con check_msg() y ticks_diff:

import network
import time
from machine import Pin
from umqtt.simple import MQTTClient

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('laboratorio', 'esp32lab')
while not wlan.isconnected():
    time.sleep_ms(100)

led = Pin(2, Pin.OUT)

def chegou(topico, msg):
    print('llegó en', topico, ':', msg)
    if msg == b'liga':
        led.on()
    elif msg == b'desliga':
        led.off()

cliente = MQTTClient('esp32-aluno', 'broker.laboratorio.test')
cliente.set_callback(chegou)
cliente.connect()
cliente.subscribe(b'lab/led')

ultimo = time.ticks_ms()
n = 0
while n < 15:
    cliente.check_msg()                      # mira si llegó un mensaje, sin esperar
    if time.ticks_diff(time.ticks_ms(), ultimo) >= 2000:
        ultimo = time.ticks_add(ultimo, 2000)
        n += 1
        cliente.publish(b'lab/contador', str(n).encode())
    time.sleep_ms(50)
cliente.disconnect()

Los pasos son siempre los mismos: crear el MQTTClient con un identificador único y la dirección del broker, set_callback() con la función que recibe los mensajes, connect(), subscribe() y, en el bucle, check_msg(). El callback recibe el topic y el mensaje como bytes; por eso la comparación es con b'liga', y no con 'liga'.

check_msg() es la versión que no bloquea: si no llegó nada, vuelve al instante. Existe también wait_msg(), que se queda parado hasta que llega un mensaje: práctico en un programa que solo espera comandos, desastroso en uno que además tiene que hacer otras cosas.

El broker del simulador

En PyPlaca, el broker corre en el propio navegador y habla MQTT 3.1.1 de verdad, byte a byte: conexión, publicación con QoS 0, 1 y 2, suscripciones con comodines, mensajes retenidos, sesión persistente y "última voluntad". La dirección broker.laboratorio.test apunta a él, y los brokers públicos que usan los tutoriales (test.mosquitto.org, broker.hivemq.com, broker.emqx.io y mqtt.eclipseprojects.io) también: no se contacta ningún broker de internet. Así, un programa copiado de un tutorial corre igual, y ningún mensaje de alumno acaba en un servidor público.

La pestaña MQTT del banco de trabajo muestra los dispositivos conectados, a qué se suscribió cada uno, los topics con el último mensaje y los mensajes recientes. También tiene el formulario Publicar como otro dispositivo: ejecuta el ejemplo, publica liga en el topic lab/led y verás encenderse el LED azul de la placa.

Captura de pantalla de PyPlaca con la pestaña MQTT del banco de trabajo abierta: dispositivos conectados al broker simulado, topics con el último mensaje y el formulario para publicar como otro dispositivo; a la izquierda, el código con umqtt.simple.
La pestaña MQTT del simulador: el broker corre en el navegador, muestra quién está conectado y te deja publicar como si fueras otro dispositivo.

QoS, Keep Alive y conexiones que se caen

Tres conceptos de MQTT que aparecen enseguida en los primeros proyectos:

  • QoS (calidad de servicio): 0 es "manda y olvida"; 1 garantiza que el mensaje llega al menos una vez (el broker confirma); 2 garantiza exactamente una vez, con más idas y venidas. Para lecturas de sensor, QoS 0 basta;
  • Keep Alive: el cliente promete enviar algún paquete cada tantos segundos. Si pasa una vez y media ese tiempo sin noticias, el broker considera muerta la conexión y la corta;
  • última voluntad (last will): un mensaje que el cliente deja acordado al conectar y que el broker publica si él se cae sin despedirse; es la manera clásica de que un panel muestre "placa fuera de línea".

Cuando la conexión se cae, las lecturas del socket empiezan a fallar. umqtt.simple no intenta reconectar; para eso existe umqtt.robust, también en el firmware, que reconecta solo.

Juntar sensores, red y tiempo

Un proyecto de IoT completo combina todo lo que mostró la serie: leer un sensor en el ADC1 o con el DHT22, organizar las tareas con ticks_diff para que nada se cuelgue, publicar las lecturas por MQTT cada cierto tiempo y reaccionar a los comandos que llegan por check_msg(). Un esqueleto típico:

  1. conectar al Wi-Fi con límite de tiempo y un mensaje claro si falla;
  2. conectar al broker y suscribirse a los topics de comando;
  3. en el bucle: check_msg(), leer los sensores a su ritmo, publicar a su ritmo, pausa corta;
  4. tratar el OSError de la red (se cayó el Wi-Fi, el broker está fuera de línea) sin derribar el resto del programa.

Antes de llevarlo a la placa real, cambia el broker del laboratorio por la dirección del broker de tu escuela o de tu proyecto; el resto del código sigue igual. Y recuerda que, en la placa de verdad, la red es de verdad: las contraseñas no van a repositorios públicos, y los topics con nombres únicos evitan que tu placa reciba los mensajes de la placa del compañero.

FastTeacherPara profesores

Del programa al semestre planificado

  • Plan de enseñanza y calendario montados solos
  • Diario de clases listo para pegar en el SIGA
  • Diapositivas y listas de ejercicios con solucionario
Conocer FastTeacher

Versión Community gratis · Windows

Diagnosticar problemas de red

Cuando un programa de IoT no funciona, conviene seguir las capas de abajo hacia arriba, como en el banco de trabajo:

  1. ¿conectó el Wi-Fi? Imprime wlan.status(): 201 es red no encontrada, 202 es contraseña incorrecta. En el simulador, el botón Wi-Fi del banco de trabajo muestra el estado y la IP;
  2. ¿se resuelve el nombre del servidor? Un OSError: -202 antes de cualquier respuesta indica que no se encontró el nombre: Wi-Fi todavía desconectado, nombre mal escrito o, en el simulador, una dirección fuera de la lista de la actividad;
  3. ¿respondió el servidor? Mira el status_code del HTTP antes de llamar a json(). Un 404 tiene cuerpo, pero no el que esperabas;
  4. en MQTT, ¿aceptó la conexión? El connect() lanza un error si el broker la rechaza (usuario y contraseña incorrectos, por ejemplo);
  5. ¿llegan los mensajes? Comprueba que el topic suscrito sea exactamente el publicado (mayúsculas, barras y espacios cuentan) y que el bucle llame a check_msg() con frecuencia;
  6. el callback recibe bytes: comparar msg == 'liga' (texto) con b'liga' (bytes) es falso, y el comando parece ignorado.

En el simulador, cada solicitud HTTP aparece en el Serial y en la ventana de la red simulada, y la pestaña MQTT muestra quién está conectado y qué se publicó. Es el equivalente a tener un analizador de red junto al banco de trabajo.

Para continuar

Este artículo cierra la serie básica de ESP32, y las tres últimas rutas de la plataforma son su continuación directa. La ruta Wi-Fi y HTTP rehace la primera mitad de este texto con lecciones y desafíos corregidos automáticamente: conectar con límite de tiempo, entender el status(), buscar e interpretar JSON, enviar lecturas con POST y no quedarse esperando para siempre a un servidor lento. La ruta MQTT practica publicar y suscribirse, topics y comodines, recibir comandos sin colgarse con check_msg(), Keep Alive y última voluntad. Y el proyecto final lo junta todo en una estación meteorológica: DHT22, OLED, configuración por HTTP y lecturas en MQTT.

Después vienen cinco artículos de profundización: ESP-NOW, para que las placas conversen sin router; servidor web en la ESP32, para que la placa sirva su propia página; guardar datos en la ESP32; baterías y deep sleep; e IA con ESP32, que incluye la integración de la placa con un modelo de lenguaje.

Para elegir los pines correctos cuando el proyecto salga del simulador y vaya al banco de trabajo, consulta la ficha de la ESP32 DevKit V1 y el mapa de pines, y recuerda el ADC1 para los sensores analógicos.

Para practicar en PyPlaca

Rutas

Lecciones

Fichas de componentes

FastTeacherPara profesores

Del programa al semestre planificado

  • Plan de enseñanza y calendario montados solos
  • Diario de clases listo para pegar en el SIGA
  • Diapositivas y listas de ejercicios con solucionario
Conocer FastTeacher

Versión Community gratis · Windows

FastMindAsistente de IA local

La IA que recuerda tu vida y vive en tu ordenador

  • Memoria a largo plazo con la fuente de cada respuesta
  • Funciona sin conexión: tus datos se quedan en tu PC
  • Lee PDF, diapositivas, fotos y grabaciones de reuniones
Descargar gratis para Windows

Plan Esencial gratis para siempre

FastTeacherPara profesores

Del programa al semestre planificado

  • Plan de enseñanza y calendario montados solos
  • Diario de clases listo para pegar en el SIGA
  • Diapositivas y listas de ejercicios con solucionario
Conocer FastTeacher

Versión Community gratis · Windows