Skip to content

Devices

ESP-IDF

Connect an ESP32 to Thinger.io from an ESP-IDF project with the IOTMP client component, with device resources, Data Buckets and automatic reconnection.

Guide2 min read

This guide connects an ESP32 to Thinger.io from an ESP-IDF project, without the Arduino framework. You add the IOTMP client component, set the credentials, expose device resources and store data in a Data Bucket.

Before you start ​

  • A Thinger.io account. Your server is YOUR_SERVER: iot.thinger.io on the free Cloud account, or your own domain on a private instance.
  • ESP-IDF 5.x or 6.x, with idf.py in your shell.
  • An ESP32 board, such as an ESP32, ESP32-S3 or ESP32-C3.

Steps ​

  1. Create the device

    Console

    Go to Devices and click Add Device. Choose IOTMP Device (Thinger.io protocol) as Device Type, set the Device Id to YOUR_DEVICE, click Random in Device Credentials and copy the credentials. Click Add Device.

    CLI

    bash
    thinger device create YOUR_DEVICE -t Generic

    AI

    Ask your AI agent

    Create an IOTMP device called YOUR_DEVICE in my Thinger.io account and show me its credentials.

  2. Add the components

    The client is two components: iotmp-espidf and the protocol core it uses, iotmp-embedded. Clone both into the components folder of your project:

    bash
    cd your-project
    git clone https://github.com/thinger-io/IOTMP-ESPIDF.git components/iotmp-espidf
    git clone https://github.com/thinger-io/IOTMP-Embedded.git components/iotmp-embedded

    To keep them outside the project, set EXTRA_COMPONENT_DIRS in the top-level CMakeLists.txt to both folders instead.

    Then require the client from your main component. Require esp-tls too, which the client headers use:

    main/CMakeLists.txt
    cmake
    idf_component_register(SRCS "main.cpp"
                           PRIV_REQUIRES iotmp-espidf esp-tls esp_wifi nvs_flash esp_timer)
  3. Write the application

    The application connects to Wi-Fi, sets the credentials, declares the device resources and starts the client:

    main/main.cpp
    cpp
    #include <cstring>
    
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "freertos/event_groups.h"
    #include "esp_wifi.h"
    #include "esp_event.h"
    #include "esp_log.h"
    #include "esp_timer.h"
    #include "nvs_flash.h"
    
    #include <thinger/iotmp/client.hpp>
    
    #define WIFI_SSID     "wifi-network"
    #define WIFI_PASSWORD "wifi-password"
    
    static EventGroupHandle_t wifi_events;
    static const int WIFI_CONNECTED = BIT0;
    
    static void on_wifi_event(void* arg, esp_event_base_t base, int32_t id, void* data) {
        if (base == WIFI_EVENT && (id == WIFI_EVENT_STA_START || id == WIFI_EVENT_STA_DISCONNECTED)) {
            esp_wifi_connect();  // connect, and reconnect after a disconnection
        } else if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP) {
            xEventGroupSetBits(wifi_events, WIFI_CONNECTED);
        }
    }
    
    static void wifi_connect() {
        wifi_events = xEventGroupCreate();
        esp_netif_init();
        esp_event_loop_create_default();
        esp_netif_create_default_wifi_sta();
        wifi_init_config_t init = WIFI_INIT_CONFIG_DEFAULT();
        esp_wifi_init(&init);
        esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &on_wifi_event, NULL, NULL);
        esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &on_wifi_event, NULL, NULL);
        wifi_config_t config = {};
        strncpy((char*) config.sta.ssid, WIFI_SSID, sizeof(config.sta.ssid));
        strncpy((char*) config.sta.password, WIFI_PASSWORD, sizeof(config.sta.password));
        esp_wifi_set_mode(WIFI_MODE_STA);
        esp_wifi_set_config(WIFI_IF_STA, &config);
        esp_wifi_start();
        xEventGroupWaitBits(wifi_events, WIFI_CONNECTED, pdFALSE, pdTRUE, portMAX_DELAY);
    }
    
    static thinger::iotmp::client thing;
    
    extern "C" void app_main(void) {
        if (nvs_flash_init() != ESP_OK) {
            nvs_flash_erase();
            nvs_flash_init();
        }
        wifi_connect();
    
        thing.set_credentials("YOUR_USER", "YOUR_DEVICE", "YOUR_TOKEN");
        // thing.set_host("YOUR_SERVER");  // only on a private instance
    
        // output resource: read by the console, the API or a Data Bucket
        thing["uptime"] = [](thinger::iotmp::output& out) {
            out["seconds"] = (uint32_t) (esp_timer_get_time() / 1000000);
            out["free_heap"] = (uint32_t) esp_get_free_heap_size();
        };
    
        // input resource: receives {"state": true} or {"state": false}
        thing["led"] = [](thinger::iotmp::input& in) {
            bool on = in["state"].get<bool>();
            ESP_LOGI("app", "LED %s", on ? "on" : "off");
        };
    
        thing.start();  // connects and reconnects in its own task
    
        while (true) {
            vTaskDelay(pdMS_TO_TICKS(5 * 60 * 1000));
            // store a reading in a Data Bucket every 5 minutes
            thinger::iotmp::json_t data;
            data["seconds"] = (uint32_t) (esp_timer_get_time() / 1000000);
            thing.write_bucket("uptime_data", data);
        }
    }
    • YOUR_TOKEN is the device credentials, not an access token.
    • Without set_host(), the client connects to the free Cloud server. On a private instance, call thing.set_host("YOUR_SERVER").
    • The client does not set up the network: your application connects to Wi-Fi (or Ethernet) before thing.start().
    • Create the uptime_data Data Bucket in Data Buckets with From Device Write Call as Data Source before the first write.
  4. Build and flash

    bash
    idf.py set-target esp32
    idf.py build flash monitor
  5. Check the connection

    Go to Devices: the device shows a green status dot. Open it and go to API, expand uptime and click Run to read its output. You can also run thinger device resource YOUR_DEVICE uptime.

