Ir al contenido principal
Todas las coleccionesConecta tus dispositivos
Conectar WisBlock de RAK Wireless a The Things Stack y Ubidots
Conectar WisBlock de RAK Wireless a The Things Stack y Ubidots

Aprende a integrar los dispositivos WisBlock LoRaWAN de RAK a Ubidots.

David Sepúlveda avatar
Escrito por David Sepúlveda
Actualizado hace más de una semana

WisBlock es un sistema modular que facilita la implementación de redes de área amplia de bajo consumo (LPWAN) en su solución IoT. WisBlock acompaña su solución desde la creación de prototipos rápidos hasta la producción en masa sin necesidad de crear nuevos módulos de hardware para cada paso. Para obtener más información, visite el sitio web.

Requisitos

Tabla de Contenidos

1. Configuración de Arduino

Siga las secciones a continuación para configurar su entorno de Arduino según sea necesario para la plataforma de bloques de hardware WisBlock:

Instalación de RAK4630 BSP Arduino

Paso 1: Abra las preferencias de Arduino.

Paso 2: Ingrese el siguiente enlace en la opción "URLs del Administrador de Placas Adicionales"

https://raw.githubusercontent.com/RAKwireless/RAKwireless-Arduino-BSP-Index/main/package_rakwireless_index.json

Paso 3: Reinicie Arduino IDE.

Paso 4: Abra el administrador de placas en Herramientas > Placa > Administrador de Placas..., e instale "RAKwireless nRF Boards"

Paso 5: Una vez que el BSP esté instalado, seleccione 'RAK4631' o 'RAK4601' en el menú Herramientas > Placa, lo que actualizará la configuración de su sistema para usar el compilador y la configuración correctos para el módulo.

Instalación de bibliotecas

Paso 1: Abra el administrador de bibliotecas en Esquema > Incluir Biblioteca > Administrador de Bibliotecas...

Paso 2: Busque la biblioteca "ClosedCube BME680" e instálela.

Paso 3: Busque la biblioteca "SX126x" e instálela.

Modificación de la biblioteca

NOTA IMPORTANTE: Los pasos a continuación son solo para usuarios dentro de la región de 915MHz de EE. UU. Esto es necesario porque el "SX126x-Arduino" actualmente tiene un error en la configuración adecuada de la tasa de datos en esta región. Siempre lo establece en 0, pero necesitamos 1 dada la longitud de la carga útil.

Paso 1: Localice su carpeta de bibliotecas en /Arduino/libraries

Paso 2: Vaya a /SX126x-Arduino/src/mac

Paso 3: Abra el archivo "LoRaMac.cpp" en su editor de código preferido.

Paso 4: Comente la declaración if en la línea 1951.

Paso 5: Comente la declaración if en la línea 2498.

Provisionamiento del dispositivo en TTS

Los pasos a continuación lo guiarán a través del proceso de provisionamiento del dispositivo dentro de su cuenta TTS para obtener el DevEUI, AppEUI y AppKey del nodo.

Paso 1: Lea el QR en el bloque Core para obtener el DevEUI del nodo

Paso 2: En su cuenta TTI. Vaya a la aplicación a la que le gustaría conectar este Nodo.

Paso 3: Navegue a la sección "Dispositivos finales" en el panel izquierdo

Paso 4: Haga clic en el botón "Agregar dispositivo final".

Paso 5: Complete el formulario de la siguiente manera y haga clic en "Iniciar"

Paso 6: Configuración básica:

  • ID del dispositivo final: ingrese un ID alfanumérico. No se permiten espacios.

  • AppEUI: 00 00 00 00 00 00 00 00

  • DevEUI: ID del Paso 1

  • Nombre del dispositivo final: ingresado a voluntad

  • Descripción del dispositivo final: ingresada a voluntad

Paso 7: Configuración de la capa de red

Paso 8: Configuración de unión: haga clic en el botón de generación para crear el AppKey.

Paso 9: Haga clic en el botón de crear dispositivo

Firmware

Paso 1: Copie y pegue el siguiente código en el editor de Arduino.

Paso 2: Ingrese el DevEUI, AppEUI y AppKey donde se requiera en el código.

