Language: EN

esp32-net6-http

How to connect an ESP32 with NET6 via HTTP

Continuing with our series of posts on how to connect an ESP32 device with an application in NET, in this post we will see how to make communication through HTTP calls.

In our previous post, we saw how to connect an ESP32 with an application in NET6, using the serial port. In this post, we will leave the cable behind and switch to being wireless through a Wi-Fi connection.

NET6 can be used on a variety of platforms, such as Windows, Android, Linux, Mac, x86/x64, and ARM7 or higher. This cross-platform capability means that we can run the same program on different devices, such as desktop computers with Windows or Linux, Android mobile devices, or Raspberry Pi, without needing to make changes to the code.

Just like in the previous post, we will use an M5 Stack, but any ESP32 or Arduino with Wi-Fi will work to follow the steps of this tutorial.

We will start with one of the simplest Wi-Fi communications, communication through an HTTP request. We have already seen HTTP communication in the ESP32 section and, as we have already mentioned, there is no one form of communication that is better than another, all have their advantages and disadvantages.

In the case of HTTP communication, we are facing a client-server type of communication, in which a server offers certain services to which one or more clients connect. The communication is not bidirectional, since the client initiates the communication, the server cannot initiate communication with the client.

Communication through HTTP requests is useful, for example, when we have a large number of clients connecting to a single server. However, the communication is not , and the server is easily locatable, understanding that it is easy to obtain its IP (directly or indirectly).

We will differentiate the case in which the ESP32 acts as a server and the computer with NET acts as a server

ESP32 as a server, NET6 client

ESP32 code

In this first case, the ESP32 acts as a server. So we need to set up a server, something we already know how to do perfectly. As always, we divide the code into files, grouping features. So we have the server.hpp file with all the relevant functionalities


#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 works");
      Serial.println("A");
   });

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

On the other hand, in the setup function we simply initialize the WiFi and the server,

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

  ConnectWiFi_STA();
 
  InitServer();
}

And finally in the loop we simply call the function that manages the server,

void loop()
{
  M5.update();

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

NET6 code

Regarding the .NET part, we need to make an Http request against the ESP32. There are several ways to make an Http request from C#, but one of the simplest is the following,

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 as a server, ESP32 client.

ESP32 code

In this case, the ESP32 has to make a request to the .NET backend. So we simply have to use an http client we have created an API.h file that encompasses these functions.

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);
}

In this case, setup is only necessary to initialize the WiFi, as there is no server to initialize,

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();
}

NET6 code

From the .NET backend, we will set up a server that listens to Http requests. There are several ways to do this, but one of the simplest is to provide a file where we have defined a simple WebServer. You can use this file in our programs whenever you need to listen to Http requests in a simple way.

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();
    }
}

With this file, we can easily set up our .NET backend to listen to the requests that the ESP32 is going to send. The code, for example, would be the following,

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";
}