Stand with Ukraine flag
Try it now Pricing
Professional Edition
Community Edition Professional Edition Cloud Edge PE Edge IoT Gateway License Server Trendz Analytics Mobile Application PE Mobile Application MQTT Broker
Getting Started
Devices Library Guides Installation Architecture API FAQ
On this page

ChirpStack Integration

Doc info icon
ThingsBoard PE Feature

Only Professional Edition supports Platform Integrations feature.
Use ThingsBoard Cloud or install your own platform instance.

The ChirpStack open-source LoRaWAN Network Server stack provides open-source components for LoRaWAN networks. After integrating ChirpStack with ThingsBoard, you can connect, communicate, process and visualize data from devices in the ThingsBoard IoT platform.

Prerequisites

In order to get data you should have configured instance of ChirpStack Network server stack. In this guide we will use the configured local instance, installed by docker compose. Click to learn, how to install ChirpStack Network server stack using docker compose.

Also, you must connect the device. How to connect it you can find in connection guide from the official site.

Add ChirpStack integration

1. Basic settings.

Go to the “Integrations” page of the “Integrations center” section. Click “plus” button to start adding new integration. Select type “ChirpStack” integration and click “Next”;

Doc info icon

Note: While debug mode is very useful for development and troubleshooting, leaving it enabled in production mode can significantly increase the disk space used by the database since all debug data is stored there. After debugging is complete, it is highly recommended turning off debug mode.

image


2. Uplink data converter.

Uplink is necessary in order to convert the incoming data from the device into the required format for displaying them in the ThingsBoard. Click on the “plus” and on “Create new converter”. To view the events, use “Debug mode”.

In the function decoder field, specify a script to parse and transform data. For our example, use the default decoder function (or use your own configuration) when adding the integration. Then, click “Next”;

One can use either TBEL (ThingsBoard expression language) or JavaScript to develop user defined functions. We recommend utilizing TBEL as it’s execution in ThingsBoard is much more efficient compared to JS.

image

The script used in the example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
var data = decodeToJson(payload);
var deviceName = data.deviceInfo.deviceName;
var deviceType = data.deviceInfo.deviceProfileName;
var groupName = 'IAQ devices';
// var customerName = 'Customer A';
// use assetName and assetType instead of deviceName and deviceType
// to automatically create assets instead of devices.
// var assetName = 'Asset A';
// var assetType = 'building';

// If you want to parse incoming data somehow, you can add your code to this function.
// input: bytes
// expected output:
//  {
//    "attributes": {"attributeKey": "attributeValue"},
//    "telemetry": {"telemetryKey": "telemetryValue"}
//  }
//
// In the example - bytes will be saved as HEX string and also parsed as light level, battery level and PIR sensor value.
//

function decodePayload(input) {
var output = { attributes:{}, telemetry: {} };
// --- Decoding code --- //

    output.telemetry.HEX_bytes = bytesToHex(input);

    // If the length of the input byte array is odd - we cannot parse it using the example below
    if (input.length > 0) {
        for (var i = 0; i < input.length; ) {
            var channel_id = input[i++];
            if (i < input.length) {
                var channel_type = input[i++];
                // BATTERY
                if (channel_id === 0x01 && channel_type === 0x75) {
                    output.telemetry.battery = input[i];
                    i += 1;
                }
                // PIR
                else if (channel_id === 0x03 && channel_type === 0x00) {
                    output.telemetry.pir = input[i] === 0 ? "normal" : "trigger";
                    i += 1;
                }
                // DAYLIGHT
                else if (channel_id === 0x04 && channel_type === 0x00) {
                    output.telemetry.daylight = input[i] === 0 ? "dark" : "light";
                    i += 1;
                }
            }
        }
    }

    // --- Decoding code --- //
    return output;
}

// --- attributes and telemetry objects ---
var telemetry = {};
var attributes = {};
// --- attributes and telemetry objects ---

// --- Timestamp parsing
var dateString = data.time;
var timestamp = -1;
if (dateString != null) {
  timestamp = new Date(dateString).getTime();
  if (timestamp == -1) {
    var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;
    var millisecondsEndIndex = dateString.lastIndexOf('+');
    if (millisecondsEndIndex == -1) {
      millisecondsEndIndex = dateString.lastIndexOf('Z');
    }
    if (millisecondsEndIndex == -1) {
      millisecondsEndIndex = dateString.lastIndexOf('-');
    }
    if (millisecondsEndIndex == -1) {
      if (dateString.length >= secondsSeparatorIndex + 3) {
        dateString = dateString.substring(0, secondsSeparatorIndex + 3);
      }
    } else {
      dateString = dateString.substring(0, secondsSeparatorIndex + 3) +
        dateString.substring(millisecondsEndIndex, dateString.length);
    }
    timestamp = new Date(dateString).getTime();
  }
}
// If we cannot parse timestamp - we will use the current timestamp
if (timestamp == -1) {
timestamp = Date.now();
}
// --- Timestamp parsing