Paso 3: Compile y flashee el código en el WisBlock.

/** * @file Environment_Monitoring.ino * @author rakwireless.com * @brief Este sketch demuestra cómo obtener datos ambientales del BME680 *    y enviar los datos a la puerta de enlace lora. * @version 0.1 * @date 2020-07-28 *  * @copyright Copyright (c) 2020 *  * @note Mapeo GPIO de RAK5005-O a puertos GPIO de RAK4631   RAK5005-O <->  nRF52840   IO1       <->  P0.17 (número GPIO de Arduino 17)   IO2       <->  P1.02 (número GPIO de Arduino 34)   IO3       <->  P0.21 (número GPIO de Arduino 21)   IO4       <->  P0.04 (número GPIO de Arduino 4)   IO5       <->  P0.09 (número GPIO de Arduino 9)   IO6       <->  P0.10 (número GPIO de Arduino 10)   SW1       <->  P0.01 (número GPIO de Arduino 1)   A0        <->  P0.04/AIN2 (Arduino Analógico A2   A1        <->  P0.31/AIN7 (Arduino Analógico A7   SPI_CS    <->  P0.26 (número GPIO de Arduino 26)  */#include <Arduino.h>#include <LoRaWan-RAK4630.h> //http://librarymanager/ALL#SX126x-Arduino#include <SPI.h>#include <Wire.h>#include "ClosedCube_BME680.h" //http://librarymanager/ALL#ClosedCube_BME680_ArduinoClosedCube_BME680 bme680;// RAK4630 proporciona dos LED#ifndef LED_BUILTIN#define LED_BUILTIN 35#endif#ifndef LED_BUILTIN2#define LED_BUILTIN2 36#endifbool doOTAA = true;#define SCHED_MAX_EVENT_DATA_SIZE APP_TIMER_SCHED_EVENT_DATA_SIZE /**< Tamaño máximo de los eventos del programador. */#define SCHED_QUEUE_SIZE 60                     /**< Número máximo de eventos en la cola del programador. */#define LORAWAN_DATERATE DR_1                   /*Definición de tasas de datos de LoRaMac, de DR_0 a DR_5*/#define LORAWAN_TX_POWER TX_POWER_5                 /*Definición de potencia de tx de LoRaMac, de TX_POWER_0 a TX_POWER_15*/#define JOINREQ_NBTRIALS 10                      /**< Número de intentos para la solicitud de unión. */DeviceClass_t gCurrentClass = CLASS_A;                /* definición de clase*/lmh_confirm gCurrentConfirm = LMH_UNCONFIRMED_MSG;          /* definición de paquete confirmado/no confirmado*/uint8_t gAppPort = LORAWAN_APP_PORT;                /* puerto de datos*//**@brief Estructura que contiene parámetros de LoRaWan, necesarios para lmh_init() */static lmh_param_t lora_param_init = {LORAWAN_ADR_ON, LORAWAN_DATERATE, LORAWAN_PUBLIC_NETWORK, JOINREQ_NBTRIALS, LORAWAN_TX_POWER, LORAWAN_DUTYCYCLE_OFF};// Declaración anticipadastatic void lorawan_has_joined_handler(void);static void lorawan_rx_handler(lmh_app_data_t *app_data);static void lorawan_confirm_class_handler(DeviceClass_t Class);static void send_lora_frame(void);/**@brief Estructura que contiene funciones de callback de LoRaWan, necesarias para lmh_init()*/static lmh_callback_t lora_callbacks = {BoardGetBatteryLevel, BoardGetUniqueId, BoardGetRandomSeed,                    lorawan_rx_handler, lorawan_has_joined_handler, lorawan_confirm_class_handler};// Claves OTAAuint8_t nodeDeviceEUI[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E}; // Ingrese DevEUIuint8_t nodeAppEUI[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // Ingrese AppEUIuint8_t nodeAppKey[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // Ingrese AppKey// Definición privada#define LORAWAN_APP_DATA_BUFF_SIZE 64                     /**< tamaño del buffer de los datos a ser transmitidos. */#define LORAWAN_APP_INTERVAL 20000                        /**< Define para el temporizador del usuario, el intervalo de transmisión de datos de la aplicación. 20s, valor en [ms]. */static uint8_t m_lora_app_data_buffer[LORAWAN_APP_DATA_BUFF_SIZE];        //< Buffer de datos de la aplicación de usuario Lora.static lmh_app_data_t m_lora_app_data = {m_lora_app_data_buffer, 0, 0, 0, 0}; //< Estructura de datos de la aplicación de usuario Lora.TimerEvent_t appTimer;static uint32_t timers_init(void);static uint32_t count = 0;static uint32_t count_fail = 0;void bme680_init();void bme680_get();void setup(){  pinMode(LED_BUILTIN, OUTPUT);  digitalWrite(LED_BUILTIN, LOW);  // Inicializar el chip LoRa.  lora_rak4630_init();  // Inicializar Serial para salida de depuración  Serial.begin(115200);  while (!Serial)  {      delay(10);  }  Serial.println("=====================================");  Serial.println("¡Bienvenido a RAK4630 LoRaWan!");  Serial.println("Tipo: OTAA");#if defined(REGION_AS923)  Serial.println("Región: AS923");#elif defined(REGION_AU915)  Serial.println("Región: AU915");#elif defined(REGION_CN470)  Serial.println("Región: CN470");#elif defined(REGION_CN779)  Serial.println("Región: CN779");#elif defined(REGION_EU433)  Serial.println("Región: EU433");#elif defined(REGION_IN865)  Serial.println("Región: IN865");#elif defined(REGION_EU868)  Serial.println("Región: EU868");#elif defined(REGION_KR920)  Serial.println("Región: KR920");#elif defined(REGION_US915)  Serial.println("Región: US915");#elif defined(REGION_US915_HYBRID)  Serial.println("Región: US915_HYBRID");#else  Serial.println("Por favor, defina una región en las opciones del compilador.");#endif  Serial.println("=====================================");  /* inicialización de bme680 */  bme680_init();  Serial.printf("¡BME680 inicializado!\n");  // crear un temporizador de usuario para enviar datos al servidor periódicamente  uint32_t err_code;  err_code = timers_init();  if (err_code != 0)  {    Serial.printf("timers_init falló - %d\n", err_code);  }  Serial.printf("Configurando DevEUI, etc...\n");  // Configurar los EUIs y Claves  lmh_setDevEui(nodeDeviceEUI);  lmh_setAppEui(nodeAppEUI);  lmh_setAppKey(nodeAppKey);  // Inicializar LoRaWan  err_code = lmh_init(&lora_callbacks, lora_param_init, doOTAA);  Serial.printf("código de respuesta lmh_init: %d\n", err_code);  if (err_code != 0)  {    Serial.printf("lmh_init falló - %d\n", err_code);  }  lmh_join();}void loop(){  // Manejar eventos de Radio  Radio.IrqProcess();}/**@brief Función LoRa para manejar el evento HasJoined. */void lorawan_has_joined_handler(void){  Serial.println("Modo OTAA, ¡Red Unida!");    delay(1000);  TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);  TimerStart(&appTimer);}/**@brief Función para manejar datos recibidos de LoRaWan desde la Puerta de enlace * * @param[in] app_data  Puntero a datos rx */void lorawan_rx_handler(lmh_app_data_t *app_data){  Serial.printf("Paquete LoRa recibido en el puerto %d, tamaño:%d, rssi:%d, snr:%d, datos:%s\n",          app_data->port, app_data->buffsize, app_data->rssi, app_data->snr, app_data->buffer);}void lorawan_confirm_class_handler(DeviceClass_t Class){  Serial.printf("cambio a clase %c hecho\n", "ABC"[Class]);  // Informa al servidor que el cambio ha ocurrido lo antes posible  m_lora_app_data.buffsize = 0;  m_lora_app_data.port = gAppPort;  lmh_send(&m_lora_app_data, gCurrentConfirm);}void send_lora_frame(void){  if (lmh_join_status_get() != LMH_SET)  {    // No unido, intente nuevamente más tarde    return;  }  bme680_get();  lmh_error_status error = lmh_send(&m_lora_app_data, gCurrentConfirm);  if (error == LMH_SUCCESS)  {    count++;    Serial.printf("lmh_send ok count %d\n", count);  }  else  {    count_fail++;    Serial.printf("lmh_send falló count %d\n", count_fail);  }}/**@brief Función para manejar el evento de temporizador del usuario. */void tx_lora_periodic_handler(void){  TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);  TimerStart(&appTimer);  Serial.println("Enviando marco ahora...");  send_lora_frame();}/**@brief Función para la inicialización del temporizador. * * @details Inicializa el módulo de temporizador. Esto crea y comienza los temporizadores de la aplicación. */uint32_t timers_init(void){  TimerInit(&appTimer, tx_lora_periodic_handler);  return 0;}void bme680_init(){  Wire.begin();  bme680.init(0x76); // Dirección I2C: 0x76 o 0x77  bme680.reset();  Serial.print("ID del Chip=0x");  Serial.println(bme680.getChipID(), HEX);  // sobremuestreo: humedad = x1, temperatura = x2, presión = x16  bme680.setOversampling(BME680_OVERSAMPLING_X1, BME680_OVERSAMPLING_X2, BME680_OVERSAMPLING_X16);  bme680.setIIRFilter(BME680_FILTER_3);  bme680.setGasOn(300, 100); // 300 grados Celsius y 100 milisegundos  bme680.setForcedMode();}String data = "";void bme680_get(){  Serial.print("resultado: ");  uint32_t i = 0;  memset(m_lora_app_data.buffer, 0, LORAWAN_APP_DATA_BUFF_SIZE);  m_lora_app_data.port = gAppPort;  double temp = bme680.readTemperature();  double pres = bme680.readPressure();  double hum = bme680.readHumidity();  uint32_t gas = bme680.readGasResistance();  data = "Tem:" + String(temp) + "C " + "Hum:" + String(hum) + "% " + "Pres:" + String(pres) + "KPa " + "Gas:" + String(gas) + "Ohms";  Serial.println(data);  uint16_t t = temp * 100;  uint16_t h = hum * 100;  uint32_t pre = pres * 100;  //resultado: T=28.25C, RH=50.00%, P=958.57hPa, G=100406 Ohms  m_lora_app_data.buffer[i++] = 0x01;  m_lora_app_data.buffer[i++] = (uint8_t)(t >> 8);  m_lora_app_data.buffer[i++] = (uint8_t)t;  m_lora_app_data.buffer[i++] = (uint8_t)(h >> 8);  m_lora_app_data.buffer[i++] = (uint8_t)h;  m_lora_app_data.buffer[i++] = (uint8_t)((pre & 0xFF000000) >> 24);  m_lora_app_data.buffer[i++] = (uint8_t)((pre & 0x00FF0000) >> 16);  m_lora_app_data.buffer[i++] = (uint8_t)((pre & 0x0000FF00) >> 8);  m_lora_app_data.buffer[i++] = (uint8_t)(pre & 0x000000FF);  m_lora_app_data.buffer[i++] = (uint8_t)((gas & 0xFF000000) >> 24);  m_lora_app_data.buffer[i++] = (uint8_t)((gas & 0x00FF0000) >> 16);  m_lora_app_data.buffer[i++] = (uint8_t)((gas & 0x0000FF00) >> 8);  m_lora_app_data.buffer[i++] = (uint8_t)(gas & 0x000000FF);  m_lora_app_data.buffsize = i;  bme680.setForcedMode();}

