Device SDK for C++


The SmartREST C++ library is a Software Development Kit (SDK) written in C++. It is designed for a wide range of devices which are powered by embedded Linux. It implements iterator-style lazy CSV lexer and parser, sophisticated request aggregation and robust request sending, as well as functionality for Cumulocity IoT integration, such as device registration, real-time device control and SmartREST template registration. The library employs an event-driven design which supports periodical timer callbacks and message-based callbacks, which will greatly reduce the development process of integrating your IoT devices to the Cumulocity IoT platform.

The C++ library supports both HTTP and MQTT as the underlying communication protocol. HTTP is a well-established, widely-adopted application protocol. For the IoT world, HTTP is considerably bloated and traffic heavy. Oppositely, MQTT is an emerging lightweight messaging protocol based on publish and subscribe mechanism; this renders it very suited for IoT use cases. The library is designed in such a way that any agent software based on the library can transit from HTTP to MQTT with very little effort.

In the following sections, we will first provide you guidelines about how to successfully build the library for your target environment. Then we explain how to use the library by walking you through a series of example agents, ranging from a simple “Hello world” to complex agents which uses Lua plugins for sending measurements and handle operations. Subsequently, we also explain how to transit one complete example from using HTTP to use MQTT as underlying communication layer. At the end, we provide a reference of all build macros for further tailoring and tuning the build to suit your needs in case you have special target devices.

Building the C++ library

Before starting developing your C++ agents for Cumulocity IoT, you need to build the library. The prerequisites for building it are listed in the table below.

Software Minimal Version Comment
Linux 2.6.32  
gcc (clang) 4.7 (3.3) both gcc and clang are supported
libcurl 7.26.0 older versions might work, but not tested
Lua 5.0 optional, for Lua plugin support only

Info: There are several libcurl packages against different TLS libraries. You can select the one you prefer.

Compiling the library

Download a copy of the library from the GitHub repository and change to the directory.

$ git clone
$ cd cumulocity-sdk-c

Initialize and update your submodule dependencies – the library depends on the paho.mqtt.embedded-c library for MQTT support.

$ git submodule init
$ git submodule update

Create an file and define the specific macros CPPFLAGS, CXXFLAGS and LDFLAGS, LDLIBS and CXX if cross-compiling.

CXXFLAGS:=-Wall -pedantic -Wextra

This is a typical file example. In essence, it defines the search path for the required C++ header files, preferred warning levels, search path for the required C++ library files, and the necessary linking flags.

When you do host compiling, many of these settings can be omitted; these are more relevant for cross-compiling, which shall be the prevalent use case for the library. Later we will explain that the file is also very important for another purpose, that is, build customization to tailor the library to your needs.

With the being defined, it’s time to define your makefile.

$ cp Makefile.template Makefile

The default Makefile.template can be used unchanged in most cases. In case some settings are not suitable for your use case, for example, you may want -Os optimization level instead of the default -O2, simply edit the copied Makefile.

Now that we have done all the preparation work, it’s time to build the library for your target device.

$ make

If everything is configured correctly, this should compile the library and output the final binary into the lib/ directory, and a watchdog daemon srwatchdogd into the bin/ directory in the root directory.

The build system supports both “debug” and “release” modes. The above command calling make without any target defaults to “debug” build. The “debug” build produces a much larger binary, more verbose output, etc; which is suitable for development phase. When releasing your software, you will likely want a “release” build. You can clear all intermediate build files and re-build the library in “release” mode when you want to release your software.

$ make clean
$ make release

C++ Device integration

Before we really get started, we need a Cumulocity IoT account. Go to and apply for a free trial. Click Try for free on the top-right corner. After signing-up and logging to your tenant, you can find the device registration page in the Device Management application. Later we will show how you may register a device in Cumulocity IoT using the library.

Cumulocity IoT Registration Page

Without any further ado, let’s start with the customary Hello world example. Open the cumulocity-sdk-c/examples/ex-01-hello/ file with the following code:

// ex-01-hello: examples/ex-01-hello/
#include <iostream>
#include <sragent.h>
#include <srlogger.h>

using namespace std;

