micropython-sockets-servidor-web-cliente

Sockets en MicroPython: cliente y servidor web básico

  • 5 min

Un socket es un extremo de comunicación que permite enviar y recibir bytes por la red.

Ya hemos conectado nuestra placa al Wi-Fi. Tenemos IP, tenemos internet. ¿Y ahora qué?

Para enviar o recibir datos, necesitamos una “tubería” digital que conecte nuestro microcontrolador con otro dispositivo (un servidor en la nube, tu móvil, otro ESP32). Esa tubería se llama Socket.

Los sockets son la API fundamental de las redes. No importa si estás viendo Netflix, cargando esta web o enviando un WhatsApp; en el fondo, todo son sockets enviando bytes de un lado a otro.

En esta entrada vamos a bajar al barro para entender cómo funcionan, configurando nuestra placa en dos modos:

  1. Servidor: Esperando conexiones (ej. para encender un LED desde el navegador).
  2. Cliente: Iniciando conexiones (ej. para descargar la hora o el tiempo).

El módulo socket

MicroPython (al igual que Python estándar) utiliza el módulo socket. La lógica siempre es:

  1. Creamos el socket.
  2. Nos conectamos (o esperamos conexión).
  3. Enviamos/Recibimos datos (send/recv).
  4. Cerramos el socket (¡Muy importante!).

MicroPython como servidor web

Este es el “Hola Mundo” del IoT. Queremos escribir la IP de nuestro ESP32 en el navegador Chrome/Firefox de nuestro móvil y ver una página web que nos permita encender un LED.

Para ello, nuestra placa debe estar “escuchando” en un puerto específico (normalmente el 80 para web).

Pasos del servidor

  1. Bind: Reservar una dirección IP y un puerto.
  2. Listen: Ponerse en modo “oreja puesta”, esperando llamadas.
  3. Accept: Cuando alguien llama, descolgar. Esto crea un nuevo socket exclusivo para ese cliente.

Control de un LED desde la web

import network
import socket
import machine
import time

# 0. Configuración previa (Conexión Wi-Fi y LED)
# Asumimos que ya te has conectado al Wi-Fi usando el código del capítulo anterior
led = machine.Pin(2, machine.Pin.OUT)

# 1. Crear el Socket
# AF_INET = IPv4, SOCK_STREAM = TCP (Protocolo fiable)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. Bind (Vincular)
# '' significa escuchar en todas las interfaces de red disponibles
# 80 es el puerto estándar HTTP
try:
    s.bind(('', 80))
except OSError as e:
    # Si el puerto estaba ocupado (reset suave), a veces falla.
    print("El puerto estaba ocupado, reintentando...")

# 3. Listen (Escuchar)
# El parámetro 5 indica cuántas conexiones mantenemos en espera si estamos ocupados
s.listen(5)
print("Servidor Web escuchando en el puerto 80...")

while True:
    try:
        # 4. Accept (Aceptar conexión)
        # Se bloquea aquí hasta que alguien se conecta
        conn, addr = s.accept()
        print('Conexión recibida desde:', addr)
        
        # 5. Recibir la petición (Request)
        request = conn.recv(1024) # Leemos hasta 1024 bytes
        request_str = request.decode('utf-8')
        print("Petición:", request_str)
        
        # Analizamos qué nos piden (Logica muy básica)
        if '/on' in request_str:
            led.value(1)
            estado = "ENCENDIDO"
        elif '/off' in request_str:
            led.value(0)
            estado = "APAGADO"
        else:
            estado = "DESCONOCIDO"

        # 6. Enviar respuesta (Response)
        # Necesitamos enviar cabeceras HTTP válidas primero
        response = """HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n
        <!DOCTYPE html>
        <html>
        <head><title>ESP32 Web</title></head>
        <body>
            <h1>Control LED</h1>
            <p>Estado: <strong>""" + estado + """</strong></p>
            <p><a href="/on"><button>ENCENDER</button></a></p>
            <p><a href="/off"><button>APAGAR</button></a></p>
        </body>
        </html>
        """
        conn.send(response.encode('utf-8'))
        
    except Exception as e:
        print("Error en la conexión:", e)
        
    finally:
        # 7. Cerrar la conexión con EL CLIENTE
        # Importante cerrar 'conn', no 's' (el servidor sigue escuchando)
        conn.close()
Copied!

Si cargas este código, verás la IP en la consola. Escríbela en tu navegador y ya lo tienes: un panel de control.

Limitaciones Este servidor es bloqueante y monohilo. Si intentas manejar muchas peticiones a la vez o el código se queda pillado procesando algo, la web no cargará. Para servidores más serios usaremos asyncio o librerías como Microdot más adelante.

MicroPython como cliente

Ahora al revés. Queremos que nuestra placa se conecte a un servidor de internet (como Google, una API de tiempo o tu propio servidor) para descargar datos.

Pasos del cliente

  1. Connect: Llamar a una IP y puerto remoto.
  2. Send: Enviar la petición (ej. “GET / HTTP/1.0”).
  3. Recv: Leer la respuesta.

Descarga de texto de internet

Vamos a hacer una petición HTTP manual para ver las tripas del protocolo.

import socket

# Configuración del destino
host = "www.example.com"
port = 80
path = "/"

# 1. Obtener la dirección IP del dominio (DNS Lookup)
addr_info = socket.getaddrinfo(host, port)
addr = addr_info[0][-1]
print("Conectando a:", addr)

# 2. Crear socket y conectar
s = socket.socket()
s.connect(addr)

# 3. Enviar petición HTTP GET
# OJO: Los saltos de línea \r\n\r\n son obligatorios en HTTP
request = f"GET {path} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n"
s.send(request.encode('utf-8'))

# 4. Recibir respuesta
print("--- Respuesta del Servidor ---")
while True:
    data = s.recv(1024) # Leemos por trozos de 1KB
    if not data:
        break # Si no hay datos, el servidor cerró la conexión
    print(data.decode('utf-8'), end='')

# 5. Cerrar
s.close()
Copied!

Al ejecutarlo, verás en la consola el código HTML crudo de la página example.com, incluidas las cabeceras HTTP/1.1 200 OK, Content-Type, etc.

requests: una interfaz más sencilla

Escribir peticiones HTTP a mano con sockets (poniendo los \r\n y gestionando cabeceras) es un dolor de cabeza y propenso a errores.

Para actuar como cliente, en el día a día podemos usar la librería requests, disponible en micropython-lib, que hace todo esto por nosotros. En firmwares antiguos todavía puedes encontrarla con el nombre urequests.

import requests

respuesta = requests.get("http://www.example.com")
print(respuesta.text)
respuesta.close()
Copied!

Mucho más fácil, ¿verdad? Pero requests está construido encima de los sockets que acabamos de aprender.

Usar sockets “crudos” es útil cuando necesitamos máxima velocidad, protocolos propios que no son HTTP, o cuando queremos reducir el consumo de memoria al mínimo absoluto.

Con esto ya sabemos cómo mover información dentro y fuera del chip. Ahora tenemos la base para conectar con servicios reales como bases de datos o brokers MQTT.