Aprendiendo MQTT 4: Construyendo Nuestro Propio Broker Usando c#, dotnet y MQTTnet. mosquitto. Instalación con Docker

Mqtt implementando un broker

En esta cuarta entrega de nuestra serie sobre MQTT, pondremos el foco en uno de los componentes más cruciales de cualquier sistema de mensajería en tiempo real: el broker. Imagina un escenario de domótica en el que múltiples dispositivos necesitan comunicarse entre sí de manera eficiente. En este contexto, el broker actúa como el cerebro operativo, encargado de distribuir mensajes desde los publicadores—en nuestro caso, sensores—hacia los suscriptores, como podría ser un display que muestra datos en tiempo real. Acompáñame mientras exploramos cómo construir nuestro propio broker y lo llevamos al siguiente nivel mediante su contenerización con Docker y la integración con Mosquitto.

Repositorio del proyecto: https://github.com/elrincondeada/mqttdemo

mosquitto logo
mosquitto logo
Video sobre este post del rincón de Ada

Mosquitto es un broker ampliamente adoptado en la comunidad de entusiastas del Internet de las Cosas (IoT), especialmente en entornos de código abierto. Se integra de manera sencilla con sistemas domóticos populares como Home Assistant. Para aquellos interesados en probarlo, Docker ofrece una forma sencilla y eficiente de hacerlo. Puedes encontrar instrucciones detalladas para utilizar la imagen oficial de Mosquitto en Docker en su página oficial en Docker Hub. Además, en el repositorio del proyecto, he incluido un ejemplo práctico utilizando Docker Compose. Al ejecutar este ejemplo, podrás observar cómo los sensores se comunican en tiempo real con el display. Muy parecido a como haciamos con la imagen de EMQX.

Implementando nuestro broker con c# y MQTTnet

¡Pero no nos quedaremos ahí! Este blog también tiene un enfoque en programación, y en esa línea, vamos a elevar el listón. Crearemos nuestro propio broker MQTT desde cero, y lo mejor de todo es que sólo necesitaremos unas pocas líneas de código. Gracias a la programación basada en eventos, podremos capturar todas las actividades esenciales que suceden en el broker, desde nuevas suscripciones y publicaciones en diferentes tópicos, hasta el envío de mensajes en tiempo real. Para lograr esto, utilizaremos la potente y flexible librería MQTTnet, que nos permite crear tanto clientes MQTT—ya sean suscriptores o publicadores—como servidores o brokers. También construiremos una imagen de Docker para nuestro broker, permitiéndonos desplegarlo en cualquier contenedor. Esto es especialmente útil en entornos de domótica, donde es común tener un servidor dedicado al control de dispositivos y servicios. Utilizar contenedores Docker para estos servicios es una estrategia inteligente; no sólo para un broker MQTT, sino también para otros servicios como Home Assistant o Node-RED. Personalmente, para estos propósitos, utilizo una Raspberry Pi.

Para que nuestro broker funcione como servicio crearemos una aplicación Host en dotnet. Registraremos un broker con el servidor MQTTnet. Simplemente tenemos dos archivos:

Program.cs

Este archivo es el punto de entrada para tu aplicación de servidor MQTT en .NET. Utiliza el modelo de hospedaje genérico de .NET para configurar y ejecutar servicios en segundo plano.

namespace DotNetMqtt.Server;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;

internal class Program
{
    private static async Task Main(string[] args)
    {
        var hostBuilder = Host.CreateDefaultBuilder(args);
        hostBuilder.ConfigureServices((hostContext, services) =>
        {
            services.AddHostedService<MqttServerWorker>();
        });
        await hostBuilder.RunConsoleAsync();
    }
}

Explicación

  • Host.CreateDefaultBuilder(args): Este método configura un nuevo host con algunas configuraciones predeterminadas.
  • ConfigureServices: Aquí es donde registras tus servicios. En este caso, estás añadiendo MqttServerWorker como un servicio alojado.
  • RunConsoleAsync(): Este método inicia el host y lo ejecuta hasta que se cierra.

MqttServerWorker.cs

Este archivo contiene la lógica principal para tu servidor MQTT. Implementa la interfaz IHostedService, lo que significa que se iniciará y detendrá automáticamente con el host de la aplicación.

  • StartAsync: Este método se llama cuando el servicio se inicia. Aquí es donde configuras y arrancas tu servidor MQTT.
    • MqttFactory: Crea una nueva instancia de un servidor MQTT.
    • MqttServerOptionsBuilder: Configura las opciones para el servidor MQTT.
    • StartAsync(): Inicia el servidor MQTT.
  • StopAsync: Este método se llama cuando el servicio se detiene. Aquí es donde detienes tu servidor MQTT.
  • Eventos como ClientConnectedAsync, InterceptingSubscriptionAsync, etc.: Estos son manejadores de eventos que se disparan cuando ocurren ciertas acciones en el servidor MQTT. Utilizas estos para registrar información útil.
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MQTTnet;
using MQTTnet.Server;

namespace DotNetMqtt.Server;

public class MqttServerWorker : IHostedService, IDisposable
{
    private readonly ILogger<MqttServerWorker> _logger;
    private MqttServer? _mqttServer;

