开发者中心

设备集成

概述

集成设备到QuarkIoE的基本生命周期在 设备交互中讨论。在这一节中,我们将展示生命周期如何在REST级别上实现。生命周期由两个阶段组成,一个启动阶段和一个循环阶段。

启动阶段负责连接设备到QuarkIoE并更新设备清单设备数据。它还执行操作所需的清理任务。包括以下步骤:

  • 步骤 0: 如果还没有请求设备凭证则请求设备凭证。
  • 步骤 1: 检查设备是否已经注册。
  • 步骤 2: 如果设备清单没有此设备则创建设备,并
  • 步骤 3: 注册此设备。
  • 步骤 4: 如果设备清单有此设备,则在设备清单中更新此设备。
  • 步骤 5: 发现子设备并在设备清单创建或更新。
  • 步骤 6: 完成需要重启的操作并订阅新操作。
启动阶段

接下来是循环阶段。它不断更新设备清单,写测量值,报警和事件,并在需要时执行操作。可以认为这是设备的 "主循环" ,直到设备关闭才停止执行。循环阶段由以下步骤组成:

循环阶段

数据的参考模型参见 设备管理库传感器库

启动阶段

步骤 0: 请求设备凭证

因为每一个向QuarkIoE的请求都需要验证,从设备发起的请求也需要验证。如果要给每个设备分配单独的凭证,可以使用设备凭证API来自动生成新凭证。要做到这一点,第一次启动时通过API请求设备凭据,并保存在设备本地,用于后续的请求。

过程如下:

  • QuarkIoE假定每个设备都有某种形式的唯一ID。一个好的设备标识符可以是网络适配器的MAC地址,移动设备的IMEI或硬件序列号
  • 当你把一个新的设备投入使用,在QuarkIoE的"设备集成"输入唯一ID并启动设备。
  • 设备连接到QuarkIoE并重复发送唯一ID。 为此,QuarkIoE提供静态主机,可以通过 support@quarkioe.com。
  • 可以在"设备注册"中接受来自设备的连接,在这种情况下,QuarkIoE发送生成的凭证给设备。
  • 设备在所有后续的请求中使用这些凭证。

从设备的角度来看,这是一个单一的REST请求:


    POST /devicecontrol/deviceCredentials
    Content-Type: application/vnd.com.nsn.cumulocity.deviceCredentials+json;ver=...
    Authorization: Basic ...
    {
    "id" : "0000000017b769d5"
                }
                  

该设备重复发出这一请求。当用户尚未注册和接受该设备时,请求返回 "404 Not Found."。 在该设备被接受后,返回以下应答:


    HTTP/1.1 200 OK
    Content-Type: application/vnd.com.nsn.cumulocity.deviceCredentials+json;ver=...
    Content-Length: ...
    {
    "id" : "0000000017b769d5",
    "self" : "<<URL of new request>>",
    "tenantId" : "test",
    "username" : "device_0000000017b769d5",
    "password" : "3rasfst4swfa"
}
                  

现在该设备可以使用租户ID,用户名和密码连接到QuarkIoE。

步骤 1: 检查设备是否已经注册

设备的唯一ID也用于在设备清单中注册设备。 注册使用 身份标识API。 在身份标识API中, 每个托管对象都可以与根据类型区分的多个标识符关联。举例来说,类型就是硬件序列号的 "c8y_Serial" , MAC地址的 "c8y_MAC" 以及IMEI的 "c8y_IMEI"。

要检查设备是否已注册,请使用设备标识符和类型用身份识别API发送GET请求。下面的例子展示了用硬件序列号"0000000017b769d5"检查树莓派。


    GET /identity/externalIds/c8y_Serial/raspi-0000000017b769d5 HTTP/1.1

    HTTP/1.1 200 OK
    Content-Type: application/vnd.com.nsn.cumulocity.externalId+json; charset=UTF-8; ver=0.9
    ...
    {
    "externalId": "raspi-0000000017b769d5",
    "managedObject": {
    "id": "2480300",
    "self": "https://.../managedObjects/2480300"
},
"self": "https://.../identity/externalIds/c8y_Serial/raspi-0000000017b769d5",
"type": "c8y_Serial"
}
                  

注意,MAC地址确保全球唯一, 而不同硬件的序列号可能重叠。因此,在上面的例子中,我们在序列号前加前缀 "raspi-"。

