开发者中心

使用

目录

使用库

入门

真正开始前,我们需要一个QuarkIoE账号。 访问 https://www.quarkioe.com/,点击右上角的"免费试用QuarkIoE"可以申请一个免费试用账号。注册并登录到租户,在设备管理 可以找到设备注册页面。接下来,我们演示如何通过库注册一个设备到QuarkIoE。

图像

很简单的,我们写第一个程序,hello world 例子如清单 1 所示。

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

int main()
{
    const char *server = "http://developer.quarkioe.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 Quarkioe
            return 0;
    cerr << "Hello, Quarkioe!" << endl;
    return 0;
}
                        
                   
强烈建议为 deviceID 选择一个不同的随机数,因为它是设备的唯一标识符。

为方便起见,我们定义一个脚本变量 C8Y_LIB_PATH 保存库根目录,以便编译器能找到需要的 C++ 头文件和共享库 .so 文件。

                        
$ export C8Y_LIB_PATH=/library/root/path
$ g++ -std=c++11 -I$C8Y_LIB_PATH/include -L$C8Y_LIB_PATH/lib -lsera main.cc
                        
                   
可以在 .bashrc 文件定义变量 C8Y_LIB_PATH ,这样就不用每次打开终端时都重新定义一次。 从现在起,我们假定你已经这样做了,在后面的例子中将不再提 C8Y_LIB_PATH
                        
$ LD_LIBRARY_PATH=$C8Y_LIB_PATH/lib ./a.out
...
Hello, Quarkioe!
                        
                   

最后,该运行第一个程序了。在注册设备页面文本框输入 deviceID (图 2) 并点击注册设备。程序运行后,将显示一个绿色的接受按钮,点击按钮在本租户接受此设备。

如图所示,程序将打印Hello, Quarkioe!然后退出。这就是注册设备到 QuarkIoE所要做的全部事情。

得到的设备凭据存储在 /tmp/helloc8y 的变量名 credentialPath 中。也可以在QuarkIoE门户页面的设备凭据页面找到凭据。

如果第二次运行程序,程序打印Hello, Quarkioe!然后立即退出。这是因为程序从凭据文件加载了凭据。如果想让程序请求新凭据,可以手动删除凭据文件。

集成到QuarkIoE

设备集成稍微复杂一些。整个过程如图 12 所示,详细解释请参阅 设备集成 指南。 步骤1,2 和3是SmartREST 协议才需要的,因为SmartREST需要模板,更多信息参见 SmartREST 指南SmartREST 参考。步骤 4 检查设备是否已经保存在QuarkIoE的数据库中,如果没有才创建。步骤6和7从QuarkIoE数据库取得设备的QuarkIoE ID。步骤 8 设置QuarkIoE ID 作为设备ID的别名,以便下次用设备ID查询时可以找到 QuarkIoE 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 */
                      
                   

清单 4 显示了实施集成过程 SrAgent 所需的API接口。基本上,需要继承纯虚类SrIntegrate并按具体集成过程实现虚函数。这是一个回调函数,当调用 SrAgentintegrate 方法时被 SrAgent 调用。按照惯例,此函数成功返回 0,失败返回非0。

                       
// 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;
}
                        
                   

清单 5 实现了图 12 所示的流程图。你可能注意到了所有的请求都是逗号分隔值 (CSV),这是由于我们用SmartREST代替直接使用REST API。清单 6 是相应的SmartREST模板。需要注意的重要事情是在继承的成员变量xidid里必须分别保存正确的SmartREST X-ID以及设备的 QuarkIoEQuarkioe ID。集成过程后初始化内部变量时由 SrAgent 使用。

图像

清单 6 拓展了清单 1 的代码。 main 函数中仅仅增加了 对 SrAgent 成员函数 integrate 的调用,用于集成到QuarkIoE 并且为执行agent中的 loop循环 。在 main 函数上面定义了SmartREST的模板版本和模板内容。

请参阅章节 (见第1.1节) 关于如何编译和运行代码。运行此示例后,应该在QuarkIoE租户的所有设备页面看到名为 HelloC8Y-Agent 的设备,如图 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.quarkioe.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 Quarkioe
            return 0;
    if (agent.integrate(srversion, srtemplate)) // integrate to Quarkioe
            return 0;
    agent.loop();
    return 0;
}
                        
                   

发送测量值

现在我们已经成功集成了一个演示设备到 QuarkIoE,我们可以做一些更有趣的事,现在尝试每10秒发送一次CPU测量值。