    public MqttServerWorker(ILogger<MqttServerWorker> logger)
    {
        _logger = logger;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {

        _logger.LogInformation("Iniciando servidor MQTT");
        var mqttFactory = new MqttFactory();
        var optionsBuilder = new MqttServerOptionsBuilder()
                             .WithDefaultEndpoint();

        _mqttServer = mqttFactory.CreateMqttServer(optionsBuilder.Build());

        _mqttServer.ClientConnectedAsync += ClienteConectado;
        _mqttServer.ClientConnectedAsync += ClienteDesconectado;
        _mqttServer.InterceptingSubscriptionAsync += InterceptandoSubscripcion;
        _mqttServer.InterceptingUnsubscriptionAsync += InterceptandoDesuscripcion;
        _mqttServer.InterceptingPublishAsync += InterceptandoPublicacion;
        _mqttServer.InterceptingClientEnqueueAsync += InterceptandoColaCliente;
        await _mqttServer.StartAsync();
        _logger.LogInformation("Servidor MQTT iniciado");

    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        if(_mqttServer != null)
        {
            _logger.LogInformation("Deteniendo servidor MQTT");
            await _mqttServer.StopAsync();
            _logger.LogInformation("Servidor MQTT detenido");
        }
    }


    private Task InterceptandoColaCliente(InterceptingClientApplicationMessageEnqueueEventArgs args)
    {
        _logger.LogInformation($"Cliente publicador {args.SenderClientId} suscriptor {args.ReceiverClientId} encola mensaje {args.ApplicationMessage.Topic} - {args.ApplicationMessage.ConvertPayloadToString()}");
        return Task.CompletedTask;
    }

    private Task InterceptandoPublicacion(InterceptingPublishEventArgs args)
    {
        _logger.LogInformation($"Cliente {args.ClientId} publicó en {args.ApplicationMessage.Topic} - {args.ApplicationMessage.ConvertPayloadToString()}");
        return Task.CompletedTask;
    }


    private Task InterceptandoDesuscripcion(InterceptingUnsubscriptionEventArgs args)
    {
       _logger.LogInformation($"Cliente {args.ClientId} se ha desuscrito a {args.Topic}");
        return Task.CompletedTask;
    }


    private Task InterceptandoSubscripcion(InterceptingSubscriptionEventArgs args)
    {
        _logger.LogInformation($"Cliente {args.ClientId} se ha suscrito a {args.TopicFilter.Topic}");
        return Task.CompletedTask;
    }


    private Task ClienteDesconectado(ClientConnectedEventArgs args)
    {
        _logger.LogInformation($"Cliente Desconectado {args.ClientId} {args.Endpoint}");
        return Task.CompletedTask;
    }


    private  Task ClienteConectado(ClientConnectedEventArgs args)
    {
        _logger.LogInformation($"Cliente conectado {args.ClientId} {args.Endpoint}");
        return Task.CompletedTask;
    }


    public void Dispose()
    {
        if(_mqttServer != null)
        {
            _mqttServer.ClientConnectedAsync -= ClienteConectado;
            _mqttServer.ClientConnectedAsync -= ClienteDesconectado;
            _mqttServer.InterceptingSubscriptionAsync -= InterceptandoSubscripcion;
            _mqttServer.InterceptingUnsubscriptionAsync -= InterceptandoDesuscripcion;
            _mqttServer.InterceptingPublishAsync -= InterceptandoPublicacion;
            _mqttServer.InterceptingClientEnqueueAsync -= InterceptandoColaCliente;
            _mqttServer.Dispose();
        }
    }
}

Interesante ver estamos usando dos patrones de desarrollo el builder y la factory para construir el Host y el servidor.

Docker del servidor

Para llevernos nuestro servidor a nuestro contenedor tenemos que crear la imagen primero. Con este Dockerfile lo realizamos:

# Utilizar la imagen base de .NET
FROM mcr.microsoft.com/dotnet/aspnet:latest AS base
WORKDIR /app
EXPOSE 1883

# Compilar la aplicación
FROM mcr.microsoft.com/dotnet/sdk:latest AS build
WORKDIR /src
COPY ["DotNetMqtt.Server.csproj", "/src"]
RUN dotnet restore "DotNetMqtt.Server.csproj"
COPY . .
WORKDIR "/src"
RUN dotnet build "DotNetMqtt.Server.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "DotNetMqtt.Server.csproj" -c Release -o /app/publish

# Copiar y ejecutar la aplicación
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DotNetMqtt.Server.dll"]

En detalle esto es lo que hace

  • FROM mcr.microsoft.com/dotnet/aspnet:latest AS base: Aquí se está utilizando la última imagen de ASP.NET Core disponible como imagen base y se le da el alias «base».
  • WORKDIR /app: Establece el directorio de trabajo en el contenedor a /app.
  • EXPOSE 1883: Informa a Docker que el contenedor escuchará en el puerto 1883, que es el puerto estándar para MQTT.
  • FROM mcr.microsoft.com/dotnet/sdk:latest AS build: Utiliza la última imagen del SDK de .NET y le da el alias «build».
  • WORKDIR /src: Establece el directorio de trabajo en el contenedor a /src.
  • COPY ["DotNetMqtt.Server.csproj", "/src"]: Copia el archivo de proyecto .csproj al contenedor.
  • RUN dotnet restore "DotNetMqtt.Server.csproj": Restaura las dependencias del proyecto.
  • COPY . .: Copia todos los archivos del directorio actual al contenedor.
  • RUN dotnet build "DotNetMqtt.Server.csproj" -c Release -o /app/build: Compila el proyecto en modo «Release» y coloca los archivos compilados en /app/build.
  • Publicación de la Aplicación:
  • FROM build AS publish: Utiliza la imagen «build» como base y le da el alias «publish».
  • RUN dotnet publish "DotNetMqtt.Server.csproj" -c Release -o /app/publish: Publica la aplicación en modo «Release» y coloca los archivos en /app/publish.
  • FROM base AS final: Utiliza la imagen «base» como la imagen final.
  • WORKDIR /app: Establece el directorio de trabajo en el contenedor a /app.
  • COPY --from=publish /app/publish .: Copia los archivos publicados desde la imagen «publish» al directorio de trabajo actual en la imagen.
  • ENTRYPOINT ["dotnet", "DotNetMqtt.Server.dll"]: Establece el punto de entrada de la aplicación, indicando que se debe ejecutar DotNetMqtt.Server.dll usando dotnet.

En resumen dos etapas: una de copia del código, construcción binarios con el sdk y otra de publicación, en la imagen final que es una imagén básica con solo en runtime de dotnet

Finalmente:

docker build -t tu_usuario_dockerhub/nombre_de_la_imagen:etiqueta .

¿Y después que viene?

En las próximas entregas vamos a elevar nuestro desarrollo al siguiente nivel llevándolo directamente a la nube. ¿Te imaginas gestionar tu proyecto en plataformas líderes como Azure y AWS? Pues eso es exactamente lo que haremos. Configuraremos un escenario completo en Azure, y no solo eso: globalizaremos nuestro desarrollo para que puedas integrarlo con Home Assistant, Alexa y mucho más. ¡No querrás perdértelo!

Referencias:

Aprendiendo MQTT 3: El suscriptor, ESP32 con sensor de temperatura y humedad ambiental.

Montaje ESP32 suscriptor MQTT con temperatura y humedad

Continuamos con la segunda entrega de nuestro montaje, en el que estamos aprendiendo a usar el protocolo de comunicaciones MQTT. Nos centraremos ahora en el suscriptor, que será el elemento encargado de recibir los mensajes de telemetría. Este suscriptor puede ser una pantalla que muestre datos (como en este montaje) o, también, un dispositivo que actúe de una manera u otra según los datos recibidos. Un ejemplo podría ser una electroválvula para riego que se abre o cierra cuando una sonda detecta un cierto nivel de humedad en el terreno.

¡Os animo a que os pongáis manos a la obra y hagáis el montaje es muy gratificante y os dejo todo lo necesario para hacerlo!

El suscriptor MQTT, desde el punto de vista de la programación, es de naturaleza asíncrona. ¿Qué quiero decir con esto? Pues que podrá realizar acciones como cambiar el texto del display, accionar un relé o guardar valores en una base de datos cuando reciba un mensaje, y esto podría ocurrir en cualquier momento. La forma más común y elegante de hacer esto es mediante una función de ‘callback’. Dependiendo del lenguaje de programación con el que estemos trabajando, a esta función también se le podría llamar ‘evento’, ‘acción’, entre otros términos, pero en esencia es un parámetro, propiedad o variable que aloja un método que se invoca cuando sucede algo específico, desencadenando así la ejecución de un bloque de código.

El montaje del display de Temperatura y Humedad.

Para este montaje usaremos un ESP32 que lo vamos a programar con ayuda de platformio en c++ con las librerías:

