Table of Contents
Using the Library
Getting Started
Before we really get started, we will need a Device Integration Platform account. 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 Device Integration Platform 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 <sragent.h>
#include <srlogger.h>
using namespace std;
int main()
{
const char *server = "http://developer.customer.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 Device Integration Platform
return 0;
cerr << "Hello, Device Integration Platform!" << endl;
return 0;
}
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 main.cc
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.
$ LD_LIBRARY_PATH=$C8Y_LIB_PATH/lib ./a.out
...
Hello, Device Integration Platform!
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, Device Integration Platform! then exit. Voila, that's all we need to register a device to Device Integration Platform.
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 Device Integration Platform portal.
Integrate to Device Integration Platform
Device integration is a little more complex. The whole process is depicted in Fig 12, please refer to the device integration guide for detailed explanation. Steps 1, 2 and 3 are specific to SmartREST protocol as SmartREST requires predefined templates, see SmartREST guide and SmartREST reference for more information. Step 4 checks if the device is already stored in Device Integration Platform's database and only create it when it's not found. Steps 6 and 7 get the Device Integration Platform ID of the device from Device Integration Platform's database. Step 8 sets the Device Integration Platform ID as an alias for the device ID so that the device can find its Device Integration Platform 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 Device Integration Platform 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 Device Integration Platform 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 Device Integration Platform 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.customer.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 Device Integration Platform
return 0;
if (agent.integrate(srversion, srtemplate)) // integrate to Device Integration Platform
return 0;
agent.loop();
return 0;
}
Send Measurement
Now we have successfully integrated a demo device to Device Integration Platform, 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 Device Integration Platform, 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;
}
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.
Handle Operation
Besides sending requests, e.g., measurements to Device Integration Platform, the other important functionality is handle messages, either responses from GET queries or real-time operations from Device Integration Platform. 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 Device Integration Platform 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 Device Integration Platform 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 Device Integration Platform 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 Device Integration Platform.
Store 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/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:
- First line contains only the template version number.
- Every template must be on one line of its own.
- A line starts with
#
as first character (with no leading spaces or tabs) is considered a comment line and will be ignored. - A complete empty line (with no spaces and tabs) will be ignored.
- No trailing spaces or tabs are allowed for any line except comment lines.
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
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/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, Device Integration Platform!"
----------------------------------------
-- 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
Footnotes:
examples
folder in the repository.doc/html/index.html
in the library repository.