Chapter 122: ThingsBoard IoT Platform Integration
Chapter Objectives
Upon completing this chapter, you will be able to:
- Understand the architecture and core concepts of the ThingsBoard IoT Platform.
- Set up a ThingsBoard instance (e.g., using Docker for local deployment or using ThingsBoard Cloud).
- Provision devices in ThingsBoard and understand authentication using Access Tokens.
- Configure an ESP32 project to communicate with ThingsBoard using MQTT.
- Send telemetry data from an ESP32 device to ThingsBoard.
- Implement Remote Procedure Calls (RPC) to control an ESP32 device from ThingsBoard.
- Work with device attributes (client-side, server-side, and shared) for state management and configuration.
- Get a basic understanding of how to visualize data in ThingsBoard dashboards.
- Identify common issues and troubleshoot ThingsBoard integration problems.
Introduction
Having explored several large-scale commercial cloud IoT platforms, we now turn our attention to a versatile and popular open-source alternative: ThingsBoard. ThingsBoard is an IoT platform for data collection, processing, visualization, and device management. It offers a robust set of features that can be deployed on-premises, in a private cloud, or used via its own cloud offering.
ThingsBoard provides a compelling option for developers and organizations looking for more control over their IoT infrastructure, wishing to avoid vendor lock-in, or needing an open-source solution that can be customized and extended. It supports standard protocols like MQTT, HTTP, and CoAP, making it highly compatible with devices like the ESP32. This chapter will guide you through connecting your ESP32 to ThingsBoard, sending data, receiving commands, and managing device attributes, empowering you to build sophisticated IoT applications with this flexible platform.
Theory
ThingsBoard Overview
ThingsBoard is designed as a scalable, fault-tolerant, and highly customizable IoT platform. Its architecture is built around several core components:
- Transport Layer: Handles device connectivity via various protocols. The primary protocols are:
- MQTT: Lightweight, publish-subscribe protocol, ideal for most IoT devices, including ESP32.
- HTTP(S): Can be used for less frequent data uploads or by devices that don’t support MQTT.
- CoAP: Constrained Application Protocol, suitable for very resource-limited devices.We will primarily focus on MQTT.
- Core Services: The heart of the platform, responsible for processing messages, managing device lifecycle, handling security, and executing business logic defined in the Rule Engine.
- Rule Engine: A powerful system for defining data processing and action workflows. You can create rule chains that:
- Filter, transform, and enrich incoming data.
- Trigger actions based on specific conditions (e.g., send an email alert, create an alarm, forward data to external systems, or send an RPC to a device).
- Route data to appropriate storage or visualization components.
- Database: ThingsBoard uses various databases to store:
- Telemetry Data: Time-series data from devices (e.g., Cassandra, PostgreSQL with TimescaleDB extension, InfluxDB).
- Entity Data: Device metadata, asset information, customer details, dashboards, rule chains (e.g., PostgreSQL, HSQLDB for PoC).
- Web UI: Provides interfaces for administrators, tenants, and customers to manage devices, assets, create dashboards, define rule chains, and view data.
graph TD
subgraph "External Entities"
direction LR
Devices["<center><b>IoT Devices (ESP32, etc.)</b></center>"]
Users["<center><b>Users / Applications</b><br>(Web Browsers, Mobile Apps, REST API Clients)</center>"]
end
subgraph "ThingsBoard Platform"
direction TB
TL["<center><b>Transport Layer</b><br>(MQTT, HTTP, CoAP Gateways)</center>"]
CORE["<center><b>Core Services</b><br>(Device Management, Security, Message Processing)</center>"]
RE["<center><b>Rule Engine</b><br>(Data Processing, Alarms, Notifications, Actions)</center>"]
DB["<center><b>Database Layer</b><br>(Telemetry: Cassandra/PostgreSQL+TimescaleDB/InfluxDB<br>Entity Data: PostgreSQL/HSQLDB)</center>"]
WebUI["<center><b>Web UI Server</b><br>(Dashboards, Device Mgmt, Rule Editor)</center>"]
TL --> CORE
CORE --> RE
RE --> CORE
CORE --> DB
RE --> DB
WebUI --> CORE
end
subgraph "External Systems (Optional Integration via Rule Engine)"
direction RL
ExtSys["<center><b>External Systems</b><br>(e.g., Kafka, RabbitMQ, External DBs, Notification Services)</center>"]
end
Devices -- "MQTT, HTTP, CoAP" --> TL
Users -- "HTTP/HTTPS, WebSockets" --> WebUI
RE -- "Actions (e.g., Webhooks, Email, SMS)" --> ExtSys
RE -- "RPC Calls" --> TL
classDef device fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6
classDef tbComponent fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
classDef tbCore fill:#FEF3C7,stroke:#D97706,stroke-width:2px,color:#92400E
classDef tbDatabase fill:#E0E7FF,stroke:#4338CA,stroke-width:1px,color:#3730A3
classDef external fill:#D1FAE5,stroke:#059669,stroke-width:1px,color:#065F46
class Devices device;
class Users external;
class ExtSys external;
class TL tbComponent;
class WebUI tbComponent;
class CORE tbCore;
class RE tbCore;
class DB tbDatabase;
Key Entities in ThingsBoard
ThingsBoard has a multi-tenant architecture with the following key entities:
| Entity | Description | Key Responsibilities / Capabilities |
|---|---|---|
| System Administrator | Top-level user managing the entire ThingsBoard platform instance. | Manages tenants, system settings, platform updates, monitors overall system health. |
| Tenant | An isolated business entity or a primary user account within the platform. | Manages its own users, customers, devices, assets, dashboards, rule chains, and billing (if applicable). Provides multi-tenancy. |
| Customer | A sub-entity of a tenant, representing an end-user or a group within the tenant’s organization. | Can view and manage devices and assets assigned to them by the tenant. Can have their own users and dashboards. |
| User | An individual who logs into and interacts with the ThingsBoard UI. Belongs to a System Admin, Tenant, or Customer. | Accesses platform features based on their role and permissions (e.g., view dashboards, manage devices, configure rules). |
| Device | A representation of a physical IoT device (e.g., ESP32) that connects to the platform. | Sends telemetry, reports attributes, can be controlled via RPC, has unique credentials (e.g., access token). |
| Asset | A logical entity that can be related to devices or other assets (e.g., a building, a room, a piece of equipment). | Aggregates data from related devices, defines relationships, can have its own attributes and telemetry. |
| Dashboard | Customizable web pages for data visualization and interaction. | Displays telemetry using various widgets (charts, gauges, maps), shows device attributes, allows sending RPC commands, and can be assigned to tenants or customers. |
| Rule Chain | A sequence of rule nodes defining message processing logic. | Filters, transforms, enriches data, generates alarms, sends notifications, triggers actions, routes data to other systems or back to devices. |
Communication with ThingsBoard via MQTT
ThingsBoard’s MQTT API is a common way for devices to interact with the platform.
| Parameter | Example Value (Local Docker) | Example Value (ThingsBoard Cloud) | Description |
|---|---|---|---|
| Broker Address/URI | mqtt://localhost or mqtt://<your-pc-ip> |
mqtt://mqtt.thingsboard.cloud (TCP)mqtts://mqtt.thingsboard.cloud (TLS) |
The address of the ThingsBoard MQTT broker. Use IP if ESP32 is on the same LAN as a local server. |
| Port | 1883 (MQTT/TCP) |
1883 (MQTT/TCP)8883 (MQTT/TLS) |
Standard MQTT port. 8883 is used for secure connections over TLS (recommended for cloud). |
| Client ID | ESP32-Client-001 (Any unique string) |
ESP32-Cloud-Device (Any unique string) |
A unique identifier for the MQTT client (your ESP32 device). Can be arbitrary but must be unique per connection. |
| Username | YOUR_DEVICE_ACCESS_TOKEN |
YOUR_DEVICE_ACCESS_TOKEN |
The Access Token of the device, obtained from the ThingsBoard UI after creating the device. This is the primary authentication credential. |
| Password | (Empty) or any string | (Empty) or any string | Can be left empty when using the Access Token as the username. Some MQTT client libraries might require a non-empty password, in which case any arbitrary string can be used as it’s typically ignored by ThingsBoard when token auth is used. |
Device Authentication: Access Tokens
The simplest and most common way to authenticate a device with ThingsBoard is using an Access Token.
- When you create a device in ThingsBoard, a unique access token is generated for it.
- The ESP32 device uses this token as the
usernamewhen connecting to the ThingsBoard MQTT broker. - This token essentially acts as the device’s credential. It should be kept secure on the device (e.g., in NVS, potentially with flash encryption).
ThingsBoard also supports X.509 certificate-based authentication for more advanced security scenarios, but access tokens are sufficient for most use cases and simpler to implement.
Data Points in ThingsBoard
ThingsBoard categorizes data sent by devices into several types:
| Data Type | MQTT Topic (Device Perspective) | Payload Format (Typical) | Direction | Primary Use Case | Example |
|---|---|---|---|---|---|
| Telemetry | v1/devices/me/telemetry |
JSON Object | Device-to-Cloud (D2C) | Time-series sensor readings, status updates, events. | {"temperature": 25.5, "active": true} |
| Client-side Attributes | v1/devices/me/attributes |
JSON Object | Device-to-Cloud (D2C) | Device metadata, static properties (firmware version, serial number, model). | {"firmware_version": "1.2", "ip": "192.168.1.10"} |
| Server-side Attributes (Request) | v1/devices/me/attributes/request/{request_id}(Payload specifies keys) |
JSON Object (Payload) | Device-to-Cloud (D2C) | Device requests specific server-side or shared attribute values from the server. | Payload: {"serverKeys":"configInterval,targetTemp"} |
| Server-side Attributes (Response/Update Notification) | Device subscribes to v1/devices/me/attributes (for all updates) or v1/devices/me/attributes/response/{request_id} (for specific request response). |
JSON Object | Cloud-to-Device (C2D) | Server sends attribute values to the device (either in response to a request or as a push update). Used for configuration. | {"configInterval": 300, "targetTemp": 22.0} |
| Shared Attributes (Update from Device) | v1/devices/me/attributes |
JSON Object | Device-to-Cloud (D2C) | Device reports its side of a shared state or configuration. | {"mode": "auto", "lastAppliedConfigId": "xyz"} |
| RPC Request (from Server) | Device subscribes to v1/devices/me/rpc/request/+ |
JSON Object | Cloud-to-Device (C2D) | Server sends a command/request to the device. | {"method": "setLedState", "params": {"value": true}} |
| RPC Response (from Device) | v1/devices/me/rpc/response/{request_id} |
JSON Object | Device-to-Cloud (D2C) | Device sends a response back to the server for a two-way RPC call. | {"status": "success", "ledIsNow": "on"} |
Practical Examples
We’ll create an ESP32 application that sends temperature telemetry, reports its firmware version as an attribute, and handles an RPC call to control an LED.
Prerequisites
- ThingsBoard Instance:
- Option A: ThingsBoard Cloud: Sign up for a free account at https://thingsboard.cloud/signup.
- Option B: Local Docker Instance: (Requires Docker installed)
# Create a directory for ThingsBoard data
mkdir -p ~/.mytb-data && sudo chown -R 799:799 ~/.mytb-data
mkdir -p ~/.mytb-logs && sudo chown -R 799:799 ~/.mytb-logs
# Run ThingsBoard CE (Community Edition)
docker run -it -p 9090:9090 -p 1883:1883 -p 7070:7070 -p 5683:5683/udp \
-v ~/.mytb-data:/data \
-v ~/.mytb-logs:/var/log/thingsboard \
--name mytb --restart always \
thingsboard/tb-postgres- Access local ThingsBoard UI at
http://localhost:9090. Default admin:sysadmin@thingsboard.org/sysadmin. - ESP-IDF Setup: ESP-IDF v5.x with VS Code.
- Wi-Fi Credentials.
Setting up a Device in ThingsBoard
- Log in to your ThingsBoard instance.
- Navigate to Device groups (or Devices directly).
- Click the “+” icon to add a new device.
- Enter a Name for your device (e.g.,
ESP32-Sensor-01). - Leave other settings as default for now and click Add.
- The device will be created. Click on the device in the list.
- In the device details page, click “Copy access token”. This token is crucial for your ESP32 to connect. Store it securely.
Code Snippet: ESP32 with ThingsBoard
main/main.c:
#include <stdio.h>
#include <string.h>
#include <cJSON.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "mqtt_client.h"
#include "driver/gpio.h"
// Configuration - Set these via menuconfig
#define WIFI_SSID CONFIG_WIFI_SSID
#define WIFI_PASS CONFIG_WIFI_PASSWORD
#define TB_BROKER_URI CONFIG_TB_BROKER_URI // e.g., "mqtt://localhost" or "mqtt://mqtt.thingsboard.cloud"
#define TB_ACCESS_TOKEN CONFIG_TB_ACCESS_TOKEN // The device access token from ThingsBoard
#define FIRMWARE_VERSION "v1.0.1" // Example firmware version
#define LED_GPIO CONFIG_LED_GPIO
static const char *TAG = "THINGSBOARD_EXAMPLE";
static EventGroupHandle_t wifi_event_group;
const int WIFI_CONNECTED_BIT = BIT0;
static esp_mqtt_client_handle_t mqtt_client_handle = NULL;
static bool g_led_state = false;
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
// (Identical to previous chapters - connect, handle disconnect, got_ip)
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGI(TAG, "Disconnected from Wi-Fi. Retrying...");
esp_wifi_connect();
xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT);
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "Got IP address: " IPSTR, IP2STR(&event->ip_info.ip));
xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);
if (mqtt_client_handle) {
esp_mqtt_client_start(mqtt_client_handle);
}
}
}
void wifi_init_sta(void) {
// (Identical to previous chapters)
wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id, instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, &instance_got_ip));
wifi_config_t wifi_config = { .sta = { .ssid = WIFI_SSID, .password = WIFI_PASS, .threshold.authmode = WIFI_AUTH_WPA2_PSK, }, };
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start()); ESP_LOGI(TAG, "wifi_init_sta finished.");
xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
ESP_LOGI(TAG, "Connected to AP SSID:%s", WIFI_SSID);
}
static void send_attributes(void) {
if (!mqtt_client_handle || !(xEventGroupGetBits(wifi_event_group) & WIFI_CONNECTED_BIT)) return;
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "firmware_version", FIRMWARE_VERSION);
cJSON_AddStringToObject(root, "serial_number", "SN-00123ABC"); // Example
char *json_payload = cJSON_PrintUnformatted(root);
if (json_payload) {
esp_mqtt_client_publish(mqtt_client_handle, "v1/devices/me/attributes", json_payload, 0, 1, 0);
ESP_LOGI(TAG, "Sent attributes: %s", json_payload);
free(json_payload);
}
cJSON_Delete(root);
}
static void mqtt_event_handler_cb(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
esp_mqtt_event_handle_t event = event_data;
char rpc_response_topic[128];
char rpc_request_id_str[16] = {0}; // To store RPC request ID from topic
switch ((esp_mqtt_event_id_t)event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED to %s", TB_BROKER_URI);
// Subscribe to RPC requests for this device
esp_mqtt_client_subscribe(mqtt_client_handle, "v1/devices/me/rpc/request/+", 1);
ESP_LOGI(TAG, "Subscribed to RPC requests: v1/devices/me/rpc/request/+");
// Subscribe to attribute updates from server (optional)
esp_mqtt_client_subscribe(mqtt_client_handle, "v1/devices/me/attributes", 1);
ESP_LOGI(TAG, "Subscribed to attribute updates: v1/devices/me/attributes");
// Send client-side attributes on connect
send_attributes();
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "MQTT_EVENT_DATA from topic: %.*s", event->topic_len, event->topic);
ESP_LOGD(TAG, "Data: %.*s", event->data_len, event->data);
// Check if it's an RPC request topic: v1/devices/me/rpc/request/{request_id}
if (strncmp(event->topic, "v1/devices/me/rpc/request/", strlen("v1/devices/me/rpc/request/")) == 0) {
// Extract request_id from the topic
// Topic format: v1/devices/me/rpc/request/{request_id}
// event->topic_len, event->topic
const char *rpc_topic_prefix = "v1/devices/me/rpc/request/";
int prefix_len = strlen(rpc_topic_prefix);
if (event->topic_len > prefix_len) {
int id_len = event->topic_len - prefix_len;
if (id_len < sizeof(rpc_request_id_str)) {
strncpy(rpc_request_id_str, event->topic + prefix_len, id_len);
rpc_request_id_str[id_len] = '\0'; // Null terminate
ESP_LOGI(TAG, "RPC Request ID: %s", rpc_request_id_str);
}
}
char *data_str = malloc(event->data_len + 1);
if (!data_str) break;
memcpy(data_str, event->data, event->data_len);
data_str[event->data_len] = '\0';
cJSON *json_rpc = cJSON_Parse(data_str);
free(data_str);
if (json_rpc) {
cJSON *method = cJSON_GetObjectItem(json_rpc, "method");
cJSON *params = cJSON_GetObjectItem(json_rpc, "params");
if (method && cJSON_IsString(method)) {
ESP_LOGI(TAG, "RPC Method: %s", method->valuestring);
if (strcmp(method->valuestring, "setLedState") == 0) {
if (params && cJSON_IsObject(params)) {
cJSON *value_item = cJSON_GetObjectItem(params, "value");
if (value_item && cJSON_IsBool(value_item)) {
g_led_state = cJSON_IsTrue(value_item);
gpio_set_level(LED_GPIO, g_led_state);
ESP_LOGI(TAG, "LED state set to: %s", g_led_state ? "ON" : "OFF");
// Send RPC response (optional for one-way, good for two-way)
if (strlen(rpc_request_id_str) > 0) {
snprintf(rpc_response_topic, sizeof(rpc_response_topic), "v1/devices/me/rpc/response/%s", rpc_request_id_str);
char *response_payload = "{\"status\":\"success\"}"; // Example response
esp_mqtt_client_publish(mqtt_client_handle, rpc_response_topic, response_payload, 0, 1, 0);
ESP_LOGI(TAG, "Sent RPC response to %s", rpc_response_topic);
}
// Report new LED state as telemetry or attribute if desired
cJSON *telemetry_payload = cJSON_CreateObject();
cJSON_AddBoolToObject(telemetry_payload, "led_on_rpc", g_led_state);
char *telemetry_str = cJSON_PrintUnformatted(telemetry_payload);
if (telemetry_str) {
esp_mqtt_client_publish(mqtt_client_handle, "v1/devices/me/telemetry", telemetry_str, 0, 0, 0);
free(telemetry_str);
}
cJSON_Delete(telemetry_payload);
}
}
}
}
cJSON_Delete(json_rpc);
}
} else if (strncmp(event->topic, "v1/devices/me/attributes", event->topic_len) == 0) {
ESP_LOGI(TAG, "Received attribute update from server: %.*s", event->data_len, event->data);
// Parse and handle server-side or shared attribute updates here
// e.g., if server sets a "targetTemperature" or "telemetryUploadInterval"
}
break;
case MQTT_EVENT_ERROR:
ESP_LOGE(TAG, "MQTT_EVENT_ERROR type %d", event->error_handle->error_type);
break;
default:
ESP_LOGI(TAG, "Other MQTT event id:%d", event->event_id);
break;
}
}
static void mqtt_app_start(void) {
const esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.uri = TB_BROKER_URI,
.credentials.username = TB_ACCESS_TOKEN, // Access token is the username
// .credentials.client_id = "ESP32Client-ThingsBoard", // Optional, can be any unique string
// For TLS with ThingsBoard Cloud or self-hosted with TLS:
// .broker.verification.crt_bundle_attach = esp_crt_bundle_attach, // Use ESP-IDF cert bundle
};
mqtt_client_handle = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_register_event(mqtt_client_handle, ESP_EVENT_ANY_ID, mqtt_event_handler_cb, NULL);
// MQTT client start is handled by Wi-Fi event handler
}
void app_main(void) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
gpio_reset_pin(LED_GPIO);
gpio_set_direction(LED_GPIO, GPIO_MODE_OUTPUT);
gpio_set_level(LED_GPIO, g_led_state);
wifi_init_sta();
mqtt_app_start();
int msg_count = 0;
while (1) {
if (mqtt_client_handle && (xEventGroupGetBits(wifi_event_group) & WIFI_CONNECTED_BIT)) {
cJSON *root = cJSON_CreateObject();
float temperature = 20.0 + ((float)esp_random() / (float)UINT32_MAX) * 15.0; // Simulated
float humidity = 40.0 + ((float)esp_random() / (float)UINT32_MAX) * 30.0; // Simulated
cJSON_AddNumberToObject(root, "temperature", temperature);
cJSON_AddNumberToObject(root, "humidity", humidity);
cJSON_AddNumberToObject(root, "messageId", msg_count++);
cJSON_AddBoolToObject(root, "led_state_telemetry", g_led_state);
char *json_payload = cJSON_PrintUnformatted(root);
if (json_payload) {
esp_mqtt_client_publish(mqtt_client_handle, "v1/devices/me/telemetry", json_payload, 0, 0, 0);
ESP_LOGI(TAG, "Sent telemetry: %s", json_payload);
free(json_payload);
}
cJSON_Delete(root);
}
vTaskDelay(pdMS_TO_TICKS(10000)); // Send telemetry every 10 seconds
}
}
graph TD
A["<center><b>Start: ESP32 app_main</b></center>"] --> B["<center>1. Init NVS, GPIO, Wi-Fi</center>"];
B --> C["<center>2. Connect to Wi-Fi</center>"];
C --> D["<center>3. <tt>mqtt_app_start()</tt><br>(Init MQTT Client with Access Token)</center>"];
subgraph "MQTT Event Handler (mqtt_event_handler_cb)"
direction TB
EvConnected["<center><b>MQTT_EVENT_CONNECTED</b></center>"] --> SubRPC["<center>Subscribe to RPC:<br><tt>v1/devices/me/rpc/request/+</tt></center>"];
SubRPC --> SubAttrs["<center>Subscribe to Attributes:<br><tt>v1/devices/me/attributes</tt></center>"];
SubAttrs --> SendAttrs["<center>Call <tt>send_attributes()</tt></center>"];
SendAttrs --> PubClientAttrs["<center>Publish Client Attributes:<br><tt>v1/devices/me/attributes</tt><br>Payload: <tt>{\fw_version\: ...}</tt></center>"];
end
D -- Triggers on Connection --> EvConnected;
subgraph "ESP32 Main Loop (while(1))"
direction TB
LoopStart["<center><b>Loop Start</b></center>"] --> CheckConn{"<center><b>4. MQTT Connected?</b></center>"};
CheckConn -- Yes --> PrepTelemetry["<center><b>5. Prepare Telemetry Data</b><br>(e.g., temperature, humidity, msgId)<br>Format as JSON</center>"];
PrepTelemetry --> PubTelemetry["<center><b>6. Publish Telemetry</b><br>Topic: <tt>v1/devices/me/telemetry</tt><br>Payload: <tt>{\temperature\: ...}</tt></center>"];
PubTelemetry --> LogSent["<center><b>7. Log Sent Telemetry</b></center>"];
LogSent --> Delay["<center><b>8. Delay (e.g., 10s)</b></center>"];
Delay --> LoopStart;
CheckConn -- No --> Delay;
end
D --> LoopStart;
subgraph "ThingsBoard Platform"
direction TB
TB_MQTT["<center><b>ThingsBoard MQTT Broker</b></center>"]
TB_Device["<center><b>Device: ESP32-Sensor-01</b><br>(in ThingsBoard)</center>"]
TB_Telemetry["<center><b>Latest Telemetry Tab</b></center>"]
TB_Attributes["<center><b>Attributes Tab (Client)</b></center>"]
TB_MQTT --- TB_Device
TB_Device --- TB_Telemetry
TB_Device --- TB_Attributes
end
PubClientAttrs -.->|MQTT Message| TB_MQTT;
PubTelemetry -.->|MQTT Message| TB_MQTT;
TB_MQTT -.->|Data Processed| TB_Device;
classDef startNode fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6;
classDef processNode fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF;
classDef decisionNode fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E;
classDef cloudNode fill:#D1FAE5,stroke:#059669,stroke-width:1px,color:#065F46;
classDef eventNode fill:#A7F3D0,stroke:#047857,stroke-width:1px,color:#064E3B;
classDef loopNode fill:#E0E7FF,stroke:#4338CA,stroke-width:1px,color:#3730A3;
class A startNode;
class B,C,D,SubRPC,SubAttrs,SendAttrs,PubClientAttrs,PrepTelemetry,PubTelemetry,LogSent,Delay processNode;
class CheckConn decisionNode;
class TB_MQTT,TB_Device,TB_Telemetry,TB_Attributes cloudNode;
class EvConnected eventNode;
class LoopStart loopNode;
main/Kconfig.projbuild (or add to existing):
menu "ThingsBoard Example Configuration"
config WIFI_SSID
string "Wi-Fi SSID"
default "YourSSID"
config WIFI_PASSWORD
string "Wi-Fi Password"
default "YourPassword"
config TB_BROKER_URI
string "ThingsBoard MQTT Broker URI"
default "mqtt://localhost" # For local Docker: mqtt://<your-pc-ip> if ESP32 is on same LAN
# For TB Cloud: mqtt://mqtt.thingsboard.cloud
help
URI of the ThingsBoard MQTT broker.
For non-TLS: mqtt://host
For TLS: mqtts://host (port 8883 usually)
config TB_ACCESS_TOKEN
string "ThingsBoard Device Access Token"
default "YOUR_DEVICE_ACCESS_TOKEN"
help
The access token for your device created in ThingsBoard.
config LED_GPIO
int "LED GPIO Pin"
default 2
help
GPIO pin connected to an LED for RPC testing.
endmenu
main/CMakeLists.txt:
Ensure cJSON is available (usually default).
Build Instructions
- Save all files.
- Open ESP-IDF Terminal in VS Code.
- Configure:
idf.py menuconfig.- Set Wi-Fi SSID and Password.
- Set ThingsBoard MQTT Broker URI (e.g.,
mqtt://mqtt.thingsboard.cloudfor cloud, ormqtt://<IP_OF_PC_RUNNING_DOCKER>for local if ESP32 is on the same network). - Set ThingsBoard Device Access Token (copied from ThingsBoard UI).
- Set LED GPIO.
- Build:
idf.py build
Run/Flash/Observe Steps
sequenceDiagram
participant TB_UI as ThingsBoard UI/Rule Engine
participant TB_Broker as ThingsBoard MQTT Broker
participant ESP32Device as ESP32 Device (MQTT Client)
Note over ESP32Device: Subscribed to 'v1/devices/me/rpc/request/+'
TB_UI->>TB_Broker: 1. User triggers RPC <br>(e.g., 'setLedState', params: {"value": true})
activate TB_Broker
TB_Broker->>ESP32Device: 2. MQTT Publish: Topic <br>'v1/devices/me/rpc/request/{request_id}'<br>Payload: {"method":"setLedState", "params":{"value":true}}
activate ESP32Device
Note over ESP32Device: MQTT_EVENT_DATA received
ESP32Device->>ESP32Device: 3. mqtt_event_handler_cb() parses topic & payload
ESP32Device->>ESP32Device: 4. Extracts method ('setLedState') & params (true)
ESP32Device->>ESP32Device: 5. Executes action (e.g., gpio_set_level(LED_GPIO, true))
ESP32Device->>ESP32Device: 6. (Optional) Prepares RPC response payload (e.g., {"status":"success"})
ESP32Device->>TB_Broker: 7. (Optional) MQTT Publish:<br> Topic 'v1/devices/me/rpc/response/{request_id}'<br>Payload: {"status":"success"}
deactivate ESP32Device
TB_Broker->>TB_UI: 8. (Optional) Delivers <br>RPC response to UI/Rule Engine
deactivate TB_Broker
Note over ESP32Device: Device might also publish telemetry/attributes<br>to reflect the new state (e.g., led_on_rpc: true)
ESP32Device->>TB_Broker: 9. (Optional) MQTT Publish: Topic 'v1/devices/me/telemetry'<br>Payload: {"led_on_rpc": true}
- Ensure your ThingsBoard instance is running and accessible.
- Connect ESP32 device.
- Flash:
idf.py -p /dev/ttyUSB0 flash monitor(adjust port). - Observe ESP32 Logs:
- Wi-Fi connection.
- MQTT connection attempts to ThingsBoard.
- “MQTT_EVENT_CONNECTED” and subscription messages.
- Published telemetry messages.
- Client-side attributes being sent.
- Check ThingsBoard UI:
- Navigate to your device (
ESP32-Sensor-01). - Latest Telemetry Tab: You should see incoming temperature, humidity, and led_state_telemetry data.
- Attributes Tab: Check “Client attributes” to see
firmware_versionandserial_number. - RPC Debug Terminal (in Device Details):
- Method Name:
setLedState - Request Body (JSON):
{"value": true}(orfalse) - Click “Send RPC”.
- Observe the ESP32’s LED and serial logs. It should receive the RPC, change LED state, and log it. You might also see the RPC response in the terminal if two-way RPC is fully implemented and working.
- Method Name:
- Dashboards: You can create a simple dashboard and add widgets (e.g., charts for telemetry, control widgets for RPC/attributes) to visualize and interact with your device.
- Navigate to your device (

Variant Notes
- ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6: All Wi-Fi enabled variants are excellent for ThingsBoard integration via MQTT.
- TLS: For production or connecting to ThingsBoard Cloud (which uses TLS on port 8883), ensure your
TB_BROKER_URIismqtts://mqtt.thingsboard.cloudand that TLS support is enabled inmenuconfigfor the MQTT client. The ESP-IDF default certificate bundle (esp_crt_bundle_attach) should generally work for public cloud services. For self-hosted ThingsBoard with TLS using self-signed certificates, you’d need to embed the CA certificate. - Resource Usage: JSON parsing/generation (cJSON) and MQTT (+TLS) will consume RAM and flash. This is generally manageable but consider optimizations for very complex JSON or highly constrained devices.
- TLS: For production or connecting to ThingsBoard Cloud (which uses TLS on port 8883), ensure your
- ESP32-H2: Lacks built-in Wi-Fi. Integration would require a gateway architecture, where an ESP32-H2 communicates its data (e.g., via BLE or 802.15.4) to a Wi-Fi enabled gateway (another ESP32, Raspberry Pi, etc.), which then forwards the data to ThingsBoard using the H2’s device credentials or acts as a ThingsBoard Gateway device.
Common Mistakes & Troubleshooting Tips
| Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
|---|---|---|
| Incorrect Access Token | ESP32 fails to connect to MQTT broker. MQTT_EVENT_ERROR with auth failure, or MQTT_EVENT_DISCONNECTED immediately after attempting connection. ThingsBoard logs might show “Authentication failed” for the device. |
Verify the TB_ACCESS_TOKEN in ESP32 code/config exactly matches the device’s access token in ThingsBoard UI (Device Details -> Copy access token).Ensure this token is used as the MQTT username. |
| Wrong MQTT Broker URI/Port | ESP32 cannot resolve hostname or connect to the broker. Connection timeouts. MQTT_EVENT_ERROR related to network or connection refused. |
Check TB_BROKER_URI. For local Docker, use PC’s LAN IP (e.g., mqtt://192.168.1.100), not localhost, if ESP32 is on the same network.For ThingsBoard Cloud: mqtt://mqtt.thingsboard.cloud (port 1883) or mqtts://mqtt.thingsboard.cloud (port 8883 for TLS).Ensure correct port: 1883 for unencrypted MQTT, 8883 for MQTTS/TLS. |
| Wi-Fi Connectivity Problems | ESP32 cannot obtain IP or connect to local network. MQTT connection fails before even attempting to reach ThingsBoard. |
Verify Wi-Fi SSID/Password. Test basic Wi-Fi connectivity with a simpler example. Check router DHCP settings and signal strength. |
| JSON Payload Formatting Errors | Messages sent by ESP32, but data doesn’t appear correctly in ThingsBoard “Latest Telemetry” or “Attributes”. ThingsBoard device event logs might show JSON parsing errors. |
ThingsBoard expects telemetry as a flat JSON object: {"key1": value1, "key2": value2}. Arrays or nested objects might be treated differently or require specific Rule Engine processing.Log the JSON string on ESP32 before publishing to verify its format. Use a JSON validator. |
| Incorrect MQTT Topics | Data sent but not appearing under telemetry/attributes, or RPC calls not received. |
Telemetry: v1/devices/me/telemetryClient Attributes: v1/devices/me/attributesRPC Subscribe: v1/devices/me/rpc/request/+RPC Response: v1/devices/me/rpc/response/{request_id}Double-check these topic strings in your ESP32 code. |
| Firewall Blocking MQTT Ports | If self-hosting ThingsBoard, ESP32 cannot connect. Connection timed out or refused. |
Ensure the firewall on the server hosting ThingsBoard allows incoming connections on MQTT port (1883 for TCP, 8883 for TLS). Network firewalls between ESP32 and server must also allow this traffic. |
| RPC Request ID Handling for Two-Way RPC | Device receives RPC, executes action, but server/UI doesn’t get a response, or response is not correlated. |
When receiving an RPC on v1/devices/me/rpc/request/{request_id}, correctly parse the {request_id} from the topic string.Use this exact {request_id} when publishing the response to v1/devices/me/rpc/response/{request_id}.
|
| TLS Handshake Issues (for MQTTS) | If using mqtts://mqtt.thingsboard.cloud (port 8883) or self-hosted TLS, connection fails with TLS errors. |
Ensure ESP-IDF’s CA certificate bundle is enabled and attached (esp_crt_bundle_attach in MQTT config) for public cloud.For self-hosted ThingsBoard with custom/self-signed certs, you must embed the CA certificate (or server cert if self-signed and no intermediate CA) in your ESP32 firmware and provide it to the MQTT client configuration. Ensure ESP32 system time is reasonably accurate (SNTP recommended) for certificate validation. |
| cJSON Memory Leaks / Errors | Device crashes or behaves erratically over time due to memory issues. cJSON_PrintUnformatted returns NULL or cJSON_Parse fails on valid-looking JSON. |
Always call cJSON_Delete() on cJSON objects after you are done with them (e.g., after cJSON_PrintUnformatted).Ensure strings passed to cJSON_Parse are null-terminated.Check return values of all cJSON functions for errors. Free memory allocated by cJSON_PrintUnformatted.
|
Exercises
- Advanced Telemetry:
- Modify the ESP32 code to send more complex telemetry, such as a nested JSON object or an array of values (e.g.,
{"sensor_data": {"internal_temp": 22.5, "external_temp": 28.1}, "uptime_seconds": 12345}). Observe how ThingsBoard stores and displays this.
- Modify the ESP32 code to send more complex telemetry, such as a nested JSON object or an array of values (e.g.,
- Two-Way RPC with Device Data:
- Implement a new two-way RPC method called
getDeviceStatus. - When ThingsBoard calls this RPC, the ESP32 should respond with a JSON payload containing its current LED state and firmware version.
- Implement a new two-way RPC method called
- Shared Attribute Control:
- In ThingsBoard, add a Shared Attribute to your device, e.g.,
targetBrightness(integer 0-100). - Modify the ESP32 code to subscribe to attribute updates (
v1/devices/me/attributes). - When
targetBrightnessis updated from ThingsBoard, have the ESP32 log the new value. (Bonus: control an actual PWM LED’s brightness). - Report the applied brightness back as a client-side attribute or telemetry.
- In ThingsBoard, add a Shared Attribute to your device, e.g.,
- ThingsBoard Dashboard:
- In ThingsBoard, create a new dashboard.
- Add a chart widget to display the
temperatureandhumiditytelemetry from your ESP32 over time. - Add a control widget (e.g., a switch or button) to trigger the
setLedStateRPC.
- Attribute Request:
- Add a server-side attribute in ThingsBoard, e.g.,
deviceNameLabelwith a string value. - Modify the ESP32 code to, on connection, request this server-side attribute using the topic
v1/devices/me/attributes/request/1(where 1 is a client-generated request ID) with a payload like{"serverKeys":"deviceNameLabel"}. - Handle the response on
v1/devices/me/attributes/response/1and log the received attribute.
- Add a server-side attribute in ThingsBoard, e.g.,
Summary
- ThingsBoard is a versatile open-source IoT platform suitable for various applications, offering both self-hosting and cloud options.
- Devices typically connect to ThingsBoard using MQTT, authenticating with a unique Access Token as the MQTT username.
- Key data interactions include:
- Telemetry: Sending time-series data (e.g., sensor readings) to
v1/devices/me/telemetry. - Attributes: Managing device properties (client-side, server-side, shared) via
v1/devices/me/attributes. - RPC: Executing commands on the device by subscribing to
v1/devices/me/rpc/request/+and optionally responding.
- Telemetry: Sending time-series data (e.g., sensor readings) to
- The ThingsBoard Rule Engine allows for powerful data processing and automation workflows.
- Dashboards provide visualization and control capabilities.
- All Wi-Fi enabled ESP32 variants can effectively integrate with ThingsBoard.
Further Reading
- ThingsBoard Official Documentation: https://thingsboard.io/docs/
- Getting Started with ThingsBoard CE: https://thingsboard.io/docs/user-guide/install/installation-options/ (Includes Docker, Kubernetes, etc.)
- ThingsBoard MQTT Device API Reference: https://thingsboard.io/docs/reference/mqtt-api/
- ThingsBoard Attributes API: https://thingsboard.io/docs/user-guide/attributes/
- ThingsBoard RPC API: https://thingsboard.io/docs/user-guide/rpc/
- ESP-IDF MQTT Client Documentation: https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/protocols/mqtt.html