  • WiFi.h: para la conexión WiFi.
  • PubSubClient.h: para la conexión y operaciones MQTT.
  • Wire.h: para comunicación I2C.
  • LiquidCrystal_I2C.h: para manejar un LCD que utiliza I2C.

El montaje, a nivel de esquema eléctrico, consta de un display LCD de dos líneas y 16 caracteres cada una (16×2). Este se puede conectar de dos maneras: la primera, enviando datos de forma paralela, y la segunda, utilizando un adaptador I2C de serie a paralelo. Con el adaptador I2C, solo necesitamos dos cables para la comunicación con el ESP32, además de otros dos cables para la alimentación eléctrica, uno positivo y otro negativo.

Montaje ESP32 suscriptor MQTT con temperatura y humedad
Montaje ESP32 suscriptor MQTT con temperatura y humedad

Para el ESP32 con el que estoy trabajado tiene la señal i2c de reloj en el pin GPIO 22 y la señal de datos en el pin GPIO 21 (aunque en realidad no están funcionando aquí como GPIO que es una salida de propósito general sino como puerto I2C). Por otro lado el pin morado es está conectado a 3V3 (3,3 Voltios) positivo y el blanco a GND o negativo.

ESP32 Pinout
ESP32 descripción de los pines

El código con el framework de arduino:

Ahora pasamos a describir el código fuente que lo podéis encontrar en https://github.com/elrincondeada/mqttdemo/blob/master/LCDMqttClient/src/main.cpp

Lo primero que hacemos es importar las bibliotecas:

#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

Aquí se importan las bibliotecas necesarias para la conexión WiFi, el protocolo MQTT, la comunicación I2C y el manejo del LCD.

Definición de constantes y variables globales

#define TOPIC_TEMPERATURA "esp32/temperatura"
#define TOPIC_HUMEDAD "esp32/humedad"

Esto se hace para evitar errores al escribir un elemento de código que es constante y se va a usar varias veces. Es este caso el tópico así sabemos que siempre lo vamos a escribir igual y evitamos errores.

// Información de WiFi y MQTT
const char* ssid = "XXXXXXXXXXXXXXXXXXX";
const char* password = "XXXXXXXXXXXXXXX";
const char* mqttServer = "XXXXXXXXXXXXX";
const int mqttPort = 1883;

//Protipado de funciones
void callback(char*, byte*, unsigned int);

// Inicializa el cliente WiFi y MQTT
WiFiClient espClient;
PubSubClient client(espClient);

// Inicializa la LCD con dirección I2C, columnas y filas
LiquidCrystal_I2C lcd(0x27, 16, 2);

Empezamos con la declaración de variables globales, donde encontrarás ‘char*’. No es un simple arreglo de caracteres, es un puntero en C. Si te interesa profundizar en los fundamentos de C y qué es un puntero, te recomiendo este vídeo https://youtu.be/AUAX9xNenuo

Luego tenemos algo llamado ‘prototipado de función’. Se trata simplemente de un adelanto de la función que jugará un rol principal en este código, la función ‘callback’.

Finalmente, instanciamos el cliente WiFi para establecer la conexión con el exterior, el cliente MQTT para la comunicación de mensajes y el controlador del display LCD 16×2, todo ello gestionado mediante el protocolo I2C.

la función setup()

void setup() {
  // Inicializa el LCD
  lcd.init();
  lcd.backlight();
  
  // Conexión WiFi
  Serial.begin(115200);
  WiFi.begin(ssid, password);

  // Conexión WiFi
   while (true) { // Bucle infinito
    if (WiFi.status() == WL_CONNECTED) {
      Serial.println("Conectado a WiFi!");
      break; // Sale del bucle cuando se conecta
    }
    Serial.print(".");
    delay(200);
   }

  
  // Conexión MQTT
  client.setServer(mqttServer, mqttPort);
  client.setCallback(callback);

  while (!client.connected()) {
    Serial.println("Conectando a MQTT...");

    if (client.connect("ESP32")) {
      Serial.println("Conectado");
    } else {
      Serial.print("Error de conexión: ");
      Serial.print(client.state());
      delay(2000);
    }
  }

  Serial.println("Suscripción a tópicos");
  // Se suscribe al tópico
  client.subscribe(TOPIC_TEMPERATURA);
  client.subscribe(TOPIC_HUMEDAD);
}

«La función void setup() es donde toda la magia inicial tiene lugar. Esta función se ejecuta una sola vez al inicio y configura todo lo necesario para que nuestro código funcione de manera óptima.

  1. Inicialización del LCD: Con las instrucciones lcd.init() y lcd.backlight(), inicializamos el display LCD y encendemos su retroiluminación para hacerlo visible.
  2. Conexión WiFi: Primero, iniciamos la comunicación serie con Serial.begin(115200). Luego, la instrucción WiFi.begin(ssid, password) intenta conectar el ESP32 a la red WiFi utilizando las credenciales almacenadas en las variables ssid y password.
  3. Verificación de Conexión WiFi: Aquí entra en juego un bucle infinito (while(true)) que comprueba constantemente si se ha establecido la conexión WiFi. Si la conexión es exitosa (WiFi.status() == WL_CONNECTED), se imprime un mensaje y se rompe el bucle con break.
  4. Conexión MQTT: Las instrucciones client.setServer(mqttServer, mqttPort) y client.setCallback(callback) configuran el servidor MQTT y la función de callback que se llamará cuando lleguen mensajes al tópico suscrito. Es el corazón de este desarrollo.
  5. Verificación de Conexión MQTT: Similar al WiFi, hay un bucle while que intenta conectar con el servidor MQTT hasta que lo logra. Si hay un error, imprime el estado del intento fallido.
  6. Suscripción a Tópicos: Finalmente, client.subscribe(TOPIC_TEMPERATURA) y client.subscribe(TOPIC_HUMEDAD) suscriben el cliente MQTT a los tópicos de temperatura y humedad, respectivamente.»

La función de callback:

void callback(char* topic, byte* payload, unsigned int length) {
  String payloadStr = "";

  Serial.println("mensaje recibido");

  // Convierte los bytes del payload a String
  for (int i = 0; i < length; i++) {
    payloadStr += (char)payload[i];
  }

  Serial.println(payloadStr);

  // Actualiza el LCD con el mensaje recibido
  if(strstr(topic,TOPIC_TEMPERATURA))
  {
    lcd.setCursor(0, 0);
    lcd.print("Temp. ");
  }

  if(strstr(topic,TOPIC_HUMEDAD)) 
  {
    lcd.setCursor(0, 1);
    lcd.print("Humd. ");
  }

  lcd.print(payloadStr);
}

La función callback es el núcleo de cualquier suscriptor MQTT, y aquí es donde reside toda la lógica que determina qué hacer cuando llega un mensaje a los tópicos a los que se ha suscrito el cliente. Vamos a desglosarla:

  1. Parámetros: La función acepta tres parámetros: char* topic, byte* payload, y unsigned int length. El topic indica el tópico al cual pertenece el mensaje recibido, payload es el cuerpo del mensaje en forma de bytes y length es la longitud del payload.
  2. Inicialización: String payloadStr = ""; inicializa una cadena de texto vacía donde almacenaremos el payload convertido.
  3. Mensaje Recibido: Serial.println("mensaje recibido"); es una forma de debugging que nos indica que ha llegado un nuevo mensaje.
  4. Conversión de Bytes a String: El bucle for toma cada byte del payload y lo convierte a su representación de carácter, acumulando todo en payloadStr.
  5. Impresión del Payload: Serial.println(payloadStr); imprime el payload recibido para verificación.
  6. Actualización de la LCD: Aquí es donde el programa se pone interesante. Si el tópico corresponde a la temperatura (strstr(topic, TOPIC_TEMPERATURA)), el cursor de la LCD se posiciona en la primera fila y escribe «Temp.». De forma similar, si el tópico es de humedad, escribe «Humd.» en la segunda fila.
  7. Imprimir Valor: Finalmente, lcd.print(payloadStr); imprime el valor del payload en la LCD, justo después de «Temp.» o «Humd.», dependiendo del tópico al que pertenece el mensaje.

función loop

void loop() {
  client.loop();
}

Esta línea se encarga de mantener el cliente MQTT en funcionamiento y es crucial para el correcto funcionamiento de todo el programa. ¿Por qué? Porque client.loop() se encarga de procesar cualquier mensaje entrante y saliente, y de mantener la conexión con el servidor MQTT viva. Si no se ejecutara esta función de forma continua en el bucle principal, no se recibirían ni procesarían mensajes MQTT, y el cliente podría incluso desconectarse del servidor.

Por lo tanto, aunque pueda parecer una parte trivial del código, no subestimes su importancia. Actúa como el corazón palpitante de tu cliente MQTT, asegurándose de que todo funcione como un reloj.

Conclusión:

Y ahí lo tienes, la segunda entrega de nuestra serie sobre MQTT, donde nos hemos centrado en el rol del suscriptor. No es magia, es tecnología bien aplicada. Hemos desentrañado el código que corre en un ESP32 y que le permite actuar como un suscriptor MQTT. Desde establecer una conexión Wi-Fi hasta interpretar los mensajes MQTT que le llegan, este pequeño dispositivo hace de todo.

Nuestra función callback es el cerebro del operativo: espera en silencio hasta que llega un mensaje relevante y luego se pone en acción para mostrarlo en nuestro LCD. Y no olvidemos nuestro persistente void loop(), el pulso del programa que asegura que el cliente MQTT está siempre listo para actuar.

Si te ha picado el gusanillo de la programación y quieres ir más allá, hay un mundo de posibilidades que puedes explorar. ¿Qué tal integrar una base de datos para guardar los datos recibidos? ¿O quizás quieras controlar otros dispositivos en función de los mensajes recibidos?

Espero que este tutorial te haya proporcionado una buena base para entender cómo funciona un suscriptor MQTT y cómo puedes empezar a jugar con estas tecnologías. Estamos en una era donde los objetos cotidianos se están volviendo cada vez más inteligentes y conectados, y entender estos conceptos básicos te coloca un paso adelante en este emocionante mundo del Internet de las Cosas.

Te animo a que realices tu mismo el montaje. Puedes cambiar el sensor, el display, conectarlo a un asistente domótico, a alexa… Seguiremos experimentando.

¡Hasta la próxima entrega!

Referencias:

Aprendiendo a programar: Estructuras de Control Básicas en C#

Programar con estructuras de control en c#

Introducción

Para los que quieran aprender programación voy a hacer una serie de posts con fundamentos de programación. He elegido el lenguaje c# porque es usado por el framework dotnet que es abierto y multiplataforma. Con este lenguaje, de proposito general, podemos desarrollar en múltiples ambitos. Aquí tienes algunos ejemplos de usos:

  1. Desarrollo Web: Utilizado extensamente en el desarrollo de aplicaciones web a través de ASP.NET, framework que facilita la creación de sitios web dinámicos y robustos.
  2. Aplicaciones de Escritorio: C# es ampliamente utilizado para desarrollar aplicaciones de Windows con interfaces gráficas de usuario mediante Windows Forms y WPF (Windows Presentation Foundation).
  3. Desarrollo de Juegos: Gracias a su integración con el motor de juegos Unity, C# se ha convertido en un lenguaje clave en el desarrollo de videojuegos para diferentes plataformas.
  4. Aplicaciones Móviles: Con Xamarin, los desarrolladores pueden utilizar C# para crear aplicaciones nativas para Android e iOS.
  5. Desarrollo Empresarial: C# es también popular en la construcción de aplicaciones empresariales, sistemas de bases de datos y aplicaciones cliente-servidor.
  6. Internet de las Cosas (IoT): Su eficiencia y escalabilidad lo hacen adecuado para dispositivos IoT donde los recursos pueden ser limitados.
  7. Inteligencia Artificial y Ciencia de Datos: Aunque no tan popular como Python en estos campos, C# se está utilizando cada vez más en proyectos de IA y análisis de datos gracias a diversas bibliotecas y frameworks.

Por otro lado dotnet es un marco de desarrollo (framework) de software gratuito y de código abierto para Windows, Linux y macOS. Es mantenido por Microsoft y la comunidad de desarrolladores. Inicialmente, fue lanzado en el año 2002 exclusivamente para Windows, pero desde el lanzamiento de .NET Core (ahora .NET 5 y más allá) en 2016, ha expandido su compatibilidad a múltiples sistemas operativos. El proyecto es de código abierto https://github.com/dotnet Estos son sus componentes principales.

  1. Lenguajes de Programación: Incluye varios lenguajes de programación como C#, F# y Visual Basic.
  2. Bibliotecas Estándar: Una extensa biblioteca estándar que ofrece una gran variedad de funciones y componentes listos para usar.
  3. CLR (Common Language Runtime): Es el entorno de ejecución que maneja la ejecución del código y proporciona servicios como la gestión de memoria, hilos, excepciones, etc.
  4. SDK y herramientas de desarrollo: Conjunto de herramientas de desarrollo para facilitar el desarrollo, la depuración y la publicación de aplicaciones.

Comenzando por crear un proyecto.

Comúnmente, C# se ha vinculado con el entorno de desarrollo integrado (IDE) Visual Studio en sistemas operativos Windows. Sin embargo, este panorama ha evolucionado significativamente desde que el proyecto se convirtió en de código abierto. En el video que sigue, se muestra cómo crear un proyecto en C# utilizando Visual Studio Code en un sistema operativo Linux, específicamente Ubuntu. Cabe destacar que estos mismos pasos son aplicables tanto en sistemas Windows como en macOS, haciendo uso de la herramienta dotnet CLI.

¿Por donde empezamos con la programación?

En el ejemplo ‘HolaMundo’, que se genera utilizando la plantilla de aplicación de consola, estamos ejecutando una serie de sentencias, cada una representada por una línea de código. Aunque estas sentencias actúan como órdenes dadas al ordenador, es importante tener en cuenta que el flujo de ejecución puede variar. Esto puede suceder debido a condiciones específicas o a la necesidad de repetir un bloque de código múltiples veces. Para gestionar estos escenarios, se emplean las estructuras de control. C# ofrece varias estructuras de control que nos permiten diseñar algoritmos complejos de una manera más organizada y legible. Este artículo aborda las estructuras de control básicas en C#: if-else, switch-case, for, while y do-while.

Estructuras condicionales

If-else

La estructura if-else es posiblemente la más reconocible de todas. Permite evaluar condiciones y ejecutar bloques de código en función de si la condición es true o false.

int numero = 10;

if (numero > 5) {
    Console.WriteLine("El número es mayor que cinco.");
} else {
    Console.WriteLine("El número no es mayor que cinco.");
}

switch-Case

es otra estructura condicional que facilita la evaluación de múltiples condiciones. Es especialmente útil cuando una variable puede tener varios valores y se debe realizar una acción diferente para cada uno.

char grado = 'A';

switch (grado) {
    case 'A':
        Console.WriteLine("Excelente");
        break;
    case 'B':
        Console.WriteLine("Muy bien");
        break;
    default:
        Console.WriteLine("Calificación no reconocida");
        break;
}

Estructuras de Bucle o Repetición.

for

El bucle for es útil cuando sabemos cuántas veces queremos que se ejecute un bloque de código.

for (int i = 0; i < 5; i++) {
    Console.WriteLine("Iteración " + i);
}

while

La estructura while ejecuta un bloque de código mientras una condición específica sea verdadera (true).

int contador = 0;

while (contador < 5) {
    Console.WriteLine("Contador: " + contador);
    contador++;
}

do-while

Similar al while, pero la condición se evalúa después de ejecutar el bloque de código al menos una vez.

int valor = 0;

do {
    Console.WriteLine("Valor: " + valor);
    valor++;
} while (valor < 5);

Conclusión

Las estructuras de control son basicas para cualquier programador y conocerlas en programador. Son los peldaños básicos para construir algoritmos. Con estas estructuras de control seguiremos trabajando en posteriores videos y entradas en este blog

¡Gracias!

Referencias

Aprendiendo MQTT: El Protocolo, Aplicaciones y un Ejemplo Práctico

Mqtt elementos y comunicación entre ellos

Como punto de inicio para este blog, me he propuesto ofrecer un enfoque práctico del protocolo de comunicaciones MQTT, una herramienta esencial en el mundo del Internet de las Cosas (IoT). Para comenzar, presentaré tres ejemplos concretos que ilustran los componentes clave del protocolo: el publicador, el suscriptor y el intermediario o ‘broker’. A continuación, encontrarás un video resumen que anticipa lo que abordaremos en detalle. Tras el video, profundizaremos en algunos conceptos teóricos para entender mejor la tecnología que estamos manejando.

¿Qué es MQTT?

El Protocolo de Cola de Mensajes Telemétricos (MQTT, por sus siglas en inglés de Message Queuing Telemetry Transport) es un protocolo de comunicación diseñado para entornos de bajo ancho de banda, alta latencia o redes inestables. Originalmente desarrollado por IBM en 1999, el MQTT ha ganado relevancia en la era del Internet de las Cosas (IoT) debido a su diseño ligero y eficiente. En este post, exploraremos los fundamentos de MQTT y veremos ejemplos de cómo se aplica en escenarios del mundo real. MQTT es un protocolo de mensajería basado en el modelo de publicación/suscripción. En este modelo, hay tres componentes principales:

  1. Publicador: Envía mensajes a un tema específico.
  2. Suscriptor: Escucha mensajes en un tema específico.
  3. Broker: Sirve como intermediario, recibiendo mensajes de los publicadores y enviándolos a los suscriptores.

A diferencia de otros protocolos de mensajería como HTTP, MQTT es más ligero, consume menos recursos y tiene la capacidad de mantener la conexión activa durante más tiempo, lo que lo hace ideal para dispositivos IoT.

Características Principales

  • QoS (Calidad del Servicio): MQTT soporta diferentes niveles de calidad de servicio para el envío de mensajes.
  • Retención de Mensajes: El broker puede retener el último mensaje en un tema para que los nuevos suscriptores puedan recibirlo inmediatamente después de suscribirse.
  • Last Will and Testament (Testamento): Un mensaje que se envía cuando el cliente se desconecta inesperadamente.
  • Seguridad: Aunque MQTT no define medidas de seguridad propias, se puede implementar mediante TLS/SSL.

Es importante mencionar que MQTT ofrece diferentes niveles de «Calidad de Servicio» (QoS) para el envío de mensajes:

  • QoS 0: El mensaje se entrega en el mejor esfuerzo posible, pero no hay confirmación de entrega. Puede haber pérdida de mensajes.
  • QoS 1: El mensaje se entrega al menos una vez, y se confirma con un mensaje PUBACK. Esto asegura la entrega, pero puede haber duplicados.
  • QoS 2: El mensaje se entrega exactamente una vez al receptor. Este es el nivel más alto de QoS y utiliza un intercambio de cuatro pasos para asegurarse de que no hay pérdidas ni duplicados.

Según el nivel de QoS, el broker y el cliente pueden intercambiar mensajes adicionales para asegurar la entrega, pero una vez que la entrega se ha confirmado, el mensaje ya no se guarda en el broker (a menos, como dije antes, que se utilice la bandera «Retain»).

Entonces sí, en general, los mensajes se entregan a los clientes suscritos y luego se «borran» o no se retienen en el broker, a menos que se especifique lo contrario.

Tópicos (Topics) en MQTT

Los tópicos son una de las características más fundamentales en MQTT. Actúan como canales de mensajería a los que los clientes pueden suscribirse o en los cuales pueden publicar mensajes. Cada tópico es simplemente una cadena de texto que suele estar estructurada en niveles, separados por el carácter ‘/’. Por ejemplo, casa/sala/temperatura podría ser un tópico utilizado para publicar la temperatura en la sala de una casa. Cuando un cliente se suscribe a un tópico, recibe todos los mensajes que se publican en ese tópico por parte de cualquier publicador (publisher). De manera similar, cualquier mensaje que un cliente publique en un tópico se enviará a todos los clientes suscritos a ese tópico.

Comodines (Wildcards) en MQTT

Los comodines son caracteres especiales que permiten a los clientes suscribirse a múltiples tópicos simultáneamente. MQTT soporta dos tipos de comodines:

  1. Signo más (+): Este comodín puede sustituir a un solo nivel en un tópico. Por ejemplo, si te suscribes al tópico casa/+/temperatura, recibirías mensajes de casa/sala/temperatura, casa/cocina/temperatura, etc.
  2. Almohadilla (#): Este comodín puede sustituir a múltiples niveles en un tópico. Debe ser utilizado solo al final de un tópico. Por ejemplo, casa/# te suscribiría a todos los tópicos que comiencen con casa, incluyendo casa/sala/temperatura, casa/sala/humedad, casa/cocina/luz, etc.

También es posible utilizar ambos comodines en una sola suscripción. Por ejemplo, casa/+/+/estado podría recibir mensajes de casa/sala/luz/estado, casa/cocina/ventilador/estado, etc.

Los tres elementos en la práctica.

En los siguientes vídeos y posts iremos viendo en detalle los tres elmentos que componen el protocolo. El publicador será un sensor de temperatura y humedad AHT20 gobernado por una placa ESP32 S3. La naturaleza del envío de mensajes va a ser síncrona y se realizará cada cierto intervalo de tiempo. Este es un ejemplo que estaría dentro del loop del framework de arduino.

 long now = millis();
  static long lastMsg = 0;

  if (now - lastMsg > 10000)
  { // Enviar telemetría cada 10 segundos
    lastMsg = now;

    // Aquí, pon el código para leer tu sensor
    float temperatura = e_temp.temperature;
    float humedad = e_humedad.relative_humidity;

    char msgt[50];
    snprintf(msgt, 50, "Valor del sensor: %f", temperatura);
    Serial.print("Publicando mensaje: ");
    Serial.println(msgt);

    // Publicar el mensaje
    client.publish("esp32/temperatura", msgt);

    char msgh[50];
    snprintf(msgh, 50, "Valor del sensor: %f", humedad);
    Serial.print("Publicando mensaje: ");
    Serial.println(msgh);

    // Publicar el mensaje
    client.publish("esp32/humedad", msgh);
  }

El broker, de momento, usaremos EMQX (sitio web EMQX) Nos podemos montar un servidor de pruebas con docker fácilmente. Este sería un ejemplo de Dockerfile:

# Usar la imagen oficial de EMQ X
FROM emqx/emqx
# Exponer los puertos MQTT, MQTT/SSL, WebSockets
EXPOSE 1883 8883 8083
# Ejecutar EMQ X
CMD ["emqx", "start"]

Después, construir la imagen y lanzar la instancia

docker build -t my_emqx .
docker run -d --name my_emqx_instance -p 1883:1883 -p 8083:8083 -p 8883:8883 my_emqx

Si lo preferéis, podemos usar directamente:

docker run -d --name emqx -p 1883:1883 -p 8083:8083 -p 8883:8883 emqx/emqx

Estos son los puertos que expone y usa el broker. Los puertos del protocolo son los estándares.

  • 1883: Puerto estándar para MQTT sin cifrado.
  • 8883: Puerto estándar para MQTT con cifrado SSL/TLS.
  • 8083: Puerto para el protocolo MQTT sobre WebSockets sin cifrado.
  • 8084: Puerto para el protocolo MQTT sobre WebSockets con cifrado SSL/TLS (esto es lo que añadiste en tu comando).
  • 18083: Puerto para la interfaz de administración web de EMQ X (esto también es adicional en tu comando).

Por último, vamos a hablar del consumidor o suscriptor. La comunicación o recepción de mensajes aquí es de naturaleza asíncrona. En programación, una función de callback, recibirá los datos del mensaje una vez que nos hayamos suscrito a un tópico. La implementación del consumidor la he hecho en c# con dotnet y la librería MQTTnet https://github.com/dotnet/MQTTnet (imaginaros un servicio en la nube que analiza datos) pero también podría ser un actuador que se implentara en otra placa ESP32.

//Evento que se va a lanzar cada vez que se publique en uno de
//los "topic" a los que estamos suscritos
mqttClient.ApplicationMessageReceivedAsync += e =>
{
	Console.WriteLine($"Recibido mensaje. topic:{e.ApplicationMessage.Topic}");
	Console.WriteLine(Encoding.ASCII.GetString(e.ApplicationMessage.PayloadSegment));
	return Task.CompletedTask;
};

Ejemplos de usos de MQTT y aplicaciones:

Automatización del Hogar

MQTT es ampliamente utilizado en sistemas de automatización del hogar como Home Assistant o Domoticz. Por ejemplo, un sensor de temperatura puede publicar lecturas en un tema, y un termostato puede suscribirse a ese tema para ajustar la calefacción o la refrigeración.

Monitoreo de Salud

En un hospital, los dispositivos médicos como monitores de signos vitales pueden utilizar MQTT para enviar datos en tiempo real a un sistema centralizado que los médicos puedan supervisar.

Vehículos Autónomos

Los vehículos autónomos generan grandes cantidades de datos de sensores que deben procesarse en tiempo real. MQTT puede facilitar la transmisión eficiente de estos datos a una estación central para su análisis y toma de decisiones.

Agricultura Inteligente

Sensores de humedad y temperatura en un campo agrícola pueden usar MQTT para enviar sus datos a un sistema central. Basado en estos datos, el sistema puede ajustar automáticamente el riego y la fertilización.

Logística y Seguimiento

Las empresas de logística pueden utilizar MQTT para monitorear en tiempo real la ubicación y el estado de sus vehículos y mercancías, lo que permite una mejor planificación y eficiencia en la entrega.

Conclusión

MQTT se ha establecido como un protocolo esencial para el Internet de las Cosas y otras aplicaciones donde la eficiencia, la simplicidad y la fiabilidad son cruciales. Desde la automatización del hogar hasta aplicaciones empresariales y sanitarias, las posibilidades son prácticamente infinitas. En los siguientes vídeos y ejemplos iremos desgrando cada uno de los elementos del protocolos y aprenderemos experimentando con el. Os animo a estar pendiente y a divertiros.

Referencias:

El código fuente:

https://github.com/elrincondeada/mqttdemo