Result ​

The ESP32 is online in Devices, its uptime device resource answers from the console, and the uptime_data Data Bucket gets a new reading every 5 minutes.

Define device resources ​

The client declares device resources with the type of their argument:

DeclarationKind
thing["name"] = [](thinger::iotmp::output& out){ … };Output resource.
thing["name"] = [](thinger::iotmp::input& in){ … };Input resource.
thing["name"] = [](thinger::iotmp::input& in, thinger::iotmp::output& out){ … };Input and output.
thing["name"] = [](){ … };Runs an action, without data.

Read inputs by field with in["field"].get<bool>() (or get<float>(), get<int>()…), and fill outputs with out["field"] = value. thing["led"] << digitalPin(GPIO_NUM_2); controls a GPIO configured as an output.

The client can also call the platform from your code: thing.write_bucket(), thing.set_property(), thing.get_property(), thing.call_endpoint() and thing.stream(). These calls are safe from any task. See the Arduino coding guide for what each one does, and the IOTMP reference for the protocol.

Connection and reconnection ​

thing.start() runs the client in its own FreeRTOS task, named iotmp, and returns at once. The task connects, authenticates and reconnects on its own:

  • After a failed attempt it waits 5 seconds, and doubles the wait on each new failure up to 60 seconds. A successful connection resets the wait. thing.set_reconnect_timeout(base_ms, max_ms) changes both values.
  • It sends a keep-alive every 60 seconds; thing.set_keepalive(seconds) changes it.
  • thing.set_state_callback() receives the state changes (SOCKET_CONNECTING, AUTHENTICATED, DISCONNECTED, AUTH_FAILED…), for example to drive a status LED.
  • thing.stop() closes the connection and stops the task.

Configuration ​

Run idf.py menuconfig and open Thinger IOTMP Client:

OptionDefaultDescription
CONFIG_THINGER_IOTMP_TLSEnabledConnect over TLS (port 25206). Disable it for plain TCP (port 25204).
CONFIG_THINGER_IOTMP_STACK_SIZE8192Stack size of the client task, in bytes.
CONFIG_THINGER_IOTMP_PRIORITY5Priority of the client task.

With TLS, the client checks the server certificate with the ESP-IDF certificate bundle (CONFIG_MBEDTLS_CERTIFICATE_BUNDLE, enabled by default). The network must allow outgoing connections to the port in use.

Troubleshooting ​

SymptomCauseFix
client.hpp cannot find esp_tls.h when buildingesp-tls is missing from the requirements of your component.Add esp-tls to REQUIRES or PRIV_REQUIRES, as in step 2.
The state goes to AUTH_FAILEDThe credentials do not match the device.Check the username, the device ID and the device credentials.
The state stays in SOCKET_CONNECTINGThe server cannot be reached.Check the network, the host and that the port is open.

More cases in Troubleshooting.

Next steps ​