All Collections
Connect your Devices
Connect RAK Wireless' WisBlock to The Things Stack and Ubidots
Connect RAK Wireless' WisBlock to The Things Stack and Ubidots

Learn to integrate RAK's WisBlock LoRaWAN devices to Ubidots

David Sepúlveda avatar
Written by David Sepúlveda
Updated over a week ago

WisBlock is a modular system that makes it easy to implement low power wide area network (LPWAN) into your IoT solution. WisBlock is going with your solution all the way from rapid prototyping to mass production without the need to create new hardware modules for each step. To learn more, visit the website.

Requirements

Table of Contents

1. Arduino Setup

Follow the below sections to set your Arduino environment as needed for WisBlock hardware block platform:

RAK4630 BSP Arduino installation

Step 1: Open Arduino's preferences.

Step 2: Enter the below link in the "Additional Boards Manager URLs" option

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

Step 3: Restart Arduino IDE.

Step 4: Open the boards manager at Tools > Board > Boards Manager..., and install "RAKwireless nRF Boards"

Step 5: Once the BSP is installed, select 'RAK4631' or 'RAK4601' from the Tools > Board menu, which will update your system config to use the right compiler and settings for the module.

Libraries installation

Step 1: Open the library manager at Sketch > Include Library > Library Manager...

Step 2: Look for the "ClosedCube BME680" library and install it.

Step 3: Look for the "SX126x" library and install it.

Library modification

IMPORTANT NOTE: The below steps are only for users within the US 915MHz region. This is required because the "SX126x-Arduino" currently has a bug for the appropriate Data rate setup in this region. It always sets it to 0, but we need 1 given the payload length.

Step 1: Locate your library folder at /Arduino/libraries

Step 2: Go to /SX126x-Arduino/src/mac

Step 3: Open the "LoRaMac.cpp" file in your preferred code editor.

Step 4: Comment the if statement in line 1951.

Step 5: Comment the if statement in line 2498.

Device provision in TTS

The below steps will guide you through the Device provision process within your TTS account to get the node's DevEUI, AppEUI and AppKey.

Step 1: Read the QR in the Core block to get the node's DevEUI

Step 2: In your TTI account. Go to the application you'd like this Node connected to.

Step 3: Navigate to the "End devices" section in the left panel

Step 4: Click on the "Add end device" button.

Step 5: Fill out the form as follows and click "Start"

Step 6: Basic settings:

  • End device ID: enter an alphanumeric ID. No spaces allowed.

  • AppEUI: 00 00 00 00 00 00 00 00

  • DevEUI: ID from Step 1

  • End device name: entered at will

  • End device description: entered at will

Step 7: Network layer settings

Step 8: Join settings: click the generation button to create the AppKey.

Step 9: Click the create device button

Firmware

Step 1: Copy and paste the below code in the Arduino editor.

Step 2: Enter the DevEUI, AppEUI and AppKey where required in the code.

Step 3: Compile and flash the code to the WisBlock.

/**
* @file Environment_Monitoring.ino
* @author rakwireless.com
* @brief This sketch demonstrate how to get environment data from BME680
* and send the data to lora gateway.
* @version 0.1
* @date 2020-07-28
*
* @copyright Copyright (c) 2020
*
* @note RAK5005-O GPIO mapping to RAK4631 GPIO ports
RAK5005-O <-> nRF52840
IO1 <-> P0.17 (Arduino GPIO number 17)
IO2 <-> P1.02 (Arduino GPIO number 34)
IO3 <-> P0.21 (Arduino GPIO number 21)
IO4 <-> P0.04 (Arduino GPIO number 4)
IO5 <-> P0.09 (Arduino GPIO number 9)
IO6 <-> P0.10 (Arduino GPIO number 10)
SW1 <-> P0.01 (Arduino GPIO number 1)
A0 <-> P0.04/AIN2 (Arduino Analog A2
A1 <-> P0.31/AIN7 (Arduino Analog A7
SPI_CS <-> P0.26 (Arduino GPIO number 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_Arduino

ClosedCube_BME680 bme680;

// RAK4630 supply two LED
#ifndef LED_BUILTIN
#define LED_BUILTIN 35
#endif

#ifndef LED_BUILTIN2
#define LED_BUILTIN2 36
#endif

bool doOTAA = true;
#define SCHED_MAX_EVENT_DATA_SIZE APP_TIMER_SCHED_EVENT_DATA_SIZE /**< Maximum size of scheduler events. */
#define SCHED_QUEUE_SIZE 60 /**< Maximum number of events in the scheduler queue. */
#define LORAWAN_DATERATE DR_1 /*LoRaMac datarates definition, from DR_0 to DR_5*/
#define LORAWAN_TX_POWER TX_POWER_5 /*LoRaMac tx power definition, from TX_POWER_0 to TX_POWER_15*/
#define JOINREQ_NBTRIALS 10 /**< Number of trials for the join request. */
DeviceClass_t gCurrentClass = CLASS_A; /* class definition*/
lmh_confirm gCurrentConfirm = LMH_UNCONFIRMED_MSG; /* confirm/unconfirm packet definition*/
uint8_t gAppPort = LORAWAN_APP_PORT; /* data port*/

