TCP Integration
TCP Integration lets devices send data to ThingsBoard over raw TCP connections. The integration runs a TCP server and splits the incoming byte stream into messages. An uplink data converter transforms each message into device or asset data, including telemetry and attributes. ThingsBoard can also send a downlink message back over the device’s open connection.
Use TCP Integration when:
- Devices use a custom text or binary protocol over raw TCP.
- Device firmware already connects to a configurable host and port.
- Devices keep a long-lived connection and must receive commands over it.
Architecture
Section titled “Architecture”- Receive data. A device opens a TCP connection to the remote integration and sends data. The handler (Text, JSON, or Binary) splits the byte stream into separate messages.
- Decode the payload. The uplink converter identifies the device or asset in each message and extracts attributes and telemetry.
- Process the message. ThingsBoard processes the converted data through the Rule Engine. If Allow create devices or assets is enabled, ThingsBoard can create an entity that does not exist yet.
- Send downlink data (optional). The downlink converter encodes the Rule Engine message, and the integration writes it to the device’s open connection, followed by
\n. If the device is not connected, the downlink waits for its next uplink.
Prerequisites
Section titled “Prerequisites”- Access to ThingsBoard Cloud with integrations enabled for your tenant.
- Permission to create integrations and data converters.
- A host for the remote integration. Devices must reach it on the TCP port (default
10560), and it needs outbound access to ThingsBoard’s gRPC port9090. - The payload format and framing your devices use.
nc(netcat) to send test data.
Set Up TCP Integration
Section titled “Set Up TCP Integration”Create the uplink converter, assign it to a new TCP integration, start the remote integration, and send a test message.
Sample Payloads
This guide uses device SN-002, which sends temperature and humidity in one of three formats. Pick one format and use the matching decoder, handler, and test command throughout.
Comma-separated records, one per line:
SN-002,default,temperature,25.7\nSN-002,default,humidity,69{ "deviceName": "SN-002", "deviceType": "default", "temperature": 25.7, "humidity": 69}A 25-byte message with a 1-byte length field:
\x30\x30\x30\x30\x11\x53\x4e\x2d\x30\x30\x32\x64\x65\x66\x61\x75\x6c\x74\x32\x35\x2e\x37\x00\x00\x00| Bytes | Hex | Value |
|---|---|---|
| 0–3 | \x30\x30\x30\x30 |
Header, stripped by the handler |
| 4 | \x11 |
Length field: 17 bytes of payload follow |
| 5–10 | \x53\x4e\x2d\x30\x30\x32 |
Device name: SN-002 |
| 11–17 | \x64\x65\x66\x61\x75\x6c\x74 |
Device type: default |
| 18–21 | \x32\x35\x2e\x37 |
Temperature: 25.7 |
| 22–24 | \x00\x00\x00 |
Padding, outside the frame |
This message needs these Binary handler settings:
| Parameter | Value |
|---|---|
| Max Frame Length (in bytes) | 128 |
| Length Field Offset | 4 |
| Length Field Length | 1 |
| Length Adjustment | 0 |
| Number of first bytes to strip out from the decoded frame | 5 |
| Byte Order of the length field | Little Endian |
Create the Uplink Data Converter
Section titled “Create the Uplink Data Converter”The uplink converter reads one framed message and maps it to ThingsBoard entities and data. TCP Integration uses the Generic converter: you write the decoder in TBEL or JavaScript. For converter types and the full decoder reference, see Uplink data converter.
-
Go to Integrations ⇾ Data converters and click + Add data converter ⇾ Create new converter.
-
Converter type — keep Uplink.
-
Integration type — select TCP.
-
Name — enter a name, for example
TCP Uplink Converter. -
Main decoding configuration — the editor is pre-filled with the default decoder template for JSON payloads. Replace it with the decoder for your payload format below and, if needed, adapt the field names to your device. The editor opens in TBEL; use the TBEL / JS toggle to switch to JavaScript.
/** Decoder **/// decode payload to stringvar strArray = decodeToString(payload);var payloadArray = strArray.replaceAll("\"", "").replaceAll("\\n", "").split(',');var telemetryPayload = {};for (var i = 2; i < payloadArray.length; i = i + 2) {var telemetryKey = payloadArray[i];var telemetryValue = parseFloat(payloadArray[i + 1]);telemetryPayload[telemetryKey] = telemetryValue;}// Result object with device attributes/telemetry datavar result = {deviceName: payloadArray[0],deviceType: payloadArray[1],telemetry: telemetryPayload,attributes: {}};/** Helper functions 'decodeToString' and 'decodeToJson' are already built-in **/return result;/** Decoder **/// decode payload to stringvar strArray = decodeToString(payload);var payloadArray = strArray.replace(/"/g, "").replace(/\s/g, "").split(',');var telemetryPayload = {};for (var i = 2; i < payloadArray.length; i = i + 2) {var telemetryKey = payloadArray[i];var telemetryValue = parseFloat(payloadArray[i + 1]);telemetryPayload[telemetryKey] = telemetryValue;}// Result object with device attributes/telemetry datavar result = {deviceName: payloadArray[0],deviceType: payloadArray[1],telemetry: telemetryPayload,attributes: {}};/** Helper functions **/function decodeToString(payload) {return String.fromCharCode.apply(String, payload);}return result;Each call receives one record. Position 0 is the device name, position 1 the device type, then telemetry key–value pairs.
To adapt:
- Another delimiter — replace
split(','), for example withsplit(';'). - Name or type at another position — change the
payloadArray[0]andpayloadArray[1]indexes. - Fixed telemetry keys — replace the loop with explicit assignments:
telemetryPayload['temperature'] = parseFloat(payloadArray[2]);. - Attributes instead of telemetry — move the fields to
attributes.
/** Decoder **/// decode payload to JSONvar data = decodeToJson(payload);var deviceName = data.deviceName;var deviceType = data.deviceType;var result = {deviceName: deviceName,deviceType: deviceType,attributes: {},telemetry: {temperature: data.temperature,humidity: data.humidity}};/** Helper functions 'decodeToString' and 'decodeToJson' are already built-in **/return result;/** Decoder **/// decode payload to JSONvar data = decodeToJson(payload);var deviceName = data.deviceName;var deviceType = data.deviceType;var result = {deviceName: deviceName,deviceType: deviceType,attributes: {},telemetry: {temperature: data.temperature,humidity: data.humidity}};/** Helper functions **/function decodeToString(payload) {return String.fromCharCode.apply(String, payload);}function decodeToJson(payload) {var str = decodeToString(payload);var data = JSON.parse(str);return data;}return result;The decoder parses the message as a JSON object and maps its fields directly.
To adapt:
- Other field names — replace
data.deviceNameanddata.deviceType, for example withdata.idanddata.model. - More telemetry — add fields, for example
pressure: data.pressure. - Device properties — move them to
attributes, for examplefirmware: data.fw.
/** Decoder **/// decode payload to stringvar payloadStr = decodeToString(payload);var deviceName = payloadStr.substring(0, 6);var deviceType = payloadStr.substring(6, 13);// Result object with device/asset attributes/telemetry datavar result = {deviceName: deviceName,deviceType: deviceType,attributes: {},telemetry: {temperature: parseFloat(payloadStr.substring(13, 17))}};/** Helper functions 'decodeToString' and 'decodeToJson' are already built-in **/return result;/** Decoder **/// decode payload to stringvar payloadStr = decodeToString(payload);var deviceName = payloadStr.substring(0, 6);var deviceType = payloadStr.substring(6, 13);// Result object with device/asset attributes/telemetry datavar result = {deviceName: deviceName,deviceType: deviceType,attributes: {},telemetry: {temperature: parseFloat(payloadStr.substring(13, 17))}};/** Helper functions **/function decodeToString(payload) {return String.fromCharCode.apply(String, payload);}function decodeToJson(payload) {var str = decodeToString(payload);var data = JSON.parse(str);return data;}return result;The handler strips the 4-byte header and the length field, so the decoder reads fixed offsets of the remaining 17 bytes.
To adapt:
- Another byte layout — change the
substring(start, end)offsets. - Another header size — change Number of first bytes to strip out from the decoded frame in the handler.
- Integers stored as raw bytes — use
parseBytesToInt(payload, offset, length, bigEndian), for exampletemperature: parseBytesToInt(payload, 13, 2, true) / 100.0.
- Another delimiter — replace
-
Click Test decoder function and check the result (see Test the Decoder).
-
Click Add.
Decoder Inputs
The decoder signature is function decoder(payload, metadata): object | object[].
| Variable | Type | Content |
|---|---|---|
payload |
byte array | One message as framed by the handler: without the delimiter if Strip delimiter is on (Text), or without the stripped header bytes (Binary). |
metadata |
{[key: string]: string} |
integrationName and the key–value pairs from the integration’s Metadata field. |
Built-in helpers:
| Function | Result |
|---|---|
decodeToString(payload) |
Converts the bytes to a UTF-8 string. |
decodeToJson(payload) |
Parses the bytes as JSON and returns an object. |
For more parsing patterns, see Common scripting patterns.
Test the Decoder
Click Test decoder function below the editor. The dialog opens with pre-filled Metadata rows, Payload content type set to JSON, and a sample JSON payload. Replace the payload with a message in your format. For the Text decoder, set Payload content type to Text and enter:
SN-002,default,temperature,25.7Click Test. The Output panel shows:
{ "deviceName": "SN-002", "deviceType": "default", "telemetry": { "temperature": 25.7 }, "attributes": {}}Check that deviceName is set and telemetry holds the expected keys. If the output is empty or shows an error, fix the decoder and test again.
Create the Integration
Section titled “Create the Integration”- Go to Integrations ⇾ Integrations and click + Add integration.
- Basic settings:
- Integration type — select TCP.
- Name — enter a name, for example
TCP integration. - Enable integration and Allow create devices or assets — keep both on, so the device is created on its first uplink.
- Click Next.
- Uplink data converter:
- Click Select existing and choose
TCP Uplink Converter. - Alternatively, click Create new to write a decoder here.
- Click Next.
- Click Select existing and choose
- Downlink data converter:
- Click Skip. Add a downlink converter later if you need to send commands to the device.
- Connection:
- Port — keep
10560or enter your own. - Handler Type — select the handler that matches your payload format. See Handler Configuration.
- Copy the Integration key and Integration secret. You need them to run the remote integration.
- Leave the other parameters at their defaults. See Connection Settings for details.
- Port — keep
- Click Add.
Connection Settings
Section titled “Connection Settings”| Parameter | Description |
|---|---|
| Port | TCP port the remote integration listens on. Default: 10560. Must be free on the remote host. If the port is busy, the integration retries binding every 5 seconds. |
| Max number of pending connects on the socket | Queue length for connections not yet accepted. Default: 128. Raise it for many simultaneous connections. |
| Size of the buffer for inbound socket (in KB) | Socket receive buffer. Default: 64. |
| Size of the buffer for outbound socket (in KB) | Socket send buffer. Default: 64. |
| Enable sending of keep-alive messages on connection-oriented sockets | Sends TCP keep-alive probes to detect dead connections. Off by default. Turn it on for long-lived connections that can drop silently. |
| Forces a socket to send the data without buffering (disable Nagle’s buffering algorithm) | Sends each write immediately instead of batching small packets. On by default. |
| Cache Size / Cache time to live in minutes | Limits of the cache that maps devices to sender addresses for downlinks. Defaults: 1000 entries and 1440 minutes (24 hours). |
| Execute remotely | Always on for TCP. Shows the Integration key and Integration secret for the remote process. See Remote Integration. |
| Metadata | In Advanced settings. Key–value pairs added to the metadata of every message from this integration. |
Handler Configuration
Section titled “Handler Configuration”TCP has no message boundaries, so the handler defines how the byte stream is split into messages. Select it in Handler Type (default: Binary).
Reads a length field from each message header and cuts the frame to that length.
| Parameter | Description |
|---|---|
| Max Frame Length (in bytes) | Largest allowed frame. Default: 128. |
| Length Field Offset | Position of the length field from the start of the frame, in bytes. Default: 0. |
| Length Field Length | Size of the length field: 1, 2, 3, 4, or 8 bytes. Default: 2. |
| Length Adjustment (the compensation value to add to the value of the length field) | Added to the length field value to get the number of bytes that follow it. Range: 0–8. Default: 0. |
| Number of first bytes to strip out from the decoded frame | Bytes removed from the start of the frame before the converter runs, typically the header. Default: 0. |
| Byte Order of the length field | Little Endian (default) or Big Endian. |
| Thrown exception soon as the decoder notices the length of the frame will exceed max size | Raises the error as soon as the length field exceeds Max Frame Length, instead of after the whole frame arrives. Off by default. |
Splits the stream at a delimiter. For CSV, plain-text lines, or text-encoded JSON.
| Parameter | Description |
|---|---|
| Max Frame Length (in bytes) | Largest allowed message. Longer messages raise an error. Default: 128. |
| Strip delimiter | Removes the delimiter before the converter runs. On by default. |
| Message separator type | System line separator (\n or \r\n), Nul delimiter (\0), or Custom separator (any string, entered in Separator). Default: System line separator. |
Reads the stream until a complete JSON object or array arrives and passes it to the converter. No delimiter or settings needed.
Install and Run the Remote Integration
Section titled “Install and Run the Remote Integration”Install and start the TCP integration service with the Integration key and Integration secret from the connection step. Follow the remote integration guide. Once the process connects, it opens the TCP port.
Test Uplink
Section titled “Test Uplink”Send a message to the integration port, then confirm that ThingsBoard decoded it and created the device.
Send a Test Uplink
Section titled “Send a Test Uplink”Run the command for your payload format on the remote integration host (localhost). From another machine, replace localhost with that host’s address. nc -q0 closes the connection right after sending; -q1 -w1 waits 1 second.
One record:
echo -e 'SN-002,default,temperature,25.7' | nc -q0 localhost 10560
Two newline-separated records:
echo -e 'SN-002,default,temperature,25.7\nSN-002,default,humidity,69' | nc -q1 -w1 localhost 10560
echo -e '{"deviceName": "SN-002", "deviceType": "default", "temperature": 25.7, "humidity": 69}' | nc -q1 -w1 localhost 10560
Set the Binary handler values for this message first.
echo -e -n '\x30\x30\x30\x30\x11\x53\x4e\x2d\x30\x30\x32\x64\x65\x66\x61\x75\x6c\x74\x32\x35\x2e\x37\x00\x00\x00' | nc -q1 -w1 localhost 10560
No output means the message was delivered.
Verify the Result
Section titled “Verify the Result”- Integration events — go to Integrations ⇾ Integrations, open the integration, and click the Events tab. The uplink appears with status OK.
- Converter events — open
TCP Uplink Converterin Integrations ⇾ Data converters and click the Events tab. In shows the framed message, Out shows the decoded result, and Error shows any error. - Device — go to Devices & assets ⇾ Devices. The integration has created SN-002. Open it and confirm
temperature = 25.7on the Latest telemetry tab.
Configure Downlink
Section titled “Configure Downlink”ThingsBoard sends a downlink over the open connection on which the device sent its last uplink, and appends \n to every downlink payload. If that connection is closed, the downlink is held and sent right after the device’s next uplink. The device must therefore keep the connection open to receive commands immediately.
The downlink converter turns a Rule Engine message into the bytes sent to the device. For the full encoder reference, see Downlink data converter.
Create and Assign the Downlink Converter
Section titled “Create and Assign the Downlink Converter”- Open the integration in Integrations ⇾ Integrations and click Toggle edit mode (pencil icon).
- In the Downlink data converter field, click Create new. The Downlink converter type and TCP integration type are preselected.
- Enter a name, for example
TCP Downlink Converter. - Main encoding configuration opens with a default encoder. Use the encoder shown below or your own.
- To record downlink events, click the debug icon next to the name, select All messages, and click Apply.
- Click Add, then Apply changes.
Encoder used in this guide:
/** Encoder **/
// Encode downlink data from the incoming Rule Engine message.// msg: JSON message payload.// msgType: message type, e.g. 'ATTRIBUTES_UPDATED', 'POST_TELEMETRY_REQUEST', etc.// metadata: key-value pairs containing additional message data.// integrationMetadata: key-value pairs configured in the integration.
var result = { // Specify the response body format: JSON, TEXT, or BINARY. contentType: "JSON",
// Serialize the incoming message and use it as the TCP payload. data: JSON.stringify(msg),
// Add optional metadata when required by the integration. metadata: {}};
return result;Configure the Root Rule Chain
Section titled “Configure the Root Rule Chain”To send downlinks through the integration, modify the Root Rule Chain:
- Go to Data processing ⇾ Rule chains and open the Root Rule Chain.
- Find the integration downlink node in the node library panel on the left. Drag it onto the canvas.
- In the node configuration dialog, enter a name (e.g.
Downlink to TCP integration) and select your TCP integration. Click Add. - Connect the message type switch node to the newly created integration downlink node using the Post attributes and Attributes Updated relation types to trigger downlinks whenever shared attributes are created or updated.
- Apply the changes.
Test Downlink
Section titled “Test Downlink”Open a connection, send an uplink, then update a shared attribute to queue a downlink. The steps below use the Text handler; for another handler, send the matching payload from Sample Payloads.
-
Open a connection and keep it open:
Terminal window nc localhost 10560 -
Type an uplink and press Enter. Keep the terminal open.
Terminal window SN-002,default,temperature,25.7 -
Go to Devices & assets ⇾ Devices, select your device, and open the Attributes tab.
-
Select Shared attributes and click +.
-
Set Key to
temperatureUploadFrequency, Type to Integer, Value to60, and click Add. ThingsBoard sends the downlink.
To test from a single command, keep the connection open for 60 seconds with -w60 and add the attribute during that time:
echo -e 'SN-002,default,temperature,25.7\nSN-002,default,humidity,69' | nc -w60 localhost 10560
echo -e '{"deviceName": "SN-002", "deviceType": "default", "temperature": 25.7, "humidity": 69}' | nc -w60 localhost 10560
Set the Binary handler values for this message first.
echo -e -n '\x30\x30\x30\x30\x11\x53\x4e\x2d\x30\x30\x32\x64\x65\x66\x61\x75\x6c\x74\x32\x35\x2e\x37\x00\x00\x00' | nc -w60 localhost 10560
Verify the Downlink
Section titled “Verify the Downlink”-
Terminal — the open connection prints the downlink:
Terminal window john@doe:~$ nc localhost 10560SN-002,default,temperature,25.7{"temperatureUploadFrequency":60} -
Converter events — open the downlink converter’s Events tab. In shows the Rule Engine message, and Out shows the payload sent to the device.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
| Device not created | deviceName is empty or undefined. |
Check Out in the converter events. Make sure the decoder extracts deviceName. |
| Converter error | The decoder threw an exception. | Open the converter’s Events tab and check Error. Test the decoder with a sample payload. |
| Messages split or merged wrongly | The handler does not match the device framing. | Match the Handler Configuration to the protocol. Capture the raw TCP bytes to check the framing. |
| Messages dropped with a frame-length error | A message is longer than Max Frame Length. | Raise Max Frame Length (in bytes). |
| Remote integration does not connect to ThingsBoard | Wrong host, port, key, or secret. | Check host, port, routingKey, and secret in tb-remote-integration.yml. Make sure gRPC port 9090 is reachable. |
| Port already in use | Another process holds the port. | Free the port or pick another one. The integration retries binding every 5 seconds. |
| Downlink not received | The connection closed after the uplink, or no uplink arrived after the downlink converter was added. | Keep the connection open (-w60 or a persistent client) and send an uplink first. Check the Root Rule Chain wiring. |
| Wrong timestamp | The device sends ts in seconds. |
Convert it: ts: data.ts * 1000. |
| Telemetry not saved | The decoder returns {}, [], or empty telemetry. |
Make sure telemetry holds at least one key. |
To read debug events, open the integration or converter, click Events, and select an event: In is the received data, Out is the converter result, and Error holds the error text.
See Also
Section titled “See Also”- Integrations overview — how integrations, converters, uplinks, and downlinks fit together.
- Uplink data converter — decoder inputs, output format, and scripting patterns.
- Downlink data converter — encoder reference.
- Remote Integration — install and run the TCP integration service.
- UDP Integration — the connectionless counterpart for datagram devices.
- TBEL — built-in functions and syntax for converter scripts.
- Rule Engine — how messages are processed after decoding.
Was this helpful?