Ubidots ha desarrollado bibliotecas para algunas de las placas de desarrollo más populares en el mercado, pero con los creadores diseñando nuevo hardware propietario a diario, queremos proporcionar los pasos básicos para desarrollar un script tú mismo para enviar datos correctamente utilizando MQTT. Recuerda que MQTT es un protocolo ligero muy popular para dispositivos IoT y se recomienda encarecidamente para fines de control (haz clic aquí para más detalles sobre MQTT).
Primero, se recomienda que leas sobre nuestra API de MQTT y entiendas los conceptos básicos. A continuación, se presentan algunas definiciones estándar necesarias para integrar tus dispositivos con nuestra plataforma de habilitación de aplicaciones:
Cliente: Un cliente es el dispositivo que envía datos a Ubidots para actualizar o crear variables. Los clientes también pueden recuperar datos.
Broker: Los brokers son los contrapartes de un cliente. Los brokers procesan todos los datos y tienen la responsabilidad principal de recibir mensajes, actualizar variables y notificar a los clientes sobre cambios en la plataforma y cualquier sesión en espera. (La URL del broker de Ubidots es http://industrial.api.ubidots.com con el puerto de comunicación 1883.)
Publicar: Publicar es similar a POST en HTTP, cuando un cliente publica un mensaje, el broker actualizará o creará un nuevo dispositivo dependiendo del código de comando.
Suscribirse: Similar a la función GET en HTTP, suscribirse es el método para obtener valores con una gran diferencia respecto a la solicitud GET. La diferencia es que no tienes que estar pidiendo continuamente al servidor cada valor en tu script personalizado. Por ejemplo, si el valor de una variable cambia, Ubidots te actualizará automáticamente (a ti, el usuario) sobre cualquier cambio. Así se ahorran solicitudes de datos y tiempo de procesamiento para tu dispositivo y la funcionalidad y gastos generales del proyecto.
Ahora que sabes más sobre los conceptos básicos de MQTT, comencemos a configurar tu Arduino IDE. Necesitamos instalar la biblioteca PubSubClient, que es una de las bibliotecas MQTT más populares en el entorno de Arduino. Si deseas saber más sobre la biblioteca, consulta su repositorio de github y su sitio de documentación de API.
Para instalar la biblioteca, sigue estos pasos:
Primero, abre el Arduino IDE y ve al administrador de bibliotecas del Arduino IDE SKETCH > INCLUDE LIBRARIES > MANAGE LIBRARIES
2. Busca e instala la biblioteca "PubSubClient" :
Nota: asegúrate de instalar la última versión de esta biblioteca.
REQUISITOS ADICIONALES: Como este tutorial está destinado a dispositivos basados en ESP8266 y utilizaremos el Arduino IDE para codificar, también deberías haber instalado la biblioteca para los chips ESP8266. Consulta este sitio web para saber cómo instalarla si aún no la has agregado a tu IDE.
CODIFICACIÓN:
Una vez que hayas instalado la biblioteca, comencemos a codificar el script para enviar datos correctamente. Para este ejemplo, utilizaremos un NodeMCU para enviar datos y usaremos este script para enviar datos.
Nota: Para esta explicación, hemos truncado el código en 9 secciones separadas para ayudarte a entender el código completo. El código completo aparecerá en tu IDE como una sola entrada una vez completado y se puede encontrar aquí.
/**************************************** * Incluir Bibliotecas ****************************************/#include <PubSubClient.h>#include <ESP8266WiFi.h>#include <ESP8266WiFiMulti.h>#include <stdio.h>/**************************************** * Definir Constantes ****************************************/#define WIFISSID "...." // Pon tu WifiSSID aquí#define PASSWORD "...." // Pon tu contraseña de wifi aquí#define TOKEN "..." // Pon tu TOKEN de Ubidots#define VARIABLE_LABEL "...." // Asigna la etiqueta de la variable#define DEVICE_LABEL "...." // Asigna la etiqueta del dispositivo#define MQTT_CLIENT_NAME "....." // Nombre del cliente MQTT
Comenzamos incluyendo las bibliotecas necesarias: La PubSubClient operará en segundo plano permitiendo una conexión MQTT. Los envoltorios de ESP8266 para el Arduino IDE y la biblioteca estándar del lenguaje C <stdio.h>. Las líneas a continuación *Definir Constantes" dentro del código anterior son para determinar la ruta correcta para transmitir datos con Ubidots. Por favor, actualiza tu nombre de WiFi, contraseña, ingresa tu token de usuario (lee aquí para obtener tu token personalizado), y las etiquetas de tu dispositivo y variable que se crearán en tu cuenta de ubidots.
La constante final necesaria es el MQTT_CLIENT_NAME, esto es especial porque es el ID con el que tu dispositivo será identificado por el broker, por lo que DEBE ser único. Si tu dispositivo intenta conectarse con el mismo ID que ya ha sido tomado por otro dispositivo, la conexión será rechazada. Por favor, crea tu propio MQTT_CLIENT_NAME alfanumérico de 8-12+ caracteres y colócalo en el código en consecuencia.
Sugerencia: ¿Necesitas ayuda para crear un MQTT_CLIENT_NAME único? Consulta este generador de ascii aleatorio, o simplemente usa la dirección MAC de tu dispositivo, ya que cada dirección MAC es globalmente única.
char mqttBroker[] = "indsutrial.api.ubidots.com";char payload[700];char topic[150];// Espacio para almacenar valores a enviarchar str_box_temp[6];char str_lat[6];char str_lng[6];
Ahora necesitamos declarar arreglos de caracteres adicionales. En el código anterior encontrarás la variable mqttBroker que almacena el broker de Ubidots, el payload reserva espacio de memoria para los datos que se enviarán más tarde en la rutina.
Las características restantes de este código permiten almacenamiento adicional de memoria y transmisión de datos rápida.
/**************************************** * Inicializar constructores para objetos ****************************************/ESP8266WiFiMulti WiFiMulti;WiFiClient ubidots;PubSubClient client(ubidots);/**************************************** * Funciones Auxiliares ****************************************/ void callback(char* topic, byte* payload, unsigned int length) { Serial.print("Mensaje recibido de ["); Serial.print(topic); Serial.print("] "); for (int i=0;i<length;i++) { Serial.print((char)payload[i]); }}
Ahora necesitamos inicializar los constructores necesarios, el primero (ESP8266WiFIMulti WiFiMulti) se utilizará exclusivamente para hacer la conexión al punto de acceso wifi. Luego inicializaremos un wificlient (ubidots) que se pasará como parámetro al constructor de PubSubClient.
A continuación, tenemos que establecer una función de callback. Esta función es muy importante porque maneja los cambios de tus variables en Ubidots y es exclusiva de la biblioteca PubSubClient. A continuación, explicaré los argumentos de esta función:
char* topic: El topic es el endpoint de tu variable, de acuerdo con nuestra API debería ser
/v1.6/devices/{LABEL_DEVICE}
o/v1.6/devices/{LABEL_DEVICE}/{LABEL_VARIABLE}/lv
si estás suscrito para obtener solo valores de tus variables.byte* payload: Es la respuesta obtenida directamente del broker una vez que ha ocurrido un cambio en una de tus variables suscritas.
unsigned int length: La longitud del payload.
Así que, habiendo aclarado los argumentos, veamos qué hará la función. Una vez que tu variable haya cambiado, imprime a través del puerto serie qué topic ha cambiado y concluye imprimiendo la respuesta del broker.
Ten en cuenta que esta función se llama cada vez que se produce un cambio en tus variables suscritas. Así que si necesitas implementar tareas adicionales en tu código, por ejemplo, controlar un relé, necesitarás editar esta función. Consulta este video para obtener soporte adicional sobre cómo editar esta función.
void reconnect() { // Bucle hasta que estemos reconectados while (!client.connected()) { Serial.println("Intentando conexión MQTT..."); // Intentar conectar if (client.connect(MQTT_CLIENT_NAME, TOKEN,"")) { Serial.println("conectado"); } else { Serial.print("fallido, rc="); Serial.print(client.state()); Serial.println(" intenta de nuevo en 2 segundos"); // Espera 2 segundos antes de reintentar delay(2000); } }}
A continuación, hemos codificado una función adicional para reconectar en caso de que el MQTT se caiga. En el primer bucle anterior, verás un argumento para determinar el estado de conexión con el método connected() del objeto PubSubClient. Si el dispositivo no está conectado, entonces el código intentará inicializar una conexión con el broker utilizando el método connect(char* clientName, char* user, char* password)
. Aquí puedes ver que insertamos como argumentos el ID del cliente previamente definido y tu TOKEN como nombre de usuario. También puedes insertar tu token como contraseña, pero en el lado de Ubidots solo procesaremos tu nombre de usuario, así que no importa cuál pongas como tu contraseña.
/**************************************** * Funciones Principales ****************************************/ void setup() { Serial.begin(115200); pinMode(A0, INPUT); WiFiMulti.addAP(WIFISSID, PASSWORD); Serial.println(); Serial.println(); Serial.print("Esperando WiFi... "); while(WiFiMulti.run() != WL_CONNECTED) { Serial.print("."); delay(500); } Serial.println(""); Serial.println("WiFi conectado"); Serial.println("Dirección IP: "); Serial.println(WiFi.localIP()); client.setServer(mqttBroker, 1883); client.setCallback(callback);
A partir de aquí, el script debería ser familiar, ya que ahora solo contiene las funciones setup y loop. Como función setup()
, inicializamos el puerto serie a 115k baudios por segundo y configuramos el pin analógico como entrada del NodeMCU para leer los valores del sensor. Luego nos conectamos al punto de acceso wifi e imprimimos algunos mensajes de depuración. El primer bucle del código es para evitar que cualquier rutina comience si el dispositivo no está conectado al punto de acceso. Una vez que se confirma que el dispositivo está conectado, imprimimos un mensaje de depuración con la IP local asignada al dispositivo.
Ahora, aquí hay dos funciones interesantes, setServer(),
un método de PubSubClient para establecer la URL del broker y el puerto para comenzar las comunicaciones. Y, setCallBack()
que hace disponible la función callback() definida anteriormente.
Ten en cuenta que los dos métodos anteriores (setServe y setCallBack) son exclusivos del objeto client definido por el constructor PubSubClient.
void loop() { if (!client.connected()) { reconnect(); // Se suscribe para obtener el valor de la variable de control en el dispositivo de la caja de temperatura char* topicToSubscribe; sprintf(topicToSubscribe, "%s", ""); // Limpia el contexto del char sprintf(topicToSubscribe, "%s%s", "/v1.6/devices/", "temperature-box"); sprintf(topicToSubscribe, "%s/%s/lv", topicToSubscribe, "control"); client.subscribe(topicToSubscribe); }
Ahora comencemos con la función loop()
. Primero, verifica que el dispositivo esté conectado; si no, llama a la función auxiliar reconnect()
anterior. Una vez conectado, nos suscribimos a un topic en Ubidots para recuperar datos de una variable llamada 'control' en un dispositivo llamado 'temperature-box.' Puedes encontrar toda esta información en la pestaña de dispositivos de tu aplicación Ubidots. (Ten en cuenta que la variable y el dispositivo deberían haberse creado previamente en tu aplicación Ubidots). Para recuperar el valor de esa variable, necesitamos suscribirnos a un topic con la estructura a continuación de acuerdo con nuestra API:
/v1.6/devices/{LABEL_DEVICE}/{LABEL_VARIABLE}/lv
Para construir la estructura anterior, utilizamos la función C sprintf()
. Luego llamamos al método subscribe()
para suscribirnos a tu topic en Ubidots. Recuerda que cualquier cambio en esa variable será manejado por la función callback()
definida anteriormente.
float temperature = analogRead(A0); float lat = 6.101; float lng= -1.293; /* 4 es el ancho mínimo, 2 es la precisión; el valor float se copia en str_temp*/ dtostrf(temperature, 4, 2, str_temp); dtostrf(lat, 4, 2, str_lat); dtostrf(lng, 4, 2, str_lng);
Ahora puedes comenzar a leer el valor de nuestro sensor y almacenarlo localmente en la variable llamada temperature. (En este ejemplo, los valores de latitud y longitud son simulados ya que nuestro NodeMCU no tiene GPS.) La función dtostrf()
simplemente nos permite almacenar un valor de temperatura float como un arreglo de caracteres para enviar el valor al broker más tarde.
Recuerda que necesitamos enviar una cadena de texto en formato plano. Estamos pasando de un tipo float a una variable de tipo string.
sprintf(topic, "%s", ""); // Limpia el contenido del topicsprintf(topic, "%s%s", "/v1.6/devices/", DEVICE_LABEL);sprintf(payload, "%s", ""); // Limpia el payloadsprintf(payload, "{\"%s\":", VARIABLE_LABEL); // Agrega la etiqueta de la variable sprintf(payload, "%s {\"value\": %s", payload, str_temp); // Agrega el valorsprintf(payload, "%s, \"context\":{\"lat\": %s, \"lng\": %s}", payload, str_lat, str_lng); // Agrega coordenadassprintf(payload, "%s } }", payload); // Cierra los corchetes del diccionario
Nuestro objetivo es publicar datos, así que comenzamos a construir el topic donde publicaremos nuestros valores. Recuerda que con la función sprintf() se reemplazará la Etiqueta del Dispositivo dentro de la estructura /v1.6/devices/{LABEL_DEVICE}
.
Ahora en el char payload almacenaremos el valor a enviar. Para esto, necesitamos construir un diccionario JSON de acuerdo con nuestra API de MQTT. En este tutorial estaremos enviando un valor de temperatura con la posición del dispositivo. Para esto, hemos construido el siguiente diccionario JSON:
{"temperature":{"value":20, "context":{"lat": 6.21, "lng":-1.2}}}
Paso a paso, el diccionario JSON utilizando la función C sprintf()
y almacenado en la variable payload a continuación.
client.publish(topic, payload); client.loop(); delay(1000);}
Finalmente, llamamos al método publish e insertamos como argumentos el topic donde publicaremos y el payload que contiene nuestros valores.
Recuerda que el código completo se puede descargar y agregar a tu IDE desde aquí.
PRUEBAS:
Ahora probemos la suscripción. He creado un panel de control con un botón de control de encendido/apagado asociado con la variable 'control' en el dispositivo 'temperature-box.' En la función callback()
, todos los cambios en la variable se manejan para activar alguna acción (en nuestro caso, la acción fue simplemente imprimir el topic y su valor, pero podrías - por ejemplo, encender o apagar un GPIO). Aquí puedes ver cómo funciona:
A continuación, verificaremos el estado de publicación, he establecido nuevamente 'temperature-box' como DEVICE_LABEL y como 'temperature' como VARIABLE_LABEL con un retraso de 1000 milisegundos para enviar valores. Si el dispositivo o variable no existe, entonces el núcleo de Ubidots los generará automáticamente para ti. Aquí están los resultados visuales de enviar datos cada segundo:
OBJETIVO CUMPLIDO:
¡Con el código anterior, nuestro script funciona genial! Ahora, es tu turno de probarlo y adaptarlo según tus necesidades específicas. Una vez que hayas dominado el envío de datos hacia y desde Ubidots, puedes comenzar a portar tu script a un archivo de biblioteca para el Arduino IDE siguiendo esta guía que cumple con el estándar de Arduino.