/**@brief Structure containing LoRaWan parameters, needed for 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};

// Foward declaration
static 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 Structure containing LoRaWan callback functions, needed for lmh_init()
*/
static lmh_callback_t lora_callbacks = {BoardGetBatteryLevel, BoardGetUniqueId, BoardGetRandomSeed,
lorawan_rx_handler, lorawan_has_joined_handler, lorawan_confirm_class_handler};


//OTAA keys
uint8_t nodeDeviceEUI[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E}; // Enter DevEUI
uint8_t nodeAppEUI[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // Enter AppEUI
uint8_t nodeAppKey[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // Enter AppKey

// Private defination
#define LORAWAN_APP_DATA_BUFF_SIZE 64 /**< buffer size of the data to be transmitted. */
#define LORAWAN_APP_INTERVAL 20000 /**< Defines for user timer, the application data transmission interval. 20s, value in [ms]. */
static uint8_t m_lora_app_data_buffer[LORAWAN_APP_DATA_BUFF_SIZE]; //< Lora user application data buffer.
static lmh_app_data_t m_lora_app_data = {m_lora_app_data_buffer, 0, 0, 0, 0}; //< Lora user application data structure.

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

// Initialize LoRa chip.
lora_rak4630_init();

// Initialize Serial for debug output
Serial.begin(115200);
while (!Serial)
{
delay(10);
}
Serial.println("=====================================");
Serial.println("Welcome to RAK4630 LoRaWan!!!");
Serial.println("Type: OTAA");

#if defined(REGION_AS923)
Serial.println("Region: AS923");
#elif defined(REGION_AU915)
Serial.println("Region: AU915");
#elif defined(REGION_CN470)
Serial.println("Region: CN470");
#elif defined(REGION_CN779)
Serial.println("Region: CN779");
#elif defined(REGION_EU433)
Serial.println("Region: EU433");
#elif defined(REGION_IN865)
Serial.println("Region: IN865");
#elif defined(REGION_EU868)
Serial.println("Region: EU868");
#elif defined(REGION_KR920)
Serial.println("Region: KR920");
#elif defined(REGION_US915)
Serial.println("Region: US915");
#elif defined(REGION_US915_HYBRID)
Serial.println("Region: US915_HYBRID");
#else
Serial.println("Please define a region in the compiler options.");
#endif
Serial.println("=====================================");

/* bme680 init */
bme680_init();
Serial.printf("BME680 initialized!\n");

//creat a user timer to send data to server period
uint32_t err_code;

err_code = timers_init();
if (err_code != 0)
{
Serial.printf("timers_init failed - %d\n", err_code);
}

Serial.printf("Setting DevEUI, etc...\n");

// Setup the EUIs and Keys
lmh_setDevEui(nodeDeviceEUI);
lmh_setAppEui(nodeAppEUI);
lmh_setAppKey(nodeAppKey);

// Initialize LoRaWan
err_code = lmh_init(&lora_callbacks, lora_param_init, doOTAA);
Serial.printf("lmh_init response code: %d\n", err_code);
if (err_code != 0)
{
Serial.printf("lmh_init failed - %d\n", err_code);
}

lmh_join();
}

void loop()
{
// Handle Radio events
Radio.IrqProcess();
}

/**@brief LoRa function for handling HasJoined event.
*/
void lorawan_has_joined_handler(void)
{
Serial.println("OTAA Mode, Network Joined!");

delay(1000);
TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);
TimerStart(&appTimer);

}

