Device SDK for C++

The SmartREST `C++` library is a `C++` Software Developer Kit (SDK) for facilitating device integration to *Cumulocity*'s Internet of Things (IoT) platform.


SmartREST is Cumulocity’s innovative communication protocol specifically designed for the IoT world. It incorporates the highly expressive strength of the REST API, whereas at the same time replaces JSON with Comma Separated Values (CSV) to avoid the complexity of JSON parsing for embedded devices. Additionally, the terseness of CSV renders it highly efficient for IoT communication via mobile networks. It can save up to 80% mobile traffic compared to other HTTP APIs.

The SmartREST C++ library 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, and functionality for Cumulocity’s IoT integration, e.g., device registration, real-time device control, SmartREST template registration. The library employs a 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 Cumulocity’s 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 IoT world, HTTP is considerably bloated and traffic heavy. Oppositely, MQTT is a emerging lightweight messaging protocol based on publish and subscribe mechanism, this renders it very suited for IoT use cases. The library is designed in a way such that any agent software based on the library can transit from HTTP to MQTT with very little effort.

In the following chapters, we will first provide guidelines about how to successfully build the library for your target environment. Then we demonstrate how to use the library by walk through a series of example agent ranging from simple “hello world” agent to complex agent which uses Lua plugins for sending measurements and handle operations. Subsequently, we will also demonstrate how to transit one complete example from using HTTP to use MQTT as underlying communication layer.

In the end, we provide a reference of all build macros for further tailor and tune the build to suit your needs in case you have special target devices.

Building the C++ library


Table 1: Prerequisites for building the library.
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

Compiling the library

First, download a copy of the library from the git repository and change to the directory.

$ git clone
$ cd cumulocity-sdk-c

Second, initialize and update your submodule dependencies, since the library depends on the paho.mqtt.embedded-c library for MQTT support.

$ git submodule init
$ git submodule update

Then, create a file, and define specific macros CPPFLAGS, CXXFLAGS and LDFLAGS, LDLIBS and CXX if cross-compiling.

CXXFLAGS:=-Wall -pedantic -Wextra

Listing 3 shows a typical file example. In essence, defines search path for required c++ header files, preferred warning levels, search path for required c++ library files, and necessary linking flags.

When you do host compiling, many of these settings can obviously be omitted, these are more relevant for cross-compiling, which shall be the prevalent use case for the library. Later we will explain the file is also very important for another purpse, i.e., 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, e.g., you may want -Os optimization level instead of the default -O2, simply edit the copied Makefile.

Now we have done all 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. debug build produces 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

Getting started

Before we really get started, we will need a Cumulocity account. Go to, you can apply for a free trial account by click the “TRY CUMULOCITY FREE” button on the top-right corner. After signing-up and login to your tenant, you would find the device registration page in Device Management. Next, we will demonstrate how to register a device to Cumulocity using the library.


Without any further ado, let’s write our first program, the customary hello world example shown in Listing 1.

// ex-01-hello: src/
#include <iostream>
#include <sragent.h>
#include <srlogger.h>
using namespace std;

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
        SrAgent agent(server, deviceID);     // instantiate SrAgent
        if (agent.bootstrap(credentialPath)) // bootstrap to Cumulocity
                return 0;
        cerr << "Hello, Cumulocity!" << endl;
        return 0;
It's strongly encouraged that you pick a different random value for `deviceID`, as it's 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 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
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, I'd assume you have done so and will mention no more about `C8Y_LIB_PATH` in later examples.
Hello, Cumulocity!

Finally, it’s time to run our first program. Type the deviceID into the text field in your registration page (Fig 2) and click Register device. After the program is running, a green Accept button shall show up, click it to accept your device into your tenant.

As illustrated, the program will print Hello, Cumulocity! then exit. Voila, that’s all we need to register a device to Cumulocity.

The obtained device credential is stored in /tmp/helloc8y as defined in variable credentialPath. You can also find the credential in the Device credential page in your Cumulocity portal.

If you re-run the program the second time, the program will print *Hello, Cumulocity!* and exit immediately. This is because the program has loaded 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 Cumulocity

Device integration is a little more complex. The whole process is depicted in Fig 12. Refer to Device SDK for REST > Device integration for 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’s database and only create it when it’s not found. Steps 6 and 7 get the Cumulocity ID of the device from Cumulocity’s database. Step 8 sets the Cumulocity ID as an alias for the device ID so that the device can find its Cumulocity ID next time by querying with its device ID.


// ex-02-integrate: src/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 */

Listing 4 shows the required API interfaceby SrAgent when implementing your own integrate process. Basically, you need to subclass the pure virtual class SrIntegrate and realize 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 returned 0 for success, and a non-0 value for failure.

// ex-02-integrate: src/
#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;

Listing 5 realizes the flow chart depicted in Fig 12. You may have noticed all requests are Comma Separated Values (CSV) since we are using SmartREST instead of REST APIs directly. The corresponding SmartREST templates can be found in Listing 6. Important thing to note is that, you must store the correct SmartREST X-ID and device’s Cumulocity ID in the inherited member variables xid and id, respectively. They will be used by SrAgent after the integrate process for initializing corresponding internal variables.