int main ()
    const char *server = "";
    const char *credentialPath = "/tmp/helloc8y";
    const char *deviceID = "myID_1234";          // unique device identifier

    srLogSetLevel(SRLOG_DEBUG);                 // set log level to debug
    SrAgent agent(server, deviceID);            // instantiate the SmartREST agent

    if (agent.bootstrap(credentialPath))        // bootstrap to Cumulocity IoT
        return 0;

    cout << "Hello world of Cumulocity IoT!" << endl;

    return 0;

Info: It is strongly encouraged that you pick a different random value for deviceID as this is the unique identifier of your device.

For convenience, let’s define a shell variable C8Y_LIB_PATH to hold the library root path and use it to feed the compiler, so it can find all the necessary C++ header files and shared library (.so file).

$ export C8Y_LIB_PATH=/library/root/path
$ g++ -std=c++11 -I$C8Y_LIB_PATH/include -L$C8Y_LIB_PATH/lib -lsera

Info: You can define the variable C8Y_LIB_PATH in your .bashrc file, so you don’t need to define it every time when launching a new terminal. From now on, let’s assume you have done it, so it won’t be mentioned in later examples.

Finally, it’s time to run our first program.

Hello world of Cumulocity IoT!

Type the deviceID into the text field in your registration page as shown in the image below.

Cumulocity IoT Registration Page

Click Next to continue with the registration. After the program has run, click Accept and your device will be registered and shown in your tenant.

Cumulocity IoT Registration Page

As illustrated previously, the program will print to the standard output Hello world of Cumulocity IoT! and then exit. Voila! That’s all we need to register a device to Cumulocity IoT.

The obtained device credential is stored in the folder /tmp/helloc8y as defined in the variable credentialPath. You can also find the credential in Management > Device credentials in the Device Management application.

Info: If you re-run the program a second time, it will print Hello world of Cumulocity IoT! and exit immediately. This is because the program has loaded the available credential from the given credential file. You can manually delete the credential file if you want the program to request a new credential.

Integrating to the platform

Device integration is a bit more complex as illustrated in the flow diagram below. Refer to Device SDK for REST > Device integration for a detailed explanation. Steps 1, 2 and 3 are specific to the SmartREST protocol as SmartREST requires predefined templates, see Using the REST interface > Using SmartREST in the Microservice SDK guide and the SmartREST reference in the Reference guide for more information. Step 4 checks if the device is already stored in Cumulocity IoT’s database and only creates it when it’s not found. Steps 6 and 7 get the ID of the device from the Cumulocity IoT’s database. Step 8 sets the Cumulocity IoT ID as an alias for the device ID, so that the device can find its Cumulocity IoT ID next time by querying with its device ID.

Device integration flowchart

The code snippet below shows the required API interface by SrAgent when implementing your own integrate process. Basically, you need to subclass the pure virtual class SrIntegrate and implement its virtual function integrate with your particular integrate process. This is a callback function, which will be called by SrAgent when you call the integrate method of the SrAgent. By convention, the function shall return 0 for success, and a non-0 value for failure.

// ex-02-integrate: examples/ex-02-integrate/integrate.h
#include <sragent.h>

class Integrate: public SrIntegrate
    Integrate(): SrIntegrate() {}
    virtual ~Integrate() {}
    virtual int integrate(const SrAgent &agent, const string &srv,
                          const string &srt);
#endif /* INTEGRATE_H */

The following code snippet implements the flow diagram depicted above. You may have noticed that all requests are comma-separated values (CSV) since we are using SmartREST instead of REST APIs directly. It is important to mention that you must store the correct SmartREST X-ID and device’s Cumulocity IoT ID in the inherited member variables xid and id respectively. They will be used by SrAgent after the integrate process for initializing the corresponding internal variables.

// ex-02-integrate: examples/ex-02-integrate/
#include <srnethttp.h>
#include <srutils.h>
#include "integrate.h"
using namespace std;