在这种情况下,设备已经注册,并返回200状态码。在应答中,指向设备清单中的设备的URL通过 "managedObject.self"返回。这个URL可以用于对这个设备的后续请求。

If a device is not yet registered, a 404 status code and an error message is returned:


    GET /identity/externalIds/c8y_Serial/raspi-0000000017b769d6 HTTP/1.1

    HTTP/1.1 404 Not Found
    Content-Type: application/vnd.com.nsn.cumulocity.error+json;charset=UTF-8;ver=0.9
    ...
    {
    "error": "identity/Not Found",
    "info": "https://doc.cumulocity.com/guides/reference-guide.html#error_reporting",
    "message": "External id not found; external id = ID [type=c8y_Serial, value=raspi-0000000017b769d6]"
}
                  
步骤 2: 在设备清单中创建设备

如果上面的步骤 1 中表明没有代表设备的托管对象存在, 则在QuarkIoE中创建托管对象。托管对象描述设备的实例和元数据。实例数据包括硬件和软件信息, 序列号, 以及设备配置数据。元数据描述设备的功能, 包括支持的操作。

要创建一个托管对象,在设备清单API的托管对象集合上执行POST请求。下面的例子使用Linux agent创建一个树莓派:


    POST /inventory/managedObjects HTTP/1.1
    Content-Type: application/vnd.com.nsn.cumulocity.managedObject+json
    Accept: application/vnd.com.nsn.cumulocity.managedObject+json
    ...
    {
    "name": "RaspPi BCM2708 0000000017b769d5",
    "type": "c8y_Linux",
    "c8y_IsDevice": {},
    "com_cumulocity_model_Agent": {},
    "c8y_SupportedOperations": [ "c8y_Restart", "c8y_Configuration", "c8y_Software", "c8y_Firmware" ],
    "c8y_Hardware": {
    "revision": "000e",
    "model": "RaspPi BCM2708",
    "serialNumber": "0000000017b769d5"
},
    "c8y_Configuration": {
    "config": "#Fri Aug 30 09:13:56 BST 2013\nc8y.log.eventLevel=INFO\n..."
},
    "c8y_Mobile": {
    "imei": "861145013087177",
    "cellId": "4904-A496",
    "iccid": "89490200000876620613"
},
    "c8y_Firmware": {
    "name": "raspberrypi-bootloader",
    "version": "1.20130207-1"
},
    "c8y_Software": {
    "pi-driver": "pi-driver-3.4.5.jar",
    "pi4j-gpio-extension": "pi4j-gpio-extension-0.0.5.jar",
...
}
}

HTTP/1.1 201 Created
Content-Type: application/vnd.com.nsn.cumulocity.managedObject+json;charset=UTF-8;ver=0.9
...
{
    "id": "2480300",
    "lastUpdated": "2013-08-30T10:12:24.378+02:00",
    "name": "RaspPi BCM2708 0000000017b769d5",
    "owner": "admin",
    "self": "https://.../inventory/managedObjects/2480300",
    "type": "c8y_Linux",
    "c8y_IsDevice": {},
    ...
    "assetParents": {
    "references": [],
    "self": "https://.../inventory/managedObjects/2480300/assetParents"
},
    "childAssets": {
    "references": [],
    "self": "https://.../inventory/managedObjects/2480300/childAssets"
},
    "childDevices": {
    "references": [],
    "self": "https://.../inventory/managedObjects/2480300/childDevices"
},
    "deviceParents": {
    "references": [],
    "self": "https://.../inventory/managedObjects/2480300/deviceParents"
}
}
                  

上面的例子包含了设备大量元数据条目:

  • "c8y_IsDevice" 标志设备可以通过QuarkIoE的"设备管理"进行管理。
  • "com_cumulocity_model_Agent" 标志设备运行QuarkIoEagent。这样的设备可以接收针对它们自己的操作和路由到子设备的操作。
  • "c8y_SupportedOperations" 标志设备可以重启和配置。此外,可以进行软件和固件的更新。

更多信息请参阅 设备管理库

如果设备成功创建,返回201状态码。如果原始请求像例子中一样包含一个 "Accept" 头, 完整的创建的对象被返回,返回完整的对象,包括ID和指向对象的URL,用于后续的的请求。返回完整的对象还包括对子设备和子资产的集合的引用,用于后续将这些子设备和子资产添加到设备中(见下文)。

步骤 3: 注册设备

新设备创建后,现在可以和步骤 1 中提到的内建标识符关联。这将确保下一次加电后设备可以在QuarkIoE找到自己。

继续上面的例子,我们将用硬件序列号关联新创建的设备 "2480300" :


    POST /identity/globalIds/2480300/externalIds HTTP/1.1
    Content-Type: application/vnd.com.nsn.cumulocity.externalId+json
    Accept: application/vnd.com.nsn.cumulocity.externalId+json
    ...
    {
    "type" : "c8y_Serial",
    "externalId" : "raspi-0000000017b769d5"
}

HTTP/1.1 201 Created
Content-Type: application/vnd.com.nsn.cumulocity.externalId+json;charset=UTF-8;ver=0.9
...
{
    "externalId": "raspi-0000000017b769d5",
    "managedObject": {
    "id": "2480300",
    "self": "https://.../inventory/managedObjects/2480300"
},
    "self": "https://.../identity/externalIds/c8y_Serial/raspi-0000000017b769d5",
    "type": "c8y_Serial"
}
                  
步骤 4: 在目录清单中更新设备

如果上述步骤 1 中返回的设备已经注册, 我们需要确保设备在设备清单中的当前状态是最新的。为了这个目的,发送PUT请求到设备清单的设备URL。请注意,只有真正变化的片段需要被发送。 (更多片段信息参见 QuarkIoE领域模型)

例如, 设备硬件信息通常不变化, 但软件安装可能变化。因此,设备重启后,设备清单的软件信息更新到最新状态可能是有意义的:


    PUT /inventory/managedObjects/2480300 HTTP/1.1
    Content-Type: application/vnd.com.nsn.cumulocity.managedObject+json
    ...
    {
    "c8y_Software": {
    "pi-driver": "pi-driver-3.4.6.jar",
    "pi4j-gpio-extension": "pi4j-gpio-extension-0.0.5.jar"
}
}   

HTTP/1.1 200 OK
                  

不要从agent更新设备名! agent为设备创建缺省名称,以便可以在设备清单中识别, 但用户应该可以编辑这个名称或用资产管理信息更新。

步骤 5: 发现子设备并在设备清单创建或更新

根据传感器网络的复杂程度, 设备可能有相关联的子设备。 一个很好的例子是家庭自动化: 你经常有一个家庭自动化网关, 在不同房间里安装许多不同的传感器和控制。事实上子设备的基本注册类似主设备, 子设备通常不运行agent实例(因此略去 "com_cumulocity_model_Agent" 片段)。要连接子设备到设备, 向创建对象时返回的子设备URL发送POST请求(见上面)。

例如, 假设子设备已经创建,要父设备连接此设备, 执行:


    POST /inventory/managedObjects/2480300/childDevices HTTP/1.1
    Content-Type: application/vnd.com.nsn.cumulocity.managedObjectReference+json
    { "managedObject" : { "self" : "https://.../inventory/managedObjects/2543801" } } 

    HTTP/1.1 201 Created
                  

最后, 设备和引用可以通过执行对它们的URL执行DELETE请求删除。 例如, 要删除我们刚刚创建的父设备对子设备的引用,执行:


   DELETE /inventory/managedObjects/2480300/childDevices/2543801 HTTP/1.1

    HTTP/1.1 204 No Content
                  

这并不会删除设备清单中设备本身, 仅仅删除引用。要删除设备,执行:


    DELETE /inventory/managedObjects/2543801 HTTP/1.1

    HTTP/1.1 204 No Content
                  

此请求也将删除与该设备相关联的所有数据,包括它的注册信息, 测量值, 报警, 事件和操作。通常情况下,不建议自动删除设备。例如,如果一个设备只是暂时失去了连接,你通常不想失去所有与此设备相关的历史信息。

执行操作

在QuarkIoE中每个操作通过执行流程循环。当通过QuarkIoE应用程序创建了一个操作, 状态是 "PENDING", 即已经排队等待处理但尚未执行。 当agent提取此操作并开始执行, 则在QuarkIoE中被标志为 "EXECUTING"。 agent将在此设备或子设备上执行操作 (例如, 启动设备, 或设置继电器)。然后,它可能更新设备清单以反映设备或子设备的新状态 (例如, 它在设备清单更新继电器状态)。 然后agent在QuarkIoE标记操作为 "SUCCESSFUL" 或 "FAILED", 用于标识错误。

操作状态图解

这个执行流程的好处是,它支持脱机和暂时不覆盖的设备。它还允许设备支持需要重新启动的操作 -- 如固件升级。重新启动后,该设备需要知道它前面做了什么,因此需要查询所有"EXECUTING"操作,看看他们是否成功了。此外,它还需要监听队列中相关的新操作。

步骤 6: 完成操作和订阅

要清理还在"EXECUTING"状态的操作, 通过agent ID 和状态查询操作。在我们的例子中,请求将是:

GET /devicecontrol/operations?agentId=2480300&status=EXECUTING HTTP/1.1

    HTTP/1.1 200 OK
    Content-Type: application/vnd.com.nsn.cumulocity.operationCollection+json;; charset=UTF-8; ver=0.9
    ...
    {
    "next": "https://.../devicecontrol/operations?agentId=2480300&status=EXECUTING",
    "operations": [
    {
    "creationTime": "2013-08-29T19:49:15.239+02:00",
    "deviceId": "2480300",
    "id": "2593101",
    "self": "https://.../devicecontrol/operations/2480300",
    "status": "EXECUTING",
    "c8y_Restart": {
}
}
],
"statistics": {
"currentPage": 1,
"pageSize": 2000
},
"self": "https://.../devicecontrol/operations?agentId=2480300&status=EXECUTING"
}
                  

重新启动似乎已经执行得很好 -- 毕竟我们回来了。所以我们设置操作为"SUCCESSFUL"。


    PUT /devicecontrol/operations/2480300 HTTP/1.1
    Content-Type: application/vnd.com.nsn.cumulocity.operation+json
    {
    "status": "SUCCESSFUL"
}

HTTP/1.1 200 OK
                  

然后, 监听QuarkIoE创建的新操作。QuarkIoE实时数据监听机制参见 实时通知 和作为基础的标准Bayeux协议。 首先, 需要一个握手。握手告诉QuarkIoEagent支持什么通知协议并给agent分配一个客户端ID。


    POST /devicecontrol/notifications HTTP/1.1
    Content-Type: application/json
    ...
    [ {
    "id": "1",
    "supportedConnectionTypes": ["long-polling"],
    "channel": "/meta/handshake",
    "version": "1.0"
} ]

HTTP/1.1 200 OK
...
[ {
"id": "1",
"supportedConnectionTypes": ["websocket","long-polling"],
"channel": "/meta/handshake",
"version": "1.0",
"clientId": "139jhm07u1dlry92fdl63rmq2c",
"minimumVersion": "1.0",
"successful": true
}]
                      

然后, 设备agent需要订阅操作的通知。这是使用作为订阅通道的设备ID的一个POST请求完成的。在我们的例子中, 树莓派运行agent, ID是2480300:


    POST /devicecontrol/notifications HTTP/1.1
    Content-Type: application/json
    ...
    [ {
    "id": "2",
    "channel": "/meta/subscribe",
    "subscription": "/2480300",
    "clientId":"139jhm07u1dlry92fdl63rmq2c"
}]

HTTP/1.1 200 OK
...
[ { 
    "id":"2",
    "channel": "/meta/subscribe",
    "subscription": "/2480300",
    "successful": true,
} ]

最后, 该设备连接并等待操作发送到它。

POST /devicecontrol/notifications HTTP/1.1
    Content-Type: application/json
    ...
    [ {
    "id": "3",
    "connectionType": "long-polling",
    "channel": "/meta/connect",
    "clientId": "139jhm07u1dlry92fdl63rmq2c"
} ]
                      

此请求将挂起直到收到操作, 即HTTP服务器不会立即应答, 会等到这个设备的操作可用 (长轮询)。

请注意, 我们订阅新操作前,可能已经有等待的操作。我们需要查询这样的情况。这是订阅后完成的,以便不错过查询和订阅间的操作。技术上的处理就像前面描述的"EXECUTING"操作, 但使用"PENDING"代替:


GET /devicecontrol/operations?agentId=2480300&status=PENDING HTTP/1.1
                      

循环阶段

步骤 7: 执行操作

假设队列中有一个agent的操作。这将发起上面提到的长轮询请求, 收到操作返回。这是一个单一配置操作的应答例子:


  HTTP/1.1 200 OK
    ...
    [ 
    {
    "id": "139", 
    "data": {
    "creationTime":"2013-09-04T10:53:35.128+02:00",
    "deviceId": "2480300",
    "id": "2546600",
    "self": "https://.../devicecontrol/operations/2546600",
    "status": "PENDING",
    "description": "Configuration update",
    "c8y_Configuration": { "config": "#Wed Sep 04 10:54:06 CEST 2013\n..." }
}, 
"channel": "/2480300"
}, {
"id": "3",
"successful": true,
"channel": "/meta/connect"
}
]
                      

当agent提取此操作, 它用PUT请求(参看上面例子中的"FAILED")在QuarkIoE中设置"EXECUTING"状态。它在设备上执行操作并在QuarkIoE设备清单中运行可能的更新。最后, 根据输出设置操作为"SUCCESSFUL"或"FAILED"。 然后, 如前所述重新连接到"/devicecontrol/notifications" 并等待下一个操作。

设备会在10秒内重连到服务器以免丢失队列中的操作。这是QuarkIoE缓存实时数据的时间。握手时可以协商此间隔。

步骤 8: 更新设备清单

设备的设备清单条目通常代码其当前状态, 这可能是连续变化的。作为一个例子, 考虑一个有GPS芯片的设备。 设备会在设备清单中保持最新的当前位置。在同一时间, 它会报告位置更新以及事件以维护位置轨迹。从技术上讲, 这样的更新和同样的请求报告如步骤 4。

步骤 9: 发送测量值

为在QuarkIoE创建测量值, 以测量值发送POST请求。下面的例子显示了如何创建信号强度测量值。


    POST /measurement/measurements HTTP/1.1
    Content-Type: application/vnd.com.nsn.cumulocity.measurement+json
    ...
    {
    "source": { "id": "2480300" },
    "time": "2013-07-02T16:32:30.152+02:00",
    "type": "huawei_E3131SignalStrength",
    "c8y_SignalStrength": {
    "rssi": { "value": -53, "unit": "dBm" },
    "ber": { "value": 0.14, "unit": "%" } 
}
}

HTTP/1.1 201 Created
                      
步骤 10: 发送事件

类似的, 对事件也是发送POST请求。 下面的例子显示了GPS传感器位置更新。


    POST /event/events HTTP/1.1
    Content-Type: application/vnd.com.nsn.cumulocity.event+json
    ...
    {
    "source": { "id": "1197500" },
    "text": "Location updated",
    "time": "2013-07-19T09:07:22.598+02:00",
    "type": "queclink_GV200LocationUpdate",
    "c8y_Position": {
    "alt": 73.9,
    "lng": 6.151782,
    "lat": 51.211971
}
}

HTTP/1.1 201 Created
                      

注意, QuarkIoE的所有数据类型都可以以附加片段的形式包含在任意扩展里。在这种情况下, 事件包括位置, 也可以添加自定义片段。

步骤 11: 发送报警

报警代表很可能需要人工干预解决的事件。 例如, 如果设备电池耗尽, 需要有人找到设备更换电池。 技术上创建报警和创建事件类似。


    POST /alarm/alarms HTTP/1.1
    Content-Type: application/vnd.com.nsn.cumulocity.alarm+json
    Accept: application/vnd.com.nsn.cumulocity.alarm+json
    ...
    {
    "source": { "id": "10400" },
    "text": "Tracker lost power",
    "time": "2013-08-19T21:31:22.740+02:00",
    "type": "c8y_PowerAlarm",
    "status": "ACTIVE",
    "severity": "MAJOR",
}

HTTP/1.1 201 Created
Content-Type: application/vnd.com.nsn.cumulocity.alarm+json
...
{
    "id": "214600",
    "self": "https://.../alarm/alarms/214600",
    ...
}
                      

然而, 如果系统中已经有一个类似的活跃报警, 则不应该为此设备创建报警。创建过多报警可能充斥用户界面并且需要用户手工清除。这个例子展示了查找我们的树莓派活跃报警:


  GET /alarm/alarms?source=2480300&status=ACTIVE HTTP/1.1
                      

和事件对比, 报警可以更新。如果一个问题解决了 (例如, 更换完电池, 电源恢复), 相应的报警应该自动清除以节省手工工作。这可以通过发送一个报警的URL的PUT请求完成。在上面创建报警的例子中, 我们在应答中使用"Accept"头获取新报警。我们使用这个URL清除报警:


    PUT /alarm/alarms/214600 HTTP/1.1
    Content-Type: application/vnd.com.nsn.cumulocity.alarm+json
    ...
    {
    "status": "CLEARED"
}

HTTP/1.1 200 OK
                      

I如果不确定发送事件或引发报警, 可以简单地只是发送事件让用户是否通过CEL规则决定是否把事件转换成报警。