/**@brief Function for handling LoRaWan received data from Gateway
*
* @param[in] app_data Pointer to rx data
*/
void lorawan_rx_handler(lmh_app_data_t *app_data)
{
Serial.printf("LoRa Packet received on port %d, size:%d, rssi:%d, snr:%d, data:%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("switch to class %c done\n", "ABC"[Class]);
// Informs the server that switch has occurred ASAP
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)
{
//Not joined, try again later
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 fail count %d\n", count_fail);
}
}

/**@brief Function for handling user timerout event.
*/
void tx_lora_periodic_handler(void)
{
TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);
TimerStart(&appTimer);
Serial.println("Sending frame now...");
send_lora_frame();
}

/**@brief Function for the Timer initialization.
*
* @details Initializes the timer module. This creates and starts application timers.
*/
uint32_t timers_init(void)
{
TimerInit(&appTimer, tx_lora_periodic_handler);
return 0;
}

void bme680_init()
{
Wire.begin();
bme680.init(0x76); // I2C address: 0x76 or 0x77
bme680.reset();

Serial.print("Chip ID=0x");
Serial.println(bme680.getChipID(), HEX);

// oversampling: humidity = x1, temperature = x2, pressure = x16
bme680.setOversampling(BME680_OVERSAMPLING_X1, BME680_OVERSAMPLING_X2, BME680_OVERSAMPLING_X16);
bme680.setIIRFilter(BME680_FILTER_3);
bme680.setGasOn(300, 100); // 300 degree Celsius and 100 milliseconds

bme680.setForcedMode();
}
String data = "";
void bme680_get()
{
Serial.print("result: ");
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;

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

Step 4: Open the Serial Monitor and make sure the Device joins to the network and start sending data every 20 seconds

2. UbiFunction Setup

From the UbiFunctions module follow the below steps to create and launch the UbiFunction which will handle TTI’s native JSON format conversion into an Ubidots’ compatible one:

IMPORTANT NOTE: This module is only available starting from the IoT Entrepreneur license.

Step 1: Click the “+” button in the upper-right corner
Step 2: Name your UbiFunction. For example, “TTI Integration”
Step 3: Select POST as the Method
Step 4: Select Python 3.6 as the default Runtime
NOTE: Leave the “Time-base trigger” option disable
Step 5: Enter the below code in the Editor

import requests
import json
import time
import base64

BASE_URL = "https://industrial.api.ubidots.com"

TOKEN = "YOUR TOKEN" # Enter an Ubidots account token here

def main(args):
# Prining args from TTI
print(f'[INFO] Args from TTI:\n {args}')

# Parsing data
payload = parse_tti_data(args)
dev_label = tti_dev_id(args)
print(f'[INFO] Parsed data:\n {payload}')
print(f'[INFO] TTI Dev_ID data:\n {dev_label}')

# Posting to Ubidots
req = update_device(dev_label, payload, TOKEN)
print(f'[INFO] Request to Ubidots Status code: {req.status_code}')
print(f'[INFO] Request ti 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] Didn't find a valid Wisblock payload")

return payload

def tti_dev_id(data):
return data['end_device_ids']['device_id']

def update_device(device, payload, token):
"""
Updates device with payload
"""
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 req

def create_request(url, headers, attempts, request_type, data=None):
"""
Function to make a request to the server
"""
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] There was an error with the request, details:")
print(e)
return None

Step 6: Enter an Ubidots account token where asked (line 7)
Step 7: Click on the “Make it live” button.
Step 8: Copy the “HTTPS Endpoint URL” by clicking the “Copy” icon and Save it for later.

3. TTS Webhook Setup

The below steps will depict the process to create the Webhook that will forward the data from TTS to the UbiFunction (and subsequently to your Ubidots account):

Step 1: Go to the Integrations option in the left panel. Then to Webhooks

Step 2: Click on the "Add webhook" button

Step 3: Select the tide for Ubidots

Step 4: Fill the form as follows:

  • Webhook ID: Identifier for the webhook. All lowercase, alphanumeric and dashes only.

  • Ubidots username: Your Ubidots account username.

  • UbiFunction name: take the last portion of the function's URL. For example:
    https://parse.ubidots.com/prv/<USERNAME>/<FUNCTION-NAME>

Step 5: Click on the "Create ubidots webhook" button.

At the end of this process, you should be seeing data coming from your WisBlock device into your Ubidots account, just as follows:


Did this answer your question?