这一节我们开始实现设备影子的设备端功能,设备端需要处理来自服务的影子设备同步,同时在本地的设备影子发送变化时,向服务端发送相应的数据,最后我们会对IotHub的设备影子功能进行验证。
主动请求设备影子数据
在设备连接到 IotHub 时,需要主动发起一个数据请求,请求设备影子的数据:
//IotHub_Devicethis.client.on("connect", function () {self.sendTagsRequest()self.sendDataRequest("$shadow")self.emit("online")})
处理$update_shadow指令
DeviceSDK 在处理$update_shadow指令时有两件事情要做,第一,如果 desired 不为空,要将 desired 数据传递给设备应用代码;第二,需要提供接口供设备端代码在更新完设备状态后向 IotHub 进行回复。
//IotHub_Device/sdk/iot_device.jshandleCommand({commandName, requestID, encoding, payload, expiresAt, commandType = "cmd"}) {...else if (commandName == "$update_shadow") {this.handleUpdateShadow(payload);}...}handleUpdateShadow(shadow) {if (this.shadowVersion <= shadow.version) {this.shadowVersion = shadow.versionif (shadow.state.desired != null) {var self = thisvar respondToShadowUpdate = function () {self.uploadData(JSON.stringify({state: {desired: null},version: self.shadowVersion}), "$shadow_updated")}this.emit("shadow", shadow.state.desired, respondToShadowUpdate)}}}
其中,this.shadowVersion 初始化为 0:
constructor(...) {...this.shadowVersion = 0}
这里我们依然使用闭包的方式提供接口给设备应用代码调用。
主动更新影子设备状态
设备可以上传 DataType=”$shadow_reported” 的数据来主动修改影子设备状态:
//IotHub_Device/sdk/iot_device.jsreportShadow(reported) {this.uploadData(JSON.stringify({state: {reported: reported},version: this.shadowVersion}), "$shadow_reported")}
处理$shadow_reply指令
当 IotHub 根据设备上传的数据成功修改影子设备之后,会下发$shadow_reply指令。处理这个指令很简单,如果指令携带的 version 大于本地的 version,就将本地的 version 更新为指令携带的 version:
handleCommand({commandName, requestID, encoding, payload, expiresAt, commandType = "cmd"}) {...else if (commandName == "$update_shadow") {this.handleUpdateShadow(payload);} else if (commandName == "$shadow_reply") {if (payload.version > this.shadowVersion && payload.status == "success") {this.shadowVersion = payload.version}}}...}
代码联调
接下来我们写一点代码来测试 IotHub 的影子设备功能,这里我们模拟以下场景:
- 设备离线时,业务系统修改设备影子,设置 desired: {lights: “on”};
- 设备上线后,按照业务系统,更改设备状态 lights=on;
- 业务系统再查询设备的影子,影子数据为 reported: {lights: “on”};
- 设备在 3 秒后主动修改影子设备状态,reporeted: {lights: “off”};
- 业务系统再查询设备的影子,影子数据为 reported: {lights: “off”}。
在调用更新设备影子之后,每隔 2 秒检查一次当前设备的影子。注意在修改设备影子前先查询当前设备影子的 version,当前的 version+1 作为新的影子 version。//IotHub_Server/samples/update_shadow.jsrequire('dotenv').config({path: "../.env"})const request = require("request")var deviceUrl = `http://127.0.0.1:3000/devices/${process.env.TARGET_PRODUCT_NAME}/${process.env.TARGET_DEVICE_NAME}`;var checkLights = function () {request.get(deviceUrl, function (err, response, body) {var shadow = JSON.parse(body).shadowvar lightsStatus = "unknown"if(shadow.state.reported && shadow.state.reported.lights){lightsStatus = shadow.state.reported.lights}console.log(`current lights status is ${lightsStatus}`)setTimeout(checkLights, 2000)})}request.get(deviceUrl, function (err, response, body) {var deviceInfo = JSON.parse(body)request.put(`${deviceUrl}/shadow`, {json: {version: deviceInfo.shadow.version + 1,desired: {lights: "on"}}}, function (err, response, body) {checkLights()})})
设备端代码:
首先运行//IotHub_Device/sampls/resp_to_shadow.js...device.on("online", function () {console.log("device is online")setTimeout(function () {console.log("turned the lights off")device.reportShadow({lights: "off"})}, 3000)})device.on("shadow", function (desired, respondToShadowUpdate) {setTimeout(function () {console.log(`turned the lights ${desired.lights}`)respondToShadowUpdate()}, 1000)})device.connect()
IotHub_Server/samples/update_shadow.js,
然后再运行IotHub_Device/sampls/resp_to_shadow.js,可以观察到以下输出:### IotHub_Server/samples/update_shadow.jscurrent lights status is unknowncurrent lights status is unknowncurrent lights status is unknowncurrent lights status is oncurrent lights status is offcurrent lights status is off
这样的话,说明 IotHub 的设备影子功能是按照预期在工作的。### IotHub_Device/sampls/resp_to_shadow.jsdevice is onlineturned the lights onturned the lights off
这一节我们完成了 IotHub 的影子设备功能,在下面一节我们来讨论和实现 IotHub 的自身状态监控功能。
