esp32-net6-http

Cómo conectar un ESP32 con NET6 por HTTP

Continuando con nuestra serie de entradas sobre cómo conectar un dispositivo ESP32 con una aplicación en NET, en esta entrada veremos cómo realizar la comunicación mediante llamadas HTTP.

En nuestra entrada anterior, vimos cómo conectar un ESP32 con una aplicación en NET6, utilizando el puerto serial. En esta entrada, dejaremos atrás el cable y pasaremos a ser inalámbricos a través de una conexión Wi-Fi.

NET6 puede ser utilizado en una variedad de plataformas, como Windows, Android, Linux, Mac, x86/x64 y ARM7 o superior. Esta multiplataforma significa que podemos ejecutar el mismo programa en diferentes dispositivos, como ordenadores de sobremesa con Windows o Linux, móviles Android o Raspberry Pi, sin necesidad de realizar cambios en el código.

Al igual que en la entrada anterior, usaremos un M5 Stack, pero cualquier ESP32 o Arduino con Wi-Fi os servirá para seguir los pasos de este tutorial.

Comenzaremos con una de las comunicaciones Wi-Fi más sencillas, la comunicación a través de una solicitud HTTP. Ya hemos visto la comunicación HTTP en la sección del ESP32 y, como ya hemos comentado, no existe una forma de comunicación que sea mejor que otra, todas tienen sus ventajas y desventajas.

En el caso de la comunicación HTTP, estamos ante una comunicación de tipo cliente-servidor, en la que un servidor ofrece ciertos servicios a los que se conectan uno o varios clientes. La comunicación no es bidireccional, ya que el cliente es el que inicia la comunicación, el servidor no puede iniciar la comunicación con el cliente.

La comunicación mediante solicitudes HTTP es útil, por ejemplo, cuando tenemos un gran número de clientes que se conectan con un único servidor. Sin embargo, la comunicación no es , y el servidor es fácilmente localizable, entendiendo que es fácil obtener su IP (directa o indirectamente.).

Vamos a diferenciar el caso en el que el ESP32 actúa como servidor y el ordenador con NET actúa como servidor

ESP32 como servidor, NET6 cliente

Código ESP32

En este primer caso el ESP32 actúa como servidor. Así que necesitamos levantar un servidor, algo que ya sabemos hacer perfectamente. Como siempre, dividimos el código en ficheros, agrupando características. Así tenemos el fichero server.hpp con todas las funcionalidades relativas


#pragma once

WebServer server(80);

void handleNotFound() 
{
   server.send(404, "text/plain", "Not found");
}

void InitServer()
{
   server.on("/A", []()
   {
      server.send(200, "text/plain", "A funciona");
      Serial.println("A");
   });

   server.on("/B", []()
   {
      server.send(200, "text/plain", "B funciona");
      Serial.println("B");
   });
 
   server.onNotFound(handleNotFound);
 
   server.begin();
   Serial.println("HTTP server started");
}

Por otro lado, en la función setup simplemente inicializamos el WiFi y el servidor,

void setup(void) 
{
  Serial.begin(115200);

  ConnectWiFi_STA();
 
  InitServer();
}

Y finalmente en el loop simplemente llamamos a la función que gestiona el servidor,

void loop()
{
  M5.update();

  server.handleClient();
  delay(100);
}

Código NET6

En cuanto a la parte del .NET necesitamos hacer una petición Http contra el ESP32. Hay varias formas de hacer una petición Http desde C#, pero una de las más sencillas es la siguiente,

using System.Diagnostics;
using System.Reactive.Linq;

var isOn = false;
var timer = Observable.Interval(TimeSpan.FromSeconds(2))
.Subscribe(async _ =>
{
    isOn = !isOn;
    var url = @$"http://192.168.1.xxx/{(isOn ? "A" : "B")}";

    var response = await new HttpClient().GetAsync(url);
    var data = await response.Content.ReadAsStringAsync();
    Console.WriteLine(data);
});

Console.ReadLine();

NET32 como servidor, ESP32 cliente.

Código ESP32

