ThingsBoard: Pulling Data From Multiple Devices

ThingsBoard allows you to pull the latest telemetry from a single device via the API, however if you want to pull that data into another system (e.g. a small display screen or to analyse results from multiple sensors), you have to make a different API call for each device, and also store the device ID for each in the place you are pulling the data from.

Wouldn’t it be useful if there was a way to pull the latest telemetry from multiple devices with one API call? If you write your client code in a generic way, you could dynamically add and remove devices without changing the code at all.

In an ideal world you could pull a JSON object similar to the following:

{
 "deviceOne" : {
         "field1" : 15,
         "field2" : 19
  },
 "deviceTwo" : {
         "field1" : 42,
         "field2" : 394
  },

This is possible in ThingsBoard using Assets to group devices.

Step 1: Set up two tests devices

Create two test devices in ThingsBoard and start posting telemetry from a script, as described at the start of ThingsBoard: Setting up mail alerts. Having a regular set of incoming data allows us to see instantly if this is working. Make two copies of the script, one for each test device, but also edit the script to send a second field (Temp), which we will just fix to one value:

curl -v -X POST -d "{\"$NAME\": $v, \"Temp\": 25}" http:/..........

Step 2: Create an asset

When we look at telemetry from the API, the documentation states we can use the API key:

/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries

On the Entities And Relations page, the different possible entity types are listed. Two of those listed are Device and Asset. A single device is no good if we want multiple devices, so we need to combine the two test (or more when live) devices together to form an asset. There is a guide for doing similar at Data Function based on telemetry from 2 devices. This is not quite what we want, but we can adapt the principals for our needs.

In the Asset section, create a test asset, with the name and type ‘testAsset’. Under relations for this asset, select Outbound relations by picking From, relation type Contains and then select Device. You can now add the two test devices we have created.

Once saved, if you look a the latest telemetry, nothing appears. We need to populate this asset with telemetry by creating a rule chain.

Step 3: Create a rule chain

In Rule Chains, create a new chain called copyMultipleToAsset, set a meaningful description and turn on debugging. Before we do anything else with this, we want to start sending telemetry data to the new chain.

Open the Root rule chain and follow the chain Message Type Switch->Post telemetry->Save Timeseries. Add the new rule chain as a node and link it to Save Timeseries with the label Success. It does not matter if there is already a link on Success, adding another will duplicate the messages.

At this stage we have all devices sending telemetry. While we will filter these later to just the asset type, copying the data from a device to an asset can create a problem if they are both the same type of device. Both have the same keys, in the case of my test scripts, this is ‘soilPC’ and ‘Temp’. What data relates to what device? Will one field overwrite another as the latest? It would be nice if when we pull the ThingsBoard data we could get nested JSON with each device on the first level then each telemetry key underneath.

Create a script node with the following code:

var newMsg = {};
newMsg[metadata.deviceName]= {};

for(var key in msg) {
    newMsg[metadata.deviceName][key]=msg[key];
}

return {msg: newMsg, metadata: metadata, msgType: msgType};

Name it “addDevToKeys” and join this to Input. If you save the rule chain, you can check data is coming in and being transformed using the debugging events.

Next we need to only filter events for devices connected to our testAsset. Create a Check Relation filter called testAssetFilter. Set the direction to ‘From’, Type ‘Asset’, then select testAsset. Again select debug, and do the same for all nodes in this chain. Link this to the addDevToKeys script, with the label “Success”. We can add other Check Relation filters for other Assets later, by creating identical notes and also joining these with “Success”.

Add a Change Originator block, and call this ChangeToTestAsset. Originator Source: Related. Set the Relation filters to Contains, Entity Types: Asset. Join this to the Check Relation filter with the label “True”.

Finally create a Save Timeseries node, call it Save Timeseries and join it to the Change Originator block with “Success”. Save the chain.

The completed rule chain. This also shows the check relation filter added for our next asset.

Step 4: Verify

Go to Assets, pick the testAsset and inspect the latest telemetry. You should see data appear from both devices.

Following the information on my post ThingsBoard: Latest Telemetry From The API, you can check this can be obtained from the API with a command similar to:

$ curl -v -X GET http://s<SERVER>:<PORT>/api/plugins/telemetry/ASSET<ASSET IT>/values/timeseries --header "Accept:application/json" --header "X-Authorization: Bearer xxxx"

{
 "TestDevice":[{"ts":1605462644325,
      "value":"{\"TestDevice-soilPC\":40,\"TestDevice-Temp\":25}"}],
 "TestDevice2":[{"ts":1605461450186,
      "value":"{\"TestDevice2-soilPC\":49,\"TestDevice2-Temp\":25}"}]
}

We can now parse this and either access individual values using the key Device:Key or grab everything as a JSON block. Note the device types do not have to be the same.

Note that one flaw with this is, the data dump from each device is encoded in the value field as a string, this is not true JSON. You do need to pull out the value and then decode that, which can be a pain.

Alternate data model

An alternate is to keep the flat data structure, but for each device, change the key to be deviceName-key. This allows values to be graphed etc. To do this, change the telemetry transformation script to the script below. You could even combine them to have the best of both.

var newMsg = {};
newMsg[metadata.deviceName]= {};

for(var key in msg) {
    var newKey=metadata.deviceName+"-"+key;
    newMsg[newKey]=msg[key];
}

return {msg: newMsg, metadata: metadata, msgType: msgType};

Leave a Reply

Your email address will not be published. Required fields are marked *