Un servidor web en nanoFramework es un pequeño servicio HTTP que corre dentro del microcontrolador para responder peticiones externas.
Hasta ahora, nuestro dispositivo “navegaba” por Internet buscando datos. Pero, ¿y si queremos que sea él quien nos muestre una interfaz?
Imagina poder escribir la IP de tu dispositivo en Chrome (http://192.168.1.50) y que aparezca un panel de control para encender luces, ver la temperatura o, lo más importante, configurar las credenciales Wi-Fi sin tener que recompilar el código.
En esta entrada aprenderemos a usar la clase HttpListener. Es el componente que nos permite “escuchar” en el puerto 80 y responder con código HTML cuando alguien nos visita.
Para este tutorial necesitas tener instalado el paquete NuGet: nanoFramework.System.Net.Http.Server
¿Qué es HttpListener?
En el mundo de .NET “grande” (ASP.NET Core), los servidores web son complejos y potentes. En nanoFramework, tenemos una versión ligera llamada HttpListener.
Su funcionamiento es el siguiente:
- Constructor: indicamos
httpohttpsy el puerto que queremos escuchar. - Start: arrancamos el servicio.
- GetContext: esperamos hasta que llegue una petición.
- Procesar: leemos la petición (request) y enviamos una respuesta (response).
Crear un servidor web básico
Vamos a crear un servidor que simplemente diga “Hola Mundo” en HTML.
using System;
using System.Net;
using System.Text;
using System.Threading;
using System.Diagnostics;
// ... Asegúrate de estar conectado al Wi-Fi antes de llamar a esto
public static void IniciarServidorWeb()
{
// 1. Crear el listener HTTP en el puerto 80
HttpListener listener = new HttpListener("http", 80);
listener.Start();
Debug.WriteLine("Servidor Web iniciado. Esperando conexiones...");
while (true)
{
try
{
// 2. Esperar una petición (El código se detiene aquí hasta que entras con el navegador)
HttpListenerContext context = listener.GetContext();
// 3. Obtener objetos Request y Response
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
Debug.WriteLine($"Petición recibida: {request.HttpMethod} {request.RawUrl}");
// 4. Preparar la respuesta HTML
string respuestaHtml = "<html><body><h1>Hola desde nanoFramework</h1><p>Funcionando en ESP32</p></body></html>";
byte[] buffer = Encoding.UTF8.GetBytes(respuestaHtml);
// 5. Configurar cabeceras
response.ContentLength64 = buffer.Length;
response.ContentType = "text/html";
// 6. Enviar datos
response.OutputStream.Write(buffer, 0, buffer.Length);
// 7. Cerrar la conexión (¡Muy importante!)
response.OutputStream.Close();
}
catch (Exception ex)
{
Debug.WriteLine($"Error en servidor: {ex.Message}");
}
}
}Si ejecutas esto, y entras en la IP de tu ESP32 desde tu móvil u ordenador, ¡verás tu página web servida desde el chip!
Controlar un LED desde el navegador
Una web estática es aburrida. Queremos botones. Vamos a añadir dos enlaces en el HTML que apunten a:
/encender/apagar
Luego, en nuestro código C#, miraremos la propiedad request.RawUrl para ver qué ha pedido el usuario y actuar sobre el GPIO.
// ... (Dentro del bucle while)
HttpListenerContext context = listener.GetContext();
HttpListenerRequest request = context.Request;
// Lógica de enrutamiento simple
string url = request.RawUrl; // Ej: "/encender"
string mensajeEstado = "El LED está ???";
if (url.Contains("encender"))
{
// Aquí encenderíamos el GPIO (usando la clase GpioController vista en lecciones anteriores)
// led.Write(PinValue.High);
mensajeEstado = "El LED está ENCENDIDO";
}
else if (url.Contains("apagar"))
{
// led.Write(PinValue.Low);
mensajeEstado = "El LED está APAGADO";
}
// Generar HTML dinámico
string html = "<html><head><style>button { padding: 20px; font-size: 20px; }</style></head>";
html += "<body><h1>Panel de Control</h1>";
html += $"<p>Estado: <strong>{mensajeEstado}</strong></p>";
html += "<a href='/encender'><button>ENCENDER</button></a> ";
html += "<a href='/apagar'><button>APAGAR</button></a>";
html += "</body></html>";
// ... Enviar respuesta como antesConfigurar la red en modo punto de acceso
Un caso de uso muy típico es el Portal Cautivo. Cuando compras un dispositivo, este no sabe tu Wi-Fi. Lo que hace es:
- Crear su propia red Wi-Fi (Access Point Mode).
- Tú te conectas a esa red con el móvil.
- El servidor web te sirve una página con un formulario.
- Tú envías (POST) el nombre y contraseña de tu red doméstica.
- El dispositivo guarda los datos, se reinicia y se conecta a tu router.
Para hacer esto, necesitaríamos configurar el Wi-Fi en modo AP (que nanoFramework soporta) y manejar una petición POST.
Recibir un formulario mediante POST
Cuando envías un formulario HTML (<form method='POST'>), los datos no van en la URL, van en el cuerpo.
if (request.HttpMethod == "POST")
{
// Leer el cuerpo de la petición
System.IO.Stream body = request.InputStream;
System.IO.StreamReader reader = new System.IO.StreamReader(body, request.ContentEncoding);
string datosFormulario = reader.ReadToEnd();
// datosFormulario será algo como: "ssid=MiCasa&pass=1234"
Debug.WriteLine($"Datos recibidos: {datosFormulario}");
// Aquí tendrías que "parsear" ese string, guardar configuración y reiniciar.
}Limitaciones del Servidor
HttpListener en un microcontrolador no está pensado para servir imágenes grandes, CSS pesados o soportar 50 usuarios a la vez. Es para configuración y control simple.
Si necesitas una web compleja, es mejor alojarla en la nube y que el ESP32 solo envíe/reciba datos JSON.
Ejecución bloqueante o asíncrona
El ejemplo anterior usa listener.GetContext(), que es bloqueante. Mientras espera una visita, el hilo se detiene.
Si tu dispositivo tiene que hacer otras cosas (leer sensores, parpadear leds) mientras espera visitas web, tienes dos opciones:
- Ejecutar el servidor web en un Hilo (
Thread) separado. - Reorganizar la aplicación con varios hilos y mecanismos de sincronización.
En la API actual de nanoFramework, HttpListener expone GetContext() de forma síncrona. Por tanto, ejecutar el listener en un hilo dedicado es la opción directa si el dispositivo debe atender otras tareas.