Skip to content

Devices

Zephyr

Connect a Zephyr RTOS board to Thinger.io with the IOTMP client module, added to your west workspace and enabled from prj.conf.

Guide3 min read

This guide connects a board running Zephyr RTOS to Thinger.io with the IOTMP client module. You add the module to your west workspace, enable it in prj.conf, expose device resources and turn on TLS.

To program an Arduino board from the Arduino IDE with the Arduino Zephyr core, use the Arduino library instead: see Portenta H7, Opta and GIGA R1 WiFi and the UNO Q.

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.
  • A Zephyr 4.0 or later workspace managed with west, and its SDK.
  • A board with a network interface supported by Zephyr (Ethernet, Wi-Fi or cellular) and C++ support.

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 module to west.yml

    Add the client and the protocol core it uses to the projects of your manifest:

    west.yml
    yaml
    manifest:
      projects:
        # ...
        - name: iotmp-zephyr
          url: https://github.com/thinger-io/IOTMP-Zephyr
          revision: main
          path: modules/lib/iotmp-zephyr
        - name: iotmp-embedded
          url: https://github.com/thinger-io/IOTMP-Embedded
          revision: main
          path: modules/lib/iotmp-embedded

    Then download them:

    bash
    west update
  3. Enable it in prj.conf

    The client needs networking with sockets, C++17 and eventfd:

    prj.conf
    ini
    # Networking
    CONFIG_NETWORKING=y
    CONFIG_NET_IPV4=y
    CONFIG_NET_TCP=y
    CONFIG_NET_SOCKETS=y
    CONFIG_DNS_RESOLVER=y
    CONFIG_NET_DHCPV4=y
    
    # C++
    CONFIG_CPP=y
    CONFIG_STD_CPP17=y
    CONFIG_GLIBCXX_LIBCPP=y
    CONFIG_EVENTFD=y
    
    # Thinger.io IOTMP client
    CONFIG_THINGER_IOTMP=y
    
    # TLS
    CONFIG_MBEDTLS=y
    CONFIG_MBEDTLS_BUILTIN=y
    CONFIG_MBEDTLS_TLS_VERSION_1_2=y
    CONFIG_NET_SOCKETS_SOCKOPT_TLS=y
    CONFIG_THINGER_IOTMP_TLS=y
    
    CONFIG_MAIN_STACK_SIZE=4096
    CONFIG_HEAP_MEM_POOL_SIZE=16384

    Add the options your board needs for its network interface, such as the Wi-Fi driver.

  4. Write the application

    src/main.cpp
    cpp
    #include <zephyr/kernel.h>
    #include <zephyr/logging/log.h>
    #include <thinger/iotmp/client.hpp>
    
    LOG_MODULE_REGISTER(app, LOG_LEVEL_INF);
    
    static thinger::iotmp::client thing;
    static float temperature = 22.5f;
    static float threshold = 30.0f;
    
    int main(void) {
        thing.set_credentials("YOUR_USER", "YOUR_DEVICE", "YOUR_TOKEN");
        // thing.set_host("YOUR_SERVER");  // only on a private instance
    
        // output resource: sensor readings
        thing["environment"] = [](thinger::iotmp::output& out) {
            out["temperature"] = temperature;
        };
    
        // input resource: receives {"state": true} or {"state": false}
        thing["led"] = [](thinger::iotmp::input& in) {
            bool on = in["state"].get<bool>();
            LOG_INF("LED %s", on ? "on" : "off");
        };
    
        // input and output resource: read and change a setting
        thing["threshold"] = [](thinger::iotmp::input& in, thinger::iotmp::output& out) {
            if (!in.is_empty()) {
                threshold = in["value"].get<float>();
            }
            out["value"] = threshold;
        };
    
        int rc = thing.start();  // connects and reconnects in its own thread
        if (rc < 0) {
            LOG_ERR("Cannot start the IOTMP client: %d", rc);
            return rc;
        }
    
        while (true) {
            k_sleep(K_MINUTES(5));
            // store a reading in a Data Bucket every 5 minutes
            thinger::iotmp::json_t data;
            data["temperature"] = temperature;
            thing.write_bucket("environment_data", data);
        }
        return 0;
    }
    • YOUR_TOKEN is the device credentials, not an access token.
    • Without set_host(), the client connects to the free Cloud server.
    • The client does not configure the network: Zephyr brings it up (DHCP in this prj.conf) and the client connects when it is ready.
    • Create the environment_data Data Bucket in Data Buckets with From Device Write Call as Data Source before the first write.
  5. Add the server certificate

    Over TLS, the client checks the server certificate against a CA certificate that your application registers. Register it with the Zephyr TLS credentials API before thing.start(), and pass the tag to the client:

    cpp
    #include <zephyr/net/tls_credentials.h>
    
    #define CA_TAG 1
    
    static const unsigned char ca_cert[] = "-----BEGIN CERTIFICATE-----\n"
                                           "...\n"
                                           "-----END CERTIFICATE-----\n";
    
    // in main(), before thing.start()
    tls_credential_add(CA_TAG, TLS_CREDENTIAL_CA_CERTIFICATE, ca_cert, sizeof(ca_cert));
    thing.set_tls_tag(CA_TAG);

    Use the root certificate of the authority that signs your server certificate.

  6. Build and flash

    bash
    west build -b YOUR_BOARD path/to/app
    west flash

    YOUR_BOARD is the Zephyr name of your board.

  7. Check the connection

    Go to Devices: the device shows a green status dot. Open it and go to API to call environment, led and threshold. You can also run thinger device resource YOUR_DEVICE environment.

Result ​

The board is online in Devices, its device resources answer from the console and the API, and the environment_data Data Bucket gets a new reading every 5 minutes.

Connection and reconnection ​

thing.start() runs the client in its own thread, named iotmp, and returns at once (a negative value is an error). The thread 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. Change it with thing.set_reconnect_timeout(base_ms, max_ms).

thing.set_state_callback() receives the state changes (SOCKET_CONNECTING, AUTHENTICATED, READY, DISCONNECTED, AUTH_FAILED…). thing.write_bucket(), thing.set_property(), thing.get_property(), thing.call_endpoint() and thing.stream() are safe from any thread. See the Arduino coding guide for what each one does.

Configuration ​

OptionDefaultDescription
CONFIG_THINGER_IOTMP—Enables the client. Needs NET_SOCKETS, CPP and STD_CPP17.
CONFIG_THINGER_IOTMP_TLSyConnects over TLS. Needs NET_SOCKETS_SOCKOPT_TLS and MBEDTLS.
CONFIG_THINGER_IOTMP_STACK_SIZE4096Stack size of the client thread, in bytes.
CONFIG_THINGER_IOTMP_PRIORITY7Priority of the client thread.
CONFIG_THINGER_IOTMP_LOG_LEVEL_DBG—Prints every step of the connection to the log. Use it while you test.

The client connects to port 25206, the TLS port. Keep TLS enabled: without it, set the plain TCP port with thing.set_host("YOUR_SERVER", 25204).

Next steps ​