En este caso el ESP32 tiene que realizar una petición contra el backend en .NET. Por lo cual simplemente tenemos que usar un http client hemos creado un fichero API.h que englobe estas funciones.

void processResponse(int httpCode, HTTPClient& http)
{
  if (httpCode > 0) {
    Serial.printf("Response code: %d\t", httpCode);

    if (httpCode == HTTP_CODE_OK) {
      String payload = http.getString();
      Serial.println(payload);
    }
  }
  else {
    Serial.printf("Request failed, error: %s\n", http.errorToString(httpCode).c_str());
  }
  http.end();
}

void SendEndpointA()
{
  HTTPClient http;
  http.begin(ApiHost + "/API/EndpointA");
  int httpCode = http.GET();
  processResponse(httpCode, http);
}

void SendEndpointB()
{
  HTTPClient http;
  http.begin(ApiHost + "/API/EndpointB");
  int httpCode = http.GET();
  processResponse(httpCode, http);
}

En este caso no setup solo es necesario que inicializamos el WiFi, ya que no hay un servidor que inicializar,

void setup() {
  Serial.begin(115200);
  WIFI_Connect();
}
bool isOn = false;
void Update()
{
  if (M5.BtnA.wasPressed())
  {
    if (isOn == false)
    {
      isOn = true;
      Serial.println("A");
      SendEndpointB();
    }
    else
    {
      isOn = false;
      Serial.println("B");
      SendEndpointA();
    }
  }
}

void loop()
{
  M5.update();

  delay(200);

  Update();
}

Código NET6

Por parte del backend en .NET vamos levantar un servidor que escuche peticiones Http. Existen varias formas de hacerlo, pero una de las más sencillas es que os voy a dar un fichero donde hemos definido un WebServer sencillo. Podéis usar este archivo en nuestros programas siempre que necesitéis escuchar peticiones Http de forma sencilla.

public class WebServer
{
    private readonly HttpListener _listener = new HttpListener();
    private readonly Func<httplistenerrequest, string=""> _responderMethod;

    public WebServer(Func<httplistenerrequest, string=""> method, params string[] prefixes)          
    {
        foreach (string s in prefixes) _listener.Prefixes.Add(s);

        _responderMethod = method;
        _listener.Start();
    }

    public void Run()
    {
        ThreadPool.QueueUserWorkItem((o) =>
        {
            while (_listener.IsListening)
            {
                ThreadPool.QueueUserWorkItem((c) =>
                {
                    var ctx = c as HttpListenerContext;
                        string rstr = _responderMethod(ctx.Request);
                        SendResponse(ctx, rstr);
                        ctx.Response.OutputStream.Close();
                }, _listener.GetContext());
            }   
        });
    }

    private static void SendResponse(HttpListenerContext ctx, string rstr)
    {
        ctx.Response.ContentType = "text/html";
        ctx.Response.ContentEncoding = Encoding.Unicode;

        byte[] buf = Encoding.Unicode.GetBytes(rstr);
        ctx.Response.ContentLength64 = buf.Length;
        ctx.Response.OutputStream.Write(buf, 0, buf.Length);
    }

    public void Stop()
    {
        _listener.Stop();
        _listener.Close();
    }
}

Con este fichero hacer nuestro backend .NET para escuchar las peticiones que le va a mandar el ESP32 es muy sencillo. El código, por ejemplo, sería el siguiente,

InitWebServer();

Console.ReadLine();

int port;
string[] prefix;
WebServer webServer;

void InitWebServer()
{
    port = 80;
    prefix = new string[] { $"http://*/API/" };
    webServer = new WebServer(ProcessRequest, prefix);
    webServer.Run();
}

string ProcessRequest(HttpListenerRequest request)
{
    var endpoint = request.Url.LocalPath;

    if (endpoint == "/API/EndpointA")
    {
        Console.WriteLine("EndpointA");
        return "";
    }

    if (endpoint == "/API/EndpointB")
    {
        Console.WriteLine("EndpointA");
        return "";
    }

    return "Invalid address";
}