Listing 6 extends the code in Listing 1. The only addition inside the main function is the call to SrAgent’s member function integrate for integrating to Cumulocity and loop for executing the agent loop. Above the main function is the definition of the SmartREST template version number and actual template content.

Please refer to Section (See section 1.1) about how to compile and run the code. After running this example code, you should see a device named HelloC8Y-Agent in All devices page in your Cumulocity tenant, as shown in Fig 15.

// ex-02-integrate: src/
#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
                return 0;
        if (agent.integrate(srversion, srtemplate)) // integrate to Cumulocity
                return 0;
        return 0;

Sending measurements

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

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

In the main function, we instantiate a CPUMEasurement and register it to an SrTimer in the constructor. SrTimer supports millisecond resolution, so 10 seconds is 10 * 1000 milliseconds.

The library is built upon an asynchronous model. Hence, the SrAgent class is not responsible for any networking duty, 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, we need to instantiate a SrReporter object and execute it in a separate thread.

// ex-03-measurement: src/
#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;
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`. Instead, 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, e.g., measurements to Cumulocity, the other important functionality is handle messages, either responses from GET queries or real-time operations from Cumulocity. Listing 8 demonstrates how to handle the c8yRestart operation. Again, first we will need to register necessary SmartREST templates. Then we define a message handler for handling restart operation.

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 starting execute device push in another thread. From now on, as soon as you execute an operation from your Cumulocity portal, device push will receive the operation immediately and your message handler will be invoked by the SrAgent.

// ex-04-operation: src/
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)
                        cerr << r.value(i) << " ";
                cerr << endl;
                agent.send("105," + r.value(2) + ",SUCCESSFUL");

int main()
        // ...
        // Inform Cumulocity 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;

Now run the program, then go to your Cumulocity tenant, execute an restart operation as shown in Fig 26. You should see the message printed in cerr and the operation is set to SUCCESSFUL in your control tab in Cumulocity.


Storing SmartREST templates in a file

Over time, your template collection would grow large, and you would like to store them in a text file instead of hard coding them in your source code. The benefits are tow-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.

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

// ex-05-template: src/
#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 following:

See listing 10 for an example of 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 your development, the library also supports rapid development in Lua. For Lua plugin support, you must build the library with explicitly enabling Lua support, as it’s disabled by default, see Chapter (See section ) about how to enable Lua plugin support.

Listing 11 demonstrates how to load a Lua plugin and add path lua/ into Lua’s package.path for library search path.

// ex-06-lua: src/
#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;

Listing 12 shows how to send CPU measurements and handle operation in Lua instead of c++. All Lua plugins are managed by SrLuaPluginManager, it is exposed to all Lua plugins as an opaque object named c8y. The only requirement for a Lua plugin is having a init function, which will be called by SrLuaPluginManager at load time to initialize the Lua plugin.

The example also shows how to define your own Lua library and share its variable myString in your Lua plugins.

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


-- 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
You may encounter an error saying "Package lua was not found in the pkg-config search path." when building this example, then you would 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, renders it 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.

Above examples are all using HTTP as the transportation layer. Besides HTTP, SrReporter also supports MQTT as the transportation layer. Listing 13 shows the modification needed for transforming the example in Section (See section 1.4) from using HTTP into using MQTT.

// ex-07-mqtt-legacy: src/

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, all modification needed is to construct SrReporter with a different constructor so SrReporter knows to use MQTT as underlying communication protocol, and remove 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.


1 All examples can be found in the `examples` folder in the 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 returns. We will get back to this function later.
4 Consult the [SmartREST reference]( about how to define SmartREST templates.
5 The code excerpt only includes 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 during the program is running.
7 Check `Lua` API reference in `doc/lua.html` for a complete list of all available APIs.

Customizing the build

In Chapter (See section ) we briefly explained how to build the library, in this chapter we will go into depth about how to customize the build options to tailor a optimal build for your particular use case.

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


    Switch for Lua plugin support, defaults to 0, which disables Lua support. Set it to 1 will enable Lua plugin support. Also remember to provide necessary Lua’s C library and add to your CPPFLAGS, CXXFLAGS, LDFLAGS and LDLIBS the required compile and link flags, etc.


    HTTP version, defaults to 1.1. Set it to 1.0 for environments when HTTP/1.1 is not supported.


    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.


    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 polling. When is parameter is set too high, the agent may appear to be sluggish, whereas when set too low, many CPU cycles are wasted. This is a trade-off parameter that needs to be fine-tuned for any particular device.


    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 in once. This number dictates the maximum number of messages that can be aggregated.


    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 comes 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, agent will be more responsive since it will not wait for aggregating next message. This is a trade-off parameter that needs to be fine-tuned for any particular use case.


    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 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.


    Whether allow 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, recommended approach is to re-built libcurl with an asynchronous DNS resolver.


    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.


    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. Possible page scale values and corresponding page size can be found in Table 1.

Table 1: List of page scale and 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

Following is a listing of existing agent software implemented based on the C++ SDK. - NetComm Agent Smartrest agent is an agent software implemented for NetComm NTC-6200 and NTC-140w routers, which is used in many production projects.