什么是设备影子
我最早是在 AWS IoT 上面看到设备影子功能的,后来国内主流云服务上的 IoT 套件中都包含了设备影子的功能。设备影子已经是 IoT 平台的标配功能了,所以 Maque IotHub 也需要实现设备影子功能。
首先让我们来看一下各个平台对设备影子的描述。
阿里云
物联网平台提供设备影子功能,用于缓存设备状态。设备在线时,可以直接获取云端指令;设备离线时,上线后可以主动拉取云端指令。 设备影子是一个 JSON 文档,用于存储设备上报状态、应用程序期望状态信息。 每个设备有且只有一个设备影子,设备可以通过 MQTT 获取和设置设备影子来同步状态,该同步可以是影子同步给设备,也可以是设备同步给影子。
设备影子文档是服务器端为设备缓存的一份状态和配置数据。它以 JSON 文本形式存储。
简单来说,设备影子包含了两种主要功能:
服务端和设备端数据同步:
设备影子提供了一种在网络情况不稳定、设备上线下线频繁的情况下,服务端和设备端稳定数据同步的功能。
这里要说明的是,在 IotHub 之前实现的数据/状态上传,指令下发功能都是可以在网络情况不稳定的情况下,稳定实现单向数据同步的。
设备影子主要解决的是,当一个状态或者数据可以被设备和服务器端同时修改时,在网络状态不稳定的情况下,如何保持其在服务端和设备端的状态一致性。所以当你需要双向同步时,就可以考虑使用设备影子了。例如,智能灯泡的开关状态既可以远程改变,也可以在本地通过物理开关来改变,那么这个状态就需要在服务端和设备端保持一致。
设备端数据/状态缓存:
设备影子还可以作为设备状态/数据在服务端的缓存,由于它保证了设备端和服务端的一致性,所以在业务系统需要在获取设备上的某个状态时,只需要读取服务端的数据就可以了,不需要和设备进行交互,实现了设备和业务系统的解耦。
设备影子的数据结构
我们引用的阿里云和腾讯云的文档里面说的那样,设备影子是一个 JSON 格式的文档,每个设备对应一个设备影子,下面是一个典型的设备影子:
{
"state": {
"reported": {
"lights": "on"
},
"desired": {
"lights": "off"
}
},
"metadata": {
"reported": {
"lights": {
"timestamp": 123456789
}
},
"desired": {
"lights": {
"timestamp": 123456789
}
}
},
"version": 1,
"timestamp": 123456789
}
state:
- reported,指当前设备上报的状态,业务系统如果需要读取当前设备的状态,以这个值为准;
- desired,是指服务端希望改变的设备状态,但还未同步到设备上。
metadata:状态的元数据,内容是state中包含的状态字段的最后更新时间。 version:设备影子的版本。 timestamp:设备影子的最后一次修改时间。
设备影子的数据流向
阿里云和腾讯云的设备影子的数量流向大体是一致的,细节上略有不同,这里我总结了和简化了一下,在 Maque IotHub 里,影子设备的数据流向包含两个方向。
服务端向设备端同步
当业务系统通过服务端的接口修改了设备影子之后,IotHub 会向设备端进行同步,这个流程分为四步。
第一步,IotHub 向设备下发指令 UPDATE_SHADOW,指令中包含了更新后的文档,以上面的设备影子文档为例子,其中最重要的部分是 desired 和 version:
{
"state": {
...
"desired": {
"lights": "off"
}
},
...
"version": 1,
...
}
第二步,设备根据 desired 里面的值去更新设备的状态,比如像这里就应该关闭智能灯。
第三步,设备向 IotHub 回复状态更新成功的信息,例如:
{
"state": {
"desired": null
},
"version": 1,
}
这里设备必须使用第二步得到 version 值,当 IotHub 收到这个回复时,检查回复里的 version 是否和设备影子里的一致。
如果一致的话,那么将设备影子中 reported 里面字段的值修改为 desired 对应的值,同时删除 desired,并修改 metadata 里面相应的值。例如:
{
"state": {
"reported": {
"lights": "off"
}
},
"metadata": {
"reported": {
"lights": {
"timestamp": 123456789
}
}
},
"version": 1,
"timestamp": 123456789
}
如果不一致的话,说明这期间影子设备又被修改了,则回到第一步,重新执行。
第四步,设备影子更新成功后,IotHub 向设备回复一条消息 SHADOW_REPLY:
{
status: "succss",
"timestamp": 123456789,
"version": 1
}
设备端向服务端同步
设备端的流程有三步。
第一步,当设备连接到 IotHub 时,向 IotHub 发起数据请求,IotHub 收到请求后会下发 UPDATE_SHADOW 指令,执行一次服务端向设备端同步,设备需要记录下当前影子设备的 version。
第二步,当设备的状态发生变化,比如通过物理开关关闭掉智能电灯的时候,IotHub 发送 REPORT_SHADOW 数据,包含第一步获得的 version,例如:
{
"state": {
"reported": {
"temperature": 27
}
},
"version": 1
}
当 IotHub 收到这个数据,检查 REPORT_SHADOW 里的 version 是否和设备影子里的数据一致:
- 如果一致,那么用 REPORT_SHADOW 里的 reported 值修改设备影子 reported 的字段;
- 如果不一致,那么 IotHub 会下发指令 UPDATE_SHADOW,执行一次服务端向设备端的同步。
第三步,IotHub 在接收到 REPORT_SHADOW 数据并成功修改设备影子后,向设备回复一条消息 SHADOW_REPLY。
在同步时,如果不是修改字段的值,而是删除字段,那么,将字段的值设为 null 就可以了。
设备影子 Server API
同时,IotHub 需要向提供设备影子相关的接口,业务系统可以通过这些接口,对设备影子进行查询和修改。
业务系统通过这些接口修改设备影子时,设备影子的 version 也应该相应的加一。
这一节我们讨论和设计了 IotHub 的设备影子功能,下一节我们开始来实现设备影子的服务端功能。