Skip to content
Stand with Ukraine flag

Serial connector example

This tutorial walks through building a complete custom connector for the IoT Gateway — the SerialConnector — which reads data from a serial port and forwards it to ThingsBoard. The same connector ships in the gateway’s built-in extensions folder, so you can use it as a reference or starting point for your own implementation.

What we’re building:

Serial device → bytes → SerialUplinkConverter → ConvertedData → ThingsBoard
ThingsBoard → RPC → SerialDownlinkConverter → bytes → Serial device

Sample device payload:

48\r2430947595\n
  • 48 — humidity value, terminated by \r
  • 2430947595 — device serial number, from byte offset 4 to end of message

Step 1. Create the connector configuration

Section titled “Step 1. Create the connector configuration”

Create custom_serial.json in the same folder as your tb_gateway.json:

Terminal window
touch custom_serial.json

Add the following configuration:

{
"name": "Custom serial connector",
"logLevel": "DEBUG",
"uplinkQueueSize": 100000,
"devices": [
{
"name": "SerialDevice1",
"type": "default",
"port": "/dev/ttyUSB0",
"baudrate": 9600,
"converter": "SerialUplinkConverter",
"downlink_converter": "SerialDownlinkConverter",
"telemetry": [
{
"type": "float",
"key": "humidity",
"untilDelimiter": "\r"
}
],
"attributes": [
{
"key": "SerialNumber",
"type": "string",
"fromByte": 4,
"toByte": -1
}
],
"attributeUpdates": [
{
"attributeOnPlatform": "attr1",
"stringToDevice": "value = ${attr1}\n"
}
],
"serverSideRpc": [
{
"method": "setValue",
"type": "int",
"withResponse": true,
"responseType": "string",
"responseUntilDelimiter": "\r",
"responseTimeoutSec": 5
},
{
"method": "getValue",
"type": "string",
"withResponse": false
}
]
}
]
}

Top-level fields:

FieldDescription
nameConnector name — must match the "name" entry in tb_gateway.json.
logLevelLog verbosity: TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL.
uplinkQueueSizeMaximum number of uplink data items to buffer before dropping.
devicesArray of device configurations.

Device fields:

FieldDescription
nameDevice name on the ThingsBoard platform.
typeDevice profile name on the platform.
portSerial port path.
baudrateSerial port baud rate.
converterClass name of the uplink converter.
downlink_converterClass name of the downlink converter.
telemetryArray of telemetry datapoint configurations.
attributesArray of attribute datapoint configurations.
attributeUpdatesArray of attribute update configurations (platform → device).
serverSideRpcArray of RPC method configurations (platform → device).

Place the connector and converter files inside the extensions folder for your installation type:

InstallationExtensions folder path
Docker Compose (default volume)tb-gw-extensions
Daemon/var/lib/thingsboard_gateway/extensions
pip (system-wide)/usr/lib/python3/site-packages/thingsboard_gateway/extensions
pip (user)/usr/local/lib/python3/dist-packages/thingsboard-gateway/extensions

Create a subfolder named serial inside the extensions folder. All connector and converter files go there.


Create extensions/serial/serial_connector.py with the following content. The connector manages SerialDevice worker threads and routes data to ThingsBoard.

from queue import Queue
from threading import Event, Thread, Lock
from typing import List, TYPE_CHECKING
import serial.tools
import serial.tools.list_ports
from thingsboard_gateway.tb_utility.tb_utility import TBUtility
from time import monotonic, sleep
try:
import serial
except ImportError:
print("pyserial library not found - installing...")
TBUtility.install_package("pyserial")
import serial
from thingsboard_gateway.connectors.connector import Connector
from thingsboard_gateway.tb_utility.tb_loader import TBModuleLoader
from thingsboard_gateway.tb_utility.tb_logger import init_logger
if TYPE_CHECKING:
from thingsboard_gateway.gateway.tb_gateway_service import TBGatewayService
class SerialDevice(Thread):