Paso 4: Abra el Monitor Serial y asegúrese de que el Dispositivo se una a la red y comience a enviar datos cada 20 segundos

2. Configuración de UbiFunction

Desde el módulo UbiFunctions, siga los pasos a continuación para crear y lanzar la UbiFunction que manejará la conversión del formato JSON nativo de TTI a uno compatible con Ubidots:

NOTA IMPORTANTE: Este módulo solo está disponible a partir de la licencia de IoT Entrepreneur.

Paso 1: Haga clic en el botón “+” en la esquina superior derecha
Paso 2: Nombre su UbiFunction. Por ejemplo, “Integración TTI”
Paso 3: Seleccione POST como el Método
Paso 4: Seleccione Python 3.6 como el Runtime predeterminado
NOTA: Deje la opción “Disparador basado en tiempo” desactivada
Paso 5: Ingrese el siguiente código en el Editor

import requestsimport jsonimport timeimport base64BASE_URL = "https://industrial.api.ubidots.com"TOKEN = "YOUR TOKEN" # Ingrese un token de cuenta de Ubidots aquídef main(args):    # Imprimiendo args de TTI    print(f'[INFO] Args from TTI:\n {args}')        # Analizando datos    payload = parse_tti_data(args)    dev_label = tti_dev_id(args)    print(f'[INFO] Datos analizados:\n {payload}')    print(f'[INFO] Datos TTI Dev_ID:\n {dev_label}')        # Publicando en Ubidots    req = update_device(dev_label, payload, TOKEN)    print(f'[INFO] Código de estado de la solicitud a Ubidots: {req.status_code}')    print(f'[INFO] Solicitud a Ubidots JSON:\n {req.json()}')    return {        'status_code': req.status_code,        'response_json': req.json()    }def parse_tti_data(data):    payload = dict()    bytes_data = base64.b64decode(data['uplink_message']['frm_payload'])    if bytes_data[0] == 1:        payload['temperature'] = (bytes_data[1] << 8 | (bytes_data[2])) / 100        payload['humidity'] = (bytes_data[3] << 8 | (bytes_data[4])) / 100        payload['pressure'] = (bytes_data[8] | (bytes_data[7] << 8) | (bytes_data[6] << 16) | (bytes_data[5] << 24)) / 100        payload['gas'] = bytes_data[12] | (bytes_data[11] << 8) | (bytes_data[10] << 16) | (bytes_data[9] << 24)    else:        print("[ERROR] No se encontró una carga útil válida de Wisblock")        return payloaddef tti_dev_id(data):    return data['end_device_ids']['device_id']def update_device(device, payload, token):    """    Actualiza el dispositivo con la carga útil    """    url = "{}/api/v1.6/devices/{}".format(BASE_URL, device)    headers = {"X-Auth-Token": token, "Content-Type": "application/json"}    req = create_request(url, headers, attempts=5, request_type="post", data=payload)        return reqdef create_request(url, headers, attempts, request_type, data=None):    """    Función para hacer una solicitud al servidor    """    request_func = getattr(requests, request_type)    kwargs = {"url": url, "headers": headers}    if request_type == "post" or request_type == "patch":        kwargs["json"] = data    try:        req = request_func(**kwargs)        status_code = req.status_code        time.sleep(1)        while status_code >= 400 and attempts < 5:            req = request_func(**kwargs)            status_code = req.status_code            attempts += 1            time.sleep(1)        return req    except Exception as e:        print("[ERROR] Hubo un error con la solicitud, detalles:")        print(e)        return None

Paso 6: Ingrese un token de cuenta de Ubidots donde se solicite (línea 7)
Paso 7: Haga clic en el botón “Hacerlo en vivo”.
Paso 8: Copie la “URL de Endpoint HTTPS” haciendo clic en el ícono “Copiar” y guárdela para más tarde.

3. Configuración de Webhook TTS

Los pasos a continuación describirán el proceso para crear el Webhook que enviará los datos de TTS a la UbiFunction (y posteriormente a su cuenta de Ubidots):

Paso 1: Vaya a la opción de Integraciones en el panel izquierdo. Luego a Webhooks

Paso 2: Haga clic en el botón "Agregar webhook"

Paso 3: Seleccione el tipo para Ubidots

Paso 4: Complete el formulario de la siguiente manera:

  • ID del Webhook: Identificador para el webhook. Solo minúsculas, alfanumérico y guiones.

  • Nombre de usuario de Ubidots: Su nombre de usuario de cuenta de Ubidots.

  • Nombre de UbiFunction: tome la última parte de la URL de la función. Por ejemplo:
    https://parse.ubidots.com/prv/<USERNAME>/<NOMBRE-FUNCION>

Paso 5: Haga clic en el botón "Crear webhook de ubidots".

Al final de este proceso, debería ver datos provenientes de su dispositivo WisBlock en su cuenta de Ubidots, tal como sigue:


¿Ha quedado contestada tu pregunta?