As shown in the previous diagram, environmental data is gathered by a RAK7204 WisNode Sense Home and its sent to the Internet over the Helium LongFi network. Once the data has been received in the Helium console, data is decoded and forwarded to Ubidots.
Requirements
1. RAK7204 WisNode Sense Home and Helium Integration
The RAK7204 is an LPWAN Node with an integrated high-precision environmental sensor. It can measure changes in temperature, humidity, air pressure. All the data sensed can be sent to Helium through a Helium Hotspot / RAK Hotspot Miner to be forwarded to the cloud later.]
IMPORTANT NOTE: In this guide, you should have already a hotspot configured in the Helium Console, or that you have network coverage nearby.
Helium Setup
To manage our devices in the Helium Console, we must use "Labels." A powerful mechanism that provides scalability and flexibility to our projects by organizing devices, assigning integrations, adding functions, and so on.
Step 1: To add a new label, go to “Labels > Create a new label.”
Step 2: Assign a Label name. For example, “RAK7204 WisNodes”. Then, click on “Create label.”
Step 3: To add a new device, go to “Devices > Add a new device.”
Step 4: Assign a Device Name. For example, “RAK7204 #1”. Then, attach the label previously created and click on “Submit.”
Step 5: Click on the device created and save the Device EUI, App EUI, App Key for the RAK7204 setup.
Step 6: To decode the incoming data create a decoder function. Go to “Functions > Create New Function.”
Step 7: Assign a function name. Then, choose “Decoder” as Function Type and “Custom script”as Format.
Step 8: Paste the following code inside the code editor box.
// Function to decode uplink data.
// Decode decodes an array of bytes into an object.
// - port contains the LoRaWAN fPort number
// - bytes is an array of bytes, e.g. [225, 230, 255, 0]
// The function must return an object, e.g. {"temperature": 22.5}
function Decoder(bytes, port) {
var decoded = {};
var hexString=bin2HexStr(bytes);
return rakSensorDataDecode(hexString);
}
// convert array of bytes to hex string.
// e.g: 0188053797109D5900DC140802017A0768580673256D0267011D040214AF0371FFFFFFDDFC2E
function bin2HexStr(bytesArr) {
var str = "";
for(var i=0; i<bytesArr.length; i++) {
var tmp = (bytesArr[i] & 0xff).toString(16);
if(tmp.length == 1) {
tmp = "0" + tmp;
}
str += tmp;
}
return str;
}
// convert string to short integer
function parseShort(str, base) {
var n = parseInt(str, base);
return (n << 16) >> 16;
}
// convert string to triple bytes integer
function parseTriple(str, base) {
var n = parseInt(str, base);
return (n << 8) >> 8;
}
// decode Hex sensor string data to object
function rakSensorDataDecode(hexStr) {
var str = hexStr;
var myObj = {};
while (str.length > 4) {
var flag = parseInt(str.substring(0, 4), 16);
switch (flag) {
case 0x0768:// Humidity
myObj.humidity = parseFloat(((parseShort(str.substring(4, 6), 16) * 0.01 / 2) * 100).toFixed(1)) + "%RH";//unit:%RH
str = str.substring(6);
break;
case 0x0673:// Atmospheric pressure
myObj.barometer = parseFloat((parseShort(str.substring(4, 8), 16) * 0.1).toFixed(2)) + "hPa";//unit:hPa
str = str.substring(8);
break;
case 0x0267:// Temperature
myObj.temperature = parseFloat((parseShort(str.substring(4, 8), 16) * 0.1).toFixed(2)) + "°C";//unit: °C
str = str.substring(8);
break;
case 0x0188:// GPS
myObj.latitude = parseFloat((parseTriple(str.substring(4, 10), 16) * 0.0001).toFixed(4)) + "°";//unit:°
myObj.longitude = parseFloat((parseTriple(str.substring(10, 16), 16) * 0.0001).toFixed(4)) + "°";//unit:°
myObj.altitude = parseFloat((parseTriple(str.substring(16, 22), 16) * 0.01).toFixed(1)) + "m";//unit:m
str = str.substring(22);
break;
case 0x0371:// Triaxial acceleration
myObj.acceleration_x = parseFloat((parseShort(str.substring(4, 8), 16) * 0.001).toFixed(3)) + "g";//unit:g
myObj.acceleration_y = parseFloat((parseShort(str.substring(8, 12), 16) * 0.001).toFixed(3)) + "g";//unit:g
myObj.acceleration_z = parseFloat((parseShort(str.substring(12, 16), 16) * 0.001).toFixed(3)) + "g";//unit:g
str = str.substring(16);
break;
case 0x0402:// air resistance
myObj.gasResistance = parseFloat((parseShort(str.substring(4, 8), 16) * 0.01).toFixed(2)) + "KΩ";//unit:KΩ
str = str.substring(8);
break;
case 0x0802:// Battery Voltage
myObj.battery = parseFloat((parseShort(str.substring(4, 8), 16) * 0.01).toFixed(2)) + "V";//unit:V
str = str.substring(8);
break;
case 0x0586:// gyroscope
myObj.gyroscope_x = parseFloat((parseShort(str.substring(4, 8), 16) * 0.01).toFixed(2)) + "°/s";//unit:°/s
myObj.gyroscope_y = parseFloat((parseShort(str.substring(8, 12), 16) * 0.01).toFixed(2)) + "°/s";//unit:°/s
myObj.gyroscope_z = parseFloat((parseShort(str.substring(12, 16), 16) * 0.01).toFixed(2)) + "°/s";//unit:°/s
str = str.substring(16);
break;
case 0x0902:// magnetometer x
myObj.magnetometer_x = parseFloat((parseShort(str.substring(4, 8), 16) * 0.01).toFixed(2)) + "μT";//unit:μT
str = str.substring(8);
break;
case 0x0a02:// magnetometer y
myObj.magnetometer_y = parseFloat((parseShort(str.substring(4, 8), 16) * 0.01).toFixed(2)) + "μT";//unit:μT
str = str.substring(8);
break;
case 0x0b02:// magnetometer z
myObj.magnetometer_z = parseFloat((parseShort(str.substring(4, 8), 16) * 0.01).toFixed(2)) + "μT";//unit:μT
str = str.substring(8);
break;
default:
str = str.substring(7);
break;
}
}
return myObj;
}
Step 9: Attach the label previously created and click on “Save Function.”
RAK7402 Setup
The WisNode Sense works out of the box with its firmware supporting LoRaWAN. You just need to interface with it using the RAK Serial Port tool to set up the parameters necessary to join the Helium Network. To start with the tool, follow all the steps provided in the following guide: Interfacing with the RAK7204 WisNode Sense Home
Once the serial communication with the device has been successfully established, check the firmware version with the AT command (at+version) and update it to the latest version if needed. Then you can execute the following commands to set the parameters required to establish a connection with the Helium Network.
Step 1: Set OTAA as activation mode
OTAA activation mode: at+set_config=lora:join_mode:0
Step 2: Type the following AT command to set the: Frequency/Region, Device EUI, Application EUI, and Application Key. Remember to replace the "xxxx" with the corresponding parameter provided in the helium device creation process (Figure X, Step X, etc.).
Frequency/Region: at+set_config=lora:region:xxxxxx
Device EUI: at+set_config=lora:dev_eui:xxxxxxxxxxxxxxxx
Application EUI: at+set_config=lora:app_eui:xxxxxxxxxxxxxxxx
Application Key: at+set_config=lora:app_key:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Step 3: Join the network in OTAA mode:
Join: at+join
Step 4: Set the interval of sending data:
Interval set up: at+set_config=lora:send_interval:X:Y
Where:
X - open or close the interval mechanism of sending data. If X is set to 0, the device will not send data automatically. If X is set to 1, the device will send data every Y second.
Y - interval time in seconds. This parameter is only valid when X is set to 1.
Now you can visualize the sensor data as it is arriving in real-time in the Helium console. Enter the page of the device you just created and check the Event Log section at the bottom:
3. Helium and Ubidots Integration
To push data from the Helium console to Ubidots to develop and deploy IoT applications, you can use a Helium feature called “Integrations.” This is a feature that forwards all the incoming data received to Ubidots using an UbiFunction.
UbiFunction Setup
The UbiFunction will handle Helium's JSON schema to manage both uplink and downlink messages within the platform.
NOTE: The example below will not manage these downlink messages. In case you desire to manage downlink messages, you must handle an HTTP POST request to the URL provided under the key "downlink_url" received in the JSON schema.
Follow the instructions below to create an UbiFunction in your Ubidots account:
Step 1: Click on “Devices > Function”
Step 2: Click the plus icon (“+”) button located in the upper-right corner.
Step 3: Assign a name for your UbiFunction. For example, “Helium Integration.”
Step 4: Select “POST” as the Method
Step 5: Select “NodeJS 10” as the Runtime
Step 6: Leave the “Time-based trigger” option disabled
Step 7: Paste the code below in the UbiFunction Editor
// Import the 'axios' library so we can make HTTP request from the function
var axios = require("axios");
// Define Ubidots constant
const UBI_TOKEN = "BBFF-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const UBI_URL_API_2 = "https://industrial.api.ubidots.com/api/v2.0";
const UBI_URL = "https://industrial.api.ubidots.com/api/v1.6";
// Main function - runs every time the function is executed.
// "args" is a dictionary containing both the URL params and the HTTP body (for POST requests).
async function main(args) {
let data = {};
let appEUI = args["app_eui"];
let payload = args["decoded"]["payload"];
let payloadStatus = args["decoded"]["status"];
let devEUI = args["dev_eui"];
let devAddr = args["devaddr"];
let downlinkUrl = args["downlink_url"];
let nodeId = args["id"];
let organizationId = args["metadata"]["organization_id"];
let name = args["name"];
let payloadBase64 = args["payload"];
let reportedAt = args["reported_at"];
// Check if the device does not exist in Ubidots Platform
let deviceStatus = await ubidotsGetDevice(UBI_TOKEN, devAddr);
// If the device does not exist, create it
if (deviceStatus == 404) {
await ubidotsDeviceCreation(UBI_TOKEN, name, devAddr);
};
// Filter the payload received from helium
const variables = Object.entries(payload).reduce((previous, current) => {
const [key, value] = current;
const varValue = parseFloat(value);
const unit = value.replace(/[\d\.]/g, '')
return [...previous, [key, varValue, unit]];
}, []);
// Create respective custom variables
for (const variable of variables) {
let variableStatus = await ubidotsVariableCreation(UBI_TOKEN, devAddr, variable[0], variable[0], variable[2]);
data[variable[0]] = variable[1]
}
// Send variables' values to Ubidots
let variablesRequest = await ubidotsSendVariableValues(UBI_TOKEN, devAddr, data);
return {"status": variablesRequest};
}
/*
* Create a custom device in Ubidots Platform
* API Documentation: docs.ubidots.com
*
* @arg token [Mandatory], Ubidots account's Token
* @arg deviceName [Mandatory], device's friendly name
* @arg deviceLabel [Mandatory], single and unique label of device
* @arg lat [Optional], latitude corresponding to the device's location
* @arg lng [Optional], longitude corresponding to the device's location
*
* @return response/error, request response
*/
async function ubidotsDeviceCreation(token, deviceName, deviceLabel, lat = 0, lng = 0) {
let endpoint = UBI_URL_API_2.concat("/devices");
let deviceParams = {
"label": deviceLabel,
"name": deviceName
};
if (lat != 0 && lng != 0) {
deviceParams.properties = {
"_location_type":"manual",
"_location_fixed": {
"lat": lat,
"lng": lng
}
};
}
return axios.post(endpoint, deviceParams, {
headers: {
"content-type": "application/json",
"X-Auth-Token": token
}
})
.then(function (response) {
//console.log(response);
})
.catch(function (error) {
//console.log(error.response);
});
}
/*
* Get an existent device in Ubidots Platform
* API Documentation: docs.ubidots.com
*
* @arg token [Mandatory], Ubidots account's Token
* @arg deviceLabel [Mandatory], single and unique label of device
*
* @return response/error, request response
*/
async function ubidotsGetDevice(token, deviceLabel) {
let endpoint = UBI_URL_API_2.concat("/devices/~" + deviceLabel);
return axios.get(endpoint, {
headers: {
"content-type": "application/json",
"X-Auth-Token": token
}
})
.then(function (response) {
return response.statusText;
})
.catch(function (error) {
return error.response.status;
});
}
/*
* Create a custom variable in Ubidots Platform
* API Documentation: docs.ubidots.com
*
* @arg token [Mandatory], Ubidots account's Token
* @arg deviceLabel [Mandatory], single and unique label of device
* @arg variableName [Mandatory], variable's friendly name
* @arg variableLabel [Mandatory], single and unique label of variable
* @arg unit [Mandatory], unit associated to the value of the variable
*
* @return response/error, request response
*/
async function ubidotsVariableCreation(token, deviceLabel, variableName, variableLabel, unit) {
let endpoint = UBI_URL_API_2.concat("/variables");
let variableParams = {
"label": variableLabel,
"name": variableName,
"device": "~".concat(deviceLabel).toLowerCase(),
"unit": unit
};
return axios.post(endpoint, variableParams, {
headers: {
"content-type": "application/json",
"X-Auth-Token": token
}
})
.then(function (response) {
return response.statusText;
})
.catch(function (error) {
return error.response.status;
});
}
/*
* Handle a POST request to Ubidots API
* API Documentation: https://ubidots.com/docs/sw/
*
* @arg token [Mandatory], Ubidots account's Token
* @arg device_label [Mandatory], single and unique label of device
* @arg payload [Mandatory], variables and values to be sent in a JSON format
*
* @return response/error, request response
*/
async function ubidotsSendVariableValues(token, deviceLabel, payload) {
let endpoint = UBI_URL.concat("/devices/" + deviceLabel);
return axios.post(endpoint, payload, {
headers: {
"content-type": "application/json",
"X-Auth-Token": token
}
})
.then(function (response) {
return response.statusText;
})
.catch(function (error) {
return error.response.status;
Step 8: Replace the value of the variable called “UBI_TOKEN” with the Ubidots Token associated with your account.
Step 9: Save the UbiFunction by clicking on the “Make it live” button located in the down-left corner.
Step 10: Copy the URL generated under the “HTTPS Endpoint URL” box.
Once the function has been successfully created, you must observe the following:
Helium Integration Setup
The Helium Integration enables devices to connect to pre-configured, cloud-based applications or send data directly over HTTP or MQTT. For this project, we’re going to be using the HTTP integration.
Follow the instructions below to create an Integration in your Helium console:
Step 1: From the left side navigation, click on “Integrations.”
Step 2: Choose “HTTP” as the custom integration type
Step 3: Under the “HTTP Connection details” option, assign “POST” as a method, and the UbiFunctionHTTPS Endpoint URL as Endpoint.
Step 4: Assign a name for your Helium Integration. For example, “Ubidots Integration.”
Step 5: Assign the “Label” associated with the device you desire to integrate with Ubidots.
Step 6: Save the integration by clicking on the “Create Integration” button.
Once the integration has been successfully created, you must observe the following:
Integration Test
Once you have set up both services, the environmental values sensed by the RAK7402 will be updated once the next data package arrives at the Helium console.
To check if the integration is running successfully, go to the Helium Console and select the "Devices" section. Then, choose any device that contains the label associated with the Ubidots integration, and click on the debug option:
At this point, you can notice the message "Waiting for data..." in the debug console until a new data packet is received. Once received, the following response will be reported in the Integration option:
After selecting the created device, we can see all the environmental variables reported by the RAK7204 WisNode Sense Home.
The last step is to check if the data arrived properly in the Ubidots Platform. Go to the account and click on "Devices > Devices" in this section, we will notice that a new device has been automatically created. After clicking on the device, we can see all the environmental variables reported by the RAK7204 WisNode Sense Home:
In case you have more than one RAK7204 configured in the Helium console, they will also be automatically created in Ubidots, as long as all the devices manage the same label.
Finally, you can customize your Dashboards and application in just a few clicks, please refer to the following guides to learn how.