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.
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.
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 |
First, download a copy of the library from the git repository and change to the directory.
$ git clone https://bitbucket.org/m2m/cumulocity-sdk-c
$ 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 init.mk file, and define specific macros CPPFLAGS, CXXFLAGS and LDFLAGS, LDLIBS and CXX if cross-compiling.
CXX:=/usr/bin/g++
CPPFLAGS:=-I/usr/include
CXXFLAGS:=-Wall -pedantic -Wextra
LDFLAGS:=-L/usr/lib
LDLIBS:=-lcurl
Listing 3 shows a typical init.mk file example. In essence, init.mk 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 init.mk file is also very important for another purpse, i.e., build customization to tailor the library to your needs.
With the init.mk 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
Before we really get started, we will need a Cumulocity account. Go to https://cumulocity.com/, 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/main.cc
#include <iostream>
#include <sragent.h>
#include <srlogger.h>
using namespace std;
int main()
{
const char *server = "http://developer.cumulocity.com";
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;
}
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 main.cc
$ LD_LIBRARY_PATH=$C8Y_LIB_PATH/lib ./a.out
...
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.
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
#ifndef INTEGRATE_H
#define INTEGRATE_H
#include <sragent.h>
class Integrate: public SrIntegrate
{
public:
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/integrate.cc
#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;
http.clear();
if (http.post("100," + agent.deviceID()) <= 0) // Step 4
return -1;
SmartRest sr(http.response());
SrRecord r = sr.next();
if (r.size() && r[0].second == "50") { // Step 4: NO
http.clear();
if (http.post("101") <= 0) // Step 5
return -1;
sr.reset(http.response());
r = sr.next();
if (r.size() == 3 && r[0].second == "501") {
id = r[2].second; // Step 7
string s = "102," + id + "," + agent.deviceID();
if (http.post(s) <= 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/main.cc
#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
"10,100,GET,/identity/externalIds/c8y_Serial/%%,,"
"application/json,%%,STRING,\n"
"10,101,POST,/inventory/managedObjects,application/json,"
"application/json,%%,,\"{\"\"name\"\":\"\"HelloC8Y-Agent\"\","
"\"\"type\"\":\"\"c8y_hello\"\",\"\"c8y_IsDevice\"\":{},"
"\"\"com_cumulocity_model_Agent\"\":{}}\"\n"
"10,102,POST,/identity/globalIds/%%/externalIds,application/json,,%%,"
"STRING STRING,\"{\"\"externalId\"\":\"\"%%\"\","
"\"\"type\"\":\"\"c8y_Serial\"\"}\"\n"
"11,500,$.managedObject,,$.id\n"
"11,501,,$.c8y_IsDevice,$.id\n";
int main()
{
const char *server = "http://developer.cumulocity.com";
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;
agent.loop();
return 0;
}
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/main.cc
#include <cstdlib>
static const char *srversion = "helloc8y_2";
static const char *srtemplate =
// ...
"10,103,POST,/measurement/measurements,application/json,,%%,"
"NOW UNSIGNED NUMBER,\"{\"\"time\"\":\"\"%%\"\","
"\"\"source\"\":{\"\"id\"\":\"\"%%\"\"},"
"\"\"type\"\":\"\"c8y_CPUMeasurement\"\","
"\"\"c8y_CPUMeasurement\"\":{\"\"Workload\"\":"
"{\"\"value\"\":%%,\"\"unit\"\":\"\"%\"\"}}}\"\n"
// ...
class CPUMeasurement: public SrTimerHandler {
public:
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;
agent.loop();
return 0;
}
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/main.cc
static const char *srversion = "helloc8y_3";
static const char *srtemplate =
// ...
"10,104,PUT,/inventory/managedObjects/%%,application/json,,%%,"
"UNSIGNED STRING,\"{\"\"c8y_SupportedOperations\"\":[%%]}\"\n"
"10,105,PUT,/devicecontrol/operations/%%,application/json,,%%,"
"UNSIGNED STRING,\"{\"\"status\"\":\"\"%%\"\"}\"\n"
// ...
"11,502,,$.c8y_Restart,$.id,$.deviceId\n";
// ...
class RestartHandler: public SrMsgHandler {
public:
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;
agent.loop();
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.
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/main.cc
#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:
#
as first character (with no leading spaces or tabs) is considered a comment line and will be ignored.See listing 10 for an example of template file.
helloc8y_3
10,100,GET,/identity/externalIds/c8y_Serial/%%,,application/json,%%,STRING,
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"":""%%""}"
11,500,$.managedObject,,$.id
11,501,,$.c8y_IsDevice,$.id
11,502,,$.c8y_Restart,$.id,$.deviceId
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/main.cc
#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
require('mylib')
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.
srDebug(r:value(i))
end
c8y:send('105,' .. r:value(2) .. ',SUCCESSFUL')
end
function cpuMeasurement()
local cpu = math.random(100)
c8y:send('103,' .. c8y.ID .. ',' .. cpu)
end
function init()
srDebug(myString) -- myString from mylib
timer = c8y:addTimer(10 * 1000, 'cpuMeasurement')
c8y:addMsgHandler(502, 'restart')
return 0 -- signify successful initialization
end
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/main.cc
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;
agent.loop();
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.
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 init.mk file.
SR_PLUGIN_LUA=0
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.
SR_PROTO_HTTP_VERSION=1.1
HTTP version, defaults to 1.1. Set it to 1.0 for environments when HTTP/1.1
is not supported.
SR_SOCK_RXBUF_SIZE=1024
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=5
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.
SR_REPORTER_NUM=512
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.
SR_REPORTER_VAL=400
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.
SR_REPORTER_RETRIES=9
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.
SR_CURL_SIGNAL=1
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.
SR_SSL_VERIFYCERT=1
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=3
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.
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 |
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.
Linux Agent Linux agent is a reference agent implementation, which can be used on any devices that are powered by a generic Linux distribution, e.g., Ubuntu, Fedora, etc.