设备影子

设备影子概念

  • 设备影子是一个 JSON 文档,用于存储设备上报状态、应用程序期望状态信息。

  • 每个设备有且只有一个设备影子,设备可以通过MQTT获取和设置设备影子以此来同步状态,该同步可以是影子同步给设备,也可以是设备同步给影子。

  • 应用程序通过物联网平台的设备影子topic获取和设置设备影子,获取设备最新状态或者下发期望状态给设备。

应用场景

  • 场景一

由于网络不稳定,设备频繁上下线。应用程序发出需要获取当前的设备状态请求时,设备掉线,无法获取设备状态,但下一秒设备又连接成功,应用程序无法正确发起请求。

使用设备影子机制存储设备最新状态,一旦设备状态产生变化,设备会将状态同步到设备影子。应用程序在请求设备当前状态时,只需要获取影子中的状态即可,不需要关心设备是否在线。

  • 场景二

如果设备网络稳定,很多应用程序请求获取设备状态,设备需要根据请求响应多次,即使响应的结果是一样的,设备本身处理能力有限,无法负载被请求多次的情况。

使用设备影子机制,设备只需要主动同步状态给设备影子一次,多个应用程序请求设备影子获取设备状态,即可获取设备最新状态,做到应用程序和设备的解耦。

  • 场景三

设备网络不稳定,导致设备频繁上下线,应用程序发送控制指令给设备时,设备掉线,指令无法下达到设备。

通过QoS=1或者2实现,但是该方法对于服务端的压力比较大,一般不建议使用。

使用设备影子机制,应用程序发送控制指令,指令携带时间戳保存在设备影子中。当设备掉线重连时,获取指令并根据时间戳确定是否执行。

设备真实掉线,指令发送失败。设备再上线时,设备影子功能通过指令加时间戳的模式,保证设备不会执行过期指令。

设备影子服务文档

设备影子服务遵守 JSON 规范下的所有规则。值、对象和数组均存储在设备的影子文档中。

文档属性

设备的影子文档具有以下属性:

state

desired

事物的预期状态。应用程序可以向本文档部分写入数据来更新事物的状态,且无需直接连接到该事物。

reported

事物的报告状态。事物可以向本文档部分写入数据,以报告其新状态。应用程序可以读取本文档部分,以确定事物的状态。

metadata

有关存储在文档 state 部分的数据的信息,其中包括 state 部分中每个属性的时间戳(以 Epoch 时间表示),让您能够确定它们的更新时间。

注意

元数据不会影响服务限制或定价的文档大小。

timestamp

指明物联网服务传输消息的时间。通过在消息中使用时间戳并对 desired 或 reported 部分中的不同属性使用时间戳,事物可以确定已更新项目的存在时间,即使它不具有内部时钟特性。

clientToken

特定于设备的字符串,让您能在 MQTT 环境中将响应与请求关联起来唯一标识,由用户自行维护。

version

文档版本。文档每次更新时,此版本号都会递增。用于确保正在更新的文档为最新版本。

影子服务的版本控制

影子服务服务对每个更新消息 (包括请求和响应) 实施版本控制,设备的影子每次更新时,JSON 文档的版本都会递增。这可以确保两件事情:

  • 如果客户端尝试使用旧版本号覆盖影子,它将收到错误消息。客户端将被告知必须先进行重新同步,然后才能更新设备的影子。

  • 如果消息的版本比客户端存储的版本低,客户端则可决定不对该消息执行操作。

  • 在有些情况下,客户端可以通过不提交版本来绕过版本匹配。

乐观锁

使用状态文档版本来确保正在更新的设备的影子文档为最新版本。当您为更新请求提供版本时,如果状态文档的当前版本与提供的版本不符,该服务将显示 HTTP 409 冲突响应代码并拒绝请求。

客户端令牌

收发基于 MQTT 的消息时,可以使用客户端令牌,以验证请求和请求响应是否包含相同的客户端令牌。这可以确保响应与请求相互关联。

注意

客户端令牌不能超过 64 字节。超过 64 字节的客户端令牌将引发 400 (错误请求) 响应和 clientToken 无效 错误消息。

示例文档

以下为示例影子文档:

{
    "state":{
        "desired":{
            "color":"RED",
            "sequence":[
                "RED",
                "GREEN",
                "BLUE"
            ]
        },
        "reported":{
            "color":"GREEN"
        }
    },
    "metadata":{
        "desired":{
            "color":{
                "timestamp":12345
            },
            "sequence":{
                "timestamp":12345
            }
        },
        "reported":{
            "color":{
                "timestamp":12345
            }
        }
    },
    "version":10,
    "clientToken":"UniqueClientToken",
    "timestamp":123456789
}

空白部分

仅当影子文档具有预期状态时,它才包含 desired 部分。例如,以下文档为没有 desired 部分的有效状态文档:

{
    "state": {
        "reported": {
            "color":"GREEN"
        }
    }
}

reported 部分也可以为空:

{
    "state": {
        "desired": {
            "color":"RED"
        }
    }
}

影子文档也有可能不包含 desired 或 reported 部分。在此情况下,影子文档将为空。例如,以下文档也是一个有效文档:

{
}

影子文档语法

在使用 OpenAPI 或 MQTT 发布/订阅消息的 UPDATE、GET 和 DELETE 操作中,设备影子 服务使用以下文档。

请求状态文档

请求状态文档具有以下格式:

{
    "state":{
        "desired":{
            "*attribute1*":123,
            "*attribute2*":"*string2*",
            "*attributeN*":true
        },
        "reported":{
            "*attribute1*":123,
            "*attribute2*":"*string1*",
            "*attributeN*":false
        }
    },
    "clientToken":"*token*",
    "version":1
}
  • state — 更新仅影响指定字段。

  • clientToken — 非必填,如果使用,您可以验证请求和响应是否包含相同的客户端令牌。

  • version — 非必填,如果使用,仅当指定的版本与 设备影子 服务拥有的最新版本相符时,该服务才会处理更新。

响应状态文档

响应状态文档具有以下格式:

{
    "state":{
        "desired":{
            "*attribute1*":123,
            "*attribute2*":"*string2*",
            "*attributeN*":true
        },
        "reported":{
            "*attribute1*":123,
            "*attribute2*":"*string1*",
            "*attributeN*":true
        },
        "delta":{
            "*attribute3*":123,
            "*attribute5*":"*stringY*"
        }
    },
    "metadata":{
        "desired":{
            "*attribute1*":{
                "timestamp":123
            },
            "*attribute2*":{
                "timestamp":123
            },
            "*attributeN*":{
                "timestamp":123
            }
        },
        "reported":{
            "*attribute1*":{
                "timestamp":123
            },
            "*attribute2*":{
                "timestamp":123
            },
            "*attributeN*":{
                "timestamp":123
            }
        }
    },
    "timestamp":123,
    "clientToken":"*token*",
    "version":1
}
  • state

    • reported — 仅当事物报告了 reported 部分的任何数据时存在,且仅包含请求状态文档中的字段。

    • desired — 仅当事物报告了 desired 部分的任何数据时存在,且仅包含请求状态文档中的字段。

  • metadata — 包含 desired 和 reported 部分中每个属性的时间戳,因此,您可以确定状态的更新时间。

  • timestamp — AWS IoT 生成响应时的 Epoch 日期和时间。

  • clientToken — 仅当客户端令牌用于向/update 主题发布有效 JSON 时存在。

  • version — AWS IoT 中共享的设备影子文档的当前版本。它将在文档先前版本号的基础上增加一个数。

错误响应文档

错误响应文档具有以下格式:

{

"code": *error-code*,

"message": "*error-message*",

"timestamp": *timestamp*,

"clientToken": "*token*"

}
  • code — 用于表明错误类型的 HTTP 响应代码。

  • message — 用于提供额外信息的文本消息。

  • timestamp — 物联网服务生成响应时的日期和时间。

  • clientToken — 仅当客户端令牌用于向/update 主题发布有效 JSON 时存在

影子错误消息

设备影子服务在尝试更改状态文档失败时向错误主题发布消息 (通过 MQTT)。此消息仅将作为对发布到其中一个预留主题的请求的响应。如果客户端使用 OpenAPI 来更新文档,则客户端将收到作为响应一部分的 HTTP 错误代码,且不会发送任何 MQTT 错误消息。

HTTP 错误代码 错误消息
400(错误请求) JSON 无效
401(未授权) 未授权
403(禁止访问) 禁止
404(未找到) 事物未找到
409(冲突) 版本冲突
413(有效负载过大) 有效负载超出允许的最大值
415(媒体类型不受支持) 文档编码不受支持;受支持的编码是 UTF-8
429(请求过多) 如果正在传输的请求超过 10 个,则 设备影子 服务将生成此条错误消息。
500(内部服务器错误) 内部服务故障
  • 必需节点缺失:状态

  • 状态节点必须是对象

  • 预期节点必须是对象

  • 报告节点必须是对象

  • 版本无效

  • clientToken 无效

注意

超过 64 字节的客户端令牌将引发此响应。

  • JSON 包含的嵌套层级过多;最多嵌套 6 个层级

  • 状态包含无效节点

设备影子接口及服务数据流

更新设备影子

设备或应用向此主题发布请求更新设备的影子:

iot/{projectCode}/{productCode}/{IotDeviceCode}/shadow/update

影子服务通过向以下主题发布消息进行设备影子更新请求响应

更新成功响应主题:iot/{projectCode}/{productCode}/{IotDeviceCode}/shadow/update/accepted(更新成功响应)

iot/{projectCode}/{productCode}/{IotDeviceCode}/shadow/update/delta(更新增量通知,增量状态是一种虚拟类型的状态,包含 desired 状态和 reported 状态之间的差异。对于 desired 部分中的字段,如果 reported 部分没有这些字段,则它们将包含在增量中。对于 reported 部分中的字段,如果 desired 部分没有这些字段,则它们不会包含在增量中。增量包含元数据,且其值与 desired 字段的元数据相同)

更新失败响应主题:

iot/{projectCode}/{productCode}/{IotDeviceCode}/shadow/update/rejected(更新失败响应)

设备向更新设备影子主题发布消息上报自身状态格式:

{
    "state": {
        "reported": {
            "color": "red"
        }
    },
    "clientToken": "token",
    "version": 1
}
  • clientToken — 非必填,如果使用,响应需携带相同的clientToken 值。
  • version — 非必填,如果使用,仅当指定的版本与影子服务拥有的最新版本相符时,该服务才会处理更新。

应用向更新设备影子主题发布消息格式:

{
    "state": {
        "desired": {
            "color": "green"
        }
    },
    "clientToken": "token",
    "version": 1
}
  • clientToken — 非必填,如果使用,响应需携带相同的clientToken 值。

  • version — 非必填,如果使用,仅当指定的版本与影子服务拥有的最新版本相符时,该服务才会处理更新。

注意:设备上报状态更新影子请求和应用下发指令更新影子状态请求的响应消息格式不同。

更新成功响应

影子服务在接受设备影子更改并更新成功时向此主题发布响应状态文档:

iot/{projectCode}/{productCode}/{IotDeviceCode}/shadow/update/accepted

此消息表明影子服务已收到 UPDATE 请求并更新了设备的影子。如果事物影子不存在, 影子服务则将创建事物影子;如果存在,则将使用消息中的数据更新该影子。

设备上报自身状态时响应格式:

{

    "state":{
        "reported":{
            "color":"red"
        }
    },
    "metadata":{
        "reported":{
            "color":{
                "timestamp":1469564492
            }
        }
    },
    "version":1,
    "timestamp":1469564492
}

应用向设备下发指令时响应格式:

{
    "state":{
        "desired":{
            "color":"green"
        }
    },
    "metadata":{
        "desired":{
            "color":{
                "timestamp":1469564658
            }
        }
    },
    "version":2,
    "timestamp":1469564658
}

更新失败响应

影子服务在拒绝设备影子更改时向此主题发布错误响应文档:

iot/{projectCode}/{productCode}/{IotDeviceCode}/shadow/update/rejected

{

"code": *error-code*,

"message": "*error-message*",

"timestamp": *timestamp*,

"clientToken": "*token*"

}

更新增量通知

平台在接受应用更改设备影子的请求时向此主题发布响应状态文档(主要应用于设备订阅):

iot/{projectCode}/{productCode}/{IotDeviceCode}/shadow/update/delta

{
    "state": {
        "color": "green"
    },
    "metadata": {
        "color": {
            "timestamp": 12345
        }
    },
    "version": 17,
    "timestamp": 123456789
}

主动获取设备影子

设备和应用主动获得设备的影子,请在此主题下发布一条空消息:

iot/{projectCode}/{productCode}/{IotDeviceCode}/shadow/get

{
}

主动获取影子成功响应

影子服务通过向此主题发布请求获取的影子进行响应:

iot/{projectCode}/{productCode}/{IotDeviceCode}/shadow/get/accepted

{
  "state" : {
    "reported" : {
      "temperature" : 5,
      "colors" : "green"
    },
    "desired" : {
      "temperature" : 3
    }
  },
  "metadata" : {
    "reported" : {
      "temperature" : {
        "timestamp" : 1560407215693
      },
      "colors" : {
        "timestamp" : 1560336814247
      }
    },
    "desired" : {
      "temperature" : {
        "timestamp" : 1560407523439
      }
    }
  },
  "timestamp" : 1560407523439,
  "version" : 269
}

主动获取影子失败响应

平台在无法返回设备的影子时向此主题发布错误响应文档:

iot/{projectCode}/{productCode}/{IotDeviceCode}/shadow/get/rejected

{

"code": *error-code*,

"message": "*error-message*",

"timestamp": *timestamp*,

"clientToken": "*token*"

}

删除设备影子

平台或应用要删除设备的影子,请将一条空消息发布到删除主题:

iot/{projectCode}/{productCode}/{IotDeviceCode}/shadow/delete

{
}

删除设备影子成功响应

平台在删除设备的影子时向此主题发布消息:

iot/{projectCode}/{productCode}/{IotDeviceCode}/shadow/delete/accepted

{
"version" : 1,
"timestamp" : 1488565234
}

删除设备影子失败响应

平台在无法删除设备的影子时向此主题发布错误响应文档:

iot/{projectCode}/{productCode}/{IotDeviceCode}/shadow/delete/rejected

{

"code": *error-code*,

"message": "*error-message*",

"timestamp": *timestamp*,

"clientToken": "*token*"

}