如清单 7 所示,我们首先需要添加一个CPU测量值的新SmartREST模板,并增加SmartREST模板版本号。然后我们继承纯虚类 SrTimerHandler 并实现 () 运算符。 CPUMEasurement 是回调函数,它用标准库的 rand 生成假的CPU测量值。SrTimer 触发后由 SrAgent 调用。

main 函数中,我们实例化了一个 CPUMEasurement 并在 构造函数时注册到 SrTimerSrTimer 支持毫秒精度,因此10秒是 10 * 1000 毫秒。

库采用异步模式构建。因此, SrAgent 类不承担任何网络任务,它实质上是所有定时器和消息处理器的调度器。SrAgent.send 仅仅把消息放入 SrAgent.egress 队列,然后立即返回。为了真正发送SmartREST请求到QuarkIoE,我们需要实例化一个 SrReporter 对象并在一个单独线程执行。

                          
// 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;
}
                          
                     
想要添加一个 SrTimerSrAgent,需要确保在整个程序生命周期都存在,因为没有办法从 SrAgent 删除 SrTimer。相反,可以使用 SrTimer.connect 注册不同的回调函数或者调用 SrTimer.stop 来停用。这是一个鼓励重用定时器,而不是动态创建销毁定时器的设计。

处理操作

除了发送请求,例如,测量值到QuarkIoE,另一个重要的功能是处理消息,要么是相应GET请求,要么是来自QuarkIoE的实时操作。清单 8 展示的如何处理 c8yRestart 操作。再提一次,我们首先需要注册需要的SmartREST模板。然后,我们定义一个消息处理函数用于处理重启操作。

main 函数,我们为SmartREST模板(502)(重启操作的模板)注册 RestartHandler。我们还要实例化一个 SrDevicePush 对象并在另一个线程执行设备推送。从现在起,只要你在QuarkIoE 设备控制中添加操作,设备推送将立即收到操作,消息处理函数立即被 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 Quarkioe 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;
}
                         
                     

现在运行程序,然后访问QuarkIoE租户,执行重启操作,如图 26 所示。 你将可以看到消息打印到 cerr 并且QuarkIoE 的控制选项卡中操作被设置为 成功。

图像

保存SmartREST模板到文件

随着时间推移,你的模板集合变大,你会希望把它们存储在文件中而不是直接写在源码中。两个好处:不会仅仅因为模板变化重新编译代码,也不需要转义特殊字符,转义特殊字符容易出错。

用工具函数 readSrTemplate 用于从文件读模板集合。清单 9 显示了如何使用这个函数。它从当前目录读 srtemplate.txt 文件并分别保存版本号和模板内容到 srversionsrtemplate 参数。

                         
// ex-05-template: src/main.cc
#include <srutils.h>
// ...

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

readSrTemplate 需要的文件格式很简单,如下所示:

  • 第一行只包含模板版本号。
  • 每个模板必须在自己的一行。
  • # 开始的行 (前面没有空格和制表符) 是注释行,将被忽略。
  • 完全的空行 (没有空格和制表符) 将被忽略。
  • 除了注释行,任何行都不允许在末尾添加空格或制表符。

模板文件示例请参见清单 10。

                         
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

除了使用 c++ 开发,库还支持用 Lua 快速开发。为了支持 Lua 插件,构建时必须明确支持 Lua,缺省情况下是不支持的,关于如何支持 Lua 插件参见其他章节。

清单 11 展示了如何加载 Lua 插件以及为库搜索添加 lua/ 目录到 Luapackage.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;
}
                         
                     

清单 12 显示了发送CPU测量值以及用Lua而不是 c++ 处理操作。所有 Lua 插件由 SrLuaPluginManager 管理,它作为一个命名为 c8y 的不透明对象对所有 Lua 插件公开。Lua 插件的唯一要求是必须有一个 init 函数,加载时由 SrLuaPluginManager 调用初始化 Lua 插件。

示例还演示了如何定义自己的 Lua 库并且在 Lua 插件中共享变量 myString

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

----------------------------------------

-- 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
                         
                     
您可能会遇到一个错误,"Package lua was not found in the pkg-config search path."。 当构建此示例时,则需要修改表达式$(shell pkg-config --cflags lua)以向lua添加正确的版本号。 正确的版本号取决于您安装的Lua版本和您的Linux发行版。

附注:

1 所有例子都可以在代码库的 examples 目录找到。
2 API参考位于相对路径 doc/html/index.html
3 agent循环是无限循环,永不返回。我们随后回到这个功能。
4 如何定义SmartREST模板,参见 SmartREST 参考
5 代码只包含了增加的部分,完整示例代码请参见 examples 目录。
6 需要注意的是,程序运行时,不能销毁在堆上动态创建的定时器。
7 完整API列表,参见 Lua API 参考 doc/lua.html