The Things Stack Industries Integration
The Things Industries (TTI) integration connects ThingsBoard to a TTI LoRaWAN network server: it receives uplink messages from LoRaWAN devices, decodes them via an uplink converter, and pushes telemetry and attributes to the platform. In the reverse direction, it encodes Rule Engine messages via a downlink converter and delivers them to devices through the TTI MQTT API.
The Things Industries setup
Section titled “The Things Industries setup”Register an application
Section titled “Register an application”- Go to the TTI console, open the Applications section, and press Add application.
- Fill in the required fields:
- Application ID — e.g.
thingsboard-integration
- Application ID — e.g.
- Press Create application.
Add a payload decoder
Section titled “Add a payload decoder”Devices submit data in binary format. This tutorial uses a hybrid decoding approach: the TTI payload formatter performs an initial transformation into JSON, and the ThingsBoard uplink converter handles further field extraction and mapping.
- Open the application and go to Payload formatters → Uplink.
- Select Custom Javascript formatter and paste the decoder function below.
- Press Save changes.
function Decoder(bytes, port) { var decoded = { temperature: bytes[0] }; return decoded;}Output JSON:
{ "temperature": 2}Register an end device
Section titled “Register an end device”- In the application, open the End devices page and press Add end device.
- Fill in the basic settings:
- Device ID — e.g.
thermostat1 - DevEUI — unique device identifier
- Device ID — e.g.
- Press Network layer settings and select the configuration for your device.
- Press Application layer settings and generate an AppSKey using the generate button.
- Press Add end device to complete registration.
Configure the TTI MQTT integration
Section titled “Configure the TTI MQTT integration”Create an MQTT integration in the TTI console to obtain the credentials required by ThingsBoard.
- In the TTI console, go to Integrations → MQTT.
- Copy the Username and Password — you will need them when configuring the ThingsBoard integration.
ThingsBoard integration setup
Section titled “ThingsBoard integration setup”Create an uplink converter
Section titled “Create an uplink converter”The uplink converter reads the raw LoRaWAN payload bytes and maps them to ThingsBoard attributes and telemetry. TTI supports all three converter types — Generic (script-based, shown below), Typed, and Library. The Library provides a built-in catalog of ready-to-use decoder functions for over 100 devices.
- Go to Integrations center ⇾ Data converters.
- Click + Add data converter ⇾ Create new converter.
- Set Converter type to Uplink (default).
- Enter a converter name:
TTI Uplink Converter. - Paste the decoder function from the tab below.
- Click Add.
The decoder function used in this tutorial:
/** * Decodes the incoming payload and returns a structured object containing telemetry data and attributes. * * @param {byte[]} input - The raw payload received as an array of bytes. * @returns {Object} output - The structured output with decoded telemetry and attributes. */
function decodePayload(input) { // Initialize the output object with empty attributes and telemetry for clarity. var result = { attributes: {}, telemetry: {}};
// Decode serial number (SN) from the first 4 bytes of the payload. // Press '?' icon in the top right corner to learn more about built in helper functions and capabilities. result.attributes.sn = parseBytesToInt(input, 0, 4);
// Extract the timestamp from metadata (represented in milliseconds). var timestamp = metadata.ts; // ts is the timestamp parsed from the incoming message's time, or returns the current time if it cannot be parsed.
// Initialize an object to store decoded key/value telemetry data. var values = {};
// Decode battery level from the 5th byte of the payload. values.battery = parseBytesToInt(input, 4, 1);
// Decode temperature from the 6th and 7th bytes of the payload (divided by 100). values.temperature = parseBytesToInt(input, 5, 2) / 100.0;
// Decode saturation from the 8th byte of the payload. values.saturation = parseBytesToInt(input, 7, 1);
// Combine the timestamp with values and add it to the telemetry. result.telemetry = { ts: timestamp, values: values };
// Return the fully constructed output object. return result; // Same logic, less code: // return { // attributes: { // sn: parseBytesToInt(input, 0, 4) // }, // telemetry: { // ts: convertDateToTimestamp(extractDateFromMetadata()), // values: { // battery: parseBytesToInt(input, 4, 1), // temperature: parseBytesToInt(input, 5, 2) / 100.0, // saturation: parseBytesToInt(input, 7, 1) // } // } // }; }
var result = decodePayload(payload); // Uncomment this code block to overwrite values set in the main configuration window. Useful if you extract device/asset/customer/group names from the payload; // result.type = 'DEVICE'; // Entity type allows you to choose type of created entity. Can be 'DEVICE' or 'ASSET'. // result.name = 'Temperature Sensor'; // Device or asset name (the value must be unique) // result.profile = 'IndustrialSensorProfile'; // Device or asset profile name. // result.customer = 'MyCustomer'; // If customer is not null - created entity will be assigned to customer with such name. // result.group = 'SensorsGroup'; // If group is not null - created entity will be added to the entity group with such name.
// Return the final result object. return result; /** * Decodes the incoming payload and returns a structured object containing telemetry data and attributes. * * @param {number[]} input - The raw payload received as an array of bytes. * @returns {Object} output - The structured output with decoded telemetry and attributes. */
function decodePayload(input) { // Initialize the output object with empty attributes and telemetry for clarity. var result = { attributes: {}, telemetry: {}};
// Decode serial number (SN) from the first 4 bytes of the payload. // Press '?' icon in the top right corner to learn more about built in helper functions and capabilities. result.attributes.sn = parseBytesToInt(input, 0, 4);
// Extract the timestamp from metadata (represented in milliseconds). var timestamp = metadata.ts; // ts is the timestamp parsed from the incoming message's time, or returns the current time if it cannot be parsed.
// Initialize an object to store decoded key/value telemetry data. var values = {};
// Decode battery level from the 5th byte of the payload. values.battery = parseBytesToInt(input, 4, 1);
// Decode temperature from the 6th and 7th bytes of the payload (divided by 100). values.temperature = parseBytesToInt(input, 5, 2) / 100.0;
// Decode saturation from the 8th byte of the payload. values.saturation = parseBytesToInt(input, 7, 1);
// Combine the timestamp with values and add it to the telemetry. result.telemetry = { ts: timestamp, values: values };
// Return the fully constructed output object. return result; // Same logic, less code: // return { // attributes: { // sn: parseBytesToInt(input, 0, 4) // }, // telemetry: { // ts: convertDateToTimestamp(extractDateFromMetadata()), // values: { // battery: parseBytesToInt(input, 4, 1), // temperature: parseBytesToInt(input, 5, 2) / 100.0, // saturation: parseBytesToInt(input, 7, 1) // } // } // }; }
var result = decodePayload(payload); // Uncomment this code block to overwrite values set in the main configuration window. Useful if you extract device/asset/customer/group names from the payload; // result.type = 'DEVICE'; // Entity type allows you to choose type of created entity. Can be 'DEVICE' or 'ASSET'. // result.name = 'Temperature Sensor'; // Device or asset name (the value must be unique) // result.profile = 'IndustrialSensorProfile'; // Device or asset profile name. // result.customer = 'MyCustomer'; // If customer is not null - created entity will be assigned to customer with such name. // result.group = 'SensorsGroup'; // If group is not null - created entity will be added to the entity group with such name.
// Return the final result object. return result;
/** * Parse a slice of bytes from an array into an integer (big-endian). * * @param {number[]} input - The array of bytes. * @param {number} offset - The starting index. * @param {number} length - The number of bytes to convert. * @returns {number} - The resulting integer. */ function parseBytesToInt(input, offset, length) { var result = 0; for (var i = offset; i < offset + length; i++) { result = (result << 8) | (input[i] & 0xFF); } return result; }The decoder reads raw payload bytes and produces the following fields:
| Byte offset | Length | Output | Notes |
|---|---|---|---|
| 0–3 | 4 | attributes.sn | Serial number (big-endian integer) |
| 4 | 1 | telemetry.battery | Battery level (0–255) |
| 5–6 | 2 | telemetry.temperature | Temperature ÷ 100 (e.g. 2500 → 25.00 °C) |
| 7 | 1 | telemetry.saturation | Saturation (0–255) |
Sample payload received from The Things Industries:
{ "end_device_ids": { "device_id": "thermostat1", "application_ids": { "application_id": "thingsboard-integration" }, "dev_eui": "ABABABABABABABAA", "dev_addr": "270000BC" }, "received_at": "2020-08-11T08:59:45.869Z", "uplink_message": { "f_port": 2, "frm_payload": "AhJF8HTI3khf", "decoded_payload": { "temperature": 2 }, "received_at": "2020-08-11T10:08:31.505Z" }}To adapt this converter to your device:
- Additional telemetry — add more
parseBytesToIntcalls for each sensor field and assign them tovalues. - Attributes — add more keys to
result.attributesfor non-time-series device metadata. - Different payload layout — adjust the byte offsets and lengths to match your device’s payload format.
Create a downlink converter
Section titled “Create a downlink converter”The downlink converter encodes ThingsBoard messages into TTI-compatible payloads. For the full encoder reference, see Downlink data converter. The output must contain a downlinks array with f_port, frm_payload, and priority fields.
Expected output structure:
{ "downlinks": [{ "f_port": 2, "frm_payload": "vu8=", "priority": "NORMAL" }]}The result object must follow this format:
contentType— how data is encoded:TEXT,JSON, orBINARYdata— the actual payload sent to the device in TTImetadata— must includedevIdto identify the target device in TTI
- Go to Integrations center ⇾ Data converters.
- Click + Add data converter ⇾ Create new converter.
- Set Converter type to Downlink.
- Enter a converter name:
TTI Downlink Converter. - Paste the encoder function from the tab below.
- Click Add.
var data = { downlinks: [{ f_port: 2, confirmed: false, frm_payload: btoa(msg.powerState), priority: "NORMAL" }]};
var result = { contentType: "JSON", data: JSON.stringify(data), metadata: { devId: 'thermostat1' }};
return result;var data = { downlinks: [{ f_port: 2, confirmed: false, frm_payload: btoa(msg.powerState), priority: "NORMAL" }]};
var result = { contentType: "JSON", data: JSON.stringify(data), metadata: { devId: 'thermostat1' }};
return result;The encoder reads msg.powerState — the value of the powerState shared attribute set on the device — and base64-encodes it as the LoRaWAN payload. metadata.devId identifies the target device in TTI.
To adapt this converter to your device:
- Different device — replace
thermostat1indevIdwith your device ID. - Different command field — replace
msg.powerStatewith whichever attribute or RPC parameter carries the command value. - Different port — change
f_portto match the port your device listens on.
Create the integration
Section titled “Create the integration”- Go to Integrations center ⇾ Integrations and click + Add integration.
- Basic settings:
- Set Integration type to The Things Industries.
- Enable integration and Allow create devices or assets are on by default.
- Click Next.
- Uplink data converter:
- Select existing — choose the previously created
TTI Uplink Converterfrom the list. - Click Next.
- Select existing — choose the previously created
- Downlink data converter:
- Select existing — choose the previously created
TTI Downlink Converterfrom the list. - Click Next.
- Select existing — choose the previously created
- Connection:
- Host type —
Region. - Region — the region where your TTI application is registered (e.g.
eu1). - Username — the username from the TTI MQTT integration (e.g.
thingsboard-integration@thingsboard). - Password — the password from the TTI MQTT integration.
- Host type —
- Optionally click Check connection to verify connectivity before saving.
- Click Add to complete the integration.
Connection settings
Section titled “Connection settings”Host type
| Value | Description |
|---|---|
| Region | Connect to a standard TTI cluster. Enter the Region identifier (e.g. eu1) — ThingsBoard constructs the host as {region}.cloud.thethings.industries. |
| Custom | Specify a fully custom broker hostname (e.g. for a private TTI deployment). |
Application Credentials
| Parameter | Description |
|---|---|
| Username | The TTI Application ID and tenant as shown in the TTI MQTT integration (e.g. thingsboard-integration@thingsboard). |
| Password | The API access key or password from the TTI MQTT integration. |
Topic filters
Defines which MQTT topics ThingsBoard subscribes to for uplink messages. The default filter v3/+/devices/+/up matches all device uplinks in the application.
| Field | Description |
|---|---|
| Topic | MQTT topic filter. + matches a single topic level. |
| QoS | 0 — at most once; 1 — at least once; 2 — exactly once |
Downlink topic pattern
MQTT topic used for publishing downlink messages. Default: v3/{appId}@{tenantId}/devices/${devId}/down/push. ${devId} is resolved from the devId field in the downlink converter output metadata.
Execute remotely
When enabled, ThingsBoard generates an Integration key and Integration secret that allow the integration to run as a separate process outside the ThingsBoard cluster — useful when the TTI broker is only accessible from a local network.
Advanced settings
| Parameter | Default | Description |
|---|---|---|
| Connection timeout (sec) | 10 | Seconds ThingsBoard waits for a broker response before marking the connection as failed. |
| Description | — | Optional text description for the integration. |
| Metadata | — | Key-value pairs injected into every message as integrationMetadata in converter scripts. |
Test uplink
Section titled “Test uplink”When a device sends data, ThingsBoard automatically creates the device and stores its telemetry.
- Power on your TTI device or use the TTI console to simulate an uplink.
- Go to Entities ⇾ Devices in ThingsBoard.
- Open the provisioned device and click the Latest Telemetry tab — you should see
temperatureand other decoded fields.
The device is created automatically once the first uplink is received. In this tutorial the device is named Thermostat A and the temperature value equals 2.
Configure and test downlink
Section titled “Configure and test downlink”To send a command to a TTI device from ThingsBoard, the Root Rule Chain must forward attribute changes to the TTI integration.
Configure the Root Rule Chain
Section titled “Configure the Root Rule Chain”- Open Rule Chains ⇾ Root Rule Chain and click the edit icon.
- Find the Integration Downlink node in the node panel and drag it onto the canvas.
- Set the Name (e.g.
TTI Downlink), select TTI Integration, and click Add. - Connect the Message Type Switch node to the Integration Downlink node using the Attributes Updated relation. The Attributes Updated relation fires whenever a shared attribute is created or changed — this is what triggers the downlink.
- Click Apply changes to save the rule chain.
Test the downlink
Section titled “Test the downlink”Trigger a downlink by adding a shared attribute to the ThingsBoard device provisioned during the uplink test:
- Go to Entities ⇾ Devices, open device Thermostat A, and click the Attributes tab.
- Switch to Shared attributes and click + to add a new attribute.
- Enter the key
powerStateand valueon, then click Add.
Adding the attribute triggers a downlink to TTI device thermostat1 with the powerState value base64-encoded in the payload (on → b24= → bytes 6F 6E). Open the TTI console, navigate to thermostat1 → Data — the downlink event appears in the list.