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
Puerta de enlace LoRaWAN cercana conectada a TTI.
WisBlock Base, Core y Sensor Ambiental
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: