这一节我们开始实现设备影子的设备端功能,设备端需要处理来自服务的影子设备同步,同时在本地的设备影子发送变化时,向服务端发送相应的数据,最后我们会对IotHub的设备影子功能进行验证。

主动请求设备影子数据

在设备连接到 IotHub 时,需要主动发起一个数据请求,请求设备影子的数据:

  1. //IotHub_Device
  2. this.client.on("connect", function () {
  3. self.sendTagsRequest()
  4. self.sendDataRequest("$shadow")
  5. self.emit("online")
  6. })

处理$update_shadow指令

DeviceSDK 在处理$update_shadow指令时有两件事情要做,第一,如果 desired 不为空,要将 desired 数据传递给设备应用代码;第二,需要提供接口供设备端代码在更新完设备状态后向 IotHub 进行回复。

  1. //IotHub_Device/sdk/iot_device.js
  2. handleCommand({commandName, requestID, encoding, payload, expiresAt, commandType = "cmd"}) {
  3. ...
  4. else if (commandName == "$update_shadow") {
  5. this.handleUpdateShadow(payload);
  6. }
  7. ...
  8. }
  9. handleUpdateShadow(shadow) {
  10. if (this.shadowVersion <= shadow.version) {
  11. this.shadowVersion = shadow.version
  12. if (shadow.state.desired != null) {
  13. var self = this
  14. var respondToShadowUpdate = function () {
  15. self.uploadData(JSON.stringify({
  16. state: {
  17. desired: null
  18. },
  19. version: self.shadowVersion
  20. }), "$shadow_updated")
  21. }
  22. this.emit("shadow", shadow.state.desired, respondToShadowUpdate)
  23. }
  24. }
  25. }

其中,this.shadowVersion 初始化为 0:

  1. constructor(...) {
  2. ...
  3. this.shadowVersion = 0
  4. }

这里我们依然使用闭包的方式提供接口给设备应用代码调用。

主动更新影子设备状态

设备可以上传 DataType=”$shadow_reported” 的数据来主动修改影子设备状态:

  1. //IotHub_Device/sdk/iot_device.js
  2. reportShadow(reported) {
  3. this.uploadData(JSON.stringify({
  4. state: {
  5. reported: reported
  6. },
  7. version: this.shadowVersion
  8. }), "$shadow_reported")
  9. }

处理$shadow_reply指令

当 IotHub 根据设备上传的数据成功修改影子设备之后,会下发$shadow_reply指令。处理这个指令很简单,如果指令携带的 version 大于本地的 version,就将本地的 version 更新为指令携带的 version:

  1. handleCommand({commandName, requestID, encoding, payload, expiresAt, commandType = "cmd"}) {
  2. ...
  3. else if (commandName == "$update_shadow") {
  4. this.handleUpdateShadow(payload);
  5. } else if (commandName == "$shadow_reply") {
  6. if (payload.version > this.shadowVersion && payload.status == "success") {
  7. this.shadowVersion = payload.version
  8. }
  9. }
  10. }
  11. ...
  12. }

代码联调

接下来我们写一点代码来测试 IotHub 的影子设备功能,这里我们模拟以下场景:

  1. 设备离线时,业务系统修改设备影子,设置 desired: {lights: “on”};
  2. 设备上线后,按照业务系统,更改设备状态 lights=on;
  3. 业务系统再查询设备的影子,影子数据为 reported: {lights: “on”};
  4. 设备在 3 秒后主动修改影子设备状态,reporeted: {lights: “off”};
  5. 业务系统再查询设备的影子,影子数据为 reported: {lights: “off”}。
    1. //IotHub_Server/samples/update_shadow.js
    2. require('dotenv').config({path: "../.env"})
    3. const request = require("request")
    4. var deviceUrl = `http://127.0.0.1:3000/devices/${process.env.TARGET_PRODUCT_NAME}/${process.env.TARGET_DEVICE_NAME}`;
    5. var checkLights = function () {
    6. request.get(deviceUrl
    7. , function (err, response, body) {
    8. var shadow = JSON.parse(body).shadow
    9. var lightsStatus = "unknown"
    10. if(shadow.state.reported && shadow.state.reported.lights){
    11. lightsStatus = shadow.state.reported.lights
    12. }
    13. console.log(`current lights status is ${lightsStatus}`)
    14. setTimeout(checkLights, 2000)
    15. })
    16. }
    17. request.get(deviceUrl
    18. , function (err, response, body) {
    19. var deviceInfo = JSON.parse(body)
    20. request.put(`${deviceUrl}/shadow`, {
    21. json: {
    22. version: deviceInfo.shadow.version + 1,
    23. desired: {lights: "on"}
    24. }
    25. }, function (err, response, body) {
    26. checkLights()
    27. })
    28. })
    在调用更新设备影子之后,每隔 2 秒检查一次当前设备的影子。注意在修改设备影子前先查询当前设备影子的 version,当前的 version+1 作为新的影子 version。
    设备端代码:
    1. //IotHub_Device/sampls/resp_to_shadow.js
    2. ...
    3. device.on("online", function () {
    4. console.log("device is online")
    5. setTimeout(function () {
    6. console.log("turned the lights off")
    7. device.reportShadow({lights: "off"})
    8. }, 3000)
    9. })
    10. device.on("shadow", function (desired, respondToShadowUpdate) {
    11. setTimeout(function () {
    12. console.log(`turned the lights ${desired.lights}`)
    13. respondToShadowUpdate()
    14. }, 1000)
    15. })
    16. device.connect()
    首先运行IotHub_Server/samples/update_shadow.js
    然后再运行IotHub_Device/sampls/resp_to_shadow.js,可以观察到以下输出:
    1. ### IotHub_Server/samples/update_shadow.js
    2. current lights status is unknown
    3. current lights status is unknown
    4. current lights status is unknown
    5. current lights status is on
    6. current lights status is off
    7. current lights status is off
    1. ### IotHub_Device/sampls/resp_to_shadow.js
    2. device is online
    3. turned the lights on
    4. turned the lights off
    这样的话,说明 IotHub 的设备影子功能是按照预期在工作的。

这一节我们完成了 IotHub 的影子设备功能,在下面一节我们来讨论和实现 IotHub 的自身状态监控功能。