Create extensions/serial/uplink_serial_converter.py. The uplink converter parses raw bytes from the device and produces a ConvertedData object.

from typing import Any, Tuple
from simplejson import loads
from thingsboard_gateway.connectors.converter import Converter
from thingsboard_gateway.gateway.constants import REPORT_STRATEGY_PARAMETER, TELEMETRY_PARAMETER, TIMESERIES_PARAMETER
from thingsboard_gateway.gateway.entities.converted_data import ConvertedData
from thingsboard_gateway.gateway.entities.datapoint_key import DatapointKey
from thingsboard_gateway.gateway.entities.report_strategy_config import ReportStrategyConfig
from thingsboard_gateway.gateway.entities.telemetry_entry import TelemetryEntry
from thingsboard_gateway.tb_utility.tb_utility import TBUtility
class SerialUplinkConverter(Converter):
"""
Converts incoming serial bytes to the ConvertedData format expected by ThingsBoard.
One converter instance is created per configured device.
"""
def __init__(self, config, logger):
self._log = logger
self.__config = config
self.__device_report_strategy = None
self.__device_name = self.__config.get('deviceName', self.__config.get('name', 'SerialDevice'))
self.__device_type = self.__config.get('deviceType', self.__config.get('type', 'default'))
try:

After processing 48\r2430947595\n, the converter produces:

Device name: "SerialDevice1"
Device type: "default"
Telemetry: [{"humidity": 48.0}]
Attributes: {"SerialNumber": "2430947595"}

Create extensions/serial/downlink_serial_converter.py. The downlink converter turns ThingsBoard RPC payloads into raw bytes to send to the device.

from math import ceil
from struct import pack, unpack
from thingsboard_gateway.connectors.converter import Converter
class SerialDownlinkConverter(Converter):
"""
Converts RPC or attribute update payloads into bytes for the serial port.
One converter instance is created per configured device.
"""
def __init__(self, config, logger):
self._log = logger
self.__config = config
def convert(self, config, data) -> bytes:
"""Returns bytes to write to the serial port."""
self._log.debug("Data to convert: %s", data)
if data is None:

Step 6. Register the connector in tb_gateway.json

Section titled “Step 6. Register the connector in tb_gateway.json”

Add the following entry to the "connectors" array in tb_gateway.json:

{
"name": "Serial Connector",
"type": "serial",
"configuration": "custom_serial.json",
"class": "SerialConnector"
}
FieldDescription
nameConnector name — must match the "name" in custom_serial.json.
typeExtensions subfolder name (serial).
configurationPath to the connector config file, relative to the gateway config folder.
classConnector class name inside the connector file.

Terminal window
sudo systemctl restart thingsboard-gateway

Default log locations:

InstallationLog folder
Docker Composetb-gw-logs volume
Daemon/var/log/thingsboard-gateway/
Python module (pip)./logs/

Connect the serial device, then open Devices in the ThingsBoard UI. You should see a device named SerialDevice1. Open it and go to the Latest telemetry tab — the humidity key should appear with the value parsed from the serial stream.


The SerialConnector class implements all required methods from the Connector interface. Key methods used in this example:

MethodRole in this connector
__init__Initialises the uplink queue, logger, and device list from config.
openStarts the connector thread.
runMain loop: loads devices, starts threads, drains the uplink queue, handles reconnects.
closeStops all device threads and the logger.
on_attributes_updateFormats the attribute value as a UTF-8 string and writes it to the device’s serial port.
server_side_rpc_handlerDelegates RPC handling to SerialDevice.handle_rpc_request and sends the reply.

The SerialUplinkConverter and SerialDownlinkConverter classes implement the Converter interface.

Classconvert(config, data)
SerialUplinkConverterconfig=None for telemetry; config dict for RPC responses. data is the raw bytes from the port. Returns ConvertedData.
SerialDownlinkConverterconfig is the RPC config section. data is the value from ThingsBoard. Returns bytes.