int Integrate::integrate(const SrAgent &agent, const string &srv, const string &srt)
    SrNetHttp http(agent.server()+"/s", srv, agent.auth());
    if (registerSrTemplate(http, xid, srt) != 0) // Step 1,2,3
        return -1;

    if ("100," + agent.deviceID()) <= 0) // Step 4
        return -1;
    SmartRest sr(http.response());
    SrRecord r =;
    if (r.size() && r[0].second == "50") { // Step 4: NO
        if ("101") <= 0) // Step 5
            return -1;
        r =;
        if (r.size() == 3 && r[0].second == "501") {
            id = r[2].second; // Step 7
            string s = "102," + id + "," + agent.deviceID();
            if ( <= 0) // Step 8
                return -1;
            return 0;
    } else if (r.size() == 3 && r[0].second == "500") { // Step 4: YES
        id = r[2].second;                               // Step 6
        return 0;
    return -1;

The corresponding SmartREST templates can be found in the snippet below, which extends the code presented above. The only addition inside the main function is the call to SrAgent’s member function integrate for integrating to Cumulocity IoT and loop for executing the agent loop. Above the main function is the definition of the SmartREST template version number and actual template content.

// ex-02-integrate: examples/ex-02-integrate/
#include <sragent.h>
#include <srlogger.h>
#include "integrate.h"

using namespace std;

static const char *srversion = "helloc8y_1"; // SmartREST template version
static const char *srtemplate =              // SmartREST template collection


    "STRING STRING,\"{\"\"externalId\"\":\"\"%%\"\","


int main ()
    const char *server = "";
    const char *credentialPath = "/tmp/helloc8y";
    const char *deviceID = "13344568"; // unique device identifier

    srLogSetLevel(SRLOG_DEBUG);        // set log level to debug

    Integrate igt;
    SrAgent agent(server, deviceID, &igt); // instantiate SrAgent

    if (agent.bootstrap(credentialPath))   // bootstrap to Cumulocity IoT
        return 0;
    if (agent.integrate(srversion, srtemplate)) // integrate to Cumulocity IoT
        return 0;

    return 0;

After running this example, you will see a device named HelloC8Y-Agent in the devices list under Devices > All devices in the Device Management application.

Created device in Cumulocity IoT after integration process

Sending measurements

Now that we have successfully integrated a demo device to Cumulocity IoT, we can indeed do something more interesting. Let’s try sending CPU measurements every 10 seconds.

As shown in Integrating to Cumulocity IoT, first we need to add a new SmartREST template for CPU measurement and also increase the template version number. Then we subclass the pure virtual class SrTimerHandler and implement the () operator. CPUMEasurement is a callback functor which generates bogus CPU measurements using the rand function from the standard library. It will be called by the SrAgent at a defined interval of the registered SrTimer.

In the main function, we instantiate a CPUMEasurement and register it to a SrTimer in the class constructor. SrTimer supports millisecond resolution, so 10 seconds are 10,000 milliseconds.

The library is built upon an asynchronous model. Hence, the SrAgent class is not responsible for any networking duty, rather it is essentially a scheduler for all timer and message handlers. SrAgent.send merely places a message into the SrAgent.egress queue and returns immediately after. For actually sending SmartREST requests to Cumulocity IoT, we need to instantiate a SrReporter object and execute it in a separate thread.

// ex-03-measurement: examples/ex-03-measurement/
#include <cstdlib>

static const char *srversion = "helloc8y_2";
static const char *srtemplate =
// ...
    "NOW UNSIGNED NUMBER,\"{\"\"time\"\":\"\"%%\"\","
// ...

class CPUMeasurement: public SrTimerHandler {
    CPUMeasurement() {}
    virtual ~CPUMeasurement() {}
    virtual void operator()(SrTimer &timer, SrAgent &agent) {
        const int cpu = rand() % 100;
        agent.send("103," + agent.ID() + "," + to_string(cpu));

int main ()
    // ...
    CPUMeasurement cpu;
    SrTimer timer(10 * 1000, &cpu); // Instantiate a SrTimer
    agent.addTimer(timer);          // Add the timer to agent scheduler
    timer.start();                  // Activate the timer
    SrReporter reporter(server, agent.XID(), agent.auth(),
                        agent.egress, agent.ingress);

    if (reporter.start() != 0)      // Start the reporter thread
        return 0;

    return 0;

Info: If you add a SrTimer to the SrAgent, you must ensure its existence throughout the program lifetime since there is no way to remove a SrTimer from the SrAgent. Alternatively, you can use SrTimer.connect to register a different callback or deactivate it by SrTimer.stop. This is a design choice for encouraging timer reuse, instead of dynamically creating and destroying timers.

Handling operations

Besides sending requests, such as measurements to Cumulocity IoT, the other important functionality is to handle messages; either responses from GET queries or real-time operations from Cumulocity IoT. The following example shows how to handle the c8y_Restart operation. Again, first we will need to register the necessary SmartREST templates. Then we define a message handler for handling the restart operation.

// ex-04-operation: examples/ex-04-operation/
static const char *srversion = "helloc8y_3";
static const char *srtemplate =
// ...
    "UNSIGNED STRING,\"{\"\"c8y_SupportedOperations\"\":[%%]}\"\n"

    "UNSIGNED STRING,\"{\"\"status\"\":\"\"%%\"\"}\"\n"
// ...
// ...

class RestartHandler: public SrMsgHandler {
    RestartHandler() {}
    virtual ~RestartHandler() {}
    virtual void operator()(SrRecord &r, SrAgent &agent) {
        agent.send("105," + r.value(2) + ",EXECUTING");
        for (int i = 0; i < r.size(); ++i)
            cout << r.value(i) << " ";
            cout << endl;
            agent.send("105," + r.value(2) + ",SUCCESSFUL");

int main()
    // ...
    // Inform Cumulocity IoT about supported operations
    agent.send("104," + agent.ID() + ",\"\"\"c8y_Restart\"\"\"");
    RestartHandler restartHandler;
    agent.addMsgHandler(502, &restartHandler);
    SrDevicePush push(server, agent.XID(), agent.auth(), agent.ID(), agent.ingress);

    if (push.start() != 0)      // Start the device push thread
        return 0;

    return 0;

In the main function, we register the RestartHandler for SmartREST template (502), which is the template for the restart operation. We also need to instantiate a SrDevicePush object and start executing device push in another thread. From now on, as soon as you execute an operation from your Cumulocity IoT tenant, device push will receive the operation immediately and your message handler will be invoked by the SrAgent.

Now run the program and go to your Cumulocity IoT tenant, execute a restart operation as shown in the image below. Afterwards, you should see the message printed in the standard output cout and the operation status set to SUCCESSFUL in your control tab.

Execute a restart operation

Storing SmartREST templates in a file

Your template collection could grow large over time. Hence, you would like to store them in a text file instead of hard coding them in your source code. The benefits are two-fold: you don’t need to recompile the code every time only because the templates change, and there is no need to escape special characters which is error-prone.

An utility function readSrTemplate is provided for reading a template collection from a text file. The following example shows the usage of this function. It reads the file srtemplate.txt from the current directory and stores the version number and template content into arguments srversion and srtemplate respectively.

// ex-05-template: examples/ex-05-template/
#include <srutils.h>
// ...

int main ()
    // ...
    string srversion, srtemplate;
    if (readSrTemplate("srtemplate.txt", srverision, srtemplate) != 0)
        return 0;
    // ...

The file format required by readSrTemplate is as simple as follows:

See below an example of a template file.



10,101,POST,/inventory/managedObjects,application/json,application/json,%%,, "{""name"":""HelloC8Y-Agent"",""type"":""c8y_hello"", ""c8y_IsDevice"":{},""com_cumulocity_model_Agent"":{}}"

10,102,POST,/identity/globalIds/%%/externalIds,application/json,,%%,STRING STRING,"{""externalId"":""%%"",""type"":""c8y_Serial""}"

10,103,POST,/measurement/measurements,application/json,,%%,NOW UNSIGNED NUMBER,"{""time"":""%%"",""source"":{""id"":""%%""}, ""type"":""c8y_CPUMeasurement"", ""c8y_CPUMeasurement"":{""Workload"":{""value"":%%,""unit"":""%""}}}"

10,104,PUT,/inventory/managedObjects/%%,application/json,,%%,UNSIGNED STRING, "{""c8y_SupportedOperations"":[%%]}"

10,105,PUT,/devicecontrol/operations/%%,application/json,,%%,UNSIGNED STRING, "{""status"":""%%""}"




Lua plugin

Instead of using C++ for development, the library also supports rapid development in Lua. For this, you must build the library explicitly enabling Lua support as it is disabled by default. Refer to Customizing the build for more details.

The following example shows how to load the Lua plugin and add the path lua/ into Lua’s package.path for library search path.

// ex-06-lua: examples/ex-06-lua/
#include <srluapluginmanager.h>
// ...

int main()
    // ...
    SrLuaPluginManager lua(agent);
    lua.addLibPath("lua/?.lua");  // add given path to Lua package.path
    lua.load("lua/myplugin.lua"); // load Lua plugin
    // ...

    return 0;

It is feasible to send CPU measurements and handle operations in Lua instead of C++. All Lua plugins are managed by SrLuaPluginManager, and it is exposed to all Lua plugins as an opaque object named c8y. The only requirement for a Lua plugin is to have an init function, which will be called by SrLuaPluginManager at load time to initialize the Lua plugin.

The following example shows how to send CPU measurements, define your own Lua library and share its variable myString in your Lua plugins.

-- ex-06-lua: examples/ex-06-lua/lua/mylib.lua
myString = "Hello, Cumulocity IoT!"


-- ex-06-lua: examples/ex-06-lua/lua/myplugin.lua
local timer

function restart(r)
   c8y:send('105,' .. r:value(2) .. ',EXECUTING')
   for i = 0, r.size - 1 do     -- index in C++ starts from 0.
   c8y:send('105,' .. r:value(2) .. ',SUCCESSFUL')

function cpuMeasurement()
   local cpu = math.random(100)
   c8y:send('103,' .. c8y.ID .. ',' .. cpu)

function init()
   srDebug(myString)            -- myString from mylib
   timer = c8y:addTimer(10 * 1000, 'cpuMeasurement')
   c8y:addMsgHandler(502, 'restart')
   return 0                     -- signify successful initialization

Info: If you encounter an error saying “Package lua was not found in the pkg-config search path.” when building this example, then you need to modify the expression $(shell pkg-config --cflags lua) to add a proper version number to Lua. The proper version number depends on your installed Lua version and your Linux distribution.

Using MQTT instead of HTTP

MQTT is a publish and subscribe based light-weight messaging protocol, and it renders very suitable for IoT communication. It solves two major issues inherit to HTTP: 1) HTTP header predominantly overweights SmartREST payload since SmartREST messages are generally very short. 2) MQTT has built-in support for real-time notification via subscribe and publish mechanism, hence, there is no need for a separate connection for device push.

All the previous examples are using HTTP as the transportation layer. Besides HTTP, SrReporter also supports MQTT as the transportation layer. The following example shows the modification needed for using MQTT instead of HTTP.

// ex-07-mqtt-legacy: examples/ex-07-mqtt-legacy/

int main()
    // ...
    SrReporter reporter(string(server) + ":1883", deviceID, agent.XID(),
                        agent.tenant() + '/' + agent.username(),
                        agent.password(), agent.egress, agent.ingress);

    // set MQTT keep-alive interval to 180 seconds.
    reporter.mqttSetOpt(SR_MQTTOPT_KEEPALIVE, 180);
    if (reporter.start() != 0)      // Start the reporter thread
        return 0;

    return 0;

As you can see, the modification needed is to construct SrReporter with a different constructor, so SrReporter now uses MQTT as underlying communication protocol, and removes SrDevicePush in the code since MQTT has built-in support for real-time notification. Optionally, you can set the keep-alive interval for MQTT to prevent the underlying TCP connection from being interrupted.

Final remarks

  1. All examples can be found in the cumulocity-sdk-c/examples folder in the GitHub repository.
  2. The API reference is located in relative path doc/html/index.html in the library repository.
  3. The agent loop is an infinite loop, so it will never really return.
  4. Consult the SmartREST reference about how to define SmartREST templates.
  5. The code excerpts only include the added part. Check the examples folder for the complete example code.
  6. This is especially important when you dynamically allocate a timer on the heap: you must not destroy it while the program is running.
  7. Check the Lua API reference in doc/lua.html for a complete list of all available APIs.

Customizing the build

In Building the C++ library we briefly explained how to build the library. In this section we will go into depth about how to customize the build options to tailor an optimal build for your particular use case.

All the following customization options shall be added in your file.

Option Description
SR_PLUGIN_LUA Switch for Lua plugin support. Defaults to 0, which disables Lua support. Setting it to 1 will enable Lua plugin support. Also remember to provide the necessary Lua’s C library as well as to add the required compile and link flags and so on to your CPPFLAGS, CXXFLAGS, LDFLAGS and LDLIBS.
SR_PROTO_HTTP_VERSION HTTP version, defaults to 1.1. Set it to 1.0 for environments when HTTP/1.1 is not supported.
SR_SOCK_RXBUF_SIZE Maximum receive buffer size for SrNetSocket, defaults to 1024 bytes. This number dictates the maximum number of bytes the recv method of SrNetSocket can block waiting for response. This parameter only affects the receive buffer of SrNetSocket.
SR_AGENT_VAL Polling interval for SrAgent, defaults to 5 milliseconds. Internally SrAgent schedules all SrTimerHandler and SrMsgHandler by constantly polling for expired SrTimer and arrived messages from ingress SrQueue. This parameter dictates the interval between two consecutive pollings. When this parameter is set too high, the agent may appear to be sluggish, whereas when it is set too low, many CPU cycles are wasted. This is a trade-off parameter that needs to be fine-tuned for any particular device.
SR_REPORTER_NUM Maximum number of aggregated requests, defaults to 512. For saving traffic use, SrReporter has a mechanism to aggregate many messages into one request and send them all at once. This number dictates the maximum number of messages that can be aggregated.
SR_REPORTER_VAL Maximum waiting time between two consecutive requests for aggregation, defaults to 400 milliseconds. When aggregating requests, SrReporter will wait for consecutive messages with a defined timeout. If the next messages come after the timeout, SrReporter will stop the waiting loop and starts sending the already aggregated messages. When set to a higher number, higher aggregation can be expected, therefore, results in lower traffic use, whereas when set to a lower number, the agent will be more responsive since it will not wait for aggregating the next message. This is a trade-off parameter that needs to be fine-tuned for any particular use case.
SR_REPORTER_RETRIES Maximum number of retries when sending fails, defaults to 9 times. For counteracting temporary network failures, SrReporter implemented an exponential wait and multi-trials measure. When the first trial fails, it waits 1 second and retries again; when the second trial fails, it waits 2 seconds; when the third trial fails, it waits 4 seconds, and so on, until the defined number of retries are exhausted. Note when SrReporter enters the retry loop, messages sent via SrAgent will be queued up in the egress SrQueue, until the SrReporter successfully sends the aggregated requests so far or exhausts all retries.
SR_CURL_SIGNAL Whether allows libcurl from installing any signal handlers, defaults to 1, which allows libcurl to install signal handlers. Certain versions of libcurl contains a bug that when built with a synchronous DNS resolver, randomly crashes when the DNS lookup timed out. When you experience this issue, you can workaround this bug by disabling libcurl from installing signal handlers. As a side effect, libcurl will not be able to terminate DNS lookup.The recommended approach is to re-built libcurl with an asynchronous DNS resolver.
SR_SSL_VERIFYCERT Whether to verify server’s certificate when using HTTPS, defaults to 1. Many embedded devices have no CA certificates installed and thus not be able to verify server’s certificate when communicating via HTTPS. As a workaround, you can disable certificate verification by setting this macro to 0.
SR_FILEBUF_PAGE_SCALE Set scale of page size for file backed buffering, default is 3. When filebuf feature is enabled for SrReporter, messages are managed at a minimum unit of one page, instead of single message, for easy and efficient buffer managing. Therefore, larger page size will buffer more messages, but messages are also discarded in bigger chunks. In contrary, smaller page size buffers less messages, but messages are also discarded in smaller chunks.

The following table has the page scale and its corresponding page size for filebuf.

Page Scale Page Size
0 512 B
1 1 KB
2 2 KB
3 4 KB
4 8 KB
5 16 KB
6 32 KB
7 64 KB

Agent software reference

The following list presents the existing agent software implemented on the C++ SDK.