// You can add some keys manually to attributes or telemetry
attributes.deduplicationId = data.deduplicationId;

// You can exclude some keys from the result
var excludeFromAttributesList = ["deviceName", "rxInfo", "confirmed", "data", "deduplicationId","time", "adr", "dr", "fCnt"];
var excludeFromTelemetryList = ["data", "deviceInfo", "txInfo", "devAddr", "adr", "time", "fPort", "region_common_name", "region_config_id", "deduplicationId"];

// Message parsing
// To avoid paths in the decoded objects we passing false value to function as "pathInKey" argument.
// Warning: pathInKey can cause already found fields to be overwritten with the last value found.

var telemetryData = toFlatMap(data, excludeFromTelemetryList, false);
var attributesData = toFlatMap(data, excludeFromAttributesList, false);

var uplinkDataList = [];

// Passing incoming bytes to decodePayload function, to get custom decoding
var customDecoding = decodePayload(base64ToBytes(data.data));

// Collecting data to result
if (customDecoding.?telemetry.size() > 0) {
  telemetry.putAll(customDecoding.telemetry);
}

if (customDecoding.?attributes.size() > 0) {
  attributes.putAll(customDecoding.attributes);
}

telemetry.putAll(telemetryData);
attributes.putAll(attributesData);

var result = {
    deviceName: deviceName,
    deviceType: deviceType,
    //  assetName: assetName,
    //  assetType: assetType,
    //  customerName: customerName,
    groupName: groupName,
    attributes: attributes,
    telemetry: {
    ts: timestamp,
    values: telemetry
    }
};

return result;

image

The script used in the example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// Decode an uplink message from a buffer
// payload - array of bytes
// metadata - key/value object

/** Decoder **/

// decode payload to string
var payloadStr = decodeToString(payload);

// decode payload to JSON
// var data = decodeToJson(payload);

var deviceName = 'Device A';
var deviceType = 'thermostat';
var customerName = 'Customer C';
var groupName = 'thermostat devices';
var manufacturer = 'Example corporation';
// use assetName and assetType instead of deviceName and deviceType
// to automatically create assets instead of devices.
// var assetName = 'Asset A';
// var assetType = 'building';

// Result object with device/asset attributes/telemetry data
var result = {
// Use deviceName and deviceType or assetName and assetType, but not both.
  deviceName: deviceName,
  deviceType: deviceType,
// assetName: assetName,
// assetType: assetType,
// customerName: customerName,
  groupName: groupName,
  attributes: {
    model: 'Model A',
    serialNumber: 'SN111',
    integrationName: metadata['integrationName'],
    manufacturer: manufacturer
  },
  telemetry: {
    temperature: 42,
    humidity: 80,
    rawData: payloadStr
  }
};

/** Helper functions **/

function decodeToString(payload) {
  return String.fromCharCode.apply(String, payload);
}

function decodeToJson(payload) {
  // covert payload to string.
  var str = decodeToString(payload);

  // parse string to JSON
  var data = JSON.parse(str);
  return data;
}

return result;

You can always change the decoder function after creating it.


3. Downlink data converter.

At the step of adding a downlink converter, you can also select a previously created or create a new downlink converter. But for now, leave the “Downlink data converter” field empty. Click “Skip”;

image


4. Connection.

To complete adding integration, you need to:

  • Specify your “Base URL”;
  • Note down “HTTP endpoint URL” we will use this value later;
  • Specify “Application server URL” - address of application server or REST API service. Usually, with a standard installation, only the port is changed to 8090;
  • Specify “Application server API Token” - taken from the application server. To get its we need to open ChirpStack application server UI, navigate to the “API keys” page from the left top menu and create new an API key.

Finally, click “Add” button to complete adding the ChirpStack integration.

image

Configure integration on your ChirpStack application

In order for data to be transferred from ChirpStack to ThingsBoard, you need to configure an integration in your ChirpStack application.

To create integration on ChirpStack Network server stack, we need to do the following steps:

  • Go to the “Applications” page in the left menu of the ChirpStack Network server user interface, and click “Add application” button;
  • Named it and click “Submit” button;
  • Application created. Now, navigate to the “Integrations” tab;
  • Find and add a HTTP integration by clicking “+” icon;
  • Fill in the field with the “HTTP endpoint URL” previously copied from the ChirpStack integration in the ThingsBoard. Then, click “Submit” button.

HTTP integration created.

When your device sends an uplink message, a new device will appear in the ThingsBoard user interface.

You will receive an uplink event in the ChirpStack integration.

Received data can be viewed in the uplink converter. In the “In” and “Out” blocks of the “Events” tab:


Use the Dashboards to work with data. Dashboards are a modern format for collecting and visualizing data sets. Visibility of data presentation is achieved through a variety of widgets.
ThingsBoard has examples of several types of dashboards that you can use. Learn more about Solution templates here.

For sending downlink messages from the Thingsboard to the device, we need to define a downlink converter. You can customize the downlink according to your configuration.

Let’s consider an example where we send an attribute update message.

One can use either TBEL (ThingsBoard expression language) or JavaScript to develop user defined functions. We recommend utilizing TBEL as it’s execution in ThingsBoard is much more efficient compared to JS.

You can use our example of downlink converter, or write your own according to your configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Encode downlink data from incoming Rule Engine message

// msg - JSON message payload downlink message json
// msgType - type of message, for ex. 'ATTRIBUTES_UPDATED', 'POST_TELEMETRY_REQUEST', etc.
// metadata - list of key-value pairs with additional data about the message
// integrationMetadata - list of key-value pairs with additional data defined in Integration executing this converter

/** Encoder **/

// Result object with encoded downlink payload
var result = {

    // downlink data content type: JSON, TEXT or BINARY (base64 format)
    contentType: "TEXT",

    // downlink data
    data: btoa(msg.downlink),

    // Optional metadata object presented in key/value format
    metadata: {
            DevEUI: metadata.cs_devEui,
            fPort: metadata.cs_fPort
    }

};

return result;

To add the downlink converter to the integration, follow this steps:

  • Go to the “Integrations” page, click ChirpStack integration to open its details, and enter integration editing mode by clicking the “pencil” icon;

  • Enter a name for the downlink data converter and click “Create new converter”;

  • Paste the script to the encoder function section, and click “Add”;

  • Apply changes.

You can use our example of downlink converter, or write your own according to your configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Encode downlink data from incoming Rule Engine message

// msg - JSON message payload downlink message json
// msgType - type of message, for ex. 'ATTRIBUTES_UPDATED', 'POST_TELEMETRY_REQUEST', etc.
// metadata - list of key-value pairs with additional data about the message
// integrationMetadata - list of key-value pairs with additional data defined in Integration executing this converter

/** Encoder **/

// Result object with encoded downlink payload
var result = {

    // downlink data content type: JSON, TEXT or BINARY (base64 format)
    contentType: "TEXT",

    // downlink data
    data: btoa(msg.downlink),

    // Optional metadata object presented in key/value format
    metadata: {
            DevEUI: metadata.cs_devEui,
            fPort: metadata.cs_fPort
    }

};

return result;

To add the downlink converter to the integration, follow this steps:

  • Go to the “Integrations” page, click ChirpStack integration to open its details, and enter integration editing mode by clicking the “pencil” icon;

  • Enter a name for the downlink data converter and click “Create new converter”;

  • Paste the script to the encoder function section, and click “Add”;

  • Apply changes.

You can add a downlink converter when creating or editing an integration.

Modify Root Rule Chain

In order to send downlink, we’ll use the rule chain to process shared attribute update. Let’s import this rule chain:

  • Download the downlink_to_chirpstack.json file;
  • Go to the “Rule Chains” page. To import this JSON file, click the “+” icon in the upper right corner of the screen and select “Import rule chain”;
  • Drag the downloaded JSON file into the import rule chain window. Click “Import”;
  • The “Downlink to Chirpstack” rule chain will open. Double-click on the “integration downlink” node, specify ChirpStack integration in the “Integration” field and save changes;
  • Save rule chain by pressing on checkmark.

Now you need to configure the Root Rule Chain:

  • Open the "Root Rule Chain", and find a "check relation presence" node;
  • Drag it to the rule chain. Name it "Check relation to ChirpStack integration", select the direction - "To originator", specify "ManagedByOriginator" relation type. Specify ChirpStack integration and click "Add";
  • Tap on a right grey circle of "message type switch" node and drag this circle to the left side of "check relation presence" node. Here, add the "Attributes Updated" link, and click "Add";
  • Find a "rule chain" node;
  • Drag it to the rule chain. Name it "Downlink to Chirpstack", specify "Downlink to Chirpstack" rule chain, and click "Add";
  • Tap on the right grey circle of the "check relation presence" node and drag this circle to left side of “rule chain” node. Here, select the "True" link, and click "Add". Finally, save Root Rule Chain.

A downlink message will be sent to the integration when an attribute is added or modified.

To see this with an example, we go to the “Devices” page. Select your device and navigate to the “Attributes” tab. Select “Shared attributes” and click on the “plus” icon to add new attribute. Then enter the attribute name and its value (for example, the key name is ‘downlink’, value: ‘01040203’) and click “Add”.

Received data and data that was sent can be viewed in the downlink converter. In the “In” block of the “Events” tab, we see what data entered and the “Out” field displays messages to device:

Next steps