设备影子
定义
设备影子是设备在云端的虚拟映射(可以理解为一个json文档),用来记录设备的最近状态和预期状态,通过设备影子可以轻松实现云端对设备状态的管控。
使用场景
场景1:网络不稳定,设备频繁上下线。
由于网络不稳定,设备频繁上下线。应用程序发出需要获取当前的设备状态请求时,设备掉线,无法获取设备状态,但下一秒设备又连接成功,应用程序无法正确发起请求。
使用设备影子机制存储设备最新状态,一旦设备状态产生变化,设备会将状态同步到设备影子。应用程序只需要请求或订阅推送方式获取影子中的状态即可,不需要关心设备是否在线。
场景2:多程序同时请求获取设备状态。
如果设备网络稳定,很多应用程序请求获取设备状态,设备需要根据请求响应多次,即使响应的结果是一样的,设备本身处理能力有限,无法负载被请求多次的情况。
使用设备影子机制,设备只需要主动同步状态给设备影子一次,多个应用程序请求或订阅推送方式,获取设备影子中存储的设备状态,即可获取设备最新状态,做到应用程序和设备的解耦。
场景3:设备掉线。
设备网络不稳定,导致设备频繁上下线,应用程序发送控制指令给设备时,设备掉线,指令无法下达到设备。通过QoS=1或者2实现,但是该方法对于服务端的压力比较大,一般不建议使用。
使用设备影子机制,应用程序发送控制指令,指令携带时间戳保存在设备影子中。当设备掉线重连时,获取指令并根据时间戳确定是否执行。
设备连接掉线,指令发送失败。设备再上线时,设备影子功能通过指令加时间戳的模式,保证设备不会执行过期指令。
场景4:设备状态比对通知
设备上报状态时,仅需报告变更的部分;此时应用对发生变化的属性值更为关心。反之应用对设备亦然。
应用或设备更新属性(desired/reported)后,设备或应用可获取差异推送delta。
场景5:设备初始配置信息获取
设备首次连接时,需要一些配置项或参数作为初始化配置,一般可将配置信息写入固件,但具有差异化的配置就较难处理。
使用设备影子机制,可以将一般性的配置写入影子模板,以此模板创建设备时模板内容将作为设备初始版本的影子。若针对特定设备变更初始配置,也可以针对性更新其影子,设备首次连接时进行 get 获取影子,即可获取期望配置。
整体架构
我们将使用设备影子的实体分为设备端和控制端:前者代表常见的物联网设备,例如网关,智能开关等。后者代表具有控制能力的实体,例如应用服务端,手机app等。
设备和应用是百度抽象出的一组概念,分属于上图中的设备端和控制端。
创建影子模板
参考 https://cloud.baidu.com/doc/IoTCore/s/2k7o8yf4k
影子模版属于模板的一部分,包含了设备要上报的属性以及其初始值,采用json格式定义。
其限制条件如下:
- 最外层只能包含三个字段,分别是tags,reported和desired
- 属性名称不能以@开头,且其中不能包含dollar($),dot(.),space( ),以及comma(,)符号,<=64bytes
- 属性值支持bool、int32、int64、float、double、string,不支持null和array,对于string类型<=512bytes
- 对于tags,reported,desired每个存储体嵌套层级<=5
- 每个JsonObject层级中key值<=50
创建设备
参考 https://cloud.baidu.com/doc/IoTCore/s/ek7o8yepa
设备创建完成后,百度云会自动生成其对应的影子,因此新建的设备可以直接使用影子服务。
设备可以设定自己的权限组key(默认为设备名),这个key用来决定控制端能否控制(更新设备desired,并接收设备reported delta,详见下文两端交互)该设备,下文会做介绍。
创建应用
参考 https://cloud.baidu.com/doc/IoTCore/s/Gkfeuwrpr
注意,在创建时,需要选择设备影子的使用方式,并填写权限组key(可以填写多个)。
这里的权限组key和上面设备的权限组key一致,只有设备和应用的权限组key相同时,该应用才能控制该设备。
两端交互
设备和应用创建完成后,即可通过相应的连接方式连接到百度云,并通过设备影子进行状态上报、控制指令下发等操作。
为了方便用户使用,我们提供了java sdk https://github.com/baidu/iotcore-sdk-java(其他语言的 SDK 开发中,亦可参考 Java 版本自行实现交互逻辑)。
我们以智能家居中常见的“灯泡”场景作为示例,来演示设备影子的使用方式。
设想卧室里的智能灯,用户可以通过手机app查看或者控制灯泡的开关、亮度、颜色等属性。
资源创建
创建影子模板
影子模板包含了设备要上报的属性以及其初始值。当前场景我们定义如下:
{
"tags": {
"name": "智慧灯泡"
},
"reported": {
"env_brightness": -1,
"status": "unknown",
"color": "unknown",
"brightness_percent": -1
},
"desired": {
"status": "closed",
"color": "white",
"brightness_percent": 50
}
}
影子模板里包含了三部分:
- tags里包含了灯泡的元信息。示例里只包含了name,实际使用还可以包含批次、型号等等信息。
- desired里包含了定义的灯泡可以改变的属性。示例里包含了状态、颜色、亮度几个信息。
- reported代表灯泡上报的信息。由于这里只是模板,因此只定义了属性名,各个值都设置为了“unknown”,表示设备还未上线初始化过。此外,可以看到这里还包含了只读(指desired中不包含,因此无法通过下发控制指令来修改)属性"env_brightness"(环境亮度)。设备影子允许reported和desired不完全同构。
创建设备(灯泡设备端使用)和应用(app端使用)
根据上文中的操作指南创建两端使用设备和应用(注意,两者要配置相同的groupKey),获取到对应连接信息,并连接到 IoT Core(连接方式参考 https://cloud.baidu.com/doc/IoTCore/s/Akck4811r)。
设备端推荐使用mqtt协议,其更适合小型物联网设备。
app端可以采用mqtt或者http等协议。mqtt协议优势在于可以通过sub相关主题,来实时监听设备的状态变化。http的优势在于开发人员相对更加熟悉,并且无状态,但无法做到实时监听。
状态上报
灯泡在上电联网并接入到iotcore之后,就可以开始上报自身的状态。
上报方式为向$iot/${deviceName}/shadow/update/reported主题发布一条自身的状态消息(${deviceName}用实际的iotcore中注册的灯泡的设备名替换)。
初始化或者每当有状态信息变化时,都可以向该主题发布消息。
在这个场景里,上报的信息如下:
{
"reported": {
"env_brightness": "0,
"status": "closed",
"color": "white",
"brightness_percent": 50
"@version": 1
}
}
这里为了方便展示,将上报信息(还有后文指令下发信息)直接用 json表示出来 。实际使用过程需要调用SDK 的上报接口。
可以看到,这里多了一个“@version”字段,用来表示当前信息的版本号,每次上报需要严格加一。
指令下发
当用户想要修改灯泡状态时,例如打开灯泡,则可以通过app下发一条指令。
下发指令的方式为向$iot/${deviceName}/shadow/update/desired主题发布一条指令信息(${deviceName}用实际的iotcore中注册的灯泡的设备名替换)。
例如打开灯泡的信息如下:
{
"desired": {
"status": "open"
}
}
设备端(灯泡)需要预先定义订阅主题$iot/${avatarId}/shadow/delta/desired。
这样,app在发布完消息后,设备端就可以实时的接收到这条指令,再通过本地程序(例如实际的硬件处理程序)处理这条指令即可。处理完成后,再通过上报状态的方式将自身状态通知到app端即可。