Sensoterra is the low-cost, robust and fully wireless solution to real-time soil moisture measurement for agriculture, horticulture, nature restoration, and landscaping.

In this tutorial, we will demonstrate how to integrate Sensoterra’s Soil Moisture probes with Ubidots using the UbiFunction addon.

Step-by-Step

  1. Requirements
  2. Creating an UbiFunction
  3. Coding an UbiFunction
  4. Verifying Data Reception
  5. Debugging and Logs
  6. Summary

1. Requirements:

  • Sensoterra account to have access your probes via API.
  • Soil Moisture probe registered and sending data to your Sensoterra’s account.
  • An active Ubidots account with UbiFunctions Add-on.

2. Creating an UbiFunctions

To create a serverless function to parse data from Sensoterra to Ubidots you need to have an active Ubidots subscription. Go to the "Data" tab and select "Function" and create a new function.

Create a new function (click the "+" icon) and give the new Function a descriptive name that resembles the parse function to be executed. In this case we called it "Sensoterra". In the HTTPS Endpoint URL, select "POST" as the method to trigger the function and set the Time based trigger to 60 minutes, then click on ‘Make it live’ button:

NOTE: if your account does not have ‘Time based trigger’ as the image below, please contact support@ubidots.com, Ubidots support team to enable this feature in your account. This trigger feature will run the function automatically every X minutes to retrieve latest data from Sensoterra’s servers through the API.

3. Coding an UbiFunction

The sample code below has 5 functions: 

  • A main function that manage the other 4 functions which handle authentication with Sensoterra, the get of the API Key, the get of the available Soil Moisture probes, and builds a JSON payload suitable with Ubidots REST API. Then the functions completes by making a POST request to Ubidots, updating any device or variable information in the process. Also, main will also parse the data coming from Sensoterra list of available probes to be understood by Ubidots.
  • A getApiKey function: authenticates with Sensoterra’s API using the account credential and retrieves and API Key valid for 30 minutes.
  • A getProbes function that uses the above Sensoterra’s API Key to retrieve a list of the available Soil Moisture probes within the account.
  • A buildJson function which structures the data parsed in main function according to Ubidots REST API.
  • A ubidotsPost function, which uses the specified TOKEN, device_label and a JSON payload as an input to make an HTTP POST request to Ubidots' API.

Using the above logic, we obtain the ability to retrieve soil moisture data from a Sensoterra account with registered probes, parse the retrieved data, and then post said data to Ubidots.

/* MODULES */
var request = require('request-promise');

/* VARIABLES */
var lat, lng, status, signal_strength, post_response, payload;

/* CONSTANTS */
const URL_SENSOTERRA = 'https://monitor.sensoterra.com/api/v3';
const EMAIL_SENSOTERRA = 'PUT-SENSOTERRA-EMAIL-HERE'; // Username
const PASSWORD_SENSOTERRA = 'PUR-SENSOTERRA-PASSWORD-HERE'; // Password
const LIMIT = 50; // Number of probes to retrieve from Sensoterra
const SKIP = 0; // Number of probes to skip from Sensoterra
const TOKEN_UBIDOTS = 'PUT-YOUR-UBIDOTS-TOKEN-HERE';
const UBIDOTS_VARIABLE_LABEL_SOIL_MOISTURE= 'soil-moisture';
const UBIDOTS_VARLABEL_GPS = 'location';

/* Get an API Key to further use the Sensoterra API. Expires after 30 minutes */
async function getApiKey(endpoint, username, password){
    var url_auth = endpoint + '/customer/auth';
    var options = {
        method: 'POST',
        url: url_auth,
        body: {"email": username, "password": password},
        json: true,
        headers: {
            'accept': 'application/json',
            'language': 'en',
            'Content-Type': 'application/json'
        }
    };
    return await request.post(options);
}

/*Get*/
async function getProbes(endpoint, key, limit, skip){
    var url_probes = endpoint + '/probe?limit=' + limit + '&skip=' + skip;
    var options_probes = {
        method: 'GET',
        url: url_probes,
        json: true,
        headers: {
            'accept': 'application/json',
            'language': 'en',
            'api_key': key
        }
    };
    return await request.get(options_probes);
}

/* Post data to Ubidots */
async function ubidotsPost(data, device_label, token){
    token = token || TOKEN_UBIDOTS;
    var options = {
        method: 'POST',
        url: 'https://industrial.api.ubidots.com/api/v1.6/devices/sensoterra' + device_label,
        body: data,
        json: true,
        headers: {
            'Content-Type': 'application/json',
            'X-Auth-Token': token
        }
    };
    return await request.post(options);
}

/* Build JSON with Soil Moisture, GPS location, signal strength variables */
function buildJson(variable_lb, reading, datetime){
    var payload = {
        [variable_lb]: {
            value: reading,
            timestamp: datetime,
            context: {
                status: status
            }
        },
        location: {
            value: 1,
            timestamp: datetime,
            context: {
                lat: lat,
                lng: lng
            }
        },
        signal_strength: {
            value: signal_strength,
            timestamp: datetime
        }
    };
    return payload;
}

async function main(params) {
    /* Getting an API Key for auth */
    var response = await getApiKey(URL_SENSOTERRA, EMAIL_SENSOTERRA, PASSWORD_SENSOTERRA);
    var apiKey = response['api_key']; // Get API key from response.
    /* Get last value from registered probes on Sensoterra */
    var probes = await getProbes(URL_SENSOTERRA, apiKey, LIMIT, SKIP);
    //console.log(probes); // Print probes
    var size = Object.keys(probes).length; // Size of the retrieved JSON file
    for(var i = 0; i < size; i++){
        var device_label = probes[i]['id'];
        var value = probes[i]['status']['last_reading']; //last value
        var timestamp = new Date(probes[i]['status']['last_update']).valueOf(); //in milliseconds
        lat = probes[i]['latitude'];
        lng = probes[i]['longitude'];
        status = probes[i]['status']['code'];
        signal_strength = probes[i]['status']['signal_strength'];
        /* Build JSON */
        payload = buildJson(UBIDOTS_VARIABLE_LABEL_SOIL_MOISTURE, value, timestamp);
        /* Post to Ubidots */
        post_response = await ubidotsPost(payload, device_label);
    }
    return post_response;
}

3. Verifying Data Reception

After you function executes the first time, data from the Sensoterra device will POST to Ubidots and be displayed similar to that of the below:
Note: remember that with Ubidots, whenever a new device label is identified a new device will be automatically created. 

And this is how it looks once inside a device, that is, the variable window:

5. Debugging and Logs

Also, a great debugging resource is the ability to view the logs of your function. To do so, refer to the control bar and select the "logs" icon as shown below to view the history of your Function's executions:

In the Function's logs, you can confirm data was successfully posted to Ubidots with the 201 response code:

6. Summary  

I just a couple of minutes, you successfully integrated your Sensoterra sensor to stream live data to Ubidots where the data can go to work in your Ubidots powered App. Now it's time to assemble dashboards and add users who can see and engage with your Apps' data. :) 

Want to share a helpful UbiFunction to teach others how to connect the dots? Feel free to post it at community.ubidots.com and help another IoT innovator achieve their goals.

Other developers also found helpful: 

Did this answer your question?