The Thinxtra devkit Xkit boasts a full suite of features and accessories to empower anyone to develop an IoT solution. Perfect for start-ups, DIY home IoT projects, and academic research, this kit has everything you need to hit the ground running using the Sigfox's global connection network.
The Thinxtra Shield have embedded the following sensors: temperature, pressure, light, shock and 3D accelerometer.
In this article, you will learn how to program an Thinxtra Xkit for an IoT application, sending date from the sensors every 10 minutes.
Requirements
1. Device Setup
1.- Register your devkit on the Sigfox backend portal; reference to the video below to learn more and register your Thinxtra Xkit Device on Sigfox backend.
Download the Thinxtra repository to receive the required libraries. The libraries should be copied to the location with Arduino libraries.
Once the libraries are saved in the Arduino IDE, copy and paste the code below to the IDE:
#include <WISOL.h>
#include <Tsensors.h>
#include <Wire.h>
#include <math.h>
#include <avr/wdt.h>
/**********************************************************
* Constants and definitions
*********************************************************/
typedef union{
float number;
uint8_t bytes[4];
} FLOATUNION_t;
typedef union{
uint16_t number;
uint8_t bytes[2];
} UINT16_t;
typedef union{
int16_t number;
uint8_t bytes[2];
} INT16_t;
Isigfox *Isigfox = new WISOL();
Tsensors *tSensors = new Tsensors();
uint8_t buttonCounter;
const uint8_t buttonPin = A1;
unsigned long my_time;
const unsigned long sendTime = 600000; // Para enviar datos cada 10 minutos.
/**********************************************************
* Auxiliar functions
*********************************************************/
// SendPayload Function => Send messages to the Sigfox Network
void Send_Pload(uint8_t *sendData, int len) {
recvMsg *RecvMsg;
RecvMsg = (recvMsg *)malloc(sizeof(recvMsg));
Isigfox->sendPayload(sendData, len, 0, RecvMsg);
for (int i = 0; i < RecvMsg->len; i++) {
Serial.print(RecvMsg->inData[i]);
}
Serial.println("");
free(RecvMsg);
}
void Send_Sensors(){
UINT16_t tempt, photo, pressure;
INT16_t x_g, y_g, z_g;
acceleration_xyz *xyz_g;
FLOATUNION_t a_g;
// Sending a float requires at least 4 bytes
// In this demo, the measure values (temperature, pressure, sensor) are scaled to ranged from 0-65535.
// Thus they can be stored in 2 bytes
tempt.number = (uint16_t) (tSensors->getTemp() * 100);
Serial.print("Temp: "); Serial.println((float)tempt.number/100);
pressure.number =(uint16_t) (tSensors->getPressure()/3);
Serial.print("Pressure: "); Serial.println((float)pressure.number*3);
photo.number = (uint16_t) (tSensors->getPhoto() * 1000);
Serial.print("Photo: "); Serial.println((float)photo.number/1000);
xyz_g = (acceleration_xyz *)malloc(sizeof(acceleration_xyz));
tSensors->getAccXYZ(xyz_g);
x_g.number = (int16_t) (xyz_g->x_g * 250);
y_g.number = (int16_t) (xyz_g->y_g * 250);
z_g.number = (int16_t) (xyz_g->z_g * 250);
Serial.print("Acc X: "); Serial.println((float)x_g.number/250);
Serial.print("Acc Y: "); Serial.println((float)y_g.number/250);
Serial.print("Acc Z: "); Serial.println((float)z_g.number/250);
Serial.print("\0");
free(xyz_g);
const uint8_t payloadSize = 12; //in bytes
// byte* buf_str = (byte*) malloc (payloadSize);
uint8_t buf_str[payloadSize];
buf_str[0] = tempt.bytes[0];
buf_str[1] = tempt.bytes[1];
buf_str[2] = pressure.bytes[0];
buf_str[3] = pressure.bytes[1];
buf_str[4] = photo.bytes[0];
buf_str[5] = photo.bytes[1];
buf_str[6] = x_g.bytes[0];
buf_str[7] = x_g.bytes[1];
buf_str[8] = y_g.bytes[0];
buf_str[9] = y_g.bytes[1];
buf_str[10] = z_g.bytes[0];
buf_str[11] = z_g.bytes[1];
Serial.println("sending payload");
Send_Pload(buf_str, payloadSize);
// free(buf_str);
}
void checkPress() {
while (digitalRead(buttonPin) != 1) {
buttonCounter++;
delay(50);
if (buttonCounter > 4) break;
}
if (buttonCounter > 4) {
Send_Sensors();
delay(1000);
}
buttonCounter = 0;
}
/**********************************************************
* Main Functions
*********************************************************/
void setup() {
int flagInit;
my_time = millis();
Wire.begin();
Wire.setClock(100000);
Serial.begin(9600);
// WISOL test
flagInit = -1;
while (flagInit == -1) {
Serial.println(""); // Make a clean restart
delay(1000);
flagInit = Isigfox->initSigfox();
Isigfox->testComms();
}
// Init sensors on Thinxtra Module
tSensors->initSensors();
buttonCounter = 0;
Serial.println(""); // Make a clean start
delay(1000);
}
void loop() {
// put your main code here, to run repeatedly:
//checkPress();
if (millis() - my_time > sendTime) {
my_time = millis();
Send_Sensors();
}
}
NOTE: Before uploading the code into the Arduino board (with the Xkit shield plugged into it), you must remove the P9 jumpers on the Xkit shield as described in the below picture or simply disconnect the Xkit shield from Arduino board.
Connect the board to the computer, then select the Arduino UNO as board from Tools > Boards, also select the Port COM of the board from Tools > Port.
Verify your code in the Arduino IDE by choosing the "Check Mark" icon in the top left of the IDE. Once the code is verified, you will receive a "Done compiling" message in the Arduino IDE.
Upload the code into the Arduino UNO by choose the "right-arrow" icon beside the check mark icon. Once uploaded, you will receive a "Done uploading" message in the Arduino IDE.
Connect the P9 jumpers of the Thinxtra shield with the Arduino UNO and reset the board by pressing the red button
The code above sends temperature value, the output voltage from the photovoltaic sensor, the pressure value and the acceleration to Sigfox every 10 seconds.
To visualize the data sent, open the serial monitor of the Arduino IDE by pressing the magnifier icon on the upper right corner.
You can also verify the messages via Sigfox's backend by selecting the ID of the Thinxtra device from the device list.
Next, select the "message" option from the left menu:
With the messages begin received by Sigfox's backend, we notice that all the date package is sent in Hexadecimal format, so in order to be read correctly from Ubidots is necessary to transform it into a json format. This feature can be done by using the UbiFunctions add-on.
2. UbiFunction & Sigfox Backend callback setup.
Before starting any coding an UbiFunction is necessary to parse the payload structure coming from the Thinxtra Xkit, first passing through Sigfox backend.
NOTE: All the images used in this step were taken from the official Thinxtra Xkit documentation.
As seen in the image above, the payload structure is set by 12 bytes where each pair of bytes encode the value of a variable. With that in mind, to encode the data, is necesarry to extract the Little Endian of each 2 bytes, meaning the LSB (Less Significant Bit) is on the left-most side of the frame when read from left to right.
In order to obtain the real value of the measurement, the following operations must be carried out for each variable:
Real Temperature = temperature (2 bytes) / 100 [°C]
Real Pressure = pressure (2 bytes) * 3 [Pa]
Output voltage photovoltaic sensor = photo (2 bytes) / 1000 [V]
x Acceleration = x_Acc (2 bytes) / 250 [g]
y Acceleration = y_Acc (2 bytes) / 250 [g]
z Acceleration = z_Acc (2 bytes) / 250 [g]
Also, it is necessary to configure a callback in the Sigfox Backend to forward the device data to an external system like Ubidots. To successfully complete this step, you must refer to steps 1, 2 and 3 from the guide "UbiFunctions: Manage Uplink messages from the Sigfox Backend to Ubidots," ensuring that you apply the necessary changes shown below:
Erase the default code located in the UbiFunction editor, then copy and paste the below sample code (Python 3) replacing the default code. After pasting, be sure to assign your Ubidots TOKEN where indicated.
import requests
import struct
import json
#Constants
TOKEN = "{PUT_YOUR_TOKEN_HERE}" #Assign your Ubidots TOKEN
BASE_URL = "https://industrial.api.ubidots.com"
#convert the data to string
def decodeHexToJson(data:str):
#convert the string to hex and take the litte endian
#H:ushort, h:short
rawTemperature=struct.unpack("<H",bytearray.fromhex(data[0:4]))
temperature=rawTemperature[0]/100
rawPreassure=struct.unpack("<H",bytearray.fromhex(data[4:8]))
preassure=rawPreassure[0]*3
rawPhoto=struct.unpack("<H",bytearray.fromhex(data[8:12]))
photo=rawPhoto[0]/1000
rawAcelaration_x=struct.unpack("<h",bytearray.fromhex(data[12:16]))
acelaration_x=rawAcelaration_x[0]/250
rawAcelaration_y=struct.unpack("<h",bytearray.fromhex(data[16:20]))
acelaration_y=rawAcelaration_y[0]/250
rawAcelaration_z=struct.unpack("<h",bytearray.fromhex(data[20:24]))
acelaration_z=rawAcelaration_z[0]/250
payload = {
"acelaration_x": acelaration_x,
"acelaration_y": acelaration_y,
"acelaration_z": acelaration_z,
"photo": photo,
"preassure": preassure,
"temperature": temperature
}
return payload
def sendToUbidots(payload:str,device:str):
url = "{}/api/v1.6/devices/{}".format(BASE_URL, device)
headers = {"X-Auth-Token": TOKEN, "Content-Type": "application/json"}
req=requests.post(url,json=payload,headers=headers)
return req
def main(args):
print(args)
#jsonData=json.loads(args)
print(args['data'])
payload = decodeHexToJson(args['data'])
req=sendToUbidots(payload,args['device'])
# Prints the request result
print("[INFO] Request result:")
print(req.text)
return {"status": "Ok", "result": req.json()}
2. Set the parameters below into the callback configuration to be able to handle this Sigfox data with the callback properly:
Type: DATA - UPLINK
Channel: URL
Custom payload config: Leave it in blank
URL pattern: This is the URL generated by the UbiFunction in the field "HTTPS Endpoint URL." You must add the "device" parameter to it. The resulting URL should look like:
https://parse.ubidots.com/prv/YOUR-USERNAME/YOUR_UBIFUNCTION_NAME
Use HTTP method: POST
Content Type: application/json
Body:
{
"data" : "data",
"device" : "device"
}
Once you've entered all the parameters required, verify your callback looks similar to the one below:
3. Sigfox Backend callback setup with custom payload
Another way to send the data successfully to Ubidots is using the callback option custom payload configuration, which parse incoming data and spits out as structured data, the news parameter will be:
Type: DATA - UPLINK
Channel: URL
Custom payload config:
temperature::float:32:little-endian
Url pattern:
Use HTTP method: POST
Content Type: application/json
Body:
{
"device" : "{device}",
"temperature" : "{temperature}",
}
NOTE: The custom payload configuration is just to decode the temperature value.
Once you've entered all the parameters required, verify your callback looks similar to the one below:
IMPORTANT DEPLOYMENT NOTE: Ubidots and Sigfox communicate via either URL or Batch URL (used for large deployments). This tutorial explains the standard URL channel. If you have a large scale sensor network, please contact sales@ubidots.com to receive additional information for Batch URL integrations.
4. Visualize your data in Ubidots.
With the above steps, your device is now integrated with Ubidots and sending data from the Thinxtra Xkit . To visualize your data, sign into your Ubidots account and verify the device just created with the Device ID and see and manipulate the collected data as needed.
IMPORTANT NOTE: If you desire to returns device messages where at least one callback has failed, reference to the Sigfox API to know how to implement it.
Result
Now it is time to create a dashboard to control and manage the variables of your Sigfox device. To learn more about Ubidots widgets and events, check out these video tutorials.
Pro Tip: Ubidots let you modify the name of the device with a friendly one when needed, if you don't know how to modify it reference to the article below: