概述
集成设备到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规则决定是否把事件转换成报警。